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