fontisan 0.4.23 → 0.4.24

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +240 -29
  3. data/Gemfile +6 -0
  4. data/Rakefile +9 -6
  5. data/benchmark/compile_benchmark.rb +13 -5
  6. data/docs/.vitepress/config.ts +1 -0
  7. data/docs/STITCHER_GUIDE.adoc +47 -3
  8. data/docs/api/layer.md +89 -0
  9. data/docs/cli/subset.md +25 -6
  10. data/docs/guide/cli/subset.md +31 -0
  11. data/docs/guide/color-fonts/bitmaps.md +17 -0
  12. data/lib/fontisan/cli.rb +2 -1
  13. data/lib/fontisan/collection/writer.rb +2 -1
  14. data/lib/fontisan/commands/validate_collection_command.rb +7 -1
  15. data/lib/fontisan/commands.rb +2 -1
  16. data/lib/fontisan/converters/collection_converter.rb +6 -1
  17. data/lib/fontisan/converters/svg_generator.rb +3 -1
  18. data/lib/fontisan/stitcher/collection_result.rb +2 -1
  19. data/lib/fontisan/stitcher/glyph_copier.rb +2 -1
  20. data/lib/fontisan/stitcher/partition_strategy/by_block.rb +3 -1
  21. data/lib/fontisan/stitcher/partition_strategy/by_script.rb +6 -2
  22. data/lib/fontisan/stitcher/source.rb +2 -1
  23. data/lib/fontisan/stitcher.rb +2 -1
  24. data/lib/fontisan/subset/table_strategy/glyf_loca_builder.rb +2 -1
  25. data/lib/fontisan/svg/standalone_glyph.rb +4 -1
  26. data/lib/fontisan/svg_to_glyf/assembler.rb +6 -3
  27. data/lib/fontisan/svg_to_glyf/geometry/transform_parser.rb +11 -4
  28. data/lib/fontisan/svg_to_glyf/geometry.rb +4 -2
  29. data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +11 -5
  30. data/lib/fontisan/tables/cff.rb +2 -1
  31. data/lib/fontisan/tables/cff2/index_builder.rb +2 -1
  32. data/lib/fontisan/tables.rb +2 -1
  33. data/lib/fontisan/ufo/cli.rb +4 -1
  34. data/lib/fontisan/ufo/compile/cbdt_cblc.rb +2 -1
  35. data/lib/fontisan/ufo/compile/cff.rb +6 -2
  36. data/lib/fontisan/ufo/compile/cmap.rb +11 -5
  37. data/lib/fontisan/ufo/compile/cpal.rb +2 -1
  38. data/lib/fontisan/ufo/compile/feature_writers/gdef.rb +3 -1
  39. data/lib/fontisan/ufo/compile/feature_writers/mark_family_base.rb +3 -1
  40. data/lib/fontisan/ufo/compile/feature_writers.rb +2 -1
  41. data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +4 -2
  42. data/lib/fontisan/ufo/compile/filters/remove_overlaps.rb +2 -1
  43. data/lib/fontisan/ufo/compile/fvar.rb +7 -3
  44. data/lib/fontisan/ufo/compile/glyf_loca.rb +4 -2
  45. data/lib/fontisan/ufo/compile/gvar.rb +5 -2
  46. data/lib/fontisan/ufo/compile/head.rb +2 -1
  47. data/lib/fontisan/ufo/compile/item_variation_store.rb +2 -1
  48. data/lib/fontisan/ufo/compile/name.rb +3 -1
  49. data/lib/fontisan/ufo/compile/sbix.rb +2 -1
  50. data/lib/fontisan/ufo/compile/stat.rb +6 -3
  51. data/lib/fontisan/ufo/convert/from_bin_data.rb +6 -2
  52. data/lib/fontisan/ufo/convert/to_dfont.rb +6 -2
  53. data/lib/fontisan/ufo/convert/to_otc.rb +4 -1
  54. data/lib/fontisan/ufo/convert/to_postscript.rb +8 -3
  55. data/lib/fontisan/ufo/convert/to_woff.rb +8 -3
  56. data/lib/fontisan/ufo/convert/to_woff2.rb +8 -3
  57. data/lib/fontisan/ufo/convert.rb +4 -1
  58. data/lib/fontisan/ufo/info.rb +4 -2
  59. data/lib/fontisan/ufo/plist.rb +7 -2
  60. data/lib/fontisan/ufo/reader.rb +3 -1
  61. data/lib/fontisan/ufo/writer.rb +13 -5
  62. data/lib/fontisan/version.rb +1 -1
  63. data/lib/fontisan/woff2/collection_decoder.rb +4 -1
  64. data/lib/fontisan/woff2/collection_encoder.rb +4 -1
  65. data/lib/fontisan/woff2/glyf_canonicalizer.rb +2 -1
  66. data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +5 -2
  67. data/lib/fontisan/woff2/sfnt_checksum.rb +2 -1
  68. data/lib/fontisan.rb +0 -1
  69. metadata +2 -3
  70. data/lib/fontisan/tasks/fixture_downloader.rb +0 -162
  71. data/lib/fontisan/tasks.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7e00355898ff37e38b4948f9a607bf95b96b73df17225c81ebe0b6f6b757b1d
4
- data.tar.gz: 9009d6fb96c8168a098236d7b80355749d6c65463376119695ab2a65c20de01d
3
+ metadata.gz: 556befd056a62c96c03bfaab023a7027b098df4145148415f4735db53da719ee
4
+ data.tar.gz: 41ddfe5df546ee2b1855644d0eddb1caed472f61e88030ad653038e536393ee6
5
5
  SHA512:
6
- metadata.gz: 0c82bc6f0d6a871e84d85e4ac6bd51260a39f2b1d373b3055801354c89d99eda1ce3f78ba62ce640e5193eededeba9c8f5101146378124b5d5ac8e5c52bb60c0
7
- data.tar.gz: 3e957b040dd2660ce7b8ddea3d4592cfef37ad8d434dd5e43d1be4417459992150fcd414f7547ead7f89a9ad8fd4ea8e01a847eb57ef352a70335e691cb386d6
6
+ metadata.gz: 057fc2598f2ad7a02b1847d502f6c0363f6f23382806de361a5dd87bb2b4c99d00993f2e272ab3f2c35697dc0b8843235df05301047b1c3cdbf0e0147498282d
7
+ data.tar.gz: 1669c95792cc2a7731f4495e2d91e2e89b545412e213f8aff78e0dfc625a60c9f9403dc9960e4e487da1563e794fb2d3a6d5126f4ab6ef35e6ebe42e9950fd5d
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-07-08 08:05:45 UTC using RuboCop version 1.86.1.
3
+ # on 2026-07-09 14:55:09 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -12,20 +12,190 @@ Gemspec/RequiredRubyVersion:
12
12
  - 'docs/node_modules/speakingurl/speakingurl-rails.gemspec'
13
13
  - 'fontisan.gemspec'
14
14
 
15
- # Offense count: 2116
15
+ # Offense count: 276
16
+ # This cop supports safe autocorrection (--autocorrect).
17
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
18
+ # SupportedStyles: with_first_argument, with_fixed_indentation
19
+ Layout/ArgumentAlignment:
20
+ Enabled: false
21
+
22
+ # Offense count: 22
23
+ # This cop supports safe autocorrection (--autocorrect).
24
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
25
+ # SupportedStyles: with_first_element, with_fixed_indentation
26
+ Layout/ArrayAlignment:
27
+ Exclude:
28
+ - 'lib/fontisan/collection/writer.rb'
29
+ - 'lib/fontisan/tables/cff2/index_builder.rb'
30
+ - 'lib/fontisan/ufo/compile/cbdt_cblc.rb'
31
+ - 'lib/fontisan/ufo/compile/cff.rb'
32
+ - 'lib/fontisan/ufo/compile/cmap.rb'
33
+ - 'lib/fontisan/ufo/compile/cpal.rb'
34
+ - 'lib/fontisan/ufo/compile/gvar.rb'
35
+ - 'lib/fontisan/ufo/compile/item_variation_store.rb'
36
+ - 'lib/fontisan/ufo/compile/sbix.rb'
37
+ - 'lib/fontisan/ufo/compile/stat.rb'
38
+ - 'lib/fontisan/ufo/info.rb'
39
+ - 'spec/fontisan/stitcher/glyph_signature_spec.rb'
40
+ - 'spec/fontisan/svg_to_glyf/contour_builder_spec.rb'
41
+ - 'spec/fontisan/ufo/compile/feature_writers/mkmk_spec.rb'
42
+ - 'spec/fontisan/ufo/compile/filters/transformations_spec.rb'
43
+
44
+ # Offense count: 1
45
+ # This cop supports safe autocorrection (--autocorrect).
46
+ # Configuration parameters: IndentationWidth.
47
+ Layout/AssignmentIndentation:
48
+ Exclude:
49
+ - 'lib/fontisan/woff2/glyf_loca_reconstruct.rb'
50
+
51
+ # Offense count: 58
52
+ # This cop supports safe autocorrection (--autocorrect).
53
+ # Configuration parameters: EnforcedStyleAlignWith.
54
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
55
+ Layout/BlockAlignment:
56
+ Enabled: false
57
+
58
+ # Offense count: 55
59
+ # This cop supports safe autocorrection (--autocorrect).
60
+ Layout/BlockEndNewline:
61
+ Enabled: false
62
+
63
+ # Offense count: 29
64
+ # This cop supports safe autocorrection (--autocorrect).
65
+ Layout/ClosingParenthesisIndentation:
66
+ Exclude:
67
+ - 'benchmark/compile_benchmark.rb'
68
+ - 'lib/fontisan/ufo/compile/fvar.rb'
69
+ - 'lib/fontisan/ufo/writer.rb'
70
+ - 'spec/fontisan/stitcher/deduplicator_spec.rb'
71
+ - 'spec/fontisan/stitcher/include_codepoints_map_spec.rb'
72
+ - 'spec/fontisan/svg_to_glyf/affine_transform_spec.rb'
73
+ - 'spec/fontisan/ufo/compile/filters/propagate_anchors_spec.rb'
74
+ - 'spec/fontisan/ufo/compile_gvar_spec.rb'
75
+ - 'spec/fontisan/ufo/compile_item_variation_store_spec.rb'
76
+ - 'spec/fontisan/ufo/filters_spec.rb'
77
+
78
+ # Offense count: 2
79
+ # This cop supports safe autocorrection (--autocorrect).
80
+ # Configuration parameters: AllowForAlignment.
81
+ Layout/CommentIndentation:
82
+ Exclude:
83
+ - 'lib/fontisan/ufo/compile/cff.rb'
84
+
85
+ # Offense count: 4
86
+ # This cop supports safe autocorrection (--autocorrect).
87
+ Layout/ElseAlignment:
88
+ Exclude:
89
+ - 'lib/fontisan/commands/validate_collection_command.rb'
90
+ - 'lib/fontisan/converters/collection_converter.rb'
91
+ - 'spec/fontisan/woff2/sfnt_checksum_spec.rb'
92
+
93
+ # Offense count: 4
94
+ # This cop supports safe autocorrection (--autocorrect).
95
+ # Configuration parameters: EnforcedStyleAlignWith.
96
+ # SupportedStylesAlignWith: keyword, variable, start_of_line
97
+ Layout/EndAlignment:
98
+ Exclude:
99
+ - 'lib/fontisan/commands/validate_collection_command.rb'
100
+ - 'lib/fontisan/converters/collection_converter.rb'
101
+ - 'spec/fontisan/woff2/sfnt_checksum_spec.rb'
102
+
103
+ # Offense count: 29
104
+ # This cop supports safe autocorrection (--autocorrect).
105
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
106
+ # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
107
+ Layout/FirstArgumentIndentation:
108
+ Exclude:
109
+ - 'benchmark/compile_benchmark.rb'
110
+ - 'lib/fontisan/ufo/compile/fvar.rb'
111
+ - 'lib/fontisan/ufo/writer.rb'
112
+ - 'spec/fontisan/stitcher/deduplicator_spec.rb'
113
+ - 'spec/fontisan/stitcher/include_codepoints_map_spec.rb'
114
+ - 'spec/fontisan/svg_to_glyf/affine_transform_spec.rb'
115
+ - 'spec/fontisan/ufo/compile/filters/propagate_anchors_spec.rb'
116
+ - 'spec/fontisan/ufo/compile_gvar_spec.rb'
117
+ - 'spec/fontisan/ufo/compile_item_variation_store_spec.rb'
118
+ - 'spec/fontisan/ufo/filters_spec.rb'
119
+
120
+ # Offense count: 14
121
+ # This cop supports safe autocorrection (--autocorrect).
122
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
123
+ # SupportedStyles: special_inside_parentheses, consistent, align_brackets
124
+ Layout/FirstArrayElementIndentation:
125
+ Exclude:
126
+ - 'spec/fontisan/stitcher/glyph_signature_spec.rb'
127
+ - 'spec/fontisan/ufo/compile/feature_writers/mark_spec.rb'
128
+
129
+ # Offense count: 194
130
+ # This cop supports safe autocorrection (--autocorrect).
131
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
132
+ # SupportedHashRocketStyles: key, separator, table
133
+ # SupportedColonStyles: key, separator, table
134
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
135
+ Layout/HashAlignment:
136
+ Enabled: false
137
+
138
+ # Offense count: 1
139
+ # This cop supports safe autocorrection (--autocorrect).
140
+ # Configuration parameters: EnforcedStyle.
141
+ # SupportedStyles: normal, indented_internal_methods
142
+ Layout/IndentationConsistency:
143
+ Exclude:
144
+ - 'lib/fontisan/ufo/compile/cff.rb'
145
+
146
+ # Offense count: 121
147
+ # This cop supports safe autocorrection (--autocorrect).
148
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
149
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
150
+ Layout/IndentationWidth:
151
+ Enabled: false
152
+
153
+ # Offense count: 2235
16
154
  # This cop supports safe autocorrection (--autocorrect).
17
155
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
18
156
  # URISchemes: http, https
19
157
  Layout/LineLength:
20
158
  Enabled: false
21
159
 
160
+ # Offense count: 7
161
+ # This cop supports safe autocorrection (--autocorrect).
162
+ # Configuration parameters: EnforcedStyle.
163
+ # SupportedStyles: symmetrical, new_line, same_line
164
+ Layout/MultilineArrayBraceLayout:
165
+ Exclude:
166
+ - 'spec/fontisan/stitcher/glyph_signature_spec.rb'
167
+ - 'spec/fontisan/ufo/compile/feature_writers/mark_spec.rb'
168
+
169
+ # Offense count: 29
170
+ # This cop supports safe autocorrection (--autocorrect).
171
+ # Configuration parameters: EnforcedStyle.
172
+ # SupportedStyles: symmetrical, new_line, same_line
173
+ Layout/MultilineMethodCallBraceLayout:
174
+ Exclude:
175
+ - 'benchmark/compile_benchmark.rb'
176
+ - 'lib/fontisan/ufo/compile/fvar.rb'
177
+ - 'lib/fontisan/ufo/writer.rb'
178
+ - 'spec/fontisan/stitcher/deduplicator_spec.rb'
179
+ - 'spec/fontisan/stitcher/include_codepoints_map_spec.rb'
180
+ - 'spec/fontisan/svg_to_glyf/affine_transform_spec.rb'
181
+ - 'spec/fontisan/ufo/compile/filters/propagate_anchors_spec.rb'
182
+ - 'spec/fontisan/ufo/compile_gvar_spec.rb'
183
+ - 'spec/fontisan/ufo/compile_item_variation_store_spec.rb'
184
+ - 'spec/fontisan/ufo/filters_spec.rb'
185
+
186
+ # Offense count: 225
187
+ # This cop supports safe autocorrection (--autocorrect).
188
+ # Configuration parameters: AllowInHeredoc.
189
+ Layout/TrailingWhitespace:
190
+ Enabled: false
191
+
22
192
  # Offense count: 2
23
193
  Lint/CopDirectiveSyntax:
24
194
  Exclude:
25
195
  - 'lib/fontisan/woff2_font.rb'
26
196
  - 'spec/fontisan/variation/cache_spec.rb'
27
197
 
28
- # Offense count: 52
198
+ # Offense count: 54
29
199
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
30
200
  Lint/DuplicateBranch:
31
201
  Enabled: false
@@ -78,21 +248,12 @@ Lint/StructNewOverride:
78
248
  Exclude:
79
249
  - 'lib/fontisan/optimizers/pattern_analyzer.rb'
80
250
 
81
- # Offense count: 5
251
+ # Offense count: 24
82
252
  # This cop supports safe autocorrection (--autocorrect).
83
253
  # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
84
254
  # NotImplementedExceptions: NotImplementedError
85
255
  Lint/UnusedMethodArgument:
86
- Exclude:
87
- - 'lib/fontisan.rb'
88
- - 'lib/fontisan/tables/cff2/private_dict_blend_handler.rb'
89
- - 'lib/fontisan/variation/table_accessor.rb'
90
- - 'lib/fontisan/woff_font.rb'
91
- # Subset strategies share a uniform (context:, tag:, table:) interface
92
- # so the dispatcher can call every registered class the same way. Not
93
- # every strategy reads every arg; the interface contract justifies the
94
- # unused warnings here.
95
- - 'lib/fontisan/subset/table_strategy/**/*'
256
+ Enabled: false
96
257
 
97
258
  # Offense count: 5
98
259
  Lint/UselessConstantScoping:
@@ -100,12 +261,12 @@ Lint/UselessConstantScoping:
100
261
  - 'lib/fontisan/conversion_options.rb'
101
262
  - 'lib/fontisan/type1/charstrings.rb'
102
263
 
103
- # Offense count: 705
264
+ # Offense count: 715
104
265
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
105
266
  Metrics/AbcSize:
106
267
  Enabled: false
107
268
 
108
- # Offense count: 36
269
+ # Offense count: 34
109
270
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
110
271
  # AllowedMethods: refine
111
272
  Metrics/BlockLength:
@@ -123,28 +284,28 @@ Metrics/CollectionLiteralLength:
123
284
  - 'lib/fontisan/type1/agl.rb'
124
285
  - 'lib/fontisan/type1/encodings.rb'
125
286
 
126
- # Offense count: 366
287
+ # Offense count: 367
127
288
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
128
289
  Metrics/CyclomaticComplexity:
129
290
  Enabled: false
130
291
 
131
- # Offense count: 978
292
+ # Offense count: 990
132
293
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
133
294
  Metrics/MethodLength:
134
295
  Max: 135
135
296
 
136
- # Offense count: 27
297
+ # Offense count: 30
137
298
  # Configuration parameters: CountKeywordArgs.
138
299
  Metrics/ParameterLists:
139
300
  Max: 39
140
301
  MaxOptionalParameters: 4
141
302
 
142
- # Offense count: 278
303
+ # Offense count: 279
143
304
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
144
305
  Metrics/PerceivedComplexity:
145
306
  Enabled: false
146
307
 
147
- # Offense count: 208
308
+ # Offense count: 202
148
309
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
149
310
  # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
150
311
  Naming/MethodParameterName:
@@ -187,7 +348,7 @@ Naming/VariableName:
187
348
  - 'lib/fontisan/type1/pfm_generator.rb'
188
349
  - 'lib/fontisan/type1/pfm_parser.rb'
189
350
 
190
- # Offense count: 120
351
+ # Offense count: 124
191
352
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
192
353
  # SupportedStyles: snake_case, normalcase, non_integer
193
354
  # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
@@ -204,13 +365,13 @@ RSpec/AnyInstance:
204
365
  - 'spec/fontisan/variation/parallel_generator_spec.rb'
205
366
  - 'spec/integration/format_conversion_spec.rb'
206
367
 
207
- # Offense count: 127
368
+ # Offense count: 128
208
369
  # Configuration parameters: Prefixes, AllowedPatterns.
209
370
  # Prefixes: when, with, without
210
371
  RSpec/ContextWording:
211
372
  Enabled: false
212
373
 
213
- # Offense count: 38
374
+ # Offense count: 39
214
375
  # Configuration parameters: IgnoredMetadata.
215
376
  RSpec/DescribeClass:
216
377
  Enabled: false
@@ -226,7 +387,7 @@ RSpec/DescribeMethod:
226
387
  - 'spec/fontisan/type1/seac_expander_spec.rb'
227
388
  - 'spec/fontisan/type1_real_fonts_spec.rb'
228
389
 
229
- # Offense count: 1673
390
+ # Offense count: 1710
230
391
  # Configuration parameters: CountAsOne.
231
392
  RSpec/ExampleLength:
232
393
  Max: 66
@@ -278,7 +439,7 @@ RSpec/IteratedExpectation:
278
439
  Exclude:
279
440
  - 'spec/fontisan/tables/glyf_spec.rb'
280
441
 
281
- # Offense count: 25
442
+ # Offense count: 27
282
443
  # Configuration parameters: EnforcedStyle.
283
444
  # SupportedStyles: have_received, receive
284
445
  RSpec/MessageSpies:
@@ -294,6 +455,7 @@ RSpec/MessageSpies:
294
455
  - 'spec/fontisan/subset/builder_spec.rb'
295
456
  - 'spec/fontisan/variation/instance_writer_spec.rb'
296
457
  - 'spec/fontisan/woff2_font_spec.rb'
458
+ - 'spec/support/fixture_downloader_spec.rb'
297
459
 
298
460
  # Offense count: 4
299
461
  RSpec/MultipleDescribes:
@@ -303,7 +465,7 @@ RSpec/MultipleDescribes:
303
465
  - 'spec/fontisan/utils/thread_pool_spec.rb'
304
466
  - 'spec/fontisan/variation/cache_spec.rb'
305
467
 
306
- # Offense count: 2105
468
+ # Offense count: 2126
307
469
  RSpec/MultipleExpectations:
308
470
  Max: 19
309
471
 
@@ -335,7 +497,7 @@ RSpec/RepeatedExample:
335
497
  - 'spec/fontisan/metrics_calculator_spec.rb'
336
498
  - 'spec/fontisan/tables/os2_spec.rb'
337
499
 
338
- # Offense count: 48
500
+ # Offense count: 51
339
501
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
340
502
  # SupportedInflectors: default, active_support
341
503
  RSpec/SpecFilePathFormat:
@@ -346,10 +508,11 @@ RSpec/SpecFilePathSuffix:
346
508
  Exclude:
347
509
  - 'spec/benchmarks/subroutine_optimization_benchmark.rb'
348
510
 
349
- # Offense count: 1
511
+ # Offense count: 2
350
512
  RSpec/StubbedMock:
351
513
  Exclude:
352
514
  - 'spec/fontisan/woff2_font_spec.rb'
515
+ - 'spec/support/fixture_downloader_spec.rb'
353
516
 
354
517
  # Offense count: 10
355
518
  RSpec/SubjectStub:
@@ -366,6 +529,16 @@ Rake/MethodDefinitionInTask:
366
529
  Exclude:
367
530
  - 'Rakefile'
368
531
 
532
+ # Offense count: 68
533
+ # This cop supports safe autocorrection (--autocorrect).
534
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
535
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
536
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
537
+ # FunctionalMethods: let, let!, subject, watch
538
+ # AllowedMethods: lambda, proc, it
539
+ Style/BlockDelimiters:
540
+ Enabled: false
541
+
369
542
  # Offense count: 6
370
543
  # This cop supports safe autocorrection (--autocorrect).
371
544
  Style/ComparableClamp:
@@ -400,9 +573,47 @@ Style/HashLikeCase:
400
573
  - 'lib/fontisan/commands/unpack_command.rb'
401
574
  - 'lib/fontisan/models/validation_report.rb'
402
575
 
576
+ # Offense count: 16
577
+ # This cop supports safe autocorrection (--autocorrect).
578
+ Style/MultilineIfModifier:
579
+ Exclude:
580
+ - 'lib/fontisan/svg/standalone_glyph.rb'
581
+ - 'lib/fontisan/svg_to_glyf/geometry/transform_parser.rb'
582
+ - 'lib/fontisan/ufo/cli.rb'
583
+ - 'lib/fontisan/ufo/convert.rb'
584
+ - 'lib/fontisan/ufo/convert/from_bin_data.rb'
585
+ - 'lib/fontisan/ufo/convert/to_dfont.rb'
586
+ - 'lib/fontisan/ufo/convert/to_otc.rb'
587
+ - 'lib/fontisan/ufo/convert/to_postscript.rb'
588
+ - 'lib/fontisan/ufo/convert/to_woff.rb'
589
+ - 'lib/fontisan/ufo/convert/to_woff2.rb'
590
+ - 'lib/fontisan/ufo/plist.rb'
591
+ - 'lib/fontisan/woff2/collection_decoder.rb'
592
+ - 'lib/fontisan/woff2/collection_encoder.rb'
593
+ - 'spec/support/fixture_downloader.rb'
594
+
595
+ # Offense count: 4
596
+ # This cop supports safe autocorrection (--autocorrect).
597
+ Style/MultilineTernaryOperator:
598
+ Exclude:
599
+ - 'lib/fontisan/commands/validate_collection_command.rb'
600
+ - 'lib/fontisan/converters/collection_converter.rb'
601
+ - 'spec/fontisan/woff2/sfnt_checksum_spec.rb'
602
+
403
603
  # Offense count: 1
404
604
  # Configuration parameters: AllowedMethods.
405
605
  # AllowedMethods: respond_to_missing?
406
606
  Style/OptionalBooleanParameter:
407
607
  Exclude:
408
608
  - 'lib/fontisan/conversion_options.rb'
609
+
610
+ # Offense count: 22
611
+ # This cop supports safe autocorrection (--autocorrect).
612
+ # Configuration parameters: EnforcedStyleForMultiline.
613
+ # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
614
+ Style/TrailingCommaInArguments:
615
+ Exclude:
616
+ - 'benchmark/compile_benchmark.rb'
617
+ - 'spec/fontisan/ufo/compile/filters/propagate_anchors_spec.rb'
618
+ - 'spec/fontisan/ufo/compile_gvar_spec.rb'
619
+ - 'spec/fontisan/ufo/filters_spec.rb'
data/Gemfile CHANGED
@@ -10,6 +10,12 @@ gem "benchmark"
10
10
  gem "bigdecimal"
11
11
  gem "canon", "~> 0.1.3"
12
12
  gem "get_process_mem", "~> 0.2"
13
+ # Octokit provides authenticated GitHub API access for the test-only
14
+ # fixture downloader (spec/support/fixture_downloader.rb). CI sets
15
+ # GITHUB_TOKEN, the downloader auto-detects it and routes GitHub URLs
16
+ # through Octokit to lift the 60/hr anonymous rate limit. NOT in
17
+ # fontisan.gemspec — production installs never pull this in.
18
+ gem "octokit", group: :test
13
19
  gem "openssl", "~> 3.0"
14
20
  gem "rake"
15
21
  gem "rspec"
data/Rakefile CHANGED
@@ -10,21 +10,24 @@ RuboCop::RakeTask.new
10
10
 
11
11
  # rubocop:disable Metrics/BlockLength
12
12
  namespace :fixtures do
13
- # Load centralized fixture configuration
13
+ # Load centralized fixture configuration and the test-only downloader.
14
+ # The downloader lives under spec/support (not lib/) because it is
15
+ # purely test infrastructure — fontisan-the-gem never downloads
16
+ # fonts at runtime.
14
17
  require_relative "spec/support/fixture_fonts"
15
- require "fontisan/tasks"
18
+ require_relative "spec/support/fixture_downloader"
16
19
 
17
20
  # Helper method to download a single file
18
21
  def download_single_file(name, url, target_path)
19
22
  puts "[fixtures:download] Downloading #{name}..."
20
23
 
21
- Fontisan::Tasks::FixtureDownloader.new(
24
+ FixtureFonts::Downloader.new(
22
25
  url: url,
23
26
  destination: target_path,
24
27
  ).call
25
28
 
26
29
  puts "[fixtures:download] #{name} downloaded successfully"
27
- rescue Fontisan::Tasks::FixtureDownloader::Error => e
30
+ rescue FixtureFonts::Downloader::Error => e
28
31
  warn "[fixtures:download] #{name} failed after retries: #{e.message}"
29
32
  raise
30
33
  end
@@ -41,7 +44,7 @@ namespace :fixtures do
41
44
  "fontisan_#{name}_#{Process.pid}_#{rand(10000)}.zip")
42
45
 
43
46
  begin
44
- Fontisan::Tasks::FixtureDownloader.new(
47
+ FixtureFonts::Downloader.new(
45
48
  url: url,
46
49
  destination: temp_path,
47
50
  ).call
@@ -89,7 +92,7 @@ namespace :fixtures do
89
92
  end
90
93
 
91
94
  puts "[fixtures:download] #{name} downloaded successfully"
92
- rescue Fontisan::Tasks::FixtureDownloader::Error => e
95
+ rescue FixtureFonts::Downloader::Error => e
93
96
  warn "[fixtures:download] #{name} failed after retries: #{e.message}"
94
97
  raise
95
98
  rescue LoadError => e
@@ -24,10 +24,17 @@ module FontisanBench
24
24
  g.width = 500
25
25
  g.add_unicode(0xE000 + i) if i < 0x1000
26
26
  g.add_contour(Fontisan::Ufo::Contour.new([
27
- Fontisan::Ufo::Point.new(x: 0, y: 0, type: "line"),
28
- Fontisan::Ufo::Point.new(x: 100, y: 0, type: "line"),
29
- Fontisan::Ufo::Point.new(x: 100, y: 100, type: "offcurve"),
30
- Fontisan::Ufo::Point.new(x: 50, y: 150, type: "curve"),
27
+ Fontisan::Ufo::Point.new(x: 0,
28
+ y: 0, type: "line"),
29
+ Fontisan::Ufo::Point.new(
30
+ x: 100, y: 0, type: "line",
31
+ ),
32
+ Fontisan::Ufo::Point.new(
33
+ x: 100, y: 100, type: "offcurve",
34
+ ),
35
+ Fontisan::Ufo::Point.new(
36
+ x: 50, y: 150, type: "curve",
37
+ ),
31
38
  ]))
32
39
  font.glyphs["g#{i}"] = g
33
40
  end
@@ -38,7 +45,8 @@ module FontisanBench
38
45
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
39
46
  yield
40
47
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
41
- puts format("%-40<label>s %8.<elapsed>.2f ms", label: label, elapsed: elapsed * 1000)
48
+ puts format("%-40<label>s %8.<elapsed>.2f ms", label: label,
49
+ elapsed: elapsed * 1000)
42
50
  elapsed
43
51
  end
44
52
  end
@@ -322,6 +322,7 @@ export default defineConfig({
322
322
  { text: "ConversionOptions", link: "/api/conversion-options" },
323
323
  { text: "SfntFont", link: "/api/sfnt-font" },
324
324
  { text: "Type1Font", link: "/api/type1-font" },
325
+ { text: "Ufo::Layer", link: "/api/layer" },
325
326
  ],
326
327
  },
327
328
  {
@@ -364,12 +364,56 @@ stitcher.write_to("out.ttf", format: :ttf, subfont: :main)
364
364
  # → out.ttf has glyf + CBDT + CBLC, emoji renders correctly
365
365
  ----
366
366
 
367
- Constraints:
367
+ === Outline-first cmap priority
368
+
369
+ When a codepoint is covered by BOTH an outline donor and the CBDT
370
+ donor, the cmap maps it to the outline glyph — never to the CBDT
371
+ placeholder. The Cmap compiler follows first-wins semantics, and
372
+ `GlyphCopier` runs before `CbdtPropagator` so the outline GID is
373
+ registered first. This was the fix for the Emoticons cmap-loss bug
374
+ where shared codepoints silently dropped out of the cmap.
375
+
376
+ === Placeholder name deconfliction
377
+
378
+ CBDT placeholders are named after the source's glyph IDs
379
+ (`"gid1"`, `"gid2"`, …), which is the SAME naming scheme
380
+ `Source#extract_truetype_glyph` uses for outline glyphs from TTF
381
+ donors. Without protection, a placeholder at `"gid110"` would
382
+ silently overwrite an outline glyph at `"gid110"` from a different
383
+ donor via `Layer#add` — the original cmap-loss vector for the
384
+ Essenfont CJK Ext G regression.
385
+
386
+ The Stitcher resolves this with two pieces:
387
+
388
+ * `Ufo::Layer#add` RAISES `Ufo::GlyphExistsError` on name conflict.
389
+ Silent overwrites are no longer possible. Callers that want to
390
+ replace an existing glyph use the explicit `Ufo::Layer#put`.
391
+ * `Stitcher::UniqueGlyphName.in(target, base)` allocates a
392
+ non-colliding name (`"gid110"`, then `"gid110.1"`, `"gid110.2"`,
393
+ …). `GlyphCopier` and `CbdtPropagator` both call it before every
394
+ `Layer#add`, so each glyph lands at its own GID slot without
395
+ touching the others.
396
+
397
+ === Limitations
368
398
 
369
399
  * Only ONE CBDT source per Stitcher. Multiple CBDT sources raise
370
400
  `Fontisan::MultipleCbdtSourcesError`.
371
- * The CBDT source is processed first (GIDs 0..N-1) so the CBLC's GID
372
- references remain valid in the output.
401
+ * **GID stability for overlapping codepoint ranges.** The raw-bytes
402
+ CBDT/CBLC propagation (`CbdtPropagator#propagate_tables_into`)
403
+ copies the source's CBLC verbatim. CBLC indexes glyphs by SOURCE
404
+ GID. After placeholder renaming (`"gid110"` → `"gid110.1"`), the
405
+ compiled GID no longer matches the source GID, so CBLC's source-
406
+ GID-indexed bitmaps can dangle. This is masked in the typical
407
+ Essenfont case (CBDT donor NotoColorEmoji and outline donors
408
+ cover disjoint codepoint ranges) but would produce wrong bitmaps
409
+ for any stitch where CBDT and outline sources share codepoint
410
+ ranges. A proper fix requires a CBLC rebuild pass that walks the
411
+ compiled cmap to remap source GIDs to compiled GIDs — tracked as
412
+ follow-up.
413
+ * **Collection-mode single-face coverage.** Each face is compiled
414
+ independently; `CbdtPropagator` currently requires at least one
415
+ outline codepoint per face. Collection-mode coverage of the
416
+ outline-first cmap-priority invariant is tracked as follow-up.
373
417
 
374
418
  == CLI
375
419