Wie vermeidet man Zeilenumbrüche, die zu kurzen Wörtern an den Zeilenrändern führen?

Wie vermeidet man Zeilenumbrüche, die zu kurzen Wörtern an den Zeilenrändern führen?

Ich habe mich gefragt, ob es eine automatische Möglichkeit gibt, kurze Wörter an den Zeilenrändern zu vermeiden – insbesondere kurze Wörter nach der Zeichensetzung am rechten Rand und kurze Wörter vor der Zeichensetzung am linken Rand?

Wenn ein Satz beispielsweise mit Ioder beginnt Ifoder direkt nach einem Komma ein steht and, können wir Latex dazu bringen, es automatisch in die nächste Zeile zu verschieben und dabei trotzdem die grundlegenden Abstandsregeln beizubehalten?Diese Frageist ähnlich, aber es sieht so aus, als ob die Lösung noch eine manuelle Korrektur erfordert (es sei denn, ich interpretiere es falsch).

(Ich arbeite in pdflatex)

Antwort1

Hier gibt es zwei Ziele:

  • unterbrechen Sie nicht nach einem kurzen Wort, das unmittelbar auf ein Satzzeichen folgt,
  • unterbrechen Sie nicht vor einem kurzen Wort, das unmittelbar vor der Interpunktion steht,

unterliegt den regulären Einschränkungen eines guten Zeilenumbruchs.

Eine einfache Lösung besteht darin, Satzzeichen als besonders gute Umbruchstellen zu deklarieren (eine negative Strafe, die ausreichend groß ist). Dadurch kann TeX den Versuch, an Satzzeichen umzubrechen, mit seinen anderen Überlegungen zum Zeilenumbruch (Schlechtheit, Nachteile, andere Strafen) abwägen, aber es wird nicht garantiert, dass es absolut keine Umbrüche dieser Art gibt.

Hier ist ein Vorher-Nachher-Vergleich zur Veranschaulichung:

vorher-nachher-kommentiert

Wie du sehen kannst,

  • Im ersten Absatz , itwurde das Ende der dritten Zeile nach der Änderung in die nächste Zeile verschoben.
  • Im zweiten Absatz wurden die el.am Anfang der vierten Zeile und die at,am Anfang der sechsten Zeile nach der Änderung in die vorherige Zeile verschoben.
  • Der dritte Absatz wurde aufgenommen, um zu zeigen, dass dieser Trick keine Garantie ist: Das it.am Anfang der vierten Zeile bleibt dort, weil es einfach keine Möglichkeit gibt, es in die vorherige Zeile einzufügen.

Dies wurde erreicht durch:

\catcode`.=\active \def.{\char`.\penalty -200\relax}
\catcode`,=\active \def,{\char`,\penalty -200\relax}

im folgenden Dokument:

\documentclass{article}
\begin{document}
\frenchspacing % Makes it easier
\hsize=20em
\parskip=10pt

% First, three paragraphs with the default settings
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut blandit placerat justo, sed dictum sem. Donec erat elit, tincidunt non, it vel, tincidunt vehicula velit. Etiam pharetra ante at porta elementum. In nulla purus, faucibus non accumsan non, consequat eget.

Natis nulla blandit luctus tellus, sit amet posuere lacus maxius quis. In sit amet mattis est, a vehiula velit. Nam interum solicitudin el. In faucibus vulputate purus nec consectelur crass metus ipsum, blandit iln ullamcorpert at, portitor vita dolor. Duis sed mauris i inset inculis malesuada. Quisque laoret eu dui eget sage melittis corpum verborum.

Volutpat libero ac auctor. Donec semper, as id ultrices rhoncus, lectus nulla consequat nisi, ac sagitis risus lectus vel felis. Ut gravida it. Nam malesuada ante turpis eget. Ipsum factum verbum verdit.

\pagebreak

% Now the same text, with the meanings of . and , changed.
\catcode`.=\active \def.{\char`.\penalty -200\relax}
\catcode`,=\active \def,{\char`,\penalty -200\relax}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut blandit placerat justo, sed dictum sem. Donec erat elit, tincidunt non, it vel, tincidunt vehicula velit. Etiam pharetra ante at porta elementum. In nulla purus, faucibus non accumsan non, consequat eget.

Natis nulla blandit luctus tellus, sit amet posuere lacus maxius quis. In sit amet mattis est, a vehiula velit. Nam interum solicitudin el. In faucibus vulputate purus nec consectelur crass metus ipsum, blandit iln ullamcorpert at, portitor vita dolor. Duis sed mauris i inset inculis malesuada. Quisque laoret eu dui eget sage melittis corpum verborum.

Volutpat libero ac auctor. Donec semper, as id ultrices rhoncus, lectus nulla consequat nisi, ac sagitis risus lectus vel felis. Ut gravida it. Nam malesuada ante turpis eget. Ipsum factum verbum verdit.

% Change it back
\catcode`.=12 \catcode`,=12
\pagebreak

% Same text again, to show that nothing's permanently changed.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut blandit placerat justo, sed dictum sem. Donec erat elit, tincidunt non, it vel, tincidunt vehicula velit. Etiam pharetra ante at porta elementum. In nulla purus, faucibus non accumsan non, consequat eget.

Natis nulla blandit luctus tellus, sit amet posuere lacus maxius quis. In sit amet mattis est, a vehiula velit. Nam interum solicitudin el. In faucibus vulputate purus nec consectelur crass metus ipsum, blandit iln ullamcorpert at, portitor vita dolor. Duis sed mauris i inset inculis malesuada. Quisque laoret eu dui eget sage melittis corpum verborum.

Volutpat libero ac auctor. Donec semper, as id ultrices rhoncus, lectus nulla consequat nisi, ac sagitis risus lectus vel felis. Ut gravida it. Nam malesuada ante turpis eget. Ipsum factum verbum verdit.

\end{document}

Anmerkungen:

  • Es würde mich nicht überraschen, wenn durch eine solche Änderung der Bedeutung von .und ,etwas kaputt geht. (Tatsächlich war ich überrascht, dass in diesem Beispiel nichts durcheinander geraten ist, dann wurde mir klar, dass Catcode-Änderungen nicht für Token gelten, die bereits eingelesen wurden.)
  • Sie können die Strafen anpassen: Ich habe -200 nur als Beispiel verwendet, aber alles von -1 bis -9999 hatmancheWirkung. (In diesem Beispiel liegt der Schwellenwert, ab dem alle diese Änderungen wirksam werden, anscheinend bei -175, obwohl eine Änderung sogar bei -100 erfolgt.) Eine Strafe von ≤ -10000 erzwingt einen Zeilenumbruch, was nicht erwünscht ist.
  • Sie können dasselbe für mehrere Satzzeichen tun ( ?!:;) oder für verschiedene Satzzeichen unterschiedliche Strafen festlegen.
  • Bei (der Standardeinstellung) ist es etwas schwieriger \nonfrenchspacing, da hier die Leerzeichen nach der Interpunktion größer sind. Es ist vielleicht machbar, aber diese Beispiele zu finden, war eine Menge Arbeit, also habe ich es nicht weiter verfolgt. Als Übung gelassen :-)
  • Mit LuaTeX können Sie sogar den Zeilenumbruch-Algorithmus ändern, was eine coole Möglichkeit wäre,Garantiekeine kurzen Wörter an den Zeilenrändern (falls Sie das benötigen).

Bearbeiten: Ich konnte nicht widerstehen, die „garantierte“ Lösung in LuaTeX zu implementieren. Diese Version sollte sowohl mit als \frenchspacingauch funktionieren \nonfrenchspacing. Sie erkennt bestimmte Sequenzen und fügt unendlich viele (10000) Strafen ein, um einen Abbruch zu verhindern:

(punct, space, short_word, space) -> (punct, space, short_word, penalty, space)

Und

(space, short_word, punct) -> (penalty, space, short_word, punct)

Für das obige Beispiel ergibt sich daraus:

mit LuaTeX

Beachten Sie das überfüllte Feld im letzten Absatz, da die Einschränkungen ziemlich streng sind, aber genau darum haben wir gebeten. (Bei breiteren und längeren Absätzen werden Sie wahrscheinlich keine überfüllten Felder haben und Sie können sie auf die übliche Weise korrigieren, indem Sie sie umschreiben oder hinzufügen \emergencystretchusw.).

Der Code, der das Obige hervorgebracht hat (und sogar die Idee), enthält möglicherweise Fehler, die sogar dazu führen können, dass Ihre LuaTeX-Kompilierung abstürzt, aber hier ist er:

\documentclass{article}
\directlua{dofile("strict.lua")}
\begin{document}
\frenchspacing % Keeping same example as before
\hsize=20em
\parskip=10pt

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut blandit placerat justo, sed dictum sem. Donec erat elit, tincidunt non, it vel, tincidunt vehicula velit. Etiam pharetra ante at porta elementum. In nulla purus, faucibus non accumsan non, consequat eget.

Natis nulla blandit luctus tellus, sit amet posuere lacus maxius quis. In sit amet mattis est, a vehiula velit. Nam interum solicitudin el. In faucibus vulputate purus nec consectelur crass metus ipsum, blandit iln ullamcorpert at, portitor vita dolor. Duis sed mauris i inset inculis malesuada. Quisque laoret eu dui eget sage melittis corpum verborum.

Volutpat libero ac auctor. Donec semper, as id ultrices rhoncus, lectus nulla consequat nisi, ac sagitis risus lectus vel felis. Ut gravida it. Nam malesuada ante turpis eget. Ipsum factum verbum verdit.
\end{document}

wo strict.luaist:

function is_punct(n)
   if node.type(n.id) ~= 'glyph' then return false end
   if n.char > 127 then return false end
   c = string.char(n.char)
   if c == '.' or c =='?' or c == '!' or c == ':' or c == ';' or c == ',' then
      return true
   end
   return false
end

function no_punct_short_word_eol(head)
   -- Prevents having a line that ends like "<punctuation><space><short_word>"
   -- How we do this:
   --   (1) detect such short words (punct, space, short_word, space)
   --   (2) insert a penalty of 10000 between the short_word and the following space.
   -- More concretely:
   --   * A punctuation is one of .?!:;, which are the ones affected by \frenchspacing
   --   * A space is any glue node.
   --   * A short_word is a sequence of only glyph and kern nodes.
   -- So we maintain a state machine: default -> seen_punct -> seen_space -> seen_word
   -- where in the last state we maintain length. If we're in seen_word state and we see
   -- a glue, and length is less than threshold, insert a penalty before the glue.
   state = 'default'
   root = head
   while head do
      if state == 'default' then
         if is_punct(head) then
            state = 'seen_punct'
         end
      elseif state == 'seen_punct' then
         if node.type(head.id) == 'glue' then
            state = 'seen_space'
         else
            state = 'default'
         end
      elseif state == 'seen_space' then
         if node.type(head.id) == 'glyph' then
            state = 'seen_word'
            length = 1
         elseif is_punct(head) then
            state = 'seen_punct'
         else
            state = 'default'
         end
      elseif state == 'seen_word' then
         if node.type(head.id) == 'glue' and length <= 2 then
            -- Moment of truth
            penalty = node.new('penalty')
            penalty.penalty = 10000
            root, new = node.insert_before(root, head, penalty)
            -- TODO: Is 'head' invalidated now? Docs don't say anything...
            state = 'default'
         elseif node.type(head.id) == 'glyph' or node.type(head.id) == 'kern' then
            if node.type(head.id) == 'glyph' then length = length + 1 end
         else
            state = 'default'
         end
      else
         assert(false, string.format('Impossible state %s', state))
      end
      head = head.next
   end
   return root
end
luatexbase.add_to_callback('pre_linebreak_filter', no_punct_short_word_eol, 'Prevent short words after punctuation at end of sentence')

function no_bol_short_word_punct(head)
   -- Prevents having a line that starts like "<short_word><punctuation>"
   -- How we do this:
   --   (1) detect such short words (space, short_word, punct)
   --   (2) insert a penalty of 10000 between the space and the following short_word.
   -- More concretely:
   --   * A punctuation is one of .?!:;, which are the ones affected by \frenchspacing
   --   * A space is any glue node.
   --   * A short_word is a sequence of only glyph and kern nodes.
   -- So we maintain a state machine: default -> seen_space -> seen_word
   -- where in the last state we maintain length. If we're in seen_word state and we see
   -- a punct, and length is less than threshold, insert a penalty before the glue.
   -- Note that for this to work, we need to maintain a pointer to where we saw the glue.
   state = 'default'
   root = head
   before_space = nil
   while head do
      if state == 'default' then
         if node.type(head.id) == 'glue' then
            state = 'seen_space'
            before_space = head.prev
         end
      elseif state == 'seen_space' then
         if node.type(head.id) == 'glyph' then
            state = 'seen_word'
            length = 1
         else
            state = 'default'
         end
      elseif state == 'seen_word' then
         if is_punct(head) and length <= 2 then
            -- Moment of truth
            penalty = node.new('penalty')
            penalty.penalty = 10000
            root, new = node.insert_after(root, before_space, penalty)
            -- TODO: Is 'head' invalidated now? Docs don't say anything...
            state = 'default'
         elseif node.type(head.id) == 'glyph' or node.type(head.id) == 'kern' then
            if node.type(head.id) == 'glyph' then length = length + 1 end
         elseif node.type(head.id) == 'glue' then
            state = 'seen_space'
            before_space = head.prev
         else
            state = 'default'
         end
      else
         assert(false, string.format('Impossible state %s', state))
      end
      head = head.next
   end
   return root
end
luatexbase.add_to_callback('pre_linebreak_filter', no_bol_short_word_punct, 'Prevent short words at beginning of sentence before punctuation')

verwandte Informationen