1if not modules then modules = { } end modules ['font-imp-quality'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv and hand-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
9if not context then return end
10
11local next, type, tonumber = next, type, tonumber
12
13local fonts = fonts
14local utilities = utilities
15
16local handlers = fonts.handlers
17local otf = handlers.otf
18local afm = handlers.afm
19local registerotffeature = otf.features.register
20local registerafmfeature = afm.features.register
21
22local allocate = utilities.storage.allocate
23local getparameters = utilities.parsers.getparameters
24
25local implement = interfaces and interfaces.implement
26
27local trace_protrusion = false trackers.register("fonts.protrusion", function(v) trace_protrusion = v end)
28local trace_expansion = false trackers.register("fonts.expansion", function(v) trace_expansion = v end)
29
30local report_expansions = logs.reporter("fonts","expansions")
31local report_protrusions = logs.reporter("fonts","protrusions")
32
33
34
35
36
37local function get_class_and_vector(tfmdata,value,where)
38 local g_where = tfmdata.goodies and tfmdata.goodies[where]
39 local f_where = fonts[where]
40 local g_classes = g_where and g_where.classes
41 local f_classes = f_where and f_where.classes
42 local class = (g_classes and g_classes[value]) or (f_classes and f_classes[value])
43 if class then
44 local class_vector = class.vector
45 local g_vectors = g_where and g_where.vectors
46 local f_vectors = f_where and f_where.vectors
47 local vector = (g_vectors and g_vectors[class_vector]) or (f_vectors and f_vectors[class_vector])
48 return class, vector
49 end
50end
51
52
53
54
55
56local expansions = fonts.expansions or allocate()
57
58fonts.expansions = expansions
59
60local classes = expansions.classes or allocate()
61local vectors = expansions.vectors or allocate()
62
63expansions.classes = classes
64expansions.vectors = vectors
65
66classes.preset = {
67 stretch = 2,
68 shrink = 2,
69 step = .5,
70 factor = 1,
71}
72
73classes['quality'] = {
74 stretch = 2,
75 shrink = 2,
76 step = .5,
77 vector = 'default',
78 factor = 1,
79}
80
81vectors['default'] = {
82 [0x0041] = 0.5,
83 [0x0042] = 0.7,
84 [0x0043] = 0.7,
85 [0x0044] = 0.5,
86 [0x0045] = 0.7,
87 [0x0046] = 0.7,
88 [0x0047] = 0.5,
89 [0x0048] = 0.7,
90 [0x004B] = 0.7,
91 [0x004D] = 0.7,
92 [0x004E] = 0.7,
93 [0x004F] = 0.5,
94 [0x0050] = 0.7,
95 [0x0051] = 0.5,
96 [0x0052] = 0.7,
97 [0x0053] = 0.7,
98 [0x0055] = 0.7,
99 [0x0057] = 0.7,
100 [0x005A] = 0.7,
101 [0x0061] = 0.7,
102 [0x0062] = 0.7,
103 [0x0063] = 0.7,
104 [0x0064] = 0.7,
105 [0x0065] = 0.7,
106 [0x0067] = 0.7,
107 [0x0068] = 0.7,
108 [0x006B] = 0.7,
109 [0x006D] = 0.7,
110 [0x006E] = 0.7,
111 [0x006F] = 0.7,
112 [0x0070] = 0.7,
113 [0x0071] = 0.7,
114 [0x0073] = 0.7,
115 [0x0075] = 0.7,
116 [0x0077] = 0.7,
117 [0x007A] = 0.7,
118 [0x0032] = 0.7,
119 [0x0033] = 0.7,
120 [0x0036] = 0.7,
121 [0x0038] = 0.7,
122 [0x0039] = 0.7,
123}
124
125vectors['quality'] = vectors['default']
126
127local function initialize(tfmdata,value)
128 if value then
129 local class, vector = get_class_and_vector(tfmdata,value,"expansions")
130 if class then
131 if vector then
132 local stretch = class.stretch or 0
133 local shrink = class.shrink or 0
134 local step = class.step or 0
135 local factor = class.factor or 1
136 if trace_expansion then
137 report_expansions("setting class %a, vector %a, factor %a, stretch %a, shrink %a, step %a",
138 value,class.vector,factor,stretch,shrink,step)
139 end
140 tfmdata.parameters.expansion = {
141 stretch = 10 * stretch,
142 shrink = 10 * shrink,
143 step = 10 * step,
144 factor = factor,
145 }
146 local data = characters and characters.data
147 for i, chr in next, tfmdata.characters do
148 local v = vector[i]
149 if data and not v then
150 local d = data[i]
151 if d then
152 local s = d.shcode
153 if not s then
154
155 elseif type(s) == "table" then
156 v = ((vector[s[1]] or 0) + (vector[s[#s]] or 0)) / 2
157 else
158 v = vector[s] or 0
159 end
160 end
161 end
162 if v and v ~= 0 then
163 chr.expansion_factor = v*factor
164 else
165 chr.expansion_factor = factor
166 end
167 end
168 elseif trace_expansion then
169 report_expansions("unknown vector %a in class %a",class.vector,value)
170 end
171 elseif trace_expansion then
172 report_expansions("unknown class %a",value)
173 end
174 end
175end
176
177local specification = {
178 name = "expansion",
179 description = "apply hz optimization",
180 initializers = {
181 base = initialize,
182 node = initialize,
183 }
184}
185
186registerotffeature(specification)
187registerafmfeature(specification)
188
189fonts.goodies.register("expansions", function(...) return fonts.goodies.report("expansions", trace_expansion, ...) end)
190
191implement {
192 name = "setupfontexpansion",
193 arguments = "2 strings",
194 actions = function(class,settings) getparameters(classes,class,'preset',settings) end
195}
196
197
198
199
200
201fonts.protrusions = allocate()
202local protrusions = fonts.protrusions
203
204protrusions.classes = allocate()
205protrusions.vectors = allocate()
206
207local classes = protrusions.classes
208local vectors = protrusions.vectors
209
210
211
212classes.preset = {
213 factor = 1,
214 left = 1,
215 right = 1,
216}
217
218classes['pure'] = { vector = 'pure', factor = 1 }
219classes['punctuation'] = { vector = 'punctuation', factor = 1 }
220classes['alpha'] = { vector = 'alpha', factor = 1 }
221classes['quality'] = { vector = 'quality', factor = 1 }
222
223vectors['pure'] = {
224
225 [0x002C] = { 0, 1 },
226 [0x002E] = { 0, 1 },
227 [0x003A] = { 0, 1 },
228 [0x003B] = { 0, 1 },
229 [0x002D] = { 0, 1 },
230 [0x00AD] = { 0, 1 },
231 [0x2013] = { 0, 0.50 },
232 [0x2014] = { 0, 0.33 },
233 [0x3001] = { 0, 1 },
234 [0x3002] = { 0, 1 },
235 [0x060C] = { 0, 1 },
236 [0x061B] = { 0, 1 },
237 [0x06D4] = { 0, 1 },
238
239}
240
241vectors['punctuation'] = {
242
243 [0x003F] = { 0, 0.20 },
244 [0x00BF] = { 0.20, 0 },
245 [0x0021] = { 0, 0.20 },
246 [0x00A1] = { 0.20, 0, },
247 [0x0028] = { 0.05, 0 },
248 [0x0029] = { 0, 0.05 },
249 [0x005B] = { 0.05, 0 },
250 [0x005D] = { 0, 0.05 },
251 [0x002C] = { 0, 0.70 },
252 [0x002E] = { 0, 0.70 },
253 [0x003A] = { 0, 0.50 },
254 [0x003B] = { 0, 0.50 },
255 [0x002D] = { 0, 0.70 },
256 [0x00AD] = { 0, 0.70 },
257 [0x2013] = { 0, 0.30 },
258 [0x2014] = { 0, 0.20 },
259 [0x060C] = { 0, 0.70 },
260 [0x061B] = { 0, 0.50 },
261 [0x06D4] = { 0, 0.70 },
262 [0x061F] = { 0, 0.20 },
263
264
265
266 [0x2039] = { 0.70, 0.70 },
267 [0x203A] = { 0.70, 0.70 },
268 [0x00AB] = { 0.50, 0.50 },
269 [0x00BB] = { 0.50, 0.50 },
270
271 [0x2018] = { 0.70, 0.70 },
272 [0x2019] = { 0, 0.70 },
273 [0x201A] = { 0.70, 0 },
274 [0x201B] = { 0.70, 0 },
275 [0x201C] = { 0.50, 0.50 },
276 [0x201D] = { 0, 0.50 },
277 [0x201E] = { 0.50, 0 },
278 [0x201F] = { 0.50, 0 },
279
280}
281
282vectors['alpha'] = {
283
284 [0x0041] = { .05, .05 },
285 [0x0046] = { 0, .05 },
286 [0x004A] = { .05, 0 },
287 [0x004B] = { 0, .05 },
288 [0x004C] = { 0, .05 },
289 [0x0054] = { .05, .05 },
290 [0x0056] = { .05, .05 },
291 [0x0057] = { .05, .05 },
292 [0x0058] = { .05, .05 },
293 [0x0059] = { .05, .05 },
294
295 [0x006B] = { 0, .05 },
296 [0x0072] = { 0, .05 },
297 [0x0074] = { 0, .05 },
298 [0x0076] = { .05, .05 },
299 [0x0077] = { .05, .05 },
300 [0x0078] = { .05, .05 },
301 [0x0079] = { .05, .05 },
302
303}
304
305vectors['quality'] = table.merged(
306 vectors['punctuation'],
307 vectors['alpha']
308)
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334classes['double'] = {
335 factor = 2,
336 left = 1,
337 right = 1,
338}
339
340local function map_opbd_onto_protrusion(tfmdata,value,opbd)
341 local characters = tfmdata.characters
342 local descriptions = tfmdata.descriptions
343 local properties = tfmdata.properties
344 local parameters = tfmdata.parameters
345 local resources = tfmdata.resources
346 local rawdata = tfmdata.shared.rawdata
347 local lookuphash = rawdata.lookuphash
348 local lookuptags = resources.lookuptags
349 local script = properties.script
350 local language = properties.language
351 local units = parameters.units
352 local done, factor, left, right = false, 1, 1, 1
353 local class = classes[value]
354 if class then
355 factor = class.factor or 1
356 left = class.left or 1
357 right = class.right or 1
358 else
359 factor = tonumber(value) or 1
360 end
361 local lfactor = left * factor
362 local rfactor = right * factor
363 if trace_protrusion then
364 report_protrusions("left factor %0.3F, right factor %0.3F",lfactor,rfactor)
365 end
366 tfmdata.parameters.protrusion = {
367 factor = factor,
368 left = left,
369 right = right,
370 }
371 if opbd ~= "right" then
372 local validlookups, lookuplist = otf.collectlookups(rawdata,"lfbd",script,language)
373 if validlookups then
374 for i=1,#lookuplist do
375 local lookup = lookuplist[i]
376 local steps = lookup.steps
377 if steps then
378 if trace_protrusion then
379 report_protrusions("setting left using lfbd")
380 end
381 for i=1,#steps do
382 local step = steps[i]
383 local coverage = step.coverage
384 if coverage then
385 for k, v in next, coverage do
386 if v == true then
387
388 else
389 local w = descriptions[k].width
390 local d = - v[1]
391 if w == 0 or d == 0 then
392
393 else
394 local p = lfactor * d/units
395 characters[k].left_protruding = p
396 if trace_protrusion then
397 report_protrusions("lfbd -> %0.3F %C",p,k)
398 end
399 end
400 end
401 end
402 end
403 end
404 done = true
405 end
406 end
407 end
408 end
409 if opbd ~= "left" then
410 local validlookups, lookuplist = otf.collectlookups(rawdata,"rtbd",script,language)
411 if validlookups then
412 for i=1,#lookuplist do
413 local lookup = lookuplist[i]
414 local steps = lookup.steps
415 if steps then
416 if trace_protrusion then
417 report_protrusions("setting right using rtbd")
418 end
419 for i=1,#steps do
420 local step = steps[i]
421 local coverage = step.coverage
422 if coverage then
423 for k, v in next, coverage do
424 if v == true then
425
426 else
427 local w = descriptions[k].width
428 local d = - v[3]
429 if w == 0 or d == 0 then
430
431 else
432 local p = rfactor * d/units
433 characters[k].right_protruding = p
434 if trace_protrusion then
435 report_protrusions("rtbd -> %0.3F %C",p,k)
436 end
437 end
438 end
439 end
440 end
441 end
442 end
443 done = true
444 end
445 end
446 end
447end
448
449
450
451
452
453local function initialize(tfmdata,value)
454 if value then
455 local opbd = tfmdata.shared.features.opbd
456 if opbd then
457
458 map_opbd_onto_protrusion(tfmdata,value,opbd)
459 else
460 local class, vector = get_class_and_vector(tfmdata,value,"protrusions")
461 if class then
462 if vector then
463 local factor = class.factor or 1
464 local left = class.left or 1
465 local right = class.right or 1
466 if trace_protrusion then
467 report_protrusions("setting class %a, vector %a, factor %a, left %a, right %a",
468 value,class.vector,factor,left,right)
469 end
470 local data = characters.data
471 local lfactor = left * factor
472 local rfactor = right * factor
473 if trace_protrusion then
474 report_protrusions("left factor %0.3F, right factor %0.3F",lfactor,rfactor)
475 end
476 tfmdata.parameters.protrusion = {
477 factor = factor,
478 left = left,
479 right = right,
480 }
481 for i, chr in next, tfmdata.characters do
482 local v = vector[i]
483 local pl = nil
484 local pr = nil
485 if v then
486 pl = v[1]
487 pr = v[2]
488 else
489 local d = data[i]
490 if d then
491 local s = d.shcode
492 if not s then
493
494 elseif type(s) == "table" then
495 local vl = vector[s[1]]
496 local vr = vector[s[#s]]
497 if vl then pl = vl[1] end
498 if vr then pr = vr[2] end
499 else
500 v = vector[s]
501 if v then
502 pl = v[1]
503 pr = v[2]
504 end
505 end
506 end
507 end
508 if pl and pl ~= 0 then
509 local p = pl * lfactor
510 chr.left_protruding = p
511 if trace_protrusion then
512 report_protrusions("left -> %0.3F %C ",p,i)
513 end
514 end
515 if pr and pr ~= 0 then
516 local p = pr * rfactor
517 chr.right_protruding = p
518 if trace_protrusion then
519 report_protrusions("right -> %0.3F %C",p,i)
520 end
521 end
522 end
523 elseif trace_protrusion then
524 report_protrusions("unknown vector %a in class %a",class.vector,value)
525 end
526 elseif trace_protrusion then
527 report_protrusions("unknown class %a",value)
528 end
529 end
530 end
531end
532
533local specification = {
534 name = "protrusion",
535 description = "l/r margin character protrusion",
536 initializers = {
537 base = initialize,
538 node = initialize,
539 }
540}
541
542registerotffeature(specification)
543registerafmfeature(specification)
544
545fonts.goodies.register("protrusions", function(...) return fonts.goodies.report("protrusions", trace_protrusion, ...) end)
546
547implement {
548 name = "setupfontprotrusion",
549 arguments = "2 strings",
550 actions = function(class,settings) getparameters(classes,class,'preset',settings) end
551}
552
553local function initialize(tfmdata,value)
554 local properties = tfmdata.properties
555 local parameters = tfmdata.parameters
556 if properties then
557 value = tonumber(value)
558 if value then
559 if value < 0 then
560 value = 0
561 elseif value > 10 then
562 report_expansions("threshold for %a @ %p limited to 10 pct",properties.fontname,parameters.size)
563 value = 10
564 end
565 if value > 5 then
566 report_expansions("threshold for %a @ %p exceeds 5 pct",properties.fontname,parameters.size)
567 end
568 end
569 properties.threshold = value or nil
570 end
571end
572
573local specification = {
574 name = "threshold",
575 description = "threshold for quality features",
576 initializers = {
577 base = initialize,
578 node = initialize,
579 }
580}
581
582registerotffeature(specification)
583registerafmfeature(specification)
584 |