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