CTeX-org / forum

A temporarily alternate forum of `bbs.ctex.org`
https://t.me/chinesetex
Apache License 2.0
210 stars 16 forks source link

[问题] pdfTeX下标点设置的另一种实现方式 #294

Closed RadioNoiseE closed 11 months ago

RadioNoiseE commented 11 months ago

(WRN:这不是个好方法)

因为该issueCJKpunct宏集的实现导致出现了一些小问题,我就想是否能够有另一种实现能够避免这个问题。
方法如下,暂时只定义和一个(我懒):

\makeatletter
\catcode`。=\active
\catcode`「=\active
\def\kern@punct{period,anglequoteleft}
\def\punct@before{nil}
\def。{%
  \@in\punct@before\kern@punct%
  \ifin@\unskip%
  \else\relax\fi%
  \def\int@r{\hbox to.5em{\string。\hss}}%
  \discretionary{\int@r}{}{%
  \int@r\glue .5em plus.015em minus.015em}%
  \xdef\punct@before{preiod}%
}
\def「{%
  \@in\punct@before\kern@punct%
  \ifin@\unskip%
  \else\relax\fi%
  \def\int@r{\hbox to.5em{\hss\string「}}%
  \discretionary{}{\int@r}{%
  \glue .5em plus.015em minus.015em\int@n}
  \xdef\punct@before{anglequoteleft}%
}
\makeatother

主要思路就是,把中文的全角标点用\hbox to的方式「切」成半角宽度,再视标点种类在前面或后面插入一个半宽的用\glue实现的「铅空」。需断行时则有两种情况:

  1. 铅空加在标点前():即使需断行这段铅空也应保留,故无需处理;
  2. 铅空位于标点后():此时由于铅空属于glue,会被TeX在断行时discard掉,完成「行末半角」。

断行时的行末半角解决后,为完成(多于两个标点相邻时的)标点挤压,使用\in@判断前一个标点类型(使用\xdef定义\punct@before)是否属于需要挤压的标点(预定义的\kern@punct)。此时也有两种情况:

  1. 这一对标点不需要挤压时(如:判断为\else分支,\relax
  2. 这对标点需要挤压时(如):判断为主分支,使用\unskip去掉先前于标点后插入的铅空。

理论上还有一些情况(比如我在写Eva-JFM的时候就遇到了各种奇奇怪怪的情况),但(同样)理论上都能够通过分支判断对应处理(比如\kern@before@punct代表铅空应插在前面的标点集合、以此类推);同时(再次)理论上是能支持简中、繁中、日文字体的(?

问题

这种实现方式有什么问题呢?

  1. 如果有问题,可否解决呢?
  2. 如果没有问题,有什么可以优化的地方吗(如方便设计、提供接口等等)?

暑假还剩几天,我想实现一下(似乎pdfTeX下的CJK支持还挺好玩的)。又麻烦各位了:)

改动(8·2)

  1. 使用\string防止死循环;
  2. 由于断行时铅空可能被留在上面阻碍行末半角,应用\discretionary结点处理。
RuixiZhang42 commented 11 months ago

关于将全角标点切成半宽

以简中句号为例,主要就是两种思路:

  1. \hbox to0.5em{。\hss},印象中babel的韩文支持就是这么做的;
  2. 。\vrule width-0.5em\relax,是xeCJK的做法。

没有「绝对正确」的做法,看你想在「horizontal list」里是加个盒子呢还是留字符本身,只要注意em是否跟汉字大小一致即可。我没试过第1种思路,但知道第2种思路可以结合rule、glue、penalty花式玩法做出「标点悬挂」的效果。

关于加「铅空」

应该用\hskip0.5em minus 0.5em(那个plus ...就匪夷所思了)。

关于用\discretionary

是「完全错误」的思路:在显式的「discretionary node」处断行是要罚\hyphenpenalty的。正确的思路应该是在铅空\hskip处的「glue node」断行,不罚penalty。

关于\xdef\punct@before{...}

两个标点可能都已经隔了十万八千里了,如。字字字字「,还要走一遍\ifin@\unskip,怕是不妥吧?

RadioNoiseE commented 11 months ago

应该用\hskip0.5em minus 0.5em(那个plus...就匪夷所思了)。

呃的确是,大概我写这里的时候没太用脑子( 不过似乎不太该全部挤压、我JFM设的是.25em(的确、.015太小了)

RadioNoiseE commented 11 months ago

是「完全错误」的思路:在显式的「discretionary node」处断行是要罚\hyphenpenalty的。正确的思路应该是在铅空\hskip处的「glue node」断行,不罚penalty。

我再去仔细看看TeXbook(地铁上拍脑袋想的、怕是背单词背傻了

RadioNoiseE commented 11 months ago

两个标点可能都已经隔了十万八千里了,如。字字字字「,还要走一遍\ifin@\unskip,怕是不妥吧?

我也觉得不太好、但当时觉得反正\unskip没有副作用就放这了。我再想想、比如用\aftergroup判断、如果是汉字就把它定义为nil
而且我看实现,这个clist的判断是用宏的机制、不是一个一个捕获判断、也、不是特别慢(?

\def\in@#1#2%
 {%
   \begingroup
     \def\in@@##1#1{}%
     \toks@\expandafter{\in@@#2{}{}#1}%
     \edef\in@@{\the\toks@}%
   \expandafter\endgroup
   \ifx\in@@\@empty
     \in@false
   \else
\in@true \fi
 }
\newif\ifin@

(是我写不出来的

RadioNoiseE commented 11 months ago

(谢谢您!

RuixiZhang42 commented 11 months ago

不过似乎不太该全部挤压、我JFM设的是.25em(的确、.015太小了)

可能是对minus 0.5em有误解吧?并不是非得全部挤压,而是「如果此行太长、需要挤压时,最多能把加上的二分铅空全部挤掉」。在实际文档中,需要挤掉二分铅空的0.25em或以上的场合太罕见了(注意,是同一行的每个标点的铅空都要被耗掉50%或以上的压缩量)。简中排版最常见的「此行过长」基本上就是「多了一个可以行末半宽的避头标点」,总挤压量也就0.5em,要分散到行內数个标点的铅空是绰绰有余的(如果行內还有西文,那西文空格也可以吸收一些总挤压量)。总之,耗掉收缩量0.5em的50%以下是常见情况,TeX评判该行的badness也就介于0到12,属于「decent」,算不上「tight」。

minus 0.25em无非大大减少了可压缩的总量,当每个铅空要被耗掉0.125em或以上时,TeX就会把该行归类为「tight」,最后效果是TeX更倾向于「推出」(即拉大此行字距,把过长的内容往下一行放)而不是「挤进」。

RadioNoiseE commented 11 months ago

嗯,我之前设成.25em是因为实在不喜欢被挤压成只有只有一个标点宽度的情况。 不过似乎(又)是我TeXbook看的不够仔细了,这么看来还是.5em看上去合理很多(

RadioNoiseE commented 11 months ago

另外,我昨天晚上去看了看\discretionary的描述。发现了在此段行惩罚值是可以通过\hyphenpenalty以及\exhyphenpenalty「改变」的。我在想是不是可以在标点定义包一个分组(如\[begin/end]group)、然后局部改变这两个值使其分别为0、也就不会影响西文的设置。
至于为什么对这个discretionary node这么执着,因为我觉得用它可以控制TeX在断行时的行为。如繁体中文句号后接一个号,句号在行末就不该做行末半角而应该保持全宽,如此就需要断行时在其后插入一个.25em宽的铅空;同时如果不在此处断行后接的应是一个.75em的铅空。如果不用discretionary node就非常难实现(TeX总是会在第一个glue处断行、因为其后的glue是discardable的?)。同时也能很简单地实现标点悬挂等等。
(大概也能避免某些奇怪的东西跑到下一行去)[迫真]

RadioNoiseE commented 11 months ago

至于「即使标点相隔汉字也需要走一遍\unskip」的问题,我还没什么想法(
我去今晚去看看aro-bend,看看能不能找到什么灵感(哪里有什么灵感可言)。

RuixiZhang42 commented 11 months ago

另外,我昨天晚上去看了看\discretionary的描述。发现了在此段行惩罚值是可以通过\hyphenpenalty以及\exhyphenpenalty「改变」的。我在想是不是可以在标点定义包一个分组(如\[begin/end]group)、然后局部改变这两个值使其分别为0、也就不会影响西文的设置。

不可行。TeX是读取整个段落之后再分行的,所以在分组里赋的零值是无效的。段落末尾(\par之前)的值才是用于整段分行的值。

至于为什么对这个discretionary node这么执着,因为我觉得用它可以控制TeX在断行时的行为。如繁体中文句号后接一个号,句号在行末就不该做行末半角而应该保持全宽,如此就需要断行时在其后插入一个.25em宽的铅空;同时如果不在此处断行后接的应是一个.75em的铅空。如果不用discretionary node就非常难实现(TeX总是会在第一个glue处断行、因为其后的glue是discardable的?)。同时也能很简单地实现标点悬挂等等。

是的,繁中句号在行末不应该半宽(准确地说是不应该丢掉后面的四分空)。但你去看港台好多Adobe InDesign排的书,那叫惨不忍睹啊。想留很简单,要善用penalty,以字。「字为例:

字\nobreak \hskip0.25em minus 0.25em \vrule width-0.25em\relax
。\vrule width-0.25em\relax \nobreak \hskip0.25em minus 0.25em \allowbreak
\vrule width-0.5em\relax 「\nobreak 字
RadioNoiseE commented 11 months ago

不可行。TeX是读取整个段落之后再分行的,所以在分组里赋的零值是无效的。段落末尾(\par之前)的值才是用于整段分行的值。

啊、你说的对(
我得再想想(麻烦了

RadioNoiseE commented 11 months ago

字\nobreak \hskip0.25em minus 0.25em \vrule width-0.25em\relax

。\vrule width-0.25em\relax \nobreak \hskip0.25em minus 0.25em \allowbreak

\vrule width-0.5em\relax 「\nobreak 字

我来按我的方法(切的)改一改!
然后想想那个判断的问题怎么解决

RuixiZhang42 commented 11 months ago

啊,我想起来我为啥弃用\hbox to...的方式切全角了,是因为会改变标点在自然状态(不伸展不压缩)下所占据的宽度。

假设1em等于1003sp,以这个不实际的值为例方便说明情况。用\hbox to0.5em{。\hss}切出来的盒子,宽501sp,后面再补\hskip0.5em minus 0.5em自然宽度同样是501sp,加在一起相当于句号少了1sp宽。繁中式的句号盒子宽501sp,前后补的四分空各宽250sp,加在一起相当于句号少了2sp宽。此分析适应于所有除4余3的整数sp汉字尺寸。

相反,用\vrule去配合\hskip,能够保证正负刚好抵消,不增添不减少1sp的宽度。

RadioNoiseE commented 11 months ago

啊,我想起来我为啥弃用\hbox to...的方式切全角了,是因为会改变标点在自然状态(不伸展不压缩)下所占据的宽度。

假设1em等于1003sp,以这个不实际的值为例方便说明情况。用\hbox to0.5em{。\hss}切出来的盒子,宽501sp,后面再补\hskip0.5em minus 0.5em自然宽度同样是501sp,加在一起相当于句号少了1sp宽。繁中式的句号盒子宽501sp,前后补的四分空各宽250sp,加在一起相当于句号少了2sp宽。此分析适应于所有除4余3的整数sp汉字尺寸。

相反,用\vrule去配合\hskip,能够保证正负刚好抵消,不增添不减少1sp的宽度。

(原来这里还有个坑) 话说1sp应该肉眼不可见吧(表达不太严谨),但毕竟有更好的方法。 所以说这个思路想来好像是不太好的,而且我也没想好比较好的方法来判断标点是否紧挨。 还是kern加rule的方法好(