1if not modules then modules = { } end modules ['s-fonts-tables'] = {
2 version = 1.001,
3 comment = "companion to s-fonts-tables.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
11moduledata.fonts = moduledata.fonts or { }
12moduledata.fonts.tables = moduledata.fonts.tables or { }
13
14require("font-cft")
15
16local rawget, type = rawget, type
17
18local setmetatableindex = table.setmetatableindex
19local sortedhash = table.sortedhash
20local sortedkeys = table.sortedkeys
21local concat = table.concat
22local insert = table.insert
23local remove = table.remove
24local formatters = string.formatters
25
26local tabletracers = moduledata.fonts.tables
27
28local new_glyph = nodes.pool.glyph
29local copy_node = nodes.copy
30local setlink = nodes.setlink
31local hpack = nodes.hpack
32local applyvisuals = nodes.applyvisuals
33
34local lefttoright_code = (tex.directioncodes and tex.directioncodes.lefttoright) or nodes.dirvalues.lefttoright
35
36local handle_positions = fonts.handlers.otf.datasetpositionprocessor
37local handle_injections = nodes.injections.handler
38
39local context = context
40local ctx_sequence = context.formatted.sequence
41local ctx_char = context.char
42local ctx_setfontid = context.setfontid
43local ctx_type = context.formatted.type
44local ctx_dontleavehmode = context.dontleavehmode
45local ctx_startPair = context.startPair
46local ctx_stopPair = context.stopPair
47local ctx_startSingle = context.startSingle
48local ctx_stopSingle = context.stopSingle
49local ctx_startSingleKern = context.startSingleKern
50local ctx_stopSingleKern = context.stopSingleKern
51local ctx_startPairKern = context.startPairKern
52local ctx_stopPairKern = context.stopPairKern
53
54local ctx_NC = context.NC
55local ctx_NR = context.NR
56
57local digits = {
58 dflt = {
59 dflt = "1234567890 1/2",
60 },
61 arab = {
62 dflt = "",
63 },
64 latn = {
65 dflt = "1234567890 1/2",
66 }
67}
68
69local punctuation = {
70 dflt = {
71 dflt = ". , : ; ? ! ‹ › « »",
72 },
73}
74
75local symbols = {
76 dflt = {
77 dflt = "@ # $ % & * () [] {} <> + - = / |",
78 },
79}
80
81local LATN = "abcdefghijklmnopqrstuvwxyz"
82
83local uppercase = {
84 latn = {
85 dflt = LATN,
86 fra = LATN .. " ÀÁÂÈÉÊÒÓÔÙÚÛÆÇ",
87 },
88 grek = {
89 dftl = "ΑΒΓΔΕΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ",
90 },
91 cyrl= {
92 dflt = "АБВГДЕЖЗИІЙКЛМНОПРСТУФХЦЧШЩЪЫЬѢЭЮЯѲ"
93 },
94}
95
96local latn = "abcdefghijklmnopqrstuvwxyz"
97
98local lowercase = {
99 latn = {
100 dftl = latn,
101 nld = latn .. " ïèéë",
102 deu = latn .. " äöüß",
103 fra = latn .. " àáâèéêòóôùúûæç",
104 },
105 grek = {
106 dftl = "αβγδεηθικλμνξοπρστυφχψω",
107 },
108 cyrl= {
109 dflt = "абвгдежзиійклмнопрстуфхцчшщъыьѣэюяѳ"
110 },
111 arab = {
112 dflt = "ابجدهوزحطيكلمنسعفصقرشتثخذضظغ"
113 },
114}
115
116local samples = {
117 digits = digits,
118 punctuation = punctuation,
119 symbols = symbols,
120 uppercase = uppercase,
121 lowercase = lowercase,
122}
123
124tabletracers.samples = samples
125
126setmetatableindex(uppercase, function(t,k) return rawget(t,"latn") end)
127setmetatableindex(lowercase, function(t,k) return rawget(t,"latn") end)
128setmetatableindex(digits, function(t,k) return rawget(t,"dflt") end)
129setmetatableindex(symbols, function(t,k) return rawget(t,"dflt") end)
130setmetatableindex(punctuation, function(t,k) return rawget(t,"dflt") end)
131
132setmetatableindex(uppercase.latn, function(t,k) return rawget(t,"dflt") end)
133setmetatableindex(uppercase.grek, function(t,k) return rawget(t,"dflt") end)
134setmetatableindex(uppercase.cyrl, function(t,k) return rawget(t,"dflt") end)
135
136setmetatableindex(lowercase.latn, function(t,k) return rawget(t,"dflt") end)
137setmetatableindex(lowercase.grek, function(t,k) return rawget(t,"dflt") end)
138setmetatableindex(lowercase.cyrl, function(t,k) return rawget(t,"dflt") end)
139
140setmetatableindex(digits.dflt, function(t,k) return rawget(t,"dflt") end)
141setmetatableindex(symbols.dflt, function(t,k) return rawget(t,"dflt") end)
142setmetatableindex(punctuation.dflt, function(t,k) return rawget(t,"dflt") end)
143
144
145
146local function checked(specification)
147 specification = interfaces.checkedspecification(specification)
148 local id, cs = fonts.definers.internal(specification,"<module:fonts:features:font>")
149 local tfmdata = fonts.hashes.identifiers[id]
150 local resources = tfmdata.resources
151 return tfmdata, id, resources
152end
153
154local function nothing()
155 context("no entries")
156 context.par()
157end
158
159local function typesettable(t,keys,synonyms,nesting,prefix,depth)
160 if t and next(keys) then
161 if not prefix then
162 context.starttabulate { "|Tl|Tl|Tl|" }
163 end
164 for k, v in sortedhash(keys) do
165 if k == "synonyms" then
166 elseif type(v) ~= "table" then
167 ctx_NC()
168 if prefix then
169 context("%s.%s",prefix,k)
170 else
171 context(k)
172 end
173 ctx_NC()
174
175 local tk = t[k]
176 if v == "<boolean>" then
177 context(tostring(tk or false))
178 elseif not tk then
179 context("<unset>")
180 elseif k == "filename" then
181 context(file.basename(tk))
182
183
184 elseif v == "<scaled>" then
185 context("%p",tk)
186 elseif v == "<table>" then
187 context("<table>")
188 else
189 context(tostring(tk))
190 end
191 ctx_NC()
192 local synonym = (not prefix and synonyms[k]) or (prefix and synonyms[formatters["%s.%s"](prefix,k)])
193 if synonym then
194 context("(% t)",synonym)
195 end
196 ctx_NC()
197 ctx_NR()
198 elseif nesting == false then
199 context("<table>")
200 elseif next(v) then
201 typesettable(t[k],v,synonyms,nesting,k,true)
202 end
203 end
204 if not prefix then
205 context.stoptabulate()
206 end
207 return
208 end
209 if not depth then
210 nothing()
211 end
212end
213
214local function typeset(t,keys,nesting,prefix)
215 local synonyms = keys.synonyms or { }
216 local collected = { }
217 for k, v in next, synonyms do
218 local c = collected[v]
219 if not c then
220 c = { }
221 collected[v] = c
222 end
223 c[#c+1] = k
224 end
225 for k, v in next, collected do
226 table.sort(v)
227 end
228 typesettable(t,keys,collected,nesting,prefix)
229end
230
231tabletracers.typeset = typeset
232
233
234
235
236
237
238
239
240
241
242
243function tabletracers.showproperties(specification)
244 local tfmdata = checked(specification)
245 if tfmdata then
246 typeset(tfmdata.properties,fonts.constructors.keys.properties)
247 else
248 nothing()
249 end
250end
251
252function tabletracers.showparameters(specification)
253 local tfmdata = checked(specification)
254 if tfmdata then
255 typeset(tfmdata.parameters,fonts.constructors.keys.parameters)
256 else
257 nothing()
258 end
259end
260
261local f_u = formatters["%U"]
262local f_p = formatters["%p"]
263
264local function morept(t)
265 local r = { }
266 for i=1,t do
267 r[i] = f_p(t[i])
268 end
269 return concat(r," ")
270end
271
272local function noprefix(kind)
273 kind = string.gsub(kind,"^gpos_","")
274 kind = string.gsub(kind,"^gsub_","")
275 return kind
276end
277
278local function banner(index,i,format,kind,order,chain)
279 if chain then
280 ctx_sequence("sequence: %i, step %i, format: %s, kind: %s, features: % t, chain: %s",
281 index,i,format,noprefix(kind),order,noprefix(chain))
282 else
283 ctx_sequence("sequence: %i, step %i, format: %s, kind: %s, features: % t",
284 index,i,format,noprefix(kind),order)
285 end
286end
287
288function tabletracers.showpositionings(specification)
289
290 local tfmdata, fontid, resources = checked(specification)
291
292 if resources then
293
294 local direction = lefttoright_code
295 local sequences = resources.sequences
296 local marks = resources.marks
297 local visuals = "fontkern,glyph,box"
298
299 local datasets = fonts.handlers.otf.dataset(tfmdata,fontid,0)
300
301 local function process(dataset,sequence,kind,order,chain)
302 local steps = sequence.steps
303 local order = sequence.order or order
304 local index = sequence.index
305 for i=1,#steps do
306 local step = steps[i]
307 local format = step.format
308 banner(index,i,format,kind,order,chain)
309 if kind == "gpos_pair" then
310 local format = step.format
311 if "kern" or format == "move" then
312 for first, seconds in sortedhash(step.coverage) do
313 local done = false
314 local zero = 0
315 for second, kern in sortedhash(seconds) do
316 if kern == 0 then
317 zero = zero + 1
318 else
319 if not done then
320 ctx_startPairKern()
321 end
322 local one = new_glyph(fontid,first)
323 local two = new_glyph(fontid,second)
324 local raw = setlink(copy_node(one),copy_node(two))
325 local pos = setlink(done and one or copy_node(one),copy_node(two))
326 pos, okay = handle_positions(pos,fontid,direction,dataset)
327 pos = handle_injections(pos)
328 applyvisuals(raw,visuals)
329 applyvisuals(pos,visuals)
330 pos = hpack(pos,"exact",nil,direction)
331 raw = hpack(raw,"exact",nil,direction)
332 ctx_NC() if not done then context(f_u(first)) end
333 ctx_NC() if not done then ctx_dontleavehmode() context(one) end
334 ctx_NC() context(f_u(second))
335 ctx_NC() ctx_dontleavehmode() context(two)
336 ctx_NC() context("%p",kern)
337 ctx_NC() ctx_dontleavehmode() context(raw)
338 ctx_NC() ctx_dontleavehmode() context(pos)
339 ctx_NC() ctx_NR()
340 done = true
341 end
342 end
343 if done then
344 ctx_stopPairKern()
345 end
346 if zero > 0 then
347 ctx_type("zero: %s",zero)
348 end
349 end
350 elseif format == "pair" then
351 for first, seconds in sortedhash(step.coverage) do
352 local done = false
353 local allnull = 0
354 local allzero = 0
355 local zeronull = 0
356 local nullzero = 0
357 for second, pair in sortedhash(seconds) do
358 local pfirst = pair[1]
359 local psecond = pair[2]
360 if not pfirst and not psecond then
361 allnull = allnull + 1
362 elseif pfirst == true and psecond == true then
363 allzero = allzero + 1
364 elseif pfirst == true and not psecond then
365 zeronull = zeronull + 1
366 elseif not pfirst and psecond == true then
367 nullzero = nullzero + 1
368 else
369 if pfirst == true then
370 pfirst = "all zero"
371 elseif pfirst then
372 pfirst = morept(pfirst)
373 else
374 pfirst = "no first"
375 end
376 if psecond == true then
377 psecond = "all zero"
378 elseif psecond then
379 psecond = morept(psecond)
380 else
381 psecond = "no second"
382 end
383 if not done then
384 ctx_startPair()
385 end
386 local one = new_glyph(fontid,first)
387 local two = new_glyph(fontid,second)
388 local raw = setlink(copy_node(one),copy_node(two))
389 local pos = setlink(done and one or copy_node(one),copy_node(two))
390 pos, okay = handle_positions(pos,fontid,direction,dataset)
391 pos = handle_injections(pos)
392 applyvisuals(raw,visuals)
393 applyvisuals(pos,visuals)
394 pos = hpack(pos,"exact",nil,direction)
395 raw = hpack(raw,"exact",nil,direction)
396 ctx_NC() if not done then context(f_u(first)) end
397 ctx_NC() if not done then ctx_dontleavehmode() context(one) end
398 ctx_NC() context(f_u(second))
399 ctx_NC() ctx_dontleavehmode() context(two)
400 ctx_NC() context(pfirst)
401 ctx_NC() context(psecond)
402 ctx_NC() ctx_dontleavehmode() context(raw)
403 ctx_NC() ctx_dontleavehmode() context(pos)
404 ctx_NC() ctx_NR()
405 done = true
406 end
407 end
408 if done then
409 ctx_stopPair()
410 end
411 if allnull > 0 or allzero > 0 or zeronull > 0 or nullzero > 0 then
412 ctx_type("both null: %s, both zero: %s, zero and null: %s, null and zero: %s",
413 allnull,allzero,zeronull,nullzero)
414 end
415 end
416 else
417
418 end
419 elseif kind == "gpos_single" then
420 local format = step.format
421 if format == "kern" or format == "move" then
422 local done = false
423 local zero = 0
424 for first, kern in sortedhash(step.coverage) do
425 if kern == 0 then
426 zero = zero + 1
427 else
428 if not done then
429 ctx_startSingleKern()
430 end
431 local one = new_glyph(fontid,first)
432 local raw = copy_node(one)
433 local pos = copy_node(one)
434 pos, okay = handle_positions(pos,fontid,direction,dataset)
435 pos = handle_injections(pos)
436 applyvisuals(raw,visuals)
437 applyvisuals(pos,visuals)
438 pos = hpack(pos,"exact",nil,direction)
439 raw = hpack(raw,"exact",nil,direction)
440 ctx_NC() context(f_u(first))
441 ctx_NC() ctx_dontleavehmode() context(one)
442 ctx_NC() context("%p",kern)
443 ctx_NC() ctx_dontleavehmode() context(raw)
444 ctx_NC() ctx_dontleavehmode() context(pos)
445 ctx_NC() ctx_NR()
446 done = true
447 end
448 end
449 if done then
450 ctx_stopSingleKern()
451 end
452 if zero > 0 then
453 ctx_type("zero: %i",zero)
454 end
455 elseif format == "single" then
456 local done = false
457 local zero = 0
458 local null = 0
459 for first, single in sortedhash(step.coverage) do
460 if single == false then
461 null = null + 1
462 elseif single == true then
463 zero = zero + 1
464 else
465 single = morept(single)
466 if not done then
467 ctx_startSingle()
468 end
469 local one = new_glyph(fontid,first)
470 local raw = copy_node(one)
471 local pos = copy_node(one)
472 pos, okay = handle_positions(pos,fontid,direction,dataset)
473 pos = handle_injections(pos)
474 applyvisuals(raw,visuals)
475 applyvisuals(pos,visuals)
476 raw = hpack(raw,"exact",nil,direction)
477 pos = hpack(pos,"exact",nil,direction)
478 ctx_NC() context(f_u(first))
479 ctx_NC() ctx_dontleavehmode() context(one)
480 ctx_NC() context(single)
481 ctx_NC() ctx_dontleavehmode() context(raw)
482 ctx_NC() ctx_dontleavehmode() context(pos)
483 ctx_NC() ctx_NR()
484 done = true
485 end
486 end
487 if done then
488 ctx_stopSingle()
489 end
490 if null > 0 then
491 if zero > 0 then
492 ctx_type("null: %i, zero: %i",null,zero)
493 else
494 ctx_type("null: %i",null)
495 end
496 else
497 if null > 0 then
498 ctx_type("both zero: %i",zero)
499 end
500 end
501 else
502
503 end
504 end
505 end
506 end
507
508 local done = false
509
510 for d=1,#datasets do
511 local dataset = datasets[d]
512 local sequence = dataset[3]
513 local kind = sequence.type
514 if kind == "gpos_contextchain" or kind == "gpos_context" then
515 local steps = sequence.steps
516 for i=1,#steps do
517 local step = steps[i]
518 local rules = step.rules
519 if rules then
520 for i=1,#rules do
521 local rule = rules[i]
522 local lookups = rule.lookups
523 if lookups then
524 for i=1,#lookups do
525 local lookup = lookups[i]
526 if lookup then
527 local look = lookup[1]
528 local dnik = look.type
529 if dnik == "gpos_pair" or dnik == "gpos_single" then
530 process(dataset,look,dnik,sequence.order,kind)
531 end
532 end
533 end
534 end
535 end
536 end
537 end
538 done = true
539 elseif kind == "gpos_pair" or kind == "gpos_single" then
540 process(dataset,sequence,kind)
541 done = true
542 end
543 end
544
545 if done then
546 return
547 end
548
549 end
550
551 nothing()
552
553end
554
555local dynamics = true
556
557function tabletracers.showsubstitutions(specification)
558
559 local tfmdata, fontid, resources = checked(specification)
560
561 if resources then
562 local features = resources.features
563 if features then
564 local gsub = features.gsub
565 if gsub then
566 local makes_sense = { }
567 for feature, scripts in sortedhash(gsub) do
568 for script, languages in sortedhash(scripts) do
569 for language in sortedhash(languages) do
570 local tag = formatters["dummy-%s-%s-%s"](feature,script,language)
571 local fnt = formatters["file:%s*%s"](file.basename(tfmdata.properties.filename),tag)
572 context.definefontfeature (
573 { tag },
574 {
575 mode = "node",
576 script = script,
577 language = language,
578 [feature] = "yes"
579 }
580 )
581 if not dynamics then
582 context.definefont( { fnt }, { fnt } )
583 end
584 makes_sense[#makes_sense+1] = {
585 feature = feature,
586 tag = tag,
587 script = script,
588 language = language,
589 fontname = fnt,
590 }
591 end
592 end
593 end
594 if #makes_sense > 0 then
595 context.starttabulate { "|Tl|Tl|Tl|p|" }
596 for i=1,#makes_sense do
597 local data = makes_sense[i]
598 local script = data.script
599 local language = data.language
600 ctx_NC()
601 context(data.feature)
602 ctx_NC()
603 context(script)
604 ctx_NC()
605 context(language)
606 ctx_NC()
607 if not dynamics then
608 context.startfont { data.fontname }
609 else
610 context.addff(data.tag)
611 end
612 context.verbatim(samples.lowercase [script][language]) context.par()
613 context.verbatim(samples.uppercase [script][language]) context.par()
614 context.verbatim(samples.digits [script][language]) context.par()
615 context.verbatim(samples.punctuation[script][language]) context.quad()
616 context.verbatim(samples.symbols [script][language])
617 if not dynamics then
618 context.stopfont()
619 end
620 ctx_NC()
621 ctx_NR()
622 end
623 context.stoptabulate()
624 return
625 end
626 end
627 end
628 end
629
630 nothing()
631
632end
633
634function tabletracers.showunicodevariants(specification)
635
636 local tfmdata, fontid, resources = checked(specification)
637
638 if resources then
639
640 local variants = fonts.hashes.variants[fontid]
641
642 if variants then
643 context.starttabulate { "|c|c|c|c|c|c|c|" }
644 for selector, unicodes in sortedhash(variants) do
645 local done = false
646 for unicode, variant in sortedhash(unicodes) do
647 ctx_NC()
648 if not done then
649 context("%U",selector)
650 done = true
651 end
652 ctx_NC()
653 context("%U",unicode)
654 ctx_NC()
655 context("%c",unicode)
656 ctx_NC()
657 context("%U",variant)
658 ctx_NC()
659 context("%c",variant)
660 ctx_NC()
661 context("%c%c",unicode,selector)
662 ctx_NC()
663 context.startoverlay()
664 context("{\\color[trace:r]{%c}}{\\color[trace:ds]{%c}}",unicode,variant)
665 context.stopoverlay()
666 ctx_NC()
667 ctx_NR()
668 end
669 end
670 context.stoptabulate()
671 return
672 end
673
674 end
675
676 nothing()
677
678end
679
680
681local function collectligatures(steps)
682
683
684
685 local series = { }
686 local stack = { }
687 local max = 0
688
689 local function add(v)
690 local n = #stack
691 if n > max then
692 max = n
693 end
694 series[#series+1] = { v, unpack(stack) }
695 end
696
697 local function make(tree)
698 for k, v in sortedhash(tree) do
699 if k == "ligature" then
700 add(v)
701 elseif tonumber(v) then
702 insert(stack,k)
703 add(v)
704 remove(stack)
705 else
706 insert(stack,k)
707 make(v)
708 remove(stack)
709 end
710 end
711 end
712
713 for i=1,#steps do
714 local step = steps[i]
715 local coverage = step.coverage
716 if coverage then
717 make(coverage)
718 end
719 end
720
721 return series, max
722end
723
724local function banner(index,kind,order)
725 ctx_sequence("sequence: %i, kind: %s, features: % t",index,noprefix(kind),order)
726end
727
728function tabletracers.showligatures(specification)
729
730 local tfmdata, fontid, resources = checked(specification)
731
732 if resources then
733
734 local characters = tfmdata.characters
735 local descriptions = tfmdata.descriptions
736 local sequences = resources.sequences
737 if sequences then
738 local done = true
739 for index=1,#sequences do
740 local sequence = sequences[index]
741 local kind = sequence.type
742 if kind == "gsub_ligature" then
743 local list, max = collectligatures(sequence.steps)
744 if #list > 0 then
745 banner(index,kind,sequence.order or { })
746 context.starttabulate { "|T|" .. string.rep("|",max) .. "|T|T|" }
747 for i=1,#list do
748 local s = list[i]
749 local n = #s
750 local u = s[1]
751 local c = characters[u]
752 local d = descriptions[u]
753 ctx_NC()
754 context("%U",u)
755 ctx_NC()
756 ctx_setfontid(fontid)
757 ctx_char(u)
758 ctx_NC()
759 ctx_setfontid(fontid)
760 for i=2,n do
761 ctx_char(s[i])
762 ctx_NC()
763 end
764 for i=n+1,max do
765 ctx_NC()
766 end
767 context(d.name)
768 ctx_NC()
769 context(c.tounicode)
770 ctx_NC()
771 ctx_NR()
772 end
773 context.stoptabulate()
774 done = true
775 end
776 end
777 end
778 if done then
779 return
780 end
781 end
782 end
783
784 nothing()
785
786end
787 |