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