Class: Fontisan::Stitcher::CbdtPropagator
- Inherits:
-
Object
- Object
- Fontisan::Stitcher::CbdtPropagator
- Defined in:
- lib/fontisan/stitcher/cbdt_propagator.rb
Overview
Owns every CBDT-specific concern in the stitch pipeline.
The Stitcher has two distinct CBDT responsibilities that benefit from being in one place:
1. Detection: exactly one CBDT source per stitch is supported.
Multiple CBDT sources raise MultipleCbdtSourcesError.
2. Action: when a CBDT source exists, the stitcher
- injects empty CBDT placeholder glyphs into the target UFO
(the bitmap data lives in CBDT/CBLC tables, not in outlines),
- propagates the raw CBDT/CBLC tables into the compiled font
file so the bitmaps survive the round-trip.
Both responsibilities are model-driven from the CBDT Source itself
(Source#bitmap_mode == :cbdt), so the propagator is stateless
given a list of sources. Tests can construct one with synthetic
sources directly.
Instance Method Summary collapse
-
#add_placeholder_glyphs(source, target) ⇒ Object
Inject empty CBDT placeholder glyphs from
sourceinto the target UFO. -
#cbdt_source ⇒ Stitcher::Source?
The single CBDT source, or nil if there is none.
-
#initialize(sources) ⇒ CbdtPropagator
constructor
A new instance of CbdtPropagator.
-
#propagate_tables_into(source, path) ⇒ Object
Propagate CBDT/CBLC tables from the CBDT source into the compiled font at
path, rewriting the file in place. -
#safe_cbdt_source ⇒ Stitcher::Source?
Safe variant: returns nil instead of raising when there are multiple CBDT sources.
Constructor Details
#initialize(sources) ⇒ CbdtPropagator
Returns a new instance of CbdtPropagator.
27 28 29 |
# File 'lib/fontisan/stitcher/cbdt_propagator.rb', line 27 def initialize(sources) @sources = sources end |
Instance Method Details
#add_placeholder_glyphs(source, target) ⇒ Object
Inject empty CBDT placeholder glyphs from source into the
target UFO. One placeholder per gid in the source's maxp/cmap.
Outline data is intentionally absent — the bitmap lives in
the CBDT table that #propagate_tables_into will copy later.
Why placeholders are name-deconflicted
Placeholders are named after the CBDT source's gid ("gidN"). Outline donors compiled earlier into the same target use the SAME naming scheme (see Source#extract_truetype_glyph), so a CBDT placeholder at "gid110" would collide with an outline glyph at "gid110" from a different donor. Layer#add raises on conflict (it never silently overwrites — that path previously erased real outline glyphs and dropped their cmap entries, causing the CJK Ext G loss in Essenfont-Regular.ttc). We allocate a unique name via UniqueGlyphName so each placeholder lands at its own GID slot without touching the outlines.
Glyphs are NOT registered with the deduplicator: each CBDT glyph is unique to its source, and deduplication would incorrectly collapse distinct bitmaps.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/fontisan/stitcher/cbdt_propagator.rb', line 82 def add_placeholder_glyphs(source, target) @placeholder_names = {} ufo = source.font.is_a?(Ufo::Font) ? source.font : nil if ufo ufo.glyphs.each_value do |g| name = UniqueGlyphName.in(target, g.name) target.layers.default_layer.add(GlyphCloner.clone(g, name: name)) end return end maxp = source.font.table("maxp") num_glyphs = maxp&.num_glyphs || 0 cmap = source.font.table("cmap") mappings = cmap&.unicode_mappings || {} gid_cps = Hash.new { |h, k| h[k] = [] } mappings.each { |cp, gid| gid_cps[gid] << cp } num_glyphs.times do |gid| base_name = gid.zero? ? ".notdef" : "gid#{gid}" name = UniqueGlyphName.in(target, base_name) glyph = Ufo::Glyph.new(name: name) glyph.width = 0 gid_cps[gid].each { |cp| glyph.add_unicode(cp) } target.layers.default_layer.add(glyph) @placeholder_names[gid] = name end end |
#cbdt_source ⇒ Stitcher::Source?
Returns the single CBDT source, or nil if there is none.
35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/fontisan/stitcher/cbdt_propagator.rb', line 35 def cbdt_source cbdts = @sources.select { |s| s.bitmap_mode == :cbdt } return nil if cbdts.empty? if cbdts.size > 1 raise MultipleCbdtSourcesError, "multiple CBDT sources not supported (found #{cbdts.size})" end cbdts.first end |
#propagate_tables_into(source, path) ⇒ Object
Propagate CBDT/CBLC tables from the CBDT source into the
compiled font at path, rewriting the file in place.
Reads every table from the compiled font as raw bytes (bypassing BinData #table because some tables — notably CFF2 — don't yet have round-trippable BinData models), splices in CBDT/CBLC rebuilt with compiled-font GIDs, and rewrites the file.
The CBDT/CBLC rebuild uses Subset::TableStrategy::ColorBitmapSubsetter to remap every bitmap block from source GID to compiled GID. This ensures CBLC's IndexSubTableArray references the compiled font's actual GIDs, not the source's.
Falls back to raw-byte copy when the GID mapping can't be resolved (no placeholder names recorded, or compiled post table is v3.0 with no glyph names).
No-op when called with a non-CBDT source or nil.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/fontisan/stitcher/cbdt_propagator.rb', line 134 def propagate_tables_into(source, path) return unless source compiled = FontLoader.load(path) tables = {} compiled.table_names.each do |tag| raw = compiled.table_data[tag] tables[tag] = raw if raw end rebuilt = rebuild_color_tables(source, compiled) tables["CBDT"] = rebuilt[:cbdt] if rebuilt[:cbdt] tables["CBLC"] = rebuilt[:cblc] if rebuilt[:cblc] sfnt = tables.key?("CFF ") || tables.key?("CFF2") ? 0x4F54544F : 0x00010000 FontWriter.write_to_file(tables, path, sfnt_version: sfnt) end |
#safe_cbdt_source ⇒ Stitcher::Source?
Safe variant: returns nil instead of raising when there are multiple CBDT sources. Used inside the per-subfont glyph-copy pass where raising mid-compile would leave partial state.
52 53 54 55 56 |
# File 'lib/fontisan/stitcher/cbdt_propagator.rb', line 52 def safe_cbdt_source cbdt_source rescue MultipleCbdtSourcesError nil end |