%% %% This is file `lua-typo.sty', %% generated with the docstrip utility. %% %% The original source files were: %% %% lua-typo.dtx (with options: `sty') %% %% IMPORTANT NOTICE: %% For the copyright see the source file `lua-typo.dtx’. %% \NeedsTeXFormat{LaTeX2e}[2021/06/01] \ProvidesPackage{lua-typo} [2024-04-18 v.0.87 Daniel Flipo] \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty} \DeclareRelease{v0.65}{2023-03-08}{lua-typo-2023-03-08.sty} \DeclareCurrentRelease{}{2023-09-13} \ifdefined\directlua \RequirePackage{luatexbase,luacode,luacolor,atveryend} \else \PackageError{This package is meant for LuaTeX only! Aborting} {No more information available, sorry!} \fi \newdimen\luatypoLLminWD \newdimen\luatypoBackPI \newdimen\luatypoBackFuzz \newdimen\luatypoMarginparTol \newcount\luatypoStretchMax \newcount\luatypoHyphMax \newcount\luatypoPageMin \newcount\luatypoMinFull \newcount\luatypoMinPart \newcount\luatypoMinLen \newcount\luatypo@LANGno \newcount\luatypo@options \newtoks\luatypo@single \newtoks\luatypo@double \begin{luacode} luatypo = { } \end{luacode} \DeclareKeys[luatypo] { ShowOptions.if = LT@ShowOptions , None.if = LT@None , BackParindent.if = LT@BackParindent , ShortLines.if = LT@ShortLines , ShortPages.if = LT@ShortPages , OverfullLines.if = LT@OverfullLines , UnderfullLines.if = LT@UnderfullLines , Widows.if = LT@Widows , Orphans.if = LT@Orphans , EOPHyphens.if = LT@EOPHyphens , RepeatedHyphens.if = LT@RepeatedHyphens , ParLastHyphen.if = LT@ParLastHyphen , EOLShortWords.if = LT@EOLShortWords , FirstWordMatch.if = LT@FirstWordMatch , LastWordMatch.if = LT@LastWordMatch , FootnoteSplit.if = LT@FootnoteSplit , ShortFinalWord.if = LT@ShortFinalWord , MarginparPos.if = LT@MarginparPos , All.if = LT@All , All.code = \LT@ShortLinestrue \LT@ShortPagestrue \LT@OverfullLinestrue \LT@UnderfullLinestrue \LT@Widowstrue \LT@Orphanstrue \LT@EOPHyphenstrue \LT@RepeatedHyphenstrue \LT@ParLastHyphentrue \LT@EOLShortWordstrue \LT@FirstWordMatchtrue \LT@LastWordMatchtrue \LT@BackParindenttrue \LT@FootnoteSplittrue \LT@ShortFinalWordtrue \LT@MarginparPostrue } \ProcessKeyOptions[luatypo] \AtEndOfPackage{% \ifLT@None \directlua{ luatypo.None = true }% \else \directlua{ luatypo.None = false }% \fi \ifLT@BackParindent \advance\luatypo@options by 1 \directlua{ luatypo.BackParindent = true }% \else \directlua{ luatypo.BackParindent = false }% \fi \ifLT@ShortLines \advance\luatypo@options by 1 \directlua{ luatypo.ShortLines = true }% \else \directlua{ luatypo.ShortLines = false }% \fi \ifLT@ShortPages \advance\luatypo@options by 1 \directlua{ luatypo.ShortPages = true }% \else \directlua{ luatypo.ShortPages = false }% \fi \ifLT@OverfullLines \advance\luatypo@options by 1 \directlua{ luatypo.OverfullLines = true }% \else \directlua{ luatypo.OverfullLines = false }% \fi \ifLT@UnderfullLines \advance\luatypo@options by 1 \directlua{ luatypo.UnderfullLines = true }% \else \directlua{ luatypo.UnderfullLines = false }% \fi \ifLT@Widows \advance\luatypo@options by 1 \directlua{ luatypo.Widows = true }% \else \directlua{ luatypo.Widows = false }% \fi \ifLT@Orphans \advance\luatypo@options by 1 \directlua{ luatypo.Orphans = true }% \else \directlua{ luatypo.Orphans = false }% \fi \ifLT@EOPHyphens \advance\luatypo@options by 1 \directlua{ luatypo.EOPHyphens = true }% \else \directlua{ luatypo.EOPHyphens = false }% \fi \ifLT@RepeatedHyphens \advance\luatypo@options by 1 \directlua{ luatypo.RepeatedHyphens = true }% \else \directlua{ luatypo.RepeatedHyphens = false }% \fi \ifLT@ParLastHyphen \advance\luatypo@options by 1 \directlua{ luatypo.ParLastHyphen = true }% \else \directlua{ luatypo.ParLastHyphen = false }% \fi \ifLT@EOLShortWords \advance\luatypo@options by 1 \directlua{ luatypo.EOLShortWords = true }% \else \directlua{ luatypo.EOLShortWords = false }% \fi \ifLT@FirstWordMatch \advance\luatypo@options by 1 \directlua{ luatypo.FirstWordMatch = true }% \else \directlua{ luatypo.FirstWordMatch = false }% \fi \ifLT@LastWordMatch \advance\luatypo@options by 1 \directlua{ luatypo.LastWordMatch = true }% \else \directlua{ luatypo.LastWordMatch = false }% \fi \ifLT@FootnoteSplit \advance\luatypo@options by 1 \directlua{ luatypo.FootnoteSplit = true }% \else \directlua{ luatypo.FootnoteSplit = false }% \fi \ifLT@ShortFinalWord \advance\luatypo@options by 1 \directlua{ luatypo.ShortFinalWord = true }% \else \directlua{ luatypo.ShortFinalWord = false }% \fi \ifLT@MarginparPos \advance\luatypo@options by 1 \directlua{ luatypo.MarginparPos = true }% \else \directlua{ luatypo.MarginparPos = false }% \fi } \ifLT@ShowOptions \GenericWarning{* }{% *** List of possible options for lua-typo ***\MessageBreak [Default values between brackets]% \MessageBreak ShowOptions [false]\MessageBreak None [false]\MessageBreak All [false]\MessageBreak BackParindent [false]\MessageBreak ShortLines [false]\MessageBreak ShortPages [false]\MessageBreak OverfullLines [false]\MessageBreak UnderfullLines [false]\MessageBreak Widows [false]\MessageBreak Orphans [false]\MessageBreak EOPHyphens [false]\MessageBreak RepeatedHyphens [false]\MessageBreak ParLastHyphen [false]\MessageBreak EOLShortWords [false]\MessageBreak FirstWordMatch [false]\MessageBreak LastWordMatch [false]\MessageBreak FootnoteSplit [false]\MessageBreak ShortFinalWord [false]\MessageBreak MarginparPos [false]\MessageBreak \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] }% \fi \AtBeginDocument{% \@ifpackageloaded{reledmac}% {\PackageWarning{lua-typo}{% 'lua-typo' is incompatible with\MessageBreak the 'reledmac' package.\MessageBreak 'lua-typo' checking disabled.\MessageBreak Reported}% \LT@Nonetrue \directlua{ luatypo.None = true }% }{}% \directlua{ luatypo.HYPHmax = tex.count.luatypoHyphMax luatypo.PAGEmin = tex.count.luatypoPageMin luatypo.Stretch = tex.count.luatypoStretchMax luatypo.MinFull = tex.count.luatypoMinFull luatypo.MinPart = tex.count.luatypoMinPart luatypo.MinFull = math.min(luatypo.MinPart,luatypo.MinFull) luatypo.MinLen = tex.count.luatypoMinLen luatypo.LLminWD = tex.dimen.luatypoLLminWD luatypo.BackPI = tex.dimen.luatypoBackPI luatypo.BackFuzz = tex.dimen.luatypoBackFuzz luatypo.MParTol = tex.dimen.luatypoMarginparTol local tbl = luatypo.colortbl local map = { } for i,v in ipairs (luatypo.colortbl) do if i == 1 or v > tbl[i-1] then table.insert(map, v) end end luatypo.map = map }% } \AtVeryEndDocument{% \ifnum\luatypo@options = 0 \LT@Nonetrue \fi \ifLT@None \directlua{ texio.write_nl(' ') texio.write_nl('************************************') texio.write_nl('*** lua-typo running with NO option:') texio.write_nl('*** NO CHECK PERFORMED! ***') texio.write_nl('************************************') texio.write_nl(' ') }% \else \directlua{ texio.write_nl(' ') texio.write_nl('*************************************') if luatypo.pagelist == " " then if luatypo.failedlist == " " then texio.write_nl('*** lua-typo: No Typo Flaws found.') else texio.write_nl('*** WARNING: ') texio.write('lua-typo failed to scan these pages:') texio.write_nl('***' .. luatypo.failedlist) texio.write_nl('*** Please report to the maintainer.') end else texio.write_nl('*** lua-typo: WARNING *************') texio.write_nl('The following pages need attention:') texio.write(luatypo.pagelist) end texio.write_nl('*************************************') texio.write_nl(' ') if luatypo.failedlist == " " then else local prt = "WARNING: lua-typo failed to scan pages " .. luatypo.failedlist .. "\string\n\string\n" luatypo.buffer = prt .. luatypo.buffer end local fileout= tex.jobname .. ".typo" local out=io.open(fileout,"w+") out:write(luatypo.buffer) io.close(out) }% \fi} \newcommand*{\luatypoOneChar}[2]{% \def\luatypo@LANG{#1}\luatypo@single={#2}% \ifcsname l@\luatypo@LANG\endcsname \luatypo@LANGno=\the\csname l@\luatypo@LANG\endcsname \relax \directlua{ local langno = \the\luatypo@LANGno local string = \the\luatypo@single luatypo.single[langno] = " " for p, c in utf8.codes(string) do local s = utf8.char(c) luatypo.single[langno] = luatypo.single[langno] .. s end }% \else \PackageWarning{luatypo}{Unknown language "\luatypo@LANG", \MessageBreak \protect\luatypoOneChar\space command ignored}% \fi} \newcommand*{\luatypoTwoChars}[2]{% \def\luatypo@LANG{#1}\luatypo@double={#2}% \ifcsname l@\luatypo@LANG\endcsname \luatypo@LANGno=\the\csname l@\luatypo@LANG\endcsname \relax \directlua{ local langno = \the\luatypo@LANGno local string = \the\luatypo@double luatypo.double[langno] = " " for p, c in utf8.codes(string) do local s = utf8.char(c) luatypo.double[langno] = luatypo.double[langno] .. s end }% \else \PackageWarning{luatypo}{Unknown language "\luatypo@LANG", \MessageBreak \protect\luatypoTwoChars\space command ignored}% \fi} \newcommand*{\luatypoSetColor}[2]{% \begingroup \color{#2}% \directlua{luatypo.colortbl[#1]=\the\LuaCol@Attribute}% \endgroup } \begin{luacode} luatypo.colortbl = { } luatypo.map = { } luatypo.single = { } luatypo.double = { } luatypo.pagelist = " " luatypo.failedlist = " " luatypo.buffer = "List of typographic flaws found for " .. tex.jobname .. ".pdf:\string\n\string\n" local char_to_discard = { } char_to_discard[string.byte(",")] = true char_to_discard[string.byte(".")] = true char_to_discard[string.byte("!")] = true char_to_discard[string.byte("?")] = true char_to_discard[string.byte(":")] = true char_to_discard[string.byte(";")] = true char_to_discard[string.byte("-")] = true local eow_char = { } eow_char[string.byte(".")] = true eow_char[string.byte("!")] = true eow_char[string.byte("?")] = true eow_char[utf8.codepoint("…")] = true local DISC = node.id("disc") local GLYPH = node.id("glyph") local GLUE = node.id("glue") local KERN = node.id("kern") local RULE = node.id("rule") local HLIST = node.id("hlist") local VLIST = node.id("vlist") local LPAR = node.id("local_par") local MKERN = node.id("margin_kern") local PENALTY = node.id("penalty") local WHATSIT = node.id("whatsit") local USRSKIP = 0 local PARSKIP = 3 local LFTSKIP = 8 local RGTSKIP = 9 local TOPSKIP = 10 local PARFILL = 15 local LINE = 1 local BOX = 2 local INDENT = 3 local ALIGN = 4 local EQN = 6 local USER = 0 local HYPH = 0x2D local LIGA = 0x102 local parline = 0 local dimensions = node.dimensions local rangedimensions = node.rangedimensions local effective_glue = node.effective_glue local set_attribute = node.set_attribute local get_attribute = node.get_attribute local slide = node.slide local traverse = node.traverse local traverse_id = node.traverse_id local has_field = node.has_field local uses_font = node.uses_font local is_glyph = node.is_glyph local utf8_len = utf8.len local utf8_find = unicode.utf8.find local utf8_gsub = unicode.utf8.gsub local utf8_reverse = function (s) if utf8_len(s) > 1 then local so = "" for p, c in utf8.codes(s) do so = utf8.char(c) .. so end s = so end return s end local utf8_sub = function (s,i,j) i=utf8.offset(s,i) j=utf8.offset(s,j+1)-1 return string.sub(s,i,j) end local color_node = function (node, color) local attr = oberdiek.luacolor.getattribute() if node and node.id == DISC then local pre = node.pre local post = node.post local repl = node.replace if pre then set_attribute(pre,attr,color) end if post then set_attribute(post,attr,color) end if repl then set_attribute(repl,attr,color) end elseif node then set_attribute(node,attr,color) end end local color_line = function (head, color) local first = head.head local map = luatypo.map local color_node_if = function (node, color) local c = oberdiek.luacolor.getattribute() local att = get_attribute(node,c) local uncolored = true for i,v in ipairs (map) do if att == v then uncolored = false break end end if uncolored then color_node (node, color) end end for n in traverse(first) do if n.id == HLIST or n.id == VLIST then local ff = n.head for nn in traverse(ff) do if nn.id == HLIST or nn.id == VLIST then local f3 = nn.head for n3 in traverse(f3) do if n3.id == HLIST or n3.id == VLIST then local f4 = n3.head for n4 in traverse(f4) do if n4.id == HLIST or n4.id == VLIST then local f5 = n4.head for n5 in traverse(f5) do if n5.id == HLIST or n5.id == VLIST then local f6 = n5.head for n6 in traverse(f6) do color_node_if(n6, color) end else color_node_if(n5, color) end end else color_node_if(n4, color) end end else color_node_if(n3, color) end end else color_node_if(nn, color) end end else color_node_if(n, color) end end end log_flaw= function (msg, line, colno, footnote) local pageno = tex.getcount("c@page") local prt ="p. " .. pageno if colno then prt = prt .. ", col." .. colno end if line then local l = string.format("%2d, ", line) if footnote then prt = prt .. ", (ftn.) line " .. l else prt = prt .. ", line " .. l end end prt = prt .. msg luatypo.buffer = luatypo.buffer .. prt .. "\string\n" end local signature = function (node, string, swap) local n = node local str = string if n and n.id == GLYPH then local b = n.char if b and not char_to_discard[b] then if n.components then local c = "" for nn in traverse_id(GLYPH, n.components) do c = c .. utf8.char(nn.char) end if swap then str = str .. utf8_reverse(c) else str = str .. c end else str = str .. utf8.char(b) end end elseif n and n.id == DISC then local pre = n.pre local post = n.post local c1 = "" local c2 = "" if pre and pre.char then if pre.components then for nn in traverse_id(GLYPH, pre.components) do c1 = c1 .. utf8.char(nn.char) end else c1 = utf8.char(pre.char) end c1 = utf8_gsub(c1, "-", "") end if post and post.char then if post.components then for nn in traverse_id(GLYPH, post.components) do c2 = c2 .. utf8.char(nn.char) end else c2 = utf8.char(post.char) end end if swap then str = str .. utf8_reverse(c2) .. c1 else str = str .. c1 .. c2 end elseif n and n.id == GLUE then str = str .. "_" end local s = utf8_gsub(str, "_", "") local len = utf8_len(s) return len, str end local check_line_last_word = function (old, node, line, colno, flag, footnote) local COLOR = luatypo.colortbl[12] local match = false local new = "" local maxlen = 0 local MinFull = luatypo.MinFull local MinPart = luatypo.MinPart if node then local swap = true local box, go local lastn = node while lastn and lastn.id ~= GLYPH and lastn.id ~= DISC and lastn.id ~= HLIST do lastn = lastn.prev end local n = lastn local words = 0 while n and (words <= 2 or maxlen < MinPart) do if n and n.id == HLIST then box = n local first = n.head local lastn = slide(first) n = lastn while n do maxlen, new = signature (n, new, swap) n = n.prev end n = box.prev local w = utf8_gsub(new, "_", "") words = words + utf8_len(new) - utf8_len(w) + 1 else repeat maxlen, new = signature (n, new, swap) n = n.prev until not n or n.id == GLUE or n.id == HLIST if n and n.id == GLUE then maxlen, new = signature (n, new, swap) words = words + 1 n = n.prev end end end new = utf8_reverse(new) new = utf8_gsub(new, "_+$", "") -- $ new = utf8_gsub(new, "^_+", "") maxlen = math.min(utf8_len(old), utf8_len(new)) if flag and old ~= "" then local oldlast = utf8_gsub (old, ".*_", "") local newlast = utf8_gsub (new, ".*_", "") local oldsub = "" local newsub = "" local dlo = utf8_reverse(old) local wen = utf8_reverse(new) for p, c in utf8.codes(dlo) do local s = utf8_gsub(oldsub, "_", "") if utf8_len(s) < MinPart then oldsub = utf8.char(c) .. oldsub end end for p, c in utf8.codes(wen) do local s = utf8_gsub(newsub, "_", "") if utf8_len(s) < MinPart then newsub = utf8.char(c) .. newsub end end if oldsub == newsub then match = true end if oldlast == newlast and utf8_len(newlast) >= MinFull then if utf8_len(newlast) > MinPart or not match then oldsub = oldlast newsub = newlast end match = true end if match then local k = utf8_len(newsub) local osub = utf8_reverse(oldsub) local nsub = utf8_reverse(newsub) while osub == nsub and k < maxlen do k = k + 1 osub = utf8_sub(dlo,1,k) nsub = utf8_sub(wen,1,k) if osub == nsub then newsub = utf8_reverse(nsub) end end newsub = utf8_gsub(newsub, "^_+", "") local msg = "E.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) local ns = utf8_gsub(newsub, "_", "") k = utf8_len(ns) oldsub = utf8_reverse(newsub) local newsub = "" local n = lastn local l = 0 local lo = 0 local li = 0 while n and newsub ~= oldsub and l < k do if n and n.id == HLIST then local first = n.head for nn in traverse_id(GLYPH, first) do color_node(nn, COLOR) local c = nn.char if not char_to_discard[c] then l = l + 1 end end elseif n then color_node(n, COLOR) li, newsub = signature(n, newsub, swap) l = l + li - lo lo = li end n = n.prev end end end end return new, match end local check_line_first_word = function (old, node, line, colno, flag, footnote) local COLOR = luatypo.colortbl[11] local match = false local swap = false local new = "" local maxlen = 0 local MinFull = luatypo.MinFull local MinPart = luatypo.MinPart local n = node local box, go while n and n.id ~= GLYPH and n.id ~= DISC and (n.id ~= HLIST or n.subtype == INDENT) do n = n.next end start = n local words = 0 while n and (words <= 2 or maxlen < MinPart) do if n and n.id == HLIST then box = n n = n.head while n do maxlen, new = signature (n, new, swap) n = n.next end n = box.next local w = utf8_gsub(new, "_", "") words = words + utf8_len(new) - utf8_len(w) + 1 else repeat maxlen, new = signature (n, new, swap) n = n.next until not n or n.id == GLUE or n.id == HLIST if n and n.id == GLUE then maxlen, new = signature (n, new, swap) words = words + 1 n = n.next end end end new = utf8_gsub(new, "_+$", "") -- $ new = utf8_gsub(new, "^_+", "") maxlen = math.min(utf8_len(old), utf8_len(new)) if flag and old ~= "" then local oldfirst = utf8_gsub (old, "_.*", "") local newfirst = utf8_gsub (new, "_.*", "") local oldsub = "" local newsub = "" for p, c in utf8.codes(old) do local s = utf8_gsub(oldsub, "_", "") if utf8_len(s) < MinPart then oldsub = oldsub .. utf8.char(c) end end for p, c in utf8.codes(new) do local s = utf8_gsub(newsub, "_", "") if utf8_len(s) < MinPart then newsub = newsub .. utf8.char(c) end end if oldsub == newsub then match = true end if oldfirst == newfirst and utf8_len(newfirst) >= MinFull then if utf8_len(newfirst) > MinPart or not match then oldsub = oldfirst newsub = newfirst end match = true end if match then local k = utf8_len(newsub) local osub = oldsub local nsub = newsub while osub == nsub and k < maxlen do k = k + 1 osub = utf8_sub(old,1,k) nsub = utf8_sub(new,1,k) if osub == nsub then newsub = nsub end end newsub = utf8_gsub(newsub, "_+$", "") --$ local msg = "B.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) local ns = utf8_gsub(newsub, "_", "") k = utf8_len(ns) oldsub = newsub local newsub = "" local n = start local l = 0 local lo = 0 local li = 0 while n and newsub ~= oldsub and l < k do if n and n.id == HLIST then local nn = n.head for nnn in traverse(nn) do color_node(nnn, COLOR) local c = nn.char if not char_to_discard[c] then l = l + 1 end end elseif n then color_node(n, COLOR) li, newsub = signature(n, newsub, swap) l = l + li - lo lo = li end n = n.next end end end return new, match end local check_page_first_word = function (node, colno, footnote) local COLOR = luatypo.colortbl[15] local match = false local swap = false local new = "" local minlen = luatypo.MinLen local len = 0 local n = node local pn while n and n.id ~= GLYPH and n.id ~= DISC and (n.id ~= HLIST or n.subtype == INDENT) do n = n.next end local start = n if n and n.id == HLIST then start = n.head n = n.head end repeat len, new = signature (n, new, swap) n = n.next until len > minlen or (n and n.id == GLYPH and eow_char[n.char]) or (n and n.id == GLUE) or (n and n.id == KERN and n.subtype == 1) if n and (n.id == GLUE or n.id == KERN) then pn = n n = n.next end if len <= minlen and n and n.id == GLYPH and eow_char[n.char] then repeat n = n.next until not n or n.id == GLYPH or (n.id == GLUE and n.subtype == PARFILL) if n and n.id == GLYPH then match = true end end if match then local msg = "ShortFinalWord=" .. new log_flaw(msg, 1, colno, footnote) local n = start repeat color_node(n, COLOR) n = n.next until eow_char[n.char] color_node(n, COLOR) end return match end local check_regexpr = function (glyph, line, colno, footnote) local COLOR = luatypo.colortbl[4] local lang = glyph.lang local match = false local retflag = false local lchar, id = is_glyph(glyph) local previous = glyph.prev if lang and luatypo.single[lang] then if lchar and previous and previous.id == GLUE then match = utf8_find(luatypo.single[lang], utf8.char(lchar)) if match then retflag = true local msg = "RGX MATCH=" .. utf8.char(lchar) log_flaw(msg, line, colno, footnote) color_node(glyph,COLOR) end end end if lang and luatypo.double[lang] then if lchar and previous and previous.id == GLYPH then local pchar, id = is_glyph(previous) local pprev = previous.prev if pchar and pprev and pprev.id == GLUE then local pattern = utf8.char(pchar) .. utf8.char(lchar) match = utf8_find(luatypo.double[lang], pattern) if match then retflag = true local msg = "RGX MATCH=" .. pattern log_flaw(msg, line, colno, footnote) color_node(previous,COLOR) color_node(glyph,COLOR) end end elseif lchar and previous and previous.id == KERN then local pprev = previous.prev if pprev and pprev.id == GLYPH then local pchar, id = is_glyph(pprev) local ppprev = pprev.prev if pchar and ppprev and ppprev.id == GLUE then local pattern = utf8.char(pchar) .. utf8.char(lchar) match = utf8_find(luatypo.double[lang], pattern) if match then retflag = true local msg = "REGEXP MATCH=" .. pattern log_flaw(msg, line, colno, footnote) color_node(pprev,COLOR) color_node(glyph,COLOR) end end end end end return retflag end local show_pre_disc = function (disc, color) local n = disc while n and n.id ~= GLUE do color_node(n, color) n = n.prev end return n end local footnoterule_ahead = function (head) local n = head local flag = false local totalht, ruleht, ht1, ht2, ht3 if n and n.id == KERN and n.subtype == 1 then totalht = n.kern n = n.next while n and n.id == GLUE do n = n.next end if n and n.id == RULE and n.subtype == 0 then ruleht = n.height totalht = totalht + ruleht n = n.next if n and n.id == KERN and n.subtype == 1 then totalht = totalht + n.kern if totalht == 0 or totalht == ruleht then flag = true else end end end end return flag end local check_EOP = function (node) local n = node local page_bot = false local body_bot = false while n and (n.id == GLUE or n.id == PENALTY or n.id == WHATSIT ) do n = n.next end if not n then page_bot = true body_bot = true elseif footnoterule_ahead(n) then body_bot = true end return page_bot, body_bot end local check_marginnote = function (head, line, colno, vpos, bpmn) local OverfullLines = luatypo.OverfullLines local UnderfullLines = luatypo.UnderfullLines local MarginparPos = luatypo.MarginparPos local margintol = luatypo.MParTol local marginpp = tex.getdimen("marginparpush") local textht = tex.getdimen("textheight") local pflag = false local ofirst = true local ufirst = true local n = head.head local bottom = vpos if vpos <= bpmn then bottom = bpmn + marginpp end repeat if n and (n.id == GLUE or n.id == PENALTY) then n = n.next elseif n and n.id == VLIST then n = n.head end until not n or (n.id == HLIST and n.subtype == LINE) local head = n if head then else end local last = head while head do local next = head.next if head.id == HLIST and head.subtype == LINE then bottom = bottom + head.height + head.depth local first = head.head local linewd = head.width local hmax = linewd + tex.hfuzz local w,h,d = dimensions(1,2,0, first) local Stretch = math.max(luatypo.Stretch/100,1) if w > hmax and OverfullLines then pflag = true local COLOR = luatypo.colortbl[8] color_line (head, COLOR) if ofirst then local msg = "OVERFULL line(s) in margin note" log_flaw(msg, line, colno, false) ofirst = false end elseif head.glue_set > Stretch and head.glue_sign == 1 and head.glue_order == 0 and UnderfullLines then pflag = true local COLOR = luatypo.colortbl[9] color_line (head, COLOR) if ufirst then local msg = "UNDERFULL line(s) in margin note" log_flaw(msg, line, colno, false) ufirst = false end end end last = head head = next end if bottom > textht + margintol and MarginparPos then pflag = true local COLOR = luatypo.colortbl[17] color_line (last, COLOR) local msg = "Margin note too low" log_flaw(msg, line, colno, false) end return bottom, pflag end local get_pagebody = function (head) local textht = tex.getdimen("textheight") local fn = head.list local body repeat fn = fn.next until fn.id == VLIST and fn.height > 0 first = fn.list repeat for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height >= textht-1 then if n.height <= textht+8 then body = n break else first = n.list end else end end until body or not first if not body then texio.write_nl('***lua-typo ERROR: PAGE BODY *NOT* FOUND!***') end return body end check_vtop = function (top, colno, vpos) local head = top.list local PAGEmin = luatypo.PAGEmin local HYPHmax = luatypo.HYPHmax local LLminWD = luatypo.LLminWD local BackPI = luatypo.BackPI local BackFuzz = luatypo.BackFuzz local BackParindent = luatypo.BackParindent local ShortLines = luatypo.ShortLines local ShortPages = luatypo.ShortPages local OverfullLines = luatypo.OverfullLines local UnderfullLines = luatypo.UnderfullLines local Widows = luatypo.Widows local Orphans = luatypo.Orphans local EOPHyphens = luatypo.EOPHyphens local RepeatedHyphens = luatypo.RepeatedHyphens local FirstWordMatch = luatypo.FirstWordMatch local ParLastHyphen = luatypo.ParLastHyphen local EOLShortWords = luatypo.EOLShortWords local LastWordMatch = luatypo.LastWordMatch local FootnoteSplit = luatypo.FootnoteSplit local ShortFinalWord = luatypo.ShortFinalWord local Stretch = math.max(luatypo.Stretch/100,1) local blskip = tex.getglue("baselineskip") local vpos_min = PAGEmin * blskip vpos_min = vpos_min * 1.5 local linewd = tex.getdimen("textwidth") local first_bot = true local done = false local footnote = false local ftnsplit = false local orphanflag = false local widowflag = false local pageshort = false local overfull = false local underfull = false local shortline = false local backpar = false local firstwd = "" local lastwd = "" local hyphcount = 0 local pageline = 0 local ftnline = 0 local line = 0 local bpmn = 0 local body_bottom = false local page_bottom = false local pageflag = false local pageno = tex.getcount("c@page") while head do local nextnode = head.next if head.id == HLIST and head.subtype == LINE and (head.height > 0 or head.depth > 0) then vpos = vpos + head.height + head.depth done = true local linewd = head.width local first = head.head local ListItem = false if footnote then ftnline = ftnline + 1 line = ftnline else pageline = pageline + 1 line = pageline end page_bottom, body_bottom = check_EOP(nextnode) local hmax = linewd + tex.hfuzz local w,h,d = dimensions(1,2,0, first) if w > hmax and OverfullLines then pageflag = true overfull = true local wpt = string.format("%.2fpt", (w-head.width)/65536) local msg = "OVERFULL line " .. wpt log_flaw(msg, line, colno, footnote) elseif head.glue_set > Stretch and head.glue_sign == 1 and head.glue_order == 0 and UnderfullLines then pageflag = true underfull = true local s = string.format("%.0f%s", 100*head.glue_set, "%") local msg = "UNDERFULL line stretch=" .. s log_flaw(msg, line, colno, footnote) end if footnote and page_bottom then ftnsplit = true end while first.id == MKERN or (first.id == GLUE and first.subtype == LFTSKIP) do first = first.next end if first.id == LPAR then hyphcount = 0 firstwd = "" lastwd = "" if not footnote then parline = 1 if body_bottom then orphanflag = true end end local nn = first.next if nn and nn.id == HLIST and nn.subtype == BOX then ListItem = true end elseif not footnote then parline = parline + 1 end if FirstWordMatch then local flag = not ListItem and (line > 1) firstwd, flag = check_line_first_word(firstwd, first, line, colno, flag, footnote) if flag then pageflag = true end end if ShortFinalWord and pageline == 1 and parline > 1 and check_page_first_word(first, colno, footnote) then pageflag = true end local ln = slide(first) if ln.id == RULE and ln.subtype == 0 then ln = ln.prev end local pn = ln.prev if pn and pn.id == GLUE and pn.subtype == PARFILL then hyphcount = 0 ftnsplit = false orphanflag = false if pageline == 1 and parline > 1 then widowflag = true end local PFskip = effective_glue(pn,head) if ShortLines then local llwd = linewd - PFskip if llwd < LLminWD then pageflag = true shortline = true local msg = "SHORT LINE: length=" .. string.format("%.0fpt", llwd/65536) log_flaw(msg, line, colno, footnote) end end if BackParindent and PFskip < BackPI and PFskip >= BackFuzz and parline > 1 then pageflag = true backpar = true local msg = "NEARLY FULL line: backskip=" .. string.format("%.1fpt", PFskip/65536) log_flaw(msg, line, colno, footnote) end if LastWordMatch then local flag = true if PFskip > BackPI or line == 1 then flag = false end local pnp = pn.prev lastwd, flag = check_line_last_word(lastwd, pnp, line, colno, flag, footnote) if flag then pageflag = true end end elseif pn and pn.id == DISC then hyphcount = hyphcount + 1 if hyphcount > HYPHmax and RepeatedHyphens then local COLOR = luatypo.colortbl[3] local pg = show_pre_disc (pn,COLOR) pageflag = true local msg = "REPEATED HYPHENS: more than " .. HYPHmax log_flaw(msg, line, colno, footnote) end if (page_bottom or body_bottom) and EOPHyphens then pageflag = true local msg = "LAST WORD SPLIT" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[2] local pg = show_pre_disc (pn,COLOR) end if LastWordMatch then local flag = true lastwd, flag = check_line_last_word(lastwd, pn, line, colno, flag, footnote) if flag then pageflag = true end end if nextnode and ParLastHyphen then local nn = nextnode.next local nnn = nil if nn and nn.next then nnn = nn.next if nnn.id == HLIST and nnn.subtype == LINE and nnn.glue_order == 2 then pageflag = true local msg = "HYPHEN on next to last line" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[1] local pg = show_pre_disc (pn,COLOR) end end end else hyphcount = 0 if LastWordMatch and pn then local flag = true lastwd, flag = check_line_last_word(lastwd, pn, line, colno, flag, footnote) if flag then pageflag = true end end if EOLShortWords then while pn and pn.id ~= GLYPH and pn.id ~= HLIST do pn = pn.prev end if pn and pn.id == GLYPH then if check_regexpr(pn, line, colno, footnote) then pageflag = true end end end end if widowflag and Widows then pageflag = true local msg = "WIDOW" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[5] if backpar or shortline or overfull or underfull then COLOR = luatypo.colortbl[16] if backpar then backpar = false end if shortline then shortline = false end if overfull then overfull = false end if underfull then underfull = false end end color_line (head, COLOR) widowflag = false elseif orphanflag and Orphans then pageflag = true local msg = "ORPHAN" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[6] if overfull or underfull then COLOR = luatypo.colortbl[16] end color_line (head, COLOR) elseif ftnsplit and FootnoteSplit then pageflag = true local msg = "FOOTNOTE SPLIT" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[14] if overfull or underfull then COLOR = luatypo.colortbl[16] end color_line (head, COLOR) elseif shortline then local COLOR = luatypo.colortbl[7] color_line (head, COLOR) shortline = false elseif overfull then local COLOR = luatypo.colortbl[8] color_line (head, COLOR) overfull = false elseif underfull then local COLOR = luatypo.colortbl[9] color_line (head, COLOR) underfull = false elseif backpar then local COLOR = luatypo.colortbl[13] color_line (head, COLOR) backpar = false end elseif head and head.id == HLIST and head.subtype == BOX then if head.width > 0 then if head.height == 0 then bpmn, pflag = check_marginnote(head, line, colno, vpos, bpmn) if pflag then pageflag = true end page_bottom, body_bottom = check_EOP(nextnode) else local hf = head.list if hf and hf.id == VLIST and hf.subtype == 0 then break end end end vpos = vpos + head.height + head.depth page_bottom, body_bottom = check_EOP (nextnode) elseif head.id == HLIST and (head.subtype == EQN or head.subtype == ALIGN) and (head.height > 0 or head.depth > 0) then vpos = vpos + head.height + head.depth if footnote then ftnline = ftnline + 1 line = ftnline else pageline = pageline + 1 line = pageline end page_bottom, body_bottom = check_EOP (nextnode) local fl = true local wd = 0 local hmax = 0 if head.subtype == EQN then local f = head.list wd = rangedimensions(head,f) hmax = head.width + tex.hfuzz else wd = head.width hmax = tex.getdimen("linewidth") + tex.hfuzz end if wd > hmax and OverfullLines then if head.subtype == ALIGN then local first = head.list for n in traverse_id(HLIST, first) do local last = slide(n.list) if last.id == GLUE and last.subtype == USER then wd = wd - effective_glue(last,n) if wd <= hmax then fl = false end end end end if fl then pageflag = true local w = wd - hmax + tex.hfuzz local wpt = string.format("%.2fpt", w/65536) local msg = "OVERFULL equation " .. wpt log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[8] color_line (head, COLOR) end end elseif head and head.id == RULE and head.subtype == 0 then vpos = vpos + head.height + head.depth local prev = head.prev if body_bottom or footnoterule_ahead (prev) then footnote = true ftnline = 0 body_bottom = false orphanflag = false hyphcount = 0 firstwd = "" lastwd = "" end elseif body_bottom and head.id == GLUE and head.subtype == 0 then if first_bot then if pageline > 1 and pageline < PAGEmin and vpos < vpos_min and ShortPages then pageshort = true pageflag = true local msg = "SHORT PAGE: only " .. pageline .. " lines" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[10] local n = head repeat n = n.prev until n.id == HLIST and n.subtype == LINE color_line (n, COLOR) end first_bot = false end elseif head.id == GLUE then vpos = vpos + effective_glue(head,top) elseif head.id == KERN and head.subtype == 1 then vpos = vpos + head.kern elseif head.id == VLIST then vpos = vpos + head.height + head.depth end head = nextnode end if pageflag then local plist = luatypo.pagelist local lastp = tonumber(string.match(plist, "%s(%d+),%s$")) if not lastp or pageno > lastp then luatypo.pagelist = luatypo.pagelist .. tostring(pageno) .. ", " end end return head, done end luatypo.check_page = function (head) local pageno = tex.getcount("c@page") local body = get_pagebody(head) local textwd, textht, checked, boxed local top, first, next local n2, n3, col, colno local vpos = 0 local footnote = false local count = 0 if body then top = body first = body.list textwd = tex.getdimen("textwidth") textht = tex.getdimen("textheight") end if ((first and first.id == HLIST and first.subtype == BOX) or (first and first.id == VLIST and first.subtype == 0)) and (first.width == textwd and first.height > 0 and not boxed) then top = body.list if first.id == VLIST then boxed = body end end while top do first = top.list next = top.next if top and top.id == VLIST and top.subtype == 0 and top.width > textwd/2 then local n, ok = check_vtop(top,colno,vpos) if ok then checked = true end if n then next = n end elseif (top and top.id == HLIST and top.subtype == BOX) and (first and first.id == VLIST and first.subtype == 0) and (first.height > 0 and first.width > 0) then colno = 0 for col in traverse_id(VLIST, first) do colno = colno + 1 local n, ok = check_vtop(col,colno,vpos) if ok then checked = true end end colno = nil top = top.next elseif (top and top.id == HLIST and top.subtype == BOX) and (first and first.id == HLIST and first.subtype == BOX) and (first.height > 0 and first.width > 0) then colno = 0 for n in traverse_id(HLIST, first) do colno = colno + 1 local col = n.list if col and col.list then local n, ok = check_vtop(col,colno,vpos) if ok then checked = true end end end colno = nil end if boxed and not next then next = boxed boxed = nil end top = next end if not checked then luatypo.failedlist = luatypo.failedlist .. tostring(pageno) .. ", " end return true end return luatypo.check_page \end{luacode} \AtBeginDocument{% \directlua{ if not luatypo.None then luatexbase.add_to_callback ("pre_shipout_filter",luatypo.check_page,"check_page",1) end }% } \InputIfFileExists{lua-typo.cfg}% {\PackageInfo{lua-typo.sty}{"lua-typo.cfg" file loaded}}% {\PackageInfo{lua-typo.sty}{"lua-typo.cfg" file not found. \MessageBreak Providing default values.}% \definecolor{LTgrey}{gray}{0.6}% \definecolor{LTred}{rgb}{1,0.55,0} \definecolor{LTline}{rgb}{0.7,0,0.3} \luatypoSetColor1{red}% Paragraph last full line hyphenated \luatypoSetColor2{red}% Page last word hyphenated \luatypoSetColor3{red}% Hyphens on to many consecutive lines \luatypoSetColor4{red}% Short word at end of line \luatypoSetColor5{cyan}% Widow \luatypoSetColor6{cyan}% Orphan \luatypoSetColor7{cyan}% Paragraph ending on a short line \luatypoSetColor8{blue}% Overfull lines \luatypoSetColor9{blue}% Underfull lines \luatypoSetColor{10}{red}% Nearly empty page \luatypoSetColor{11}{LTred}% First word matches \luatypoSetColor{12}{LTred}% Last word matches \luatypoSetColor{13}{LTgrey}% Paragraph ending on a nearly full line \luatypoSetColor{14}{cyan}% Footnote split \luatypoSetColor{15}{red}% Too short first (final) word on the page \luatypoSetColor{16}{LTline}% Line color for multiple flaws \luatypoSetColor{17}{red}% Margin note ending too low \luatypoBackPI=1em\relax \luatypoBackFuzz=2pt\relax \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax \else\luatypoLLminWD=2\parindent\relax\fi \luatypoStretchMax=200\relax \luatypoHyphMax=2\relax \luatypoPageMin=5\relax \luatypoMinFull=3\relax \luatypoMinPart=4\relax \luatypoMinLen=4\relax \luatypoMarginparTol=\baselineskip }% %% %% %% End of file `lua-typo.sty'.