1if not modules then modules = { } end modules ['font-onr'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to font-ini.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files"
8}
9
10
22
23local fonts, logs, trackers, resolvers = fonts, logs, trackers, resolvers
24
25local next, type, tonumber, rawset = next, type, tonumber, rawset
26local match, lower, gsub, strip, find = string.match, string.lower, string.gsub, string.strip, string.find
27local char, byte, sub = string.char, string.byte, string.sub
28local abs = math.abs
29local bxor, rshift = bit32.bxor, bit32.rshift
30local P, S, R, V, Cmt, C, Ct, Cs, Carg, Cf, Cg, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.V, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg, lpeg.Cf, lpeg.Cg, lpeg.Cc
31local lpegmatch, patterns = lpeg.match, lpeg.patterns
32
33local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end)
34local trace_loading = false trackers.register("afm.loading", function(v) trace_loading = v end)
35
36local report_afm = logs.reporter("fonts","afm loading")
37local report_pfb = logs.reporter("fonts","pfb loading")
38
39local handlers = fonts.handlers
40local afm = handlers.afm or { }
41handlers.afm = afm
42local readers = afm.readers or { }
43afm.readers = readers
44
45afm.version = 1.513
46
47
53
54local get_indexes, get_shapes
55
56do
57
58 local decrypt
59
60 do
61
62 local r, c1, c2, n = 0, 0, 0, 0
63
64 local function step(c)
65 local cipher = byte(c)
66 local plain = bxor(cipher,rshift(r,8))
67 r = ((cipher + r) * c1 + c2) % 65536
68 return char(plain)
69 end
70
71 decrypt = function(binary,initial,seed)
72 r, c1, c2, n = initial, 52845, 22719, seed
73 binary = gsub(binary,".",step)
74 return sub(binary,n+1)
75 end
76
77
78
79
80
81
82
83
84
85 end
86
87 local charstrings = P("/CharStrings")
88 local subroutines = P("/Subrs")
89 local encoding = P("/Encoding")
90 local dup = P("dup")
91 local put = P("put")
92 local array = P("array")
93 local name = P("/") * C((R("az","AZ","09")+S("-_."))^1)
94 local digits = R("09")^1
95 local cardinal = digits / tonumber
96 local spaces = P(" ")^1
97 local spacing = patterns.whitespace^0
98
99 local routines, vector, chars, n, m
100
101 local initialize = function(str,position,size)
102 n = 0
103 m = size
104 return position + 1
105 end
106
107 local setroutine = function(str,position,index,size,filename)
108 if routines[index] then
109
110 return false
111 end
112 local forward = position + size
113 local stream = decrypt(sub(str,position+1,forward),4330,4)
114 routines[index] = { byte(stream,1,#stream) }
115 n = n + 1
116 if n >= m then
117
118 return #str
119 end
120 return forward + 1
121 end
122
123 local setvector = function(str,position,name,size,filename)
124 local forward = position + tonumber(size)
125 if n >= m then
126 return #str
127 elseif forward < #str then
128 if n == 0 and name ~= ".notdef" then
129 report_pfb("reserving .notdef at index 0 in %a",filename)
130 n = n + 1
131 end
132 vector[n] = name
133 n = n + 1
134 return forward
135 else
136 return #str
137 end
138 end
139
140 local setshapes = function(str,position,name,size,filename)
141 local forward = position + tonumber(size)
142 local stream = sub(str,position+1,forward)
143 if n > m then
144 return #str
145 elseif forward < #str then
146 if n == 0 and name ~= ".notdef" then
147 report_pfb("reserving .notdef at index 0 in %a",filename)
148 n = n + 1
149 end
150 vector[n] = name
151 n = n + 1
152 chars [n] = decrypt(stream,4330,4)
153 return forward
154 else
155 return #str
156 end
157 end
158
159 local p_rd = spacing * (P("RD") + P("-|"))
160 local p_np = spacing * (P("NP") + P( "|"))
161 local p_nd = spacing * (P("ND") + P( "|"))
162
163 local p_filterroutines =
164 (1-subroutines)^0 * subroutines * spaces * Cmt(cardinal,initialize)
165 * (Cmt(cardinal * spaces * cardinal * p_rd * Carg(1), setroutine) * p_np + (1-p_nd))^1
166
167 local p_filtershapes =
168 (1-charstrings)^0 * charstrings * spaces * Cmt(cardinal,initialize)
169 * (Cmt(name * spaces * cardinal * p_rd * Carg(1) , setshapes) * p_nd + P(1))^1
170
171 local p_filternames = Ct (
172 (1-charstrings)^0 * charstrings * spaces * Cmt(cardinal,initialize)
173 * (Cmt(name * spaces * cardinal * Carg(1), setvector) + P(1))^1
174 )
175
176
177
178
179
180 local p_filterencoding =
181 (1-encoding)^0 * encoding * spaces * digits * spaces * array * (1-dup)^0
182 * Cf(
183 Ct("") * Cg(spacing * dup * spaces * cardinal * spaces * name * spaces * put)^1
184 ,rawset)
185
186
187
188 local key = spacing * P("/") * R("az","AZ")
189 local str = spacing * Cs { (P("(")/"") * ((1 - P("\\(") - P("\\)") - S("()")) + V(1))^0 * (P(")")/"") }
190 local num = spacing * (R("09") + S("+-."))^1 / tonumber
191 local arr = spacing * Ct (S("[{") * (num)^0 * spacing * S("]}"))
192 local boo = spacing * (P("true") * Cc(true) + P("false") * Cc(false))
193 local nam = spacing * P("/") * Cs(R("az","AZ")^1)
194
195 local p_filtermetadata = (
196 P("/") * Carg(1) * ( (
197 C("version") * str
198 + C("Copyright") * str
199 + C("Notice") * str
200 + C("FullName") * str
201 + C("FamilyName") * str
202 + C("Weight") * str
203 + C("ItalicAngle") * num
204 + C("isFixedPitch") * boo
205 + C("UnderlinePosition") * num
206 + C("UnderlineThickness") * num
207 + C("FontName") * nam
208 + C("FontMatrix") * arr
209 + C("FontBBox") * arr
210 ) ) / function(t,k,v) t[lower(k)] = v end
211 + P(1)
212 )^0 * Carg(1)
213
214 local function loadpfbvector(filename,shapestoo,streams)
215
216
217 local data = io.loaddata(resolvers.findfile(filename))
218
219 if not data then
220 report_pfb("no data in %a",filename)
221 return
222 end
223
224 if not (find(data,"!PS-AdobeFont-",1,true) or find(data,"%!FontType1",1,true)) then
225 report_pfb("no font in %a",filename)
226 return
227 end
228
229 local ascii, binary = match(data,"(.*)eexec%s+......(.*)")
230
231 if not binary then
232 report_pfb("no binary data in %a",filename)
233 return
234 end
235
236 binary = decrypt(binary,55665,4)
237
238 local names = { }
239 local encoding = lpegmatch(p_filterencoding,ascii)
240 local metadata = lpegmatch(p_filtermetadata,ascii,1,{})
241 local glyphs = { }
242
243 routines, vector, chars = { }, { }, { }
244 if shapestoo or streams then
245
246 lpegmatch(p_filterroutines,binary,1,filename)
247 lpegmatch(p_filtershapes,binary,1,filename)
248 local data = {
249 dictionaries = {
250 {
251 charstrings = chars,
252 charset = vector,
253 subroutines = routines,
254 }
255 },
256 }
257
258 fonts.handlers.otf.readers.parsecharstrings(false,data,glyphs,true,"cff",streams,true)
259 else
260 lpegmatch(p_filternames,binary,1,filename)
261 end
262
263 names = vector
264
265 routines, vector, chars = nil, nil, nil
266
267 return names, encoding, glyphs, metadata
268
269 end
270
271 local pfb = handlers.pfb or { }
272 handlers.pfb = pfb
273 pfb.loadvector = loadpfbvector
274
275 get_indexes = function(data,pfbname)
276 local vector = loadpfbvector(pfbname)
277 if vector then
278 local characters = data.characters
279 if trace_loading then
280 report_afm("getting index data from %a",pfbname)
281 end
282 for index=0,#vector do
283 local name = vector[index]
284 local char = characters[name]
285 if char then
286 if trace_indexing then
287 report_afm("glyph %a has index %a",name,index)
288 end
289 char.index = index
290 else
291 if trace_indexing then
292 report_afm("glyph %a has index %a but no data",name,index)
293 end
294 end
295 end
296 end
297 end
298
299 get_shapes = function(pfbname)
300 local vector, encoding, glyphs = loadpfbvector(pfbname,true)
301 return glyphs
302 end
303
304end
305
306
311
312local spacer = patterns.spacer
313local whitespace = patterns.whitespace
314local lineend = patterns.newline
315local spacing = spacer^0
316local number = spacing * S("+-")^-1 * (R("09") + S("."))^1 / tonumber
317local name = spacing * C((1 - whitespace)^1)
318local words = spacing * ((1 - lineend)^1 / strip)
319local rest = (1 - lineend)^0
320local fontdata = Carg(1)
321local semicolon = spacing * P(";")
322local plus = spacing * P("plus") * number
323local minus = spacing * P("minus") * number
324
325
326
327local function addkernpair(data,one,two,value)
328 local chr = data.characters[one]
329 if chr then
330 local kerns = chr.kerns
331 if kerns then
332 kerns[two] = tonumber(value)
333 else
334 chr.kerns = { [two] = tonumber(value) }
335 end
336 end
337end
338
339local p_kernpair = (fontdata * P("KPX") * name * name * number) / addkernpair
340
341
342
343local chr = false
344local ind = 0
345
346local function start(data,version)
347 data.metadata.afmversion = version
348 ind = 0
349 chr = { }
350end
351
352local function stop()
353 ind = 0
354 chr = false
355end
356
357local function setindex(i)
358 if i < 0 then
359 ind = ind + 1
360 else
361 ind = i
362 end
363 chr = {
364 index = ind
365 }
366end
367
368local function setwidth(width)
369 chr.width = width
370end
371
372local function setname(data,name)
373 data.characters[name] = chr
374end
375
376local function setboundingbox(boundingbox)
377 chr.boundingbox = boundingbox
378end
379
380local function setligature(plus,becomes)
381 local ligatures = chr.ligatures
382 if ligatures then
383 ligatures[plus] = becomes
384 else
385 chr.ligatures = { [plus] = becomes }
386 end
387end
388
389local p_charmetric = ( (
390 P("C") * number / setindex
391 + P("WX") * number / setwidth
392 + P("N") * fontdata * name / setname
393 + P("B") * Ct((number)^4) / setboundingbox
394 + P("L") * (name)^2 / setligature
395 ) * semicolon )^1
396
397local p_charmetrics = P("StartCharMetrics") * number * (p_charmetric + (1-P("EndCharMetrics")))^0 * P("EndCharMetrics")
398local p_kernpairs = P("StartKernPairs") * number * (p_kernpair + (1-P("EndKernPairs" )))^0 * P("EndKernPairs" )
399
400local function set_1(data,key,a) data.metadata[lower(key)] = a end
401local function set_2(data,key,a,b) data.metadata[lower(key)] = { a, b } end
402local function set_3(data,key,a,b,c) data.metadata[lower(key)] = { a, b, c } end
403
404
405
406
407
408
409
410
411
412
413
414local p_parameters = P(false)
415 + fontdata
416 * ((P("FontName") + P("FullName") + P("FamilyName"))/lower)
417 * words / function(data,key,value)
418 data.metadata[key] = value
419 end
420 + fontdata
421 * ((P("Weight") + P("Version"))/lower)
422 * name / function(data,key,value)
423 data.metadata[key] = value
424 end
425 + fontdata
426 * P("IsFixedPitch")
427 * name / function(data,pitch)
428 data.metadata.monospaced = toboolean(pitch,true)
429 end
430 + fontdata
431 * P("FontBBox")
432 * Ct(number^4) / function(data,boundingbox)
433 data.metadata.boundingbox = boundingbox
434 end
435 + fontdata
436 * ((P("CharWidth") + P("CapHeight") + P("XHeight") + P("Descender") + P("Ascender") + P("ItalicAngle"))/lower)
437 * number / function(data,key,value)
438 data.metadata[key] = value
439 end
440 + P("Comment") * spacing * ( P(false)
441 + (fontdata * C("DESIGNSIZE") * number * rest) / set_1
442 + (fontdata * C("TFM designsize") * number * rest) / set_1
443 + (fontdata * C("DesignSize") * number * rest) / set_1
444 + (fontdata * C("CODINGSCHEME") * words * rest) / set_1
445 + (fontdata * C("CHECKSUM") * number * words * rest) / set_1
446 + (fontdata * C("SPACE") * number * plus * minus * rest) / set_3
447 + (fontdata * C("QUAD") * number * rest) / set_1
448 + (fontdata * C("EXTRASPACE") * number * rest) / set_1
449 + (fontdata * C("NUM") * number * number * number * rest) / set_3
450 + (fontdata * C("DENOM") * number * number * rest) / set_2
451 + (fontdata * C("SUP") * number * number * number * rest) / set_3
452 + (fontdata * C("SUB") * number * number * rest) / set_2
453 + (fontdata * C("SUPDROP") * number * rest) / set_1
454 + (fontdata * C("SUBDROP") * number * rest) / set_1
455 + (fontdata * C("DELIM") * number * number * rest) / set_2
456 + (fontdata * C("AXISHEIGHT") * number * rest) / set_1
457 )
458
459local fullparser = ( P("StartFontMetrics") * fontdata * name / start )
460 * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0
461 * ( P("EndFontMetrics") / stop )
462
463local infoparser = ( P("StartFontMetrics") * fontdata * name / start )
464 * ( p_parameters + (1-P("EndFontMetrics")) )^0
465 * ( P("EndFontMetrics") / stop )
466
467
468
469
470
471local function read(filename,parser)
472 local afmblob = io.loaddata(filename)
473 if afmblob then
474 local data = {
475 resources = {
476 filename = resolvers.unresolve(filename),
477 version = afm.version,
478 creator = "context mkiv",
479 },
480 properties = {
481 hasitalics = false,
482 },
483 goodies = {
484 },
485 metadata = {
486 filename = file.removesuffix(file.basename(filename))
487 },
488 characters = {
489
490 },
491 descriptions = {
492
493 },
494 }
495 if trace_loading then
496 report_afm("parsing afm file %a",filename)
497 end
498 lpegmatch(parser,afmblob,1,data)
499 return data
500 else
501 if trace_loading then
502 report_afm("no valid afm file %a",filename)
503 end
504 return nil
505 end
506end
507
508function readers.loadfont(afmname,pfbname)
509 local data = read(resolvers.findfile(afmname),fullparser)
510 if data then
511 if not pfbname or pfbname == "" then
512 pfbname = resolvers.findfile(file.replacesuffix(file.nameonly(afmname),"pfb"))
513 end
514 if pfbname and pfbname ~= "" then
515 data.resources.filename = resolvers.unresolve(pfbname)
516 get_indexes(data,pfbname)
517 return data
518 else
519 report_afm("no pfb file for %a",afmname)
520
521
522 end
523 end
524end
525
526
527
528function readers.loadshapes(filename)
529 local fullname = resolvers.findfile(filename) or ""
530 if fullname == "" then
531 return {
532 filename = "not found: " .. filename,
533 glyphs = { }
534 }
535 else
536 return {
537 filename = fullname,
538 format = "opentype",
539 glyphs = get_shapes(fullname) or { },
540 units = 1000,
541 }
542 end
543end
544
545
546function readers.getinfo(filename)
547 local data = read(resolvers.findfile(filename),infoparser)
548 if data then
549 return data.metadata
550 end
551end
552 |