% \CheckSum{373} % % \iffalse meta-comment % % Copyright © 2020-2021 Daniel Flipo. % % This program can be distributed and/or modified under the terms % of the LaTeX Project Public License either version 1.3c of this % license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This file has the LPPL maintenance status "maintained". % % Lua-Typo package for LaTeX version 2e % % Copyright © 2020-2021 by Daniel Flipo % % Please report errors to: daniel (dot) flipo (at) free (dot) fr % %<*batch> %<*gobble> \ifx\jobname\relax\let\documentclass\undefined\fi \ifx\documentclass\undefined \csname fi\endcsname % \input docstrip.tex \keepsilent \let\MetaPrefix\relax \preamble \endpreamble \postamble \endpostamble \let\MetaPrefix\DoubleperCent \askforoverwritefalse \generate{% \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}}% \file{lua-typo.ltx}{\from{lua-typo.dtx}{driver,doc}}% } \endbatchfile % %<*gobble> \fi \expandafter\ifx\csname @currname\endcsname\empty \csname fi\endcsname % %<*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} \setmainfont{erewhon} \setsansfont{Cabin}[Scale=MatchLowercase] \setmonofont{VeraMono.ttf}[ ItalicFont = VeraMoIt.ttf, BoldFont = VeraMoBd.ttf, BoldItalicFont = VeraMoBI.ttf, Scale = MatchLowercase, HyphenChar=None, Color=Sepia, ] \usepackage[expansion=true, protrusion=true]{microtype} %<-docfr>\usepackage[british]{babel} %<-doc>\usepackage[french]{babel} %<-doc>\frenchsetup{og=«, fg=»} \usepackage[ShortPages, OverfullLines, UnderfullLines, Widows, Orphans, EOPHyphens, RepeatedHyphens ]{lua-typo} \luatypoLLminWD=3em \renewcommand*\descriptionlabel[1]{% \hspace{\labelsep}\texttt{#1}} \usepackage{array,url,verbatim} \usepackage[numbered]{hypdoc} \hypersetup{colorlinks,urlcolor=blue,unicode} % %<-doc>\OnlyDescription %<-doc>\let\FrenchDoc\begingroup\let\endFrenchDoc\endgroup % %<-docfr>\let\FrenchDoc\comment\let\endFrenchDoc\endcomment %<-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}} \newcommand*\opt[1]{\texttt{#1}} \renewcommand\meta[1]{\texttt{\textsl{\color{Sepia}<#1>}}} % \setlength{\parindent}{0pt} \setlength{\parskip}{.3\baselineskip plus 0.3pt minus 0.3pt} \begin{document} \GetFileInfo{lua-typo.sty} \DocInput{lua-typo.dtx} \end{document} %%% Local Variables: %%% coding: utf-8 %%% TeX-engine: luatex %%% End: % %<*gobble> \fi % % \fi % % \begin{FrenchDoc} % \begin{center} % \textbf{\Large Recherche d’imperfections typographiques\\[3pt] % avec LuaLaTeX} % \\[.5\baselineskip]^^A\] % {\large Daniel Flipo}\\ % \texttt{daniel.flipo@free.fr} % \end{center} % % \section{De quoi s’agit-il ?} % % 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 % fichier \file{.log}, permettant un accès rapide aux pages % potentiellement défectueuses. % % Normalement, c’est-à-dire lorsque la justification n’est pas trop % étroite, (Lua)TeX fait du bon travail, mais il peut rester des % points à vérifier, notamment des lignes trop pleines ou lavées % (\emph{Overfull, Underfull box}), des veuves et des orphelines, % des mots coupés en fin de page ou d’alinéa ou sur plusieurs % 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 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. % % \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 humain entrainé peut décider si une ligne % légèrement lavée est acceptable ou non, ou si la suppression % 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 % « presque au point », d’améliorer ce qui peut l’être puis de % \emph{supprimer} l’appel à \pkg{lua-typo} afin de ne pas risquer % de mettre en lumière les imperfections que l’on aura renoncé % à corriger. Pour appliquer toutes les vérifications prévues par % \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}. % % Un grand merci à Jacques André et Thomas Savary pour avoir accepté % de tester les pré-versions et pour leurs retours riches et toujours % pertinents ; leurs suggestions et leurs encouragements ont % grandement contribué à améliorer la première version mise en ligne. % % \section{Utilisation} % % Comme indiqué plus haut la vérification la plus complète s’obtient % par :\\ % |\usepackage[All]{lua-typo}| % % Il est possible de choisir les tests à activer de deux % manières, soit « tout sauf … » soit « seulement ceci et cela ». % Pour tout activer sauf les options \meta{OptX} et \meta{OptY} :\\ % |\usepackage[All, |\meta{OptX}|=false, |\meta{OptY}|=false]{lua-typo}|\\ % ou pour se limiter aux tests \meta{OptX} et \meta{OptY} :\\ % |\usepackage[|\meta{OptX}|, |\meta{OptY}|, |\meta{OptZ}|]{lua-typo}| % % 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 % 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 ?\\ % ShortPages & Page quasi vide (quelques lines) ?\\ % OverfullLines & Ligne trop pleine ?\\ % UnderfullLines & Ligne lavée ? \\ % Widows & Veuve (haut de page) ?\\ % Orphans & Orpheline (bas de page) ?\\ % EOPHyphens & Mot coupé en bas de page ?\\ % 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 % ou creuses, il suffit de coder :\\ % |\usepackage[OverfullLines, UnderfullLines]{lua-typo}|\\ % Pour tout vérifier sauf les coupures répétées en fin de ligne on % codera :\\ % |\usepackage[All, RepeatedHyphens=false]{lua-typo}|\\ % Notez que l’option that \opt{All} doit être la première de la % liste, les suivantes étant rétirées de la liste complète définie % par~\opt{All}. % % Le nom des différentes options n’étant pas facile à mémoriser, il % est possible de les retrouver sans devoir consulter la % documentation ; l’option \opt{ShowOptions} affiche la liste % complète dans le fichier \file{.log} : % |\usepackage[ShowOptions]{lua-typo}| % % L’option \opt{None}, empêche toute vérification : % |\usepackage[None]{lua-typo}| % a pour effet de supprimer complètement tout ajout de code LuaTeX % (aucune fonction n’est ajoutée aux \emph{callbacks} de LuaTeX). % Cette option peut-être utile lors de la toute dernière compilation, % elle n’est pas tout-à-fait équivalente à la mise en commentaire de la % ligne car les variables utilisées par \pkg{luatypo} restent % définies ; si certaines ont été modifiées dans le préambule aucun % message d’erreur du type ``\emph{Undefined Control Sequence}’’ ne % sera émis à leur sujet. % % Terminons par quelques précisions sur ces options. % \begin{description} % \item[FirstWordMatch :] les répétitions en début de ligne dans les % listes ne sont pas signalées. Ceci est voulu car elles résultent % d’un choix délibéré de l’auteur. % \item[LastWordMatch:] le dernier mot d’un alinéa qui se termine % à plus de |1em| (en fait |\luatypoBackPI|) de la marge droite % 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. % \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 coupures dépasse le seuil fixé % (voir ci-dessous), ne sont coloriées que les coupures en excès. % \end{description} % % À 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 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 % le fichier \file{lua-typo.cfg} soit dans le préambule après l’appel % de \pkg{lua-typo} ; les réglages placés dans le préambule prévalent % sur ceux du fichier \file{lua-typo.cfg} qui eux-mêmes prévalent sur % les réglages internes de l’extension. % % Le fichier \file{lua-typo.cfg} fourni avec la distribution reprend % exactement les réglages internes, il se trouve normalement dans le % répertoire \textsc{texmfdist} des distributions TeXLive, MikTeX, etc. % L’utilisateur a la possibilité de recopier ce fichier soit dans son % répertoire de travail pour un document particulier, soit dans son % répertoire \textsc{texmfhome} ou \textsc{texmflocal} et de le % personnaliser comme il l’entend. % % 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 possibles conflits avec d’autres % extensions. % \begin{description} % \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|% % \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 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 un mot coupé. % % \item[UnderfullLines :] |\luatypoStretchMax=200| fixe le % pourcentage maximal acceptable pour l’étirement des % espaces-mots, au-delà la ligne est déclarée lavée. % La valeur donnée doit être un entier supérieur ou égal à~100, % cette valeur 100 correspond à l’étirement maximal prévu par la % fonte (|\fontdimen3|) ; avec ce réglage attendez-vous à trouver % une kyrielle de lignes les creuses ! En fait la valeur par défaut % (200) correspond approximativement à ce que TeX, avec les % réglages par défaut (|\tolerance=200|, |\hbadness=1000|), % considère comme \emph{Underfull hbox}. % % \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 % déclenchant l’avertissement. % Avec ce réglage (3 et 4), seront détectées deux lignes se % terminant par « cible» et « invincible » (quatre lettres en % commun), ainsi que la présence de « mon » en début ou fin de % deux lignes consécutives (trois lettres en commun). % % \item[EOLShortWords:] cette option signale la présence en fin de % 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{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, % il y a deux lignes (non actives) prévues pour le français :\\ % |\luatypoOneChar{french}{'À à Ô'}|\\ % |\luatypoTwoChars{french}{'Je Tu Il On'}| % % Deux contraintes sont à respecter lorsqu’on veut % personnaliser ces listes :\\ % a) le premier argument (langue) \emph{doit être connu de} % \pkg{babel}, aussi les commandes |\luatypoOneChar| et % |\luatypoTwoChars|, si elles sont utilisées, doivent l’être % \emph{après} le chargement de \pkg{babel}, une bonne habitude % à prendre est donc de toujours charger \pkg{lua-typo} % \emph{après} \pkg{babel} ; % b) le second argument \emph{doit être une chaîne de % caractères}, donc entourée de simples ou doubles % \emph{quotes} \textsc{ascii} et composées de mots séparés % par des espaces comme dans les exemples ci-dessus. % \end{description} % % À chacune des vérifications faites par \pkg{lua-typo} peut être % attachée une couleur spécifique pour mettre en évidence les % imperfections détectées. % Actuellement, seulement cinq couleurs sont utilisées par défaut, % voici leur définition dans \file{lua-typo.cfg} : % \begin{verbatim} % \definecolor{mygrey}{gray}{0.6} % \definecolor{myred}{rgb}{1,0.55,0} % \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 % \luatypoSetColor7{mygrey} % Ligne trop pleine % \luatypoSetColor8{mygrey} % Ligne creuse % \luatypoSetColor9{red} % Page presque vide (qq. lignes) % \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 % être utilisées ; pour en définir de nouvelles, il faut donc soit % utiliser la commande |\definecolor| de l’extension \pkg{color} % (comme ci-dessus pour |mygrey| ou |myred|), soit charger l’extension % \pkg{xcolor} package qui donne accès à une kyrielle de noms de % couleurs. % \end{FrenchDoc} % % \StopEventually{} % % \begin{center} % \textbf{\Large Highlighting Typographical Flaws with LuaLaTeX} % \\[.5\baselineskip]^^A\] % {\large Daniel Flipo}\\ % \texttt{daniel.flipo@free.fr} % \end{center} % % \section{What is it about?} % % The file \file{\filename}\footnote{The file described in this % section has version number \fileversion\ and was last revised on % \filedate.}, is meant for careful writers and proofreaders who do % not feel totally satisfied with LaTeX output, the most frequent % issues being widows and orphans, hyphenated words split across two % pages, consecutive lines ending with hyphens, paragraphs ending % on too short or nearly full lines, homeoarchy, etc. % % This package, which works with LuaLaTeX only, % \emph{does not try to correct anything} but just highlights % potential issues (the offending lines or end of lines are printed % in colour) and provides at the end of the \file{.log} file a summary % of pages to be checked and manually corrected if possible. % My understanding is that automatic correction often introduces new % issues (underflow/overfull lines) when fixing one of the flaws % mentionned above, human correction providing much better results. % For completeness, overfull and underfull lines are also coloured % (in grey by default) and mentionned in the summary provided at the % end of the \file{.log} file. % % I suggest to add a call |\usepackage[All]{lua-typo}| to the % preamble of a document which is ``nearly finished’’ % \emph{and to remove it} once all possible corrections have been % 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). % % I am very grateful to Jacques André and Thomas Savary, who kindly % tested my beta versions, providing much valuable feedback and % suggesting many improvements for the first released version. % Special thanks to both of them! % % \section{Usage} % % The easiest way to trigger all checks perfomed by \pkg{lua-typo} % is:\\ % |\usepackage[All]{lua-typo}| % % It is possible to enable or disable some checks through boolean % options passed to \pkg{lua-typo}; % you may want to perform all checks except a few, then % \pkg{lua-typo} should be loaded this way:\\ % |\usepackage[All, |\meta{OptX}|=false, |\meta{OptY}|=false]{lua-typo}|\\ % or to enable just a few checks, then do it this way:\\ % |\usepackage[|\meta{OptX}|, |\meta{OptY}|, |\meta{OptZ}|]{lua-typo}| % % \pagebreak % Here is the full list of possible checks (name and purpose):\\[12pt] % \begin{tabular}{>{\ttfamily}ll} % \multicolumn{1}{l}{Name} & Glitch to highlight\\ \hline % All & Turns all options to \opt{true}\\ % BackParindent & paragraph’s last line \emph{nearly} full?\\ % ShortLines & paragraph’s last line too short?\\ % ShortPages & nearly empty page (just a few lines)?\\ % OverfullLines & overfull lines?\\ % UnderfullLines & underfull lines?\\ % Widows & widows (top 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?\\ % 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:\\ % |\usepackage[OverfullLines, UnderfullLines]{lua-typo}|\\ % If you want everything to be checked except paragraphs ending on % a short line try:\\ % |\usepackage[All, ShortLines=false]{lua-typo}|\\ % please note that \opt{All} has to be the first one, as options are % 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}, % 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 % 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. % % Please be aware of the following features: % \begin{description} % \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’’ % 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. % \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} % \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} % % Some of the checks mentionned above require tuning, for % instance, when is a last paragraph’s length called too short? % how many hyphens ending consecutive lines are acceptable? % \pkg{lua-typo} provides user customisable parameters to set % what is regarded as acceptable or not. % % 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 % this file into their working directory (or \textsc{texmfhome} or % \textsc{texmflocal}) and tune the defaults according to their own % taste. % % It is also possible to provide defaults directly in the % document’s preamble (this overwrites the corresponding settings % done in the configuration file found on TeX’s search path: current % directory, then \textsc{texmfhome}, \textsc{texmflocal} and % finally \textsc{texmfdist}. % % 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 % touch the right margin (actually end at less than % |\luatypoBackFuzz|, default |2pt|, from it) or leave at least % |\luatypoBackPI|, default |1em|, between its end and the right % margin. % % \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). 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. % % \item[UnderfullLines:] |\luatypoStretchMax=200| sets the maximum % acceptable percentage of stretch acceptable before a line is % tagged by \pkg{lua-typo} as underfull; it must be an integer % over 100, 100 means that the slightest stretch exceeding the % font tolerance (|\fontdimen3|) will be warned about (be % prepared for a lot of ``underfull lines’’ with this setting), % the default value 200 is just below what triggers TeX’s % ``Underfull hbox’’ message (when |\tolerance=200| and % |\hbadness=1000|). % % \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 % 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 % ``erfull’’ will be highlighted. % % \item[EOLShortWords:] this check deals with lines ending with % very short words (one or two characters), not all of them but % a user selected list depending on the current language.\\ % |\luatypoOneChar{|\meta{language}|}{'|\meta{list of words}|'}|\\ % |\luatypoTwoChars{|\meta{language}|}{'|\meta{list of words}|'}| % % Currently, defaults (commented out) are suggested for the French % language only:\\ % |\luatypoOneChar{french}{'À à Ô'}|\\ % |\luatypoTwoChars{french}{'Je Tu Il On'}| % % Feel free to customise these lists for French or to add your % own shorts words for other languages but remember that % a) the first argument (language name) \emph{must be known by} % \pkg{babel}, so if you add |\luatypoOneChar| or % |\luatypoTwoChars| commands, please make sure that % \pkg{lua-typo} is loaded \emph{after} \pkg{babel}; % b) the second argument \emph{must be a string} % (\emph{i.e.} surrounded by single or double \textsc{ascii} % quotes) made of your words separated by spaces. % % \end{description} % % It is possible to define a specific colour for each % typographic flaws that \pkg{lua-typo} deals with. % Currently, only five colours are used in \file{lua-typo.cfg}: % \begin{verbatim} % \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 on 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{mygrey} % Overfull lines % \luatypoSetColor8{mygrey} % Underfull lines % \luatypoSetColor9{red} % Nearly empty page (a few lines) % \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 % \pkg{xcolor} package which provides a bunch of named colours. % % \clearpage % \section{\TeX{}nical details} % % \iffalse %<*sty> %% IMPORTANT NOTICE: %% For the copyright see the source file `lua-typo.dtx’. %% \ProvidesPackage{lua-typo} % %<*dtx> \ProvidesFile{lua-typo.dtx} % %<*dtx|sty> [2021/05/13 v.0.50 Daniel Flipo] % %<*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}. % % \begin{macrocode} \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 % \end{macrocode} % % Let’s define the necessary internal counters, dimens, token % registers and commands… % % \begin{macrocode} \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 % \end{macrocode} % … and define a global table for this package. % \begin{macrocode} \begin{luacode} luatypo = { } \end{luacode} % \end{macrocode} % % Set up \pkg{kvoptions} initializations. % % \begin{macrocode} \SetupKeyvalOptions{ family=luatypo, prefix=LT@, } \DeclareBoolOption[false]{ShowOptions} \DeclareBoolOption[false]{None} \DeclareBoolOption[false]{All} \DeclareBoolOption[false]{BackParindent} \DeclareBoolOption[false]{ShortLines} \DeclareBoolOption[false]{ShortPages} \DeclareBoolOption[false]{OverfullLines} \DeclareBoolOption[false]{UnderfullLines} \DeclareBoolOption[false]{Widows} \DeclareBoolOption[false]{Orphans} \DeclareBoolOption[false]{EOPHyphens} \DeclareBoolOption[false]{RepeatedHyphens} \DeclareBoolOption[false]{ParLastHyphen} \DeclareBoolOption[false]{EOLShortWords} \DeclareBoolOption[false]{FirstWordMatch} \DeclareBoolOption[false]{LastWordMatch} \DeclareBoolOption[false]{FootnoteSplit} % \end{macrocode} % Option \opt{All} resets all booleans relative to specific % typographic checks to \opt{true}. % \begin{macrocode} \AddToKeyvalOption{luatypo}{All}{% \LT@ShortLinestrue \LT@ShortPagestrue \LT@OverfullLinestrue \LT@UnderfullLinestrue \LT@Widowstrue \LT@Orphanstrue \LT@EOPHyphenstrue \LT@RepeatedHyphenstrue \LT@ParLastHyphentrue \LT@EOLShortWordstrue \LT@FirstWordMatchtrue \LT@LastWordMatchtrue \LT@BackParindenttrue \LT@FootnoteSplittrue } \ProcessKeyvalOptions{luatypo} % \end{macrocode} % % Forward these options to the |luatypo| global table. % Wait until the config file \file{lua-typo.cfg} has been read % in order to give it a chance of overruling the boolean options. % This enables the user to permanently change the defaults. % % \begin{macrocode} \AtEndOfPackage{% \ifLT@None \directlua{ luatypo.None = true }% \else \directlua{ luatypo.None = false }% \fi \ifLT@BackParindent \advance\luatypo@options by 1 \directlua{ luatypo.BackParindent = true }% \else \directlua{ luatypo.BackParindent = false }% \fi \ifLT@ShortLines \advance\luatypo@options by 1 \directlua{ luatypo.ShortLines = true }% \else \directlua{ luatypo.ShortLines = false }% \fi \ifLT@ShortPages \advance\luatypo@options by 1 \directlua{ luatypo.ShortPages = true }% \else \directlua{ luatypo.ShortPages = false }% \fi \ifLT@OverfullLines \advance\luatypo@options by 1 \directlua{ luatypo.OverfullLines = true }% \else \directlua{ luatypo.OverfullLines = false }% \fi \ifLT@UnderfullLines \advance\luatypo@options by 1 \directlua{ luatypo.UnderfullLines = true }% \else \directlua{ luatypo.UnderfullLines = false }% \fi \ifLT@Widows \advance\luatypo@options by 1 \directlua{ luatypo.Widows = true }% \else \directlua{ luatypo.Widows = false }% \fi \ifLT@Orphans \advance\luatypo@options by 1 \directlua{ luatypo.Orphans = true }% \else \directlua{ luatypo.Orphans = false }% \fi \ifLT@EOPHyphens \advance\luatypo@options by 1 \directlua{ luatypo.EOPHyphens = true }% \else \directlua{ luatypo.EOPHyphens = false }% \fi \ifLT@RepeatedHyphens \advance\luatypo@options by 1 \directlua{ luatypo.RepeatedHyphens = true }% \else \directlua{ luatypo.RepeatedHyphens = false }% \fi \ifLT@ParLastHyphen \advance\luatypo@options by 1 \directlua{ luatypo.ParLastHyphen = true }% \else \directlua{ luatypo.ParLastHyphen = false }% \fi \ifLT@EOLShortWords \advance\luatypo@options by 1 \directlua{ luatypo.EOLShortWords = true }% \else \directlua{ luatypo.EOLShortWords = false }% \fi \ifLT@FirstWordMatch \advance\luatypo@options by 1 \directlua{ luatypo.FirstWordMatch = true }% \else \directlua{ luatypo.FirstWordMatch = false }% \fi \ifLT@LastWordMatch \advance\luatypo@options by 1 \directlua{ luatypo.LastWordMatch = true }% \else \directlua{ luatypo.LastWordMatch = false }% \fi \ifLT@FootnoteSplit \advance\luatypo@options by 1 \directlua{ luatypo.FootnoteSplit = true }% \else \directlua{ luatypo.FootnoteSplit = false }% \fi } % \end{macrocode} % % |ShowOptions| is specific: % % \begin{macrocode} \ifLT@ShowOptions \GenericWarning{* }{% *** List of possible options for lua-typo ***\MessageBreak [Default values between brackets]% \MessageBreak ShowOptions [false]\MessageBreak None [false]\MessageBreak BackParindent [false]\MessageBreak ShortLines [false]\MessageBreak ShortPages [false]\MessageBreak OverfullLines [false]\MessageBreak UnderfullLines [false]\MessageBreak Widows [false]\MessageBreak Orphans [false]\MessageBreak EOPHyphens [false]\MessageBreak RepeatedHyphens [false]\MessageBreak ParLastHyphen [false]\MessageBreak EOLShortWords [false]\MessageBreak FirstWordMatch [false]\MessageBreak LastWordMatch [false]\MessageBreak FootnoteSplit [false]\MessageBreak \MessageBreak *********************************************% \MessageBreak Lua-typo [ShowOptions] }% \fi % \end{macrocode} % % Some defaut values which can be customised in the preamble % are forwarded to Lua AtBeginDocument. % % \begin{macrocode} \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 }% } % \end{macrocode} % % Print the summary of offending pages ---if any--- at the % (very) end of document and write the report file on disc, % unless option |None| has been selected. % % \begin{macrocode} \AtVeryEndDocument{% \ifnum\luatypo@options = 0 \LT@Nonetrue \fi \ifLT@None \directlua{ texio.write_nl(' ') texio.write_nl('***********************************') texio.write_nl('*** lua-typo loaded with NO option:') texio.write_nl('*** NO CHECK PERFORMED! ***') texio.write_nl('***********************************') texio.write_nl(' ') }% \else \directlua{ texio.write_nl(' ') texio.write_nl('*************************************') if luatypo.pagelist == "" then texio.write_nl('*** lua-typo: No Typo Flaws found.') else texio.write_nl('*** lua-typo: WARNING *************') texio.write_nl('The following pages need attention: ') texio.write(luatypo.pagelist) end texio.write_nl('***********************************') texio.write_nl(' ') local fileout= tex.jobname .. ".typo" local out=io.open(fileout,"w+") out:write(luatypo.buffer) io.close(out) }% \fi} % \end{macrocode} % % \begin{macro}{\luatypoOneChar} % \begin{macro}{\luatypoTwoChars} % These commands set which short words should be avoided at end of % lines. The first argument is a language name, say \opt{french}, % which is turned into a command |\l@french| expanding to a number % known by luatex, otherwise an error message occurs. % The UTF8 string entered as second argument has to be converted % into the font internal coding. % \begin{macrocode} \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 % texio.write_nl("SINGLE=" .. luatypo.single[langno]) % texio.write_nl(' ') }% \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 % texio.write_nl("DOUBLE=" .. luatypo.double[langno]) % texio.write_nl(' ') }% \else \PackageWarning{luatypo}{Unknown language "\luatypo@LANG", \MessageBreak \protect\luatypoTwoChars\space command ignored}% \fi} % \end{macrocode} % \end{macro} % \end{macro} % % \vspace*{\baselineskip} % \begin{macro}{\luatypoSetColor} % This is a user-level command to customise the colours highlighting % 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 % attributes). % \begin{macrocode} \newcommand*{\luatypoSetColor}[2]{% \begingroup \color{#2}% \directlua{luatypo.colortbl[#1]=\the\LuaCol@Attribute}% \endgroup } % \end{macrocode} % \end{macro} % % The Lua code now, initialisations. % % \begin{macrocode} \begin{luacode} luatypo.single = { } luatypo.double = { } luatypo.colortbl = { } luatypo.pagelist = "" 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 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 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") % \end{macrocode} % Glue subtypes: % \begin{macrocode} local USRSKIP = 0 local PARSKIP = 3 local LFTSKIP = 8 local RGTSKIP = 9 local TOPSKIP = 10 local PARFILL = 15 % \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} local USER = 0 local HYPH = 0x2D % \end{macrocode} % 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 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 % \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. % % \begin{macrocode} 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) % texio.write_nl('PRE=' .. tostring(pre.char)) end if post then set_attribute(post,attr,color) % if pre then % texio.write(' POST=' .. tostring(post.char)) % else % texio.write_nl('POST=' .. tostring(post.char)) % end end if repl then set_attribute(repl,attr,color) % if pre or post then % texio.write(' REPL=' .. tostring(repl.char)) % else % texio.write_nl('REPL=' .. tostring(repl.char)) % end end % if pre or post or repl then % texio.write_nl(' ') % end elseif node then set_attribute(node,attr,color) end end % \end{macrocode} % % This auxillary function colours a whole line. It requires two % 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_line = function (head, color) local first = head.head for n in traverse(first) do if n.id == HLIST or n.id == VLIST then local ff = n.head for nn in traverse(ff) do if nn.id == HLIST or nn.id == VLIST then local f3 = nn.head for n3 in traverse(f3) do if n3.id == HLIST or n3.id == VLIST then local f4 = n3.head for n4 in traverse(f4) do if n4.id == HLIST or n4.id == VLIST then local f5 = n4.head for n5 in traverse(f5) do if n5.id == HLIST or n5.id == VLIST then local f6 = n5.head for n6 in traverse(f6) do color_node(n6, color) end else color_node(n5, color) end end else color_node(n4, color) end end else color_node(n3, color) end end else color_node(nn, color) end end else color_node(n, color) end end end % \end{macrocode} % % This function appends a line to a buffer which will be written % to file `\cs{jobname.typo}’; it takes four arguments: % a string, two numbers (which can be \texttt{nil}) and a flag. % % \changes{v0.50}{2021/05/13}{Summary of flaws written to file % `\cs{jobname.typo}’.} % % \begin{macrocode} 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.} % 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 % 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 % backwards (see |check_last_word|). % \begin{macrocode} local signature = function (node, string, swap) local n = node local str = string if n and n.id == GLYPH then local b, id = is_glyph(n) if b and not char_to_discard[b] then % \end{macrocode} % Punctuation has to be discarded; the French apostrophe % (right quote U+2019) has a char code ``out of range'', % we replace it with U+0027; % Other glyphs should have char codes less than 0x100 (or 0x180?) or % be ligatures… standard ones (U+FB00 to U+FB06) are converted using % table |split_lig|. % \begin{macrocode} if b == 0x2019 then b = 0x27 end if b < 0x100 then str = str .. string.char(b) elseif split_lig[b] then local c = split_lig[b] if swap then c = string.reverse(c) end str = str .. c % \end{macrocode} % Experimental: store other ligatures as the last two digits of their % decimal code… % \begin{macrocode} elseif n.subtype == LIGA and b > 0xE000 then local c = string.sub(b,-2) if swap then c = string.reverse(c) end str = str .. c end end elseif n and n.id == DISC then % \end{macrocode} % Ligatures are split into |pre| and |post| and both parts are % stored. In case of \emph{ffl, ffi}, the post part is also % a ligature… % \begin{macrocode} local pre = n.pre local post = n.post local c1 = "" local c2 = "" if pre and pre.char and pre.char ~= HYPH and pre.char < 0x100 then c1 = string.char(pre.char) 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 % \end{macrocode} % The returned length is the number of \emph{letters}. % \begin{macrocode} local len = string.len(str) if string.find(str, "_") then len = len - 1 end return len, str end % \end{macrocode} % % This auxillary function looks for consecutive lines ending with the % same letters. % 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, 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 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) until not n or n.id == GLUE end new = string.reverse(new) % texio.write_nl('EOLsigold=' .. old) % texio.write(' EOLsig=' .. new) local MinFull = luatypo.MinFull local MinPart = luatypo.MinPart MinFull = math.min(MinPart,MinFull) local k = MinPart local oldlast = string.gsub (old, '.*_', '') local newlast = string.gsub (new, '.*_', '') local i = string.find(new, "_") if i and i > maxlen - MinPart + 1 then k = MinPart + 1 end local oldsub = string.sub(old,-k) local newsub = string.sub(new,-k) local l = string.len(new) if oldsub == newsub and l >= k then % texio.write_nl('EOLnewsub=' .. newsub) match = true elseif oldlast == newlast and string.len(newlast) >= MinFull then % texio.write_nl('EOLnewlast=' .. newlast) match = true oldsub = oldlast newsub = newlast k = string.len(newlast) end if match then % \end{macrocode} % Minimal partial match; any more glyphs matching? % \begin{macrocode} local osub = oldsub local nsub = newsub while osub == nsub and k <= maxlen do k = k +1 osub = string.sub(old,-k) nsub = string.sub(new,-k) if osub == nsub then newsub = nsub end end pageflag = true newsub = string.gsub(newsub, '^_', '') % texio.write_nl('EOLfullmatch=' .. newsub) local msg = "E.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) % \end{macrocode} % Lest's colour the matching string. % \begin{macrocode} oldsub = string.reverse(newsub) local newsub = "" local n = lastn repeat if n and n.id ~= GLUE then color_node(n, COLOR) l, newsub = signature(n, newsub, swap) elseif n and n.id == GLUE then newsub = newsub .. "_" elseif not n and box then n = box else break end n = n.prev until newsub == oldsub or l >= k end end 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. % % \begin{macrocode} 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 n = node local box, go while n and n.id ~= GLYPH and n.id ~= DISC and (n.id ~= HLIST or n.subtype == INDENT) do n = n.next end local start = n if n and n.id == HLIST then box = n start = n.head n = n.head end while n and n.id ~= GLUE do maxlen, new = signature (n, new, swap) n = n.next end if n and n.id == GLUE then new = new .. "_" go = true elseif box and not n then local bn = box.next if bn.id == GLUE then new = new .. "_" n = bn else n = box end go = true end if go then repeat n = n.next maxlen, new = signature (n, new, swap) until not n or n.id == GLUE end % texio.write_nl('BOLsigold=' .. old) % texio.write(' BOLsig=' .. new) % \end{macrocode} % When called with flag |false|, |check_first_word| returns % the first word’s signature, but doesn’t compare it with the % previous line’s. % \begin{macrocode} 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 % texio.write_nl('BOLnewsub=' .. newsub) match = true elseif oldfirst == newfirst and string.len(newfirst) >= MinFull then % texio.write_nl('BOLnewfirst=' .. newfirst) match = true oldsub = oldfirst newsub = newfirst k = string.len(newfirst) end if match then % \end{macrocode} % Minimal partial match; any more glyphs matching? % \begin{macrocode} local osub = oldsub local nsub = newsub while osub == nsub and k <= maxlen do k = k + 1 osub = string.sub(old,1,k) nsub = string.sub(new,1,k) if osub == nsub then newsub = nsub end end pageflag = true newsub = string.gsub(newsub, '_$', '') --$ % texio.write_nl('BOLfullmatch=' .. newsub) local msg = "B.O.L. MATCH=" .. newsub log_flaw(msg, line, colno, footnote) % \end{macrocode} % Lest’s colour the matching string. % \begin{macrocode} 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 and n.id == GLUE then newsub = newsub .. "_" elseif not n and box then n = box else break end n = n.next until newsub == oldsub or l >= k end end return new 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 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, line) local pageno = tex.getcount("c@page") local COLOR = luatypo.colortbl[3] local lang = glyph.lang local match = false local lchar, id = is_glyph(glyph) local previous = glyph.prev % \end{macrocode} % First look for single chars unless the list of words is empty. % \begin{macrocode} if lang and luatypo.single[lang] then % \end{macrocode} % For single char words, the previous node is a glue. % \begin{macrocode} if lchar and lchar < 0x100 and previous and previous.id == GLUE then match = string.find(luatypo.single[lang], string.char(lchar)) if match then pageflag = true local msg = "RGX MATCH=" .. string.char(lchar) log_flaw(msg, line, colno, footnote) color_node(glyph,COLOR) end end end % \end{macrocode} % Look for two chars words unless the list of words is empty. % \begin{macrocode} 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 % \end{macrocode} % For two chars words, the previous node is a glue… % \begin{macrocode} if pchar and pchar < 0x100 and pprev and pprev.id == GLUE then local pattern = string.char(pchar) .. string.char(lchar) match = string.find(luatypo.double[lang], pattern) if match then pageflag = true local msg = "RGX MATCH=" .. pattern log_flaw(msg, line, colno, footnote) color_node(previous,COLOR) color_node(glyph,COLOR) end end % \end{macrocode} % …unless a kern is found between the two chars. % \begin{macrocode} 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 local msg = "RGX MATCH=" .. pattern log_flaw(msg, line, colno, footnote) color_node(pprev,COLOR) color_node(glyph,COLOR) end end end end end 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. % % \begin{macrocode} 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 % \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 % texio.write_nl(' ht=' .. fn.height/65536 .. 'pt') % 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 % texio.write_nl(' BODY: ' .. n.height/65536 .. 'pt') body = n break else % texio.write_nl(' ht=' .. n.height/65536 .. 'pt') % 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 % 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 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} 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 % if debug then % ht1 = string.format("%.2fpt", n.kern/65536) % end n = n.next if n and n.id == RULE and n.subtype == 0 then htr = htr + n.height % if debug then % ht2 = string.format("%.2fpt", n.height/65536) % end n = n.next if n and n.id == KERN and n.subtype == 1 then htr = htr + n.kern % if debug then % ht3 = string.format("%.2fpt", n.kern/65536) % texio.write_nl(' ') % texio.write_nl('KERN height: ' .. ht1) % texio.write(' RULE height: ' .. ht2) % texio.write(' KERN height: ' .. ht3) % texio.write_nl('TOTAL height: ' .. htr .. 'sp') % end if htr == 0 then flag = true % if debug then % texio.write(' => footnoterule found!') % 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 RepeatedHyphens = luatypo.RepeatedHyphens 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 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 % \end{macrocode} % 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 = "" else vpos = vpos + head.kern end elseif head.id == HLIST and head.subtype == LINE and (head.height > 0 or head.depth > 0) then % \end{macrocode} % This is a text line, increment counters |pageline| or % |ftnline| and |line| (for |log_flaw|). % \begin{macrocode} if footnote then ftnline = ftnline + 1 line = ftnline else pageline = pageline + 1 line = pageline end % \end{macrocode} % 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} 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 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… Reset |parline| except in footnotes % (|parline| and |pageline| counts are for ``body'' \emph{only}, % they are frozen in footnotes). % \begin{macrocode} hyphcount = 0 if not footnote then parline = 1 end if body_bottom then % \end{macrocode} % 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. % \begin{macrocode} local nn = first.next if nn and nn.id == HLIST and nn.subtype == BOX then ListItem = true end elseif not footnote then parline = parline + 1 end % \end{macrocode} % Let’s track lines beginning with the same word (except lists). % \begin{macrocode} if FirstWordMatch then local flag = not ListItem firstwd = check_first_word(firstwd, first, line, flag) end % \end{macrocode} % Let’s check the end of line: |ln| (usually a rightskip) and |pn| % are the last two nodes. % \begin{macrocode} local ln = slide(first) local pn = ln.prev if pn and pn.id == GLUE and pn.subtype == PARFILL then % \end{macrocode} % 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 pageline == 1 and parline > 1 then widowflag = true end % \end{macrocode} % |PFskip| is the rubber length (in sp) added to complete the line. % \begin{macrocode} local PFskip = effective_glue(pn,head) if ShortLines then local llwd = tex.hsize - PFskip % local PFskip_pt = string.format("%.1fpt", PFskip/65536) % local llwd_pt = string.format("%.1fpt", llwd/65536) % texio.write_nl('PFskip= ' .. PFskip_pt) % texio.write(' llwd= ' .. llwd_pt) % \end{macrocode} % |llwd| is the line’s length. Is it too short? % \begin{macrocode} if llwd < LLminWD then pageflag = true 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. % \begin{macrocode} color_line (head, COLOR) end end % \end{macrocode} % Is this line nearly full? (ending too close to the right margin) % \begin{macrocode} if BackParindent and PFskip < BackPI and PFskip > BackFuzz then pageflag = true 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 % \end{macrocode} % Does the last word and the one on the previous line match? % \begin{macrocode} if LastWordMatch then local flag = textline if PFskip > BackPI then flag = false end lastwd = check_last_word(lastwd, pn, line, flag) end elseif pn and pn.id == DISC then % \end{macrocode} % CASE 2: the current line ends with an hyphen. % \begin{macrocode} hyphcount = hyphcount + 1 if LastWordMatch then 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 (page_bottom or body_bottom) and EOPHyphens then % \end{macrocode} % This hyphen occurs on the page’s last line (body or footnote). % \begin{macrocode} lwhyphflag = true end if nextnode and ParLastHyphen then % \end{macrocode} % Does the next line end the current paragraph? If so, |nextnode| is % 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 nn = nextnode.next local nnn = nil if nn and nn.next then nnn = nn.next if nnn.id == HLIST and nnn.subtype == LINE and nnn.glue_order == 2 then pageflag = true local msg = "HYPHEN on next to last line" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[0] local pg = show_pre_disc (pn,COLOR) end end end % \end{macrocode} % 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 and pn then lastwd = check_last_word(lastwd, pn, line, 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 % \end{macrocode} % Colour the whole line if is is a widow. % \begin{macrocode} 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 % \end{macrocode} % Colour the whole line if is is a orphan or footenote continuing % on the next page. % \begin{macrocode} if orphanflag and Orphans then pageflag = true local msg = "ORPHAN" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[5] color_line (head, COLOR) end if ftnsplit and FootnoteSplit then pageflag = true local msg = "FOOTNOTE SPLIT" log_flaw(msg, line, colno, footnote) local COLOR = luatypo.colortbl[13] color_line (head, COLOR) end % \end{macrocode} % Colour (differently) the last word if hyphenated. % \begin{macrocode} 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 head.id == HLIST and (head.subtype == EQN or head.subtype == ALIGN) and (head.height > 0 or head.depth > 0) then % \end{macrocode} % % \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} 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 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 % local vpos_pt = string.format("%.1fpt", vpos/65536) % local vmin_pt = string.format("%.1fpt", vpos_min/65536) % texio.write_nl('pageline=' .. pageline) % texio.write_nl('vpos=' .. vpos_pt) % texio.write(' vpos_min=' .. vmin_pt) % if page_bottom then % local tht = tex.getdimen("textheight") % local tht_pt = string.format("%.1fpt", tht/65536) % texio.write(' textheight=' .. tht_pt) % end % 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 % texio.write_nl('body.id=' .. tostring(node.type(body.id))) % texio.write('-' .. body.subtype) % texio.write_nl(' ') % texio.write_nl('first.id=' .. tostring(node.type(first.id))) % texio.write('-' .. first.subtype) % 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 % texio.write_nl('n.id=' .. tostring(node.type(n.id))) % texio.write('-' .. n.subtype) % texio.write(' ht=' .. n.height) % texio.write_nl(' ') if n.id == HLIST and n.subtype == 2 then n3 = n.list % texio.write_nl('n3.id=' .. tostring(node.type(n3.id))) % texio.write('-' .. n3.subtype) % texio.write(' ht=' .. n3.height) % 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 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} % \end{macrocode} % % 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_shipout_filter",luatypo.check_page,"check_page",1) end } } % \end{macrocode} % % Load a local config file if present in LaTeX’s search path.\\ % Otherwise, set reasonnable defaults. % \enlargethispage*{\baselineskip} % \begin{macrocode} \InputIfFileExists{lua-typo.cfg}% {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file loaded}}% {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file not found. \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 on to many consecutive lines \luatypoSetColor3{red}% Short word at end of line \luatypoSetColor4{cyan}% Widow \luatypoSetColor5{cyan}% Orphan \luatypoSetColor6{cyan}% Paragraph ending on a short line \luatypoSetColor7{blue}% Overfull lines \luatypoSetColor8{blue}% Underfull lines \luatypoSetColor9{red}% Nearly empty page \luatypoSetColor{10}{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 \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax \else\luatypoLLminWD=2\parindent\relax\fi \luatypoStretchMax=200\relax \luatypoHyphMax=2\relax \luatypoPageMin=5\relax \luatypoMinFull=4\relax \luatypoMinPART=4\relax }% % \end{macrocode} % \iffalse % % \fi % \clearpage % \section{Configuration file} % % \iffalse %<*cfg> % \fi % % \let\theCodelineNo\relax % \begin{macrocode} %%% Configuration file for lua-typo.sty %%% These settings can also be overruled in the preamble. %% Minimum gap between end of paragraphs’ last lines and the right margin \luatypoBackPI=1em\relax \luatypoBackFuzz=2pt\relax %% Minimum length of paragraphs’ last lines \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax \else \luatypoLLminWD=2\parindent\relax \fi %% Maximum number of consecutive hyphenated lines \luatypoHyphMax=2\relax %% Nearly empty pages: minimum number of lines \luatypoPageMin=5\relax %% Maximum acceptable stretch before a line is tagged as Underfull \luatypoStretchMax=200\relax %% Minimum number of matching characters for words at begin/end of line \luatypoMinFull=3\relax \luatypoMinPart=4\relax %% Default colours = red, cyan, mygrey \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 on to many consecutive lines \luatypoSetColor3{red} % Short word at end of line \luatypoSetColor4{cyan} % Widow \luatypoSetColor5{cyan} % Orphan \luatypoSetColor6{cyan} % Paragraph ending on a short line \luatypoSetColor7{blue} % Overfull lines \luatypoSetColor8{blue} % Underfull lines \luatypoSetColor9{red} % Nearly empty page (just a few lines) \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): %% short words (two letters max) to be avoided at end of lines. %%\luatypoOneChar{french}{'À à Ô'} %%\luatypoTwoChars{french}{'Je Tu Il On'} % \end{macrocode} % %\iffalse % % \fi % % \iffalse %<*dtx> % \fi %% %% \CharacterTable %% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z %% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z %% Digits \0\1\2\3\4\5\6\7\8\9 %% Exclamation \! Double quote \" Hash (number) \# %% Dollar \$ Percent \% Ampersand \& %% Acute accent \' Left paren \( Right paren \) %% Asterisk \* Plus \+ Comma \, %% Minus \- Point \. Solidus \/ %% Colon \: Semicolon \; Less than \< %% Equals \= Greater than \> Question mark \? %% Commercial at \@ Left bracket \[ Backslash \\ %% Right bracket \] Circumflex \^ Underscore \_ %% Grave accent \` Left brace \{ Vertical bar \| %% Right brace \} Tilde \~} %% % \iffalse % % \fi % % \Finale \endinput %%% Local Variables: %%% fill-column: 72 %%% coding: utf-8 %%% TeX-engine: luatex %%% End: