1if not modules then modules = { } end modules ['luatex-fonts-tfm'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12local next, type = next, type
13local match, format = string.match, string.format
14local concat, sortedhash = table.concat, table.sortedhash
15local idiv = number.idiv
16
17local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
18local trace_features = false trackers.register("tfm.features", function(v) trace_features = v end)
19
20local report_defining = logs.reporter("fonts","defining")
21local report_tfm = logs.reporter("fonts","tfm loading")
22
23local findbinfile = resolvers.findbinfile
24local setmetatableindex = table.setmetatableindex
25
26local fonts = fonts
27local handlers = fonts.handlers
28local helpers = fonts.helpers
29local readers = fonts.readers
30local constructors = fonts.constructors
31local encodings = fonts.encodings
32
33local tfm = constructors.handlers.tfm
34tfm.version = 1.000
35tfm.maxnestingdepth = 5
36tfm.maxnestingsize = 65536*1024
37
38local otf = fonts.handlers.otf
39local otfenhancers = otf.enhancers
40
41local tfmfeatures = constructors.features.tfm
42local registertfmfeature = tfmfeatures.register
43
44local tfmenhancers = constructors.enhancers.tfm
45local registertfmenhancer = tfmenhancers.register
46
47local charcommand = helpers.commands.char
48
49constructors.resolvevirtualtoo = false
50
51fonts.formats.tfm = "type1"
52fonts.formats.ofm = "type1"
53
54function tfm.setfeatures(tfmdata,features)
55 local okay = constructors.initializefeatures("tfm",tfmdata,features,trace_features,report_tfm)
56 if okay then
57 return constructors.collectprocessors("tfm",tfmdata,features,trace_features,report_tfm)
58 else
59 return { }
60 end
61end
62
63local depth = { }
64
65local loadtfm = font.read_tfm
66local loadvf = font.read_vf
67
68local function read_from_tfm(specification)
69 local filename = specification.filename
70 local size = specification.size
71 depth[filename] = (depth[filename] or 0) + 1
72 if trace_defining then
73 report_defining("loading tfm file %a at size %s",filename,size)
74 end
75 local tfmdata = loadtfm(filename,size)
76 if tfmdata then
77
78 local features = specification.features and specification.features.normal or { }
79 local features = constructors.checkedfeatures("tfm",features)
80 specification.features.normal = features
81
82
83
84
85 local newtfmdata = (depth[filename] == 1) and tfm.reencode(tfmdata,specification)
86 if newtfmdata then
87 tfmdata = newtfmdata
88 end
89
90 local resources = tfmdata.resources or { }
91 local properties = tfmdata.properties or { }
92 local parameters = tfmdata.parameters or { }
93 local shared = tfmdata.shared or { }
94
95 shared.features = features
96 shared.resources = resources
97
98 properties.name = tfmdata.name
99 properties.fontname = tfmdata.fontname
100 properties.psname = tfmdata.psname
101 properties.fullname = tfmdata.fullname
102 properties.filename = specification.filename
103 properties.format = tfmdata.format or fonts.formats.tfm
104 properties.usedbitmap = tfmdata.usedbitmap
105
106 tfmdata.properties = properties
107 tfmdata.resources = resources
108 tfmdata.parameters = parameters
109 tfmdata.shared = shared
110
111 shared.rawdata = { resources = resources }
112 shared.features = features
113
114
115
116
117
118 if newtfmdata then
119
120
121
122 if not resources.marks then
123 resources.marks = { }
124 end
125 if not resources.sequences then
126 resources.sequences = { }
127 end
128 if not resources.features then
129 resources.features = {
130 gsub = { },
131 gpos = { },
132 }
133 end
134 if not tfmdata.changed then
135 tfmdata.changed = { }
136 end
137 if not tfmdata.descriptions then
138 tfmdata.descriptions = tfmdata.characters
139 end
140
141
142
143 otf.readers.addunicodetable(tfmdata)
144
145
146
147 tfmenhancers.apply(tfmdata,filename)
148
149
150
151 constructors.applymanipulators("tfm",tfmdata,features,trace_features,report_tfm)
152
153
154
155
156
157 otf.readers.unifymissing(tfmdata)
158
159
160
161
162 fonts.mappings.addtounicode(tfmdata,filename)
163
164
165
166 tfmdata.tounicode = 1
167 local tounicode = fonts.mappings.tounicode
168 for unicode, v in next, tfmdata.characters do
169 local u = v.unicode
170 if u then
171 v.tounicode = tounicode(u)
172 end
173 end
174
175
176
177
178
179 if tfmdata.usedbitmap then
180 tfm.addtounicode(tfmdata)
181 end
182 end
183
184 shared.processes = next(features) and tfm.setfeatures(tfmdata,features) or nil
185
186 if size < 0 then
187 size = idiv(65536 * -size,100)
188 end
189 parameters.factor = 1
190 parameters.units = 1000
191 parameters.size = size
192 parameters.slant = parameters.slant or parameters[1] or 0
193 parameters.space = parameters.space or parameters[2] or 0
194 parameters.space_stretch = parameters.space_stretch or parameters[3] or 0
195 parameters.space_shrink = parameters.space_shrink or parameters[4] or 0
196 parameters.x_height = parameters.x_height or parameters[5] or 0
197 parameters.quad = parameters.quad or parameters[6] or 0
198 parameters.extra_space = parameters.extra_space or parameters[7] or 0
199
200 constructors.enhanceparameters(parameters)
201
202 properties.private = properties.private or tfmdata.private or privateoffset
203
204 if newtfmdata then
205
206
207
208
209 elseif constructors.resolvevirtualtoo then
210 fonts.loggers.register(tfmdata,file.suffix(filename),specification)
211 local vfname = findbinfile(specification.name, 'ovf')
212 if vfname and vfname ~= "" then
213 local vfdata = loadvf(vfname,size)
214 if vfdata then
215 local chars = tfmdata.characters
216 for k,v in next, vfdata.characters do
217 chars[k].commands = v.commands
218 end
219 properties.virtualized = true
220 tfmdata.fonts = vfdata.fonts
221 tfmdata.type = "virtual"
222 local fontlist = vfdata.fonts
223 local name = file.nameonly(filename)
224 for i=1,#fontlist do
225 local n = fontlist[i].name
226 local s = fontlist[i].size
227 local d = depth[filename]
228 s = constructors.scaled(s,vfdata.designsize)
229 if d > tfm.maxnestingdepth then
230 report_defining("too deeply nested virtual font %a with size %a, max nesting depth %s",n,s,tfm.maxnestingdepth)
231 fontlist[i] = { id = 0 }
232 elseif (d > 1) and (s > tfm.maxnestingsize) then
233 report_defining("virtual font %a exceeds size %s",n,s)
234 fontlist[i] = { id = 0 }
235 else
236 local t, id = constructors.readanddefine(n,s)
237 fontlist[i] = { id = id }
238 end
239 end
240 end
241 end
242 end
243
244 properties.haskerns = true
245 properties.hasligatures = true
246 properties.hasitalics = true
247 resources.unicodes = { }
248 resources.lookuptags = { }
249
250 depth[filename] = depth[filename] - 1
251
252 return tfmdata
253 else
254 depth[filename] = depth[filename] - 1
255 end
256end
257
258local function check_tfm(specification,fullname)
259 local foundname = findbinfile(fullname, 'tfm') or ""
260 if foundname == "" then
261 foundname = findbinfile(fullname, 'ofm') or ""
262 end
263 if foundname == "" then
264 foundname = fonts.names.getfilename(fullname,"tfm") or ""
265 end
266 if foundname ~= "" then
267 specification.filename = foundname
268 specification.format = "ofm"
269 return read_from_tfm(specification)
270 elseif trace_defining then
271 report_defining("loading tfm with name %a fails",specification.name)
272 end
273end
274
275readers.check_tfm = check_tfm
276
277function readers.tfm(specification)
278 local fullname = specification.filename or ""
279 if fullname == "" then
280 local forced = specification.forced or ""
281 if forced ~= "" then
282 fullname = specification.name .. "." .. forced
283 else
284 fullname = specification.name
285 end
286 end
287 return check_tfm(specification,fullname)
288end
289
290readers.ofm = readers.tfm
291
292
293
294
295
296
297do
298
299 local outfiles = { }
300
301 local tfmcache = table.setmetatableindex(function(t,tfmdata)
302 local id = font.define(tfmdata)
303 t[tfmdata] = id
304 return id
305 end)
306
307 local encdone = table.setmetatableindex("table")
308
309 function tfm.reencode(tfmdata,specification)
310
311 local features = specification.features
312
313 if not features then
314 return
315 end
316
317 local features = features.normal
318
319 if not features then
320 return
321 end
322
323 local tfmfile = file.basename(tfmdata.name)
324 local encfile = features.reencode
325 local pfbfile = features.pfbfile
326 local bitmap = features.bitmap
327
328 if not encfile then
329 return
330 end
331
332 local pfbfile = outfiles[tfmfile]
333
334 if pfbfile == nil then
335 if bitmap then
336 pfbfile = false
337 elseif type(pfbfile) ~= "string" then
338 pfbfile = tfmfile
339 end
340 if type(pfbfile) == "string" then
341 pfbfile = file.addsuffix(pfbfile,"pfb")
342
343 report_tfm("using type1 shapes from %a for %a",pfbfile,tfmfile)
344 else
345 report_tfm("using bitmap shapes for %a",tfmfile)
346 pfbfile = false
347 end
348 outfiles[tfmfile] = pfbfile
349 end
350
351 local encoding = false
352 local vector = false
353
354 if type(pfbfile) == "string" then
355 local pfb = constructors.handlers.pfb
356 if pfb and pfb.loadvector then
357 local v, e = pfb.loadvector(pfbfile)
358 if v then
359 vector = v
360 end
361 if e then
362 encoding = e
363 end
364 end
365 end
366 if type(encfile) == "string" and encfile ~= "auto" then
367 encoding = fonts.encodings.load(file.addsuffix(encfile,"enc"))
368 if encoding then
369 encoding = encoding.vector
370 end
371 end
372 if not encoding then
373 report_tfm("bad encoding for %a, quitting",tfmfile)
374 return
375 end
376
377 local unicoding = fonts.encodings.agl and fonts.encodings.agl.unicodes
378 local virtualid = tfmcache[tfmdata]
379 local tfmdata = table.copy(tfmdata)
380 local characters = { }
381 local originals = tfmdata.characters
382 local indices = { }
383 local parentfont = { "font", 1 }
384 local private = tfmdata.privateoffset or constructors.privateoffset
385 local reported = encdone[tfmfile][encfile]
386
387
388
389 local backmap = vector and table.swapped(vector)
390 local done = { }
391
392 for index, name in sortedhash(encoding) do
393 local unicode = unicoding[name]
394 local original = originals[index]
395 if original then
396 if unicode then
397 original.unicode = unicode
398 else
399 unicode = private
400 private = private + 1
401 if not reported then
402 report_tfm("glyph %a in font %a with encoding %a gets unicode %U",name,tfmfile,encfile,unicode)
403 end
404 end
405 characters[unicode] = original
406 indices[index] = unicode
407 original.name = name
408 if backmap then
409 original.index = backmap[name]
410 else
411 original.commands = { parentfont, charcommand[index] }
412 original.oindex = index
413 end
414 done[name] = true
415 elseif not done[name] then
416 report_tfm("bad index %a in font %a with name %a",index,tfmfile,name)
417 end
418 end
419
420 encdone[tfmfile][encfile] = true
421
422
423
424 for k, v in next, characters do
425 local kerns = v.kerns
426 if kerns then
427 local t = { }
428 for k, v in next, kerns do
429 local i = indices[k]
430 if i then
431 t[i] = v
432 end
433 end
434 v.kerns = next(t) and t or nil
435 end
436 local ligatures = v.ligatures
437 if ligatures then
438 local t = { }
439 for k, v in next, ligatures do
440 local i = indices[k]
441 if i then
442 t[i] = v
443 v.char = indices[v.char]
444 end
445 end
446 v.ligatures = next(t) and t or nil
447 end
448 end
449
450
451
452 tfmdata.fonts = { { id = virtualid } }
453 tfmdata.characters = characters
454 tfmdata.fullname = tfmdata.fullname or tfmdata.name
455 tfmdata.psname = file.nameonly(pfbfile or tfmdata.name)
456 tfmdata.filename = pfbfile
457 tfmdata.encodingbytes = 2
458
459 tfmdata.format = "type1"
460 tfmdata.tounicode = 1
461 tfmdata.embedding = "subset"
462 tfmdata.usedbitmap = bitmap and virtualid
463 tfmdata.private = private
464
465 return tfmdata
466 end
467
468end
469
470
471
472
473
474do
475
476 local template = [[
477/CIDInit /ProcSet findresource begin
478 12 dict begin
479 begincmap
480 /CIDSystemInfo << /Registry (TeX) /Ordering (bitmap-%s) /Supplement 0 >> def
481 /CMapName /TeX-bitmap-%s def
482 /CMapType 2 def
483 1 begincodespacerange
484 <00> <FF>
485 endcodespacerange
486 %s beginbfchar
487%s
488 endbfchar
489 endcmap
490CMapName currentdict /CMap defineresource pop end
491end
492end
493]]
494
495 local flushstreamobject = lpdf and lpdf.flushstreamobject
496 local setfontattributes = lpdf and lpdf.setfontattributes
497
498 if not flushstreamobject then
499 flushstreamobject = function(data)
500 return pdf.obj { immediate = true, type = "stream", string = data }
501 end
502 end
503
504 if not setfontattributes then
505 setfontattributes = function(id,data)
506 return pdf.setfontattributes(id,data)
507 end
508 end
509
510 function tfm.addtounicode(tfmdata)
511 local id = tfmdata.usedbitmap
512 local map = { }
513 local char = { }
514 for k, v in next, tfmdata.characters do
515 local index = v.oindex
516 local tounicode = v.tounicode
517 if index and tounicode then
518 map[index] = tounicode
519 end
520 end
521 for k, v in sortedhash(map) do
522 char[#char+1] = format("<%02X> <%s>",k,v)
523 end
524 char = concat(char,"\n")
525 local stream = format(template,id,id,#char,char)
526 local reference = flushstreamobject(stream,nil,true)
527 setfontattributes(id,format("/ToUnicode %i 0 R",reference))
528 end
529
530end
531
532
533
534
535
536do
537
538 local everywhere = { ["*"] = { ["*"] = true } }
539 local noflags = { false, false, false, false }
540
541 local function enhance_normalize_features(data)
542 local ligatures = setmetatableindex("table")
543 local kerns = setmetatableindex("table")
544 local characters = data.characters
545 for u, c in next, characters do
546 local l = c.ligatures
547 local k = c.kerns
548 if l then
549 ligatures[u] = l
550 for u, v in next, l do
551 l[u] = { ligature = v.char }
552 end
553 c.ligatures = nil
554 end
555 if k then
556 kerns[u] = k
557 for u, v in next, k do
558 k[u] = v
559 end
560 c.kerns = nil
561 end
562 end
563
564 for u, l in next, ligatures do
565 for k, v in next, l do
566 local vl = v.ligature
567 local dl = ligatures[vl]
568 if dl then
569 for kk, vv in next, dl do
570 v[kk] = vv
571 end
572 end
573 end
574 end
575
576 local features = {
577 gpos = { },
578 gsub = { },
579 }
580 local sequences = {
581
582 }
583 if next(ligatures) then
584 features.gsub.liga = everywhere
585 data.properties.hasligatures = true
586 sequences[#sequences+1] = {
587 features = {
588 liga = everywhere,
589 },
590 flags = noflags,
591 name = "s_s_0",
592 nofsteps = 1,
593 order = { "liga" },
594 type = "gsub_ligature",
595 steps = {
596 {
597 coverage = ligatures,
598 },
599 },
600 }
601 end
602 if next(kerns) then
603 features.gpos.kern = everywhere
604 data.properties.haskerns = true
605 sequences[#sequences+1] = {
606 features = {
607 kern = everywhere,
608 },
609 flags = noflags,
610 name = "p_s_0",
611 nofsteps = 1,
612 order = { "kern" },
613 type = "gpos_pair",
614 steps = {
615 {
616 format = "kern",
617 coverage = kerns,
618 },
619 },
620 }
621 end
622 data.resources.features = features
623 data.resources.sequences = sequences
624 data.shared.resources = data.shared.resources or resources
625 end
626
627 registertfmenhancer("normalize features", enhance_normalize_features)
628 registertfmenhancer("check extra features", otfenhancers.enhance)
629
630end
631
632
633
634registertfmfeature {
635 name = "mode",
636 description = "mode",
637 initializers = {
638 base = otf.modeinitializer,
639 node = otf.modeinitializer,
640 }
641}
642
643registertfmfeature {
644 name = "features",
645 description = "features",
646 default = true,
647 initializers = {
648 base = otf.basemodeinitializer,
649 node = otf.nodemodeinitializer,
650 },
651 processors = {
652 node = otf.featuresprocessor,
653 }
654}
655 |