CTeX-org / ctex-kit

Macro Packages and Scripts for Chinese TeX users
980 stars 124 forks source link

关于jiazhu宏包竖排时折行的夹注丢失一部分的问题 #616

Closed chenxiao445566 closed 2 years ago

chenxiao445566 commented 2 years ago

请看图 4de4d7e2de4b9ef46135e2db42073f4 竖排时,当夹注折行时,会有一部分丢失,如何解决? 代码如下

\documentclass[12pt]{ctexbook} 
\setCJKmainfont[RawFeature={vertical:+vert},BoldFont=SimHei]{SimSun}
\setCJKsansfont[RawFeature={vertical:+vert}]{SimHei}
\setCJKfamilyfont{zhfang}[RawFeature={vertical:+vert}]{SimFang.ttf}
\def\fangsong{\CJKfamily{zhfang}}
\xeCJKsetup{PunctStyle=kaiming,CheckSingle,RubberPunctSkip = false, PunctBoundWidth=0.3em}
\usepackage[a4paper, landscape,margin=1in]{geometry}

\newcommand*\CJKmovesymbol[1]{\raise.29em\hbox{#1}}
\newcommand*\CJKmove{%\punctstyle{plain}
  \let\CJKsymbol\CJKmovesymbol
  \let\CJKpunctsymbol\CJKsymbol} %修正baseline
  \AtBeginDocument{\Large \CJKmove \sloppy}

\usepackage{atbegshi}
\AtBeginShipout{\global\setbox\AtBeginShipoutBox\vbox{
    \special{pdf: put @thispage <</Rotate 90>>}
    \box\AtBeginShipoutBox}}

\usepackage{jiazhu}
\jiazhuset{
format =\fangsong,
beforeskip =0.5em plus 0.2em minus 0.2em,
afterskip =0.5em plus 0.2em minus 0.2em}

\begin{document}
世祖光武皇帝讳秀,字文叔,\jiazhu{测礼“祖有功而宗有德”,光武中兴,故庙称世祖。谥法:“能绍前业曰光,克定祸乱曰武。”伏侯古今注曰:“秀之字曰茂。伯、仲、叔、季,兄弟之次。长兄伯升,次仲,故字文叔焉。”}南阳蔡阳人,\jiazhu{南阳,郡,今邓州县也。蔡阳,县,故城在今随州枣阳县西南。}高祖九世之孙也,出自景帝生长沙定王发。\jiazhu{长沙,郡,今潭州县也。}发生舂陵节侯买,\jiazhu{舂陵,乡名,本属零陵泠道县,在今永州唐兴县北,元帝时徙南阳,仍号舂陵,故城在今随州枣阳县东。事具宗室四王传。}买生郁林太守外,\jiazhu{郁林,郡,今贵州县。前书曰:“郡守,秦官。秩二千石。景帝更名太守。”}外生钜鹿都尉回,\jiazhu{钜鹿,郡,今邢州县也。前书曰:“都尉,本{郡尉},秦官也。掌佐守,典武职,秩比二千石。景帝更名都尉。”}回生南顿令钦,\jiazhu{南顿,县,属汝南郡,故城在今陈州项城县西。前书曰:“令、长,皆秦官也。万户以上为令,秩千石至六百石;不满万户为长,秩五百石至三百石。”}钦生光武。光武年九岁而孤,养于叔父良。

身长七尺三寸,美须眉,大口,隆准,日角。\jiazhu{隆,高也。许负云:“鼻头为准。”郑玄尚书中候注云:“日角谓庭中骨起,状如日。”}性勤于稼穑,\jiazhu{种曰稼,敛曰穑。}而兄伯升好侠养士,常非笑光武事田业,比之高祖兄仲。\jiazhu{仲,合阳侯喜也,能为产业。见前书。}王莽天凤中,\jiazhu{王莽始建国六年改为天凤。}乃之长安,受尚书,略通大义。\jiazhu{东观记曰:“受尚书于中大夫庐江许子威。资用乏,与同舍生韩子合钱买驴,令从者僦,以给诸公费。”}

莽末,天下连岁灾蝗,寇盗锋起。\jiazhu{言贼锋锐竞起。字或作“蜂”,谕多也。}地皇三年,\jiazhu{天凤六年改为地皇。}南阳荒饥,\jiazhu{《韩诗外传》曰:“一谷不升曰歉,二谷不升曰饥,三谷不升曰馑,四谷不升曰荒,五谷不升曰大侵。”}诸家宾客多为小盗。光武避吏新野,\jiazhu{新野属南阳郡,今邓州县。《续汉书》曰:“伯升宾客劫人,上避吏于新野邓晨家。”}因卖谷于宛。\jiazhu{《东观记》曰:“时南阳旱饥,而上田独收。”宛,县,属南阳郡,故城今邓州南阳县也。}宛人李通等以图谶说光武云:“刘氏复起,李氏为辅。”\jiazhu{图,《河图》也。谶,符命之书。谶,验也。言为王者受命之征验也。《易·坤灵图》曰:“汉之臣李阳也。”}光武初不敢当,然独念兄伯升素结轻客,必举大事,且王莽败亡已兆,天下方乱,遂与定谋,于是乃市兵弩。十月,与李通从弟轶等起于宛,时年二十八。
\end{document}
RuixiZhang42 commented 2 years ago

通过 \CJKmove 移动基线是 undocumented method,是一个 quick-and-dirty hack,不建议使用。

请试着将 \newcommand*\CJKmovesymbol...\AtBeginDocument{\Large \CJKmove \sloppy} 这部分代码注释掉,jiazhu 默认设置的 ideohtratio=0.5 不需要修改、就是为竖排设置的。

RuixiZhang42 commented 2 years ago

问题分析

中易仿宋的字身高是 85.9375%,用 RawFeature=vertical 进行竖排时,XeTeX 会帮忙自动开启字体本身的特性 vertvrt2。但是,XeTeX 仍然是用横排的度量测量每个字(这是 XeTeX 引擎底层的 bug,没办法)。

这时候,你再用 \raise.29em\hbox{...} 将汉字抬高 29%,行内的汉字就会占据大约 115% 的高度,超过了夹注默认的行距 100%;夹注如果很长、需要折行,宏包内部是这么处理的:

% 夹注是一个三行的自然段,装着夹注内容的盒子大概长这样
第一行㊀㊁㊂㊃
㊄㊅第二行㊆㊇
㊈㊉剩余的夹注内容全部在第三行等着被折行

宏包接下来会尝试分离出两行的内容,更准确的说法是「分离出高度为 200% 的内容」。但因为第一行高到 115%,前两行加在一起就有 215%,取出两行内容就会溢出。所以,TeX 只会分离出第一行:

% 因为太高,只能分离出第一行,装进 \l__jiazhu_typeset_box
第一行㊀㊁㊂㊃

% 剩下的部分留在 \l__jiazhu_text_box 里面
㊄㊅第二行㊆㊇
㊈㊉剩余的夹注内容全部在第三行等着被折行

接下来的工作就是:

  1. 把分离出来的内容 repackage 一下,并输出;
  2. 把「等着被折行的内容」叠成新的两行并输出(当然,如果还装不下,准确来说会把剩余的内容变成一个三行的自然段,然后会重复上述步骤,折到下一行)。

第一步里面的 repackage 会做底部对齐。第二步只会取出 ㊈㊉剩余的夹注内容全部在第三行等着被折行 这个部分,而丢掉了 ㊄㊅第二行㊆㊇ 这个部分。

复现例子

抬高 29%,并且设置夹注为三行,考虑一个跨三行正文的长夹注:

% Expected result:
㈩㈨㈦ ㈤㈢㊆  正
︒︐㈧ ︙︙︙  文
 正  ︙︙㈠ ㊄㊂㊀
 文  ㈥㈣㈡ ㊅㊃㊁

% Actual result:
㈩㈨㈦ ㈢㊆   正
︒︐㈧ ︙︙   文
 正  ︙㈠  ㊂㊀
 文  ㈣㈡  ㊃㊁
RuixiZhang42 commented 2 years ago

@chenxiao445566 我看你已经试过注释掉 \CJKmove 了,问题应该消失了吧。

我想多啰嗦几句:这个 xeCJK+\raise.29em\hbox{...} 的办法,过去了这么多年,在我看来是颠倒主次、本末倒置的,可以算是我们简体中文TeX社区在摸索竖排过程中走的弯路。

横排的时候,各个厂家的字身高度不尽相同;竖排的时候,其实就根本没有这个问题,此时所有厂家的汉字皆被放置在基线对称的位置上。

comparefoundry

既然可以竖排,就说明文本内容是以汉字为主的,而文本中出现的拉丁字母(英文)、数字必然是少量的。能够转正的英文(如缩写)、数字(如单位数或双位数的月份日期)都是正立着竖排下去,长一点的数字如四位数的年份会把阿拉伯数字改写成汉字然后也是正立着竖排下去。真正「侧躺九十度」的只是一些零散的英文单词。

所以,如果你要匹配这些侧躺英文单词跟汉字的关系,去做所谓的对齐,那么要移动的不应该是汉字、不应该是逐个逐个汉字去 \raise.29em,动的应该是零散的英文单词,如 \lower.29em\hbox{English} \lower.29em\hbox{words}

顺便一提,这种「动英文、不动汉字」的方法是日文TeX社区的习惯做法。说到底还是因为人家到现在还经常用竖排、总结出了 best practice,我们简体中文社区不常用竖排、难免走弯路……

RuixiZhang42 commented 2 years ago

解决夹注第一行过高的伪算法

目前 \splittopskip 被设为零,可以扩大它,适当解决第一行过高的问题。分离的时候不再是分离出 n×\baselineskip,而是分离出 \splittopskip+(n−1)×\baselineskip

默认可以将 \splittopskip 设为 1.5\baselineskip,应该够了。第一行实在是太高,就让用户自生自灭吧(其实用户可以自行滥用 \jiazhuset{format=\setlength\splittopskip{...}} 设置更大的值)。

第一步

\penalty0+\vsplit\textbox to0pt 这个技巧,为夹注第一行的上方也加上 \splittopskip

...
\linespread{1}%
\splittopskip=\glueexpr\baselineskip*3/2\relax % <- 1.5\baselineskip
<user's code for format (a token list)>
...
\setbox\textbox=\vbox{%
  \penalty0 % neat trick
  \parshape <n+1>
    <zero dim> <jiazhu line length> % line 1
    ...
    <zero dim> <jiazhu line length> % line n
    <zero dim> <max dim>            % line n+1
  <jiazhu content>
}%
\setbox0=\vsplit\textbox to0pt % neat trick

第二步

分离出 \splittopskip+(n−1)×\baselineskip

\setbox\typesetbox=\vsplit\textbox
  to\dimexpr\splittopskip+<n-1>\baselineskip\relax

第三步

修正分离出来的 \typesetbox 的高度。

\setbox\typesetbox=\vbox{%
  \vskip-\splittopskip
  \vskip<height of jiazhu ideographs>
  \unvbox\typesetbox
}%

如果用了这种修正高度的方法,那就不需要支架了

第四步

测试 \textbox 是否为空。若已空,则结束,返回正文;若不空,则

\setbox\textbox=\vbox{%
  \penalty0
  \parshape ...
  \unhbox<the last line being carried over>
}%
\setbox0=\vsplit\textbox to0pt

然后返回第二步。

chenxiao445566 commented 2 years ago

感谢各位大佬指点!

qinglee commented 2 years ago

https://github.com/CTeX-org/ctex-kit/commit/e254c61a0f08f0c1f098d9bf5ab644697dd7b807 通过设置 \interlinepenalties 可以避免分离错误,但其实 \vsplit 不是必要的。我们可以在 \vbox 中通过 \prevgraf\lastbox 获得相应信息,然后处理。当然,无论是使用 \vsplit 还是 \lastbox,都要求 \vbox 垂直列上的内容比较规整,不能出现 whatsitmark 等结点。鉴于 jiazhu 的使用场景,我想这个要求并不过分。