From 7df76069beaebbde72a91ad3fd9102eee6857877 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Thu, 4 Mar 2021 22:09:16 +0000 Subject: lua-typo (4mar21) git-svn-id: svn://tug.org/texlive/trunk@58124 c570f23f-e606-0410-a88d-b1316a301751 --- .../source/lualatex/lua-typo/lua-typo.dtx | 1942 ++++++++++++++++++++ 1 file changed, 1942 insertions(+) create mode 100644 Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx (limited to 'Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx') diff --git a/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx b/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx new file mode 100644 index 00000000000..d1a4b0068a8 --- /dev/null +++ b/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx @@ -0,0 +1,1942 @@ +% \CheckSum{337} +% +% \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 +\generate{% + \file{lua-typo.sty}{\from{lua-typo.dtx}{sty}}% {sty,msg,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> +\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|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>} +% +\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 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 œil 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 +% 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}| +% +% 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 & Césures sur trop de lignes consécutives ?\\ +% ParLastHyphen & Césure à 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 ?\\ +% \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 césures 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. +% +% \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 +% 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 possible 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| 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. +% +% \item[RepeatedHyphens :] |\luatypoHyphMax=2| fixe le nombre maximal +% acceptable de lignes consécutives terminées par une césure. +% +% \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{language}|}{'|\meta{list of words}|'}|\\ +% |\luatypoTwoChars{|\meta{language}|}{'|\meta{list of words}|'}| +% +% 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} % 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 +% \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 +% \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! +% +% 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?\\ +% \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}, 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} +% +% 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! +% +% \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| 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[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 +% \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> +\NeedsTeXFormat{LaTeX2e}[2020/01/01] +\ProvidesPackage{lua-typo} +% +%<*dtx> +\ProvidesFile{lua-typo.dtx} +% +%<*dtx|sty> + [2021/03/03 v.0.30 Daniel Flipo] +% +%<*sty> +% \fi +% +% 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} +% \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 +} +\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 +} +% \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 + \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 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: ' + .. luatypo.pagelist) + end + texio.write_nl('***********************************') + texio.write_nl(' ') + }% +\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 nine 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 = "" + +local hyphcount = 0 +local parlines = 0 +local pagelines = 0 +local pageno = 0 +local pageflag = false + +local char_to_discard = { } +char_to_discard[string.byte(",")] = true +char_to_discard[string.byte(".")] = true +char_to_discard[string.byte("!")] = true +char_to_discard[string.byte("?")] = true +char_to_discard[string.byte(":")] = true +char_to_discard[string.byte(";")] = true +char_to_discard[string.byte("-")] = true + +local split_lig = { } +split_lig[0xFB00] = "ff" +split_lig[0xFB01] = "fi" +split_lig[0xFB02] = "fl" +split_lig[0xFB03] = "ffi" +split_lig[0xFB04] = "ffl" + +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") +% \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 +% \end{macrocode} +% Penalty subtypes: +% \begin{macrocode} +local USER = 0 +local HYPH = 0x2D + +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} +% +% 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.id == DISC then + local pre = node.pre + local post = node.post + local replace = 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) +% texio.write_nl('POST=' .. tostring(post.char)) + end + if replace then + set_attribute(replace,attr,color) +% texio.write_nl('REPL=' .. tostring(replace.char)) + end +% texio.write_nl(' ') + else + 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. +% +% \begin{macrocode} +local color_hbox = function (head, color) + local first = head.head + for n in traverse(first) do + color_node(n, color) + end +end +% +% This auxillary function colours a whole line. It requires two +% arguments: a line’s node and a (named) colour. +% +% \begin{macrocode} +local color_line = function (head, color) + local first = head.head + for n in traverse(first) do + if n.id == HLIST and n.subtype == BOX then + color_hbox(n, color) + else + color_node(n, color) + end + end +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. +% +% 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.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+FB04) 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 + end + elseif 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 + local len = string.len(str) + return len, str +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). +% 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). +% +% \begin{macrocode} +local check_last_word = function (old, node, color, flag) + local match = false + local new = "" + local len = 0 + if flag then +% \end{macrocode} +% Get the last glyph one the line, skipping discretionaries, +% margin kerns, etc., unless |flag| cancels the whole process. +% \begin{macrocode} + local swap = true + local lastn = node + while lastn and lastn.id ~= GLYPH and lastn.prev 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 + repeat + len, new = signature (n, new, swap) + n = n.prev + until not n or n.id == GLUE + if n and n.id == GLUE then + new = new .. "_" + len = len + 1 + repeat + n = n.prev + len, new = signature (n, new, swap) + until n.id == GLUE or not n.prev + 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, '%w+_', '') + local newlast = string.gsub (new, '%w+_', '') + len = string.len(newlast) + if len < MinPart then + k = MinPart + 1 + end + local oldsub = string.sub(old,-k) + local newsub = string.sub(new,-k) + local maxlen = string.len(new) + if oldsub == newsub then +% texio.write_nl('EOLnewsub=' .. newsub) + match = true + elseif oldlast == newlast and len >= MinFull then +% texio.write_nl('EOLnewlast=' .. newlast) + match = true + oldsub = oldlast + newsub = newlast + k = len + 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 +% texio.write_nl('EOLfullmatch=' .. newsub) +% texio.write_nl('***MATCH=' .. newsub .. +% " on page " .. pageno) +% texio.write_nl(' ') +% \end{macrocode} +% Lest's colour the matching string. +% \begin{macrocode} + oldsub = string.reverse(newsub) + local newsub = "" + local k = string.len(oldsub) + local n = lastn + repeat + if n and n.id ~= GLUE then + color_node(n, color) + len, newsub = signature(n, newsub, swap) + elseif n then + newsub = newsub .. "_" + len = len + 1 + end + n = n.prev + until not n or newsub == oldsub or len >= k + end + end + return new, match +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, color, flag) + local match = false + local swap = false + local new = "" + local len = 0 + local start = node + local n = start + while n and n.id ~= GLUE do + len, new = signature (n, new, swap) + n = n.next + end + n = n.next + new = new .. "_" + while n and n.id ~= GLUE do + len, new = signature (n, new, swap) + n = n.next + 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 L = MinPart -1 + local oldsub = "" + local newsub = "" + local oldfirst = string.gsub (old, '_%w+', '') + local newfirst = string.gsub (new, '_%w+', '') + len = string.len(newfirst) + if len < MinPart then + k = MinPart + 1 + end + local maxlen = string.len(new) + local oldsub = string.sub(old,1,L) + local newsub = string.sub(new,1,L) + if oldsub == newsub then +% texio.write_nl('BOLnewsub=' .. newsub) + match = true + k = L + elseif oldfirst == newfirst and len >= MinFull then +% texio.write_nl('BOLnewfirst=' .. newfirst) + match = true + oldsub = oldfirst + newsub = newfirst + k = len + 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 + if k < MinPart then + match =false + end + end + if match then +% texio.write_nl('BOLfullmatch=' .. newsub) +% texio.write_nl('***MATCH=' .. newsub .. +% " on page " .. pageno) +% texio.write_nl(' ') +% \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) + len, newsub = signature(n, newsub, swap) + elseif n then + newsub = newsub .. "_" + len = len + 1 + end + n = n.next + until not n or newsub == oldsub or len >= k + end + end + return new, match +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.\\ +% TODO: where does ``out of range’’ starts? U+0100? U+0180? +% +% \begin{macrocode} +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 +% \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 + color_node(glyph,COLOR) +% texio.write_nl('***MATCH=' .. string.char(lchar) .. +% " on page " .. pageno) +% texio.write_nl(' ') + 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 + color_node(previous,COLOR) + color_node(glyph,COLOR) +% texio.write_nl('***MATCH=' .. pattern .. +% " on page " .. pageno) +% texio.write_nl(' ') + 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 + color_node(pprev,COLOR) + color_node(glyph,COLOR) +% texio.write_nl('***MATCH=' .. pattern .. +% " on page " .. pageno) +% 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. +% +% \begin{macrocode} +local show_pre_disc = function (disc, color) + local n = disc + while n.id ~= GLUE do + color_node(n, color) + n = n.prev + end + return n +end +% \end{macrocode} +% +% This is the main function which will be added to the +% |"pre_output_filter"| callback unless option |None| is selected. +% +% \begin{macrocode} +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 Stretch = math.max(luatypo.Stretch/100,1) + + local orphanflag = false + local widowflag = false + local lwhyphflag = false + local match1 = false + local match2 = false + local firstwd = "" + local lastwd = "" + 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 + pageflag = false + match1 = false + pageno = tex.getcount("c@page") + hyphcount = 0 + pagelines = 0 + firstwd = "" + lastwd = "" + 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 + end + pagelines = pagelines + 1 + local ListItem = false +% \end{macrocode} +% Is this line really a text line (one glyph at least)? +% \begin{macrocode} + local textline = true + local n = first + while (n and n.id ~= GLYPH) and + not (n.id == GLUE and n.subtype == RGTSKIP) do + n = n.next + end + if n.id == GLUE then + textline = false + end +% \end{macrocode} +% Is this line overfull or underfull? +% \begin{macrocode} + if head.glue_set == 1 and head.glue_sign == 2 and + head.glue_order == 0 and OverfullLines then +% texio.write_nl('***OVERFULL line on page ' .. pageno) +% texio.write_nl(' ') + 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 +% texio.write_nl('***UNDERFULL line on page ' .. +% tex.getcount("c@page")) +% texio.write_nl(' ') + local COLOR = luatypo.colortbl[8] + pageflag = true + color_line (head, COLOR) + end +% \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, +% \begin{macrocode} + hyphcount = 0 + parlines = 1 + if not nextnode 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. +% \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 + else + parlines = parlines + 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 + 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} +% The paragraph ends with this line, it is not an orphan then… +% \begin{macrocode} + hyphcount = 0 + 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 + 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 = PFskip/65536 +% local llwd_pt = llwd/65536 +% texio.write_nl('PFskip= ' .. PFskip_pt .. ' pt') +% texio.write_nl('llwd= ' .. llwd_pt .. ' pt') +% \end{macrocode} +% |llwd| is the line’s length. Is it too short? +% \begin{macrocode} + if llwd < LLminWD then +% texio.write_nl('***Last line too short, page ' .. pageno) +% texio.write_nl(' ') + pageflag = true + 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 +% texio.write_nl('***Last line nearly full, page ' .. pageno) +% texio.write_nl(' ') + pageflag = true + 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 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 + end + elseif pn and pn.id == DISC then +% \end{macrocode} +% 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 + end + if hyphcount > HYPHmax and RepeatedHyphens then + local COLOR = luatypo.colortbl[2] + local pg = show_pre_disc (pn,COLOR) + pageflag = true +% texio.write_nl('***HYPH issue page ' .. pageno) +% texio.write_nl(' ') + end + if not nextnode and EOPHyphens then +% \end{macrocode} +% No more nodes: this hyphen occurs on the page last line. +% \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 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 + 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. +% \begin{macrocode} + hyphcount = 0 + if LastWordMatch then + local COLOR = luatypo.colortbl[11] + lastwd, match1 = check_last_word(lastwd, pn, COLOR, 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) + end + if EOLShortWords then + match2 = check_regexpr(ppn) + end + if match1 or match2 then + pageflag = true + 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. +% \begin{macrocode} + if widowflag and Widows then + pageflag = true + widowflag = false +% texio.write_nl('***WIDOW on top of page ' .. pageno) +% texio.write_nl(' ') + local COLOR = luatypo.colortbl[4] + color_line (head, COLOR) + end +% \end{macrocode} +% Colour the whole line if is is a orphan. +% \begin{macrocode} + if orphanflag and Orphans then +% texio.write_nl('***ORPHAN at bottom of page ' .. pageno) +% texio.write_nl(' ') + pageflag = true + local COLOR = luatypo.colortbl[5] + color_line (head, COLOR) + end +% \end{macrocode} +% Colour (differently) the last word if hyphenated. +% \begin{macrocode} + if lwhyphflag and EOPHyphens then +% texio.write_nl('***LAST WORD SPLIT page ' .. pageno) +% texio.write_nl(' ') + local COLOR = luatypo.colortbl[1] + local pg = show_pre_disc (pn,COLOR) + pageflag = true + end +% \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… +% \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] +% texio.write_nl('***Only ' .. pagelines .. +% ' lines on page ' .. pageno) +% texio.write_nl(' ') + local node = head + while node.id ~= HLIST or node.subtype ~= LINE do + node = node.prev + end + color_line(node, COLOR) + end + end + head = nextnode + end +% \end{macrocode} +% Add this page number to the summary if any flaw has been found on it. +% \begin{macrocode} + if pageflag then + luatypo.pagelist = luatypo.pagelist .. tostring(pageno) .. ", " + end + return true +end +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’… +% \begin{macrocode} +\AtEndOfPackage{% + \directlua{ + if not luatypo.None then + luatexbase.add_to_callback + ("pre_output_filter",luatypo.check_page,"check_page") + end + } +} +% \end{macrocode} +% +% Load a local config file if present in LaTeX’s search path. +% Otherwise, set reasonnable defaults. +% \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 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{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 +\luatypoLLminWD=2\parindent\relax + +%% 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 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 (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 + +%% Language specific settings (example for French only currently): +%% short words (two letters max) to be avoided at end of lines. +%% French: +%%\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: -- cgit v1.2.3