-- cmgraded-hyphenation.lua -- -- Restore OT1+pdflatex hyphenation for Computer Modern Graded text under -- lualatex. In OT1 an accented letter is built with TeX's \accent primitive, -- which makes it a boundary for the hyphenation ALGORITHM: only the run before -- a word's first accent is hyphenated automatically; from the accent onward the -- word is not (e.g. "Metalloberflaeche" may break in "Metallober-", but not -- around or after the accent). A literal '-' or an explicit \- is unaffected -- -- those are not the algorithm's doing and OT1 breaks there regardless (marking -- the rest of an accented word by hand with \- is exactly the OT1 idiom). This -- package's OpenType text uses precomposed Unicode accented glyphs, which -- lualatex would hyphenate in full the modern way; this file reproduces the -- classic OT1 behaviour regarding accents instead. -- -- Self-contained: the accent set A below and the callback that follows are all -- it needs, so this generated file can be copied and used on its own (the set A -- is exactly the accented code points cmgraded composes, i.e. the OT1 \accent -- letters). Loaded by cmgraded.sty only when the 'exacthyphenation' key is on. -- local A = { [0x00C0]=true, [0x00C1]=true, [0x00C2]=true, [0x00C3]=true, [0x00C4]=true, [0x00C5]=true, [0x00C7]=true, [0x00C8]=true, [0x00C9]=true, [0x00CA]=true, [0x00CB]=true, [0x00CC]=true, [0x00CD]=true, [0x00CE]=true, [0x00CF]=true, [0x00D1]=true, [0x00D2]=true, [0x00D3]=true, [0x00D4]=true, [0x00D5]=true, [0x00D6]=true, [0x00D9]=true, [0x00DA]=true, [0x00DB]=true, [0x00DC]=true, [0x00DD]=true, [0x00E0]=true, [0x00E1]=true, [0x00E2]=true, [0x00E3]=true, [0x00E4]=true, [0x00E5]=true, [0x00E7]=true, [0x00E8]=true, [0x00E9]=true, [0x00EA]=true, [0x00EB]=true, [0x00EC]=true, [0x00ED]=true, [0x00EE]=true, [0x00EF]=true, [0x00F1]=true, [0x00F2]=true, [0x00F3]=true, [0x00F4]=true, [0x00F5]=true, [0x00F6]=true, [0x00F9]=true, [0x00FA]=true, [0x00FB]=true, [0x00FC]=true, [0x00FD]=true, [0x00FF]=true, [0x0100]=true, [0x0101]=true, [0x0102]=true, [0x0103]=true, [0x0106]=true, [0x0107]=true, [0x0108]=true, [0x0109]=true, [0x010A]=true, [0x010B]=true, [0x010C]=true, [0x010D]=true, [0x010E]=true, [0x010F]=true, [0x0112]=true, [0x0113]=true, [0x0114]=true, [0x0115]=true, [0x0116]=true, [0x0117]=true, [0x011A]=true, [0x011B]=true, [0x011C]=true, [0x011D]=true, [0x011E]=true, [0x011F]=true, [0x0120]=true, [0x0121]=true, [0x0124]=true, [0x0125]=true, [0x0128]=true, [0x0129]=true, [0x012A]=true, [0x012B]=true, [0x012C]=true, [0x012D]=true, [0x0130]=true, [0x0134]=true, [0x0135]=true, [0x0139]=true, [0x013A]=true, [0x013D]=true, [0x013E]=true, [0x0143]=true, [0x0144]=true, [0x0147]=true, [0x0148]=true, [0x014C]=true, [0x014D]=true, [0x014E]=true, [0x014F]=true, [0x0150]=true, [0x0151]=true, [0x0154]=true, [0x0155]=true, [0x0158]=true, [0x0159]=true, [0x015A]=true, [0x015B]=true, [0x015C]=true, [0x015D]=true, [0x015E]=true, [0x015F]=true, [0x0160]=true, [0x0161]=true, [0x0162]=true, [0x0163]=true, [0x0164]=true, [0x0165]=true, [0x0168]=true, [0x0169]=true, [0x016A]=true, [0x016B]=true, [0x016C]=true, [0x016D]=true, [0x016E]=true, [0x016F]=true, [0x0170]=true, [0x0171]=true, [0x0174]=true, [0x0175]=true, [0x0176]=true, [0x0177]=true, [0x0178]=true, [0x0179]=true, [0x017A]=true, [0x017B]=true, [0x017C]=true, [0x017D]=true, [0x017E]=true, [0x01CD]=true, [0x01CE]=true, [0x01CF]=true, [0x01D0]=true, [0x01D1]=true, [0x01D2]=true, [0x01D3]=true, [0x01D4]=true, [0x01E6]=true, [0x01E7]=true, [0x01F0]=true, [0x01F4]=true, [0x01F5]=true, [0x1E26]=true, [0x1E27]=true, [0x1E44]=true, [0x1E45]=true, [0x1E58]=true, [0x1E59]=true, [0x1E80]=true, [0x1E81]=true, [0x1E82]=true, [0x1E83]=true, [0x1E84]=true, [0x1E85]=true, [0x1E97]=true, [0x1EBC]=true, [0x1EBD]=true, [0x1EF2]=true, [0x1EF3]=true, [0x1EF8]=true, [0x1EF9]=true, } -- Register once. Reproduce OT1's hyphenation of accented words: TeX's \accent -- turns the accented letter into a boundary, so only the run BEFORE the first -- accent of a word is hyphenated by the algorithm -- nothing from an accent -- onward is (its run starts after the accent box, not after a space). We -- therefore neutralise every ALGORITHMIC break (disc subtype 'regular') that -- sits at or after an accented glyph in its word, or within righthyphenmin -- characters before one. A literal '-' ('automatic') and an explicit \- -- ('explicit') are left untouched: OT1 breaks there regardless of accents (and -- explicit \- is exactly how one hand-marks the parts OT1 won't do itself). -- Runs on pre_linebreak_filter (a list callback), so it composes with -- babel/polyglossia language switches and microtype. if not cmgraded_exacthyphenation then cmgraded_exacthyphenation = true local traverse = node.traverse local DISC = node.id("disc") local GLYPH = node.id("glyph") local KERN = node.id("kern") local GLUE = node.id("glue") local REGULAR for k, v in pairs(node.subtypes("disc")) do if v == "regular" then REGULAR = k break end end -- The f-ligatures stand for several characters, so righthyphenmin is counted -- in characters (as TeX does), not glyphs -- otherwise a break like -- "Metallober-ache" would see the accent one glyph too close. local LIG = { [0xFB00]=2, [0xFB01]=2, [0xFB02]=2, [0xFB03]=3, [0xFB04]=3 } local function suppress(head) local rh = tex.righthyphenmin if rh < 1 then rh = 2 end local sawaccent = false -- an accent earlier in the current word? for n in traverse(head) do local id = n.id if id == GLYPH then if A[n.char] then sawaccent = true end elseif id == DISC and n.subtype == REGULAR then local kill = sawaccent -- (a) break lies at/after an accent if not kill then -- (b) an accent within righthyphenmin chars after local chars, q = 0, n.next while q and chars < rh do local qid = q.id if qid == GLYPH then if A[q.char] then kill = true break end chars = chars + (LIG[q.char] or 1) elseif qid == KERN then -- interior kern: skip else break -- word boundary end q = q.next end end if kill then n.penalty = 10000 end elseif id == KERN then -- interior kern: keep the word going elseif id == GLUE then sawaccent = false -- inter-word space: reset end end return head end luatexbase.add_to_callback("pre_linebreak_filter", suppress, "cmgraded.exacthyphenation") end