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

1
  • 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 at 8:46

4 Answers 4

7

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

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

5
  • 2
    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.
    – Rmano
    May 20 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 ~/<space-token> as separator between items. May 20 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 at 11:38
  • @Skillmon ah, smart! Applied!
    – Rmano
    May 24 at 12:04
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.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.