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 I
oder beginnt If
oder 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:
Wie du sehen kannst,
- Im ersten Absatz
, it
wurde 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 dieat,
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 \frenchspacing
auch 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:
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 \emergencystretch
usw.).
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.lua
ist:
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')