%% %% 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} [2023-03-08 v.0.65 Daniel Flipo] \ifdefined\directlua \RequirePackage{luatexbase,luacode,luacolor} \RequirePackage{kvoptions,atveryend} \else \PackageError{This package is meant for LuaTeX only! Aborting} {No more information available, sorry!} \fi \newdimen\luatypoLLminWD \newdimen\luatypoBackPI \newdimen\luatypoBackFuzz \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} \SetupKeyvalOptions{ family=luatypo, prefix=LT@, } \DeclareBoolOption[false]{ShowOptions} \DeclareBoolOption[false]{None} \DeclareBoolOption[false]{All} \DeclareBoolOption[false]{BackParindent} \DeclareBoolOption[false]{ShortLines} \DeclareBoolOption[false]{ShortPages} \DeclareBoolOption[false]{OverfullLines} \DeclareBoolOption[false]{UnderfullLines} \DeclareBoolOption[false]{Widows} \DeclareBoolOption[false]{Orphans} \DeclareBoolOption[false]{EOPHyphens} \DeclareBoolOption[false]{RepeatedHyphens} \DeclareBoolOption[false]{ParLastHyphen} \DeclareBoolOption[false]{EOLShortWords} \DeclareBoolOption[false]{FirstWordMatch} \DeclareBoolOption[false]{LastWordMatch} \DeclareBoolOption[false]{FootnoteSplit} \DeclareBoolOption[false]{ShortFinalWord} \AddToKeyvalOption{luatypo}{All}{% \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 } \ProcessKeyvalOptions{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@ShowOptions \GenericWarning{* }{% *** List of possible options for lua-typo ***\MessageBreak [Default values between brackets]% \MessageBreak ShowOptions [false]\MessageBreak None [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 \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] }% \fi \AtBeginDocument{% \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.MinLen = tex.count.luatypoMinLen luatypo.LLminWD = tex.dimen.luatypoLLminWD luatypo.BackPI = tex.dimen.luatypoBackPI luatypo.BackFuzz = tex.dimen.luatypoBackFuzz }% } \AtVeryEndDocument{% \ifnum\luatypo@options = 0 \LT@Nonetrue \fi \ifLT@None \directlua{ texio.write_nl(' ') texio.write_nl('***********************************') texio.write_nl('*** lua-typo loaded 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 texio.write_nl('*** lua-typo: No Typo Flaws found.') 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(' ') 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.single = { } luatypo.double = { } luatypo.colortbl = { } luatypo.pagelist = " " 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 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_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 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 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(n6, color) end else color_node(n5, color) end end else color_node(n4, color) end end else color_node(n3, color) end end else color_node(nn, color) end end else color_node(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, post.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 end local len = utf8.len(str) if utf8_find(str, "_") then len = len - 1 end return len, str end local check_line_last_word = function (old, node, line, colno, flag) local COLOR = luatypo.colortbl[11] local match = false local new = "" local maxlen = 0 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 if n and n.id == HLIST then box = n prev = n.prev lastn = slide(n.head) n = lastn end while n and n.id ~= GLUE do maxlen, new = signature (n, new, swap) n = n.prev end if n and n.id == GLUE then new = new .. "_" go = true elseif box and not n then local p = box.prev if p.id == GLUE then new = new .. "_" n = p else n = box end go = true end if go then repeat n = n.prev maxlen, new = signature (n, new, swap) until not n or n.id == GLUE end new = utf8_reverse(new) if flag then local MinFull = luatypo.MinFull local MinPart = luatypo.MinPart MinFull = math.min(MinPart,MinFull) local k = MinPart local dlo = utf8_reverse(old) local wen = utf8_reverse(new) local oldlast = utf8_gsub (old, ".*_", "_") local newlast = utf8_gsub (new, ".*_", "_") local i if utf8_find(newlast, "_") then i = utf8.len(newlast) end if i and i > maxlen - MinPart + 1 then k = MinPart + 1 end local oldsub = "" local newsub = "" for p, c in utf8.codes(dlo) do if utf8.len(oldsub) < k then oldsub = utf8.char(c) .. oldsub end end for p, c in utf8.codes(wen) do if utf8.len(newsub) < k then newsub = utf8.char(c) .. newsub end end local l = utf8.len(new) if oldsub == newsub and l >= k then match = true elseif oldlast == newlast and utf8.len(newlast) > MinFull then match = true oldsub = oldlast newsub = newlast k = utf8.len(newlast) end if match then local osub = oldsub local nsub = newsub while osub == nsub and k <= maxlen do k = k +1 osub = string.sub(old,-k) nsub = string.sub(new,-k) if osub == nsub then newsub = nsub end end newsub = utf8_gsub(newsub, "^_", "") local msg = "E.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) oldsub = utf8_reverse(newsub) local newsub = "" local n = lastn repeat if n and n.id ~= GLUE then color_node(n, COLOR) l, newsub = signature(n, newsub, swap) elseif n and n.id == GLUE then newsub = newsub .. "_" elseif not n and box then n = box else break end n = n.prev until newsub == oldsub or l >= k end end end return new, match end local check_line_first_word = function (old, node, line, colno, flag) local COLOR = luatypo.colortbl[10] local match = false local swap = false local new = "" local maxlen = 0 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 local start = n if n and n.id == HLIST then box = n start = n.head n = n.head end while n and n.id ~= GLUE do maxlen, new = signature (n, new, swap) n = n.next end if n and n.id == GLUE then new = new .. "_" go = true elseif box and not n then local bn = box.next if bn.id == GLUE then new = new .. "_" n = bn else n = box end go = true end if go then repeat n = n.next maxlen, new = signature (n, new, swap) until not n or n.id == GLUE end if flag then local MinFull = luatypo.MinFull local MinPart = luatypo.MinPart MinFull = math.min(MinPart,MinFull) local k = MinPart local oldfirst = utf8_gsub (old, "_.*", "_") local newfirst = utf8_gsub (new, "_.*", "_") local i if utf8_find(newfirst, "_") then i = utf8.len(newfirst) end if i and i <= MinPart then k = MinPart + 1 end local oldsub = "" local newsub = "" for p, c in utf8.codes(old) do if utf8.len(oldsub) < k then oldsub = oldsub .. utf8.char(c) end end for p, c in utf8.codes(new) do if utf8.len(newsub) < k then newsub = newsub .. utf8.char(c) end end local l = utf8.len(newsub) if oldsub == newsub and l >= k then match = true elseif oldfirst == newfirst and utf8.len(newfirst) > MinFull then match = true oldsub = oldfirst newsub = newfirst k = utf8.len(newfirst) end if match then local osub = oldsub local nsub = newsub while osub == nsub and k <= maxlen do k = k + 1 osub = string.sub(old,1,k) nsub = string.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) oldsub = newsub local newsub = "" local k = utf8.len(oldsub) local n = start repeat if n and n.id ~= GLUE then color_node(n, COLOR) l, newsub = signature(n, newsub, swap) elseif n and n.id == GLUE then newsub = newsub .. "_" elseif not n and box then n = box else break end n = n.next until newsub == oldsub or l >= k end end return new, match end local check_page_first_word = function (node, colno) local COLOR = luatypo.colortbl[14] local match = false local swap = false local new = "" local maxlen = 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 > maxlen 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 <= maxlen and n and n.id == GLYPH and eow_char[n.char] then match =true if pn and (pn.id == GLUE or pn.id == KERN) then new = new .. " " len = len + 1 end len = len + 1 end if match then local msg = "ShortFinalWord=" .. new log_flaw(msg, 1, colno, false) 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) local COLOR = luatypo.colortbl[3] 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 get_pagebody = function (head) local textht = tex.getdimen("textheight") local fn = head.list local body = nil repeat fn = fn.next until fn.id == VLIST and fn.height > 0 first = fn.list for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then body = n break else first = n.list for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then body = n break end end end end if not body then texio.write_nl("***lua-typo ERROR: PAGE BODY *NOT* FOUND!***") end return body end check_vtop = function (head, colno, vpos) 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 footnote = false local ftnsplit = false local orphanflag = false local widowflag = false local pageshort = false local firstwd = "" local lastwd = "" local hyphcount = 0 local pageline = 0 local ftnline = 0 local line = 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 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 local n = nextnode while n and (n.id == GLUE or n.id == PENALTY or n.id == WHATSIT ) do n = n.next end if not n then page_bottom = true body_bottom = true elseif footnoterule_ahead(n) then body_bottom = true end local hmax = linewd + tex.hfuzz local w,h,d = dimensions(1,2,0, first) if w > hmax and OverfullLines then pageflag = true local wpt = string.format("%.2fpt", (w-head.width)/65536) local msg = "OVERFULL line " .. wpt log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[7] color_line (head, COLOR) elseif head.glue_set > Stretch and head.glue_sign == 1 and head.glue_order == 0 and UnderfullLines then pageflag = true local s = string.format("%.0f%s", 100*head.glue_set, "%") local msg = "UNDERFULL line stretch=" .. s log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[8] color_line (head, COLOR) 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 local ln = slide(first) 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 local msg = "SHORT LINE: length=" .. string.format("%.0fpt", llwd/65536) log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[6] local attr = oberdiek.luacolor.getattribute() color_line (head, COLOR) end end if BackParindent and PFskip < BackPI and PFskip >= BackFuzz and parline > 1 then pageflag = true local msg = "NEARLY FULL line: backskip=" .. string.format("%.1fpt", PFskip/65536) log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[12] local attr = oberdiek.luacolor.getattribute() color_line (head, COLOR) end if Widows and widowflag then pageflag = true local msg = "WIDOW" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[4] color_line (head, COLOR) widowflag = false end if FirstWordMatch then local flag = not ListItem firstwd, flag = check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end end if LastWordMatch then local flag = true if PFskip > BackPI then flag = false end lastwd, flag = check_line_last_word(lastwd, pn, line, colno, flag) if flag then pageflag = true end end elseif pn and pn.id == DISC then hyphcount = hyphcount + 1 if orphanflag and Orphans then pageflag = true local msg = "ORPHAN" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[5] color_line (head, COLOR) end if ftnsplit and FootnoteSplit then pageflag = true local msg = "FOOTNOTE SPLIT" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[13] color_line (head, COLOR) 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[1] local pg = show_pre_disc (pn,COLOR) end if FirstWordMatch then local flag = not ListItem firstwd, flag = check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end end if LastWordMatch then local flag = true lastwd, flag = check_line_last_word(lastwd, ln, line, colno, flag) if flag then pageflag = true end end if hyphcount > HYPHmax and RepeatedHyphens then local COLOR = luatypo.colortbl[2] local pg = show_pre_disc (pn,COLOR) pageflag = true local msg = "REPEATED HYPHENS: more than " .. HYPHmax log_flaw(msg, line, colno, footnote) 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[0] local pg = show_pre_disc (pn,COLOR) end end end else hyphcount = 0 if orphanflag and Orphans then pageflag = true local msg = "ORPHAN" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[5] color_line (head, COLOR) end if ftnsplit and FootnoteSplit then pageflag = true local msg = "FOOTNOTE SPLIT" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[13] color_line (head, COLOR) end if FirstWordMatch then local flag = not ListItem firstwd, flag = check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end end if LastWordMatch and pn then local flag = true lastwd, flag = check_line_last_word(lastwd, pn, line, colno, flag) 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) then pageflag = true end end end end if ShortFinalWord and pageline == 1 and parline > 1 and check_page_first_word(first,colno) then pageflag = true end 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 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[7] color_line (head, COLOR) end end elseif head and head.id == RULE and head.subtype == 0 then vpos = vpos + head.height + head.depth if body_bottom 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 ShortPages then pageshort = true end if pageshort and vpos < vpos_min then pageflag = true local msg = "SHORT PAGE: only " .. pageline .. " lines" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[9] local n = head repeat n = n.prev until n.id == HLIST color_line (n, COLOR) end first_bot = false end elseif head.id == GLUE then vpos = vpos + effective_glue(head,body) elseif head.id == KERN and head.subtype == 1 then vpos = vpos + head.kern elseif head.id == VLIST then vpos = vpos + head.height + head.depth elseif head.id == HLIST and head.subtype == BOX then local hf = head.list if hf and hf.id == VLIST and hf.subtype == 0 then break end 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 end luatypo.check_page = function (head) local textwd = tex.getdimen("textwidth") local vpos = 0 local n2, n3, col, colno local body = get_pagebody(head) local footnote = false local top = body local first = body.list if (first and first.id == HLIST and first.subtype == BOX) or (first and first.id == VLIST and first.subtype == 0) then top = body.list first = top.list end while top do first = top.list if top and top.id == VLIST and top.subtype == 0 and top.width > textwd/2 then local next = check_vtop(first,colno,vpos) if next then top = next elseif top then top = top.next 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 n in traverse_id(VLIST, first) do colno = colno + 1 col = n.list check_vtop(col,colno,vpos) 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 nn = n.list if nn and nn.list then col = nn.list check_vtop(col,colno,vpos) end end colno = nil top = top.next else top = top.next end end return true end return luatypo.check_page \end{luacode} \AtEndOfPackage{% \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} \luatypoSetColor0{red}% Paragraph last full line hyphenated \luatypoSetColor1{red}% Page last word hyphenated \luatypoSetColor2{red}% Hyphens on to many consecutive lines \luatypoSetColor3{red}% Short word at end of line \luatypoSetColor4{cyan}% Widow \luatypoSetColor5{cyan}% Orphan \luatypoSetColor6{cyan}% Paragraph ending on a short line \luatypoSetColor7{blue}% Overfull lines \luatypoSetColor8{blue}% Underfull lines \luatypoSetColor9{red}% Nearly empty page \luatypoSetColor{10}{LTred}% First word matches \luatypoSetColor{11}{LTred}% Last word matches \luatypoSetColor{12}{LTgrey}% Paragraph ending on a nearly full line \luatypoSetColor{13}{cyan}% Footnote split \luatypoSetColor{14}{red}% Too short first (final) word on the page \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=4\relax \luatypoMinPART=4\relax \luatypoMinLen=4\relax }% %% %% %% End of file `lua-typo.sty'.