Eu queria saber se existe uma maneira automática de evitar palavras curtas nas bordas das linhas - evitando especificamente palavras curtas após a pontuação na borda direita e palavras curtas antes da pontuação na borda esquerda?
Por exemplo, se uma frase começa com I
ou If
, ou há um and
direito após uma vírgula, podemos fazer com que o látex a empurre para a próxima linha automaticamente, mantendo as regras básicas de espaçamento?Essa questãoé semelhante, mas parece que a solução ainda requer uma correção manual (a menos que eu esteja interpretando errado).
(Estou trabalhando em pdflatex)
Responder1
Existem dois objetivos aqui:
- não interrompa após uma palavra curta que segue imediatamente a pontuação,
- não interrompa antes de uma palavra curta que precede imediatamente a pontuação,
sujeito a restrições regulares de boa quebra de linha.
Uma solução simples é declarar a pontuação como um lugar particularmente bom para quebrar (uma penalidade negativa, de magnitude suficientemente grande). Isso permitirá que o TeX negocie a tentativa de quebra de pontuação com suas outras considerações de quebra de linha (maldade, deméritos, outras penalidades), mas não garantirá que não haja absolutamente nenhuma quebra desse tipo.
Aqui está um antes e depois, para ilustrar:
Como você pode ver,
- No primeiro parágrafo, o
, it
final da terceira linha passou para a próxima linha após a alteração. - No segundo parágrafo, o que
el.
está no início da quarta linha e o que estáat,
no início da sexta linha passou para a linha anterior após a alteração. - O terceiro parágrafo foi incluído para mostrar que esse truque não é uma garantia: o que
it.
está no início da quarta linha permanece lá, porque simplesmente não há como encaixá-lo na linha anterior.
Isto foi conseguido com:
\catcode`.=\active \def.{\char`.\penalty -200\relax}
\catcode`,=\active \def,{\char`,\penalty -200\relax}
no seguinte documento:
\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}
Notas:
- Eu não ficaria surpreso se mudar os significados de
.
e,
assim quebrasse alguma coisa. (Na verdade, fiquei surpreso que nada tenha errado neste exemplo, então percebi que as alterações no catcode não se aplicam a tokens que já foram lidos.) - Você pode ajustar as penalidades: usei -200 apenas como exemplo, mas qualquer coisa entre -1 e -9999 teráalgunsefeito. (Neste exemplo, o limite para que todas essas alterações entrem em vigor parece ser -175, embora uma alteração aconteça mesmo em -100.) Uma penalidade ≤ -10000 força uma quebra de linha, o que não é o que você deseja.
- Você pode fazer o mesmo para mais caracteres de pontuação (
?!:;
) ou aplicar penalidades diferentes para caracteres de pontuação diferentes. - As coisas são um pouco mais difíceis com
\nonfrenchspacing
(o padrão), onde os espaços são maiores após a pontuação. Pode ser factível, mas criar esses exemplos deu muito trabalho, então não continuei. Deixado como exercício :-) - Com LuaTeX você pode até alterar o algoritmo de quebra de linha, o que seria uma maneira legal degarantiasem palavras curtas nas bordas das linhas (se é isso que você precisa).
Editar: Não resisti em implementar a solução “garantida” no LuaTeX. Esta versão deve funcionar com \frenchspacing
e \nonfrenchspacing
. O que ele faz é detectar certas sequências e inserir penalidades infinitas (10.000) para evitar uma quebra:
(punct, space, short_word, space) -> (punct, space, short_word, penalty, space)
e
(space, short_word, punct) -> (penalty, space, short_word, punct)
Para o exemplo acima, isso produz:
Observe a caixa cheia demais no último parágrafo porque as restrições são bastante rígidas, mas foi isso que pedimos. (De qualquer forma, você provavelmente não terá caixas muito cheias com parágrafos mais largos e longos, e poderá corrigi-los da maneira usual, reescrevendo ou adicionando \emergencystretch
e assim por diante.)
O código que produziu o texto acima (e até mesmo a ideia) possivelmente contém bugs que podem até causar o travamento da sua compilação LuaTeX, mas aqui está:
\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}
Onde strict.lua
é:
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')