fontisan 0.4.42 → 0.4.44
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/TODO.bug-fixes/09-type1-cff-seac-expansion.md +22 -0
- data/TODO.bug-fixes/10-ttf-type1-composite-glyphs.md +23 -0
- data/TODO.bug-fixes/11-remaining-respond-to.md +25 -0
- data/TODO.bug-fixes/12-type1-generators-duck-typing.md +80 -0
- data/TODO.bug-fixes/13-hint-extractor-duck-typing.md +30 -0
- data/TODO.bug-fixes/14-font-interface-type-checks.md +82 -0
- data/TODO.bug-fixes/15-glyph-type-checks-and-simple-glyph-points-bug.md +49 -0
- data/TODO.bug-fixes/16-variation-table-checks.md +31 -0
- data/TODO.bug-fixes/17-ufo-and-stitcher-cleanup.md +32 -0
- data/TODO.bug-fixes/18-generic-value-and-io-checks.md +56 -0
- data/TODO.bug-fixes/19-serialization-migration.md +52 -0
- data/TODO.bug-fixes/20-variation-validator-type-checks.md +29 -0
- data/TODO.bug-fixes/21-xml-builder-send.md +24 -0
- data/TODO.bug-fixes/22-type1-converter-font-info.md +25 -0
- data/TODO.bug-fixes/23-boundary-type-checks.md +31 -0
- data/TODO.bug-fixes/24-validator-dynamic-field-key.md +25 -0
- data/TODO.bug-fixes/25-serialization-migration.md +49 -0
- data/TODO.bug-fixes/26-variation-preserver-spec-migration.md +25 -0
- data/TODO.bug-fixes/27-table-class-registry.md +50 -0
- data/TODO.bug-fixes/28-spec-doubles-cleanup.md +40 -0
- data/TODO.bug-fixes/29-strategy-registries.md +30 -0
- data/TODO.bug-fixes/README.md +29 -2
- data/lib/fontisan/binary/base_record.rb +13 -12
- data/lib/fontisan/collection/builder.rb +3 -5
- data/lib/fontisan/collection/dfont_builder.rb +4 -11
- data/lib/fontisan/collection/writer.rb +1 -1
- data/lib/fontisan/commands/pack_command.rb +4 -4
- data/lib/fontisan/commands/unpack_command.rb +1 -1
- data/lib/fontisan/commands/validate_command.rb +1 -1
- data/lib/fontisan/converters/format_converter.rb +2 -2
- data/lib/fontisan/converters/glyf_table_builder.rb +1 -1
- data/lib/fontisan/converters/outline_converter.rb +2 -6
- data/lib/fontisan/converters/table_copier.rb +4 -12
- data/lib/fontisan/converters/type1_converter.rb +12 -37
- data/lib/fontisan/converters/woff2_encoder.rb +8 -10
- data/lib/fontisan/converters/woff_writer.rb +3 -3
- data/lib/fontisan/export/exporter.rb +8 -9
- data/lib/fontisan/export/table_serializer.rb +71 -186
- data/lib/fontisan/export/transformers/font_to_ttx.rb +18 -18
- data/lib/fontisan/export/transformers/head_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/hhea_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/maxp_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/name_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/os2_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/post_transformer.rb +1 -1
- data/lib/fontisan/export/ttx_generator.rb +36 -37
- data/lib/fontisan/font_writer.rb +1 -1
- data/lib/fontisan/glyph_accessor.rb +9 -15
- data/lib/fontisan/hints/postscript_hint_extractor.rb +24 -64
- data/lib/fontisan/hints/truetype_hint_extractor.rb +1 -1
- data/lib/fontisan/models/ttx/tables/binary_table.rb +1 -1
- data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
- data/lib/fontisan/outline_extractor.rb +1 -5
- data/lib/fontisan/sfnt_font.rb +3 -3
- data/lib/fontisan/sfnt_source.rb +28 -0
- data/lib/fontisan/stitcher/source.rb +8 -10
- data/lib/fontisan/subset/builder.rb +2 -2
- data/lib/fontisan/subset/table_strategy/cff2.rb +2 -3
- data/lib/fontisan/svg/font_face_generator.rb +0 -4
- data/lib/fontisan/svg/font_generator.rb +0 -4
- data/lib/fontisan/svg_to_glyf/assembler.rb +59 -20
- data/lib/fontisan/svg_to_glyf/document.rb +28 -9
- data/lib/fontisan/svg_to_glyf/geometry/normalizer.rb +34 -17
- data/lib/fontisan/tables/cff/charstring.rb +3 -0
- data/lib/fontisan/tables/cff/dict.rb +6 -3
- data/lib/fontisan/tables/cff/index.rb +1 -1
- data/lib/fontisan/tables/cff/private_dict_writer.rb +2 -2
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff.rb +3 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +2 -4
- data/lib/fontisan/tables/cff2.rb +3 -0
- data/lib/fontisan/tables/cmap.rb +3 -0
- data/lib/fontisan/tables/cvar.rb +3 -0
- data/lib/fontisan/tables/fvar.rb +3 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +9 -0
- data/lib/fontisan/tables/glyf.rb +12 -8
- data/lib/fontisan/tables/glyf_table.rb +4 -4
- data/lib/fontisan/tables/gvar.rb +3 -0
- data/lib/fontisan/tables/head.rb +3 -0
- data/lib/fontisan/tables/hhea.rb +3 -0
- data/lib/fontisan/tables/hmtx.rb +3 -7
- data/lib/fontisan/tables/hvar.rb +4 -0
- data/lib/fontisan/tables/loca.rb +3 -0
- data/lib/fontisan/tables/maxp.rb +3 -0
- data/lib/fontisan/tables/mvar.rb +3 -0
- data/lib/fontisan/tables/name.rb +3 -0
- data/lib/fontisan/tables/os2.rb +3 -0
- data/lib/fontisan/tables/post.rb +3 -0
- data/lib/fontisan/tables/registry.rb +71 -0
- data/lib/fontisan/tables/vvar.rb +3 -0
- data/lib/fontisan/tables.rb +2 -0
- data/lib/fontisan/type1/afm_generator.rb +12 -65
- data/lib/fontisan/type1/charstring_converter.rb +21 -23
- data/lib/fontisan/type1/conversion_options.rb +3 -2
- data/lib/fontisan/type1/decryptor.rb +1 -1
- data/lib/fontisan/type1/generator.rb +14 -9
- data/lib/fontisan/type1/inf_generator.rb +10 -35
- data/lib/fontisan/type1/pfa_generator.rb +36 -26
- data/lib/fontisan/type1/pfb_generator.rb +24 -12
- data/lib/fontisan/type1/pfm_generator.rb +14 -31
- data/lib/fontisan/type1/ttf_to_type1_converter.rb +88 -26
- data/lib/fontisan/ufo/bounds.rb +91 -0
- data/lib/fontisan/ufo/compile/fvar.rb +2 -2
- data/lib/fontisan/ufo/contour.rb +5 -0
- data/lib/fontisan/ufo/convert/from_bin_data.rb +15 -29
- data/lib/fontisan/ufo/info.rb +4 -0
- data/lib/fontisan/ufo/point.rb +0 -7
- data/lib/fontisan/ufo.rb +1 -0
- data/lib/fontisan/utilities/brotli_wrapper.rb +3 -5
- data/lib/fontisan/utilities/padding.rb +9 -8
- data/lib/fontisan/validation/collection_validator.rb +2 -2
- data/lib/fontisan/validators/validator.rb +4 -4
- data/lib/fontisan/variable/delta_applicator.rb +4 -11
- data/lib/fontisan/variable/metric_delta_processor.rb +3 -3
- data/lib/fontisan/variable/static_font_builder.rb +19 -23
- data/lib/fontisan/variation/instance_font_wrapper.rb +41 -0
- data/lib/fontisan/variation/instance_writer.rb +19 -17
- data/lib/fontisan/variation/metrics_adjuster.rb +17 -20
- data/lib/fontisan/variation/validator.rb +26 -38
- data/lib/fontisan/variation/variable_svg_generator.rb +0 -48
- data/lib/fontisan/variation/variation_preserver.rb +2 -3
- data/lib/fontisan/variation.rb +1 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/sfnt_checksum.rb +1 -1
- data/lib/fontisan/woff2/table_transformer.rb +22 -29
- data/lib/fontisan/woff2_font.rb +11 -10
- data/lib/fontisan/woff_font.rb +2 -1
- data/lib/fontisan.rb +1 -0
- metadata +27 -2
|
@@ -139,32 +139,37 @@ module Fontisan
|
|
|
139
139
|
def generate
|
|
140
140
|
result = {}
|
|
141
141
|
|
|
142
|
+
opts = options_hash
|
|
143
|
+
|
|
142
144
|
# Always generate AFM
|
|
143
|
-
result[:afm] = AFMGenerator.generate(@font,
|
|
145
|
+
result[:afm] = AFMGenerator.generate(@font, opts)
|
|
144
146
|
|
|
145
147
|
# Always generate PFM (for Windows compatibility)
|
|
146
|
-
result[:pfm] = PFMGenerator.generate(@font,
|
|
148
|
+
result[:pfm] = PFMGenerator.generate(@font, opts)
|
|
147
149
|
|
|
148
150
|
# Generate PFB or PFA based on format option
|
|
149
151
|
format = @options.format || :pfb
|
|
150
152
|
if format == :pfa
|
|
151
|
-
result[:pfa] = PFAGenerator.generate(@font,
|
|
153
|
+
result[:pfa] = PFAGenerator.generate(@font, opts)
|
|
152
154
|
else
|
|
153
|
-
result[:pfb] = PFBGenerator.generate(@font,
|
|
155
|
+
result[:pfb] = PFBGenerator.generate(@font, opts)
|
|
154
156
|
end
|
|
155
157
|
|
|
156
158
|
# Generate INF for Windows installation
|
|
157
|
-
result[:inf] = INFGenerator.generate(@font,
|
|
159
|
+
result[:inf] = INFGenerator.generate(@font, opts)
|
|
158
160
|
|
|
159
161
|
result
|
|
160
162
|
end
|
|
161
163
|
|
|
162
164
|
private
|
|
163
165
|
|
|
164
|
-
#
|
|
166
|
+
# Build the options hash forwarded to the per-format generators.
|
|
167
|
+
# This is an internal adapter — ConversionOptions exposes typed
|
|
168
|
+
# accessors; the per-format generators accept a Hash for historical
|
|
169
|
+
# reasons.
|
|
165
170
|
#
|
|
166
171
|
# @return [Hash] Options as hash
|
|
167
|
-
def
|
|
172
|
+
def options_hash
|
|
168
173
|
{
|
|
169
174
|
upm_scale: @options.upm_scale || 1000,
|
|
170
175
|
encoding: @options.encoding || Encodings::AdobeStandard,
|
|
@@ -200,8 +205,8 @@ module Fontisan
|
|
|
200
205
|
# @return [String] Base filename
|
|
201
206
|
def self.extract_base_name(font)
|
|
202
207
|
name_table = font.table(Constants::NAME_TAG)
|
|
203
|
-
if name_table
|
|
204
|
-
name = name_table.
|
|
208
|
+
if name_table
|
|
209
|
+
name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
|
|
205
210
|
return name if name
|
|
206
211
|
end
|
|
207
212
|
|
|
@@ -181,15 +181,10 @@ module Fontisan
|
|
|
181
181
|
# @return [String] Font name
|
|
182
182
|
def extract_font_name
|
|
183
183
|
name_table = @font.table(Constants::NAME_TAG)
|
|
184
|
-
return
|
|
184
|
+
return extract_postscript_name unless name_table
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
if name_table.respond_to?(:full_font_name)
|
|
188
|
-
name_table.full_font_name(1) || name_table.full_font_name(3) ||
|
|
189
|
-
extract_postscript_name
|
|
190
|
-
else
|
|
186
|
+
name_table.english_name(Tables::Name::FULL_NAME) ||
|
|
191
187
|
extract_postscript_name
|
|
192
|
-
end
|
|
193
188
|
end
|
|
194
189
|
|
|
195
190
|
# Extract PostScript name
|
|
@@ -199,12 +194,8 @@ module Fontisan
|
|
|
199
194
|
name_table = @font.table(Constants::NAME_TAG)
|
|
200
195
|
return @font.post_script_name || "Unknown" unless name_table
|
|
201
196
|
|
|
202
|
-
|
|
203
|
-
name_table.postscript_name(1) || name_table.postscript_name(3) ||
|
|
204
|
-
@font.post_script_name || "Unknown"
|
|
205
|
-
else
|
|
197
|
+
name_table.english_name(Tables::Name::POSTSCRIPT_NAME) ||
|
|
206
198
|
@font.post_script_name || "Unknown"
|
|
207
|
-
end
|
|
208
199
|
end
|
|
209
200
|
|
|
210
201
|
# Extract family name
|
|
@@ -214,9 +205,7 @@ module Fontisan
|
|
|
214
205
|
name_table = @font.table(Constants::NAME_TAG)
|
|
215
206
|
return nil unless name_table
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
name_table.font_family(1) || name_table.font_family(3)
|
|
219
|
-
end
|
|
208
|
+
name_table.english_name(Tables::Name::FAMILY)
|
|
220
209
|
end
|
|
221
210
|
|
|
222
211
|
# Extract weight
|
|
@@ -226,11 +215,7 @@ module Fontisan
|
|
|
226
215
|
os2 = @font.table(Constants::OS2_TAG)
|
|
227
216
|
return nil unless os2
|
|
228
217
|
|
|
229
|
-
weight_class =
|
|
230
|
-
os2.us_weight_class
|
|
231
|
-
elsif os2.respond_to?(:weight_class)
|
|
232
|
-
os2.weight_class
|
|
233
|
-
end
|
|
218
|
+
weight_class = os2.us_weight_class
|
|
234
219
|
return nil unless weight_class
|
|
235
220
|
|
|
236
221
|
case weight_class
|
|
@@ -254,9 +239,7 @@ module Fontisan
|
|
|
254
239
|
post = @font.table(Constants::POST_TAG)
|
|
255
240
|
return nil unless post
|
|
256
241
|
|
|
257
|
-
|
|
258
|
-
post.italic_angle
|
|
259
|
-
end
|
|
242
|
+
post.italic_angle
|
|
260
243
|
end
|
|
261
244
|
|
|
262
245
|
# Extract version
|
|
@@ -266,9 +249,7 @@ module Fontisan
|
|
|
266
249
|
name_table = @font.table(Constants::NAME_TAG)
|
|
267
250
|
return nil unless name_table
|
|
268
251
|
|
|
269
|
-
|
|
270
|
-
name_table.version_string(1) || name_table.version_string(3)
|
|
271
|
-
end
|
|
252
|
+
name_table.english_name(Tables::Name::VERSION)
|
|
272
253
|
end
|
|
273
254
|
|
|
274
255
|
# Extract copyright
|
|
@@ -278,9 +259,7 @@ module Fontisan
|
|
|
278
259
|
name_table = @font.table(Constants::NAME_TAG)
|
|
279
260
|
return nil unless name_table
|
|
280
261
|
|
|
281
|
-
|
|
282
|
-
name_table.copyright(1) || name_table.copyright(3)
|
|
283
|
-
end
|
|
262
|
+
name_table.english_name(Tables::Name::COPYRIGHT)
|
|
284
263
|
end
|
|
285
264
|
|
|
286
265
|
# Extract vendor/manufacturer
|
|
@@ -290,9 +269,7 @@ module Fontisan
|
|
|
290
269
|
name_table = @font.table(Constants::NAME_TAG)
|
|
291
270
|
return nil unless name_table
|
|
292
271
|
|
|
293
|
-
|
|
294
|
-
name_table.manufacturer(1) || name_table.manufacturer(3)
|
|
295
|
-
end
|
|
272
|
+
name_table.english_name(Tables::Name::MANUFACTURER)
|
|
296
273
|
end
|
|
297
274
|
|
|
298
275
|
# Extract license information
|
|
@@ -302,9 +279,7 @@ module Fontisan
|
|
|
302
279
|
name_table = @font.table(Constants::NAME_TAG)
|
|
303
280
|
return nil unless name_table
|
|
304
281
|
|
|
305
|
-
|
|
306
|
-
name_table.license(1) || name_table.license(3)
|
|
307
|
-
end
|
|
282
|
+
name_table.english_name(Tables::Name::LICENSE_DESCRIPTION)
|
|
308
283
|
end
|
|
309
284
|
|
|
310
285
|
# Get default PFB filename
|
|
@@ -134,15 +134,11 @@ module Fontisan
|
|
|
134
134
|
|
|
135
135
|
# Font info
|
|
136
136
|
if name_table
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
dict << "/Version (#{version}) def" if version
|
|
140
|
-
end
|
|
137
|
+
version = name_table.english_name(Tables::Name::VERSION)
|
|
138
|
+
dict << "/Version (#{version}) def" if version
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
dict << "/Notice (#{copyright}) def" if copyright
|
|
145
|
-
end
|
|
140
|
+
copyright = name_table.english_name(Tables::Name::COPYRIGHT)
|
|
141
|
+
dict << "/Notice (#{copyright}) def" if copyright
|
|
146
142
|
end
|
|
147
143
|
|
|
148
144
|
dict << "currentdict end"
|
|
@@ -161,12 +157,12 @@ module Fontisan
|
|
|
161
157
|
# Blue values (for hinting)
|
|
162
158
|
# These are typically derived from the font's alignment zones
|
|
163
159
|
os2 = @font.table(Constants::OS2_TAG)
|
|
164
|
-
if os2
|
|
160
|
+
if os2&.s_typo_ascender
|
|
165
161
|
blue_values = [
|
|
166
|
-
@scaler.scale(os2.
|
|
167
|
-
@scaler.scale(os2.
|
|
168
|
-
@scaler.scale(os2.
|
|
169
|
-
@scaler.scale(os2.
|
|
162
|
+
@scaler.scale(os2.s_typo_descender || -200),
|
|
163
|
+
@scaler.scale(os2.s_typo_descender || -200) + 20,
|
|
164
|
+
@scaler.scale(os2.s_typo_ascender),
|
|
165
|
+
@scaler.scale(os2.s_typo_ascender) + 10,
|
|
170
166
|
]
|
|
171
167
|
dict << "/BlueValues {#{blue_values.join(' ')}} def"
|
|
172
168
|
else
|
|
@@ -178,15 +174,15 @@ module Fontisan
|
|
|
178
174
|
dict << "/BlueFuzz 1 def"
|
|
179
175
|
|
|
180
176
|
# Stem snap hints
|
|
181
|
-
if os2
|
|
177
|
+
if os2&.us_weight_class
|
|
182
178
|
stem_width = @scaler.scale([100, 80,
|
|
183
|
-
90][os2.
|
|
179
|
+
90][os2.us_weight_class / 100] || 80)
|
|
184
180
|
dict << "/StemSnapH [#{stem_width}] def"
|
|
185
181
|
dict << "/StemSnapV [#{stem_width}] def"
|
|
186
182
|
end
|
|
187
183
|
|
|
188
184
|
# Force bold flag
|
|
189
|
-
dict << if os2
|
|
185
|
+
dict << if os2&.us_weight_class && os2.us_weight_class >= 700
|
|
190
186
|
"/ForceBold true def"
|
|
191
187
|
else
|
|
192
188
|
"/ForceBold false def"
|
|
@@ -286,33 +282,33 @@ module Fontisan
|
|
|
286
282
|
|
|
287
283
|
# Generate a simple CharString for a glyph
|
|
288
284
|
#
|
|
289
|
-
# @param glyf_table [
|
|
285
|
+
# @param glyf_table [Tables::Glyf] TTF glyf table
|
|
290
286
|
# @param gid [Integer] Glyph ID
|
|
291
287
|
# @return [String] Type 1 CharString data
|
|
292
288
|
def simple_charstring(glyf_table, gid)
|
|
293
289
|
glyph = glyf_table.glyph(gid)
|
|
294
290
|
|
|
295
291
|
# Empty or compound glyph
|
|
296
|
-
if glyph.nil? || glyph.
|
|
292
|
+
if glyph.nil? || glyph.num_contours.zero? || glyph.compound?
|
|
297
293
|
# Return empty charstring (hsbw + endchar)
|
|
298
294
|
return [0, 500, 14].pack("C*")
|
|
299
295
|
end
|
|
300
296
|
|
|
301
297
|
# For simple glyphs without curve conversion, generate minimal charstring
|
|
302
|
-
lsb =
|
|
303
|
-
|
|
304
|
-
bytes = [13, lsb, width] # hsbw command (13)
|
|
298
|
+
lsb, width = hmtx_metrics_for(gid)
|
|
299
|
+
bytes = [13, @scaler.scale(lsb), @scaler.scale(width)] # hsbw command (13)
|
|
305
300
|
|
|
306
301
|
# Add simple line commands (very basic)
|
|
307
|
-
|
|
302
|
+
points = glyph.points
|
|
303
|
+
unless points.empty?
|
|
308
304
|
# Just draw lines between consecutive on-curve points
|
|
309
305
|
prev_point = nil
|
|
310
|
-
|
|
311
|
-
next unless point
|
|
306
|
+
points.each do |point|
|
|
307
|
+
next unless point[:on_curve]
|
|
312
308
|
|
|
313
309
|
if prev_point
|
|
314
|
-
dx = @scaler.scale(point
|
|
315
|
-
dy = @scaler.scale(point
|
|
310
|
+
dx = @scaler.scale(point[:x]) - @scaler.scale(prev_point[:x])
|
|
311
|
+
dy = @scaler.scale(point[:y]) - @scaler.scale(prev_point[:y])
|
|
316
312
|
bytes << 5 # rlineto
|
|
317
313
|
bytes.concat(encode_number(dx))
|
|
318
314
|
bytes.concat(encode_number(dy))
|
|
@@ -325,6 +321,20 @@ module Fontisan
|
|
|
325
321
|
bytes.pack("C*")
|
|
326
322
|
end
|
|
327
323
|
|
|
324
|
+
# Look up LSB and advance width from hmtx for a glyph.
|
|
325
|
+
#
|
|
326
|
+
# @param gid [Integer] Glyph ID
|
|
327
|
+
# @return [Array(Integer, Integer)] [lsb, advance_width]
|
|
328
|
+
def hmtx_metrics_for(gid)
|
|
329
|
+
hmtx = @font.table(Constants::HMTX_TAG)
|
|
330
|
+
return [0, 500] unless hmtx
|
|
331
|
+
|
|
332
|
+
metric = hmtx.metric_for(gid)
|
|
333
|
+
return [0, 500] unless metric
|
|
334
|
+
|
|
335
|
+
[metric[:lsb] || 0, metric[:advance_width] || 500]
|
|
336
|
+
end
|
|
337
|
+
|
|
328
338
|
# Encode a number for Type 1 CharString
|
|
329
339
|
#
|
|
330
340
|
# @param value [Integer] Number to encode
|
|
@@ -159,15 +159,15 @@ module Fontisan
|
|
|
159
159
|
|
|
160
160
|
# Stem snap hints
|
|
161
161
|
os2 = @font.table(Constants::OS2_TAG)
|
|
162
|
-
if os2
|
|
162
|
+
if os2&.us_weight_class
|
|
163
163
|
stem_width = @scaler.scale([100, 80,
|
|
164
|
-
90][os2.
|
|
164
|
+
90][os2.us_weight_class / 100] || 80)
|
|
165
165
|
dict << "/StemSnapH [#{stem_width}] def"
|
|
166
166
|
dict << "/StemSnapV [#{stem_width}] def"
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Force bold flag
|
|
170
|
-
dict << if os2
|
|
170
|
+
dict << if os2&.us_weight_class && os2.us_weight_class >= 700
|
|
171
171
|
"/ForceBold true def"
|
|
172
172
|
else
|
|
173
173
|
"/ForceBold false def"
|
|
@@ -261,30 +261,42 @@ module Fontisan
|
|
|
261
261
|
glyph = glyf_table.glyph(gid)
|
|
262
262
|
|
|
263
263
|
# Empty or compound glyph
|
|
264
|
-
if glyph.nil? || glyph.
|
|
264
|
+
if glyph.nil? || glyph.num_contours.zero? || glyph.compound?
|
|
265
265
|
# Return empty charstring (hsbw + endchar)
|
|
266
266
|
return [0, 500, 14].pack("C*")
|
|
267
267
|
end
|
|
268
268
|
|
|
269
269
|
# For simple glyphs without curve conversion, generate line-based charstring
|
|
270
270
|
# This is a simplified implementation
|
|
271
|
-
lsb =
|
|
272
|
-
|
|
273
|
-
bytes = [0, lsb, width] # hsbw
|
|
271
|
+
lsb, width = hmtx_metrics_for(gid)
|
|
272
|
+
bytes = [0, @scaler.scale(lsb), @scaler.scale(width)] # hsbw
|
|
274
273
|
|
|
275
274
|
# Add lines between points (simplified)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
points = glyph.points
|
|
276
|
+
points.each do |point|
|
|
277
|
+
next unless point[:on_curve]
|
|
279
278
|
|
|
280
|
-
|
|
281
|
-
end
|
|
279
|
+
# This is very simplified - proper implementation would handle curves
|
|
282
280
|
end
|
|
283
281
|
|
|
284
282
|
bytes << 14 # endchar
|
|
285
283
|
bytes.pack("C*")
|
|
286
284
|
end
|
|
287
285
|
|
|
286
|
+
# Look up LSB and advance width from hmtx for a glyph.
|
|
287
|
+
#
|
|
288
|
+
# @param gid [Integer] Glyph ID
|
|
289
|
+
# @return [Array(Integer, Integer)] [lsb, advance_width]
|
|
290
|
+
def hmtx_metrics_for(gid)
|
|
291
|
+
hmtx = @font.table(Constants::HMTX_TAG)
|
|
292
|
+
return [0, 500] unless hmtx
|
|
293
|
+
|
|
294
|
+
metric = hmtx.metric_for(gid)
|
|
295
|
+
return [0, 500] unless metric
|
|
296
|
+
|
|
297
|
+
[metric[:lsb] || 0, metric[:advance_width] || 500]
|
|
298
|
+
end
|
|
299
|
+
|
|
288
300
|
# Build second ASCII segment (trailer)
|
|
289
301
|
#
|
|
290
302
|
# @return [String] ASCII trailer
|
|
@@ -145,7 +145,7 @@ module Fontisan
|
|
|
145
145
|
return widths unless cmap
|
|
146
146
|
|
|
147
147
|
# Get Unicode mappings
|
|
148
|
-
mappings = if cmap
|
|
148
|
+
mappings = if cmap
|
|
149
149
|
cmap.unicode_mappings || {}
|
|
150
150
|
else
|
|
151
151
|
{}
|
|
@@ -369,18 +369,14 @@ module Fontisan
|
|
|
369
369
|
metrics << [0].pack("V")
|
|
370
370
|
|
|
371
371
|
# etmCapHeight (4 bytes)
|
|
372
|
-
cap_height =
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
os2.s_typo_ascender
|
|
376
|
-
else
|
|
377
|
-
@metrics.ascent || 1000
|
|
378
|
-
end
|
|
372
|
+
cap_height = os2.s_cap_height ||
|
|
373
|
+
os2.s_typo_ascender ||
|
|
374
|
+
@metrics.ascent || 1000
|
|
379
375
|
metrics << [@scaler.scale(cap_height)].pack("V")
|
|
380
376
|
|
|
381
377
|
# etmXHeight (4 bytes)
|
|
382
|
-
x_height = if os2.
|
|
383
|
-
os2.
|
|
378
|
+
x_height = if os2.sx_height&.positive?
|
|
379
|
+
os2.sx_height
|
|
384
380
|
else
|
|
385
381
|
# Fallback: use roughly half the ascent for x-height
|
|
386
382
|
(@metrics.ascent / 2) || 500
|
|
@@ -503,11 +499,7 @@ module Fontisan
|
|
|
503
499
|
name_table = @font.table(Constants::NAME_TAG)
|
|
504
500
|
return "" unless name_table
|
|
505
501
|
|
|
506
|
-
|
|
507
|
-
name_table.copyright(1) || name_table.copyright(3) || ""
|
|
508
|
-
else
|
|
509
|
-
""
|
|
510
|
-
end
|
|
502
|
+
name_table.english_name(Tables::Name::COPYRIGHT) || ""
|
|
511
503
|
end
|
|
512
504
|
|
|
513
505
|
# Extract face name from font
|
|
@@ -515,18 +507,13 @@ module Fontisan
|
|
|
515
507
|
# @return [String] Face name
|
|
516
508
|
def extract_face_name
|
|
517
509
|
name_table = @font.table(Constants::NAME_TAG)
|
|
518
|
-
return
|
|
510
|
+
return @font.post_script_name.to_s unless name_table
|
|
519
511
|
|
|
520
512
|
# Try full font name first, then font family, then postscript name
|
|
521
|
-
face_name =
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
elsif name_table.respond_to?(:postscript_name)
|
|
526
|
-
name_table.postscript_name(1) || name_table.postscript_name(3) || ""
|
|
527
|
-
else
|
|
528
|
-
@font.post_script_name || ""
|
|
529
|
-
end
|
|
513
|
+
face_name = name_table.english_name(Tables::Name::FULL_NAME) ||
|
|
514
|
+
name_table.english_name(Tables::Name::FAMILY) ||
|
|
515
|
+
name_table.english_name(Tables::Name::POSTSCRIPT_NAME) ||
|
|
516
|
+
@font.post_script_name
|
|
530
517
|
|
|
531
518
|
face_name.to_s
|
|
532
519
|
end
|
|
@@ -538,11 +525,7 @@ module Fontisan
|
|
|
538
525
|
os2 = @font.table(Constants::OS2_TAG)
|
|
539
526
|
return 400 unless os2
|
|
540
527
|
|
|
541
|
-
weight_class =
|
|
542
|
-
os2.us_weight_class
|
|
543
|
-
elsif os2.respond_to?(:weight_class)
|
|
544
|
-
os2.weight_class
|
|
545
|
-
end
|
|
528
|
+
weight_class = os2.us_weight_class
|
|
546
529
|
return 400 unless weight_class
|
|
547
530
|
|
|
548
531
|
# Map OS/2 weight class to PFM weight
|
|
@@ -564,7 +547,7 @@ module Fontisan
|
|
|
564
547
|
# @return [Integer] Pitch and family byte
|
|
565
548
|
def pitch_and_family_value
|
|
566
549
|
post = @font.table(Constants::POST_TAG)
|
|
567
|
-
is_fixed = post
|
|
550
|
+
is_fixed = post&.is_fixed_pitch || false
|
|
568
551
|
|
|
569
552
|
pitch = is_fixed ? FIXED_PITCH : VARIABLE_PITCH
|
|
570
553
|
|
|
@@ -97,23 +97,23 @@ module Fontisan
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Convert simple glyph
|
|
100
|
-
convert_simple_glyph(glyph)
|
|
100
|
+
convert_simple_glyph(glyph, gid)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Convert a simple glyph to CharString
|
|
104
104
|
#
|
|
105
|
-
# @param glyph [
|
|
105
|
+
# @param glyph [Tables::SimpleGlyph] TTF simple glyph
|
|
106
|
+
# @param gid [Integer] Glyph ID (for hmtx lookup)
|
|
106
107
|
# @return [String] Type 1 CharString data
|
|
107
|
-
def convert_simple_glyph(glyph)
|
|
108
|
+
def convert_simple_glyph(glyph, gid)
|
|
108
109
|
commands = []
|
|
109
110
|
points = extract_points(glyph)
|
|
110
111
|
|
|
111
112
|
return empty_charstring if points.empty?
|
|
112
113
|
|
|
113
114
|
# Start with hsbw (horizontal side bearing and width)
|
|
114
|
-
lsb =
|
|
115
|
-
|
|
116
|
-
commands << [HSBW, lsb, width]
|
|
115
|
+
lsb, width = hmtx_metrics_for(gid)
|
|
116
|
+
commands << [HSBW, @scaler.scale(lsb), @scaler.scale(width)]
|
|
117
117
|
|
|
118
118
|
# Convert contours to Type 1 commands
|
|
119
119
|
contour_commands = convert_contours(points)
|
|
@@ -126,23 +126,34 @@ module Fontisan
|
|
|
126
126
|
encode_charstring(commands)
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
# Extract points from a simple glyph
|
|
129
|
+
# Extract points from a simple glyph, scaled to target UPM.
|
|
130
130
|
#
|
|
131
|
-
# @param glyph [
|
|
132
|
-
# @return [Array<Hash>] Array of
|
|
131
|
+
# @param glyph [Tables::SimpleGlyph] TTF simple glyph
|
|
132
|
+
# @return [Array<Hash>] Array of {x:, y:, on_curve:} hashes
|
|
133
133
|
def extract_points(glyph)
|
|
134
|
-
return [] unless glyph.
|
|
135
|
-
|
|
136
|
-
points
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
on_curve: point.on_curve?,
|
|
134
|
+
return [] unless glyph.is_a?(Tables::SimpleGlyph)
|
|
135
|
+
|
|
136
|
+
glyph.points.map do |point|
|
|
137
|
+
{
|
|
138
|
+
x: @scaler.scale(point[:x]),
|
|
139
|
+
y: @scaler.scale(point[:y]),
|
|
140
|
+
on_curve: point[:on_curve],
|
|
142
141
|
}
|
|
143
142
|
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Look up LSB and advance width from hmtx for a glyph.
|
|
146
|
+
#
|
|
147
|
+
# @param gid [Integer] Glyph ID
|
|
148
|
+
# @return [Array(Integer, Integer)] [lsb, advance_width]
|
|
149
|
+
def hmtx_metrics_for(gid)
|
|
150
|
+
hmtx = @font.table(Constants::HMTX_TAG)
|
|
151
|
+
return [0, 500] unless hmtx
|
|
152
|
+
|
|
153
|
+
metric = hmtx.metric_for(gid)
|
|
154
|
+
return [0, 500] unless metric
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
[metric[:lsb] || 0, metric[:advance_width] || 500]
|
|
146
157
|
end
|
|
147
158
|
|
|
148
159
|
# Convert contours to Type 1 commands
|
|
@@ -251,19 +262,70 @@ module Fontisan
|
|
|
251
262
|
# @param glyph [Object] TTF composite glyph
|
|
252
263
|
# @param glyf_table [Object] TTF glyf table
|
|
253
264
|
# @return [String] Type 1 CharString data
|
|
254
|
-
def convert_composite_glyph(
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
def convert_composite_glyph(glyph, glyf_table)
|
|
266
|
+
commands = []
|
|
267
|
+
|
|
268
|
+
glyph.components.each do |component|
|
|
269
|
+
next unless component.args_are_xy?
|
|
270
|
+
|
|
271
|
+
component_glyph = glyf_table.glyph_for(
|
|
272
|
+
component.glyph_index,
|
|
273
|
+
@loca_table,
|
|
274
|
+
@head_table,
|
|
275
|
+
)
|
|
276
|
+
next unless component_glyph&.simple?
|
|
257
277
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
matrix = component.transformation_matrix
|
|
279
|
+
commands.concat(transform_simple_glyph_commands(component_glyph, matrix))
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
return empty_charstring if commands.empty?
|
|
263
283
|
|
|
284
|
+
encode_charstring(commands)
|
|
285
|
+
rescue StandardError
|
|
264
286
|
empty_charstring
|
|
265
287
|
end
|
|
266
288
|
|
|
289
|
+
# Generate Type 1 charstring commands from a simple glyph's
|
|
290
|
+
# contours, applying a 2x3 affine transformation matrix.
|
|
291
|
+
def transform_simple_glyph_commands(simple, matrix)
|
|
292
|
+
commands = []
|
|
293
|
+
num_contours = simple.end_pts_of_contours&.size || 0
|
|
294
|
+
|
|
295
|
+
num_contours.times do |ci|
|
|
296
|
+
points = simple.points_for_contour(ci)
|
|
297
|
+
next unless points && !points.empty?
|
|
298
|
+
|
|
299
|
+
first = points.first
|
|
300
|
+
fx = transform_x(first[:x], first[:y], matrix)
|
|
301
|
+
fy = transform_y(first[:x], first[:y], matrix)
|
|
302
|
+
commands << [RMOVETO, fx, fy]
|
|
303
|
+
|
|
304
|
+
prev_x = first[:x].to_f
|
|
305
|
+
prev_y = first[:y].to_f
|
|
306
|
+
points[1..].each do |pt|
|
|
307
|
+
tx = transform_x(pt[:x], pt[:y], matrix)
|
|
308
|
+
ty = transform_y(pt[:x], pt[:y], matrix)
|
|
309
|
+
dx = tx - transform_x(prev_x, prev_y, matrix)
|
|
310
|
+
dy = ty - transform_y(prev_x, prev_y, matrix)
|
|
311
|
+
|
|
312
|
+
commands << [RLINETO, dx.to_i, dy.to_i]
|
|
313
|
+
prev_x = pt[:x].to_f
|
|
314
|
+
prev_y = pt[:y].to_f
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
commands
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def transform_x(x, y, m)
|
|
322
|
+
(m[0] * x + m[2] * y + m[4]).to_i
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def transform_y(x, y, m)
|
|
326
|
+
(m[1] * x + m[3] * y + m[5]).to_i
|
|
327
|
+
end
|
|
328
|
+
|
|
267
329
|
# Encode commands to Type 1 CharString binary format
|
|
268
330
|
#
|
|
269
331
|
# @param commands [Array<Array<Integer>>] Array of command arrays
|