Макрос для средней ширины символа

Макрос для средней ширины символа

Как я могу создать макрос, аналогичный \averagecharwidthConTeX в LaTeX, который вычисляет среднюю ширину символа на основе частоты его появления в моем документе? Этот макрос показан вэтотпочта.

введите описание изображения здесь

решение1

Вот наивный подход.

  1. Сохраните весь документ в списке токенов.
  2. Подсчитайте количество появлений каждого алфавитного символа (в основном)
  3. Разделите количество каждого символа на общее количество буквенных символов, чтобы получить относительную частоту этого символа.
  4. Умножьте это отношение на ширину символа и сложите, чтобы получить среднюю ширину символа.

Некоторые примечания:

  • Группы фигурных скобок считаются как один токен, поэтому такие символы, как \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 символам на строку, нам нужно знать относительную частоту символов, используемых в документе. Если большинство символов широкие, то нам нужны более широкие строки. Если большинство символов узкие, то нам нужны соответственно более узкие строки.
  • Поэтому мы вычисляем среднюю ширину используемых символов и используем ее для определения ширины строки. Например, если наш документ состоит из m' и i' в равном соотношении (50/50), то "средний символ" имеет ширину где-то между шириной mи шириной i. В частности, средний символ имеет ширину x=(wd(m)+wd(i))/2, и мы должны установить наше \textwidthзначение равным 66*x. Экстраполируя на произвольный документ, мы вычисляем средневзвешенное значение ширины используемых символов в соответствии с их относительной частотой в документе и умножаем его на 66 (или используем его любым другим способом), чтобы получить , которое \textwidthнаилучшим образом соответствует критерию 66 символов на строку.

решение2

Макросы довольно низкого уровня TeX, поэтому их легко использовать в LaTeX, добавив несколько недостающих определений. С этими определениями вы можете просто импортироватьlang-frq.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).

Связанный контент