我怎样才能在 LaTeX 中制作一个类似于\averagecharwidth
ConTeX 的宏,根据该字符在我的文档中出现的频率计算该字符的平均宽度?该宏显示在这邮政。
答案1
这是一种幼稚的方法。
- 将整个文档存储在标记列表中
- 计算每个字母出现的次数(大多数情况下)
- 将每个字符的数量除以字母字符的总数,即可得到该字符的相对频率。
- 将该比率乘以字符的宽度并相加即可得到平均字符宽度。
一些说明:
- 大括号组被视为单个标记,因此像
\begin{environment}
和这样\par
的字符不会匹配任何字母字符,这是一个优点。 - 同时,里面的字
\text{some text}
也不会被计算,这是一个缺点。 - 可以考虑大写字母,但速度较慢。
- 我认为我没有错过任何重要的事情,但谁也不知道。
- 编辑:现在空格已包含在计算中,宏的效果是累积的。在处理空格时,我假设从长远来看拉伸和收缩会相互抵消,并且空格的平均宽度就是空格的正常宽度。如果有更好的方法来处理这个问题,请告诉我。
- 编辑:编译两次即可自动调整文本宽度为所需值。
无论如何,对于直文本,这给出了一个精确的平均字符宽度。如果更多打印文本隐藏在括号组中,结果会变得不那么准确。
\documentclass{article}
\usepackage{xparse}
\usepackage{siunitx}
\usepackage{booktabs}
\usepackage{environ}
\ExplSyntaxOn
\bool_new:N \g_has_run_bool
\tl_new:N \l_aw_text_tl
\int_new:N \l_aw_tot_int
\int_new:N \g_aw_tot_alph_int
\int_new:N \g_wid_space_int
\int_new:N \g_space_int
\fp_new:N \g_rat_space_int
\fp_new:N \g_aw_avg_width_fp
\dim_new:N \myalphabetwidth
\dim_new:N \mytextwidth
\input{testing.aux}
\tl_const:Nx \c_aw_the_alphabet_tl {abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.;?()!' \token_to_str:N :}
% this can be changed to an evironment or renamed or whatever
\NewDocumentCommand {\avgwidthstart} {}
{
\aw_avg_width:w
}
\NewDocumentCommand {\avgwidthend}{}{}
% Here is the environment version, using just "text" as a name is probably a bad idea.
\NewEnviron{awtext}
{
\expandafter\avgwidthstart\BODY\avgwidthend
}
\makeatletter
\cs_new:Npn \aw_avg_width:w #1 \avgwidthend
{
% if first run, then generate variables to be used
\bool_if:NF \g_has_run_bool
{
\tl_map_inline:Nn \c_aw_the_alphabet_tl
{
\int_new:c {g_##1_int}
\fp_new:c {g_rat_##1_fp}
\fp_new:c {g_wid_##1_fp}
}
}
\tl_set:Nn \l_aw_text_tl {#1}
% this can be used rather than the preceding line to take capital
% letters into account, but is Slooooooow
%\tl_set:Nx \l_aw_text_tl {\tl_expandable_lowercase:n {#1}}
\int_set:Nn \l_aw_tot_int {\tl_count:N \l_aw_text_tl}
\tl_map_function:NN \c_aw_the_alphabet_tl \aw_get_counts:n
\deal_with_spaces:n {#1}
\tl_map_function:NN \c_aw_the_alphabet_tl \aw_calc_ratios:n
\tl_map_function:NN \c_aw_the_alphabet_tl \aw_calc_avg_width:n
\fp_gset_eq:NN \g_aw_avg_width_fp \l_tmpa_fp
\fp_zero:N \l_tmpa_fp
% the dimension \myalphabetwidth gives the width of the alphabet based on your character freq,
% can be accessed by \the\myalphabetwidth
\dim_gset:Nn \myalphabetwidth {\fp_to_dim:n {\fp_eval:n {61*\g_aw_avg_width_fp}}}
% the dimension \mytextwidth gives the recommended \textwidth based on 66 chars per line.
% can be accessed by \the\mytextwidth
\dim_gset:Nn \mytextwidth {\fp_to_dim:n {\fp_eval:n {66*\g_aw_avg_width_fp}}}
\protected@write\@mainaux{}{\mytextwidth=\the\mytextwidth}
\bool_gset_true:N \g_has_run_bool
% and lastly print the content
#1
}
\makeatother
\cs_new:Npn \aw_get_counts:n #1
{
% make a temporary token list from the document body
\tl_set_eq:NN \l_tmpb_tl \l_aw_text_tl
% remove all occurrences of the character
\tl_remove_all:Nn \l_tmpb_tl {#1}
% add to appropriate int the number of occurrences of that character in current block
\int_set:Nn \l_tmpa_int {\int_eval:n{\l_aw_tot_int -\tl_count:N \l_tmpb_tl}}
% add to appropriate int the number of occurrences of that character in current block
\int_gadd:cn {g_#1_int} {\l_tmpa_int}
% add this to the total
\int_gadd:Nn \g_aw_tot_alph_int {\l_tmpa_int}
}
\cs_new:Npn \deal_with_spaces:n #1
{
\tl_set:Nn \l_tmpa_tl {#1}
% rescan body with spaces as characters
\tl_set_rescan:Nnn \l_tmpb_tl {\char_set_catcode_letter:N \ }{#1}
% find number of new characters introduced. add to number of spaces and alph chars
\int_set:Nn \l_tmpa_int {\tl_count:N \l_tmpb_tl -\tl_count:N \l_tmpa_tl}
\int_gadd:Nn \g_space_int {\l_tmpa_int}
\int_gadd:Nn \g_aw_tot_alph_int {\l_tmpa_int}
% since this comes after the rest of chars are dealt with, tot_alph is final total
\fp_set:Nn \g_rat_space_fp {\g_space_int/\g_aw_tot_alph_int}
% get width of space and use it. obviously space is stretchable, so i'll assume
% that the expansions and contractions cancel one another over large text. is this
% a terrible assumption???
\hbox_set:Nn \l_tmpa_box {\ }
\fp_gset:Nn \g_wid_space_fp {\dim_to_fp:n {\box_wd:N \l_tmpa_box}}
\fp_add:Nn \l_tmpa_fp {\g_wid_space_fp*\g_rat_space_fp}
}
\cs_new:Npn \aw_calc_ratios:n #1
{
% divide number of occurrences of char by total alphabetic chars
\fp_gset:cn {g_rat_#1_fp}{{\int_use:c {g_#1_int}}/\g_aw_tot_alph_int}
}
\cs_new:Npn \aw_calc_avg_width:n #1
{
% only need to find char widths once
\bool_if:NF \g_has_run_bool
{
% find width of char box
\hbox_set:Nn \l_tmpa_box {#1}
\fp_gset:cn {g_wid_#1_fp}{\dim_to_fp:n {\box_wd:N \l_tmpa_box}}
}
% multiply it by char frequency and add to avg width
\fp_add:Nn \l_tmpa_fp {{\fp_use:c {g_wid_#1_fp}}*{\fp_use:c {g_rat_#1_fp}}}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This part is just for fun. Delete it and the showtable command from the document if
% it isn't wanted
\tl_new:N \l_aw_tab_rows_tl
\seq_new:N \g_aw_the_alphabet_seq
\NewDocumentCommand {\showtable}{}
{
\clearpage
\aw_make_table:
}
\cs_generate_variant:Nn \seq_set_split:Nnn {NnV}
\cs_new:Npn \aw_make_table:
{
\thispagestyle{empty}
\seq_set_split:NnV \g_aw_the_alphabet_seq {} \c_aw_the_alphabet_tl
\seq_map_function:NN \g_aw_the_alphabet_seq \aw_generate_row:n
\begin{table}
\centering
\sisetup{round-mode = places,round-precision = 5,output-decimal-marker={,},table-format = 3.5}
\begin{tabular}{lll}
\toprule
{Average\,text\,width}&{Average\,character\,width}&{Average\,alphabet\,width}\\
\midrule
\the\mytextwidth&\fp_eval:n {round(\g_aw_avg_width_fp,5)}pt&\the\myalphabetwidth\\
\bottomrule
\end{tabular}\par
\end{table}
\vfil
\centering
\sisetup{round-mode = places,round-precision = 5,output-decimal-marker={,},table-format = 3.5}
\begin{longtable}{cS}
\toprule
{Letter}&{Actual}\\
\midrule
spaces&\fp_eval:n {\g_rat_space_fp*100}\%\\
\tl_use:N \l_aw_tab_rows_tl
\bottomrule
\end{longtable}\par
}
\cs_new:Npn \aw_generate_row:n #1
{
\tl_put_right:Nn \l_aw_tab_rows_tl {#1&}
\tl_put_right:Nx \l_aw_tab_rows_tl {\fp_eval:n {100*{\fp_use:c {g_rat_#1_fp}}}\%}
\tl_put_right:Nn \l_aw_tab_rows_tl {\\}
}
\ExplSyntaxOff
\begin{document}
\avgwidthstart
My audit group's Group Manager and his wife have an infant I can describe only as fierce.
Its expression is fierce; its demeanor is fierce; its gaze over bottle or pacifier or finger-fierce,
intimidating, aggressive. I have never heard it cry. When it feeds or sleeps, its pale face reddens,
which makes it look all the fiercer.
\avgwidthend
\avgwidthstart
On those workdays when our Group Manager, Mr. Yeagle, brought it in to the District office, hanging papoose-style in a nylon device on his back, the infant appeared to
be riding him as a mahout does an elephant. It hung there, radiating authority. Its back lay directly
against Mr. Yeagle's, its large head resting in the hollow of its father's neck and forcing our Group
Manager's head out and down into a posture of classic oppression. They made a creature with two faces,
one of which was calm and blandly adult and the other unformed and yet emphatically fierce. The infant
never wiggled or fussed in the device. Its gaze around the corridor at the rest of us gathered waiting
for the morning elevator was level and unblinking and (it seemed) almost accusing. The infant's face, as
I experienced it, was mostly eyes and lower lip, its nose a mere pinch, its forehead milky and domed,
its pale red hair wispy, no eyebrows or lashes or even eyelids I could see. I never saw it blink. Its
features seemed suggestions only. It had roughly as much face as a whale does. I did not like it at all.\par\noindent
http://harpers.org/media/pdf/dfw/HarpersMagazine-2008-02-0081893.pdf
\avgwidthend
\begin{awtext}
Here is some more text in an environment this time. This text is included in the calculation of the average width.
\end{awtext}
\showtable{}
\end{document}
解释 我从这个“字符的平均宽度”中得到的要点如下。
- 人们认为每行约 66 个字符可以提高文本的可读性。
- 由于行宽是固定的,每行的实际字符数取决于输入的字符。例如,全是 的行包含的字符
m
比全是 的行包含的字符少,i
因为m
比 宽i
。 - 因此,要将合理的行宽设置为每行大约 66 个字符,我们需要了解文档中使用的字符的相对频率。如果大多数字符较宽,则我们需要较宽的行。如果大多数字符较窄,则我们需要相应较窄的行。
- 因此,我们计算所用字符的平均宽度,并以此确定行宽。例如,如果我们的文档由相等比例(50/50)的 和
m
组成,那么“平均字符”的宽度介于和 的宽度之间。具体来说,平均字符的宽度为 ,我们应将 设置为。推断到任意文档,我们根据文档中字符的相对频率计算所用字符宽度的加权平均值,并将其乘以 66(或以任何方式使用它)以获得最符合每行 66 个字符标准的 。i
m
i
x=(wd(m)+wd(i))/2
\textwidth
66*x
\textwidth
答案2
这些宏是相当低级的 TeX,因此只需添加一些缺失的定义即可在 LaTeX 中轻松使用它们。有了这些定义,您只需导入语言频率.mkii,
lang-frd.mkii以及帮助文件supp-mis.mkii(在目标页面上,单击raw
下载)并直接使用 ConTeXt \averagecharwidth
。
% Copy definition of \emptybox from supp-box.mkii
\ifx\voidbox\undefined \newbox\voidbox \fi
\def\emptybox{\box\voidbox}
% Copy definition of \startnointerference from syst-new.mkii
\newbox\nointerferencebox
\def\startnointerference
{\setbox\nointerferencebox\vbox
\bgroup}
\def\stopnointerference
{\egroup
\setbox\nointerferencebox\emptybox}
% Load a trimmed down version of ConTeXt macros
\input supp-mis.mkii
\input lang-frq.mkii
\input lang-frd.mkii
% Set the main language. (I don't know what the LateX equivalent of
% \currentmainlanguage)
\def\currentmainlanguage{en}
\documentclass{article}
\begin{document}
The average character width is \the\averagecharwidth
\end{document}
笔记:注释第 116 行lang-frd.mkii
(内容为\startcharactertable[en] 100 x \stopcharactertable % kind of default
)。