texlive[68267] Master/texmf-dist: lua-typo (13sep23)

commits+karl at tug.org commits+karl at tug.org
Wed Sep 13 22:38:02 CEST 2023


Revision: 68267
          http://tug.org/svn/texlive?view=revision&revision=68267
Author:   karl
Date:     2023-09-13 22:38:02 +0200 (Wed, 13 Sep 2023)
Log Message:
-----------
lua-typo (13sep23)

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.pdf
    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-09-13 20:37:39 UTC (rev 68266)
+++ trunk/Master/texmf-dist/doc/lualatex/lua-typo/README.md	2023-09-13 20:38:02 UTC (rev 68267)
@@ -90,7 +90,7 @@
 
 * v.0.70: 
   - Options handled via `ltkeys` instead of `kvoptions`.
-  - code cleaning, bug fixes.
+  - Code cleaning, bug fixes.
 
 * v.0.80: 
   - Config file `lua-typo.cfg` changed (new colours added,  all colour 
@@ -99,6 +99,11 @@
     added for lines affected by multiple flaws (f.i. widow+overfull).
   - Bug fix (file `.typo`): in footnotes, line numbers for homeoarchy 
     were not reset.
+
+* v.0.85: 
+  - A warning is issued if some pages fail to be checked properly.
+  - Margin notes checking added (over/underfull lines, position).
+  - Code cleaning, bug fixes.
   
 --
 Copyright 2020--2023 Daniel Flipo 

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.pdf
===================================================================
(Binary files differ)

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-09-13 20:37:39 UTC (rev 68266)
+++ trunk/Master/texmf-dist/source/lualatex/lua-typo/lua-typo.dtx	2023-09-13 20:38:02 UTC (rev 68267)
@@ -204,7 +204,7 @@
 %    \pkg{lua-typo}, il suffit d’ajouter dans le préambule la ligne %\\
 %    |\usepackage[All]{lua-typo}|
 %
-%    La version courante (0.80) nécessite un noyau LaTeX récent,
+%    La version courante (0.85) nécessite un noyau LaTeX récent,
 %    2022/06/01 ou ultérieur. Ceux qui ne disposent que d’un noyau
 %    antérieur à 2021/06/01 reçoivent un message d’erreur
 %    «\texttt{Unable to register callback}» ; une version «rollback »
@@ -269,6 +269,7 @@
 %                        consécutives ?\\
 %      FootnoteSplit   & Fin de note de bas de page sur page suivante?\\
 %      ShortFinalWord  & Mot de fin de phrase court en haut de page\\
+%      MarginparPos    & Note marginale se terminant trop bas\\
 %      \hline
 %    \end{tabular}
 %    \end{table}
@@ -363,7 +364,7 @@
 %        La valeur donnée doit être un entier supérieur ou égal à~100,
 %        cette valeur 100 correspond à l’étirement maximal prévu par la
 %        fonte (|\fontdimen3|) ; avec ce réglage attendez-vous à trouver
-%        une kyrielle de lignes les creuses ! En fait la valeur par défaut
+%        une kyrielle de lignes creuses ! En fait la valeur par défaut
 %        (200) correspond approximativement à ce que TeX, avec les
 %        réglages par défaut (|\tolerance=200|, |\hbadness=1000|),
 %        considère comme \emph{Underfull hbox}.
@@ -380,7 +381,7 @@
 %        (trois lettres en commun), mais « mon » et « mont » en début de
 %        ligne échappent à la détection.
 %
-%    \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) :\\
@@ -405,6 +406,12 @@
 %        caractères}, donc entourée de simples ou doubles
 %        \emph{quotes} \textsc{ascii} et composées de mots séparés
 %        par des espaces comme dans les exemples ci-dessus.
+%
+%      \item[\cs{luatypoMarginparTol}] est une \emph{dimension} qui vaut
+%        |\baselineskip| par défaut; les notes marginales qui se
+%        terminent à plus de |\luatypoMarginparTol| en dessous de la
+%        dernière ligne de la page sont déclarées fautives.
+%
 %    \end{description}
 %
 %    À chacune des vérifications faites par \pkg{lua-typo} peut être
@@ -432,6 +439,7 @@
 % \luatypoSetColor{14}{cyan}  % Note de bas de page éclatée
 % \luatypoSetColor{15}{red}   % Mot de fin de phrase en haut de page
 % \luatypoSetColor{16}{LTline}% Ligne présentant plusieurs "défauts"
+% \luatypoSetColor{17}{red}   % Note marginale se terminant trop bas
 %    \end{verbatim}
 %    \pkg{lua-typo} charge les extensions \pkg{luacolor} et donc
 %    \pkg{color}. Seules les couleurs portant un nom (\emph{named
@@ -512,7 +520,8 @@
 %    for them, a ``rollback’’ version of \pkg{lua-typo} is provided,
 %    it can be loaded this way: |\usepackage[All]{lua-typo}[=v0.4]|.
 %
-%    Version 0.80 requires a LaTeX kernel dated 2022/06/01 or later.
+%    \enlargethispage*{\baselineskip}
+%    Version 0.85 requires a LaTeX kernel dated 2022/06/01 or later.
 %    Another ``rollback’’ version |[=v0.65]| has been added for those
 %    who run an older kernel.
 %
@@ -558,6 +567,7 @@
 %      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\\
+%      MarginparPos    & Margin note ending too low on the page\\
 %       \hline
 %    \end{tabular}\\[12pt]
 %    For example, if you want \pkg{lua-typo} to only warn about overfull
@@ -574,6 +584,7 @@
 %    this option provides an easy way to get their names without having
 %    to look into the documentation.
 %
+%    \newpage
 %    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
@@ -594,8 +605,9 @@
 %      \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}
 %
+%      \end{description}
+%
 %    \section{Customisation}
 %
 %    Some of the checks mentionned above require tuning, for
@@ -682,8 +694,13 @@
 %        (\emph{i.e.} surrounded by single or double \textsc{ascii}
 %        quotes) made of your words separated by spaces.
 %
-%    \end{description}
+%      \item[\cs{luatypoMarginparTol}] is a \emph{dimension} which
+%        defaults to |\baselineskip|; marginal notes trigger a flaw
+%        if they end lower than |\luatypoMarginparTol| under the page’s
+%        last line.
 %
+%      \end{description}
+%
 %    It is possible to define a specific colour for each
 %    typographic flaws that \pkg{lua-typo} deals with.
 %    Currently, only six colours are used in \file{lua-typo.cfg}:
@@ -706,8 +723,8 @@
 % \luatypoSetColor{13}{LTgrey}% Paragraph’s last line nearly full
 % \luatypoSetColor{14}{cyan}  % Footnotes spread over two pages
 % \luatypoSetColor{15}{red}   % Short final word on top of the page
-% \luatypoSetColor{16}{LTline}% Line color for multiple flaws\end{verbatim}
-%
+% \luatypoSetColor{16}{LTline}% Line color for multiple flaws
+% \luatypoSetColor{17}{red}   % Margin note ending too low\end{verbatim}
 %    \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
@@ -728,7 +745,7 @@
 %<+scan>\ProvidesPackage{scan-page}
 %<+dtx>\ProvidesFile{lua-typo.dtx}
 %<*dtx|sty|scan>
-                [2023-04-28 v.0.80 Daniel Flipo]
+                [2023-09-13 v.0.85 Daniel Flipo]
 %</dtx|sty|scan>
 %<*sty>
 % \fi
@@ -746,7 +763,7 @@
 %    \begin{macrocode}
 \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty}
 \DeclareRelease{v0.65}{2023-03-08}{lua-typo-2023-03-08.sty}
-\DeclareCurrentRelease{}{2023-04-12}
+\DeclareCurrentRelease{}{2023-09-13}
 %    \end{macrocode}
 %
 %    This package only runs with LuaLaTeX and requires packages
@@ -769,6 +786,7 @@
 \newdimen\luatypoLLminWD
 \newdimen\luatypoBackPI
 \newdimen\luatypoBackFuzz
+\newdimen\luatypoMarginparTol
 \newcount\luatypoStretchMax
 \newcount\luatypoHyphMax
 \newcount\luatypoPageMin
@@ -814,6 +832,7 @@
    LastWordMatch.if   = LT at LastWordMatch   ,
    FootnoteSplit.if   = LT at FootnoteSplit   ,
    ShortFinalWord.if  = LT at ShortFinalWord  ,
+   MarginparPos.if    = LT at MarginparPos    ,
    All.if             = LT at All             ,
    All.code           = \LT at ShortLinestrue     \LT at ShortPagestrue
                         \LT at OverfullLinestrue  \LT at UnderfullLinestrue
@@ -822,7 +841,7 @@
                         \LT at ParLastHyphentrue  \LT at EOLShortWordstrue
                         \LT at FirstWordMatchtrue \LT at LastWordMatchtrue
                         \LT at BackParindenttrue  \LT at FootnoteSplittrue
-                        \LT at ShortFinalWordtrue
+                        \LT at ShortFinalWordtrue \LT at MarginparPostrue
   }
 \ProcessKeyOptions[luatypo]
 %    \end{macrocode}
@@ -929,6 +948,12 @@
   \else
     \directlua{ luatypo.ShortFinalWord = false }%
   \fi
+  \ifLT at MarginparPos
+    \advance\luatypo at options by 1
+    \directlua{ luatypo.MarginparPos = true }%
+  \else
+    \directlua{ luatypo.MarginparPos = false }%
+  \fi
 }
 %    \end{macrocode}
 %
@@ -958,6 +983,7 @@
      LastWordMatch   [false]\MessageBreak
      FootnoteSplit   [false]\MessageBreak
      ShortFinalWord  [false]\MessageBreak
+     MarginparPos    [false]\MessageBreak
      \MessageBreak
      *********************************************%
      \MessageBreak Lua-typo [ShowOptions]
@@ -979,11 +1005,12 @@
 %    \end{macrocode}
 %    Ensure |MinFull|$\leq$|MinPart|.
 %    \begin{macrocode}
-    luatypo.MinFull = math.min(luatypo.MinPart,luatypo.MinFull)
-    luatypo.MinLen  = tex.count.luatypoMinLen
-    luatypo.LLminWD = tex.dimen.luatypoLLminWD
-    luatypo.BackPI  = tex.dimen.luatypoBackPI
-    luatypo.BackFuzz  = tex.dimen.luatypoBackFuzz
+    luatypo.MinFull  = math.min(luatypo.MinPart,luatypo.MinFull)
+    luatypo.MinLen   = tex.count.luatypoMinLen
+    luatypo.LLminWD  = tex.dimen.luatypoLLminWD
+    luatypo.BackPI   = tex.dimen.luatypoBackPI
+    luatypo.BackFuzz = tex.dimen.luatypoBackFuzz
+    luatypo.MParTol  = tex.dimen.luatypoMarginparTol
 %    \end{macrocode}
 % \changes{0.80}{2023/04/28}{New table `luatypo.map’ for colours.}
 %
@@ -1006,6 +1033,16 @@
 %    (very) end of document and write the report file on disc,
 %    unless option |None| has been selected.
 %
+%    On every page, at least one line of text should be found.
+%    Otherwise, \pkg{lua-typo} presumes something went wrong and
+%    writes the page number to a |failedlist| list.
+%    In case |pagelist| is empty \emph{and} |failedlist| \emph{is not},
+%    a warning is issued instead of the \texttt{No Typo Flaws found.}
+%    message (new to version~0.85).
+%
+% \changes{0.85}{2023/09/07}{Warn in case some pages failed to
+%    be checked properly.}
+%
 %    \begin{macrocode}
 \AtVeryEndDocument{%
 \ifnum\luatypo at options = 0 \LT at Nonetrue \fi
@@ -1023,14 +1060,27 @@
     texio.write_nl(' ')
     texio.write_nl('*************************************')
     if luatypo.pagelist == " " then
-       texio.write_nl('*** lua-typo: No Typo Flaws found.')
+       if luatypo.failedlist == " " then
+          texio.write_nl('*** lua-typo: No Typo Flaws found.')
+       else
+          texio.write_nl('*** WARNING: ')
+          texio.write('lua-typo failed to scan these pages:')
+          texio.write_nl('***' .. luatypo.failedlist)
+          texio.write_nl('*** Please report to the maintainer.')
+       end
     else
        texio.write_nl('*** lua-typo: WARNING *************')
        texio.write_nl('The following pages need attention:')
        texio.write(luatypo.pagelist)
     end
-    texio.write_nl('***********************************')
+    texio.write_nl('*************************************')
     texio.write_nl(' ')
+    if luatypo.failedlist == " " then
+    else
+       local prt = "WARNING: lua-typo failed to scan pages " ..
+                   luatypo.failedlist .. "\string\n\string\n"
+       luatypo.buffer = prt .. luatypo.buffer
+    end
     local fileout= tex.jobname .. ".typo"
     local out=io.open(fileout,"w+")
     out:write(luatypo.buffer)
@@ -1060,7 +1110,7 @@
         local s = utf8.char(c)
         luatypo.single[langno] = luatypo.single[langno] .. s
       end
-%<dbg>      texio.write_nl("SINGLE=" .. luatypo.single[langno])
+%<dbg>      texio.write_nl('SINGLE=' .. luatypo.single[langno])
 %<dbg>      texio.write_nl(' ')
      }%
   \else
@@ -1079,7 +1129,7 @@
         local s = utf8.char(c)
         luatypo.double[langno] = luatypo.double[langno] .. s
       end
-%<dbg>      texio.write_nl("DOUBLE=" .. luatypo.double[langno])
+%<dbg>      texio.write_nl('DOUBLE=' .. luatypo.double[langno])
 %<dbg>      texio.write_nl(' ')
     }%
   \else
@@ -1113,13 +1163,14 @@
 %
 %    \begin{macrocode}
 \begin{luacode}
-luatypo.colortbl = { }
-luatypo.map      = { }
-luatypo.single   = { }
-luatypo.double   = { }
-luatypo.pagelist = " "
-luatypo.buffer   = "List of typographic flaws found for "
-                    .. tex.jobname .. ".pdf:\string\n\string\n"
+luatypo.colortbl   = { }
+luatypo.map        = { }
+luatypo.single     = { }
+luatypo.double     = { }
+luatypo.pagelist   = " "
+luatypo.failedlist = " "
+luatypo.buffer     = "List of typographic flaws found for "
+                      .. tex.jobname .. ".pdf:\string\n\string\n"
 
 local char_to_discard = { }
 char_to_discard[string.byte(",")] = true
@@ -1535,12 +1586,12 @@
      new = utf8_gsub(new, "_+$", "")  -- $
      new = utf8_gsub(new, "^_+", "")
      maxlen = math.min(utf8_len(old), utf8_len(new))
-%<dbg>     texio.write_nl("EOLsigold=" .. old)
-%<dbg>     texio.write("   EOLsig=" .. new)
+%<dbg>     texio.write_nl('EOLsigold=' .. old)
+%<dbg>     texio.write('   EOLsig=' .. new)
 %    \end{macrocode}
-%     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.
+%     When called with flag |false|, |check_line_last_word| doesn’t
+%     compare it with the previous line’s, but just returns the
+%     last word’s signature.
 %    \begin{macrocode}
      if flag and old ~= "" then
 %    \end{macrocode}
@@ -1571,11 +1622,11 @@
           end
         end
         if oldsub == newsub then
-%<dbg>           texio.write_nl("EOLnewsub=" .. newsub)
+%<dbg>           texio.write_nl('EOLnewsub=' .. newsub)
            match = true
         end
         if oldlast == newlast and utf8_len(newlast) >= MinFull then
-%<dbg>           texio.write_nl("EOLnewlast=" .. newlast)
+%<dbg>           texio.write_nl('EOLnewlast=' .. newlast)
            if utf8_len(newlast) > MinPart or not match then
               oldsub = oldlast
               newsub = newlast
@@ -1621,13 +1672,13 @@
                   local c = nn.char
                   if not char_to_discard[c] then l = l + 1 end
                 end
-%<dbg>           texio.write_nl("l (box)=" .. l)
+%<dbg>           texio.write_nl('l (box)=' .. l)
              elseif n then
                 color_node(n, COLOR)
                 li, newsub = signature(n, newsub, swap)
                 l = l + li - lo
                 lo = li
-%<dbg>           texio.write_nl("l=" .. l)
+%<dbg>           texio.write_nl('l=' .. l)
              end
              n = n.prev
            end
@@ -1688,12 +1739,12 @@
   new = utf8_gsub(new, "_+$", "") -- $
   new = utf8_gsub(new, "^_+", "")
   maxlen = math.min(utf8_len(old), utf8_len(new))
-%<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_line_first_word| returns
-%     the first word’s signature, but doesn’t compare it with the
-%     previous line’s.
+%     When called with flag |false|, |check_line_first_word| doesn’t
+%     compare it with the previous line’s, but returns
+%     the first word’s signature.
 %    \begin{macrocode}
   if flag and old ~= "" then
      local oldfirst = utf8_gsub (old, "_.*", "")
@@ -1713,11 +1764,11 @@
        end
      end
      if oldsub == newsub then
-%<dbg>        texio.write_nl("BOLnewsub=" .. newsub)
+%<dbg>        texio.write_nl('BOLnewsub=' .. newsub)
         match = true
      end
      if oldfirst == newfirst  and utf8_len(newfirst) >= MinFull then
-%<dbg>        texio.write_nl("BOLnewfirst=" .. newfirst)
+%<dbg>        texio.write_nl('BOLnewfirst=' .. newfirst)
         if utf8_len(newfirst) > MinPart or not match then
            oldsub = oldfirst
            newsub = newfirst
@@ -1741,7 +1792,7 @@
           end
         end
         newsub = utf8_gsub(newsub, "_+$", "")   --$
-%<dbg>        texio.write_nl("BOLfullmatch=" .. newsub)
+%<dbg>        texio.write_nl('BOLfullmatch=' .. newsub)
         local msg = "B.O.L. MATCH=" .. newsub
         log_flaw(msg, line, colno, footnote)
 %    \end{macrocode}
@@ -1832,7 +1883,7 @@
         match = true
      end
   end
-%<dbg>  texio.write_nl("FinalWord=" .. new)
+%<dbg>  texio.write_nl('FinalWord=' .. new)
   if match then
      local msg = "ShortFinalWord=" .. new
      log_flaw(msg, 1, colno, footnote)
@@ -1981,11 +2032,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
@@ -2013,9 +2064,9 @@
      body_bot = true
   elseif footnoterule_ahead(n) then
      body_bot = 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
   return page_bot, body_bot
 end
@@ -2022,6 +2073,99 @@
 %    \end{macrocode}
 % \end{macro}
 %
+% \changes{v0.85}{2023/09/07}{New function `check\_marginnote’.}
+%
+% \begin{macro}{check-marginnote}
+%    This function checks margin notes for overfull/underfull lines;
+%    It also warns if a margin note ends too low under the last line
+%    of text.
+%    \begin{macrocode}
+local check_marginnote = function (head, line, colno, vpos, bpmn)
+  local OverfullLines   = luatypo.OverfullLines
+  local UnderfullLines  = luatypo.UnderfullLines
+  local MarginparPos    = luatypo.MarginparPos
+  local margintol       = luatypo.MParTol
+  local marginpp        = tex.getdimen("marginparpush")
+  local pflag  = false
+  local ofirst = true
+  local ufirst = true
+  local n = head.head
+  local bottom = vpos
+  if vpos <= bpmn then
+     bottom = bpmn + marginpp
+  end
+%<dbg>  texio.write_nl('*** Margin note? ***')
+  repeat
+    if n and (n.id == GLUE or n.id == PENALTY) then
+%<dbg>     texio.write_nl('    Found GLUE or PENALTY')
+       n = n.next
+    elseif n and n.id == VLIST then
+%<dbg>     texio.write_nl('    Found VLIST')
+       n = n.head
+    end
+  until not n or (n.id == HLIST and n.subtype == LINE)
+  local head = n
+  if head then
+%<dbg>     texio.write_nl('    Found HLIST')
+  else
+%<dbg>     texio.write_nl('    No text line found.')
+  end
+%<dbg>  local l = 0
+  local last = head
+  while head do
+    local next = head.next
+    if head.id == HLIST and head.subtype == LINE then
+%<dbg>       l = l + 1
+%<dbg>       texio.write_nl('    Checking line ' .. l)
+       bottom = bottom + head.height + head.depth
+       local first = head.head
+       local linewd = head.width
+       local hmax = linewd + tex.hfuzz
+       local w,h,d = dimensions(1,2,0, first)
+       local Stretch = math.max(luatypo.Stretch/100,1)
+       if w > hmax and OverfullLines then
+%<dbg>          texio.write(': Overfull!')
+          pflag = true
+          local COLOR = luatypo.colortbl[8]
+          color_line (head, COLOR)
+          if ofirst then
+             local msg = "OVERFULL line(s) in margin note"
+             log_flaw(msg, line, colno, false)
+             ofirst = false
+          end
+       elseif head.glue_set > Stretch and head.glue_sign == 1 and
+              head.glue_order == 0 and UnderfullLines then
+%<dbg>          texio.write(': Underfull!')
+          pflag = true
+          local COLOR = luatypo.colortbl[9]
+          color_line (head, COLOR)
+          if ufirst then
+             local msg = "UNDERFULL line(s) in margin note"
+             log_flaw(msg, line, colno, false)
+             ufirst = false
+          end
+       end
+    end
+    last = head
+    head = next
+  end
+  local textht = tex.getdimen("textheight")
+%<dbg>  local tht  = string.format("%.1fpt", textht/65536)
+%<dbg>  local bott = string.format("%.1fpt", bottom/65536)
+%<dbg>  texio.write_nl('    Bottom=' .. bott)
+%<dbg>  texio.write('  TextBottom=' ..tht)
+  if bottom > textht + margintol and MarginparPos then
+     pflag = true
+     local COLOR = luatypo.colortbl[17]
+     color_line (last, COLOR)
+     local msg = "Margin note too low"
+     log_flaw(msg, line, colno, false)
+  end
+  return bottom, pflag
+end
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{macro}{get-pagebody}
 %    The next function scans the \node{vlist}s on the current
 %    page in search of the page body.
@@ -2038,29 +2182,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
@@ -2068,7 +2212,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
@@ -2120,6 +2264,7 @@
   vpos_min = vpos_min * 1.5
   local linewd = tex.getdimen("textwidth")
   local first_bot  = true
+  local done       = false
   local footnote   = false
   local ftnsplit   = false
   local orphanflag = false
@@ -2135,6 +2280,7 @@
   local pageline = 0
   local ftnline = 0
   local line = 0
+  local bpmn = 0
   local body_bottom = false
   local page_bottom = false
   local pageflag = false
@@ -2154,9 +2300,11 @@
 %    \end{macrocode}
 %    This is a text line, store its width, increment counters
 %    |pageline| or |ftnline| and |line| (for |log_flaw|).
-%    Let’s update |vpos| (vertical position in `sp’ units) too.
+%    Let’s update |vpos| (vertical position in `sp’ units) and
+%    set flag |done| to |true|.
 %    \begin{macrocode}
        vpos = vpos + head.height + head.depth
+       done = true
        local linewd = head.width
        local first = head.head
        local ListItem = false
@@ -2256,15 +2404,15 @@
 %    Does the first word and the one on the previous line match
 %    (except lists)?
 %    \begin{macrocode}
-          if FirstWordMatch then
-             local flag = not ListItem and (line > 1)
-             firstwd, flag =
-                check_line_first_word(firstwd, first, line, colno,
-                                      flag, footnote)
-             if flag then
-                pageflag = true
-             end
+       if FirstWordMatch then
+          local flag = not ListItem and (line > 1)
+          firstwd, flag =
+             check_line_first_word(firstwd, first, line, colno,
+                                   flag, footnote)
+          if flag then
+             pageflag = true
           end
+       end
 %    \end{macrocode}
 %    Check the page’s first word (end of sentence?).
 %    \begin{macrocode}
@@ -2277,6 +2425,12 @@
 %    and |pn| are the last two nodes.
 %    \begin{macrocode}
        local ln = slide(first)
+%    \end{macrocode}
+%    Skip a possible RULE pointing an overfull line.
+%    \begin{macrocode}
+       if ln.id == RULE and ln.subtype == 0 then
+          ln = ln.prev
+       end
        local pn = ln.prev
        if pn and pn.id == GLUE and pn.subtype == PARFILL then
 %    \end{macrocode}
@@ -2283,6 +2437,7 @@
 %    CASE 1: this line ends the paragraph, reset |ftnsplit| and |orphan|
 %    flags to false…
 %    \begin{macrocode}
+%<dbg>    texio.write_nl('EOL CASE 1: end of paragraph')
           hyphcount = 0
           ftnsplit = false
           orphanflag = false
@@ -2302,8 +2457,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}
@@ -2347,6 +2502,7 @@
 %    \end{macrocode}
 %    CASE 2: the current line ends with an hyphen.
 %    \begin{macrocode}
+%<dbg>    texio.write_nl('EOL CASE 2: hyphen')
           hyphcount = hyphcount + 1
           if hyphcount > HYPHmax and RepeatedHyphens then
              local COLOR = luatypo.colortbl[3]
@@ -2400,10 +2556,11 @@
           end
 %    \end{macrocode}
 %    CASE 3: the current line ends with anything else (\node{glyph},
-%    \node{mkern}, \node{hlist}, etc.), reset |hyphcount|, check for
-%    `LastWordMatch’ and `EOLShortWords’.
+%    \node{mkern}, \node{hlist}, etc.), then reset |hyphcount| and
+%    check for `LastWordMatch’ and `EOLShortWords’.
 %    \begin{macrocode}
        else
+%<dbg>    texio.write_nl('EOL CASE 3')
           hyphcount = 0
 %    \end{macrocode}
 %    Track matching words at end of line and short words.
@@ -2485,6 +2642,36 @@
           color_line (head, COLOR)
           backpar = false
        end
+    elseif head and head.id == HLIST and head.subtype == BOX and
+           head.width > 0                                    then
+      if head.height == 0 then
+%    \end{macrocode}
+%    This is a possible margin note.
+%    \begin{macrocode}
+         bpmn, pflag = check_marginnote(head, line, colno, vpos, bpmn)
+         if pflag then pageflag = true end
+      else
+%    \end{macrocode}
+%    Leave |check_vtop| if a two columns box starts.
+%    \begin{macrocode}
+         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(' ')
+            break
+         else
+%    \end{macrocode}
+% \changes{v0.80}{2023/04/18}{hlist-2: added detection of page bottom
+%    and increment line number and vpos.}
+%    This is an |\hbox| (f.i.\ centred), let’s update |vpos|, line
+%    and check for page bottom
+%    \begin{macrocode}
+            vpos = vpos + head.height + head.depth
+            pageline = pageline + 1
+            line = pageline
+            page_bottom, body_bottom = check_EOP (nextnode)
+         end
+      end
     elseif head.id == HLIST and
           (head.subtype == EQN or head.subtype == ALIGN) and
           (head.height > 0 or head.depth > 0) then
@@ -2561,8 +2748,8 @@
 %    If it is, 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
@@ -2580,15 +2767,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 vpos < vpos_min  and ShortPages  then
              pageshort = true
@@ -2619,38 +2806,20 @@
 %    This is a |\vbox|, let’s update |vpos|.
 %    \begin{macrocode}
        vpos = vpos + head.height + head.depth
-    elseif head.id == HLIST and head.subtype == BOX then
-%    \end{macrocode}
-% \changes{v0.80}{2023/04/18}{hlist-2: added detection of page bottom
-%    and increment line number and vpos.}
-%
-%    This is an |\hbox| (f.i.\ centred), let’s update |vpos|, line
-%    and check for page bottom
-%    \begin{macrocode}
-       vpos = vpos + head.height + head.depth
-       pageline = pageline + 1
-       line = pageline
-       page_bottom, body_bottom = check_EOP (nextnode)
-       local hf = head.list
-%    \end{macrocode}
-%    Leave |check_vtop| if a two columns box starts.
-%    \begin{macrocode}
-       if hf and hf.id == VLIST and hf.subtype == 0 then
-%<dbg>          texio.write_nl("check_vtop: BREAK => multicol")
-%<dbg>          texio.write_nl(" ")
-          break
-       end
+%<dbg>    local tht = head.height + head.depth
+%<dbg>    local tht_pt = string.format("%.1fpt", tht/65536)
+%<dbg>    texio.write(' vbox: height=' .. tht_pt)
     end
   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}
@@ -2661,9 +2830,10 @@
         luatypo.pagelist = luatypo.pagelist .. tostring(pageno) .. ", "
      end
   end
-  return head
+  return head, done
 %    \end{macrocode}
 %    |head| is nil unless |check_vtop| exited on a two column start.
+%    |done| is true unless |check_vtop| found no text line.
 %    \begin{macrocode}
 end
 %    \end{macrocode}
@@ -2686,23 +2856,40 @@
 %    \begin{macrocode}
 luatypo.check_page = function (head)
   local textwd = tex.getdimen("textwidth")
+  local textht = tex.getdimen("textheight")
+  local checked, boxed, n2, n3, col, colno
+  local body = get_pagebody(head)
+  local pageno = tex.getcount("c at page")
   local vpos = 0
-  local n2, n3, col, colno
-  local body = get_pagebody(head)
   local footnote = false
   local top = body
   local first = body.list
-  if (first and first.id == HLIST and first.subtype == BOX) or
-     (first and first.id == VLIST and first.subtype == 0) then
+  local next
+  local count = 0
+%<dbg>  texio.write_nl('Body=' .. tostring(node.type(top.id)))
+%<dbg>  texio.write('-' .. tostring(top.subtype))
+%<dbg>  texio.write(';  First=' .. tostring(node.type(first.id)))
+%<dbg>  texio.write('-' .. tostring(first.subtype))
+%<dbg>  texio.write_nl(' ')
+  if ((first and first.id == HLIST and first.subtype == BOX) or
+      (first and first.id == VLIST and first.subtype == 0))      and
+     (first.width == textwd and first.height > 0 and not boxed)  then
 %    \end{macrocode}
 %    Some classes (\cls{memoir}, \cls{tugboat} …) use one more level
-%    of bowing, let’s step down one level.
+%    of bowing for two columns, 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{macrocode}
+%    A float on top of a page is a VLIST-0 included in a VLIST-0 (body),
+%    it should not trigger this step down. Workaround: the body will be
+%    read again.
+%    \begin{macrocode}
+     if first.id == VLIST then
+        boxed = body
+     end
   end
 %    \end{macrocode}
 %    Main loop:
@@ -2709,26 +2896,31 @@
 %    \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(" ")
+    next = top.next
+%<dbg>    count = count + 1
+%<dbg>    texio.write_nl('Page loop' .. count)
+%<dbg>    texio.write(': top=' .. tostring(node.type(top.id)))
+%<dbg>    texio.write('-' .. tostring(top.subtype))
+%<dbg>    if first then
+%<dbg>      texio.write('  first=' .. tostring(node.type(first.id)))
+%<dbg>      texio.write('-' .. tostring(first.subtype))
+%<dbg>    end
     if top and top.id == VLIST and top.subtype == 0 and
-       top.width > textwd/2                              then
+       top.width > textwd/2                             then
 %    \end{macrocode}
 %    Single column, run |check_vtop| on the top vlist.
 %    \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(" ")
-       local next = check_vtop(top,colno,vpos)
-       if next then
-          top = next
-       elseif top then
-          top = top.next
+%<dbg>       texio.write('-' .. tostring(top.subtype))
+%<dbg>       texio.write('  wd=' .. boxwd .. '  ht=' .. boxht)
+%<dbg>       texio.write_nl(' ')
+       local n, ok = check_vtop(top,colno,vpos)
+       if ok then checked = true end
+       if n then
+          next = n
        end
     elseif (top and top.id == HLIST and top.subtype == BOX) and
            (first and first.id == VLIST and first.subtype == 0) and
@@ -2737,23 +2929,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 col in traverse_id(VLIST, first) do
            colno = colno + 1
-%<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('Start of col.' .. colno)
+%<dbg>           texio.write_nl(' ')
+       local n, ok = check_vtop(col,colno,vpos)
+       if ok then checked = true end
+%<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('-' .. tostring(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
@@ -2762,26 +2955,39 @@
 %    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
            local col = n.list
            if col and col.list then
-%<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('Start of col.' .. colno)
+%<dbg>              texio.write_nl(' ')
+              local n, ok = check_vtop(col,colno,vpos)
+              if ok then checked = true end
+%<dbg>              texio.write_nl('End of col.' .. colno)
+%<dbg>              texio.write_nl(' ')
            end
        end
        colno = nil
-       top = top.next
-    else
-       top = top.next
     end
+%    \end{macrocode}
+%    Workaround for top floats: check the whole body again.
+%    \begin{macrocode}
+    if boxed and not next then
+       next = boxed
+       boxed = nil
+    end
+    top = next
   end
+  if not checked then
+     luatypo.failedlist = luatypo.failedlist .. tostring(pageno) .. ", "
+%<dbg>     texio.write_nl(' ')
+%<dbg>     texio.write_nl('WARNING: no text line found on page ')
+%<dbg>     texio.write(tostring(pageno))
+%<dbg>     texio.write_nl(' ')
+  end
   return true
 end
 return luatypo.check_page
@@ -2842,6 +3048,7 @@
     \luatypoSetColor{14}{cyan}%   Footnote split
     \luatypoSetColor{15}{red}%    Too short first (final) word on the page
     \luatypoSetColor{16}{LTline}% Line color for multiple flaws
+    \luatypoSetColor{17}{red}%    Margin note ending too low
     \luatypoBackPI=1em\relax
     \luatypoBackFuzz=2pt\relax
     \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax
@@ -2852,12 +3059,13 @@
     \luatypoMinFull=3\relax
     \luatypoMinPart=4\relax
     \luatypoMinLen=4\relax
+    \luatypoMarginparTol=\baselineskip
    }%
 %    \end{macrocode}
 % \iffalse
 %</sty>
 % \fi
-%  \clearpage
+%
 %  \section{Configuration file}
 %
 % \iffalse
@@ -2892,9 +3100,13 @@
 \luatypoMinPart=4\relax
 
 %% Minimum number of characters for the first word on a page if it ends
-%% a sentence.
-\luatypoMinLen=4\relax
+%% a sentence (version >= 0.65).
+\ifdefined\luatypoMinLen \luatypoMinLen=4\relax\fi
 
+%% Acceptable marginpars must end at |\luatypoMarginparTol| under
+%% the page’s last line or above (version >= 0.85).
+\ifdefined\luatypoMarginparTol \luatypoMarginparTol=\baselineskip \fi
+
 %% Default colours = red, cyan, blue, LTgrey, LTred, LTline.
 \definecolor{LTgrey}{gray}{0.6}
 \definecolor{LTred}{rgb}{1,0.55,0}
@@ -2915,6 +3127,7 @@
 \luatypoSetColor{14}{cyan}%   Footnote split
 \luatypoSetColor{15}{red}%    Too short first (final) word on the page
 \luatypoSetColor{16}{LTline}% Line color for multiple flaws
+\luatypoSetColor{17}{red}%    Margin note ending too low
 
 %% Language specific settings (example for French):
 %% short words (two letters max) to be avoided at end of lines.
@@ -2984,12 +3197,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(' ')
    }%
 }
 
@@ -3003,6 +3216,7 @@
 luatypo.double = { }
 luatypo.colortbl  = { }
 luatypo.pagelist  = " "
+luatypo.failedlist  = " "
 
 local hyphcount = 0
 local pageno = 0
@@ -3055,20 +3269,20 @@
   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
@@ -3076,7 +3290,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
@@ -3088,7 +3302,7 @@
   elseif n.id == GLUE then
      texio.write(tostring(n.subtype))
      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
      local c = ""
      if n.replace then
@@ -3102,7 +3316,7 @@
         for nn in traverse_id(GLYPH, n.pre) do
             c = c .. utf8.char(nn.char)
         end
-        texio.write(" pre=" .. c)
+        texio.write(' pre=' .. c)
      end
      if n.post then
         c = ""
@@ -3109,7 +3323,7 @@
         for nn in traverse_id(GLYPH, n.post) do
             c = c .. utf8.char(nn.char)
         end
-     texio.write(" post=" .. c)
+     texio.write(' post=' .. c)
      end
   elseif n.subtype and n.subtype < 256  then
      texio.write(tostring(n.subtype))
@@ -3116,10 +3330,10 @@
      if 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)
+           texio.write(' wd=' .. wd_pt)
         end
      end
   end
@@ -3133,9 +3347,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)
@@ -3142,49 +3356,53 @@
   if body then
      parent = body
      head = body.head
+     texio.write_nl('Enter outer vbox (body) of type ')
+     texio.write(tostring(node.type(body.id)))
+     texio.write('-')
+     texio.write(tostring(body.subtype))
   end
-  while (head) do
+  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(n5.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-09-13 20:37:39 UTC (rev 68266)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.cfg	2023-09-13 20:38:02 UTC (rev 68267)
@@ -24,9 +24,13 @@
 \luatypoMinPart=4\relax
 
 %% Minimum number of characters for the first word on a page if it ends
-%% a sentence.
-\luatypoMinLen=4\relax
+%% a sentence (version >= 0.65).
+\ifdefined\luatypoMinLen \luatypoMinLen=4\relax\fi
 
+%% Acceptable marginpars must end at |\luatypoMarginparTol| under
+%% the page’s last line or above (version >= 0.85).
+\ifdefined\luatypoMarginparTol \luatypoMarginparTol=\baselineskip \fi
+
 %% Default colours = red, cyan, blue, LTgrey, LTred, LTline.
 \definecolor{LTgrey}{gray}{0.6}
 \definecolor{LTred}{rgb}{1,0.55,0}
@@ -47,6 +51,7 @@
 \luatypoSetColor{14}{cyan}%   Footnote split
 \luatypoSetColor{15}{red}%    Too short first (final) word on the page
 \luatypoSetColor{16}{LTline}% Line color for multiple flaws
+\luatypoSetColor{17}{red}%    Margin note ending too low
 
 %% Language specific settings (example for French):
 %% short words (two letters max) to be avoided at end of lines.

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty	2023-09-13 20:37:39 UTC (rev 68266)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-typo/lua-typo.sty	2023-09-13 20:38:02 UTC (rev 68267)
@@ -11,10 +11,10 @@
 %%
 \NeedsTeXFormat{LaTeX2e}[2021/06/01]
 \ProvidesPackage{lua-typo}
-                [2023-04-28 v.0.80 Daniel Flipo]
+                [2023-09-13 v.0.85 Daniel Flipo]
 \DeclareRelease{v0.4}{2021-01-01}{lua-typo-2021-04-18.sty}
 \DeclareRelease{v0.65}{2023-03-08}{lua-typo-2023-03-08.sty}
-\DeclareCurrentRelease{}{2023-04-12}
+\DeclareCurrentRelease{}{2023-09-13}
 \ifdefined\directlua
   \RequirePackage{luatexbase,luacode,luacolor,atveryend}
 \else
@@ -24,6 +24,7 @@
 \newdimen\luatypoLLminWD
 \newdimen\luatypoBackPI
 \newdimen\luatypoBackFuzz
+\newdimen\luatypoMarginparTol
 \newcount\luatypoStretchMax
 \newcount\luatypoHyphMax
 \newcount\luatypoPageMin
@@ -56,6 +57,7 @@
    LastWordMatch.if   = LT at LastWordMatch   ,
    FootnoteSplit.if   = LT at FootnoteSplit   ,
    ShortFinalWord.if  = LT at ShortFinalWord  ,
+   MarginparPos.if    = LT at MarginparPos    ,
    All.if             = LT at All             ,
    All.code           = \LT at ShortLinestrue     \LT at ShortPagestrue
                         \LT at OverfullLinestrue  \LT at UnderfullLinestrue
@@ -64,7 +66,7 @@
                         \LT at ParLastHyphentrue  \LT at EOLShortWordstrue
                         \LT at FirstWordMatchtrue \LT at LastWordMatchtrue
                         \LT at BackParindenttrue  \LT at FootnoteSplittrue
-                        \LT at ShortFinalWordtrue
+                        \LT at ShortFinalWordtrue \LT at MarginparPostrue
   }
 \ProcessKeyOptions[luatypo]
 \AtEndOfPackage{%
@@ -163,6 +165,12 @@
   \else
     \directlua{ luatypo.ShortFinalWord = false }%
   \fi
+  \ifLT at MarginparPos
+    \advance\luatypo at options by 1
+    \directlua{ luatypo.MarginparPos = true }%
+  \else
+    \directlua{ luatypo.MarginparPos = false }%
+  \fi
 }
 \ifLT at ShowOptions
   \GenericWarning{* }{%
@@ -187,6 +195,7 @@
      LastWordMatch   [false]\MessageBreak
      FootnoteSplit   [false]\MessageBreak
      ShortFinalWord  [false]\MessageBreak
+     MarginparPos    [false]\MessageBreak
      \MessageBreak
      *********************************************%
      \MessageBreak Lua-typo [ShowOptions]
@@ -199,11 +208,12 @@
     luatypo.Stretch = tex.count.luatypoStretchMax
     luatypo.MinFull = tex.count.luatypoMinFull
     luatypo.MinPart = tex.count.luatypoMinPart
-    luatypo.MinFull = math.min(luatypo.MinPart,luatypo.MinFull)
-    luatypo.MinLen  = tex.count.luatypoMinLen
-    luatypo.LLminWD = tex.dimen.luatypoLLminWD
-    luatypo.BackPI  = tex.dimen.luatypoBackPI
-    luatypo.BackFuzz  = tex.dimen.luatypoBackFuzz
+    luatypo.MinFull  = math.min(luatypo.MinPart,luatypo.MinFull)
+    luatypo.MinLen   = tex.count.luatypoMinLen
+    luatypo.LLminWD  = tex.dimen.luatypoLLminWD
+    luatypo.BackPI   = tex.dimen.luatypoBackPI
+    luatypo.BackFuzz = tex.dimen.luatypoBackFuzz
+    luatypo.MParTol  = tex.dimen.luatypoMarginparTol
     local tbl = luatypo.colortbl
     local map = { }
     for i,v in ipairs (luatypo.colortbl) do
@@ -230,14 +240,27 @@
     texio.write_nl(' ')
     texio.write_nl('*************************************')
     if luatypo.pagelist == " " then
-       texio.write_nl('*** lua-typo: No Typo Flaws found.')
+       if luatypo.failedlist == " " then
+          texio.write_nl('*** lua-typo: No Typo Flaws found.')
+       else
+          texio.write_nl('*** WARNING: ')
+          texio.write('lua-typo failed to scan these pages:')
+          texio.write_nl('***' .. luatypo.failedlist)
+          texio.write_nl('*** Please report to the maintainer.')
+       end
     else
        texio.write_nl('*** lua-typo: WARNING *************')
        texio.write_nl('The following pages need attention:')
        texio.write(luatypo.pagelist)
     end
-    texio.write_nl('***********************************')
+    texio.write_nl('*************************************')
     texio.write_nl(' ')
+    if luatypo.failedlist == " " then
+    else
+       local prt = "WARNING: lua-typo failed to scan pages " ..
+                   luatypo.failedlist .. "\string\n\string\n"
+       luatypo.buffer = prt .. luatypo.buffer
+    end
     local fileout= tex.jobname .. ".typo"
     local out=io.open(fileout,"w+")
     out:write(luatypo.buffer)
@@ -285,13 +308,14 @@
   \endgroup
 }
 \begin{luacode}
-luatypo.colortbl = { }
-luatypo.map      = { }
-luatypo.single   = { }
-luatypo.double   = { }
-luatypo.pagelist = " "
-luatypo.buffer   = "List of typographic flaws found for "
-                    .. tex.jobname .. ".pdf:\string\n\string\n"
+luatypo.colortbl   = { }
+luatypo.map        = { }
+luatypo.single     = { }
+luatypo.double     = { }
+luatypo.pagelist   = " "
+luatypo.failedlist = " "
+luatypo.buffer     = "List of typographic flaws found for "
+                      .. tex.jobname .. ".pdf:\string\n\string\n"
 
 local char_to_discard = { }
 char_to_discard[string.byte(",")] = true
@@ -894,6 +918,75 @@
   end
   return page_bot, body_bot
 end
+local check_marginnote = function (head, line, colno, vpos, bpmn)
+  local OverfullLines   = luatypo.OverfullLines
+  local UnderfullLines  = luatypo.UnderfullLines
+  local MarginparPos    = luatypo.MarginparPos
+  local margintol       = luatypo.MParTol
+  local marginpp        = tex.getdimen("marginparpush")
+  local pflag  = false
+  local ofirst = true
+  local ufirst = true
+  local n = head.head
+  local bottom = vpos
+  if vpos <= bpmn then
+     bottom = bpmn + marginpp
+  end
+  repeat
+    if n and (n.id == GLUE or n.id == PENALTY) then
+       n = n.next
+    elseif n and n.id == VLIST then
+       n = n.head
+    end
+  until not n or (n.id == HLIST and n.subtype == LINE)
+  local head = n
+  if head then
+  else
+  end
+  local last = head
+  while head do
+    local next = head.next
+    if head.id == HLIST and head.subtype == LINE then
+       bottom = bottom + head.height + head.depth
+       local first = head.head
+       local linewd = head.width
+       local hmax = linewd + tex.hfuzz
+       local w,h,d = dimensions(1,2,0, first)
+       local Stretch = math.max(luatypo.Stretch/100,1)
+       if w > hmax and OverfullLines then
+          pflag = true
+          local COLOR = luatypo.colortbl[8]
+          color_line (head, COLOR)
+          if ofirst then
+             local msg = "OVERFULL line(s) in margin note"
+             log_flaw(msg, line, colno, false)
+             ofirst = false
+          end
+       elseif head.glue_set > Stretch and head.glue_sign == 1 and
+              head.glue_order == 0 and UnderfullLines then
+          pflag = true
+          local COLOR = luatypo.colortbl[9]
+          color_line (head, COLOR)
+          if ufirst then
+             local msg = "UNDERFULL line(s) in margin note"
+             log_flaw(msg, line, colno, false)
+             ufirst = false
+          end
+       end
+    end
+    last = head
+    head = next
+  end
+  local textht = tex.getdimen("textheight")
+  if bottom > textht + margintol and MarginparPos then
+     pflag = true
+     local COLOR = luatypo.colortbl[17]
+     color_line (last, COLOR)
+     local msg = "Margin note too low"
+     log_flaw(msg, line, colno, false)
+  end
+  return bottom, pflag
+end
 local get_pagebody = function (head)
   local textht = tex.getdimen("textheight")
   local fn = head.list
@@ -917,7 +1010,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
@@ -949,6 +1042,7 @@
   vpos_min = vpos_min * 1.5
   local linewd = tex.getdimen("textwidth")
   local first_bot  = true
+  local done       = false
   local footnote   = false
   local ftnsplit   = false
   local orphanflag = false
@@ -964,6 +1058,7 @@
   local pageline = 0
   local ftnline = 0
   local line = 0
+  local bpmn = 0
   local body_bottom = false
   local page_bottom = false
   local pageflag = false
@@ -973,6 +1068,7 @@
     if head.id == HLIST and head.subtype == LINE and
           (head.height > 0 or head.depth > 0) then
        vpos = vpos + head.height + head.depth
+       done = true
        local linewd = head.width
        local first = head.head
        local ListItem = false
@@ -1024,20 +1120,23 @@
        elseif not footnote then
           parline = parline + 1
        end
-          if FirstWordMatch then
-             local flag = not ListItem and (line > 1)
-             firstwd, flag =
-                check_line_first_word(firstwd, first, line, colno,
-                                      flag, footnote)
-             if flag then
-                pageflag = true
-             end
+       if FirstWordMatch then
+          local flag = not ListItem and (line > 1)
+          firstwd, flag =
+             check_line_first_word(firstwd, first, line, colno,
+                                   flag, footnote)
+          if flag then
+             pageflag = true
           end
+       end
        if ShortFinalWord and pageline == 1 and parline > 1 and
           check_page_first_word(first, colno, footnote) then
           pageflag = true
        end
        local ln = slide(first)
+       if ln.id == RULE and ln.subtype == 0 then
+          ln = ln.prev
+       end
        local pn = ln.prev
        if pn and pn.id == GLUE and pn.subtype == PARFILL then
           hyphcount = 0
@@ -1189,6 +1288,22 @@
           color_line (head, COLOR)
           backpar = false
        end
+    elseif head and head.id == HLIST and head.subtype == BOX and
+           head.width > 0                                    then
+      if head.height == 0 then
+         bpmn, pflag = check_marginnote(head, line, colno, vpos, bpmn)
+         if pflag then pageflag = true end
+      else
+         local hf = head.list
+         if hf and hf.id == VLIST and hf.subtype == 0 then
+            break
+         else
+            vpos = vpos + head.height + head.depth
+            pageline = pageline + 1
+            line = pageline
+            page_bottom, body_bottom = check_EOP (nextnode)
+         end
+      end
     elseif head.id == HLIST and
           (head.subtype == EQN or head.subtype == ALIGN) and
           (head.height > 0 or head.depth > 0) then
@@ -1268,15 +1383,6 @@
        vpos = vpos + head.kern
     elseif head.id == VLIST then
        vpos = vpos + head.height + head.depth
-    elseif head.id == HLIST and head.subtype == BOX then
-       vpos = vpos + head.height + head.depth
-       pageline = pageline + 1
-       line = pageline
-       page_bottom, body_bottom = check_EOP (nextnode)
-       local hf = head.list
-       if hf and hf.id == VLIST and hf.subtype == 0 then
-          break
-       end
     end
   head = nextnode
   end
@@ -1287,30 +1393,37 @@
         luatypo.pagelist = luatypo.pagelist .. tostring(pageno) .. ", "
      end
   end
-  return head
+  return head, done
 end
 luatypo.check_page = function (head)
   local textwd = tex.getdimen("textwidth")
+  local textht = tex.getdimen("textheight")
+  local checked, boxed, n2, n3, col, colno
+  local body = get_pagebody(head)
+  local pageno = tex.getcount("c at page")
   local vpos = 0
-  local n2, n3, col, colno
-  local body = get_pagebody(head)
   local footnote = false
   local top = body
   local first = body.list
-  if (first and first.id == HLIST and first.subtype == BOX) or
-     (first and first.id == VLIST and first.subtype == 0) then
+  local next
+  local count = 0
+  if ((first and first.id == HLIST and first.subtype == BOX) or
+      (first and first.id == VLIST and first.subtype == 0))      and
+     (first.width == textwd and first.height > 0 and not boxed)  then
      top = body.list
-     first = top.list
+     if first.id == VLIST then
+        boxed = body
+     end
   end
   while top do
     first = top.list
+    next = top.next
     if top and top.id == VLIST and top.subtype == 0 and
-       top.width > textwd/2                              then
-       local next = check_vtop(top,colno,vpos)
-       if next then
-          top = next
-       elseif top then
-          top = top.next
+       top.width > textwd/2                             then
+       local n, ok = check_vtop(top,colno,vpos)
+       if ok then checked = true end
+       if n then
+          next = n
        end
     elseif (top and top.id == HLIST and top.subtype == BOX) and
            (first and first.id == VLIST and first.subtype == 0) and
@@ -1318,7 +1431,8 @@
        colno = 0
        for col in traverse_id(VLIST, first) do
            colno = colno + 1
-           check_vtop(col,colno,vpos)
+       local n, ok = check_vtop(col,colno,vpos)
+       if ok then checked = true end
        end
        colno = nil
        top = top.next
@@ -1330,15 +1444,21 @@
            colno = colno + 1
            local col = n.list
            if col and col.list then
-              check_vtop(col,colno,vpos)
+              local n, ok = check_vtop(col,colno,vpos)
+              if ok then checked = true end
            end
        end
        colno = nil
-       top = top.next
-    else
-       top = top.next
     end
+    if boxed and not next then
+       next = boxed
+       boxed = nil
+    end
+    top = next
   end
+  if not checked then
+     luatypo.failedlist = luatypo.failedlist .. tostring(pageno) .. ", "
+  end
   return true
 end
 return luatypo.check_page
@@ -1374,6 +1494,7 @@
     \luatypoSetColor{14}{cyan}%   Footnote split
     \luatypoSetColor{15}{red}%    Too short first (final) word on the page
     \luatypoSetColor{16}{LTline}% Line color for multiple flaws
+    \luatypoSetColor{17}{red}%    Margin note ending too low
     \luatypoBackPI=1em\relax
     \luatypoBackFuzz=2pt\relax
     \ifdim\parindent=0pt \luatypoLLminWD=20pt\relax
@@ -1384,6 +1505,7 @@
     \luatypoMinFull=3\relax
     \luatypoMinPart=4\relax
     \luatypoMinLen=4\relax
+    \luatypoMarginparTol=\baselineskip
    }%
 %% 
 %%



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