fontisan 0.2.12 → 0.2.13
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 +185 -106
- data/Gemfile +5 -0
- data/README.adoc +3 -2
- data/docs/CONVERSION_GUIDE.adoc +633 -0
- data/docs/TYPE1_FONTS.adoc +445 -0
- data/lib/fontisan/commands/info_command.rb +83 -2
- data/lib/fontisan/converters/format_converter.rb +15 -5
- data/lib/fontisan/converters/type1_converter.rb +734 -59
- data/lib/fontisan/font_loader.rb +1 -1
- data/lib/fontisan/hints/hint_converter.rb +4 -1
- data/lib/fontisan/type1/cff_to_type1_converter.rb +302 -0
- data/lib/fontisan/type1/font_dictionary.rb +62 -0
- data/lib/fontisan/type1/pfa_generator.rb +31 -5
- data/lib/fontisan/type1/pfa_parser.rb +31 -30
- data/lib/fontisan/type1/pfb_generator.rb +28 -5
- data/lib/fontisan/type1/private_dict.rb +57 -0
- data/lib/fontisan/type1/seac_expander.rb +501 -0
- data/lib/fontisan/type1.rb +2 -0
- data/lib/fontisan/type1_font.rb +21 -34
- data/lib/fontisan/version.rb +1 -1
- metadata +6 -3
- data/docs/DOCUMENTATION_SUMMARY.md +0 -141
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
= Type 1 Font Support
|
|
2
|
+
|
|
3
|
+
Fontisan provides comprehensive support for Adobe Type 1 fonts (PFB/PFA), including reading, converting, and generating Type 1 format fonts.
|
|
4
|
+
|
|
5
|
+
== Implementation Status
|
|
6
|
+
|
|
7
|
+
*Complete* features:
|
|
8
|
+
* Reading PFB (binary) and PFA (ASCII) format files
|
|
9
|
+
* eexec decryption for encrypted font portions (key: 55665)
|
|
10
|
+
* CharString decryption with lenIV support (key: 4330)
|
|
11
|
+
* Font dictionary and private dictionary parsing
|
|
12
|
+
* CharString parsing with `seac` composite expansion
|
|
13
|
+
* Type 1 → OTF conversion with CFF CharString generation
|
|
14
|
+
* Type 1 → TTF conversion (via OTF intermediate)
|
|
15
|
+
* OTF → Type 1 conversion with CFF → Type 1 CharString conversion
|
|
16
|
+
* TTF → Type 1 conversion (via OTF intermediate)
|
|
17
|
+
* Adobe Glyph List (AGL) integration for Unicode mapping
|
|
18
|
+
* SFNT table generation (head, hhea, maxp, name, OS/2, post, cmap)
|
|
19
|
+
* PFM, AFM, INF file generation for Type 1 fonts
|
|
20
|
+
|
|
21
|
+
*Preview/Planned* features:
|
|
22
|
+
* Multiple master Type 1 fonts
|
|
23
|
+
* Subroutine optimization in CharStrings
|
|
24
|
+
|
|
25
|
+
== Overview
|
|
26
|
+
|
|
27
|
+
Type 1 is a font format developed by Adobe Systems that uses PostScript outline descriptions. Type 1 fonts were widely used in professional typography and desktop publishing before being largely superseded by OpenType.
|
|
28
|
+
|
|
29
|
+
Fontisan's Type 1 support includes:
|
|
30
|
+
|
|
31
|
+
* Reading PFB and PFA files
|
|
32
|
+
* Converting Type 1 to modern formats (OTF, TTF, WOFF, WOFF2)
|
|
33
|
+
* Converting modern formats back to Type 1
|
|
34
|
+
* Preserving Type 1 metadata and hinting information
|
|
35
|
+
* Generating Unicode mappings from glyph names
|
|
36
|
+
|
|
37
|
+
== Type 1 Font Structure
|
|
38
|
+
|
|
39
|
+
=== Encryption
|
|
40
|
+
|
|
41
|
+
Type 1 fonts typically use two levels of encryption to protect font data:
|
|
42
|
+
|
|
43
|
+
* *eexec encryption* - Protects the private dictionary and CharStrings with key 55665
|
|
44
|
+
* *CharString encryption* - Encrypts individual CharStrings with key 4330
|
|
45
|
+
|
|
46
|
+
Fontisan automatically decrypts both levels when loading Type 1 fonts using the Rabin-Miller cipher with byte shuffle:
|
|
47
|
+
|
|
48
|
+
[source,ruby]
|
|
49
|
+
----
|
|
50
|
+
# Automatic decryption on load
|
|
51
|
+
font = Fontisan::FontLoader.from_file('font.pfb')
|
|
52
|
+
puts font.decrypted? # => true
|
|
53
|
+
|
|
54
|
+
# Access decrypted data
|
|
55
|
+
decrypted = font.decrypted_data
|
|
56
|
+
----
|
|
57
|
+
|
|
58
|
+
=== PFB vs PFA Formats
|
|
59
|
+
|
|
60
|
+
*PFB (Printer Font Binary)*::
|
|
61
|
+
Binary format with segmented structure using chunk markers:
|
|
62
|
+
* `0x8001` - ASCII text chunk
|
|
63
|
+
* `0x8002` - Binary data chunk (encrypted)
|
|
64
|
+
* `0x8003` - End of file marker
|
|
65
|
+
|
|
66
|
+
Each chunk is prefixed with a 4-byte little-endian length. PFB is most common on Windows systems.
|
|
67
|
+
|
|
68
|
+
*PFA (Printer Font ASCII)*::
|
|
69
|
+
Pure ASCII text format with encrypted portions marked by `currentfile eexec`. The encrypted data is represented as hexadecimal strings, followed by 512 ASCII zeros as a separator marker. PFA is most common on Unix/Linux systems.
|
|
70
|
+
|
|
71
|
+
=== Type 1 Font Structure
|
|
72
|
+
|
|
73
|
+
A Type 1 font consists of three main parts:
|
|
74
|
+
|
|
75
|
+
. *Font Dictionary* - Contains font-level metadata (font name, version, bounding box, etc.)
|
|
76
|
+
. *Private Dictionary* - Contains hinting parameters (blue values, stem snap, etc.)
|
|
77
|
+
. *CharStrings* - Contains glyph outline descriptions in PostScript format
|
|
78
|
+
|
|
79
|
+
=== Font Dictionary
|
|
80
|
+
|
|
81
|
+
The font dictionary contains essential font information:
|
|
82
|
+
|
|
83
|
+
[source,ruby]
|
|
84
|
+
----
|
|
85
|
+
{
|
|
86
|
+
version: "001.000",
|
|
87
|
+
notice: "Copyright notice",
|
|
88
|
+
copyright: "Copyright string",
|
|
89
|
+
full_name: "Font Full Name",
|
|
90
|
+
family_name: "Font Family",
|
|
91
|
+
weight: "Medium",
|
|
92
|
+
font_bbox: [x_min, y_min, x_max, y_max],
|
|
93
|
+
font_matrix: [0.001, 0, 0, 0.001, 0, 0]
|
|
94
|
+
}
|
|
95
|
+
----
|
|
96
|
+
|
|
97
|
+
Where,
|
|
98
|
+
|
|
99
|
+
`version`:: Version string in the format "XXX.YYY"
|
|
100
|
+
`notice`:: Copyright and license information
|
|
101
|
+
`copyright`:: Copyright string
|
|
102
|
+
`full_name`:: Full font name including style
|
|
103
|
+
`family_name`:: Font family name
|
|
104
|
+
`weight`:: Font weight (e.g., "Medium", "Bold", "Light")
|
|
105
|
+
`font_bbox`:: Font bounding box as [x_min, y_min, x_max, y_max]
|
|
106
|
+
`font_matrix`:: Transformation matrix for scaling coordinates
|
|
107
|
+
|
|
108
|
+
=== Private Dictionary
|
|
109
|
+
|
|
110
|
+
The private dictionary contains hinting parameters:
|
|
111
|
+
|
|
112
|
+
[source,ruby]
|
|
113
|
+
----
|
|
114
|
+
{
|
|
115
|
+
blue_values: [-10, 0, 470, 480],
|
|
116
|
+
other_blues: [250, 260],
|
|
117
|
+
blue_scale: 0.039625,
|
|
118
|
+
blue_shift: 7,
|
|
119
|
+
blue_fuzz: 1,
|
|
120
|
+
std_hw: 50,
|
|
121
|
+
std_vw: 80,
|
|
122
|
+
stem_snap_h: [50, 52],
|
|
123
|
+
stem_snap_v: [80, 82],
|
|
124
|
+
force_bold: false
|
|
125
|
+
}
|
|
126
|
+
----
|
|
127
|
+
|
|
128
|
+
Where,
|
|
129
|
+
|
|
130
|
+
`blue_values`:: Alignment zones for baseline and cap heights
|
|
131
|
+
`other_blues`:: Additional alignment zones
|
|
132
|
+
`blue_scale`:: Scaling factor for alignment zones
|
|
133
|
+
`blue_shift`:: Maximum deviation from alignment zone
|
|
134
|
+
`blue_fuzz`:: Fuzz factor for alignment
|
|
135
|
+
`std_hw`:: Standard horizontal stem width
|
|
136
|
+
`std_vw`:: Standard vertical stem width
|
|
137
|
+
`stem_snap_h`:: Array of horizontal stem widths
|
|
138
|
+
`stem_snap_v`:: Array of vertical stem widths
|
|
139
|
+
`force_bold`:: Whether to force bold rendering
|
|
140
|
+
|
|
141
|
+
=== CharStrings
|
|
142
|
+
|
|
143
|
+
CharStrings contain glyph outline descriptions using PostScript commands:
|
|
144
|
+
|
|
145
|
+
[source]
|
|
146
|
+
----
|
|
147
|
+
hsbw 0 0 # Horizontal sidebearings and width
|
|
148
|
+
rmoveto 100 0 # Relative move to
|
|
149
|
+
rlineto 50 0 # Relative line to
|
|
150
|
+
rlineto 0 50 # Relative line to
|
|
151
|
+
rlineto -50 0 # Relative line to (close path)
|
|
152
|
+
rlineto 0 -50 # Relative line to (close path)
|
|
153
|
+
endchar # End of glyph
|
|
154
|
+
----
|
|
155
|
+
|
|
156
|
+
=== Seac Composite Glyphs
|
|
157
|
+
|
|
158
|
+
Type 1 fonts include composite glyphs using the `seac` operator for accented characters. For example, "À" (A grave) might be defined as a base "A" with a combining grave accent.
|
|
159
|
+
|
|
160
|
+
Fontisan provides automatic `seac` expansion:
|
|
161
|
+
|
|
162
|
+
[source,ruby]
|
|
163
|
+
----
|
|
164
|
+
# Enable seac decomposition
|
|
165
|
+
options = Fontisan::ConversionOptions.new(
|
|
166
|
+
from: :type1,
|
|
167
|
+
to: :otf,
|
|
168
|
+
opening: { decompose_composites: true }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
172
|
+
tables = converter.convert(font, options: options)
|
|
173
|
+
----
|
|
174
|
+
|
|
175
|
+
The `SeacExpander` resolves composite glyphs by:
|
|
176
|
+
1. Extracting the base character and accent from the encoding
|
|
177
|
+
2. Recursively retrieving component CharStrings
|
|
178
|
+
3. Merging the outlines into a single decomposed glyph
|
|
179
|
+
|
|
180
|
+
This is necessary because CFF fonts (used in OTF) do not support the `seac` operator.
|
|
181
|
+
|
|
182
|
+
== Loading Type 1 Fonts
|
|
183
|
+
|
|
184
|
+
Use `FontLoader.load` to load Type 1 fonts:
|
|
185
|
+
|
|
186
|
+
[source,ruby]
|
|
187
|
+
----
|
|
188
|
+
require 'fontisan'
|
|
189
|
+
|
|
190
|
+
# Load PFB file
|
|
191
|
+
font = Fontisan::FontLoader.load('font.pfb')
|
|
192
|
+
|
|
193
|
+
# Load PFA file
|
|
194
|
+
font = Fontisan::FontLoader.load('font.pfa')
|
|
195
|
+
|
|
196
|
+
# Access font dictionary
|
|
197
|
+
dict = font.font_dictionary
|
|
198
|
+
puts "Font: #{dict.font_name}"
|
|
199
|
+
puts "Family: #{dict.family_name}"
|
|
200
|
+
----
|
|
201
|
+
|
|
202
|
+
== Converting Type 1 Fonts
|
|
203
|
+
|
|
204
|
+
=== Type 1 to OpenType (OTF)
|
|
205
|
+
|
|
206
|
+
Convert Type 1 fonts to modern OpenType format with CFF outlines:
|
|
207
|
+
|
|
208
|
+
[source,ruby]
|
|
209
|
+
----
|
|
210
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
211
|
+
tables = converter.convert(font, target_format: :otf)
|
|
212
|
+
|
|
213
|
+
# Write to file
|
|
214
|
+
Fontisan::FontWriter.write(tables, 'output.otf')
|
|
215
|
+
----
|
|
216
|
+
|
|
217
|
+
Using the CLI:
|
|
218
|
+
|
|
219
|
+
[source,shell]
|
|
220
|
+
----
|
|
221
|
+
fontisan convert font.pfb --to otf --output font.otf
|
|
222
|
+
----
|
|
223
|
+
|
|
224
|
+
=== Type 1 to TrueType (TTF)
|
|
225
|
+
|
|
226
|
+
Convert Type 1 fonts to TrueType format with quadratic curves:
|
|
227
|
+
|
|
228
|
+
[source,ruby]
|
|
229
|
+
----
|
|
230
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
231
|
+
tables = converter.convert(font, target_format: :ttf)
|
|
232
|
+
|
|
233
|
+
# Write to file
|
|
234
|
+
Fontisan::FontWriter.write(tables, 'output.ttf')
|
|
235
|
+
----
|
|
236
|
+
|
|
237
|
+
Using the CLI:
|
|
238
|
+
|
|
239
|
+
[source,shell]
|
|
240
|
+
----
|
|
241
|
+
fontisan convert font.pfb --to ttf --output font.ttf
|
|
242
|
+
----
|
|
243
|
+
|
|
244
|
+
=== Type 1 to Web Fonts
|
|
245
|
+
|
|
246
|
+
Convert Type 1 fonts directly to web font formats:
|
|
247
|
+
|
|
248
|
+
[source,shell]
|
|
249
|
+
----
|
|
250
|
+
# Convert to WOFF
|
|
251
|
+
fontisan convert font.pfb --to woff --output font.woff
|
|
252
|
+
|
|
253
|
+
# Convert to WOFF2
|
|
254
|
+
fontisan convert font.pfb --to woff2 --output font.woff2
|
|
255
|
+
----
|
|
256
|
+
|
|
257
|
+
=== Conversion with Options
|
|
258
|
+
|
|
259
|
+
Use ConversionOptions for advanced control:
|
|
260
|
+
|
|
261
|
+
[source,ruby]
|
|
262
|
+
----
|
|
263
|
+
options = Fontisan::ConversionOptions.recommended(from: :type1, to: :otf)
|
|
264
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
265
|
+
tables = converter.convert(font, options: options)
|
|
266
|
+
----
|
|
267
|
+
|
|
268
|
+
Using presets:
|
|
269
|
+
|
|
270
|
+
[source,ruby]
|
|
271
|
+
----
|
|
272
|
+
options = Fontisan::ConversionOptions.from_preset(:type1_to_modern)
|
|
273
|
+
tables = converter.convert(font, options: options)
|
|
274
|
+
----
|
|
275
|
+
|
|
276
|
+
Using the CLI:
|
|
277
|
+
|
|
278
|
+
[source,shell]
|
|
279
|
+
----
|
|
280
|
+
fontisan convert font.pfb --to otf --output font.otf --preset type1_to_modern
|
|
281
|
+
----
|
|
282
|
+
|
|
283
|
+
== Converting to Type 1
|
|
284
|
+
|
|
285
|
+
=== OpenType to Type 1
|
|
286
|
+
|
|
287
|
+
Convert OpenType/CFF fonts back to Type 1 format:
|
|
288
|
+
|
|
289
|
+
[source,ruby]
|
|
290
|
+
----
|
|
291
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
292
|
+
type1_data = converter.convert(otf_font, target_format: :type1)
|
|
293
|
+
|
|
294
|
+
# Write PFB file
|
|
295
|
+
File.write('output.pfb', type1_data[:pfb])
|
|
296
|
+
----
|
|
297
|
+
|
|
298
|
+
Using the CLI:
|
|
299
|
+
|
|
300
|
+
[source,shell]
|
|
301
|
+
----
|
|
302
|
+
fontisan convert font.otf --to type1 --output font.pfb --preset modern_to_type1
|
|
303
|
+
----
|
|
304
|
+
|
|
305
|
+
=== TrueType to Type 1
|
|
306
|
+
|
|
307
|
+
Convert TrueType fonts to Type 1 (via OpenType intermediate):
|
|
308
|
+
|
|
309
|
+
[source,shell]
|
|
310
|
+
----
|
|
311
|
+
fontisan convert font.ttf --to type1 --output font.pfb
|
|
312
|
+
----
|
|
313
|
+
|
|
314
|
+
== Conversion Options
|
|
315
|
+
|
|
316
|
+
=== Opening Options
|
|
317
|
+
|
|
318
|
+
Options applied when reading Type 1 fonts:
|
|
319
|
+
|
|
320
|
+
`generate_unicode`:: Generate Unicode codepoints from glyph names using the Adobe Glyph List
|
|
321
|
+
`decompose_composites`:: Decompose seac composite glyphs into base glyphs
|
|
322
|
+
`read_all_records`:: Force loading of all font dictionary records
|
|
323
|
+
|
|
324
|
+
=== Generating Options
|
|
325
|
+
|
|
326
|
+
Options applied when writing Type 1 fonts:
|
|
327
|
+
|
|
328
|
+
`write_pfm`:: Generate PFM (Printer Font Metrics) file (default: true)
|
|
329
|
+
`write_afm`:: Generate AFM (Adobe Font Metrics) file (default: true)
|
|
330
|
+
`write_inf`:: Generate INF file for installation (default: true)
|
|
331
|
+
`select_encoding_automatically`:: Automatically select encoding (default: true)
|
|
332
|
+
`hinting_mode`:: Hint preservation mode: preserve, auto, or none
|
|
333
|
+
`decompose_on_output`:: Decompose composite glyphs on output
|
|
334
|
+
|
|
335
|
+
=== Presets
|
|
336
|
+
|
|
337
|
+
Fontisan includes predefined presets for common Type 1 workflows:
|
|
338
|
+
|
|
339
|
+
* `type1_to_modern` - Optimize Type 1 fonts for modern use (generates Unicode, preserves hints)
|
|
340
|
+
* `modern_to_type1` - Convert modern fonts back to Type 1 format with proper metrics
|
|
341
|
+
|
|
342
|
+
[source,ruby]
|
|
343
|
+
----
|
|
344
|
+
# Using preset programmatically
|
|
345
|
+
options = Fontisan::ConversionOptions.from_preset(:type1_to_modern)
|
|
346
|
+
|
|
347
|
+
# Using preset via CLI
|
|
348
|
+
fontisan convert font.pfb --to otf --preset type1_to_modern --output font.otf
|
|
349
|
+
----
|
|
350
|
+
|
|
351
|
+
== Working with Glyphs
|
|
352
|
+
|
|
353
|
+
=== Accessing Glyph Outlines
|
|
354
|
+
|
|
355
|
+
[source,ruby]
|
|
356
|
+
----
|
|
357
|
+
font = Fontisan::FontLoader.load('font.pfb')
|
|
358
|
+
|
|
359
|
+
# Iterate over glyphs
|
|
360
|
+
font.charstrings.each_charstring do |glyph_name, charstring|
|
|
361
|
+
puts "Glyph: #{glyph_name}"
|
|
362
|
+
|
|
363
|
+
# Convert to universal outline format
|
|
364
|
+
outline = charstring.to_outline
|
|
365
|
+
|
|
366
|
+
# Access contour points
|
|
367
|
+
outline.contours.each do |contour|
|
|
368
|
+
contour.points.each do |point|
|
|
369
|
+
puts " Point: #{point.x}, #{point.y} (#{point.on_curve? ? 'on' : 'off'} curve)"
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
----
|
|
374
|
+
|
|
375
|
+
=== Glyph Names to Unicode
|
|
376
|
+
|
|
377
|
+
Type 1 fonts use glyph names rather than Unicode codepoints. Use the Adobe Glyph List to map names to codepoints:
|
|
378
|
+
|
|
379
|
+
[source,ruby]
|
|
380
|
+
----
|
|
381
|
+
# Enable Unicode generation
|
|
382
|
+
options = Fontisan::ConversionOptions.new(
|
|
383
|
+
from: :type1,
|
|
384
|
+
to: :otf,
|
|
385
|
+
opening: { generate_unicode: true }
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
converter = Fontisan::Converters::Type1Converter.new
|
|
389
|
+
tables = converter.convert(font, options: options)
|
|
390
|
+
----
|
|
391
|
+
|
|
392
|
+
== Limitations
|
|
393
|
+
|
|
394
|
+
Current limitations of Type 1 support:
|
|
395
|
+
|
|
396
|
+
* Subroutines in CharStrings are not fully optimized for space efficiency
|
|
397
|
+
* Multiple master Type 1 fonts are not supported (only single master fonts)
|
|
398
|
+
* Some hinting parameters may not convert perfectly between Type 1 and CFF formats
|
|
399
|
+
* Converting modern fonts back to Type 1 may lose some OpenType features (GSUB/GPOS)
|
|
400
|
+
|
|
401
|
+
== Technical Details
|
|
402
|
+
|
|
403
|
+
=== Decryption Algorithm
|
|
404
|
+
|
|
405
|
+
Fontisan uses the Rabin-Miller cipher with byte shuffle for decryption:
|
|
406
|
+
|
|
407
|
+
[source,ruby]
|
|
408
|
+
----
|
|
409
|
+
# eexec decryption (key: 55665)
|
|
410
|
+
cipher = key
|
|
411
|
+
data.each_byte do |byte|
|
|
412
|
+
cipher = ((cipher << 8) & 0xFFFFFFFF) | byte
|
|
413
|
+
plain = cipher >> 8
|
|
414
|
+
result << plain.chr
|
|
415
|
+
cipher = plain ^ (cipher >> 16)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# CharString decryption (key: 4330)
|
|
419
|
+
# Same cipher, but skips lenIV bytes at start
|
|
420
|
+
decrypted = decrypt(charstring_data, 4330)
|
|
421
|
+
charstring = lenIV > 0 ? decrypted[lenIV..-1] : decrypted
|
|
422
|
+
----
|
|
423
|
+
|
|
424
|
+
=== Command Mapping
|
|
425
|
+
|
|
426
|
+
Type 1 CharString commands map directly to CFF commands for conversion:
|
|
427
|
+
|
|
428
|
+
| Type 1 Command | CFF Command | Notes |
|
|
429
|
+
|----------------|-------------|-------|
|
|
430
|
+
| hsbw | hsbw | Identical |
|
|
431
|
+
| sbw | sbw | Identical |
|
|
432
|
+
| endchar | endchar | Identical |
|
|
433
|
+
| hstem/vstem | hstem/vstem | Identical |
|
|
434
|
+
| rmoveto/hmoveto/vmoveto | rmoveto/hmoveto/vmoveto | Identical |
|
|
435
|
+
| rlineto/hlineto/vlineto | rlineto/hlineto/vlineto | Identical |
|
|
436
|
+
| rrcurveto/hhcurveto/vvcurveto/hvhcurveto/vhcurveto | rrcurveto/hhcurveto/vvcurveto/hvhcurveto/vhcurveto | Identical |
|
|
437
|
+
| seac | - | Decomposed (not in CFF) |
|
|
438
|
+
| closepath | endchar | Mapped |
|
|
439
|
+
| callsubr/callgsubr | callsubr/callgsubr | Identical |
|
|
440
|
+
|
|
441
|
+
== See Also
|
|
442
|
+
|
|
443
|
+
* https://www.adobe.com/devnet/font/pdfs/Type1.pdf[Adobe Type 1 Font Format Specification]
|
|
444
|
+
* link:CONVERSION_GUIDE.adoc[Conversion Guide] - Comprehensive conversion options reference
|
|
445
|
+
* link:README.adoc[README] - Main Fontisan documentation
|
|
@@ -153,12 +153,18 @@ module Fontisan
|
|
|
153
153
|
"truetype"
|
|
154
154
|
when OpenTypeFont
|
|
155
155
|
"cff"
|
|
156
|
+
when Type1Font
|
|
157
|
+
"type1"
|
|
156
158
|
else
|
|
157
159
|
"unknown"
|
|
158
160
|
end
|
|
159
161
|
|
|
160
|
-
# Check if variable font
|
|
161
|
-
info.is_variable = font.
|
|
162
|
+
# Check if variable font (Type1 fonts are never variable)
|
|
163
|
+
info.is_variable = if font.is_a?(Type1Font)
|
|
164
|
+
false
|
|
165
|
+
else
|
|
166
|
+
font.has_table?(Constants::FVAR_TAG)
|
|
167
|
+
end
|
|
162
168
|
end
|
|
163
169
|
|
|
164
170
|
# Populate essential fields for brief mode (metadata tables only).
|
|
@@ -169,6 +175,17 @@ module Fontisan
|
|
|
169
175
|
#
|
|
170
176
|
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
171
177
|
def populate_brief_fields(info)
|
|
178
|
+
if font.is_a?(Type1Font)
|
|
179
|
+
populate_type1_brief_fields(info)
|
|
180
|
+
else
|
|
181
|
+
populate_sfnt_brief_fields(info)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Populate SFNT font brief fields (name, head, OS/2 tables).
|
|
186
|
+
#
|
|
187
|
+
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
188
|
+
def populate_sfnt_brief_fields(info)
|
|
172
189
|
# Essential names from name table
|
|
173
190
|
if font.has_table?(Constants::NAME_TAG)
|
|
174
191
|
name_table = font.table(Constants::NAME_TAG)
|
|
@@ -193,12 +210,45 @@ module Fontisan
|
|
|
193
210
|
end
|
|
194
211
|
end
|
|
195
212
|
|
|
213
|
+
# Populate Type 1 font brief fields.
|
|
214
|
+
#
|
|
215
|
+
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
216
|
+
def populate_type1_brief_fields(info)
|
|
217
|
+
# Get Type 1 font metadata
|
|
218
|
+
font_dict = font.font_dictionary
|
|
219
|
+
font_info = font_dict&.font_info if font_dict
|
|
220
|
+
|
|
221
|
+
return unless font_info
|
|
222
|
+
|
|
223
|
+
# Essential names from Type 1 font
|
|
224
|
+
info.family_name = font_info.family_name
|
|
225
|
+
info.full_name = font_info.full_name
|
|
226
|
+
info.postscript_name = font.font_name
|
|
227
|
+
info.version = font_info.version
|
|
228
|
+
|
|
229
|
+
# Metrics from font dictionary
|
|
230
|
+
if font_dict&.font_b_box
|
|
231
|
+
info.bounding_box = font_dict.font_b_box
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
196
235
|
# Populate all fields for full mode.
|
|
197
236
|
#
|
|
198
237
|
# Full mode extracts comprehensive metadata from all available tables.
|
|
199
238
|
#
|
|
200
239
|
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
201
240
|
def populate_full_fields(info)
|
|
241
|
+
if font.is_a?(Type1Font)
|
|
242
|
+
populate_type1_full_fields(info)
|
|
243
|
+
else
|
|
244
|
+
populate_sfnt_full_fields(info)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Populate SFNT font full fields.
|
|
249
|
+
#
|
|
250
|
+
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
251
|
+
def populate_sfnt_full_fields(info)
|
|
202
252
|
populate_from_name_table(info) if font.has_table?(Constants::NAME_TAG)
|
|
203
253
|
populate_from_os2_table(info) if font.has_table?(Constants::OS2_TAG)
|
|
204
254
|
populate_from_head_table(info) if font.has_table?(Constants::HEAD_TAG)
|
|
@@ -207,6 +257,37 @@ module Fontisan
|
|
|
207
257
|
populate_bitmap_info(info) if font.has_table?("CBLC") || font.has_table?("sbix")
|
|
208
258
|
end
|
|
209
259
|
|
|
260
|
+
# Populate Type 1 font full fields.
|
|
261
|
+
#
|
|
262
|
+
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
263
|
+
def populate_type1_full_fields(info)
|
|
264
|
+
font_dict = font.font_dictionary
|
|
265
|
+
font_info = font_dict&.font_info if font_dict
|
|
266
|
+
|
|
267
|
+
return unless font_info
|
|
268
|
+
|
|
269
|
+
# Names from Type 1 font
|
|
270
|
+
info.family_name = font_info.family_name
|
|
271
|
+
info.full_name = font_info.full_name
|
|
272
|
+
info.postscript_name = font.font_name
|
|
273
|
+
info.version = font_info.version
|
|
274
|
+
info.copyright = font_info.copyright
|
|
275
|
+
info.description = font_info.notice
|
|
276
|
+
info.designer = nil # Type 1 fonts may not have designer info
|
|
277
|
+
|
|
278
|
+
# Metrics
|
|
279
|
+
if font_dict&.font_b_box
|
|
280
|
+
info.bounding_box = font_dict.font_b_box
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if font_dict&.font_matrix
|
|
284
|
+
info.font_matrix = font_dict.font_matrix
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Glyph count
|
|
288
|
+
info.glyph_count = font.charstrings&.count || 0
|
|
289
|
+
end
|
|
290
|
+
|
|
210
291
|
# Populate FontInfo from the name table.
|
|
211
292
|
#
|
|
212
293
|
# @param info [Models::FontInfo] FontInfo instance to populate
|
|
@@ -193,9 +193,12 @@ module Fontisan
|
|
|
193
193
|
|
|
194
194
|
# Check if font is a variable font
|
|
195
195
|
#
|
|
196
|
-
# @param font [TrueTypeFont, OpenTypeFont] Font to check
|
|
196
|
+
# @param font [TrueTypeFont, OpenTypeFont, Type1Font] Font to check
|
|
197
197
|
# @return [Boolean] True if font has fvar table
|
|
198
198
|
def variable_font?(font)
|
|
199
|
+
# Type 1 fonts are never variable fonts
|
|
200
|
+
return false if font.is_a?(Type1Font)
|
|
201
|
+
|
|
199
202
|
font.has_table?("fvar")
|
|
200
203
|
end
|
|
201
204
|
|
|
@@ -343,8 +346,12 @@ _options)
|
|
|
343
346
|
def validate_parameters!(font, target_format)
|
|
344
347
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
345
348
|
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
# Type1Font uses a different interface (font_dictionary, charstrings, etc.)
|
|
350
|
+
# rather than the SFNT table interface
|
|
351
|
+
is_type1 = font.is_a?(Type1Font)
|
|
352
|
+
|
|
353
|
+
unless is_type1 || font.respond_to?(:table)
|
|
354
|
+
raise ArgumentError, "Font must respond to :table method or be a Type1Font"
|
|
348
355
|
end
|
|
349
356
|
|
|
350
357
|
unless target_format.is_a?(Symbol)
|
|
@@ -394,10 +401,13 @@ _options)
|
|
|
394
401
|
|
|
395
402
|
# Detect font format from tables
|
|
396
403
|
#
|
|
397
|
-
# @param font [TrueTypeFont, OpenTypeFont] Font to detect
|
|
398
|
-
# @return [Symbol] Format (:ttf or :
|
|
404
|
+
# @param font [TrueTypeFont, OpenTypeFont, Type1Font] Font to detect
|
|
405
|
+
# @return [Symbol] Format (:ttf, :otf, or :type1)
|
|
399
406
|
# @raise [Error] If format cannot be detected
|
|
400
407
|
def detect_format(font)
|
|
408
|
+
# Check for Type1Font first (uses different interface)
|
|
409
|
+
return :type1 if font.is_a?(Type1Font)
|
|
410
|
+
|
|
401
411
|
# Check for CFF/CFF2 tables (OpenType/CFF)
|
|
402
412
|
if font.has_table?("CFF ") || font.has_table?("CFF2")
|
|
403
413
|
:otf
|