我如何\averagecharwidth
在 LaTeX 中製作一個類似於 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 個字元可以提高文字的可讀性。
- 由於行寬是固定的,每行的實際字元數取決於鍵入的字符,例如,一行 all 的字元
m
將比一行 alli
的字元少,因為 anm
比 an 寬i
。 - 因此,為了將合理的行寬設定為每行大約 66 個字符,我們需要知道文件中使用的字符的相對頻率。如果大多數字元都很寬,那麼我們就需要更寬的線條。如果大多數字元都很窄,那麼我們需要相應更窄的線條。
- 因此,我們計算所使用的字元的平均寬度,並用它來確定線寬。例如,如果我們的文件由
m
's 和i
's 以相等的比例 (50/50) 組成,則「平均字元」的寬度介於 anm
和 的寬度之間i
。具體來說,平均字元有寬度x=(wd(m)+wd(i))/2
,我們應該將其\textwidth
設為66*x
。外推到任意文檔,我們根據文檔中的相對頻率計算所用字元寬度的加權平均值,並將其乘以 66(或以任何方式使用)以獲得\textwidth
最適合每行 66 個字元的寬度標準。
答案2
這些巨集是相當低階的 TeX,因此透過添加一些缺少的定義很容易在 LaTeX 中使用它們。有了這些定義,您可以簡單地匯入lang-frq.mkii,
lang-frd.mkii,以及幫助文件補充錯誤.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}
筆記:註解來自lang-frd.mkii
(讀取 的行\startcharactertable[en] 100 x \stopcharactertable % kind of default
)的第 116 行。