fontisan 0.2.12 → 0.2.13

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.
@@ -0,0 +1,501 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Type1
5
+ # Expands Type 1 seac composite glyphs into base + accent outlines
6
+ #
7
+ # [`SeacExpander`](lib/fontisan/type1/seac_expander.rb) handles the
8
+ # decomposition of Type 1 composite glyphs that use the `seac` operator.
9
+ # The seac operator combines two glyphs (a base character and an accent)
10
+ # with a positioning offset, which must be decomposed for CFF conversion.
11
+ #
12
+ # The seac operator format is:
13
+ # ```
14
+ # seac asb adx ady bchar achar
15
+ # ```
16
+ #
17
+ # Where:
18
+ # - `asb`: Accent side bearing (not used in decomposition)
19
+ # - `adx`: X offset for accent placement
20
+ # - `ady`: Y offset for accent placement
21
+ # - `bchar`: Character code of base glyph
22
+ # - `achar`: Character code of accent glyph
23
+ #
24
+ # @example Decompose a seac composite
25
+ # expander = Fontisan::Type1::SeacExpander.new(charstrings, private_dict)
26
+ # decomposed = expander.decompose("Agrave")
27
+ # # => Returns merged outline of base 'A' + accent 'grave'
28
+ #
29
+ # @see https://www.adobe.com/devnet/font/pdfs/Type1.pdf
30
+ class SeacExpander
31
+ # @return [CharStrings] Type 1 CharStrings dictionary
32
+ attr_reader :charstrings
33
+
34
+ # @return [PrivateDict] Private dictionary for hinting
35
+ attr_reader :private_dict
36
+
37
+ # Initialize a new SeacExpander
38
+ #
39
+ # @param charstrings [CharStrings] Type 1 CharStrings dictionary
40
+ # @param private_dict [PrivateDict] Private dictionary for context
41
+ def initialize(charstrings, private_dict)
42
+ @charstrings = charstrings
43
+ @private_dict = private_dict
44
+ end
45
+
46
+ # Decompose a seac composite glyph into base + accent outlines
47
+ #
48
+ # This method:
49
+ # 1. Parses the seac operator to extract components
50
+ # 2. Gets CharStrings for base and accent glyphs
51
+ # 3. Parses both CharStrings into outline commands
52
+ # 4. Transforms the accent by (adx, ady) offset
53
+ # 5. Merges base and accent outlines into a single path
54
+ # 6. Returns the decomposed CharString data
55
+ #
56
+ # @param glyph_name [String] Name of the composite glyph to decompose
57
+ # @return [String, nil] Decomposed CharString bytecode, or nil if not a seac composite
58
+ # @raise [Fontisan::Error] If base or accent glyphs are not found
59
+ #
60
+ # @example Decompose "Agrave" glyph
61
+ # expander.decompose("Agrave")
62
+ def decompose(glyph_name)
63
+ components = @charstrings.components_for(glyph_name)
64
+ return nil unless components
65
+
66
+ # Use the encoding map to lookup glyph names from character codes
67
+ base_glyph_name = @charstrings.encoding[components[:base]]
68
+ accent_glyph_name = @charstrings.encoding[components[:accent]]
69
+
70
+ if base_glyph_name.nil?
71
+ raise Fontisan::Error, "Base glyph for char code #{components[:base]} not found"
72
+ end
73
+
74
+ if accent_glyph_name.nil?
75
+ raise Fontisan::Error, "Accent glyph for char code #{components[:accent]} not found"
76
+ end
77
+
78
+ # Get CharStrings for base and accent
79
+ base_charstring = @charstrings[base_glyph_name]
80
+ accent_charstring = @charstrings[accent_glyph_name]
81
+
82
+ if base_charstring.nil?
83
+ raise Fontisan::Error, "CharString not found for base glyph #{base_glyph_name}"
84
+ end
85
+
86
+ if accent_charstring.nil?
87
+ raise Fontisan::Error, "CharString not found for accent glyph #{accent_glyph_name}"
88
+ end
89
+
90
+ # Parse both CharStrings into command sequences
91
+ base_commands = parse_charstring_to_commands(base_charstring)
92
+ accent_commands = parse_charstring_to_commands(accent_charstring)
93
+
94
+ # Transform accent by (adx, ady) offset
95
+ accent_commands = transform_commands(accent_commands, components[:adx], components[:ady])
96
+
97
+ # Merge base and accent commands
98
+ merged_commands = merge_outline_commands(base_commands, accent_commands)
99
+
100
+ # Convert merged commands back to CharString bytecode
101
+ generate_charstring(merged_commands)
102
+ end
103
+
104
+ # Check if a glyph is a seac composite
105
+ #
106
+ # @param glyph_name [String] Glyph name to check
107
+ # @return [Boolean] True if the glyph uses seac operator
108
+ def composite?(glyph_name)
109
+ @charstrings.composite?(glyph_name)
110
+ end
111
+
112
+ # Get all seac composite glyphs in the font
113
+ #
114
+ # @return [Array<String>] List of composite glyph names
115
+ def composite_glyphs
116
+ @charstrings.glyph_names.select { |name| composite?(name) }
117
+ end
118
+
119
+ private
120
+
121
+ # Parse Type 1 CharString into drawing commands
122
+ #
123
+ # Converts Type 1 CharString bytecode into a list of drawing commands
124
+ # that can be manipulated and transformed.
125
+ #
126
+ # @param charstring [String] Binary CharString data
127
+ # @return [Array<Hash>] Array of command hashes
128
+ def parse_charstring_to_commands(charstring)
129
+ parser = CharStrings::CharStringParser.new(@private_dict)
130
+ parser.parse(charstring)
131
+ # Preprocess to combine numbers with operators
132
+ combined_commands = combine_numbers_with_operators(parser.commands)
133
+ commands_to_outline(combined_commands)
134
+ end
135
+
136
+ # Combine separate number commands with their operators
137
+ #
138
+ # The CharStringParser produces commands like [[:number, 100], [:number, 0], [:hsbw]]
139
+ # This method combines them into [[:hsbw, 100, 0]]
140
+ #
141
+ # @param commands [Array] Parser commands
142
+ # @return [Array] Combined commands
143
+ def combine_numbers_with_operators(commands)
144
+ result = []
145
+ number_stack = []
146
+
147
+ commands.each do |cmd|
148
+ if cmd[0] == :number
149
+ number_stack << cmd[1]
150
+ elsif cmd[0] == :seac
151
+ # seac is special - it uses the number stack for components
152
+ result << cmd
153
+ parse_seac_from_stack(result, number_stack)
154
+ else
155
+ # Operator - pop required numbers from stack
156
+ operator = cmd[0]
157
+ args = get_args_for_operator(operator, number_stack)
158
+ result << [operator, *args] if args
159
+ end
160
+ end
161
+
162
+ result
163
+ end
164
+
165
+ # Get arguments for an operator from the number stack
166
+ #
167
+ # @param operator [Symbol] Operator symbol
168
+ # @param stack [Array] Number stack
169
+ # @return [Array, nil] Arguments or nil if operator unknown
170
+ def get_args_for_operator(operator, stack)
171
+ case operator
172
+ when :hsbw
173
+ # hsbw takes 2 args: sbw, width
174
+ stack.pop(2).reverse
175
+ when :sbw
176
+ # sbw takes 3 args: sbw, wy, wx
177
+ stack.pop(3).reverse
178
+ when :rmoveto
179
+ # rmoveto takes 2 args: dy, dx
180
+ stack.pop(2).reverse
181
+ when :hmoveto
182
+ # hmoveto takes 1 arg: dx
183
+ stack.pop(1)
184
+ when :vmoveto
185
+ # vmoveto takes 1 arg: dy
186
+ stack.pop(1)
187
+ when :rlineto
188
+ # rlineto takes 2 args: dy, dx
189
+ stack.pop(2).reverse
190
+ when :hlineto
191
+ # hlineto takes 1 arg: dx
192
+ stack.pop(1)
193
+ when :vlineto
194
+ # vlineto takes 1 arg: dy
195
+ stack.pop(1)
196
+ when :rrcurveto
197
+ # rrcurveto takes 6 args: dy3, dx3, dy2, dx2, dy1, dx1
198
+ stack.pop(6).reverse
199
+ when :vhcurveto, :hvcurveto
200
+ # vhcurveto/hvcurveto take 4 args
201
+ stack.pop(4).reverse
202
+ when :hstem, :vstem, :callsubr, :return, :endchar
203
+ # These operators don't need args for outline conversion
204
+ []
205
+ else
206
+ # Unknown operator - return nil
207
+ nil
208
+ end
209
+ end
210
+
211
+ # Parse seac components from number stack
212
+ #
213
+ # @param result [Array] Result array to append to
214
+ # @param stack [Array] Number stack
215
+ def parse_seac_from_stack(result, stack)
216
+ return if stack.length < 5
217
+
218
+ # Last 5 numbers are: asb, adx, ady, bchar, achar
219
+ args = stack.pop(5).reverse
220
+ result.last[1] = args[1] # adx
221
+ result.last[2] = args[2] # ady
222
+ result.last[3] = args[3] # bchar
223
+ result.last[4] = args[4] # achar
224
+ end
225
+
226
+ # Convert parser commands to outline format
227
+ #
228
+ # @param commands [Array] Parser command format [type, *args]
229
+ # @return [Array<Hash>] Outline command format
230
+ def commands_to_outline(commands)
231
+ outline_commands = []
232
+ x = 0
233
+ y = 0
234
+
235
+ commands.each do |cmd|
236
+ case cmd[0]
237
+ when :hsbw
238
+ # hsbw x0 sbw: Set horizontal width and left sidebearing
239
+ # This is usually the first command in a CharString
240
+ x = cmd[1]
241
+ y = 0
242
+ outline_commands << { type: :move_to, x: x, y: y }
243
+ when :sbw
244
+ # sbw sbw x0 sbw: Set width and side bearings (vertical)
245
+ y = cmd[1]
246
+ x = cmd[2]
247
+ outline_commands << { type: :move_to, x: x, y: y }
248
+ when :rmoveto
249
+ # rmoveto dx dy: Relative move to
250
+ x += cmd[1]
251
+ y += cmd[2]
252
+ outline_commands << { type: :line_to, x: x, y: y }
253
+ when :hmoveto
254
+ # hmoveto dx: Horizontal move to
255
+ x += cmd[1]
256
+ outline_commands << { type: :line_to, x: x, y: y }
257
+ when :vmoveto
258
+ # vmoveto dy: Vertical move to
259
+ y += cmd[1]
260
+ outline_commands << { type: :line_to, x: x, y: y }
261
+ when :rlineto
262
+ # rlineto dx dy: Relative line to
263
+ x += cmd[1]
264
+ y += cmd[2]
265
+ outline_commands << { type: :line_to, x: x, y: y }
266
+ when :hlineto
267
+ # hlineto dx: Horizontal line to
268
+ x += cmd[1]
269
+ outline_commands << { type: :line_to, x: x, y: y }
270
+ when :vlineto
271
+ # vlineto dy: Vertical line to
272
+ y += cmd[1]
273
+ outline_commands << { type: :line_to, x: x, y: y }
274
+ when :rrcurveto
275
+ # rrcurveto dx1 dy1 dx2 dy2 dx3 dy3: Relative curved line to
276
+ # This is a quadratic curve in Type 1
277
+ # Convert to our outline format (quadratic)
278
+ dx1, dy1, dx2, dy2, dx3, dy3 = cmd[1..6]
279
+ control_x = x + dx1
280
+ control_y = y + dy1
281
+ anchor_x = x + dx1 + dx2
282
+ anchor_y = y + dy1 + dy2
283
+ end_x = x + dx1 + dx2 + dx3
284
+ end_y = y + dy1 + dy2 + dy3
285
+
286
+ outline_commands << {
287
+ type: :quad_to,
288
+ cx: control_x,
289
+ cy: control_y,
290
+ x: end_x,
291
+ y: end_y
292
+ }
293
+ x = end_x
294
+ y = end_y
295
+ when :vhcurveto, :hvcurveto
296
+ # vhcurveto: Vertical-to-horizontal curve
297
+ # hvcurveto: Horizontal-to-vertical curve
298
+ # These are also quadratic curves
299
+ # For now, treat as simple curves
300
+ handle_curve_command(outline_commands, cmd, x, y)
301
+ x = outline_commands.last[:x]
302
+ y = outline_commands.last[:y]
303
+ when :endchar
304
+ # End of CharString
305
+ break
306
+ when :number
307
+ # Number - skip, consumed by operators
308
+ nil
309
+ else
310
+ # Unknown command - skip
311
+ nil
312
+ end
313
+ end
314
+
315
+ outline_commands
316
+ end
317
+
318
+ # Handle curve commands (vhcurveto, hvcurveto)
319
+ #
320
+ # @param commands [Array] Command list to append to
321
+ # @param cmd [Array] The curve command
322
+ # @param x [Integer] Current X position
323
+ # @param y [Integer] Current Y position
324
+ def handle_curve_command(commands, cmd, x, y)
325
+ # Simplified handling - treat as quadratic curve
326
+ # vhcurveto: dy1 dx2 dy2 dx3
327
+ # hvcurveto: dx1 dy2 dx3 dy3
328
+ if cmd[0] == :vhcurveto
329
+ dy1, dx2, dy2, dx3 = cmd[1..4]
330
+ control_x = x
331
+ control_y = y + dy1
332
+ end_x = x + dx2
333
+ end_y = y + dy1 + dy2
334
+ else
335
+ dx1, dy2, dx3, dy3 = cmd[1..4]
336
+ control_x = x + dx1
337
+ control_y = y
338
+ end_x = x + dx1 + dx2
339
+ end_y = y + dy2 + dy3
340
+ end
341
+
342
+ commands << {
343
+ type: :quad_to,
344
+ cx: control_x,
345
+ cy: control_y,
346
+ x: end_x,
347
+ y: end_y
348
+ }
349
+ end
350
+
351
+ # Transform outline commands by translation
352
+ #
353
+ # @param commands [Array<Hash>] Outline commands
354
+ # @param dx [Integer] X offset
355
+ # @param dy [Integer] Y offset
356
+ # @return [Array<Hash>] Transformed commands
357
+ def transform_commands(commands, dx, dy)
358
+ return commands if dx == 0 && dy == 0
359
+
360
+ commands.map do |cmd|
361
+ case cmd[:type]
362
+ when :move_to, :line_to
363
+ { type: cmd[:type], x: cmd[:x] + dx, y: cmd[:y] + dy }
364
+ when :quad_to
365
+ {
366
+ type: :quad_to,
367
+ cx: cmd[:cx] + dx,
368
+ cy: cmd[:cy] + dy,
369
+ x: cmd[:x] + dx,
370
+ y: cmd[:y] + dy
371
+ }
372
+ when :curve_to
373
+ {
374
+ type: :curve_to,
375
+ cx1: cmd[:cx1] + dx,
376
+ cy1: cmd[:cy1] + dy,
377
+ cx2: cmd[:cx2] + dx,
378
+ cy2: cmd[:cy2] + dy,
379
+ x: cmd[:x] + dx,
380
+ y: cmd[:y] + dy
381
+ }
382
+ else
383
+ cmd # close_path, etc. pass through
384
+ end
385
+ end
386
+ end
387
+
388
+ # Merge base and accent outline commands
389
+ #
390
+ # Combines two outline sequences into one. The accent outline is
391
+ # appended to the base outline with proper contour separation.
392
+ #
393
+ # @param base_commands [Array<Hash>] Base glyph commands
394
+ # @param accent_commands [Array<Hash>] Accent glyph commands
395
+ # @return [Array<Hash>] Merged commands
396
+ def merge_outline_commands(base_commands, accent_commands)
397
+ # Remove the final close_path from base (if any)
398
+ # Then append accent commands
399
+ merged = base_commands.dup
400
+
401
+ # If base ends with close_path, remove it (accent will have its own)
402
+ merged.pop if merged.last && merged.last[:type] == :close_path
403
+
404
+ # Add accent commands
405
+ merged.concat(accent_commands)
406
+
407
+ merged
408
+ end
409
+
410
+ # Generate CharString bytecode from outline commands
411
+ #
412
+ # Converts outline commands back to Type 1 CharString format.
413
+ # This is a simplified implementation that handles common cases.
414
+ #
415
+ # @param commands [Array<Hash>] Outline commands
416
+ # @return [String] Binary CharString data
417
+ def generate_charstring(commands)
418
+ return "" if commands.empty?
419
+
420
+ charstring = String.new(encoding: Encoding::ASCII_8BIT)
421
+
422
+ x = 0
423
+ y = 0
424
+
425
+ commands.each do |cmd|
426
+ case cmd[:type]
427
+ when :move_to
428
+ # Use hsbw to set initial position
429
+ dx = cmd[:x] - x
430
+ # hsbw is a two-byte operator: 12 34
431
+ charstring << encode_number(dx) # sbw value
432
+ charstring << encode_number(0) # width (always 0 for decomposed glyphs)
433
+ charstring << 12 # First byte of two-byte operator
434
+ charstring << 34 # Second byte - hsbw
435
+ x = cmd[:x]
436
+ y = cmd[:y]
437
+ when :line_to
438
+ dx = cmd[:x] - x
439
+ dy = cmd[:y] - y
440
+ if dx != 0 && dy != 0
441
+ charstring << encode_number(dx)
442
+ charstring << encode_number(dy)
443
+ charstring << 5 # rlineto
444
+ elsif dx != 0
445
+ charstring << encode_number(dx)
446
+ charstring << 6 # hlineto
447
+ elsif dy != 0
448
+ charstring << encode_number(dy)
449
+ charstring << 7 # vlineto
450
+ end
451
+ x = cmd[:x]
452
+ y = cmd[:y]
453
+ when :quad_to
454
+ # rrcurveto: dx1 dy1 dx2 dy2 dx3 dy3
455
+ dx1 = cmd[:cx] - x
456
+ dy1 = cmd[:cy] - y
457
+ # For quadratic curves, we need to convert to cubic (Type 1 rrcurveto)
458
+ # This is a simplified conversion
459
+ anchor_dx = (cmd[:x] - x) / 2 - dx1 / 2
460
+ anchor_dy = (cmd[:y] - y) / 2 - dy1 / 2
461
+ end_dx = (cmd[:x] - x) - dx1 - anchor_dx
462
+ end_dy = (cmd[:y] - y) - dy1 - anchor_dy
463
+
464
+ charstring << encode_number(dx1)
465
+ charstring << encode_number(dy1)
466
+ charstring << encode_number(anchor_dx)
467
+ charstring << encode_number(anchor_dy)
468
+ charstring << encode_number(end_dx)
469
+ charstring << encode_number(end_dy)
470
+ charstring << 8 # rrcurveto
471
+ x = cmd[:x]
472
+ y = cmd[:y]
473
+ end
474
+ end
475
+
476
+ # Add endchar
477
+ charstring << 14
478
+
479
+ charstring
480
+ end
481
+
482
+ # Encode integer for Type 1 CharString
483
+ #
484
+ # Type 1 CharStrings use a variable-length integer encoding:
485
+ # - Numbers from -107 to 107: single byte (byte + 139)
486
+ # - Larger numbers: escaped with 255, then 2-byte value
487
+ #
488
+ # @param num [Integer] Number to encode
489
+ # @return [String] Encoded bytes
490
+ def encode_number(num)
491
+ if num >= -107 && num <= 107
492
+ [num + 139].pack("C*")
493
+ else
494
+ # Use escape sequence (255) followed by 2-byte signed integer
495
+ num += 32768 if num < 0
496
+ [255, num % 256, num >> 8].pack("C*")
497
+ end
498
+ end
499
+ end
500
+ end
501
+ end
@@ -46,6 +46,8 @@ require_relative "type1/font_dictionary"
46
46
  require_relative "type1/private_dict"
47
47
  require_relative "type1/charstrings"
48
48
  require_relative "type1/charstring_converter"
49
+ require_relative "type1/cff_to_type1_converter"
50
+ require_relative "type1/seac_expander"
49
51
 
50
52
  # Infrastructure
51
53
  require_relative "type1/upm_scaler"
@@ -37,27 +37,41 @@ module Fontisan
37
37
  # @return [Symbol] Format type (:pfb or :pfa)
38
38
  attr_reader :format
39
39
 
40
+ # @return [Symbol] Loading mode (:metadata or :full)
41
+ attr_reader :loading_mode
42
+
40
43
  # @return [String, nil] Decrypted font data
41
44
  attr_reader :decrypted_data
42
45
 
43
46
  # @return [FontDictionary, nil] Font dictionary
44
- attr_reader :font_dictionary
47
+ def font_dictionary
48
+ parse_dictionaries! unless @font_dictionary
49
+ @font_dictionary
50
+ end
45
51
 
46
52
  # @return [PrivateDict, nil] Private dictionary
47
- attr_reader :private_dict
53
+ def private_dict
54
+ parse_dictionaries! unless @private_dict
55
+ @private_dict
56
+ end
48
57
 
49
58
  # @return [CharStrings, nil] CharStrings dictionary
50
- attr_reader :charstrings
59
+ def charstrings
60
+ parse_dictionaries! unless @charstrings
61
+ @charstrings
62
+ end
51
63
 
52
64
  # Initialize a new Type1Font instance
53
65
  #
54
66
  # @param data [String] Font file data (binary or text)
55
67
  # @param format [Symbol] Format type (:pfb or :pfa, auto-detected if nil)
56
68
  # @param file_path [String, nil] Optional file path for reference
57
- def initialize(data, format: nil, file_path: nil)
69
+ # @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
70
+ def initialize(data, format: nil, file_path: nil, mode: :full)
58
71
  @file_path = file_path
59
72
  @format = format || detect_format(data)
60
73
  @data = data
74
+ @loading_mode = mode
61
75
 
62
76
  parse_font_data
63
77
  end
@@ -65,6 +79,7 @@ module Fontisan
65
79
  # Load Type 1 font from file
66
80
  #
67
81
  # @param file_path [String] Path to PFB or PFA file
82
+ # @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
68
83
  # @return [Type1Font] Loaded font instance
69
84
  # @raise [ArgumentError] If file_path is nil
70
85
  # @raise [Fontisan::Error] If file cannot be read or parsed
@@ -74,7 +89,7 @@ module Fontisan
74
89
  #
75
90
  # @example Load PFA file
76
91
  # font = Fontisan::Type1Font.from_file('font.pfa')
77
- def self.from_file(file_path)
92
+ def self.from_file(file_path, mode: :full)
78
93
  raise ArgumentError, "File path cannot be nil" if file_path.nil?
79
94
 
80
95
  unless File.exist?(file_path)
@@ -84,7 +99,7 @@ module Fontisan
84
99
  # Read file
85
100
  data = File.binread(file_path)
86
101
 
87
- new(data, file_path: file_path)
102
+ new(data, file_path: file_path, mode: mode)
88
103
  end
89
104
 
90
105
  # Get clear text portion (before eexec)
@@ -101,34 +116,6 @@ module Fontisan
101
116
  @encrypted_portion ||= ""
102
117
  end
103
118
 
104
- # Get font name from font dictionary
105
- #
106
- # @return [String, nil] Font name or nil if not found
107
- def font_name
108
- extract_dictionary_value("/FontName")
109
- end
110
-
111
- # Get full name from FontInfo
112
- #
113
- # @return [String, nil] Full name or nil if not found
114
- def full_name
115
- extract_fontinfo_value("FullName")
116
- end
117
-
118
- # Get family name from FontInfo
119
- #
120
- # @return [String, nil] Family name or nil if not found
121
- def family_name
122
- extract_fontinfo_value("FamilyName")
123
- end
124
-
125
- # Get version from FontInfo
126
- #
127
- # @return [String, nil] Version or nil if not found
128
- def version
129
- extract_fontinfo_value("version")
130
- end
131
-
132
119
  # Check if font has been decrypted
133
120
  #
134
121
  # @return [Boolean] True if font data has been decrypted
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.12"
4
+ VERSION = "0.2.13"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-20 00:00:00.000000000 Z
11
+ date: 2026-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -120,9 +120,10 @@ files:
120
120
  - docs/APPLE_LEGACY_FONTS.adoc
121
121
  - docs/COLLECTION_VALIDATION.adoc
122
122
  - docs/COLOR_FONTS.adoc
123
- - docs/DOCUMENTATION_SUMMARY.md
123
+ - docs/CONVERSION_GUIDE.adoc
124
124
  - docs/EXTRACT_TTC_MIGRATION.md
125
125
  - docs/FONT_HINTING.adoc
126
+ - docs/TYPE1_FONTS.adoc
126
127
  - docs/VALIDATION.adoc
127
128
  - docs/VARIABLE_FONT_OPERATIONS.adoc
128
129
  - docs/WOFF_WOFF2_FORMATS.adoc
@@ -354,6 +355,7 @@ files:
354
355
  - lib/fontisan/type1/afm_generator.rb
355
356
  - lib/fontisan/type1/afm_parser.rb
356
357
  - lib/fontisan/type1/agl.rb
358
+ - lib/fontisan/type1/cff_to_type1_converter.rb
357
359
  - lib/fontisan/type1/charstring_converter.rb
358
360
  - lib/fontisan/type1/charstrings.rb
359
361
  - lib/fontisan/type1/conversion_options.rb
@@ -369,6 +371,7 @@ files:
369
371
  - lib/fontisan/type1/pfm_generator.rb
370
372
  - lib/fontisan/type1/pfm_parser.rb
371
373
  - lib/fontisan/type1/private_dict.rb
374
+ - lib/fontisan/type1/seac_expander.rb
372
375
  - lib/fontisan/type1/ttf_to_type1_converter.rb
373
376
  - lib/fontisan/type1/upm_scaler.rb
374
377
  - lib/fontisan/type1_font.rb