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
742end
743
744local firstprivate = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
745local puafirst = 0xE000
746local pualast = 0xF8FF
747
748local function unifymissing(fontdata)
749 if not fonts.mappings then
750 require("font-map")
751 require("font-agl")
752 end
753 local unicodes = { }
754 local resources = fontdata.resources
755 resources.unicodes = unicodes
756 for unicode, d in next, fontdata.descriptions do
757 if unicode < privateoffset then
758 if unicode >= puafirst and unicode <= pualast then
759
760 else
761 local name = d.name
762 if name then
763 unicodes[name] = unicode
764 end
765 end
766 else
767
768 end
769 end
770 fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups)
771 resources.unicodes = nil
772end
773
774local function unifyglyphs(fontdata,usenames)
775 local private = fontdata.private or privateoffset
776 local glyphs = fontdata.glyphs
777 local indices = { }
778 local descriptions = { }
779 local names = usenames and { }
780 local resources = fontdata.resources
781 local zero = glyphs[0]
782 local zerocode = zero.unicode
783 local nofglyphs = #glyphs
784 if not zerocode then
785 zerocode = private
786 zero.unicode = zerocode
787 private = private + 1
788 end
789 descriptions[zerocode] = zero
790 if names then
791 local name = glyphs[0].name or f_private(zerocode)
792 indices[0] = name
793 names[name] = zerocode
794 else
795 indices[0] = zerocode
796 end
797
798 if names then
799
800 for index=1,nofglyphs do
801 local glyph = glyphs[index]
802 local unicode = glyph.unicode
803 if not unicode then
804 unicode = private
805 local name = glyph.name or f_private(unicode)
806 indices[index] = name
807 names[name] = unicode
808 private = private + 1
809 elseif unicode >= firstprivate then
810 unicode = private
811 local name = glyph.name or f_private(unicode)
812 indices[index] = name
813 names[name] = unicode
814 private = private + 1
815 elseif unicode >= puafirst and unicode <= pualast then
816 local name = glyph.name or f_private(unicode)
817 indices[index] = name
818 names[name] = unicode
819 elseif descriptions[unicode] then
820 unicode = private
821 local name = glyph.name or f_private(unicode)
822 indices[index] = name
823 names[name] = unicode
824 private = private + 1
825 else
826 local name = glyph.name or f_unicode(unicode)
827 indices[index] = name
828 names[name] = unicode
829 end
830 descriptions[unicode] = glyph
831 end
832 elseif trace_unicodes then
833 for index=1,nofglyphs do
834 local glyph = glyphs[index]
835 local unicode = glyph.unicode
836 if not unicode then
837 unicode = private
838 indices[index] = unicode
839 private = private + 1
840 elseif unicode >= firstprivate then
841 local name = glyph.name
842 if name then
843 report_unicodes("moving glyph %a indexed %05X from private %U to %U ",name,index,unicode,private)
844 else
845 report_unicodes("moving glyph indexed %05X from private %U to %U ",index,unicode,private)
846 end
847 unicode = private
848 indices[index] = unicode
849 private = private + 1
850 elseif unicode >= puafirst and unicode <= pualast then
851 local name = glyph.name
852 if name then
853 report_unicodes("keeping private unicode %U for glyph %a indexed %05X",unicode,name,index)
854 else
855 report_unicodes("keeping private unicode %U for glyph indexed %05X",unicode,index)
856 end
857 indices[index] = unicode
858 elseif descriptions[unicode] then
859 local name = glyph.name
860 if name then
861 report_unicodes("assigning duplicate unicode %U to %U for glyph %a indexed %05X ",unicode,private,name,index)
862 else
863 report_unicodes("assigning duplicate unicode %U to %U for glyph indexed %05X ",unicode,private,index)
864 end
865 unicode = private
866 indices[index] = unicode
867 private = private + 1
868 else
869 indices[index] = unicode
870 end
871 descriptions[unicode] = glyph
872 end
873 else
874 for index=1,nofglyphs do
875 local glyph = glyphs[index]
876 local unicode = glyph.unicode
877 if not unicode then
878 unicode = private
879 indices[index] = unicode
880 private = private + 1
881 elseif unicode >= firstprivate then
882 local name = glyph.name
883 unicode = private
884 indices[index] = unicode
885 private = private + 1
886 elseif unicode >= puafirst and unicode <= pualast then
887 local name = glyph.name
888 indices[index] = unicode
889 elseif descriptions[unicode] then
890 local name = glyph.name
891 unicode = private
892 indices[index] = unicode
893 private = private + 1
894 else
895 indices[index] = unicode
896 end
897 descriptions[unicode] = glyph
898 end
899 end
900
901 if LUATEXENGINE == "luametatex" then
902 for index=1,nofglyphs do
903 local math = glyphs[index].math
904 if math then
905 local list = math.parts
906 if list then
907 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
908 end
909 local list = math.variants
910 if list then
911 for i=1,#list do list[i] = indices[list[i]] end
912 end
913 end
914 end
915 else
916 for index=1,nofglyphs do
917 local math = glyphs[index].math
918 if math then
919 local list = math.vparts
920 if list then
921 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
922 end
923 local list = math.hparts
924 if list then
925 for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
926 end
927 local list = math.vvariants
928 if list then
929
930 for i=1,#list do list[i] = indices[list[i]] end
931 end
932 local list = math.hvariants
933 if list then
934
935 for i=1,#list do list[i] = indices[list[i]] end
936 end
937 end
938 end
939 end
940
941 local colorpalettes = resources.colorpalettes
942 if colorpalettes then
943 for index=1,nofglyphs do
944 local colors = glyphs[index].colors
945 if colors then
946 for i=1,#colors do
947 local c = colors[i]
948 if c then
949 c.slot = indices[c.slot]
950 end
951 end
952 end
953 end
954 end
955
956 fontdata.private = private
957 fontdata.glyphs = nil
958 fontdata.names = names
959 fontdata.descriptions = descriptions
960 fontdata.hashmethod = hashmethod
961 fontdata.nofglyphs = nofglyphs
962
963 return indices, names
964end
965
966local stripredundant do
967
968 local p_hex = R("af","AF","09")
969 local p_digit = R("09")
970 local p_done = S("._-")^0 + P(-1)
971
972 local p_style = P(".")
973 local p_alpha = R("az","AZ")
974 local p_ALPHA = R("AZ")
975
976 local p_crappyname = (
977
978 lpeg.utfchartabletopattern({ "uni", "u" },true)
979 * S("Xx_")^0
980 * p_hex^1
981
982 + lpeg.utfchartabletopattern({ "identity", "glyph", "jamo" },true)
983 * p_hex^1
984
985 + lpeg.utfchartabletopattern({ "index", "afii" }, true)
986 * p_digit^1
987
988 + p_digit
989 * p_hex^3
990 + p_alpha
991 * p_digit^1
992
993 + P("aj")
994 * p_digit^1
995 + P("eh_")
996 * (p_digit^1 + p_ALPHA * p_digit^1)
997 + (1-P("_"))^1
998 * P("_uni")
999 * p_hex^1
1000 + P("_")
1001 * P(1)^1
1002 ) * p_done
1003
1004
1005
1006
1007 if context then
1008
1009 local forcekeep = false
1010
1011
1012 directives.register("otf.keepnames",function(v)
1013 report_cleanup("keeping weird glyph names, expect larger files and more memory usage")
1014 forcekeep = v
1015 end)
1016
1017
1018
1019
1020
1021
1022
1023 local function stripvariants(descriptions,list)
1024 local n = list and #list or 0
1025 if n > 0 then
1026 for i=1,n do
1027 local g = list[i]
1028 if g then
1029 local d = descriptions[g]
1030 if d and d.name then
1031 d.name = nil
1032 n = n + 1
1033 end
1034 end
1035 end
1036 end
1037 return n
1038 end
1039
1040 local function stripparts(descriptions,list)
1041 local n = list and #list or 0
1042 if n > 0 then
1043 for i=1,n do
1044 local g = list[i].glyph
1045 if g then
1046 local d = descriptions[g]
1047 if d and d.name then
1048 d.name = nil
1049 n = n + 1
1050 end
1051 end
1052 end
1053 end
1054 return n
1055 end
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093 local function collectsimple(fontdata)
1094 return nil
1095 end
1096
1097 stripredundant = function(fontdata)
1098 local descriptions = fontdata.descriptions
1099 if descriptions then
1100 local n = 0
1101 local c = 0
1102 for unicode, d in next, descriptions do
1103 local m = d.math
1104 if m then
1105 n = n + stripvariants(descriptions,m.vvariants)
1106 n = n + stripvariants(descriptions,m.hvariants)
1107 n = n + stripparts (descriptions,m.vparts)
1108 n = n + stripparts (descriptions,m.hparts)
1109 end
1110 end
1111 if forcekeep then
1112 for unicode, d in next, descriptions do
1113 if d.class == "base" then
1114 d.class = nil
1115 c = c + 1
1116 end
1117 end
1118 else
1119 local keeplist = collectsimple(fontdata)
1120 for unicode, d in next, descriptions do
1121 local name = d.name
1122 if name then
1123
1124 if keeplist and keeplist[name] then
1125
1126 elseif lpegmatch(p_crappyname,name) then
1127 d.name = nil
1128 n = n + 1
1129 end
1130 end
1131 if d.class == "base" then
1132 d.class = nil
1133 c = c + 1
1134 end
1135 end
1136 end
1137 if trace_cleanup then
1138 if n > 0 then
1139 report_cleanup("%s bogus names removed (verbose unicode)",n)
1140 end
1141 if c > 0 then
1142 report_cleanup("%s base class tags removed (default is base)",c)
1143 end
1144 end
1145 end
1146 end
1147
1148 else
1149
1150 stripredundant = function(fontdata)
1151 local descriptions = fontdata.descriptions
1152 if descriptions then
1153 if fonts.privateoffsets.keepnames then
1154 for unicode, d in next, descriptions do
1155 if d.class == "base" then
1156 d.class = nil
1157 end
1158 end
1159 else
1160 for unicode, d in next, descriptions do
1161 local name = d.name
1162 if name then
1163 if lpegmatch(p_crappyname,name) then
1164 d.name = nil
1165 end
1166 end
1167 if d.class == "base" then
1168 d.class = nil
1169 end
1170 end
1171 end
1172 end
1173 end
1174
1175 end
1176
1177 readers.stripredundant = stripredundant
1178
1179end
1180
1181function readers.getcomponents(fontdata)
1182 local resources = fontdata.resources
1183 if resources then
1184 local sequences = resources.sequences
1185 if sequences then
1186 local collected = { }
1187 for i=1,#sequences do
1188 local sequence = sequences[i]
1189 if sequence.type == "gsub_ligature" then
1190 local steps = sequence.steps
1191 if steps then
1192 local l = { }
1193 local function traverse(p,k,v)
1194 if k == "ligature" then
1195 collected[v] = { unpack(l) }
1196 elseif tonumber(v) then
1197 insert(l,k)
1198 collected[v] = { unpack(l) }
1199 remove(l)
1200 else
1201 insert(l,k)
1202 for k, vv in next, v do
1203 traverse(p,k,vv)
1204 end
1205 remove(l)
1206 end
1207 end
1208 for i=1,#steps do
1209
1210 local c = steps[i].coverage
1211 if c then
1212 for k, v in next, c do
1213 traverse(k,k,v)
1214 end
1215 end
1216 end
1217 end
1218 end
1219 end
1220 if next(collected) then
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 while true do
1232 local done = false
1233 for k, v in next, collected do
1234 for i=1,#v do
1235 local vi = v[i]
1236 if vi == k then
1237
1238 collected[k] = nil
1239 break
1240 else
1241 local c = collected[vi]
1242 if c then
1243 done = true
1244 local t = { }
1245 local n = i - 1
1246 for j=1,n do
1247 t[j] = v[j]
1248 end
1249 for j=1,#c do
1250 n = n + 1
1251 t[n] = c[j]
1252 end
1253 for j=i+1,#v do
1254 n = n + 1
1255 t[n] = v[j]
1256 end
1257 collected[k] = t
1258 break
1259 end
1260 end
1261 end
1262 end
1263 if not done then
1264 break
1265 end
1266 end
1267 return collected
1268 end
1269 end
1270 end
1271end
1272
1273readers.unifymissing = unifymissing
1274
1275function readers.rehash(fontdata,hashmethod)
1276 if not (fontdata and fontdata.glyphs) then
1277 return
1278 elseif hashmethod == "indices" then
1279 fontdata.hashmethod = "indices"
1280 elseif hashmethod == "names" then
1281 fontdata.hashmethod = "names"
1282 local indices = unifyglyphs(fontdata,true)
1283 unifyresources(fontdata,indices)
1284 copyduplicates(fontdata)
1285 unifymissing(fontdata)
1286 else
1287 fontdata.hashmethod = "unicodes"
1288 local indices = unifyglyphs(fontdata)
1289 unifyresources(fontdata,indices)
1290 copyduplicates(fontdata)
1291 unifymissing(fontdata)
1292 stripredundant(fontdata)
1293 end
1294
1295end
1296
1297function readers.checkhash(fontdata)
1298 local hashmethod = fontdata.hashmethod
1299 if hashmethod == "unicodes" then
1300 fontdata.names = nil
1301 elseif hashmethod == "names" and fontdata.names then
1302 unifyresources(fontdata,fontdata.names)
1303 copyduplicates(fontdata)
1304 fontdata.hashmethod = "unicodes"
1305 fontdata.names = nil
1306 else
1307 readers.rehash(fontdata,"unicodes")
1308 end
1309end
1310
1311function readers.addunicodetable(fontdata)
1312 local resources = fontdata.resources
1313 local unicodes = resources.unicodes
1314 if not unicodes then
1315 local descriptions = fontdata.descriptions
1316 if descriptions then
1317 unicodes = { }
1318 resources.unicodes = unicodes
1319 for u, d in next, descriptions do
1320 local n = d.name
1321 if n then
1322 unicodes[n] = u
1323 end
1324 end
1325 end
1326 end
1327end
1328
1329
1330
1331local concat, sort = table.concat, table.sort
1332local next, type, tostring = next, type, tostring
1333
1334local criterium = 1
1335local threshold = 0
1336
1337local trace_packing = false trackers.register("otf.packing", function(v) trace_packing = v end)
1338local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
1339
1340local report_otf = logs.reporter("fonts","otf loading")
1341
1342local function tabstr_normal(t)
1343 local s = { }
1344 local n = 0
1345 for k, v in next, t do
1346 n = n + 1
1347 if type(v) == "table" then
1348 s[n] = k .. ">" .. tabstr_normal(v)
1349 elseif v == true then
1350 s[n] = k .. "+"
1351 elseif v then
1352 s[n] = k .. "=" .. v
1353 else
1354 s[n] = k .. "-"
1355 end
1356 end
1357 if n == 0 then
1358 return ""
1359 elseif n == 1 then
1360 return s[1]
1361 else
1362 sort(s)
1363 return concat(s,",")
1364 end
1365end
1366
1367local function tabstr_flat(t)
1368 local s = { }
1369 local n = 0
1370 for k, v in next, t do
1371 n = n + 1
1372 s[n] = k .. "=" .. v
1373 end
1374 if n == 0 then
1375 return ""
1376 elseif n == 1 then
1377 return s[1]
1378 else
1379 sort(s)
1380 return concat(s,",")
1381 end
1382end
1383
1384local function tabstr_mixed(t)
1385 local n = #t
1386 if n == 0 then
1387 return ""
1388 elseif n == 1 then
1389 local k = t[1]
1390 if k == true then
1391 return "++"
1392 elseif k == false then
1393 return "--"
1394 else
1395 return tostring(k)
1396 end
1397 else
1398 local s = { }
1399 for i=1,n do
1400 local k = t[i]
1401 if k == true then
1402 s[i] = "++"
1403 elseif k == false then
1404 s[i] = "--"
1405 else
1406 s[i] = k
1407 end
1408 end
1409 return concat(s,",")
1410 end
1411end
1412
1413local function tabstr_boolean(t)
1414 local s = { }
1415 local n = 0
1416 for k, v in next, t do
1417 n = n + 1
1418 if v then
1419 s[n] = k .. "+"
1420 else
1421 s[n] = k .. "-"
1422 end
1423 end
1424 if n == 0 then
1425 return ""
1426 elseif n == 1 then
1427 return s[1]
1428 else
1429 sort(s)
1430 return concat(s,",")
1431 end
1432end
1433
1434
1435
1436
1437
1438
1439function readers.pack(data)
1440
1441 if data then
1442
1443 local h, t, c = { }, { }, { }
1444 local hh, tt, cc = { }, { }, { }
1445 local nt, ntt = 0, 0
1446
1447 local function pack_normal(v)
1448 local tag = tabstr_normal(v)
1449 local ht = h[tag]
1450 if ht then
1451 c[ht] = c[ht] + 1
1452 return ht
1453 else
1454 nt = nt + 1
1455 t[nt] = v
1456 h[tag] = nt
1457 c[nt] = 1
1458 return nt
1459 end
1460 end
1461
1462 local function pack_normal_cc(v)
1463 local tag = tabstr_normal(v)
1464 local ht = h[tag]
1465 if ht then
1466 c[ht] = c[ht] + 1
1467 return ht
1468 else
1469 v[1] = 0
1470 nt = nt + 1
1471 t[nt] = v
1472 h[tag] = nt
1473 c[nt] = 1
1474 return nt
1475 end
1476 end
1477
1478 local function pack_flat(v)
1479 local tag = tabstr_flat(v)
1480 local ht = h[tag]
1481 if ht then
1482 c[ht] = c[ht] + 1
1483 return ht
1484 else
1485 nt = nt + 1
1486 t[nt] = v
1487 h[tag] = nt
1488 c[nt] = 1
1489 return nt
1490 end
1491 end
1492
1493 local function pack_indexed(v)
1494 local tag = concat(v," ")
1495 local ht = h[tag]
1496 if ht then
1497 c[ht] = c[ht] + 1
1498 return ht
1499 else
1500 nt = nt + 1
1501 t[nt] = v
1502 h[tag] = nt
1503 c[nt] = 1
1504 return nt
1505 end
1506 end
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523 local function pack_mixed(v)
1524 local tag = tabstr_mixed(v)
1525 local ht = h[tag]
1526 if ht then
1527 c[ht] = c[ht] + 1
1528 return ht
1529 else
1530 nt = nt + 1
1531 t[nt] = v
1532 h[tag] = nt
1533 c[nt] = 1
1534 return nt
1535 end
1536 end
1537
1538
1539
1540
1541
1542 local function pack_boolean(v)
1543 local tag = tabstr_boolean(v)
1544 local ht = h[tag]
1545 if ht then
1546 c[ht] = c[ht] + 1
1547 return ht
1548 else
1549 nt = nt + 1
1550 t[nt] = v
1551 h[tag] = nt
1552 c[nt] = 1
1553 return nt
1554 end
1555 end
1556
1557 local function pack_final(v)
1558
1559 if c[v] <= criterium then
1560 return t[v]
1561 else
1562
1563 local hv = hh[v]
1564 if hv then
1565 return hv
1566 else
1567 ntt = ntt + 1
1568 tt[ntt] = t[v]
1569 hh[v] = ntt
1570 cc[ntt] = c[v]
1571 return ntt
1572 end
1573 end
1574 end
1575
1576 local function pack_final_cc(v)
1577
1578 if c[v] <= criterium then
1579 return t[v]
1580 else
1581
1582 local hv = hh[v]
1583 if hv then
1584 return hv
1585 else
1586 ntt = ntt + 1
1587 tt[ntt] = t[v]
1588 hh[v] = ntt
1589 cc[ntt] = c[v]
1590 return ntt
1591 end
1592 end
1593 end
1594
1595 local function success(stage,pass)
1596 if nt == 0 then
1597 if trace_loading or trace_packing then
1598 report_otf("pack quality: nothing to pack")
1599 end
1600 return false
1601 elseif nt >= threshold then
1602 local one = 0
1603 local two = 0
1604 local rest = 0
1605 if pass == 1 then
1606 for k,v in next, c do
1607 if v == 1 then
1608 one = one + 1
1609 elseif v == 2 then
1610 two = two + 1
1611 else
1612 rest = rest + 1
1613 end
1614 end
1615 else
1616 for k,v in next, cc do
1617 if v > 20 then
1618 rest = rest + 1
1619 elseif v > 10 then
1620 two = two + 1
1621 else
1622 one = one + 1
1623 end
1624 end
1625 data.tables = tt
1626 end
1627 if trace_loading or trace_packing then
1628 report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",
1629 stage, pass, one+two+rest, one, two, rest, criterium)
1630 end
1631 return true
1632 else
1633 if trace_loading or trace_packing then
1634 report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",
1635 stage, pass, nt, threshold)
1636 end
1637 return false
1638 end
1639 end
1640
1641 local function packers(pass)
1642 if pass == 1 then
1643 return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc
1644 else
1645 return pack_final, pack_final, pack_final, pack_final, pack_final, pack_final_cc
1646 end
1647 end
1648
1649 local resources = data.resources
1650 local sequences = resources.sequences
1651 local sublookups = resources.sublookups
1652 local features = resources.features
1653 local palettes = resources.colorpalettes
1654 local variable = resources.variabledata
1655
1656 local chardata = characters and characters.data
1657 local descriptions = data.descriptions or data.glyphs
1658
1659 if not descriptions then
1660 return
1661 end
1662
1663 for pass=1,2 do
1664
1665 if trace_packing then
1666 report_otf("start packing: stage 1, pass %s",pass)
1667 end
1668
1669 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1670
1671 for unicode, description in next, descriptions do
1672 local boundingbox = description.boundingbox
1673 if boundingbox then
1674 description.boundingbox = pack_indexed(boundingbox)
1675 end
1676 local math = description.math
1677 if math then
1678 local kerns = math.kerns
1679 if kerns then
1680 for tag, kern in next, kerns do
1681 kerns[tag] = pack_normal(kern)
1682 end
1683 end
1684 end
1685
1686
1687
1688
1689
1690
1691
1692
1693 end
1694
1695 local function packthem(sequences)
1696 for i=1,#sequences do
1697 local sequence = sequences[i]
1698 local kind = sequence.type
1699 local steps = sequence.steps
1700 local order = sequence.order
1701 local features = sequence.features
1702 local flags = sequence.flags
1703 if steps then
1704 for i=1,#steps do
1705 local step = steps[i]
1706 if kind == "gpos_pair" then
1707 local c = step.coverage
1708 if c then
1709 if step.format ~= "pair" then
1710 for g1, d1 in next, c do
1711 c[g1] = pack_normal(d1)
1712 end
1713 elseif step.shared then
1714
1715
1716
1717
1718 local shared = { }
1719 for g1, d1 in next, c do
1720 for g2, d2 in next, d1 do
1721 if not shared[d2] then
1722 local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1723 local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1724 shared[d2] = true
1725 end
1726 end
1727 end
1728 if pass == 2 then
1729 step.shared = nil
1730 end
1731 else
1732 for g1, d1 in next, c do
1733 for g2, d2 in next, d1 do
1734 local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1735 local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1736 end
1737 end
1738 end
1739 end
1740 elseif kind == "gpos_single" then
1741 local c = step.coverage
1742 if c then
1743 if step.format == "single" then
1744 for g1, d1 in next, c do
1745 if d1 and d1 ~= true then
1746 c[g1] = pack_indexed(d1)
1747 end
1748 end
1749 else
1750 step.coverage = pack_normal(c)
1751 end
1752 end
1753 elseif kind == "gpos_cursive" then
1754 local c = step.coverage
1755 if c then
1756 for g1, d1 in next, c do
1757 local f = d1[2] if f then d1[2] = pack_indexed(f) end
1758 local s = d1[3] if s then d1[3] = pack_indexed(s) end
1759 end
1760 end
1761 elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
1762 local c = step.baseclasses
1763 if c then
1764 for g1, d1 in next, c do
1765 for g2, d2 in next, d1 do
1766 d1[g2] = pack_indexed(d2)
1767 end
1768 end
1769 end
1770 local c = step.coverage
1771 if c then
1772 for g1, d1 in next, c do
1773 d1[2] = pack_indexed(d1[2])
1774 end
1775 end
1776 elseif kind == "gpos_mark2ligature" then
1777 local c = step.baseclasses
1778 if c then
1779 for g1, d1 in next, c do
1780 for g2, d2 in next, d1 do
1781 for g3, d3 in next, d2 do
1782 d2[g3] = pack_indexed(d3)
1783 end
1784 end
1785 end
1786 end
1787 local c = step.coverage
1788 if c then
1789 for g1, d1 in next, c do
1790 d1[2] = pack_indexed(d1[2])
1791 end
1792 end
1793 end
1794
1795 local rules = step.rules
1796 if rules then
1797 for i=1,#rules do
1798 local rule = rules[i]
1799 local r = rule.before if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1800 local r = rule.after if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1801 local r = rule.current if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1802
1803 local r = rule.replacements if r then rule.replacements = pack_flat (r) end
1804 end
1805 end
1806 end
1807 end
1808 if order then
1809 sequence.order = pack_indexed(order)
1810 end
1811 if features then
1812 for script, feature in next, features do
1813 features[script] = pack_normal(feature)
1814 end
1815 end
1816 if flags then
1817 sequence.flags = pack_normal(flags)
1818 end
1819 end
1820 end
1821
1822 if sequences then
1823 packthem(sequences)
1824 end
1825
1826 if sublookups then
1827 packthem(sublookups)
1828 end
1829
1830 if features then
1831 for k, list in next, features do
1832 for feature, spec in next, list do
1833 list[feature] = pack_normal(spec)
1834 end
1835 end
1836 end
1837
1838 if palettes then
1839 for i=1,#palettes do
1840 local p = palettes[i]
1841 for j=1,#p do
1842 p[j] = pack_indexed(p[j])
1843 end
1844 end
1845
1846 end
1847
1848 if variable then
1849
1850
1851
1852 local instances = variable.instances
1853 if instances then
1854 for i=1,#instances do
1855 local v = instances[i].values
1856 for j=1,#v do
1857 v[j] = pack_normal(v[j])
1858 end
1859 end
1860 end
1861
1862 local function packdeltas(main)
1863 if main then
1864 local deltas = main.deltas
1865 if deltas then
1866 for i=1,#deltas do
1867 local di = deltas[i]
1868 local d = di.deltas
1869
1870 for j=1,#d do
1871 d[j] = pack_indexed(d[j])
1872 end
1873 di.regions = pack_indexed(di.regions)
1874 end
1875 end
1876 local regions = main.regions
1877 if regions then
1878 for i=1,#regions do
1879 local r = regions[i]
1880 for j=1,#r do
1881 r[j] = pack_normal(r[j])
1882 end
1883 end
1884 end
1885 end
1886 end
1887
1888 packdeltas(variable.global)
1889 packdeltas(variable.horizontal)
1890 packdeltas(variable.vertical)
1891 packdeltas(variable.metrics)
1892
1893 end
1894
1895 if not success(1,pass) then
1896 return
1897 end
1898
1899 end
1900
1901 if nt > 0 then
1902
1903 for pass=1,2 do
1904
1905 if trace_packing then
1906 report_otf("start packing: stage 2, pass %s",pass)
1907 end
1908
1909 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1910
1911 for unicode, description in next, descriptions do
1912 local math = description.math
1913 if math then
1914 local kerns = math.kerns
1915 if kerns then
1916 math.kerns = pack_normal(kerns)
1917 end
1918 end
1919 end
1920
1921 local function packthem(sequences)
1922 for i=1,#sequences do
1923 local sequence = sequences[i]
1924 local kind = sequence.type
1925 local steps = sequence.steps
1926 local features = sequence.features
1927 if steps then
1928 for i=1,#steps do
1929 local step = steps[i]
1930 if kind == "gpos_pair" then
1931 local c = step.coverage
1932 if c then
1933 if step.format == "pair" then
1934 for g1, d1 in next, c do
1935 for g2, d2 in next, d1 do
1936 d1[g2] = pack_normal(d2)
1937 end
1938 end
1939 end
1940 end
1941
1942
1943
1944
1945
1946
1947
1948 elseif kind == "gpos_mark2ligature" then
1949 local c = step.baseclasses
1950 if c then
1951 for g1, d1 in next, c do
1952 for g2, d2 in next, d1 do
1953 d1[g2] = pack_normal(d2)
1954 end
1955 end
1956 end
1957 end
1958 local rules = step.rules
1959 if rules then
1960 for i=1,#rules do
1961 local rule = rules[i]
1962 local r = rule.before if r then rule.before = pack_normal(r) end
1963 local r = rule.after if r then rule.after = pack_normal(r) end
1964 local r = rule.current if r then rule.current = pack_normal(r) end
1965 end
1966 end
1967 end
1968 end
1969 if features then
1970 sequence.features = pack_normal(features)
1971 end
1972 end
1973 end
1974 if sequences then
1975 packthem(sequences)
1976 end
1977 if sublookups then
1978 packthem(sublookups)
1979 end
1980 if variable then
1981 local function unpackdeltas(main)
1982 if main then
1983 local regions = main.regions
1984 if regions then
1985 main.regions = pack_normal(regions)
1986 end
1987 end
1988 end
1989 unpackdeltas(variable.global)
1990 unpackdeltas(variable.horizontal)
1991 unpackdeltas(variable.vertical)
1992 unpackdeltas(variable.metrics)
1993 end
1994
1995
1996
1997 end
1998
1999 for pass=1,2 do
2000 if trace_packing then
2001 report_otf("start packing: stage 3, pass %s",pass)
2002 end
2003
2004 local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
2005
2006 local function packthem(sequences)
2007 for i=1,#sequences do
2008 local sequence = sequences[i]
2009 local kind = sequence.type
2010 local steps = sequence.steps
2011 local features = sequence.features
2012 if steps then
2013 for i=1,#steps do
2014 local step = steps[i]
2015 if kind == "gpos_pair" then
2016 local c = step.coverage
2017 if c then
2018 if step.format == "pair" then
2019 for g1, d1 in next, c do
2020 c[g1] = pack_normal(d1)
2021 end
2022 end
2023 end
2024 elseif kind == "gpos_cursive" then
2025 local c = step.coverage
2026 if c then
2027 for g1, d1 in next, c do
2028 c[g1] = pack_normal_cc(d1)
2029 end
2030 end
2031 end
2032 end
2033 end
2034 end
2035 end
2036
2037 if sequences then
2038 packthem(sequences)
2039 end
2040 if sublookups then
2041 packthem(sublookups)
2042 end
2043
2044 end
2045
2046 end
2047
2048 end
2049end
2050
2051local unpacked_mt = {
2052 __index =
2053 function(t,k)
2054 t[k] = false
2055 return k
2056 end
2057}
2058
2059function readers.unpack(data)
2060
2061 if data then
2062 local tables = data.tables
2063 if tables then
2064 local resources = data.resources
2065 local descriptions = data.descriptions or data.glyphs
2066 local sequences = resources.sequences
2067 local sublookups = resources.sublookups
2068 local features = resources.features
2069 local palettes = resources.colorpalettes
2070 local variable = resources.variabledata
2071 local unpacked = { }
2072 setmetatable(unpacked,unpacked_mt)
2073 for unicode, description in next, descriptions do
2074 local tv = tables[description.boundingbox]
2075 if tv then
2076 description.boundingbox = tv
2077 end
2078 local math = description.math
2079 if math then
2080 local kerns = math.kerns
2081 if kerns then
2082 local tm = tables[kerns]
2083 if tm then
2084 math.kerns = tm
2085 kerns = unpacked[tm]
2086 end
2087 if kerns then
2088 for k, kern in next, kerns do
2089 local tv = tables[kern]
2090 if tv then
2091 kerns[k] = tv
2092 end
2093 end
2094 end
2095 end
2096 end
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108 end
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119 local function unpackthem(sequences)
2120 for i=1,#sequences do
2121 local sequence = sequences[i]
2122 local kind = sequence.type
2123 local steps = sequence.steps
2124 local order = sequence.order
2125 local features = sequence.features
2126 local flags = sequence.flags
2127 local markclass = sequence.markclass
2128 if features then
2129 local tv = tables[features]
2130 if tv then
2131 sequence.features = tv
2132 features = tv
2133 end
2134 for script, feature in next, features do
2135 local tv = tables[feature]
2136 if tv then
2137 features[script] = tv
2138 end
2139 end
2140 end
2141 if steps then
2142 for i=1,#steps do
2143 local step = steps[i]
2144 if kind == "gpos_pair" then
2145 local c = step.coverage
2146 if c then
2147 if step.format == "pair" then
2148 for g1, d1 in next, c do
2149 local tv = tables[d1]
2150 if tv then
2151 c[g1] = tv
2152 d1 = tv
2153 end
2154 for g2, d2 in next, d1 do
2155 local tv = tables[d2]
2156 if tv then
2157 d1[g2] = tv
2158 d2 = tv
2159 end
2160 local f = tables[d2[1]] if f then d2[1] = f end
2161 local s = tables[d2[2]] if s then d2[2] = s end
2162 end
2163 end
2164 else
2165 for g1, d1 in next, c do
2166 local tv = tables[d1]
2167 if tv then
2168 c[g1] = tv
2169 end
2170 end
2171 end
2172 end
2173 elseif kind == "gpos_single" then
2174 local c = step.coverage
2175 if c then
2176 if step.format == "single" then
2177 for g1, d1 in next, c do
2178 local tv = tables[d1]
2179 if tv then
2180 c[g1] = tv
2181 end
2182 end
2183 else
2184 local tv = tables[c]
2185 if tv then
2186 step.coverage = tv
2187 end
2188 end
2189 end
2190 elseif kind == "gpos_cursive" then
2191 local c = step.coverage
2192 if c then
2193 for g1, d1 in next, c do
2194 local tv = tables[d1]
2195 if tv then
2196 d1 = tv
2197 c[g1] = d1
2198 end
2199 local f = tables[d1[2]] if f then d1[2] = f end
2200 local s = tables[d1[3]] if s then d1[3] = s end
2201 end
2202 end
2203 elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
2204 local c = step.baseclasses
2205 if c then
2206 for g1, d1 in next, c do
2207 for g2, d2 in next, d1 do
2208 local tv = tables[d2]
2209 if tv then
2210 d1[g2] = tv
2211 end
2212 end
2213 end
2214 end
2215 local c = step.coverage
2216 if c then
2217 for g1, d1 in next, c do
2218 local tv = tables[d1[2]]
2219 if tv then
2220 d1[2] = tv
2221 end
2222 end
2223 end
2224 elseif kind == "gpos_mark2ligature" then
2225 local c = step.baseclasses
2226 if c then
2227 for g1, d1 in next, c do
2228 for g2, d2 in next, d1 do
2229 local tv = tables[d2]
2230 if tv then
2231 d2 = tv
2232 d1[g2] = d2
2233 end
2234 for g3, d3 in next, d2 do
2235 local tv = tables[d2[g3]]
2236 if tv then
2237 d2[g3] = tv
2238 end
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 end
2253 local rules = step.rules
2254 if rules then
2255 for i=1,#rules do
2256 local rule = rules[i]
2257 local before = rule.before
2258 if before then
2259 local tv = tables[before]
2260 if tv then
2261 rule.before = tv
2262 before = tv
2263 end
2264 for i=1,#before do
2265 local tv = tables[before[i]]
2266 if tv then
2267 before[i] = tv
2268 end
2269 end
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282 end
2283 local after = rule.after
2284 if after then
2285 local tv = tables[after]
2286 if tv then
2287 rule.after = tv
2288 after = tv
2289 end
2290 for i=1,#after do
2291 local tv = tables[after[i]]
2292 if tv then
2293 after[i] = tv
2294 end
2295 end
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308 end
2309 local current = rule.current
2310 if current then
2311 local tv = tables[current]
2312 if tv then
2313 rule.current = tv
2314 current = tv
2315 end
2316 for i=1,#current do
2317 local tv = tables[current[i]]
2318 if tv then
2319 current[i] = tv
2320 end
2321 end
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334 end
2335
2336
2337
2338
2339
2340
2341
2342 local replacements = rule.replacements
2343 if replacements then
2344 local tv = tables[replacements]
2345 if tv then
2346 rule.replacements = tv
2347 end
2348 end
2349 end
2350 end
2351 end
2352 end
2353 if order then
2354 local tv = tables[order]
2355 if tv then
2356 sequence.order = tv
2357 end
2358 end
2359 if flags then
2360 local tv = tables[flags]
2361 if tv then
2362 sequence.flags = tv
2363 end
2364 end
2365 end
2366 end
2367
2368 if sequences then
2369 unpackthem(sequences)
2370 end
2371
2372 if sublookups then
2373 unpackthem(sublookups)
2374 end
2375
2376 if features then
2377 for k, list in next, features do
2378 for feature, spec in next, list do
2379 local tv = tables[spec]
2380 if tv then
2381 list[feature] = tv
2382 end
2383 end
2384 end
2385 end
2386
2387 if palettes then
2388 for i=1,#palettes do
2389 local p = palettes[i]
2390 for j=1,#p do
2391 local tv = tables[p[j]]
2392 if tv then
2393 p[j] = tv
2394 end
2395 end
2396 end
2397 end
2398
2399 if variable then
2400
2401
2402
2403 local instances = variable.instances
2404 if instances then
2405 for i=1,#instances do
2406 local v = instances[i].values
2407 for j=1,#v do
2408 local tv = tables[v[j]]
2409 if tv then
2410 v[j] = tv
2411 end
2412 end
2413 end
2414 end
2415
2416 local function unpackdeltas(main)
2417 if main then
2418 local deltas = main.deltas
2419 if deltas then
2420 for i=1,#deltas do
2421 local di = deltas[i]
2422 local d = di.deltas
2423 local r = di.regions
2424 for j=1,#d do
2425 local tv = tables[d[j]]
2426 if tv then
2427 d[j] = tv
2428 end
2429 end
2430 local tv = di.regions
2431 if tv then
2432 di.regions = tv
2433 end
2434 end
2435 end
2436 local regions = main.regions
2437 if regions then
2438 local tv = tables[regions]
2439 if tv then
2440 main.regions = tv
2441 regions = tv
2442 end
2443 for i=1,#regions do
2444 local r = regions[i]
2445 for j=1,#r do
2446 local tv = tables[r[j]]
2447 if tv then
2448 r[j] = tv
2449 end
2450 end
2451 end
2452 end
2453 end
2454 end
2455
2456 unpackdeltas(variable.global)
2457 unpackdeltas(variable.horizontal)
2458 unpackdeltas(variable.vertical)
2459 unpackdeltas(variable.metrics)
2460
2461 end
2462
2463 data.tables = nil
2464 end
2465 end
2466end
2467
2468local mt = {
2469 __index = function(t,k)
2470 if k == "height" then
2471 local ht = t.boundingbox[4]
2472 return ht < 0 and 0 or ht
2473 elseif k == "depth" then
2474 local dp = -t.boundingbox[2]
2475 return dp < 0 and 0 or dp
2476 elseif k == "width" then
2477 return 0
2478 elseif k == "name" then
2479 return forcenotdef and ".notdef"
2480 end
2481 end
2482}
2483
2484local function sameformat(sequence,steps,first,nofsteps,kind)
2485 return true
2486end
2487
2488local function mergesteps_1(lookup,strict)
2489 local steps = lookup.steps
2490 local nofsteps = lookup.nofsteps
2491 local first = steps[1]
2492 if strict then
2493 local f = first.format
2494 for i=2,nofsteps do
2495 if steps[i].format ~= f then
2496 if trace_optimizations then
2497 report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2498 end
2499 return 0
2500 end
2501 end
2502 end
2503 if trace_optimizations then
2504 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2505 end
2506 local target = first.coverage
2507 for i=2,nofsteps do
2508 local c = steps[i].coverage
2509 if c then
2510 for k, v in next, c do
2511 if not target[k] then
2512 target[k] = v
2513 end
2514 end
2515 end
2516 end
2517 lookup.nofsteps = 1
2518 lookup.merged = true
2519 lookup.steps = { first }
2520 return nofsteps - 1
2521end
2522
2523local function mergesteps_2(lookup)
2524
2525
2526
2527 local steps = lookup.steps
2528 local nofsteps = lookup.nofsteps
2529 local first = steps[1]
2530 if strict then
2531 local f = first.format
2532 for i=2,nofsteps do
2533 if steps[i].format ~= f then
2534 if trace_optimizations then
2535 report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2536 end
2537 return 0
2538 end
2539 end
2540 end
2541 if trace_optimizations then
2542 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2543 end
2544 local target = first.coverage
2545 for i=2,nofsteps do
2546 local c = steps[i].coverage
2547 if c then
2548 for k, v in next, c do
2549 local tk = target[k]
2550 if tk then
2551 for kk, vv in next, v do
2552 if tk[kk] == nil then
2553 tk[kk] = vv
2554 end
2555 end
2556 else
2557 target[k] = v
2558 end
2559 end
2560 end
2561 end
2562 lookup.nofsteps = 1
2563 lookup.merged = true
2564 lookup.steps = { first }
2565 return nofsteps - 1
2566end
2567
2568
2569
2570
2571local function mergesteps_3(lookup,strict)
2572 local steps = lookup.steps
2573 local nofsteps = lookup.nofsteps
2574 if trace_optimizations then
2575 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2576 end
2577
2578 local coverage = { }
2579 for i=1,nofsteps do
2580 local c = steps[i].coverage
2581 if c then
2582 for k, v in next, c do
2583 local tk = coverage[k]
2584 if tk then
2585 if trace_optimizations then
2586 report_optimizations("quitting merge due to multiple checks")
2587 end
2588 return nofsteps
2589 else
2590 coverage[k] = v
2591 end
2592 end
2593 end
2594 end
2595
2596 local first = steps[1]
2597 local baseclasses = { }
2598 for i=1,nofsteps do
2599 local offset = i*10
2600 local step = steps[i]
2601 for k, v in sortedhash(step.baseclasses) do
2602 baseclasses[offset+k] = v
2603 end
2604 for k, v in next, step.coverage do
2605 v[1] = offset + v[1]
2606 end
2607 end
2608 first.baseclasses = baseclasses
2609 first.coverage = coverage
2610 lookup.nofsteps = 1
2611 lookup.merged = true
2612 lookup.steps = { first }
2613 return nofsteps - 1
2614end
2615
2616local function nested(old,new)
2617 for k, v in next, old do
2618 if k == "ligature" then
2619 if not new.ligature then
2620 new.ligature = v
2621 end
2622 else
2623 local n = new[k]
2624 if n then
2625 nested(v,n)
2626 else
2627 new[k] = v
2628 end
2629 end
2630 end
2631end
2632
2633local function mergesteps_4(lookup)
2634 local steps = lookup.steps
2635 local nofsteps = lookup.nofsteps
2636 local first = steps[1]
2637 if trace_optimizations then
2638 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2639 end
2640 local target = first.coverage
2641 for i=2,nofsteps do
2642 local c = steps[i].coverage
2643 if c then
2644 for k, v in next, c do
2645 local tk = target[k]
2646 if tk then
2647 nested(v,tk)
2648 else
2649 target[k] = v
2650 end
2651 end
2652 end
2653 end
2654 lookup.nofsteps = 1
2655 lookup.steps = { first }
2656 return nofsteps - 1
2657end
2658
2659
2660
2661
2662
2663local function mergesteps_5(lookup)
2664 local steps = lookup.steps
2665 local nofsteps = lookup.nofsteps
2666 local first = steps[1]
2667 if trace_optimizations then
2668 report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2669 end
2670 local target = first.coverage
2671 local hash = nil
2672 for k, v in next, target do
2673 hash = v[1]
2674 break
2675 end
2676 for i=2,nofsteps do
2677 local c = steps[i].coverage
2678 if c then
2679 for k, v in next, c do
2680 local tk = target[k]
2681 if tk then
2682 if not tk[2] then
2683 tk[2] = v[2]
2684 end
2685 if not tk[3] then
2686 tk[3] = v[3]
2687 end
2688 else
2689 target[k] = v
2690 v[1] = hash
2691 end
2692 end
2693 end
2694 end
2695 lookup.nofsteps = 1
2696 lookup.merged = true
2697 lookup.steps = { first }
2698 return nofsteps - 1
2699end
2700
2701local function checkkerns(lookup)
2702 local steps = lookup.steps
2703 local nofsteps = lookup.nofsteps
2704 local kerned = 0
2705 for i=1,nofsteps do
2706 local step = steps[i]
2707 if step.format == "pair" then
2708 local coverage = step.coverage
2709 local kerns = true
2710 for g1, d1 in next, coverage do
2711 if d1 == true then
2712
2713 elseif not d1 then
2714
2715 elseif d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
2716 kerns = false
2717 break
2718 end
2719 end
2720 if kerns then
2721 if trace_optimizations then
2722 report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2723 end
2724 local c = { }
2725 for g1, d1 in next, coverage do
2726 if d1 and d1 ~= true then
2727 c[g1] = d1[3]
2728 end
2729 end
2730 step.coverage = c
2731 step.format = "move"
2732 kerned = kerned + 1
2733 end
2734 end
2735 end
2736 return kerned
2737end
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753local strip_pairs = true
2754
2755local compact_pairs = true
2756local compact_singles = true
2757
2758local merge_pairs = true
2759local merge_singles = true
2760local merge_substitutions = true
2761local merge_alternates = true
2762local merge_multiples = true
2763local merge_ligatures = true
2764local merge_cursives = true
2765local merge_marks = true
2766
2767directives.register("otf.strip.pairs", function(v) strip_pairs = v end)
2768
2769directives.register("otf.compact.pairs", function(v) compact_pairs = v end)
2770directives.register("otf.compact.singles", function(v) compact_singles = v end)
2771
2772directives.register("otf.merge.pairs", function(v) merge_pairs = v end)
2773directives.register("otf.merge.singles", function(v) merge_singles = v end)
2774directives.register("otf.merge.substitutions", function(v) merge_substitutions = v end)
2775directives.register("otf.merge.alternates", function(v) merge_alternates = v end)
2776directives.register("otf.merge.multiples", function(v) merge_multiples = v end)
2777directives.register("otf.merge.ligatures", function(v) merge_ligatures = v end)
2778directives.register("otf.merge.cursives", function(v) merge_cursives = v end)
2779directives.register("otf.merge.marks", function(v) merge_marks = v end)
2780
2781local function checkpairs(lookup)
2782 local steps = lookup.steps
2783 local nofsteps = lookup.nofsteps
2784 local kerned = 0
2785
2786 local function onlykerns(step)
2787 local coverage = step.coverage
2788 for g1, d1 in next, coverage do
2789 for g2, d2 in next, d1 do
2790 if d2[2] then
2791
2792 return false
2793 else
2794 local v = d2[1]
2795 if v == true then
2796
2797 elseif v and (v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0) then
2798
2799 return false
2800 end
2801 end
2802 end
2803 end
2804 return coverage
2805 end
2806
2807 for i=1,nofsteps do
2808 local step = steps[i]
2809 if step.format == "pair" then
2810 local coverage = onlykerns(step)
2811 if coverage then
2812 if trace_optimizations then
2813 report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2814 end
2815 for g1, d1 in next, coverage do
2816 local d = { }
2817 for g2, d2 in next, d1 do
2818 local v = d2[1]
2819 if v == true then
2820
2821 elseif v then
2822 d[g2] = v[3]
2823 end
2824 end
2825 coverage[g1] = d
2826 end
2827 step.format = "move"
2828 kerned = kerned + 1
2829 end
2830 end
2831 end
2832 return kerned
2833end
2834
2835local function strippairs(lookup)
2836 local steps = lookup.steps
2837 local nofsteps = lookup.nofsteps
2838 local stripped = 0
2839
2840 for i=1,nofsteps do
2841 local step = steps[i]
2842 if step.format == "pair" then
2843 local coverage = step.coverage
2844 for g1, d1 in next, coverage do
2845 for g2, d2 in next, d1 do
2846 if d2[2] then
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858 elseif d2[1] == true then
2859 d1[g2] = nil
2860 stripped = stripped + 1
2861 end
2862 end
2863 end
2864 end
2865 end
2866 return stripped
2867end
2868
2869function readers.compact(data)
2870 if not data or data.compacted then
2871 return
2872 else
2873 data.compacted = true
2874 end
2875 local resources = data.resources
2876 local stripped = 0
2877 local merged = 0
2878 local kerned = 0
2879 local allsteps = 0
2880 local function compact(what)
2881 local lookups = resources[what]
2882 if lookups then
2883 for i=1,#lookups do
2884 local lookup = lookups[i]
2885 local nofsteps = lookup.nofsteps
2886 local kind = lookup.type
2887 allsteps = allsteps + nofsteps
2888 if nofsteps > 1 then
2889 local merg = merged
2890 if kind == "gsub_single" then
2891 if merge_substitutions then
2892 merged = merged + mergesteps_1(lookup)
2893 end
2894 elseif kind == "gsub_alternate" then
2895 if merge_alternates then
2896 merged = merged + mergesteps_1(lookup)
2897 end
2898 elseif kind == "gsub_multiple" then
2899 if merge_multiples then
2900 merged = merged + mergesteps_1(lookup)
2901 end
2902 elseif kind == "gsub_ligature" then
2903 if merge_ligatures then
2904 merged = merged + mergesteps_4(lookup)
2905 end
2906 elseif kind == "gpos_single" then
2907
2908 if merge_singles then
2909 merged = merged + mergesteps_1(lookup,true)
2910 end
2911 if compact_singles then
2912 kerned = kerned + checkkerns(lookup)
2913 end
2914 elseif kind == "gpos_pair" then
2915 if strip_pairs then
2916 stripped = stripped + strippairs(lookup)
2917 end
2918 if merge_pairs then
2919 merged = merged + mergesteps_2(lookup)
2920 end
2921 if compact_pairs then
2922 kerned = kerned + checkpairs(lookup)
2923 end
2924 elseif kind == "gpos_cursive" then
2925 if merge_cursives then
2926 merged = merged + mergesteps_5(lookup)
2927 end
2928 elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
2929 if merge_marks then
2930 merged = merged + mergesteps_3(lookup)
2931 end
2932 end
2933 if merg ~= merged then
2934 lookup.merged = true
2935 end
2936 elseif nofsteps == 1 then
2937 local kern = kerned
2938 if kind == "gpos_single" then
2939 if compact_singles then
2940 kerned = kerned + checkkerns(lookup)
2941 end
2942 elseif kind == "gpos_pair" then
2943 if compact_pairs then
2944 kerned = kerned + checkpairs(lookup)
2945 end
2946 end
2947 if kern ~= kerned then
2948
2949 end
2950 end
2951 end
2952 elseif trace_optimizations then
2953 report_optimizations("no lookups in %a",what)
2954 end
2955 end
2956 compact("sequences")
2957 compact("sublookups")
2958 if trace_optimizations then
2959 if stripped > 0 then
2960 report_optimizations("%i zero positions stripped before merging",stripped)
2961 end
2962 if merged > 0 then
2963 report_optimizations("%i steps of %i removed due to merging",merged,allsteps)
2964 end
2965 if kerned > 0 then
2966 report_optimizations("%i steps of %i steps turned from pairs into kerns",kerned,allsteps)
2967 end
2968 end
2969end
2970
2971if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
2972
2973 local done = 0
2974
2975 local function condense_1(k,v,t)
2976 if type(v) == "table" then
2977 local u = false
2978 local l = false
2979 for k, v in next, v do
2980 if k == "ligature" then
2981 l = v
2982 if u then
2983 break
2984 end
2985 elseif u then
2986 break
2987 else
2988 u = true
2989 end
2990 end
2991 if l and not u then
2992 t[k] = l
2993 done = done + 1
2994 end
2995 if u then
2996 for k, vv in next, v do
2997 if k ~= "ligature" then
2998 condense_1(k,vv,v)
2999 end
3000 end
3001 end
3002 end
3003 end
3004
3005 local function condensesteps_1(lookup)
3006 done = 0
3007 if lookup.type == "gsub_ligature" then
3008 local steps = lookup.steps
3009 if steps then
3010 for i=1,#steps do
3011 local step = steps[i]
3012 local coverage = step.coverage
3013 if coverage then
3014 for k, v in next, coverage do
3015 if condense_1(k,v,coverage) then
3016 coverage[k] = v.ligature
3017 done = done + 1
3018 end
3019 end
3020 end
3021 end
3022 end
3023 end
3024 return done
3025 end
3026
3027 function readers.condense(data)
3028 if not data or data.condensed then
3029 return
3030 else
3031 data.condensed = true
3032 end
3033 local resources = data.resources
3034 local condensed = 0
3035 local function condense(what)
3036 local lookups = resources[what]
3037 if lookups then
3038 for i=1,#lookups do
3039 condensed = condensed + condensesteps_1(lookups[i])
3040 end
3041 elseif trace_optimizations then
3042 report_optimizations("no lookups in %a",what)
3043 end
3044 end
3045 condense("sequences")
3046 condense("sublookups")
3047 if trace_optimizations then
3048 if condensed > 0 then
3049 report_optimizations("%i ligatures condensed",condensed)
3050 end
3051 end
3052 end
3053
3054end
3055
3056local function mergesteps(t,k)
3057 if k == "merged" then
3058 local merged = { }
3059 for i=1,#t do
3060 local step = t[i]
3061 local coverage = step.coverage
3062 for k in next, coverage do
3063 local m = merged[k]
3064 if m then
3065 m[2] = i
3066
3067 else
3068 merged[k] = { i, i }
3069
3070 end
3071 end
3072 end
3073 t.merged = merged
3074 return merged
3075 end
3076end
3077
3078local function checkmerge(sequence)
3079 local steps = sequence.steps
3080 if steps then
3081 setmetatableindex(steps,mergesteps)
3082 end
3083end
3084
3085local function checkflags(sequence,resources)
3086 if not sequence.skiphash then
3087 local flags = sequence.flags
3088 if flags then
3089 local skipmark = flags[1]
3090 local skipligature = flags[2]
3091 local skipbase = flags[3]
3092 local markclass = sequence.markclass
3093 local skipsome = skipmark or skipligature or skipbase or markclass or false
3094 if skipsome then
3095 sequence.skiphash = setmetatableindex(function(t,k)
3096 local c = resources.classes[k]
3097 local v = c == skipmark
3098 or (markclass and c == "mark" and not markclass[k])
3099 or c == skipligature
3100 or c == skipbase
3101 or false
3102 t[k] = v
3103 return v
3104 end)
3105 else
3106 sequence.skiphash = false
3107 end
3108 else
3109 sequence.skiphash = false
3110 end
3111 end
3112end
3113
3114local function checksteps(sequence)
3115 local steps = sequence.steps
3116 if steps then
3117 for i=1,#steps do
3118 steps[i].index = i
3119 end
3120 end
3121end
3122
3123if fonts.helpers then
3124 fonts.helpers.checkmerge = checkmerge
3125 fonts.helpers.checkflags = checkflags
3126 fonts.helpers.checksteps = checksteps
3127end
3128
3129function readers.expand(data)
3130 if not data or data.expanded then
3131 return
3132 else
3133 data.expanded = true
3134 end
3135 local resources = data.resources
3136 local sublookups = resources.sublookups
3137 local sequences = resources.sequences
3138 local markclasses = resources.markclasses
3139 local descriptions = data.descriptions
3140 if descriptions then
3141 local defaultwidth = resources.defaultwidth or 0
3142 local defaultheight = resources.defaultheight or 0
3143 local defaultdepth = resources.defaultdepth or 0
3144 local basename = trace_markwidth and file.basename(resources.filename)
3145 for u, d in next, descriptions do
3146 local bb = d.boundingbox
3147 local wd = d.width
3148 if not wd then
3149
3150 d.width = defaultwidth
3151 elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
3152 report_markwidth("mark %a with width %b found in %a",d.name or "<noname>",wd,basename)
3153 end
3154 if bb then
3155 local ht = bb[4]
3156 local dp = -bb[2]
3157 if ht == 0 or ht < 0 then
3158
3159 else
3160 d.height = ht
3161 end
3162 if dp == 0 or dp < 0 then
3163
3164 else
3165 d.depth = dp
3166 end
3167 end
3168 end
3169 end
3170
3171
3172
3173
3174
3175 local function expandlookups(sequences,whatever)
3176 if sequences then
3177
3178 for i=1,#sequences do
3179 local sequence = sequences[i]
3180 local steps = sequence.steps
3181 if steps then
3182 local nofsteps = sequence.nofsteps
3183
3184 local kind = sequence.type
3185 local markclass = sequence.markclass
3186 if markclass then
3187 if not markclasses then
3188 report_warning("missing markclasses")
3189 sequence.markclass = false
3190 else
3191 sequence.markclass = markclasses[markclass]
3192 end
3193 end
3194
3195 for i=1,nofsteps do
3196 local step = steps[i]
3197 local baseclasses = step.baseclasses
3198 if baseclasses then
3199 local coverage = step.coverage
3200 for k, v in next, coverage do
3201 v[1] = baseclasses[v[1]]
3202 end
3203 elseif kind == "gpos_cursive" then
3204 local coverage = step.coverage
3205 for k, v in next, coverage do
3206 v[1] = coverage
3207 end
3208 end
3209 local rules = step.rules
3210 if rules then
3211 local rulehash = { n = 0 }
3212 local rulesize = 0
3213 local coverage = { }
3214 local lookuptype = sequence.type
3215 local nofrules = #rules
3216 step.coverage = coverage
3217 for currentrule=1,nofrules do
3218 local rule = rules[currentrule]
3219 local current = rule.current
3220 local before = rule.before
3221 local after = rule.after
3222 local replacements = rule.replacements or false
3223 local sequence = { }
3224 local nofsequences = 0
3225 if before then
3226 for n=1,#before do
3227 nofsequences = nofsequences + 1
3228 sequence[nofsequences] = before[n]
3229 end
3230 end
3231 local start = nofsequences + 1
3232 for n=1,#current do
3233 nofsequences = nofsequences + 1
3234 sequence[nofsequences] = current[n]
3235 end
3236 local stop = nofsequences
3237 if after then
3238 for n=1,#after do
3239 nofsequences = nofsequences + 1
3240 sequence[nofsequences] = after[n]
3241 end
3242 end
3243 local lookups = rule.lookups or false
3244 local subtype = nil
3245 if lookups then
3246 for i=1,#lookups do
3247 local lookups = lookups[i]
3248 if lookups then
3249 for k, v in next, lookups do
3250 local lookup = sublookups[v]
3251if not lookup and whatever then
3252 lookup = whatever[v]
3253end
3254 if lookup then
3255 lookups[k] = lookup
3256 if not subtype then
3257 subtype = lookup.type
3258 end
3259 else
3260
3261 end
3262 end
3263 end
3264 end
3265 end
3266 if sequence[1] then
3267 sequence.n = #sequence
3268 local ruledata = {
3269 currentrule,
3270 lookuptype,
3271 sequence,
3272 start,
3273 stop,
3274 lookups,
3275 replacements,
3276 subtype,
3277 }
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289 rulesize = rulesize + 1
3290 rulehash[rulesize] = ruledata
3291 rulehash.n = rulesize
3292
3293 if true then
3294
3295 for unic in next, sequence[start] do
3296 local cu = coverage[unic]
3297 if cu then
3298 local n = #cu+1
3299 cu[n] = ruledata
3300 cu.n = n
3301 else
3302 coverage[unic] = { ruledata, n = 1 }
3303 end
3304 end
3305
3306 else
3307
3308 for unic in next, sequence[start] do
3309 local cu = coverage[unic]
3310 if cu then
3311
3312
3313 else
3314 coverage[unic] = rulehash
3315 end
3316 end
3317
3318 end
3319 end
3320 end
3321 end
3322 end
3323
3324 checkmerge(sequence)
3325 checkflags(sequence,resources)
3326 checksteps(sequence)
3327
3328 end
3329 end
3330 end
3331 end
3332
3333 expandlookups(sequences)
3334 expandlookups(sublookups,sequences)
3335end
3336 |