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.514
43
44local pfb = handlers.pfb or { }
45handlers.pfb = pfb
46pfb.version = 1.002
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,"cff",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 pfb.loadvector = loadpfbvector
336
337 get_indexes = function(data,pfbname)
338 local vector = loadpfbvector(pfbname)
339 if vector then
340 local characters = data.characters
341 if trace_loading then
342 report_afm("getting index data from %a",pfbname)
343 end
344 for index=0,#vector do
345 local name = vector[index]
346 local char = characters[name]
347 if char then
348 if trace_indexing then
349 report_afm("glyph %a has index %a",name,index)
350 end
351 char.index = index
352 else
353 if trace_indexing then
354 report_afm("glyph %a has index %a but no data",name,index)
355 end
356 end
357 end
358 end
359 end
360
361 get_shapes = function(pfbname)
362 local vector, encoding, glyphs = loadpfbvector(pfbname,true)
363 return glyphs
364 end
365
366end
367
368
369
370
371
372
373local spacer = patterns.spacer
374local whitespace = patterns.whitespace
375local lineend = patterns.newline
376local spacing = spacer^0
377local number = spacing * S("+-")^-1 * (R("09") + S("."))^1 / tonumber
378local name = spacing * C((1 - whitespace)^1)
379local words = spacing * ((1 - lineend)^1 / strip)
380local rest = (1 - lineend)^0
381local fontdata = Carg(1)
382local semicolon = spacing * P(";")
383local plus = spacing * P("plus") * number
384local minus = spacing * P("minus") * number
385
386
387
388local function addkernpair(data,one,two,value)
389 local chr = data.characters[one]
390 if chr then
391 local kerns = chr.kerns
392 if kerns then
393 kerns[two] = tonumber(value)
394 else
395 chr.kerns = { [two] = tonumber(value) }
396 end
397 end
398end
399
400local p_kernpair = (fontdata * P("KPX") * name * name * number) / addkernpair
401
402
403
404local chr = false
405local ind = 0
406
407local function start(data,version)
408 data.metadata.afmversion = version
409 ind = 0
410 chr = { }
411end
412
413local function stop()
414 ind = 0
415 chr = false
416end
417
418local function setindex(i)
419 if i < 0 then
420 ind = ind + 1
421 else
422 ind = i
423 end
424 chr = {
425 index = ind
426 }
427end
428
429local function setwidth(width)
430 chr.width = width
431end
432
433local function setname(data,name)
434 data.characters[name] = chr
435end
436
437local function setboundingbox(boundingbox)
438 chr.boundingbox = boundingbox
439end
440
441local function setligature(plus,becomes)
442 local ligatures = chr.ligatures
443 if ligatures then
444 ligatures[plus] = becomes
445 else
446 chr.ligatures = { [plus] = becomes }
447 end
448end
449
450local p_charmetric = ( (
451 P("C") * number / setindex
452 + P("WX") * number / setwidth
453 + P("N") * fontdata * name / setname
454 + P("B") * Ct((number)^4) / setboundingbox
455 + P("L") * (name)^2 / setligature
456 ) * semicolon )^1
457
458local p_charmetrics = P("StartCharMetrics") * number * (p_charmetric + (1-P("EndCharMetrics")))^0 * P("EndCharMetrics")
459local p_kernpairs = P("StartKernPairs") * number * (p_kernpair + (1-P("EndKernPairs" )))^0 * P("EndKernPairs" )
460
461local function set_1(data,key,a) data.metadata[lower(key)] = a end
462local function set_2(data,key,a,b) data.metadata[lower(key)] = { a, b } end
463local function set_3(data,key,a,b,c) data.metadata[lower(key)] = { a, b, c } end
464
465
466
467
468
469
470
471
472
473
474
475local p_parameters = P(false)
476 + fontdata
477 * ((P("FontName") + P("FullName") + P("FamilyName"))/lower)
478 * words / function(data,key,value)
479 data.metadata[key] = value
480 end
481 + fontdata
482 * ((P("Weight") + P("Version"))/lower)
483 * name / function(data,key,value)
484 data.metadata[key] = value
485 end
486 + fontdata
487 * P("IsFixedPitch")
488 * name / function(data,pitch)
489 data.metadata.monospaced = toboolean(pitch,true)
490 end
491 + fontdata
492 * P("FontBBox")
493 * Ct(number^4) / function(data,boundingbox)
494 data.metadata.boundingbox = boundingbox
495 end
496 + fontdata
497 * ((P("CharWidth") + P("CapHeight") + P("XHeight") + P("Descender") + P("Ascender") + P("ItalicAngle"))/lower)
498 * number / function(data,key,value)
499 data.metadata[key] = value
500 end
501 + P("Comment") * spacing * ( P(false)
502 + (fontdata * C("DESIGNSIZE") * number * rest) / set_1
503 + (fontdata * C("TFM designsize") * number * rest) / set_1
504 + (fontdata * C("DesignSize") * number * rest) / set_1
505 + (fontdata * C("CODINGSCHEME") * words * rest) / set_1
506 + (fontdata * C("CHECKSUM") * number * words * rest) / set_1
507 + (fontdata * C("SPACE") * number * plus * minus * rest) / set_3
508 + (fontdata * C("QUAD") * number * rest) / set_1
509 + (fontdata * C("EXTRASPACE") * number * rest) / set_1
510 + (fontdata * C("NUM") * number * number * number * rest) / set_3
511 + (fontdata * C("DENOM") * number * number * rest) / set_2
512 + (fontdata * C("SUP") * number * number * number * rest) / set_3
513 + (fontdata * C("SUB") * number * number * rest) / set_2
514 + (fontdata * C("SUPDROP") * number * rest) / set_1
515 + (fontdata * C("SUBDROP") * number * rest) / set_1
516 + (fontdata * C("DELIM") * number * number * rest) / set_2
517 + (fontdata * C("AXISHEIGHT") * number * rest) / set_1
518 )
519
520local fullparser = ( P("StartFontMetrics") * fontdata * name / start )
521 * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0
522 * ( P("EndFontMetrics") / stop )
523
524local infoparser = ( P("StartFontMetrics") * fontdata * name / start )
525 * ( p_parameters + (1-P("EndFontMetrics")) )^0
526 * ( P("EndFontMetrics") / stop )
527
528
529
530
531
532local function read(filename,parser)
533 local afmblob = io.loaddata(filename)
534 if afmblob then
535 local data = {
536 creator = "context mkiv",
537 resources = {
538 filename = resolvers.unresolve(filename),
539 version = afm.version,
540 },
541 properties = {
542 hasitalics = false,
543 },
544 goodies = {
545 },
546 metadata = {
547 filename = file.removesuffix(file.basename(filename))
548 },
549 characters = {
550
551 },
552 descriptions = {
553
554 },
555 }
556 if trace_loading then
557 report_afm("parsing afm file %a",filename)
558 end
559 lpegmatch(parser,afmblob,1,data)
560 return data
561 else
562 if trace_loading then
563 report_afm("no valid afm file %a",filename)
564 end
565 return nil
566 end
567end
568
569function readers.loadfont(afmname,pfbname)
570 local data = read(resolvers.findfile(afmname),fullparser)
571 if data then
572 if not pfbname or pfbname == "" then
573 pfbname = resolvers.findfile(file.replacesuffix(file.nameonly(afmname),"pfb"))
574 end
575 if pfbname and pfbname ~= "" then
576 data.resources.filename = resolvers.unresolve(pfbname)
577 get_indexes(data,pfbname)
578 return data
579 else
580 report_afm("no pfb file for %a",afmname)
581
582
583 end
584 end
585end
586
587
588
589function readers.loadshapes(filename)
590 local fullname = resolvers.findfile(filename) or ""
591 if fullname == "" then
592 return {
593 filename = "not found: " .. filename,
594 glyphs = { }
595 }
596 else
597 return {
598 filename = fullname,
599 format = "opentype",
600 glyphs = get_shapes(fullname) or { },
601 units = 1000,
602 }
603 end
604end
605
606
607function readers.getinfo(filename)
608 local data = read(resolvers.findfile(filename),infoparser)
609 if data then
610 return data.metadata
611 end
612end
613 |