fontisan 0.2.1 → 0.2.2
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 +57 -385
- data/README.adoc +1483 -1435
- data/Rakefile +3 -2
- data/benchmark/variation_quick_bench.rb +4 -4
- data/docs/FONT_HINTING.adoc +562 -0
- data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
- data/lib/fontisan/cli.rb +10 -3
- data/lib/fontisan/collection/builder.rb +2 -1
- data/lib/fontisan/collection/offset_calculator.rb +2 -0
- data/lib/fontisan/commands/base_command.rb +5 -2
- data/lib/fontisan/commands/convert_command.rb +6 -2
- data/lib/fontisan/commands/info_command.rb +111 -5
- data/lib/fontisan/commands/instance_command.rb +8 -7
- data/lib/fontisan/commands/validate_command.rb +4 -1
- data/lib/fontisan/constants.rb +24 -24
- data/lib/fontisan/converters/format_converter.rb +8 -4
- data/lib/fontisan/converters/outline_converter.rb +21 -16
- data/lib/fontisan/converters/woff_writer.rb +8 -3
- data/lib/fontisan/font_loader.rb +11 -4
- data/lib/fontisan/font_writer.rb +2 -0
- data/lib/fontisan/formatters/text_formatter.rb +45 -1
- data/lib/fontisan/hints/hint_converter.rb +43 -47
- data/lib/fontisan/hints/hint_validator.rb +284 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +1 -3
- data/lib/fontisan/hints/postscript_hint_extractor.rb +78 -43
- data/lib/fontisan/hints/truetype_hint_extractor.rb +22 -26
- data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
- data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
- data/lib/fontisan/loading_modes.rb +4 -4
- data/lib/fontisan/models/collection_brief_info.rb +31 -0
- data/lib/fontisan/models/font_export.rb +2 -2
- data/lib/fontisan/models/font_info.rb +3 -30
- data/lib/fontisan/models/hint.rb +22 -23
- data/lib/fontisan/models/outline.rb +4 -1
- data/lib/fontisan/models/validation_report.rb +1 -1
- data/lib/fontisan/open_type_font.rb +3 -1
- data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
- data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
- data/lib/fontisan/pipeline/output_writer.rb +8 -3
- data/lib/fontisan/pipeline/transformation_pipeline.rb +8 -3
- data/lib/fontisan/subset/table_subsetter.rb +5 -5
- data/lib/fontisan/tables/cff/charstring.rb +38 -12
- data/lib/fontisan/tables/cff/charstring_parser.rb +23 -11
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +14 -14
- data/lib/fontisan/tables/cff/dict_builder.rb +4 -1
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +6 -4
- data/lib/fontisan/tables/cff/offset_recalculator.rb +1 -1
- data/lib/fontisan/tables/cff/private_dict_writer.rb +10 -4
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +7 -6
- data/lib/fontisan/tables/cff2/region_matcher.rb +2 -2
- data/lib/fontisan/tables/cff2/table_builder.rb +26 -20
- data/lib/fontisan/tables/cff2/table_reader.rb +35 -33
- data/lib/fontisan/tables/cff2/variation_data_extractor.rb +2 -2
- data/lib/fontisan/tables/cff2.rb +1 -1
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
- data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
- data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
- data/lib/fontisan/tables/name.rb +4 -4
- data/lib/fontisan/true_type_font.rb +3 -1
- data/lib/fontisan/validation/checksum_validator.rb +2 -2
- data/lib/fontisan/variation/cache.rb +3 -1
- data/lib/fontisan/variation/converter.rb +2 -1
- data/lib/fontisan/variation/delta_applier.rb +2 -1
- data/lib/fontisan/variation/inspector.rb +2 -1
- data/lib/fontisan/variation/instance_generator.rb +2 -1
- data/lib/fontisan/variation/optimizer.rb +6 -3
- data/lib/fontisan/variation/subsetter.rb +32 -10
- data/lib/fontisan/variation/variation_preserver.rb +4 -1
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/glyf_transformer.rb +57 -30
- data/lib/fontisan/woff2_font.rb +31 -15
- data/lib/fontisan.rb +42 -2
- data/scripts/measure_optimization.rb +15 -7
- metadata +8 -2
|
@@ -36,37 +36,9 @@ module Fontisan
|
|
|
36
36
|
attribute :font_revision, :float
|
|
37
37
|
attribute :permissions, :string
|
|
38
38
|
attribute :units_per_em, :integer
|
|
39
|
+
attribute :collection_offset, :integer
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
map "font_format", to: :font_format
|
|
42
|
-
map "is_variable", to: :is_variable
|
|
43
|
-
map "family_name", to: :family_name
|
|
44
|
-
map "subfamily_name", to: :subfamily_name
|
|
45
|
-
map "full_name", to: :full_name
|
|
46
|
-
map "postscript_name", to: :postscript_name
|
|
47
|
-
map "postscript_cid_name", to: :postscript_cid_name
|
|
48
|
-
map "preferred_family", to: :preferred_family
|
|
49
|
-
map "preferred_subfamily", to: :preferred_subfamily
|
|
50
|
-
map "mac_font_menu_name", to: :mac_font_menu_name
|
|
51
|
-
map "version", to: :version
|
|
52
|
-
map "unique_id", to: :unique_id
|
|
53
|
-
map "description", to: :description
|
|
54
|
-
map "designer", to: :designer
|
|
55
|
-
map "designer_url", to: :designer_url
|
|
56
|
-
map "manufacturer", to: :manufacturer
|
|
57
|
-
map "vendor_url", to: :vendor_url
|
|
58
|
-
map "vendor_id", to: :vendor_id
|
|
59
|
-
map "trademark", to: :trademark
|
|
60
|
-
map "copyright", to: :copyright
|
|
61
|
-
map "license_description", to: :license_description
|
|
62
|
-
map "license_url", to: :license_url
|
|
63
|
-
map "sample_text", to: :sample_text
|
|
64
|
-
map "font_revision", to: :font_revision
|
|
65
|
-
map "permissions", to: :permissions
|
|
66
|
-
map "units_per_em", to: :units_per_em
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
yaml do
|
|
41
|
+
key_value do
|
|
70
42
|
map "font_format", to: :font_format
|
|
71
43
|
map "is_variable", to: :is_variable
|
|
72
44
|
map "family_name", to: :family_name
|
|
@@ -93,6 +65,7 @@ module Fontisan
|
|
|
93
65
|
map "font_revision", to: :font_revision
|
|
94
66
|
map "permissions", to: :permissions
|
|
95
67
|
map "units_per_em", to: :units_per_em
|
|
68
|
+
map "collection_offset", to: :collection_offset
|
|
96
69
|
end
|
|
97
70
|
end
|
|
98
71
|
end
|
data/lib/fontisan/models/hint.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Fontisan
|
|
|
76
76
|
{
|
|
77
77
|
type: h.type,
|
|
78
78
|
data: h.data,
|
|
79
|
-
source_format: h.source_format
|
|
79
|
+
source_format: h.source_format,
|
|
80
80
|
}
|
|
81
81
|
end
|
|
82
82
|
glyph_hints_hash[glyph_id.to_s] = hints_data
|
|
@@ -124,6 +124,7 @@ module Fontisan
|
|
|
124
124
|
# Parse glyph hints JSON
|
|
125
125
|
def parse_glyph_hints
|
|
126
126
|
return {} if @glyph_hints.nil? || @glyph_hints.empty? || @glyph_hints == "{}"
|
|
127
|
+
|
|
127
128
|
JSON.parse(@glyph_hints)
|
|
128
129
|
rescue JSON::ParserError
|
|
129
130
|
{}
|
|
@@ -202,7 +203,7 @@ module Fontisan
|
|
|
202
203
|
when :interpolate
|
|
203
204
|
# IUP instruction
|
|
204
205
|
axis = data[:axis] || :y
|
|
205
|
-
axis == :x ? [0x31] : [0x30]
|
|
206
|
+
axis == :x ? [0x31] : [0x30] # IUP[x] or IUP[y]
|
|
206
207
|
when :shift
|
|
207
208
|
# SHP instruction
|
|
208
209
|
data[:instructions] || []
|
|
@@ -268,23 +269,22 @@ module Fontisan
|
|
|
268
269
|
|
|
269
270
|
# Convert stem hint to TrueType instructions
|
|
270
271
|
def convert_stem_to_truetype
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
data[:position] || 0
|
|
273
|
+
data[:width] || 0
|
|
273
274
|
orientation = data[:orientation] || :vertical
|
|
274
275
|
|
|
275
276
|
# TrueType uses MDAP (Move Direct Absolute Point) and MDRP (Move Direct Relative Point)
|
|
276
277
|
# to control stem positioning
|
|
277
278
|
instructions = []
|
|
278
279
|
|
|
279
|
-
if orientation == :vertical
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
end
|
|
280
|
+
instructions << if orientation == :vertical
|
|
281
|
+
# Vertical stem: use Y-axis instructions
|
|
282
|
+
0x2E # MDAP[rnd] - mark reference point
|
|
283
|
+
else
|
|
284
|
+
# Horizontal stem: use X-axis instructions
|
|
285
|
+
0x2F # MDAP[rnd]
|
|
286
|
+
end
|
|
287
|
+
instructions << 0xC0 # MDRP[min,rnd,black] - move relative point
|
|
288
288
|
|
|
289
289
|
instructions
|
|
290
290
|
end
|
|
@@ -313,16 +313,15 @@ module Fontisan
|
|
|
313
313
|
# Generate MDAP/MDRP pairs for each stem
|
|
314
314
|
instructions = []
|
|
315
315
|
|
|
316
|
-
stems.each do |
|
|
317
|
-
if orientation == :vertical
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
end
|
|
316
|
+
stems.each do |_stem|
|
|
317
|
+
instructions << if orientation == :vertical
|
|
318
|
+
# Vertical stem: use Y-axis instructions
|
|
319
|
+
0x2E # MDAP[rnd] - mark reference point
|
|
320
|
+
else
|
|
321
|
+
# Horizontal stem: use X-axis instructions
|
|
322
|
+
0x2F # MDAP[rnd]
|
|
323
|
+
end
|
|
324
|
+
instructions << 0xC0 # MDRP[min,rnd,black] - move relative point
|
|
326
325
|
end
|
|
327
326
|
|
|
328
327
|
instructions
|
|
@@ -121,7 +121,10 @@ module Fontisan
|
|
|
121
121
|
|
|
122
122
|
# Get path from CharString
|
|
123
123
|
path = charstring.path
|
|
124
|
-
|
|
124
|
+
if path.nil? || path.empty?
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"CharString has no path data"
|
|
127
|
+
end
|
|
125
128
|
|
|
126
129
|
commands = convert_cff_path_to_commands(path)
|
|
127
130
|
|
|
@@ -211,7 +211,8 @@ module Fontisan
|
|
|
211
211
|
batch_entries.each do |entry|
|
|
212
212
|
relative_offset = entry.offset - batch_offset
|
|
213
213
|
tag_key = entry.tag.dup.force_encoding("UTF-8")
|
|
214
|
-
@table_data[tag_key] =
|
|
214
|
+
@table_data[tag_key] =
|
|
215
|
+
batch_data[relative_offset, entry.table_length]
|
|
215
216
|
end
|
|
216
217
|
end
|
|
217
218
|
|
|
@@ -286,6 +287,7 @@ module Fontisan
|
|
|
286
287
|
# @return [Boolean] true if table is available in current mode
|
|
287
288
|
def table_available?(tag)
|
|
288
289
|
return false unless has_table?(tag)
|
|
290
|
+
|
|
289
291
|
LoadingModes.table_allowed?(@loading_mode, tag)
|
|
290
292
|
end
|
|
291
293
|
|
|
@@ -222,7 +222,8 @@ module Fontisan
|
|
|
222
222
|
if @stack_aware
|
|
223
223
|
tracker = @stack_trackers[glyph_id]
|
|
224
224
|
next unless tracker
|
|
225
|
-
next unless tracker.stack_neutral?(start_pos,
|
|
225
|
+
next unless tracker.stack_neutral?(start_pos,
|
|
226
|
+
start_pos + length)
|
|
226
227
|
end
|
|
227
228
|
|
|
228
229
|
pattern_bytes = charstring[start_pos, length]
|
|
@@ -69,7 +69,10 @@ module Fontisan
|
|
|
69
69
|
# @return [Integer] Number of bytes written
|
|
70
70
|
def write_svg(result)
|
|
71
71
|
svg_xml = result[:svg_xml] || result["svg_xml"]
|
|
72
|
-
|
|
72
|
+
unless svg_xml
|
|
73
|
+
raise ArgumentError,
|
|
74
|
+
"SVG result must contain :svg_xml key"
|
|
75
|
+
end
|
|
73
76
|
|
|
74
77
|
File.write(@output_path, svg_xml)
|
|
75
78
|
end
|
|
@@ -80,7 +83,8 @@ module Fontisan
|
|
|
80
83
|
# @return [Integer] Number of bytes written
|
|
81
84
|
def write_sfnt(tables)
|
|
82
85
|
sfnt_version = determine_sfnt_version
|
|
83
|
-
FontWriter.write_to_file(tables, @output_path,
|
|
86
|
+
FontWriter.write_to_file(tables, @output_path,
|
|
87
|
+
sfnt_version: sfnt_version)
|
|
84
88
|
end
|
|
85
89
|
|
|
86
90
|
# Write WOFF format
|
|
@@ -145,7 +149,8 @@ module Fontisan
|
|
|
145
149
|
when :otf
|
|
146
150
|
OpenTypeFont.from_tables(tables)
|
|
147
151
|
else
|
|
148
|
-
raise ArgumentError,
|
|
152
|
+
raise ArgumentError,
|
|
153
|
+
"Cannot determine font type for format: #{@format}"
|
|
149
154
|
end
|
|
150
155
|
end
|
|
151
156
|
end
|
|
@@ -294,7 +294,8 @@ module Fontisan
|
|
|
294
294
|
when ".woff" then :woff
|
|
295
295
|
when ".woff2" then :woff2
|
|
296
296
|
else
|
|
297
|
-
raise ArgumentError,
|
|
297
|
+
raise ArgumentError,
|
|
298
|
+
"Cannot determine target format from extension: #{ext}"
|
|
298
299
|
end
|
|
299
300
|
end
|
|
300
301
|
|
|
@@ -304,7 +305,10 @@ module Fontisan
|
|
|
304
305
|
def variation_options
|
|
305
306
|
opts = {}
|
|
306
307
|
opts[:coordinates] = @options[:coordinates] if @options[:coordinates]
|
|
307
|
-
|
|
308
|
+
if @options[:instance_index]
|
|
309
|
+
opts[:instance_index] =
|
|
310
|
+
@options[:instance_index]
|
|
311
|
+
end
|
|
308
312
|
opts
|
|
309
313
|
end
|
|
310
314
|
|
|
@@ -344,7 +348,8 @@ module Fontisan
|
|
|
344
348
|
when :otf
|
|
345
349
|
OpenTypeFont.from_tables(tables)
|
|
346
350
|
else
|
|
347
|
-
raise ArgumentError,
|
|
351
|
+
raise ArgumentError,
|
|
352
|
+
"Cannot determine font type: format=#{format}, has_cff=#{has_cff}, has_glyf=#{has_glyf}"
|
|
348
353
|
end
|
|
349
354
|
end
|
|
350
355
|
end
|
|
@@ -122,11 +122,11 @@ module Fontisan
|
|
|
122
122
|
data = table.to_binary_s.dup
|
|
123
123
|
|
|
124
124
|
# Calculate new numberOfHMetrics
|
|
125
|
-
new_num_h_metrics = if hmtx
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
new_num_h_metrics = if hmtx&.h_metrics
|
|
126
|
+
hmtx.h_metrics.size
|
|
127
|
+
else
|
|
128
|
+
calculate_number_of_h_metrics
|
|
129
|
+
end
|
|
130
130
|
|
|
131
131
|
# Update numberOfHMetrics field (at offset 34, uint16)
|
|
132
132
|
data[34, 2] = [new_num_h_metrics].pack("n")
|
|
@@ -239,8 +239,11 @@ module Fontisan
|
|
|
239
239
|
# 3-byte signed integer (16-bit)
|
|
240
240
|
b1 = io.getbyte
|
|
241
241
|
b2 = io.getbyte
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
if b1.nil? || b2.nil?
|
|
243
|
+
raise CorruptedTableError,
|
|
244
|
+
"Unexpected end of CharString reading shortint"
|
|
245
|
+
end
|
|
246
|
+
|
|
244
247
|
value = (b1 << 8) | b2
|
|
245
248
|
value > 0x7FFF ? value - 0x10000 : value
|
|
246
249
|
when 32..246
|
|
@@ -249,20 +252,29 @@ module Fontisan
|
|
|
249
252
|
when 247..250
|
|
250
253
|
# Positive 2-byte integer: +108 to +1131
|
|
251
254
|
b2 = io.getbyte
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
if b2.nil?
|
|
256
|
+
raise CorruptedTableError,
|
|
257
|
+
"Unexpected end of CharString reading positive integer"
|
|
258
|
+
end
|
|
259
|
+
|
|
254
260
|
(byte - 247) * 256 + b2 + 108
|
|
255
261
|
when 251..254
|
|
256
262
|
# Negative 2-byte integer: -108 to -1131
|
|
257
263
|
b2 = io.getbyte
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
if b2.nil?
|
|
265
|
+
raise CorruptedTableError,
|
|
266
|
+
"Unexpected end of CharString reading negative integer"
|
|
267
|
+
end
|
|
268
|
+
|
|
260
269
|
-(byte - 251) * 256 - b2 - 108
|
|
261
270
|
when 255
|
|
262
271
|
# 5-byte signed integer (32-bit) as fixed-point 16.16
|
|
263
272
|
bytes = io.read(4)
|
|
264
|
-
|
|
265
|
-
|
|
273
|
+
if bytes.nil? || bytes.length < 4
|
|
274
|
+
raise CorruptedTableError,
|
|
275
|
+
"Unexpected end of CharString reading fixed-point"
|
|
276
|
+
end
|
|
277
|
+
|
|
266
278
|
value = bytes.unpack1("l>") # Signed 32-bit big-endian
|
|
267
279
|
value / 65536.0 # Convert to float
|
|
268
280
|
else
|
|
@@ -368,7 +380,8 @@ module Fontisan
|
|
|
368
380
|
# rmoveto takes 2 operands, so if stack has 3 and width not parsed,
|
|
369
381
|
# first is width
|
|
370
382
|
parse_width_for_operator(width_parsed, 2)
|
|
371
|
-
return if @stack.size < 2
|
|
383
|
+
return if @stack.size < 2 # Need at least 2 values
|
|
384
|
+
|
|
372
385
|
dy = @stack.pop
|
|
373
386
|
dx = @stack.pop
|
|
374
387
|
@x += dx
|
|
@@ -383,7 +396,8 @@ module Fontisan
|
|
|
383
396
|
# hmoveto takes 1 operand, so if stack has 2 and width not parsed,
|
|
384
397
|
# first is width
|
|
385
398
|
parse_width_for_operator(width_parsed, 1)
|
|
386
|
-
return if @stack.empty?
|
|
399
|
+
return if @stack.empty? # Need at least 1 value
|
|
400
|
+
|
|
387
401
|
dx = @stack.pop || 0
|
|
388
402
|
@x += dx
|
|
389
403
|
@path << { type: :move_to, x: @x, y: @y }
|
|
@@ -396,7 +410,8 @@ module Fontisan
|
|
|
396
410
|
# vmoveto takes 1 operand, so if stack has 2 and width not parsed,
|
|
397
411
|
# first is width
|
|
398
412
|
parse_width_for_operator(width_parsed, 1)
|
|
399
|
-
return if @stack.empty?
|
|
413
|
+
return if @stack.empty? # Need at least 1 value
|
|
414
|
+
|
|
400
415
|
dy = @stack.pop || 0
|
|
401
416
|
@y += dy
|
|
402
417
|
@path << { type: :move_to, x: @x, y: @y }
|
|
@@ -688,7 +703,7 @@ module Fontisan
|
|
|
688
703
|
dy3 = @stack.shift
|
|
689
704
|
|
|
690
705
|
x1 = @x + dx1
|
|
691
|
-
y1 = @y +dy1
|
|
706
|
+
y1 = @y + dy1
|
|
692
707
|
x2 = x1 + dx2
|
|
693
708
|
y2 = y1 + dy2
|
|
694
709
|
@x = x2 + dx3
|
|
@@ -785,6 +800,7 @@ module Fontisan
|
|
|
785
800
|
# Call local subroutine
|
|
786
801
|
def callsubr
|
|
787
802
|
return if @stack.empty?
|
|
803
|
+
|
|
788
804
|
subr_num = @stack.pop
|
|
789
805
|
return unless subr_num # Guard against empty stack
|
|
790
806
|
|
|
@@ -798,6 +814,7 @@ module Fontisan
|
|
|
798
814
|
# Call global subroutine
|
|
799
815
|
def callgsubr
|
|
800
816
|
return if @stack.empty?
|
|
817
|
+
|
|
801
818
|
subr_num = @stack.pop
|
|
802
819
|
return unless subr_num # Guard against empty stack
|
|
803
820
|
|
|
@@ -838,6 +855,7 @@ module Fontisan
|
|
|
838
855
|
# Arithmetic operators
|
|
839
856
|
def arithmetic_add
|
|
840
857
|
return if @stack.size < 2
|
|
858
|
+
|
|
841
859
|
b = @stack.pop
|
|
842
860
|
a = @stack.pop
|
|
843
861
|
@stack << (a + b)
|
|
@@ -845,6 +863,7 @@ module Fontisan
|
|
|
845
863
|
|
|
846
864
|
def arithmetic_sub
|
|
847
865
|
return if @stack.size < 2
|
|
866
|
+
|
|
848
867
|
b = @stack.pop
|
|
849
868
|
a = @stack.pop
|
|
850
869
|
@stack << (a - b)
|
|
@@ -852,6 +871,7 @@ module Fontisan
|
|
|
852
871
|
|
|
853
872
|
def arithmetic_mul
|
|
854
873
|
return if @stack.size < 2
|
|
874
|
+
|
|
855
875
|
b = @stack.pop
|
|
856
876
|
a = @stack.pop
|
|
857
877
|
@stack << (a * b)
|
|
@@ -859,26 +879,32 @@ module Fontisan
|
|
|
859
879
|
|
|
860
880
|
def arithmetic_div
|
|
861
881
|
return if @stack.size < 2
|
|
882
|
+
|
|
862
883
|
b = @stack.pop
|
|
863
884
|
a = @stack.pop
|
|
864
885
|
return if b.zero?
|
|
886
|
+
|
|
865
887
|
@stack << (a / b.to_f)
|
|
866
888
|
end
|
|
867
889
|
|
|
868
890
|
def arithmetic_neg
|
|
869
891
|
return if @stack.empty?
|
|
892
|
+
|
|
870
893
|
@stack << -@stack.pop
|
|
871
894
|
end
|
|
872
895
|
|
|
873
896
|
def arithmetic_abs
|
|
874
897
|
return if @stack.empty?
|
|
898
|
+
|
|
875
899
|
@stack << @stack.pop.abs
|
|
876
900
|
end
|
|
877
901
|
|
|
878
902
|
def arithmetic_sqrt
|
|
879
903
|
return if @stack.empty?
|
|
904
|
+
|
|
880
905
|
val = @stack.pop
|
|
881
906
|
return if val.negative?
|
|
907
|
+
|
|
882
908
|
@stack << Math.sqrt(val)
|
|
883
909
|
end
|
|
884
910
|
|
|
@@ -130,7 +130,7 @@ module Fontisan
|
|
|
130
130
|
type: :operator,
|
|
131
131
|
name: operator,
|
|
132
132
|
operands: operand_stack.dup,
|
|
133
|
-
hint_data: hint_data
|
|
133
|
+
hint_data: hint_data,
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
# Clear operand stack
|
|
@@ -163,7 +163,7 @@ module Fontisan
|
|
|
163
163
|
# @param byte [Integer] Byte value
|
|
164
164
|
# @return [Boolean] True if operator byte
|
|
165
165
|
def operator_byte?(byte)
|
|
166
|
-
|
|
166
|
+
byte <= 31 && byte != 28 # Operators are 0-31 except 28 (shortint)
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Read operator from CharString
|
|
@@ -200,8 +200,11 @@ module Fontisan
|
|
|
200
200
|
# 3-byte signed integer (16-bit)
|
|
201
201
|
b1 = io.getbyte
|
|
202
202
|
b2 = io.getbyte
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
if b1.nil? || b2.nil?
|
|
204
|
+
raise CorruptedTableError,
|
|
205
|
+
"Unexpected end of CharString reading shortint"
|
|
206
|
+
end
|
|
207
|
+
|
|
205
208
|
value = (b1 << 8) | b2
|
|
206
209
|
value > 0x7FFF ? value - 0x10000 : value
|
|
207
210
|
when 32..246
|
|
@@ -210,20 +213,29 @@ module Fontisan
|
|
|
210
213
|
when 247..250
|
|
211
214
|
# Positive 2-byte integer: +108 to +1131
|
|
212
215
|
b2 = io.getbyte
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
if b2.nil?
|
|
217
|
+
raise CorruptedTableError,
|
|
218
|
+
"Unexpected end of CharString reading positive integer"
|
|
219
|
+
end
|
|
220
|
+
|
|
215
221
|
(byte - 247) * 256 + b2 + 108
|
|
216
222
|
when 251..254
|
|
217
223
|
# Negative 2-byte integer: -108 to -1131
|
|
218
224
|
b2 = io.getbyte
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
if b2.nil?
|
|
226
|
+
raise CorruptedTableError,
|
|
227
|
+
"Unexpected end of CharString reading negative integer"
|
|
228
|
+
end
|
|
229
|
+
|
|
221
230
|
-(byte - 251) * 256 - b2 - 108
|
|
222
231
|
when 255
|
|
223
232
|
# 5-byte signed integer (32-bit) as fixed-point 16.16
|
|
224
233
|
bytes = io.read(4)
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
if bytes.nil? || bytes.length < 4
|
|
235
|
+
raise CorruptedTableError,
|
|
236
|
+
"Unexpected end of CharString reading fixed-point"
|
|
237
|
+
end
|
|
238
|
+
|
|
227
239
|
value = bytes.unpack1("l>") # Signed 32-bit big-endian
|
|
228
240
|
value / 65536.0 # Convert to float
|
|
229
241
|
else
|
|
@@ -234,4 +246,4 @@ module Fontisan
|
|
|
234
246
|
end
|
|
235
247
|
end
|
|
236
248
|
end
|
|
237
|
-
end
|
|
249
|
+
end
|
|
@@ -56,7 +56,7 @@ module Fontisan
|
|
|
56
56
|
# @yield [operations] Block to modify operations
|
|
57
57
|
# @yieldparam operations [Array<Hash>] Parsed operations
|
|
58
58
|
# @yieldreturn [Array<Hash>] Modified operations
|
|
59
|
-
def modify_charstring(glyph_index
|
|
59
|
+
def modify_charstring(glyph_index)
|
|
60
60
|
# Get original CharString data
|
|
61
61
|
original_data = @source_index[glyph_index]
|
|
62
62
|
return unless original_data
|
|
@@ -66,7 +66,7 @@ module Fontisan
|
|
|
66
66
|
operations = parser.parse
|
|
67
67
|
|
|
68
68
|
# Apply modification
|
|
69
|
-
modified_operations =
|
|
69
|
+
modified_operations = yield(operations)
|
|
70
70
|
|
|
71
71
|
# Build new CharString
|
|
72
72
|
new_data = CharStringBuilder.build_from_operations(modified_operations)
|
|
@@ -86,13 +86,13 @@ module Fontisan
|
|
|
86
86
|
charstrings = []
|
|
87
87
|
|
|
88
88
|
(0...@source_index.count).each do |i|
|
|
89
|
-
if @modifications.key?(i)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
charstrings << if @modifications.key?(i)
|
|
90
|
+
# Use modified CharString
|
|
91
|
+
@modifications[i]
|
|
92
|
+
else
|
|
93
|
+
# Use original CharString
|
|
94
|
+
@source_index[i]
|
|
95
|
+
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
# Build INDEX
|
|
@@ -108,10 +108,10 @@ module Fontisan
|
|
|
108
108
|
# @yieldparam glyph_index [Integer] Current glyph index
|
|
109
109
|
# @yieldparam operations [Array<Hash>] Parsed operations
|
|
110
110
|
# @yieldreturn [Array<Hash>] Modified operations
|
|
111
|
-
def batch_modify(glyph_indices
|
|
111
|
+
def batch_modify(glyph_indices)
|
|
112
112
|
glyph_indices.each do |glyph_index|
|
|
113
113
|
modify_charstring(glyph_index) do |operations|
|
|
114
|
-
|
|
114
|
+
yield(glyph_index, operations)
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
end
|
|
@@ -124,10 +124,10 @@ module Fontisan
|
|
|
124
124
|
# @yieldparam glyph_index [Integer] Current glyph index
|
|
125
125
|
# @yieldparam operations [Array<Hash>] Parsed operations
|
|
126
126
|
# @yieldreturn [Array<Hash>] Modified operations
|
|
127
|
-
def modify_all
|
|
127
|
+
def modify_all
|
|
128
128
|
(0...@source_index.count).each do |i|
|
|
129
129
|
modify_charstring(i) do |operations|
|
|
130
|
-
|
|
130
|
+
yield(i, operations)
|
|
131
131
|
end
|
|
132
132
|
end
|
|
133
133
|
end
|
|
@@ -169,4 +169,4 @@ module Fontisan
|
|
|
169
169
|
end
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
|
-
end
|
|
172
|
+
end
|
|
@@ -93,7 +93,10 @@ module Fontisan
|
|
|
93
93
|
dict_hash.each do |operator_name, value|
|
|
94
94
|
# Get operator bytes
|
|
95
95
|
operator_bytes = operator_for_name(operator_name)
|
|
96
|
-
|
|
96
|
+
unless operator_bytes
|
|
97
|
+
raise ArgumentError,
|
|
98
|
+
"Unknown operator: #{operator_name}"
|
|
99
|
+
end
|
|
97
100
|
|
|
98
101
|
# Write operands (value can be single value or array)
|
|
99
102
|
if value.is_a?(Array)
|
|
@@ -120,7 +120,7 @@ module Fontisan
|
|
|
120
120
|
type: :operator,
|
|
121
121
|
name: operator,
|
|
122
122
|
operands: args,
|
|
123
|
-
hint_data: nil
|
|
123
|
+
hint_data: nil,
|
|
124
124
|
}]
|
|
125
125
|
end
|
|
126
126
|
|
|
@@ -144,7 +144,7 @@ module Fontisan
|
|
|
144
144
|
type: :operator,
|
|
145
145
|
name: :hintmask,
|
|
146
146
|
operands: [],
|
|
147
|
-
hint_data: hint_data
|
|
147
|
+
hint_data: hint_data,
|
|
148
148
|
}]
|
|
149
149
|
end
|
|
150
150
|
|
|
@@ -168,7 +168,7 @@ module Fontisan
|
|
|
168
168
|
type: :operator,
|
|
169
169
|
name: :cntrmask,
|
|
170
170
|
operands: [],
|
|
171
|
-
hint_data: hint_data
|
|
171
|
+
hint_data: hint_data,
|
|
172
172
|
}]
|
|
173
173
|
end
|
|
174
174
|
|
|
@@ -188,6 +188,7 @@ module Fontisan
|
|
|
188
188
|
vvcurveto hhcurveto vhcurveto hvcurveto
|
|
189
189
|
]
|
|
190
190
|
|
|
191
|
+
# rubocop:disable Style/CombinableLoops
|
|
191
192
|
# Find first path operator
|
|
192
193
|
operations.each_with_index do |op, index|
|
|
193
194
|
return index if path_operators.include?(op[:name])
|
|
@@ -197,6 +198,7 @@ module Fontisan
|
|
|
197
198
|
operations.each_with_index do |op, index|
|
|
198
199
|
return index if op[:name] == :endchar
|
|
199
200
|
end
|
|
201
|
+
# rubocop:enable Style/CombinableLoops
|
|
200
202
|
|
|
201
203
|
# Empty or malformed - inject at start
|
|
202
204
|
0
|
|
@@ -204,4 +206,4 @@ module Fontisan
|
|
|
204
206
|
end
|
|
205
207
|
end
|
|
206
208
|
end
|
|
207
|
-
end
|
|
209
|
+
end
|