fontisan 0.4.21 → 0.4.22

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55632b625474e82c1cf9573ad6fce2de32f5636823701023abee87fc8817d8fc
4
- data.tar.gz: 5a26b3428874b792c101484dbed0468c25eb9f8eb7644a2c0ffff61c8f2baf6a
3
+ metadata.gz: 208fa8e2c44bdea2e800ecb13639d50e06e6914406822f6e3c92152d21414fd7
4
+ data.tar.gz: cdce57d3a157d5a5549b45104f93de38263697989bae6a66572907bc54b58bb0
5
5
  SHA512:
6
- metadata.gz: e7334f4be856d9a86712a0215c3671c7148d623649d4b81d232aa5f70b8aee71e1e55047a2780f48a7018721026e13dc99f0fff6dcbeda49801f88a832cbf99e
7
- data.tar.gz: a6432a0a4db97bdfedd597f5f79f40be36b0fd5d450f21e43319023a85b3e331377b2ee7e002fceb9f90c2e9e9a9ee4c1489ebd25e295058e5605d5be0482f57
6
+ metadata.gz: c87e9ec10e9cadd168dda76b38ef225c1e286bc63501b75268a7cd29fcdfbef3d86abacac211052d529d153aebf1f8db29aaab7445c51a5a83e3eb091f752ab4
7
+ data.tar.gz: 4632d882fe8ab4897ed32722ec17efb942819ded33e2005bf51c3032384466164d2c2c09a1a8f4388f1889b5c169f42b9dc14ae5cafce72ef5680c94d2333c02
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 06:50:36 UTC using RuboCop version 1.86.1.
3
+ # on 2026-07-08 08:05:45 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,7 +12,7 @@ Gemspec/RequiredRubyVersion:
12
12
  - 'docs/node_modules/speakingurl/speakingurl-rails.gemspec'
13
13
  - 'fontisan.gemspec'
14
14
 
15
- # Offense count: 2110
15
+ # Offense count: 2116
16
16
  # This cop supports safe autocorrection (--autocorrect).
17
17
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
18
18
  # URISchemes: http, https
@@ -95,7 +95,7 @@ Lint/UselessConstantScoping:
95
95
  - 'lib/fontisan/conversion_options.rb'
96
96
  - 'lib/fontisan/type1/charstrings.rb'
97
97
 
98
- # Offense count: 704
98
+ # Offense count: 705
99
99
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
100
100
  Metrics/AbcSize:
101
101
  Enabled: false
@@ -221,7 +221,7 @@ RSpec/DescribeMethod:
221
221
  - 'spec/fontisan/type1/seac_expander_spec.rb'
222
222
  - 'spec/fontisan/type1_real_fonts_spec.rb'
223
223
 
224
- # Offense count: 1666
224
+ # Offense count: 1673
225
225
  # Configuration parameters: CountAsOne.
226
226
  RSpec/ExampleLength:
227
227
  Max: 66
@@ -298,7 +298,7 @@ RSpec/MultipleDescribes:
298
298
  - 'spec/fontisan/utils/thread_pool_spec.rb'
299
299
  - 'spec/fontisan/variation/cache_spec.rb'
300
300
 
301
- # Offense count: 2103
301
+ # Offense count: 2105
302
302
  RSpec/MultipleExpectations:
303
303
  Max: 19
304
304
 
@@ -0,0 +1,151 @@
1
+ # frozen_string: true
2
+
3
+ module Fontisan
4
+ class Stitcher
5
+ # Owns every CBDT-specific concern in the stitch pipeline.
6
+ #
7
+ # The Stitcher has two distinct CBDT responsibilities that benefit
8
+ # from being in one place:
9
+ #
10
+ # 1. Detection: exactly one CBDT source per stitch is supported.
11
+ # Multiple CBDT sources raise MultipleCbdtSourcesError.
12
+ #
13
+ # 2. Action: when a CBDT source exists, the stitcher
14
+ # - injects empty CBDT placeholder glyphs into the target UFO
15
+ # (the bitmap data lives in CBDT/CBLC tables, not in outlines),
16
+ # - propagates the raw CBDT/CBLC tables into the compiled font
17
+ # file so the bitmaps survive the round-trip.
18
+ #
19
+ # Both responsibilities are model-driven from the CBDT Source itself
20
+ # (`Source#bitmap_mode == :cbdt`), so the propagator is stateless
21
+ # given a list of sources. Tests can construct one with synthetic
22
+ # sources directly.
23
+ class CbdtPropagator
24
+ # @param sources [Array<Stitcher::Source>] all sources the stitcher
25
+ # knows about, in label order (the propagator does not care
26
+ # about labels).
27
+ def initialize(sources)
28
+ @sources = sources
29
+ end
30
+
31
+ # @return [Stitcher::Source, nil] the single CBDT source, or nil
32
+ # if there is none.
33
+ # @raise [MultipleCbdtSourcesError] if more than one source is
34
+ # marked :cbdt.
35
+ def cbdt_source
36
+ cbdts = @sources.select { |s| s.bitmap_mode == :cbdt }
37
+ return nil if cbdts.empty?
38
+
39
+ if cbdts.size > 1
40
+ raise MultipleCbdtSourcesError,
41
+ "multiple CBDT sources not supported (found #{cbdts.size})"
42
+ end
43
+
44
+ cbdts.first
45
+ end
46
+
47
+ # Safe variant: returns nil instead of raising when there are
48
+ # multiple CBDT sources. Used inside the per-subfont glyph-copy
49
+ # pass where raising mid-compile would leave partial state.
50
+ #
51
+ # @return [Stitcher::Source, nil]
52
+ def safe_cbdt_source
53
+ cbdt_source
54
+ rescue MultipleCbdtSourcesError
55
+ nil
56
+ end
57
+
58
+ # Inject empty CBDT placeholder glyphs from `source` into the
59
+ # target UFO. One placeholder per gid in the source's maxp/cmap.
60
+ # Outline data is intentionally absent — the bitmap lives in
61
+ # the CBDT table that #propagate_tables_into will copy later.
62
+ #
63
+ # Glyphs are NOT registered with the deduplicator: each CBDT
64
+ # glyph is unique to its source, and deduplication would
65
+ # incorrectly collapse distinct bitmaps.
66
+ #
67
+ # @param source [Stitcher::Source] the CBDT source
68
+ # @param target [Ufo::Font] target UFO font to receive placeholders
69
+ def add_placeholder_glyphs(source, target)
70
+ ufo = source.font.is_a?(Ufo::Font) ? source.font : nil
71
+ if ufo
72
+ ufo.glyphs.each_value { |g| target.layers.default_layer.add(clone_glyph(g, name: g.name)) }
73
+ return
74
+ end
75
+
76
+ maxp = source.font.table("maxp")
77
+ num_glyphs = maxp&.num_glyphs || 0
78
+ cmap = source.font.table("cmap")
79
+ mappings = cmap&.unicode_mappings || {}
80
+
81
+ gid_cps = Hash.new { |h, k| h[k] = [] }
82
+ mappings.each { |cp, gid| gid_cps[gid] << cp }
83
+
84
+ num_glyphs.times do |gid|
85
+ name = gid.zero? ? ".notdef" : "gid#{gid}"
86
+ glyph = Ufo::Glyph.new(name: name)
87
+ glyph.width = 0
88
+ gid_cps[gid].each { |cp| glyph.add_unicode(cp) }
89
+ target.layers.default_layer.add(glyph)
90
+ end
91
+ end
92
+
93
+ # Propagate the raw CBDT/CBLC tables from the CBDT source into
94
+ # the compiled font at `path`, rewriting the file in place.
95
+ #
96
+ # Reads every table from the compiled font as raw bytes (bypassing
97
+ # BinData #table because some tables — notably CFF2 — don't yet
98
+ # have round-trippable BinData models), splices in CBDT/CBLC,
99
+ # and rewrites the file.
100
+ #
101
+ # No-op when called with a non-CBDT source or nil.
102
+ #
103
+ # @param source [Stitcher::Source, nil] the CBDT source
104
+ # @param path [String] compiled font file to rewrite
105
+ def propagate_tables_into(source, path)
106
+ return unless source
107
+
108
+ compiled = FontLoader.load(path)
109
+
110
+ # Read every table as raw bytes straight from the file's table
111
+ # directory. We deliberately bypass #table (which parses via
112
+ # BinData) because some tables — notably CFF2 — don't yet have
113
+ # round-trippable BinData models; calling #table on them returns
114
+ # nil and would silently drop them from the rewritten font.
115
+ tables = {}
116
+ compiled.table_names.each do |tag|
117
+ raw = compiled.table_data[tag]
118
+ tables[tag] = raw if raw
119
+ end
120
+
121
+ cbdt_bytes = source.raw_table_bytes("CBDT")
122
+ cblc_bytes = source.raw_table_bytes("CBLC")
123
+ tables["CBDT"] = cbdt_bytes if cbdt_bytes
124
+ tables["CBLC"] = cblc_bytes if cblc_bytes
125
+
126
+ sfnt = tables.key?("CFF ") || tables.key?("CFF2") ? 0x4F54544F : 0x00010000
127
+ FontWriter.write_to_file(tables, path, sfnt_version: sfnt)
128
+ end
129
+
130
+ private
131
+
132
+ def clone_glyph(original, name:)
133
+ copy = Ufo::Glyph.new(name: name)
134
+ copy.width = original.width
135
+ copy.height = original.height
136
+ original.contours.each { |c| copy.add_contour(clone_contour(c)) }
137
+ original.components.each { |c| copy.add_component(c) }
138
+ original.anchors.each { |a| copy.add_anchor(a) }
139
+ original.guidelines.each { |g| copy.add_guideline(g) }
140
+ copy
141
+ end
142
+
143
+ def clone_contour(original)
144
+ points = original.points.map do |p|
145
+ Ufo::Point.new(x: p.x, y: p.y, type: p.type, smooth: p.smooth)
146
+ end
147
+ Ufo::Contour.new(points)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string: true
2
+
3
+ module Fontisan
4
+ class Stitcher
5
+ # Owns the mechanical concerns of copying glyphs from donor sources
6
+ # into a target UFO font.
7
+ #
8
+ # The Stitcher's per-subfont build needs to:
9
+ # - inject a `.notdef` glyph at GID 0
10
+ # - copy outline glyphs from donor sources, optionally skipping
11
+ # donors the caller names (e.g. the CBDT source, whose glyphs
12
+ # take a different code path)
13
+ # - register each copied glyph with the deduplicator so visually
14
+ # identical glyphs from different donors share a gid
15
+ #
16
+ # GlyphCopier centralizes that logic in one stateless-per-call
17
+ # collaborator. The Stitcher constructs one with a deduplicator and
18
+ # calls its methods in the order the cmap contract requires
19
+ # (see Fontisan::Ufo::Compile::Cmap for first-wins semantics).
20
+ class GlyphCopier
21
+ # @param deduplicator [Stitcher::Deduplicator, nil] when non-nil,
22
+ # visually identical glyphs share a gid in the target. Pass nil
23
+ # to disable deduplication.
24
+ def initialize(deduplicator)
25
+ @deduplicator = deduplicator
26
+ end
27
+
28
+ # Inject a `.notdef` glyph at GID 0 of `target`.
29
+ #
30
+ # If a binding exists with `donor_gid == 0`, copy the donor's
31
+ # .notdef; otherwise synthesize an empty one. The injected glyph
32
+ # is registered with the deduplicator so subsequent .notdef
33
+ # references deduplicate against it.
34
+ #
35
+ # @param bindings [Array<Hash>] stitch bindings to search
36
+ # @param target [Ufo::Font] target UFO
37
+ def inject_notdef(bindings, target)
38
+ notdef_binding = bindings.find { |b| b[:donor_gid].zero? }
39
+ if notdef_binding
40
+ copy_glyph_into(target, name: ".notdef",
41
+ source: notdef_binding[:source],
42
+ donor_gid: 0)
43
+ else
44
+ target.layers.default_layer.add(Ufo::Glyph.new(name: ".notdef"))
45
+ end
46
+ dedup_target = target.glyphs[".notdef"]
47
+ @deduplicator&.register(dedup_target, ".notdef") if dedup_target
48
+ end
49
+
50
+ # Copy outline glyphs from `bindings` into `target`, skipping any
51
+ # sources the caller names (e.g. the CBDT source).
52
+ #
53
+ # Bindings are sorted by `[codepoint, donor_gid]` for deterministic
54
+ # output. For each binding:
55
+ # - if deduplicator has seen a visually identical glyph that's
56
+ # already in the target, attach the codepoint to that glyph
57
+ # (extra unicode mapping);
58
+ # - otherwise copy the glyph under a unique name and register
59
+ # with the deduplicator.
60
+ #
61
+ # @param bindings [Array<Hash>] stitch bindings
62
+ # @param target [Ufo::Font] target UFO
63
+ # @param skip_sources [Array<Stitcher::Source>] sources whose
64
+ # bindings should be skipped (e.g. CBDT placeholder source)
65
+ def copy_outlines(bindings, target, skip_sources: [])
66
+ sorted_bindings(bindings).each do |binding|
67
+ next if binding[:donor_gid].zero?
68
+ next if skip_sources.any? { |s| s.equal?(binding[:source]) }
69
+
70
+ glyph = binding[:source].glyph_for_gid(binding[:donor_gid])
71
+ next unless glyph
72
+
73
+ canonical = @deduplicator&.find(glyph)
74
+ if canonical && target.glyphs.key?(canonical)
75
+ add_extra_unicode(target, canonical, binding[:codepoint])
76
+ else
77
+ name = unique_target_name(target, glyph.name)
78
+ copy_glyph_into(target, name: name, source: binding[:source],
79
+ donor_gid: binding[:donor_gid],
80
+ codepoint: binding[:codepoint])
81
+ @deduplicator&.register(glyph, name)
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def sorted_bindings(bindings)
89
+ bindings.sort_by { |b| [b[:codepoint] || Float::INFINITY, b[:donor_gid]] }
90
+ end
91
+
92
+ def copy_glyph_into(target_font, name:, source:, donor_gid:, codepoint: nil)
93
+ original = source.glyph_for_gid(donor_gid)
94
+ return unless original
95
+
96
+ copy = clone_glyph(original, name: name)
97
+ copy.add_unicode(codepoint) if codepoint
98
+ target_font.layers.default_layer.add(copy)
99
+ end
100
+
101
+ def add_extra_unicode(target_font, glyph_name, codepoint)
102
+ return unless codepoint
103
+
104
+ glyph = target_font.glyph(glyph_name)
105
+ glyph.add_unicode(codepoint) unless glyph.unicodes.include?(codepoint)
106
+ end
107
+
108
+ def unique_target_name(target_font, base_name)
109
+ return base_name unless target_font.glyphs.key?(base_name)
110
+
111
+ suffix = 1
112
+ loop do
113
+ candidate = "#{base_name}.#{suffix}"
114
+ return candidate unless target_font.glyphs.key?(candidate)
115
+
116
+ suffix += 1
117
+ end
118
+ end
119
+
120
+ def clone_glyph(original, name:)
121
+ copy = Ufo::Glyph.new(name: name)
122
+ copy.width = original.width
123
+ copy.height = original.height
124
+ original.contours.each { |c| copy.add_contour(clone_contour(c)) }
125
+ original.components.each { |c| copy.add_component(c) }
126
+ original.anchors.each { |a| copy.add_anchor(a) }
127
+ original.guidelines.each { |g| copy.add_guideline(g) }
128
+ copy
129
+ end
130
+
131
+ def clone_contour(original)
132
+ points = original.points.map do |p|
133
+ Ufo::Point.new(x: p.x, y: p.y, type: p.type, smooth: p.smooth)
134
+ end
135
+ Ufo::Contour.new(points)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -34,6 +34,8 @@ module Fontisan
34
34
  autoload :SubfontStats, "fontisan/stitcher/collection_result"
35
35
  autoload :FormatMetadata, "fontisan/stitcher/format_metadata"
36
36
  autoload :PartitionStrategy, "fontisan/stitcher/partition_strategy"
37
+ autoload :CbdtPropagator, "fontisan/stitcher/cbdt_propagator"
38
+ autoload :GlyphCopier, "fontisan/stitcher/glyph_copier"
37
39
 
38
40
  # Internal: pairs a compiled loaded font with its stats so
39
41
  # +write_collection+ can build the collection and the result from a
@@ -98,7 +100,7 @@ module Fontisan
98
100
  metadata = FormatMetadata.resolve(format)
99
101
  metadata.compiler_class.new(target).compile(output_path: path)
100
102
 
101
- propagate_cbdt_tables(path) if cbdt_source
103
+ cbdt_propagator.propagate_tables_into(cbdt_propagator.cbdt_source, path)
102
104
  path
103
105
  end
104
106
 
@@ -129,25 +131,6 @@ module Fontisan
129
131
  end
130
132
  end
131
133
 
132
- def cbdt_source
133
- cbdts = @sources.values.select { |s| s.bitmap_mode == :cbdt }
134
- if cbdts.size > 1
135
- raise MultipleCbdtSourcesError,
136
- "multiple CBDT sources not supported (found #{cbdts.size})"
137
- end
138
-
139
- cbdts.first
140
- end
141
-
142
- # Safe variant of #cbdt_source: returns nil instead of raising when
143
- # the project has multiple CBDT sources. Used inside the per-subfont
144
- # glyph-copy pass where raising mid-compile would leave partial state.
145
- def safe_cbdt_source
146
- cbdt_source
147
- rescue MultipleCbdtSourcesError
148
- nil
149
- end
150
-
151
134
  def build_target_for(subfont_name)
152
135
  bindings = @subfonts[subfont_name] || []
153
136
  target = Ufo::Font.new
@@ -165,7 +148,7 @@ module Fontisan
165
148
  Dir.mktmpdir do |dir|
166
149
  sub_path = File.join(dir, "sub#{subfont_name}#{metadata.extension}")
167
150
  metadata.compiler_class.new(target).compile(output_path: sub_path)
168
- propagate_cbdt_tables(sub_path) if cbdt_source
151
+ cbdt_propagator.propagate_tables_into(cbdt_propagator.cbdt_source, sub_path)
169
152
 
170
153
  loaded = Fontisan::FontLoader.load(sub_path)
171
154
  stats = SubfontStats.new(
@@ -178,152 +161,22 @@ module Fontisan
178
161
  end
179
162
 
180
163
  def assign_gids_and_copy_glyphs(bindings, target, deduplicator)
181
- cbdt = safe_cbdt_source
164
+ cbdt = cbdt_propagator.safe_cbdt_source
165
+ copier = GlyphCopier.new(deduplicator)
182
166
 
183
167
  # Glyph ordering matters: Cmap.build uses first-wins semantics
184
168
  # (Cmap.build docstring), so outline donors must be stitched
185
169
  # before CBDT placeholders. Otherwise the empty CBDT placeholder
186
170
  # would land at a lower GID and win the cp→gid mapping for any
187
171
  # codepoint covered by both donors, hiding the real outline glyph.
188
- # CBDT bitmap data still propagates via propagate_cbdt_tables.
189
- add_notdef_from(bindings, target, deduplicator)
190
- add_outline_glyphs(bindings, target, deduplicator,
191
- skip_sources: cbdt ? [cbdt] : [])
192
- add_all_cbdt_glyphs(cbdt, target) if cbdt
193
- end
194
-
195
- def add_outline_glyphs(bindings, target, deduplicator, skip_sources:)
196
- sorted_bindings(bindings).each do |binding|
197
- next if binding[:donor_gid].zero?
198
- next if skip_sources.any? { |s| s.equal?(binding[:source]) }
199
-
200
- glyph = binding[:source].glyph_for_gid(binding[:donor_gid])
201
- next unless glyph
202
-
203
- canonical = deduplicator&.find(glyph)
204
- if canonical && target.glyphs.key?(canonical)
205
- add_extra_unicode(target, canonical, binding[:codepoint])
206
- else
207
- name = unique_target_name(target, glyph.name)
208
- copy_glyph_into(target, name: name, source: binding[:source],
209
- donor_gid: binding[:donor_gid],
210
- codepoint: binding[:codepoint])
211
- deduplicator&.register(glyph, name)
212
- end
213
- end
214
- end
215
-
216
- def sorted_bindings(bindings)
217
- bindings.sort_by { |b| [b[:codepoint] || Float::INFINITY, b[:donor_gid]] }
218
- end
219
-
220
- def add_notdef_from(bindings, target, deduplicator)
221
- notdef_binding = bindings.find { |b| b[:donor_gid].zero? }
222
- if notdef_binding
223
- copy_glyph_into(target, name: ".notdef",
224
- source: notdef_binding[:source],
225
- donor_gid: 0)
226
- else
227
- target.layers.default_layer.add(Ufo::Glyph.new(name: ".notdef"))
228
- end
229
- dedup_target = target.glyphs[".notdef"]
230
- deduplicator&.register(dedup_target, ".notdef") if dedup_target
231
- end
232
-
233
- def copy_glyph_into(target_font, name:, source:, donor_gid:, codepoint: nil)
234
- original = source.glyph_for_gid(donor_gid)
235
- return unless original
236
-
237
- copy = clone_glyph(original, name: name)
238
- copy.add_unicode(codepoint) if codepoint
239
- target_font.layers.default_layer.add(copy)
240
- end
241
-
242
- def add_extra_unicode(target_font, glyph_name, codepoint)
243
- return unless codepoint
244
-
245
- glyph = target_font.glyph(glyph_name)
246
- glyph.add_unicode(codepoint) unless glyph.unicodes.include?(codepoint)
172
+ # CBDT bitmap data still propagates via CbdtPropagator.
173
+ copier.inject_notdef(bindings, target)
174
+ copier.copy_outlines(bindings, target, skip_sources: cbdt ? [cbdt] : [])
175
+ cbdt_propagator.add_placeholder_glyphs(cbdt, target) if cbdt
247
176
  end
248
177
 
249
- def unique_target_name(target_font, base_name)
250
- return base_name unless target_font.glyphs.key?(base_name)
251
-
252
- suffix = 1
253
- loop do
254
- candidate = "#{base_name}.#{suffix}"
255
- return candidate unless target_font.glyphs.key?(candidate)
256
-
257
- suffix += 1
258
- end
259
- end
260
-
261
- def clone_glyph(original, name:)
262
- copy = Ufo::Glyph.new(name: name)
263
- copy.width = original.width
264
- copy.height = original.height
265
- original.contours.each { |c| copy.add_contour(clone_contour(c)) }
266
- original.components.each { |c| copy.add_component(c) }
267
- original.anchors.each { |a| copy.add_anchor(a) }
268
- original.guidelines.each { |g| copy.add_guideline(g) }
269
- copy
270
- end
271
-
272
- def clone_contour(original)
273
- points = original.points.map do |p|
274
- Ufo::Point.new(x: p.x, y: p.y, type: p.type, smooth: p.smooth)
275
- end
276
- Ufo::Contour.new(points)
277
- end
278
-
279
- def propagate_cbdt_tables(path)
280
- source = cbdt_source
281
- return unless source
282
-
283
- compiled = Fontisan::FontLoader.load(path)
284
-
285
- # Read every table as raw bytes straight from the file's table
286
- # directory. We deliberately bypass #table (which parses via
287
- # BinData) because some tables — notably CFF2 — don't yet have
288
- # round-trippable BinData models; calling #table on them returns
289
- # nil and would silently drop them from the rewritten font.
290
- tables = {}
291
- compiled.table_names.each do |tag|
292
- raw = compiled.table_data[tag]
293
- tables[tag] = raw if raw
294
- end
295
-
296
- cbdt_bytes = source.raw_table_bytes("CBDT")
297
- cblc_bytes = source.raw_table_bytes("CBLC")
298
- tables["CBDT"] = cbdt_bytes if cbdt_bytes
299
- tables["CBLC"] = cblc_bytes if cblc_bytes
300
-
301
- sfnt = tables.key?("CFF ") || tables.key?("CFF2") ? 0x4F54544F : 0x00010000
302
- Fontisan::FontWriter.write_to_file(tables, path, sfnt_version: sfnt)
303
- end
304
-
305
- def add_all_cbdt_glyphs(source, target)
306
- ufo = source.font.is_a?(Ufo::Font) ? source.font : nil
307
- if ufo
308
- ufo.glyphs.each_value { |g| target.layers.default_layer.add(clone_glyph(g, name: g.name)) }
309
- return
310
- end
311
-
312
- maxp = source.font.table("maxp")
313
- num_glyphs = maxp&.num_glyphs || 0
314
- cmap = source.font.table("cmap")
315
- mappings = cmap&.unicode_mappings || {}
316
-
317
- gid_cps = Hash.new { |h, k| h[k] = [] }
318
- mappings.each { |cp, gid| gid_cps[gid] << cp }
319
-
320
- num_glyphs.times do |gid|
321
- name = gid.zero? ? ".notdef" : "gid#{gid}"
322
- glyph = Ufo::Glyph.new(name: name)
323
- glyph.width = 0
324
- gid_cps[gid].each { |cp| glyph.add_unicode(cp) }
325
- target.layers.default_layer.add(glyph)
326
- end
178
+ def cbdt_propagator
179
+ @cbdt_propagator ||= CbdtPropagator.new(@sources.values)
327
180
  end
328
181
  end
329
182
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.4.21"
4
+ VERSION = "0.4.22"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.21
4
+ version: 0.4.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -436,9 +436,11 @@ files:
436
436
  - lib/fontisan/sfnt_font.rb
437
437
  - lib/fontisan/sfnt_table.rb
438
438
  - lib/fontisan/stitcher.rb
439
+ - lib/fontisan/stitcher/cbdt_propagator.rb
439
440
  - lib/fontisan/stitcher/collection_result.rb
440
441
  - lib/fontisan/stitcher/deduplicator.rb
441
442
  - lib/fontisan/stitcher/format_metadata.rb
443
+ - lib/fontisan/stitcher/glyph_copier.rb
442
444
  - lib/fontisan/stitcher/glyph_limit.rb
443
445
  - lib/fontisan/stitcher/glyph_signature.rb
444
446
  - lib/fontisan/stitcher/partition_strategy.rb