fontisan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +529 -65
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1301 -275
- data/Rakefile +27 -2
- data/benchmark/variation_quick_bench.rb +47 -0
- data/docs/EXTRACT_TTC_MIGRATION.md +549 -0
- data/fontisan.gemspec +4 -1
- data/lib/fontisan/binary/base_record.rb +22 -1
- data/lib/fontisan/cli.rb +309 -0
- data/lib/fontisan/collection/builder.rb +260 -0
- data/lib/fontisan/collection/offset_calculator.rb +227 -0
- data/lib/fontisan/collection/table_analyzer.rb +204 -0
- data/lib/fontisan/collection/table_deduplicator.rb +241 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +8 -1
- data/lib/fontisan/commands/convert_command.rb +291 -0
- data/lib/fontisan/commands/export_command.rb +161 -0
- data/lib/fontisan/commands/info_command.rb +40 -6
- data/lib/fontisan/commands/instance_command.rb +295 -0
- data/lib/fontisan/commands/ls_command.rb +113 -0
- data/lib/fontisan/commands/pack_command.rb +241 -0
- data/lib/fontisan/commands/subset_command.rb +245 -0
- data/lib/fontisan/commands/unpack_command.rb +338 -0
- data/lib/fontisan/commands/validate_command.rb +178 -0
- data/lib/fontisan/commands/variable_command.rb +30 -1
- data/lib/fontisan/config/collection_settings.yml +56 -0
- data/lib/fontisan/config/conversion_matrix.yml +212 -0
- data/lib/fontisan/config/export_settings.yml +66 -0
- data/lib/fontisan/config/subset_profiles.yml +100 -0
- data/lib/fontisan/config/svg_settings.yml +60 -0
- data/lib/fontisan/config/validation_rules.yml +149 -0
- data/lib/fontisan/config/variable_settings.yml +99 -0
- data/lib/fontisan/config/woff2_settings.yml +77 -0
- data/lib/fontisan/constants.rb +69 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +259 -0
- data/lib/fontisan/converters/outline_converter.rb +936 -0
- data/lib/fontisan/converters/svg_generator.rb +244 -0
- data/lib/fontisan/converters/table_copier.rb +117 -0
- data/lib/fontisan/converters/woff2_encoder.rb +416 -0
- data/lib/fontisan/converters/woff_writer.rb +391 -0
- data/lib/fontisan/error.rb +203 -0
- data/lib/fontisan/export/exporter.rb +262 -0
- data/lib/fontisan/export/table_serializer.rb +255 -0
- data/lib/fontisan/export/transformers/font_to_ttx.rb +172 -0
- data/lib/fontisan/export/transformers/head_transformer.rb +96 -0
- data/lib/fontisan/export/transformers/hhea_transformer.rb +59 -0
- data/lib/fontisan/export/transformers/maxp_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/name_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/os2_transformer.rb +121 -0
- data/lib/fontisan/export/transformers/post_transformer.rb +51 -0
- data/lib/fontisan/export/ttx_generator.rb +527 -0
- data/lib/fontisan/export/ttx_parser.rb +300 -0
- data/lib/fontisan/font_loader.rb +121 -12
- data/lib/fontisan/font_writer.rb +301 -0
- data/lib/fontisan/formatters/text_formatter.rb +102 -0
- data/lib/fontisan/glyph_accessor.rb +503 -0
- data/lib/fontisan/hints/hint_converter.rb +177 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +185 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +254 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +71 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +162 -0
- data/lib/fontisan/loading_modes.rb +113 -0
- data/lib/fontisan/metrics_calculator.rb +277 -0
- data/lib/fontisan/models/collection_font_summary.rb +52 -0
- data/lib/fontisan/models/collection_info.rb +76 -0
- data/lib/fontisan/models/collection_list_info.rb +37 -0
- data/lib/fontisan/models/font_export.rb +158 -0
- data/lib/fontisan/models/font_summary.rb +48 -0
- data/lib/fontisan/models/glyph_outline.rb +343 -0
- data/lib/fontisan/models/hint.rb +233 -0
- data/lib/fontisan/models/outline.rb +664 -0
- data/lib/fontisan/models/table_sharing_info.rb +40 -0
- data/lib/fontisan/models/ttx/glyph_order.rb +31 -0
- data/lib/fontisan/models/ttx/tables/binary_table.rb +67 -0
- data/lib/fontisan/models/ttx/tables/head_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/hhea_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/maxp_table.rb +55 -0
- data/lib/fontisan/models/ttx/tables/name_table.rb +45 -0
- data/lib/fontisan/models/ttx/tables/os2_table.rb +157 -0
- data/lib/fontisan/models/ttx/tables/post_table.rb +50 -0
- data/lib/fontisan/models/ttx/ttfont.rb +49 -0
- data/lib/fontisan/models/validation_report.rb +203 -0
- data/lib/fontisan/open_type_collection.rb +156 -2
- data/lib/fontisan/open_type_font.rb +296 -10
- data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
- data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
- data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
- data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
- data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
- data/lib/fontisan/outline_extractor.rb +423 -0
- data/lib/fontisan/subset/builder.rb +268 -0
- data/lib/fontisan/subset/glyph_mapping.rb +215 -0
- data/lib/fontisan/subset/options.rb +142 -0
- data/lib/fontisan/subset/profile.rb +152 -0
- data/lib/fontisan/subset/table_subsetter.rb +461 -0
- data/lib/fontisan/svg/font_face_generator.rb +278 -0
- data/lib/fontisan/svg/font_generator.rb +264 -0
- data/lib/fontisan/svg/glyph_generator.rb +168 -0
- data/lib/fontisan/svg/view_box_calculator.rb +137 -0
- data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
- data/lib/fontisan/tables/cff/charset.rb +282 -0
- data/lib/fontisan/tables/cff/charstring.rb +905 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +322 -0
- data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
- data/lib/fontisan/tables/cff/dict.rb +351 -0
- data/lib/fontisan/tables/cff/dict_builder.rb +242 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -0
- data/lib/fontisan/tables/cff/index.rb +237 -0
- data/lib/fontisan/tables/cff/index_builder.rb +170 -0
- data/lib/fontisan/tables/cff/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +487 -0
- data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
- data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
- data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
- data/lib/fontisan/tables/cff2.rb +341 -0
- data/lib/fontisan/tables/cvar.rb +242 -0
- data/lib/fontisan/tables/fvar.rb +2 -2
- data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
- data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
- data/lib/fontisan/tables/glyf.rb +235 -0
- data/lib/fontisan/tables/gvar.rb +270 -0
- data/lib/fontisan/tables/hhea.rb +124 -0
- data/lib/fontisan/tables/hmtx.rb +287 -0
- data/lib/fontisan/tables/hvar.rb +191 -0
- data/lib/fontisan/tables/loca.rb +322 -0
- data/lib/fontisan/tables/maxp.rb +192 -0
- data/lib/fontisan/tables/mvar.rb +185 -0
- data/lib/fontisan/tables/name.rb +99 -30
- data/lib/fontisan/tables/variation_common.rb +346 -0
- data/lib/fontisan/tables/vvar.rb +234 -0
- data/lib/fontisan/true_type_collection.rb +156 -2
- data/lib/fontisan/true_type_font.rb +297 -11
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +18 -0
- data/lib/fontisan/utils/thread_pool.rb +134 -0
- data/lib/fontisan/validation/checksum_validator.rb +170 -0
- data/lib/fontisan/validation/consistency_validator.rb +197 -0
- data/lib/fontisan/validation/structure_validator.rb +198 -0
- data/lib/fontisan/validation/table_validator.rb +158 -0
- data/lib/fontisan/validation/validator.rb +152 -0
- data/lib/fontisan/variable/axis_normalizer.rb +215 -0
- data/lib/fontisan/variable/delta_applicator.rb +313 -0
- data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
- data/lib/fontisan/variable/instancer.rb +344 -0
- data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
- data/lib/fontisan/variable/region_matcher.rb +208 -0
- data/lib/fontisan/variable/static_font_builder.rb +213 -0
- data/lib/fontisan/variable/table_updater.rb +219 -0
- data/lib/fontisan/variation/blend_applier.rb +199 -0
- data/lib/fontisan/variation/cache.rb +298 -0
- data/lib/fontisan/variation/cache_key_builder.rb +162 -0
- data/lib/fontisan/variation/converter.rb +268 -0
- data/lib/fontisan/variation/data_extractor.rb +86 -0
- data/lib/fontisan/variation/delta_applier.rb +266 -0
- data/lib/fontisan/variation/delta_parser.rb +228 -0
- data/lib/fontisan/variation/inspector.rb +275 -0
- data/lib/fontisan/variation/instance_generator.rb +273 -0
- data/lib/fontisan/variation/interpolator.rb +231 -0
- data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
- data/lib/fontisan/variation/optimizer.rb +418 -0
- data/lib/fontisan/variation/parallel_generator.rb +150 -0
- data/lib/fontisan/variation/region_matcher.rb +221 -0
- data/lib/fontisan/variation/subsetter.rb +463 -0
- data/lib/fontisan/variation/table_accessor.rb +105 -0
- data/lib/fontisan/variation/validator.rb +345 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +712 -0
- data/lib/fontisan/woff_font.rb +483 -0
- data/lib/fontisan.rb +120 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +205 -4
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "conversion_strategy"
|
|
4
|
+
require_relative "../outline_extractor"
|
|
5
|
+
require_relative "../svg/font_generator"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Converters
|
|
9
|
+
# SVG font generator conversion strategy
|
|
10
|
+
#
|
|
11
|
+
# [`SvgGenerator`](lib/fontisan/converters/svg_generator.rb) implements
|
|
12
|
+
# the ConversionStrategy interface to convert TTF or OTF fonts to SVG
|
|
13
|
+
# font format for web use, inspection, or conversion purposes.
|
|
14
|
+
#
|
|
15
|
+
# SVG font generation process:
|
|
16
|
+
# 1. Extract font metadata from tables
|
|
17
|
+
# 2. Extract glyph outlines using OutlineExtractor
|
|
18
|
+
# 3. Get unicode mappings from cmap table
|
|
19
|
+
# 4. Get advance widths from hmtx table
|
|
20
|
+
# 5. Build glyph data map
|
|
21
|
+
# 6. Generate complete SVG XML using FontGenerator
|
|
22
|
+
#
|
|
23
|
+
# Note: SVG fonts are deprecated in favor of WOFF/WOFF2 but remain useful
|
|
24
|
+
# for fallback, conversion workflows, and font inspection.
|
|
25
|
+
#
|
|
26
|
+
# @example Convert TTF to SVG
|
|
27
|
+
# generator = SvgGenerator.new
|
|
28
|
+
# svg_xml = generator.convert(font)
|
|
29
|
+
# File.write('font.svg', svg_xml[:svg_xml])
|
|
30
|
+
class SvgGenerator
|
|
31
|
+
include ConversionStrategy
|
|
32
|
+
|
|
33
|
+
# Convert font to SVG format
|
|
34
|
+
#
|
|
35
|
+
# Returns a hash with :svg_xml key containing complete SVG font XML.
|
|
36
|
+
# This follows the same pattern as Woff2Encoder.
|
|
37
|
+
#
|
|
38
|
+
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
39
|
+
# @param options [Hash] Conversion options
|
|
40
|
+
# @option options [Boolean] :pretty_print Pretty print XML (default: true)
|
|
41
|
+
# @option options [Array<Integer>] :glyph_ids Specific glyph IDs to include (default: all)
|
|
42
|
+
# @option options [Integer] :max_glyphs Maximum glyphs to include (default: all)
|
|
43
|
+
# @return [Hash] Hash with :svg_xml key containing SVG XML string
|
|
44
|
+
# @raise [Error] If conversion fails
|
|
45
|
+
def convert(font, options = {})
|
|
46
|
+
validate(font, :svg)
|
|
47
|
+
|
|
48
|
+
# Extract glyph data
|
|
49
|
+
glyph_data = extract_glyph_data(font, options)
|
|
50
|
+
|
|
51
|
+
# Generate SVG XML
|
|
52
|
+
generator = Svg::FontGenerator.new(font, glyph_data, options)
|
|
53
|
+
svg_xml = generator.generate
|
|
54
|
+
|
|
55
|
+
# Return in special format for ConvertCommand to handle
|
|
56
|
+
{ svg_xml: svg_xml }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get list of supported conversions
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<Array<Symbol>>] Supported conversion pairs
|
|
62
|
+
def supported_conversions
|
|
63
|
+
[
|
|
64
|
+
%i[ttf svg],
|
|
65
|
+
%i[otf svg],
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Validate that conversion is possible
|
|
70
|
+
#
|
|
71
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font to validate
|
|
72
|
+
# @param target_format [Symbol] Target format
|
|
73
|
+
# @return [Boolean] True if valid
|
|
74
|
+
# @raise [Error] If conversion is not possible
|
|
75
|
+
def validate(font, target_format)
|
|
76
|
+
unless target_format == :svg
|
|
77
|
+
raise Fontisan::Error,
|
|
78
|
+
"SvgGenerator only supports conversion to svg, " \
|
|
79
|
+
"got: #{target_format}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Verify font has required tables
|
|
83
|
+
required_tables = %w[head hhea maxp cmap]
|
|
84
|
+
required_tables.each do |tag|
|
|
85
|
+
unless font.table(tag)
|
|
86
|
+
raise Fontisan::Error,
|
|
87
|
+
"Font is missing required table: #{tag}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Verify font has either glyf or CFF table
|
|
92
|
+
unless font.has_table?("glyf") || font.has_table?("CFF ") || font.has_table?("CFF2")
|
|
93
|
+
raise Fontisan::Error,
|
|
94
|
+
"Font must have either glyf or CFF/CFF2 table"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# Extract glyph data from font
|
|
103
|
+
#
|
|
104
|
+
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
105
|
+
# @param options [Hash] Extraction options
|
|
106
|
+
# @return [Hash] Glyph data map (glyph_id => {outline, unicode, name, advance})
|
|
107
|
+
def extract_glyph_data(font, options = {})
|
|
108
|
+
extractor = OutlineExtractor.new(font)
|
|
109
|
+
cmap = font.table("cmap")
|
|
110
|
+
hmtx = font.table("hmtx")
|
|
111
|
+
post = font.table("post")
|
|
112
|
+
maxp = font.table("maxp")
|
|
113
|
+
|
|
114
|
+
glyph_data = {}
|
|
115
|
+
num_glyphs = maxp&.num_glyphs || 0
|
|
116
|
+
max_glyphs = options[:max_glyphs] || num_glyphs
|
|
117
|
+
|
|
118
|
+
# Get unicode mappings
|
|
119
|
+
unicode_map = build_unicode_map(cmap)
|
|
120
|
+
|
|
121
|
+
# Extract specified or all glyphs
|
|
122
|
+
glyph_ids = options[:glyph_ids] || (0...num_glyphs).to_a
|
|
123
|
+
glyph_ids = glyph_ids.take(max_glyphs) if max_glyphs
|
|
124
|
+
|
|
125
|
+
glyph_ids.each do |glyph_id|
|
|
126
|
+
next if glyph_id >= num_glyphs
|
|
127
|
+
|
|
128
|
+
# Extract outline
|
|
129
|
+
outline = extractor.extract(glyph_id)
|
|
130
|
+
|
|
131
|
+
# Get advance width
|
|
132
|
+
advance = extract_advance_width(hmtx, glyph_id)
|
|
133
|
+
|
|
134
|
+
# Get unicode character
|
|
135
|
+
unicode = unicode_map[glyph_id]
|
|
136
|
+
|
|
137
|
+
# Get glyph name
|
|
138
|
+
glyph_name = extract_glyph_name(post, glyph_id)
|
|
139
|
+
|
|
140
|
+
glyph_data[glyph_id] = {
|
|
141
|
+
outline: outline,
|
|
142
|
+
unicode: unicode,
|
|
143
|
+
name: glyph_name,
|
|
144
|
+
advance: advance,
|
|
145
|
+
}
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
warn "Failed to extract glyph #{glyph_id}: #{e.message}"
|
|
148
|
+
next
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
glyph_data
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Build unicode to glyph ID map from cmap table
|
|
155
|
+
#
|
|
156
|
+
# @param cmap [Tables::Cmap, nil] Cmap table
|
|
157
|
+
# @return [Hash<Integer, String>] Map of glyph_id to unicode character
|
|
158
|
+
def build_unicode_map(cmap)
|
|
159
|
+
return {} unless cmap
|
|
160
|
+
|
|
161
|
+
unicode_map = {}
|
|
162
|
+
|
|
163
|
+
# Get best cmap subtable (prefer Unicode BMP or full)
|
|
164
|
+
subtable = find_best_cmap_subtable(cmap)
|
|
165
|
+
return {} unless subtable
|
|
166
|
+
|
|
167
|
+
# Build reverse map: glyph_id => unicode
|
|
168
|
+
subtable.each do |code_point, glyph_id|
|
|
169
|
+
# Store first unicode for each glyph
|
|
170
|
+
next if unicode_map[glyph_id]
|
|
171
|
+
|
|
172
|
+
unicode_map[glyph_id] = [code_point].pack("U")
|
|
173
|
+
rescue StandardError
|
|
174
|
+
# Skip invalid code points
|
|
175
|
+
next
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
unicode_map
|
|
179
|
+
rescue StandardError => e
|
|
180
|
+
warn "Failed to build unicode map: #{e.message}"
|
|
181
|
+
{}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Find best cmap subtable for unicode mapping
|
|
185
|
+
#
|
|
186
|
+
# @param cmap [Tables::Cmap] Cmap table
|
|
187
|
+
# @return [Hash, nil] Subtable or nil
|
|
188
|
+
def find_best_cmap_subtable(cmap)
|
|
189
|
+
# Try Unicode BMP (platform 3, encoding 1) - Windows Unicode BMP
|
|
190
|
+
subtable = cmap.subtable(3, 1)
|
|
191
|
+
return subtable if subtable
|
|
192
|
+
|
|
193
|
+
# Try Unicode full (platform 3, encoding 10) - Windows Unicode full
|
|
194
|
+
subtable = cmap.subtable(3, 10)
|
|
195
|
+
return subtable if subtable
|
|
196
|
+
|
|
197
|
+
# Try Unicode (platform 0, encoding 3) - Unicode 2.0+ BMP
|
|
198
|
+
subtable = cmap.subtable(0, 3)
|
|
199
|
+
return subtable if subtable
|
|
200
|
+
|
|
201
|
+
# Try Unicode (platform 0, encoding 4) - Unicode 2.0+ full
|
|
202
|
+
subtable = cmap.subtable(0, 4)
|
|
203
|
+
return subtable if subtable
|
|
204
|
+
|
|
205
|
+
# Fallback to any available subtable
|
|
206
|
+
cmap.subtables.first
|
|
207
|
+
rescue StandardError
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Extract advance width for glyph
|
|
212
|
+
#
|
|
213
|
+
# @param hmtx [Tables::Hmtx, nil] Hmtx table
|
|
214
|
+
# @param glyph_id [Integer] Glyph ID
|
|
215
|
+
# @return [Integer] Advance width
|
|
216
|
+
def extract_advance_width(hmtx, glyph_id)
|
|
217
|
+
return 0 unless hmtx
|
|
218
|
+
|
|
219
|
+
advance = hmtx.advance_width_for(glyph_id)
|
|
220
|
+
return 0 unless advance
|
|
221
|
+
|
|
222
|
+
advance
|
|
223
|
+
rescue StandardError
|
|
224
|
+
0
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Extract glyph name from post table
|
|
228
|
+
#
|
|
229
|
+
# @param post [Tables::Post, nil] Post table
|
|
230
|
+
# @param glyph_id [Integer] Glyph ID
|
|
231
|
+
# @return [String, nil] Glyph name or nil
|
|
232
|
+
def extract_glyph_name(post, glyph_id)
|
|
233
|
+
return nil unless post
|
|
234
|
+
|
|
235
|
+
name = post.glyph_name_for(glyph_id)
|
|
236
|
+
return nil if name.nil? || name.empty?
|
|
237
|
+
|
|
238
|
+
name
|
|
239
|
+
rescue StandardError
|
|
240
|
+
nil
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "conversion_strategy"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Converters
|
|
7
|
+
# Strategy for same-format font operations (copy/optimize)
|
|
8
|
+
#
|
|
9
|
+
# [`TableCopier`](lib/fontisan/converters/table_copier.rb) handles
|
|
10
|
+
# conversions where the source and target formats are the same.
|
|
11
|
+
# This is useful for:
|
|
12
|
+
# - Creating a clean copy of a font
|
|
13
|
+
# - Re-ordering tables
|
|
14
|
+
# - Removing corruption
|
|
15
|
+
# - Normalizing structure
|
|
16
|
+
#
|
|
17
|
+
# The strategy simply copies all tables from the source font
|
|
18
|
+
# and reassembles them with proper checksums and offsets.
|
|
19
|
+
#
|
|
20
|
+
# @example Using TableCopier
|
|
21
|
+
# copier = Fontisan::Converters::TableCopier.new
|
|
22
|
+
# tables = copier.convert(font)
|
|
23
|
+
# binary = FontWriter.write_font(tables)
|
|
24
|
+
class TableCopier
|
|
25
|
+
include ConversionStrategy
|
|
26
|
+
|
|
27
|
+
# Convert font by copying all tables
|
|
28
|
+
#
|
|
29
|
+
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
30
|
+
# @param options [Hash] Conversion options (currently unused)
|
|
31
|
+
# @return [Hash<String, String>] Map of table tags to binary data
|
|
32
|
+
def convert(font, _options = {})
|
|
33
|
+
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
34
|
+
|
|
35
|
+
unless font.respond_to?(:tables)
|
|
36
|
+
raise ArgumentError, "Font must respond to :tables"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
unless font.respond_to?(:table_data)
|
|
40
|
+
raise ArgumentError, "Font must respond to :table_data"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
target_format = detect_format(font)
|
|
44
|
+
validate(font, target_format)
|
|
45
|
+
|
|
46
|
+
tables = {}
|
|
47
|
+
|
|
48
|
+
# Copy all tables from source font
|
|
49
|
+
font.table_data.each do |tag, data|
|
|
50
|
+
tables[tag] = data if data
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
tables
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get supported conversions
|
|
57
|
+
#
|
|
58
|
+
# Supports same-format conversions for TTF and OTF
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Array<Symbol>>] Supported conversion pairs
|
|
61
|
+
def supported_conversions
|
|
62
|
+
[
|
|
63
|
+
%i[ttf ttf],
|
|
64
|
+
%i[otf otf],
|
|
65
|
+
]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Validate font for copying
|
|
69
|
+
#
|
|
70
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font to validate
|
|
71
|
+
# @param target_format [Symbol] Target format (same as source for copier)
|
|
72
|
+
# @return [Boolean] True if valid
|
|
73
|
+
# @raise [ArgumentError] If font is invalid
|
|
74
|
+
# @raise [Error] If formats don't match
|
|
75
|
+
def validate(font, target_format)
|
|
76
|
+
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
77
|
+
|
|
78
|
+
unless font.respond_to?(:tables)
|
|
79
|
+
raise ArgumentError, "Font must respond to :tables"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
unless font.respond_to?(:table_data)
|
|
83
|
+
raise ArgumentError, "Font must respond to :table_data"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Detect source format and verify it matches target
|
|
87
|
+
source_format = detect_format(font)
|
|
88
|
+
unless source_format == target_format
|
|
89
|
+
raise Fontisan::Error,
|
|
90
|
+
"TableCopier requires source and target formats to match. " \
|
|
91
|
+
"Got source: #{source_format}, target: #{target_format}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
# Detect font format from tables
|
|
100
|
+
#
|
|
101
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font to detect
|
|
102
|
+
# @return [Symbol] Format (:ttf or :otf)
|
|
103
|
+
def detect_format(font)
|
|
104
|
+
# Check for CFF/CFF2 tables (OpenType/CFF)
|
|
105
|
+
if font.has_table?("CFF ") || font.has_table?("CFF2")
|
|
106
|
+
:otf
|
|
107
|
+
# Check for glyf table (TrueType)
|
|
108
|
+
elsif font.has_table?("glyf")
|
|
109
|
+
:ttf
|
|
110
|
+
else
|
|
111
|
+
raise Fontisan::Error,
|
|
112
|
+
"Cannot detect font format: missing both CFF and glyf tables"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|