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