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,905 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "../../binary/base_record"
|
|
5
|
+
|
|
6
|
+
module Fontisan
|
|
7
|
+
module Tables
|
|
8
|
+
class Cff
|
|
9
|
+
# Type 2 CharString interpreter
|
|
10
|
+
#
|
|
11
|
+
# CharStrings are stack-based programs that draw glyph outlines using
|
|
12
|
+
# a series of operators. They are stored in the CharStrings INDEX and
|
|
13
|
+
# contain path construction, hinting, and arithmetic operations.
|
|
14
|
+
#
|
|
15
|
+
# Type 2 CharString Format:
|
|
16
|
+
# - Numbers are pushed onto an operand stack
|
|
17
|
+
# - Operators pop operands and execute commands
|
|
18
|
+
# - Path operators build the glyph outline
|
|
19
|
+
# - Hint operators define stem hints (can be ignored for rendering)
|
|
20
|
+
# - Subroutine operators allow code reuse
|
|
21
|
+
# - Arithmetic operators perform calculations on the stack
|
|
22
|
+
#
|
|
23
|
+
# Path Construction Flow:
|
|
24
|
+
# 1. Optional width value (first operand if odd number before first move)
|
|
25
|
+
# 2. Initial moveto operator to start a path
|
|
26
|
+
# 3. Line/curve operators to construct the path
|
|
27
|
+
# 4. Optional closepath (implicit at endchar)
|
|
28
|
+
# 5. endchar to finish the glyph
|
|
29
|
+
#
|
|
30
|
+
# Reference: Adobe Type 2 CharString Format
|
|
31
|
+
# https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf
|
|
32
|
+
#
|
|
33
|
+
# @example Interpreting a CharString
|
|
34
|
+
# charstring = CharString.new(data, private_dict, global_subrs,
|
|
35
|
+
# local_subrs)
|
|
36
|
+
# puts charstring.width # => glyph width
|
|
37
|
+
# puts charstring.path # => array of path commands
|
|
38
|
+
# bbox = charstring.bounding_box # => [xMin, yMin, xMax, yMax]
|
|
39
|
+
class CharString
|
|
40
|
+
# @return [Integer, nil] Glyph width (nil if using default width)
|
|
41
|
+
attr_reader :width
|
|
42
|
+
|
|
43
|
+
# @return [Array<Hash>] Path commands array
|
|
44
|
+
attr_reader :path
|
|
45
|
+
|
|
46
|
+
# @return [Float] Current X coordinate
|
|
47
|
+
attr_reader :x
|
|
48
|
+
|
|
49
|
+
# @return [Float] Current Y coordinate
|
|
50
|
+
attr_reader :y
|
|
51
|
+
|
|
52
|
+
# Type 2 CharString operators
|
|
53
|
+
#
|
|
54
|
+
# These operators define the behavior of the CharString interpreter
|
|
55
|
+
OPERATORS = {
|
|
56
|
+
# Path construction operators
|
|
57
|
+
1 => :hstem,
|
|
58
|
+
3 => :vstem,
|
|
59
|
+
4 => :vmoveto,
|
|
60
|
+
5 => :rlineto,
|
|
61
|
+
6 => :hlineto,
|
|
62
|
+
7 => :vlineto,
|
|
63
|
+
8 => :rrcurveto,
|
|
64
|
+
10 => :callsubr,
|
|
65
|
+
11 => :return,
|
|
66
|
+
14 => :endchar,
|
|
67
|
+
18 => :hstemhm,
|
|
68
|
+
19 => :hintmask,
|
|
69
|
+
20 => :cntrmask,
|
|
70
|
+
21 => :rmoveto,
|
|
71
|
+
22 => :hmoveto,
|
|
72
|
+
23 => :vstemhm,
|
|
73
|
+
24 => :rcurveline,
|
|
74
|
+
25 => :rlinecurve,
|
|
75
|
+
26 => :vvcurveto,
|
|
76
|
+
27 => :hhcurveto,
|
|
77
|
+
28 => :shortint,
|
|
78
|
+
29 => :callgsubr,
|
|
79
|
+
30 => :vhcurveto,
|
|
80
|
+
31 => :hvcurveto,
|
|
81
|
+
# 12 prefix for two-byte operators
|
|
82
|
+
[12, 3] => :and,
|
|
83
|
+
[12, 4] => :or,
|
|
84
|
+
[12, 5] => :not,
|
|
85
|
+
[12, 9] => :abs,
|
|
86
|
+
[12, 10] => :add,
|
|
87
|
+
[12, 11] => :sub,
|
|
88
|
+
[12, 12] => :div,
|
|
89
|
+
[12, 14] => :neg,
|
|
90
|
+
[12, 15] => :eq,
|
|
91
|
+
[12, 18] => :drop,
|
|
92
|
+
[12, 20] => :put,
|
|
93
|
+
[12, 21] => :get,
|
|
94
|
+
[12, 22] => :ifelse,
|
|
95
|
+
[12, 23] => :random,
|
|
96
|
+
[12, 24] => :mul,
|
|
97
|
+
[12, 26] => :sqrt,
|
|
98
|
+
[12, 27] => :dup,
|
|
99
|
+
[12, 28] => :exch,
|
|
100
|
+
[12, 29] => :index,
|
|
101
|
+
[12, 30] => :roll,
|
|
102
|
+
[12, 34] => :hflex,
|
|
103
|
+
[12, 35] => :flex,
|
|
104
|
+
[12, 36] => :hflex1,
|
|
105
|
+
[12, 37] => :flex1,
|
|
106
|
+
}.freeze
|
|
107
|
+
|
|
108
|
+
# Initialize and interpret a CharString
|
|
109
|
+
#
|
|
110
|
+
# @param data [String] Binary CharString data
|
|
111
|
+
# @param private_dict [PrivateDict] Private DICT for width defaults
|
|
112
|
+
# @param global_subrs [Index] Global subroutines INDEX
|
|
113
|
+
# @param local_subrs [Index, nil] Local subroutines INDEX
|
|
114
|
+
def initialize(data, private_dict, global_subrs, local_subrs = nil)
|
|
115
|
+
@data = data
|
|
116
|
+
@private_dict = private_dict
|
|
117
|
+
@global_subrs = global_subrs
|
|
118
|
+
@local_subrs = local_subrs
|
|
119
|
+
|
|
120
|
+
@stack = []
|
|
121
|
+
@path = []
|
|
122
|
+
@x = 0.0
|
|
123
|
+
@y = 0.0
|
|
124
|
+
@width = nil
|
|
125
|
+
@stems = 0
|
|
126
|
+
@transient_array = []
|
|
127
|
+
@subroutine_bias = calculate_bias(local_subrs)
|
|
128
|
+
@global_subroutine_bias = calculate_bias(global_subrs)
|
|
129
|
+
|
|
130
|
+
parse!
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Calculate the bounding box of the glyph
|
|
134
|
+
#
|
|
135
|
+
# @return [Array<Float>] [xMin, yMin, xMax, yMax] or nil if no path
|
|
136
|
+
def bounding_box
|
|
137
|
+
return nil if @path.empty?
|
|
138
|
+
|
|
139
|
+
x_coords = []
|
|
140
|
+
y_coords = []
|
|
141
|
+
|
|
142
|
+
@path.each do |cmd|
|
|
143
|
+
case cmd[:type]
|
|
144
|
+
when :move_to, :line_to
|
|
145
|
+
x_coords << cmd[:x]
|
|
146
|
+
y_coords << cmd[:y]
|
|
147
|
+
when :curve_to
|
|
148
|
+
x_coords << cmd[:x1] << cmd[:x2] << cmd[:x]
|
|
149
|
+
y_coords << cmd[:y1] << cmd[:y2] << cmd[:y]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return nil if x_coords.empty?
|
|
154
|
+
|
|
155
|
+
[x_coords.min, y_coords.min, x_coords.max, y_coords.max]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Convert path to drawing commands
|
|
159
|
+
#
|
|
160
|
+
# @return [Array<Array>] Array of command arrays:
|
|
161
|
+
# [:move_to, x, y], [:line_to, x, y], [:curve_to, x1, y1, x2, y2,
|
|
162
|
+
# x, y]
|
|
163
|
+
def to_commands
|
|
164
|
+
@path.map do |cmd|
|
|
165
|
+
case cmd[:type]
|
|
166
|
+
when :move_to
|
|
167
|
+
[:move_to, cmd[:x], cmd[:y]]
|
|
168
|
+
when :line_to
|
|
169
|
+
[:line_to, cmd[:x], cmd[:y]]
|
|
170
|
+
when :curve_to
|
|
171
|
+
[:curve_to, cmd[:x1], cmd[:y1], cmd[:x2], cmd[:y2],
|
|
172
|
+
cmd[:x], cmd[:y]]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
# Parse and execute the CharString program
|
|
180
|
+
def parse!
|
|
181
|
+
io = StringIO.new(@data)
|
|
182
|
+
width_parsed = false
|
|
183
|
+
|
|
184
|
+
until io.eof?
|
|
185
|
+
byte = io.getbyte
|
|
186
|
+
|
|
187
|
+
if byte <= 31 && byte != 28
|
|
188
|
+
# Operator byte
|
|
189
|
+
operator = read_operator(io, byte)
|
|
190
|
+
result = execute_operator(operator, width_parsed)
|
|
191
|
+
# Mark width as parsed after move operators or hint operators
|
|
192
|
+
if result == true || %i[hstem vstem hstemhm
|
|
193
|
+
vstemhm].include?(operator)
|
|
194
|
+
width_parsed = true
|
|
195
|
+
end
|
|
196
|
+
else
|
|
197
|
+
# Operand byte
|
|
198
|
+
io.pos -= 1
|
|
199
|
+
number = read_number(io)
|
|
200
|
+
@stack << number
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
rescue StandardError => e
|
|
204
|
+
raise CorruptedTableError,
|
|
205
|
+
"Failed to parse CharString: #{e.message}"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Read an operator from the CharString
|
|
209
|
+
#
|
|
210
|
+
# @param io [StringIO] Input stream
|
|
211
|
+
# @param first_byte [Integer] First operator byte
|
|
212
|
+
# @return [Symbol] Operator name
|
|
213
|
+
def read_operator(io, first_byte)
|
|
214
|
+
if first_byte == 12
|
|
215
|
+
# Two-byte operator
|
|
216
|
+
second_byte = io.getbyte
|
|
217
|
+
raise CorruptedTableError, "Unexpected end of CharString" if
|
|
218
|
+
second_byte.nil?
|
|
219
|
+
|
|
220
|
+
operator_key = [first_byte, second_byte]
|
|
221
|
+
OPERATORS[operator_key] || :unknown
|
|
222
|
+
else
|
|
223
|
+
# Single-byte operator
|
|
224
|
+
OPERATORS[first_byte] || :unknown
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Read a number (integer or real) from the CharString
|
|
229
|
+
#
|
|
230
|
+
# @param io [StringIO] Input stream
|
|
231
|
+
# @return [Integer, Float] The number value
|
|
232
|
+
def read_number(io)
|
|
233
|
+
byte = io.getbyte
|
|
234
|
+
raise CorruptedTableError, "Unexpected end of CharString" if
|
|
235
|
+
byte.nil?
|
|
236
|
+
|
|
237
|
+
case byte
|
|
238
|
+
when 28
|
|
239
|
+
# 3-byte signed integer (16-bit)
|
|
240
|
+
b1 = io.getbyte
|
|
241
|
+
b2 = io.getbyte
|
|
242
|
+
value = (b1 << 8) | b2
|
|
243
|
+
value > 0x7FFF ? value - 0x10000 : value
|
|
244
|
+
when 32..246
|
|
245
|
+
# Small integer: -107 to +107
|
|
246
|
+
byte - 139
|
|
247
|
+
when 247..250
|
|
248
|
+
# Positive 2-byte integer: +108 to +1131
|
|
249
|
+
b2 = io.getbyte
|
|
250
|
+
(byte - 247) * 256 + b2 + 108
|
|
251
|
+
when 251..254
|
|
252
|
+
# Negative 2-byte integer: -108 to -1131
|
|
253
|
+
b2 = io.getbyte
|
|
254
|
+
-(byte - 251) * 256 - b2 - 108
|
|
255
|
+
when 255
|
|
256
|
+
# 5-byte signed integer (32-bit) as fixed-point 16.16
|
|
257
|
+
bytes = io.read(4)
|
|
258
|
+
value = bytes.unpack1("l>") # Signed 32-bit big-endian
|
|
259
|
+
value / 65536.0 # Convert to float
|
|
260
|
+
else
|
|
261
|
+
raise CorruptedTableError, "Invalid CharString number byte: #{byte}"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Execute a CharString operator
|
|
266
|
+
#
|
|
267
|
+
# @param operator [Symbol] Operator name
|
|
268
|
+
# @param width_parsed [Boolean] Whether width has been parsed
|
|
269
|
+
def execute_operator(operator, width_parsed)
|
|
270
|
+
case operator
|
|
271
|
+
# Path construction operators
|
|
272
|
+
when :rmoveto
|
|
273
|
+
rmoveto(width_parsed)
|
|
274
|
+
true # Width has now been parsed
|
|
275
|
+
when :hmoveto
|
|
276
|
+
hmoveto(width_parsed)
|
|
277
|
+
true # Width has now been parsed
|
|
278
|
+
when :vmoveto
|
|
279
|
+
vmoveto(width_parsed)
|
|
280
|
+
true # Width has now been parsed
|
|
281
|
+
when :rlineto
|
|
282
|
+
rlineto
|
|
283
|
+
when :hlineto
|
|
284
|
+
hlineto
|
|
285
|
+
when :vlineto
|
|
286
|
+
vlineto
|
|
287
|
+
when :rrcurveto
|
|
288
|
+
rrcurveto
|
|
289
|
+
when :hhcurveto
|
|
290
|
+
hhcurveto
|
|
291
|
+
when :vvcurveto
|
|
292
|
+
vvcurveto
|
|
293
|
+
when :hvcurveto
|
|
294
|
+
hvcurveto
|
|
295
|
+
when :vhcurveto
|
|
296
|
+
vhcurveto
|
|
297
|
+
when :rcurveline
|
|
298
|
+
rcurveline
|
|
299
|
+
when :rlinecurve
|
|
300
|
+
rlinecurve
|
|
301
|
+
when :endchar
|
|
302
|
+
endchar
|
|
303
|
+
|
|
304
|
+
# Hint operators (stub for now)
|
|
305
|
+
when :hstem, :vstem, :hstemhm, :vstemhm
|
|
306
|
+
hint_operator(width_parsed)
|
|
307
|
+
when :hintmask, :cntrmask
|
|
308
|
+
hintmask_operator
|
|
309
|
+
|
|
310
|
+
# Subroutine operators
|
|
311
|
+
when :callsubr
|
|
312
|
+
callsubr
|
|
313
|
+
when :callgsubr
|
|
314
|
+
callgsubr
|
|
315
|
+
when :return
|
|
316
|
+
# Return is handled by subroutine execution
|
|
317
|
+
|
|
318
|
+
# Arithmetic operators
|
|
319
|
+
when :add
|
|
320
|
+
arithmetic_add
|
|
321
|
+
when :sub
|
|
322
|
+
arithmetic_sub
|
|
323
|
+
when :mul
|
|
324
|
+
arithmetic_mul
|
|
325
|
+
when :div
|
|
326
|
+
arithmetic_div
|
|
327
|
+
when :neg
|
|
328
|
+
arithmetic_neg
|
|
329
|
+
when :abs
|
|
330
|
+
arithmetic_abs
|
|
331
|
+
when :sqrt
|
|
332
|
+
arithmetic_sqrt
|
|
333
|
+
when :drop
|
|
334
|
+
@stack.pop
|
|
335
|
+
when :exch
|
|
336
|
+
@stack[-1], @stack[-2] = @stack[-2], @stack[-1]
|
|
337
|
+
when :dup
|
|
338
|
+
@stack << @stack.last
|
|
339
|
+
when :put
|
|
340
|
+
value = @stack.pop
|
|
341
|
+
index = @stack.pop
|
|
342
|
+
@transient_array[index] = value
|
|
343
|
+
when :get
|
|
344
|
+
index = @stack.pop
|
|
345
|
+
@stack << (@transient_array[index] || 0)
|
|
346
|
+
|
|
347
|
+
# Flex operators
|
|
348
|
+
when :hflex, :flex, :hflex1, :flex1
|
|
349
|
+
flex_operator(operator)
|
|
350
|
+
|
|
351
|
+
when :unknown
|
|
352
|
+
# Unknown operator - clear stack and continue
|
|
353
|
+
@stack.clear
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# rmoveto: dx dy rmoveto
|
|
358
|
+
# Relative move to (dx, dy)
|
|
359
|
+
def rmoveto(width_parsed)
|
|
360
|
+
# rmoveto takes 2 operands, so if stack has 3 and width not parsed,
|
|
361
|
+
# first is width
|
|
362
|
+
parse_width_for_operator(width_parsed, 2)
|
|
363
|
+
dy = @stack.pop
|
|
364
|
+
dx = @stack.pop
|
|
365
|
+
@x += dx
|
|
366
|
+
@y += dy
|
|
367
|
+
@path << { type: :move_to, x: @x, y: @y }
|
|
368
|
+
@stack.clear
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# hmoveto: dx hmoveto
|
|
372
|
+
# Horizontal move to (dx, 0)
|
|
373
|
+
def hmoveto(width_parsed)
|
|
374
|
+
# hmoveto takes 1 operand, so if stack has 2 and width not parsed,
|
|
375
|
+
# first is width
|
|
376
|
+
parse_width_for_operator(width_parsed, 1)
|
|
377
|
+
dx = @stack.pop || 0
|
|
378
|
+
@x += dx
|
|
379
|
+
@path << { type: :move_to, x: @x, y: @y }
|
|
380
|
+
@stack.clear
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# vmoveto: dy vmoveto
|
|
384
|
+
# Vertical move to (0, dy)
|
|
385
|
+
def vmoveto(width_parsed)
|
|
386
|
+
# vmoveto takes 1 operand, so if stack has 2 and width not parsed,
|
|
387
|
+
# first is width
|
|
388
|
+
parse_width_for_operator(width_parsed, 1)
|
|
389
|
+
dy = @stack.pop || 0
|
|
390
|
+
@y += dy
|
|
391
|
+
@path << { type: :move_to, x: @x, y: @y }
|
|
392
|
+
@stack.clear
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# rlineto: {dxa dya}+ rlineto
|
|
396
|
+
# Relative line to
|
|
397
|
+
def rlineto
|
|
398
|
+
while @stack.size >= 2
|
|
399
|
+
dx = @stack.shift
|
|
400
|
+
dy = @stack.shift
|
|
401
|
+
@x += dx
|
|
402
|
+
@y += dy
|
|
403
|
+
@path << { type: :line_to, x: @x, y: @y }
|
|
404
|
+
end
|
|
405
|
+
@stack.clear
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# hlineto: dx1 {dya dxb}* hlineto
|
|
409
|
+
# Alternating horizontal and vertical lines
|
|
410
|
+
def hlineto
|
|
411
|
+
horizontal = true
|
|
412
|
+
while @stack.any?
|
|
413
|
+
delta = @stack.shift
|
|
414
|
+
if horizontal
|
|
415
|
+
@x += delta
|
|
416
|
+
else
|
|
417
|
+
@y += delta
|
|
418
|
+
end
|
|
419
|
+
@path << { type: :line_to, x: @x, y: @y }
|
|
420
|
+
horizontal = !horizontal
|
|
421
|
+
end
|
|
422
|
+
@stack.clear
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# vlineto: dy1 {dxb dya}* vlineto
|
|
426
|
+
# Alternating vertical and horizontal lines
|
|
427
|
+
def vlineto
|
|
428
|
+
vertical = true
|
|
429
|
+
while @stack.any?
|
|
430
|
+
delta = @stack.shift
|
|
431
|
+
if vertical
|
|
432
|
+
@y += delta
|
|
433
|
+
else
|
|
434
|
+
@x += delta
|
|
435
|
+
end
|
|
436
|
+
@path << { type: :line_to, x: @x, y: @y }
|
|
437
|
+
vertical = !vertical
|
|
438
|
+
end
|
|
439
|
+
@stack.clear
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# rrcurveto: {dxa dya dxb dyb dxc dyc}+ rrcurveto
|
|
443
|
+
# Relative cubic Bézier curve
|
|
444
|
+
def rrcurveto
|
|
445
|
+
while @stack.size >= 6
|
|
446
|
+
dx1 = @stack.shift
|
|
447
|
+
dy1 = @stack.shift
|
|
448
|
+
dx2 = @stack.shift
|
|
449
|
+
dy2 = @stack.shift
|
|
450
|
+
dx3 = @stack.shift
|
|
451
|
+
dy3 = @stack.shift
|
|
452
|
+
|
|
453
|
+
x1 = @x + dx1
|
|
454
|
+
y1 = @y + dy1
|
|
455
|
+
x2 = x1 + dx2
|
|
456
|
+
y2 = y1 + dy2
|
|
457
|
+
@x = x2 + dx3
|
|
458
|
+
@y = y2 + dy3
|
|
459
|
+
|
|
460
|
+
@path << {
|
|
461
|
+
type: :curve_to,
|
|
462
|
+
x1: x1, y1: y1,
|
|
463
|
+
x2: x2, y2: y2,
|
|
464
|
+
x: @x, y: @y
|
|
465
|
+
}
|
|
466
|
+
end
|
|
467
|
+
@stack.clear
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# hhcurveto: dy1? {dxa dxb dyb dxc}+ hhcurveto
|
|
471
|
+
# Horizontal-horizontal curve
|
|
472
|
+
def hhcurveto
|
|
473
|
+
# First value might be dy1 if odd number of args
|
|
474
|
+
if @stack.size.odd?
|
|
475
|
+
@y += @stack.shift
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
while @stack.size >= 4
|
|
479
|
+
dx1 = @stack.shift
|
|
480
|
+
dx2 = @stack.shift
|
|
481
|
+
dy2 = @stack.shift
|
|
482
|
+
dx3 = @stack.shift
|
|
483
|
+
|
|
484
|
+
x1 = @x + dx1
|
|
485
|
+
y1 = @y
|
|
486
|
+
x2 = x1 + dx2
|
|
487
|
+
y2 = y1 + dy2
|
|
488
|
+
@x = x2 + dx3
|
|
489
|
+
@y = y2
|
|
490
|
+
|
|
491
|
+
@path << {
|
|
492
|
+
type: :curve_to,
|
|
493
|
+
x1: x1, y1: y1,
|
|
494
|
+
x2: x2, y2: y2,
|
|
495
|
+
x: @x, y: @y
|
|
496
|
+
}
|
|
497
|
+
end
|
|
498
|
+
@stack.clear
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# vvcurveto: dx1? {dya dxb dyb dyc}+ vvcurveto
|
|
502
|
+
# Vertical-vertical curve
|
|
503
|
+
def vvcurveto
|
|
504
|
+
# First value might be dx1 if odd number of args
|
|
505
|
+
if @stack.size.odd?
|
|
506
|
+
@x += @stack.shift
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
while @stack.size >= 4
|
|
510
|
+
dy1 = @stack.shift
|
|
511
|
+
dx2 = @stack.shift
|
|
512
|
+
dy2 = @stack.shift
|
|
513
|
+
dy3 = @stack.shift
|
|
514
|
+
|
|
515
|
+
x1 = @x
|
|
516
|
+
y1 = @y + dy1
|
|
517
|
+
x2 = x1 + dx2
|
|
518
|
+
y2 = y1 + dy2
|
|
519
|
+
@x = x2
|
|
520
|
+
@y = y2 + dy3
|
|
521
|
+
|
|
522
|
+
@path << {
|
|
523
|
+
type: :curve_to,
|
|
524
|
+
x1: x1, y1: y1,
|
|
525
|
+
x2: x2, y2: y2,
|
|
526
|
+
x: @x, y: @y
|
|
527
|
+
}
|
|
528
|
+
end
|
|
529
|
+
@stack.clear
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# hvcurveto: dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
|
|
533
|
+
# hvcurveto
|
|
534
|
+
# Horizontal-vertical curve
|
|
535
|
+
def hvcurveto
|
|
536
|
+
horizontal_first = true
|
|
537
|
+
while @stack.size >= 4
|
|
538
|
+
if horizontal_first
|
|
539
|
+
dx1 = @stack.shift
|
|
540
|
+
dx2 = @stack.shift
|
|
541
|
+
dy2 = @stack.shift
|
|
542
|
+
dy3 = @stack.shift
|
|
543
|
+
# Handle final dx if this is the last curve
|
|
544
|
+
dx3 = @stack.size == 1 ? @stack.shift : 0
|
|
545
|
+
|
|
546
|
+
x1 = @x + dx1
|
|
547
|
+
y1 = @y
|
|
548
|
+
else
|
|
549
|
+
dy1 = @stack.shift
|
|
550
|
+
dx2 = @stack.shift
|
|
551
|
+
dy2 = @stack.shift
|
|
552
|
+
dx3 = @stack.shift
|
|
553
|
+
# Handle final dy if this is the last curve
|
|
554
|
+
dy3 = @stack.size == 1 ? @stack.shift : 0
|
|
555
|
+
|
|
556
|
+
x1 = @x
|
|
557
|
+
y1 = @y + dy1
|
|
558
|
+
end
|
|
559
|
+
x2 = x1 + dx2
|
|
560
|
+
y2 = y1 + dy2
|
|
561
|
+
@x = x2 + dx3
|
|
562
|
+
@y = y2 + dy3
|
|
563
|
+
|
|
564
|
+
@path << {
|
|
565
|
+
type: :curve_to,
|
|
566
|
+
x1: x1, y1: y1,
|
|
567
|
+
x2: x2, y2: y2,
|
|
568
|
+
x: @x, y: @y
|
|
569
|
+
}
|
|
570
|
+
horizontal_first = !horizontal_first
|
|
571
|
+
end
|
|
572
|
+
@stack.clear
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# vhcurveto: dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
|
|
576
|
+
# vhcurveto
|
|
577
|
+
# Vertical-horizontal curve
|
|
578
|
+
def vhcurveto
|
|
579
|
+
vertical_first = true
|
|
580
|
+
while @stack.size >= 4
|
|
581
|
+
if vertical_first
|
|
582
|
+
dy1 = @stack.shift
|
|
583
|
+
dx2 = @stack.shift
|
|
584
|
+
dy2 = @stack.shift
|
|
585
|
+
dx3 = @stack.shift
|
|
586
|
+
# Handle final dy if this is the last curve
|
|
587
|
+
dy3 = @stack.size == 1 ? @stack.shift : 0
|
|
588
|
+
|
|
589
|
+
x1 = @x
|
|
590
|
+
y1 = @y + dy1
|
|
591
|
+
else
|
|
592
|
+
dx1 = @stack.shift
|
|
593
|
+
dx2 = @stack.shift
|
|
594
|
+
dy2 = @stack.shift
|
|
595
|
+
dy3 = @stack.shift
|
|
596
|
+
# Handle final dx if this is the last curve
|
|
597
|
+
dx3 = @stack.size == 1 ? @stack.shift : 0
|
|
598
|
+
|
|
599
|
+
x1 = @x + dx1
|
|
600
|
+
y1 = @y
|
|
601
|
+
end
|
|
602
|
+
x2 = x1 + dx2
|
|
603
|
+
y2 = y1 + dy2
|
|
604
|
+
@x = x2 + dx3
|
|
605
|
+
@y = y2 + dy3
|
|
606
|
+
|
|
607
|
+
@path << {
|
|
608
|
+
type: :curve_to,
|
|
609
|
+
x1: x1, y1: y1,
|
|
610
|
+
x2: x2, y2: y2,
|
|
611
|
+
x: @x, y: @y
|
|
612
|
+
}
|
|
613
|
+
vertical_first = !vertical_first
|
|
614
|
+
end
|
|
615
|
+
@stack.clear
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# rcurveline: {dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline
|
|
619
|
+
# Curves followed by a line
|
|
620
|
+
def rcurveline
|
|
621
|
+
# Process curves (all but last 2 values)
|
|
622
|
+
while @stack.size > 2
|
|
623
|
+
break if @stack.size < 6
|
|
624
|
+
|
|
625
|
+
dx1 = @stack.shift
|
|
626
|
+
dy1 = @stack.shift
|
|
627
|
+
dx2 = @stack.shift
|
|
628
|
+
dy2 = @stack.shift
|
|
629
|
+
dx3 = @stack.shift
|
|
630
|
+
dy3 = @stack.shift
|
|
631
|
+
|
|
632
|
+
x1 = @x + dx1
|
|
633
|
+
y1 = @y + dy1
|
|
634
|
+
x2 = x1 + dx2
|
|
635
|
+
y2 = y1 + dy2
|
|
636
|
+
@x = x2 + dx3
|
|
637
|
+
@y = y2 + dy3
|
|
638
|
+
|
|
639
|
+
@path << {
|
|
640
|
+
type: :curve_to,
|
|
641
|
+
x1: x1, y1: y1,
|
|
642
|
+
x2: x2, y2: y2,
|
|
643
|
+
x: @x, y: @y
|
|
644
|
+
}
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Process final line
|
|
648
|
+
if @stack.size == 2
|
|
649
|
+
dx = @stack.shift
|
|
650
|
+
dy = @stack.shift
|
|
651
|
+
@x += dx
|
|
652
|
+
@y += dy
|
|
653
|
+
@path << { type: :line_to, x: @x, y: @y }
|
|
654
|
+
end
|
|
655
|
+
@stack.clear
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# rlinecurve: {dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve
|
|
659
|
+
# Lines followed by a curve
|
|
660
|
+
def rlinecurve
|
|
661
|
+
# Process lines (all but last 6 values)
|
|
662
|
+
while @stack.size > 6
|
|
663
|
+
dx = @stack.shift
|
|
664
|
+
dy = @stack.shift
|
|
665
|
+
@x += dx
|
|
666
|
+
@y += dy
|
|
667
|
+
@path << { type: :line_to, x: @x, y: @y }
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
# Process final curve
|
|
671
|
+
if @stack.size == 6
|
|
672
|
+
dx1 = @stack.shift
|
|
673
|
+
dy1 = @stack.shift
|
|
674
|
+
dx2 = @stack.shift
|
|
675
|
+
dy2 = @stack.shift
|
|
676
|
+
dx3 = @stack.shift
|
|
677
|
+
dy3 = @stack.shift
|
|
678
|
+
|
|
679
|
+
x1 = @x + dx1
|
|
680
|
+
y1 = @y + dy1
|
|
681
|
+
x2 = x1 + dx2
|
|
682
|
+
y2 = y1 + dy2
|
|
683
|
+
@x = x2 + dx3
|
|
684
|
+
@y = y2 + dy3
|
|
685
|
+
|
|
686
|
+
@path << {
|
|
687
|
+
type: :curve_to,
|
|
688
|
+
x1: x1, y1: y1,
|
|
689
|
+
x2: x2, y2: y2,
|
|
690
|
+
x: @x, y: @y
|
|
691
|
+
}
|
|
692
|
+
end
|
|
693
|
+
@stack.clear
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# endchar: endchar
|
|
697
|
+
# End of glyph definition
|
|
698
|
+
def endchar
|
|
699
|
+
# Implicitly closes the path
|
|
700
|
+
@stack.clear
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Hint operators (stubbed - hints not needed for rendering)
|
|
704
|
+
def hint_operator(width_parsed)
|
|
705
|
+
parse_width_if_needed(width_parsed) unless width_parsed
|
|
706
|
+
# Count stems for width calculation
|
|
707
|
+
@stems += @stack.size / 2
|
|
708
|
+
@stack.clear
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# Hintmask/cntrmask operators
|
|
712
|
+
def hintmask_operator
|
|
713
|
+
# Calculate number of bytes needed for hint mask
|
|
714
|
+
hint_bytes = (@stems + 7) / 8
|
|
715
|
+
# Skip hint mask bytes (not needed for rendering)
|
|
716
|
+
@io&.read(hint_bytes)
|
|
717
|
+
@stack.clear
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Flex operators (convert to curves)
|
|
721
|
+
def flex_operator(operator)
|
|
722
|
+
case operator
|
|
723
|
+
when :hflex
|
|
724
|
+
# dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex
|
|
725
|
+
dx1, dx2, dy2, dx3, dx4, dx5, dx6 = @stack.shift(7)
|
|
726
|
+
# Convert to two curves
|
|
727
|
+
add_curve(dx1, 0, dx2, dy2, dx3, 0)
|
|
728
|
+
add_curve(dx4, 0, dx5, -dy2, dx6, 0)
|
|
729
|
+
when :flex
|
|
730
|
+
# dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex
|
|
731
|
+
dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6,
|
|
732
|
+
_fd = @stack.shift(13)
|
|
733
|
+
# Convert to two curves
|
|
734
|
+
add_curve(dx1, dy1, dx2, dy2, dx3, dy3)
|
|
735
|
+
add_curve(dx4, dy4, dx5, dy5, dx6, dy6)
|
|
736
|
+
when :hflex1
|
|
737
|
+
# dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1
|
|
738
|
+
dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = @stack.shift(9)
|
|
739
|
+
add_curve(dx1, dy1, dx2, dy2, dx3, 0)
|
|
740
|
+
add_curve(dx4, 0, dx5, dy5, dx6, -(dy1 + dy2 + dy5))
|
|
741
|
+
when :flex1
|
|
742
|
+
# dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1
|
|
743
|
+
dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 =
|
|
744
|
+
@stack.shift(11)
|
|
745
|
+
dx = dx1 + dx2 + dx3 + dx4 + dx5
|
|
746
|
+
dy = dy1 + dy2 + dy3 + dy4 + dy5
|
|
747
|
+
add_curve(dx1, dy1, dx2, dy2, dx3, dy3)
|
|
748
|
+
if dx.abs > dy.abs
|
|
749
|
+
add_curve(dx4, dy4, dx5, dy5, d6, -dy)
|
|
750
|
+
else
|
|
751
|
+
add_curve(dx4, dy4, dx5, dy5, -dx, d6)
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
@stack.clear
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Helper to add a curve to the path
|
|
758
|
+
def add_curve(dx1, dy1, dx2, dy2, dx3, dy3)
|
|
759
|
+
x1 = @x + dx1
|
|
760
|
+
y1 = @y + dy1
|
|
761
|
+
x2 = x1 + dx2
|
|
762
|
+
y2 = y1 + dy2
|
|
763
|
+
@x = x2 + dx3
|
|
764
|
+
@y = y2 + dy3
|
|
765
|
+
|
|
766
|
+
@path << {
|
|
767
|
+
type: :curve_to,
|
|
768
|
+
x1: x1, y1: y1,
|
|
769
|
+
x2: x2, y2: y2,
|
|
770
|
+
x: @x, y: @y
|
|
771
|
+
}
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# Call local subroutine
|
|
775
|
+
def callsubr
|
|
776
|
+
subr_index = @stack.pop + @subroutine_bias
|
|
777
|
+
if @local_subrs && subr_index >= 0 && subr_index < @local_subrs.count
|
|
778
|
+
subr_data = @local_subrs[subr_index]
|
|
779
|
+
execute_subroutine(subr_data)
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# Call global subroutine
|
|
784
|
+
def callgsubr
|
|
785
|
+
subr_index = @stack.pop + @global_subroutine_bias
|
|
786
|
+
if subr_index >= 0 && subr_index < @global_subrs.count
|
|
787
|
+
subr_data = @global_subrs[subr_index]
|
|
788
|
+
execute_subroutine(subr_data)
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Execute a subroutine
|
|
793
|
+
def execute_subroutine(data)
|
|
794
|
+
saved_io = @io
|
|
795
|
+
saved_data = @data
|
|
796
|
+
@data = data
|
|
797
|
+
@io = StringIO.new(data)
|
|
798
|
+
|
|
799
|
+
# Process subroutine until return or end
|
|
800
|
+
until @io.eof?
|
|
801
|
+
byte = @io.getbyte
|
|
802
|
+
|
|
803
|
+
if byte <= 31 && byte != 28
|
|
804
|
+
operator = read_operator(@io, byte)
|
|
805
|
+
break if operator == :return
|
|
806
|
+
|
|
807
|
+
execute_operator(operator, true) # Width already parsed
|
|
808
|
+
else
|
|
809
|
+
@io.pos -= 1
|
|
810
|
+
number = read_number(@io)
|
|
811
|
+
@stack << number
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
@io = saved_io
|
|
816
|
+
@data = saved_data
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# Arithmetic operators
|
|
820
|
+
def arithmetic_add
|
|
821
|
+
b = @stack.pop
|
|
822
|
+
a = @stack.pop
|
|
823
|
+
@stack << (a + b)
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def arithmetic_sub
|
|
827
|
+
b = @stack.pop
|
|
828
|
+
a = @stack.pop
|
|
829
|
+
@stack << (a - b)
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
def arithmetic_mul
|
|
833
|
+
b = @stack.pop
|
|
834
|
+
a = @stack.pop
|
|
835
|
+
@stack << (a * b)
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
def arithmetic_div
|
|
839
|
+
b = @stack.pop
|
|
840
|
+
a = @stack.pop
|
|
841
|
+
@stack << (a / b.to_f)
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def arithmetic_neg
|
|
845
|
+
@stack << -@stack.pop
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
def arithmetic_abs
|
|
849
|
+
@stack << @stack.pop.abs
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def arithmetic_sqrt
|
|
853
|
+
@stack << Math.sqrt(@stack.pop)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
# Parse width for a specific operator
|
|
857
|
+
#
|
|
858
|
+
# @param width_parsed [Boolean] Whether width has already been parsed
|
|
859
|
+
# @param expected_operands [Integer] Number of operands this operator
|
|
860
|
+
# expects
|
|
861
|
+
def parse_width_for_operator(width_parsed, expected_operands)
|
|
862
|
+
return if width_parsed || @width
|
|
863
|
+
|
|
864
|
+
# Width is present if there's one more operand than expected
|
|
865
|
+
if @stack.size == expected_operands + 1
|
|
866
|
+
width_value = @stack.shift
|
|
867
|
+
@width = @private_dict.nominal_width_x + width_value
|
|
868
|
+
else
|
|
869
|
+
@width = @private_dict.default_width_x
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# Parse width if present (for hint operators)
|
|
874
|
+
def parse_width_if_needed(width_parsed)
|
|
875
|
+
return if width_parsed || @width
|
|
876
|
+
|
|
877
|
+
# For hint operators, width is present if odd number of operands
|
|
878
|
+
if @stack.size.odd?
|
|
879
|
+
width_value = @stack.shift
|
|
880
|
+
@width = @private_dict.nominal_width_x + width_value
|
|
881
|
+
else
|
|
882
|
+
@width = @private_dict.default_width_x
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
# Calculate subroutine bias based on INDEX count
|
|
887
|
+
#
|
|
888
|
+
# @param index [Index, nil] Subroutine INDEX
|
|
889
|
+
# @return [Integer] Bias value
|
|
890
|
+
def calculate_bias(index)
|
|
891
|
+
return 0 unless index
|
|
892
|
+
|
|
893
|
+
count = index.count
|
|
894
|
+
if count < 1240
|
|
895
|
+
107
|
|
896
|
+
elsif count < 33900
|
|
897
|
+
1131
|
|
898
|
+
else
|
|
899
|
+
32768
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
end
|