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.
- checksums.yaml +4 -4
- data/.gitignore +0 -4
- data/.rubocop.yml +7 -9
- data/.rubocop_todo.yml +135 -0
- data/Gemfile +6 -6
- data/README.adoc +856 -55
- data/Rakefile +7 -101
- data/exe/extract_ttc +7 -0
- data/extract_ttc.gemspec +3 -4
- data/lib/extract_ttc/cli.rb +47 -0
- data/lib/extract_ttc/commands/extract.rb +88 -0
- data/lib/extract_ttc/commands/info.rb +112 -0
- data/lib/extract_ttc/commands/list.rb +60 -0
- data/lib/extract_ttc/configuration.rb +126 -0
- data/lib/extract_ttc/constants.rb +42 -0
- data/lib/extract_ttc/models/extraction_result.rb +56 -0
- data/lib/extract_ttc/models/validation_result.rb +53 -0
- data/lib/extract_ttc/true_type_collection.rb +79 -0
- data/lib/extract_ttc/true_type_font.rb +239 -0
- data/lib/extract_ttc/utilities/checksum_calculator.rb +89 -0
- data/lib/extract_ttc/utilities/output_path_generator.rb +100 -0
- data/lib/extract_ttc/version.rb +1 -1
- data/lib/extract_ttc.rb +83 -55
- data/sig/extract_ttc/configuration.rbs +19 -0
- data/sig/extract_ttc/constants.rbs +17 -0
- data/sig/extract_ttc/models/extraction_result.rbs +19 -0
- data/sig/extract_ttc/models/font_data.rbs +17 -0
- data/sig/extract_ttc/models/table_directory_entry.rbs +15 -0
- data/sig/extract_ttc/models/true_type_collection_header.rbs +15 -0
- data/sig/extract_ttc/models/true_type_font_offset_table.rbs +17 -0
- data/sig/extract_ttc/models/validation_result.rbs +17 -0
- data/sig/extract_ttc/utilities/checksum_calculator.rbs +13 -0
- data/sig/extract_ttc/utilities/output_path_generator.rbs +11 -0
- data/sig/extract_ttc/validators/true_type_collection_validator.rbs +9 -0
- data/sig/extract_ttc.rbs +20 -0
- metadata +44 -28
- data/ext/stripttc/LICENSE +0 -31
- data/ext/stripttc/dummy.c +0 -2
- data/ext/stripttc/extconf.rb +0 -5
- 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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
83
|
+
Add this line to your application's Gemfile:
|
|
25
84
|
|
|
26
85
|
[source,ruby]
|
|
27
86
|
----
|
|
28
|
-
gem
|
|
87
|
+
gem "extract_ttc"
|
|
29
88
|
----
|
|
30
89
|
|
|
31
90
|
And then execute:
|
|
32
91
|
|
|
33
|
-
[source,
|
|
92
|
+
[source,shell]
|
|
34
93
|
----
|
|
35
|
-
|
|
94
|
+
bundle install
|
|
36
95
|
----
|
|
37
96
|
|
|
38
97
|
Or install it yourself as:
|
|
39
98
|
|
|
40
|
-
[source,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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,
|
|
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
|
-
|
|
824
|
+
# Run specific test file
|
|
825
|
+
bundle exec rspec spec/extract_ttc/true_type_collection_spec.rb
|
|
73
826
|
|
|
74
|
-
|
|
827
|
+
# Run tests for a component
|
|
828
|
+
bundle exec rspec spec/extract_ttc/utilities/
|
|
75
829
|
----
|
|
76
|
-
|
|
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
|
-
|
|
856
|
+
=== Development console
|
|
80
857
|
|
|
81
|
-
|
|
858
|
+
Run an interactive console for experimentation.
|
|
859
|
+
|
|
860
|
+
[source,shell]
|
|
82
861
|
----
|
|
83
|
-
|
|
862
|
+
bin/console
|
|
84
863
|
----
|
|
85
864
|
|
|
86
|
-
|
|
865
|
+
This opens an IRB session with the gem loaded.
|
|
866
|
+
|
|
867
|
+
=== Installing locally
|
|
87
868
|
|
|
88
|
-
|
|
869
|
+
Install the gem onto your local machine for testing.
|
|
870
|
+
|
|
871
|
+
[source,shell]
|
|
89
872
|
----
|
|
90
|
-
bundle exec rake
|
|
873
|
+
bundle exec rake install
|
|
91
874
|
----
|
|
92
875
|
|
|
93
|
-
|
|
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
|
|
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.
|
|
101
|
-
participating in this project, you hereby grant
|
|
102
|
-
right to grant or transfer an
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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`
|
|
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
|
-
|
|
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.]
|