1if not modules then modules = { } end modules ['node-fnt'] = { 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 9if not context then os.exit() end -- generic function in node-dum 10 11local next, type = next, type 12local concat, keys = table.concat, table.keys 13 14local nodes, node, fonts = nodes, node, fonts 15 16local trace_characters = false trackers.register("nodes.characters", function(v) trace_characters = v end) 17local trace_fontrun = false trackers.register("nodes.fontrun", function(v) trace_fontrun = v end) 18local trace_variants = false trackers.register("nodes.variants", function(v) trace_variants = v end) 19 20-- bad namespace for directives 21 22local force_discrun = true directives.register("nodes.discrun", function(v) force_discrun = v end) 23local force_boundaryrun = true directives.register("nodes.boundaryrun", function(v) force_boundaryrun = v end) 24----- force_basepass = true directives.register("nodes.basepass", function(v) force_basepass = v end) 25local keep_redundant = false directives.register("nodes.keepredundant",function(v) keep_redundant = v end) 26 27local report_fonts = logs.reporter("fonts","processing") 28 29local fonthashes = fonts.hashes 30local fontdata = fonthashes.identifiers 31local fontvariants = fonthashes.variants 32local fontmodes = fonthashes.modes 33 34local otf = fonts.handlers.otf 35 36local starttiming = statistics.starttiming 37local stoptiming = statistics.stoptiming 38 39local nodecodes = nodes.nodecodes 40local boundarycodes = nodes.boundarycodes 41 42local handlers = nodes.handlers 43 44local nuts = nodes.nuts 45 46local getid = nuts.getid 47local getsubtype = nuts.getsubtype 48local getreplace = nuts.getreplace 49local getnext = nuts.getnext 50local getprev = nuts.getprev 51local getboth = nuts.getboth 52local getdata = nuts.getdata 53local getglyphdata = nuts.getglyphdata 54 55local setchar = nuts.setchar 56local setlink = nuts.setlink 57local setnext = nuts.setnext 58local setprev = nuts.setprev 59 60local isglyph = nuts.isglyph -- unchecked 61local ischar = nuts.ischar -- checked 62 63local nextboundary = nuts.traversers.boundary 64local nextdisc = nuts.traversers.disc 65local nextchar = nuts.traversers.char 66 67local flushnode = nuts.flush 68local removefromlist = nuts.removefromlist 69 70local disc_code = nodecodes.disc 71local boundary_code = nodecodes.boundary 72 73local wordboundary_code = boundarycodes.word 74 75local protectglyphs = nuts.protectglyphs 76local unprotectglyphs = nuts.unprotectglyphs 77local protectglyphsnone = nuts.protectglyphsnone 78 79local setmetatableindex = table.setmetatableindex 80 81-- some tests with using an array of dynamics[id] and processes[id] demonstrated 82-- that there was nothing to gain (unless we also optimize other parts) 83-- 84-- maybe getting rid of the intermediate shared can save some time 85 86local run = 0 87 88local setfontdynamics = { } 89local fontprocesses = { } 90 91setmetatableindex(setfontdynamics, function(t,font) 92 local tfmdata = fontdata[font] 93 local shared = tfmdata.shared 94 local f = shared and shared.dynamics and otf.setdynamics or false 95 if f then 96 local v = { } 97 t[font] = v 98 setmetatableindex(v,function(t,k) 99 local v = f(font,k) 100 t[k] = v 101 return v 102 end) 103 return v 104 else 105 t[font] = false 106 return false 107 end 108end) 109 110setmetatableindex(fontprocesses, function(t,font) 111 local tfmdata = fontdata[font] 112 local shared = tfmdata.shared -- we need to check shared, only when same features 113 local processes = shared and shared.processes 114 if processes and #processes > 0 then 115 t[font] = processes 116 return processes 117 else 118 t[font] = false 119 return false 120 end 121end) 122 123fonts.hashes.setdynamics = setfontdynamics 124fonts.hashes.processes = fontprocesses 125 126-- if we forget about basemode we don't need to test too much here and we can consider running 127-- over sub-ranges .. this involves a bit more initializations but who cares .. in that case we 128-- also need to use the stop criterium (we already use head too) ... we cannot use traverse 129-- then, so i'll test it on some local clone first ... the only pitfall is changed directions 130-- inside a run which means that we need to keep track of this which in turn complicates matters 131-- in a way i don't like 132 133-- we need to deal with the basemode fonts here and can only run over ranges as we otherwise get 134-- luatex craches due to all kind of asserts in the disc/lig builder 135 136-- there is no gain in merging used (dynamic 0) and dynamics apart from a bit less code 137 138local ligaturing = nuts.ligaturing 139local kerning = nuts.kerning 140 141-- local function start_trace(head) 142-- run = run + 1 143-- report_fonts() 144-- report_fonts("checking node list, run %s",run) 145-- report_fonts() 146-- local n = head 147-- while n do 148-- local char, id = isglyph(n) 149-- if char then 150-- local font = id 151-- local dynamic = getglyphdata(n) or 0 152-- report_fonts("font %03i, dynamic %03i, glyph %C",font,dynamic,char) 153-- elseif id == disc_code then 154-- report_fonts("[disc] %s",nodes.listtoutf(n,true,false,n)) 155-- elseif id == boundary_code then 156-- report_fonts("[boundary] %i:%i",getsubtype(n),getdata(n)) 157-- else 158-- report_fonts("[%s]",nodecodes[id]) 159-- end 160-- n = getnext(n) 161-- end 162-- end 163 164-- local function stop_trace(u,usedfonts,d,dynamicfonts,b,basefonts,r,redundant) 165-- report_fonts() 166-- report_fonts("statics : %s",u > 0 and concat(keys(usedfonts)," ") or "none") 167-- report_fonts("dynamics: %s",d > 0 and concat(keys(dynamicfonts)," ") or "none") 168-- report_fonts("built-in: %s",b > 0 and b or "none") 169-- report_fonts("removed : %s",r > 0 and r or "none") 170-- report_fonts() 171-- end 172 173-- This is the original handler and we keep it around as reference. It served us 174-- well for quite a while. 175 176-- do 177-- 178-- local usedfonts 179-- local dynamicfonts 180-- local basefonts -- could be reused 181-- local basefont 182-- local prevfont 183-- local prevdynamic 184-- local variants 185-- local redundant -- could be reused 186-- local firstnone 187-- local lastfont 188-- local lastproc 189-- local lastnone 190-- 191-- local d, u, b, r 192-- 193-- local function protectnone() 194-- protectglyphs(firstnone,lastnone) 195-- firstnone = nil 196-- end 197-- 198-- local function setnone(n) 199-- if firstnone then 200-- protectnone() 201-- end 202-- if basefont then 203-- basefont[2] = getprev(n) 204-- basefont = false 205-- end 206-- if not firstnone then 207-- firstnone = n 208-- end 209-- lastnone = n 210-- end 211-- 212-- local function setbase(n) 213-- if firstnone then 214-- protectnone() 215-- end 216-- if force_basepass then 217-- if basefont then 218-- basefont[2] = getprev(n) 219-- end 220-- b = b + 1 221-- basefont = { n, false } 222-- basefonts[b] = basefont 223-- end 224-- end 225-- 226-- local function setnode(n,font,dynamic) -- we could use prevfont and prevdynamic when we set then first 227-- if firstnone then 228-- protectnone() 229-- end 230-- if basefont then 231-- basefont[2] = getprev(n) 232-- basefont = false 233-- end 234-- if dynamic > 0 then 235-- local used = dynamicfonts[font] 236-- if not used then 237-- used = { } 238-- dynamicfonts[font] = used 239-- end 240-- if not used[dynamic] then 241-- local fd = setfontdynamics[font] 242-- if fd then 243-- used[dynamic] = fd[dynamic] 244-- d = d + 1 245-- end 246-- end 247-- else 248-- local used = usedfonts[font] 249-- if not used then 250-- lastfont = font 251-- lastproc = fontprocesses[font] 252-- if lastproc then 253-- usedfonts[font] = lastproc 254-- u = u + 1 255-- end 256-- end 257-- end 258-- end 259-- 260-- function handlers.characters(head,groupcode,direction) 261-- -- either next or not, but definitely no already processed list 262-- starttiming(nodes) 263-- 264-- usedfonts = { } 265-- dynamicfonts = { } 266-- basefonts = { } 267-- basefont = nil 268-- prevfont = nil 269-- prevdynamic = 0 270-- variants = nil 271-- redundant = nil 272-- firstnone = nil 273-- lastfont = nil 274-- lastproc = nil 275-- lastnone = nil 276-- 277-- local fontmode = nil -- base none or other 278-- 279-- d, u, b, r = 0, 0, 0, 0 280-- 281-- if trace_fontrun then 282-- start_trace(head) 283-- end 284-- 285-- -- There is no gain in checking for a single glyph and then having a fast path. On the 286-- -- metafun manual (with some 2500 single char lists) the difference is just noise. 287-- 288-- for n, char, font, dynamic in nextchar, head do 289-- 290-- if font ~= prevfont then 291-- prevfont = font 292-- fontmode = fontmodes[font] 293-- if fontmode == "none" then 294-- prevdynamic = 0 295-- variants = false 296-- setnone(n) 297-- elseif fontmode == "base" then 298-- prevdynamic = 0 299-- variants = false 300-- setbase(n) 301-- else 302-- -- local dynamic = getglyphdata(n) or 0 -- zero dynamic is reserved for fonts in context 303-- prevdynamic = dynamic 304-- variants = fontvariants[font] 305-- setnode(n,font,dynamic) 306-- end 307-- elseif fontmode == "node" then 308-- local dynamic = getglyphdata(n) or 0 -- zero dynamic is reserved for fonts in context 309-- if dynamic ~= prevdynamic then 310-- prevdynamic = dynamic 311-- variants = fontvariants[font] 312-- setnode(n,font,dynamic) 313-- end 314-- elseif firstnone then 315-- lastnone = n 316-- end 317-- 318-- if variants then 319-- if (char >= 0xFE00 and char <= 0xFE0F) or (char >= 0xE0100 and char <= 0xE01EF) then 320-- -- if variants and char >= 0xFE00 then 321-- -- if char < 0xFE0F or (char >= 0xE0100 and char <= 0xE01EF) then 322-- local hash = variants[char] 323-- if hash then 324-- local p = getprev(n) 325-- if p then 326-- local char = ischar(p) -- checked 327-- local variant = hash[char] 328-- if variant then 329-- if trace_variants then 330-- report_fonts("replacing %C by %C",char,variant) 331-- end 332-- setchar(p,variant) 333-- if redundant then 334-- r = r + 1 335-- redundant[r] = n 336-- else 337-- r = 1 338-- redundant = { n } 339-- end 340-- end 341-- end 342-- elseif keep_redundant then 343-- -- go on, can be used for tracing 344-- elseif redundant then 345-- r = r + 1 346-- redundant[r] = n 347-- else 348-- r = 1 349-- redundant = { n } 350-- end 351-- end 352-- end 353-- 354-- end 355-- 356-- if firstnone then 357-- protectnone() 358-- end 359-- 360-- if force_boundaryrun then 361-- 362-- -- we can inject wordboundaries and then let the hyphenator do its work 363-- -- but we need to get rid of those nodes in order to build ligatures 364-- -- and kern (a rather context thing) 365-- 366-- for b, subtype in nextboundary, head do 367-- if subtype == wordboundary_code then 368-- if redundant then 369-- r = r + 1 370-- redundant[r] = b 371-- else 372-- r = 1 373-- redundant = { b } 374-- end 375-- end 376-- end 377-- 378-- end 379-- 380-- if redundant then 381-- for i=1,r do 382-- local r = redundant[i] 383-- local p, n = getboth(r) 384-- if r == head then 385-- head = n 386-- setprev(n) 387-- else 388-- setlink(p,n) 389-- end 390-- if b > 0 then 391-- for i=1,b do 392-- local bi = basefonts[i] 393-- local b1 = bi[1] 394-- local b2 = bi[2] 395-- if b1 == b2 then 396-- if b1 == r then 397-- bi[1] = false 398-- bi[2] = false 399-- end 400-- elseif b1 == r then 401-- bi[1] = n 402-- elseif b2 == r then 403-- bi[2] = p 404-- end 405-- end 406-- end 407-- flushnode(r) 408-- end 409-- end 410-- 411-- if force_discrun then 412-- -- basefont is not supported in disc only runs ... it would mean a lot of 413-- -- ranges .. we could try to run basemode as a separate processor run but not 414-- -- for now (we can consider it when the new node code is tested 415-- for disc in nextdisc, head do 416-- -- doing only replace is good enough because pre and post are normally used 417-- -- for hyphens and these come from fonts that part of the hyphenated word 418-- local r = getreplace(disc) 419-- if r then 420-- local prevfont = nil 421-- local prevdynamic = nil 422-- local none = false 423-- firstnone = nil 424-- basefont = nil 425-- for n, char, font, dynamic in nextchar, r do 426-- -- local dynamic = getglyphdata(n) or 0 -- zero dynamic is reserved for fonts in context 427-- if font ~= prevfont or dynamic ~= prevdynamic then 428-- prevfont = font 429-- prevdynamic = dynamic 430-- local fontmode = fontmodes[font] 431-- if fontmode == "none" then 432-- setnone(n) 433-- elseif fontmode == "base" then 434-- -- so the replace gets an extra treatment ... so be it 435-- setbase(n) 436-- else 437-- setnode(n,font,dynamic) 438-- end 439-- elseif firstnone then 440-- -- lastnone = n 441-- lastnone = nil 442-- end 443-- -- we assume one font for now (and if there are more and we get into issues then 444-- -- we can always remove the break) 445-- break 446-- end 447-- if firstnone then 448-- protectnone() 449-- end 450-- end 451-- end 452-- 453-- end 454-- 455-- if trace_fontrun then 456-- stop_trace(u,usedfonts,d,dynamicfonts,b,basefonts,r,redundant) 457-- end 458-- 459-- -- in context we always have at least 2 processors 460-- if u == 0 then 461-- -- skip 462-- elseif u == 1 then 463-- for i=1,#lastproc do 464-- head = lastproc[i](head,lastfont,0,direction) 465-- end 466-- else 467-- for font, processors in next, usedfonts do -- unordered 468-- for i=1,#processors do 469-- head = processors[i](head,font,0,direction,u) -- u triggers disc optimizer 470-- end 471-- end 472-- end 473-- 474-- if d == 0 then 475-- -- skip 476-- elseif d == 1 then 477-- local font, dynamics = next(dynamicfonts) 478-- for dynamic, processors in next, dynamics do -- unordered, dynamic can switch in between 479-- for i=1,#processors do 480-- head = processors[i](head,font,dynamic,direction) 481-- end 482-- end 483-- else 484-- for font, dynamics in next, dynamicfonts do 485-- for dynamic, processors in next, dynamics do -- unordered, dynamic can switch in between 486-- for i=1,#processors do 487-- head = processors[i](head,font,dynamic,direction,d) -- d triggers disc optimizer 488-- end 489-- end 490-- end 491-- end 492-- if b == 0 then 493-- -- skip 494-- elseif b == 1 then 495-- -- only one font 496-- local range = basefonts[1] 497-- local start = range[1] 498-- local stop = range[2] 499-- if (start or stop) and (start ~= stop) then 500-- local front = head == start 501-- if stop then 502-- start = ligaturing(start,stop) 503-- start = kerning(start,stop) 504-- elseif start then -- safeguard 505-- start = ligaturing(start) 506-- start = kerning(start) 507-- end 508-- if front and head ~= start then 509-- head = start 510-- end 511-- end 512-- else 513-- -- multiple fonts 514-- for i=1,b do 515-- local range = basefonts[i] 516-- local start = range[1] 517-- local stop = range[2] 518-- if start then -- and start ~= stop but that seldom happens 519-- local front = head == start 520-- local prev = getprev(start) 521-- local next = getnext(stop) 522-- if stop then 523-- start, stop = ligaturing(start,stop) 524-- start, stop = kerning(start,stop) 525-- else 526-- start = ligaturing(start) 527-- start = kerning(start) 528-- end 529-- -- is done automatically 530-- if prev then 531-- setlink(prev,start) 532-- end 533-- if next then 534-- setlink(stop,next) 535-- end 536-- -- till here 537-- if front and head ~= start then 538-- head = start 539-- end 540-- end 541-- end 542-- end 543-- 544-- stoptiming(nodes) 545-- 546-- if trace_characters then 547-- nodes.report(head) 548-- end 549-- 550-- return head 551-- end 552-- 553-- end 554 555 556-- This variant uses less code but relies on the engine checking the textcontrol 557-- flags: 558-- 559-- baseligatures : 0x02 560-- basekerns : 0x04 561-- noneprotected : 0x08 562-- 563-- This permits one 'base' pass instead of multiple over ranges which is kind of 564-- tricky because we then can have clashes when we process replace fields 565-- independently. We can also protect 'none' in one go. It is actually not that 566-- much faster (and in some cases it might even be slower). We can make the code 567-- a bit leaner (no setbase and setnone). 568 569do 570 571 local usedfonts 572 local dynamicfonts 573 local prevfont 574 local prevdynamic 575 local variants 576 local redundant -- could be reused 577 local lastfont 578 local lastproc 579 -- local basedone 580 -- local nonedone 581 582 -- local d, u, b, r 583 local d, u, r 584 585 -- local function setnone() 586 -- nonedone = true 587 -- end 588 589 -- local function setbase() 590 -- if force_basepass then 591 -- basedone = true 592 -- end 593 -- end 594 595-- local function setnode(font,dynamic) -- we could use prevfont and prevdynamic when we set them first 596-- if dynamic > 0 then 597-- local used = dynamicfonts[font] 598-- if not used then 599-- used = { } 600-- dynamicfonts[font] = used 601-- end 602-- if not used[dynamic] then 603-- local fd = setfontdynamics[font] 604-- if fd then 605-- used[dynamic] = fd[dynamic] 606-- d = d + 1 607-- end 608-- end 609-- else 610-- local used = usedfonts[font] 611-- if not used then 612-- lastfont = font 613-- lastproc = fontprocesses[font] 614-- if lastproc then 615-- usedfonts[font] = lastproc 616-- u = u + 1 617-- end 618-- end 619-- end 620-- end 621 622 local function setnode() -- we could use prevfont and prevdynamic when we set them first 623 if prevdynamic > 0 then 624 local used = dynamicfonts[prevfont] 625 if not used then 626 used = { } 627 dynamicfonts[prevfont] = used 628 end 629 if not used[prevdynamic] then 630 local fd = setfontdynamics[prevfont] 631 if fd then 632 used[prevdynamic] = fd[prevdynamic] 633 d = d + 1 634 end 635 end 636 else 637 local used = usedfonts[prevfont] 638 if not used then 639 lastfont = prevfont 640 lastproc = fontprocesses[prevfont] 641 if lastproc then 642 usedfonts[prevfont] = lastproc 643 u = u + 1 644 end 645 end 646 end 647 end 648 649 -- local hasglyph = nuts.hasglyph 650 651 function handlers.characters(head,groupcode,direction) 652 653 -- no gain: 654 655 -- local h = hasglyph(head) 656 -- if not h then 657 -- return head 658 -- end 659 660 starttiming(nodes) 661 662 usedfonts = { } 663 dynamicfonts = { } 664 prevfont = nil -- local 665 prevdynamic = 0 -- local 666 variants = nil -- local 667 redundant = nil -- local 668 lastfont = nil 669 lastproc = nil 670 -- nonedone = nil 671 -- basedone = nil 672 673 local nonedone = nil 674 local basedone = nil 675 676 local fontmode = nil -- base none or other 677 678 -- d, u, b, r = 0, 0, 0, 0 679 d, u, r = 0, 0, 0 680 681 -- if trace_fontrun then 682 -- start_trace(head) 683 -- end 684 685 -- There is no gain in checking for a single glyph and then having a fast path. On the 686 -- metafun manual (with some 2500 single char lists) the difference is just noise. 687 688 for n, char, font, dynamic in nextchar, head do 689 690 if font ~= prevfont then 691 prevfont = font 692 fontmode = fontmodes[font] 693 if fontmode == "none" then 694 prevdynamic = 0 695 variants = false 696 -- setnone() 697 nonedone = true 698 elseif fontmode == "base" then 699 prevdynamic = 0 700 variants = false 701 -- setbase() 702 basedone = true 703 else 704 prevdynamic = dynamic 705 variants = fontvariants[font] 706 -- setnode(font,dynamic) 707 setnode() 708 end 709 elseif fontmode == "node" then 710 if dynamic ~= prevdynamic then 711 prevdynamic = dynamic 712 variants = fontvariants[font] 713 -- setnode(font,dynamic) 714 setnode() 715 end 716 end 717 718 -- we could just mark them and then have a separate pass .. happens seldom 719 720 if variants then 721 -- We need a proper test for this! 722 if (char >= 0xFE00 and char <= 0xFE0F) or (char >= 0xE0100 and char <= 0xE01EF) then 723 local hash = variants[char] 724 if hash then 725 -- local p, _, char = isprevchar(n) 726 -- if char then 727 -- local variant = hash[char] 728 local p = getprev(n) 729 if p then 730 local char = ischar(p) -- checked 731 local variant = hash[char] 732 if variant then 733 if trace_variants then 734 report_fonts("replacing %C by %C",char,variant) 735 end 736 setchar(p,variant) 737 if redundant then 738 r = r + 1 739 redundant[r] = n 740 else 741 r = 1 742 redundant = { n } 743 end 744 end 745 end 746 elseif keep_redundant then 747 -- go on, can be used for tracing 748 elseif redundant then 749 r = r + 1 750 redundant[r] = n 751 else 752 r = 1 753 redundant = { n } 754 end 755 end 756 end 757 758 end 759 760 if force_boundaryrun then 761 -- we can inject wordboundaries and then let the hyphenator do its work 762 -- but we need to get rid of those nodes in order to build ligatures 763 -- and kern (a rather context thing) 764 765 -- for b, subtype in nextboundary, head do 766 -- if subtype == wordboundary_code then 767 -- if redundant then 768 -- r = r + 1 769 -- redundant[r] = b 770 -- else 771 -- r = 1 772 -- redundant = { b } 773 -- end 774 -- end 775 -- end 776 777 head = removefromlist(head,boundary_code,wordboundary_code) 778 779 end 780 781 if redundant then 782 for i=1,r do 783 local r = redundant[i] 784 local p, n = getboth(r) 785 if r == head then 786 head = n 787 setprev(n) 788 else 789 setlink(p,n) 790 end 791 flushnode(r) 792 end 793 end 794 795 -- todo: make this more clever 796 797 if force_discrun then 798 for disc in nextdisc, head do 799 -- doing only replace is good enough because pre and post are normally used 800 -- for hyphens and these come from fonts that part of the hyphenated word 801 local r = getreplace(disc) 802 if r then 803 prevfont = nil 804 prevdynamic = nil 805 -- fontmode = nil 806 for n, char, font, dynamic in nextchar, r do 807 if font ~= prevfont or dynamic ~= prevdynamic then 808 prevfont = font 809 prevdynamic = dynamic 810 fontmode = fontmodes[font] 811 if fontmode == "none" then 812 -- setnone() 813 nonedone = true 814 elseif fontmode == "base" then 815 -- setbase() 816 basedone = true 817 else 818 setnode() -- (font,dynamic) 819 end 820 end 821 -- we assume one font for now (and if there are more and we get into issues then 822 -- we can always remove the break) 823 break 824 end 825 end 826 end 827 828 end 829 830 -- if trace_fontrun then 831 -- stop_trace(u,usedfonts,d,dynamicfonts,b,basefonts,r,redundant) 832 -- end 833 834 if nonedone then 835 protectglyphsnone(head) 836 end 837 838 -- in context we always have at least 2 processors 839 if u == 0 then 840 -- skip 841 elseif u == 1 then 842 for i=1,#lastproc do 843 head = lastproc[i](head,lastfont,0,direction) 844 end 845 else 846 for font, processors in next, usedfonts do -- unordered 847 for i=1,#processors do 848 head = processors[i](head,font,0,direction,u) -- u triggers disc optimizer 849 end 850 end 851 end 852 853 if d == 0 then 854 -- skip 855 elseif d == 1 then 856 local font, dynamics = next(dynamicfonts) 857 for dynamic, processors in next, dynamics do -- unordered, dynamic can switch in between 858 for i=1,#processors do 859 head = processors[i](head,font,dynamic,direction) 860 end 861 end 862 else 863 for font, dynamics in next, dynamicfonts do 864 for dynamic, processors in next, dynamics do -- unordered, dynamic can switch in between 865 for i=1,#processors do 866 head = processors[i](head,font,dynamic,direction,d) -- d triggers disc optimizer 867 end 868 end 869 end 870 end 871 872 if basedone then 873 local start = head 874 start = ligaturing(start) 875 start = kerning(start) 876 if head ~= start then 877 head = start 878 end 879 end 880 881 stoptiming(nodes) 882 883 if trace_characters then 884 nodes.report(head) 885 end 886 887 return head 888 end 889 890end 891 892handlers.protectglyphs = protectglyphs 893handlers.unprotectglyphs = unprotectglyphs 894 |