fontisan 0.2.10 → 0.2.12
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 +216 -42
- data/README.adoc +160 -0
- data/lib/fontisan/cli.rb +177 -6
- data/lib/fontisan/collection/table_analyzer.rb +88 -3
- data/lib/fontisan/commands/convert_command.rb +32 -1
- data/lib/fontisan/config/conversion_matrix.yml +132 -4
- data/lib/fontisan/constants.rb +12 -0
- data/lib/fontisan/conversion_options.rb +378 -0
- data/lib/fontisan/converters/cff_table_builder.rb +198 -0
- data/lib/fontisan/converters/collection_converter.rb +45 -10
- data/lib/fontisan/converters/format_converter.rb +2 -0
- data/lib/fontisan/converters/glyf_table_builder.rb +63 -0
- data/lib/fontisan/converters/outline_converter.rb +111 -374
- data/lib/fontisan/converters/outline_extraction.rb +93 -0
- data/lib/fontisan/converters/outline_optimizer.rb +89 -0
- data/lib/fontisan/converters/type1_converter.rb +559 -0
- data/lib/fontisan/font_loader.rb +46 -3
- data/lib/fontisan/glyph_accessor.rb +29 -1
- data/lib/fontisan/type1/afm_generator.rb +436 -0
- data/lib/fontisan/type1/afm_parser.rb +298 -0
- data/lib/fontisan/type1/agl.rb +456 -0
- data/lib/fontisan/type1/charstring_converter.rb +240 -0
- data/lib/fontisan/type1/charstrings.rb +408 -0
- data/lib/fontisan/type1/conversion_options.rb +243 -0
- data/lib/fontisan/type1/decryptor.rb +183 -0
- data/lib/fontisan/type1/encodings.rb +697 -0
- data/lib/fontisan/type1/font_dictionary.rb +514 -0
- data/lib/fontisan/type1/generator.rb +220 -0
- data/lib/fontisan/type1/inf_generator.rb +332 -0
- data/lib/fontisan/type1/pfa_generator.rb +343 -0
- data/lib/fontisan/type1/pfa_parser.rb +158 -0
- data/lib/fontisan/type1/pfb_generator.rb +291 -0
- data/lib/fontisan/type1/pfb_parser.rb +166 -0
- data/lib/fontisan/type1/pfm_generator.rb +610 -0
- data/lib/fontisan/type1/pfm_parser.rb +433 -0
- data/lib/fontisan/type1/private_dict.rb +285 -0
- data/lib/fontisan/type1/ttf_to_type1_converter.rb +327 -0
- data/lib/fontisan/type1/upm_scaler.rb +118 -0
- data/lib/fontisan/type1.rb +73 -0
- data/lib/fontisan/type1_font.rb +331 -0
- data/lib/fontisan/variation/cache.rb +1 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2_font.rb +3 -3
- data/lib/fontisan.rb +2 -0
- metadata +30 -2
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Type1
|
|
5
|
+
# Adobe Glyph List (AGL) for Unicode to glyph name mapping
|
|
6
|
+
#
|
|
7
|
+
# [`AGL`](lib/fontisan/type1/agl.rb) provides mapping between Unicode codepoints
|
|
8
|
+
# and glyph names according to the Adobe Glyph List Specification.
|
|
9
|
+
#
|
|
10
|
+
# The AGL defines standard names for glyphs to ensure compatibility across
|
|
11
|
+
# font applications and systems.
|
|
12
|
+
#
|
|
13
|
+
# @see https://github.com/adobe-type-tools/agl-specification
|
|
14
|
+
module AGL
|
|
15
|
+
# Unicode to glyph name mapping (subset of AGL)
|
|
16
|
+
# Includes commonly used glyphs from Latin-1 and Latin Extended-A
|
|
17
|
+
UNICODE_TO_NAME = {
|
|
18
|
+
# ASCII control characters and basic Latin
|
|
19
|
+
0x0020 => "space",
|
|
20
|
+
0x0021 => "exclam",
|
|
21
|
+
0x0022 => "quotedbl",
|
|
22
|
+
0x0023 => "numbersign",
|
|
23
|
+
0x0024 => "dollar",
|
|
24
|
+
0x0025 => "percent",
|
|
25
|
+
0x0026 => "ampersand",
|
|
26
|
+
0x0027 => "quotesingle",
|
|
27
|
+
0x0028 => "parenleft",
|
|
28
|
+
0x0029 => "parenright",
|
|
29
|
+
0x002A => "asterisk",
|
|
30
|
+
0x002B => "plus",
|
|
31
|
+
0x002C => "comma",
|
|
32
|
+
0x002D => "hyphen",
|
|
33
|
+
0x002E => "period",
|
|
34
|
+
0x002F => "slash",
|
|
35
|
+
0x0030 => "zero",
|
|
36
|
+
0x0031 => "one",
|
|
37
|
+
0x0032 => "two",
|
|
38
|
+
0x0033 => "three",
|
|
39
|
+
0x0034 => "four",
|
|
40
|
+
0x0035 => "five",
|
|
41
|
+
0x0036 => "six",
|
|
42
|
+
0x0037 => "seven",
|
|
43
|
+
0x0038 => "eight",
|
|
44
|
+
0x0039 => "nine",
|
|
45
|
+
0x003A => "colon",
|
|
46
|
+
0x003B => "semicolon",
|
|
47
|
+
0x003C => "less",
|
|
48
|
+
0x003D => "equal",
|
|
49
|
+
0x003E => "greater",
|
|
50
|
+
0x003F => "question",
|
|
51
|
+
0x0040 => "at",
|
|
52
|
+
0x0041 => "A",
|
|
53
|
+
0x0042 => "B",
|
|
54
|
+
0x0043 => "C",
|
|
55
|
+
0x0044 => "D",
|
|
56
|
+
0x0045 => "E",
|
|
57
|
+
0x0046 => "F",
|
|
58
|
+
0x0047 => "G",
|
|
59
|
+
0x0048 => "H",
|
|
60
|
+
0x0049 => "I",
|
|
61
|
+
0x004A => "J",
|
|
62
|
+
0x004B => "K",
|
|
63
|
+
0x004C => "L",
|
|
64
|
+
0x004D => "M",
|
|
65
|
+
0x004E => "N",
|
|
66
|
+
0x004F => "O",
|
|
67
|
+
0x0050 => "P",
|
|
68
|
+
0x0051 => "Q",
|
|
69
|
+
0x0052 => "R",
|
|
70
|
+
0x0053 => "S",
|
|
71
|
+
0x0054 => "T",
|
|
72
|
+
0x0055 => "U",
|
|
73
|
+
0x0056 => "V",
|
|
74
|
+
0x0057 => "W",
|
|
75
|
+
0x0058 => "X",
|
|
76
|
+
0x0059 => "Y",
|
|
77
|
+
0x005A => "Z",
|
|
78
|
+
0x005B => "bracketleft",
|
|
79
|
+
0x005C => "backslash",
|
|
80
|
+
0x005D => "bracketright",
|
|
81
|
+
0x005E => "asciicircum",
|
|
82
|
+
0x005F => "underscore",
|
|
83
|
+
0x0060 => "grave",
|
|
84
|
+
0x0061 => "a",
|
|
85
|
+
0x0062 => "b",
|
|
86
|
+
0x0063 => "c",
|
|
87
|
+
0x0064 => "d",
|
|
88
|
+
0x0065 => "e",
|
|
89
|
+
0x0066 => "f",
|
|
90
|
+
0x0067 => "g",
|
|
91
|
+
0x0068 => "h",
|
|
92
|
+
0x0069 => "i",
|
|
93
|
+
0x006A => "j",
|
|
94
|
+
0x006B => "k",
|
|
95
|
+
0x006C => "l",
|
|
96
|
+
0x006D => "m",
|
|
97
|
+
0x006E => "n",
|
|
98
|
+
0x006F => "o",
|
|
99
|
+
0x0070 => "p",
|
|
100
|
+
0x0071 => "q",
|
|
101
|
+
0x0072 => "r",
|
|
102
|
+
0x0073 => "s",
|
|
103
|
+
0x0074 => "t",
|
|
104
|
+
0x0075 => "u",
|
|
105
|
+
0x0076 => "v",
|
|
106
|
+
0x0077 => "w",
|
|
107
|
+
0x0078 => "x",
|
|
108
|
+
0x0079 => "y",
|
|
109
|
+
0x007A => "z",
|
|
110
|
+
0x007B => "braceleft",
|
|
111
|
+
0x007C => "bar",
|
|
112
|
+
0x007D => "braceright",
|
|
113
|
+
0x007E => "asciitilde",
|
|
114
|
+
# Latin-1 Supplement
|
|
115
|
+
0x00A0 => "space",
|
|
116
|
+
0x00A1 => "exclamdown",
|
|
117
|
+
0x00A2 => "cent",
|
|
118
|
+
0x00A3 => "sterling",
|
|
119
|
+
0x00A4 => "currency",
|
|
120
|
+
0x00A5 => "yen",
|
|
121
|
+
0x00A6 => "brokenbar",
|
|
122
|
+
0x00A7 => "section",
|
|
123
|
+
0x00A8 => "dieresis",
|
|
124
|
+
0x00A9 => "copyright",
|
|
125
|
+
0x00AA => "ordfeminine",
|
|
126
|
+
0x00AB => "guillemotleft",
|
|
127
|
+
0x00AC => "logicalnot",
|
|
128
|
+
0x00AD => "hyphen",
|
|
129
|
+
0x00AE => "registered",
|
|
130
|
+
0x00AF => "macron",
|
|
131
|
+
0x00B0 => "degree",
|
|
132
|
+
0x00B1 => "plusminus",
|
|
133
|
+
0x00B2 => "twosuperior",
|
|
134
|
+
0x00B3 => "threesuperior",
|
|
135
|
+
0x00B4 => "acute",
|
|
136
|
+
0x00B5 => "mu",
|
|
137
|
+
0x00B6 => "paragraph",
|
|
138
|
+
0x00B7 => "periodcentered",
|
|
139
|
+
0x00B8 => "cedilla",
|
|
140
|
+
0x00B9 => "onesuperior",
|
|
141
|
+
0x00BA => "ordmasculine",
|
|
142
|
+
0x00BB => "guillemotright",
|
|
143
|
+
0x00BC => "onequarter",
|
|
144
|
+
0x00BD => "onehalf",
|
|
145
|
+
0x00BE => "threequarters",
|
|
146
|
+
0x00BF => "questiondown",
|
|
147
|
+
0x00C0 => "Agrave",
|
|
148
|
+
0x00C1 => "Aacute",
|
|
149
|
+
0x00C2 => "Acircumflex",
|
|
150
|
+
0x00C3 => "Atilde",
|
|
151
|
+
0x00C4 => "Adieresis",
|
|
152
|
+
0x00C5 => "Aring",
|
|
153
|
+
0x00C6 => "AE",
|
|
154
|
+
0x00C7 => "Ccedilla",
|
|
155
|
+
0x00C8 => "Egrave",
|
|
156
|
+
0x00C9 => "Eacute",
|
|
157
|
+
0x00CA => "Ecircumflex",
|
|
158
|
+
0x00CB => "Edieresis",
|
|
159
|
+
0x00CC => "Igrave",
|
|
160
|
+
0x00CD => "Iacute",
|
|
161
|
+
0x00CE => "Icircumflex",
|
|
162
|
+
0x00CF => "Idieresis",
|
|
163
|
+
0x00D0 => "Eth",
|
|
164
|
+
0x00D1 => "Ntilde",
|
|
165
|
+
0x00D2 => "Ograve",
|
|
166
|
+
0x00D3 => "Oacute",
|
|
167
|
+
0x00D4 => "Ocircumflex",
|
|
168
|
+
0x00D5 => "Otilde",
|
|
169
|
+
0x00D6 => "Odieresis",
|
|
170
|
+
0x00D7 => "multiply",
|
|
171
|
+
0x00D8 => "Oslash",
|
|
172
|
+
0x00D9 => "Ugrave",
|
|
173
|
+
0x00DA => "Uacute",
|
|
174
|
+
0x00DB => "Ucircumflex",
|
|
175
|
+
0x00DC => "Udieresis",
|
|
176
|
+
0x00DD => "Yacute",
|
|
177
|
+
0x00DE => "Thorn",
|
|
178
|
+
0x00DF => "germandbls",
|
|
179
|
+
0x00E0 => "agrave",
|
|
180
|
+
0x00E1 => "aacute",
|
|
181
|
+
0x00E2 => "acircumflex",
|
|
182
|
+
0x00E3 => "atilde",
|
|
183
|
+
0x00E4 => "adieresis",
|
|
184
|
+
0x00E5 => "aring",
|
|
185
|
+
0x00E6 => "ae",
|
|
186
|
+
0x00E7 => "ccedilla",
|
|
187
|
+
0x00E8 => "egrave",
|
|
188
|
+
0x00E9 => "eacute",
|
|
189
|
+
0x00EA => "ecircumflex",
|
|
190
|
+
0x00EB => "edieresis",
|
|
191
|
+
0x00EC => "igrave",
|
|
192
|
+
0x00ED => "iacute",
|
|
193
|
+
0x00EE => "icircumflex",
|
|
194
|
+
0x00EF => "idieresis",
|
|
195
|
+
0x00F0 => "eth",
|
|
196
|
+
0x00F1 => "ntilde",
|
|
197
|
+
0x00F2 => "ograve",
|
|
198
|
+
0x00F3 => "oacute",
|
|
199
|
+
0x00F4 => "ocircumflex",
|
|
200
|
+
0x00F5 => "otilde",
|
|
201
|
+
0x00F6 => "odieresis",
|
|
202
|
+
0x00F7 => "divide",
|
|
203
|
+
0x00F8 => "oslash",
|
|
204
|
+
0x00F9 => "ugrave",
|
|
205
|
+
0x00FA => "uacute",
|
|
206
|
+
0x00FB => "ucircumflex",
|
|
207
|
+
0x00FC => "udieresis",
|
|
208
|
+
0x00FD => "yacute",
|
|
209
|
+
0x00FE => "thorn",
|
|
210
|
+
0x00FF => "ydieresis",
|
|
211
|
+
# Latin Extended-A
|
|
212
|
+
0x0100 => "Amacron",
|
|
213
|
+
0x0101 => "amacron",
|
|
214
|
+
0x0102 => "Abreve",
|
|
215
|
+
0x0103 => "abreve",
|
|
216
|
+
0x0104 => "Aogonek",
|
|
217
|
+
0x0105 => "aogonek",
|
|
218
|
+
0x0106 => "Cacute",
|
|
219
|
+
0x0107 => "cacute",
|
|
220
|
+
0x0108 => "Ccircumflex",
|
|
221
|
+
0x0109 => "ccircumflex",
|
|
222
|
+
0x010A => "Cdotaccent",
|
|
223
|
+
0x010B => "cdotaccent",
|
|
224
|
+
0x010C => "Ccaron",
|
|
225
|
+
0x010D => "ccaron",
|
|
226
|
+
0x010E => "Dcaron",
|
|
227
|
+
0x010F => "dcaron",
|
|
228
|
+
0x0110 => "Dcroat",
|
|
229
|
+
0x0111 => "dcroat",
|
|
230
|
+
0x0112 => "Emacron",
|
|
231
|
+
0x0113 => "emacron",
|
|
232
|
+
0x0114 => "Ebreve",
|
|
233
|
+
0x0115 => "ebreve",
|
|
234
|
+
0x0116 => "Edotaccent",
|
|
235
|
+
0x0117 => "edotaccent",
|
|
236
|
+
0x0118 => "Eogonek",
|
|
237
|
+
0x0119 => "eogonek",
|
|
238
|
+
0x011A => "Ecaron",
|
|
239
|
+
0x011B => "ecaron",
|
|
240
|
+
0x011C => "Gcircumflex",
|
|
241
|
+
0x011D => "gcircumflex",
|
|
242
|
+
0x011E => "Gbreve",
|
|
243
|
+
0x011F => "gbreve",
|
|
244
|
+
0x0120 => "Gdotaccent",
|
|
245
|
+
0x0121 => "gdotaccent",
|
|
246
|
+
0x0122 => "Gcommaaccent",
|
|
247
|
+
0x0123 => "gcommaaccent",
|
|
248
|
+
0x0124 => "Hcircumflex",
|
|
249
|
+
0x0125 => "hcircumflex",
|
|
250
|
+
0x0126 => "Hbar",
|
|
251
|
+
0x0127 => "hbar",
|
|
252
|
+
0x0128 => "Itilde",
|
|
253
|
+
0x0129 => "itilde",
|
|
254
|
+
0x012A => "Imacron",
|
|
255
|
+
0x012B => "imacron",
|
|
256
|
+
0x012C => "Ibreve",
|
|
257
|
+
0x012D => "ibreve",
|
|
258
|
+
0x012E => "Iogonek",
|
|
259
|
+
0x012F => "iogonek",
|
|
260
|
+
0x0130 => "Idotaccent",
|
|
261
|
+
0x0131 => "dotlessi",
|
|
262
|
+
0x0132 => "Lig",
|
|
263
|
+
0x0133 => "lig",
|
|
264
|
+
0x0134 => "Lslash",
|
|
265
|
+
0x0135 => "lslash",
|
|
266
|
+
0x0136 => "Nacute",
|
|
267
|
+
0x0137 => "nacute",
|
|
268
|
+
0x0138 => "kgreenlandic",
|
|
269
|
+
0x0139 => "Ncommaaccent",
|
|
270
|
+
0x013A => "ncommaaccent",
|
|
271
|
+
0x013B => "Ncaron",
|
|
272
|
+
0x013C => "ncaron",
|
|
273
|
+
0x013D => "napostrophe",
|
|
274
|
+
0x013E => "Eng",
|
|
275
|
+
0x013F => "eng",
|
|
276
|
+
0x0140 => "Omacron",
|
|
277
|
+
0x0141 => "omacron",
|
|
278
|
+
0x0142 => "Obreve",
|
|
279
|
+
0x0143 => "obreve",
|
|
280
|
+
0x0144 => "Ohungarumlaut",
|
|
281
|
+
0x0145 => "ohungarumlaut",
|
|
282
|
+
0x0146 => "Oogonek",
|
|
283
|
+
0x0147 => "oogonek",
|
|
284
|
+
0x0148 => "Racute",
|
|
285
|
+
0x0149 => "racute",
|
|
286
|
+
0x014A => "Rcaron",
|
|
287
|
+
0x014B => "rcaron",
|
|
288
|
+
0x014C => "Sacute",
|
|
289
|
+
0x014D => "sacute",
|
|
290
|
+
0x014E => "Scircumflex",
|
|
291
|
+
0x014F => "scircumflex",
|
|
292
|
+
0x0150 => "Scedilla",
|
|
293
|
+
0x0151 => "scedilla",
|
|
294
|
+
0x0152 => "Scaron",
|
|
295
|
+
0x0153 => "scaron",
|
|
296
|
+
0x0154 => "Tcommaaccent",
|
|
297
|
+
0x0155 => "tcommaaccent",
|
|
298
|
+
0x0156 => "Tcaron",
|
|
299
|
+
0x0157 => "tcaron",
|
|
300
|
+
0x0158 => "Tbar",
|
|
301
|
+
0x0159 => "tbar",
|
|
302
|
+
0x015A => "Utilde",
|
|
303
|
+
0x015B => "utilde",
|
|
304
|
+
0x015C => "Umacron",
|
|
305
|
+
0x015D => "umacron",
|
|
306
|
+
0x015E => "Ubreve",
|
|
307
|
+
0x015F => "ubreve",
|
|
308
|
+
0x0160 => "Uring",
|
|
309
|
+
0x0161 => "uring",
|
|
310
|
+
0x0162 => "Uhungarumlaut",
|
|
311
|
+
0x0163 => "uhungarumlaut",
|
|
312
|
+
0x0164 => "Uogonek",
|
|
313
|
+
0x0165 => "uogonek",
|
|
314
|
+
0x0166 => "Wcircumflex",
|
|
315
|
+
0x0167 => "wcircumflex",
|
|
316
|
+
0x0168 => "Ycircumflex",
|
|
317
|
+
0x0169 => "ycircumflex",
|
|
318
|
+
0x016A => "Zacute",
|
|
319
|
+
0x016B => "zacute",
|
|
320
|
+
0x016C => "Zdotaccent",
|
|
321
|
+
0x016D => "zdotaccent",
|
|
322
|
+
0x016E => "Zcaron",
|
|
323
|
+
0x016F => "zcaron",
|
|
324
|
+
0x0170 => "longs",
|
|
325
|
+
0x0171 => "caron",
|
|
326
|
+
0x0172 => "breve",
|
|
327
|
+
0x0173 => "dotaccent",
|
|
328
|
+
0x0174 => "ring",
|
|
329
|
+
0x0175 => "ogonek",
|
|
330
|
+
0x0176 => "tilde",
|
|
331
|
+
0x0177 => "hungarumlaut",
|
|
332
|
+
0x0178 => "commaaccent",
|
|
333
|
+
0x0179 => "slash",
|
|
334
|
+
0x017A => "hyphen",
|
|
335
|
+
0x017B => "period",
|
|
336
|
+
0x017F => "florin",
|
|
337
|
+
# Greek (some common)
|
|
338
|
+
0x0391 => "Alpha",
|
|
339
|
+
0x0392 => "Beta",
|
|
340
|
+
0x0393 => "Gamma",
|
|
341
|
+
0x0394 => "Delta",
|
|
342
|
+
0x0395 => "Epsilon",
|
|
343
|
+
0x0396 => "Zeta",
|
|
344
|
+
0x0397 => "Eta",
|
|
345
|
+
0x0398 => "Theta",
|
|
346
|
+
0x0399 => "Iota",
|
|
347
|
+
0x039A => "Kappa",
|
|
348
|
+
0x039B => "Lambda",
|
|
349
|
+
0x039C => "Mu",
|
|
350
|
+
0x039D => "Nu",
|
|
351
|
+
0x039E => "Xi",
|
|
352
|
+
0x039F => "Omicron",
|
|
353
|
+
0x03A0 => "Pi",
|
|
354
|
+
0x03A1 => "Rho",
|
|
355
|
+
0x03A3 => "Sigma",
|
|
356
|
+
0x03A4 => "Tau",
|
|
357
|
+
0x03A5 => "Upsilon",
|
|
358
|
+
0x03A6 => "Phi",
|
|
359
|
+
0x03A7 => "Chi",
|
|
360
|
+
0x03A8 => "Psi",
|
|
361
|
+
0x03A9 => "Omega",
|
|
362
|
+
# Currency and other symbols
|
|
363
|
+
0x20AC => "Euro",
|
|
364
|
+
0x2113 => "literalsign",
|
|
365
|
+
0x2116 => "numerosign",
|
|
366
|
+
0x2122 => "trademark",
|
|
367
|
+
0x2126 => "Omega",
|
|
368
|
+
0x212E => "estimated",
|
|
369
|
+
0x2202 => "partialdiff",
|
|
370
|
+
0x2206 => "Delta",
|
|
371
|
+
0x220F => "product",
|
|
372
|
+
0x2211 => "summation",
|
|
373
|
+
0x221A => "radical",
|
|
374
|
+
0x221E => "infinity",
|
|
375
|
+
0x222B => "integral",
|
|
376
|
+
0x2248 => "approxequal",
|
|
377
|
+
0x2260 => "notequal",
|
|
378
|
+
0x2264 => "lessequal",
|
|
379
|
+
0x2265 => "greaterequal",
|
|
380
|
+
}.freeze
|
|
381
|
+
|
|
382
|
+
# Glyph name to Unicode mapping (inverse of UNICODE_TO_NAME)
|
|
383
|
+
# When duplicates exist, uses the first (lowest) codepoint
|
|
384
|
+
NAME_TO_UNICODE = begin
|
|
385
|
+
result = {}
|
|
386
|
+
UNICODE_TO_NAME.each do |codepoint, name|
|
|
387
|
+
result[name] ||= codepoint # Only set first occurrence
|
|
388
|
+
end
|
|
389
|
+
result.freeze
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Get glyph name for Unicode codepoint
|
|
393
|
+
#
|
|
394
|
+
# @param codepoint [Integer] Unicode codepoint
|
|
395
|
+
# @return [String] Glyph name from AGL, or uniXXXX format if not found
|
|
396
|
+
def self.glyph_name_for_unicode(codepoint)
|
|
397
|
+
UNICODE_TO_NAME[codepoint] || generate_uni_name(codepoint)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Get Unicode codepoint for glyph name
|
|
401
|
+
#
|
|
402
|
+
# @param name [String] Glyph name
|
|
403
|
+
# @return [Integer, nil] Unicode codepoint or nil if not found
|
|
404
|
+
def self.unicode_for_glyph_name(name)
|
|
405
|
+
# Try direct lookup
|
|
406
|
+
code = NAME_TO_UNICODE[name]
|
|
407
|
+
return code if code
|
|
408
|
+
|
|
409
|
+
# Try parsing uniXXXX or uXXXXX format
|
|
410
|
+
parse_uni_name(name)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Check if a glyph name is in the AGL
|
|
414
|
+
#
|
|
415
|
+
# @param name [String] Glyph name
|
|
416
|
+
# @return [Boolean] True if name is in AGL
|
|
417
|
+
def self.agl_include?(name)
|
|
418
|
+
NAME_TO_UNICODE.key?(name)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Generate uniXXXX name for codepoint not in AGL
|
|
422
|
+
#
|
|
423
|
+
# @param codepoint [Integer] Unicode codepoint
|
|
424
|
+
# @return [String] uniXXXX name
|
|
425
|
+
def self.generate_uni_name(codepoint)
|
|
426
|
+
format("uni%04X", codepoint)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Parse uniXXXX or uXXXXX glyph name
|
|
430
|
+
#
|
|
431
|
+
# @param name [String] Glyph name in uni/u format
|
|
432
|
+
# @return [Integer, nil] Unicode codepoint or nil if not a uni name
|
|
433
|
+
def self.parse_uni_name(name)
|
|
434
|
+
if name =~ /^uni([0-9A-Fa-f]{4})$/
|
|
435
|
+
$1.to_i(16)
|
|
436
|
+
elsif name =~ /^u([0-9A-Fa-f]+)$/
|
|
437
|
+
$1.to_i(16)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Get all AGL glyph names
|
|
442
|
+
#
|
|
443
|
+
# @return [Array<String>] All glyph names in the AGL subset
|
|
444
|
+
def self.all_glyph_names
|
|
445
|
+
NAME_TO_UNICODE.keys.sort
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Get all Unicode codepoints in AGL
|
|
449
|
+
#
|
|
450
|
+
# @return [Array<Integer>] All Unicode codepoints in the AGL subset
|
|
451
|
+
def self.all_codepoints
|
|
452
|
+
UNICODE_TO_NAME.keys.sort
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Type1
|
|
5
|
+
# Converter for Type 1 CharStrings to CFF CharStrings
|
|
6
|
+
#
|
|
7
|
+
# [`CharStringConverter`](lib/fontisan/type1/charstring_converter.rb) converts
|
|
8
|
+
# Type 1 CharString bytecode to CFF (Compact Font Format) CharString bytecode.
|
|
9
|
+
#
|
|
10
|
+
# Type 1 and CFF use similar stack-based languages but have different operator
|
|
11
|
+
# codes and some structural differences:
|
|
12
|
+
# - Operator codes differ between formats
|
|
13
|
+
# - Type 1 has seac operator for composites; CFF doesn't support it
|
|
14
|
+
# - Hint operators need to be preserved with code translation
|
|
15
|
+
#
|
|
16
|
+
# @example Convert a Type 1 CharString to CFF
|
|
17
|
+
# converter = Fontisan::Type1::CharStringConverter.new
|
|
18
|
+
# cff_charstring = converter.convert(type1_charstring)
|
|
19
|
+
#
|
|
20
|
+
# @see https://www.adobe.com/devnet/font/pdfs/Type1.pdf
|
|
21
|
+
# @see https://www.microsoft.com/typography/otspec/cff.htm
|
|
22
|
+
class CharStringConverter
|
|
23
|
+
# Type 1 to CFF operator mapping
|
|
24
|
+
#
|
|
25
|
+
# Maps Type 1 operator codes to CFF operator codes.
|
|
26
|
+
# Some operators have the same code, others differ.
|
|
27
|
+
TYPE1_TO_CFF = {
|
|
28
|
+
# Path construction operators
|
|
29
|
+
hmoveto: 22, # Type 1: 22, CFF: 22
|
|
30
|
+
vmoveto: 4, # Type 1: 4, CFF: 4
|
|
31
|
+
rlineto: 5, # Type 1: 5, CFF: 5
|
|
32
|
+
hlineto: 6, # Type 1: 6, CFF: 6
|
|
33
|
+
vlineto: 7, # Type 1: 7, CFF: 7
|
|
34
|
+
rrcurveto: 8, # Type 1: 8, CFF: 8
|
|
35
|
+
hhcurveto: 27, # Type 1: 27, CFF: 27
|
|
36
|
+
hvcurveto: 31, # Type 1: 31, CFF: 31
|
|
37
|
+
vhcurveto: 30, # Type 1: 30, CFF: 30
|
|
38
|
+
rcurveline: 24, # Type 1: 24, CFF: 24
|
|
39
|
+
rlinecurve: 25, # Type 1: 25, CFF: 25
|
|
40
|
+
|
|
41
|
+
# Hint operators
|
|
42
|
+
hstem: 1, # Type 1: 1, CFF: 1
|
|
43
|
+
vstem: 3, # Type 1: 3, CFF: 3
|
|
44
|
+
hstemhm: 18, # Type 1: 18, CFF: 18
|
|
45
|
+
vstemhm: 23, # Type 1: 23, CFF: 23
|
|
46
|
+
|
|
47
|
+
# Hint substitution (not in Type 1, but we preserve for compatibility)
|
|
48
|
+
hintmask: 19, # Type 1: N/A, CFF: 19
|
|
49
|
+
cntrmask: 20, # Type 1: N/A, CFF: 20
|
|
50
|
+
|
|
51
|
+
# End char
|
|
52
|
+
endchar: 14, # Type 1: 14 (or 11 in some specs), CFF: 14
|
|
53
|
+
|
|
54
|
+
# Miscellaneous
|
|
55
|
+
callsubr: 10, # Type 1: 10, CFF: 10
|
|
56
|
+
return: 11, # Type 1: 11, CFF: 11
|
|
57
|
+
|
|
58
|
+
# Deprecated operators (preserve for compatibility)
|
|
59
|
+
hstem3: 12, # Type 1: 12 (escape 0), CFF: 12 (escape 0)
|
|
60
|
+
vstem3: 13, # Type 1: 13 (escape 1), CFF: 13 (escape 1)
|
|
61
|
+
seac: 12, # Type 1: 12 (escape 6), CFF: Not supported
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# Escape code for two-byte operators
|
|
65
|
+
ESCAPE_BYTE = 12
|
|
66
|
+
|
|
67
|
+
# seac operator escape code (second byte)
|
|
68
|
+
SEAC_ESCAPE_CODE = 6
|
|
69
|
+
|
|
70
|
+
# Initialize a new CharStringConverter
|
|
71
|
+
#
|
|
72
|
+
# @param charstrings [CharStrings, nil] CharStrings dictionary for seac expansion
|
|
73
|
+
def initialize(charstrings = nil)
|
|
74
|
+
@charstrings = charstrings
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Convert Type 1 CharString to CFF CharString
|
|
78
|
+
#
|
|
79
|
+
# @param type1_charstring [String] Type 1 CharString bytecode
|
|
80
|
+
# @return [String] CFF CharString bytecode
|
|
81
|
+
#
|
|
82
|
+
# @example Convert a CharString
|
|
83
|
+
# converter = Fontisan::Type1::CharStringConverter.new
|
|
84
|
+
# cff_bytes = converter.convert(type1_bytes)
|
|
85
|
+
def convert(type1_charstring)
|
|
86
|
+
# Parse Type 1 CharString into commands
|
|
87
|
+
parser = Type1::CharStrings::CharStringParser.new
|
|
88
|
+
commands = parser.parse(type1_charstring)
|
|
89
|
+
|
|
90
|
+
# Check for seac operator and expand if needed
|
|
91
|
+
if parser.seac_components
|
|
92
|
+
return expand_seac(parser.seac_components)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Convert commands to CFF format
|
|
96
|
+
convert_commands(commands)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Convert parsed commands to CFF CharString
|
|
100
|
+
#
|
|
101
|
+
# @param commands [Array<Array>] Parsed Type 1 commands
|
|
102
|
+
# @return [String] CFF CharString bytecode
|
|
103
|
+
def convert_commands(commands)
|
|
104
|
+
result = String.new(encoding: Encoding::ASCII_8BIT)
|
|
105
|
+
|
|
106
|
+
commands.each do |command|
|
|
107
|
+
case command[0]
|
|
108
|
+
when :number
|
|
109
|
+
# Encode number in CFF format
|
|
110
|
+
result << encode_cff_number(command[1])
|
|
111
|
+
when :seac
|
|
112
|
+
# seac should be expanded before this point
|
|
113
|
+
raise Fontisan::Error,
|
|
114
|
+
"seac operator not supported in CFF, must be expanded first"
|
|
115
|
+
else
|
|
116
|
+
# Convert operator
|
|
117
|
+
op_code = TYPE1_TO_CFF[command[0]]
|
|
118
|
+
if op_code.nil?
|
|
119
|
+
# Unknown operator, skip or raise error
|
|
120
|
+
next
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
result << encode_cff_operator(op_code)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Expand seac composite glyph
|
|
131
|
+
#
|
|
132
|
+
# The seac operator in Type 1 creates composite glyphs (like À = A + `).
|
|
133
|
+
# CFF doesn't support seac, so we need to expand it into the base glyphs
|
|
134
|
+
# with appropriate positioning.
|
|
135
|
+
#
|
|
136
|
+
# @param seac_data [Hash] seac component data
|
|
137
|
+
# @return [String] CFF CharString bytecode with expanded seac
|
|
138
|
+
def expand_seac(seac_data)
|
|
139
|
+
# seac format: adx ady bchar achar seac
|
|
140
|
+
# adx, ady: accent offset
|
|
141
|
+
# bchar: base character code
|
|
142
|
+
# achar: accent character code
|
|
143
|
+
# The accent is positioned at (adx, ady) relative to the base
|
|
144
|
+
|
|
145
|
+
seac_data[:base]
|
|
146
|
+
seac_data[:accent]
|
|
147
|
+
seac_data[:adx]
|
|
148
|
+
seac_data[:ady]
|
|
149
|
+
|
|
150
|
+
# For now, we'll create a simple placeholder that indicates seac expansion
|
|
151
|
+
# In a full implementation, we would:
|
|
152
|
+
# 1. Parse the base glyph's CharString
|
|
153
|
+
# 2. Parse the accent glyph's CharString
|
|
154
|
+
# 3. Merge them with the appropriate offset
|
|
155
|
+
# 4. Convert to CFF format
|
|
156
|
+
|
|
157
|
+
# This is a simplified implementation that creates a composite reference
|
|
158
|
+
# CFF doesn't have native seac, so we need to actually merge the outlines
|
|
159
|
+
|
|
160
|
+
# For now, return endchar as placeholder
|
|
161
|
+
# TODO: Implement full seac expansion by merging glyph outlines
|
|
162
|
+
encode_cff_operator(TYPE1_TO_CFF[:endchar])
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check if CharString contains seac operator
|
|
166
|
+
#
|
|
167
|
+
# @param type1_charstring [String] Type 1 CharString bytecode
|
|
168
|
+
# @return [Boolean] True if CharString contains seac
|
|
169
|
+
def seac?(type1_charstring)
|
|
170
|
+
parser = Type1::CharStrings::CharStringParser.new
|
|
171
|
+
parser.parse(type1_charstring)
|
|
172
|
+
!parser.seac_components.nil?
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Encode number in CFF format
|
|
178
|
+
#
|
|
179
|
+
# CFF uses a variable-length encoding for numbers:
|
|
180
|
+
# - 32-246: 1 byte (value - 139)
|
|
181
|
+
# - 247-250: 2 bytes (first byte indicates format)
|
|
182
|
+
# - 251-254: 3 bytes (first byte indicates format)
|
|
183
|
+
# - 255: 5 bytes (signed 16-bit integer)
|
|
184
|
+
# - 28: 2 bytes (signed 16.16 fixed point, not used for CharStrings)
|
|
185
|
+
#
|
|
186
|
+
# @param value [Integer] Number to encode
|
|
187
|
+
# @return [String] Encoded number bytes
|
|
188
|
+
def encode_cff_number(value)
|
|
189
|
+
result = String.new(encoding: Encoding::ASCII_8BIT)
|
|
190
|
+
|
|
191
|
+
if value >= -107 && value <= 107
|
|
192
|
+
# 1-byte number: value + 139
|
|
193
|
+
result << (value + 139).chr
|
|
194
|
+
elsif value >= 108 && value <= 1131
|
|
195
|
+
# 2-byte positive number
|
|
196
|
+
value -= 108
|
|
197
|
+
result << ((value >> 8) + 247).chr
|
|
198
|
+
result << (value & 0xFF).chr
|
|
199
|
+
elsif value >= -1131 && value <= -108
|
|
200
|
+
# 2-byte negative number
|
|
201
|
+
value = -value - 108
|
|
202
|
+
result << ((value >> 8) + 251).chr
|
|
203
|
+
result << (value & 0xFF).chr
|
|
204
|
+
elsif value >= -32768 && value <= 32767
|
|
205
|
+
# 5-byte number (16-bit integer)
|
|
206
|
+
result << 255.chr
|
|
207
|
+
result << [(value >> 8) & 0xFF, value & 0xFF].pack("CC")
|
|
208
|
+
result << [0, 0].pack("CC") # Pad to 5 bytes
|
|
209
|
+
else
|
|
210
|
+
raise Fontisan::Error,
|
|
211
|
+
"Number out of range for CFF encoding: #{value}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
result
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Encode operator in CFF format
|
|
218
|
+
#
|
|
219
|
+
# Most operators are single-byte. Some use escape byte (12) followed
|
|
220
|
+
# by a second byte.
|
|
221
|
+
#
|
|
222
|
+
# @param op_code [Integer] Operator code
|
|
223
|
+
# @return [String] Encoded operator bytes
|
|
224
|
+
def encode_cff_operator(op_code)
|
|
225
|
+
result = String.new(encoding: Encoding::ASCII_8BIT)
|
|
226
|
+
|
|
227
|
+
if op_code > 31 && op_code != ESCAPE_BYTE
|
|
228
|
+
# Two-byte operator (escape + code)
|
|
229
|
+
result << ESCAPE_BYTE.chr
|
|
230
|
+
result << (op_code - ESCAPE_BYTE).chr
|
|
231
|
+
else
|
|
232
|
+
# Single-byte operator
|
|
233
|
+
result << op_code.chr
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
result
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|