fontisan 0.4.8 → 0.4.9
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/README.adoc +116 -2
- data/docs/.vitepress/config.ts +1 -0
- data/docs/COLLECTION_VALIDATION.adoc +61 -0
- data/docs/STITCHER_GUIDE.adoc +138 -1
- data/docs/cli/convert.md +44 -2
- data/docs/cli/index.md +19 -3
- data/docs/cli/validate-collection.md +95 -0
- data/docs/cli/validate.md +8 -0
- data/lib/fontisan/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38b49343b74d1876d57ec13992fc49fdc2879a44d1f0fe3c6529bd3a2f8f7a75
|
|
4
|
+
data.tar.gz: cc69156566e86294177d9046f39ebde8334f87807da38a7a5dd83ddd2eea8247
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2822ae02861c4183bbd42ded8005fa17d28d59175155767413f94b4400faaf28fb61dad38736f3fcd11235746e1b254151c8e8918480b5ccb146882742c3ec99
|
|
7
|
+
data.tar.gz: eefb0dbf8e3360e7b8aeb89dfcfbf62425c255075ee1a3d2c030cd2551f90f08d980f7ea7b8c56c382b0a1cb69933654a57fc4e57d069ebe6809ace84e7d0b42
|
data/README.adoc
CHANGED
|
@@ -1260,6 +1260,42 @@ Status: INVALID
|
|
|
1260
1260
|
----
|
|
1261
1261
|
====
|
|
1262
1262
|
|
|
1263
|
+
=== Collection structural validation (`validate-collection`)
|
|
1264
|
+
|
|
1265
|
+
For collection-level structural checks (face count, per-face glyph cap,
|
|
1266
|
+
optional cmap-union size) without running the full per-face profile
|
|
1267
|
+
machinery, use the dedicated `validate-collection` command:
|
|
1268
|
+
|
|
1269
|
+
[source,shell]
|
|
1270
|
+
----
|
|
1271
|
+
# Glyph-cap-only check (default)
|
|
1272
|
+
fontisan validate-collection family.ttc
|
|
1273
|
+
|
|
1274
|
+
# Require exactly 5 faces
|
|
1275
|
+
fontisan validate-collection family.ttc --expected-faces 5
|
|
1276
|
+
|
|
1277
|
+
# Tighter per-face glyph cap + minimum cmap coverage
|
|
1278
|
+
fontisan validate-collection family.ttc \
|
|
1279
|
+
--max-glyphs 60000 \
|
|
1280
|
+
--expected-cmap-union 100000
|
|
1281
|
+
----
|
|
1282
|
+
|
|
1283
|
+
Exit code is `0` when every requested check passes, `1` when any
|
|
1284
|
+
check fails. With no options, only the per-face glyph cap is checked
|
|
1285
|
+
(default `65,535`).
|
|
1286
|
+
|
|
1287
|
+
This command is intentionally separate from `fontisan validate PATH`
|
|
1288
|
+
(MECE): `validate` runs profile-based per-face checks;
|
|
1289
|
+
`validate-collection` runs collection-level structural checks. They
|
|
1290
|
+
do not share a subcommand tree.
|
|
1291
|
+
|
|
1292
|
+
For programmatic access to the same metadata, see
|
|
1293
|
+
`Fontisan::Collection::Reader` — the read-only counterpart to
|
|
1294
|
+
`Collection::Builder`. It opens a TTC/OTC/dfont and exposes
|
|
1295
|
+
`face_count`, per-face `stats` (glyph/codepoint counts), and the
|
|
1296
|
+
cmap union across all faces, without hand-rolling the TTC header
|
|
1297
|
+
bytes.
|
|
1298
|
+
|
|
1263
1299
|
=== Ruby API usage
|
|
1264
1300
|
|
|
1265
1301
|
==== Architecture
|
|
@@ -2187,6 +2223,29 @@ fontisan convert font.ttf --to woff2 --output font.woff2 \
|
|
|
2187
2223
|
fontisan convert font.ttf --to woff --output font.woff --zlib-level 9
|
|
2188
2224
|
----
|
|
2189
2225
|
|
|
2226
|
+
`--to` accepts multiple targets so a single invocation produces N
|
|
2227
|
+
output files from one input font. Pass a comma-separated list
|
|
2228
|
+
(`--to woff,woff2`) or repeat the flag (`--to woff --to woff2`).
|
|
2229
|
+
Duplicates are deduplicated.
|
|
2230
|
+
|
|
2231
|
+
[source,shell]
|
|
2232
|
+
----
|
|
2233
|
+
# Emit both WOFF and WOFF2 in one invocation
|
|
2234
|
+
fontisan convert font.ttf --to woff,woff2 --output font
|
|
2235
|
+
# → font.woff, font.woff2
|
|
2236
|
+
----
|
|
2237
|
+
|
|
2238
|
+
Output-path rules for multi-format:
|
|
2239
|
+
|
|
2240
|
+
* `--output out` (no extension) + N formats → append `.<format>` per
|
|
2241
|
+
target (`out.woff`, `out.woff2`).
|
|
2242
|
+
* `--output out.ttf` (extension present) + N formats → exits 1
|
|
2243
|
+
(ambiguous which format gets the extension).
|
|
2244
|
+
* `--output out.ttf` + one format → use as given (existing behaviour).
|
|
2245
|
+
|
|
2246
|
+
Multi-format is single-font → single-font only. Combining multi-format
|
|
2247
|
+
with collection input (TTC/OTC/dfont) exits 1.
|
|
2248
|
+
|
|
2190
2249
|
For detailed information on all available options and conversion scenarios,
|
|
2191
2250
|
see the link:docs/CONVERSION_GUIDE.adoc[Conversion Guide].
|
|
2192
2251
|
|
|
@@ -2499,8 +2558,63 @@ stitcher.add_source(:noto_cjk, Fontisan::FontLoader.load("NotoSansCJK.ttf"))
|
|
|
2499
2558
|
stitcher.include_range(0x41..0x5A, from: :noto_sans, into: :latin)
|
|
2500
2559
|
stitcher.include_range(0x4E00..0x9FFF, from: :noto_cjk, into: :cjk)
|
|
2501
2560
|
|
|
2502
|
-
# Write as OTC with CFF2 subfonts and table deduplication
|
|
2503
|
-
|
|
2561
|
+
# Write as OTC with CFF2 subfonts and table deduplication.
|
|
2562
|
+
# Returns a Stitcher::CollectionResult (path, bytes, per-subfont stats).
|
|
2563
|
+
result = stitcher.write_collection("out.otc", format: :otf2)
|
|
2564
|
+
result.face_count # => 2
|
|
2565
|
+
result.subfonts.map(&:name) # => [:latin, :cjk]
|
|
2566
|
+
result.subfonts.first.glyph_count # maxp.num_glyphs of face 0
|
|
2567
|
+
----
|
|
2568
|
+
|
|
2569
|
+
=== Donor maps and partitioning
|
|
2570
|
+
|
|
2571
|
+
`include_codepoints_map(cp_map, into:)` takes a `{codepoint => donor}`
|
|
2572
|
+
map and groups internally — one call handles N codepoints from M
|
|
2573
|
+
donors:
|
|
2574
|
+
|
|
2575
|
+
[source,ruby]
|
|
2576
|
+
----
|
|
2577
|
+
stitcher.include_codepoints_map(
|
|
2578
|
+
{ 0x41 => :noto_sans, 0x4E00 => :noto_cjk },
|
|
2579
|
+
into: :main,
|
|
2580
|
+
)
|
|
2581
|
+
----
|
|
2582
|
+
|
|
2583
|
+
For automatic plane-aware splitting, use
|
|
2584
|
+
`Stitcher::PartitionStrategy::ByPlane`. It groups codepoints by
|
|
2585
|
+
Unicode plane and sub-splits planes that overflow the cap along the
|
|
2586
|
+
large CJK-extension block boundaries:
|
|
2587
|
+
|
|
2588
|
+
[source,ruby]
|
|
2589
|
+
----
|
|
2590
|
+
require "fontisan/stitcher/partition_strategy"
|
|
2591
|
+
|
|
2592
|
+
blueprint = Fontisan::Stitcher::PartitionStrategy::ByPlane.new
|
|
2593
|
+
.call(cp_map, cap: 65_484)
|
|
2594
|
+
blueprint.apply_to(stitcher) # declares one subfont per partition
|
|
2595
|
+
----
|
|
2596
|
+
|
|
2597
|
+
If a single CJK extension block alone exceeds the cap, ByPlane raises
|
|
2598
|
+
`Fontisan::PartitionCapExceededError`. ByBlock/ByScript partitioners
|
|
2599
|
+
(via Unicode Blocks.txt / Scripts.txt) are tracked as a follow-up —
|
|
2600
|
+
the framework is open for them.
|
|
2601
|
+
|
|
2602
|
+
=== Per-subfont metadata
|
|
2603
|
+
|
|
2604
|
+
`Ufo::Info.for_subfont(family:, subfont:, version:, ...)` builds the
|
|
2605
|
+
standard name-table fields for one subfont of a collection. The
|
|
2606
|
+
family name embeds the subfont (`"essenfont SIP"`), the PostScript
|
|
2607
|
+
name uses the hyphenated form (`"essenfont-SIP"`), and the version is
|
|
2608
|
+
parsed into `version_major` / `version_minor` per the UFO
|
|
2609
|
+
major.minor shape.
|
|
2610
|
+
|
|
2611
|
+
[source,ruby]
|
|
2612
|
+
----
|
|
2613
|
+
stitcher.set_info(
|
|
2614
|
+
Fontisan::Ufo::Info.for_subfont(
|
|
2615
|
+
family: "essenfont", subfont: :SIP, version: "0.1"
|
|
2616
|
+
).to_plist,
|
|
2617
|
+
)
|
|
2504
2618
|
----
|
|
2505
2619
|
|
|
2506
2620
|
=== CBDT/CBLC passthrough (color emoji)
|
data/docs/.vitepress/config.ts
CHANGED
|
@@ -285,6 +285,7 @@ export default defineConfig({
|
|
|
285
285
|
{ text: "convert", link: "/cli/convert" },
|
|
286
286
|
{ text: "subset", link: "/cli/subset" },
|
|
287
287
|
{ text: "validate", link: "/cli/validate" },
|
|
288
|
+
{ text: "validate-collection", link: "/cli/validate-collection" },
|
|
288
289
|
{ text: "instance", link: "/cli/instance" },
|
|
289
290
|
{ text: "export", link: "/cli/export" },
|
|
290
291
|
{ text: "dump-table", link: "/cli/dump-table" },
|
|
@@ -141,3 +141,64 @@ puts "Valid fonts: #{valid_count}"
|
|
|
141
141
|
puts "Invalid fonts: #{invalid_count}"
|
|
142
142
|
----
|
|
143
143
|
====
|
|
144
|
+
|
|
145
|
+
== Fast structural checks: `validate-collection`
|
|
146
|
+
|
|
147
|
+
For collection-level structural checks (face count, per-face glyph
|
|
148
|
+
cap, optional cmap-union size) without running the full per-face
|
|
149
|
+
profile machinery, use the dedicated `validate-collection` command:
|
|
150
|
+
|
|
151
|
+
[source,shell]
|
|
152
|
+
----
|
|
153
|
+
# Glyph-cap-only check (default)
|
|
154
|
+
fontisan validate-collection family.ttc
|
|
155
|
+
|
|
156
|
+
# Require exactly 5 faces
|
|
157
|
+
fontisan validate-collection family.ttc --expected-faces 5
|
|
158
|
+
|
|
159
|
+
# Tighter per-face glyph cap + minimum cmap coverage
|
|
160
|
+
fontisan validate-collection family.ttc \
|
|
161
|
+
--max-glyphs 60000 \
|
|
162
|
+
--expected-cmap-union 100000
|
|
163
|
+
----
|
|
164
|
+
|
|
165
|
+
Exit code is `0` when every requested check passes, `1` when any
|
|
166
|
+
check fails. With no options, only the per-face glyph cap is checked
|
|
167
|
+
(default `65,535`).
|
|
168
|
+
|
|
169
|
+
This command is intentionally separate from `fontisan validate PATH`
|
|
170
|
+
(MECE): the two cover different validator categories — `validate`
|
|
171
|
+
runs profile-based per-face checks, `validate-collection` runs
|
|
172
|
+
collection-level structural checks. They don't share a subcommand tree.
|
|
173
|
+
|
|
174
|
+
== Reading collection metadata: `Collection::Reader`
|
|
175
|
+
|
|
176
|
+
`Fontisan::Collection::Reader` is the read-only counterpart to
|
|
177
|
+
`Collection::Builder`. It opens an existing TTC/OTC/dfont and
|
|
178
|
+
exposes per-face metadata plus the cmap union across all faces,
|
|
179
|
+
without hand-rolling the TTC header bytes.
|
|
180
|
+
|
|
181
|
+
[source,ruby]
|
|
182
|
+
----
|
|
183
|
+
reader = Fontisan::Collection::Reader.open("family.ttc")
|
|
184
|
+
|
|
185
|
+
reader.face_count # => 3
|
|
186
|
+
reader.path # => "family.ttc"
|
|
187
|
+
|
|
188
|
+
reader.each_face do |face|
|
|
189
|
+
puts face.table("maxp").num_glyphs
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# One Stats struct per face (index, glyph_count, codepoint_count, sfnt_version)
|
|
193
|
+
reader.stats.map(&:glyph_count) # => [3541, 21048, 991]
|
|
194
|
+
|
|
195
|
+
# Union of every face's cmap keys
|
|
196
|
+
reader.cmap_union.size # => 24580
|
|
197
|
+
reader.cmap_union.is_a?(Set) # => true
|
|
198
|
+
----
|
|
199
|
+
|
|
200
|
+
`Reader` delegates header parsing to `FontLoader` (which already
|
|
201
|
+
handles TTC, OTC, and dfont via `BinData`). Constructing a `Reader`
|
|
202
|
+
on a non-collection file raises `ArgumentError`. This is the API the
|
|
203
|
+
`validate-collection` command uses internally — reach for it directly
|
|
204
|
+
when you want collection metadata without invoking the CLI.
|
data/docs/STITCHER_GUIDE.adoc
CHANGED
|
@@ -25,15 +25,19 @@ There are no defaults and no after-the-fact splitting.
|
|
|
25
25
|
|
|
26
26
|
| `include_codepoints(cps, from:, into:)` | Add an explicit Array of codepoints
|
|
27
27
|
|
|
28
|
+
| `include_codepoints_map(cp_map, into:)` | Add `{codepoint => donor}` for many codepoints and donors in one call
|
|
29
|
+
|
|
28
30
|
| `include_gid(donor_gid, from:, into:)` | Add a specific GID from the source
|
|
29
31
|
|
|
30
32
|
| `include_notdef(from:, into:)` | Convenience for `include_gid(0, ...)`
|
|
31
33
|
|
|
32
34
|
| `set_info(hash)` | Override font info (family name, etc.)
|
|
33
35
|
|
|
36
|
+
| `subfont_names` | List declared subfont names (insertion order)
|
|
37
|
+
|
|
34
38
|
| `write_to(path, format:, subfont:)` | Compile one subfont to a single file
|
|
35
39
|
|
|
36
|
-
| `write_collection(path, format:)` | Compile all declared subfonts into a TTC/OTC
|
|
40
|
+
| `write_collection(path, format:)` | Compile all declared subfonts into a TTC/OTC; returns a `CollectionResult`
|
|
37
41
|
|===
|
|
38
42
|
|
|
39
43
|
`format:` is one of `:ttf`, `:otf`, `:otf2`. For collections, `:ttf`
|
|
@@ -77,6 +81,139 @@ Each subfont is compiled independently, then packed by
|
|
|
77
81
|
(head, name, OS/2, ...) are stored once and referenced from each
|
|
78
82
|
subfont's offset table.
|
|
79
83
|
|
|
84
|
+
`write_collection` returns a `Stitcher::CollectionResult` Struct with
|
|
85
|
+
the output `path`, total `bytes`, and one `Stitcher::SubfontStats`
|
|
86
|
+
per declared subfont (`name`, `glyph_count`, `codepoint_count`). Stats
|
|
87
|
+
are read from the compiled on-disk face so they reflect what was
|
|
88
|
+
actually written — including glyphs the compiler adds (e.g. `.notdef`).
|
|
89
|
+
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
result = stitcher.write_collection("out.otc", format: :otf2)
|
|
93
|
+
result.path # => "out.otc"
|
|
94
|
+
result.bytes # => File.size("out.otc")
|
|
95
|
+
result.face_count # => result.subfonts.size
|
|
96
|
+
result.subfonts.first.glyph_count # maxp.num_glyphs of face 0
|
|
97
|
+
result.subfonts.first.codepoint_count # cmap size of face 0
|
|
98
|
+
----
|
|
99
|
+
|
|
100
|
+
== Donor maps (many codepoints, many donors, one call)
|
|
101
|
+
|
|
102
|
+
`include_codepoints_map(cp_map, into:)` takes a `{codepoint => donor}`
|
|
103
|
+
map and forwards each donor group to `include_codepoints` after sorting
|
|
104
|
+
the codepoints (reproducible GID assignment). Useful when the cp →
|
|
105
|
+
donor routing comes from an external plan (Unicode block table,
|
|
106
|
+
partitioner output, coverage analysis).
|
|
107
|
+
|
|
108
|
+
[source,ruby]
|
|
109
|
+
----
|
|
110
|
+
cp_map = {
|
|
111
|
+
0x41 => :latin,
|
|
112
|
+
0x42 => :latin,
|
|
113
|
+
0x4E00 => :cjk,
|
|
114
|
+
0x4E01 => :cjk,
|
|
115
|
+
}
|
|
116
|
+
stitcher.include_codepoints_map(cp_map, into: :main)
|
|
117
|
+
# equivalent to:
|
|
118
|
+
# stitcher.include_codepoints([0x41, 0x42], from: :latin, into: :main)
|
|
119
|
+
# stitcher.include_codepoints([0x4E00, 0x4E01], from: :cjk, into: :main)
|
|
120
|
+
----
|
|
121
|
+
|
|
122
|
+
== Codepoint partitioning (PartitionStrategy)
|
|
123
|
+
|
|
124
|
+
When the codepoint set is too large for one subfont (or when you want
|
|
125
|
+
to split by Unicode plane / block / script for organizational reasons),
|
|
126
|
+
`Stitcher::PartitionStrategy` provides ready-to-use partitioner classes
|
|
127
|
+
that produce a `Blueprint` (an ordered list of `Partition`s). Each
|
|
128
|
+
partition names a target subfont and carries its codepoints and
|
|
129
|
+
donor map.
|
|
130
|
+
|
|
131
|
+
The framework is open for new partitioners — adding one is a new file
|
|
132
|
+
under `lib/fontisan/stitcher/partition_strategy/` plus an autoload
|
|
133
|
+
entry. No edits to existing partitioners required (OCP).
|
|
134
|
+
|
|
135
|
+
=== ByPlane
|
|
136
|
+
|
|
137
|
+
Groups codepoints by Unicode plane (`cp >> 16`). Each plane that fits
|
|
138
|
+
under the cap becomes one partition named `:plane_<n>` (e.g.
|
|
139
|
+
`:plane_0` for BMP, `:plane_2` for SIP). When a plane overflows the
|
|
140
|
+
cap, ByPlane sub-splits along the large CJK-extension block
|
|
141
|
+
boundaries (`CJK_Ext_B` through `CJK_Ext_F`), naming partitions
|
|
142
|
+
`:plane_<n>_a`, `:plane_<n>_b`, etc.
|
|
143
|
+
|
|
144
|
+
[source,ruby]
|
|
145
|
+
----
|
|
146
|
+
require "fontisan/stitcher/partition_strategy"
|
|
147
|
+
|
|
148
|
+
cp_map = build_coverage_map # { codepoint => donor_label, ... }
|
|
149
|
+
blueprint = Fontisan::Stitcher::PartitionStrategy::ByPlane.new
|
|
150
|
+
.call(cp_map, cap: 65_484)
|
|
151
|
+
|
|
152
|
+
blueprint.names # => [:plane_0, :plane_2, ...]
|
|
153
|
+
blueprint.partitions # => [#<Partition name=:plane_0 cps=[...] ...>, ...]
|
|
154
|
+
|
|
155
|
+
# Push every partition's bindings into the Stitcher in one go:
|
|
156
|
+
declared = blueprint.apply_to(stitcher)
|
|
157
|
+
# declared == blueprint.names
|
|
158
|
+
----
|
|
159
|
+
|
|
160
|
+
If a single CJK extension block alone exceeds `cap` (e.g. `CJK_Ext_B`
|
|
161
|
+
has ~6,592 codepoints and the cap is set to 5,000), ByPlane raises
|
|
162
|
+
`Fontisan::PartitionCapExceededError` — its codepoints are contiguous
|
|
163
|
+
and ByPlane has no finer-grained boundary to sub-split on. The caller
|
|
164
|
+
must either raise the cap, drop codepoints, or switch to a format
|
|
165
|
+
with a higher glyph limit.
|
|
166
|
+
|
|
167
|
+
The Unicode plane metadata used by ByPlane lives in
|
|
168
|
+
`Fontisan::Unicode::Plane` (`Plane.of(cp)`, `Plane.label(n)`,
|
|
169
|
+
`Plane::LARGE_CJK_BLOCKS`).
|
|
170
|
+
|
|
171
|
+
=== Partition / Blueprint
|
|
172
|
+
|
|
173
|
+
A `Partition` is a `Struct.new(:name, :cps, :donor_map, keyword_init: true)`.
|
|
174
|
+
`Partition#apply_to(stitcher)` pushes its bindings via
|
|
175
|
+
`include_codepoints_map`.
|
|
176
|
+
|
|
177
|
+
A `Blueprint` is a `Struct.new(:partitions, keyword_init: true)`;
|
|
178
|
+
`Blueprint#apply_to(stitcher)` calls `apply_to` on each partition in
|
|
179
|
+
order and returns the list of declared subfont names.
|
|
180
|
+
|
|
181
|
+
=== Future: ByBlock, ByScript
|
|
182
|
+
|
|
183
|
+
ByBlock (Unicode Blocks.txt, ~350 entries) and ByScript (Scripts.txt)
|
|
184
|
+
are tracked as a follow-up. The framework is already open for them:
|
|
185
|
+
add `by_block.rb` / `by_script.rb` under
|
|
186
|
+
`lib/fontisan/stitcher/partition_strategy/` and an autoload entry in
|
|
187
|
+
`partition_strategy.rb`.
|
|
188
|
+
|
|
189
|
+
== Per-subfont metadata (Ufo::Info.for_subfont)
|
|
190
|
+
|
|
191
|
+
`Fontisan::Ufo::Info.for_subfont(family:, subfont:, version:, ...)`
|
|
192
|
+
builds the standard name-table fields for one subfont of a collection.
|
|
193
|
+
The family name embeds the subfont name (e.g. `"MyFont CJK"`), and
|
|
194
|
+
the PostScript name uses the hyphenated form (e.g. `"MyFont-CJK"`).
|
|
195
|
+
`version` is parsed into `version_major` / `version_minor` per the
|
|
196
|
+
UFO major.minor shape (semver patch is dropped).
|
|
197
|
+
|
|
198
|
+
[source,ruby]
|
|
199
|
+
----
|
|
200
|
+
info = Fontisan::Ufo::Info.for_subfont(
|
|
201
|
+
family: "essenfont",
|
|
202
|
+
subfont: :SIP,
|
|
203
|
+
version: "0.1",
|
|
204
|
+
)
|
|
205
|
+
info.family_name # => "essenfont SIP"
|
|
206
|
+
info.postscript_font_name # => "essenfont-SIP"
|
|
207
|
+
info.postscript_full_name # => "essenfont SIP"
|
|
208
|
+
info.version_major # => 0
|
|
209
|
+
info.version_minor # => 1
|
|
210
|
+
----
|
|
211
|
+
|
|
212
|
+
Optional kwargs: `subfamily:` (default `"Regular"`), `copyright:`,
|
|
213
|
+
`trademark:`. `trademark` lands in `extras["openTypeNameTrademark"]`
|
|
214
|
+
because it is not in `Ufo::Info::STANDARD_FIELDS` yet — round-trip
|
|
215
|
+
still works through the extras channel.
|
|
216
|
+
|
|
80
217
|
== Glyph deduplication
|
|
81
218
|
|
|
82
219
|
By default the Stitcher deduplicates glyphs across all sources using
|
data/docs/cli/convert.md
CHANGED
|
@@ -32,8 +32,8 @@ These are individual font formats that can be converted:
|
|
|
32
32
|
|
|
33
33
|
| Option | Description |
|
|
34
34
|
|--------|-------------|
|
|
35
|
-
| `--to FORMAT` | Target format
|
|
36
|
-
| `--output FILE` | Output file path |
|
|
35
|
+
| `--to FORMAT[,FORMAT...]` | Target format(s): `ttf`, `otf`, `woff`, `woff2`, `type1`/`t1`, `ttc`, `otc`, `dfont`, `svg`. Pass once with comma-separated values (`--to woff,woff2`) or multiple times (`--to woff --to woff2`) for multi-format output. |
|
|
36
|
+
| `--output FILE` | Output file path (see "Output path rules" below) |
|
|
37
37
|
| `--optimize` | Enable outline optimization |
|
|
38
38
|
| `--flatten` | Flatten composite glyphs |
|
|
39
39
|
| `--zlib-level=N` | WOFF only: zlib compression level (0–9, default 6) |
|
|
@@ -46,6 +46,37 @@ The format you pick (`--to woff` vs `--to woff2`) **is** the algorithm
|
|
|
46
46
|
choice — WOFF mandates zlib, WOFF2 mandates Brotli. Passing a WOFF knob
|
|
47
47
|
to a WOFF2 target (or vice versa) exits 1 with a clear error.
|
|
48
48
|
|
|
49
|
+
## Multi-format output
|
|
50
|
+
|
|
51
|
+
`--to` accepts multiple targets so a single invocation produces N
|
|
52
|
+
output files from one input font. Both spellings work and produce
|
|
53
|
+
identical results:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Comma-separated (one --to flag)
|
|
57
|
+
fontisan convert font.ttf --to woff,woff2 --output font
|
|
58
|
+
|
|
59
|
+
# Repeated flag (Thor array form)
|
|
60
|
+
fontisan convert font.ttf --to woff --to woff2 --output font
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Duplicates are deduplicated, so `--to woff,woff2,woff` is equivalent to
|
|
64
|
+
`--to woff,woff2`.
|
|
65
|
+
|
|
66
|
+
### Output path rules
|
|
67
|
+
|
|
68
|
+
| `--output` shape | `--to` shape | Behaviour |
|
|
69
|
+
|------------------|--------------|-----------|
|
|
70
|
+
| `out.ttf` (has extension) | one format | Use as given |
|
|
71
|
+
| `out` (no extension) | one format | Append `.<format>` → `out.ttf` |
|
|
72
|
+
| `out` (no extension) | many formats | Append `.<format>` per target → `out.woff`, `out.woff2` |
|
|
73
|
+
| `out.ttf` (has extension) | many formats | **Error**: ambiguous which format gets the extension |
|
|
74
|
+
|
|
75
|
+
Multi-format is single-font → single-font only. Combining multi-format
|
|
76
|
+
with collection input (TTC/OTC/dfont) exits 1 — collections pack
|
|
77
|
+
multiple faces and N formats × M faces explodes output count. Convert
|
|
78
|
+
collections to one target format at a time.
|
|
79
|
+
|
|
49
80
|
## Common Workflows
|
|
50
81
|
|
|
51
82
|
### Convert for Web
|
|
@@ -85,6 +116,17 @@ fontisan convert font.otf --to ttf --output font.ttf
|
|
|
85
116
|
fontisan convert font.pfb --to otf --output font.otf
|
|
86
117
|
```
|
|
87
118
|
|
|
119
|
+
### Emit Multiple Web Formats in One Invocation
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Both WOFF (legacy IE9+) and WOFF2 (modern browsers) from one input
|
|
123
|
+
fontisan convert font.ttf --to woff,woff2 --output font
|
|
124
|
+
# → font.woff, font.woff2
|
|
125
|
+
|
|
126
|
+
# Same result with the repeated-flag form
|
|
127
|
+
fontisan convert font.ttf --to woff --to woff2 --output font
|
|
128
|
+
```
|
|
129
|
+
|
|
88
130
|
## Working with Collections
|
|
89
131
|
|
|
90
132
|
Collection formats (TTC, OTC, dfont) contain multiple fonts. To convert fonts from a collection:
|
data/docs/cli/index.md
CHANGED
|
@@ -40,7 +40,8 @@ Always check your font's End User License Agreement (EULA) before processing. Ma
|
|
|
40
40
|
|---------|-------------|---------|
|
|
41
41
|
| `convert` | Convert between formats | `fontisan convert input.ttf --to otf` |
|
|
42
42
|
| `subset` | Subset fonts | `fontisan subset font.ttf --chars "ABC"` |
|
|
43
|
-
| `validate` | Validate
|
|
43
|
+
| `validate` | Validate single-font or per-face quality | `fontisan validate font.ttf` |
|
|
44
|
+
| `validate-collection` | Structural checks on a TTC/OTC/dfont | `fontisan validate-collection fonts.ttc --expected-faces 5` |
|
|
44
45
|
| `instance` | Generate variable font instances | `fontisan instance var.ttf --wght 700` |
|
|
45
46
|
| `dump-table` | Extract raw table data | `fontisan dump-table font.ttf head` |
|
|
46
47
|
|
|
@@ -120,6 +121,10 @@ fontisan unicode font.ttf
|
|
|
120
121
|
```bash
|
|
121
122
|
# Convert single font to WOFF2
|
|
122
123
|
fontisan convert font.ttf --to woff2 --output font.woff2
|
|
124
|
+
|
|
125
|
+
# Emit both WOFF and WOFF2 in one invocation
|
|
126
|
+
fontisan convert font.ttf --to woff,woff2 --output font
|
|
127
|
+
# → font.woff, font.woff2
|
|
123
128
|
```
|
|
124
129
|
|
|
125
130
|
### Work with Variable Fonts
|
|
@@ -165,6 +170,16 @@ fontisan validate font.ttf --profile google_fonts
|
|
|
165
170
|
fontisan validate font.ttf --profile production
|
|
166
171
|
```
|
|
167
172
|
|
|
173
|
+
### Validate Collection Structure
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Glyph-cap-only check (default behaviour)
|
|
177
|
+
fontisan validate-collection family.ttc
|
|
178
|
+
|
|
179
|
+
# Require exactly 5 faces, max 60,000 glyphs per face
|
|
180
|
+
fontisan validate-collection family.ttc --expected-faces 5 --max-glyphs 60000
|
|
181
|
+
```
|
|
182
|
+
|
|
168
183
|
### Audit a Font (or Library)
|
|
169
184
|
|
|
170
185
|
The `audit` and `ucd` commands have been removed from fontisan. The
|
|
@@ -202,9 +217,10 @@ Detailed documentation for each command:
|
|
|
202
217
|
- [optical-size](/cli/optical-size) — Display optical size information
|
|
203
218
|
|
|
204
219
|
### Font Operations
|
|
205
|
-
- [convert](/cli/convert) — Format conversion (TTF, OTF, WOFF, WOFF2)
|
|
220
|
+
- [convert](/cli/convert) — Format conversion (TTF, OTF, WOFF, WOFF2); supports multi-format output
|
|
206
221
|
- [subset](/cli/subset) — Create character subsets
|
|
207
|
-
- [validate](/cli/validate) —
|
|
222
|
+
- [validate](/cli/validate) — Per-face profile validation
|
|
223
|
+
- [validate-collection](/cli/validate-collection) — Structural checks on TTC/OTC/dfont (face count, glyph cap, cmap union)
|
|
208
224
|
- [instance](/cli/instance) — Generate static instances from variable fonts
|
|
209
225
|
- [export](/cli/export) — Export to TTX, YAML, JSON
|
|
210
226
|
- [dump-table](/cli/dump-table) — Extract raw binary table data
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: validate-collection
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# validate-collection
|
|
6
|
+
|
|
7
|
+
Validate the structural integrity of a TTC / OTC / dfont collection:
|
|
8
|
+
face count, per-face glyph cap, and optional cmap-union size.
|
|
9
|
+
|
|
10
|
+
This is a collection-level counterpart to
|
|
11
|
+
[validate](/cli/validate), which runs profile-based per-face quality
|
|
12
|
+
checks (OpenType compliance, hint validity, etc.). Use
|
|
13
|
+
`validate-collection` when you care about the *shape* of the
|
|
14
|
+
collection (number of faces, glyph-cap overflows, codepoint coverage)
|
|
15
|
+
rather than per-face quality.
|
|
16
|
+
|
|
17
|
+
## Quick Reference
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
fontisan validate-collection <path> [options]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The command exits `0` when every requested check passes and `1` when
|
|
24
|
+
any check fails. With no options, only the per-face glyph cap is
|
|
25
|
+
checked (default `65,535`).
|
|
26
|
+
|
|
27
|
+
## Options
|
|
28
|
+
|
|
29
|
+
| Option | Default | Description |
|
|
30
|
+
|--------|---------|-------------|
|
|
31
|
+
| `--expected-faces N` | (none) | Require exactly `N` faces in the collection. |
|
|
32
|
+
| `--max-glyphs N` | `65535` | Per-face glyph cap (`maxp.numGlyphs`). Faces over this cap fail. |
|
|
33
|
+
| `--expected-cmap-union N` | (none) | Require the union of cmap codepoints across all faces to be at least `N`. |
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Glyph-cap-only check (default behaviour, no optional checks)
|
|
39
|
+
fontisan validate-collection family.ttc
|
|
40
|
+
|
|
41
|
+
# Require exactly 5 faces
|
|
42
|
+
fontisan validate-collection family.ttc --expected-faces 5
|
|
43
|
+
|
|
44
|
+
# Tighter per-face glyph cap
|
|
45
|
+
fontisan validate-collection family.ttc --max-glyphs 60000
|
|
46
|
+
|
|
47
|
+
# Require at least 100,000 codepoints covered across all faces
|
|
48
|
+
fontisan validate-collection family.ttc --expected-cmap-union 100000
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Sample output
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
face 0: 3541 glyphs ✓
|
|
55
|
+
face 1: 21048 glyphs ✓
|
|
56
|
+
face 2: 991 glyphs ✓
|
|
57
|
+
all 3 faces within 65535-glyph cap ✓
|
|
58
|
+
face_count: ✓
|
|
59
|
+
cmap union: 24580 cps ✓
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
When a check fails, the marker becomes `✗` and a message is printed
|
|
63
|
+
in parentheses; the exit code is `1`.
|
|
64
|
+
|
|
65
|
+
## Programmatic access
|
|
66
|
+
|
|
67
|
+
The command is a thin wrapper around
|
|
68
|
+
`Fontisan::Collection::Reader`, which exposes per-face metadata
|
|
69
|
+
directly:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
reader = Fontisan::Collection::Reader.open("family.ttc")
|
|
73
|
+
reader.face_count # => 3
|
|
74
|
+
reader.stats.map(&:glyph_count) # => [3541, 21048, 991]
|
|
75
|
+
reader.cmap_union.size # => 24580
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use the command-class directly when you want structured results
|
|
79
|
+
without stdout:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
cmd = Fontisan::Commands::ValidateCollectionCommand.new(
|
|
83
|
+
input: "family.ttc",
|
|
84
|
+
expected_faces: 3,
|
|
85
|
+
)
|
|
86
|
+
exit_code = cmd.run # 0 or 1
|
|
87
|
+
cmd.checks # => [#<Check name=:face_count passed=true ...>, ...]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## See also
|
|
91
|
+
|
|
92
|
+
- [validate](/cli/validate) — per-face profile-based checks (OpenType
|
|
93
|
+
compliance, hint validity, etc.)
|
|
94
|
+
- [pack](/cli/pack) / [unpack](/cli/pack) — create and extract
|
|
95
|
+
collections
|
data/docs/cli/validate.md
CHANGED
|
@@ -46,3 +46,11 @@ fontisan validate font.ttf --profile opentype --format json
|
|
|
46
46
|
## Detailed Documentation
|
|
47
47
|
|
|
48
48
|
For comprehensive documentation including profile details and validation helpers, see the [validate command guide](/guide/cli/validate).
|
|
49
|
+
|
|
50
|
+
## See also
|
|
51
|
+
|
|
52
|
+
For collection-level structural checks (face count, per-face glyph cap,
|
|
53
|
+
cmap-union size) on a TTC/OTC/dfont, see
|
|
54
|
+
[validate-collection](/cli/validate-collection). The two commands are
|
|
55
|
+
complementary: `validate` runs profile-based per-face checks;
|
|
56
|
+
`validate-collection` runs collection-level structural checks.
|
data/lib/fontisan/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -188,6 +188,7 @@ files:
|
|
|
188
188
|
- docs/cli/subset.md
|
|
189
189
|
- docs/cli/tables.md
|
|
190
190
|
- docs/cli/unicode.md
|
|
191
|
+
- docs/cli/validate-collection.md
|
|
191
192
|
- docs/cli/validate.md
|
|
192
193
|
- docs/cli/variable.md
|
|
193
194
|
- docs/cli/version.md
|