탑 플로트 후 간격을 어떻게 개선할 수 있나요?

탑 플로트 후 간격을 어떻게 개선할 수 있나요?

문맥

\myCommand두 문단을 분리하고 상황에 따라 서로 다른 세로 간격을 추가하는 매크로(여기서는 )를 만들려고 합니다 . 그러나 나는 이것이 \section{...}.

일반적으로 값의 시작 부분과 끝 부분에 값 공백(이 예에서는 = 30pt \myLength사용 ) 을 삽입하고 싶습니다 . 매크로가 페이지 상단에 있으면 페이지 나누기 후 무시되고 수직 공백이 추가되지 않습니다. 이것이 바로 제가 원하는 것입니다. 그러나 이전 경우와 마찬가지로 매크로가 페이지 나누기 이후에 있고 하나 이상의 상위 부동 소수점 앞에 매크로가 있을 때 문제가 발생합니다. 이 경우 일관성을 유지하기 위해 매크로에 수직을 삽입하고 싶습니다 .\myLength\myCommand
\vspace{\myLength}
\vspace*{\dimexpr\myLength-\textfloatsep}


다음은 설명하는 예입니다.

아래에서 매크로는 올바르게 작동합니다. 페이지 나누기 후 페이지 상단에 오면 매크로 시작 부분에 공백이 삽입되지 않습니다. 페이지 상단에 있지 않으면 \vspace{\myLength}매크로 시작 부분에 올바르게 삽입됩니다.

여기에 이미지 설명을 입력하세요

그러나 매크로가 페이지 나누기 후 페이지 상단에 있고 앞에 상단 부동이 오면 공백이 완전히 불균형합니다.

여기에 이미지 설명을 입력하세요

문제가 일반적인 것이지 특정적인 것이 아니라는 것을 보여주기 위해 다음 \myCommand과 같은 문제가 있습니다 \section{...}.

여기에 이미지 설명을 입력하세요

다음은 작동하지 않는 최소한의 예입니다.

\documentclass{article}
\usepackage{lipsum, mwe}

\newlength{\myLength}
\setlength{\myLength}{30pt}% exaggerated value to illustrate

\newcommand{\myCommand}{\par
    \vspace{\myLength}
    {\centering\LARGE * * *\par}
    \vspace{\myLength}
}

\begin{document}
\lipsum[1]

\lipsum[1]

\lipsum[1]

\lipsum[2]

\begin{figure}[t]
\centering
\includegraphics{example-image}
\end{figure}

\myCommand

\lipsum[1]

\lipsum[2]

\myCommand

\lipsum[1]

\myCommand

\lipsum[1]
\end{document}

목표

위에서 언급한 문제를 극복하기 위해 다음과 같은 경우에만 \vspace*{\dimexpr\myLength-\textfloatsep}(상단 플로트에 의해 삽입된 수직 공간을 보상하기 위해 ) 값을 갖는 수직 공간을 원합니다 : 페이지 시작 시(페이지 나누기 후) AND 상단 플로트가 존재합니다.\textfloatsep

다른 모든 경우에는 사용이 \vspace{\myLength}완벽하게 작동합니다.


시험

내 (지금까지 성공하지 못한) 접근 방식은 두 가지 조건을 사용하여 올바른 수직 공간을 올바른 위치에 삽입하는 것이었습니다.

  • 현재 페이지에 상단 플로트가 있는지 테스트하는 첫 번째 조건입니다.
  • 페이지 나누기 후 새 페이지의 맨 위에 있는지 확인하는 두 번째 조건입니다.

두 조건이 모두 충족되면 실제로 페이지 시작 부분에 있고(페이지 나누기 후) 상단 플로트가 존재합니다. 그런 다음 를 삽입합니다 \vspace*{\dimexpr\myLength-\textfloatsep}. 다른 모든 경우에는 \vspace{\myLength}.

이전 질문 덕분에 페이지에서 페이지 상단 플로트의 존재를 감지하는 방법을 찾을 수 있었습니다.페이지에 상단 플로트가 있는지 여부를 문서 본문에서 확인하는 방법은 무엇입니까?.

그러나 두 번째 조건은 나에게 문제를 일으키고 를 사용해 보려고 했지만 \pagetotal적합하지 않은 것 같습니다.액세스 방법진실\pagetotal의 가치는?.

내 매크로는 \myCommand다음과 같습니다.

\makeatletter
\usepackage{refcount}
%From : https://tex.stackexchange.com/questions/712713/how-to-determine-from-the-document-body-whether-or-not-a-page-has-a-top-float
\def \@combinefloats {%
    \ifx \@toplist\@empty%
    \else%
        \@cflt%
        \immediate\write\@auxout{\string\global\string\@namedef{pageWithTopFloat-\thepage}{1}}%
    \fi%
    \ifx \@botlist\@empty \else \@cflb \fi%
}

\newcounter{ifTopFloatCnt}

\long\def\ifTopFloat#1#2{%
    \global\advance\c@ifTopFloatCnt\@ne%
    \label{\the\c@ifTopFloatCnt @ifTopFloat}\nopagebreak%
    \ifcsname pageWithTopFloat-\getpagerefnumber{\the\c@ifTopFloatCnt @ifTopFloat}\endcsname%
        #1%
    \else%
        #2%
    \fi%
}

\newlength{\myLength}
\setlength{\myLength}{30pt}% exaggerated value to illustrate

\newcommand{\mycommand}{\par%
    \ifTopFloat{%
        \ifdim\pagetotal=0pt% If there is a top float AND you are at the start of a new page
            \vspace*{\dimexpr\myLength-\textfloatsep}
            {\centering\LARGE * * *\par}
        \else% If there is a top float BUT you are not at the start of a new page
            \vspace{\myLength}
            {\centering\LARGE * * *\par}
        \fi
    }{% If there is no top float
        \vspace{\myLength}
        {\centering\LARGE * * *\par}
    }    
    \vspace{\myLength}
}
\makeatother

내 접근 방식은 확실히 올바른 접근 방식이 아닙니다.

답변1

LuaTeX 기반 솔루션은 다음과 같습니다. 명령 내부의 모든 내용은 \removefromtop페이지 상단에 있으면 제거되고 그렇지 않으면 유지됩니다. 여기서는 효과를 더욱 분명하게 만들기 위해 규칙을 사용하고 있지만 \vspace최종 문서에 대한 규칙을 (또는 다른 수직 모드 재질)로 대체할 수 있습니다.

\documentclass{article}

\usepackage{luacode}
\begin{luacode*}
    local attr = luatexbase.new_attribute("removefromtop")
    token.set_char("removefromtopattr", attr, "global")

    local cancel_output = false
    local last_cancelled = false

    luatexbase.add_to_callback("pre_output_filter", function (head)
        local outputpenalty = tex.outputpenalty
        if outputpenalty <= -10002 and outputpenalty >= -10005 then
            return true
        end

        if token.get_macro("@toplist") ~= "" then
            return true
        end

        for n in node.traverse(head) do
            if node.get_attribute(n, attr) == 1 then
                head = node.remove(head, n)
                cancel_output = true
            elseif n.id ~= node.id("penalty") and
                   n.id ~= node.id("glue")    and
                   n.id ~= node.id("kern")    and
                   n.id ~= node.id("mark")    and
                   n.id ~= node.id("whatsit") and
                   n.id ~= node.id("ins")
            then
                break
            end
        end

        if last_cancelled then
            last_cancelled = false
            for n in node.traverse_id(node.id("glue"), head) do
                if n.subtype == 10 then
                    n.width = -tex.baselineskip.width
                end
            end
        end

        if cancel_output then
            return head
        else
            return true
        end
    end, "remove_from_top")

    luatexbase.add_to_callback("buildpage_filter", function (info)
        if status.output_active then
            return
        end

        if not tex.output:match("%}%}") then
            tex.runtoks(function()
                tex.sprint[[\output\expandafter{\the\output}]]
            end)
        end

        tex.triggerbuildpage()

        if not (status.output_active and cancel_output) then
            return
        end

        cancel_output = false
        last_cancelled = true

        local level = 0
        repeat
            local t = token.get_next()
            if t.command == 1 then
                level = level + 1
            elseif t.command == 2 then
                level = level - 1
            end
        until level == 0

        local box = node.copy_list(tex.box[255])
        node.write(box)
        tex.setbox("global", 255, nil)
    end, "remove_from_top")
\end{luacode*}

\newcommand{\removefromtop}[1]{%
    \vbox attr \removefromtopattr=1 {%
        #1%
    }%
}

\usepackage{lipsum, mwe}

\newlength{\myLength}
\setlength{\myLength}{20pt}% exaggerated value to illustrate

\newcommand{\myCommand}{
    \removefromtop{
        \hrule height \myLength width \textwidth depth 0pt
    }
    \nobreak
    \vbox{
        {\centering\LARGE * * *\par}
        \hrule height \myLength width \textwidth depth 0pt
    }
}

\begin{document}
\lipsum[1]

\lipsum[1]

\lipsum[1]

\lipsum[2]

\begin{figure}[t]
\centering
\includegraphics{example-image}
\end{figure}

\myCommand

\lipsum[1]

\lipsum[2]

\myCommand

\lipsum[1]

\myCommand

\lipsum[1]

\lipsum[2]

\myCommand

\lipsum[1]

\myCommand

\lipsum[1-3]

\myCommand

\lipsum[1-9]

\myCommand

\lipsum[1]

\begin{figure}[t]
    \centering
    \includegraphics{example-image}
\end{figure}

\end{document}

산출

코드는 상당히 복잡하고 많이 테스트하지 않았으므로 더 복잡한 문서에는 일부 버그가 있을 것으로 예상됩니다.

작동 원리

먼저, 의 내용을 표시하는 데 사용할 수 있는 사용자 정의 "속성"을 정의합니다 \removefromtop.

buildpage_filter다음으로 TeX가 "주 수직 목록"에 콘텐츠를 추가하기 직전에 호출되는 에 연결합니다 . 여기서 까다로운 부분은 우리가 전화하는 것입니다tex.triggerbuildpage() 내부에이 콜백은 TeX가 "최근 기여"를 플러시하고 페이지 나누기를 시도하도록 트리거합니다.

페이지 나누기가 트리거되면 컨트롤이 pre_output_filter. 여기에는 이것이 "실제" 페이지 나누기인지(LaTeX가 부동 소수점을 플러시하기 위해 사용하는 가짜 페이지 나누기가 아님)인지 그리고 현재 페이지에 상단 부동이 없는지 먼저 확인하는 또 다른 콜백을 추가했습니다. 이 경우 페이지 콘텐츠를 반복하고 사용자 정의 속성으로 표시된 초기 콘텐츠를 제거합니다. 콘텐츠를 표시한 경우 이를 다시 우리에게 알리고 buildpage_filter새 페이지 콘텐츠를 반환합니다.

일단 pre_output_filter완료되면 제어는 이후에 다시 시작됩니다 tex.triggerbuildpage(). 단, 지금은 출력 루틴 내부에 있습니다. 가장 최근에 pre_output_filter성공적으로 일부 콘텐츠를 제거하고 첫 번째 출력 루틴 내부에 있는 경우 TeX 입력 스택의 모든 토큰을 제거합니다. 출력 루틴 내부에 있기 때문에 이는 \output토큰 목록의 현재 확장을 비웁니다. 그런 다음 \box255(현재 페이지의 내용)을 지우고 해당 내용을 최근 기여 목록으로 다시 이동한 다음 돌아갑니다.

왜 이렇게 할까요?

\removefromtop출력 루틴이 트리거될 때까지 상자가 페이지 상단에 있는지 여부를 알 수 없습니다 . 그러나 상자를 제거하면 \removefromtop페이지가 짧아지므로 pre_output_filter상자를 제거하기 위해 후크만 연결하면 모든 페이지가 짧아집니다.

대신 출력 루틴이 정상적으로 트리거되게 한 다음 상자 중 하나를 제거하면 \removefromtop현재 페이지의 내용을 최근 기여 목록으로 다시 푸시하고 출력 루틴을 취소한 다음 TeX가 정상적으로 진행하여 더 많은 내용을 추가하도록 합니다. 페이지 하단.

답변2

문제는 플로트 뒤에 추가된 수직 공간에 있는 것 같습니다. 그래서 아래 제안에서 a를 사용하여 수정 \myLength하고 추가합니다 \baselineskip.

\documentclass{article}
\usepackage{lipsum, mwe}

\newlength{\myLength}
\setlength{\myLength}{30pt}% exaggerated value to illustrate

% Below, I increase the separation between float and text and reduce
% the flexibility of the space
% You can see the default values by adding \the\textfloatsep inside
% the document environment
\newlength{\myFloatskip}
\setlength{\myFloatskip}{\the\myLength}
\addtolength{\myFloatskip}{\baselineskip}
\setlength{\textfloatsep}{\the\myFloatskip plus 0.0pt minus 0.0pt}

\newcommand{\myCommand}{\par%
    \vspace{\myLength}%
    {\centering\LARGE * * *\par}%
    \vspace{\myLength}%
}

\begin{document}

\lipsum[1]

\lipsum[1]

\lipsum[1]

\lipsum[2]

\begin{figure}[t]
\centering
\includegraphics{example-image}
\end{figure}

\myCommand

\lipsum[2]

\lipsum[3]

\myCommand

\lipsum[1]

\myCommand

\lipsum[1]
\end{document}

답변3

며칠간의 연구 끝에 내 문제에 대한 해결책을 찾은 것 같습니다. 아이디어는 다음과 같습니다.

출력 루틴에서 \@cflt명령은 적절한 경우 상단이 본문 텍스트와 함께 떠다니도록 조합합니다. 텍스트 본문의 시작을 나타내는 Y 좌표(즉, 페이지 상단이 떠 있는 직후)를 보조 파일에 기록하도록 이 명령을 수정했습니다. 를 실행할 때 \myCommand동일한 작업을 수행하고 매크로의 시작을 나타내는 Y 좌표를 보조 파일에 저장합니다.

다음 컴파일에서는 두 Y 좌표를 비교합니다. 동일하면 \myCommand상단 플로트 바로 뒤에 있으며 \vspace*{\dimexpr\myLength-\textfloatsep}질문에 요청한 대로 삽입할 수 있습니다.

결과는 다음과 같습니다.

여기에 이미지 설명을 입력하세요

이는 원래 결과보다 더 조화롭습니다(사소한 조정이 가능하지만).

여기에 이미지 설명을 입력하세요


그러나 테스트 중에 애플리케이션에 내재된 문제를 발견했습니다. 실제로 매크로에 의해 삽입된 공간은 페이지 내 매크로의 위치에 따라 달라지며, 이는 다시 매크로 등에 의해 삽입된 공간에 따라 달라집니다. 순환 종속성이 있습니다. 이를 방지하려면 \myCommand사용 방법에 따라 전후에 페이지 나누기를 방지해야 한다고 생각합니다 . 나는 개선을 위한 제안을 환영합니다.

예를 들어, 내 MWE에서 네 번째 단락에 \lipsum[2]대신 을 사용하면 \lipsum[3]불안정성이 발생하고 문서를 편집하면 아래 사례 A(왼쪽)와 B(오른쪽)가 연속적으로 표시됩니다.

여기에 이미지 설명을 입력하세요여기에 이미지 설명을 입력하세요

사례 A로 시작하면 매크로는 2페이지의 본문 텍스트 상단에서 끝납니다. 따라서 다음 컴파일에서는 의 시작 부분
에 와 같은 공백을 삽입합니다 . 이제 수직 공간이 수직 공간보다 작 으므로 명령이 페이지 1에 있을 공간이 충분하고 B의 경우입니다. 다음 컴파일에서는 더 이상 상단 부동 소수점 뒤에 있지 않으므로 다음을 삽입합니다. 수직 공간은 . 이번에는 1페이지에 공간이 부족하여 2페이지 처음으로 다시 보내지므로 사례 A로 돌아왔습니다.\myLength-\textfloatsep\myCommand\myLength-\textfloatsep\myLength
\myCommand\myLength\myCommand


MWE:

\documentclass{article}
\usepackage{lipsum, mwe, refcount, iftex}

\ifluatex% For compatibility with LuaTeX, which I generally use.
    \let\pdfsavepos\savepos
    \let\pdflastypos\lastypos
\fi

\makeatletter
\def \@cflt{%
    \let \@elt \@comflelt
    \setbox\@tempboxa \vbox{}%
    \@toplist
    \setbox\@outputbox \vbox{%
        \boxmaxdepth \maxdepth
        \unvbox\@tempboxa
        \vskip -\floatsep
        \topfigrule
        \vskip \textfloatsep
        \pdfsavepos% <--- Added
        \write\@auxout{\string\global\string\@namedef{@pageWithTopFloatYPos-\thepage}{\the\pdflastypos}}% <--- Added. We save the Y position of the top of "\@outputbox", i. e. the body of the text.
        \unvbox\@outputbox
    }%
    \let\@elt\relax
    \xdef\@freelist{\@freelist\@toplist}%
    \global\let\@toplist\@empty
}

\newcounter{@ifTopFloatCnt}

\long\def\ifTopFloat#1#2{% A conditional to see if there is a top float on the current page. See https://tex.stackexchange.com/questions/712713/how-to-determine-from-the-document-body-whether-or-not-a-page-has-a-top-float.
    \stepcounter{@ifTopFloatCnt}
    \label{@ifTopFloat-\the@ifTopFloatCnt}\nopagebreak%
    \ifcsname @pageWithTopFloatYPos-\getpagerefnumber{@ifTopFloat-\the@ifTopFloatCnt}\endcsname%
        #1%
    \else%
        #2%
    \fi%
}

\newlength{\myLength}
\setlength{\myLength}{30pt}% exaggerated value to illustrate

\newcounter{@myCommandCnt}
\newcounter{@myCommandCntAux}

\newcommand{\myCommand}{\par%
    \stepcounter{@myCommandCnt}%
    \pdfsavepos%
    \write\@auxout{%
        \string\stepcounter{@myCommandCntAux}%
        ^^J% New line in the auxiliary file
        \string\global\string\@namedef{@myCommandYPos-\string\the@myCommandCntAux}{\the\pdflastypos}% Save the Y coordinate.
    }%
    \ifTopFloat{%
        \ifnum \@nameuse{@myCommandYPos-\the@myCommandCnt} = 
            \csname @pageWithTopFloatYPos-\getpagerefnumber{@ifTopFloat-\the@ifTopFloatCnt}\endcsname% If there's a top float AND you're right after it
            \vspace*{\dimexpr\myLength-\textfloatsep}
            {\centering\LARGE * * * (a)\par}
        \else% If there is a top float BUT you are not right after it
            \vspace{\myLength}
            {\centering\LARGE * * * (b)\par}
        \fi
    }{% If there is no top float
        \vspace{\myLength}
        {\centering\LARGE * * * (c)\par}
    }    
    \vspace{\myLength}
}
\makeatother

\begin{document}
\lipsum[1]

\lipsum[1]

\lipsum[1]

\lipsum[3]% <--- If the current line is uncommented and the next line is commented, stability is achieved.
%\lipsum[2]% <--- If the previous line is commented and the current line is uncommented, instability occurs.

\begin{figure}[t]
\centering
\includegraphics{example-image}
\end{figure}

\myCommand

\lipsum[1]

\lipsum[2]

\begin{figure}[t]
\centering
\includegraphics{example-image-9x16}
\end{figure}

\myCommand

\lipsum[2]

\myCommand

\lipsum[1]

\myCommand

\lipsum[1]
\end{document}

관련 정보