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