From f6cb3d263826846efe7b926e6ad2092198db0522 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 10 Mar 2023 03:04:05 +0000 Subject: CTAN sync 202303100304 --- macros/luatex/latex/lua-typo/README.md | 7 +- macros/luatex/latex/lua-typo/doc/lua-typo-demo.pdf | Bin 16859 -> 16859 bytes macros/luatex/latex/lua-typo/doc/lua-typo-fr.pdf | Bin 105225 -> 106404 bytes macros/luatex/latex/lua-typo/doc/lua-typo.pdf | Bin 173735 -> 179022 bytes macros/luatex/latex/lua-typo/source/lua-typo.dtx | 957 ++++++++++++--------- 5 files changed, 556 insertions(+), 408 deletions(-) (limited to 'macros/luatex') diff --git a/macros/luatex/latex/lua-typo/README.md b/macros/luatex/latex/lua-typo/README.md index 0930ac70cf..9577fee1cf 100644 --- a/macros/luatex/latex/lua-typo/README.md +++ b/macros/luatex/latex/lua-typo/README.md @@ -81,7 +81,12 @@ Changes * v.0.61: - bug fixes and documentation enhanced. - - colours `mygrey` and `myred` renamed `LTgrey` and `LTred`. + - colours `mygrey` and `myred` renamed as `LTgrey` and `LTred`. + +* v.0.65: + - new option `ShortFinalWord` to detect short end-of-sentence word + on top of next page. + - code cleaning. -- Copyright 2020--2023 Daniel Flipo diff --git a/macros/luatex/latex/lua-typo/doc/lua-typo-demo.pdf b/macros/luatex/latex/lua-typo/doc/lua-typo-demo.pdf index f5ea36a35e..35fe6bc907 100644 Binary files a/macros/luatex/latex/lua-typo/doc/lua-typo-demo.pdf and b/macros/luatex/latex/lua-typo/doc/lua-typo-demo.pdf differ diff --git a/macros/luatex/latex/lua-typo/doc/lua-typo-fr.pdf b/macros/luatex/latex/lua-typo/doc/lua-typo-fr.pdf index 287b2660b5..ed50a38d11 100644 Binary files a/macros/luatex/latex/lua-typo/doc/lua-typo-fr.pdf and b/macros/luatex/latex/lua-typo/doc/lua-typo-fr.pdf differ diff --git a/macros/luatex/latex/lua-typo/doc/lua-typo.pdf b/macros/luatex/latex/lua-typo/doc/lua-typo.pdf index 55acdc97b8..6990971202 100644 Binary files a/macros/luatex/latex/lua-typo/doc/lua-typo.pdf and b/macros/luatex/latex/lua-typo/doc/lua-typo.pdf differ diff --git a/macros/luatex/latex/lua-typo/source/lua-typo.dtx b/macros/luatex/latex/lua-typo/source/lua-typo.dtx index 9929679bc2..41a9966280 100644 --- a/macros/luatex/latex/lua-typo/source/lua-typo.dtx +++ b/macros/luatex/latex/lua-typo/source/lua-typo.dtx @@ -52,29 +52,28 @@ \csname fi\endcsname % %<*driver> -\RequirePackage{pdfmanagement-testphase} -%<+doc>\DeclareDocumentMetadata{pdfstandard=A-2b, lang=en-GB} -%<+docfr>\DeclareDocumentMetadata{pdfstandard=A-2b, lang=fr-FR} -\documentclass[a4paper]{ltxdoc} -\usepackage[dvipsnames]{xcolor} +%<+doc>\DocumentMetadata{pdfstandard=A-2b, lang=en-GB} +%<+doc>\documentclass[a4paper,british]{ltxdoc} +%<+docfr>\DocumentMetadata{pdfstandard=A-2b, lang=fr-FR} +%<+docfr>\documentclass[a4paper,french]{ltxdoc} \usepackage{fontspec} \setmainfont{erewhon} \setsansfont{Cabin}[Scale=MatchLowercase] \setmonofont{VeraMono.ttf}[ Scale = MatchLowercase, - ItalicFont = VeraMoIt.ttf, - BoldFont = VeraMoBd.ttf, - BoldItalicFont = VeraMoBI.ttf, - HyphenChar=None, Color=Sepia, + ItalicFont = VeraMoIt.ttf, + BoldFont = VeraMoBd.ttf, + BoldItalicFont = VeraMoBI.ttf, + HyphenChar=None, + Color=5D1D00, ] \usepackage[expansion=true, protrusion=true]{microtype} -%<+doc>\usepackage[british]{babel} -%<+docfr>\usepackage[french]{babel} +\usepackage{babel,varioref} %<+docfr>\frenchsetup{og=«, fg=»} + \usepackage[ShortPages, OverfullLines, UnderfullLines, Widows, Orphans, EOPHyphens, RepeatedHyphens ]{lua-typo} -\luatypoLLminWD=3em \renewcommand*\descriptionlabel[1]{% \hspace{\labelsep}\texttt{#1}} \usepackage{array,url,verbatim} @@ -100,7 +99,7 @@ \newcommand*\file[1]{\texttt{#1}} \newcommand*\pkg[1]{\texttt{#1}} \newcommand*\opt[1]{\texttt{#1}} -\renewcommand\meta[1]{\texttt{\textsl{\color{Sepia}<#1>}}} +\renewcommand\meta[1]{\texttt{\textsl{<#1>}}} \newcommand*\node[1]{\textsc{#1}} % \setlength{\parindent}{0pt} @@ -205,7 +204,7 @@ % \pkg{lua-typo}, il suffit d’ajouter dans le préambule la ligne %\\ % |\usepackage[All]{lua-typo}| % -% La version courante (0.61) nécessite un noyau LaTeX récent, +% La version courante (0.65) nécessite un noyau LaTeX récent, % 2021/06/01 ou ultérieur. Ceux qui ne disposent que d’un noyau plus % ancient reçoivent un message d’avertissement et un message d’erreur % «\texttt{Unable to register callback}» ; une version «rollback » @@ -235,10 +234,20 @@ % ou pour se limiter aux tests \meta{OptX} et \meta{OptY} :\\ % |\usepackage[|\meta{OptX}|, |\meta{OptY}|]{lua-typo}| % -% \pagebreak[4] -% Le tableau suivant donne le nom des options et le type des -% vérifications proposées : +% La liste des options et le type des vérifications proposées sont +% présentés dans le tableau \vpageref{options-fr}. +% Par exemple, pour limiter les vérifications aux lignes trop pleines +% ou creuses, il suffit de coder :\\ +% |\usepackage[OverfullLines, UnderfullLines]{lua-typo}|\\ +% Pour tout vérifier sauf les coupures répétées en fin de ligne on +% codera :\\ +% |\usepackage[All, RepeatedHyphens=false]{lua-typo}|\\ +% Notez que l’option that \opt{All} doit être la première de la +% liste, les suivantes étant rétirées de la liste complète définie +% par~\opt{All}. % +% \begin{table}[ht] +% \centering\label{options-fr} % \begin{tabular}{>{\ttfamily}ll} % \multicolumn{1}{l}{Nom} & Imperfection à signaler\\ \hline % All & Active toutes les options ci-dessous\\ @@ -258,18 +267,10 @@ % LastWordMatch & Même (partie de) mot en fin de lignes % consécutives ?\\ % FootnoteSplit & Fin de note de bas de page sur page suivante?\\ +% ShortFinalWord & Mot de fin de phrase court en haut de page\\ % \hline % \end{tabular} -% -% Par exemple, pour limiter les vérifications aux lignes trop pleines -% ou creuses, il suffit de coder :\\ -% |\usepackage[OverfullLines, UnderfullLines]{lua-typo}|\\ -% Pour tout vérifier sauf les coupures répétées en fin de ligne on -% codera :\\ -% |\usepackage[All, RepeatedHyphens=false]{lua-typo}|\\ -% Notez que l’option that \opt{All} doit être la première de la -% liste, les suivantes étant rétirées de la liste complète définie -% par~\opt{All}. +% \end{table} % % Le nom des différentes options n’étant pas facile à mémoriser, il % est possible de les retrouver sans devoir consulter la @@ -299,6 +300,9 @@ % \item[RepeatedHyphens :] de même, lorsque le nombre de lignes % consécutives affectées par des coupures dépasse le seuil fixé % (voir ci-dessous), ne sont coloriées que les coupures en excès. +% \item[ShortFinalWord :] lorsque le premier mot de la première ligne +% d’une page termine une phrase et qu’il est court (au plus +% |\luatypoMinLen=4| lettres), on le signale. % \end{description} % % \section{Paramétrage personnalisé} @@ -374,7 +378,7 @@ % commun), ainsi que la présence de « mon » en début ou fin de % deux lignes consécutives (trois lettres en commun). % -% \item[EOLShortWords:] cette option signale la présence en fin de +% \item[EOLShortWords:] cette option signale la présence en fin de % ligne de mots très courts (une ou deux lettres) % qui sont répertoriés dans une des listes % suivantes (elles dépendent de la langue courante) :\\ @@ -423,14 +427,16 @@ % \luatypoSetColor{11}{LTred} % Répétitions en fin de ligne % \luatypoSetColor{12}{LTgrey}% Dernière ligne d’alinéa presque pleine % \luatypoSetColor{13}{cyan} % Note de bas de page éclatée +% \luatypoSetColor{14}{red} % Mot de fin de phrase en haut de page % \end{verbatim} -% \pkg{lua-typo} charge l’extension graphique \pkg{color}. -% Seules les couleurs portant un nom (\emph{named colors}) peuvent -% être utilisées ; pour en définir de nouvelles, il faut donc soit -% utiliser la commande |\definecolor| de l’extension \pkg{color} -% (comme ci-dessus pour |LTgrey| ou |LTred|), soit charger l’extension -% \pkg{xcolor} package qui donne accès à une kyrielle de noms de -% couleurs. +% \pkg{lua-typo} charge les extensions \pkg{luacolor} et donc +% \pkg{color}. Seules les couleurs portant un nom (\emph{named +% colors}) peuvent être utilisées dans la commande +% |\luatypoSetColor| ; pour en définir de nouvelles il faut donc, +% soit utiliser la commande |\definecolor| de l’extension +% \pkg{color} (comme ci-dessus pour |LTgrey| ou |LTred|), soit +% charger l’extension \pkg{xcolor} qui donne accès à une +% kyrielle de noms de couleurs. % \end{FrenchDoc} % % \StopEventually{} @@ -449,9 +455,9 @@ % \filedate.}, is meant for careful writers and proofreaders who do % not feel totally satisfied with LaTeX output, the most frequent % issues being overfull or underfull lines, widows and orphans, -% hyphenated words split across two pages, consecutive lines ending -% with hyphens, paragraphs ending on too short or nearly full lines, -% homeoarchy, etc. +% hyphenated words split across two pages, two many consecutive lines +% ending with hyphens, paragraphs ending on too short or nearly full +% lines, homeoarchy, etc. % % This package, which works with LuaLaTeX only, % \emph{does not try to correct anything} but just highlights @@ -469,7 +475,7 @@ % may be acceptable in some conditions (multi-columns, technical % papers) and unbearable in others (literary works f.i.). % Moreover, correcting a potential issue somewhere may result in -% other much more serious flaws elsewhere…\\ +% other much more serious flaws somewhere else …\\ % b) Conversely, possible bugs in \pkg{lua-typo} might hide issues % that should normally be highlighted. % @@ -543,6 +549,7 @@ % FirstWordMatch & same (part of) word starting two consecutive lines?\\ % LastWordMatch & same (part of) word ending two consecutive lines?\\ % FootnoteSplit & footnotes spread over two pages or more?\\ +% ShortFinalWord & Short word ending a sentence on the next page\\ % \hline % \end{tabular}\\[12pt] % For example, if you want \pkg{lua-typo} to only warn about overfull @@ -559,7 +566,6 @@ % this option provides an easy way to get their names without having % to look into the documentation. % -% \enlargethispage*{\baselineskip} % With option \opt{None}, \pkg{lua-typo} \emph{does absolutely % nothing}, all checks are disabled as the main function is not added % to any LuaTeX callback. It not quite equivalent to commenting out @@ -577,6 +583,9 @@ % \item[RepeatedHyphens:] ditto, when the number of consecutives % hyphenated lines is too high, only the hyphenated words in % excess (the last ones) are hightlighted. +% \item[ShortFinalWord :] the first word on a page is highlighted +% if it ends a sentence and is short (up to |\luatypoMinLen=4| +% letters). % \end{description} % % \section{Customisation} @@ -685,17 +694,18 @@ % \luatypoSetColor9{red} % Nearly empty page (a few lines) % \luatypoSetColor{10}{LTred} % First word matches % \luatypoSetColor{11}{LTred} % Last word matches -% \luatypoSetColor{12}{LTgrey}% paragraph’s last line nearly full -% \luatypoSetColor{13}{cyan} % footnotes spread over two pages +% \luatypoSetColor{12}{LTgrey}% Paragraph’s last line nearly full +% \luatypoSetColor{13}{cyan} % Footnotes spread over two pages +% \luatypoSetColor{14}{red} % Short final word on top of the page % \end{verbatim} % -% \pkg{lua-typo} loads the \pkg{color} package from the LaTeX graphic -% bundle. Only named colours can be used by \pkg{lua-typo}, so you -% can either use the |\definecolor| from \pkg{color} package to -% define yours (as done in the config file for `LTgrey’) or load the +% \pkg{lua-typo} loads the \pkg{luacolor} package which loads the +% \pkg{color} package from the LaTeX graphic bundle. +% |\luatypoSetColor| requires named colours, so you can either use +% the |\definecolor| from \pkg{color} package to define yours +% (as done in the config file for `LTgrey’ and `LTred’) or load the % \pkg{xcolor} package which provides a bunch of named colours. % -% \clearpage % \section{\TeX{}nical details} % % \iffalse @@ -709,7 +719,7 @@ %<+scan>\ProvidesPackage{scan-page} %<+dtx>\ProvidesFile{lua-typo.dtx} %<*dtx|sty|scan> - [2023-02-10 v.0.61 Daniel Flipo] + [2023-03-08 v.0.65 Daniel Flipo] % %<*sty> % \fi @@ -725,7 +735,7 @@ % \begin{macrocode} \ifdefined\DeclareRelease \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty} - \DeclareCurrentRelease{}{2023-02-04} + \DeclareCurrentRelease{}{2023-03-08} \else \PackageWarning{lua-typo}{Your LaTeX kernel is too old to provide access\MessageBreak to former versions of the lua-typo package.% @@ -761,6 +771,7 @@ \newcount\luatypoPageMin \newcount\luatypoMinFull \newcount\luatypoMinPart +\newcount\luatypoMinLen \newcount\luatypo@LANGno \newcount\luatypo@options \newtoks\luatypo@single @@ -797,6 +808,7 @@ luatypo = { } \DeclareBoolOption[false]{FirstWordMatch} \DeclareBoolOption[false]{LastWordMatch} \DeclareBoolOption[false]{FootnoteSplit} +\DeclareBoolOption[false]{ShortFinalWord} % \end{macrocode} % Option \opt{All} resets all booleans relative to specific % typographic checks to \opt{true}. @@ -809,6 +821,7 @@ luatypo = { } \LT@ParLastHyphentrue \LT@EOLShortWordstrue \LT@FirstWordMatchtrue \LT@LastWordMatchtrue \LT@BackParindenttrue \LT@FootnoteSplittrue + \LT@ShortFinalWordtrue } \ProcessKeyvalOptions{luatypo} % \end{macrocode} @@ -909,6 +922,12 @@ luatypo = { } \else \directlua{ luatypo.FootnoteSplit = false }% \fi + \ifLT@ShortFinalWord + \advance\luatypo@options by 1 + \directlua{ luatypo.ShortFinalWord = true }% + \else + \directlua{ luatypo.ShortFinalWord = false }% + \fi } % \end{macrocode} % @@ -936,6 +955,7 @@ luatypo = { } FirstWordMatch [false]\MessageBreak LastWordMatch [false]\MessageBreak FootnoteSplit [false]\MessageBreak + ShortFinalWord [false]\MessageBreak \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] @@ -954,6 +974,7 @@ luatypo = { } 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 @@ -1004,8 +1025,8 @@ luatypo = { } % lines. The first argument is a language name, say \opt{french}, % which is turned into a command |\l@french| expanding to a number % known by luatex, otherwise an error message occurs. -% The UTF8 string entered as second argument has to be converted -% into the font internal coding. +% The utf-8 string entered as second argument has to be +% converted into the font internal coding. % \begin{macrocode} \newcommand*{\luatypoOneChar}[2]{% \def\luatypo@LANG{#1}\luatypo@single={#2}% @@ -1016,7 +1037,7 @@ luatypo = { } local string = \the\luatypo@single luatypo.single[langno] = " " for p, c in utf8.codes(string) do - local s = string.char(c) + local s = utf8.char(c) luatypo.single[langno] = luatypo.single[langno] .. s end % texio.write_nl("SINGLE=" .. luatypo.single[langno]) @@ -1035,7 +1056,7 @@ luatypo = { } local string = \the\luatypo@double luatypo.double[langno] = " " for p, c in utf8.codes(string) do - local s = string.char(c) + local s = utf8.char(c) luatypo.double[langno] = luatypo.double[langno] .. s end % texio.write_nl("DOUBLE=" .. luatypo.double[langno]) @@ -1087,14 +1108,11 @@ char_to_discard[string.byte(":")] = true char_to_discard[string.byte(";")] = true char_to_discard[string.byte("-")] = true -local split_lig = { } -split_lig[0xFB00] = "ff" -split_lig[0xFB01] = "fi" -split_lig[0xFB02] = "fl" -split_lig[0xFB03] = "ffi" -split_lig[0xFB04] = "ffl" -split_lig[0xFB05] = "st" -split_lig[0xFB06] = "st" +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") @@ -1134,10 +1152,13 @@ local HYPH = 0x2D % \begin{macrocode} local LIGA = 0x102 % \end{macrocode} -% |parline| (current paragraph) must not be reset on every new page! +% Counter |parline| (current paragraph) \emph{must not be reset} +% on every new page! % \begin{macrocode} local parline = 0 - +% \end{macrocode} +% Local definitions for the `node’ library: +% \begin{macrocode} local dimensions = node.dimensions local rangedimensions = node.rangedimensions local effective_glue = node.effective_glue @@ -1148,13 +1169,44 @@ local traverse_id = node.traverse_id local has_field = node.has_field local uses_font = node.uses_font local is_glyph = node.is_glyph - % \end{macrocode} +% Local definitions from the `unicode.utf8’ library: replacements are +% needed for functions |string.gsub()|, |string.find()| and +% |string.reverse()| which are meant for one-byte characters only. +% +% \changes{v0.65}{2023/03/02}{Three new functions for utf-8 strings’ +% manipulations.} % -% \changes{v0.32}{2021/03/14}{Better protection against unexpected +% |utf8_find| requires an utf-8 string and a `pattern’ (also utf-8), +% it returns |nil| if pattern is not found, or the \emph{byte} +% position of the first match otherwise [not an issue as we only +% care for true/false]. +% \begin{macrocode} +local utf8_find = unicode.utf8.find +% \end{macrocode} +% |utf8_gsub| mimics |string.gsub| for utf-8 strings. +% \begin{macrocode} +local utf8_gsub = unicode.utf8.gsub +% \end{macrocode} +% |utf8_reverse| returns the reversed string (utf-8 chars read from +% end to beginning) [same as |string.reverse| but for utf-8 strings]. +% \begin{macrocode} +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 +% \end{macrocode} +% +%\changes{v0.32}{2021/03/14}{Better protection against unexpected % nil nodes.} % -% This auxillary function colours glyphs and discretionaries. +% The next function colours glyphs and discretionaries. % It requires two arguments: a node and a (named) colour. % % \begin{macrocode} @@ -1166,34 +1218,20 @@ local color_node = function (node, color) local repl = node.replace if pre then set_attribute(pre,attr,color) -% texio.write_nl('PRE=' .. tostring(pre.char)) end if post then set_attribute(post,attr,color) -% if pre then -% texio.write(' POST=' .. tostring(post.char)) -% else -% texio.write_nl('POST=' .. tostring(post.char)) -% end end if repl then set_attribute(repl,attr,color) -% if pre or post then -% texio.write(' REPL=' .. tostring(repl.char)) -% else -% texio.write_nl('REPL=' .. tostring(repl.char)) -% end end -% if pre or post or repl then -% texio.write_nl(' ') -% end elseif node then set_attribute(node,attr,color) end end % \end{macrocode} % -% This auxillary function colours a whole line. It requires two +% The nextfunction colours a whole line. It requires two % arguments: a line’s node and a (named) colour.\par % Digging into nested hlists and vlists is needed f.i.\ to colour % aligned equations. @@ -1245,9 +1283,9 @@ local color_line = function (head, color) end % \end{macrocode} % -% This function appends a line to a buffer which will be written -% to file `\cs{jobname.typo}’; it takes four arguments: -% a string, two numbers (which can be \node{nil}) and a flag. +% The next function takes four arguments: a string, two numbers +% (which can be \node{nil}) and a flag. It appends a line to +% a buffer which will be written to file `\cs{jobname.typo}’. % % \changes{v0.50}{2021/05/13}{Summary of flaws written to file % `\cs{jobname.typo}’.} @@ -1277,77 +1315,72 @@ end % While comparing two words, the only significant nodes are glyphs % and ligatures, dicretionnaries other than ligatures, kerns % (letterspacing) should be discarded. -% For each word to be compared we build a ``signature'' made of +% For each word to be compared we build a ``signature’’ made of % glyphs and split ligatures. % -% \changes{v0.32}{2021/03/14}{Experimental code to deal with non -% standard ligatures.} +% \changes{v0.65}{2023/03/02}{All ligatures are now split using the +% node’s `components’ field rather than a table.} % % The first function adds a node to a signature of type string. % It returns the augmented string and its length. % The last argument is a boolean needed when building a signature -% backwards (see |check_last_word|). +% backwards (see |check_line_last_word|). % \begin{macrocode} local signature = function (node, string, swap) local n = node local str = string if n and n.id == GLYPH then - local b, id = is_glyph(n) - if b and not char_to_discard[b] then -% \end{macrocode} -% Punctuation has to be discarded; the French apostrophe -% (right quote U+2019) has a char code ``out of range'', -% we replace it with U+0027; -% Other glyphs should have char codes less than 0x100 (or 0x180?) or -% be ligatures… standard ones (U+FB00 to U+FB06) are converted using -% table |split_lig|. -% \begin{macrocode} - if b == 0x2019 then b = 0x27 end - if b < 0x100 then - str = str .. string.char(b) - elseif split_lig[b] then - local c = split_lig[b] - if swap then - c = string.reverse(c) - end - str = str .. c + local b = n.char + if b and not char_to_discard[b] then % \end{macrocode} -% Experimental: store other ligatures as the last two digits of their -% decimal code… +% Punctuation has to be discarded; other glyphs may be ligatures, +% then they have a |components| field which holds the list of glyphs +% which compose the ligature. % \begin{macrocode} - elseif n.subtype == LIGA and b > 0xE000 then - local c = string.sub(b,-2) - if swap then - c = string.reverse(c) - end - str = str .. c - end - end + 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 % \end{macrocode} -% Ligatures are split into |pre| and |post| and both parts are -% stored. In case of \emph{ffl, ffi}, the post part is also -% a ligature… +% Discretionaries are split into |pre| and |post| and both parts +% are stored. They might be ligatures (\emph{ffl, ffi})… % \begin{macrocode} local pre = n.pre local post = n.post local c1 = "" local c2 = "" - if pre and pre.char and pre.char ~= HYPH and pre.char < 0x100 then - c1 = string.char(pre.char) + 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.char < 0x100 then - c2 = string.char(post.char) - elseif split_lig[post.char] then - c2 = split_lig[post.char] - if swap then - c2 = string.reverse(c2) + 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 .. c2 .. c1 + str = str .. utf8_reverse(c2) .. c1 else str = str .. c1 .. c2 end @@ -1355,39 +1388,39 @@ local signature = function (node, string, swap) % \end{macrocode} % The returned length is the number of \emph{letters}. % \begin{macrocode} - local len = string.len(str) - if string.find(str, "_") then + local len = utf8.len(str) + if utf8_find(str, "_") then len = len - 1 end return len, str end % \end{macrocode} % -% This auxillary function looks for consecutive lines ending with the -% same letters.\par +% The next function looks for consecutive lines ending with +% the same letters.\par % It requires five arguments: a string (previous line’s signature), % a node (the last one on the current line), a line number, a column % number (possibly |nil|) and a boolean to cancel checking in some -% cases (end of paragraphs).\par +% cases (end of paragraphs). % It prints the matching part at end of linewith with the supplied % colour and returns the current line’s last word and a boolean (match). % -% \changes{v0.32}{2021/03/14}{Functions `check\_first\_word’ and -% `check\_last\_word’ rewritten.} +% \changes{v0.32}{2021/03/14}{Functions `check\_line\_first\_word’ and +% `check\_line\_last\_word’ rewritten.} % % \changes{v0.50}{2021/05/13}{Homeoarchy detection added for lines % starting or ending on \cs{mbox}.} % -% \changes{v0.61}{2023/02/06}{`check\_last\_word’ returns a flag +% \changes{v0.61}{2023/02/06}{`check\_line\_last\_word’ returns a flag % to set pageflag.} % % \begin{macrocode} -local check_last_word = function (old, node, line, colno, flag) +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 flag and node then + if node then local swap = true local box, go % \end{macrocode} @@ -1431,69 +1464,91 @@ local check_last_word = function (old, node, line, colno, flag) maxlen, new = signature (n, new, swap) until not n or n.id == GLUE end - new = string.reverse(new) -% texio.write_nl('EOLsigold=' .. old) -% texio.write(' EOLsig=' .. new) - local MinFull = luatypo.MinFull - local MinPart = luatypo.MinPart - MinFull = math.min(MinPart,MinFull) - local k = MinPart - local oldlast = string.gsub (old, '.*_', '') - local newlast = string.gsub (new, '.*_', '') - local i = string.find(new, "_") - if i and i > maxlen - MinPart + 1 then - k = MinPart + 1 - end - local oldsub = string.sub(old,-k) - local newsub = string.sub(new,-k) - local l = string.len(new) - if oldsub == newsub and l >= k then -% texio.write_nl('EOLnewsub=' .. newsub) - match = true - elseif oldlast == newlast and string.len(newlast) >= MinFull then -% texio.write_nl('EOLnewlast=' .. newlast) - match = true - oldsub = oldlast - newsub = newlast - k = string.len(newlast) - end - if match then + new = utf8_reverse(new) +% texio.write_nl("EOLsigold=" .. old) +% texio.write(" EOLsig=" .. new) % \end{macrocode} -% Minimal partial match; any more glyphs matching? +% When called with flag |false|, |check_line_last_word| returns +% the last word’s signature, but doesn’t compare it with the +% previous line’s. % \begin{macrocode} - 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 + 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 - newsub = string.gsub(newsub, '^_', '') -% texio.write_nl('EOLfullmatch=' .. newsub) - local msg = "E.O.L. MATCH=" .. newsub - log_flaw(msg, line, colno, footnote) + 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 +% texio.write_nl("EOLnewsub=" .. newsub) + match = true + elseif oldlast == newlast and utf8.len(newlast) > MinFull then +% texio.write_nl("EOLnewlast=" .. newlast) + match = true + oldsub = oldlast + newsub = newlast + k = utf8.len(newlast) + end + if match then +% \end{macrocode} +% Minimal partial match; any more glyphs matching? +% \begin{macrocode} + 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, "^_", "") +% texio.write_nl("EOLfullmatch=" .. newsub) + local msg = "E.O.L. MATCH=" .. newsub + log_flaw(msg, line, colno, footnote) % \end{macrocode} % Lest’s colour the matching string. % \begin{macrocode} - oldsub = string.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 + 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 @@ -1503,11 +1558,11 @@ end % Same thing for beginning of lines: check the first two words % and compare their signature with the previous line’s. % -% \changes{v0.61}{2023/02/06}{`check\_first\_word’ returns a flag +% \changes{v0.61}{2023/02/06}{`check\_line\_first\_word’ returns a flag % to set pageflag.} % % \begin{macrocode} -local check_first_word = function (old, node, line, colno, flag) +local check_line_first_word = function (old, node, line, colno, flag) local COLOR = luatypo.colortbl[10] local match = false local swap = false @@ -1548,10 +1603,10 @@ local check_first_word = function (old, node, line, colno, flag) maxlen, new = signature (n, new, swap) until not n or n.id == GLUE end -% texio.write_nl('BOLsigold=' .. old) -% texio.write(' BOLsig=' .. new) +% texio.write_nl("BOLsigold=" .. old) +% texio.write(" BOLsig=" .. new) % \end{macrocode} -% When called with flag |false|, |check_first_word| returns +% When called with flag |false|, |check_line_first_word| returns % the first word’s signature, but doesn’t compare it with the % previous line’s. % \begin{macrocode} @@ -1560,26 +1615,33 @@ local check_first_word = function (old, node, line, colno, flag) local MinPart = luatypo.MinPart MinFull = math.min(MinPart,MinFull) local k = MinPart - local oldsub = "" - local newsub = "" - local oldfirst = string.gsub (old, '_.*', '') - local newfirst = string.gsub (new, '_.*', '') - local i = string.find(new, "_") + 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 = string.sub(old,1,k) - local newsub = string.sub(new,1,k) - local l = string.len(newsub) + 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 -% texio.write_nl('BOLnewsub=' .. newsub) +% texio.write_nl("BOLnewsub=" .. newsub) match = true - elseif oldfirst == newfirst and string.len(newfirst) >= MinFull then -% texio.write_nl('BOLnewfirst=' .. newfirst) + elseif oldfirst == newfirst and utf8.len(newfirst) > MinFull then +% texio.write_nl("BOLnewfirst=" .. newfirst) match = true oldsub = oldfirst newsub = newfirst - k = string.len(newfirst) + k = utf8.len(newfirst) end if match then % \end{macrocode} @@ -1595,8 +1657,8 @@ local check_first_word = function (old, node, line, colno, flag) newsub = nsub end end - newsub = string.gsub(newsub, '_$', '') --$ -% texio.write_nl('BOLfullmatch=' .. newsub) + newsub = utf8_gsub(newsub, "_$", "") --$ +% texio.write_nl("BOLfullmatch=" .. newsub) local msg = "B.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) % \end{macrocode} @@ -1604,7 +1666,7 @@ local check_first_word = function (old, node, line, colno, flag) % \begin{macrocode} oldsub = newsub local newsub = "" - local k = string.len(oldsub) + local k = utf8.len(oldsub) local n = start repeat if n and n.id ~= GLUE then @@ -1625,13 +1687,79 @@ local check_first_word = function (old, node, line, colno, flag) end % \end{macrocode} % -% \pagebreak[4] -% This auxillary function looks for a short word (one or two chars) -% at end of lines, compares it to a given list and colours it if -% matches. The first argument must be a node of type |GLYPH|, +% \changes{v0.65}{2023/03/02}{New `check\_page\_first\_word’ function.} +% +% The next function checks the first word on a new page: if +% it ends a sentence and is short (up to |luatypoMinLen| characters), +% the function returns |true| and colors the offending word. +% Otherwise it just retrurs |false|. +% The function requires two arguments: the line’s first node and +% a column number (possibly |nil|). +% +% \begin{macrocode} +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) +% \end{macrocode} +% In French `?’ and `!’ are preceded by a glue (babel) or a kern +% (polyglossia). +% \begin{macrocode} + 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 +% texio.write_nl("FinalWord=" .. new) + if match then + local msg = "ShortFinalWord=" .. new + log_flaw(msg, 1, colno, false) +% \end{macrocode} +% Lest’s colour the final word and punctuation sign. +% \begin{macrocode} + local n = start + repeat + color_node(n, COLOR) + n = n.next + until eow_char[n.char] + color_node(n, COLOR) + end + return match +end +% \end{macrocode} +% +% The next function looks for a short word (one or two +% chars) at end of lines, compares it to a given list and colours it +% if matches. The first argument must be a node of type |GLYPH|, % usually the last line’s node, the next two are the line and -% column number.\par -% TODO: where does ``out of range’’ starts? U+0100? U+0180? +% column number. % % \changes{v0.61}{2023/02/06}{`check\_regexpr’ returns a flag % to set pageflag in `check\_vtop’.} @@ -1651,11 +1779,11 @@ local check_regexpr = function (glyph, line, colno) % \end{macrocode} % For single char words, the previous node is a glue. % \begin{macrocode} - if lchar and lchar < 0x100 and previous and previous.id == GLUE then - match = string.find(luatypo.single[lang], string.char(lchar)) + 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=" .. string.char(lchar) + local msg = "RGX MATCH=" .. utf8.char(lchar) log_flaw(msg, line, colno, footnote) color_node(glyph,COLOR) end @@ -1671,9 +1799,9 @@ local check_regexpr = function (glyph, line, colno) % \end{macrocode} % For two chars words, the previous node is a glue… % \begin{macrocode} - if pchar and pchar < 0x100 and pprev and pprev.id == GLUE then - local pattern = string.char(pchar) .. string.char(lchar) - match = string.find(luatypo.double[lang], pattern) + 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 @@ -1690,10 +1818,9 @@ local check_regexpr = function (glyph, line, colno) if pprev and pprev.id == GLYPH then local pchar, id = is_glyph(pprev) local ppprev = pprev.prev - if pchar and pchar < 0x100 and - ppprev and ppprev.id == GLUE then - local pattern = string.char(pchar) .. string.char(lchar) - match = string.find(luatypo.double[lang], pattern) + 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 @@ -1709,8 +1836,8 @@ return retflag end % \end{macrocode} % -% This auxillary function prints the first part of an hyphenated word -% up to the discretionary, with a supplied colour. +% The next function prints the first part of an hyphenated +% word up to the discretionary, with a supplied colour. % It requires two arguments: a \node{disc} node and a (named) colour. % % \begin{macrocode} @@ -1725,7 +1852,7 @@ local show_pre_disc = function (disc, color) % \end{macrocode} % % \begin{macro}{footnoterule-ahead} -% This auxillary function scans the current \node{vlist} in search +% The next function scans the current \node{vlist} in search % of a |\footnoterule|; it returns |true| if found, false otherwise. % The \node{rule} node above footnotes is normaly surrounded by % two (vertical) \node{kern} nodes, the total height is either @@ -1759,11 +1886,11 @@ local footnoterule_ahead = function (head) if totalht == 0 or totalht == ruleht then flag = true else -% texio.write_nl(' ') -% texio.write_nl('Not a footnoterule:') -% texio.write(' KERN height=' .. ht1) -% texio.write(' RULE height=' .. ht2) -% texio.write(' KERN height=' .. ht3) +% texio.write_nl(" ") +% texio.write_nl("Not a footnoterule:") +% texio.write(" KERN height=" .. ht1) +% texio.write(" RULE height=" .. ht2) +% texio.write(" KERN height=" .. ht3) end end end @@ -1774,7 +1901,7 @@ end % \end{macro} % % \begin{macro}{get-pagebody} -% This auxillary function scans the \node{vlist}s on the current +% The next function scans the \node{vlist}s on the current % page in search of the page body. % It returns the corresponding node or nil in case of failure. % @@ -1789,29 +1916,29 @@ local get_pagebody = function (head) repeat fn = fn.next until fn.id == VLIST and fn.height > 0 -% texio.write_nl(' ') +% texio.write_nl(" ") % local ht = string.format("%.1fpt", fn.height/65536) % local dp = string.format("%.1fpt", fn.depth/65536) -% texio.write_nl('get_pagebody: TOP VLIST') -% texio.write(' ht=' .. ht .. ' dp=' .. dp) +% texio.write_nl("get_pagebody: TOP VLIST") +% texio.write(" ht=" .. ht .. " dp=" .. dp) first = fn.list for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then % local ht = string.format("%.1fpt", n.height/65536) -% texio.write_nl('BODY found: ht=' .. ht) -% texio.write_nl(' ') +% texio.write_nl("BODY found: ht=" .. ht) +% texio.write_nl(" ") body = n break else -% texio.write_nl('Skip short VLIST:') +% texio.write_nl("Skip short VLIST:") % local ht = string.format("%.1fpt", n.height/65536) % local dp = string.format("%.1fpt", n.depth/65536) -% texio.write(' ht=' .. ht .. ' dp=' .. dp) +% texio.write(" ht=" .. ht .. " dp=" .. dp) first = n.list for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then % local ht = string.format("%.1fpt", n.height/65536) -% texio.write_nl(' BODY: ht=' .. ht) +% texio.write_nl(" BODY: ht=" .. ht) body = n break end @@ -1819,7 +1946,7 @@ local get_pagebody = function (head) end end if not body then - texio.write_nl('***lua-typo ERROR: PAGE BODY *NOT* FOUND!***') + texio.write_nl("***lua-typo ERROR: PAGE BODY *NOT* FOUND!***") end return body end @@ -1827,7 +1954,7 @@ end % \end{macro} % % \begin{macro}{check-vtop} -% This function is called repeatedly by |check_page| (see below); +% The next function is called repeatedly by |check_page| (see below); % it scans the boxes found in the page body (f.i. columns) % in search of typographical flaws and logs. % @@ -1863,6 +1990,7 @@ check_vtop = function (head, colno, vpos) 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 @@ -1904,6 +2032,7 @@ check_vtop = function (head, colno, vpos) 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 @@ -1926,9 +2055,9 @@ check_vtop = function (head, colno, vpos) body_bottom = true elseif footnoterule_ahead(n) then body_bottom = true -% texio.write_nl('=> FOOTNOTE RULE ahead') -% texio.write_nl('check_vtop: last line before footnotes') -% texio.write_nl(' ') +% texio.write_nl("=> FOOTNOTE RULE ahead") +% texio.write_nl("check_vtop: last line before footnotes") +% texio.write_nl(" ") end % \end{macrocode} % Is the current line overfull or underfull? @@ -1954,6 +2083,7 @@ check_vtop = function (head, colno, vpos) color_line (head, COLOR) end % \end{macrocode} +% \enlargethispage*{2\baselineskip} % Set flag |ftnsplit| to |true| on every page’s last line. % This flag will be reset to false if the current line ends a % paragraph. @@ -1976,7 +2106,6 @@ check_vtop = function (head, colno, vpos) (first.id == GLUE and first.subtype == LFTSKIP) do first = first.next end - local ListItem = false % \end{macrocode} % Now let’s analyse the beginning of the current line. % \begin{macrocode} @@ -1987,6 +2116,8 @@ check_vtop = function (head, colno, vpos) % they are frozen in footnotes). % \begin{macrocode} hyphcount = 0 + firstwd = "" + lastwd = "" if not footnote then parline = 1 if body_bottom then @@ -2043,8 +2174,8 @@ check_vtop = function (head, colno, vpos) local llwd = linewd - PFskip % local PFskip_pt = string.format("%.1fpt", PFskip/65536) % local llwd_pt = string.format("%.1fpt", llwd/65536) -% texio.write_nl('PFskip= ' .. PFskip_pt) -% texio.write(' llwd= ' .. llwd_pt) +% texio.write_nl("PFskip= " .. PFskip_pt) +% texio.write(" llwd= " .. llwd_pt) % \end{macrocode} % |llwd| is the line’s length. Is it too short? % \begin{macrocode} @@ -2095,7 +2226,7 @@ check_vtop = function (head, colno, vpos) if FirstWordMatch then local flag = not ListItem firstwd, flag = - check_first_word(firstwd, first, line, colno, flag) + check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end @@ -2109,7 +2240,7 @@ check_vtop = function (head, colno, vpos) flag = false end lastwd, flag = - check_last_word(lastwd, pn, line, colno, flag) + check_line_last_word(lastwd, pn, line, colno, flag) if flag then pageflag = true end @@ -2123,46 +2254,46 @@ check_vtop = function (head, colno, vpos) % Colour the whole line now if it is a orphan or a footnote % continuing on the next page. % \begin{macrocode} - 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 + 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 % \end{macrocode} % This hyphen occurs on the page’s last line (body or footnote), % colour (differently) the last word. % \begin{macrocode} - 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 + 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 % \end{macrocode} % Track matching words at the beginning and end of line. % \begin{macrocode} if FirstWordMatch then local flag = not ListItem firstwd, flag = - check_first_word(firstwd, first, line, colno, flag) + check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end end if LastWordMatch then - local flag = false + local flag = true lastwd, flag = - check_last_word(lastwd, ln, line, colno, true) + check_line_last_word(lastwd, ln, line, colno, flag) if flag then pageflag = true end @@ -2206,36 +2337,36 @@ check_vtop = function (head, colno, vpos) % Colour the whole line now if it is a orphan or a footnote % continuing on the next page. % \begin{macrocode} - 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 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 % \end{macrocode} -% Track matching words at the beginning and end of line and shot +% Track matching words at the beginning and end of line and short % words. % \begin{macrocode} if FirstWordMatch then local flag = not ListItem firstwd, flag = - check_first_word(firstwd, first, line, colno, flag) + check_line_first_word(firstwd, first, line, colno, flag) if flag then pageflag = true end end if LastWordMatch and pn then - local flag + local flag = true lastwd, flag = - check_last_word(lastwd, pn, line, colno, true) + check_line_last_word(lastwd, pn, line, colno, flag) if flag then pageflag = true end @@ -2252,6 +2383,13 @@ check_vtop = function (head, colno, vpos) end end % \end{macrocode} +% Check the page’s first word (end of sentence?). +% \begin{macrocode} + if ShortFinalWord and pageline == 1 and parline > 1 and + check_page_first_word(first,colno) then + pageflag = true + end +% \end{macrocode} % End of scanning for the main type of node (text lines). % \begin{macrocode} elseif head.id == HLIST and @@ -2323,8 +2461,8 @@ check_vtop = function (head, colno, vpos) % set the |footnote| flag and reset some counters and flags % for the coming footnote lines. % \begin{macrocode} -% texio.write_nl('check_vtop: footnotes start') -% texio.write_nl(' ') +% texio.write_nl("check_vtop: footnotes start") +% texio.write_nl(" ") footnote = true ftnline = 0 body_bottom = false @@ -2342,15 +2480,15 @@ check_vtop = function (head, colno, vpos) if first_bot then % local vpos_pt = string.format("%.1fpt", vpos/65536) % local vmin_pt = string.format("%.1fpt", vpos_min/65536) -% texio.write_nl('pageline=' .. pageline) -% texio.write_nl('vpos=' .. vpos_pt) -% texio.write(' vpos_min=' .. vmin_pt) +% texio.write_nl("pageline=" .. pageline) +% texio.write_nl("vpos=" .. vpos_pt) +% texio.write(" vpos_min=" .. vmin_pt) % if page_bottom then % local tht = tex.getdimen("textheight") % local tht_pt = string.format("%.1fpt", tht/65536) -% texio.write(' textheight=' .. tht_pt) +% texio.write(" textheight=" .. tht_pt) % end -% texio.write_nl(' ') +% texio.write_nl(" ") if pageline > 1 and pageline < PAGEmin and ShortPages then pageshort = true end @@ -2388,21 +2526,21 @@ check_vtop = function (head, colno, vpos) elseif head.id == HLIST and head.subtype == BOX then local hf = head.list if hf and hf.id == VLIST and hf.subtype == 0 then -% texio.write_nl('check_vtop: BREAK => multicol') -% texio.write_nl(' ') +% texio.write_nl("check_vtop: BREAK => multicol") +% texio.write_nl(" ") break end end head = nextnode end % if nextnode then -% texio.write('Exit check_vtop, next=') +% texio.write("Exit check_vtop, next=") % texio.write(tostring(node.type(nextnode.id))) -% texio.write('-'.. nextnode.subtype) +% texio.write("-".. nextnode.subtype) % else -% texio.write_nl('Exit check_vtop, next=nil') +% texio.write_nl("Exit check_vtop, next=nil") % end -% texio.write_nl('') +% texio.write_nl("") % \end{macrocode} % Update the list of flagged pages avoiding duplicates: % \begin{macrocode} @@ -2451,8 +2589,8 @@ luatypo.check_page = function (head) % of bowing, let’s step down one level. % \begin{macrocode} % local boxwd = string.format("%.1fpt", first.width/65536) -% texio.write_nl('One step down: boxwd=' .. boxwd) -% texio.write_nl(' ') +% texio.write_nl("One step down: boxwd=" .. boxwd) +% texio.write_nl(" ") top = body.list first = top.list end @@ -2461,9 +2599,9 @@ luatypo.check_page = function (head) % \begin{macrocode} while top do first = top.list -% texio.write_nl('Page loop: top=' .. tostring(node.type(top.id))) -% texio.write('-' .. top.subtype) -% texio.write_nl(' ') +% texio.write_nl("Page loop: top=" .. tostring(node.type(top.id))) +% texio.write("-" .. top.subtype) +% texio.write_nl(" ") if top and top.id == VLIST and top.subtype == 0 and top.width > textwd/2 then % \end{macrocode} @@ -2471,11 +2609,11 @@ luatypo.check_page = function (head) % \begin{macrocode} % local boxht = string.format("%.1fpt", top.height/65536) % local boxwd = string.format("%.1fpt", top.width/65536) -% texio.write_nl('**VLIST: ') +% texio.write_nl("**VLIST: ") % texio.write(tostring(node.type(top.id))) -% texio.write('-' .. top.subtype) -% texio.write(' wd=' .. boxwd .. ' ht=' .. boxht) -% texio.write_nl(' ') +% texio.write("-" .. top.subtype) +% texio.write(" wd=" .. boxwd .. " ht=" .. boxht) +% texio.write_nl(" ") local next = check_vtop(first,colno,vpos) if next then top = next @@ -2489,24 +2627,24 @@ luatypo.check_page = function (head) % Two or more columns, each one is boxed in a vlist.\par % Run |check_vtop| on every column. % \begin{macrocode} -% texio.write_nl('**MULTICOL type1:') -% texio.write_nl(' ') +% texio.write_nl("**MULTICOL type1:") +% texio.write_nl(" ") colno = 0 for n in traverse_id(VLIST, first) do colno = colno + 1 col = n.list -% texio.write_nl('Start of col.' .. colno) -% texio.write_nl(' ') +% texio.write_nl("Start of col." .. colno) +% texio.write_nl(" ") check_vtop(col,colno,vpos) -% texio.write_nl('End of col.' .. colno) -% texio.write_nl(' ') +% texio.write_nl("End of col." .. colno) +% texio.write_nl(" ") end colno = nil top = top.next -% texio.write_nl('MULTICOL type1 END: next=') +% texio.write_nl("MULTICOL type1 END: next=") % texio.write(tostring(node.type(top.id))) -% texio.write('-' .. top.subtype) -% texio.write_nl(' ') +% texio.write("-" .. top.subtype) +% texio.write_nl(" ") 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 @@ -2515,19 +2653,19 @@ luatypo.check_page = function (head) % holds a vlist.\par % Run |check_vtop| on every column. % \begin{macrocode} -% texio.write_nl('**MULTICOL type2:') -% texio.write_nl(' ') +% texio.write_nl("**MULTICOL type2:") +% texio.write_nl(" ") 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 -% texio.write_nl('Start of col.' .. colno) -% texio.write_nl(' ') +% texio.write_nl("Start of col." .. colno) +% texio.write_nl(" ") check_vtop(col,colno,vpos) -% texio.write_nl('End of col.' .. colno) -% texio.write_nl(' ') +% texio.write_nl("End of col." .. colno) +% texio.write_nl(" ") end end colno = nil @@ -2543,18 +2681,17 @@ return luatypo.check_page % \end{macrocode} % \end{macro} % +% \changes{v0.50}{2021/05/02}{Callback `pre\_output\_filter’ replaced +% by `pre\_shipout\_filter’, in the former the material is not boxed +% yet and footnotes are not visible.} +% % NOTE: |effective_glue| requires a `parent’ node, as pointed out by % Marcel Krüger on S.E., this implies using |pre_shipout_filter| % instead of |pre_output_filter|. % % Add the |luatypo.check_page| function to the |pre_shipout_filter| % callback (with priority 1 for colour attributes to be effective), -% unless option |None| is selected ; remember that the |None| -% boolean’s value is forwarded to Lua `AtEndOfPackage’… -% -% \changes{v0.50}{2021/05/02}{Callback `pre\_output\_filter’ replaced -% by `pre\_shipout\_filter’, in the former the material is not boxed -% yet and footnotes are not visible.} +% unless option |None| is selected. % % \begin{macrocode} \AtEndOfPackage{% @@ -2567,17 +2704,16 @@ return luatypo.check_page } % \end{macrocode} % -% Load a local config file if present in LaTeX’s search path.\par -% Otherwise, set reasonnable defaults. +% Load a config file if present in LaTeX’s search path or +% set reasonnable defaults. % -% \changes{v0.61}{2023/02/07}{Colours mygrey, myred renamed +% \changes{v0.61}{2023/02/07}{Colours mygrey, myred renamed as % LTgrey, LTred.} % % \begin{macrocode} - \InputIfFileExists{lua-typo.cfg}% - {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file loaded}}% - {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file not found. + {\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} @@ -2595,6 +2731,7 @@ return luatypo.check_page \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 @@ -2604,6 +2741,7 @@ return luatypo.check_page \luatypoPageMin=5\relax \luatypoMinFull=4\relax \luatypoMinPART=4\relax + \luatypoMinLen=4\relax }% % \end{macrocode} % \iffalse @@ -2643,6 +2781,10 @@ return luatypo.check_page \luatypoMinFull=3\relax \luatypoMinPart=4\relax +%% Minimum number of characters for the first word on a page if it ends +%% a sentence. +\luatypoMinLen=4\relax + %% Default colours = red, cyan, LTgrey \definecolor{LTgrey}{gray}{0.6} \definecolor{LTred}{rgb}{1,0.55,0} @@ -2660,11 +2802,12 @@ return luatypo.check_page \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 %% Language specific settings (example for French): %% short words (two letters max) to be avoided at end of lines. -%%\luatypoOneChar{french}{'À Ô Y'} -%%\luatypoTwoChars{french}{'Je Tu Il On Au De'} +%%\luatypoOneChar{french}{"À Ô Y"} +%%\luatypoTwoChars{french}{"Je Tu Il On Au De"} % \end{macrocode} % % \iffalse @@ -2729,12 +2872,12 @@ return luatypo.check_page \AtVeryEndDocument{% \directlua{ - texio.write_nl(' ') - texio.write_nl('***************************') - texio.write_nl('*** PAGE SCANNING ONLY: ***') - texio.write_nl('*** NO CHECK PERFORMED! ***') - texio.write_nl('***************************') - texio.write_nl(' ') + texio.write_nl(" ") + texio.write_nl("***************************") + texio.write_nl("*** PAGE SCANNING ONLY: ***") + texio.write_nl("*** NO CHECK PERFORMED! ***") + texio.write_nl("***************************") + texio.write_nl(" ") }% } @@ -2750,21 +2893,10 @@ luatypo.colortbl = { } luatypo.pagelist = " " local hyphcount = 0 -local parlines = 0 -local pagelines = 0 local pageno = 0 local prevno = 0 local pageflag = false -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 DISC = node.id("disc") local GLYPH = node.id("glyph") local GLUE = node.id("glue") @@ -2807,24 +2939,24 @@ local get_pagebody = function (head) first = fn.list local ht = string.format("%.1fpt", fn.height/65536) local dp = string.format("%.1fpt", fn.depth/65536) - texio.write_nl('SKIP vlist: ht=' .. ht .. ' dp=' .. dp) + texio.write_nl("SKIP vlist: ht=" .. ht .. " dp=" .. dp) for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then local ht = string.format("%.1fpt", n.height/65536) - texio.write_nl(' BODY: ' .. ht) - texio.write_nl(' ') + texio.write_nl(" BODY: " .. ht) + texio.write_nl(" ") body = n break else local ht = string.format("%.1fpt", n.height/65536) local dp = string.format("%.1fpt", n.depth/65536) - texio.write_nl(' SKIP vlist: ht=' .. ht .. ' dp=' .. dp) + texio.write_nl(" SKIP vlist: ht=" .. ht .. " dp=" .. dp) first = n.list for n in traverse_id(VLIST,first) do if n.subtype == 0 and n.height == textht then ht = string.format("%.1fpt", n.height/65536) - texio.write_nl(' BODY: ' .. ht) - texio.write_nl(' ') + texio.write_nl(" BODY: " .. ht) + texio.write_nl(" ") body = n break end @@ -2832,37 +2964,48 @@ local get_pagebody = function (head) end end if not body then - texio.write_nl('***lua-typo ERROR: PAGE BODY *NOT* FOUND!***') + texio.write_nl("***lua-typo ERROR: PAGE BODY *NOT* FOUND!***") end return body end local print_subtype = function (node,parent) local n = node - if n.id == GLYPH and n.subtype == 256 and n.char < 0x100 then - texio.write(string.char(n.char)) - else - if n.subtype then - texio.write(n.subtype) - else - texio.write(' ') - end - end - if n.id == GLUE then + if n.id == GLYPH then + texio.write(utf8.char(n.char)) + elseif n.id == GLUE then real_pt = string.format("%.1fpt", effective_glue(n,parent)/65536) - texio.write(' realwd=' .. real_pt) + texio.write(" realwd=" .. real_pt) elseif n.id == DISC then - texio.write(' pre=' .. tostring(n.pre)) - texio.write(' post=' .. tostring(n.post)) - texio.write(' repl=' .. tostring(n.replace)) + local c = "" + if n.replace then + for nn in traverse_id(GLYPH, n.replace) do + c = c .. utf8.char(nn.char) + end + texio.write(c) + end + if n.pre then + c = "" + for nn in traverse_id(GLYPH, n.pre) do + c = c .. utf8.char(nn.char) + end + texio.write(" pre=" .. c) + end + if n.post then + c = "" + for nn in traverse_id(GLYPH, n.post) do + c = c .. utf8.char(nn.char) + end + texio.write(" post=" .. c) + end elseif n.subtype and n.subtype < 256 and n.height and n.depth then ht_pt = string.format("%.1fpt", n.height/65536) dp_pt = string.format("%.1fpt", n.depth/65536) - texio.write(' ht=' .. ht_pt .. ' dp=' .. dp_pt) + texio.write(" ht=" .. ht_pt .. " dp=" .. dp_pt) if n.width then wd_pt = string.format("%.1fpt", n.width/65536) - texio.write(' wd=' .. wd_pt) - end + texio.write(" wd=" .. wd_pt) + end end end @@ -2874,9 +3017,9 @@ local scanvlist = function (head) local textht = tex.getdimen("textheight") local textwd = tex.getdimen("textwidth") local pageno = tex.getcount("c@page") - texio.write_nl('PAGE ' .. pageno) - texio.write(' textheight='.. string.format("%.1fpt", textht/65536)) - texio.write(' textwidth=' .. string.format("%.1fpt", textwd/65536)) + texio.write_nl("PAGE " .. pageno) + texio.write(" textheight=".. string.format("%.1fpt", textht/65536)) + texio.write(" textwidth=" .. string.format("%.1fpt", textwd/65536)) local linecount = 0 local body, parent, ht_pt, dp_pt, real_pt body = get_pagebody(head) @@ -2886,46 +3029,46 @@ local scanvlist = function (head) end while (head) do texio.write_nl(tostring(node.type(head.id))) - texio.write('-') + texio.write("-") print_subtype(head,body) if head.kern then ht_pt = string.format("%.1fpt", head.kern/65536) - texio.write(' KERN ht/wd=' .. ht_pt) + texio.write(" KERN ht/wd=" .. ht_pt) end local next = head.next local first = head.head for n in traverse(first) do parent = head - texio.write_nl(' ' .. tostring(node.type(n.id))) - texio.write('-') + texio.write_nl(" " .. tostring(node.type(n.id))) + texio.write("-") print_subtype(n,head) if n.id == VLIST or n.id == HLIST then local ff = n.head for nn in traverse(ff) do parent = n - texio.write_nl(' ' .. tostring(node.type(nn.id))) - texio.write('-') + texio.write_nl(" " .. tostring(node.type(nn.id))) + texio.write("-") print_subtype(nn,n) if nn.id == VLIST or nn.id == HLIST then local f3 = nn.head for n3 in traverse(f3) do parent = nn - texio.write_nl(' ' .. tostring(node.type(n3.id))) - texio.write('-') + texio.write_nl(" " .. tostring(node.type(n3.id))) + texio.write("-") print_subtype(n3,nn) if n3.id == VLIST or n3.id == HLIST then local f4 = n3.head for n4 in traverse(f4) do parent = n3 - texio.write_nl(' ' .. tostring(node.type(n4.id))) - texio.write('-') + texio.write_nl(" " .. tostring(node.type(n4.id))) + texio.write("-") print_subtype(n4,n3) if n4.id == VLIST or n4.id == HLIST then local f5 = n4.head for n5 in traverse(f5) do parent = n4 - texio.write_nl(' ' .. tostring(node.type(n4.id))) - texio.write('-') + texio.write_nl(" " .. tostring(node.type(n4.id))) + texio.write("-") print_subtype(n5,n4) end end -- cgit v1.2.3