fontisan 0.1.0 → 0.2.0
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 +529 -65
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1301 -275
- data/Rakefile +27 -2
- data/benchmark/variation_quick_bench.rb +47 -0
- data/docs/EXTRACT_TTC_MIGRATION.md +549 -0
- data/fontisan.gemspec +4 -1
- data/lib/fontisan/binary/base_record.rb +22 -1
- data/lib/fontisan/cli.rb +309 -0
- data/lib/fontisan/collection/builder.rb +260 -0
- data/lib/fontisan/collection/offset_calculator.rb +227 -0
- data/lib/fontisan/collection/table_analyzer.rb +204 -0
- data/lib/fontisan/collection/table_deduplicator.rb +241 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +8 -1
- data/lib/fontisan/commands/convert_command.rb +291 -0
- data/lib/fontisan/commands/export_command.rb +161 -0
- data/lib/fontisan/commands/info_command.rb +40 -6
- data/lib/fontisan/commands/instance_command.rb +295 -0
- data/lib/fontisan/commands/ls_command.rb +113 -0
- data/lib/fontisan/commands/pack_command.rb +241 -0
- data/lib/fontisan/commands/subset_command.rb +245 -0
- data/lib/fontisan/commands/unpack_command.rb +338 -0
- data/lib/fontisan/commands/validate_command.rb +178 -0
- data/lib/fontisan/commands/variable_command.rb +30 -1
- data/lib/fontisan/config/collection_settings.yml +56 -0
- data/lib/fontisan/config/conversion_matrix.yml +212 -0
- data/lib/fontisan/config/export_settings.yml +66 -0
- data/lib/fontisan/config/subset_profiles.yml +100 -0
- data/lib/fontisan/config/svg_settings.yml +60 -0
- data/lib/fontisan/config/validation_rules.yml +149 -0
- data/lib/fontisan/config/variable_settings.yml +99 -0
- data/lib/fontisan/config/woff2_settings.yml +77 -0
- data/lib/fontisan/constants.rb +69 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +259 -0
- data/lib/fontisan/converters/outline_converter.rb +936 -0
- data/lib/fontisan/converters/svg_generator.rb +244 -0
- data/lib/fontisan/converters/table_copier.rb +117 -0
- data/lib/fontisan/converters/woff2_encoder.rb +416 -0
- data/lib/fontisan/converters/woff_writer.rb +391 -0
- data/lib/fontisan/error.rb +203 -0
- data/lib/fontisan/export/exporter.rb +262 -0
- data/lib/fontisan/export/table_serializer.rb +255 -0
- data/lib/fontisan/export/transformers/font_to_ttx.rb +172 -0
- data/lib/fontisan/export/transformers/head_transformer.rb +96 -0
- data/lib/fontisan/export/transformers/hhea_transformer.rb +59 -0
- data/lib/fontisan/export/transformers/maxp_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/name_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/os2_transformer.rb +121 -0
- data/lib/fontisan/export/transformers/post_transformer.rb +51 -0
- data/lib/fontisan/export/ttx_generator.rb +527 -0
- data/lib/fontisan/export/ttx_parser.rb +300 -0
- data/lib/fontisan/font_loader.rb +121 -12
- data/lib/fontisan/font_writer.rb +301 -0
- data/lib/fontisan/formatters/text_formatter.rb +102 -0
- data/lib/fontisan/glyph_accessor.rb +503 -0
- data/lib/fontisan/hints/hint_converter.rb +177 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +185 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +254 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +71 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +162 -0
- data/lib/fontisan/loading_modes.rb +113 -0
- data/lib/fontisan/metrics_calculator.rb +277 -0
- data/lib/fontisan/models/collection_font_summary.rb +52 -0
- data/lib/fontisan/models/collection_info.rb +76 -0
- data/lib/fontisan/models/collection_list_info.rb +37 -0
- data/lib/fontisan/models/font_export.rb +158 -0
- data/lib/fontisan/models/font_summary.rb +48 -0
- data/lib/fontisan/models/glyph_outline.rb +343 -0
- data/lib/fontisan/models/hint.rb +233 -0
- data/lib/fontisan/models/outline.rb +664 -0
- data/lib/fontisan/models/table_sharing_info.rb +40 -0
- data/lib/fontisan/models/ttx/glyph_order.rb +31 -0
- data/lib/fontisan/models/ttx/tables/binary_table.rb +67 -0
- data/lib/fontisan/models/ttx/tables/head_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/hhea_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/maxp_table.rb +55 -0
- data/lib/fontisan/models/ttx/tables/name_table.rb +45 -0
- data/lib/fontisan/models/ttx/tables/os2_table.rb +157 -0
- data/lib/fontisan/models/ttx/tables/post_table.rb +50 -0
- data/lib/fontisan/models/ttx/ttfont.rb +49 -0
- data/lib/fontisan/models/validation_report.rb +203 -0
- data/lib/fontisan/open_type_collection.rb +156 -2
- data/lib/fontisan/open_type_font.rb +296 -10
- data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
- data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
- data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
- data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
- data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
- data/lib/fontisan/outline_extractor.rb +423 -0
- data/lib/fontisan/subset/builder.rb +268 -0
- data/lib/fontisan/subset/glyph_mapping.rb +215 -0
- data/lib/fontisan/subset/options.rb +142 -0
- data/lib/fontisan/subset/profile.rb +152 -0
- data/lib/fontisan/subset/table_subsetter.rb +461 -0
- data/lib/fontisan/svg/font_face_generator.rb +278 -0
- data/lib/fontisan/svg/font_generator.rb +264 -0
- data/lib/fontisan/svg/glyph_generator.rb +168 -0
- data/lib/fontisan/svg/view_box_calculator.rb +137 -0
- data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
- data/lib/fontisan/tables/cff/charset.rb +282 -0
- data/lib/fontisan/tables/cff/charstring.rb +905 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +322 -0
- data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
- data/lib/fontisan/tables/cff/dict.rb +351 -0
- data/lib/fontisan/tables/cff/dict_builder.rb +242 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -0
- data/lib/fontisan/tables/cff/index.rb +237 -0
- data/lib/fontisan/tables/cff/index_builder.rb +170 -0
- data/lib/fontisan/tables/cff/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +487 -0
- data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
- data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
- data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
- data/lib/fontisan/tables/cff2.rb +341 -0
- data/lib/fontisan/tables/cvar.rb +242 -0
- data/lib/fontisan/tables/fvar.rb +2 -2
- data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
- data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
- data/lib/fontisan/tables/glyf.rb +235 -0
- data/lib/fontisan/tables/gvar.rb +270 -0
- data/lib/fontisan/tables/hhea.rb +124 -0
- data/lib/fontisan/tables/hmtx.rb +287 -0
- data/lib/fontisan/tables/hvar.rb +191 -0
- data/lib/fontisan/tables/loca.rb +322 -0
- data/lib/fontisan/tables/maxp.rb +192 -0
- data/lib/fontisan/tables/mvar.rb +185 -0
- data/lib/fontisan/tables/name.rb +99 -30
- data/lib/fontisan/tables/variation_common.rb +346 -0
- data/lib/fontisan/tables/vvar.rb +234 -0
- data/lib/fontisan/true_type_collection.rb +156 -2
- data/lib/fontisan/true_type_font.rb +297 -11
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +18 -0
- data/lib/fontisan/utils/thread_pool.rb +134 -0
- data/lib/fontisan/validation/checksum_validator.rb +170 -0
- data/lib/fontisan/validation/consistency_validator.rb +197 -0
- data/lib/fontisan/validation/structure_validator.rb +198 -0
- data/lib/fontisan/validation/table_validator.rb +158 -0
- data/lib/fontisan/validation/validator.rb +152 -0
- data/lib/fontisan/variable/axis_normalizer.rb +215 -0
- data/lib/fontisan/variable/delta_applicator.rb +313 -0
- data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
- data/lib/fontisan/variable/instancer.rb +344 -0
- data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
- data/lib/fontisan/variable/region_matcher.rb +208 -0
- data/lib/fontisan/variable/static_font_builder.rb +213 -0
- data/lib/fontisan/variable/table_updater.rb +219 -0
- data/lib/fontisan/variation/blend_applier.rb +199 -0
- data/lib/fontisan/variation/cache.rb +298 -0
- data/lib/fontisan/variation/cache_key_builder.rb +162 -0
- data/lib/fontisan/variation/converter.rb +268 -0
- data/lib/fontisan/variation/data_extractor.rb +86 -0
- data/lib/fontisan/variation/delta_applier.rb +266 -0
- data/lib/fontisan/variation/delta_parser.rb +228 -0
- data/lib/fontisan/variation/inspector.rb +275 -0
- data/lib/fontisan/variation/instance_generator.rb +273 -0
- data/lib/fontisan/variation/interpolator.rb +231 -0
- data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
- data/lib/fontisan/variation/optimizer.rb +418 -0
- data/lib/fontisan/variation/parallel_generator.rb +150 -0
- data/lib/fontisan/variation/region_matcher.rb +221 -0
- data/lib/fontisan/variation/subsetter.rb +463 -0
- data/lib/fontisan/variation/table_accessor.rb +105 -0
- data/lib/fontisan/variation/validator.rb +345 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +712 -0
- data/lib/fontisan/woff_font.rb +483 -0
- data/lib/fontisan.rb +120 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +205 -4
data/Rakefile
CHANGED
|
@@ -11,6 +11,20 @@ RuboCop::RakeTask.new
|
|
|
11
11
|
namespace :fixtures do
|
|
12
12
|
fixtures_dir = "spec/fixtures/fonts"
|
|
13
13
|
|
|
14
|
+
# Helper method to download a single file
|
|
15
|
+
def download_single_file(name, url, target_path)
|
|
16
|
+
require "open-uri"
|
|
17
|
+
|
|
18
|
+
puts "[fixtures:download] Downloading #{name}..."
|
|
19
|
+
FileUtils.mkdir_p(File.dirname(target_path))
|
|
20
|
+
|
|
21
|
+
URI.open(url) do |remote|
|
|
22
|
+
File.binwrite(target_path, remote.read)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
puts "[fixtures:download] #{name} downloaded successfully"
|
|
26
|
+
end
|
|
27
|
+
|
|
14
28
|
# Helper method to download and extract a font archive
|
|
15
29
|
def download_font(name, url, target_dir)
|
|
16
30
|
require "open-uri"
|
|
@@ -44,6 +58,12 @@ namespace :fixtures do
|
|
|
44
58
|
# Font configurations with target directories and marker files
|
|
45
59
|
# All fonts are downloaded via Rake
|
|
46
60
|
fonts = {
|
|
61
|
+
"NotoSans" => {
|
|
62
|
+
url: "https://github.com/notofonts/notofonts.github.io/raw/refs/heads/main/fonts/NotoSans/full/ttf/NotoSans-Regular.ttf",
|
|
63
|
+
target_dir: fixtures_dir.to_s,
|
|
64
|
+
marker: "#{fixtures_dir}/NotoSans-Regular.ttf",
|
|
65
|
+
single_file: true,
|
|
66
|
+
},
|
|
47
67
|
"Libertinus" => {
|
|
48
68
|
url: "https://github.com/alerque/libertinus/releases/download/v7.051/Libertinus-7.051.zip",
|
|
49
69
|
target_dir: "#{fixtures_dir}/libertinus",
|
|
@@ -69,7 +89,11 @@ namespace :fixtures do
|
|
|
69
89
|
# Create file tasks for each font
|
|
70
90
|
fonts.each do |name, config|
|
|
71
91
|
file config[:marker] do
|
|
72
|
-
|
|
92
|
+
if config[:single_file]
|
|
93
|
+
download_single_file(name, config[:url], config[:marker])
|
|
94
|
+
else
|
|
95
|
+
download_font(name, config[:url], config[:target_dir])
|
|
96
|
+
end
|
|
73
97
|
end
|
|
74
98
|
end
|
|
75
99
|
|
|
@@ -78,7 +102,8 @@ namespace :fixtures do
|
|
|
78
102
|
|
|
79
103
|
desc "Clean downloaded fixtures"
|
|
80
104
|
task :clean do
|
|
81
|
-
%w[libertinus MonaSans NotoSerifCJK NotoSerifCJK-VF
|
|
105
|
+
%w[libertinus MonaSans NotoSerifCJK NotoSerifCJK-VF
|
|
106
|
+
NotoSans-Regular.ttf].each do |dir|
|
|
82
107
|
path = File.join(fixtures_dir, dir)
|
|
83
108
|
if File.exist?(path)
|
|
84
109
|
FileUtils.rm_rf(path)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'benchmark'
|
|
4
|
+
require 'fontisan'
|
|
5
|
+
|
|
6
|
+
# Load variable font
|
|
7
|
+
FONT_PATH = 'spec/fixtures/SourceSans3VF-Roman.otf'
|
|
8
|
+
|
|
9
|
+
unless File.exist?(FONT_PATH)
|
|
10
|
+
puts "Error: Test font not found at #{FONT_PATH}"
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
font = Fontisan::FontLoader.load(FONT_PATH)
|
|
15
|
+
|
|
16
|
+
puts "=== Variable Font Parallel Performance ==="
|
|
17
|
+
puts "Font: #{FONT_PATH}"
|
|
18
|
+
puts
|
|
19
|
+
|
|
20
|
+
# Test coordinates
|
|
21
|
+
coords = 4.times.map { |i| { "wght" => 300 + i * 200 } }
|
|
22
|
+
puts "Generating #{coords.size} instances"
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
# Sequential (1 thread)
|
|
26
|
+
seq_gen = Fontisan::Variation::ParallelGenerator.new(font, threads: 1)
|
|
27
|
+
seq_time = Benchmark.realtime do
|
|
28
|
+
seq_gen.generate_batch(coords)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Parallel (4 threads)
|
|
32
|
+
par_gen = Fontisan::Variation::ParallelGenerator.new(font, threads: 4)
|
|
33
|
+
par_time = Benchmark.realtime do
|
|
34
|
+
par_gen.generate_batch(coords)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "Sequential (1 thread): #{format('%.3f', seq_time)}s"
|
|
38
|
+
puts "Parallel (4 threads): #{format('%.3f', par_time)}s"
|
|
39
|
+
puts "Speedup: #{format('%.2f', seq_time / par_time)}x"
|
|
40
|
+
puts
|
|
41
|
+
|
|
42
|
+
# Cache stats
|
|
43
|
+
cache = Fontisan::Variation::ThreadSafeCache.new
|
|
44
|
+
5.times { cache.fetch("test_#{rand(100)}") { rand } }
|
|
45
|
+
|
|
46
|
+
puts "Cache Statistics:"
|
|
47
|
+
puts cache.statistics.inspect
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# Migration Guide: extract_ttc → fontisan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**fontisan now fully supersedes extract_ttc** with superior functionality and a better user experience. All extract_ttc features are available in fontisan, plus many more.
|
|
6
|
+
|
|
7
|
+
## Quick Command Translation
|
|
8
|
+
|
|
9
|
+
| extract_ttc Command | fontisan Equivalent | Notes |
|
|
10
|
+
|---------------------|---------------------|-------|
|
|
11
|
+
| `extract_ttc ls file.ttc` | `fontisan ls file.ttc` | ✅ Identical output, better formatting |
|
|
12
|
+
| `extract_ttc info file.ttc` | `fontisan info file.ttc` | ✅ Plus table sharing statistics |
|
|
13
|
+
| `extract_ttc extract file.ttc` | `fontisan unpack file.ttc --output-dir .` | ✅ More powerful with format conversion |
|
|
14
|
+
|
|
15
|
+
## Feature Comparison
|
|
16
|
+
|
|
17
|
+
| Feature | extract_ttc | fontisan | Advantage |
|
|
18
|
+
|---------|-------------|----------|-----------|
|
|
19
|
+
| **List fonts in TTC** | ✓ | ✓ | Equal |
|
|
20
|
+
| **Show TTC metadata** | ✓ | ✓ | Equal |
|
|
21
|
+
| **Extract TTC fonts** | ✓ | ✓ | Equal |
|
|
22
|
+
| **Table sharing stats** | ✗ | ✓ | 🎯 fontisan |
|
|
23
|
+
| **Auto-detection** | ✗ | ✓ | 🎯 fontisan |
|
|
24
|
+
| **Works on TTF/OTF** | ✗ | ✓ | 🎯 fontisan |
|
|
25
|
+
| **Format conversion** | ✗ | ✓ | 🎯 fontisan |
|
|
26
|
+
| **Output formats** | Text only | Text, YAML, JSON | 🎯 fontisan |
|
|
27
|
+
| **Font analysis** | ✗ | ✓ (17 commands) | 🎯 fontisan |
|
|
28
|
+
| **Variable fonts** | ✗ | ✓ | 🎯 fontisan |
|
|
29
|
+
| **Font subsetting** | ✗ | ✓ | 🎯 fontisan |
|
|
30
|
+
| **Font validation** | ✗ | ✓ | 🎯 fontisan |
|
|
31
|
+
| **Create collections** | ✗ | ✓ | 🎯 fontisan |
|
|
32
|
+
|
|
33
|
+
## Migration Examples
|
|
34
|
+
|
|
35
|
+
### Listing Fonts
|
|
36
|
+
|
|
37
|
+
**extract_ttc:**
|
|
38
|
+
```bash
|
|
39
|
+
extract_ttc ls fonts.ttc
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**fontisan:**
|
|
43
|
+
```bash
|
|
44
|
+
fontisan ls fonts.ttc
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Output is cleaner and shows more info:
|
|
48
|
+
```
|
|
49
|
+
Collection: fonts.ttc
|
|
50
|
+
Fonts: 2
|
|
51
|
+
|
|
52
|
+
0. Helvetica Regular
|
|
53
|
+
PostScript: Helvetica-Regular
|
|
54
|
+
Format: TrueType
|
|
55
|
+
Glyphs: 268, Tables: 14
|
|
56
|
+
|
|
57
|
+
1. Helvetica Bold
|
|
58
|
+
PostScript: Helvetica-Bold
|
|
59
|
+
Format: TrueType
|
|
60
|
+
Glyphs: 268, Tables: 14
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Collection Information
|
|
64
|
+
|
|
65
|
+
**extract_ttc:**
|
|
66
|
+
```bash
|
|
67
|
+
extract_ttc info fonts.ttc
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**fontisan:**
|
|
71
|
+
```bash
|
|
72
|
+
fontisan info fonts.ttc
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
fontisan shows MORE information:
|
|
76
|
+
```
|
|
77
|
+
=== Collection Information ===
|
|
78
|
+
File: fonts.ttc
|
|
79
|
+
Format: TTC
|
|
80
|
+
Size: 2.55 KB
|
|
81
|
+
|
|
82
|
+
=== Header ===
|
|
83
|
+
Tag: ttcf
|
|
84
|
+
Version: 1.0
|
|
85
|
+
Number of fonts: 2
|
|
86
|
+
|
|
87
|
+
=== Font Offsets ===
|
|
88
|
+
0. Offset: 20
|
|
89
|
+
1. Offset: 272
|
|
90
|
+
|
|
91
|
+
=== Table Sharing === ← NEW!
|
|
92
|
+
Shared tables: 17 ← NEW!
|
|
93
|
+
Unique tables: 13 ← NEW!
|
|
94
|
+
Sharing: 56.67% ← NEW!
|
|
95
|
+
Space saved: 2.02 KB ← NEW!
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Extracting Fonts
|
|
99
|
+
|
|
100
|
+
**extract_ttc:**
|
|
101
|
+
```bash
|
|
102
|
+
extract_ttc extract fonts.ttc -o output/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**fontisan:**
|
|
106
|
+
```bash
|
|
107
|
+
fontisan unpack fonts.ttc --output-dir output/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
fontisan can also convert during extraction:
|
|
111
|
+
```bash
|
|
112
|
+
# Extract as WOFF2 for web use
|
|
113
|
+
fontisan unpack fonts.ttc --output-dir web/ --format woff2
|
|
114
|
+
|
|
115
|
+
# Extract specific font only
|
|
116
|
+
fontisan unpack fonts.ttc --output-dir . --font-index 0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Why Switch to fontisan?
|
|
120
|
+
|
|
121
|
+
### 1. Universal Commands
|
|
122
|
+
|
|
123
|
+
fontisan commands work on **all** font formats:
|
|
124
|
+
```bash
|
|
125
|
+
# Works on collections
|
|
126
|
+
fontisan ls fonts.ttc
|
|
127
|
+
fontisan info fonts.ttc
|
|
128
|
+
|
|
129
|
+
# Also works on individual fonts
|
|
130
|
+
fontisan ls font.ttf
|
|
131
|
+
fontisan info font.otf
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 2. Comprehensive Font Analysis
|
|
135
|
+
|
|
136
|
+
Beyond extract_ttc, fontisan provides:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Analyze font features
|
|
140
|
+
fontisan features font.ttf --script latn
|
|
141
|
+
|
|
142
|
+
# Check variable font axes
|
|
143
|
+
fontisan variable VariableFont.ttf
|
|
144
|
+
|
|
145
|
+
# Validate font integrity
|
|
146
|
+
fontisan validate font.ttf
|
|
147
|
+
|
|
148
|
+
# Subset to specific characters
|
|
149
|
+
fontisan subset font.ttf --text "Hello" --output hello.ttf
|
|
150
|
+
|
|
151
|
+
# Convert formats
|
|
152
|
+
fontisan convert font.ttf --to woff2 --output font.woff2
|
|
153
|
+
|
|
154
|
+
# Create collections
|
|
155
|
+
fontisan pack Regular.ttf Bold.ttf --output Family.ttc
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. Modern Output Formats
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Human-readable text (default)
|
|
162
|
+
fontisan info fonts.ttc
|
|
163
|
+
|
|
164
|
+
# Machine-readable YAML
|
|
165
|
+
fontisan info fonts.ttc --format yaml
|
|
166
|
+
|
|
167
|
+
# Machine-readable JSON
|
|
168
|
+
fontisan info fonts.ttc --format json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 4. Better Error Messages
|
|
172
|
+
|
|
173
|
+
fontisan provides clear, actionable error messages:
|
|
174
|
+
```
|
|
175
|
+
Error: Font index 5 out of range (collection has 2 fonts)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
vs extract_ttc generic Ruby errors.
|
|
179
|
+
|
|
180
|
+
## Installation
|
|
181
|
+
|
|
182
|
+
### Remove extract_ttc (optional)
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
gem uninstall extract_ttc
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Install fontisan
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
gem install fontisan
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Or add to Gemfile:
|
|
195
|
+
```ruby
|
|
196
|
+
gem 'fontisan'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## API Usage
|
|
200
|
+
|
|
201
|
+
### extract_ttc API
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
require 'extract_ttc'
|
|
205
|
+
|
|
206
|
+
# List fonts
|
|
207
|
+
output_files = ExtractTtc.extract("fonts.ttc")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### fontisan API (More Powerful)
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
require 'fontisan'
|
|
214
|
+
|
|
215
|
+
# List fonts in collection
|
|
216
|
+
collection = Fontisan::FontLoader.load_collection("fonts.ttc")
|
|
217
|
+
File.open("fonts.ttc", "rb") do |io|
|
|
218
|
+
list = collection.list_fonts(io)
|
|
219
|
+
list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Get collection metadata
|
|
223
|
+
File.open("fonts.ttc", "rb") do |io|
|
|
224
|
+
info = collection.collection_info(io, "fonts.ttc")
|
|
225
|
+
puts "Fonts: #{info.num_fonts}"
|
|
226
|
+
puts "Sharing: #{info.table_sharing.sharing_percentage}%"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Extract fonts with more control
|
|
230
|
+
File.open("fonts.ttc", "rb") do |io|
|
|
231
|
+
fonts = collection.extract_fonts(io)
|
|
232
|
+
fonts.each_with_index do |font, i|
|
|
233
|
+
font.to_file("output/font_#{i}.ttf")
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Backward Compatibility
|
|
239
|
+
|
|
240
|
+
fontisan maintains 100% backward compatibility:
|
|
241
|
+
- All existing `fontisan` commands continue to work
|
|
242
|
+
- No breaking changes to existing functionality
|
|
243
|
+
- New commands add features without removing anything
|
|
244
|
+
|
|
245
|
+
#=========================================================================
|
|
246
|
+
|
|
247
|
+
= ExtractTTC to Fontisan Migration Guide
|
|
248
|
+
|
|
249
|
+
This guide helps users migrate from https://github.com/fontist/extract_ttc[ExtractTTC] to Fontisan.
|
|
250
|
+
|
|
251
|
+
Fontisan provides complete compatibility with all ExtractTTC functionality while adding comprehensive font analysis, subsetting, validation, and format conversion capabilities.
|
|
252
|
+
|
|
253
|
+
== Command Mapping Reference
|
|
254
|
+
|
|
255
|
+
[cols="2,2,3"]
|
|
256
|
+
|===
|
|
257
|
+
|ExtractTTC Command |Fontisan Equivalent |Description
|
|
258
|
+
|
|
259
|
+
|`extract_ttc --list FONT.ttc`
|
|
260
|
+
|`fontisan ls FONT.ttc`
|
|
261
|
+
|List fonts in collection with index, family, and style
|
|
262
|
+
|
|
263
|
+
|`extract_ttc --info FONT.ttc`
|
|
264
|
+
|`fontisan info FONT.ttc`
|
|
265
|
+
|Show detailed font information for collection
|
|
266
|
+
|
|
267
|
+
|`extract_ttc --unpack FONT.ttc OUTPUT_DIR`
|
|
268
|
+
|`fontisan unpack FONT.ttc OUTPUT_DIR`
|
|
269
|
+
|Extract all fonts from collection to directory
|
|
270
|
+
|
|
271
|
+
|`extract_ttc --font-index INDEX FONT.ttc OUTPUT.ttf`
|
|
272
|
+
|`fontisan unpack FONT.ttc --font-index INDEX OUTPUT.ttf`
|
|
273
|
+
|Extract specific font by index
|
|
274
|
+
|
|
275
|
+
|`extract_ttc --validate FONT.ttc`
|
|
276
|
+
|`fontisan validate FONT.ttc`
|
|
277
|
+
|Validate font/collection structure and checksums
|
|
278
|
+
|===
|
|
279
|
+
|
|
280
|
+
== Enhanced Collection Management
|
|
281
|
+
|
|
282
|
+
Fontisan provides additional collection features beyond ExtractTTC:
|
|
283
|
+
|
|
284
|
+
=== List Collection Contents
|
|
285
|
+
|
|
286
|
+
[source,shell]
|
|
287
|
+
----
|
|
288
|
+
# List all fonts in a TTC with detailed info
|
|
289
|
+
$ fontisan ls spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc
|
|
290
|
+
|
|
291
|
+
Font 0: Noto Serif CJK JP
|
|
292
|
+
Family: Noto Serif CJK JP
|
|
293
|
+
Subfamily: Regular
|
|
294
|
+
PostScript: NotoSerifCJKJP-Regular
|
|
295
|
+
|
|
296
|
+
Font 1: Noto Serif CJK KR
|
|
297
|
+
Family: Noto Serif CJK KR
|
|
298
|
+
Subfamily: Regular
|
|
299
|
+
PostScript: NotoSerifCJKKR-Regular
|
|
300
|
+
|
|
301
|
+
Font 2: Noto Serif CJK SC
|
|
302
|
+
Family: Noto Serif CJK SC
|
|
303
|
+
Subfamily: Regular
|
|
304
|
+
PostScript: NotoSerifCJKSC-Regular
|
|
305
|
+
|
|
306
|
+
Font 3: Noto Serif CJK TC
|
|
307
|
+
Family: Noto Serif CJK TC
|
|
308
|
+
Subfamily: Regular
|
|
309
|
+
PostScript: NotoSerifCJKTC-Regular
|
|
310
|
+
----
|
|
311
|
+
|
|
312
|
+
=== Extract with Validation
|
|
313
|
+
|
|
314
|
+
[source,shell]
|
|
315
|
+
----
|
|
316
|
+
# Extract and validate simultaneously
|
|
317
|
+
$ fontisan unpack spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc extracted_fonts/ --validate
|
|
318
|
+
|
|
319
|
+
Extracting font 0: Noto Serif CJK JP → extracted_fonts/NotoSerifCJKJP-Regular.ttf
|
|
320
|
+
Extracting font 1: Noto Serif CJK KR → extracted_fonts/NotoSerifCJKKR-Regular.ttf
|
|
321
|
+
Extracting font 2: Noto Serif CJK SC → extracted_fonts/NotoSerifCJKSC-Regular.ttf
|
|
322
|
+
Extracting font 3: Noto Serif CJK TC → extracted_fonts/NotoSerifCJKTC-Regular.ttf
|
|
323
|
+
|
|
324
|
+
Validation: All fonts extracted successfully
|
|
325
|
+
----
|
|
326
|
+
|
|
327
|
+
=== Get Collection Information
|
|
328
|
+
|
|
329
|
+
[source,shell]
|
|
330
|
+
----
|
|
331
|
+
# Detailed collection analysis
|
|
332
|
+
$ fontisan info spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc --format yaml
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
collection_type: ttc
|
|
336
|
+
font_count: 4
|
|
337
|
+
fonts:
|
|
338
|
+
- index: 0
|
|
339
|
+
family_name: Noto Serif CJK JP
|
|
340
|
+
subfamily_name: Regular
|
|
341
|
+
postscript_name: NotoSerifCJKJP-Regular
|
|
342
|
+
font_format: opentype
|
|
343
|
+
- index: 1
|
|
344
|
+
family_name: Noto Serif CJK KR
|
|
345
|
+
subfamily_name: Regular
|
|
346
|
+
postscript_name: NotoSerifCJKKR-Regular
|
|
347
|
+
font_format: opentype
|
|
348
|
+
- index: 2
|
|
349
|
+
family_name: Noto Serif CJK SC
|
|
350
|
+
subfamily_name: Regular
|
|
351
|
+
postscript_name: NotoSerifCJKSC-Regular
|
|
352
|
+
font_format: opentype
|
|
353
|
+
- index: 3
|
|
354
|
+
family_name: Noto Serif CJK TC
|
|
355
|
+
subfamily_name: Regular
|
|
356
|
+
postscript_name: NotoSerifCJKTC-Regular
|
|
357
|
+
font_format: opentype
|
|
358
|
+
----
|
|
359
|
+
|
|
360
|
+
== Advanced Features Beyond ExtractTTC
|
|
361
|
+
|
|
362
|
+
Fontisan provides capabilities that ExtractTTC never had:
|
|
363
|
+
|
|
364
|
+
=== Collection Creation
|
|
365
|
+
|
|
366
|
+
Create new TTC/OTC files from individual fonts with automatic table sharing optimization:
|
|
367
|
+
|
|
368
|
+
[source,shell]
|
|
369
|
+
----
|
|
370
|
+
# Pack fonts into TTC with table sharing optimization
|
|
371
|
+
$ fontisan pack font1.ttf font2.ttf font3.ttf --output family.ttc --analyze
|
|
372
|
+
|
|
373
|
+
Collection Analysis:
|
|
374
|
+
Total fonts: 3
|
|
375
|
+
Shared tables: 12
|
|
376
|
+
Potential space savings: 45.2 KB
|
|
377
|
+
Table sharing: 68.5%
|
|
378
|
+
|
|
379
|
+
Collection created successfully:
|
|
380
|
+
Output: family.ttc
|
|
381
|
+
Format: TTC
|
|
382
|
+
Fonts: 3
|
|
383
|
+
Size: 245.8 KB
|
|
384
|
+
Space saved: 45.2 KB
|
|
385
|
+
Sharing: 68.5%
|
|
386
|
+
----
|
|
387
|
+
|
|
388
|
+
=== Format Conversion and Subsetting
|
|
389
|
+
|
|
390
|
+
Convert between font formats and create optimized subsets:
|
|
391
|
+
|
|
392
|
+
[source,shell]
|
|
393
|
+
----
|
|
394
|
+
# Convert TTF to WOFF2 for web usage
|
|
395
|
+
$ fontisan convert font.ttf --to woff2 --output font.woff2
|
|
396
|
+
|
|
397
|
+
# Create PDF-optimized subset
|
|
398
|
+
$ fontisan subset font.ttf --text "Hello World" --output subset.ttf --profile pdf
|
|
399
|
+
----
|
|
400
|
+
|
|
401
|
+
=== Font Analysis and Inspection
|
|
402
|
+
|
|
403
|
+
Comprehensive font analysis capabilities:
|
|
404
|
+
|
|
405
|
+
[source,shell]
|
|
406
|
+
----
|
|
407
|
+
# Extract OpenType tables with details
|
|
408
|
+
$ fontisan tables font.ttf --format yaml
|
|
409
|
+
|
|
410
|
+
# Display variable font information
|
|
411
|
+
$ fontisan variable font.ttf
|
|
412
|
+
|
|
413
|
+
# Show supported scripts and features
|
|
414
|
+
$ fontisan scripts font.ttf
|
|
415
|
+
$ fontisan features font.ttf --script latn
|
|
416
|
+
|
|
417
|
+
# Dump raw table data for analysis
|
|
418
|
+
$ fontisan dump-table font.ttf name > name_table.bin
|
|
419
|
+
----
|
|
420
|
+
|
|
421
|
+
=== Font Validation
|
|
422
|
+
|
|
423
|
+
Multiple validation levels with detailed reporting:
|
|
424
|
+
|
|
425
|
+
[source,shell]
|
|
426
|
+
----
|
|
427
|
+
# Standard validation (allows warnings)
|
|
428
|
+
$ fontisan validate font.ttf
|
|
429
|
+
|
|
430
|
+
# Strict validation (no warnings allowed)
|
|
431
|
+
$ fontisan validate font.ttf --level strict
|
|
432
|
+
|
|
433
|
+
# Detailed validation report
|
|
434
|
+
$ fontisan validate font.ttf --format yaml
|
|
435
|
+
----
|
|
436
|
+
|
|
437
|
+
== Migration Examples
|
|
438
|
+
|
|
439
|
+
=== Basic Collection Operations
|
|
440
|
+
|
|
441
|
+
[source,shell]
|
|
442
|
+
# ExtractTTC style operations
|
|
443
|
+
extract_ttc --list collection.ttc
|
|
444
|
+
fontisan ls collection.ttc
|
|
445
|
+
|
|
446
|
+
extract_ttc --info collection.ttc
|
|
447
|
+
fontisan info collection.ttc
|
|
448
|
+
|
|
449
|
+
extract_ttc --unpack collection.ttc output_dir/
|
|
450
|
+
fontisan unpack collection.ttc output_dir/
|
|
451
|
+
|
|
452
|
+
extract_ttc --font-index 0 collection.ttc font0.ttf
|
|
453
|
+
fontisan unpack collection.ttc --font-index 0 font0.ttf
|
|
454
|
+
----
|
|
455
|
+
|
|
456
|
+
=== Enhanced Operations (Fontisan Only)
|
|
457
|
+
|
|
458
|
+
[source,shell]
|
|
459
|
+
# Create collections (not possible with ExtractTTC)
|
|
460
|
+
fontisan pack font1.ttf font2.ttf --output new_collection.ttc
|
|
461
|
+
|
|
462
|
+
# Convert formats
|
|
463
|
+
fontisan convert font.ttf --to woff2 --output font.woff2
|
|
464
|
+
|
|
465
|
+
# Create subsets
|
|
466
|
+
fontisan subset font.ttf --unicode "U+0041-U+005A" --output latin.ttf
|
|
467
|
+
|
|
468
|
+
# Validate fonts
|
|
469
|
+
fontisan validate font.ttf --level strict
|
|
470
|
+
|
|
471
|
+
# Analyze fonts
|
|
472
|
+
fontisan scripts font.ttf
|
|
473
|
+
fontisan features font.ttf --script latn
|
|
474
|
+
fontisan variable font.ttf
|
|
475
|
+
----
|
|
476
|
+
|
|
477
|
+
== Configuration and Options
|
|
478
|
+
|
|
479
|
+
Fontisan provides extensive configuration options:
|
|
480
|
+
|
|
481
|
+
=== Global Options
|
|
482
|
+
All commands support these options:
|
|
483
|
+
|
|
484
|
+
* `--format FORMAT`: Output format (`text`, `json`, `yaml`)
|
|
485
|
+
* `--font-index INDEX`: Font index for TTC files (default: 0)
|
|
486
|
+
* `--verbose`: Enable verbose output
|
|
487
|
+
* `--quiet`: Suppress non-error output
|
|
488
|
+
|
|
489
|
+
=== Collection-Specific Options
|
|
490
|
+
|
|
491
|
+
* `--analyze`: Show space analysis for pack operations
|
|
492
|
+
* `--optimize`: Enable table sharing optimization (default: true)
|
|
493
|
+
* `--format {ttc|otc}`: Collection format for pack operations
|
|
494
|
+
|
|
495
|
+
=== Conversion Options
|
|
496
|
+
|
|
497
|
+
* `--to FORMAT`: Target format for conversion
|
|
498
|
+
* `--quality LEVEL`: Compression quality (0-11)
|
|
499
|
+
* `--profile PROFILE`: Subsetting profile (`pdf`, `web`, `minimal`)
|
|
500
|
+
|
|
501
|
+
== Error Handling
|
|
502
|
+
|
|
503
|
+
Fontisan provides improved error handling compared to ExtractTTC:
|
|
504
|
+
|
|
505
|
+
[source,shell]
|
|
506
|
+
----
|
|
507
|
+
# Clear error messages
|
|
508
|
+
$ fontisan convert font.ttf --to unsupported
|
|
509
|
+
Error: Unsupported conversion from ttf to unsupported
|
|
510
|
+
Available targets: ttf, otf, woff2, svg
|
|
511
|
+
|
|
512
|
+
# Helpful validation errors
|
|
513
|
+
$ fontisan validate corrupted.ttf
|
|
514
|
+
Validation failed: Invalid checksum in head table
|
|
515
|
+
----
|
|
516
|
+
|
|
517
|
+
== Performance
|
|
518
|
+
|
|
519
|
+
Fontisan is optimized for performance:
|
|
520
|
+
|
|
521
|
+
* **Collection Operations**: Sub-second for typical collections
|
|
522
|
+
* **Format Conversion**: <1 second for most conversions
|
|
523
|
+
* **Font Analysis**: <100ms for table inspection
|
|
524
|
+
* **Memory Efficient**: Streaming processing for large fonts
|
|
525
|
+
|
|
526
|
+
== Backward Compatibility
|
|
527
|
+
|
|
528
|
+
Fontisan maintains full backward compatibility:
|
|
529
|
+
|
|
530
|
+
* All ExtractTTC commands work identically
|
|
531
|
+
* Same exit codes and error handling
|
|
532
|
+
* Compatible with existing scripts and workflows
|
|
533
|
+
* Enhanced with additional features
|
|
534
|
+
|
|
535
|
+
== Getting Help
|
|
536
|
+
|
|
537
|
+
[source,shell]
|
|
538
|
+
----
|
|
539
|
+
# General help
|
|
540
|
+
$ fontisan --help
|
|
541
|
+
|
|
542
|
+
# Command-specific help
|
|
543
|
+
$ fontisan pack --help
|
|
544
|
+
$ fontisan convert --help
|
|
545
|
+
$ fontisan subset --help
|
|
546
|
+
|
|
547
|
+
# Version information
|
|
548
|
+
$ fontisan version
|
|
549
|
+
----
|
data/fontisan.gemspec
CHANGED
|
@@ -38,7 +38,10 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
39
39
|
spec.require_paths = ["lib"]
|
|
40
40
|
|
|
41
|
+
spec.add_dependency "base64"
|
|
41
42
|
spec.add_dependency "bindata", "~> 2.5"
|
|
43
|
+
spec.add_dependency "brotli", "~> 0.5"
|
|
42
44
|
spec.add_dependency "lutaml-model", "~> 0.7"
|
|
43
|
-
spec.add_dependency "
|
|
45
|
+
spec.add_dependency "nokogiri", "~> 1.16"
|
|
46
|
+
spec.add_dependency "thor", "~> 1.3"
|
|
44
47
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bindata"
|
|
4
|
+
require "stringio"
|
|
4
5
|
|
|
5
6
|
module Fontisan
|
|
6
7
|
module Binary
|
|
@@ -25,7 +26,27 @@ module Fontisan
|
|
|
25
26
|
def self.read(io)
|
|
26
27
|
return new if io.nil? || (io.respond_to?(:empty?) && io.empty?)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
# Store the original data for later parsing
|
|
30
|
+
# Convert IO to string if needed
|
|
31
|
+
if io.is_a?(String)
|
|
32
|
+
data = io
|
|
33
|
+
instance = super(StringIO.new(data))
|
|
34
|
+
else
|
|
35
|
+
# For IO objects, read to string first
|
|
36
|
+
data = io.read
|
|
37
|
+
io.rewind if io.respond_to?(:rewind)
|
|
38
|
+
instance = super(io)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
instance.instance_variable_set(:@raw_data, data)
|
|
42
|
+
instance
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get the raw binary data that was read
|
|
46
|
+
#
|
|
47
|
+
# @return [String] Raw binary data
|
|
48
|
+
def raw_data
|
|
49
|
+
@raw_data || to_binary_s
|
|
29
50
|
end
|
|
30
51
|
|
|
31
52
|
# Check if the record is valid
|