fontisan 0.2.5 → 0.2.6
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/.rubocop_todo.yml +36 -20
- data/README.adoc +184 -857
- data/lib/fontisan/cli.rb +27 -7
- data/lib/fontisan/collection/dfont_builder.rb +315 -0
- data/lib/fontisan/commands/convert_command.rb +118 -7
- data/lib/fontisan/commands/pack_command.rb +129 -22
- data/lib/fontisan/config/conversion_matrix.yml +175 -1
- data/lib/fontisan/constants.rb +8 -0
- data/lib/fontisan/converters/collection_converter.rb +438 -0
- data/lib/fontisan/dfont_collection.rb +185 -0
- data/lib/fontisan/font_loader.rb +91 -6
- data/lib/fontisan/parsers/dfont_parser.rb +192 -0
- data/lib/fontisan/true_type_font.rb +8 -46
- data/lib/fontisan/validation/collection_validator.rb +265 -0
- data/lib/fontisan/version.rb +1 -1
- metadata +7 -2
data/README.adoc
CHANGED
|
@@ -10,7 +10,7 @@ Fontisan is a Ruby gem providing font analysis tools and utilities.
|
|
|
10
10
|
|
|
11
11
|
It is designed as a pure Ruby implementation with full object-oriented
|
|
12
12
|
architecture, supporting extraction of information from OpenType and TrueType
|
|
13
|
-
fonts (OTF, TTF, TTC).
|
|
13
|
+
fonts (OTF, TTF, OTC, TTC, dfont).
|
|
14
14
|
|
|
15
15
|
The gem provides both a Ruby library API and a command-line interface, with
|
|
16
16
|
structured output formats (YAML, JSON, text) via lutaml-model.
|
|
@@ -71,6 +71,7 @@ gem install fontisan
|
|
|
71
71
|
* Font validation with multiple severity levels
|
|
72
72
|
* Collection management (pack/unpack TTC/OTC files with table deduplication)
|
|
73
73
|
* Support for TTF, OTF, TTC, OTC font formats (production ready)
|
|
74
|
+
* Apple legacy font support: 'true' signature TrueType fonts and dfont (Data Fork Font) format
|
|
74
75
|
* WOFF format support (reading complete, writing functional, pending full integration)
|
|
75
76
|
* WOFF2 format support (reading complete with table transformations, writing planned)
|
|
76
77
|
* SVG font generation (complete)
|
|
@@ -100,12 +101,13 @@ and font metrics.
|
|
|
100
101
|
|
|
101
102
|
[source,shell]
|
|
102
103
|
----
|
|
104
|
+
|
|
103
105
|
$ fontisan info FONT_FILE [--format FORMAT] [--brief]
|
|
104
106
|
----
|
|
105
107
|
|
|
106
108
|
Where,
|
|
107
109
|
|
|
108
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC,
|
|
110
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
109
111
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
110
112
|
`--brief`:: Show only basic font information
|
|
111
113
|
|
|
@@ -366,7 +368,7 @@ $ fontisan tables FONT_FILE [--format FORMAT]
|
|
|
366
368
|
|
|
367
369
|
Where,
|
|
368
370
|
|
|
369
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
371
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
370
372
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
371
373
|
|
|
372
374
|
.List of OpenType tables in Libertinus Serif Regular
|
|
@@ -424,10 +426,6 @@ tables:
|
|
|
424
426
|
length: 17870
|
|
425
427
|
offset: 542992
|
|
426
428
|
checksum: 701383168
|
|
427
|
-
- tag: OS/2
|
|
428
|
-
length: 96
|
|
429
|
-
offset: 392
|
|
430
|
-
checksum: 1193824355
|
|
431
429
|
...
|
|
432
430
|
----
|
|
433
431
|
====
|
|
@@ -451,7 +449,7 @@ $ fontisan glyphs FONT_FILE [--format FORMAT]
|
|
|
451
449
|
|
|
452
450
|
Where,
|
|
453
451
|
|
|
454
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
452
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
455
453
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
456
454
|
|
|
457
455
|
|
|
@@ -466,7 +464,7 @@ $ fontisan glyphs spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
|
466
464
|
[source,text]
|
|
467
465
|
----
|
|
468
466
|
Glyph count: 2731
|
|
469
|
-
Source:
|
|
467
|
+
Source: post-2.0
|
|
470
468
|
|
|
471
469
|
Glyph names:
|
|
472
470
|
0 .notdef
|
|
@@ -512,7 +510,7 @@ $ fontisan unicode FONT_FILE [--format FORMAT]
|
|
|
512
510
|
|
|
513
511
|
Where:
|
|
514
512
|
|
|
515
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
513
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
516
514
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
517
515
|
|
|
518
516
|
|
|
@@ -802,7 +800,7 @@ $ fontisan scripts FONT_FILE [--format FORMAT]
|
|
|
802
800
|
|
|
803
801
|
Where,
|
|
804
802
|
|
|
805
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
803
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
806
804
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
807
805
|
|
|
808
806
|
|
|
@@ -845,7 +843,7 @@ $ fontisan features FONT_FILE [--script SCRIPT] [--format FORMAT]
|
|
|
845
843
|
|
|
846
844
|
Where,
|
|
847
845
|
|
|
848
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
846
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
849
847
|
`SCRIPT`:: Optional 4-character script tag (e.g., `latn`, `cyrl`, `arab`). If not specified, shows features for all scripts
|
|
850
848
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
851
849
|
|
|
@@ -942,7 +940,7 @@ $ fontisan dump-table FONT_FILE TABLE_TAG
|
|
|
942
940
|
|
|
943
941
|
Where,
|
|
944
942
|
|
|
945
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
943
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
946
944
|
`TABLE_TAG`:: Four-character table tag (e.g., `name`, `head`, `GSUB`, `GPOS`)
|
|
947
945
|
|
|
948
946
|
|
|
@@ -980,7 +978,7 @@ $ fontisan export FONT_FILE [--output FILE] [--format FORMAT] [--tables TABLES]
|
|
|
980
978
|
|
|
981
979
|
Where,
|
|
982
980
|
|
|
983
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF,
|
|
981
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
|
|
984
982
|
`--output FILE`:: Output file path (default: stdout)
|
|
985
983
|
`--format FORMAT`:: Export format: `yaml` (default), `json`, or `ttx`
|
|
986
984
|
`--tables TABLES`:: Specific tables to export (space-separated list)
|
|
@@ -1281,21 +1279,21 @@ class MyFontValidator < Fontisan::Validators::Validator
|
|
|
1281
1279
|
|
|
1282
1280
|
def define_checks
|
|
1283
1281
|
# Check name table
|
|
1284
|
-
check_table :
|
|
1285
|
-
table.valid_version?
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
table.family_name_present?
|
|
1282
|
+
check_table :name_validation, 'name', severity: :error do |table|
|
|
1283
|
+
table.valid_version? &&
|
|
1284
|
+
table.valid_encoding_heuristics? &&
|
|
1285
|
+
table.family_name_present? &&
|
|
1286
|
+
table.postscript_name_valid?
|
|
1290
1287
|
end
|
|
1291
1288
|
|
|
1292
1289
|
# Check head table
|
|
1293
|
-
check_table :
|
|
1294
|
-
table.valid_magic?
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1290
|
+
check_table :head_validation, 'head', severity: :error do |table|
|
|
1291
|
+
table.valid_magic? &&
|
|
1292
|
+
table.valid_version? &&
|
|
1293
|
+
table.valid_units_per_em? &&
|
|
1294
|
+
table.valid_bounding_box? &&
|
|
1295
|
+
table.valid_index_to_loc_format? &&
|
|
1296
|
+
table.valid_glyph_data_format?
|
|
1299
1297
|
end
|
|
1300
1298
|
|
|
1301
1299
|
# Check structure
|
|
@@ -1317,7 +1315,7 @@ puts report.summary.errors # => number of errors
|
|
|
1317
1315
|
puts report.summary.warnings # => number of warnings
|
|
1318
1316
|
|
|
1319
1317
|
# Query specific checks
|
|
1320
|
-
result = report.result_of(:
|
|
1318
|
+
result = report.result_of(:name_validation)
|
|
1321
1319
|
puts result.passed? # => true/false
|
|
1322
1320
|
puts result.severity # => "error"
|
|
1323
1321
|
puts result.messages # => array of messages
|
|
@@ -1337,10 +1335,6 @@ puts report.to_json
|
|
|
1337
1335
|
.Using table validation helpers
|
|
1338
1336
|
[example]
|
|
1339
1337
|
====
|
|
1340
|
-
The validation framework integrates with 56 built-in validation helpers across
|
|
1341
|
-
core OpenType tables. These helpers perform specific validation checks and
|
|
1342
|
-
return boolean values.
|
|
1343
|
-
|
|
1344
1338
|
[source,ruby]
|
|
1345
1339
|
----
|
|
1346
1340
|
class ComprehensiveValidator < Fontisan::Validators::Validator
|
|
@@ -1348,7 +1342,7 @@ class ComprehensiveValidator < Fontisan::Validators::Validator
|
|
|
1348
1342
|
|
|
1349
1343
|
def define_checks
|
|
1350
1344
|
# Name table validation helpers
|
|
1351
|
-
check_table :name_validation, 'name' do |table|
|
|
1345
|
+
check_table :name_validation, 'name', severity: :error do |table|
|
|
1352
1346
|
table.valid_version? &&
|
|
1353
1347
|
table.valid_encoding_heuristics? &&
|
|
1354
1348
|
table.family_name_present? &&
|
|
@@ -1356,7 +1350,7 @@ class ComprehensiveValidator < Fontisan::Validators::Validator
|
|
|
1356
1350
|
end
|
|
1357
1351
|
|
|
1358
1352
|
# Head table validation helpers
|
|
1359
|
-
check_table :head_validation, 'head' do |table|
|
|
1353
|
+
check_table :head_validation, 'head', severity: :error do |table|
|
|
1360
1354
|
table.valid_magic? &&
|
|
1361
1355
|
table.valid_version? &&
|
|
1362
1356
|
table.valid_units_per_em? &&
|
|
@@ -1366,7 +1360,7 @@ class ComprehensiveValidator < Fontisan::Validators::Validator
|
|
|
1366
1360
|
end
|
|
1367
1361
|
|
|
1368
1362
|
# Maxp table validation helpers
|
|
1369
|
-
check_table :maxp_validation, 'maxp' do |table|
|
|
1363
|
+
check_table :maxp_validation, 'maxp', severity: :error do |table|
|
|
1370
1364
|
table.valid_version? &&
|
|
1371
1365
|
table.valid_num_glyphs? &&
|
|
1372
1366
|
table.valid_max_zones? &&
|
|
@@ -1405,7 +1399,7 @@ else
|
|
|
1405
1399
|
end
|
|
1406
1400
|
|
|
1407
1401
|
# Check specific validation results
|
|
1408
|
-
if report.result_of(:
|
|
1402
|
+
if report.result_of(:name_validation)&.passed?
|
|
1409
1403
|
puts "Name table version is valid"
|
|
1410
1404
|
else
|
|
1411
1405
|
puts "Name table version check failed"
|
|
@@ -1524,16 +1518,51 @@ fontisan version
|
|
|
1524
1518
|
----
|
|
1525
1519
|
|
|
1526
1520
|
|
|
1527
|
-
== Font collections
|
|
1521
|
+
== Font containers and collections
|
|
1528
1522
|
|
|
1529
1523
|
=== General
|
|
1530
1524
|
|
|
1531
|
-
Fontisan provides comprehensive tools for managing
|
|
1532
|
-
|
|
1533
|
-
|
|
1525
|
+
Fontisan provides comprehensive tools for managing font containers and
|
|
1526
|
+
collections. A font container is defined as a packaging format that can hold one
|
|
1527
|
+
or more font assets.
|
|
1528
|
+
|
|
1529
|
+
In general, Fontisan supports the following levels of font packaging:
|
|
1530
|
+
|
|
1531
|
+
Level 1:: Outline formats. This level defines the actual glyph outline data
|
|
1532
|
+
format for fonts.
|
|
1533
|
+
|
|
1534
|
+
TrueType::: Quadratic Bézier curves (glyf/loca tables)
|
|
1535
|
+
CFF::: Cubic Bézier curves (CFF table, PostScript outlines)
|
|
1536
|
+
CFF2::: Variable CFF with cubic curves (CFF2 table)
|
|
1537
|
+
|
|
1538
|
+
Level 2:: Curve and metadata container. This level defines the overall font
|
|
1539
|
+
file format that encapsulates outline data along with various metadata tables.
|
|
1540
|
+
|
|
1541
|
+
SFNT::: "Spine/Scalable font" defines the overall structure and directory system
|
|
1542
|
+
for a font file, which houses different "tables" containing specific data like
|
|
1543
|
+
glyph outlines, character maps, and kerning information.
|
|
1544
|
+
|
|
1545
|
+
TrueType (TTF)::: A specific implementation of the SFNT container that uses
|
|
1546
|
+
TrueType outlines.
|
|
1547
|
+
|
|
1548
|
+
OpenType (OTF)::: A more versatile SFNT container that can house either TrueType
|
|
1549
|
+
or CFF outlines, along with additional typographic features.
|
|
1550
|
+
|
|
1551
|
+
Web Open Font Format (WOFF and WOFF2)::: Compressed SFNT-based formats optimized
|
|
1552
|
+
for web delivery.
|
|
1534
1553
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1554
|
+
Level 3:: Individual font file, representing a single font asset packaged in a
|
|
1555
|
+
specific container format.
|
|
1556
|
+
|
|
1557
|
+
TTF `.ttf`::: A single font file in TrueType format
|
|
1558
|
+
OTF `.otf`::: A single font file in OpenType format
|
|
1559
|
+
WOFF `.woff`::: A single font file in Web Open Font Format 1.0 that is SFNT-based
|
|
1560
|
+
and uses zlib compression
|
|
1561
|
+
WOFF2 `.woff2`::: A single font file in Web Open Font Format 2.0 that is
|
|
1562
|
+
SFNT-based and uses Brotli compression
|
|
1563
|
+
|
|
1564
|
+
Level 4:: Font collections, which are container formats that can hold multiple
|
|
1565
|
+
font assets within a single file.
|
|
1537
1566
|
|
|
1538
1567
|
TTC (TrueType Collection):: Supported since OpenType 1.4. Contains fonts with
|
|
1539
1568
|
TrueType outlines (glyf table). Multiple fonts can share identical tables for
|
|
@@ -1543,7 +1572,38 @@ OTC (OpenType Collection):: Supported since OpenType 1.8. Contains fonts with
|
|
|
1543
1572
|
CFF-format outlines (CFF table). Provides the same storage benefits and
|
|
1544
1573
|
glyph-count advantages as TTC but for CFF fonts. File extension: `.otc`
|
|
1545
1574
|
|
|
1546
|
-
|
|
1575
|
+
dfont (Apple suitcase)::: Apple proprietary format storing complete Mac font
|
|
1576
|
+
suitcase resources in the data fork. Supports any SFNT fonts (TTF, OTF, or
|
|
1577
|
+
mixed). Mac OS X specific. File extension: `.dfont`
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
=== Collection compatibility
|
|
1581
|
+
|
|
1582
|
+
.Compatibility matrix
|
|
1583
|
+
|===
|
|
1584
|
+
| Container | TrueType | CFF | Mixed | WOFF/WOFF2 | Platform
|
|
1585
|
+
|
|
1586
|
+
| TTC | ✅ ONLY | ❌ | ❌ | ❌ | Cross-platform
|
|
1587
|
+
| OTC | ✅ | ✅ | ✅ | ❌ | Cross-platform
|
|
1588
|
+
| dfont | ✅ | ✅ | ✅ | ❌ | Apple only
|
|
1589
|
+
|
|
1590
|
+
|===
|
|
1591
|
+
|
|
1592
|
+
NOTE: WOFF/WOFF2 cannot be packaged into TTC, OTC or dfont collections. Web
|
|
1593
|
+
fonts are designed for single-font delivery only.
|
|
1594
|
+
|
|
1595
|
+
|
|
1596
|
+
=== Working with collections
|
|
1597
|
+
|
|
1598
|
+
Fontist supports working with TrueType Collections (TTC), OpenType Collections
|
|
1599
|
+
(OTC) and the legacy Apple dfont format. You can list fonts in a collection,
|
|
1600
|
+
extract individual fonts, unpack entire collections, and validate collection
|
|
1601
|
+
integrity.
|
|
1602
|
+
|
|
1603
|
+
NOTE: Both TTC and OTC files use the same `ttcf` tag in their binary format, but
|
|
1604
|
+
differ in the type of font data they contain.
|
|
1605
|
+
|
|
1606
|
+
In particular, the TTC and OTC formats allows:
|
|
1547
1607
|
|
|
1548
1608
|
Table sharing::
|
|
1549
1609
|
Identical tables are stored once and referenced by multiple fonts
|
|
@@ -1568,8 +1628,7 @@ Fontist returns the appropriate collection type based on the font data:
|
|
|
1568
1628
|
|
|
1569
1629
|
==== General
|
|
1570
1630
|
|
|
1571
|
-
List all fonts in a
|
|
1572
|
-
their index, family name, and style.
|
|
1631
|
+
List all fonts in a font collection, with their index, family name, and style.
|
|
1573
1632
|
|
|
1574
1633
|
==== Command-line usage
|
|
1575
1634
|
|
|
@@ -1580,7 +1639,6 @@ $ fontisan ls FONT.{ttc,otc}
|
|
|
1580
1639
|
|
|
1581
1640
|
NOTE: In `extract_ttc`, this was done with `extract_ttc --list FONT.ttc`.
|
|
1582
1641
|
|
|
1583
|
-
|
|
1584
1642
|
.List collection contents
|
|
1585
1643
|
[example]
|
|
1586
1644
|
====
|
|
@@ -1611,6 +1669,27 @@ Font 3: Noto Serif CJK TC
|
|
|
1611
1669
|
----
|
|
1612
1670
|
====
|
|
1613
1671
|
|
|
1672
|
+
.List fonts in dfont suitcase
|
|
1673
|
+
[example]
|
|
1674
|
+
====
|
|
1675
|
+
[source,shell]
|
|
1676
|
+
----
|
|
1677
|
+
$ fontisan ls family.dfont
|
|
1678
|
+
|
|
1679
|
+
Font 0: Arial
|
|
1680
|
+
Family: Arial
|
|
1681
|
+
Subfamily: Regular
|
|
1682
|
+
|
|
1683
|
+
Font 1: Arial
|
|
1684
|
+
Family: Arial
|
|
1685
|
+
Subfamily: Bold
|
|
1686
|
+
|
|
1687
|
+
Font 2: Arial
|
|
1688
|
+
Family: Arial
|
|
1689
|
+
Subfamily: Italic
|
|
1690
|
+
----
|
|
1691
|
+
====
|
|
1692
|
+
|
|
1614
1693
|
|
|
1615
1694
|
=== Show collection info
|
|
1616
1695
|
|
|
@@ -1771,863 +1850,111 @@ Collection created successfully:
|
|
|
1771
1850
|
$ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otc
|
|
1772
1851
|
----
|
|
1773
1852
|
|
|
1774
|
-
|
|
1775
|
-
=== Validate collection
|
|
1776
|
-
|
|
1777
|
-
==== General
|
|
1778
|
-
|
|
1779
|
-
Validate the structure and checksums of a TrueType Collection (TTC) or OpenType
|
|
1780
|
-
Collection (OTC).
|
|
1781
|
-
|
|
1782
|
-
==== Command-line usage
|
|
1783
|
-
|
|
1784
|
-
[source,shell]
|
|
1785
|
-
----
|
|
1786
|
-
$ fontisan validate FONT.{ttc,otc}
|
|
1787
|
-
----
|
|
1788
|
-
|
|
1789
|
-
NOTE: In `extract_ttc`, this was done with `extract_ttc --validate FONT.ttc`.
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
== Advanced features
|
|
1793
|
-
|
|
1794
|
-
Fontisan provides capabilities:
|
|
1795
|
-
|
|
1796
|
-
.Font analysis and inspection
|
|
1797
|
-
* Extract OpenType tables with checksums and offsets
|
|
1798
|
-
* Display Unicode mappings and glyph names
|
|
1799
|
-
* Analyze variable font axes and instances
|
|
1800
|
-
* Show supported scripts and OpenType features
|
|
1801
|
-
* Dump raw binary table data
|
|
1802
|
-
|
|
1803
|
-
.Format conversion and subsetting
|
|
1804
|
-
* Convert between TTF, OTF, WOFF, and WOFF2 formats
|
|
1805
|
-
* Create font subsets with specific glyph ranges
|
|
1806
|
-
* Validate font structure and integrity
|
|
1807
|
-
* Generate SVG representations of glyphs
|
|
1808
|
-
|
|
1809
|
-
.Collection creation
|
|
1810
|
-
* Build new TTC files from individual fonts
|
|
1811
|
-
* Optimize collection with table deduplication
|
|
1812
|
-
* Pack fonts with shared tables for smaller file sizes
|
|
1813
|
-
|
|
1814
|
-
For complete migration guide, see link:docs/EXTRACT_TTC_MIGRATION.md[extract_ttc Migration Guide].
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
== Loading modes
|
|
1820
|
-
|
|
1821
|
-
=== General
|
|
1822
|
-
|
|
1823
|
-
Fontisan provides a flexible loading modes architecture that enables efficient
|
|
1824
|
-
font parsing for different use cases.
|
|
1825
|
-
|
|
1826
|
-
The system supports two distinct modes:
|
|
1827
|
-
|
|
1828
|
-
`:full` mode:: (default) Loads all tables in the font for complete analysis and
|
|
1829
|
-
manipulation
|
|
1830
|
-
|
|
1831
|
-
`:metadata` mode:: Loads only metadata tables needed for font identification and
|
|
1832
|
-
metrics (similar to `otfinfo` functionality). This mode is around 5x faster
|
|
1833
|
-
than full parsing and uses significantly less memory.
|
|
1834
|
-
|
|
1835
|
-
This architecture is particularly useful for software that only
|
|
1836
|
-
needs basic font information without full parsing overhead, such as
|
|
1837
|
-
font indexing systems or font discovery tools.
|
|
1838
|
-
|
|
1839
|
-
This mode was developed to improve performance in font indexing in the
|
|
1840
|
-
https://github.com/fontist/fontist[Fontist] library, where system fonts
|
|
1841
|
-
need to be scanned quickly without loading unnecessary data.
|
|
1842
|
-
|
|
1843
|
-
A font file opened in `:metadata` mode will only have a subset of tables
|
|
1844
|
-
loaded, and attempts to access non-loaded tables will return `nil`.
|
|
1845
|
-
|
|
1846
|
-
[source,ruby]
|
|
1847
|
-
----
|
|
1848
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
|
|
1849
|
-
|
|
1850
|
-
# Check table availability before accessing
|
|
1851
|
-
font.table_available?("name") # => true
|
|
1852
|
-
font.table_available?("GSUB") # => false
|
|
1853
|
-
|
|
1854
|
-
# Access allowed tables
|
|
1855
|
-
font.table("name") # => Works
|
|
1856
|
-
font.table("head") # => Works
|
|
1857
|
-
|
|
1858
|
-
# Restricted tables return nil
|
|
1859
|
-
font.table("GSUB") # => nil (not loaded in metadata mode)
|
|
1860
|
-
----
|
|
1861
|
-
|
|
1862
|
-
You can also set loading modes via the environment:
|
|
1863
|
-
|
|
1864
|
-
[source,ruby]
|
|
1865
|
-
----
|
|
1866
|
-
# Set defaults via environment
|
|
1867
|
-
ENV['FONTISAN_MODE'] = 'metadata'
|
|
1868
|
-
ENV['FONTISAN_LAZY'] = 'false'
|
|
1869
|
-
|
|
1870
|
-
# Uses environment settings
|
|
1871
|
-
font = Fontisan::FontLoader.load('font.ttf')
|
|
1872
|
-
|
|
1873
|
-
# Explicit parameters override environment
|
|
1874
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :full)
|
|
1875
|
-
----
|
|
1876
|
-
|
|
1877
|
-
The loading mode can be queried at any time.
|
|
1878
|
-
|
|
1879
|
-
[source,ruby]
|
|
1880
|
-
----
|
|
1881
|
-
# Mode stored as font property
|
|
1882
|
-
font.loading_mode # => :metadata or :full
|
|
1883
|
-
|
|
1884
|
-
# Table availability checked before access
|
|
1885
|
-
font.table_available?(tag) # => boolean
|
|
1886
|
-
|
|
1887
|
-
# Access restricted based on mode
|
|
1888
|
-
font.table(tag) # => Returns table or raises error
|
|
1889
|
-
----
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
=== Metadata mode
|
|
1894
|
-
|
|
1895
|
-
Loads only 6 tables (name, head, hhea, maxp, OS/2, post) instead of 15-20 tables.
|
|
1896
|
-
|
|
1897
|
-
.Metadata mode: Fast loading for font identification
|
|
1898
|
-
[source,ruby]
|
|
1899
|
-
----
|
|
1900
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
|
|
1901
|
-
puts font.family_name # => "Arial"
|
|
1902
|
-
puts font.subfamily_name # => "Regular"
|
|
1903
|
-
puts font.post_script_name # => "ArialMT"
|
|
1904
|
-
----
|
|
1905
|
-
|
|
1906
|
-
Tables loaded:
|
|
1907
|
-
|
|
1908
|
-
name:: Font names and metadata
|
|
1909
|
-
head:: Font header with global metrics
|
|
1910
|
-
hhea:: Horizontal header with line spacing
|
|
1911
|
-
maxp:: Maximum profile with glyph count
|
|
1912
|
-
OS/2:: OS/2 and Windows metrics
|
|
1913
|
-
post:: PostScript information
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
In metadata mode, these convenience methods provide direct access to name table
|
|
1917
|
-
fields:
|
|
1918
|
-
|
|
1919
|
-
`family_name`:: Font family name (nameID 1)
|
|
1920
|
-
`subfamily_name`:: Font subfamily/style name (nameID 2)
|
|
1921
|
-
`full_name`:: Full font name (nameID 4)
|
|
1922
|
-
`post_script_name`:: PostScript name (nameID 6)
|
|
1923
|
-
`preferred_family_name`:: Preferred family name (nameID 16, may be nil)
|
|
1924
|
-
`preferred_subfamily_name`:: Preferred subfamily name (nameID 17, may be nil)
|
|
1925
|
-
`units_per_em`:: Units per em from head table
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
=== Full mode
|
|
1929
|
-
|
|
1930
|
-
Loads all tables in the font for complete analysis and manipulation.
|
|
1931
|
-
|
|
1932
|
-
.Full mode: Complete font analysis
|
|
1933
|
-
[source,ruby]
|
|
1934
|
-
----
|
|
1935
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :full)
|
|
1936
|
-
font.table("GSUB") # => Available
|
|
1937
|
-
font.table("GPOS") # => Available
|
|
1938
|
-
|
|
1939
|
-
# Check which mode is active
|
|
1940
|
-
puts font.loading_mode # => :metadata or :full
|
|
1941
|
-
----
|
|
1942
|
-
|
|
1943
|
-
Tables loaded:
|
|
1944
|
-
|
|
1945
|
-
* All tables in the font
|
|
1946
|
-
* Including GSUB, GPOS, cmap, glyf/CFF, etc.
|
|
1947
|
-
|
|
1948
|
-
=== Lazy loading option
|
|
1949
|
-
|
|
1950
|
-
Fontisan supports lazy loading of tables in both `:metadata` and `:full` modes.
|
|
1951
|
-
When lazy loading is enabled (optional), tables are only parsed when accessed.
|
|
1952
|
-
|
|
1953
|
-
Options:
|
|
1954
|
-
|
|
1955
|
-
`false`:: (default) Eager loading. All tables for the selected mode are parsed
|
|
1956
|
-
upfront.
|
|
1957
|
-
|
|
1958
|
-
`true`:: Lazy loading enabled. Tables are parsed on-demand.
|
|
1959
|
-
|
|
1960
|
-
[source,ruby]
|
|
1961
|
-
----
|
|
1962
|
-
# Metadata mode with lazy loading (default, fastest)
|
|
1963
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: true)
|
|
1964
|
-
|
|
1965
|
-
# Metadata mode with eager loading (loads all metadata tables upfront)
|
|
1966
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: false)
|
|
1967
|
-
|
|
1968
|
-
# Full mode with lazy loading (tables loaded on-demand)
|
|
1969
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: true)
|
|
1970
|
-
|
|
1971
|
-
# Full mode with eager loading (all tables loaded upfront)
|
|
1972
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: false)
|
|
1973
|
-
----
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
== Outline format conversion
|
|
1979
|
-
|
|
1980
|
-
=== General
|
|
1981
|
-
|
|
1982
|
-
Fontisan supports bidirectional conversion between TrueType (TTF) and
|
|
1983
|
-
OpenType/CFF (OTF) outline formats through the Fontist universal outline model
|
|
1984
|
-
(UOM).
|
|
1985
|
-
|
|
1986
|
-
The outline converter enables transformation between glyph outline formats:
|
|
1987
|
-
|
|
1988
|
-
TrueType (TTF):: Uses quadratic Bézier curves stored in glyf/loca tables
|
|
1989
|
-
OpenType/CFF (OTF):: Uses cubic Bézier curves stored in CFF table
|
|
1990
|
-
|
|
1991
|
-
Conversion uses a format-agnostic universal outline model as an intermediate
|
|
1992
|
-
representation, ensuring high-quality results while preserving glyph metrics and
|
|
1993
|
-
bounding boxes.
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
=== Convert between TTF and OTF
|
|
1997
|
-
|
|
1998
|
-
==== Command-line usage
|
|
1999
|
-
|
|
2000
|
-
Syntax:
|
|
2001
|
-
|
|
2002
|
-
[source,bash]
|
|
2003
|
-
----
|
|
2004
|
-
$ fontisan convert INPUT_FONT --to FORMAT --output OUTPUT_FONT
|
|
2005
|
-
----
|
|
2006
|
-
|
|
2007
|
-
Where,
|
|
2008
|
-
|
|
2009
|
-
`INPUT_FONT`:: Path to the input font file (TTF or OTF)
|
|
2010
|
-
`FORMAT`:: Target format:
|
|
2011
|
-
`ttf`, `truetype`::: TrueType format
|
|
2012
|
-
`otf`, `opentype`, `cff`::: OpenType/CFF format
|
|
2013
|
-
`OUTPUT_FONT`:: Path to the output font file
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
[source,bash]
|
|
2017
|
-
----
|
|
2018
|
-
# Convert TrueType font to OpenType/CFF
|
|
2019
|
-
fontisan convert input.ttf --to otf --output output.otf
|
|
2020
|
-
|
|
2021
|
-
# Convert OpenType/CFF font to TrueType
|
|
2022
|
-
fontisan convert input.otf --to ttf --output output.ttf
|
|
2023
|
-
----
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
==== Ruby API usage
|
|
2027
|
-
|
|
2028
|
-
Basic conversion with OutlineConverter:
|
|
2029
|
-
|
|
2030
|
-
[source,ruby]
|
|
2031
|
-
----
|
|
2032
|
-
require 'fontisan'
|
|
2033
|
-
|
|
2034
|
-
# Load a TrueType font
|
|
2035
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
2036
|
-
|
|
2037
|
-
# Convert to OpenType/CFF
|
|
2038
|
-
converter = Fontisan::Converters::OutlineConverter.new
|
|
2039
|
-
tables = converter.convert(font, target_format: :otf)
|
|
2040
|
-
|
|
2041
|
-
# Write output
|
|
2042
|
-
Fontisan::FontWriter.write_to_file(
|
|
2043
|
-
tables,
|
|
2044
|
-
'output.otf',
|
|
2045
|
-
sfnt_version: 0x4F54544F # 'OTTO' for OpenType/CFF
|
|
2046
|
-
)
|
|
2047
|
-
----
|
|
2048
|
-
|
|
2049
|
-
Using FormatConverter:
|
|
2050
|
-
|
|
2051
|
-
[source,ruby]
|
|
2052
|
-
----
|
|
2053
|
-
require 'fontisan'
|
|
2054
|
-
|
|
2055
|
-
# Load font
|
|
2056
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
2057
|
-
|
|
2058
|
-
# Convert using high-level API
|
|
2059
|
-
converter = Fontisan::Converters::FormatConverter.new
|
|
2060
|
-
if converter.supported?(:ttf, :otf)
|
|
2061
|
-
tables = converter.convert(font, :otf)
|
|
2062
|
-
|
|
2063
|
-
# Write output
|
|
2064
|
-
Fontisan::FontWriter.write_to_file(
|
|
2065
|
-
tables,
|
|
2066
|
-
'output.otf',
|
|
2067
|
-
sfnt_version: 0x4F54544F
|
|
2068
|
-
)
|
|
2069
|
-
end
|
|
2070
|
-
----
|
|
2071
|
-
|
|
2072
|
-
To check supported conversions:
|
|
2073
|
-
|
|
2074
|
-
[source,ruby]
|
|
2075
|
-
----
|
|
2076
|
-
converter = Fontisan::Converters::FormatConverter.new
|
|
2077
|
-
|
|
2078
|
-
# Check if conversion is supported
|
|
2079
|
-
converter.supported?(:ttf, :otf) # => true
|
|
2080
|
-
converter.supported?(:otf, :ttf) # => true
|
|
2081
|
-
|
|
2082
|
-
# Get all supported conversions
|
|
2083
|
-
converter.all_conversions
|
|
2084
|
-
# => [{from: :ttf, to: :otf}, {from: :otf, to: :ttf}, ...]
|
|
2085
|
-
|
|
2086
|
-
# Get supported targets for a source format
|
|
2087
|
-
converter.supported_targets(:ttf)
|
|
2088
|
-
# => [:ttf, :otf, :woff2, :svg]
|
|
2089
|
-
----
|
|
2090
|
-
|
|
2091
|
-
=== Convert into WOFF2
|
|
2092
|
-
|
|
2093
|
-
==== Validation
|
|
2094
|
-
|
|
2095
|
-
WOFF2 encoding can be validated automatically to ensure spec compliance:
|
|
2096
|
-
|
|
2097
|
-
.Validation example
|
|
1853
|
+
.Pack into OTC
|
|
2098
1854
|
[example]
|
|
2099
1855
|
====
|
|
2100
|
-
[source,
|
|
2101
|
-
----
|
|
2102
|
-
encoder = Fontisan::Converters::Woff2Encoder.new
|
|
2103
|
-
result = encoder.convert(font, {
|
|
2104
|
-
transform_tables: true,
|
|
2105
|
-
validate: true,
|
|
2106
|
-
validation_level: :strict # :strict, :standard, or :lenient
|
|
2107
|
-
})
|
|
2108
|
-
|
|
2109
|
-
report = result[:validation_report]
|
|
2110
|
-
puts "Valid: #{report.valid}"
|
|
2111
|
-
puts "Compression: #{report.info_issues.first.message}"
|
|
2112
|
-
----
|
|
2113
|
-
====
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
=== Validation
|
|
2118
|
-
|
|
2119
|
-
Font integrity validation is enabled by default for all conversions.
|
|
2120
|
-
|
|
2121
|
-
The validator ensures proper OpenType checksum calculation including correct
|
|
2122
|
-
handling of the head table's checksumAdjustment field per the OpenType
|
|
2123
|
-
specification.
|
|
2124
|
-
|
|
2125
|
-
After conversion, validate the output font:
|
|
2126
|
-
|
|
2127
|
-
[source,bash]
|
|
2128
|
-
----
|
|
2129
|
-
fontisan validate output.otf
|
|
2130
|
-
fontisan info output.otf
|
|
2131
|
-
fontisan tables output.otf
|
|
2132
|
-
----
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
== Technical details of outline conversion
|
|
2136
|
-
|
|
2137
|
-
=== General
|
|
2138
|
-
|
|
2139
|
-
The converter uses a three-stage pipeline:
|
|
2140
|
-
|
|
2141
|
-
[source]
|
|
2142
|
-
----
|
|
2143
|
-
Source Format Universal Outline Target Format
|
|
2144
|
-
------------- ------------------ -------------
|
|
2145
|
-
TrueType (glyf) →→→ Command-based model →→→ OpenType/CFF
|
|
2146
|
-
Quadratic curves Path representation Cubic curves
|
|
2147
|
-
On/off-curve pts (format-agnostic) CharStrings
|
|
2148
|
-
Delta encoding Bounding boxes Type 2 operators
|
|
2149
|
-
Metrics Compact encoding
|
|
2150
|
-
----
|
|
2151
|
-
|
|
2152
|
-
=== Conversion steps
|
|
2153
|
-
|
|
2154
|
-
TTF → OTF conversion:
|
|
2155
|
-
|
|
2156
|
-
. Extract glyphs from glyf/loca tables
|
|
2157
|
-
. Convert quadratic Bézier curves to universal outline format
|
|
2158
|
-
. Build CFF table with CharStrings INDEX
|
|
2159
|
-
. Update maxp table to version 0.5 (CFF format)
|
|
2160
|
-
. Update head table (clear indexToLocFormat)
|
|
2161
|
-
. Remove glyf/loca tables
|
|
2162
|
-
. Preserve all other tables
|
|
2163
|
-
|
|
2164
|
-
OTF → TTF conversion:
|
|
2165
|
-
|
|
2166
|
-
. Extract CharStrings from CFF table
|
|
2167
|
-
. Convert cubic Bézier curves to universal outline format
|
|
2168
|
-
. Convert cubic curves to quadratic using adaptive subdivision
|
|
2169
|
-
. Build glyf and loca tables with optimal format selection
|
|
2170
|
-
. Update maxp table to version 1.0 (TrueType format)
|
|
2171
|
-
. Update head table (set indexToLocFormat)
|
|
2172
|
-
. Remove CFF table
|
|
2173
|
-
. Preserve all other tables
|
|
2174
|
-
|
|
2175
|
-
=== Curve conversion
|
|
2176
|
-
|
|
2177
|
-
**Quadratic to cubic** (lossless):
|
|
2178
|
-
|
|
2179
|
-
[source]
|
|
2180
|
-
----
|
|
2181
|
-
Given quadratic curve with control point Q:
|
|
2182
|
-
P0 (start), Q (control), P2 (end)
|
|
2183
|
-
|
|
2184
|
-
Calculate cubic control points:
|
|
2185
|
-
CP1 = P0 + (2/3) × (Q - P0)
|
|
2186
|
-
CP2 = P2 + (2/3) × (Q - P2)
|
|
2187
|
-
|
|
2188
|
-
Result: Exact mathematical equivalent
|
|
2189
|
-
----
|
|
2190
|
-
|
|
2191
|
-
**Cubic to quadratic** (adaptive):
|
|
2192
|
-
|
|
2193
|
-
[source]
|
|
2194
|
-
----
|
|
2195
|
-
Given cubic curve with control points:
|
|
2196
|
-
P0 (start), CP1, CP2, P3 (end)
|
|
2197
|
-
|
|
2198
|
-
Use adaptive subdivision algorithm:
|
|
2199
|
-
1. Estimate error of quadratic approximation
|
|
2200
|
-
2. If error > threshold (0.5 units):
|
|
2201
|
-
- Subdivide cubic curve at midpoint
|
|
2202
|
-
- Recursively convert each half
|
|
2203
|
-
3. Otherwise: Output quadratic approximation
|
|
2204
|
-
|
|
2205
|
-
Result: High-quality approximation with < 0.5 unit deviation
|
|
2206
|
-
----
|
|
2207
|
-
|
|
2208
|
-
=== Compound glyph support
|
|
2209
|
-
|
|
2210
|
-
==== General
|
|
2211
|
-
|
|
2212
|
-
Fontisan fully supports compound (composite) glyphs in both conversion directions:
|
|
2213
|
-
|
|
2214
|
-
* **TTF → OTF**: Compound glyphs are decomposed into simple outlines with transformations applied
|
|
2215
|
-
* **OTF → TTF**: CFF glyphs are converted to simple TrueType glyphs
|
|
2216
|
-
|
|
2217
|
-
==== Decomposition process
|
|
2218
|
-
|
|
2219
|
-
When converting TTF to OTF, compound glyphs undergo the following process:
|
|
2220
|
-
|
|
2221
|
-
. Detected from glyf table flags (numberOfContours = -1)
|
|
2222
|
-
. Components recursively resolved (handling nested compound glyphs)
|
|
2223
|
-
. Transformation matrices applied to each component (translation, scale, rotation)
|
|
2224
|
-
. All components merged into a single simple outline
|
|
2225
|
-
. Converted to CFF CharString format
|
|
2226
|
-
|
|
2227
|
-
This ensures that all glyphs render identically while maintaining proper metrics
|
|
2228
|
-
and bounding boxes.
|
|
2229
|
-
|
|
2230
|
-
==== Technical implementation
|
|
2231
|
-
|
|
2232
|
-
Compound glyphs reference other glyphs by index and apply 2×3 affine
|
|
2233
|
-
transformation matrices:
|
|
2234
|
-
|
|
2235
|
-
[source]
|
|
2236
|
-
----
|
|
2237
|
-
x' = a*x + c*y + e
|
|
2238
|
-
y' = b*x + d*y + f
|
|
2239
|
-
|
|
2240
|
-
Where:
|
|
2241
|
-
- a, d: Scale factors for x and y axes
|
|
2242
|
-
- b, c: Rotation/skew components
|
|
2243
|
-
- e, f: Translation offsets (x, y position)
|
|
2244
|
-
----
|
|
2245
|
-
|
|
2246
|
-
The resolver handles:
|
|
2247
|
-
|
|
2248
|
-
* Simple glyphs referenced by compounds
|
|
2249
|
-
* Nested compound glyphs (compounds referencing other compounds)
|
|
2250
|
-
* Circular reference detection with maximum recursion depth (32 levels)
|
|
2251
|
-
* Complex transformation matrices (uniform scale, x/y scale, full 2×2 matrix)
|
|
2252
|
-
|
|
2253
|
-
=== Subroutine optimization
|
|
2254
|
-
|
|
2255
|
-
==== General
|
|
2256
|
-
|
|
2257
|
-
When converting TrueType (TTF) to OpenType/CFF (OTF), Fontisan can automatically
|
|
2258
|
-
generate CFF subroutines to reduce file size.
|
|
2259
|
-
|
|
2260
|
-
Subroutines extract repeated CharString patterns across glyphs and store them
|
|
2261
|
-
once, significantly reducing CFF table size while maintaining identical glyph
|
|
2262
|
-
rendering.
|
|
2263
|
-
|
|
2264
|
-
Key features:
|
|
2265
|
-
|
|
2266
|
-
* Pattern analysis: Analyzes byte sequences across all CharStrings to identify repeating patterns
|
|
2267
|
-
* Frequency-based selection: Prioritizes patterns that provide maximum space savings
|
|
2268
|
-
* Configurable thresholds: Customizable minimum pattern length and maximum subroutine count
|
|
2269
|
-
* Ordering optimization: Automatically orders subroutines by frequency for better compression
|
|
2270
|
-
|
|
2271
|
-
Typical space savings: 30-50% reduction in CFF table size for fonts with similar
|
|
2272
|
-
glyph shapes.
|
|
2273
|
-
|
|
2274
|
-
NOTE: Current implementation calculates accurate optimization metrics but does
|
|
2275
|
-
not modify the output CFF table. Full CFF serialization with subroutines will be
|
|
2276
|
-
available in the next development phase.
|
|
2277
|
-
|
|
2278
|
-
==== Edge cases
|
|
2279
|
-
|
|
2280
|
-
The optimizer correctly handles:
|
|
2281
|
-
|
|
2282
|
-
* Multi-byte numbers: Number encodings from 1-5 bytes (CFF Type 2 format)
|
|
2283
|
-
* Two-byte operators: Operators with 0x0c prefix (e.g., `div` in `lib/fontisan/tables/cff/charstring.rb`, `flex` in `lib/fontisan/tables/cff/charstring.rb`)
|
|
2284
|
-
* Overlapping patterns: Multiple patterns at same byte positions
|
|
2285
|
-
* Stack-neutral validation: Patterns verified to maintain consistent stack state
|
|
2286
|
-
|
|
2287
|
-
==== Technical details
|
|
2288
|
-
|
|
2289
|
-
The subroutine optimizer uses a four-stage pipeline:
|
|
2290
|
-
|
|
2291
|
-
[source]
|
|
2292
|
-
----
|
|
2293
|
-
CharStrings → Pattern Analysis → Selection → Ordering → Metadata
|
|
2294
|
-
(Input) (Find repeats) (Optimize) (Frequency) (Output)
|
|
2295
|
-
----
|
|
2296
|
-
|
|
2297
|
-
**Pattern analysis**:
|
|
2298
|
-
|
|
2299
|
-
. Extracts byte sequences from all CharStrings
|
|
2300
|
-
. Identifies repeating patterns across glyphs
|
|
2301
|
-
. Filters by minimum pattern length (default: 10 bytes)
|
|
2302
|
-
. Builds pattern frequency map
|
|
2303
|
-
|
|
2304
|
-
**Selection algorithm**:
|
|
2305
|
-
|
|
2306
|
-
. Calculates savings for each pattern: `frequency × (length - overhead)`
|
|
2307
|
-
. Ranks patterns by total savings (descending)
|
|
2308
|
-
. Selects top patterns up to `max_subroutines` limit
|
|
2309
|
-
. Ensures selected patterns don't exceed CFF limits
|
|
2310
|
-
|
|
2311
|
-
**Ordering optimization**:
|
|
2312
|
-
|
|
2313
|
-
. Sorts subroutines by usage frequency (most used first)
|
|
2314
|
-
. Optimizes CFF bias calculation for better compression
|
|
2315
|
-
. Ensures subroutine indices fit within CFF constraints
|
|
2316
|
-
|
|
2317
|
-
**CFF bias calculation**:
|
|
2318
|
-
|
|
2319
|
-
[source]
|
|
2320
|
-
----
|
|
2321
|
-
Subroutine count CFF Bias
|
|
2322
|
-
----------------- ---------
|
|
2323
|
-
0-1239 107
|
|
2324
|
-
1240-33899 1131
|
|
2325
|
-
33900-65535 32768
|
|
2326
|
-
----
|
|
2327
|
-
|
|
2328
|
-
The bias value determines how subroutine indices are encoded in CharStrings,
|
|
2329
|
-
affecting the final size.
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
==== Troubleshooting
|
|
2333
|
-
|
|
2334
|
-
If you encounter CharString parsing errors after optimization:
|
|
2335
|
-
|
|
2336
|
-
. Verify bias calculation: Ensure bias matches CFF specification (107, 1131, or 32768)
|
|
2337
|
-
. Check operator boundaries: Patterns should only be extracted at valid boundaries
|
|
2338
|
-
. Ensure no overlaps: Multiple patterns should not occupy same byte positions
|
|
2339
|
-
. Enable verbose mode: Use `--verbose` flag for detailed diagnostics
|
|
2340
|
-
|
|
2341
|
-
.Subroutine debugging workflow example
|
|
2342
|
-
====
|
|
2343
|
-
[source,bash]
|
|
2344
|
-
----
|
|
2345
|
-
# Convert with verbose output
|
|
2346
|
-
$ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
|
|
2347
|
-
|
|
2348
|
-
# Validate the output
|
|
2349
|
-
$ fontisan validate output.otf
|
|
2350
|
-
|
|
2351
|
-
# Check CharString structure
|
|
2352
|
-
$ fontisan info output.otf
|
|
2353
|
-
----
|
|
2354
|
-
|
|
2355
|
-
If validation fails, try:
|
|
2356
|
-
|
|
2357
|
-
[source,bash]
|
|
2358
|
-
----
|
|
2359
|
-
# Disable optimization
|
|
2360
|
-
$ fontisan convert input.ttf --to otf --output output.otf
|
|
2361
|
-
|
|
2362
|
-
# Use stack-aware mode for safer optimization
|
|
2363
|
-
$ fontisan convert input.ttf --to otf --output output.otf --optimize --stack-aware
|
|
1856
|
+
[source,shell]
|
|
2364
1857
|
----
|
|
2365
|
-
|
|
1858
|
+
$ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otc
|
|
2366
1859
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
1860
|
+
Collection created successfully:
|
|
1861
|
+
Output: family.otc
|
|
1862
|
+
Format: OTC
|
|
1863
|
+
Fonts: 3
|
|
2371
1864
|
----
|
|
2372
|
-
# Adjust pattern matching sensitivity
|
|
2373
|
-
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
2374
|
-
--optimize \
|
|
2375
|
-
--min-pattern-length 15 \
|
|
2376
|
-
--max-subroutines 10000 \
|
|
2377
|
-
--verbose
|
|
2378
1865
|
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
--optimize \
|
|
2382
|
-
--no-optimize-ordering
|
|
2383
|
-
----
|
|
1866
|
+
NOTE: dfont supports mixed TrueType and OpenType fonts in the same suitcase.
|
|
1867
|
+
dfont does not perform table deduplication like TTC/OTC.
|
|
2384
1868
|
====
|
|
2385
1869
|
|
|
2386
|
-
Where,
|
|
2387
|
-
|
|
2388
|
-
`--optimize`:: Enable subroutine optimization (default: false)
|
|
2389
|
-
`--min-pattern-length N`:: Minimum pattern length in bytes (default: 10)
|
|
2390
|
-
`--max-subroutines N`:: Maximum number of subroutines to generate (default: 65,535)
|
|
2391
|
-
`--optimize-ordering`:: Optimize subroutine ordering by frequency (default: true)
|
|
2392
|
-
`--verbose`:: Show detailed optimization statistics
|
|
2393
1870
|
|
|
2394
|
-
|
|
2395
|
-
=== Stack-aware optimization
|
|
1871
|
+
=== Convert between font container formats
|
|
2396
1872
|
|
|
2397
1873
|
==== General
|
|
2398
1874
|
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
Unlike normal byte-level pattern matching, stack-aware mode simulates CharString
|
|
2403
|
-
execution to track operand stack depth, only extracting patterns that maintain
|
|
2404
|
-
consistent stack state.
|
|
1875
|
+
Fontisan supports converting font collections between TrueType Collection (TTC),
|
|
1876
|
+
OpenType Collection (OTC), and Apple dfont formats.
|
|
2405
1877
|
|
|
2406
|
-
|
|
1878
|
+
All collection formats (TTC, OTC, dfont) support mixed TrueType and OpenType fonts.
|
|
2407
1879
|
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
* **Faster Processing**: 6-12x faster than normal optimization due to early filtering
|
|
2411
|
-
* **Smaller Pattern Set**: Significantly fewer candidates reduce memory usage
|
|
1880
|
+
By default, original font formats are preserved during conversion. Use the
|
|
1881
|
+
`--target-format` option to standardize all fonts to a specific outline format:
|
|
2412
1882
|
|
|
2413
|
-
|
|
1883
|
+
* `--target-format preserve` (default) - Keep original mixed TTF+OTF formats
|
|
1884
|
+
* `--target-format ttf` - Convert all fonts to TrueType outlines
|
|
1885
|
+
* `--target-format otf` - Convert all fonts to OpenType/CFF outlines
|
|
2414
1886
|
|
|
2415
|
-
* **Lower Compression**: ~6% reduction vs ~11% with normal mode
|
|
2416
|
-
* **Fewer Patterns**: Filters out 90%+ of raw patterns for safety
|
|
2417
|
-
* **Stack Validation Overhead**: Adds stack tracking during analysis
|
|
2418
1887
|
|
|
2419
|
-
|
|
1888
|
+
NOTE: dfont supports mixed TrueType and OpenType fonts in the same suitcase.
|
|
2420
1889
|
|
|
2421
|
-
.
|
|
1890
|
+
.TTC to OTC conversion (preserves formats)
|
|
2422
1891
|
[example]
|
|
2423
1892
|
====
|
|
2424
|
-
[source,
|
|
1893
|
+
[source,shell]
|
|
2425
1894
|
----
|
|
2426
|
-
|
|
2427
|
-
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
2428
|
-
--optimize \
|
|
2429
|
-
--stack-aware \
|
|
2430
|
-
--verbose
|
|
2431
|
-
|
|
2432
|
-
Converting input.ttf to otf...
|
|
2433
|
-
|
|
2434
|
-
Analyzing CharString patterns (4515 glyphs)...
|
|
2435
|
-
Found 8566 potential patterns
|
|
2436
|
-
Selecting optimal patterns...
|
|
2437
|
-
Selected 832 patterns for subroutinization
|
|
2438
|
-
Building subroutines...
|
|
2439
|
-
Generated 832 subroutines
|
|
2440
|
-
Rewriting CharStrings with subroutine calls...
|
|
2441
|
-
Rewrote 4515 CharStrings
|
|
2442
|
-
|
|
2443
|
-
Subroutine Optimization Results:
|
|
2444
|
-
Patterns found: 8566
|
|
2445
|
-
Patterns selected: 832
|
|
2446
|
-
Subroutines generated: 832
|
|
2447
|
-
Estimated bytes saved: 46,280
|
|
2448
|
-
CFF bias: 0
|
|
1895
|
+
$ fontisan convert family.ttc --to otc --output family.otc
|
|
2449
1896
|
|
|
2450
1897
|
Conversion complete!
|
|
2451
|
-
Input:
|
|
2452
|
-
Output:
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
.Stack-aware vs normal mode
|
|
2457
|
-
[example]
|
|
2458
|
-
====
|
|
2459
|
-
[source,bash]
|
|
1898
|
+
Input: family.ttc (245.8 KB)
|
|
1899
|
+
Output: family.otc (312.4 KB)
|
|
1900
|
+
Format: TTC → OTC
|
|
1901
|
+
Fonts: 3
|
|
2460
1902
|
----
|
|
2461
|
-
# Use the comparison script
|
|
2462
|
-
$ ruby scripts/compare_stack_aware.rb input.ttf
|
|
2463
1903
|
|
|
2464
|
-
|
|
2465
|
-
Normal: 81.49 KB (11.27%)
|
|
2466
|
-
Stack-Aware: 43.17 KB (6.13%)
|
|
2467
|
-
|
|
2468
|
-
Processing Times:
|
|
2469
|
-
Normal: 18.38 s
|
|
2470
|
-
Stack-Aware: 1.54 s (12x faster)
|
|
2471
|
-
|
|
2472
|
-
Stack-Aware Efficiency: 52.97% of normal optimization
|
|
2473
|
-
----
|
|
1904
|
+
Original font formats are preserved (mixed TTF+OTF supported).
|
|
2474
1905
|
====
|
|
2475
1906
|
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
`--stack-aware`:: Enable stack-aware pattern detection (default: false)
|
|
2479
|
-
|
|
2480
|
-
==== Using the Ruby API
|
|
2481
|
-
|
|
2482
|
-
.Basic stack-aware optimization
|
|
1907
|
+
.TTC to OTC with forced conversion
|
|
2483
1908
|
[example]
|
|
2484
1909
|
====
|
|
2485
|
-
[source,
|
|
2486
|
-
----
|
|
2487
|
-
require 'fontisan'
|
|
2488
|
-
|
|
2489
|
-
# Load TrueType font
|
|
2490
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
2491
|
-
|
|
2492
|
-
# Convert with stack-aware optimization
|
|
2493
|
-
converter = Fontisan::Converters::OutlineConverter.new
|
|
2494
|
-
tables = converter.convert(font, {
|
|
2495
|
-
target_format: :otf,
|
|
2496
|
-
optimize_subroutines: true,
|
|
2497
|
-
stack_aware: true # Enable safe mode
|
|
2498
|
-
})
|
|
2499
|
-
|
|
2500
|
-
# Access optimization results
|
|
2501
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
2502
|
-
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
2503
|
-
puts "Stack-neutral patterns: #{optimization[:selected_count]}"
|
|
2504
|
-
puts "Processing time: #{optimization[:processing_time]}s"
|
|
2505
|
-
|
|
2506
|
-
# Write output
|
|
2507
|
-
Fontisan::FontWriter.write_to_file(
|
|
2508
|
-
tables,
|
|
2509
|
-
'output.otf',
|
|
2510
|
-
sfnt_version: 0x4F54544F
|
|
2511
|
-
)
|
|
2512
|
-
----
|
|
2513
|
-
====
|
|
2514
|
-
|
|
2515
|
-
==== Technical details
|
|
2516
|
-
|
|
2517
|
-
Stack-aware mode uses a three-stage validation process:
|
|
2518
|
-
|
|
2519
|
-
[source]
|
|
2520
|
-
----
|
|
2521
|
-
CharString Bytes → Stack Tracking → Pattern Validation → Safe Patterns
|
|
2522
|
-
(Input) (Simulate) (Filter) (Output)
|
|
1910
|
+
[source,shell]
|
|
2523
1911
|
----
|
|
1912
|
+
$ fontisan convert family.ttc --to otc --output family.otc --target-format otf
|
|
2524
1913
|
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
.
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
**Pattern validation**:
|
|
2532
|
-
|
|
2533
|
-
. Checks if pattern start and end have same stack depth
|
|
2534
|
-
. Ensures no stack underflow during pattern execution
|
|
2535
|
-
. Verifies consistent results regardless of initial stack state
|
|
2536
|
-
|
|
2537
|
-
**Stack-neutral pattern** criteria. Pattern is stack-neutral if:
|
|
2538
|
-
|
|
2539
|
-
. depth_at(pattern_start) == depth_at(pattern_end)
|
|
2540
|
-
. No negative depth during pattern execution
|
|
2541
|
-
. Pattern produces same result for any valid initial stack
|
|
2542
|
-
+
|
|
2543
|
-
.Example Stack-Neutral Pattern
|
|
2544
|
-
[source]
|
|
2545
|
-
----
|
|
2546
|
-
10 20 rmoveto # Pushes 2 operands, consumes 2 → neutral
|
|
2547
|
-
----
|
|
2548
|
-
+
|
|
2549
|
-
.Example Non-Neutral Pattern
|
|
2550
|
-
[source]
|
|
2551
|
-
----
|
|
2552
|
-
10 20 add # Pushes 2, consumes 2, produces 1 → NOT neutral
|
|
1914
|
+
Conversion complete!
|
|
1915
|
+
Input: family.ttc (245.8 KB)
|
|
1916
|
+
Output: family.otc (312.4 KB)
|
|
1917
|
+
Format: TTC → OTC
|
|
1918
|
+
Fonts: 3
|
|
2553
1919
|
----
|
|
2554
1920
|
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
**Recommended for**:
|
|
2558
|
-
|
|
2559
|
-
* Production font conversion where reliability is critical
|
|
2560
|
-
* Fonts that will undergo further processing
|
|
2561
|
-
* Web fonts where correctness matters more than minimal size
|
|
2562
|
-
* Situations where testing/validation is limited
|
|
2563
|
-
|
|
2564
|
-
**Normal mode acceptable for**:
|
|
2565
|
-
|
|
2566
|
-
* Development/testing environments
|
|
2567
|
-
* When full validation will be performed post-conversion
|
|
2568
|
-
* Maximum compression is priority over guaranteed safety
|
|
2569
|
-
|
|
2570
|
-
==== Using the Ruby API
|
|
1921
|
+
All TrueType fonts are converted to OpenType/CFF format.
|
|
1922
|
+
====
|
|
2571
1923
|
|
|
2572
|
-
.
|
|
1924
|
+
.dfont to OTC conversion (preserves formats)
|
|
2573
1925
|
[example]
|
|
2574
1926
|
====
|
|
2575
|
-
[source,
|
|
1927
|
+
[source,shell]
|
|
2576
1928
|
----
|
|
2577
|
-
|
|
1929
|
+
$ fontisan convert family.dfont --to otc --output family.otc
|
|
2578
1930
|
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
tables = converter.convert(font, {
|
|
2585
|
-
target_format: :otf,
|
|
2586
|
-
optimize_subroutines: true
|
|
2587
|
-
})
|
|
2588
|
-
|
|
2589
|
-
# Access optimization results
|
|
2590
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
2591
|
-
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
2592
|
-
puts "Selected: #{optimization[:selected_count]}"
|
|
2593
|
-
puts "Savings: #{optimization[:savings]} bytes"
|
|
2594
|
-
|
|
2595
|
-
# Write output
|
|
2596
|
-
Fontisan::FontWriter.write_to_file(
|
|
2597
|
-
tables,
|
|
2598
|
-
'output.otf',
|
|
2599
|
-
sfnt_version: 0x4F54544F
|
|
2600
|
-
)
|
|
1931
|
+
Conversion complete!
|
|
1932
|
+
Input: family.dfont (387.6 KB)
|
|
1933
|
+
Output: family.otc (312.4 KB)
|
|
1934
|
+
Format: DFONT → OTC
|
|
1935
|
+
Fonts: 3
|
|
2601
1936
|
----
|
|
1937
|
+
|
|
1938
|
+
Fonts are extracted from dfont and repacked as OTC.
|
|
1939
|
+
Original font formats are preserved (mixed TTF+OTF supported).
|
|
2602
1940
|
====
|
|
2603
1941
|
|
|
2604
|
-
.
|
|
1942
|
+
.dfont to OTC with forced conversion
|
|
2605
1943
|
[example]
|
|
2606
1944
|
====
|
|
2607
|
-
[source,
|
|
1945
|
+
[source,shell]
|
|
2608
1946
|
----
|
|
2609
|
-
|
|
1947
|
+
$ fontisan convert family.dfont --to otc --output family.otc --target-format otf
|
|
2610
1948
|
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
target_format: :otf,
|
|
2617
|
-
optimize_subroutines: true,
|
|
2618
|
-
min_pattern_length: 15,
|
|
2619
|
-
max_subroutines: 5000,
|
|
2620
|
-
optimize_ordering: true,
|
|
2621
|
-
verbose: true
|
|
2622
|
-
})
|
|
2623
|
-
|
|
2624
|
-
# Analyze results
|
|
2625
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
2626
|
-
if optimization[:selected_count] > 0
|
|
2627
|
-
efficiency = optimization[:savings].to_f / optimization[:selected_count]
|
|
2628
|
-
puts "Average savings per subroutine: #{efficiency.round(2)} bytes"
|
|
2629
|
-
end
|
|
1949
|
+
Conversion complete!
|
|
1950
|
+
Input: family.dfont (387.6 KB)
|
|
1951
|
+
Output: family.otc (312.4 KB)
|
|
1952
|
+
Format: DFONT → OTC
|
|
1953
|
+
Fonts: 3
|
|
2630
1954
|
----
|
|
1955
|
+
|
|
1956
|
+
Fonts are extracted from dfont and repacked as OTC.
|
|
1957
|
+
All TrueType fonts are converted to OpenType/CFF format.
|
|
2631
1958
|
====
|
|
2632
1959
|
|
|
2633
1960
|
|
|
@@ -2736,7 +2063,7 @@ Fontisan can:
|
|
|
2736
2063
|
|
|
2737
2064
|
=== Universal color layers
|
|
2738
2065
|
|
|
2739
|
-
(
|
|
2066
|
+
(Fontisan, converted TTF, OTF files)
|
|
2740
2067
|
|
|
2741
2068
|
Fontisan can:
|
|
2742
2069
|
|