Class: Fontisan::Subset::TableSubsetter
- Inherits:
-
Object
- Object
- Fontisan::Subset::TableSubsetter
- Defined in:
- lib/fontisan/subset/table_subsetter.rb
Overview
Table-specific subsetting strategies
This class provides methods for subsetting individual font tables according to the glyph mapping. Each table type has different subsetting requirements:
- maxp: Update glyph count
- hhea: Update horizontal metrics count
- hmtx: Subset horizontal metrics
- glyf: Subset glyph data and remap component references
- loca: Rebuild glyph location index
- cmap: Remap character to glyph mappings
- post: Optionally drop glyph names
- name: Pass through (no subsetting needed)
- head: Update checksum adjustment (handled by FontWriter)
- OS/2: Optionally prune Unicode ranges
The subsetting process preserves font validity by updating all references and recalculating offsets and checksums.
Instance Attribute Summary collapse
-
#font ⇒ TrueTypeFont, OpenTypeFont
readonly
Font instance being subset.
-
#mapping ⇒ GlyphMapping
readonly
Glyph ID mapping (old GID → new GID).
-
#options ⇒ Options
readonly
Subsetting options.
Instance Method Summary collapse
-
#initialize(font, mapping, options) ⇒ TableSubsetter
constructor
Initialize table subsetter.
-
#subset_cmap(table) ⇒ String
Subset cmap table (remap character to glyph mappings).
-
#subset_glyf(table) ⇒ String
Subset glyf table (subset glyph data).
-
#subset_head(_table) ⇒ String
Subset head table (pass through).
-
#subset_hhea(table, hmtx = nil) ⇒ String
Subset hhea table (update numberOfHMetrics + advanceWidthMax).
-
#subset_hmtx(table) ⇒ String
Subset hmtx table (subset horizontal metrics).
-
#subset_loca(_table) ⇒ String
Subset loca table (rebuild glyph location index).
-
#subset_maxp(table) ⇒ String
Subset maxp table (update numGlyphs).
-
#subset_name(_table) ⇒ String
Subset name table (pass through).
-
#subset_os2(_table) ⇒ String
Subset OS/2 table (optionally prune Unicode ranges).
-
#subset_post(table) ⇒ String
Subset post table (optionally drop glyph names).
-
#subset_table(tag, table) ⇒ String
Subset a table by tag.
Constructor Details
#initialize(font, mapping, options) ⇒ TableSubsetter
Initialize table subsetter
53 54 55 56 57 58 59 60 61 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 53 def initialize(font, mapping, ) @font = font @mapping = mapping @options = @glyf_data = nil @loca_offsets = nil @subset_bbox = nil # [xMin, yMin, xMax, yMax] over actual subset glyphs @subset_max_advance = 0 # largest advanceWidth in subset hmtx end |
Instance Attribute Details
#font ⇒ TrueTypeFont, OpenTypeFont (readonly)
Font instance being subset
38 39 40 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 38 def font @font end |
#mapping ⇒ GlyphMapping (readonly)
Glyph ID mapping (old GID → new GID)
42 43 44 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 42 def mapping @mapping end |
#options ⇒ Options (readonly)
Subsetting options
46 47 48 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 46 def @options end |
Instance Method Details
#subset_cmap(table) ⇒ String
Subset cmap table (remap character to glyph mappings)
Builds new cmap table with only mappings for glyphs in the subset. Updates glyph IDs to new values from the mapping.
235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 235 def subset_cmap(table) # Get old mappings old_mappings = table.unicode_mappings new_mappings = {} # Remap to new glyph IDs old_mappings.each do |char_code, old_gid| new_gid = mapping.new_id(old_gid) new_mappings[char_code] = new_gid if new_gid end # Build cmap binary with new mappings build_cmap_binary(new_mappings) end |
#subset_glyf(table) ⇒ String
Subset glyf table (subset glyph data)
Extracts glyph data for subset glyphs and remaps component references in compound glyphs. Also builds loca offsets.
190 191 192 193 194 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 190 def subset_glyf(table) # Build glyf and loca together build_glyf_and_loca(table) @glyf_data end |
#subset_head(_table) ⇒ String
Subset head table (pass through)
head table will have checksum updated by FontWriter, no subsetting needed.
Subset head table (recompute bbox from actual subset glyphs)
The source TTC's head.xMin/yMin/xMax/yMax covers every donor font in the collection — far larger than any per-block subset. Browsers and layout engines use head bbox (plus hhea + OS/2) to compute line height and clip text; a too-large bbox makes glyphs render at the wrong visual size.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 295 def subset_head(_table) # Trigger glyf build (which populates @subset_bbox) if not done. glyf = font.table("glyf") build_glyf_and_loca(glyf) unless @glyf_data data = font.table_data["head"].dup if @subset_bbox x_min, y_min, x_max, y_max = @subset_bbox # head layout: xMin at offset 36, yMin 38, xMax 40, yMax 42 # (each int16, big-endian, signed) data[36, 8] = [x_min, y_min, x_max, y_max].pack("n4") end data end |
#subset_hhea(table, hmtx = nil) ⇒ String
Subset hhea table (update numberOfHMetrics + advanceWidthMax)
Updates numberOfHMetrics to reflect the subset's glyph count and recomputes advanceWidthMax from the subset's actual hmtx. The source TTC's advanceWidthMax covers every donor font and is far larger than any per-block subset needs.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 125 def subset_hhea(table, hmtx = nil) data = table.to_binary_s.dup # Calculate new numberOfHMetrics new_num_h_metrics = if hmtx&.h_metrics hmtx.h_metrics.size else calculate_number_of_h_metrics end # numberOfHMetrics at offset 34 (uint16) data[34, 2] = [new_num_h_metrics].pack("n") # advanceWidthMax at offset 10 (uint16). Recompute from subset # hmtx so a per-block subset doesn't keep the source TTC's # max (which can be 4x larger than any glyph in the subset). # Tables are processed alphabetically (hhea before hmtx), so # we read hmtx directly here rather than relying on a cached # value from subset_hmtx. new_max = compute_subset_max_advance data[10, 2] = [new_max].pack("n") if new_max.positive? data end |
#subset_hmtx(table) ⇒ String
Subset hmtx table (subset horizontal metrics)
Builds new hmtx table with metrics for subset glyphs only, preserving the order of the glyph mapping.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 157 def subset_hmtx(table) # Ensure hmtx is parsed unless table.parsed? hhea = font.table("hhea") maxp = font.table("maxp") table.parse_with_context(hhea.number_of_h_metrics, maxp.num_glyphs) end # Build new hmtx data data = String.new(encoding: Encoding::BINARY) max_advance = 0 mapping.old_ids.each do |old_id| metric = table.metric_for(old_id) next unless metric advance = metric[:advance_width] max_advance = advance if advance && advance > max_advance data << [advance].pack("n") data << [metric[:lsb]].pack("n") end @subset_max_advance = max_advance data end |
#subset_loca(_table) ⇒ String
Subset loca table (rebuild glyph location index)
Builds new loca table based on subset glyph offsets. Must be called after subset_glyf.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 203 def subset_loca(_table) # Build glyf and loca together if not already done glyf = font.table("glyf") build_glyf_and_loca(glyf) unless @loca_offsets head = font.table("head") format = head.index_to_loc_format data = String.new(encoding: Encoding::BINARY) if format.zero? # Short format: offsets / 2 as uint16 @loca_offsets.each do |offset| data << [offset / 2].pack("n") end else # Long format: offsets as uint32 @loca_offsets.each do |offset| data << [offset].pack("N") end end data end |
#subset_maxp(table) ⇒ String
Subset maxp table (update numGlyphs)
Updates the numGlyphs field to reflect the number of glyphs in the subset font.
106 107 108 109 110 111 112 113 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 106 def subset_maxp(table) data = table.to_binary_s.dup # Update numGlyphs field (at offset 4, uint16) data[4, 2] = [mapping.size].pack("n") data end |
#subset_name(_table) ⇒ String
Subset name table (pass through)
Name table doesn't require subsetting, pass through unchanged.
273 274 275 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 273 def subset_name(_table) font.table_data["name"] end |
#subset_os2(_table) ⇒ String
Subset OS/2 table (optionally prune Unicode ranges)
If unicode_ranges option is set, updates Unicode range bits to reflect only the characters in the subset.
319 320 321 322 323 324 325 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 319 def subset_os2(_table) if .unicode_ranges # TODO: Implement Unicode range pruning # For now, pass through end font.table_data["OS/2"] end |
#subset_post(table) ⇒ String
Subset post table (optionally drop glyph names)
If drop_names option is set, converts to post version 3.0 (no glyph names). Otherwise passes through unchanged.
257 258 259 260 261 262 263 264 265 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 257 def subset_post(table) if .drop_names # Build post table version 3.0 (no glyph names) build_post_v3(table) else # Keep as-is font.table_data["post"] end end |
#subset_table(tag, table) ⇒ String
Subset a table by tag
Delegates to table-specific subsetting methods. Unknown tables are passed through unchanged.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/fontisan/subset/table_subsetter.rb', line 71 def subset_table(tag, table) case tag when "maxp" subset_maxp(table) when "hhea" subset_hhea(table) when "hmtx" subset_hmtx(table) when "loca" subset_loca(table) when "glyf" subset_glyf(table) when "cmap" subset_cmap(table) when "post" subset_post(table) when "name" subset_name(table) when "head" subset_head(table) when "OS/2" subset_os2(table) else # Unknown tables pass through unchanged font.table_data[tag] end end |