fontisan 0.4.26 → 0.4.27
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 +4 -4
- data/TODO.improvements/README.md +6 -6
- data/lib/fontisan/stitcher/cbdt_propagator.rb +76 -37
- data/lib/fontisan/subset/glyph_mapping.rb +7 -2
- data/lib/fontisan/tables/cff.rb +122 -18
- data/lib/fontisan/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa4e6e2127d519f9f74aaf89b3d94e675ac09306d6d99eff7735e4789830bd26
|
|
4
|
+
data.tar.gz: a1f2188729497aff910dcc70e59856e3c72858e33323b4871f626d8443ff505a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09ec3032389068dd5870d82c03a89f1b318ad3b76b9b26c1f509d33419fc8728b6d8ff4f3c0e16798380312b003f79118501c9155f581f2f031c9d9893d0baba'
|
|
7
|
+
data.tar.gz: a5a3cc828bbbd463fda1b74fa7e85b110bc83c55d058a5c484f44213ea4052001c0cf958d73c3b95ec1a3564f4e4ed1cf7901cf6002e7cde8b0ad0c43e98cc04
|
data/TODO.improvements/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Each file is `NN-short-name.md` where `NN` is the priority order.
|
|
|
6
6
|
## Priorities
|
|
7
7
|
|
|
8
8
|
### P0 — Correctness gaps (open bugs / silent failures)
|
|
9
|
-
- [01 — CBDT/CBLC GID-stable propagation](01-cbdt-cblc-gid-stable-propagation.md)
|
|
10
|
-
- [02 — Collection-mode outline-priority regression](02-collection-outline-priority.md)
|
|
9
|
+
- [x] ~~[01 — CBDT/CBLC GID-stable propagation](01-cbdt-cblc-gid-stable-propagation.md)~~ ✓ Done (PR #115)
|
|
10
|
+
- [x] ~~[02 — Collection-mode outline-priority regression](02-collection-outline-priority.md)~~ ✓ Already fixed (PR #108 unskipped the test; now passing with TODO 01)
|
|
11
11
|
|
|
12
12
|
### P1 — High-value features
|
|
13
13
|
- [03 — `fontisan audit` command (identity+style+features lens)](03-fontisan-audit-command.md)
|
|
@@ -15,16 +15,16 @@ Each file is `NN-short-name.md` where `NN` is the priority order.
|
|
|
15
15
|
- [05 — OTF compiler real CFF charstrings](05-otf-compiler-real-cff.md)
|
|
16
16
|
|
|
17
17
|
### P2 — Specialist feature parity
|
|
18
|
-
- [x] ~~[07 — CPAL v1 header fields](07-cpal-v1-header-fields.md)~~ ✓ Done
|
|
18
|
+
- [x] ~~[07 — CPAL v1 header fields](07-cpal-v1-header-fields.md)~~ ✓ Done (v0.4.25)
|
|
19
|
+
- [x] ~~[08 — CFF standard string table](08-cff-standard-string-table.md)~~ ✓ Partial (SID 0-95, ASCII subset)
|
|
19
20
|
- [06 — CFF2 blend/vsindex operators](06-cff2-blend-vsindex-operators.md)
|
|
20
|
-
- [08 — CFF standard string table](08-cff-standard-string-table.md)
|
|
21
21
|
- [09 — Type 1 seac expansion](09-type1-seac-expansion.md)
|
|
22
22
|
- [10 — UFO image set + feature writers](10-ufo-image-set-feature-writers.md)
|
|
23
23
|
- [11 — kern groups.plist support](11-kern-groups-plist.md)
|
|
24
24
|
|
|
25
25
|
### P3 — Code-quality cleanup
|
|
26
|
-
- [x] ~~[13 — Split `OctokitFetcher` out of `fixture_downloader.rb`](13-split-octokit-fetcher.md)~~ ✓ Done (
|
|
27
|
-
- [12 — `cbdt_fixture.rb` full BinData conversion](12-cbdt-fixture-bindata-conversion.md)
|
|
26
|
+
- [x] ~~[13 — Split `OctokitFetcher` out of `fixture_downloader.rb`](13-split-octokit-fetcher.md)~~ ✓ Done (v0.4.24)
|
|
27
|
+
- [x] ~~[12 — `cbdt_fixture.rb` full BinData conversion](12-cbdt-fixture-bindata-conversion.md)~~ ✓ Partial (head/hhea/post converted; os2/name/hmtx/cmap remain)
|
|
28
28
|
- [14 — Rubocop baseline chip (per-namespace)](14-rubocop-baseline-chip.md)
|
|
29
29
|
|
|
30
30
|
## Convention
|
|
@@ -80,6 +80,8 @@ module Fontisan
|
|
|
80
80
|
# @param source [Stitcher::Source] the CBDT source
|
|
81
81
|
# @param target [Ufo::Font] target UFO font to receive placeholders
|
|
82
82
|
def add_placeholder_glyphs(source, target)
|
|
83
|
+
@placeholder_names = {}
|
|
84
|
+
|
|
83
85
|
ufo = source.font.is_a?(Ufo::Font) ? source.font : nil
|
|
84
86
|
if ufo
|
|
85
87
|
ufo.glyphs.each_value do |g|
|
|
@@ -104,43 +106,28 @@ module Fontisan
|
|
|
104
106
|
glyph.width = 0
|
|
105
107
|
gid_cps[gid].each { |cp| glyph.add_unicode(cp) }
|
|
106
108
|
target.layers.default_layer.add(glyph)
|
|
109
|
+
@placeholder_names[gid] = name
|
|
107
110
|
end
|
|
108
111
|
end
|
|
109
112
|
|
|
110
|
-
# Propagate
|
|
111
|
-
#
|
|
113
|
+
# Propagate CBDT/CBLC tables from the CBDT source into the
|
|
114
|
+
# compiled font at `path`, rewriting the file in place.
|
|
112
115
|
#
|
|
113
116
|
# Reads every table from the compiled font as raw bytes (bypassing
|
|
114
117
|
# BinData #table because some tables — notably CFF2 — don't yet
|
|
115
|
-
# have round-trippable BinData models), splices in CBDT/CBLC
|
|
116
|
-
# and rewrites the file.
|
|
117
|
-
#
|
|
118
|
-
# No-op when called with a non-CBDT source or nil.
|
|
118
|
+
# have round-trippable BinData models), splices in CBDT/CBLC
|
|
119
|
+
# rebuilt with compiled-font GIDs, and rewrites the file.
|
|
119
120
|
#
|
|
120
|
-
#
|
|
121
|
+
# The CBDT/CBLC rebuild uses Subset::TableStrategy::ColorBitmapSubsetter
|
|
122
|
+
# to remap every bitmap block from source GID to compiled GID.
|
|
123
|
+
# This ensures CBLC's IndexSubTableArray references the compiled
|
|
124
|
+
# font's actual GIDs, not the source's.
|
|
121
125
|
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
126
|
+
# Falls back to raw-byte copy when the GID mapping can't be
|
|
127
|
+
# resolved (no placeholder names recorded, or compiled post table
|
|
128
|
+
# is v3.0 with no glyph names).
|
|
125
129
|
#
|
|
126
|
-
#
|
|
127
|
-
# UniqueGlyphName when their default "gid{N}" name collides
|
|
128
|
-
# with outline glyphs sharing the same donor-gid scheme
|
|
129
|
-
# (see Layer's naming contract).
|
|
130
|
-
# 2. The compiler assigns GIDs in target-namespace order, which
|
|
131
|
-
# is not the same as source-GID order once renaming happens.
|
|
132
|
-
#
|
|
133
|
-
# The bitmaps line up correctly only when the CBDT source and
|
|
134
|
-
# every outline source cover disjoint codepoint ranges (the
|
|
135
|
-
# Essenfont TTC case: emoji vs CJK Ext G). When ranges overlap,
|
|
136
|
-
# CBLC's source-GID-indexed bitmaps may point at the wrong
|
|
137
|
-
# compiled glyphs and the colour rendering for affected
|
|
138
|
-
# codepoints will fall back to outlines.
|
|
139
|
-
#
|
|
140
|
-
# A proper fix requires a CBLC rebuild pass: walk the compiled
|
|
141
|
-
# font's cmap to find the new GID for every CBDT-covered source
|
|
142
|
-
# glyph, then rewrite CBLC's IndexSubTableArray + IndexSubTable
|
|
143
|
-
# offsets to match. Tracked as a follow-up.
|
|
130
|
+
# No-op when called with a non-CBDT source or nil.
|
|
144
131
|
#
|
|
145
132
|
# @param source [Stitcher::Source, nil] the CBDT source
|
|
146
133
|
# @param path [String] compiled font file to rewrite
|
|
@@ -149,25 +136,77 @@ module Fontisan
|
|
|
149
136
|
|
|
150
137
|
compiled = FontLoader.load(path)
|
|
151
138
|
|
|
152
|
-
# Read every table as raw bytes straight from the file's table
|
|
153
|
-
# directory. We deliberately bypass #table (which parses via
|
|
154
|
-
# BinData) because some tables — notably CFF2 — don't yet have
|
|
155
|
-
# round-trippable BinData models; calling #table on them returns
|
|
156
|
-
# nil and would silently drop them from the rewritten font.
|
|
157
139
|
tables = {}
|
|
158
140
|
compiled.table_names.each do |tag|
|
|
159
141
|
raw = compiled.table_data[tag]
|
|
160
142
|
tables[tag] = raw if raw
|
|
161
143
|
end
|
|
162
144
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
tables["
|
|
166
|
-
tables["CBLC"] = cblc_bytes if cblc_bytes
|
|
145
|
+
rebuilt = rebuild_color_tables(source, compiled)
|
|
146
|
+
tables["CBDT"] = rebuilt[:cbdt] if rebuilt[:cbdt]
|
|
147
|
+
tables["CBLC"] = rebuilt[:cblc] if rebuilt[:cblc]
|
|
167
148
|
|
|
168
149
|
sfnt = tables.key?("CFF ") || tables.key?("CFF2") ? 0x4F54544F : 0x00010000
|
|
169
150
|
FontWriter.write_to_file(tables, path, sfnt_version: sfnt)
|
|
170
151
|
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
# Rebuild CBDT/CBLC bytes with compiled-font GIDs instead of
|
|
156
|
+
# source GIDs. Delegates to Subset::TableStrategy::ColorBitmapSubsetter
|
|
157
|
+
# which already implements the offset-remap algorithm for the
|
|
158
|
+
# subsetter — the Stitcher case is the same algorithm with a
|
|
159
|
+
# different GID mapping (source → compiled instead of source →
|
|
160
|
+
# sequential subset GID).
|
|
161
|
+
#
|
|
162
|
+
# Falls back to raw-byte copy when the GID mapping can't be
|
|
163
|
+
# resolved (no placeholder names recorded, or compiled post/cmap
|
|
164
|
+
# can't reverse-map them).
|
|
165
|
+
def rebuild_color_tables(source, compiled)
|
|
166
|
+
source_cbdt = source.raw_table_bytes("CBDT")
|
|
167
|
+
source_cblc = source.raw_table_bytes("CBLC")
|
|
168
|
+
return {} unless source_cbdt && source_cblc
|
|
169
|
+
|
|
170
|
+
gid_map = resolve_gid_mapping(compiled)
|
|
171
|
+
return raw_bytes_fallback(source_cbdt, source_cblc) unless gid_map
|
|
172
|
+
|
|
173
|
+
mapping = Subset::GlyphMapping.new(mapping: gid_map)
|
|
174
|
+
subsetter = Subset::TableStrategy::ColorBitmapSubsetter.new(
|
|
175
|
+
font: source.font, mapping: mapping,
|
|
176
|
+
).build
|
|
177
|
+
|
|
178
|
+
{ cbdt: subsetter.cbdt_bytes, cblc: subsetter.cblc_bytes }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Build {source_gid => compiled_gid} from the placeholder names
|
|
182
|
+
# recorded during add_placeholder_glyphs. Walks the compiled
|
|
183
|
+
# font's post table to find each placeholder's compiled GID.
|
|
184
|
+
# Returns nil when the mapping can't be resolved (e.g., no
|
|
185
|
+
# placeholders recorded, or post v3.0 with no names).
|
|
186
|
+
def resolve_gid_mapping(compiled)
|
|
187
|
+
return nil unless @placeholder_names && !@placeholder_names.empty?
|
|
188
|
+
|
|
189
|
+
post = compiled.table("post")
|
|
190
|
+
return nil unless post
|
|
191
|
+
|
|
192
|
+
names = post.glyph_names
|
|
193
|
+
return nil unless names
|
|
194
|
+
|
|
195
|
+
name_to_gid = {}
|
|
196
|
+
names.each_with_index { |name, gid| name_to_gid[name] = gid }
|
|
197
|
+
|
|
198
|
+
gid_map = {}
|
|
199
|
+
@placeholder_names.each do |source_gid, placeholder_name|
|
|
200
|
+
compiled_gid = name_to_gid[placeholder_name]
|
|
201
|
+
gid_map[source_gid] = compiled_gid if compiled_gid
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
gid_map.empty? ? nil : gid_map
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def raw_bytes_fallback(cbdt_bytes, cblc_bytes)
|
|
208
|
+
{ cbdt: cbdt_bytes, cblc: cblc_bytes }
|
|
209
|
+
end
|
|
171
210
|
end
|
|
172
211
|
end
|
|
173
212
|
end
|
|
@@ -52,12 +52,17 @@ module Fontisan
|
|
|
52
52
|
#
|
|
53
53
|
# @example Create mapping that retains GIDs
|
|
54
54
|
# mapping = GlyphMapping.new([0, 3, 5, 10], retain_gids: true)
|
|
55
|
-
def initialize(old_glyph_ids, retain_gids: false)
|
|
55
|
+
def initialize(old_glyph_ids = [], retain_gids: false, mapping: nil)
|
|
56
56
|
@old_to_new = {}
|
|
57
57
|
@new_to_old = {}
|
|
58
58
|
@retain_gids = retain_gids
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
if mapping
|
|
61
|
+
@old_to_new = mapping
|
|
62
|
+
@new_to_old = mapping.invert
|
|
63
|
+
else
|
|
64
|
+
build_mappings(old_glyph_ids)
|
|
65
|
+
end
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
# Get new glyph ID for an old glyph ID
|
data/lib/fontisan/tables/cff.rb
CHANGED
|
@@ -408,30 +408,134 @@ module Fontisan
|
|
|
408
408
|
true
|
|
409
409
|
end
|
|
410
410
|
|
|
411
|
+
# Adobe Standard Encoding glyph names that map 1:1 to CFF SIDs
|
|
412
|
+
# 0..95. Source: Adobe CFF Specification 1.0 (TN 5176), Appendix A.
|
|
413
|
+
# These cover the printable ASCII range plus .notdef — the most
|
|
414
|
+
# common glyph names encountered in CFF fonts.
|
|
415
|
+
STANDARD_STRINGS_ASCII = [
|
|
416
|
+
".notdef", # SID 0
|
|
417
|
+
"space", # SID 1 (code 32)
|
|
418
|
+
"exclam", # SID 2 (code 33)
|
|
419
|
+
"quotedbl", # SID 3 (code 34)
|
|
420
|
+
"numbersign", # SID 4 (code 35)
|
|
421
|
+
"dollar", # SID 5 (code 36)
|
|
422
|
+
"percent", # SID 6 (code 37)
|
|
423
|
+
"ampersand", # SID 7 (code 38)
|
|
424
|
+
"quoteright", # SID 8 (code 39)
|
|
425
|
+
"parenleft", # SID 9 (code 40)
|
|
426
|
+
"parenright", # SID 10 (code 41)
|
|
427
|
+
"asterisk", # SID 11 (code 42)
|
|
428
|
+
"plus", # SID 12 (code 43)
|
|
429
|
+
"comma", # SID 13 (code 44)
|
|
430
|
+
"hyphen", # SID 14 (code 45)
|
|
431
|
+
"period", # SID 15 (code 46)
|
|
432
|
+
"slash", # SID 16 (code 47)
|
|
433
|
+
"zero", # SID 17 (code 48)
|
|
434
|
+
"one", # SID 18 (code 49)
|
|
435
|
+
"two", # SID 19 (code 50)
|
|
436
|
+
"three", # SID 20 (code 51)
|
|
437
|
+
"four", # SID 21 (code 52)
|
|
438
|
+
"five", # SID 22 (code 53)
|
|
439
|
+
"six", # SID 23 (code 54)
|
|
440
|
+
"seven", # SID 24 (code 55)
|
|
441
|
+
"eight", # SID 25 (code 56)
|
|
442
|
+
"nine", # SID 26 (code 57)
|
|
443
|
+
"colon", # SID 27 (code 58)
|
|
444
|
+
"semicolon", # SID 28 (code 59)
|
|
445
|
+
"less", # SID 29 (code 60)
|
|
446
|
+
"equal", # SID 30 (code 61)
|
|
447
|
+
"greater", # SID 31 (code 62)
|
|
448
|
+
"question", # SID 32 (code 63)
|
|
449
|
+
"at", # SID 33 (code 64)
|
|
450
|
+
"A", # SID 34 (code 65)
|
|
451
|
+
"B", # SID 35 (code 66)
|
|
452
|
+
"C", # SID 36 (code 67)
|
|
453
|
+
"D", # SID 37 (code 68)
|
|
454
|
+
"E", # SID 38 (code 69)
|
|
455
|
+
"F", # SID 39 (code 70)
|
|
456
|
+
"G", # SID 40 (code 71)
|
|
457
|
+
"H", # SID 41 (code 72)
|
|
458
|
+
"I", # SID 42 (code 73)
|
|
459
|
+
"J", # SID 43 (code 74)
|
|
460
|
+
"K", # SID 44 (code 75)
|
|
461
|
+
"L", # SID 45 (code 76)
|
|
462
|
+
"M", # SID 46 (code 77)
|
|
463
|
+
"N", # SID 47 (code 78)
|
|
464
|
+
"O", # SID 48 (code 79)
|
|
465
|
+
"P", # SID 49 (code 80)
|
|
466
|
+
"Q", # SID 50 (code 81)
|
|
467
|
+
"R", # SID 51 (code 82)
|
|
468
|
+
"S", # SID 52 (code 83)
|
|
469
|
+
"T", # SID 53 (code 84)
|
|
470
|
+
"U", # SID 54 (code 85)
|
|
471
|
+
"V", # SID 55 (code 86)
|
|
472
|
+
"W", # SID 56 (code 87)
|
|
473
|
+
"X", # SID 57 (code 88)
|
|
474
|
+
"Y", # SID 58 (code 89)
|
|
475
|
+
"Z", # SID 59 (code 90)
|
|
476
|
+
"bracketleft", # SID 60 (code 91)
|
|
477
|
+
"backslash", # SID 61 (code 92)
|
|
478
|
+
"bracketright", # SID 62 (code 93)
|
|
479
|
+
"asciicircum", # SID 63 (code 94)
|
|
480
|
+
"underscore", # SID 64 (code 95)
|
|
481
|
+
"quoteleft", # SID 65 (code 96)
|
|
482
|
+
"a", # SID 66 (code 97)
|
|
483
|
+
"b", # SID 67 (code 98)
|
|
484
|
+
"c", # SID 68 (code 99)
|
|
485
|
+
"d", # SID 69 (code 100)
|
|
486
|
+
"e", # SID 70 (code 101)
|
|
487
|
+
"f", # SID 71 (code 102)
|
|
488
|
+
"g", # SID 72 (code 103)
|
|
489
|
+
"h", # SID 73 (code 104)
|
|
490
|
+
"i", # SID 74 (code 105)
|
|
491
|
+
"j", # SID 75 (code 106)
|
|
492
|
+
"k", # SID 76 (code 107)
|
|
493
|
+
"l", # SID 77 (code 108)
|
|
494
|
+
"m", # SID 78 (code 109)
|
|
495
|
+
"n", # SID 79 (code 110)
|
|
496
|
+
"o", # SID 80 (code 111)
|
|
497
|
+
"p", # SID 81 (code 112)
|
|
498
|
+
"q", # SID 82 (code 113)
|
|
499
|
+
"r", # SID 83 (code 114)
|
|
500
|
+
"s", # SID 84 (code 115)
|
|
501
|
+
"t", # SID 85 (code 116)
|
|
502
|
+
"u", # SID 86 (code 117)
|
|
503
|
+
"v", # SID 87 (code 118)
|
|
504
|
+
"w", # SID 88 (code 119)
|
|
505
|
+
"x", # SID 89 (code 120)
|
|
506
|
+
"y", # SID 90 (code 121)
|
|
507
|
+
"z", # SID 91 (code 122)
|
|
508
|
+
"braceleft", # SID 92 (code 123)
|
|
509
|
+
"bar", # SID 93 (code 124)
|
|
510
|
+
"braceright", # SID 94 (code 125)
|
|
511
|
+
"asciitilde", # SID 95 (code 126)
|
|
512
|
+
].freeze
|
|
513
|
+
|
|
514
|
+
# First SID outside the ASCII subset. SIDs in
|
|
515
|
+
# EXTENDED_STRINGS_START..390 cover ligatures, currency symbols,
|
|
516
|
+
# Cyrillic (afii*), etc. Resolving those requires the full
|
|
517
|
+
# Adobe CFF spec Appendix A table; tracked as a follow-up to
|
|
518
|
+
# avoid embedding unverified data.
|
|
519
|
+
EXTENDED_STRINGS_START = STANDARD_STRINGS_ASCII.length
|
|
520
|
+
|
|
411
521
|
private
|
|
412
522
|
|
|
413
|
-
# Get a standard CFF string by SID
|
|
414
|
-
#
|
|
415
|
-
# This is a placeholder that returns a generic string.
|
|
416
|
-
# A complete implementation would include all 391 standard strings
|
|
417
|
-
# from CFF spec Appendix A.
|
|
523
|
+
# Get a standard CFF string by SID (0-390).
|
|
418
524
|
#
|
|
419
|
-
#
|
|
525
|
+
# The ASCII subset (SID 0-95) is fully covered. Extended SIDs
|
|
526
|
+
# (96-390) cover ligatures, currency, Cyrillic, etc., and
|
|
527
|
+
# require the full Adobe CFF spec Appendix A table to fill in
|
|
528
|
+
# correctly. Until that follow-up lands, extended SIDs return
|
|
529
|
+
# nil so callers can detect the gap and fall back to the
|
|
530
|
+
# custom string index.
|
|
420
531
|
#
|
|
421
532
|
# @param sid [Integer] String ID (0-390)
|
|
422
|
-
# @return [String]
|
|
533
|
+
# @return [String, nil] standard string, or nil for uncovered SIDs
|
|
423
534
|
def standard_string(sid)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
when 0 then ".notdef"
|
|
429
|
-
when 1 then "space"
|
|
430
|
-
when 2 then "exclam"
|
|
431
|
-
# ... (388 more standard strings)
|
|
432
|
-
else
|
|
433
|
-
".notdef" # Fallback
|
|
434
|
-
end
|
|
535
|
+
return nil if sid.negative?
|
|
536
|
+
return STANDARD_STRINGS_ASCII[sid] if sid < EXTENDED_STRINGS_START
|
|
537
|
+
|
|
538
|
+
nil
|
|
435
539
|
end
|
|
436
540
|
|
|
437
541
|
# Get the Charset for a specific font
|
data/lib/fontisan/version.rb
CHANGED