1if not modules then modules = { } end modules ['font-oup'] = {
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
9local next, type = next, type
10local P, R, S = lpeg.P, lpeg.R, lpeg.S
11local lpegmatch = lpeg.match
12local insert, remove, copy, unpack = table.insert, table.remove, table.copy, table.unpack
13local find = string.find
14
15local formatters = string.formatters
16local sortedkeys = table.sortedkeys
17local sortedhash = table.sortedhash
18local tohash = table.tohash
19local setmetatableindex = table.setmetatableindex
20
21local report_error = logs.reporter("otf reader","error")
22local report_markwidth = logs.reporter("otf reader","markwidth")
23local report_cleanup = logs.reporter("otf reader","cleanup")
24local report_optimizations = logs.reporter("otf reader","merges")
25local report_unicodes = logs.reporter("otf reader","unicodes")
26
27local trace_markwidth = false trackers.register("otf.markwidth", function(v) trace_markwidth = v end)
28local trace_cleanup = false trackers.register("otf.cleanups", function(v) trace_cleanups = v end)
29local trace_optimizations = false trackers.register("otf.optimizations", function(v) trace_optimizations = v end)
30local trace_unicodes = false trackers.register("otf.unicodes", function(v) trace_unicodes = v end)
31
32local readers = fonts.handlers.otf.readers
33local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000
34
35local f_private = formatters["P%05X"]
36local f_unicode = formatters["U%05X"]
37local f_index = formatters["I%05X"]
38local f_character_y = formatters["%C"]
39local f_character_n = formatters["[ %C ]"]
40
41local check_duplicates = true
42local check_soft_hyphen = context
43
44directives.register("otf.checksofthyphen",function(v)
45 check_soft_hyphen = v
46end)
47
48
49
50
51local function replaced(list,index,replacement)
52 if type(list) == "number" then
53 return replacement
54 elseif type(replacement) == "table" then
55 local t = { }
56 local n = index-1
57 for i=1,n do
58 t[i] = list[i]
59 end
60 for i=1,#replacement do
61 n = n + 1
62 t[n] = replacement[i]
63 end
64 for i=index+1,#list do
65 n = n + 1
66 t[n] = list[i]
67 end
68 else
69 list[index] = replacement
70 return list
71 end
72end
73
74local function unifyresources(fontdata,indices)
75 local descriptions = fontdata.descriptions
76 local resources = fontdata.resources
77 if not descriptions or not resources then
78 return
79 end
80
81 local nofindices = #indices
82
83 local variants = fontdata.resources.variants
84 if variants then
85 for selector, unicodes in next, variants do
86 for unicode, index in next, unicodes do
87 unicodes[unicode] = indices[index]
88 end
89 end
90 end
91
92 local function remark(marks)
93 if marks then
94 local newmarks = { }
95 for k, v in next, marks do
96 local u = indices[k]
97 if u then
98 newmarks[u] = v
99 elseif trace_optimizations then
100 report_optimizations("discarding mark %i",k)
101 end
102 end
103 return newmarks
104 end
105 end
106
107 local marks = resources.marks
108 if marks then
109 resources.marks = remark(marks)
110 end
111
112 local markclasses = resources.markclasses
113 if markclasses then
114 for class, marks in next, markclasses do
115 markclasses[class] = remark(marks)
116 end
117 end
118
119 local marksets = resources.marksets
120 if marksets then
121 for class, marks in next, marksets do
122 marksets[class] = remark(marks)
123 end
124 end
125
126 local done = { }
127
128 local duplicates = check_duplicates and resources.duplicates
129 if duplicates and not next(duplicates) then
130 duplicates = false
131 end
132
133 local function recover(cover)
134 for i=1,#cover do
135 local c = cover[i]
136 if not done[c] then
137 local t = { }
138 for k, v in next, c do
139 local ug = indices[k]
140 if ug then
141 t[ug] = v
142 else
143 report_error("case %i, bad index in unifying %s: %s of %s",1,"coverage",k,nofindices)
144 end
145 end
146 cover[i] = t
147 done[c] = d
148 end
149 end
150 end
151
152 local function recursed(c,kind)
153 local t = { }
154 for g, d in next, c do
155 if type(d) == "table" then
156 local ug = indices[g]
157 if ug then
158 t[ug] = recursed(d,kind)
159 else
160 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g,nofindices)
161 end
162 else
163 t[g] = indices[d]
164 end
165 end
166 return t
167 end
168
169
170
171
172 local function unifythem(sequences)
173 if not sequences then
174 return
175 end
176 for i=1,#sequences do
177 local sequence = sequences[i]
178 local kind = sequence.type
179 local steps = sequence.steps
180 local features = sequence.features
181 if steps then
182 for i=1,#steps do
183 local step = steps[i]
184 if kind == "gsub_single" then
185 local c = step.coverage
186 if c then
187 local t1 = done[c]
188 if not t1 then
189 t1 = { }
190 if duplicates then
191 for g1, d1 in next, c do
192 local ug1 = indices[g1]
193 if ug1 then
194 local ud1 = indices[d1]
195 if ud1 then
196 t1[ug1] = ud1
197 local dg1 = duplicates[ug1]
198 if dg1 then
199 for u in next, dg1 do
200 t1[u] = ud1
201 end
202 end
203 else
204 report_error("case %i, bad index in unifying %s: %s of %s",3,kind,d1,nofindices)
205 end
206 else
207 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
208 end
209 end
210 else
211 for g1, d1 in next, c do
212 local ug1 = indices[g1]
213 if ug1 then
214 t1[ug1] = indices[d1]
215 else
216 report_error("fuzzy case %i in unifying %s: %i",2,kind,g1)
217 end
218 end
219 end
220 done[c] = t1
221 end
222 step.coverage = t1
223 end
224 elseif kind == "gpos_pair" then
225 local c = step.coverage
226 if c then
227 local t1 = done[c]
228 if not t1 then
229 t1 = { }
230 for g1, d1 in next, c do
231 local ug1 = indices[g1]
232 if ug1 then
233 local t2 = done[d1]
234 if not t2 then
235 t2 = { }
236 for g2, d2 in next, d1 do
237 local ug2 = indices[g2]
238 if ug2 then
239 t2[ug2] = d2
240 else
241 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g2,nofindices,nofindices)
242 end
243 end
244 done[d1] = t2
245 end
246 t1[ug1] = t2
247 else
248 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
249 end
250 end
251 done[c] = t1
252 end
253 step.coverage = t1
254 end
255 elseif kind == "gsub_ligature" then
256 local c = step.coverage
257 if c then
258 step.coverage = recursed(c,kind)
259 end
260 elseif kind == "gsub_alternate" or kind == "gsub_multiple" then
261 local c = step.coverage
262 if c then
263 local t1 = done[c]
264 if not t1 then
265 t1 = { }
266 if duplicates then
267 for g1, d1 in next, c do
268 for i=1,#d1 do
269 local d1i = d1[i]
270 local d1u = indices[d1i]
271 if d1u then
272 d1[i] = d1u
273 else
274 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,i,d1i,nofindices)
275 end
276 end
277 local ug1 = indices[g1]
278 if ug1 then
279 t1[ug1] = d1
280 local dg1 = duplicates[ug1]
281 if dg1 then
282 for u in next, dg1 do
283 t1[u] = copy(d1)
284 end
285 end
286 else
287 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
288 end
289 end
290 else
291 for g1, d1 in next, c do
292 for i=1,#d1 do
293 local d1i = d1[i]
294 local d1u = indices[d1i]
295 if d1u then
296 d1[i] = d1u
297 else
298 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,d1i,nofindices)
299 end
300 end
301 t1[indices[g1]] = d1
302 end
303 end
304 done[c] = t1
305 end
306 step.coverage = t1
307 end
308 elseif kind == "gpos_single" then
309 local c = step.coverage
310 if c then
311 local t1 = done[c]
312 if not t1 then
313 t1 = { }
314 if duplicates then
315 for g1, d1 in next, c do
316 local ug1 = indices[g1]
317 if ug1 then
318 t1[ug1] = d1
319 local dg1 = duplicates[ug1]
320 if dg1 then
321 for u in next, dg1 do
322 t1[u] = d1
323 end
324 end
325 else
326 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
327 end
328 end
329 else
330 for g1, d1 in next, c do
331 local ug1 = indices[g1]
332 if ug1 then
333 t1[ug1] = d1
334 else
335 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
336 end
337 end
338 end
339 done[c] = t1
340 end
341 step.coverage = t1
342 end
343 elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" or kind == "gpos_mark2ligature" then
344 local c = step.coverage
345 if c then
346 local t1 = done[c]
347 if not t1 then
348 t1 = { }
349 for g1, d1 in next, c do
350 local ug1 = indices[g1]
351 if ug1 then
352 t1[ug1] = d1
353 else
354 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
355 end
356 end
357 done[c] = t1
358 end
359 step.coverage = t1
360 end
361 local c = step.baseclasses
362 if c then
363 local t1 = done[c]
364 if not t1 then
365 for g1, d1 in next, c do
366 local t2 = done[d1]
367 if not t2 then
368 t2 = { }
369 for g2, d2 in next, d1 do
370 local ug2 = indices[g2]
371 if ug2 then
372 t2[ug2] = d2
373 else
374 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g2,nofindices)
375 end
376 end
377 done[d1] = t2
378 end
379 c[g1] = t2
380 end
381 done[c] = c
382 end
383 end
384 elseif kind == "gpos_cursive" then
385 local c = step.coverage
386 if c then
387 local t1 = done[c]
388 if not t1 then
389 t1 = { }
390 if duplicates then
391 for g1, d1 in next, c do
392 local ug1 = indices[g1]
393 if ug1 then
394 t1[ug1] = d1
395
396 local dg1 = duplicates[ug1]
397 if dg1 then
398
399 for u in next, dg1 do
400 t1[u] = copy(d1)
401 end
402 end
403 else
404 report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
405 end
406 end
407 else
408 for g1, d1 in next, c do
409 local ug1 = indices[g1]
410 if ug1 then
411 t1[ug1] = d1
412 else
413 report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
414 end
415 end
416 end
417 done[c] = t1
418 end
419 step.coverage = t1
420 end
421 end
422
423 local rules = step.rules
424 if rules then
425 for i=1,#rules do
426 local rule = rules[i]
427
428 local before = rule.before if before then recover(before) end
429 local after = rule.after if after then recover(after) end
430 local current = rule.current if current then recover(current) end
431
432 local replacements = rule.replacements
433 if replacements then
434 if not done[replacements] then
435 local r = { }
436 for k, v in next, replacements do
437 r[indices[k]] = indices[v]
438 end
439 rule.replacements = r
440 done[replacements] = r
441 end
442 end
443 end
444 end
445 end
446 end
447 end
448 end
449
450 unifythem(resources.sequences)
451 unifythem(resources.sublookups)
452end
453
454local function copyduplicates(fontdata)
455 if check_duplicates then
456 local descriptions = fontdata.descriptions
457 local resources = fontdata.resources
458 local duplicates = resources.duplicates
459 if check_soft_hyphen then
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 local dh = descriptions[0x2D]
476 if dh then
477 local ds = descriptions[0xAD]
478 if not ds or ds.width ~= dh.width then
479 descriptions[0xAD] = nil
480 if ds then
481 if trace_unicodes then
482 report_unicodes("patching soft hyphen")
483 end
484 else
485 if trace_unicodes then
486 report_unicodes("adding soft hyphen")
487 end
488 end
489 if not duplicates then
490 duplicates = { }
491 resources.duplicates = duplicates
492 end
493 local d = duplicates[0x2D]
494 if d then
495 d[0xAD] = true
496 else
497 duplicates[0x2D] = { [0xAD] = true }
498 end
499 end
500 end
501
502 end
503 if duplicates then
504 for u, d in next, duplicates do
505 local du = descriptions[u]
506 if du then
507 local t = { f_character_y(u), "@", f_index(du.index), "->" }
508 local n = 0
509 local m = 25
510 for u in next, d do
511 if descriptions[u] then
512 if n < m then
513 t[n+4] = f_character_n(u)
514 end
515 else
516 local c = copy(du)
517 c.unicode = u
518 descriptions[u] = c
519 if n < m then
520 t[n+4] = f_character_y(u)
521 end
522 end
523 n = n + 1
524 end
525 if trace_unicodes then
526 if n <= m then
527 report_unicodes("%i : % t",n,t)
528 else
529 report_unicodes("%i : % t ...",n,t)
530 end
531 end
532 else
533
534 end
535 end
536 end
537 end
538end
539
540local ignore = {
541 ["notdef"] = true,
542 [".notdef"] = true,
543 ["null"] = true,
544 [".null"] = true,
545 ["nonmarkingreturn"] = true,
546}
547
548
549local function checklookups(fontdata,missing,nofmissing)
550 local descriptions = fontdata.descriptions
551 local resources = fontdata.resources
552 if missing and nofmissing and nofmissing <= 0 then
553 return
554 end
555
556 local singles = { }
557 local alternates = { }
558 local ligatures = { }
559
560 if not missing then
561 missing = { }
562 nofmissing = 0
563 for u, d in next, descriptions do
564 if not d.unicode then
565 nofmissing = nofmissing + 1
566 missing[u] = true
567 end
568 end
569 end
570 local function collectthem(sequences)
571 if not sequences then
572 return
573 end
574 for i=1,#sequences do
575 local sequence = sequences[i]
576 local kind = sequence.type
577 local steps = sequence.steps
578 if steps then
579 for i=1,#steps do
580 local step = steps[i]
581 if kind == "gsub_single" then
582 local c = step.coverage
583 if c then
584 singles[#singles+1] = c
585 end
586 elseif kind == "gsub_alternate" then
587 local c = step.coverage
588 if c then
589 alternates[#alternates+1] = c
590 end
591 elseif kind == "gsub_ligature" then
592 local c = step.coverage
593 if c then
594 ligatures[#ligatures+1] = c
595 end
596 end
597 end
598 end
599 end
600 end
601
602 collectthem(resources.sequences)
603 collectthem(resources.sublookups)
604
605 local loops = 0
606 while true do
607 loops = loops + 1
608 local old = nofmissing
609 for i=1,#singles do
610 local c = singles[i]
611 for g1, g2 in next, c do
612 if missing[g1] then
613 local u2 = descriptions[g2].unicode
614 if u2 then
615 missing[g1] = false
616 descriptions[g1].unicode = u2
617 nofmissing = nofmissing - 1
618 end
619 end
620 if missing[g2] then
621 local u1 = descriptions[g1].unicode
622 if u1 then
623 missing[g2] = false
624 descriptions[g2].unicode = u1
625 nofmissing = nofmissing - 1
626 end
627 end
628 end
629 end
630 for i=1,#alternates do
631 local c = alternates[i]
632
633 for g1, d1 in next, c do
634 if missing[g1] then
635 for i=1,#d1 do
636 local g2 = d1[i]
637 local u2 = descriptions[g2].unicode
638 if u2 then
639 missing[g1] = false
640 descriptions[g1].unicode = u2
641 nofmissing = nofmissing - 1
642 end
643 end
644 end
645 if not missing[g1] then
646 for i=1,#d1 do
647 local g2 = d1[i]
648 if missing[g2] then
649 local u1 = descriptions[g1].unicode
650 if u1 then
651 missing[g2] = false
652 descriptions[g2].unicode = u1
653 nofmissing = nofmissing - 1
654 end
655 end
656 end
657 end
658 end
659 end
660 if nofmissing <= 0 then
661 if trace_unicodes then
662 report_unicodes("all missings done in %s loops",loops)
663 end
664 return
665 elseif old == nofmissing then
666 break
667 end
668 end
669
670 local t, n
671
672 local function recursed(c)
673 for g, d in next, c do
674 if g ~= "ligature" then
675 local u = descriptions[g].unicode
676 if u then
677 n = n + 1
678 t[n] = u
679 recursed(d)
680 n = n - 1
681 end
682 elseif missing[d] then
683 local l = { }
684 local m = 0
685 for i=1,n do
686 local u = t[i]
687 if type(u) == "table" then
688 for i=1,#u do
689 m = m + 1
690 l[m] = u[i]
691 end
692 else
693 m = m + 1
694 l[m] = u
695 end
696 end
697 missing[d] = false
698 descriptions[d].unicode = l
699 nofmissing = nofmissing - 1
700 end
701 end
702 end
703
704 if nofmissing > 0 then
705 t = { }
706 n = 0
707 local loops = 0
708 while true do
709 loops = loops + 1
710 local old = nofmissing
711 for i=1,#ligatures do
712 recursed(ligatures[i])
713 end
714 if nofmissing <= 0 then
715 if trace_unicodes then
716 report_unicodes("all missings done in %s loops",loops)
717 end
718 return
719 elseif old == nofmissing then
720 break
721 end
722 end
723 t = nil
724 n = 0
725 end
726
727 if trace_unicodes and nofmissing > 0 then
728 local done = { }
729 for i, r in next, missing do
730 if r then
731 local data = descriptions[i]
732 local name = data and data.name or f_index(i)
733 if not ignore[name] then
734 done[name] = true
735 end
736 end
737 end
738 if next(done) then
739 report_unicodes("not unicoded: % t",sortedkeys(done))
740 end
741 end
742
743 for k, v in next, descriptions do
744 local math = v.math
745 if math then
746 local variants = math.variants
747 local parts = math.parts
748 local unicode = v.unicode
749 if variants then
750 if unicode then
751 for i=1,#variants do
752 local v = descriptions[variants[i]]
753 if not v then
754
755 elseif v.unicode then
756
757 else
758 v.unicode = unicode
759 end
760 end
761 end
762 end
763 if parts then
764 parts[#parts//2+1].unicode = unicode
765 end
766 end
767 end
768
769end
770
771local firstprivate <const> = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
772local puafirst <const> = 0xE000
773local pualast <const> = 0xF8FF
774
775local function unifymissing(fontdata)
776 if not fonts.mappings then
777 require("font-map")
778 require("font-agl")
779 end
780 local unicodes = { }
781 local resources = fontdata.resources
782 resources.unicodes = unicodes
783 for unicode, d in next, fontdata.descriptions do
784 if unicode < privateoffset then
785 if unicode >= puafirst and unicode <= pualast then
786
787 else
788 local name = d.name
789 if name then
790 unicodes[name] = unicode
791 end
792 end
793 else
794
795 end
796 end
797 fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups)
798 resources.unicodes = nil
799end
800
801local function unifyglyphs(fontdata,usenames)
802 local private = fontdata.private or privateoffset
803 local glyphs = fontdata.glyphs
804 local indices = { }
805 local descriptions = { }
806 local names = usenames and { }
807 local resources = fontdata.resources
808 local zero = glyphs[0]
809 local zerocode = zero.unicode
810 local nofglyphs = #glyphs
811 if not zerocode then
812 zerocode = private
813 zero.unicode = zerocode
814 private = private + 1
815 end
816 descriptions[zerocode] = zero
817 if names then
818 local name = glyphs[0].name or f_private(zerocode)
819 indices[0] = name
820 names[name] = zerocode
821 else
822 indices[0] = zerocode
823 end
824
825 if names then
826
827 for index=1,nofglyphs do
828 local glyph = glyphs[index]
829 local unicode = glyph.unicode
830 if not unicode then
831 unicode = private
832 local name = glyph.name or f_private(unicode)
833 indices[index] = name
834 names[name] = unicode
835 private = private + 1
836 elseif unicode >= firstprivate then
837 unicode = private
838 local name = glyph.name or f_private(unicode)
839 indices[index] = name
840 names[name] = unicode
841 private = private + 1
842 elseif unicode >= puafirst and unicode <= pualast then
843 local name = glyph.name or f_private(unicode)
844 indices[index] = name
845 names[name] = unicode
846 elseif descriptions[unicode] then
847 unicode = private
848 local name = glyph.name or f_private(unicode)
849 indices[index] = name
850 names[name] = unicode
851 private = private + 1
852 else
853 local name = glyph.name or f_unicode(unicode)
854 indices[index] = name
855 names[name] = unicode
856 end
857 descriptions[unicode] = glyph
858 end
859 elseif trace_unicodes then
860 for index=1,nofglyphs do
861 local glyph = glyphs[index]
862 local unicode = glyph.unicode
863 if not unicode then
864 unicode = private
865 indices[index] = unicode
866 private = private + 1
867 elseif unicode >= firstprivate then
868 local name = glyph.name
869 if name then
870 report_unicodes("moving glyph %a indexed %05X from private %U to %U ",name,index,unicode,private)
871 else
872 report_unicodes("moving glyph indexed %05X from private %U to %U ",index,unicode,private)
873 end
874 unicode = private
875 indices[index] = unicode
876 private = private + 1
877 elseif unicode >= puafirst and unicode <= pualast then
878 local name = glyph.name
879 if name then
880 report_unicodes("keeping private unicode %U for glyph %a indexed %05X",unicode,name,index)
881 else
882 report_unicodes("keeping private unicode %U for glyph indexed %05X",unicode,index)
883 end
884 indices[index] = unicode
885 elseif descriptions[unicode] then
886 local name = glyph.name
887 if name then
888 report_unicodes("assigning duplicate unicode %U to %U for glyph %a indexed %05X ",unicode,private,name,index)
889 else
890 report_unicodes("assigning duplicate unicode %U to %U for glyph indexed %05X ",unicode,private,index)
891 end
892 unicode = private
893 indices[index] = unicode
894 private = private + 1
895 else
896 indices[index] = unicode
897 end
898 descriptions[unicode] = glyph
899 end
900 else
901 for index=1,nofglyphs do
902 local glyph = glyphs[index]
903 local unicode = glyph.unicode
904 if not unicode then
905 unicode = private
906 indices[index] = unicode
907 private = private + 1
908 elseif unicode >= firstprivate then
909 local name = glyph.name
910 unicode = private
911 indices[index] = unicode
912 private = private + 1
913 elseif unicode >= puafirst and unicode <= pualast then
914 local name = glyph.name
915 indices[index] = unicode
916 elseif descriptions[unicode] then
917 local name = glyph.name
918 unicode = private
919 indices[index] = unicode
920 private = private + 1
921 else
922 indices[index] = unicode
923 end
924 descriptions[unicode] = glyph
925 end
926 end
927
928 if LUATEXENGINE == "luametatex" then
929 for index=1,nofglyphs do
930 local math = glyphs[index].math
931 if math then
932 local list = math.parts
933 if list then
934 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
935 end
936 local list = math.variants
937 if list then
938 for i=1,#list do list[i] = indices[list[i]] end
939 end
940 end
941 end
942 else
943 for index=1,nofglyphs do
944 local math = glyphs[index].math
945 if math then
946 local list = math.vparts
947 if list then
948 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
949 end
950 local list = math.hparts
951 if list then
952 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
953 end
954 local list = math.vvariants
955 if list then
956
957 for i=1,#list do list[i] = indices[list[i]] end
958 end
959 local list = math.hvariants
960 if list then
961
962 for i=1,#list do list[i] = indices[list[i]] end
963 end
964 end
965 end
966 end
967
968 local colorpalettes = resources.colorpalettes
969 if colorpalettes then
970 for index=1,nofglyphs do
971 local colors = glyphs[index].colors
972 if colors then
973 for i=1,#colors do
974 local c = colors[i]
975 if c then
976 c.slot = indices[c.slot]
977 end
978 end
979 end
980 end
981 end
982
983 fontdata.private = private
984 fontdata.glyphs = nil
985 fontdata.names = names
986 fontdata.descriptions = descriptions
987 fontdata.hashmethod = hashmethod
988 fontdata.nofglyphs = nofglyphs
989
990 return indices, names
991end
992
993local stripredundant do
994
995 local p_hex = R("af","AF","09")
996 local p_digit = R("09")
997 local p_done = S("._-")^0 + P(-1)
998
999 local p_style = P(".")
1000 local p_alpha = R("az","AZ")
1001 local p_ALPHA = R("AZ")
1002
1003 local p_crappyname = (
1004
1005 lpeg.utfchartabletopattern({ "uni", "u" },true)
1006 * S("Xx_")^0
1007 * p_hex^1
1008
1009 + lpeg.utfchartabletopattern({ "identity", "glyph", "jamo" },true)
1010 * p_hex^1
1011
1012 + lpeg.utfchartabletopattern({ "index", "afii" }, true)
1013 * p_digit^1
1014
1015 + p_digit
1016 * p_hex^3
1017 + p_alpha
1018 * p_digit^1
1019
1020 + P("aj")
1021 * p_digit^1
1022 + P("eh_")
1023 * (p_digit^1 + p_ALPHA * p_digit^1)
1024 + (1-P("_"))^1
1025 * P("_uni")
1026 * p_hex^1
1027 + P("_")
1028 * P(1)^1
1029 ) * p_done
1030
1031
1032
1033
1034 if context then
1035
1036 local forcekeep = false
1037
1038
1039 directives.register("otf.keepnames",function(v)
1040 report_cleanup("keeping weird glyph names, expect larger files and more memory usage")
1041 forcekeep = v
1042 end)
1043
1044
1045
1046
1047
1048
1049
1050 local function stripvariants(descriptions,list)
1051 local n = list and #list or 0
1052 if n > 0 then
1053 for i=1,n do
1054 local g = list[i]
1055 if g then
1056 local d = descriptions[g]
1057 if d and d.name then
1058 d.name = nil
1059 n = n + 1
1060 end
1061 end
1062 end
1063 end
1064 return n
1065 end
1066
1067 local function stripparts(descriptions,list)
1068 local n = list and #list or 0
1069 if n > 0 then
1070 for i=1,n do
1071 local g = list[i].glyph
1072 if g then
1073 local d = descriptions[g]
1074 if d and d.name then
1075 d.name = nil
1076 n = n + 1
1077 end
1078 end
1079 end
1080 end
1081 return n
1082 end
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120 local function collectsimple(fontdata)
1121 return nil
1122 end
1123
1124 stripredundant = function(fontdata)
1125 local descriptions = fontdata.descriptions
1126 if descriptions then
1127 local n = 0
1128 local c = 0
1129 for unicode, d in next, descriptions do
1130 local m = d.math
1131 if m then
1132 n = n + stripvariants(descriptions,m.vvariants)
1133 n = n + stripvariants(descriptions,m.hvariants)
1134 n = n + stripparts (descriptions,m.vparts)
1135 n = n + stripparts (descriptions,m.hparts)
1136 end
1137 end
1138 if forcekeep then
1139 for unicode, d in next, descriptions do
1140 if d.class == "base" then
1141 d.class = nil
1142 c = c + 1
1143 end
1144 end
1145 else
1146 local keeplist = collectsimple(fontdata)
1147 for unicode, d in next, descriptions do
1148 local name = d.name
1149 if name then
1150
1151 if keeplist and keeplist[name] then
1152
1153 elseif lpegmatch(p_crappyname,name) then
1154 d.name = nil
1155 n = n + 1
1156 end
1157 end
1158 if d.class == "base" then
1159 d.class = nil
1160 c = c + 1
1161 end
1162 end
1163 end
1164 if trace_cleanup then
1165 if n > 0 then
1166 report_cleanup("%s bogus names removed (verbose unicode)",n)
1167 end
1168 if c > 0 then
1169 report_cleanup("%s base class tags removed (default is base)",c)
1170 end
1171 end
1172 end
1173 end
1174
1175 else
1176
1177 stripredundant = function(fontdata)
1178 local descriptions = fontdata.descriptions
1179 if descriptions then
1180 if fonts.privateoffsets.keepnames then
1181 for unicode, d in next, descriptions do
1182 if d.class == "base" then
1183 d.class = nil
1184 end
1185 end
1186 else
1187 for unicode, d in next, descriptions do
1188 local name = d.name
1189 if name then
1190 if lpegmatch(p_crappyname,name) then
1191 d.name = nil
1192 end
1193 end
1194 if d.class == "base" then
1195 d.class = nil
1196 end
1197 end
1198 end
1199 end
1200 end
1201
1202 end
1203
1204 readers.stripredundant = stripredundant
1205
1206end
1207
1208function readers.getcomponents(fontdata)
1209 local resources = fontdata.resources
1210 if resources then
1211 local sequences = resources.sequences
1212 if sequences then
1213 local collected = { }
1214 for i=1,#sequences do
1215 local sequence = sequences[i]
1216 if sequence.type == "gsub_ligature" then
1217 local steps = sequence.steps
1218 if steps then
1219 local l = { }
1220 local function traverse(p,k,v)
1221 if k == "ligature" then
1222 collected[v] = { unpack(l) }
1223 elseif tonumber(v) then
1224 insert(l,k)
1225 collected[v] = { unpack(l) }
1226 remove(l)
1227 else
1228 insert(l,k)
1229 for k, vv in next, v do
1230 traverse(p,k,vv)
1231 end
1232 remove(l)
1233 end
1234 end
1235 for i=1,#steps do
1236
1237 local c = steps[i].coverage
1238 if c then
1239 for k, v in next, c do
1240 traverse(k,k,v)
1241 end
1242 end
1243 end
1244 end
1245 end
1246 end
1247 if next(collected) then
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258 while true do
1259 local done = false
1260 for k, v in next, collected do
1261 for i=1,#v do
1262 local vi = v[i]
1263 if vi == k then
1264
1265 collected[k] = nil
1266 break
1267 else
1268 local c = collected[vi]
1269 if c then
1270 done = true
1271 local t = { }
1272 local n = i - 1
1273 for j=1,n do
1274 t[j] = v[j]
1275 end
1276 for j=1,#c do
1277 n = n + 1
1278 t[n] = c[j]
1279 end
1280 for j=i+1,#v do
1281 n = n + 1
1282 t[n] = v[j]
1283 end
1284 collected[k] = t
1285 break
1286 end
1287 end
1288 end
1289 end
1290 if not done then
1291 break
1292 end
1293 end
1294 return collected
1295 end
1296 end
1297 end
1298end
1299
1300readers.unifymissing = unifymissing
1301
1302function readers.rehash(fontdata,hashmethod)
1303 if not (fontdata and fontdata.glyphs) then
1304 return
1305 elseif hashmethod == "indices" then
1306 fontdata.hashmethod = "indices"
1307 elseif hashmethod == "names" then
1308 fontdata.hashmethod = "names"
1309 local indices = unifyglyphs(fontdata,true)
1310 unifyresources(fontdata,indices)
1311 copyduplicates(fontdata)
1312 unifymissing(fontdata)
1313 else
1314 fontdata.hashmethod = "unicodes"
1315 local indices = unifyglyphs(fontdata)
1316 unifyresources(fontdata,indices)
1317 copyduplicates(fontdata)
1318 unifymissing(fontdata)
1319 stripredundant(fontdata)
1320 end
1321
1322end
1323
1324function readers.checkhash(fontdata)
1325 local hashmethod = fontdata.hashmethod
1326 if hashmethod == "unicodes" then
1327 fontdata.names = nil
1328 elseif hashmethod == "names" and fontdata.names then
1329 unifyresources(fontdata,fontdata.names)
1330 copyduplicates(fontdata)
1331 fontdata.hashmethod = "unicodes"
1332 fontdata.names = nil
1333 else
1334 readers.rehash(fontdata,"unicodes")
1335 end
1336end
1337
1338function readers.addunicodetable(fontdata)
1339 local resources = fontdata.resources
1340 local unicodes = resources.unicodes
1341 if not unicodes then
1342 local descriptions = fontdata.descriptions
1343 if descriptions then
1344 unicodes = { }
1345 resources.unicodes = unicodes
1346 for u, d in next, descriptions do
1347 local n = d.name
1348 if n then
1349 unicodes[n] = u
1350 end
1351 end
1352 end
1353 end
1354end
1355
1356
1357
1358local concat, sort = table.concat, table.sort
1359local next, type, tostring = next, type, tostring
1360
1361local criterium <const> = 1
1362local threshold <const> = 0
1363
1364local trace_packing = false trackers.register("otf.packing", function(v) trace_packing = v end)
1365local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
1366
1367local report_otf = logs.reporter("fonts","otf loading")
1368
1369local function tabstr_normal(t)
1370 local s = { }
1371 local n = 0
1372 for k, v in next, t do
1373 n = n + 1
1374 if type(v) == "table" then
1375 s[n] = k .. ">" .. tabstr_normal(v)
1376 elseif v == true then
1377 s[n] = k .. "+"
1378 elseif v then
1379 s[n] = k .. "=" .. v
1380 else
1381 s[n] = k .. "-"
1382 end
1383 end
1384 if n == 0 then
1385 return ""
1386 elseif n == 1 then
1387 return s[1]
1388 else
1389 sort(s)
1390 return concat(s,",")
1391 end
1392end
1393
1394local function tabstr_flat(t)
1395 local s = { }
1396 local n = 0
1397 for k, v in next, t do
1398 n = n + 1
1399 s[n] = k .. "=" .. v
1400 end
1401 if n == 0 then
1402 return ""
1403 elseif n == 1 then
1404 return s[1]
1405 else
1406 sort(s)
1407 return concat(s,",")
1408 end
1409end
1410
1411local function tabstr_mixed(t)
1412 local n = #t
1413 if n == 0 then
1414 return ""
1415 elseif n == 1 then
1416 local k = t[1]
1417 if k == true then
1418 return "++"
1419 elseif k == false then
1420 return "--"
1421 else
1422 return tostring(k)
1423 end
1424 else
1425 local s = { }
1426 for i=1,n do
1427 local k = t[i]
1428 if k == true then
1429 s[i] = "++"
1430 elseif k == false then
1431 s[i] = "--"
1432 else
1433 s[i] = k
1434 end
1435 end
1436 return concat(s,",")
1437 end
1438end
1439
1440local function tabstr_boolean(t)
1441 local s = { }
1442 local n = 0
1443 for k, v in next, t do
1444 n = n + 1
1445 if v then
1446 s[n] = k .. "+"
1447 else
1448 s[n] = k .. "-"
1449 end
1450 end
1451 if n == 0 then
1452 return ""
1453 elseif n == 1 then
1454 return s[1]
1455 else
1456 sort(s)
1457 return concat(s,",")
1458 end
1459end
1460
1461
1462
1463
1464
1465
1466function readers.pack(data)
1467
1468 if data then
1469
1470 local h, t, c = { }, { }, { }
1471 local hh, tt, cc = { }, { }, { }
1472 local nt, ntt = 0, 0
1473
1474 local function pack_normal(v)
1475 local tag = tabstr_normal(v)
1476 local ht = h[tag]
1477 if ht then
1478 c[ht] = c[ht] + 1
1479 return ht
1480 else
1481 nt = nt + 1
1482 t[nt] = v
1483 h[tag] = nt
1484 c[nt] = 1
1485 return nt
1486 end
1487 end
1488
1489 local function pack_normal_cc(v)
1490 local tag = tabstr_normal(v)
1491 local ht = h[tag]
1492 if ht then
1493 c[ht] = c[ht] + 1
1494 return ht
1495 else
1496 v[1] = 0
1497 nt = nt + 1
1498 t[nt] = v
1499 h[tag] = nt
1500 c[nt] = 1
1501 return nt
1502 end
1503 end
1504
1505 local function pack_flat(v)
1506 local tag = tabstr_flat(v)
1507 local ht = h[tag]
1508 if ht then
1509 c[ht] = c[ht] + 1
1510 return ht
1511 else
1512 nt = nt + 1
1513 t[nt] = v
1514 h[tag] = nt
1515 c[nt] = 1
1516 return nt
1517 end
1518 end
1519
1520 local function pack_indexed(v)
1521 local tag = concat(v," ")
1522 local ht = h[tag]
1523 if ht then
1524 c[ht] = c[ht] + 1
1525 return ht
1526 else
1527 nt = nt + 1
1528 t[nt] = v
1529 h[tag] = nt
1530 c[nt] = 1
1531 return nt
1532 end
1533 end
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550 local function pack_mixed(v)
1551 local tag = tabstr_mixed(v)
1552 local ht = h[tag]
1553 if ht then
1554 c[ht] = c[ht] + 1
1555 return ht
1556 else
1557 nt = nt + 1
1558 t[nt] = v
1559 h[tag] = nt
1560 c[nt] = 1
1561 return nt
1562 end
1563 end
1564
1565
1566
1567
1568
1569 local function pack_boolean(v)
1570 local tag = tabstr_boolean(v)
1571 local ht = h[tag]
1572 if ht then
1573 c[ht] = c[ht] + 1
1574 return ht
1575 else
1576 nt = nt + 1
1577 t[nt] = v
1578 h[tag] = nt
1579 c[nt] = 1
1580 return nt
1581 end
1582 end
1583
1584 local function pack_final(v)
1585
1586 if c[v] <= criterium then
1587 return t[v]
1588 else
1589
1590 local hv = hh[v]
1591 if hv then
1592 return hv
1593 else
1594 ntt = ntt + 1
1595 tt[ntt] = t[v]
1596 hh[v] = ntt
1597 cc[ntt] = c[v]
1598 return ntt
1599 end
1600 end
1601 end
1602
1603 local function pack_final_cc(v)
1604
1605 if c[v] <= criterium then
1606 return t[v]
1607 else
1608
1609 local hv = hh[v]
1610 if hv then
1611 return hv
1612 else
1613 ntt = ntt + 1
1614 tt[ntt] = t[v]
1615 hh[v] = ntt
1616 cc[ntt] = c[v]
1617 return ntt
1618 end
1619 end
1620 end
1621
1622 local function success(stage,pass)
1623 if nt == 0 then
1624 if trace_loading or trace_packing then
1625 report_otf("pack quality: nothing to pack")
1626 end
1627 return false
1628 elseif nt >= threshold then
1629 local one = 0
1630 local two = 0
1631 local rest = 0
1632 if pass == 1 then
1633 for k,v in next, c do
1634 if v == 1 then
1635 one = one + 1
1636 elseif v == 2 then
1637 two = two + 1
1638 else
1639 rest = rest + 1
1640 end
1641 end
1642 else
1643 for k,v in next, cc do
1644 if v > 20 then
1645 rest = rest + 1
1646 elseif v > 10 then
1647 two = two + 1
1648 else
1649 one = one + 1
1650 end
1651 end
1652 data.tables = tt
1653 end
1654 if trace_loading or trace_packing then
1655 report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",
1656 stage, pass, one+two+rest, one, two, rest, criterium)
1657 end
1658 return true
1659 else
1660 if trace_loading or trace_packing then
1661 report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",
1662 stage, pass, nt, threshold)
1663 end
1664 return false
1665 end
1666 end
1667
1668 local function packers(pass)
1669 if pass == 1 then
1670 return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc
1671 else
1672 return pack_final, pack_final, pack_final, pack_final, pack_final, pack_final_cc
1673 end
1674 end
1675
1676 local resources = data.resources
1677 local sequences = resources.sequences
1678 local sublookups = resources.sublookups
1679 local features = resources.features
1680 local palettes = resources.colorpalettes
1681 local variable = resources.variabledata
1682
1683 local chardata = characters and characters.data
1684 local descriptions = data.descriptions or data.glyphs
1685
1686 if not descriptions then
1687 return
1688 end
1689
1690 for pass=1,2 do
1691
1692 if trace_packing then
1693 report_otf("start packing: stage 1, pass %s",pass)
1694 end
1695
1696 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1697
1698 for unicode, description in next, descriptions do
1699 local boundingbox = description.boundingbox
1700 if boundingbox then
1701 description.boundingbox = pack_indexed(boundingbox)
1702 end
1703 local math = description.math
1704 if math then
1705 local kerns = math.kerns
1706 if kerns then
1707 for tag, kern in next, kerns do
1708 kerns[tag] = pack_normal(kern)
1709 end
1710 end
1711 end
1712
1713
1714
1715
1716
1717
1718
1719
1720 end
1721
1722 local function packthem(sequences)
1723 for i=1,#sequences do
1724 local sequence = sequences[i]
1725 local kind = sequence.type
1726 local steps = sequence.steps
1727 local order = sequence.order
1728 local features = sequence.features
1729 local flags = sequence.flags
1730 if steps then
1731 for i=1,#steps do
1732 local step = steps[i]
1733 if kind == "gpos_pair" then
1734 local c = step.coverage
1735 if c then
1736 if step.format ~= "pair" then
1737 for g1, d1 in next, c do
1738 c[g1] = pack_normal(d1)
1739 end
1740 elseif step.shared then
1741
1742
1743
1744
1745 local shared = { }
1746 for g1, d1 in next, c do
1747 for g2, d2 in next, d1 do
1748 if not shared[d2] then
1749 local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1750 local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1751 shared[d2] = true
1752 end
1753 end
1754 end
1755 if pass == 2 then
1756 step.shared = nil
1757 end
1758 else
1759 for g1, d1 in next, c do
1760 for g2, d2 in next, d1 do
1761 local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1762 local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1763 end
1764 end
1765 end
1766 end
1767 elseif kind == "gpos_single" then
1768 local c = step.coverage
1769 if c then
1770 if step.format == "single" then
1771 for g1, d1 in next, c do
1772 if d1 and d1 ~= true then
1773 c[g1] = pack_indexed(d1)
1774 end
1775 end
1776 else
1777 step.coverage = pack_normal(c)
1778 end
1779 end
1780 elseif kind == "gpos_cursive" then
1781 local c = step.coverage
1782 if c then
1783 for g1, d1 in next, c do
1784 local f = d1[2] if f then d1[2] = pack_indexed(f) end
1785 local s = d1[3] if s then d1[3] = pack_indexed(s) end
1786 end
1787 end
1788 elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
1789 local c = step.baseclasses
1790 if c then
1791 for g1, d1 in next, c do
1792 for g2, d2 in next, d1 do
1793 d1[g2] = pack_indexed(d2)
1794 end
1795 end
1796 end
1797 local c = step.coverage
1798 if c then
1799 for g1, d1 in next, c do
1800 d1[2] = pack_indexed(d1[2])
1801 end
1802 end
1803 elseif kind == "gpos_mark2ligature" then
1804 local c = step.baseclasses
1805 if c then
1806 for g1, d1 in next, c do
1807 for g2, d2 in next, d1 do
1808 for g3, d3 in next, d2 do
1809 d2[g3] = pack_indexed(d3)
1810 end
1811 end
1812 end
1813 end
1814 local c = step.coverage
1815 if c then
1816 for g1, d1 in next, c do
1817 d1[2] = pack_indexed(d1[2])
1818 end
1819 end
1820 end
1821
1822 local rules = step.rules
1823 if rules then
1824 for i=1,#rules do
1825 local rule = rules[i]
1826 local r = rule.before if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1827 local r = rule.after if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1828 local r = rule.current if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1829
1830 local r = rule.replacements if r then rule.replacements = pack_flat (r) end
1831 end
1832 end
1833 end
1834 end
1835 if order then
1836 sequence.order = pack_indexed(order)
1837 end
1838 if features then
1839 for script, feature in next, features do
1840 features[script] = pack_normal(feature)
1841 end
1842 end
1843 if flags then
1844 sequence.flags = pack_normal(flags)
1845 end
1846 end
1847 end
1848
1849 if sequences then
1850 packthem(sequences)
1851 end
1852
1853 if sublookups then
1854 packthem(sublookups)
1855 end
1856
1857 if features then
1858 for k, list in next, features do
1859 for feature, spec in next, list do
1860 list[feature] = pack_normal(spec)
1861 end
1862 end
1863 end
1864
1865 if palettes then
1866 for i=1,#palettes do
1867 local p = palettes[i]
1868 for j=1,#p do
1869 p[j] = pack_indexed(p[j])
1870 end
1871 end
1872
1873 end
1874
1875 if variable then
1876
1877
1878
1879 local instances = variable.instances
1880 if instances then
1881 for i=1,#instances do
1882 local v = instances[i].values
1883 for j=1,#v do
1884 v[j] = pack_normal(v[j])
1885 end
1886 end
1887 end
1888
1889 local function packdeltas(main)
1890 if main then
1891 local deltas = main.deltas
1892 if deltas then
1893 for i=1,#deltas do
1894 local di = deltas[i]
1895 local d = di.deltas
1896
1897 for j=1,#d do
1898 d[j] = pack_indexed(d[j])
1899 end
1900 di.regions = pack_indexed(di.regions)
1901 end
1902 end
1903 local regions = main.regions
1904 if regions then
1905 for i=1,#regions do
1906 local r = regions[i]
1907 for j=1,#r do
1908 r[j] = pack_normal(r[j])
1909 end
1910 end
1911 end
1912 end
1913 end
1914
1915 packdeltas(variable.global)
1916 packdeltas(variable.horizontal)
1917 packdeltas(variable.vertical)
1918 packdeltas(variable.metrics)
1919
1920 end
1921
1922 if not success(1,pass) then
1923 return
1924 end
1925
1926 end
1927
1928 if nt > 0 then
1929
1930 for pass=1,2 do
1931
1932 if trace_packing then
1933 report_otf("start packing: stage 2, pass %s",pass)
1934 end
1935
1936 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1937
1938 for unicode, description in next, descriptions do
1939 local math = description.math
1940 if math then
1941 local kerns = math.kerns
1942 if kerns then
1943 math.kerns = pack_normal(kerns)
1944 end
1945 end
1946 end
1947
1948 local function packthem(sequences)
1949 for i=1,#sequences do
1950 local sequence = sequences[i]
1951 local kind = sequence.type
1952 local steps = sequence.steps
1953 local features = sequence.features
1954 if steps then
1955 for i=1,#steps do
1956 local step = steps[i]
1957 if kind == "gpos_pair" then
1958 local c = step.coverage
1959 if c then
1960 if step.format == "pair" then
1961 for g1, d1 in next, c do
1962 for g2, d2 in next, d1 do
1963 d1[g2] = pack_normal(d2)
1964 end
1965 end
1966 end
1967 end
1968
1969
1970
1971
1972
1973
1974
1975 elseif kind == "gpos_mark2ligature" then
1976 local c = step.baseclasses
1977 if c then
1978 for g1, d1 in next, c do
1979 for g2, d2 in next, d1 do
1980 d1[g2] = pack_normal(d2)
1981 end
1982 end
1983 end
1984 end
1985 local rules = step.rules
1986 if rules then
1987 for i=1,#rules do
1988 local rule = rules[i]
1989 local r = rule.before if r then rule.before = pack_normal(r) end
1990 local r = rule.after if r then rule.after = pack_normal(r) end
1991 local r = rule.current if r then rule.current = pack_normal(r) end
1992 end
1993 end
1994 end
1995 end
1996 if features then
1997 sequence.features = pack_normal(features)
1998 end
1999 end
2000 end
2001 if sequences then
2002 packthem(sequences)
2003 end
2004 if sublookups then
2005 packthem(sublookups)
2006 end
2007 if variable then
2008 local function unpackdeltas(main)
2009 if main then
2010 local regions = main.regions
2011 if regions then
2012 main.regions = pack_normal(regions)
2013 end
2014 end
2015 end
2016 unpackdeltas(variable.global)
2017 unpackdeltas(variable.horizontal)
2018 unpackdeltas(variable.vertical)
2019 unpackdeltas(variable.metrics)
2020 end
2021
2022
2023
2024 end
2025
2026 for pass=1,2 do
2027 if trace_packing then
2028 report_otf("start packing: stage 3, pass %s",pass)
2029 end
2030
2031 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
2032
2033 local function packthem(sequences)
2034 for i=1,#sequences do
2035 local sequence = sequences[i]
2036 local kind = sequence.type
2037 local steps = sequence.steps
2038 local features = sequence.features
2039 if steps then
2040 for i=1,#steps do
2041 local step = steps[i]
2042 if kind == "gpos_pair" then
2043 local c = step.coverage
2044 if c then
2045 if step.format == "pair" then
2046 for g1, d1 in next, c do
2047 c[g1] = pack_normal(d1)
2048 end
2049 end
2050 end
2051 elseif kind == "gpos_cursive" then
2052 local c = step.coverage
2053 if c then
2054 for g1, d1 in next, c do
2055 c[g1] = pack_normal_cc(d1)
2056 end
2057 end
2058 end
2059 end
2060 end
2061 end
2062 end
2063
2064 if sequences then
2065 packthem(sequences)
2066 end
2067 if sublookups then
2068 packthem(sublookups)
2069 end
2070
2071 end
2072
2073 end
2074
2075 end
2076end
2077
2078local unpacked_mt = {
2079 __index =
2080 function(t,k)
2081 t[k] = false
2082 return k
2083 end
2084}
2085
2086function readers.unpack(data)
2087
2088 if data then
2089 local tables = data.tables
2090 if tables then
2091 local resources = data.resources
2092 local descriptions = data.descriptions or data.glyphs
2093 local sequences = resources.sequences
2094 local sublookups = resources.sublookups
2095 local features = resources.features
2096 local palettes = resources.colorpalettes
2097 local variable = resources.variabledata
2098 local unpacked = { }
2099 setmetatable(unpacked,unpacked_mt)
2100 for unicode, description in next, descriptions do
2101 local tv = tables[description.boundingbox]
2102 if tv then
2103 description.boundingbox = tv
2104 end
2105 local math = description.math
2106 if math then
2107 local kerns = math.kerns
2108 if kerns then
2109 local tm = tables[kerns]
2110 if tm then
2111 math.kerns = tm
2112 kerns = unpacked[tm]
2113 end
2114 if kerns then
2115 for k, kern in next, kerns do
2116 local tv = tables[kern]
2117 if tv then
2118 kerns[k] = tv
2119 end
2120 end
2121 end
2122 end
2123 end
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135 end
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146 local function unpackthem(sequences)
2147 for i=1,#sequences do
2148 local sequence = sequences[i]
2149 local kind = sequence.type
2150 local steps = sequence.steps
2151 local order = sequence.order
2152 local features = sequence.features
2153 local flags = sequence.flags
2154 local markclass = sequence.markclass
2155 if features then
2156 local tv = tables[features]
2157 if tv then
2158 sequence.features = tv
2159 features = tv
2160 end
2161 for script, feature in next, features do
2162 local tv = tables[feature]
2163 if tv then
2164 features[script] = tv
2165 end
2166 end
2167 end
2168 if steps then
2169 for i=1,#steps do
2170 local step = steps[i]
2171 if kind == "gpos_pair" then
2172 local c = step.coverage
2173 if c then
2174 if step.format == "pair" then
2175 for g1, d1 in next, c do
2176 local tv = tables[d1]
2177 if tv then
2178 c[g1] = tv
2179 d1 = tv
2180 end
2181 for g2, d2 in next, d1 do
2182 local tv = tables[d2]
2183 if tv then
2184 d1[g2] = tv
2185 d2 = tv
2186 end
2187 local f = tables[d2[1]] if f then d2[1] = f end
2188 local s = tables[d2[2]] if s then d2[2] = s end
2189 end
2190 end
2191 else
2192 for g1, d1 in next, c do
2193 local tv = tables[d1]
2194 if tv then
2195 c[g1] = tv
2196 end
2197 end
2198 end
2199 end
2200 elseif kind == "gpos_single" then
2201 local c = step.coverage
2202 if c then
2203 if step.format == "single" then
2204 for g1, d1 in next, c do
2205 local tv = tables[d1]
2206 if tv then
2207 c[g1] = tv
2208 end
2209 end
2210 else
2211 local tv = tables[c]
2212 if tv then
2213 step.coverage = tv
2214 end
2215 end
2216 end
2217 elseif kind == "gpos_cursive" then
2218 local c = step.coverage
2219 if c then
2220 for g1, d1 in next, c do
2221 local tv = tables[d1]
2222 if tv then
2223 d1 = tv
2224 c[g1] = d1
2225 end
2226 local f = tables[d1[2]] if f then d1[2] = f end
2227 local s = tables[d1[3]] if s then d1[3] = s end
2228 end
2229 end
2230 elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
2231 local c = step.baseclasses
2232 if c then
2233 for g1, d1 in next, c do
2234 for g2, d2 in next, d1 do
2235 local tv = tables[d2]
2236 if tv then
2237 d1[g2] = tv
2238 end
2239 end
2240 end
2241 end
2242 local c = step.coverage
2243 if c then
2244 for g1, d1 in next, c do
2245 local tv = tables[d1[2]]
2246 if tv then
2247 d1[2] = tv
2248 end
2249 end
2250 end
2251 elseif kind == "gpos_mark2ligature" then
2252 local c = step.baseclasses
2253 if c then
2254 for g1, d1 in next, c do
2255 for g2, d2 in next, d1 do
2256 local tv = tables[d2]
2257 if tv then
2258 d2 = tv
2259 d1[g2] = d2
2260 end
2261 for g3, d3 in next, d2 do
2262 local tv = tables[d2[g3]]
2263 if tv then
2264 d2[g3] = tv
2265 end
2266 end
2267 end
2268 end
2269 end
2270 local c = step.coverage
2271 if c then
2272 for g1, d1 in next, c do
2273 local tv = tables[d1[2]]
2274 if tv then
2275 d1[2] = tv
2276 end
2277 end
2278 end
2279 end
2280 local rules = step.rules
2281 if rules then
2282 for i=1,#rules do
2283 local rule = rules[i]
2284 local before = rule.before
2285 if before then
2286 local tv = tables[before]
2287 if tv then
2288 rule.before = tv
2289 before = tv
2290 end
2291 for i=1,#before do
2292 local tv = tables[before[i]]
2293 if tv then
2294 before[i] = tv
2295 end
2296 end
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309 end
2310 local after = rule.after
2311 if after then
2312 local tv = tables[after]
2313 if tv then
2314 rule.after = tv
2315 after = tv
2316 end
2317 for i=1,#after do
2318 local tv = tables[after[i]]
2319 if tv then
2320 after[i] = tv
2321 end
2322 end
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335 end
2336 local current = rule.current
2337 if current then
2338 local tv = tables[current]
2339 if tv then
2340 rule.current = tv
2341 current = tv
2342 end
2343 for i=1,#current do
2344 local tv = tables[current[i]]
2345 if tv then
2346 current[i] = tv
2347 end
2348 end
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361 end
2362
2363
2364
2365
2366
2367
2368
2369 local replacements = rule.replacements
2370 if replacements then
2371 local tv = tables[replacements]
2372 if tv then
2373 rule.replacements = tv
2374 end
2375 end
2376 end
2377 end
2378 end
2379 end
2380 if order then
2381 local tv = tables[order]
2382 if tv then
2383 sequence.order = tv
2384 end
2385 end
2386 if flags then
2387 local tv = tables[flags]
2388 if tv then
2389 sequence.flags = tv
2390 end
2391 end
2392 end
2393 end
2394
2395 if sequences then
2396 unpackthem(sequences)
2397 end
2398
2399 if sublookups then
2400 unpackthem(sublookups)
2401 end
2402
2403 if features then
2404 for k, list in next, features do
2405 for feature, spec in next, list do
2406 local tv = tables[spec]
2407 if tv then
2408 list[feature] = tv
2409 end
2410 end
2411 end
2412 end
2413
2414 if palettes then
2415 for i=1,#palettes do
2416 local p = palettes[i]
2417 for j=1,#p do
2418 local tv = tables[p[j]]
2419 if tv then
2420 p[j] = tv
2421 end
2422 end
2423 end
2424 end
2425
2426 if variable then
2427
2428
2429
2430 local instances = variable.instances
2431 if instances then
2432 for i=1,#instances do
2433 local v = instances[i].values
2434 for j=1,#v do
2435 local tv = tables[v[j]]
2436 if tv then
2437 v[j] = tv
2438 end
2439 end
2440 end
2441 end
2442
2443 local function unpackdeltas(main)
2444 if main then
2445 local deltas = main.deltas
2446 if deltas then
2447 for i=1,#deltas do
2448 local di = deltas[i]
2449 local d = di.deltas
2450 local r = di.regions
2451 for j=1,#d do
2452 local tv = tables[d[j]]
2453 if tv then
2454 d[j] = tv
2455 end
2456 end
2457 local tv = di.regions
2458 if tv then
2459 di.regions = tv
2460 end
2461 end
2462 end
2463 local regions = main.regions
2464 if regions then
2465 local tv = tables[regions]
2466 if tv then
2467 main.regions = tv
2468 regions = tv
2469 end
2470 for i=1,#regions do
2471 local r = regions[i]
2472 for j=1,#r do
2473 local tv = tables[r[j]]
2474 if tv then
2475 r[j] = tv
2476 end
2477 end
2478 end
2479 end
2480 end
2481 end
2482
2483 unpackdeltas(variable.global)
2484 unpackdeltas(variable.horizontal)
2485 unpackdeltas(variable.vertical)
2486 unpackdeltas(variable.metrics)
2487
2488 end
2489
2490 data.tables = nil
2491 end
2492 end
2493end
2494
2495local mt = {
2496 __index = function(t,k)
2497 if k == "height" then
2498 local ht = t.boundingbox[4]
2499 return ht < 0 and 0 or ht
2500 elseif k == "depth" then
2501 local dp = -t.boundingbox[2]
2502 return dp < 0 and 0 or dp
2503 elseif k == "width" then
2504 return 0
2505 elseif k == "name" then
2506 return forcenotdef and ".notdef"
2507 end
2508 end
2509}
2510
2511local function sameformat(sequence,steps,first,nofsteps,kind)
2512 return true
2513end
2514
2515local function mergesteps_1(lookup,strict)
2516 local steps = lookup.steps
2517 local nofsteps = lookup.nofsteps
2518 local first = steps[1]
2519 if strict then
2520 local f = first.format
2521 for i=2,nofsteps do
2522 if steps[i].format ~= f then
2523 if trace_optimizations then
2524 report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2525 end
2526 return 0
2527 end
2528 end
2529 end
2530 if trace_optimizations then
2531 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2532 end
2533 local target = first.coverage
2534 for i=2,nofsteps do
2535 local c = steps[i].coverage
2536 if c then
2537 for k, v in next, c do
2538 if not target[k] then
2539 target[k] = v
2540 end
2541 end
2542 end
2543 end
2544 lookup.nofsteps = 1
2545 lookup.merged = true
2546 lookup.steps = { first }
2547 return nofsteps - 1
2548end
2549
2550local function mergesteps_2(lookup)
2551
2552
2553
2554 local steps = lookup.steps
2555 local nofsteps = lookup.nofsteps
2556 local first = steps[1]
2557 if strict then
2558 local f = first.format
2559 for i=2,nofsteps do
2560 if steps[i].format ~= f then
2561 if trace_optimizations then
2562 report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2563 end
2564 return 0
2565 end
2566 end
2567 end
2568 if trace_optimizations then
2569 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2570 end
2571 local target = first.coverage
2572 for i=2,nofsteps do
2573 local c = steps[i].coverage
2574 if c then
2575 for k, v in next, c do
2576 local tk = target[k]
2577 if tk then
2578 for kk, vv in next, v do
2579 if tk[kk] == nil then
2580 tk[kk] = vv
2581 end
2582 end
2583 else
2584 target[k] = v
2585 end
2586 end
2587 end
2588 end
2589 lookup.nofsteps = 1
2590 lookup.merged = true
2591 lookup.steps = { first }
2592 return nofsteps - 1
2593end
2594
2595
2596
2597
2598local function mergesteps_3(lookup,strict)
2599 local steps = lookup.steps
2600 local nofsteps = lookup.nofsteps
2601 if trace_optimizations then
2602 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2603 end
2604
2605 local coverage = { }
2606 for i=1,nofsteps do
2607 local c = steps[i].coverage
2608 if c then
2609 for k, v in next, c do
2610 local tk = coverage[k]
2611 if tk then
2612 if trace_optimizations then
2613 report_optimizations("quitting merge due to multiple checks")
2614 end
2615 return nofsteps
2616 else
2617 coverage[k] = v
2618 end
2619 end
2620 end
2621 end
2622
2623 local first = steps[1]
2624 local baseclasses = { }
2625 for i=1,nofsteps do
2626 local offset = i*10
2627 local step = steps[i]
2628 for k, v in sortedhash(step.baseclasses) do
2629 baseclasses[offset+k] = v
2630 end
2631 for k, v in next, step.coverage do
2632 v[1] = offset + v[1]
2633 end
2634 end
2635 first.baseclasses = baseclasses
2636 first.coverage = coverage
2637 lookup.nofsteps = 1
2638 lookup.merged = true
2639 lookup.steps = { first }
2640 return nofsteps - 1
2641end
2642
2643local function nested(old,new)
2644 for k, v in next, old do
2645 if k == "ligature" then
2646 if not new.ligature then
2647 new.ligature = v
2648 end
2649 else
2650 local n = new[k]
2651 if n then
2652 nested(v,n)
2653 else
2654 new[k] = v
2655 end
2656 end
2657 end
2658end
2659
2660local function mergesteps_4(lookup)
2661 local steps = lookup.steps
2662 local nofsteps = lookup.nofsteps
2663 local first = steps[1]
2664 if trace_optimizations then
2665 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2666 end
2667 local target = first.coverage
2668 for i=2,nofsteps do
2669 local c = steps[i].coverage
2670 if c then
2671 for k, v in next, c do
2672 local tk = target[k]
2673 if tk then
2674 nested(v,tk)
2675 else
2676 target[k] = v
2677 end
2678 end
2679 end
2680 end
2681 lookup.nofsteps = 1
2682 lookup.steps = { first }
2683 return nofsteps - 1
2684end
2685
2686
2687
2688
2689
2690local function mergesteps_5(lookup)
2691 local steps = lookup.steps
2692 local nofsteps = lookup.nofsteps
2693 local first = steps[1]
2694 if trace_optimizations then
2695 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2696 end
2697 local target = first.coverage
2698 local hash = nil
2699 for k, v in next, target do
2700 hash = v[1]
2701 break
2702 end
2703 for i=2,nofsteps do
2704 local c = steps[i].coverage
2705 if c then
2706 for k, v in next, c do
2707 local tk = target[k]
2708 if tk then
2709 if not tk[2] then
2710 tk[2] = v[2]
2711 end
2712 if not tk[3] then
2713 tk[3] = v[3]
2714 end
2715 else
2716 target[k] = v
2717 v[1] = hash
2718 end
2719 end
2720 end
2721 end
2722 lookup.nofsteps = 1
2723 lookup.merged = true
2724 lookup.steps = { first }
2725 return nofsteps - 1
2726end
2727
2728local function checkkerns(lookup)
2729 local steps = lookup.steps
2730 local nofsteps = lookup.nofsteps
2731 local kerned = 0
2732 for i=1,nofsteps do
2733 local step = steps[i]
2734 if step.format == "pair" then
2735 local coverage = step.coverage
2736 local kerns = true
2737 for g1, d1 in next, coverage do
2738 if d1 == true then
2739
2740 elseif not d1 then
2741
2742 elseif d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
2743 kerns = false
2744 break
2745 end
2746 end
2747 if kerns then
2748 if trace_optimizations then
2749 report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2750 end
2751 local c = { }
2752 for g1, d1 in next, coverage do
2753 if d1 and d1 ~= true then
2754 c[g1] = d1[3]
2755 end
2756 end
2757 step.coverage = c
2758 step.format = "move"
2759 kerned = kerned + 1
2760 end
2761 end
2762 end
2763 return kerned
2764end
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780local strip_pairs = true
2781
2782local compact_pairs = true
2783local compact_singles = true
2784
2785local merge_pairs = true
2786local merge_singles = true
2787local merge_substitutions = true
2788local merge_alternates = true
2789local merge_multiples = true
2790local merge_ligatures = true
2791local merge_cursives = true
2792local merge_marks = true
2793
2794directives.register("otf.strip.pairs", function(v) strip_pairs = v end)
2795
2796directives.register("otf.compact.pairs", function(v) compact_pairs = v end)
2797directives.register("otf.compact.singles", function(v) compact_singles = v end)
2798
2799directives.register("otf.merge.pairs", function(v) merge_pairs = v end)
2800directives.register("otf.merge.singles", function(v) merge_singles = v end)
2801directives.register("otf.merge.substitutions", function(v) merge_substitutions = v end)
2802directives.register("otf.merge.alternates", function(v) merge_alternates = v end)
2803directives.register("otf.merge.multiples", function(v) merge_multiples = v end)
2804directives.register("otf.merge.ligatures", function(v) merge_ligatures = v end)
2805directives.register("otf.merge.cursives", function(v) merge_cursives = v end)
2806directives.register("otf.merge.marks", function(v) merge_marks = v end)
2807
2808local function checkpairs(lookup)
2809 local steps = lookup.steps
2810 local nofsteps = lookup.nofsteps
2811 local kerned = 0
2812
2813 local function onlykerns(step)
2814 local coverage = step.coverage
2815 for g1, d1 in next, coverage do
2816 for g2, d2 in next, d1 do
2817 if d2[2] then
2818
2819 return false
2820 else
2821 local v = d2[1]
2822 if v == true then
2823
2824 elseif v and (v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0) then
2825
2826 return false
2827 end
2828 end
2829 end
2830 end
2831 return coverage
2832 end
2833
2834 for i=1,nofsteps do
2835 local step = steps[i]
2836 if step.format == "pair" then
2837 local coverage = onlykerns(step)
2838 if coverage then
2839 if trace_optimizations then
2840 report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2841 end
2842 for g1, d1 in next, coverage do
2843 local d = { }
2844 for g2, d2 in next, d1 do
2845 local v = d2[1]
2846 if v == true then
2847
2848 elseif v then
2849 d[g2] = v[3]
2850 end
2851 end
2852 coverage[g1] = d
2853 end
2854 step.format = "move"
2855 kerned = kerned + 1
2856 end
2857 end
2858 end
2859 return kerned
2860end
2861
2862local function strippairs(lookup)
2863 local steps = lookup.steps
2864 local nofsteps = lookup.nofsteps
2865 local stripped = 0
2866
2867 for i=1,nofsteps do
2868 local step = steps[i]
2869 if step.format == "pair" then
2870 local coverage = step.coverage
2871 for g1, d1 in next, coverage do
2872 for g2, d2 in next, d1 do
2873 if d2[2] then
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885 elseif d2[1] == true then
2886 d1[g2] = nil
2887 stripped = stripped + 1
2888 end
2889 end
2890 end
2891 end
2892 end
2893 return stripped
2894end
2895
2896function readers.compact(data)
2897 if not data or data.compacted then
2898 return
2899 else
2900 data.compacted = true
2901 end
2902 local resources = data.resources
2903 local stripped = 0
2904 local merged = 0
2905 local kerned = 0
2906 local allsteps = 0
2907 local function compact(what)
2908 local lookups = resources[what]
2909 if lookups then
2910 for i=1,#lookups do
2911 local lookup = lookups[i]
2912 local nofsteps = lookup.nofsteps
2913 local kind = lookup.type
2914 allsteps = allsteps + nofsteps
2915 if nofsteps > 1 then
2916 local merg = merged
2917 if kind == "gsub_single" then
2918 if merge_substitutions then
2919 merged = merged + mergesteps_1(lookup)
2920 end
2921 elseif kind == "gsub_alternate" then
2922 if merge_alternates then
2923 merged = merged + mergesteps_1(lookup)
2924 end
2925 elseif kind == "gsub_multiple" then
2926 if merge_multiples then
2927 merged = merged + mergesteps_1(lookup)
2928 end
2929 elseif kind == "gsub_ligature" then
2930 if merge_ligatures then
2931 merged = merged + mergesteps_4(lookup)
2932 end
2933 elseif kind == "gpos_single" then
2934
2935 if merge_singles then
2936 merged = merged + mergesteps_1(lookup,true)
2937 end
2938 if compact_singles then
2939 kerned = kerned + checkkerns(lookup)
2940 end
2941 elseif kind == "gpos_pair" then
2942 if strip_pairs then
2943 stripped = stripped + strippairs(lookup)
2944 end
2945 if merge_pairs then
2946 merged = merged + mergesteps_2(lookup)
2947 end
2948 if compact_pairs then
2949 kerned = kerned + checkpairs(lookup)
2950 end
2951 elseif kind == "gpos_cursive" then
2952 if merge_cursives then
2953 merged = merged + mergesteps_5(lookup)
2954 end
2955 elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
2956 if merge_marks then
2957 merged = merged + mergesteps_3(lookup)
2958 end
2959 end
2960 if merg ~= merged then
2961 lookup.merged = true
2962 end
2963 elseif nofsteps == 1 then
2964 local kern = kerned
2965 if kind == "gpos_single" then
2966 if compact_singles then
2967 kerned = kerned + checkkerns(lookup)
2968 end
2969 elseif kind == "gpos_pair" then
2970 if compact_pairs then
2971 kerned = kerned + checkpairs(lookup)
2972 end
2973 end
2974 if kern ~= kerned then
2975
2976 end
2977 end
2978 end
2979 elseif trace_optimizations then
2980 report_optimizations("no lookups in %a",what)
2981 end
2982 end
2983 compact("sequences")
2984 compact("sublookups")
2985 if trace_optimizations then
2986 if stripped > 0 then
2987 report_optimizations("%i zero positions stripped before merging",stripped)
2988 end
2989 if merged > 0 then
2990 report_optimizations("%i steps of %i removed due to merging",merged,allsteps)
2991 end
2992 if kerned > 0 then
2993 report_optimizations("%i steps of %i steps turned from pairs into kerns",kerned,allsteps)
2994 end
2995 end
2996end
2997
2998if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
2999
3000 local done = 0
3001
3002 local function condense_1(k,v,t)
3003 if type(v) == "table" then
3004 local u = false
3005 local l = false
3006 for k, v in next, v do
3007 if k == "ligature" then
3008 l = v
3009 if u then
3010 break
3011 end
3012 elseif u then
3013 break
3014 else
3015 u = true
3016 end
3017 end
3018 if l and not u then
3019 t[k] = l
3020 done = done + 1
3021 end
3022 if u then
3023 for k, vv in next, v do
3024 if k ~= "ligature" then
3025 condense_1(k,vv,v)
3026 end
3027 end
3028 end
3029 end
3030 end
3031
3032 local function condensesteps_1(lookup)
3033 done = 0
3034 if lookup.type == "gsub_ligature" then
3035 local steps = lookup.steps
3036 if steps then
3037 for i=1,#steps do
3038 local step = steps[i]
3039 local coverage = step.coverage
3040 if coverage then
3041 for k, v in next, coverage do
3042 if condense_1(k,v,coverage) then
3043 coverage[k] = v.ligature
3044 done = done + 1
3045 end
3046 end
3047 end
3048 end
3049 end
3050 end
3051 return done
3052 end
3053
3054 function readers.condense(data)
3055 if not data or data.condensed then
3056 return
3057 else
3058 data.condensed = true
3059 end
3060 local resources = data.resources
3061 local condensed = 0
3062 local function condense(what)
3063 local lookups = resources[what]
3064 if lookups then
3065 for i=1,#lookups do
3066 condensed = condensed + condensesteps_1(lookups[i])
3067 end
3068 elseif trace_optimizations then
3069 report_optimizations("no lookups in %a",what)
3070 end
3071 end
3072 condense("sequences")
3073 condense("sublookups")
3074 if trace_optimizations then
3075 if condensed > 0 then
3076 report_optimizations("%i ligatures condensed",condensed)
3077 end
3078 end
3079 end
3080
3081end
3082
3083local function mergesteps(t,k)
3084 if k == "merged" then
3085 local merged = { }
3086 for i=1,#t do
3087 local step = t[i]
3088 local coverage = step.coverage
3089 for k in next, coverage do
3090 local m = merged[k]
3091 if m then
3092 m[2] = i
3093
3094 else
3095 merged[k] = { i, i }
3096
3097 end
3098 end
3099 end
3100 t.merged = merged
3101 return merged
3102 end
3103end
3104
3105local function checkmerge(sequence)
3106 local steps = sequence.steps
3107 if steps then
3108 setmetatableindex(steps,mergesteps)
3109 end
3110end
3111
3112local function checkflags(sequence,resources)
3113 if not sequence.skiphash then
3114 local flags = sequence.flags
3115 if flags then
3116 local skipmark = flags[1]
3117 local skipligature = flags[2]
3118 local skipbase = flags[3]
3119 local markclass = sequence.markclass
3120 local skipsome = skipmark or skipligature or skipbase or markclass or false
3121 if skipsome then
3122 sequence.skiphash = setmetatableindex(function(t,k)
3123 local c = resources.classes[k]
3124 local v = c == skipmark
3125 or (markclass and c == "mark" and not markclass[k])
3126 or c == skipligature
3127 or c == skipbase
3128 or false
3129 t[k] = v
3130 return v
3131 end)
3132 else
3133 sequence.skiphash = false
3134 end
3135 else
3136 sequence.skiphash = false
3137 end
3138 end
3139end
3140
3141local function checksteps(sequence)
3142 local steps = sequence.steps
3143 if steps then
3144 for i=1,#steps do
3145 steps[i].index = i
3146 end
3147 end
3148end
3149
3150if fonts.helpers then
3151 fonts.helpers.checkmerge = checkmerge
3152 fonts.helpers.checkflags = checkflags
3153 fonts.helpers.checksteps = checksteps
3154end
3155
3156function readers.expand(data)
3157 if not data or data.expanded then
3158 return
3159 else
3160 data.expanded = true
3161 end
3162 local resources = data.resources
3163 local sublookups = resources.sublookups
3164 local sequences = resources.sequences
3165 local markclasses = resources.markclasses
3166 local descriptions = data.descriptions
3167 if descriptions then
3168 local defaultwidth = resources.defaultwidth or 0
3169
3170
3171 local basename = trace_markwidth and file.basename(resources.filename)
3172 for u, d in next, descriptions do
3173 local bb = d.boundingbox
3174 local wd = d.width
3175 if d.class == "mark" then
3176 if trace_markwidth and wd ~= 0 then
3177 report_markwidth("mark %a with width %p found in %a",d.name or "<noname>",wd,basename)
3178 end
3179 d.width = 0
3180 elseif not wd then
3181 d.width = defaultwidth
3182 end
3183 if bb then
3184 local ht = bb[4]
3185 local dp = -bb[2]
3186 if ht == 0 or ht < 0 then
3187
3188 else
3189 d.height = ht
3190 end
3191 if dp == 0 or dp < 0 then
3192
3193 else
3194 d.depth = dp
3195 end
3196 end
3197 end
3198 end
3199
3200
3201
3202
3203
3204 local function expandlookups(sequences,whatever)
3205 if sequences then
3206
3207 for i=1,#sequences do
3208 local sequence = sequences[i]
3209 local steps = sequence.steps
3210 if steps then
3211 local nofsteps = sequence.nofsteps
3212
3213 local kind = sequence.type
3214 local markclass = sequence.markclass
3215 if markclass then
3216 if not markclasses then
3217 report_warning("missing markclasses")
3218 sequence.markclass = false
3219 else
3220 sequence.markclass = markclasses[markclass]
3221 end
3222 end
3223
3224 for i=1,nofsteps do
3225 local step = steps[i]
3226 local baseclasses = step.baseclasses
3227 if baseclasses then
3228 local coverage = step.coverage
3229 for k, v in next, coverage do
3230 v[1] = baseclasses[v[1]]
3231 end
3232 elseif kind == "gpos_cursive" then
3233 local coverage = step.coverage
3234 for k, v in next, coverage do
3235 v[1] = coverage
3236 end
3237 end
3238 local rules = step.rules
3239 if rules then
3240 local rulehash = { n = 0 }
3241 local rulesize = 0
3242 local coverage = { }
3243 local lookuptype = sequence.type
3244 local nofrules = #rules
3245 step.coverage = coverage
3246 for currentrule=1,nofrules do
3247 local rule = rules[currentrule]
3248 local current = rule.current
3249 local before = rule.before
3250 local after = rule.after
3251 local replacements = rule.replacements or false
3252 local sequence = { }
3253 local nofsequences = 0
3254 if before then
3255 for n=1,#before do
3256 nofsequences = nofsequences + 1
3257 sequence[nofsequences] = before[n]
3258 end
3259 end
3260 local start = nofsequences + 1
3261 for n=1,#current do
3262 nofsequences = nofsequences + 1
3263 sequence[nofsequences] = current[n]
3264 end
3265 local stop = nofsequences
3266 if after then
3267 for n=1,#after do
3268 nofsequences = nofsequences + 1
3269 sequence[nofsequences] = after[n]
3270 end
3271 end
3272 local lookups = rule.lookups or false
3273 local subtype = nil
3274 if lookups then
3275 for i=1,#lookups do
3276 local lookups = lookups[i]
3277 if lookups then
3278 for k, v in next, lookups do
3279 local lookup = sublookups[v]
3280if not lookup and whatever then
3281 lookup = whatever[v]
3282end
3283 if lookup then
3284 lookups[k] = lookup
3285 if not subtype then
3286 subtype = lookup.type
3287 end
3288 else
3289
3290 end
3291 end
3292 end
3293 end
3294 end
3295 if sequence[1] then
3296 sequence.n = #sequence
3297 local ruledata = {
3298 currentrule,
3299 lookuptype,
3300 sequence,
3301 start,
3302 stop,
3303 lookups,
3304 replacements,
3305 subtype,
3306 }
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318 rulesize = rulesize + 1
3319 rulehash[rulesize] = ruledata
3320 rulehash.n = rulesize
3321
3322 if true then
3323
3324 for unic in next, sequence[start] do
3325 local cu = coverage[unic]
3326 if cu then
3327 local n = #cu+1
3328 cu[n] = ruledata
3329 cu.n = n
3330 else
3331 coverage[unic] = { ruledata, n = 1 }
3332 end
3333 end
3334
3335 else
3336
3337 for unic in next, sequence[start] do
3338 local cu = coverage[unic]
3339 if cu then
3340
3341
3342 else
3343 coverage[unic] = rulehash
3344 end
3345 end
3346
3347 end
3348 end
3349 end
3350 end
3351 end
3352
3353 checkmerge(sequence)
3354 checkflags(sequence,resources)
3355 checksteps(sequence)
3356
3357 end
3358 end
3359 end
3360 end
3361
3362 expandlookups(sequences)
3363 expandlookups(sublookups,sequences)
3364end
3365 |