13

The Bionic Reading tries to find every word, and make the first part bold.

I wish there is an environment to imitate that, which types

\bionic{Confucius said: Madam, I'm Adam.}

and it is interpreted the same as the code

{\bf Conf}ucius {\bf sa}id: {\bf Ma}dam, I'm {\bf Ad}am.

and to be shown in the compiled PDF file like

Confucius said: Madam, I'm Adam.

Or there is an optional parameter of a number between 0 and 1, to show how much part of a word should be bold.

For example, the code

\bionic{internationalization}
\bionic[0.5]{internationalization}
\bionic[0.25]{internationalization}
\bionic[0.8]{internationalization}

will be compiled respectively into

internationalization

internationalization

internationalization

internationalization


Edit in 5.24: How to adapt the bionic environment to math environment and SI units? for example: I wish the following codes work, where the codes of formula and SI units codes are executed, instead of listed as it is, and not bold.

\bionic{
    Substances A and B are both volatile liquids with
    $p_A^* = \SI{300}{Torr}$, 
    $p_B^* = \SI{250}{Torr}$, and 
    $K_B   = \SI{200}{Torr}$ 
    (concentration expressed in mole fraction). 
}

Substances A and B are both volatile liquids with pA* = 300 Torr, pB* = 250 Torr and KB = 200 Torr (concentration expressed in mole fraction).

  • Welcome! (Fun idea, probably doable with expl3). Hmm... how does the algorithm knows that it should not apply the bold trick to "I'm"? – Rmano May 20 '22 at 08:46

4 Answers4

11

This is a solution using expl3 (LaTeX3, if you like ) --- I'm a novice with it, so probably it can be made better. It uses percentages instead of fractions so that I can use integer arithmetic only.

\documentclass{article}
\usepackage[T1]{fontenc}
\ExplSyntaxOn
\seq_new:N \l_br_main_seq
\str_new:N \l_br_tmp_str
\int_new:N \l_br_len_int
\int_new:N \l_br_mid_int
% main command --- conditional argument with default 50.
\NewDocumentCommand\bionic{ O{50} m}{
    \br_process_all:nn{#2}{#1}
}
% split the string at spaces.
\cs_new_protected:Npn \br_process_all:nn #1 #2 {
    \seq_set_split:Nnn \l_br_main_seq {~} {#1}
    \seq_map_inline:Nn \l_br_main_seq {\br_process_word:nn {##1} {#2}}
    % thanks to @Skillmon suggestion in comments! --- unskip last space
    \unskip
}
% split a word at the #2 percentage, set the first part in bold,
% emit the second part, emit a space
\cs_new_protected:Npn \br_process_word:nn #1 #2 {
    \str_set:Nn \l_br_tmp_str {#1}
    % find length of the string and (#2) percentage position
    \int_set:Nn \l_br_len_int {\str_count:N \l_br_tmp_str}
    \int_set:Nn \l_br_mid_int {\int_eval:n {\l_br_len_int *  #2 / 100}}
    % split the string: typeset the first part in bold, the second one plain
    \textbf{\str_range:Nnn \l_br_tmp_str {1}{\l_br_mid_int}}
    \str_range:Nnn \l_br_tmp_str {\l_br_mid_int+1}{\l_br_len_int}
    % a space at the end of the word. Will also be emitted for the last word...
    \space
}
\ExplSyntaxOff
\begin{document}

\bionic{Confucius said: Madam, I'm Adam.}

\bionic[30]{Confucius said: Madam, I'm Adam.}

\bionic[30]{Confucius said: {Madam, I'm} Adam.}

\bionic{internationalization}

\bionic[50]{internationalization}

\bionic[25]{internationalization}

\bionic[80]{internationalization}

\end{document}

example output

Rmano
  • 40,848
  • 3
  • 64
  • 125
  • 2
    Nitpick: \l_br_len and \l_br_mid should be \l_br_len_int and \l_br_mid_int – Phelype Oleinik May 20 '22 at 13:32
  • @PhelypeOleinik you're right! Fixed. – Rmano May 20 '22 at 14:16
  • 1
    > There is always a spurious space after the expansion; If getting rid of leading/trailing space if present is an option: With your code \seq_map_inline:Nn delivers to the input-stream calls to \br_process_word:nn where \br_process_word:nn with each element of the sequence appends \space. Instead: Don't have \br_process_word:nn append \space. Have \seq_map_inline:Nn accumulate calls to \br_process_word:nn within a scratch-sequence. Then use \seq_use:Nn for placing the contents of the scratch-sequence into the input-stream with ~/ as separator between items. – Ulrich Diez May 20 '22 at 19:50
  • 1
    Alternative to Ulrich Diez's \seq_use:Nn solution use one \unskip after the last word (so append the \unskip to \br_process_all:nn's definition; not an expl3 solution, though). – Skillmon May 24 '22 at 11:38
  • @Skillmon ah, smart! Applied! – Rmano May 24 '22 at 12:04
8

My solution uses only TeX primitives and common macro \newcount. We need nothing more.

\newcount\tmpnum
\def\bionic{\futurelet\next\bionicA}
\def\bionicA{\ifx\next[\afterfi\bionicB \else \afterfi{\bionicB[50]}\fi}
\def\bionicB[#1]#2{{\def\wratio{#1}\bionicC#2 {} }}
\def\bionicC #1 {\ifx^#1^\else
    \spacebetweenwords
    \tmpnum=0 \lcount #1{}% saves the number of letters to \tmpnum
    \multiply\tmpnum by\wratio \divide\tmpnum by100
    \testIm #1''\end % says \tmpnum=0 if #1 includes ' inside the word
    \bgroup \bf \wordpart #1%
    \expandafter\bionicC \fi
}
\def\spacebetweenwords{\def\spacebetweenwords{ }}
\def\lcount #1{\ifx^#1^\else \advance\tmpnum by1 \expandafter\lcount\fi}
\def\wordpart #1{%
   \ifnum\tmpnum=0 \egroup#1% 
   \else #1\advance\tmpnum by-1 \expandafter \wordpart \fi}
\def\testIm #1'#2'#3\end{\ifx^#2^\else \tmpnum=0 \fi}
\def\afterfi#1#2\fi{\fi#1}

%tests: \bionic{Confucius said: Madam, I'm Adam.}

\bionic[50]{internationalization} \bionic[25]{internationalization} \bionic[80]{internationalization}

If OpTeX is used then the code can be more compact and the optional parameter can be decimal digit as given in the OP.

\optdef\bionic[.5]#1{{\bionicC #1 {} }}
\def\bionicC #1 {\ifx^#1^\else
    \spacebetweenwords
    \tmpnum=0 \foreach #1\do{\incr\tmpnum}%
    \def\_decdigits{0}\tmpnum=\expr{\the\opt*(\the\tmpnum-1)}%
    \testIm #1''\end % says \tmpnum=0 if #1 includes ' inside the word
    \bgroup \bf \wordpart #1%
    \ea\bionicC \fi
}
\def\spacebetweenwords{\def\spacebetweenwords{ }}
\def\wordpart #1{\ifnum\tmpnum=0 \egroup#1\else #1\decr\tmpnum \ea\wordpart\fi}
\def\testIm #1'#2'#3\end{\ifx^#2^\else \tmpnum=0 \fi}

%tests: \bionic{Confucius said: Madam, I'm Adam.}

\bionic[.5]{internationalization} \bionic[.25]{internationalization} \bionic[.8]{internationalization}

\bye

wipet
  • 74,238
  • Hi, is there a way to automatically enclose every paragraph in the tex file within \bionic{}, so that we can apply this to older documents, like this – noir1993 Jun 08 '22 at 09:12
4

Approach with the xstring package for string manipulation and xfp for calculating the substring length:

\documentclass{article}
\usepackage{xstring}
\usepackage{xfp}

\newcommand{\bionic}[2][0.5]{% % split on first space \StrCut{#2}{ }{\nextword}{\otherwords}% % count length of first word \exploregroups% \StrLen{\nextword}[\currlen]% % calculate nr. of characters for left part \edef\halflen{\fpeval{ceil(\currlen*#1)}}% % print left part in bold \bfseries\StrLeft{\nextword}{\halflen}% % print right part and add space back \normalfont\StrGobbleLeft{\nextword}{\halflen}\space% \noexploregroups% % call function recursively if there are other words \IfStrEq{\otherwords}{}{}{% \bionic[#1]{\otherwords}% }}

\begin{document} \bionic{Confucius said: Madam, I'm Adam.}

\bionic[0.3]{Confucius said: Madam, I'm Adam.}

\bionic[0.3]{Confucius said: {Madam, I'm} Adam.}

\bionic{internationalization}

\bionic[0.5]{internationalization}

\bionic[0.25]{internationalization}

\bionic[0.8]{internationalization} \end{document}

The output is the same as in Rmano's answer. Note that the \exploregroups commands are needed for the third example with {Madam, I'm}, if there are no groups in the string then the code can be simplified further by removing these commands.

Marijn
  • 37,699
4

As the question was so interesting I tried it (in LaTeX2e) even though It was already answered. I'm not an expert and I think it will fail if you have accentuation marks or some other weird stuff.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}

\makeatletter % This command counts the number of letters in a word \newcounter{ACtotal} \def\AC@count#1#2\AC@delim{% \ifx\AC@undef#2\AC@undef% \ifcat.#1\else\stepcounter{ACtotal}\fi% \else% \ifcat.#1\else\stepcounter{ACtotal}\fi\AC@count#2\AC@delim% \fi% } \newcommand{\LtCount}[1]{% \AC@count#1\AC@delim% }

% This command formats the bold in the word \newcounter{ACcount} \def\AC@format#1#2#3\AC@delim{% \ifx\AC@undef#3\AC@undef% \stepcounter{ACcount}% \ifnum\value{ACcount}<#1% \textbf{#2}% \else% #2% \fi% \else% \stepcounter{ACcount}% \ifnum\value{ACcount}<#1% \textbf{#2}% \else% #2% \fi% \AC@format{#1}#3\AC@delim% \fi% } \newcommand{\LtForm}[2][4]{% \AC@format{#1}#2\AC@delim% \setcounter{ACcount}{0}% \setcounter{ACtotal}{0}% }

% This command sepparates a text by the spaces and applies the count of characters to each word and the formatting \long\def\AC@sep#1#2 #3\AC@delim{% \LtCount{#2} \multiply\value{ACtotal} by #1 \divide\value{ACtotal} by 100 \advance\value{ACtotal} by 1 \ifx\AC@undef#3\AC@undef% \LtForm[\value{ACtotal}]{#2} \else% \LtForm[\value{ACtotal}]{#2} \AC@sep{#1}#3\AC@delim% \fi% } \newcommand{\bionic}[2][50]{\AC@sep{#1}#2 \AC@delim} \makeatother

\begin{document}

\bionic{Confucius said: Madam, I'm Adam.}

\bionic[30]{Confucius said: Madam, I'm Adam.}

\bionic[30]{Confucius said: {Madam,\ I'm} Adam.}

\bionic{internationalization}

\bionic[50]{internationalization}

\bionic[25]{internationalization}

\bionic[80]{internationalization}

\end{document}

here the result result

AmadoC
  • 435