texlive[66495] Master/texmf-dist: lua-typo (9mar23)

commits+karl at tug.org commits+karl at tug.org
Thu Mar 9 22:11:15 CET 2023


Revision: 66495
          http://tug.org/svn/texlive?view=revision&revision=66495
Author:   karl
Date:     2023-03-09 22:11:15 +0100 (Thu, 09 Mar 2023)
Log Message:
-----------
lua-typo (9mar23)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/README.md
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx
    trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf
    trunk/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx
    trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg
    trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/lualatex/lua-typo/README.md	2023-03-09 21:11:02 UTC (rev 66494)
+++ trunk/Master/texmf-dist/doc/lualatex/lua-typo/README.md	2023-03-09 21:11:15 UTC (rev 66495)
@@ -81,8 +81,13 @@
 
 * v.0.61: 
   - bug fixes and documentation enhanced.
-  - colours `mygrey` and `myred` renamed `LTgrey` and `LTred`.
+  - colours `mygrey` and `myred` renamed as `LTgrey` and `LTred`.
 
+* v.0.65: 
+  - new option `ShortFinalWord` to detect short end-of-sentence word
+    on top of next page.
+  - code cleaning.
+
 --
 Copyright 2020--2023 Daniel Flipo
 E-mail: daniel (dot) flipo (at) free (dot) fr

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-demo.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx
===================================================================
--- trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx	2023-03-09 21:11:02 UTC (rev 66494)
+++ trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.ltx	2023-03-09 21:11:15 UTC (rev 66495)
@@ -1,24 +1,23 @@
-\RequirePackage{pdfmanagement-testphase}
-\DeclareDocumentMetadata{pdfstandard=A-2b, lang=fr-FR}
-\documentclass[a4paper]{ltxdoc}
-\usepackage[dvipsnames]{xcolor}
+\DocumentMetadata{pdfstandard=A-2b, lang=fr-FR}
+\documentclass[a4paper,french]{ltxdoc}
 \usepackage{fontspec}
 \setmainfont{erewhon}
 \setsansfont{Cabin}[Scale=MatchLowercase]
 \setmonofont{VeraMono.ttf}[
   Scale = MatchLowercase,
-  ItalicFont        = VeraMoIt.ttf,
-  BoldFont          = VeraMoBd.ttf,
-  BoldItalicFont    = VeraMoBI.ttf,
-  HyphenChar=None,   Color=Sepia,
+  ItalicFont     = VeraMoIt.ttf,
+  BoldFont       = VeraMoBd.ttf,
+  BoldItalicFont = VeraMoBI.ttf,
+  HyphenChar=None,
+  Color=5D1D00,
   ]
 \usepackage[expansion=true, protrusion=true]{microtype}
-\usepackage[french]{babel}
+\usepackage{babel,varioref}
 \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}
@@ -31,7 +30,7 @@
 \newcommand*\file[1]{\texttt{#1}}
 \newcommand*\pkg[1]{\texttt{#1}}
 \newcommand*\opt[1]{\texttt{#1}}
-\renewcommand\meta[1]{\texttt{\textsl{\color{Sepia}<#1>}}}
+\renewcommand\meta[1]{\texttt{\textsl{<#1>}}}
 \newcommand*\node[1]{\textsc{#1}}
 \setlength{\parindent}{0pt}
 \setlength{\parskip}{.3\baselineskip plus 0.3pt minus 0.3pt}

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo-fr.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx
===================================================================
--- trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx	2023-03-09 21:11:02 UTC (rev 66494)
+++ trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.ltx	2023-03-09 21:11:15 UTC (rev 66495)
@@ -1,23 +1,22 @@
-\RequirePackage{pdfmanagement-testphase}
-\DeclareDocumentMetadata{pdfstandard=A-2b, lang=en-GB}
-\documentclass[a4paper]{ltxdoc}
-\usepackage[dvipsnames]{xcolor}
+\DocumentMetadata{pdfstandard=A-2b, lang=en-GB}
+\documentclass[a4paper,british]{ltxdoc}
 \usepackage{fontspec}
 \setmainfont{erewhon}
 \setsansfont{Cabin}[Scale=MatchLowercase]
 \setmonofont{VeraMono.ttf}[
   Scale = MatchLowercase,
-  ItalicFont        = VeraMoIt.ttf,
-  BoldFont          = VeraMoBd.ttf,
-  BoldItalicFont    = VeraMoBI.ttf,
-  HyphenChar=None,   Color=Sepia,
+  ItalicFont     = VeraMoIt.ttf,
+  BoldFont       = VeraMoBd.ttf,
+  BoldItalicFont = VeraMoBI.ttf,
+  HyphenChar=None,
+  Color=5D1D00,
   ]
 \usepackage[expansion=true, protrusion=true]{microtype}
-\usepackage[british]{babel}
+\usepackage{babel,varioref}
+
 \usepackage[ShortPages, OverfullLines, UnderfullLines,
             Widows, Orphans, EOPHyphens, RepeatedHyphens
            ]{lua-typo}
-\luatypoLLminWD=3em
 \renewcommand*\descriptionlabel[1]{%
    \hspace{\labelsep}\texttt{#1}}
 \usepackage{array,url,verbatim}
@@ -38,7 +37,7 @@
 \newcommand*\file[1]{\texttt{#1}}
 \newcommand*\pkg[1]{\texttt{#1}}
 \newcommand*\opt[1]{\texttt{#1}}
-\renewcommand\meta[1]{\texttt{\textsl{\color{Sepia}<#1>}}}
+\renewcommand\meta[1]{\texttt{\textsl{<#1>}}}
 \newcommand*\node[1]{\textsc{#1}}
 \setlength{\parindent}{0pt}
 \setlength{\parskip}{.3\baselineskip plus 0.3pt minus 0.3pt}

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-typo/lua-typo.pdf
===================================================================
(Binary files differ)

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

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg	2023-03-09 21:11:02 UTC (rev 66494)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg	2023-03-09 21:11:15 UTC (rev 66495)
@@ -23,6 +23,10 @@
 \luatypoMinFull=3\relax
 \luatypoMinPart=4\relax
 
+%% Minimum number of characters for the first word on a page if it ends
+%% a sentence.
+\luatypoMinLen=4\relax
+
 %% Default colours = red, cyan, LTgrey
 \definecolor{LTgrey}{gray}{0.6}
 \definecolor{LTred}{rgb}{1,0.55,0}
@@ -40,11 +44,12 @@
 \luatypoSetColor{11}{LTred} % Last word matches
 \luatypoSetColor{12}{LTgrey}% Paragraph ending on a nearly full line
 \luatypoSetColor{13}{cyan}  % Footnote split
+\luatypoSetColor{14}{red}   % Too short first (final) word on the page
 
 %% Language specific settings (example for French):
 %% short words (two letters max) to be avoided at end of lines.
-%%\luatypoOneChar{french}{'À Ô Y'}
-%%\luatypoTwoChars{french}{'Je Tu Il On Au De'}
+%%\luatypoOneChar{french}{"À Ô Y"}
+%%\luatypoTwoChars{french}{"Je Tu Il On Au De"}
 %% 
 %%
 %% End of file `lua-typo.cfg'.

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty	2023-03-09 21:11:02 UTC (rev 66494)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty	2023-03-09 21:11:15 UTC (rev 66495)
@@ -11,10 +11,10 @@
 %%
 \NeedsTeXFormat{LaTeX2e}[2021/06/01]
 \ProvidesPackage{lua-typo}
-                [2023-02-10 v.0.61 Daniel Flipo]
+                [2023-03-08 v.0.65 Daniel Flipo]
 \ifdefined\DeclareRelease
   \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty}
-  \DeclareCurrentRelease{}{2023-02-04}
+  \DeclareCurrentRelease{}{2023-03-08}
 \else
   \PackageWarning{lua-typo}{Your LaTeX kernel is too old to provide
     access\MessageBreak to former versions of the lua-typo package.%
@@ -37,6 +37,7 @@
 \newcount\luatypoPageMin
 \newcount\luatypoMinFull
 \newcount\luatypoMinPart
+\newcount\luatypoMinLen
 \newcount\luatypo at LANGno
 \newcount\luatypo at options
 \newtoks\luatypo at single
@@ -65,6 +66,7 @@
 \DeclareBoolOption[false]{FirstWordMatch}
 \DeclareBoolOption[false]{LastWordMatch}
 \DeclareBoolOption[false]{FootnoteSplit}
+\DeclareBoolOption[false]{ShortFinalWord}
 \AddToKeyvalOption{luatypo}{All}{%
   \LT at ShortLinestrue     \LT at ShortPagestrue
   \LT at OverfullLinestrue  \LT at UnderfullLinestrue
@@ -73,6 +75,7 @@
   \LT at ParLastHyphentrue  \LT at EOLShortWordstrue
   \LT at FirstWordMatchtrue \LT at LastWordMatchtrue
   \LT at BackParindenttrue  \LT at FootnoteSplittrue
+  \LT at ShortFinalWordtrue
 }
 \ProcessKeyvalOptions{luatypo}
 \AtEndOfPackage{%
@@ -165,6 +168,12 @@
   \else
     \directlua{ luatypo.FootnoteSplit = false }%
   \fi
+  \ifLT at ShortFinalWord
+    \advance\luatypo at options by 1
+    \directlua{ luatypo.ShortFinalWord = true }%
+  \else
+    \directlua{ luatypo.ShortFinalWord = false }%
+  \fi
 }
 \ifLT at ShowOptions
   \GenericWarning{* }{%
@@ -187,6 +196,7 @@
      FirstWordMatch  [false]\MessageBreak
      LastWordMatch   [false]\MessageBreak
      FootnoteSplit   [false]\MessageBreak
+     ShortFinalWord  [false]\MessageBreak
      \MessageBreak
      *********************************************%
      \MessageBreak Lua-typo [ShowOptions]
@@ -199,6 +209,7 @@
     luatypo.Stretch = tex.count.luatypoStretchMax
     luatypo.MinFull = tex.count.luatypoMinFull
     luatypo.MinPart = tex.count.luatypoMinPart
+    luatypo.MinLen  = tex.count.luatypoMinLen
     luatypo.LLminWD = tex.dimen.luatypoLLminWD
     luatypo.BackPI  = tex.dimen.luatypoBackPI
     luatypo.BackFuzz  = tex.dimen.luatypoBackFuzz
@@ -243,7 +254,7 @@
       local string = \the\luatypo at single
       luatypo.single[langno] = " "
       for p, c in utf8.codes(string) do
-        local s = string.char(c)
+        local s = utf8.char(c)
         luatypo.single[langno] = luatypo.single[langno] .. s
       end
      }%
@@ -260,7 +271,7 @@
       local string = \the\luatypo at double
       luatypo.double[langno] = " "
       for p, c in utf8.codes(string) do
-        local s = string.char(c)
+        local s = utf8.char(c)
         luatypo.double[langno] = luatypo.double[langno] .. s
       end
     }%
@@ -291,14 +302,11 @@
 char_to_discard[string.byte(";")] = true
 char_to_discard[string.byte("-")] = true
 
-local split_lig = { }
-split_lig[0xFB00] = "ff"
-split_lig[0xFB01] = "fi"
-split_lig[0xFB02] = "fl"
-split_lig[0xFB03] = "ffi"
-split_lig[0xFB04] = "ffl"
-split_lig[0xFB05] = "st"
-split_lig[0xFB06] = "st"
+local eow_char = { }
+eow_char[string.byte(".")] = true
+eow_char[string.byte("!")] = true
+eow_char[string.byte("?")] = true
+eow_char[utf8.codepoint("…")] = true
 
 local DISC  = node.id("disc")
 local GLYPH = node.id("glyph")
@@ -326,7 +334,6 @@
 local HYPH = 0x2D
 local LIGA = 0x102
 local parline = 0
-
 local dimensions = node.dimensions
 local rangedimensions = node.rangedimensions
 local effective_glue = node.effective_glue
@@ -337,7 +344,18 @@
 local has_field = node.has_field
 local uses_font = node.uses_font
 local is_glyph  = node.is_glyph
-
+local utf8_find = unicode.utf8.find
+local utf8_gsub = unicode.utf8.gsub
+local utf8_reverse = function (s)
+  if utf8.len(s) > 1 then
+     local so = ""
+     for p, c in utf8.codes(s) do
+         so = utf8.char(c) .. so
+     end
+     s = so
+  end
+  return s
+end
 local color_node = function (node, color)
   local attr = oberdiek.luacolor.getattribute()
   if node and node.id == DISC then
@@ -419,61 +437,64 @@
   local n = node
   local str = string
   if n and n.id == GLYPH then
-    local b, id = is_glyph(n)
-    if b and not char_to_discard[b] then
-       if b == 0x2019 then b = 0x27 end
-       if b < 0x100 then
-          str = str .. string.char(b)
-       elseif split_lig[b] then
-          local c = split_lig[b]
-          if swap then
-             c = string.reverse(c)
-          end
-          str = str .. c
-       elseif n.subtype == LIGA and b > 0xE000 then
-          local c = string.sub(b,-2)
-          if swap then
-             c = string.reverse(c)
-          end
-          str = str .. c
-       end
-    end
+     local b = n.char
+     if b and not char_to_discard[b] then
+        if n.components then
+           local c = ""
+           for nn in traverse_id(GLYPH, n.components) do
+             c = c .. utf8.char(nn.char)
+           end
+           if swap then
+              str = str .. utf8_reverse(c)
+           else
+              str = str .. c
+           end
+        else
+           str = str .. utf8.char(b)
+        end
+     end
   elseif n and n.id == DISC then
     local pre = n.pre
     local post = n.post
     local c1 = ""
     local c2 = ""
-    if pre and pre.char and pre.char ~= HYPH and pre.char < 0x100 then
-       c1 = string.char(pre.char)
+    if pre and pre.char then
+       if pre.components then
+          for nn in traverse_id(GLYPH, post.components) do
+            c1 = c1 .. utf8.char(nn.char)
+          end
+       else
+          c1 = utf8.char(pre.char)
+       end
+       c1 = utf8_gsub(c1, "-", "")
     end
     if post and post.char then
-       if post.char < 0x100 then
-          c2 = string.char(post.char)
-       elseif split_lig[post.char] then
-          c2 = split_lig[post.char]
-          if swap then
-             c2 = string.reverse(c2)
+       if post.components then
+          for nn in traverse_id(GLYPH, post.components) do
+            c2 = c2 .. utf8.char(nn.char)
           end
+       else
+          c2 = utf8.char(post.char)
        end
     end
     if swap then
-       str = str .. c2 .. c1
+       str = str .. utf8_reverse(c2) .. c1
     else
        str = str .. c1 .. c2
     end
   end
-  local len = string.len(str)
-  if string.find(str, "_") then
+  local len = utf8.len(str)
+  if utf8_find(str, "_") then
      len = len - 1
   end
   return len, str
 end
-local check_last_word = function (old, node, line, colno, flag)
+local check_line_last_word = function (old, node, line, colno, flag)
   local COLOR = luatypo.colortbl[11]
   local match = false
   local new = ""
   local maxlen = 0
-  if flag and node then
+  if node then
      local swap = true
      local box, go
      local lastn = node
@@ -511,63 +532,80 @@
           maxlen, new = signature (n, new, swap)
         until not n or n.id == GLUE
      end
-     new = string.reverse(new)
-     local MinFull = luatypo.MinFull
-     local MinPart = luatypo.MinPart
-     MinFull = math.min(MinPart,MinFull)
-     local k = MinPart
-     local oldlast = string.gsub (old, '.*_', '')
-     local newlast = string.gsub (new, '.*_', '')
-     local i = string.find(new, "_")
-     if i and i > maxlen - MinPart + 1 then
-        k = MinPart + 1
-     end
-     local oldsub = string.sub(old,-k)
-     local newsub = string.sub(new,-k)
-     local l = string.len(new)
-     if oldsub == newsub and l >= k then
-        match = true
-     elseif oldlast == newlast and string.len(newlast) >= MinFull then
-        match = true
-        oldsub = oldlast
-        newsub = newlast
-        k = string.len(newlast)
-     end
-     if match then
-        local osub = oldsub
-        local nsub = newsub
-        while osub == nsub and k <= maxlen do
-          k = k +1
-          osub = string.sub(old,-k)
-          nsub = string.sub(new,-k)
-          if osub == nsub then
-             newsub = nsub
-          end
+     new = utf8_reverse(new)
+     if flag then
+        local MinFull = luatypo.MinFull
+        local MinPart = luatypo.MinPart
+        MinFull = math.min(MinPart,MinFull)
+        local k = MinPart
+        local dlo = utf8_reverse(old)
+        local wen = utf8_reverse(new)
+        local oldlast = utf8_gsub (old, ".*_", "_")
+        local newlast = utf8_gsub (new, ".*_", "_")
+        local i
+        if utf8_find(newlast, "_") then
+           i = utf8.len(newlast)
         end
-        newsub = string.gsub(newsub, '^_', '')
-        local msg = "E.O.L. MATCH=" .. newsub
-        log_flaw(msg, line, colno, footnote)
-        oldsub = string.reverse(newsub)
+        if i and i > maxlen - MinPart + 1 then
+           k = MinPart + 1
+        end
+        local oldsub = ""
         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
+        for p, c in utf8.codes(dlo) do
+          if utf8.len(oldsub) < k then
+             oldsub = utf8.char(c) .. oldsub
           end
-          n = n.prev
-        until newsub == oldsub or l >= k
+        end
+        for p, c in utf8.codes(wen) do
+          if utf8.len(newsub) < k then
+             newsub = utf8.char(c) .. newsub
+          end
+        end
+        local l = utf8.len(new)
+        if oldsub == newsub and l >= k then
+           match = true
+        elseif oldlast == newlast and utf8.len(newlast) > MinFull then
+           match = true
+           oldsub = oldlast
+           newsub = newlast
+           k = utf8.len(newlast)
+        end
+        if match then
+           local osub = oldsub
+           local nsub = newsub
+           while osub == nsub and k <= maxlen do
+             k = k +1
+             osub = string.sub(old,-k)
+             nsub = string.sub(new,-k)
+             if osub == nsub then
+                newsub = nsub
+             end
+           end
+           newsub = utf8_gsub(newsub, "^_", "")
+           local msg = "E.O.L. MATCH=" .. newsub
+           log_flaw(msg, line, colno, footnote)
+           oldsub = utf8_reverse(newsub)
+           local newsub = ""
+           local n = lastn
+           repeat
+             if n and n.id ~= GLUE then
+                color_node(n, COLOR)
+                l, newsub = signature(n, newsub, swap)
+             elseif n and n.id == GLUE then
+                newsub = newsub .. "_"
+             elseif not n and box then
+                n = box
+             else
+                break
+             end
+             n = n.prev
+           until newsub == oldsub or l >= k
+        end
      end
   end
   return new, match
 end
-local check_first_word = function (old, node, line, colno, flag)
+local check_line_first_word = function (old, node, line, colno, flag)
   local COLOR = luatypo.colortbl[10]
   local match = false
   local swap = false
@@ -613,24 +651,31 @@
      local MinPart = luatypo.MinPart
      MinFull = math.min(MinPart,MinFull)
      local k = MinPart
-     local oldsub = ""
-     local newsub = ""
-     local oldfirst = string.gsub (old, '_.*', '')
-     local newfirst = string.gsub (new, '_.*', '')
-     local i = string.find(new, "_")
+     local oldfirst = utf8_gsub (old, "_.*", "_")
+     local newfirst = utf8_gsub (new, "_.*", "_")
+     local i
+     if utf8_find(newfirst, "_") then
+        i = utf8.len(newfirst)
+     end
      if i and i <= MinPart then
         k = MinPart + 1
      end
-     local oldsub = string.sub(old,1,k)
-     local newsub = string.sub(new,1,k)
-     local l = string.len(newsub)
+     local oldsub = ""
+     local newsub = ""
+     for p, c in utf8.codes(old) do
+       if utf8.len(oldsub) < k then oldsub = oldsub .. utf8.char(c) end
+     end
+     for p, c in utf8.codes(new) do
+       if utf8.len(newsub) < k then newsub = newsub .. utf8.char(c) end
+     end
+     local l = utf8.len(newsub)
      if oldsub == newsub and l >= k then
         match = true
-     elseif oldfirst == newfirst  and string.len(newfirst) >= MinFull then
+     elseif oldfirst == newfirst  and utf8.len(newfirst) > MinFull then
         match = true
         oldsub = oldfirst
         newsub = newfirst
-        k = string.len(newfirst)
+        k = utf8.len(newfirst)
      end
      if match then
         local osub = oldsub
@@ -643,12 +688,12 @@
              newsub = nsub
           end
         end
-        newsub = string.gsub(newsub, '_$', '')   --$
+        newsub = utf8_gsub(newsub, "_$", "")   --$
         local msg = "B.O.L. MATCH=" .. newsub
         log_flaw(msg, line, colno, footnote)
         oldsub = newsub
         local newsub = ""
-        local k = string.len(oldsub)
+        local k = utf8.len(oldsub)
         local n = start
         repeat
           if n and n.id ~= GLUE then
@@ -667,6 +712,54 @@
   end
   return new, match
 end
+local check_page_first_word = function (node, colno)
+  local COLOR = luatypo.colortbl[14]
+  local match = false
+  local swap = false
+  local new = ""
+  local maxlen = luatypo.MinLen
+  local len = 0
+  local n = node
+  local pn
+  while n and n.id ~= GLYPH and n.id ~= DISC and
+        (n.id ~= HLIST or n.subtype == INDENT) do
+     n = n.next
+  end
+  local start = n
+  if n and n.id == HLIST then
+     start = n.head
+     n = n.head
+  end
+  repeat
+    len, new = signature (n, new, swap)
+    n = n.next
+  until len > maxlen or (n and n.id == GLYPH and eow_char[n.char]) or
+        (n and n.id == GLUE) or
+        (n and n.id == KERN and n.subtype == 1)
+  if n and (n.id == GLUE or n.id == KERN) then
+     pn = n
+     n = n.next
+  end
+  if len <= maxlen and n and n.id == GLYPH and eow_char[n.char] then
+     match =true
+     if pn and (pn.id == GLUE or pn.id == KERN) then
+        new = new .. " "
+        len = len + 1
+     end
+     len = len + 1
+  end
+   if match then
+     local msg = "ShortFinalWord=" .. new
+     log_flaw(msg, 1, colno, false)
+     local n = start
+     repeat
+       color_node(n, COLOR)
+       n = n.next
+     until eow_char[n.char]
+     color_node(n, COLOR)
+  end
+  return match
+end
 local check_regexpr = function (glyph, line, colno)
   local COLOR = luatypo.colortbl[3]
   local lang = glyph.lang
@@ -675,11 +768,11 @@
   local lchar, id = is_glyph(glyph)
   local previous = glyph.prev
   if lang and luatypo.single[lang] then
-     if lchar and lchar < 0x100 and previous and previous.id == GLUE then
-        match = string.find(luatypo.single[lang], string.char(lchar))
+     if lchar and previous and previous.id == GLUE then
+        match = utf8_find(luatypo.single[lang], utf8.char(lchar))
         if match then
            retflag = true
-           local msg = "RGX MATCH=" .. string.char(lchar)
+           local msg = "RGX MATCH=" .. utf8.char(lchar)
            log_flaw(msg, line, colno, footnote)
            color_node(glyph,COLOR)
         end
@@ -689,9 +782,9 @@
      if lchar and previous and previous.id == GLYPH then
         local pchar, id = is_glyph(previous)
         local pprev = previous.prev
-        if pchar and pchar < 0x100 and pprev and pprev.id == GLUE then
-           local pattern = string.char(pchar) .. string.char(lchar)
-           match = string.find(luatypo.double[lang], pattern)
+        if pchar and pprev and pprev.id == GLUE then
+           local pattern = utf8.char(pchar) .. utf8.char(lchar)
+           match = utf8_find(luatypo.double[lang], pattern)
            if match then
               retflag = true
               local msg = "RGX MATCH=" .. pattern
@@ -705,10 +798,9 @@
         if pprev and pprev.id == GLYPH then
            local pchar, id = is_glyph(pprev)
            local ppprev = pprev.prev
-           if pchar and pchar < 0x100 and
-              ppprev and ppprev.id == GLUE then
-              local pattern = string.char(pchar) .. string.char(lchar)
-              match = string.find(luatypo.double[lang], pattern)
+           if pchar and ppprev and ppprev.id == GLUE then
+              local pattern = utf8.char(pchar) .. utf8.char(lchar)
+              match = utf8_find(luatypo.double[lang], pattern)
               if match then
                  retflag = true
                  local msg = "REGEXP MATCH=" .. pattern
@@ -776,7 +868,7 @@
       end
   end
   if not body then
-     texio.write_nl('***lua-typo ERROR: PAGE BODY *NOT* FOUND!***')
+     texio.write_nl("***lua-typo ERROR: PAGE BODY *NOT* FOUND!***")
   end
   return body
 end
@@ -800,6 +892,7 @@
   local EOLShortWords   = luatypo.EOLShortWords
   local LastWordMatch   = luatypo.LastWordMatch
   local FootnoteSplit   = luatypo.FootnoteSplit
+  local ShortFinalWord  = luatypo.ShortFinalWord
   local Stretch  = math.max(luatypo.Stretch/100,1)
   local blskip   = tex.getglue("baselineskip")
   local vpos_min = PAGEmin * blskip
@@ -828,6 +921,7 @@
        vpos = vpos + head.height + head.depth
        local linewd = head.width
        local first = head.head
+       local ListItem = false
        if footnote then
           ftnline = ftnline + 1
           line = ftnline
@@ -871,9 +965,10 @@
              (first.id == GLUE and first.subtype == LFTSKIP) do
           first = first.next
        end
-       local ListItem = false
        if first.id == LPAR then
           hyphcount = 0
+          firstwd = ""
+          lastwd = ""
           if not footnote then
              parline = 1
              if body_bottom then
@@ -930,7 +1025,7 @@
           if FirstWordMatch then
              local flag = not ListItem
              firstwd, flag =
-                check_first_word(firstwd, first, line, colno, flag)
+                check_line_first_word(firstwd, first, line, colno, flag)
              if flag then
                 pageflag = true
              end
@@ -941,7 +1036,7 @@
                 flag = false
              end
              lastwd, flag =
-                check_last_word(lastwd, pn, line, colno, flag)
+                check_line_last_word(lastwd, pn, line, colno, flag)
              if flag then
                 pageflag = true
              end
@@ -948,39 +1043,39 @@
           end
        elseif pn and pn.id == DISC then
           hyphcount = hyphcount + 1
-       if orphanflag and Orphans then
-          pageflag = true
-          local msg = "ORPHAN"
-          log_flaw(msg, line, colno, footnote)
-          local COLOR = luatypo.colortbl[5]
-          color_line (head, COLOR)
-       end
-       if ftnsplit and FootnoteSplit then
-          pageflag = true
-          local msg = "FOOTNOTE SPLIT"
-          log_flaw(msg, line, colno, footnote)
-          local COLOR = luatypo.colortbl[13]
-          color_line (head, COLOR)
-       end
-       if (page_bottom or body_bottom) and EOPHyphens then
-          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
+          if orphanflag and Orphans then
+             pageflag = true
+             local msg = "ORPHAN"
+             log_flaw(msg, line, colno, footnote)
+             local COLOR = luatypo.colortbl[5]
+             color_line (head, COLOR)
+          end
+          if ftnsplit and FootnoteSplit then
+             pageflag = true
+             local msg = "FOOTNOTE SPLIT"
+             log_flaw(msg, line, colno, footnote)
+             local COLOR = luatypo.colortbl[13]
+             color_line (head, COLOR)
+          end
+          if (page_bottom or body_bottom) and EOPHyphens then
+             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
           if FirstWordMatch then
              local flag = not ListItem
              firstwd, flag =
-                check_first_word(firstwd, first, line, colno, flag)
+                check_line_first_word(firstwd, first, line, colno, flag)
              if flag then
                 pageflag = true
              end
           end
           if LastWordMatch then
-             local flag = false
+             local flag = true
              lastwd, flag =
-                check_last_word(lastwd, ln, line, colno, true)
+                check_line_last_word(lastwd, ln, line, colno, flag)
              if flag then
                 pageflag = true
              end
@@ -1009,32 +1104,32 @@
           end
        else
           hyphcount = 0
-       if orphanflag and Orphans then
-          pageflag = true
-          local msg = "ORPHAN"
-          log_flaw(msg, line, colno, footnote)
-          local COLOR = luatypo.colortbl[5]
-          color_line (head, COLOR)
-       end
-       if ftnsplit and FootnoteSplit then
-          pageflag = true
-          local msg = "FOOTNOTE SPLIT"
-          log_flaw(msg, line, colno, footnote)
-          local COLOR = luatypo.colortbl[13]
-          color_line (head, COLOR)
-       end
+          if orphanflag and Orphans then
+             pageflag = true
+             local msg = "ORPHAN"
+             log_flaw(msg, line, colno, footnote)
+             local COLOR = luatypo.colortbl[5]
+             color_line (head, COLOR)
+          end
+          if ftnsplit and FootnoteSplit then
+             pageflag = true
+             local msg = "FOOTNOTE SPLIT"
+             log_flaw(msg, line, colno, footnote)
+             local COLOR = luatypo.colortbl[13]
+             color_line (head, COLOR)
+          end
           if FirstWordMatch then
              local flag = not ListItem
              firstwd, flag =
-                check_first_word(firstwd, first, line, colno, flag)
+                check_line_first_word(firstwd, first, line, colno, flag)
              if flag then
                 pageflag = true
              end
           end
           if LastWordMatch and pn then
-             local flag
+             local flag = true
              lastwd, flag =
-                check_last_word(lastwd, pn, line, colno, true)
+                check_line_last_word(lastwd, pn, line, colno, flag)
              if flag then
                 pageflag = true
              end
@@ -1050,6 +1145,10 @@
              end
           end
        end
+       if ShortFinalWord and pageline == 1 and parline > 1 and
+          check_page_first_word(first,colno) then
+          pageflag = true
+       end
     elseif head.id == HLIST and
           (head.subtype == EQN or head.subtype == ALIGN) and
           (head.height > 0 or head.depth > 0) then
@@ -1209,10 +1308,9 @@
     end
   }%
 }
-
 \InputIfFileExists{lua-typo.cfg}%
-   {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file loaded}}%
-   {\PackageInfo{lua-typo.sty}{'lua-typo.cfg' file not found.
+   {\PackageInfo{lua-typo.sty}{"lua-typo.cfg" file loaded}}%
+   {\PackageInfo{lua-typo.sty}{"lua-typo.cfg" file not found.
                                \MessageBreak Providing default values.}%
     \definecolor{LTgrey}{gray}{0.6}%
     \definecolor{LTred}{rgb}{1,0.55,0}
@@ -1230,6 +1328,7 @@
     \luatypoSetColor{11}{LTred}%  Last word matches
     \luatypoSetColor{12}{LTgrey}% Paragraph ending on a nearly full line
     \luatypoSetColor{13}{cyan}%   Footnote split
+    \luatypoSetColor{14}{red}%    Too short first (final) word on the page
     \luatypoBackPI=1em\relax
     \luatypoBackFuzz=2pt\relax
     \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax
@@ -1239,6 +1338,7 @@
     \luatypoPageMin=5\relax
     \luatypoMinFull=4\relax
     \luatypoMinPART=4\relax
+    \luatypoMinLen=4\relax
    }%
 %% 
 %%



More information about the tex-live-commits mailing list.