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.541
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 local complex = unicodes[complex]
367 local 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 local complex = unicodes[complex]
390 local 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.spacestretch = 500
522 parameters.spaceshrink = 333
523 parameters.xheight = 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.spacestretch = 0
533 parameters.spaceshrink = 0
534 elseif afm.syncspace then
535 parameters.spacestretch = spaceunits/2
536 parameters.spaceshrink = spaceunits/3
537 end
538 parameters.extraspace = parameters.spaceshrink
539 if charxheight then
540 parameters.xheight = charxheight
541 else
542
543 local x = 0x0078
544 if x then
545 local x = descriptions[x]
546 if x then
547 parameters.xheight = 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.capheight = abs(metadata.capheight or 0)
583 parameters.ascent = abs(metadata.ascent or parameters.ascender or 0)
584 parameters.descent = abs(metadata.descent or parameters.descender or 0)
585 parameters.units = 1000
586
587 properties.spacer = spacer
588 properties.format = fonts.formats[filename] or "type1"
589 properties.filename = filename
590 properties.fontname = fontname
591 properties.fullname = fullname
592 properties.psname = fullname
593 properties.name = filename or fullname or fontname
594 properties.private = properties.private or data.private or privateoffset
595
596 if next(characters) then
597 return {
598 characters = characters,
599 descriptions = descriptions,
600 parameters = parameters,
601 resources = resources,
602 properties = properties,
603 goodies = goodies,
604 }
605 end
606 end
607 return nil
608end
609
610
611
612
613
614function afm.setfeatures(tfmdata,features)
615 local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
616 if okay then
617 return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
618 else
619 return { }
620 end
621end
622
623local function addtables(data)
624 local resources = data.resources
625 local lookuptags = resources.lookuptags
626 local unicodes = resources.unicodes
627 if not lookuptags then
628 lookuptags = { }
629 resources.lookuptags = lookuptags
630 end
631 setmetatableindex(lookuptags,function(t,k)
632 local v = type(k) == "number" and ("lookup " .. k) or k
633 t[k] = v
634 return v
635 end)
636 if not unicodes then
637 unicodes = { }
638 resources.unicodes = unicodes
639 setmetatableindex(unicodes,function(t,k)
640 setmetatableindex(unicodes,nil)
641 for u, d in next, data.descriptions do
642 local n = d.name
643 if n then
644 t[n] = u
645 end
646 end
647 return rawget(t,k)
648 end)
649 end
650 constructors.addcoreunicodes(unicodes)
651end
652
653local function afmtotfm(specification)
654 local afmname = specification.filename or specification.name
655 if specification.forced == "afm" or specification.format == "afm" then
656 if trace_loading then
657 report_afm("forcing afm format for %a",afmname)
658 end
659 else
660 local tfmname = findbinfile(afmname,"ofm") or ""
661 if tfmname ~= "" then
662 if trace_loading then
663 report_afm("fallback from afm to tfm for %a",afmname)
664 end
665 return
666 end
667 end
668 if afmname ~= "" then
669
670 local features = constructors.checkedfeatures("afm",specification.features.normal)
671 specification.features.normal = features
672 constructors.hashinstance(specification,true)
673
674 specification = definers.resolve(specification)
675 local cache_id = specification.hash
676 local tfmdata = containers.read(constructors.cache, cache_id)
677 if not tfmdata then
678 local rawdata = afm.load(afmname)
679 if rawdata and next(rawdata) then
680 addtables(rawdata)
681 adddimensions(rawdata)
682 tfmdata = copytotfm(rawdata)
683 if tfmdata and next(tfmdata) then
684 local shared = tfmdata.shared
685 if not shared then
686 shared = { }
687 tfmdata.shared = shared
688 end
689 shared.rawdata = rawdata
690 shared.dynamics = { }
691 tfmdata.changed = { }
692 shared.features = features
693 shared.processes = afm.setfeatures(tfmdata,features)
694 end
695 elseif trace_loading then
696 report_afm("no (valid) afm file found with name %a",afmname)
697 end
698 tfmdata = containers.write(constructors.cache,cache_id,tfmdata)
699 end
700 return tfmdata
701 end
702end
703
704
705
706
707
708
709local function read_from_afm(specification)
710 local tfmdata = afmtotfm(specification)
711 if tfmdata then
712 tfmdata.properties.name = specification.name
713 tfmdata.properties.id = specification.id
714 tfmdata = constructors.scale(tfmdata,specification)
715 local allfeatures = tfmdata.shared.features or specification.features.normal
716 constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
717 fonts.loggers.register(tfmdata,'afm',specification)
718 end
719 return tfmdata
720end
721
722
723
724registerafmfeature {
725 name = "mode",
726 description = "mode",
727 initializers = {
728 base = otf.modeinitializer,
729 node = otf.modeinitializer,
730 }
731}
732
733registerafmfeature {
734 name = "features",
735 description = "features",
736 default = true,
737 initializers = {
738 node = otf.nodemodeinitializer,
739 base = otf.basemodeinitializer,
740 },
741 processors = {
742 node = otf.featuresprocessor,
743 }
744}
745
746
747
748fonts.formats.afm = "type1"
749fonts.formats.pfb = "type1"
750
751local function check_afm(specification,fullname)
752 local foundname = findbinfile(fullname, 'afm') or ""
753 if foundname == "" then
754 foundname = fonts.names.getfilename(fullname,"afm") or ""
755 end
756 if fullname and foundname == "" and afm.autoprefixed then
757 local encoding, shortname = match(fullname,"^(.-)%-(.*)$")
758 if encoding and shortname and fonts.encodings.known[encoding] then
759 shortname = findbinfile(shortname,'afm') or ""
760 if shortname ~= "" then
761 foundname = shortname
762 if trace_defining then
763 report_afm("stripping encoding prefix from filename %a",afmname)
764 end
765 end
766 end
767 end
768 if foundname ~= "" then
769 specification.filename = foundname
770 specification.format = "afm"
771 return read_from_afm(specification)
772 end
773end
774
775function readers.afm(specification,method)
776 local fullname = specification.filename or ""
777 local tfmdata = nil
778 if fullname == "" then
779 local forced = specification.forced or ""
780 if forced ~= "" then
781 tfmdata = check_afm(specification,specification.name .. "." .. forced)
782 end
783 if not tfmdata then
784 local check_tfm = readers.check_tfm
785 method = (check_tfm and (method or definers.method or "afm or tfm")) or "afm"
786 if method == "tfm" then
787 tfmdata = check_tfm(specification,specification.name)
788 elseif method == "afm" then
789 tfmdata = check_afm(specification,specification.name)
790 elseif method == "tfm or afm" then
791 tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name)
792 else
793 tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
794 end
795 end
796 else
797 tfmdata = check_afm(specification,fullname)
798 end
799 return tfmdata
800end
801
802function readers.pfb(specification,method)
803 local original = specification.specification
804 if trace_defining then
805 report_afm("using afm reader for %a",original)
806 end
807 specification.forced = "afm"
808 local function swap(name)
809 local value = specification[swap]
810 if value then
811 specification[swap] = gsub("%.pfb",".afm",1)
812 end
813 end
814 swap("filename")
815 swap("fullname")
816 swap("forcedname")
817 swap("specification")
818 return readers.afm(specification,method)
819end
820
821
822
823registerafmenhancer("unify names", enhance_unify_names)
824registerafmenhancer("add ligatures", enhance_add_ligatures)
825registerafmenhancer("add extra kerns", enhance_add_extra_kerns)
826registerafmenhancer("normalize features", enhance_normalize_features)
827registerafmenhancer("check extra features", otfenhancers.enhance)
828registerafmenhancer("fix names", enhance_fix_names)
829 |