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