diff options
author | Karl Berry <karl@freefriends.org> | 2021-06-03 20:50:26 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-06-03 20:50:26 +0000 |
commit | 01945ee778f661b15c72ca982b7f19293042057b (patch) | |
tree | 25d71cf808044d76c4ebddf96e87ee2007e848ad /Master/texmf-dist/source/lualatex | |
parent | 7cff8b44d3f87cd67e7037e61050c11eec8360b7 (diff) |
lua-typo (3jun21)
git-svn-id: svn://tug.org/texlive/trunk@59457 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/lualatex')
-rw-r--r-- | Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx | 1177 |
1 files changed, 835 insertions, 342 deletions
diff --git a/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx b/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx index f783033fdd0..ebcd727a67b 100644 --- a/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx +++ b/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx @@ -1,4 +1,4 @@ -% \CheckSum{336} +% \CheckSum{373} % % \iffalse meta-comment % @@ -34,8 +34,9 @@ \postamble \endpostamble \let\MetaPrefix\DoubleperCent +\askforoverwritefalse \generate{% - \file{lua-typo.sty}{\from{lua-typo.dtx}{sty}}% {sty,msg,dbg} + \file{lua-typo.sty}{\from{lua-typo.dtx}{sty}}% {sty,dbg} \nopreamble \file{lua-typo.cfg}{\from{lua-typo.dtx}{cfg}}% \file{lua-typo-fr.ltx}{\from{lua-typo.dtx}{driver,docfr}}% @@ -49,6 +50,9 @@ \csname fi\endcsname %</gobble> %<*driver> +\RequirePackage{pdfmanagement-testphase} +%<-docfr>\DeclareDocumentMetadata{pdfstandard=A-2b, lang=en-GB} +%<-doc>\DeclareDocumentMetadata{pdfstandard=A-2b, lang=fr-FR} \documentclass[a4paper]{ltxdoc} \usepackage[dvipsnames]{xcolor} \usepackage{fontspec} @@ -79,14 +83,15 @@ %<-doc>\let\FrenchDoc\begingroup\let\endFrenchDoc\endgroup % %<-docfr>\let\FrenchDoc\comment\let\endFrenchDoc\endcomment -%<-docfr|doc>\RecordChanges -%<-docfr|doc>\AtEndDocument{% -%<-docfr|doc> \clearpage -%<-docfr|doc> \section{Change History}% -%<-docfr|doc> \GlossaryPrologue{}% -%<-docfr|doc> Changes are listed in reverse order (latest first) from version~0.1 -%<-docfr|doc> \PrintChanges -%<-docfr|doc>} +%<-docfr>\RecordChanges +%<-docfr>\AtEndDocument{% +%<-docfr> \clearpage +%<-docfr> \section{Change History}% +%<-docfr> \GlossaryPrologue{}% +%<-docfr> Changes are listed in reverse order (latest first) +%<-docfr> from version~0.30. +%<-docfr> \PrintChanges +%<-docfr>} % \newcommand*\file[1]{\texttt{#1}} \newcommand*\pkg[1]{\texttt{#1}} @@ -124,8 +129,8 @@ % L’extension \pkg{lua-typo} décrite ci-dessous% % \footnote{Version \fileversion, mise à jour le \filedate.} % permet de mettre en lumière par un changement de couleur, les -% lignes typographiquement imparfaites d'un fichier PDF produit par -% LuaLaTex. Une liste des pages concernées est affichée à la fin du +% lignes typographiquement imparfaites d’un fichier PDF produit par +% LuaLaTeX. Une liste des pages concernées est affichée à la fin du % fichier \file{.log}, permettant un accès rapide aux pages % potentiellement défectueuses. % @@ -137,7 +142,7 @@ % lignes consécutives, des dernières lignes d’alinéa trop courtes ou % presque pleines, des pages quasi vides. % La répétition d’un même mot ou partie de mot au début ou à la fin -% de deux lignes consécutives est détectée. +% de deux lignes consécutives est aussi détectée. % La présence en fin de ligne de certains mots très courts (une ou % deux lettres, liste dépendant de la langue) peut également être % recherchée. @@ -145,9 +150,9 @@ % \pkg{lua-typo}, ne fonctionne qu’avec LuaLaTeX et % \emph{ne corrige aucun des défauts relevés} mais se contente de % \emph{signaler} au relecteur les points qui peuvent nécessiter une -% correction. Seul un œil humain entrainé peut décider si une ligne +% correction. Seul un humain entrainé peut décider si une ligne % légèrement lavée est acceptable ou non, ou si la suppression -% d’une césure malvenue ne va pas provoquer des désordres plus +% d’une coupure malvenue ne va pas provoquer des désordres plus % graves encore. % % Je conseille de n’appliquer \pkg{lua-typo} que sur des textes @@ -158,6 +163,13 @@ % \pkg{lua-typo}, il suffit d’ajouter dans le préambule la ligne\\ % |\usepackage[All]{lua-typo}| % +% La version courante (0.50) nécessite un noyau LaTeX très 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 » +% est prévue à leur intention, elle se charge par la commande +% |\usepackage[All]{lua-typo}[=v0.4]|. +% % Les fichiers \file{demo.tex} et \file{demo.pdf} fournissent un % exemple du traitement opéré par \pkg{lua-typo}. % @@ -182,7 +194,7 @@ % Le tableau suivant donne le nom des options et le type des % vérifications proposées :\\[12pt] % \begin{tabular}{>{\ttfamily}ll} -% \multicolumn{1}{l}{Nom} & Imperfection à signaler\\ \hline +% \multicolumn{1}{l}{Nom} & Imperfection à signaler\\ \hline % All & Active toutes les options ci-dessous\\ % ShortLines & Dernière ligne d’alinéa trop courte ?\\ % BackParindent & Dernière ligne d’alinéa \emph{presque} pleine ?\\ @@ -190,15 +202,16 @@ % OverfullLines & Ligne trop pleine ?\\ % UnderfullLines & Ligne lavée ? \\ % Widows & Veuve (haut de page) ?\\ -% Orphans & Orpheline (bas de page)\\ +% Orphans & Orpheline (bas de page) ?\\ % EOPHyphens & Mot coupé en bas de page ?\\ -% RepeatedHyphens & Césures sur trop de lignes consécutives ?\\ -% ParLastHyphen & Césure à l’avant-dernière ligne d’un alinéa ?\\ +% RepeatedHyphens & Coupures sur trop de lignes consécutives ?\\ +% ParLastHyphen & Coupure à l’avant-dernière ligne d’un alinéa ?\\ % EOLShortWords & Mots courts (1 or 2 lettres) en fin de ligne ?\\ % FirstWordMatch & Même (partie de) mot en début de lignes % consécutives ?\\ % LastWordMatch & Même (partie de) mot en fin de lignes % consécutives ?\\ +% FootnoteSplit & Fin de note de bas de page sur page suivante?\\ % \hline % \end{tabular}\\[12pt] % Par exemple, pour limiter les vérifications aux lignes trop pleines @@ -238,28 +251,31 @@ % n’est jamais signalé comme répétition, même s’il est identique % au mot de la ligne du dessus. % De même, s’il est identique au dernier mot de la ligne -% suivante, la répéttion ne sera pas signalée non plus. +% suivante, la répéttion ne sera pas signalée non plus. % \item[ShortPages :] lorsque le nombre de lignes d’une page est jugé % insuffisant (voir ci-dessous), seule la dernière ligne de celle-ci % est mise en couleur. % \enlargethispage*{\baselineskip} % \item[RepeatedHyphens :] de même, lorsque le nombre de lignes -% consécutives affectées par des césures dépasse le seuil fixé +% 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. % \end{description} % -% Enfin, le contenu des notes de bas de pages n’est pas vérifié -% par \pkg{lua-typo} car celles-ci passent pour l’instant en dessous -% de nos radars… il faut le savoir. +% À partir de la version~0.50, le contenu des notes de bas de pages +% est également vérifié, les notes trop longues pour se terminer sur +% la page de leur appel sont signalées (option \opt{FootnoteSplit}). +% Un fichier de suffixe \file{.typo} est créé, il contient la liste +% complète des défauts repérés avec leur localisation dans le fichier +% de sortie. % % \section{Paramétrage personnalisé} % % Pour certaines vérifications faites par \pkg{lua-typo} un % paramétrage est nécessaire : à partir de quelle limite une dernière % ligne d’alinéa est-elle considérée comme trop courte ? -% Combien de césures consécutives en bout de ligne sont-elles -% acceptables ? Ces réglages dépendent évidemment du contexte, un -% correcteur de romans aura des exigences plus strictes qu’un auteur +% Combien de coupures consécutives en bout de ligne sont-elles +% acceptables ? Ces réglages dépendent évidemment du contexte, un +% correcteur de romans aura des exigences plus strictes qu’un auteur % de documentation technique par exemple… % % \pkg{lua-typo} permet de modifier le réglage des curseurs soit dans @@ -278,23 +294,27 @@ % % Voici la liste complète des paramètres personnalisables avec leur % valeur par défaut, leurs noms sont systématiquement préfixés par -% |luatypo| afin d’éviter de possible conflits avec d’autres +% |luatypo| afin d’éviter de possibles conflits avec d’autres % extensions. % \begin{description} -% \item[BackParindent :] la dernière ligne d’un alinéa fixe +% \item[BackParindent :] la dernière ligne d’un alinéa fixe % devrait, soit être pleine ---~en fait se terminer à moins de % |\luatypoBackFuzz=2pt| de la marge droite~---, soit % s’en éloigner d’au moins de |\luatypoBackPI=1em|. % -% \item[ShortLines :] |\luatypoLLminWD=2\parindent| fixe la -% longueur minimale acceptable pour la dernière ligne d’un alinéa. +% \item[ShortLines :] |\luatypoLLminWD=2\parindent|% +% \footnote{Ou \texttt{20pt} si \cs{parindent=0pt}.} +% fixe la longueur minimale acceptable pour la dernière ligne +% d’un alinéa. % % \item[ShortPages :] |\luatypoPageMin=5| fixe le nombre minimal de -% lignes d’une page pour que celle-ci ne pas être -% signalée comme trop courte. +% lignes d’une page pour que celle-ci ne soit pas déclarée trop +% courte. En fait, la position de la dernière ligne est prise en +% compte afin que les pages de titre ou celles contenant une +% image ne soient pas signalées comme fautives. % % \item[RepeatedHyphens :] |\luatypoHyphMax=2| fixe le nombre maximal -% acceptable de lignes consécutives terminées par une césure. +% acceptable de lignes consécutives terminées par un mot coupé. % % \item[UnderfullLines :] |\luatypoStretchMax=200| fixe le % pourcentage maximal acceptable pour l’étirement des @@ -307,7 +327,7 @@ % réglages par défaut (|\tolerance=200|, |\hbadness=1000|), % considère comme \emph{Underfull hbox}. % -% \item[First/LastWordMatch:] |\luatypoMinFull=3|\hfil et\hfil +% \item[First/LastWordMatch:] |\luatypoMinFull=3|\hfil et\hfil % |\luatypoMinPart=4|\hfil nombres\linebreak[4] minimaux de lettres % identiques (resp. pour un mot complet ou pour une partie de % mot) au début ou à la fin de deux lignes consécutives @@ -321,11 +341,11 @@ % 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) :\\ -% |\luatypoOneChar{|\meta{language}|}{'|\meta{list of words}|'}|\\ -% |\luatypoTwoChars{|\meta{language}|}{'|\meta{list of words}|'}| +% |\luatypoOneChar{|\meta{langue}|}{'|\meta{liste de mots}|'}|\\ +% |\luatypoTwoChars{|\meta{langue}|}{'|\meta{liste de mots}|'}| % % Lorsque les listes correspondant à la langue du document sont -% vides, aucune vérification n’est effectuée. Pour l’instant, +% vides, aucune vérification n’est effectuée. Pour l’instant, % il y a deux lignes (non actives) prévues pour le français :\\ % |\luatypoOneChar{french}{'À à Ô'}|\\ % |\luatypoTwoChars{french}{'Je Tu Il On'}| @@ -352,10 +372,10 @@ % \begin{verbatim} % \definecolor{mygrey}{gray}{0.6} % \definecolor{myred}{rgb}{1,0.55,0} -% \luatypoSetColor0{red} % Césure à l’avant-dernière ligne d’un alinéa -% \luatypoSetColor1{red} % Césure en bas de page -% \luatypoSetColor2{red} % Césures sur trop de lignes consécutives -% \luatypoSetColor3{red} % Mot d’une ou deux lettres en fin de ligne +% \luatypoSetColor0{red} % Coupure à l’avant-dernière ligne +% \luatypoSetColor1{red} % Coupure en bas de page +% \luatypoSetColor2{red} % Coupures consécutives +% \luatypoSetColor3{red} % Mot court en fin de ligne % \luatypoSetColor4{cyan} % Veuve % \luatypoSetColor5{cyan} % Orpheline % \luatypoSetColor6{cyan} % Dernière ligne d’alinéa trop courte @@ -365,6 +385,7 @@ % \luatypoSetColor{10}{myred} % Répétitions en début de ligne % \luatypoSetColor{11}{myred} % Répétitions en fin de ligne % \luatypoSetColor{12}{mygrey}% Dernière ligne d’alinéa presque pleine +% \luatypoSetColor{13}{cyan} % Note de bas de page éclatée % \end{verbatim} % \pkg{lua-typo} charge l’extension graphique \pkg{color}. % Seules les couleurs portant un nom (\emph{named colors}) peuvent @@ -412,6 +433,12 @@ % made: if some flaws remain, getting them printed in colour in % the final document would be a shame! % +% This version (0.50) requires the latest LaTeX kernel (dated +% 2021/06/01). Users running an older kernel will get a warning +% and an error message ``\texttt{Unable to register callback}’’; +% for them, a ``rollback’’ version of \pkg{lua-typo} is provided, +% it can be loaded this way: |\usepackage[All]{lua-typo}[=v0.4]|. +% % See files \file{demo.tex} and \file{demo.pdf} for a short example % (in French). % @@ -445,14 +472,15 @@ % OverfullLines & overfull lines?\\ % UnderfullLines & underfull lines?\\ % Widows & widows (top of page)?\\ -% Orphans & orphans (bottom of page)\\ +% Orphans & orphans (bottom of page)?\\ % EOPHyphens & hyphenated word split across two pages?\\ % RepeatedHyphens & too many consecutive hyphens?\\ % ParLastHyphen & paragraph’s last full line hyphenated?\\ % EOLShortWords & short words (1 or 2 chars) at end of line?\\ % FirstWordMatch & same (part of) word starting two consecutive lines?\\ % LastWordMatch & same (part of) word ending two consecutive lines?\\ -% \hline +% FootnoteSplit & footnotes spread over two pages or more?\\ +% \hline % \end{tabular}\\[12pt] % For example, if you want \pkg{lua-typo} to only warn about overfull % and underfull lines, you can load \pkg{lua-typo} like this:\\ @@ -464,13 +492,13 @@ % taken into account as they are read \emph{i.e.} from left to right. % % The list of all available options is printed to the \file{.log} -% file when option \opt{ShowOptions} is passed to \pkg{lua-typo}, an -% easy way to get their names without having to look into the -% documentation. +% file when option \opt{ShowOptions} is passed to \pkg{lua-typo}, +% this option provides an easy way to get their names without having +% to look into the documentation. % % 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 +% to any LuaTeX callback. It not quite equivalent to commenting out % the |\usepackage{lua-typo}| line though, as user defined commands % related to \pkg{lua-typo} are still defined and will not print % any error message. @@ -480,22 +508,24 @@ % \item[FirstWordMatch:] the first word of consecutive list items % is not highlighted, as these repetitions result of the author’s % choice. -% \item[LastWordMatch:] a paragraphs’ last word ending ``too far’’ +% \item[LastWordMatch:] a paragraphs’ last word ending ``too far’’ % from the right margin (\emph{i.e.} more than % |\luatypoBackPI| --default=1em-- away) is never highlighted % even if it matches the one on the previous line. % Similarly, if it matches the one on the next line, the latter % will not be highlighted either. % \item[ShortPages:] if a page is considered too short, its last -% line only is highlighted, not the whole page. +% line only is highlighted, not the whole page. % \item[RepeatedHyphens:] ditto, when the number of consecutives % hyphenated lines is too high, only the hyphenated words in % excess (the last ones) are hightlighted. % \end{description} -% -% Finally, please note that the footnotes’ contents are not checked -% by \pkg{lua-typo}, I have currently no clue of how to do that, -% hints are welcome! +% \enlargethispage*{\baselineskip} +% Starting with version~0.50, the footnotes’ contents are checked as +% well by \pkg{lua-typo} and footnotes too long to end on the current +% page are mentionned as a flaw (option \opt{FootnoteSplit}). +% The list of all flaws found is written to a specific log-file whose +% name is suffixed by \file{.typo}. % % \section{Customisation} % @@ -507,7 +537,7 @@ % % A default configuration file \file{lua-typo.cfg} is provided % with all parameters set to their defaults; it is located under -% the \textsc{texmfdist} directory. It is up to the users to copy +% the \textsc{texmfdist} directory. It is up to the users to copy % this file into their working directory (or \textsc{texmfhome} or % \textsc{texmflocal}) and tune the defaults according to their own % taste. @@ -521,17 +551,21 @@ % Here are the parameters names (all prefixed by |luatypo| in order % to avoid conflicts with other packages) and their default values: % \begin{description} -% \item[BackParindent :] paragraphs’ last line should either +% \item[BackParindent :] paragraphs’ last line should either % touch the right margin (actually end at less than -% |\luatypoBackFuzz|, default |2pt|, from it) or leave at least +% |\luatypoBackFuzz|, default |2pt|, from it) or leave at least % |\luatypoBackPI|, default |1em|, between its end and the right % margin. % -% \item[ShortLines:] |\luatypoLLminWD=2\parindent| sets the -% minimum acceptable length for paragraphs’ last lines. +% \item[ShortLines:] |\luatypoLLminWD=2\parindent|% +% \footnote{Or \texttt{20pt} if \cs{parindent=0pt}.} +% sets the minimum acceptable length for paragraphs’ last lines. % -% \item[ShortPages:] |\luatypoPageMin=5| sets the minimum acceptable -% number of lines on a page (chapters’ last page for instance). +% \item[ShortPages:] |\luatypoPageMin=5| sets the minimum +% acceptable number of lines on a page (chapters’ last page +% for instance). Actually, the last line’s vertical position on +% the page is taken into account so that f.i.\ title pages or +% pages ending on a picture are not pointed out. % % \item[RepeatedHyphens:] |\luatypoHyphMax=2| sets the maximum % acceptable number of consecutive hyphenated lines. @@ -546,14 +580,14 @@ % ``Underfull hbox’’ message (when |\tolerance=200| and % |\hbadness=1000|). % -% \item[First/LastWordMatch:] |\luatypoMinFull=3| and +% \item[First/LastWordMatch:] |\luatypoMinFull=3| and % |\luatypoMinPart=4| set the minimum number of characters -% required for a match to be pointed out. With this setting (3 +% required for a match to be pointed out. With this setting (3 % and 4), two occurrences of the word `out’ at the beginning or % end of two consecutive lines will be highlighted (three chars, % `in’ wouldn’t match), whereas a line ending with ``full’’ or % ``overfull’’ followed by one ending with ``underfull’’ will -% match (four chars): the second occurence of ``full’’ or +% match (four chars): the second occurence of ``full’’ or % ``erfull’’ will be highlighted. % % \item[EOLShortWords:] this check deals with lines ending with @@ -598,12 +632,13 @@ % \luatypoSetColor{10}{myred} % First word matches % \luatypoSetColor{11}{myred} % Last word matches % \luatypoSetColor{12}{mygrey}% paragraph’s last line nearly full +% \luatypoSetColor{13}{cyan} % footnotes spread over two pages % \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 `mygrey’) or load the +% 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 `mygrey’) or load the % \pkg{xcolor} package which provides a bunch of named colours. % % \clearpage @@ -611,18 +646,41 @@ % % \iffalse %<*sty> -\NeedsTeXFormat{LaTeX2e}[2020/01/01] +%% IMPORTANT NOTICE: +%% For the copyright see the source file `lua-typo.dtx’. +%% \ProvidesPackage{lua-typo} %</sty> %<*dtx> \ProvidesFile{lua-typo.dtx} %</dtx> %<*dtx|sty> - [2021/03/14 v.0.32 Daniel Flipo] + [2021/05/13 v.0.50 Daniel Flipo] %</dtx|sty> %<*sty> % \fi % +% \changes{v0.50}{2021/05/05}{Rollback mechanism used for recovering +% older versions.} +% +% Starting with version~0.50, this package uses the rollback +% mechanism to provide easier backward compatibility. +% Rollback version~0.40 is provided for users who would have +% a LaTeX kernel older than 2021/06/01. +% +% \begin{macrocode} +\ifdefined\DeclareRelease + \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty} + \DeclareCurrentRelease{}{2021-05-13} +\else + \PackageWarning{lua-typo}{Your LaTeX kernel is too old to provide + access\MessageBreak to former versions of the lettrine package.% + \MessageBreak Anyway, lua-typo requires a LaTeX kernel dated% + \MessageBreak 2020-01-01 or newer; reported} +\fi +\NeedsTeXFormat{LaTeX2e}[2021/06/01] +% \end{macrocode} +% % This package only runs with LuaLaTeX and requires packages % \pkg{luatexbase}, \pkg{luacode}, \pkg{luacolor} and % \pkg{atveryend}. @@ -684,6 +742,7 @@ luatypo = { } \DeclareBoolOption[false]{EOLShortWords} \DeclareBoolOption[false]{FirstWordMatch} \DeclareBoolOption[false]{LastWordMatch} +\DeclareBoolOption[false]{FootnoteSplit} % \end{macrocode} % Option \opt{All} resets all booleans relative to specific % typographic checks to \opt{true}. @@ -695,7 +754,7 @@ luatypo = { } \LT@EOPHyphenstrue \LT@RepeatedHyphenstrue \LT@ParLastHyphentrue \LT@EOLShortWordstrue \LT@FirstWordMatchtrue \LT@LastWordMatchtrue - \LT@BackParindenttrue + \LT@BackParindenttrue \LT@FootnoteSplittrue } \ProcessKeyvalOptions{luatypo} % \end{macrocode} @@ -790,6 +849,12 @@ luatypo = { } \else \directlua{ luatypo.LastWordMatch = false }% \fi + \ifLT@FootnoteSplit + \advance\luatypo@options by 1 + \directlua{ luatypo.FootnoteSplit = true }% + \else + \directlua{ luatypo.FootnoteSplit = false }% + \fi } % \end{macrocode} % @@ -816,6 +881,7 @@ luatypo = { } EOLShortWords [false]\MessageBreak FirstWordMatch [false]\MessageBreak LastWordMatch [false]\MessageBreak + FootnoteSplit [false]\MessageBreak \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] @@ -842,7 +908,8 @@ luatypo = { } % \end{macrocode} % % Print the summary of offending pages ---if any--- at the -% (very) end of document unless option |None| has been selected. +% (very) end of document and write the report file on disc, +% unless option |None| has been selected. % % \begin{macrocode} \AtVeryEndDocument{% @@ -869,6 +936,10 @@ luatypo = { } 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} % \end{macrocode} @@ -927,7 +998,7 @@ luatypo = { } % \vspace*{\baselineskip} % \begin{macro}{\luatypoSetColor} % This is a user-level command to customise the colours highlighting -% the nine types of possible typographic flaws. +% the fourteen types of possible typographic flaws. % The first argument is a number (flaw type), the second the named % colour associated to it. % The colour support is based on the \pkg{luacolor} package (color @@ -950,13 +1021,8 @@ luatypo.single = { } luatypo.double = { } luatypo.colortbl = { } luatypo.pagelist = "" - -local hyphcount = 0 -local parlines = 0 -local pagelines = 0 -local pageno = 0 -local prevno = 0 -local pageflag = false +luatypo.buffer = "List of typographic flaws found for " + .. tex.jobname .. ".tex:\string\n\string\n" local char_to_discard = { } char_to_discard[string.byte(",")] = true @@ -980,11 +1046,14 @@ 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") -% \end{macrocode} +local WHATSIT = node.id("whatsit") +% \end{macrocode} % Glue subtypes: % \begin{macrocode} local USRSKIP = 0 @@ -993,11 +1062,14 @@ local LFTSKIP = 8 local RGTSKIP = 9 local TOPSKIP = 10 local PARFILL = 15 -% \end{macrocode} +% \end{macrocode} % Hlist subtypes: % \begin{macrocode} local LINE = 1 local BOX = 2 +local INDENT = 3 +local ALIGN = 4 +local EQN = 6 % \end{macrocode} % Penalty subtypes: % \begin{macrocode} @@ -1007,7 +1079,13 @@ local HYPH = 0x2D % Glyph subtypes: % \begin{macrocode} local LIGA = 0x102 +% \end{macrocode} +% |parline| (current paragraph) must not be reset on every new page! +% \begin{macrocode} +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 @@ -1019,6 +1097,9 @@ local is_glyph = node.is_glyph % \end{macrocode} % +% \changes{v0.32}{2021/03/14}{Better protection against unexpected +% nil nodes.} +% % This auxillary function colours glyphs and discretionaries. % It requires two arguments: a node and a (named) colour. % @@ -1035,55 +1116,119 @@ local color_node = function (node, color) end if post then set_attribute(post,attr,color) -%<dbg> texio.write_nl('POST=' .. tostring(post.char)) +%<dbg> if pre then +%<dbg> texio.write(' POST=' .. tostring(post.char)) +%<dbg> else +%<dbg> texio.write_nl('POST=' .. tostring(post.char)) +%<dbg> end end if repl then set_attribute(repl,attr,color) -%<dbg> texio.write_nl('REPL=' .. tostring(repl.char)) +%<dbg> if pre or post then +%<dbg> texio.write(' REPL=' .. tostring(repl.char)) +%<dbg> else +%<dbg> texio.write_nl('REPL=' .. tostring(repl.char)) +%<dbg> end end +%<dbg> if pre or post or repl then %<dbg> texio.write_nl(' ') +%<dbg> end elseif node then set_attribute(node,attr,color) end end % \end{macrocode} % -% This auxillary function colours the content of an |\hbox|. -% It requires two arguments: a node (the box) and a (named) colour. +% This auxillary function 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. +% +% \changes{v0.50}{2021/05/05}{Go down deeper into hlists and vlists to +% colour nodes.} % % \begin{macrocode} -local color_hbox = function (head, color) +local color_line = function (head, color) local first = head.head for n in traverse(first) do - color_node(n, color) + 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 % \end{macrocode} % -% This auxillary function colours a whole line. It requires two -% arguments: a line’s node and a (named) colour. +% 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 \texttt{nil}) and a flag. +% +% \changes{v0.50}{2021/05/13}{Summary of flaws written to file +% `\cs{jobname.typo}’.} % % \begin{macrocode} -local color_line = function (head, color) - local first = head.head - for n in traverse(first) do - if n and n.id == HLIST and n.subtype == BOX then - color_hbox(n, color) - else - color_node(n, color) - 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 % \end{macrocode} % -% The next three functions deal with ``homeoarchy’’, \emph{i.e.} +% The next three functions deal with ``homeoarchy'', \emph{i.e.} % lines beginning or ending with the same (part of) word. % 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.} +% % 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 @@ -1096,8 +1241,9 @@ local signature = function (node, string, swap) 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; +% 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|. @@ -1126,7 +1272,7 @@ local signature = function (node, string, swap) 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 +% stored. In case of \emph{ffl, ffi}, the post part is also % a ligature… % \begin{macrocode} local pre = n.pre @@ -1163,37 +1309,65 @@ local signature = function (node, string, swap) end % \end{macrocode} % -% This auxillary function looks for lines ending with the same letters. -% It requires four arguments: a string (previous line’s signature), -% a node (the last one on the current line), a (named) colour and a -% boolean to cancel the coloration in some cases (end of paragraphs). +% This auxillary function looks for consecutive lines ending with the +% same letters. +% It requires four arguments: a string (previous line’s signature), +% a node (the last one on the current line), a line number and +% a boolean to cancel checking in some 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\_last\_word’ and +% `check\_last\_word’ rewritten.} +% +% \changes{v0.50}{2021/05/13}{Homeoarchy detection added for lines +% starting or ending on \cs{mbox}.} +% % \begin{macrocode} -local check_last_word = function (old, node, color, flag) +local check_last_word = function (old, node, line, flag) + local COLOR = luatypo.colortbl[11] local match = false local new = "" local maxlen = 0 if flag and node then local swap = true + local box, go % \end{macrocode} % Step back to the last glyph or discretionary. % \begin{macrocode} local lastn = node - while lastn and lastn.id ~= GLYPH and lastn.id ~= DISC do + while lastn and lastn.id ~= GLYPH and lastn.id ~= DISC and + lastn.id ~= HLIST do lastn = lastn.prev end % \end{macrocode} % A signature is built from the last two words on the current line. % \begin{macrocode} 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) @@ -1239,11 +1413,11 @@ local check_last_word = function (old, node, color, flag) newsub = nsub end end + pageflag = true newsub = string.gsub(newsub, '^_', '') -%<dbg> texio.write_nl('EOLfullmatch=' .. newsub) -%<msg> texio.write_nl('***EOLMATCH=' .. newsub .. -%<msg> " on page " .. pageno) -%<msg> texio.write_nl(' ') +%<dbg> 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} @@ -1252,39 +1426,63 @@ local check_last_word = function (old, node, color, flag) local n = lastn repeat if n and n.id ~= GLUE then - color_node(n, color) + color_node(n, COLOR) l, newsub = signature(n, newsub, swap) - elseif n then + elseif n and n.id == GLUE then newsub = newsub .. "_" + elseif not n and box then + n = box + else + break end n = n.prev - until not n or newsub == oldsub or l >= k + until newsub == oldsub or l >= k end end - return new, match + return new end % \end{macrocode} % -% Same thing for beginning of lines: check the first two words and -% compare their signature with the previous line’s. +% Same thing for beginning of lines: check the first two words +% and compare their signature with the previous line’s. % % \begin{macrocode} -local check_first_word = function (old, node, color, flag) +local check_first_word = function (old, node, line, flag) + local COLOR = luatypo.colortbl[10] local match = false local swap = false local new = "" local maxlen = 0 - local start = node - local n = start - while n and n.id ~= GLYPH and n.id ~= DISC do + 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) @@ -1337,13 +1535,13 @@ local check_first_word = function (old, node, color, flag) newsub = nsub end end + pageflag = true newsub = string.gsub(newsub, '_$', '') --$ %<dbg> texio.write_nl('BOLfullmatch=' .. newsub) -%<msg> texio.write_nl('***BOLMATCH=' .. newsub .. -%<msg> " on page " .. pageno) -%<msg> texio.write_nl(' ') + local msg = "B.O.L. MATCH=" .. newsub + log_flaw(msg, line, colno, footnote) % \end{macrocode} -% Lest's colour the matching string. +% Lest’s colour the matching string. % \begin{macrocode} oldsub = newsub local newsub = "" @@ -1351,27 +1549,32 @@ local check_first_word = function (old, node, color, flag) local n = start repeat if n and n.id ~= GLUE then - color_node(n, color) + color_node(n, COLOR) l, newsub = signature(n, newsub, swap) - elseif n then + elseif n and n.id == GLUE then newsub = newsub .. "_" + elseif not n and box then + n = box + else + break end n = n.next - until not n or newsub == oldsub or l >= k + until newsub == oldsub or l >= k end end - return new, match + return new end % \end{macrocode} % % 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 argument must be a node of type GLYPH, usually -% the last line’s node.\\ +% matches. The first argument must be a node of type |GLYPH|, +% usually the last line’s node, the second one is the line number.\\ % TODO: where does ``out of range’’ starts? U+0100? U+0180? % % \begin{macrocode} -local check_regexpr = function (glyph) +local check_regexpr = function (glyph, line) + local pageno = tex.getcount("c@page") local COLOR = luatypo.colortbl[3] local lang = glyph.lang local match = false @@ -1387,10 +1590,10 @@ local check_regexpr = function (glyph) if lchar and lchar < 0x100 and previous and previous.id == GLUE then match = string.find(luatypo.single[lang], string.char(lchar)) if match then + pageflag = true + local msg = "RGX MATCH=" .. string.char(lchar) + log_flaw(msg, line, colno, footnote) color_node(glyph,COLOR) -%<msg> texio.write_nl('***RGXMATCH=' .. string.char(lchar) .. -%<msg> " on page " .. pageno) -%<msg> texio.write_nl(' ') end end end @@ -1408,11 +1611,11 @@ local check_regexpr = function (glyph) local pattern = string.char(pchar) .. string.char(lchar) match = string.find(luatypo.double[lang], pattern) if match then + pageflag = true + local msg = "RGX MATCH=" .. pattern + log_flaw(msg, line, colno, footnote) color_node(previous,COLOR) color_node(glyph,COLOR) -%<msg> texio.write_nl('***RGXMATCH=' .. pattern .. -%<msg> " on page " .. pageno) -%<msg> texio.write_nl(' ') end end % \end{macrocode} @@ -1423,27 +1626,27 @@ local check_regexpr = function (glyph) 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 + 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 match then + pageflag = true + local msg = "RGX MATCH=" .. pattern + log_flaw(msg, line, colno, footnote) color_node(pprev,COLOR) color_node(glyph,COLOR) -%<msg> texio.write_nl('***RGXMATCH=' .. pattern .. -%<msg> " on page " .. pageno) -%<msg> texio.write_nl(' ') end end end end end - return match end % \end{macrocode} % % This auxillary function prints the first part of an hyphenated word % up to the discretionary, with a supplied colour. -% It requires two arguments: a DISC node and a (named) colour. +% It requires two arguments: a |DISC| node and a (named) colour. % % \begin{macrocode} local show_pre_disc = function (disc, color) @@ -1453,149 +1656,297 @@ local show_pre_disc = function (disc, color) n = n.prev end return n + end +% \end{macrocode} +% +% This auxillary function scans the `vlists’ in search of the page body. +% It returns the corresponding node or nil in case of failure. +% +% \changes{v0.50}{2021/05/02}{New function `get\_pagebody’ required for +% callback `pre\_shipout\_filter’.} +% +% \begin{macrocode} +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 +%<dbg> texio.write_nl(' ht=' .. fn.height/65536 .. 'pt') +%<dbg> texio.write(' dp=' .. fn.depth/65536 .. 'pt') + first = fn.list + for n in traverse_id(VLIST,first) do + if n.subtype == 0 and n.height == textht then +%<dbg> texio.write_nl(' BODY: ' .. n.height/65536 .. 'pt') + body = n + break + else +%<dbg> texio.write_nl(' ht=' .. n.height/65536 .. 'pt') +%<dbg> texio.write_nl(' dp=' .. n.depth/65536 .. 'pt') + first = n.list + for n in traverse_id(VLIST,first) do + if n.subtype == 0 and n.height == textht then +%<dbg> texio.write_nl(' BODY: ' .. n.height/65536 .. 'pt') + 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 % \end{macrocode} % -% This is the main function which will be added to the -% |"pre_output_filter"| callback unless option |None| is selected. +% This auxillary function scans the current `vlist’ in search +% of a |\footnoterule| (kern, rule, kern, totalheight=0). +% It returns |true| if found, false othewise. +% +% \changes{v0.50}{2021/05/02}{New function `footnoterule\_ahead’.} % % \begin{macrocode} -luatypo.check_page = function (head) +local footnoterule_ahead = function (head, debug) + local n = head + local flag = false + if n and n.id == KERN and n.subtype == 1 then + local htr = n.kern + local ht1, ht2, ht3 +%<dbg> if debug then +%<dbg> ht1 = string.format("%.2fpt", n.kern/65536) +%<dbg> end + n = n.next + if n and n.id == RULE and n.subtype == 0 then + htr = htr + n.height +%<dbg> if debug then +%<dbg> ht2 = string.format("%.2fpt", n.height/65536) +%<dbg> end + n = n.next + if n and n.id == KERN and n.subtype == 1 then + htr = htr + n.kern +%<dbg> if debug then +%<dbg> ht3 = string.format("%.2fpt", n.kern/65536) +%<dbg> texio.write_nl(' ') +%<dbg> texio.write_nl('KERN height: ' .. ht1) +%<dbg> texio.write(' RULE height: ' .. ht2) +%<dbg> texio.write(' KERN height: ' .. ht3) +%<dbg> texio.write_nl('TOTAL height: ' .. htr .. 'sp') +%<dbg> end + if htr == 0 then + flag = true +%<dbg> if debug then +%<dbg> texio.write(' => footnoterule found!') +%<dbg> end + end + end + end + end + return flag +end +% \end{macrocode} +% +% This function scans the page body (or each column) in search of +% typographical flaws. +% +% \changes{v0.40}{2021/04/18}{Title pages, pages with figures and/or +% tables may not be empty pages: check `vpos’ last line’s position.} +% +% \begin{macrocode} +check_vtop = function (head, colno) 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 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 Stretch = math.max(luatypo.Stretch/100,1) - + local FirstWordMatch = luatypo.FirstWordMatch + local ParLastHyphen = luatypo.ParLastHyphen + local EOLShortWords = luatypo.EOLShortWords + local LastWordMatch = luatypo.LastWordMatch + local FootnoteSplit = luatypo.FootnoteSplit + local Stretch = math.max(luatypo.Stretch/100,1) + local blskip = tex.getglue("baselineskip") + local pageno = tex.getcount("c@page") + local vpos_min = PAGEmin * blskip + vpos_min = vpos_min * 1.5 + local vpos = 0 + local pageflag = false + local body_bottom = false + local page_bottom = false + local first_bot = true + local footnote = false + local ftnsplit = false local orphanflag = false local widowflag = false local lwhyphflag = false - local match1 = false - local match2 = false + local pageshort = false local firstwd = "" local lastwd = "" + local hyphcount = 0 + local pageline = 0 + local ftnline = 0 + local line = 0 +% \end{macrocode} +% The main loop scans the content of the |\vtop| holding the page +% (or column) body, footnotes included. +% The vertical position of the current node is stored in the |vpos| +% dimension (integer in `sp’ units). +% \begin{macrocode} while head do local nextnode = head.next - local prevnode = head.prev - local pprevnode = nil - if prevnode then - pprevnode = prevnode.prev - end % \end{macrocode} -% If the current node is a glue of type topskip, we are starting new -% page, let’s reset some counters and flags : |pageflag| will be set -% to |true| if a possible typographic flaw is found on this page, -% and trigger the addition of this page number to the summary list. -% |hyphcount| hold the number the consecutive hyphenated lines. -% \begin{macrocode} - if head.id == GLUE and head.subtype == TOPSKIP then - pageno = tex.getcount("c@page") - hyphcount = 0 - if pageno > prevno then - pageflag = false - pagelines = 0 - match1 = false +% If a |\footnoterule| is found, set the |footnote| flag and reset +% some counters and flags for the coming footnotes. +% \begin{macrocode} + if not footnote and head.id == KERN and head.subtype == 1 then + if footnoterule_ahead(head, true) then + footnote = true + ftnline = 0 + body_bottom = false + orphanflag = false + lwhyphflag = false + hyphcount = 0 firstwd = "" lastwd = "" - prevno = pageno - end - elseif head.id == HLIST and head.subtype == LINE then -% \end{macrocode} -% The current node is a line, |first| is the line’s first node. -% Skip margin kern or leftskip if any. -% \begin{macrocode} - local first = head.head - if first.id == MKERN or - (first.id == GLUE and first.subtype == LFTSKIP) then - first = first.next + else + vpos = vpos + head.kern end - pagelines = pagelines + 1 - local ListItem = false + elseif head.id == HLIST and head.subtype == LINE and + (head.height > 0 or head.depth > 0) then % \end{macrocode} -% Is this line really a text line (one glyph at least)? +% This is a text line, increment counters |pageline| or +% |ftnline| and |line| (for |log_flaw|). % \begin{macrocode} - local textline = false - if first.id == GLYPH then - textline = true + if footnote then + ftnline = ftnline + 1 + line = ftnline else - local n = first - repeat - n = n.next - if n and n.id == GLYPH then - textline = true - break - end - until not n or (n.id == GLUE and n.subtype == RGTSKIP) + pageline = pageline + 1 + line = pageline end % \end{macrocode} -% Is this line overfull or underfull? +% Is it overfull or underfull? +% \changes{v0.50}{2021/05/13}{Detection of overfull boxes fixed: the +% former code didn’t work for typewriter fonts.} % \begin{macrocode} - if head.glue_set == 1 and head.glue_sign == 2 and - head.glue_order == 0 and OverfullLines then -%<msg> texio.write_nl('***OVERFULL line on page ' .. pageno) -%<msg> texio.write_nl(' ') + local first = head.head + local hmax = head.width + 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 -%<msg> texio.write_nl('***UNDERFULL line on page ' .. -%<msg> tex.getcount("c@page")) -%<msg> texio.write_nl(' ') - local COLOR = luatypo.colortbl[8] + 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 % \end{macrocode} +% Let’s update |vpos| and check if the current line is the last one +% of the page body; this requires to look ahead \emph{now} for the +% next nodes in the `vlist’ as this information is needed to decide +% about orphans, last page’s word hyphenated, etc. +% \begin{macrocode} + vpos = vpos + head.height + head.depth + local n = head.next + while n and + (n.id == GLUE or n.id == PENALTY or n.id == WHATSIT) do + n = n.next + end +% \end{macrocode} +% Is this line the last one on the current page? … +% \begin{macrocode} + if not n then + if footnote then + page_bottom = true + else + page_bottom = true + body_bottom = true + end +% \end{macrocode} +% or the last one before |\footnoterule|? +% \begin{macrocode} + elseif footnoterule_ahead(n, false) then + body_bottom = true + end +% \end{macrocode} +% 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. +% \begin{macrocode} + if footnote and page_bottom then + ftnsplit = true + end +% \end{macrocode} +% The current node is a line, |first| is the line’s first node. +% Skip margin kern and/or leftskip if any. +% +% \changes{v0.40}{2021/04/18}{Both MKERN and LFTSKIP may occur on +% the same line.} +% +% \changes{v0.40}{2021/04/18}{All hlists of subtype LINE now +% count as a pageline.} +% +% \begin{macrocode} + while first.id == MKERN or + (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} if first.id == LPAR then % \end{macrocode} -% It starts a paragraph… +% It starts a paragraph… Reset |parline| except in footnotes +% (|parline| and |pageline| counts are for ``body'' \emph{only}, +% they are frozen in footnotes). % \begin{macrocode} hyphcount = 0 - parlines = 1 - if not nextnode then + if not footnote then + parline = 1 + end + if body_bottom then % \end{macrocode} -% No more nodes: we are at the page bottom, this ligne is -% an orphan (unless it is the unique line of the paragraph)… see below. +% We are at the page bottom (footnotes excluded), this ligne is +% an orphan (unless it is the unique line of the paragraph, this +% will be checked later when scanning the end of line). % \begin{macrocode} orphanflag = true end % \end{macrocode} -% List items begin with LPAR followed by an hbox. +% List items begin with |LPAR| followed by an hbox. % \begin{macrocode} local nn = first.next - if nn and nn.id == HLIST and nn.subtype == BOX then - ListItem = true + if nn and nn.id == HLIST and nn.subtype == BOX then + ListItem = true end - else - parlines = parlines + 1 + elseif not footnote then + parline = parline + 1 end % \end{macrocode} % Let’s track lines beginning with the same word (except lists). % \begin{macrocode} if FirstWordMatch then local flag = not ListItem - local COLOR = luatypo.colortbl[10] - firstwd, match1 = check_first_word(firstwd, first, COLOR, flag) - if match1 then - pageflag = true - end + firstwd = check_first_word(firstwd, first, line, flag) end % \end{macrocode} % Let’s check the end of line: |ln| (usually a rightskip) and |pn| @@ -1605,15 +1956,17 @@ luatypo.check_page = function (head) local pn = ln.prev if pn and pn.id == GLUE and pn.subtype == PARFILL then % \end{macrocode} -% The paragraph ends with this line, it is not an orphan then… +% CASE 1: this line ends the paragraph, reset |ftnsplit| and |orphan| +% flags to false… % \begin{macrocode} hyphcount = 0 + ftnsplit = false orphanflag = false % \end{macrocode} % but it is a widow if it is the page’s first line and it does’nt % start a new paragraph.\\ Orphans and widows will be colored later. % \begin{macrocode} - if pagelines == 1 and parlines > 1 then + if pageline == 1 and parline > 1 then widowflag = true end % \end{macrocode} @@ -1622,18 +1975,19 @@ luatypo.check_page = function (head) local PFskip = effective_glue(pn,head) if ShortLines then local llwd = tex.hsize - PFskip -%<dbg> local PFskip_pt = PFskip/65536 -%<dbg> local llwd_pt = llwd/65536 -%<dbg> texio.write_nl('PFskip= ' .. PFskip_pt .. ' pt') -%<dbg> texio.write_nl('llwd= ' .. llwd_pt .. ' pt') +%<dbg> local PFskip_pt = string.format("%.1fpt", PFskip/65536) +%<dbg> local llwd_pt = string.format("%.1fpt", llwd/65536) +%<dbg> texio.write_nl('PFskip= ' .. PFskip_pt) +%<dbg> texio.write(' llwd= ' .. llwd_pt) % \end{macrocode} % |llwd| is the line’s length. Is it too short? % \begin{macrocode} if llwd < LLminWD then -%<msg> texio.write_nl('***Last line too short, page ' .. pageno) -%<msg> texio.write_nl(' ') pageflag = true - local COLOR = luatypo.colortbl[6] + local msg = "SHORT LINE: " .. + string.format("%.0fpt", llwd/65536) + log_flaw(msg, line, colno, footnote) + local COLOR = luatypo.colortbl[6] local attr = oberdiek.luacolor.getattribute() % \end{macrocode} % let’s colour the whole line. @@ -1645,10 +1999,11 @@ luatypo.check_page = function (head) % Is this line nearly full? (ending too close to the right margin) % \begin{macrocode} if BackParindent and PFskip < BackPI and PFskip > BackFuzz then -%<msg> texio.write_nl('***Last line nearly full, page ' .. pageno) -%<msg> texio.write_nl(' ') pageflag = true - local COLOR = luatypo.colortbl[12] + local msg = "LINE NEARLY FULL: missing " .. + 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 @@ -1656,38 +2011,30 @@ luatypo.check_page = function (head) % Does the last word and the one on the previous line match? % \begin{macrocode} if LastWordMatch then - local COLOR = luatypo.colortbl[11] local flag = textline if PFskip > BackPI then flag = false end - lastwd, match1 = check_last_word(lastwd, pn, COLOR, flag) - if match1 then - pageflag = true - end + lastwd = check_last_word(lastwd, pn, line, flag) end elseif pn and pn.id == DISC then % \end{macrocode} -% The current line ends with an hyphen. +% CASE 2: the current line ends with an hyphen. % \begin{macrocode} hyphcount = hyphcount + 1 if LastWordMatch then - local COLOR = luatypo.colortbl[11] - lastwd, match1 = check_last_word(lastwd, ln, COLOR, true) - if match1 then - pageflag = true - end + lastwd = check_last_word(lastwd, ln, line, true) end if hyphcount > HYPHmax and RepeatedHyphens then local COLOR = luatypo.colortbl[2] local pg = show_pre_disc (pn,COLOR) pageflag = true -%<msg> texio.write_nl('***HYPH issue page ' .. pageno) -%<msg> texio.write_nl(' ') + local msg = "REPEATED HYPHENS: more than " .. HYPHmax + log_flaw(msg, line, colno, footnote) end - if not nextnode and EOPHyphens then + if (page_bottom or body_bottom) and EOPHyphens then % \end{macrocode} -% No more nodes: this hyphen occurs on the page last line. +% This hyphen occurs on the page’s last line (body or footnote). % \begin{macrocode} lwhyphflag = true end @@ -1697,64 +2044,38 @@ luatypo.check_page = function (head) % a `linebreak penalty’, the next one is a `baseline skip’ and the % node after a `hlist of subtype line’ with |glue_order=2|. % \begin{macrocode} - local nnnode = nextnode.next - local nnnnode = nil - if nnnode and nnnode.next then - nnnnode = nnnode.next - if nnnnode and nnnnode.id == HLIST and - nnnnode.subtype == 1 and nnnnode.glue_order == 2 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) - pageflag = true end end end - elseif pn and pn.id == GLYPH then % \end{macrocode} -% \enlargethispage*{\baselineskip} -% The current line ends with a character, reset |hyphcount| and -% compare the end of line’s word with the one on previous line. +% CASE 3: the current line ends with anything else (|MKERN|, +% |GLYPH|, |HLIST|, etc.), reset |hyphcount|, perform checks +% for `LastWordMatch’ and for `EOLShortWords’. % \begin{macrocode} + else hyphcount = 0 - if LastWordMatch then - local COLOR = luatypo.colortbl[11] - lastwd, match1 = check_last_word(lastwd, pn, COLOR, true) + if LastWordMatch and pn then + lastwd = check_last_word(lastwd, pn, line, true) end -% \end{macrocode} -% Look for a short unwanted short word at the end of the current -% line. -% \begin{macrocode} if EOLShortWords then - match2 = check_regexpr(pn) - end - if match1 or match2 then - pageflag = true - end -% \end{macrocode} -% Microtype sometimes adds a margin kern at the right margin, -% |check_regexpr| has to operate on the previous node then. -% \begin{macrocode} - elseif pn and pn.id == MKERN then - hyphcount = 0 - local ppn = pn.prev - if ppn and ppn.id == GLYPH then - if LastWordMatch then - local COLOR = luatypo.colortbl[11] - lastwd, match1 = check_last_word(lastwd, pn, COLOR, true) + while pn and pn.id ~= GLYPH and pn.id ~= HLIST do + pn = pn.prev end - if EOLShortWords then - match2 = check_regexpr(ppn) - end - if match1 or match2 then - pageflag = true + if pn and pn.id == GLYPH then + check_regexpr(pn,line) end end -% \end{macrocode} -% If the current line ends with anything else (no hyphen), -% reset |hyphcount|. -% \begin{macrocode} - else - hyphcount = 0 end % \end{macrocode} % Colour the whole line if is is a widow. @@ -1762,59 +2083,221 @@ luatypo.check_page = function (head) if widowflag and Widows then pageflag = true widowflag = false -%<msg> texio.write_nl('***WIDOW on top of page ' .. pageno) -%<msg> texio.write_nl(' ') + local msg = "WIDOW" + log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[4] color_line (head, COLOR) end % \end{macrocode} -% Colour the whole line if is is a orphan. +% Colour the whole line if is is a orphan or footenote continuing +% on the next page. % \begin{macrocode} if orphanflag and Orphans then -%<msg> texio.write_nl('***ORPHAN at bottom of page ' .. pageno) -%<msg> texio.write_nl(' ') 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} % Colour (differently) the last word if hyphenated. % \begin{macrocode} if lwhyphflag and EOPHyphens then -%<msg> texio.write_nl('***LAST WORD SPLIT page ' .. pageno) -%<msg> texio.write_nl(' ') 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 + elseif head.id == HLIST and + (head.subtype == EQN or head.subtype == ALIGN) and + (head.height > 0 or head.depth > 0) then % \end{macrocode} -% Track empty pages: check the number of lines at end of page. -% Widows are already detected; get the last line and colour it. -% NOTE: there are usually two consecutive nodes of type 12-0 at -% end of pages… +% +% \changes{v0.50}{2021/05/05}{Consider displayed and aligned equations +% too for overfull boxes.} +% +% This line is a displayed or aligned equation. +% Let’s update |vpos| and the line number. % \begin{macrocode} - elseif not nextnode and head.id == GLUE and - head.subtype == USRSKIP then - if pagelines > 1 and pagelines < PAGEmin and ShortPages then - pageflag = true - local COLOR = luatypo.colortbl[9] -%<msg> texio.write_nl('***Only ' .. pagelines .. -%<msg> ' lines on page ' .. pageno) -%<msg> texio.write_nl(' ') - local n = head - while n and (n.id ~= HLIST or n.subtype ~= LINE) do - n = n.prev + vpos = vpos + head.height + head.depth + if footnote then + ftnline = ftnline + 1 + line = ftnline + else + pageline = pageline + 1 + line = pageline + end +% \end{macrocode} +% Let’s check for an ``Overfull box''. For a displayed equation +% it is straightforward. A set of aligned equations all have the +% same (maximal) width; in order to avoid highlighting the whole +% set, we have to look for glues at the end of embedded `hlists’. +% \begin{macrocode} + 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 n then - color_line(n, COLOR) + 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 +% \end{macrocode} +% We also need to set flag |body_bottom| and to increment the +% |pageline| counter to track empty pages. +% \begin{macrocode} + local n = head.next + while n and (n.id == GLUE or n.id == PENALTY or + n.id == WHATSIT or n.id == VLIST) do + n = n.next + end + if not n then + page_bottom = true + body_bottom = true + elseif footnoterule_ahead(n, false) then + body_bottom = true + end +% \end{macrocode} +% This is a |\vbox|, let’s update |vpos|. +% \begin{macrocode} + elseif head.id == VLIST then + vpos = vpos + head.height + head.depth +% \end{macrocode} +% Track empty pages: check the number of lines at end of page, +% in case this number is low, \emph{and} |vpos| is less than +% |vpos_min|, fetch the last line and colour it.\\ +% NOTE1: |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|.\\ +% NOTE2: Widows are already detected, skip them here; there are +% usually two consecutive nodes of type 12-0 at end of pages… +% \begin{macrocode} + elseif body_bottom and head.id == GLUE and head.subtype == 0 then + if first_bot then +%<dbg> local vpos_pt = string.format("%.1fpt", vpos/65536) +%<dbg> local vmin_pt = string.format("%.1fpt", vpos_min/65536) +%<dbg> texio.write_nl('pageline=' .. pageline) +%<dbg> texio.write_nl('vpos=' .. vpos_pt) +%<dbg> texio.write(' vpos_min=' .. vmin_pt) +%<dbg> if page_bottom then +%<dbg> local tht = tex.getdimen("textheight") +%<dbg> local tht_pt = string.format("%.1fpt", tht/65536) +%<dbg> texio.write(' textheight=' .. tht_pt) +%<dbg> end +%<dbg> texio.write_nl(' ') + 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 +% \end{macrocode} +% Increment |vpos| on other vertical glues. +% \begin{macrocode} + elseif head.id == GLUE then + vpos = vpos + effective_glue(head,body) end head = nextnode end + return pageflag +end +% \end{macrocode} +% +% This is the main function which will be added to the +% |pre_shipout_filter| callback unless option |None| is selected. +% It executes |get_pagebody|, then scans the page body for possible +% columns (multi column page). +% +% \begin{macrocode} +luatypo.check_page = function (head) + local pageno = tex.getcount("c@page") + local pageflag = false + local n2, n3, col, colno + local body = get_pagebody(head) + local first = body.list +%<dbg> texio.write_nl('body.id=' .. tostring(node.type(body.id))) +%<dbg> texio.write('-' .. body.subtype) +%<dbg> texio.write_nl(' ') +%<dbg> texio.write_nl('first.id=' .. tostring(node.type(first.id))) +%<dbg> texio.write('-' .. first.subtype) +%<dbg> texio.write_nl(' ') + if first.id == HLIST and first.subtype == 2 then +% \end{macrocode} +% Two or more columns, each one is boxed in an hlist. +% Run |check_vtop| on every column. +% \begin{macrocode} + n2 = first.list + colno = 0 + for n in traverse_id(HLIST, n2) do +%<dbg> texio.write_nl('n.id=' .. tostring(node.type(n.id))) +%<dbg> texio.write('-' .. n.subtype) +%<dbg> texio.write(' ht=' .. n.height) +%<dbg> texio.write_nl(' ') + if n.id == HLIST and n.subtype == 2 then + n3 = n.list +%<dbg> texio.write_nl('n3.id=' .. tostring(node.type(n3.id))) +%<dbg> texio.write('-' .. n3.subtype) +%<dbg> texio.write(' ht=' .. n3.height) +%<dbg> texio.write_nl(' ') + col = n3.list + colno = colno + 1 + pageflag = check_vtop(col,colno) + end + end + elseif body.id == VLIST and body.subtype == 0 then +% \end{macrocode} +% Single column, run |check_vtop| on the top vlist. +% \begin{macrocode} + col = body.list + pageflag = check_vtop(col,colno) + end % \end{macrocode} % Add this page number to the summary if any flaw has been found on it. % Skip duplicates. +% +% \changes{v0.32}{2021/03/14}{Remove duplicates in the summary of pages.} +% % \begin{macrocode} if pageflag then local pl = luatypo.pagelist @@ -1829,22 +2312,29 @@ return luatypo.check_page \end{luacode} % \end{macrocode} % -% Add the |luatypo.check_page| function to the |pre_output_filter| -% callback, unless option |None| is selected ; remember that the -% |None| boolean’s value is forwarded to Lua `AtEndOfPackage’… +% Add the |luatypo.check_page| function to the |pre_shipout_filter| +% callback (with priority 1 for color 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 later the material is not boxed +% yet and footnotes are not visible.} +% % \begin{macrocode} \AtEndOfPackage{% \directlua{ if not luatypo.None then luatexbase.add_to_callback - ("pre_output_filter",luatypo.check_page,"check_page") + ("pre_shipout_filter",luatypo.check_page,"check_page",1) end } } % \end{macrocode} % -% Load a local config file if present in LaTeX’s search path. +% Load a local config file if present in LaTeX’s search path.\\ % Otherwise, set reasonnable defaults. +% \enlargethispage*{\baselineskip} % \begin{macrocode} \InputIfFileExists{lua-typo.cfg}% @@ -1855,7 +2345,7 @@ return luatypo.check_page \definecolor{myred}{rgb}{1,0.55,0} \luatypoSetColor0{red}% Paragraph last full line hyphenated \luatypoSetColor1{red}% Page last word hyphenated - \luatypoSetColor2{red}% Hyphens ending two many consecutive lines + \luatypoSetColor2{red}% Hyphens on to many consecutive lines \luatypoSetColor3{red}% Short word at end of line \luatypoSetColor4{cyan}% Widow \luatypoSetColor5{cyan}% Orphan @@ -1866,9 +2356,11 @@ return luatypo.check_page \luatypoSetColor{10}{myred}% First word matches \luatypoSetColor{11}{myred}% Last word matches \luatypoSetColor{12}{mygrey}% Paragraph ending on a nearly full line + \luatypoSetColor{13}{cyan}% Footnote split \luatypoBackPI=1em\relax \luatypoBackFuzz=2pt\relax - \luatypoLLminWD=2\parindent\relax + \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax + \else\luatypoLLminWD=2\parindent\relax\fi \luatypoStretchMax=200\relax \luatypoHyphMax=2\relax \luatypoPageMin=5\relax @@ -1879,8 +2371,7 @@ return luatypo.check_page % \iffalse %</sty> % \fi -% -% \clearpage +% \clearpage % \section{Configuration file} % % \iffalse @@ -1897,7 +2388,9 @@ return luatypo.check_page \luatypoBackFuzz=2pt\relax %% Minimum length of paragraphs’ last lines -\luatypoLLminWD=2\parindent\relax +\ifdim\parindent=0pt \luatypoLLminWD=20pt\relax +\else \luatypoLLminWD=2\parindent\relax +\fi %% Maximum number of consecutive hyphenated lines \luatypoHyphMax=2\relax @@ -1917,7 +2410,7 @@ return luatypo.check_page \definecolor{myred}{rgb}{1,0.55,0} \luatypoSetColor0{red} % Paragraph last full line hyphenated \luatypoSetColor1{red} % Page last word hyphenated -\luatypoSetColor2{red} % Hyphens ending two many consecutive lines +\luatypoSetColor2{red} % Hyphens on to many consecutive lines \luatypoSetColor3{red} % Short word at end of line \luatypoSetColor4{cyan} % Widow \luatypoSetColor5{cyan} % Orphan @@ -1928,10 +2421,10 @@ return luatypo.check_page \luatypoSetColor{10}{myred} % First word matches \luatypoSetColor{11}{myred} % Last word matches \luatypoSetColor{12}{mygrey}% Paragraph ending on a nearly full line +\luatypoSetColor{13}{cyan} % Footnote split -%% Language specific settings (example for French only currently): +%% Language specific settings (example for French): %% short words (two letters max) to be avoided at end of lines. -%% French: %%\luatypoOneChar{french}{'À à Ô'} %%\luatypoTwoChars{french}{'Je Tu Il On'} % \end{macrocode} |