extract_ttc 0.3.6 → 0.3.7

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -4
  3. data/.rubocop.yml +7 -9
  4. data/.rubocop_todo.yml +135 -0
  5. data/Gemfile +6 -6
  6. data/README.adoc +856 -55
  7. data/Rakefile +7 -101
  8. data/exe/extract_ttc +7 -0
  9. data/extract_ttc.gemspec +3 -4
  10. data/lib/extract_ttc/cli.rb +47 -0
  11. data/lib/extract_ttc/commands/extract.rb +88 -0
  12. data/lib/extract_ttc/commands/info.rb +112 -0
  13. data/lib/extract_ttc/commands/list.rb +60 -0
  14. data/lib/extract_ttc/configuration.rb +126 -0
  15. data/lib/extract_ttc/constants.rb +42 -0
  16. data/lib/extract_ttc/models/extraction_result.rb +56 -0
  17. data/lib/extract_ttc/models/validation_result.rb +53 -0
  18. data/lib/extract_ttc/true_type_collection.rb +79 -0
  19. data/lib/extract_ttc/true_type_font.rb +239 -0
  20. data/lib/extract_ttc/utilities/checksum_calculator.rb +89 -0
  21. data/lib/extract_ttc/utilities/output_path_generator.rb +100 -0
  22. data/lib/extract_ttc/version.rb +1 -1
  23. data/lib/extract_ttc.rb +83 -55
  24. data/sig/extract_ttc/configuration.rbs +19 -0
  25. data/sig/extract_ttc/constants.rbs +17 -0
  26. data/sig/extract_ttc/models/extraction_result.rbs +19 -0
  27. data/sig/extract_ttc/models/font_data.rbs +17 -0
  28. data/sig/extract_ttc/models/table_directory_entry.rbs +15 -0
  29. data/sig/extract_ttc/models/true_type_collection_header.rbs +15 -0
  30. data/sig/extract_ttc/models/true_type_font_offset_table.rbs +17 -0
  31. data/sig/extract_ttc/models/validation_result.rbs +17 -0
  32. data/sig/extract_ttc/utilities/checksum_calculator.rbs +13 -0
  33. data/sig/extract_ttc/utilities/output_path_generator.rbs +11 -0
  34. data/sig/extract_ttc/validators/true_type_collection_validator.rbs +9 -0
  35. data/sig/extract_ttc.rbs +20 -0
  36. metadata +44 -28
  37. data/ext/stripttc/LICENSE +0 -31
  38. data/ext/stripttc/dummy.c +0 -2
  39. data/ext/stripttc/extconf.rb +0 -5
  40. data/ext/stripttc/stripttc.c +0 -187
data/README.adoc CHANGED
@@ -1,126 +1,927 @@
1
+ = ExtractTTC: extract TTF fonts from TTC collections
2
+
1
3
  image:https://img.shields.io/gem/v/extract_ttc.svg["Gem Version", link="https://rubygems.org/gems/extract_ttc"]
2
4
  image:https://github.com/fontist/extract_ttc/workflows/test-and-release/badge.svg["Build Status", link="https://github.com/fontist/extract_ttc/actions?workflow=test-and-release"]
3
- // image:https://codeclimate.com/github/metanorma/extract_ttc/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/fontist/extract_ttc"]
4
5
  image:https://img.shields.io/github/issues-pr-raw/fontist/extract_ttc.svg["Pull Requests", link="https://github.com/fontist/extract_ttc/pulls"]
5
6
 
6
- = ExtractTtc: Ruby gem to extract TTF from TTC
7
-
8
7
  == Purpose
9
8
 
10
- The gem lets you extract individual TrueType font files (`.ttf`) from a TrueType Collection file (`.ttc`).
9
+ `ExtractTTC` is a pure Ruby gem that extracts individual TrueType font files
10
+ (`.ttf`) from TrueType Collection files (`.ttc`).
11
+
12
+
13
+ == Features
11
14
 
12
- `ExtractTtc` wraps functionality provided by `stripttc.c`, originally from the
13
- https://github.com/fontforge/fontforge[FontForge project] as an FFI extension.
15
+ * <<simple-extraction,Simple extraction API>>
16
+ * <<cli-usage,Command-line interface>>
17
+ * <<custom-output,Custom output directories>>
18
+ * <<declarative-structures,Declarative BinData structures>>
19
+ * <<domain-objects,Object-oriented domain objects>>
20
+ * <<error-handling,Comprehensive error handling>>
21
+ * <<type-safety,Type safety with RBS signatures>>
22
+ * <<high-performance,High performance binary parsing>>
14
23
 
15
- Specifically, `stripttc.c` is part of the `contribs` directory of FontForge, which
16
- is
17
- https://github.com/fontforge/fontforge/blob/21ad4a18fb3d4becfe566d8215eba4483b0ddc4b/contrib/CMakeLists.txt#L1[assigned]
18
- the
19
- https://github.com/fontforge/fontforge/blob/21ad4a18fb3d4becfe566d8215eba4483b0ddc4b/LICENSE#L12-L57[BSD 3-Clause license].
24
+ == Architecture
20
25
 
26
+ ExtractTTC uses a model-driven architecture where domain objects
27
+ inherit from `BinData::Record` and handle their own binary I/O through
28
+ declarative structure definitions.
29
+
30
+ .ExtractTTC architecture overview
31
+ [source]
32
+ ----
33
+ ┌──────────────────────────────────────────────────────────────┐
34
+ │ Client Code │
35
+ │ ExtractTtc.extract(path) │
36
+ └────────────────────────┬─────────────────────────────────────┘
37
+
38
+
39
+ ┌──────────────────────────────────────────────────────────────┐
40
+ │ Simple Extraction API │
41
+ │ 1. Read TTC (TrueTypeCollection.read via BinData) │
42
+ │ 2. Extract fonts (ttc.extract_fonts) │
43
+ │ 3. Write TTFs (font.to_file via BinData) │
44
+ └──┬────────────┬──────────────┬───────────────────────────────┘
45
+ │ │ │
46
+ ▼ ▼ ▼
47
+ ┌─────────────┐ ┌─────────────┐ ┌────────────┐
48
+ │TrueType │ │TrueType │ │Utilities │
49
+ │Collection │ │Font │ │ │
50
+ │(BinData:: │ │(BinData:: │ │ │
51
+ │ Record) │ │ Record) │ │ │
52
+ └─────────────┘ └─────────────┘ └────────────┘
53
+ ----
54
+
55
+ .Core domain model relationships
56
+ [source]
57
+ ----
58
+ TrueTypeCollection (Model)
59
+ ├── tag (Value: String) = "ttcf"
60
+ ├── major_version (Value: Integer)
61
+ ├── minor_version (Value: Integer)
62
+ ├── num_fonts (Value: Integer)
63
+ └── font_offsets (Array<Integer>)
64
+
65
+ └──> Maps to TrueTypeFont instances
66
+
67
+ ├── TrueTypeFont (Model)
68
+ │ ├── offset_table (Model)
69
+ │ │ ├── scaler_type (Value: Integer)
70
+ │ │ ├── num_tables (Value: Integer)
71
+ │ │ └── search_range (Value: Integer)
72
+ │ └── table_directory (Array<TableEntry>)
73
+ │ └── TableEntry (Model)
74
+ │ ├── tag (Value: String)
75
+ │ ├── checksum (Value: Integer)
76
+ │ ├── offset (Value: Integer)
77
+ │ └── length (Value: Integer)
78
+ └── (Recursive pattern for each font)
79
+ ----
21
80
 
22
81
  == Installation
23
82
 
24
- Add this line to your application's `Gemfile`:
83
+ Add this line to your application's Gemfile:
25
84
 
26
85
  [source,ruby]
27
86
  ----
28
- gem 'extract_ttc'
87
+ gem "extract_ttc"
29
88
  ----
30
89
 
31
90
  And then execute:
32
91
 
33
- [source,sh]
92
+ [source,shell]
34
93
  ----
35
- $ bundle install
94
+ bundle install
36
95
  ----
37
96
 
38
97
  Or install it yourself as:
39
98
 
40
- [source,sh]
99
+ [source,shell]
100
+ ----
101
+ gem install extract_ttc
102
+ ----
103
+
104
+ [[cli-usage]]
105
+ == Command-line interface
106
+
107
+ === General
108
+
109
+ ExtractTTC provides a command-line interface for extracting fonts from
110
+ TTC files without writing code. The CLI is built using Thor and follows
111
+ standard Unix conventions for options and arguments.
112
+
113
+ The command-line interface is particularly useful for:
114
+
115
+ * Quick one-off extractions
116
+ * Shell scripts and automation
117
+ * CI/CD pipelines
118
+ * Interactive exploration of TTC files
119
+
120
+ === Basic extraction
121
+
122
+ Extract all fonts from a TTC file to the current directory.
123
+
124
+ Syntax:
125
+
126
+ [source,shell]
127
+ ----
128
+ extract_ttc extract FILE <1>
129
+ ----
130
+ <1> The `extract` command with the input TTC file path.
131
+
132
+ Where,
133
+
134
+ `FILE`:: The path to the input TrueType Collection file (`.ttc`).
135
+
136
+ .Extract fonts to current directory
137
+ [example]
138
+ ====
139
+ [source,shell]
140
+ ----
141
+ extract_ttc extract fonts/Helvetica.ttc
142
+
143
+ # Output:
144
+ # Successfully extracted 6 font(s):
145
+ # - Helvetica_00.ttf
146
+ # - Helvetica_01.ttf
147
+ # - Helvetica_02.ttf
148
+ # - Helvetica_03.ttf
149
+ # - Helvetica_04.ttf
150
+ # - Helvetica_05.ttf
151
+ ----
152
+
153
+ This extracts all fonts from the TTC file and creates TTF files in the
154
+ current directory with sequential numbering.
155
+ ====
156
+
157
+ === Extract to specific directory
158
+
159
+ Specify an output directory for the extracted font files.
160
+
161
+ The output directory is created automatically if it does not exist.
162
+
163
+ Syntax:
164
+
165
+ [source,shell]
166
+ ----
167
+ extract_ttc extract FILE -o OUTPUT_DIR <1>
168
+ ----
169
+ <1> The `extract` command with custom output directory.
170
+
171
+ Where,
172
+
173
+ `FILE`:: The path to the input TrueType Collection file (`.ttc`).
174
+ `OUTPUT_DIR`:: The directory where extracted TTF files will be created.
175
+
176
+ .Extract to custom directory
177
+ [example]
178
+ ====
179
+ [source,shell]
180
+ ----
181
+ extract_ttc extract fonts/Helvetica.ttc -o output/fonts
182
+
183
+ # Output:
184
+ # Successfully extracted 6 font(s):
185
+ # - output/fonts/Helvetica_00.ttf
186
+ # - output/fonts/Helvetica_01.ttf
187
+ # - output/fonts/Helvetica_02.ttf
188
+ # - output/fonts/Helvetica_03.ttf
189
+ # - output/fonts/Helvetica_04.ttf
190
+ # - output/fonts/Helvetica_05.ttf
191
+ ----
192
+ ====
193
+
194
+ === Verbose output
195
+
196
+ Enable detailed output showing the extraction process.
197
+
198
+ Syntax:
199
+
200
+ [source,shell]
201
+ ----
202
+ extract_ttc extract FILE -v <1>
203
+ extract_ttc extract FILE --verbose <2>
204
+ ----
205
+ <1> Short form of the verbose option.
206
+ <2> Long form of the verbose option.
207
+
208
+ Where,
209
+
210
+ `FILE`:: The path to the input TrueType Collection file (`.ttc`).
211
+
212
+ .Extract with verbose output
213
+ [example]
214
+ ====
215
+ [source,shell]
216
+ ----
217
+ extract_ttc extract fonts/Helvetica.ttc -v
218
+
219
+ # Output:
220
+ # Extracting fonts from fonts/Helvetica.ttc...
221
+ # Successfully extracted 6 font(s):
222
+ # - Helvetica_00.ttf
223
+ # - Helvetica_01.ttf
224
+ # - Helvetica_02.ttf
225
+ # - Helvetica_03.ttf
226
+ # - Helvetica_04.ttf
227
+ # - Helvetica_05.ttf
228
+ ----
229
+
230
+ Verbose mode provides additional information about the extraction
231
+ process.
232
+ ====
233
+
234
+ === Getting help
235
+
236
+ View help for the extract command.
237
+
238
+ Syntax:
239
+
240
+ [source,shell]
241
+ ----
242
+ extract_ttc help extract <1>
243
+ extract_ttc extract --help <2>
244
+ ----
245
+ <1> Display help using the help command.
246
+ <2> Display help using the --help flag.
247
+
248
+ .View command help
249
+ [example]
250
+ ====
251
+ [source,shell]
252
+ ----
253
+ extract_ttc help extract
254
+
255
+ # Output:
256
+ # Usage:
257
+ # extract_ttc extract FILE
258
+ #
259
+ # Options:
260
+ # -o, [--output-dir=OUTPUT_DIR] # Output directory for TTF files
261
+ # -v, [--verbose] # Enable verbose output
262
+ #
263
+ # Extract TTF files from a TTC file
264
+ ----
265
+
266
+ The help output shows all available options and their descriptions.
267
+ ====
268
+
269
+ [[simple-extraction]]
270
+ == Simple extraction API
271
+
272
+ === General
273
+
274
+ The simple extraction API provides a single method to extract all fonts
275
+ from a TrueType Collection file with minimal configuration. This is the
276
+ recommended approach for most use cases, as it handles all the
277
+ complexity of reading TTC structures, extracting individual fonts, and
278
+ === List fonts in collection
279
+
280
+ List all fonts contained in a TTC file without extracting them.
281
+
282
+ Syntax:
283
+
284
+ [source,shell]
285
+ ----
286
+ extract_ttc ls FILE <1>
287
+ ----
288
+ <1> The `ls` command to list fonts in the TTC file.
289
+
290
+ Where,
291
+
292
+ `FILE`:: The path to the input TrueType Collection file (`.ttc`).
293
+
294
+ .List fonts in collection
295
+ [example]
296
+ ====
297
+ [source,shell]
298
+ ----
299
+ extract_ttc ls fonts/Helvetica.ttc
300
+
301
+ # Output:
302
+ # 📦 TTC File: fonts/Helvetica.ttc
303
+ # Fonts: 6
304
+ #
305
+ # 0. 📄 Helvetica_00.ttf
306
+ # 1. 📄 Helvetica_01.ttf
307
+ # 2. 📄 Helvetica_02.ttf
308
+ # 3. 📄 Helvetica_03.ttf
309
+ # 4. 📄 Helvetica_04.ttf
310
+ # 5. 📄 Helvetica_05.ttf
311
+ ----
312
+
313
+ The `ls` command provides a quick way to see what fonts are available
314
+ in a TTC collection before extracting them.
315
+ ====
316
+
317
+ === Show collection information
318
+
319
+ Display detailed metadata about a TTC file, including header
320
+ information and font offsets.
321
+
322
+ Syntax:
323
+
324
+ [source,shell]
325
+ ----
326
+ extract_ttc info FILE <1>
327
+ extract_ttc info FILE -v <2>
328
+ ----
329
+ <1> Show basic TTC information.
330
+ <2> Show detailed information including table data for each font.
331
+
332
+ Where,
333
+
334
+ `FILE`:: The path to the input TrueType Collection file (`.ttc`).
335
+
336
+ .Show basic TTC information
337
+ [example]
338
+ ====
339
+ [source,shell]
340
+ ----
341
+ extract_ttc info fonts/Helvetica.ttc
342
+
343
+ # Output:
344
+ # ═══ TTC File Information ═══
345
+ #
346
+ # 📦 File: fonts/Helvetica.ttc
347
+ # 💾 Size: 2.24 MB
348
+ #
349
+ # ═══ Header ═══
350
+ # 🏷️ Tag: ttcf
351
+ # 📌 Version: 2.0 (0x20000)
352
+ # 🔢 Number of fonts: 6
353
+ #
354
+ # ═══ Font Offsets ═══
355
+ # 0. Offset: 48 (0x30)
356
+ # 1. Offset: 380 (0x17C)
357
+ # 2. Offset: 712 (0x2C8)
358
+ # 3. Offset: 1044 (0x414)
359
+ # 4. Offset: 1376 (0x560)
360
+ # 5. Offset: 1676 (0x68C)
361
+ ----
362
+
363
+ This displays the TTC file metadata, including the file size, version,
364
+ number of fonts, and their offsets in the file.
365
+ ====
366
+
367
+ .Show detailed font information
368
+ [example]
369
+ ====
370
+ [source,shell]
371
+ ----
372
+ extract_ttc info fonts/Helvetica.ttc -v
373
+
374
+ # Output includes above, plus:
375
+ # ═══ Font Details ═══
376
+ #
377
+ # 📝 Font 0:
378
+ # SFNT version: 0x10000
379
+ # Number of tables: 20
380
+ # Tables:
381
+ # • OS/2 checksum: 0x1047244E offset: 2884 length: 96
382
+ # • cmap checksum: 0xEF626C81 offset: 2205772 length: 8040
383
+ # • glyf checksum: 0x2B563595 offset: 1393876 length: 267440
384
+ # • head checksum: 0xB8ED709D offset: 2416 length: 54
385
+ # ... (and more tables)
386
+ ----
387
+
388
+ The verbose mode (`-v`) shows detailed information about each font,
389
+ including the SFNT version, number of tables, and a complete list of
390
+ all tables with their checksums, offsets, and lengths.
391
+ ====
392
+
393
+ writing TTF files.
394
+
395
+ === Extract all fonts to current directory
396
+
397
+ The simplest way to extract all fonts from a TTC file is to provide
398
+ just the input file path.
399
+
400
+ Syntax:
401
+
402
+ [source,ruby]
403
+ ----
404
+ ExtractTtc.extract(path) → Array<String> <1>
405
+ ----
406
+ <1> The `extract` method with input file path only.
407
+
408
+ Where,
409
+
410
+ `path`:: The path to the input TrueType Collection file (`.ttc`).
411
+
412
+ Returns an array of output file paths for the extracted TTF files.
413
+
414
+ .Extract fonts to current directory
415
+ [example]
416
+ ====
417
+ [source,ruby]
418
+ ----
419
+ require "extract_ttc"
420
+
421
+ # Extract all fonts from TTC file to current directory
422
+ output_files = ExtractTtc.extract("fonts/Helvetica.ttc")
423
+ # => ["Helvetica_00.ttf", "Helvetica_01.ttf", "Helvetica_02.ttf"]
424
+
425
+ puts "Extracted #{output_files.size} fonts"
426
+ output_files.each { |file| puts " - #{file}" }
427
+ ----
428
+
429
+ This extracts all fonts and creates TTF files in the current directory
430
+ with names like `Helvetica_00.ttf`, `Helvetica_01.ttf`, etc.
431
+ ====
432
+
433
+ [[custom-output]]
434
+ == Custom output directories
435
+
436
+ === General
437
+
438
+ For organized file management, the extraction API supports specifying a
439
+ custom output directory. This is useful when you want to organize
440
+ extracted fonts in a specific location or maintain a clean directory
441
+ structure.
442
+
443
+ The output directory will be created automatically if it does not exist.
444
+
445
+ === Extract to specific directory
446
+
447
+ Specify a custom output directory using the `output_dir` parameter.
448
+
449
+ Syntax:
450
+
451
+ [source,ruby]
41
452
  ----
42
- $ gem install extract_ttc
453
+ ExtractTtc.extract(path, output_dir: directory) → Array<String> <1>
43
454
  ----
455
+ <1> The `extract` method with custom output directory.
456
+
457
+ Where,
44
458
 
45
- == Usage
459
+ `path`:: The path to the input TrueType Collection file (`.ttc`).
460
+ `directory`:: The path to the output directory for extracted TTF files.
46
461
 
462
+ Returns an array of output file paths for the extracted TTF files.
463
+
464
+ .Extract to custom directory
465
+ [example]
466
+ ====
47
467
  [source,ruby]
48
468
  ----
49
- ExtractTtc.extract("path/to/ttc/Helvetica.ttc")
469
+ require "extract_ttc"
470
+
471
+ # Extract to specific directory
472
+ output_files = ExtractTtc.extract(
473
+ "fonts/MyFont.ttc",
474
+ output_dir: "output/fonts"
475
+ )
476
+ # => ["output/fonts/MyFont_00.ttf", "output/fonts/MyFont_01.ttf"]
477
+
478
+ # Directory is created automatically if it doesn't exist
479
+ output_files.each { |file| puts "Created: #{file}" }
50
480
  ----
51
481
 
52
- Would extract contained TTF files from TTC to a current directory.
482
+ The output directory `output/fonts` is created automatically if it does
483
+ not exist, and all extracted fonts are placed there.
484
+ ====
485
+
486
+ === Extract with custom path composition
487
+
488
+ For flexibility in path management, the API returns relative filenames when
489
+ extracting to the current directory.
490
+
491
+ This allows custom path composition with any directory structure.
492
+
493
+ Syntax:
494
+
495
+ [source,ruby]
496
+ ----
497
+ filenames = ExtractTtc.extract(path) <1>
498
+ full_paths = filenames.map { |filename| File.join(dir, filename) } <2>
499
+ ----
500
+ <1> Extract to current directory, returns relative filenames.
501
+ <2> Compose full paths with custom directory.
502
+
503
+ Where,
504
+
505
+ `path`:: The path to the input TrueType Collection file (`.ttc`).
506
+ `dir`:: The custom directory to prepend to filenames.
507
+
508
+ .Custom path composition with temporary directory
509
+ [example]
510
+ ====
511
+ [source,ruby]
512
+ ----
513
+ require "extract_ttc"
514
+ require "tmpdir"
515
+
516
+ def extract_ttfs(ttc_path, tmp_dir)
517
+ # Extract returns relative filenames
518
+ filenames = ExtractTtc.extract(ttc_path)
519
+ # => ["Font_00.ttf", "Font_01.ttf", ...]
520
+
521
+ # Compose full paths with your directory
522
+ filenames.map do |filename|
523
+ File.join(tmp_dir, filename)
524
+ end
525
+ # => ["/tmp/xyz/Font_00.ttf", "/tmp/xyz/Font_01.ttf", ...]
526
+ end
527
+
528
+ # Use the method
529
+ Dir.mktmpdir do |tmp_dir|
530
+ font_paths = extract_ttfs("fonts/MyFont.ttc", tmp_dir)
531
+
532
+ # Process fonts at their full paths
533
+ font_paths.each do |path|
534
+ puts "Font available at: #{path}"
535
+ # Your processing logic here
536
+ end
537
+ end
538
+ ----
539
+
540
+ This pattern is useful when you need to extract fonts to the current
541
+ directory but want to manipulate them with absolute paths, or when
542
+ integrating with code that expects to control the output directory.
543
+
544
+ The relative filenames make it easy to move or organize the extracted
545
+ files after extraction.
546
+ ====
547
+
548
+
549
+ [[domain-objects]]
550
+ == Object-oriented domain objects
551
+
552
+ === General
553
+
554
+ ExtractTTC follows object-oriented design principles where domain
555
+ objects encapsulate both data and behavior. The core domain objects are
556
+ `TrueTypeCollection` and `TrueTypeFont`, which inherit from
557
+ `BinData::Record` to gain automatic binary I/O capabilities while
558
+ maintaining clean domain APIs.
559
+
560
+ This approach ensures that objects know their own structure and can
561
+ handle their own persistence, leading to maintainable and
562
+ self-documenting code.
563
+
564
+ === Using TrueTypeCollection objects
565
+
566
+ The `TrueTypeCollection` class represents a TTC file and provides
567
+ methods to read the collection and extract individual fonts.
568
+
569
+ .Working with TrueTypeCollection objects
570
+ [example]
571
+ ====
572
+ [source,ruby]
573
+ ----
574
+ require "extract_ttc"
575
+
576
+ # Open TTC file and read with BinData
577
+ File.open("fonts/Helvetica.ttc", "rb") do |io|
578
+ # Parse TTC structure automatically via BinData
579
+ ttc = ExtractTtc::TrueTypeCollection.read(io)
580
+
581
+ # Access TTC metadata
582
+ puts "TTC Tag: #{ttc.tag}"
583
+ puts "Version: #{ttc.major_version}.#{ttc.minor_version}"
584
+ puts "Number of fonts: #{ttc.num_fonts}"
585
+
586
+ # Extract fonts as TrueTypeFont objects
587
+ fonts = ttc.extract_fonts(io)
588
+ puts "Extracted #{fonts.size} font objects"
589
+ end
590
+ ----
591
+
592
+ The `TrueTypeCollection.read` method automatically parses the binary
593
+ TTC file structure using BinData, and the `extract_fonts` method
594
+ returns an array of `TrueTypeFont` objects.
595
+ ====
596
+
597
+ === Using TrueTypeFont objects
598
+
599
+ The `TrueTypeFont` class represents an individual TTF file and provides
600
+ methods to write the font to a file.
601
+
602
+ .Working with TrueTypeFont objects
603
+ [example]
604
+ ====
605
+ [source,ruby]
606
+ ----
607
+ require "extract_ttc"
608
+
609
+ File.open("fonts/Helvetica.ttc", "rb") do |io|
610
+ ttc = ExtractTtc::TrueTypeCollection.read(io)
611
+ fonts = ttc.extract_fonts(io)
612
+
613
+ # Write each font to file
614
+ fonts.each_with_index do |font, index|
615
+ output_path = "output/font_#{index}.ttf"
616
+
617
+ # Access font metadata
618
+ puts "Font #{index}:"
619
+ puts " Scaler type: 0x#{font.scaler_type.to_s(16)}"
620
+ puts " Number of tables: #{font.num_tables}"
621
+
622
+ # Write font to file using BinData
623
+ font.to_file(output_path)
624
+ puts " Wrote: #{output_path}"
625
+ end
626
+ end
627
+ ----
628
+
629
+ Each `TrueTypeFont` object encapsulates the complete font data and can
630
+ write itself to a file in the correct TTF binary format.
631
+ ====
632
+
633
+ [[error-handling]]
634
+ == Comprehensive error handling
635
+
636
+ === General
637
+
638
+ ExtractTTC defines specific error types for different failure
639
+ scenarios, allowing applications to handle errors appropriately. All
640
+ errors inherit from `ExtractTtc::Error`, making it easy to catch all
641
+ gem-related errors if desired.
642
+
643
+ The error types provide clear, actionable messages that help diagnose
644
+ issues with input files, invalid formats, or output operations.
645
+
646
+ === Error types
647
+
648
+ The gem defines the following error hierarchy:
649
+
650
+ .Error type hierarchy
651
+ [source]
652
+ ----
653
+ ExtractTtc::Error (base class)
654
+ ├── ExtractTtc::ReadFileError
655
+ ├── ExtractTtc::InvalidFileError
656
+ └── ExtractTtc::WriteFileError
657
+ ----
658
+
659
+ `ExtractTtc::Error`:: Base error class for all gem-related errors.
660
+ `ExtractTtc::ReadFileError`:: Raised when the input file cannot be
661
+ read (e.g., file not found, permission denied).
662
+ `ExtractTtc::InvalidFileError`:: Raised when the file is not a valid
663
+ TTC file (e.g., wrong format, corrupted data).
664
+ `ExtractTtc::WriteFileError`:: Raised when output files cannot be
665
+ written (e.g., permission denied, disk full).
666
+
667
+ === Handling extraction errors
668
+
669
+ Applications can catch specific error types to handle different failure
670
+ scenarios appropriately.
671
+
672
+ .Error handling example
673
+ [example]
674
+ ====
675
+ [source,ruby]
676
+ ----
677
+ require "extract_ttc"
678
+
679
+ begin
680
+ output_files = ExtractTtc.extract("fonts/example.ttc")
681
+ puts "Successfully extracted #{output_files.size} fonts"
682
+ rescue ExtractTtc::ReadFileError => e
683
+ puts "File not found or cannot be read: #{e.message}"
684
+ exit 1
685
+ rescue ExtractTtc::InvalidFileError => e
686
+ puts "Not a valid TTC file: #{e.message}"
687
+ exit 2
688
+ rescue ExtractTtc::WriteFileError => e
689
+ puts "Cannot write output files: #{e.message}"
690
+ exit 3
691
+ rescue ExtractTtc::Error => e
692
+ puts "Extraction failed: #{e.message}"
693
+ exit 4
694
+ end
695
+ ----
696
+
697
+ This handles each error type separately, allowing appropriate recovery
698
+ or reporting for different failure scenarios.
699
+ ====
700
+
701
+ === Catching all gem errors
702
+
703
+ To catch all gem-related errors, rescue `ExtractTtc::Error`.
704
+
705
+ .Catch all gem errors
706
+ [example]
707
+ ====
708
+ [source,ruby]
709
+ ----
710
+ require "extract_ttc"
711
+
712
+ begin
713
+ ExtractTtc.extract("fonts/example.ttc")
714
+ rescue ExtractTtc::Error => e
715
+ # Handle any gem-related error
716
+ puts "Extraction failed: #{e.message}"
717
+ puts "Error type: #{e.class.name}"
718
+ end
719
+ ----
720
+
721
+ This catches all errors raised by the gem, including any future error
722
+ types that may be added.
723
+ ====
724
+
725
+ [[type-safety]]
726
+ == Type safety with RBS signatures
727
+
728
+ === General
729
+
730
+ ExtractTTC includes comprehensive RBS type signatures in the `sig/`
731
+ directory, providing static type checking capabilities for applications
732
+ using the gem. The type signatures cover all public APIs and domain
733
+ objects.
734
+
735
+ Type signatures help catch type-related errors during development and
736
+ provide better IDE support for autocomplete and inline documentation.
737
+
738
+ === Using type signatures with Steep
739
+
740
+ The gem can be type-checked using Steep, a static type checker for
741
+ Ruby.
742
+
743
+ .Type checking with Steep
744
+ [example]
745
+ ====
746
+ [source,shell]
747
+ ----
748
+ # Install Steep
749
+ gem install steep
750
+
751
+ # Type check the gem
752
+ steep check
753
+
754
+ # Type check your application using the gem
755
+ steep check --steep-file Steepfile
756
+ ----
757
+
758
+ Steep will verify that all method calls and variable assignments match
759
+ the type signatures defined in the `sig/` directory.
760
+ ====
761
+
762
+ === Viewing type signatures
763
+
764
+ Type signatures are organized to mirror the source code structure.
765
+
766
+ .Type signature organization
767
+ [source]
768
+ ----
769
+ sig/
770
+ ├── extract_ttc.rbs # Main API signatures
771
+ └── extract_ttc/
772
+ ├── configuration.rbs
773
+ ├── true_type_collection.rbs
774
+ ├── true_type_font.rbs
775
+ ├── models/
776
+ │ ├── extraction_result.rbs
777
+ │ └── validation_result.rbs
778
+ └── utilities/
779
+ ├── checksum_calculator.rbs
780
+ └── output_path_generator.rbs
781
+ ----
782
+
783
+ [[high-performance]]
784
+ == High performance binary parsing
785
+
786
+ ExtractTTC achieves high performance through BinData's efficient binary
787
+ parsing implementation. The declarative structure definitions compile
788
+ to optimized read/write operations, providing performance equivalent to
789
+ hand-written binary I/O code.
53
790
 
54
791
 
55
792
  == Development
56
793
 
57
- We are following Sandi Metz's Rules for this gem, you can read the
58
- http://robots.thoughtbot.com/post/50655960596/sandi-metz-rules-for-developers[description of the rules here].
59
- All new code should follow these
60
- rules. If you make changes in a pre-existing file that violates these rules you
61
- should fix the violations as part of your contribution.
794
+ === General
795
+
796
+ The development environment uses standard Ruby tooling with RSpec for
797
+ testing and RuboCop for code style enforcement.
62
798
 
63
799
  === Setup
64
800
 
65
- Clone the repository:
801
+ Clone the repository and set up the development environment.
66
802
 
67
- [source,sh]
803
+ [source,shell]
68
804
  ----
69
805
  git clone https://github.com/fontist/extract_ttc
806
+ cd extract_ttc
807
+ bin/setup
808
+ ----
809
+
810
+ This will install dependencies and prepare the development environment.
811
+
812
+ === Running tests
813
+
814
+ Run the complete test suite with RSpec.
815
+
816
+ [source,shell]
70
817
  ----
818
+ # Run all tests
819
+ bundle exec rspec
820
+
821
+ # Run with verbose output
822
+ bundle exec rspec --format documentation
71
823
 
72
- Setup your environment:
824
+ # Run specific test file
825
+ bundle exec rspec spec/extract_ttc/true_type_collection_spec.rb
73
826
 
74
- [source,sh]
827
+ # Run tests for a component
828
+ bundle exec rspec spec/extract_ttc/utilities/
75
829
  ----
76
- bin/setup
830
+
831
+ === Test organization
832
+
833
+ Tests are organized to mirror the source code structure, with one RSpec
834
+ file per class.
835
+
836
+ .Test directory structure
837
+ [source]
838
+ ----
839
+ spec/
840
+ ├── extract_ttc_spec.rb # Main API tests
841
+ ├── spec_helper.rb
842
+ ├── extract_ttc/
843
+ │ ├── configuration_spec.rb
844
+ │ ├── true_type_collection_spec.rb
845
+ │ ├── true_type_font_spec.rb
846
+ │ ├── models/
847
+ │ │ ├── extraction_result_spec.rb
848
+ │ │ └── validation_result_spec.rb
849
+ │ └── utilities/
850
+ │ ├── checksum_calculator_spec.rb
851
+ │ └── output_path_generator_spec.rb
852
+ └── fixtures/
853
+ └── Helvetica.ttc # Test fixture files
77
854
  ----
78
855
 
79
- Run the test suite:
856
+ === Development console
80
857
 
81
- [source,sh]
858
+ Run an interactive console for experimentation.
859
+
860
+ [source,shell]
82
861
  ----
83
- bundle exec rspec
862
+ bin/console
84
863
  ----
85
864
 
86
- If any changes are made in the C code, then the extension needs to be recompiled:
865
+ This opens an IRB session with the gem loaded.
866
+
867
+ === Installing locally
87
868
 
88
- [source,sh]
869
+ Install the gem onto your local machine for testing.
870
+
871
+ [source,shell]
89
872
  ----
90
- bundle exec rake recompile
873
+ bundle exec rake install
91
874
  ----
92
875
 
93
- You can also run `bin/console` for an interactive prompt that will allow you to experiment.
876
+ === Code style
877
+
878
+ Follow these coding guidelines:
879
+
880
+ * Follow object-oriented design principles
881
+ * Use declarative structures with BinData for binary formats
882
+ * Maintain clear separation of concerns
883
+ * One class per file
884
+ * Comprehensive tests for all classes
885
+ * Clear documentation with examples
886
+
887
+ === Releasing
94
888
 
95
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].
889
+ To release a new version:
96
890
 
891
+ . Update the version number in
892
+ link:lib/extract_ttc/version.rb[`version.rb`]
893
+ . Run `bundle exec rake release`
894
+
895
+ This will create a git tag for the version, push git commits and tags,
896
+ and push the `.gem` file to https://rubygems.org[rubygems.org].
97
897
 
98
898
  == Contributing
99
899
 
100
- First, thank you for contributing! We love pull requests from everyone. By
101
- participating in this project, you hereby grant https://www.ribose.com[Ribose Inc.] the
102
- right to grant or transfer an unlimited number of non exclusive licenses or
103
- sub-licenses to third parties, under the copyright covering the contribution
104
- to use the contribution by all means.
900
+ First, thank you for contributing! We love pull requests from everyone.
901
+ By participating in this project, you hereby grant
902
+ https://www.ribose.com[Ribose Inc.] the right to grant or transfer an
903
+ unlimited number of non-exclusive licenses or sub-licenses to third
904
+ parties, under the copyright covering the contribution to use the
905
+ contribution by all means.
105
906
 
106
907
  Here are a few technical guidelines to follow:
107
908
 
108
- 1. Open an https://github.com/fontist/extract_ttc/issues[issue] to discuss a new feature.
109
- 1. Write tests to support your new feature.
110
- 1. Make sure the entire test suite passes locally and on CI.
111
- 1. Open a Pull Request.
112
- 1. https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature[Squash your commits]
113
- after receiving feedback.
114
- 1. Party!
115
-
909
+ . Open an https://github.com/fontist/extract_ttc/issues[issue] to
910
+ discuss a new feature.
911
+ . Write tests to support your new feature.
912
+ . Make sure the entire test suite passes locally and on CI.
913
+ . Open a Pull Request.
914
+ . https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature[Squash your commits]
915
+ after receiving feedback.
916
+ . Party!
116
917
 
117
918
  == License
118
919
 
119
920
  This gem is distributed with a BSD 3-Clause license.
120
921
 
121
- `stripttc.c` is obtained from:
922
+ The original C implementation was based on `stripttc.c` from the
923
+ FontForge project:
122
924
  https://github.com/fontforge/fontforge/blob/master/contrib/fonttools/stripttc.c
123
925
 
124
- The BSD 3-Clause licence for `stripttc.c` is provided in `ext/stripttc/LICENSE`.
125
-
126
- This gem is developed, maintained and funded by https://www.ribose.com/[Ribose Inc.]
926
+ This gem is developed, maintained and funded by
927
+ https://www.ribose.com/[Ribose Inc.]