1if not modules then modules = { } end modules ['font-def'] = {
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
11local lower, gsub = string.lower, string.gsub
12local tostring, next = tostring, next
13local lpegmatch = lpeg.match
14local suffixonly, removesuffix, basename = file.suffix, file.removesuffix, file.basename
15local formatters = string.formatters
16local sortedhash, sortedkeys, setmetatableindex = table.sortedhash, table.sortedkeys, table.setmetatableindex
17
18local allocate = utilities.storage.allocate
19
20local trace_defining = false trackers .register("fonts.defining", function(v) trace_defining = v end)
21local directive_embedall = false directives.register("fonts.embedall", function(v) directive_embedall = v end)
22
23trackers.register("fonts.loading", "fonts.defining", "otf.loading", "afm.loading", "tfm.loading")
24
25local report_defining = logs.reporter("fonts","defining")
26
27
28
29
30
31local nextfont = font.nextid
32
33local fonts = fonts
34local fontdata = fonts.hashes.identifiers
35local readers = fonts.readers
36local definers = fonts.definers
37local specifiers = fonts.specifiers
38local constructors = fonts.constructors
39local fontgoodies = fonts.goodies
40
41readers.sequence = allocate { 'otf', 'ttf', 'afm', 'tfm', 'lua' }
42
43local variants = allocate()
44specifiers.variants = variants
45
46definers.methods = definers.methods or { }
47
48local internalized = allocate()
49
50local loadedfonts = constructors.loadedfonts
51local designsizes = constructors.designsizes
52
53
54
55local resolvefile = fontgoodies and fontgoodies.filenames and fontgoodies.filenames.resolve or function(s) return s end
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76local function makespecification(specification,lookup,name,sub,method,detail,size)
77 size = size or 655360
78 if not lookup or lookup == "" then
79 lookup = definers.defaultlookup
80 end
81 if trace_defining then
82 report_defining("specification %a, lookup %a, name %a, sub %a, method %a, detail %a",
83 specification, lookup, name, sub, method, detail)
84 end
85 local t = {
86 lookup = lookup,
87 specification = specification,
88 size = size,
89 name = name,
90 sub = sub,
91 method = method,
92 detail = detail,
93 resolved = "",
94 forced = "",
95 features = { },
96 }
97 return t
98end
99
100definers.makespecification = makespecification
101
102do
103
104 local splitter, splitspecifiers = nil, ""
105
106 local P, C, S, Cc, Cs = lpeg.P, lpeg.C, lpeg.S, lpeg.Cc, lpeg.Cs
107
108 local left = P("(")
109 local right = P(")")
110 local colon = P(":")
111 local space = P(" ")
112 local lbrace = P("{")
113 local rbrace = P("}")
114
115 definers.defaultlookup = "file"
116
117 local prefixpattern = P(false)
118
119 local function addspecifier(symbol)
120 splitspecifiers = splitspecifiers .. symbol
121 local method = S(splitspecifiers)
122 local lookup = C(prefixpattern) * colon
123 local sub = left * C(P(1-left-right-method)^1) * right
124 local specification = C(method) * C(P(1)^1)
125 local name = Cs((lbrace/"") * (1-rbrace)^1 * (rbrace/"") + (1-sub-specification)^1)
126 splitter = P((lookup + Cc("")) * name * (sub + Cc("")) * (specification + Cc("")))
127 end
128
129 local function addlookup(str)
130 prefixpattern = prefixpattern + P(str)
131 end
132
133 definers.addlookup = addlookup
134
135 addlookup("file")
136 addlookup("name")
137 addlookup("spec")
138
139 local function getspecification(str)
140 return lpegmatch(splitter,str or "")
141 end
142
143 definers.getspecification = getspecification
144
145 function definers.registersplit(symbol,action,verbosename)
146 addspecifier(symbol)
147 variants[symbol] = action
148 if verbosename then
149 variants[verbosename] = action
150 end
151 end
152
153 function definers.analyze(specification, size)
154
155 local lookup, name, sub, method, detail = getspecification(specification or "")
156 return makespecification(specification, lookup, name, sub, method, detail, size)
157 end
158
159end
160
161
162
163definers.resolvers = definers.resolvers or { }
164local resolvers = definers.resolvers
165
166
167
168function resolvers.file(specification)
169 local name = resolvefile(specification.name)
170 local suffix = lower(suffixonly(name))
171 if fonts.formats[suffix] then
172 specification.forced = suffix
173 specification.forcedname = name
174 specification.name = removesuffix(name)
175 else
176
177 specification.name = name
178 end
179end
180
181function resolvers.name(specification)
182 local resolve = fonts.names.resolve
183 if resolve then
184 local resolved, sub, subindex, instance = resolve(specification.name,specification.sub,specification)
185 if resolved then
186 specification.resolved = resolved
187 specification.sub = sub
188 specification.subindex = subindex
189
190 if instance then
191 specification.instance = instance
192 local features = specification.features
193 if not features then
194 features = { }
195 specification.features = features
196 end
197 local normal = features.normal
198 if not normal then
199 normal = { }
200 features.normal = normal
201 end
202 normal.instance = instance
203 end
204
205 local suffix = lower(suffixonly(resolved))
206 if fonts.formats[suffix] then
207 specification.forced = suffix
208 specification.forcedname = resolved
209 specification.name = removesuffix(resolved)
210 else
211 specification.name = resolved
212 end
213 end
214 else
215 resolvers.file(specification)
216 end
217end
218
219function resolvers.spec(specification)
220 local resolvespec = fonts.names.resolvespec
221 if resolvespec then
222 local resolved, sub, subindex = resolvespec(specification.name,specification.sub,specification)
223 if resolved then
224 specification.resolved = resolved
225 specification.sub = sub
226 specification.subindex = subindex
227 specification.forced = lower(suffixonly(resolved))
228 specification.forcedname = resolved
229 specification.name = removesuffix(resolved)
230 end
231 else
232 resolvers.name(specification)
233 end
234end
235
236function definers.resolve(specification)
237 if not specification.resolved or specification.resolved == "" then
238 local r = resolvers[specification.lookup]
239 if r then
240 r(specification)
241 end
242 end
243 if specification.forced == "" then
244 specification.forced = nil
245 specification.forcedname = nil
246 end
247 specification.hash = lower(specification.name .. ' @ ' .. constructors.hashfeatures(specification))
248 if specification.sub and specification.sub ~= "" then
249 specification.hash = specification.sub .. ' @ ' .. specification.hash
250 end
251 return specification
252end
253
254
255
256
257
258
259
260
261
262
263
264
265
266function definers.applypostprocessors(tfmdata)
267 local postprocessors = tfmdata.postprocessors
268 if postprocessors then
269 local properties = tfmdata.properties
270 for i=1,#postprocessors do
271 local extrahash = postprocessors[i](tfmdata)
272 if type(extrahash) == "string" and extrahash ~= "" then
273
274 extrahash = gsub(lower(extrahash),"[^a-z]","-")
275 properties.fullname = formatters["%s-%s"](properties.fullname,extrahash)
276 end
277 end
278 end
279 return tfmdata
280end
281
282
283
284
285
286local function checkfeatures(tfmdata)
287 local resources = tfmdata.resources
288 local shared = tfmdata.shared
289 if resources and shared then
290 local features = resources.features
291 local usedfeatures = shared.features
292 if features and usedfeatures then
293 local usedlanguage = usedfeatures.language or "dflt"
294 local usedscript = usedfeatures.script or "dflt"
295 local function check(what)
296 if what then
297 local foundlanguages = { }
298 for feature, scripts in next, what do
299 if usedscript == "auto" or scripts["*"] then
300
301 elseif not scripts[usedscript] then
302
303
304 else
305 for script, languages in next, scripts do
306 if languages["*"] then
307
308 elseif not languages[usedlanguage] then
309 report_defining("font %!font:name!, feature %a, script %a, no language %a",
310 tfmdata,feature,script,usedlanguage)
311 end
312 end
313 end
314 for script, languages in next, scripts do
315 for language in next, languages do
316 foundlanguages[language] = true
317 end
318 end
319 end
320 if false then
321 foundlanguages["*"] = nil
322 foundlanguages = sortedkeys(foundlanguages)
323 for feature, scripts in sortedhash(what) do
324 for script, languages in next, scripts do
325 if not languages["*"] then
326 for i=1,#foundlanguages do
327 local language = foundlanguages[i]
328 if not languages[language] then
329 report_defining("font %!font:name!, feature %a, script %a, no language %a",
330 tfmdata,feature,script,language)
331 end
332 end
333 end
334 end
335 end
336 end
337 end
338 end
339 check(features.gsub)
340 check(features.gpos)
341 end
342 end
343end
344
345local reported = setmetatableindex(function(t,k)
346 if k then
347 t[k] = true
348 end
349 return false
350end)
351
352function definers.loadfont(specification)
353 local hash = constructors.hashinstance(specification)
354
355 local tfmdata = loadedfonts[hash]
356 local name = specification.name
357 if not tfmdata then
358
359 local forced = specification.forced or ""
360 local id = nextfont(true)
361 specification.id = id
362 if forced ~= "" then
363 local reader = readers[lower(forced)]
364 tfmdata = reader and reader(specification)
365 if not tfmdata and not reported[name] then
366 report_defining("forced type %a of %a not found",forced,name)
367 end
368 else
369 local sequence = readers.sequence
370 for s=1,#sequence do
371 local reader = sequence[s]
372 if readers[reader] then
373 if trace_defining then
374 report_defining("trying (reader sequence driven) type %a for %a with file %a",reader,specification.name,specification.filename)
375 end
376 tfmdata = readers[reader](specification)
377 if tfmdata then
378 break
379 else
380 specification.filename = nil
381 end
382 end
383 end
384 end
385 if tfmdata then
386 tfmdata = definers.applypostprocessors(tfmdata)
387 loadedfonts[hash] = tfmdata
388 designsizes[specification.hash] = tfmdata.parameters.designsize
389 checkfeatures(tfmdata)
390 end
391 end
392 if not tfmdata and not reported[name] then
393 report_defining("font with asked name %a is not found using lookup %a",name,specification.lookup)
394 end
395 return tfmdata
396end
397
398function constructors.readanddefine(name,size)
399 local specification = definers.analyze(name,size)
400 local method = specification.method
401 if method and variants[method] then
402 specification = variants[method](specification)
403 end
404 specification = definers.resolve(specification)
405 local hash = constructors.hashinstance(specification)
406 local id = definers.registered(hash)
407 if not id then
408 local tfmdata = definers.loadfont(specification)
409 if tfmdata then
410 tfmdata.properties.hash = hash
411 id = font.define(tfmdata)
412 definers.register(tfmdata,id)
413 else
414 id = 0
415 end
416 end
417 return fontdata[id], id
418end
419
420
421
422
423
424
425
426
427
428function definers.registered(hash)
429 local id = internalized[hash]
430 return id, id and fontdata[id]
431end
432
433function definers.register(tfmdata,id)
434 if tfmdata and id then
435 local hash = tfmdata.properties.hash
436 if not hash then
437 report_defining("registering font, id %a, name %a, invalid hash",id,tfmdata.properties.filename or "?")
438 elseif not internalized[hash] then
439 internalized[hash] = id
440 if trace_defining then
441 report_defining("registering font, id %s, hash %a",id,hash)
442 end
443 fontdata[id] = tfmdata
444 end
445 end
446end
447
448function definers.read(specification,size,id,compact)
449 statistics.starttiming(fonts)
450 if type(specification) == "string" then
451 specification = definers.analyze(specification,size)
452 end
453 local method = specification.method
454 if method and variants[method] then
455 specification = variants[method](specification)
456 end
457 specification = definers.resolve(specification)
458
459 local hash, extend, squeeze, slant, weight, auto = constructors.hashinstance(specification,false,compact)
460 local tfmdata = definers.registered(hash)
461 local name = specification.name
462 if tfmdata then
463 if trace_defining then
464 report_defining("already hashed: %s",hash)
465 end
466 else
467 tfmdata = definers.loadfont(specification)
468 if tfmdata then
469 tfmdata.original = specification.specification
470 if trace_defining then
471 report_defining("loaded and hashed: %s",hash)
472 end
473 tfmdata.properties.hash = hash
474 if id then
475 definers.register(tfmdata,id)
476 end
477 else
478 if trace_defining then
479 report_defining("not loaded and hashed: %s",hash)
480 end
481 end
482 end
483 if not tfmdata then
484 if not reported[name] then
485 report_defining( "unknown font %a, loading aborted",name)
486 end
487 elseif trace_defining and type(tfmdata) == "table" then
488 local properties = tfmdata.properties or { }
489 local parameters = tfmdata.parameters or { }
490 report_defining("using %a font with id %a, name %a, size %a, fullname %a, filename %a",
491 properties.format or "unknown", id or "-", properties.name, parameters.size,
492 properties.fullname, basename(properties.filename))
493 end
494 statistics.stoptiming(fonts)
495 if compact and weight ~= 0 then
496 local parameters = (tfmtype == "table" and tfmdata or fontdata[tfmdata]).parameters
497 local amount = 65536 * weight
498 if auto then
499 if not squeeze or squeeze == 1 then
500 local xheight = parameters.xheight
501 squeeze = xheight / (xheight + amount)
502 end
503 if not extend or extend == 1 then
504 local emwidth = parameters.quad
505 extend = emwidth / (emwidth + 2 * amount)
506 end
507 end
508 parameters.hshift = amount
509 end
510 return tfmdata, extend, squeeze, slant, weight
511end
512
513function font.getfont(id)
514 return fontdata[id]
515end
516 |