Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xeCJK: 关于重构的一些要点 #511

Open
RuixiZhang42 opened this issue May 22, 2020 · 20 comments
Open

xeCJK: 关于重构的一些要点 #511

RuixiZhang42 opened this issue May 22, 2020 · 20 comments

Comments

@RuixiZhang42
Copy link
Member

目的

开这条 issue 的目的是为了统一整合 xeCJK 未来重构需要注意的要点。个人能力有限,会有总结不到位之处,欢迎大家补充。

本 issue 的结构

这条评论里提出第一个要点,未来的要点在下面依次提出。每个要点关注某些具体方面,会提供 MWE、截取相关 log、指出目前的不足、提出「理想的效果」。

单个标点符号的处理

通过如下 MWE 可以看出 xeCJK 其实对待标点符号也是「加法模式」,但是「过于激进」。

\documentclass{article}
\usepackage{xeCJK}
\newcommand\sampletext{逗,句。号}
\begin{document}

\showboxbreadth=\maxdimen
\showboxdepth=\maxdimen

\CJKfontspec{SimSun.ttc}%
\sampletext

\CJKfontspec{SourceHanSerifSC-Regular.otf}[Language=Chinese Simplified]%
\sampletext

\CJKfontspec{SourceHanSerifSC-Regular.otf}[Language=Chinese Traditional]%
\sampletext

\showlists

\end{document}

log 里相关的部分如下:

...
.\TU/SimSun.ttc(0)/m/n/10 逗
.\penalty 10000
.\TU/SimSun.ttc(0)/m/n/10 ,
.\rule(0.0+0.0)x-7.22656
.\glue 7.22656 minus 6.01563
.\glue 0.0 plus 0.96002
.\TU/SimSun.ttc(0)/m/n/10 句
.\penalty 10000
.\TU/SimSun.ttc(0)/m/n/10 。
.\rule(0.0+0.0)x-6.44531
.\glue 6.44531 minus 5.07813
.\glue 0.0 plus 0.96002
.\TU/SimSun.ttc(0)/m/n/10 号
...
.\TU/SourceHanSerifSC-Regular.otf(0)/m/n/10 逗
.\penalty 10000
.\TU/SourceHanSerifSC-Regular.otf(0)/m/n/10 ,
.\rule(0.0+0.0)x-7.71
.\glue 7.71 minus 6.95001
.\glue 0.0 plus 0.96002
.\TU/SourceHanSerifSC-Regular.otf(0)/m/n/10 句
.\penalty 10000
.\TU/SourceHanSerifSC-Regular.otf(0)/m/n/10 。
.\rule(0.0+0.0)x-6.77
.\glue 6.77 minus 6.35
.\glue 0.0 plus 0.96002
.\TU/SourceHanSerifSC-Regular.otf(0)/m/n/10 号
...
.\TU/SourceHanSerifSC-Regular.otf(1)/m/n/10 逗
.\penalty 10000
.\TU/SourceHanSerifSC-Regular.otf(1)/m/n/10 ,
.\rule(0.0+0.0)x-7.71
.\glue 7.71 minus 6.95001
.\glue 0.0 plus 0.96002
.\TU/SourceHanSerifSC-Regular.otf(1)/m/n/10 句
.\penalty 10000
.\TU/SourceHanSerifSC-Regular.otf(1)/m/n/10 。
.\rule(0.0+0.0)x-6.77
.\glue 6.77 minus 6.35
.\glue 0.0 plus 0.96002
.\TU/SourceHanSerifSC-Regular.otf(1)/m/n/10 号
...

问题分析

以全角句号 U+3002 为例,目前的效果是

<字> <penalty> <句号> <rule> <glue> <CJKglue> <字>

通过 log 可见两大不足:

  1. \rule 的宽度随字体而变,例如中易宋体的句号后面紧跟 -64.453125% 宽的 rule,而思源宋体(简体)的句号后面紧跟 -67.7% 宽的 rule;
  2. 思源宋体切换成繁体之后,句号已经居中,但其后的 rule 仍然是 -67.7% 宽。

重构要点

我们从铅字排印入手,提出如下两条要点:

  1. 我们应该摒弃目前的「测量标点字面」的做法,对那些占据字面小于 50% 的标点,不应该「抹掉全部空白」,而应该「统一抹掉 50%」;
  2. 我们应该分离「避头尾禁则」与「字面位置」这两个属性。例如「全角句号」是 closing 标点,不可以出现在行首,这是「禁则」属性;与之独立的应该还有「偏左半边」、「偏右半边」、「居中一半」、「居中占满」这些「字面位置」属性,其中「偏左半边」、「偏右半边」可以直接对应到直排/竖排的「偏上半边」、「偏下半边」,而保持代码实现不变。

组合:closing + 偏左半边

例如

  • 横竖排皆适用:简体中文跟日文用到的逗号 U+FF0C、句号 U+3002、顿号 U+3001、点号 U+FF0E
  • 仅限横排适用:简体中文用到的冒号 U+FF1A、分号 U+FF1B、叹号 U+FF01、问号 U+FF1F

理想效果应该是

<字>
<nobreak CJKglue> % 支持疏排,标点、汉字一起被拉开
% <nobreak penalty> 此处不再需要 penalty 了,因为已经不是合法的断行点了
<这种标点>
<rule: -50%> % 抹掉二分空白
<glue: 50% minus 50%> % 这里是可断行的位置
<CJKglue>
<字>

组合:closing + 偏右半边

这种组合不可能。

组合:closing + 居中一半

例如

  • 横竖排皆适用:繁体中文用到的逗号 U+FF0C、句号 U+3002、顿号 U+3001、点号 U+FF0E,日文用到的冒号 U+FF1A
  • 仅限横排适用:繁体中文用到的冒号 U+FF1A、分号 U+FF1B,日文用到的分号 U+FF1B

理想效果应该是

<字>
<nobreak CJKglue> % 支持疏排,标点、汉字一起被拉开
% <nobreak penalty> 此处不再需要 penalty 了,因为已经不是合法的断行点了
<nobreak glue: 25% minus 25%> % 这里不可断行
<rule: -25%> % 抹掉四分空白
<这种标点>
<rule: -25%> % 抹掉四分空白
<glue: 25% minus 25%> % 这里是可断行的位置
<CJKglue>
<字>

组合:closing + 居中占满

例如

  • 横竖排皆适用:繁体中文跟日文用到的叹号 U+FF01、问号 U+FF1F
  • 仅限竖排适用:简体中文、繁体中文跟日文用到的分号 U+FF1B,简体中文用到的叹号 U+FF01、问号 U+FF1F,等等。

理想效果应该是

<字>
<nobreak CJKglue> % 支持疏排,标点、汉字一起被拉开
% <nobreak penalty> 此处不再需要 penalty 了,因为已经不是合法的断行点了
<这种标点> % 没有空间可以挤压的标点
<CJKglue>
<字>

其余组合:opening + XXXX

如上分析,可以依次给出理想的效果。

@RuixiZhang42
Copy link
Member Author

单个标点符号的处理(示意图)

数了一下,好像只有4种组合(拓展还是重写?):

  1. closing + 偏左半边/上半边;
  2. closing + 居中一半;
  3. closing + 居中占满;
  4. opening + 偏右半边/下半边。

combo1

combo2

combo3

combo4

@qinglee
Copy link
Member

qinglee commented May 23, 2020

嗯,当初模型没选好,做法激进了些。

另外,xeCJK 在标点后面加的 \vrule 的宽度是通过 \XeTeXglyphbounds 直接从字体中取得的。但 \XeTeXglyphbounds 似乎对 language 标识的支持有问题,导致顶楼的例子中,繁体中文居中的标点度量信息不准确。

简单的例子:

\font\zhs   = "[SourceHanSerifSC-Regular.otf]" at 10pt \relax
\font\zhti  = "[SourceHanSerifSC-Regular.otf];language=ZHT" at 10pt \relax
\font\zhtii = "[SourceHanSerifTC-Regular.otf]" at 10pt \relax

\def\SHOW{\showthe\XeTeXglyphbounds 3 \XeTeXcharglyph`,\relax}

\zhs   \SHOW
\zhti  \SHOW
\zhtii \SHOW

\bye

结果是

> 7.71pt.
<to be read again> 
                   \relax 
l.8 \zhs   \SHOW
                
? 
> 7.71pt.
<to be read again> 
                   \relax 
l.9 \zhti  \SHOW
                
? 
> 4.34pt.
<to be read again> 
                   \relax 
l.10 \zhtii \SHOW

\zhti\zhtii 的字形是一样的,但 \XeTeXglyphbounds\zhti 的度量结果不准确。

@RuixiZhang42
Copy link
Member Author

@qinglee 嗯,最早我是在「破折号宽度」那个 issue 里发现的,其实不只是 language 的问题,而是 GSUB 的问题。通过

\XeTeXglyphbounds <int> \XeTeXcharglyph`<char>

测量出来的值全都对应被替换之前的,如果给简体中文字体加上 vertical 的标签,那么度量逗号 的结果仍然是对应横排符号,而不是直排符号。

  1. 这就是为什么我提议「摒弃 \XeTeXglyphbounds」,GSUB 到 glyph 都变了,但度量仍然是替换之前的,不可靠(也不稳定),还不如统一「抹掉二分空」;
  2. 就算 \XeTeXglyphbounds 可以提取替换之后的度量,标点符号的挤压也得重构。例如逗号 ,现在的规则是「抹掉右边全部、再补上 glue」,替换成繁体逗号之后,不能直接「用正确的度量代入现有规则」,因为这样的结果是只挤压了右侧,繁体必须改成「抹掉左右两边、再分别补上 glue」。这就是我提到的第二点「分离属性、给各种组合设置不同的挤压方案」。

@qinglee
Copy link
Member

qinglee commented May 23, 2020

目前居中标点是在 FullLeftFullRight 类当作特例处理的,实现起来比较省力,其实并不合理也低效。

@RuixiZhang42
Copy link
Member Author

@qinglee 我觉得新模型可以试一试,我加了一小节「兼容性讨论」:

backward-compatibility

@RuixiZhang42
Copy link
Member Author

代码得再整理整理,但是效果还不错的样子。

% 只载入 fontspec,不载入 xeCJK,从底层重建
\makeatletter
\ExplSyntaxOn

\tl_new:N  \l_@@_CJKglue_tl
\tl_set:Nn \l_@@_CJKglue_tl { 0em plus 0.25em }
\fp_new:N  \l_@@_punct_width_ratio_fp
\fp_set:Nn \l_@@_punct_width_ratio_fp { 1 } % 1=全角样式,0.5=半角样式

\XeTeXinterchartokenstate = \c_one_int

\newXeTeXintercharclass \g_@@_CJK_class
\newXeTeXintercharclass \g_@@_Closing_LeftHalf_class
\newXeTeXintercharclass \g_@@_Closing_MiddleHalf_class
\newXeTeXintercharclass \g_@@_Closing_Full_class
\newXeTeXintercharclass \g_@@_Opening_RightHalf_class
\newXeTeXintercharclass \g_@@_Opening_Full_class

% 省略 300 多行非常粗糙的实验代码……

对简体中文横排的默认设置如下

scsetup

试排《后汉书》

全角样式:
scquanjiao

半角样式:
scbanjiao

对繁体中文横排需要重新分配类型

全角样式:
tcquanjiao

半角样式:
tcbanjiao

@tanukihee
Copy link
Contributor

tanukihee commented May 25, 2020

繁中的感叹号是可以左右挤压的吗?在 clreq 的此条评论中

不可调整的标点包括:中国大陆 GB 式的半字连接号、间隔号、分隔号,因为这几个标点固定半个字宽;横排的港台式问号、感叹号和直排的冒号、分号、问号、感叹号(包括 GB 偏靠式和港台居中式),因为这几个标点固定一个字宽。

似乎认为繁体感叹号跟问号一样是不可挤压的。

@RuixiZhang42
Copy link
Member Author

@tanukihee

  1. 简中叹号 GB 里说「可以挤压」,日文叹号 JIS 里说「禁止挤压」,繁中我没查到港台的相关文献(欢迎补充)。
  2. 我现在把繁中横排的叹号设为「居中一半」,很大原因是个人审美:叹号这么苗条,感觉它的两端能被挤压好看一点。不可挤压的话,那么叹号在半角样式下仍然全宽,有点丑……
  3. 重构之后,各语言的默认 configurations 也不是定死的,还得设计 UI 允许用户 customize,比如下面这个「简中、横排、半角」的例子:
    yahei
  4. 底层的新模型还没写完呢,标点到标点的挤压规则还没开始仔细研究,这个必须是 long term effort,且等着……

@tanukihee
Copy link
Contributor

tanukihee commented May 26, 2020

标点到标点的挤压规则还没开始仔细研究

感觉这个可以直接参考 LuaTeX-ja 的默认标点挤压规则了,在 texmf-dist/tex/luatex/luatexja 下的 jfm-ujis.lua(横排)和 jfm-ujisv.lua(竖排)内,默认规则很详细,调整方法跟 InDesign 类似,也挺直观的。

@tanukihee
Copy link
Contributor

JIS 对于中间标点的密排规定还有些特殊:
「(行末における)中点類の前は原則として四分アキとし,後ろはベタ組とする.((行末时)中间标点前原则上加四分空,后密排)」
效果如下图
image

不过应该可以先暂时不管这个

@RuixiZhang42
Copy link
Member Author

@tanukihee JIS 对中间标点的规定已经实现呀,按照下面这个算法

<closing + 居中一半>
<rule: -25%> % 抹掉四分空白
<glue: 25% minus 25%> % 这里是可断行的位置
<CJKglue>
<字>

如果 TeX 决定在 <glue: 25% minus 25%> 处断行,那么 <closing + 居中一半> 就会「密排」在行末(因为这个标点右边的「四分空」被 <rule: -25%> 抹掉了)。你可以回头看看繁中全角的那张图,行末的句号、逗号都是占据 3/4 的空间(前面四分空,加本身二分)。

另外,你展示的那张图里有个比较迷的问题:

  • 右起第4行,行末句号占据全角空间;右起第5行,行末顿号占据半角空间。

我猜第4行是「为了网格对齐,牺牲行末对齐」,但是第5行又「为了行末对齐,牺牲网格对齐」,很不一致啊。按照目前「closing + 偏左半边/上半边」的算法,是处于行末的偏靠式标点一律占据半角空间(段末除外),这是符合 GB/T 15834—2011 的:

[GB/T 15834—2011] 5.1.10 标点符号排在一行末尾时,若为全角字符则应占半角字符的宽度(即半个字位置),以使视觉效果更美观。

@tanukihee
Copy link
Contributor

tanukihee commented May 28, 2020

右起第 4 行,行末句号占据全角空间;右起第 5 行,行末顿号占据半角空间。

JIS 中句号在末尾是不能调整的
「句点類の後ろは,行末に配置する場合を含めて必ず二分アキを確保する.この二分アキは,行の調整処理の詰める場合の対象にしてはならない.」
(句点类后的二分空必须保留,即使在在行末,也不能作为行调整的挤压对象)

但是在 DTP 中,句点后面的二分空也会去掉
image

这大概也能算是一种标准与实际的脱节?

回头看看繁中全角的那张图,行末的句号、逗号都是占据 3/4 的空间(前面四分空,加本身二分)。

繁中的标点挤压我并不太清楚,不过 MS Word 的行末标点确实是优先调右空白,再调左空白的……在大多数 DTP 软件中(比如 Adobe InDesign)中,中点前后的空白是要一起挤压,前后必须相同的,看来是我先入为主了😂

@RuixiZhang42
Copy link
Member Author

RuixiZhang42 commented May 28, 2020

@tanukihee

中点前后的空白是要一起挤压,前后必须相同的

新算法的确是前后空白一起挤压。但是断行时发生的事情,是另外一回事。举例「繁中全角的句号」,出现在行中时大概是这样的:

% 繁中全角
... 行中句号 <可挤压四分> <二分居中句号> <可挤压四分> 后文继续 ...

而出现在行末时会变成这样:

% 繁中全角
... ... ... ... 行末句号 <可挤压四分> <二分居中句号>
下一行后文继续 ...

所谓「前后必须相同的」,其实是要求保留行末句号后面的 <可挤压四分>,跟行中句号一致:

% 繁中全角
... ... ... ... 行末句号 <可挤压四分> <二分居中句号> <可挤压四分>
下一行后文继续 ...

小结一下,对于这些 closing 标点(偏靠式、居中一半式)后面补上的可以挤压的空白,各文种要求如下:

  • 简中,GB/T 15834—2011,偏靠式,行末一律半角,即补充的空白在断行时统一忽略。
  • 日文,JIS X 4051,严格来说,句号和点号后面的空白必须保留,其余补充的空白在断行时可以忽略。宽松来说(DTP),也可以统一忽略补充的空白。
  • 繁中,(官方文件?),居中一半式标点后面的空白统一保留(以求对称好看),偏靠式标点后面的空白在断行时可以忽略。

@RuixiZhang42
Copy link
Member Author

新模型(仍在开发)现在支持「断行时保留标点后方的空白」,可以自定义这种属性的标点列表,以适应不同规范:

日文设置

jkeepblank
句号后方保留空白(可压缩):
jkeepblank2
顿号占据二分空间,间隔号占据 3/4 空间:
jkeepblank3

繁中设置

行末时,统一保留居中标点后方的空白(可压缩):
tckeepblank
偏靠式标点则只占据二分空间:
tckeepblank2

@RuixiZhang42
Copy link
Member Author

支持「真・plain」样式

仅按照「避头尾/禁则」排版,真正地「完全不做调整」,包括「行末不挤压」、「行首不挤压」、「相邻标点不挤压」。

plainstyle

支持「真・开明」样式

句末标点全角、其余一律半角:

kaimingstyle

请注意句号的影响可以跨越别的标点!!

@RuixiZhang42
Copy link
Member Author

最后需要解决的是「标点悬挂」的问题。根据 JIS X 4051,「允许被悬挂的标点」只包括句号类(句号、点号)和逗号类(逗号、顿号)。更进一步,所谓「允许标点悬挂」,是指「优先考虑挤压行中的空白,试图『推进』行末标点」,而「实在推进不了,再考虑悬挂出版心」。

可以悬挂的标点列表,悬挂的算法仅对「偏靠式」标点有效,「居中式」标点一律不可悬挂。

hangable

做出来大概是这种效果:

hanging

@RuixiZhang42
Copy link
Member Author

以下内容摘自《組文社的青葱歲月》(作者:許定銘,《明報月刊》文化附冊《明月》2015年三月號),截图来源:香港文化資料庫。遵守《中华人民共和国著作权法》和《著作權法》合理使用。

繁中印刷品里,标点符号的排版有点迷啊

几乎不考虑「避头尾」

sample1
《阡陌》,1963年。

推测两个原因:

  1. 居中标点体积庞大,不便调整;
  2. 栏宽太窄(行长只有9个字),不便调整。

但其实也有为了「避头尾」调过标点啊

sample2
《蒲公英》,1964年。

标点之间的挤压更迷

sample3
《蒲公英》,1964年。

@RuixiZhang42
Copy link
Member Author

字符类之间需要插入的代码基本稳定下来了,做了一个比较实用的拓展——支持「窄体」(等价地支持「宽体」)。

测试字体是「未来荧黑」,用了GlowSansSC-Wide-Regular.otf(120%宽体)和GlowSansSC-Compressed-Regular.otf(80%窄体)。

condensedCJK

开发仓库:https://github.com/RuixiZhang42/newxeCJK
尚不支持中文字体独立设置,没有代码说明/注释,也没有用户手册,只有一个大概的骨架
目前的「用法」是:

\documentclass{article}
\usepackage{fontspec}
\input{newxeCJK}
\setmainfont{<中文字体家族>}
\begin{document}
<正文>
\end{document}

@stone-zeng stone-zeng pinned this issue Oct 6, 2020
@suiyun0234
Copy link

大神,现在写latex时,要求用microtype包,可是没法用xelatex编译。我看到你空间里给了个兼容的包,然后该怎么办呢?我是小白,不知道怎么重新替换掉已经安装的xelatex了,谢谢大神。

@muzimuzhi
Copy link
Contributor

@suiyun0234 建议单独提问,可以发到 https://github.com/CTeX-org/forum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants