# How can LaTeX form an environment of Bionic-Reading style?

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

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"? May 20 at 8:46

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\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[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[.5]{internationalization}
\bionic[.25]{internationalization}
\bionic[.8]{internationalization}

\bye

• 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 Jun 8 at 9:12

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{internationalization}

\bionic[50]{internationalization}

\bionic[25]{internationalization}

\bionic[80]{internationalization}

\end{document}


• Nitpick: \l_br_len and \l_br_mid should be \l_br_len_int and \l_br_mid_int May 20 at 13:32
• @PhelypeOleinik you're right! Fixed. May 20 at 14:16
• > 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 ~/<space-token> as separator between items. May 20 at 19:50
• 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). May 24 at 11:38
• @Skillmon ah, smart! Applied! May 24 at 12:04

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{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.

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
\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}