diff options
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/README.md | 24 | ||||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf | bin | 16794 -> 16794 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx | 2 | ||||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf | bin | 59619 -> 64171 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx | 11 | ||||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf | bin | 115609 -> 138442 bytes | |||
-rw-r--r-- | Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx | 1177 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/lua-typo/lua-typo-2021-04-18.sty | 843 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg | 10 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty | 613 |
10 files changed, 2172 insertions, 508 deletions
diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/README.md b/Master/texmf-dist/doc/lualatex/lua-typo/README.md index 5e88fafb62c..64373e69135 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/README.md +++ b/Master/texmf-dist/doc/lualatex/lua-typo/README.md @@ -48,12 +48,26 @@ Recommended loactions for installation: Changes ------- -- First release version: 0.30, March 2021. +* First release version: 0.30, March 2021. -- v.0.32: bug fixes - better protection against nil nodes, - new page detection corrected, - homeoarchy detection improved. +* v.0.32: bug fixes + - better protection against nil nodes, + - new page detection corrected, + - homeoarchy detection improved. + +* v.0.40 (not released): bug fixes + - in some cases parlines count was wrong, fixed; + - partial improvement of short pages detection. + +* v.0.50: new implementation, May 2021 + - callback change: `pre_shipout_filter` instead of `pre_output_filter` + this change requires latex release 2021-06-01; + - rollback enabled in case `pre_shipout_filter` is missing; + - footnotes are scanned now; + - overfull box detection fixed (works for equations and tt fonts now); + - short pages detection fixed; + - coloration of faulty lines improved; + - all flaws found are now recorded into file "`\jobname`.typo". -- Copyright 2020--2021 Daniel Flipo diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf Binary files differindex f5c82e70150..89951bdf862 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf +++ b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx index 449dfe7bfd0..e99c7b92124 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx +++ b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx @@ -1,3 +1,5 @@ +\RequirePackage{pdfmanagement-testphase} +\DeclareDocumentMetadata{pdfstandard=A-2b, lang=fr-FR} \documentclass[a4paper]{ltxdoc} \usepackage[dvipsnames]{xcolor} \usepackage{fontspec} diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf Binary files differindex 5b2c788bd0f..315c8306e10 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf +++ b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx index 310c7dae04a..5e94a0075df 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx +++ b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx @@ -1,3 +1,5 @@ +\RequirePackage{pdfmanagement-testphase} +\DeclareDocumentMetadata{pdfstandard=A-2b, lang=en-GB} \documentclass[a4paper]{ltxdoc} \usepackage[dvipsnames]{xcolor} \usepackage{fontspec} @@ -22,6 +24,15 @@ \usepackage[numbered]{hypdoc} \hypersetup{colorlinks,urlcolor=blue,unicode} \let\FrenchDoc\comment\let\endFrenchDoc\endcomment +\RecordChanges +\AtEndDocument{% + \clearpage + \section{Change History}% + \GlossaryPrologue{}% + Changes are listed in reverse order (latest first) + from version~0.30. + \PrintChanges +} \newcommand*\file[1]{\texttt{#1}} \newcommand*\pkg[1]{\texttt{#1}} \newcommand*\opt[1]{\texttt{#1}} diff --git a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf Binary files differindex 764e6374a4a..5b2b2dcbdf7 100644 --- a/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf +++ b/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf 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} diff --git a/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo-2021-04-18.sty b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo-2021-04-18.sty new file mode 100644 index 00000000000..1580bcb1470 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo-2021-04-18.sty @@ -0,0 +1,843 @@ +%% +%% This is file `lua-typo.sty' v0.40, +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% lua-typo.dtx (with options: `sty') +%% +%% IMPORTANT NOTICE: +%% For the copyright see the source file `lua-typo.dtx'. +%% +\NeedsTeXFormat{LaTeX2e}[2020/01/01] +\ProvidesPackage{lua-typo} + [2021/04/18 v.0.40 Daniel Flipo] +\ifdefined\directlua + \RequirePackage{luatexbase,luacode,luacolor} + \RequirePackage{kvoptions,atveryend} +\else + \PackageError{This package is meant for LuaTeX only! Aborting} + {No more information available, sorry!} +\fi +\newdimen\luatypoLLminWD +\newdimen\luatypoBackPI +\newdimen\luatypoBackFuzz +\newcount\luatypoStretchMax +\newcount\luatypoHyphMax +\newcount\luatypoPageMin +\newcount\luatypoMinFull +\newcount\luatypoMinPart +\newcount\luatypo@LANGno +\newcount\luatypo@options +\newtoks\luatypo@single +\newtoks\luatypo@double + +\begin{luacode} +luatypo = { } +\end{luacode} +\SetupKeyvalOptions{ + family=luatypo, + prefix=LT@, +} +\DeclareBoolOption[false]{ShowOptions} +\DeclareBoolOption[false]{None} +\DeclareBoolOption[false]{All} +\DeclareBoolOption[false]{BackParindent} +\DeclareBoolOption[false]{ShortLines} +\DeclareBoolOption[false]{ShortPages} +\DeclareBoolOption[false]{OverfullLines} +\DeclareBoolOption[false]{UnderfullLines} +\DeclareBoolOption[false]{Widows} +\DeclareBoolOption[false]{Orphans} +\DeclareBoolOption[false]{EOPHyphens} +\DeclareBoolOption[false]{RepeatedHyphens} +\DeclareBoolOption[false]{ParLastHyphen} +\DeclareBoolOption[false]{EOLShortWords} +\DeclareBoolOption[false]{FirstWordMatch} +\DeclareBoolOption[false]{LastWordMatch} +\AddToKeyvalOption{luatypo}{All}{% + \LT@ShortLinestrue \LT@ShortPagestrue + \LT@OverfullLinestrue \LT@UnderfullLinestrue + \LT@Widowstrue \LT@Orphanstrue + \LT@EOPHyphenstrue \LT@RepeatedHyphenstrue + \LT@ParLastHyphentrue \LT@EOLShortWordstrue + \LT@FirstWordMatchtrue \LT@LastWordMatchtrue + \LT@BackParindenttrue +} +\ProcessKeyvalOptions{luatypo} +\AtEndOfPackage{% + \ifLT@None + \directlua{ luatypo.None = true }% + \else + \directlua{ luatypo.None = false }% + \fi + \ifLT@BackParindent + \advance\luatypo@options by 1 + \directlua{ luatypo.BackParindent = true }% + \else + \directlua{ luatypo.BackParindent = false }% + \fi + \ifLT@ShortLines + \advance\luatypo@options by 1 + \directlua{ luatypo.ShortLines = true }% + \else + \directlua{ luatypo.ShortLines = false }% + \fi + \ifLT@ShortPages + \advance\luatypo@options by 1 + \directlua{ luatypo.ShortPages = true }% + \else + \directlua{ luatypo.ShortPages = false }% + \fi + \ifLT@OverfullLines + \advance\luatypo@options by 1 + \directlua{ luatypo.OverfullLines = true }% + \else + \directlua{ luatypo.OverfullLines = false }% + \fi + \ifLT@UnderfullLines + \advance\luatypo@options by 1 + \directlua{ luatypo.UnderfullLines = true }% + \else + \directlua{ luatypo.UnderfullLines = false }% + \fi + \ifLT@Widows + \advance\luatypo@options by 1 + \directlua{ luatypo.Widows = true }% + \else + \directlua{ luatypo.Widows = false }% + \fi + \ifLT@Orphans + \advance\luatypo@options by 1 + \directlua{ luatypo.Orphans = true }% + \else + \directlua{ luatypo.Orphans = false }% + \fi + \ifLT@EOPHyphens + \advance\luatypo@options by 1 + \directlua{ luatypo.EOPHyphens = true }% + \else + \directlua{ luatypo.EOPHyphens = false }% + \fi + \ifLT@RepeatedHyphens + \advance\luatypo@options by 1 + \directlua{ luatypo.RepeatedHyphens = true }% + \else + \directlua{ luatypo.RepeatedHyphens = false }% + \fi + \ifLT@ParLastHyphen + \advance\luatypo@options by 1 + \directlua{ luatypo.ParLastHyphen = true }% + \else + \directlua{ luatypo.ParLastHyphen = false }% + \fi + \ifLT@EOLShortWords + \advance\luatypo@options by 1 + \directlua{ luatypo.EOLShortWords = true }% + \else + \directlua{ luatypo.EOLShortWords = false }% + \fi + \ifLT@FirstWordMatch + \advance\luatypo@options by 1 + \directlua{ luatypo.FirstWordMatch = true }% + \else + \directlua{ luatypo.FirstWordMatch = false }% + \fi + \ifLT@LastWordMatch + \advance\luatypo@options by 1 + \directlua{ luatypo.LastWordMatch = true }% + \else + \directlua{ luatypo.LastWordMatch = false }% + \fi +} +\ifLT@ShowOptions + \GenericWarning{* }{% + *** List of possible options for lua-typo ***\MessageBreak + [Default values between brackets]% + \MessageBreak + ShowOptions [false]\MessageBreak + None [false]\MessageBreak + BackParindent [false]\MessageBreak + ShortLines [false]\MessageBreak + ShortPages [false]\MessageBreak + OverfullLines [false]\MessageBreak + UnderfullLines [false]\MessageBreak + Widows [false]\MessageBreak + Orphans [false]\MessageBreak + EOPHyphens [false]\MessageBreak + RepeatedHyphens [false]\MessageBreak + ParLastHyphen [false]\MessageBreak + EOLShortWords [false]\MessageBreak + FirstWordMatch [false]\MessageBreak + LastWordMatch [false]\MessageBreak + \MessageBreak + *********************************************% + \MessageBreak Lua-typo [ShowOptions] + }% +\fi +\AtBeginDocument{% + \directlua{ + luatypo.HYPHmax = tex.count.luatypoHyphMax + luatypo.PAGEmin = tex.count.luatypoPageMin + luatypo.Stretch = tex.count.luatypoStretchMax + luatypo.MinFull = tex.count.luatypoMinFull + luatypo.MinPart = tex.count.luatypoMinPart + luatypo.LLminWD = tex.dimen.luatypoLLminWD + luatypo.BackPI = tex.dimen.luatypoBackPI + luatypo.BackFuzz = tex.dimen.luatypoBackFuzz + }% +} +\AtVeryEndDocument{% +\ifnum\luatypo@options = 0 \LT@Nonetrue \fi +\ifLT@None + \directlua{ + texio.write_nl(' ') + texio.write_nl('***********************************') + texio.write_nl('*** lua-typo loaded with NO option:') + texio.write_nl('*** NO CHECK PERFORMED! ***') + texio.write_nl('***********************************') + texio.write_nl(' ') + }% +\else + \directlua{ + texio.write_nl(' ') + texio.write_nl('*************************************') + if luatypo.pagelist == "" then + texio.write_nl('*** lua-typo: No Typo Flaws found.') + else + texio.write_nl('*** lua-typo: WARNING *************') + texio.write_nl('The following pages need attention: ') + texio.write(luatypo.pagelist) + end + texio.write_nl('***********************************') + texio.write_nl(' ') + }% +\fi} +\newcommand*{\luatypoOneChar}[2]{% + \def\luatypo@LANG{#1}\luatypo@single={#2}% + \ifcsname l@\luatypo@LANG\endcsname + \luatypo@LANGno=\the\csname l@\luatypo@LANG\endcsname \relax + \directlua{ + local langno = \the\luatypo@LANGno + local string = \the\luatypo@single + luatypo.single[langno] = " " + for p, c in utf8.codes(string) do + local s = string.char(c) + luatypo.single[langno] = luatypo.single[langno] .. s + end + }% + \else + \PackageWarning{luatypo}{Unknown language "\luatypo@LANG", + \MessageBreak \protect\luatypoOneChar\space command ignored}% + \fi} +\newcommand*{\luatypoTwoChars}[2]{% + \def\luatypo@LANG{#1}\luatypo@double={#2}% + \ifcsname l@\luatypo@LANG\endcsname + \luatypo@LANGno=\the\csname l@\luatypo@LANG\endcsname \relax + \directlua{ + local langno = \the\luatypo@LANGno + local string = \the\luatypo@double + luatypo.double[langno] = " " + for p, c in utf8.codes(string) do + local s = string.char(c) + luatypo.double[langno] = luatypo.double[langno] .. s + end + }% + \else + \PackageWarning{luatypo}{Unknown language "\luatypo@LANG", + \MessageBreak \protect\luatypoTwoChars\space command ignored}% + \fi} +\newcommand*{\luatypoSetColor}[2]{% + \begingroup + \color{#2}% + \directlua{luatypo.colortbl[#1]=\the\LuaCol@Attribute}% + \endgroup +} +\begin{luacode} +luatypo.single = { } +luatypo.double = { } +luatypo.colortbl = { } +luatypo.pagelist = "" + +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 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 DISC = node.id("disc") +local GLYPH = node.id("glyph") +local GLUE = node.id("glue") +local KERN = node.id("kern") +local HLIST = node.id("hlist") +local LPAR = node.id("local_par") +local MKERN = node.id("margin_kern") +local PENALTY = node.id("penalty") +local USRSKIP = 0 +local PARSKIP = 3 +local LFTSKIP = 8 +local RGTSKIP = 9 +local TOPSKIP = 10 +local PARFILL = 15 +local LINE = 1 +local BOX = 2 +local EQN = 6 +local USER = 0 +local HYPH = 0x2D +local LIGA = 0x102 +local parline = 0 + +local effective_glue = node.effective_glue +local set_attribute = node.set_attribute +local slide = node.slide +local traverse = node.traverse +local traverse_id = node.traverse_id +local has_field = node.has_field +local uses_font = node.uses_font +local is_glyph = node.is_glyph + +local color_node = function (node, color) + local attr = oberdiek.luacolor.getattribute() + if node and node.id == DISC then + local pre = node.pre + local post = node.post + local repl = node.replace + if pre then + set_attribute(pre,attr,color) + end + if post then + set_attribute(post,attr,color) + end + if repl then + set_attribute(repl,attr,color) + end + elseif node then + set_attribute(node,attr,color) + end +end +local color_hlist = function (head, color) + local first = head.head + for n in traverse(first) do + color_node(n, color) + end +end +local color_line = function (head, color) + local first = head.head + for n in traverse(first) do + if n and n.id == HLIST then + color_hlist(n, color) + else + color_node(n, color) + end + end +end +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 + 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 + 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 + elseif n and n.id == DISC then + 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) + 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) + end + end + end + if swap then + str = str .. c2 .. c1 + else + str = str .. c1 .. c2 + end + end + local len = string.len(str) + if string.find(str, "_") then + len = len - 1 + end + return len, str +end +local check_last_word = function (old, node, flag) + local COLOR = luatypo.colortbl[11] + local match = false + local new = "" + local maxlen = 0 + if flag and node then + local swap = true + local lastn = node + while lastn and lastn.id ~= GLYPH and lastn.id ~= DISC do + lastn = lastn.prev + end + local n = lastn + 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 .. "_" + repeat + n = n.prev + maxlen, new = signature (n, new, swap) + until not n or n.id == GLUE + end + new = string.reverse(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 + match = true + elseif oldlast == newlast and string.len(newlast) >= MinFull then + match = true + oldsub = oldlast + newsub = newlast + k = string.len(newlast) + end + if match then + local osub = oldsub + local nsub = newsub + while osub == nsub and k <= maxlen do + k = k +1 + osub = string.sub(old,-k) + nsub = string.sub(new,-k) + if osub == nsub then + newsub = nsub + end + end + pageflag = true + newsub = string.gsub(newsub, '^_', '') + 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 then + newsub = newsub .. "_" + end + n = n.prev + until not n or newsub == oldsub or l >= k + end + end + return new +end +local check_first_word = function (old, node, 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 + n = n.next + 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 .. "_" + repeat + n = n.next + maxlen, new = signature (n, new, swap) + until not n or n.id == GLUE + end + if flag then + local MinFull = luatypo.MinFull + local MinPart = luatypo.MinPart + MinFull = math.min(MinPart,MinFull) + local k = MinPart + local oldsub = "" + local newsub = "" + local oldfirst = string.gsub (old, '_.*', '') + local newfirst = string.gsub (new, '_.*', '') + local i = string.find(new, "_") + 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) + if oldsub == newsub and l >= k then + match = true + elseif oldfirst == newfirst and string.len(newfirst) >= MinFull then + match = true + oldsub = oldfirst + newsub = newfirst + k = string.len(newfirst) + end + if match then + local osub = oldsub + local nsub = newsub + while osub == nsub and k <= maxlen do + k = k + 1 + osub = string.sub(old,1,k) + nsub = string.sub(new,1,k) + if osub == nsub then + newsub = nsub + end + end + pageflag = true + newsub = string.gsub(newsub, '_$', '') --$ + oldsub = newsub + local newsub = "" + local k = string.len(oldsub) + local n = start + repeat + if n and n.id ~= GLUE then + color_node(n, COLOR) + l, newsub = signature(n, newsub, swap) + elseif n then + newsub = newsub .. "_" + end + n = n.next + until not n or newsub == oldsub or l >= k + end + end + return new +end +local check_regexpr = function (glyph) + local COLOR = luatypo.colortbl[3] + local lang = glyph.lang + local match = false + local lchar, id = is_glyph(glyph) + local previous = glyph.prev + if lang and luatypo.single[lang] then + 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 + color_node(glyph,COLOR) + end + end + end + if lang and luatypo.double[lang] then + if lchar and previous and previous.id == GLYPH then + local pchar, id = is_glyph(previous) + local pprev = previous.prev + if pchar and 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 match then + pageflag = true + color_node(previous,COLOR) + color_node(glyph,COLOR) + end + end + elseif lchar and previous and previous.id == KERN then + local pprev = previous.prev + if pprev and pprev.id == GLYPH then + local pchar, id = is_glyph(pprev) + local ppprev = pprev.prev + if pchar and 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 + color_node(pprev,COLOR) + color_node(glyph,COLOR) + end + end + end + end + end +end +local show_pre_disc = function (disc, color) + local n = disc + while n and n.id ~= GLUE do + color_node(n, color) + n = n.prev + end + return n +end +luatypo.check_page = function (head) + local PAGEmin = luatypo.PAGEmin + local HYPHmax = luatypo.HYPHmax + local LLminWD = luatypo.LLminWD + local BackPI = luatypo.BackPI + local BackFuzz = luatypo.BackFuzz + local BackParindent = luatypo.BackParindent + local ShortLines = luatypo.ShortLines + local ShortPages = luatypo.ShortPages + local OverfullLines = luatypo.OverfullLines + local UnderfullLines = luatypo.UnderfullLines + local Widows = luatypo.Widows + local Orphans = luatypo.Orphans + local EOPHyphens = luatypo.EOPHyphens + local RepeatedHyphens = luatypo.RepeatedHyphens + local FirstWordMatch = luatypo.FirstWordMatch + local ParLastHyphen = luatypo.ParLastHyphen + local EOLShortWords = luatypo.EOLShortWords + local LastWordMatch = luatypo.LastWordMatch + local pageno = tex.getcount("c@page") + local Stretch = math.max(luatypo.Stretch/100,1) + local blskip = tex.getglue("baselineskip") + local textht = tex.getdimen("textheight") + local vpos_min = (PAGEmin+1) * blskip + local vpos = 0 + + local pageflag = false + local orphanflag = false + local widowflag = false + local lwhyphflag = false + local pageshort = false + local firstwd = "" + local lastwd = "" + local hyphcount = 0 + local pageline = 0 + + while head do + local nextnode = head.next + local prevnode = head.prev + local pprevnode = nil + if prevnode then + pprevnode = prevnode.prev + end + if head.id == HLIST and head.subtype == LINE then + vpos = vpos + head.height + head.depth + local first = head.head + while first.id == MKERN or + (first.id == GLUE and first.subtype == LFTSKIP) do + first = first.next + end + local ListItem = false + pageline = pageline + 1 + if head.glue_set == 1 and head.glue_sign == 2 and + head.glue_order == 0 and OverfullLines then + pageflag = true + 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 + local COLOR = luatypo.colortbl[8] + pageflag = true + color_line (head, COLOR) + end + if first.id == LPAR then + hyphcount = 0 + parline = 1 + if not nextnode then + orphanflag = true + end + local nn = first.next + if nn and nn.id == HLIST and nn.subtype == BOX then + ListItem = true + end + else + parline = parline + 1 + end + if FirstWordMatch then + local flag = not ListItem + firstwd = check_first_word(firstwd, first, flag) + end + local ln = slide(first) + local pn = ln.prev + if pn and pn.id == GLUE and pn.subtype == PARFILL then + hyphcount = 0 + orphanflag = false + if pageline == 1 and parline > 1 then + widowflag = true + end + local PFskip = effective_glue(pn,head) + if ShortLines then + local llwd = tex.hsize - PFskip + if llwd < LLminWD then + pageflag = true + local COLOR = luatypo.colortbl[6] + local attr = oberdiek.luacolor.getattribute() + color_line (head, COLOR) + end + end + if BackParindent and PFskip < BackPI and PFskip > BackFuzz then + pageflag = true + local COLOR = luatypo.colortbl[12] + local attr = oberdiek.luacolor.getattribute() + color_line (head, COLOR) + end + if LastWordMatch then + local flag = textline + if PFskip > BackPI then + flag = false + end + lastwd = check_last_word(lastwd, pn, flag) + end + elseif pn and pn.id == DISC then + hyphcount = hyphcount + 1 + if LastWordMatch then + lastwd = check_last_word(lastwd, ln, true) + end + if hyphcount > HYPHmax and RepeatedHyphens then + local COLOR = luatypo.colortbl[2] + local pg = show_pre_disc (pn,COLOR) + pageflag = true + end + if not nextnode and EOPHyphens then + lwhyphflag = true + end + if nextnode and ParLastHyphen then + 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 COLOR = luatypo.colortbl[0] + local pg = show_pre_disc (pn,COLOR) + pageflag = true + end + end + end + else + hyphcount = 0 + if LastWordMatch and pn then + lastwd = check_last_word(lastwd, pn, true) + end + if EOLShortWords then + while pn and pn.id ~= GLYPH and pn.id ~= HLIST do + pn = pn.prev + end + if pn and pn.id == GLYPH then + check_regexpr(pn,line) + end + end + end + if widowflag and Widows then + pageflag = true + widowflag = false + local COLOR = luatypo.colortbl[4] + color_line (head, COLOR) + end + if orphanflag and Orphans then + pageflag = true + local COLOR = luatypo.colortbl[5] + color_line (head, COLOR) + end + if lwhyphflag and EOPHyphens then + pageflag = true + local COLOR = luatypo.colortbl[1] + local pg = show_pre_disc (pn,COLOR) + end + elseif head.id == GLUE and head.subtype == USRSKIP then + vpos = vpos + head.width + if not nextnode and ShortPages and + pageline > 1 and pageline < PAGEmin then + pageshort = true + end + if pageshort and vpos < vpos_min then + pageflag = true + local COLOR = luatypo.colortbl[9] + local n = head + repeat + n = n.prev + until n.id == HLIST and (n.subtype == LINE or n.subtype == EQN) + if n then + color_line(n, COLOR) + end + end + elseif head.id == GLUE then + vpos = vpos + head.width + end + head = nextnode + end + if pageflag then + local pl = luatypo.pagelist + local p = tonumber(string.match(pl, "%s(%d+),%s$")) + if not p or pageno > p then + luatypo.pagelist = luatypo.pagelist .. tostring(pageno) .. ", " + end + end + return true +end +return luatypo.check_page +\end{luacode} +\AtEndOfPackage{% + \directlua{ + if not luatypo.None then + luatexbase.add_to_callback + ("pre_output_filter",luatypo.check_page,"check_page") + end + } +} + +\InputIfFileExists{lua-typo.cfg}% + {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file loaded}}% + {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file not found. + \MessageBreak Providing default values.}% + \definecolor{mygrey}{gray}{0.6}% + \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 + \luatypoSetColor3{red}% Short word at end of line + \luatypoSetColor4{cyan}% Widow + \luatypoSetColor5{cyan}% Orphan + \luatypoSetColor6{cyan}% Paragraph ending on a short line + \luatypoSetColor7{blue}% Overfull lines + \luatypoSetColor8{blue}% Underfull lines + \luatypoSetColor9{red}% Nearly empty page + \luatypoSetColor{10}{myred}% First word matches + \luatypoSetColor{11}{myred}% Last word matches + \luatypoSetColor{12}{mygrey}% Paragraph ending on a nearly full line + \luatypoBackPI=1em\relax + \luatypoBackFuzz=2pt\relax + \luatypoLLminWD=2\parindent\relax + \luatypoStretchMax=200\relax + \luatypoHyphMax=2\relax + \luatypoPageMin=5\relax + \luatypoMinFull=4\relax + \luatypoMinPART=4\relax + }% +%% +%% +%% End of file `lua-typo.sty'. diff --git a/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg index a395e4fe2a5..5ad15f92242 100644 --- a/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg +++ b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg @@ -6,7 +6,9 @@ \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 @@ -26,7 +28,7 @@ \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 @@ -37,10 +39,10 @@ \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'} %% diff --git a/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty index ed88c065c35..5110fb0e605 100644 --- a/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty +++ b/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty @@ -6,9 +6,21 @@ %% %% lua-typo.dtx (with options: `sty') %% -\NeedsTeXFormat{LaTeX2e}[2020/01/01] +%% IMPORTANT NOTICE: +%% For the copyright see the source file `lua-typo.dtx’. +%% \ProvidesPackage{lua-typo} - [2021/03/14 v.0.32 Daniel Flipo] + [2021/05/13 v.0.50 Daniel Flipo] +\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] \ifdefined\directlua \RequirePackage{luatexbase,luacode,luacolor} \RequirePackage{kvoptions,atveryend} @@ -51,6 +63,7 @@ luatypo = { } \DeclareBoolOption[false]{EOLShortWords} \DeclareBoolOption[false]{FirstWordMatch} \DeclareBoolOption[false]{LastWordMatch} +\DeclareBoolOption[false]{FootnoteSplit} \AddToKeyvalOption{luatypo}{All}{% \LT@ShortLinestrue \LT@ShortPagestrue \LT@OverfullLinestrue \LT@UnderfullLinestrue @@ -58,7 +71,7 @@ luatypo = { } \LT@EOPHyphenstrue \LT@RepeatedHyphenstrue \LT@ParLastHyphentrue \LT@EOLShortWordstrue \LT@FirstWordMatchtrue \LT@LastWordMatchtrue - \LT@BackParindenttrue + \LT@BackParindenttrue \LT@FootnoteSplittrue } \ProcessKeyvalOptions{luatypo} \AtEndOfPackage{% @@ -145,6 +158,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 } \ifLT@ShowOptions \GenericWarning{* }{% @@ -166,6 +185,7 @@ luatypo = { } EOLShortWords [false]\MessageBreak FirstWordMatch [false]\MessageBreak LastWordMatch [false]\MessageBreak + FootnoteSplit [false]\MessageBreak \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] @@ -207,6 +227,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} \newcommand*{\luatypoOneChar}[2]{% @@ -254,13 +278,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 @@ -284,10 +303,13 @@ local DISC = node.id("disc") local GLYPH = node.id("glyph") local GLUE = node.id("glue") local KERN = node.id("kern") +local RULE = node.id("rule") local HLIST = node.id("hlist") +local VLIST = node.id("vlist") local LPAR = node.id("local_par") local MKERN = node.id("margin_kern") local PENALTY = node.id("penalty") +local WHATSIT = node.id("whatsit") local USRSKIP = 0 local PARSKIP = 3 local LFTSKIP = 8 @@ -296,10 +318,16 @@ local TOPSKIP = 10 local PARFILL = 15 local LINE = 1 local BOX = 2 +local INDENT = 3 +local ALIGN = 4 +local EQN = 6 local USER = 0 local HYPH = 0x2D local LIGA = 0x102 +local parline = 0 +local dimensions = node.dimensions +local rangedimensions = node.rangedimensions local effective_glue = node.effective_glue local set_attribute = node.set_attribute local slide = node.slide @@ -328,22 +356,64 @@ local color_node = function (node, color) set_attribute(node,attr,color) end end -local color_hbox = function (head, color) - local first = head.head - for n in traverse(first) do - color_node(n, color) - end -end 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) + if n.id == HLIST or n.id == VLIST then + local ff = n.head + for nn in traverse(ff) do + if nn.id == HLIST or nn.id == VLIST then + local f3 = nn.head + for n3 in traverse(f3) do + if n3.id == HLIST or n3.id == VLIST then + local f4 = n3.head + for n4 in traverse(f4) do + if n4.id == HLIST or n4.id == VLIST then + local f5 = n4.head + for n5 in traverse(f5) do + if n5.id == HLIST or n5.id == VLIST then + local f6 = n5.head + for n6 in traverse(f6) do + color_node(n6, color) + end + else + color_node(n5, color) + end + end + else + color_node(n4, color) + end + end + else + color_node(n3, color) + end + end + else + color_node(nn, color) + end + end else color_node(n, color) end end end +log_flaw= function (msg, line, colno, footnote) + local pageno = tex.getcount("c@page") + local prt ="p. " .. pageno + if colno then + prt = prt .. ", col." .. colno + end + if line then + local l = string.format("%2d, ", line) + if footnote then + prt = prt .. ", (ftn.) line " .. l + else + prt = prt .. ", line " .. l + end + end + prt = prt .. msg + luatypo.buffer = luatypo.buffer .. prt .. "\string\n" +end local signature = function (node, string, swap) local n = node local str = string @@ -397,23 +467,44 @@ local signature = function (node, string, swap) end return len, str end -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 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 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) @@ -452,39 +543,66 @@ local check_last_word = function (old, node, color, flag) newsub = nsub end end + pageflag = true newsub = string.gsub(newsub, '^_', '') + local msg = "E.O.L. MATCH=" .. newsub + log_flaw(msg, line, colno, footnote) oldsub = string.reverse(newsub) local newsub = "" 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 -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) @@ -525,25 +643,33 @@ local check_first_word = function (old, node, color, flag) newsub = nsub end end + pageflag = true newsub = string.gsub(newsub, '_$', '') --$ + local msg = "B.O.L. MATCH=" .. newsub + log_flaw(msg, line, colno, footnote) oldsub = newsub local newsub = "" local k = string.len(oldsub) 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 -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 @@ -553,6 +679,9 @@ 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) end end @@ -565,6 +694,9 @@ 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) end @@ -574,10 +706,14 @@ 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) end @@ -585,7 +721,6 @@ local check_regexpr = function (glyph) end end end - return match end local show_pre_disc = function (disc, color) local n = disc @@ -594,112 +729,189 @@ local show_pre_disc = function (disc, color) n = n.prev end return n + end +local get_pagebody = function (head) + local textht = tex.getdimen("textheight") + local fn = head.list + local body = nil + repeat + fn = fn.next + until fn.id == VLIST and fn.height > 0 + first = fn.list + for n in traverse_id(VLIST,first) do + if n.subtype == 0 and n.height == textht then + body = n + break + else + first = n.list + for n in traverse_id(VLIST,first) do + if n.subtype == 0 and n.height == textht then + body = n + break + end + end + end + end + if not body then + texio.write_nl('***lua-typo ERROR: PAGE BODY *NOT* FOUND!***') + end + return body end -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 + n = n.next + if n and n.id == RULE and n.subtype == 0 then + htr = htr + n.height + n = n.next + if n and n.id == KERN and n.subtype == 1 then + htr = htr + n.kern + if htr == 0 then + flag = true + end + end + end + end + return flag +end +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 while head do local nextnode = head.next - local prevnode = head.prev - local pprevnode = nil - if prevnode then - pprevnode = prevnode.prev - end - 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 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 - 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 - local textline = false - if first.id == GLYPH then - textline = true + elseif head.id == HLIST and head.subtype == LINE and + (head.height > 0 or head.depth > 0) then + 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 - if head.glue_set == 1 and head.glue_sign == 2 and - head.glue_order == 0 and OverfullLines then + 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 - 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 + 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 + if not n then + if footnote then + page_bottom = true + else + page_bottom = true + body_bottom = true + end + elseif footnoterule_ahead(n, false) then + body_bottom = true + end + if footnote and page_bottom then + ftnsplit = true + end + while first.id == MKERN or + (first.id == GLUE and first.subtype == LFTSKIP) do + first = first.next + end + local ListItem = false if first.id == LPAR then hyphcount = 0 - parlines = 1 - if not nextnode then + if not footnote then + parline = 1 + end + if body_bottom then orphanflag = true end 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 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 local ln = slide(first) local pn = ln.prev if pn and pn.id == GLUE and pn.subtype == PARFILL then hyphcount = 0 + ftnsplit = false orphanflag = false - if pagelines == 1 and parlines > 1 then + if pageline == 1 and parline > 1 then widowflag = true end local PFskip = effective_glue(pn,head) @@ -707,120 +919,205 @@ luatypo.check_page = function (head) local llwd = tex.hsize - PFskip if llwd < LLminWD then 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() color_line (head, COLOR) end end if BackParindent and PFskip < BackPI and PFskip > BackFuzz then 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 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 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 + 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 lwhyphflag = true end if nextnode and ParLastHyphen then - 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 + 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 if EOLShortWords then - match2 = check_regexpr(pn) - end - if match1 or match2 then - pageflag = true - end - 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) - end - if EOLShortWords then - match2 = check_regexpr(ppn) + while pn and pn.id ~= GLYPH and pn.id ~= HLIST do + pn = pn.prev end - if match1 or match2 then - pageflag = true + if pn and pn.id == GLYPH then + check_regexpr(pn,line) end end - else - hyphcount = 0 end if widowflag and Widows then pageflag = true widowflag = false + local msg = "WIDOW" + log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[4] 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 if lwhyphflag and EOPHyphens then pageflag = true + local msg = "LAST WORD SPLIT" + log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[1] local pg = show_pre_disc (pn,COLOR) end - 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] - local n = head - while n and (n.id ~= HLIST or n.subtype ~= LINE) do - n = n.prev + elseif head.id == HLIST and + (head.subtype == EQN or head.subtype == ALIGN) and + (head.height > 0 or head.depth > 0) then + vpos = vpos + head.height + head.depth + if footnote then + ftnline = ftnline + 1 + line = ftnline + else + pageline = pageline + 1 + line = pageline + end + local fl = true + local wd = 0 + local hmax = 0 + if head.subtype == EQN then + local f = head.list + wd = rangedimensions(head,f) + hmax = head.width + tex.hfuzz + else + wd = head.width + hmax = tex.getdimen("linewidth") + tex.hfuzz + end + if wd > hmax and OverfullLines then + if head.subtype == ALIGN then + local first = head.list + for n in traverse_id(HLIST, first) do + local last = slide(n.list) + if last.id == GLUE and last.subtype == USER then + wd = wd - effective_glue(last,n) + if wd <= hmax then fl = false end + end + end end - if 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 + 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 + elseif head.id == VLIST then + vpos = vpos + head.height + head.depth + elseif body_bottom and head.id == GLUE and head.subtype == 0 then + if first_bot then + if pageline > 1 and pageline < PAGEmin and ShortPages then + pageshort = true end + if pageshort and vpos < vpos_min then + pageflag = true + local msg = "SHORT PAGE: only " .. pageline .. " lines" + log_flaw(msg, line, colno, footnote) + local COLOR = luatypo.colortbl[9] + local n = head + repeat + n = n.prev + until n.id == HLIST + color_line (n, COLOR) + end + first_bot = false end + elseif head.id == GLUE then + vpos = vpos + effective_glue(head,body) end head = nextnode end + return pageflag +end +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 + if first.id == HLIST and first.subtype == 2 then + n2 = first.list + colno = 0 + for n in traverse_id(HLIST, n2) do + if n.id == HLIST and n.subtype == 2 then + n3 = n.list + col = n3.list + colno = colno + 1 + pageflag = check_vtop(col,colno) + end + end + elseif body.id == VLIST and body.subtype == 0 then + col = body.list + pageflag = check_vtop(col,colno) + end if pageflag then local pl = luatypo.pagelist local p = tonumber(string.match(pl, "%s(%d+),%s$")) @@ -836,7 +1133,7 @@ return luatypo.check_page \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 } } @@ -849,7 +1146,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 @@ -860,9 +1157,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 |