줄 가장자리에 짧은 단어가 표시되는 줄 바꿈을 방지하는 방법은 무엇입니까?

줄 가장자리에 짧은 단어가 표시되는 줄 바꿈을 방지하는 방법은 무엇입니까?

줄 가장자리에서 짧은 단어를 피하는 자동 방법이 있는지 궁금합니다. 특히 오른쪽 가장자리의 구두점 뒤의 짧은 단어와 왼쪽 가장자리의 구두점 앞의 짧은 단어를 피하는 것입니까?

예를 들어 문장이 I또는 로 시작하거나 쉼표 바로 뒤에 If있는 경우 and기본 간격 규칙을 유지하면서 자동으로 다음 줄로 밀어넣는 라텍스를 얻을 수 있습니까?이 질문비슷하지만 솔루션에 여전히 수동 수정이 필요한 것 같습니다(잘못 해석하지 않는 한).

(저는 pdflatex에서 일하고 있습니다)

답변1

여기에는 두 가지 목표가 있습니다.

  • 구두점 바로 뒤에 오는 짧은 단어 뒤에는 끊지 마세요.
  • 구두점 바로 앞에 오는 짧은 단어 앞에는 끊지 마십시오.

좋은 줄바꿈에 대한 정기적인 제약이 적용됩니다.

한 가지 간단한 해결책은 구두점을 특히 나누기에 좋은 위치로 선언하는 것입니다(음의 페널티, 충분히 큰 크기). 이것은 TeX가 다른 줄바꿈 고려사항(불량, 단점, 기타 처벌)과 구두점에서 중단을 시도하는 것을 절충할 수 있게 하지만 그러한 종류의 중단이 전혀 없다는 것을 보장하지는 않습니다.

설명하기 위해 전후는 다음과 같습니다.

주석이 달린 전후

보시다시피,

  • 첫 번째 문단에서 , it세 번째 줄 끝의 가 변경되어 다음 줄로 이동되었습니다.
  • 두 번째 문단에서는 el.네 번째 줄의 시작 부분과 at,여섯 번째 줄의 시작 부분이 변경 후 이전 줄로 이동되었습니다.
  • 세 번째 문단은 이 트릭이 보장되지 않는다는 것을 보여주기 위해 포함되었습니다. it.네 번째 줄의 시작 부분은 이전 줄에 맞출 방법이 없기 때문에 그대로 남아 있습니다.

이는 다음을 통해 달성되었습니다.

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

다음 문서에서:

\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}

노트:

  • .이와 같은 의미를 바꾸면 뭔가 가 깨지 더라도 놀라지 않을 것입니다 ,. (사실 이 예에서는 아무것도 엉망이 되지 않았다는 사실에 놀랐습니다. 그러다가 이미 읽은 토큰에는 catcode 변경 사항이 적용되지 않는다는 것을 깨달았습니다.)
  • 페널티를 조정할 수 있습니다. 예를 들어 -200을 사용했지만 -1에서 -9999까지의 페널티는 모두 적용됩니다.일부효과. (이 예에서 이러한 모든 변경 사항이 적용되는 임계값은 -175로 보이지만 -100에서도 한 가지 변경이 발생합니다.) ≤ -10000인 페널티는 줄 바꿈을 강제로 수행하는데 이는 원하는 바가 아닙니다.
  • 더 많은 구두점 문자( )에 대해 동일한 작업을 수행 ?!:;하거나 구두점 문자마다 다른 페널티를 적용할 수 있습니다.
  • \nonfrenchspacing구두점 뒤의 공백이 더 큰 (기본값)을 사용하면 상황이 조금 더 어려워집니다 . 가능할 수도 있지만 이러한 예를 생각해 내는 것은 많은 작업이므로 추구하지 않았습니다. 연습용으로 남겨두었습니다 :-)
  • LuaTeX를 사용하면 줄 바꿈 알고리즘을 변경할 수도 있습니다.보장하다줄 가장자리에 짧은 단어가 있으면 안 됩니다(필요한 경우).

편집하다: 나는 LuaTeX에서 "보장된" 솔루션을 구현하는 것을 거부할 수 없었습니다. 이 버전은 \frenchspacing\nonfrenchspacing. 이것이 하는 일은 특정 시퀀스를 감지하고 중단을 방지하기 위해 무한(10000) 페널티를 삽입하는 것입니다.

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

그리고

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

위의 예에서는 다음이 생성됩니다.

루아텍스와 함께

제약 조건이 매우 엄격하기 때문에 마지막 단락의 초과 상자에 주목하세요. 그러나 이것이 우리가 요청한 것입니다. (어쨌든 더 넓고 긴 문단이 포함된 상자가 너무 많아서 다시 작성하거나 추가하는 \emergencystretch등의 일반적인 방법으로 수정할 수 있습니다.)

위의 코드(및 심지어 아이디어)를 생성한 코드에는 LuaTeX 컴파일이 중단될 수도 있는 버그가 있을 가능성이 높지만, 그 내용은 다음과 같습니다.

\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}

어디에 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')

관련 정보