Skip to content

大致思路

竖直排版在 upTeX 系列的编译引擎中是天然支持的,而在 xelatex/pdflatex 中却不见得那么容易。由于汉字字形优美,方方正正的,所以断行分页算法都相对较容易实现。为此打算使用 tikz 来实现,步骤如下:

  • 首先是将所有需要排版的文字都存到一个 token 或者其他数据类型中
  • 每一个文字应该对应一个坐标,假设我们准备以 10×13\small 10 \times 13 的布局来排版,那么第 8 个文字的坐标应该是 (0,8)\small (0, 8),第 16 个文字的坐标应该是 (1,3)\small (1, 3)
  • 每一个点应该映射到页面上唯一的坐标
  • 遍历每一个汉字,然后完成渲染
  • 处理标点符号
  • ...

页面框线

使用下面代码可以在每一页都绘制好框线

latex
\AddToHook{shipout/background}{\put(1in, -1in){%
    \begin{tikzpicture}[remember picture, overlay]
        \draw[line width = 2pt] ([shift = {(1.8, -1.8)}]current page.north west) rectangle ([shift = {(-1.8, 1.8)}]current page.south east);
        \draw[thick] ([shift = {(2, -2)}]current page.north west) rectangle ([shift = {(-2, 2)}]current page.south east);
        \foreach \x in {0.1, 0.2, ..., 0.9} {
            \draw[thick] ([shift = {(2cm + \x*\textwidth, -2cm)}]current page.north west) --++ (0, -\textheight);
        }
    \end{tikzpicture}
}}

token 列表

latex3
\NewDocumentEnvironment{page}{+b}{
    \titlepage 
    \tl_set:Nn \l__words_tl {#1}
}
{
    \endtitlepage
}

使用

latex
\begin{page} 
那甄家丫鬟擷了花,方欲走時,猛抬頭見窗內有人,敝巾舊服,雖是貧窘,然生得腰圓背厚,麵闊口方,更兼劍眉星眼,直鼻權腮。這丫鬟忙轉身回避,心下乃想,這人生的這樣雄壯,卻又這樣襤褸,想他定是我家主人常説的什麼賈雨村了,每有意幫助週濟,隻是沒甚機會。我家並無這樣貧窘親友,想定是此人無疑了。
\end{page}

就可以将

latex
那甄家丫鬟擷了花,方欲走時,猛抬頭見窗內有人,敝巾舊服,雖是貧窘,然生得腰圓背厚,麵闊口方,更兼劍眉星眼,直鼻權腮。這丫鬟忙轉身回避,心下乃想,這人生的這樣雄壯,卻又這樣襤褸,想他定是我家主人常説的什麼賈雨村了,每有意幫助週濟,隻是沒甚機會。我家並無這樣貧窘親友,想定是此人無疑了。

存到 \l__words_tl 这个变量里面了

文字坐标

latex3
\cs_new_protected:Npn \__get_next_coordinate:NN #1#2 
{
    \int_incr:N \l_tmpb_int 
    \int_compare:nT {\l_tmpb_int > 12} {
        \int_zero:N \l_tmpb_int 
        \int_incr:N \l_tmpa_int
    }

    \int_set_eq:NN #1 \l_tmpa_int
    \int_set_eq:NN #2 \l_tmpb_int
}

使用这个函数将会得到下一个文字的坐标,例如

latex3
\__get_next_coordinate:NN \l__tmp_x_int \l__tmp_y_int

将会得到

latex3
\l__tmp_x_int = 0, \l__tmp_y_int = 0

再次使用

latex3
\__get_next_coordinate:NN \l__tmp_x_int \l__tmp_y_int

将会得到

latex3
\l__tmp_x_int = 0, \l__tmp_y_int = 1

坐标映射

latex3
\cs_new_protected:Npn \__eval_position_with_page:NNNN #1#2#3#4 
{
    \dim_set:Nn #3 { -2cm - 0.05\textwidth - #1\textwidth / 10 } 
    \dim_set:Nn #4 { -2cm - 0.08\textwidth - #2\textwidth / 10 } 
}

这个函数接受四个参数,前两个是输入坐标,后两个是返回的映射坐标(实际上的页面右上角的偏移向量)

遍历汉字

latex3
\int_step_inline:nn { \tl_count:N \l__words_tl }
{
    \exp_args:NNx \tl_if_in:NnTF \g__ignore_punct_tl {\tl_item:Nn \l__words_tl {##1}} 
    {
        \dim_add:Nn \l__tmp_x_dim {0.025\textwidth}
        \dim_add:Nn \l__tmp_y_dim {-0.025\textwidth}
        \tikz[remember~picture, overlay]
        \node[scale = 2, font = \kaishu, text = red] at ([shift = {
            (\dim_use:N \l__tmp_x_dim, \dim_use:N \l__tmp_y_dim)
        }]current~page.north~east) {\tl_item:Nn \l__words_tl {##1}};
    }
    {
        \__eval_position_with_page:NNNN \l__tmp_x_int \l__tmp_y_int \l__tmp_x_dim \l__tmp_y_dim
        \int_log:N \l__tmp_y_int
        \tikz[remember~picture, overlay]
        \node[scale = 2, font = \kaishu] at ([shift = {
            (\dim_use:N \l__tmp_x_dim, \dim_use:N \l__tmp_y_dim)
        }]current~page.north~east) {\tl_item:Nn \l__words_tl {##1}};
        \__get_last_coordinate:NN \l__tmp_x_int \l__tmp_y_int
    }

}

这一行是处理标点符号的分支

latex3
\exp_args:NNx \tl_if_in:NnTF \g__ignore_punct_tl {\tl_item:Nn \l__words_tl {##1}}

其它基本上就没啥了.

完整实现

latex3
\documentclass{ctexart}
\usepackage[margin = 2cm]{geometry}
\pagestyle{empty}
\usepackage{tikz}

\AddToHook{shipout/background}{\put(1in, -1in){%
    \begin{tikzpicture}[remember picture, overlay]
        \draw[line width = 2pt] ([shift = {(1.8, -1.8)}]current page.north west) rectangle ([shift = {(-1.8, 1.8)}]current page.south east);
        \draw[thick] ([shift = {(2, -2)}]current page.north west) rectangle ([shift = {(-2, 2)}]current page.south east);
        \foreach \x in {0.1, 0.2, ..., 0.9} {
            \draw[thick] ([shift = {(2cm + \x*\textwidth, -2cm)}]current page.north west) --++ (0, -\textheight);
        }
    \end{tikzpicture}
}}

\ExplSyntaxOn

    \tl_new:N \l__words_tl
    \int_new:N \l__tmp_x_int
    \int_new:N \l__tmp_y_int
    \dim_new:N \l__tmp_x_dim
    \dim_new:N \l__tmp_y_dim

    \tl_new:N \g__ignore_punct_tl 

    \tl_gset:Nn \g__ignore_punct_tl {,。}

    \NewDocumentCommand{\ignorepunct}{m}{\tl_gset:Nn \g__ignore_punct_tl{#1}}

    \cs_new_protected:Npn \__get_next_coordinate:NN #1#2 {
        \int_incr:N \l_tmpb_int 
        \int_compare:nT {\l_tmpb_int > 12} {
            \int_zero:N \l_tmpb_int 
            \int_incr:N \l_tmpa_int
        }

        \int_set_eq:NN #1 \l_tmpa_int
        \int_set_eq:NN #2 \l_tmpb_int
    }

    \cs_new_protected:Npn \__eval_position_with_page:NNNN #1#2#3#4 {
        \dim_set:Nn #3 { -2cm - 0.05\textwidth - #1\textwidth / 10 } 
        \dim_set:Nn #4 { -2cm - 0.08\textwidth - #2\textwidth / 10 } 
    }

    \NewDocumentEnvironment{page}{+b}{
        \titlepage 
        \tl_set:Nn \l__words_tl {#1}
        \int_step_inline:nn { \tl_count:N \l__words_tl }
        {
            \exp_args:NNx \tl_if_in:NnTF \g__ignore_punct_tl {\tl_item:Nn \l__words_tl {##1}} 
            {
                \dim_add:Nn \l__tmp_x_dim {0.025\textwidth}
                \dim_add:Nn \l__tmp_y_dim {-0.025\textwidth}
                \tikz[remember~picture, overlay]
                \node[scale = 2, font = \twkaiti, text = red] at ([shift = {
                    (\dim_use:N \l__tmp_x_dim, \dim_use:N \l__tmp_y_dim)
                }]current~page.north~east) {\tl_item:Nn \l__words_tl {##1}};
            }
            {

                \__eval_position_with_page:NNNN \l__tmp_x_int \l__tmp_y_int \l__tmp_x_dim \l__tmp_y_dim
                \int_log:N \l__tmp_y_int
                \tikz[remember~picture, overlay]
                \node[scale = 2, font = \twkaiti] at ([shift = {
                    (\dim_use:N \l__tmp_x_dim, \dim_use:N \l__tmp_y_dim)
                }]current~page.north~east) {\tl_item:Nn \l__words_tl {##1}};
                \__get_next_coordinate:NN \l__tmp_x_int \l__tmp_y_int
            }
        }
    }
    {
        \endtitlepage
    }
\ExplSyntaxOff

\begin{document}

\ignorepunct{,。}
\begin{page} 
那甄家丫鬟擷了花,方欲走時,猛抬頭見窗內有人,敝巾舊服,雖是貧窘,然生得腰圓背厚,麵闊口方,更兼劍眉星眼,直鼻權腮。這丫鬟忙轉身回避,心下乃想,這人生的這樣雄壯,卻又這樣襤褸,想他定是我家主人常説的什麼賈雨村了,每有意幫助週濟,隻是沒甚機會。我家並無這樣貧窘親友,想定是此人無疑了。
\end{page}

\begin{page} 
須臾茶畢,早已設下盃盤,那美酒佳餚自不必説。二人歸坐,先是款斟漫飲,次漸談至興濃,不覺飛觥限斝起來。當時街坊上家家簫管,戶戶絃歌,當頭一輪明月,飛彩凝輝,二人愈添豪興,酒到盃幹。雨村此時已有七八分酒意,狂興不禁,乃對月寓懷,口號一絶雲。時逢三五便團圓,滿把晴光護玉欄。陋室空堂,當年笏滿床,衰草枯楊。
\end{page}

\end{document}

效果

效果效果