1if not modules then modules = { } end modules ['font-one'] = {
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, containers, resolvers = fonts, logs, trackers, containers, resolvers
22
23local next, type, tonumber, rawget = next, type, tonumber, rawget
24local match, gsub = string.match, string.gsub
25local abs = math.abs
26local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg
27local lpegmatch, patterns = lpeg.match, lpeg.patterns
28local sortedhash = table.sortedhash
29
30local trace_features = false trackers.register("afm.features", function(v) trace_features = v end)
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)
33local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
34
35local report_afm = logs.reporter("fonts","afm loading")
36
37local setmetatableindex = table.setmetatableindex
38local derivetable = table.derive
39
40local findbinfile = resolvers.findbinfile
41
42local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000
43
44local definers = fonts.definers
45local readers = fonts.readers
46local constructors = fonts.constructors
47
48local afm = constructors.handlers.afm
49local pfb = constructors.handlers.pfb
50local otf = fonts.handlers.otf
51
52local otfreaders = otf.readers
53local otfenhancers = otf.enhancers
54
55local afmfeatures = constructors.features.afm
56local registerafmfeature = afmfeatures.register
57
58local afmenhancers = constructors.enhancers.afm
59local registerafmenhancer = afmenhancers.register
60
61afm.version = 1.513
62afm.cache = containers.define("fonts", "one", afm.version, true)
63afm.autoprefixed = true
64
65afm.helpdata = { }
66afm.syncspace = true
67
68local overloads = fonts.mappings.overloads
69
70local applyruntimefixes = fonts.treatments and fonts.treatments.applyfixes
71
72
73
74
75
76
77
78
79
80function afm.load(filename)
81 filename = resolvers.findfile(filename,'afm') or ""
82 if filename ~= "" and not fonts.names.ignoredfile(filename) then
83 local name = file.removesuffix(file.basename(filename))
84 local data = containers.read(afm.cache,name)
85 local attr = lfs.attributes(filename)
86 local size = attr and attr.size or 0
87 local time = attr and attr.modification or 0
88
89 local pfbfile = file.replacesuffix(name,"pfb")
90 local pfbname = resolvers.findfile(pfbfile,"pfb") or ""
91 if pfbname == "" then
92 pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or ""
93 end
94 local pfbsize = 0
95 local pfbtime = 0
96 if pfbname ~= "" then
97 local attr = lfs.attributes(pfbname)
98 pfbsize = attr.size or 0
99 pfbtime = attr.modification or 0
100 end
101 if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then
102 report_afm("reading %a",filename)
103 data = afm.readers.loadfont(filename,pfbname)
104 if data then
105 afmenhancers.apply(data,filename)
106
107 fonts.mappings.addtounicode(data,filename)
108 otfreaders.stripredundant(data)
109
110 otfreaders.pack(data)
111 data.size = size
112 data.time = time
113 data.pfbsize = pfbsize
114 data.pfbtime = pfbtime
115 report_afm("saving %a in cache",name)
116
117 data = containers.write(afm.cache, name, data)
118 data = containers.read(afm.cache,name)
119 end
120 end
121 if data then
122
123 otfreaders.unpack(data)
124 otfreaders.expand(data)
125 otfreaders.addunicodetable(data)
126 otfenhancers.apply(data,filename,data)
127 if applyruntimefixes then
128 applyruntimefixes(filename,data)
129 end
130 end
131 return data
132 end
133end
134
135
136
137local uparser = fonts.mappings.makenameparser()
138
139local function enhance_unify_names(data, filename)
140 local unicodevector = fonts.encodings.agl.unicodes
141 local unicodes = { }
142 local names = { }
143 local private = data.private or privateoffset
144 local descriptions = data.descriptions
145 for name, blob in sortedhash(data.characters) do
146 local code = unicodevector[name]
147 if not code then
148 code = lpegmatch(uparser,name)
149 if type(code) ~= "number" then
150 code = private
151 private = private + 1
152 report_afm("assigning private slot %U for unknown glyph name %a",code,name)
153 end
154 end
155 local index = blob.index
156 unicodes[name] = code
157 names[name] = index
158 blob.name = name
159 descriptions[code] = {
160 boundingbox = blob.boundingbox,
161 width = blob.width,
162 kerns = blob.kerns,
163 index = index,
164 name = name,
165 }
166 end
167 for unicode, description in next, descriptions do
168 local kerns = description.kerns
169 if kerns then
170 local krn = { }
171 for name, kern in next, kerns do
172 local unicode = unicodes[name]
173 if unicode then
174 krn[unicode] = kern
175 else
176
177 end
178 end
179 description.kerns = krn
180 end
181 end
182 data.characters = nil
183 data.private = private
184 local resources = data.resources
185 local filename = resources.filename or file.removesuffix(file.basename(filename))
186 resources.filename = resolvers.unresolve(filename)
187 resources.unicodes = unicodes
188 resources.marks = { }
189
190end
191
192local everywhere = { ["*"] = { ["*"] = true } }
193local noflags = { false, false, false, false }
194
195local function enhance_normalize_features(data)
196 local ligatures = setmetatableindex("table")
197 local kerns = setmetatableindex("table")
198 local extrakerns = setmetatableindex("table")
199 for u, c in next, data.descriptions do
200 local l = c.ligatures
201 local k = c.kerns
202 local e = c.extrakerns
203 if l then
204 ligatures[u] = l
205 for u, v in next, l do
206 l[u] = { ligature = v }
207 end
208 c.ligatures = nil
209 end
210 if k then
211 kerns[u] = k
212 for u, v in next, k do
213 k[u] = v
214 end
215 c.kerns = nil
216 end
217 if e then
218 extrakerns[u] = e
219 for u, v in next, e do
220 e[u] = v
221 end
222 c.extrakerns = nil
223 end
224 end
225 local features = {
226 gpos = { },
227 gsub = { },
228 }
229 local sequences = {
230
231 }
232 if next(ligatures) then
233 features.gsub.liga = everywhere
234 data.properties.hasligatures = true
235 sequences[#sequences+1] = {
236 features = {
237 liga = everywhere,
238 },
239 flags = noflags,
240 name = "s_s_0",
241 nofsteps = 1,
242 order = { "liga" },
243 type = "gsub_ligature",
244 steps = {
245 {
246 coverage = ligatures,
247 },
248 },
249 }
250 end
251 if next(kerns) then
252 features.gpos.kern = everywhere
253 data.properties.haskerns = true
254 sequences[#sequences+1] = {
255 features = {
256 kern = everywhere,
257 },
258 flags = noflags,
259 name = "p_s_0",
260 nofsteps = 1,
261 order = { "kern" },
262 type = "gpos_pair",
263 steps = {
264 {
265 format = "kern",
266 coverage = kerns,
267 },
268 },
269 }
270 end
271 if next(extrakerns) then
272 features.gpos.extrakerns = everywhere
273 data.properties.haskerns = true
274 sequences[#sequences+1] = {
275 features = {
276 extrakerns = everywhere,
277 },
278 flags = noflags,
279 name = "p_s_1",
280 nofsteps = 1,
281 order = { "extrakerns" },
282 type = "gpos_pair",
283 steps = {
284 {
285 format = "kern",
286 coverage = extrakerns,
287 },
288 },
289 }
290 end
291
292 data.resources.features = features
293 data.resources.sequences = sequences
294end
295
296local function enhance_fix_names(data)
297 for k, v in next, data.descriptions do
298 local n = v.name
299 local r = overloads[n]
300 if r then
301 local name = r.name
302 if trace_indexing then
303 report_afm("renaming characters %a to %a",n,name)
304 end
305 v.name = name
306 v.unicode = r.unicode
307 end
308 end
309end
310
311
312
313
314local addthem = function(rawdata,ligatures)
315 if ligatures then
316 local descriptions = rawdata.descriptions
317 local resources = rawdata.resources
318 local unicodes = resources.unicodes
319
320 for ligname, ligdata in next, ligatures do
321 local one = descriptions[unicodes[ligname]]
322 if one then
323 for _, pair in next, ligdata do
324 local two = unicodes[pair[1]]
325 local three = unicodes[pair[2]]
326 if two and three then
327 local ol = one.ligatures
328 if ol then
329 if not ol[two] then
330 ol[two] = three
331 end
332 else
333 one.ligatures = { [two] = three }
334 end
335 end
336 end
337 end
338 end
339 end
340end
341
342local function enhance_add_ligatures(rawdata)
343 addthem(rawdata,afm.helpdata.ligatures)
344end
345
346
347
348
349
350
351
352
353
354
355local function enhance_add_extra_kerns(rawdata)
356 local descriptions = rawdata.descriptions
357 local resources = rawdata.resources
358 local unicodes = resources.unicodes
359 local function do_it_left(what)
360 if what then
361 for unicode, description in next, descriptions do
362 local kerns = description.kerns
363 if kerns then
364 local extrakerns
365 for complex, simple in next, what do
366 complex = unicodes[complex]
367 simple = unicodes[simple]
368 if complex and simple then
369 local ks = kerns[simple]
370 if ks and not kerns[complex] then
371 if extrakerns then
372 extrakerns[complex] = ks
373 else
374 extrakerns = { [complex] = ks }
375 end
376 end
377 end
378 end
379 if extrakerns then
380 description.extrakerns = extrakerns
381 end
382 end
383 end
384 end
385 end
386 local function do_it_copy(what)
387 if what then
388 for complex, simple in next, what do
389 complex = unicodes[complex]
390 simple = unicodes[simple]
391 if complex and simple then
392 local complexdescription = descriptions[complex]
393 if complexdescription then
394 local simpledescription = descriptions[complex]
395 if simpledescription then
396 local extrakerns
397 local kerns = simpledescription.kerns
398 if kerns then
399 for unicode, kern in next, kerns do
400 if extrakerns then
401 extrakerns[unicode] = kern
402 else
403 extrakerns = { [unicode] = kern }
404 end
405 end
406 end
407 local extrakerns = simpledescription.extrakerns
408 if extrakerns then
409 for unicode, kern in next, extrakerns do
410 if extrakerns then
411 extrakerns[unicode] = kern
412 else
413 extrakerns = { [unicode] = kern }
414 end
415 end
416 end
417 if extrakerns then
418 complexdescription.extrakerns = extrakerns
419 end
420 end
421 end
422 end
423 end
424 end
425 end
426
427 do_it_left(afm.helpdata.leftkerned)
428 do_it_left(afm.helpdata.bothkerned)
429
430 do_it_copy(afm.helpdata.bothkerned)
431 do_it_copy(afm.helpdata.rightkerned)
432end
433
434
435
436local function adddimensions(data)
437 if data then
438 for unicode, description in next, data.descriptions do
439 local bb = description.boundingbox
440 if bb then
441 local ht = bb[4]
442 local dp = -bb[2]
443 if ht == 0 or ht < 0 then
444
445 else
446 description.height = ht
447 end
448 if dp == 0 or dp < 0 then
449
450 else
451 description.depth = dp
452 end
453 end
454 end
455 end
456end
457
458local function copytotfm(data)
459 if data and data.descriptions then
460 local metadata = data.metadata
461 local resources = data.resources
462 local properties = derivetable(data.properties)
463 local descriptions = derivetable(data.descriptions)
464 local goodies = derivetable(data.goodies)
465 local characters = { }
466 local parameters = { }
467 local unicodes = resources.unicodes
468
469 for unicode, description in next, data.descriptions do
470 characters[unicode] = { }
471 end
472
473 local filename = constructors.checkedfilename(resources)
474 local fontname = metadata.fontname or metadata.fullname
475 local fullname = metadata.fullname or metadata.fontname
476 local endash = 0x2013
477 local emdash = 0x2014
478 local space = 0x0020
479 local spacer = "space"
480 local spaceunits = 500
481
482 local monospaced = metadata.monospaced
483 local charwidth = metadata.charwidth
484 local italicangle = metadata.italicangle
485 local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight
486 properties.monospaced = monospaced
487 parameters.italicangle = italicangle
488 parameters.charwidth = charwidth
489 parameters.charxheight = charxheight
490
491 local d_endash = descriptions[endash]
492 local d_emdash = descriptions[emdash]
493 local d_space = descriptions[space]
494 if not d_space or d_space == 0 then
495 d_space = d_endash
496 end
497 if d_space then
498 spaceunits, spacer = d_space.width or 0, "space"
499 end
500 if properties.monospaced then
501 if spaceunits == 0 and d_emdash then
502 spaceunits, spacer = d_emdash.width or 0, "emdash"
503 end
504 else
505 if spaceunits == 0 and d_endash then
506 spaceunits, spacer = d_emdash.width or 0, "endash"
507 end
508 end
509 if spaceunits == 0 and charwidth then
510 spaceunits, spacer = charwidth or 0, "charwidth"
511 end
512 if spaceunits == 0 then
513 spaceunits = tonumber(spaceunits) or 500
514 end
515 if spaceunits == 0 then
516 spaceunits = 500
517 end
518
519 parameters.slant = 0
520 parameters.space = spaceunits
521 parameters.space_stretch = 500
522 parameters.space_shrink = 333
523 parameters.x_height = 400
524 parameters.quad = 1000
525
526 if italicangle and italicangle ~= 0 then
527 parameters.italicangle = italicangle
528 parameters.italicfactor = math.cos(math.rad(90+italicangle))
529 parameters.slant = - math.tan(italicangle*math.pi/180)
530 end
531 if monospaced then
532 parameters.space_stretch = 0
533 parameters.space_shrink = 0
534 elseif afm.syncspace then
535 parameters.space_stretch = spaceunits/2
536 parameters.space_shrink = spaceunits/3
537 end
538 parameters.extra_space = parameters.space_shrink
539 if charxheight then
540 parameters.x_height = charxheight
541 else
542
543 local x = 0x0078
544 if x then
545 local x = descriptions[x]
546 if x then
547 parameters.x_height = x.height
548 end
549 end
550
551 end
552
553 if metadata.sup then
554 local dummy = { 0, 0, 0 }
555 parameters[ 1] = metadata.designsize or 0
556 parameters[ 2] = metadata.checksum or 0
557 parameters[ 3],
558 parameters[ 4],
559 parameters[ 5] = unpack(metadata.space or dummy)
560 parameters[ 6] = metadata.quad or 0
561 parameters[ 7] = metadata.extraspace or 0
562 parameters[ 8],
563 parameters[ 9],
564 parameters[10] = unpack(metadata.num or dummy)
565 parameters[11],
566 parameters[12] = unpack(metadata.denom or dummy)
567 parameters[13],
568 parameters[14],
569 parameters[15] = unpack(metadata.sup or dummy)
570 parameters[16],
571 parameters[17] = unpack(metadata.sub or dummy)
572 parameters[18] = metadata.supdrop or 0
573 parameters[19] = metadata.subdrop or 0
574 parameters[20],
575 parameters[21] = unpack(metadata.delim or dummy)
576 parameters[22] = metadata.axisheight or 0
577 end
578
579 parameters.designsize = (metadata.designsize or 10)*65536
580 parameters.ascender = abs(metadata.ascender or 0)
581 parameters.descender = abs(metadata.descender or 0)
582 parameters.units = 1000
583
584 properties.spacer = spacer
585 properties.format = fonts.formats[filename] or "type1"
586 properties.filename = filename
587 properties.fontname = fontname
588 properties.fullname = fullname
589 properties.psname = fullname
590 properties.name = filename or fullname or fontname
591 properties.private = properties.private or data.private or privateoffset
592
593if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then
594 properties.encodingbytes = 2
595end
596
597 if next(characters) then
598 return {
599 characters = characters,
600 descriptions = descriptions,
601 parameters = parameters,
602 resources = resources,
603 properties = properties,
604 goodies = goodies,
605 }
606 end
607 end
608 return nil
609end
610
611
612
613
614
615function afm.setfeatures(tfmdata,features)
616 local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
617 if okay then
618 return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
619 else
620 return { }
621 end
622end
623
624local function addtables(data)
625 local resources = data.resources
626 local lookuptags = resources.lookuptags
627 local unicodes = resources.unicodes
628 if not lookuptags then
629 lookuptags = { }
630 resources.lookuptags = lookuptags
631 end
632 setmetatableindex(lookuptags,function(t,k)
633 local v = type(k) == "number" and ("lookup " .. k) or k
634 t[k] = v
635 return v
636 end)
637 if not unicodes then
638 unicodes = { }
639 resources.unicodes = unicodes
640 setmetatableindex(unicodes,function(t,k)
641 setmetatableindex(unicodes,nil)
642 for u, d in next, data.descriptions do
643 local n = d.name
644 if n then
645 t[n] = u
646 end
647 end
648 return rawget(t,k)
649 end)
650 end
651 constructors.addcoreunicodes(unicodes)
652end
653
654local function afmtotfm(specification)
655 local afmname = specification.filename or specification.name
656 if specification.forced == "afm" or specification.format == "afm" then
657 if trace_loading then
658 report_afm("forcing afm format for %a",afmname)
659 end
660 else
661 local tfmname = findbinfile(afmname,"ofm") or ""
662 if tfmname ~= "" then
663 if trace_loading then
664 report_afm("fallback from afm to tfm for %a",afmname)
665 end
666 return
667 end
668 end
669 if afmname ~= "" then
670
671 local features = constructors.checkedfeatures("afm",specification.features.normal)
672 specification.features.normal = features
673 constructors.hashinstance(specification,true)
674
675 specification = definers.resolve(specification)
676 local cache_id = specification.hash
677 local tfmdata = containers.read(constructors.cache, cache_id)
678 if not tfmdata then
679 local rawdata = afm.load(afmname)
680 if rawdata and next(rawdata) then
681 addtables(rawdata)
682 adddimensions(rawdata)
683 tfmdata = copytotfm(rawdata)
684 if tfmdata and next(tfmdata) then
685 local shared = tfmdata.shared
686 if not shared then
687 shared = { }
688 tfmdata.shared = shared
689 end
690 shared.rawdata = rawdata
691 shared.dynamics = { }
692 tfmdata.changed = { }
693 shared.features = features
694 shared.processes = afm.setfeatures(tfmdata,features)
695 end
696 elseif trace_loading then
697 report_afm("no (valid) afm file found with name %a",afmname)
698 end
699 tfmdata = containers.write(constructors.cache,cache_id,tfmdata)
700 end
701 return tfmdata
702 end
703end
704
705
706
707
708
709
710local function read_from_afm(specification)
711 local tfmdata = afmtotfm(specification)
712 if tfmdata then
713 tfmdata.properties.name = specification.name
714 tfmdata.properties.id = specification.id
715 tfmdata = constructors.scale(tfmdata, specification)
716 local allfeatures = tfmdata.shared.features or specification.features.normal
717 constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
718 fonts.loggers.register(tfmdata,'afm',specification)
719 end
720 return tfmdata
721end
722
723
724
725registerafmfeature {
726 name = "mode",
727 description = "mode",
728 initializers = {
729 base = otf.modeinitializer,
730 node = otf.modeinitializer,
731 }
732}
733
734registerafmfeature {
735 name = "features",
736 description = "features",
737 default = true,
738 initializers = {
739 node = otf.nodemodeinitializer,
740 base = otf.basemodeinitializer,
741 },
742 processors = {
743 node = otf.featuresprocessor,
744 }
745}
746
747
748
749fonts.formats.afm = "type1"
750fonts.formats.pfb = "type1"
751
752local function check_afm(specification,fullname)
753 local foundname = findbinfile(fullname, 'afm') or ""
754 if foundname == "" then
755 foundname = fonts.names.getfilename(fullname,"afm") or ""
756 end
757 if fullname and foundname == "" and afm.autoprefixed then
758 local encoding, shortname = match(fullname,"^(.-)%-(.*)$")
759 if encoding and shortname and fonts.encodings.known[encoding] then
760 shortname = findbinfile(shortname,'afm') or ""
761 if shortname ~= "" then
762 foundname = shortname
763 if trace_defining then
764 report_afm("stripping encoding prefix from filename %a",afmname)
765 end
766 end
767 end
768 end
769 if foundname ~= "" then
770 specification.filename = foundname
771 specification.format = "afm"
772 return read_from_afm(specification)
773 end
774end
775
776function readers.afm(specification,method)
777 local fullname = specification.filename or ""
778 local tfmdata = nil
779 if fullname == "" then
780 local forced = specification.forced or ""
781 if forced ~= "" then
782 tfmdata = check_afm(specification,specification.name .. "." .. forced)
783 end
784 if not tfmdata then
785 local check_tfm = readers.check_tfm
786 method = (check_tfm and (method or definers.method or "afm or tfm")) or "afm"
787 if method == "tfm" then
788 tfmdata = check_tfm(specification,specification.name)
789 elseif method == "afm" then
790 tfmdata = check_afm(specification,specification.name)
791 elseif method == "tfm or afm" then
792 tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name)
793 else
794 tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
795 end
796 end
797 else
798 tfmdata = check_afm(specification,fullname)
799 end
800 return tfmdata
801end
802
803function readers.pfb(specification,method)
804 local original = specification.specification
805 if trace_defining then
806 report_afm("using afm reader for %a",original)
807 end
808 specification.forced = "afm"
809 local function swap(name)
810 local value = specification[swap]
811 if value then
812 specification[swap] = gsub("%.pfb",".afm",1)
813 end
814 end
815 swap("filename")
816 swap("fullname")
817 swap("forcedname")
818 swap("specification")
819 return readers.afm(specification,method)
820end
821
822
823
824registerafmenhancer("unify names", enhance_unify_names)
825registerafmenhancer("add ligatures", enhance_add_ligatures)
826registerafmenhancer("add extra kerns", enhance_add_extra_kerns)
827registerafmenhancer("normalize features", enhance_normalize_features)
828registerafmenhancer("check extra features", otfenhancers.enhance)
829registerafmenhancer("fix names", enhance_fix_names)
830 |