1

Recently I happened to process ~4500 paragraphes in my 6-volum book set, ~1500 A4 pages, and I had many operations for page-proofing (widow and orphan control, amount of lines of last paragraph of the chapter, shape of last paragraph on page, etc).
Most things LaTeX doing automatically, but not all and not always.

I wrote some ugly paragraph tuning macro for makeup process.

Main idea - unite into one macro this components:

  • \parfillskip
  • \looseness=+-1
  • microtype:
    • tracking (\textls)
    • font-expansion
  • interword spacing (\spacekip)

and quickly combine them.

Each paragraph is placed into such construction (via perl script):

\parx[][][]{}{}{}{}{}{%  
TEXT   
OF   
PARAGRAPH  
} 

Now we can set values [N or none] and flags {1 or none} like:

\parx[2][1.15][]{1}{}{}{}{1}{%

(we can reset all this values via script (for all files in project)

It possible change this parametres via GUI elements from TEX editors also (f.e. with TexStudio-preview feature - with feedback almost on-the-fly).

Examples: (we select paragraph, and preview it)

step 1

pic 1: Original position. \parx without options

step 2

pic 2: Run TexStudio GUI dialog

step 3

pic 3: Decrease last line

step 4

pic 4: Trying add line. (We get short last line)

step 5

pic 5: Try increase last line. (Not enough)

step 6

pic 6: Get it.

st 7

pic 7: Fill last line.

After closing this GUI dialog we can return to whole document and compile it wit new settings for our paragraph.


Can this macro be optimized and improved (or simplified)?

Now it bulky - I wrote this macro for russian text with some greek words (via \begingreek) and tried to save protrusion, spaceskip, for italic and for greek piece of text. First version was extremely short, but UNBEARABLE SLOW after ~300 pages.


Here MWE:

\documentclass[10pt,a5paper, notitlepage,final]{book}

\usepackage[OT1,T1,T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polutonikogreek,russian,english]{babel}

\usepackage[text={3in,5in},centering]{geometry}

\usepackage{xifthen}
\usepackage{ltxcmds}
\usepackage{letltxmacro}
\usepackage{lipsum}
\usepackage{blindtext}
\newcommand{\ffamily}{cmr}% default typeface
\newcommand{\ParaInwAm}{1}% default inter-word space coeff. в \parx - 
\newcommand{\ParaInwAmByTypeface}{1}%  inter-word space coeff. 
% (you can change it when select typeface)

% set aliaces for font-expansion contexts (defined below)
\usepackage{expl3}
\ExplSyntaxOn
\newcommand{\setFontExpansion}[1]{%
    \str_case_x:nn {#1} {%
    {1}{\microtypecontext{expansion=small}}
    {2}{\microtypecontext{expansion=soft}}
    {3}{\microtypecontext{expansion=mid}}
    {4}{\microtypecontext{expansion=hard}}
    {5}{\microtypecontext{expansion=half}}
    }
}
\ExplSyntaxOff

\usepackage{xargs}
\usepackage{xparse}

%\lefthyphenmin=2 % 
%\righthyphenmin=2 %
%\finalhyphendemerits=50000%def=5000
%\doublehyphendemerits=12000%def=10000

\usepackage[babel=true,
protrusion=true, 
kerning=true,
expansion=alltext,
tracking=smallcaps
%spacing=true
]{microtype}

% settings FOR EXAMPLE 

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/{n}/*}% NORMAL
}{
    « = {300,     },
    » = {    , 300}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 120},
    ! = {    , 120}, %120
    ? = {    , 0}, %0
    : = {    , 1000},
    ; = {    , 1000},
    . = {    , 1000},
    - = {    ,  1000},
    {,} = {    , 1000}
}

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/it/*}% NORMAL
}{
    « = {300,     },
    » = {    , 200}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 70},
    ! = {    , 0}, %120
    ? = {    , 0}, %0
    : = {    , 100},
    ; = {    , 100},
    . = {    , 1000},
    - = {    ,  950},
    {,} = {    , 1000}
}

% settings FOR EXAMPLE
\SetExpansion% 
[ context = default,
    stretch = 20,
    shrink = 20,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = small,
    stretch = 25,
    shrink = 25,
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = mid, % sloppy from doc
    stretch = 30,
    shrink = 60,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = soft,
    stretch = 25,
    shrink = 40,
    step = 3% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = hard,
    stretch = 40,
    shrink = 70,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion % for ALL symbols
[ factor = 500, context = half ] { encoding = {OT1,T1,T2A}, shape = * }{ }

\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, size = {large}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape={n,it} }{ 0 }
\SetTracking{encoding={*}, shape={n,it} }{ 0 }

\DeclareMicrotypeSet{t2atext}{%
    encoding={OT1,T1,T2A,LGR},
    family={cmr},
    font={{OT1,T1,T2A,LGR}/*/*/{n,it}/*}}
\UseMicrotypeSet{t2atext}

\microtypecontext{protrusion=en-default}
\microtypecontext{kerning=en-default}
\microtypecontext{expansion=default}
\microtypecontext{tracking=en-default}

% save \emph and \footnote 
\LetLtxMacro\SVDemph\emph%
\LetLtxMacro\SVDfootnote\footnote%

% variant for change \spaceskip: 
% thank's https://tex.stackexchange.com/users/34311/pantlmn 
\newlength{\savedspaceskip}
\newcommand{\interword}[2]{%
  \setlength{\savedspaceskip}{\spaceskip}%
    \begingroup%
    \setlength{\spaceskip}{#1\fontdimen2\font plus #1\fontdimen3\font minus #1\fontdimen4\font}%
    #2%
    \endgroup%
  \setlength{\spaceskip}{\savedspaceskip}%
}

% main macro
% \parx
%  [tracking] ~ -10..10 - whole numb.
%  [inter-word] ~ 0 .. 1.25 float numb.
%  [fontexpansion] 1,2,3,4,5 - aliases for contexts
%  {\looseness=+1} if 1 - add line if possiple
%  {\looseness=-1} if 1 - del line if possible
%  {\parfillskip=...} if 1 - for increasing short last line 
%  {\parfillskip=...} if 1 - for decreasing long last line
%  {\parfillskip=...} if 1 - for filling last line to \textwidth

% without redefining \emph work not correctly
% \footnote redefined for turning-off tracking and set font expansion to default

\NewDocumentCommand\parx{o o o m m m m m +m}{%
  \ifthenelse{\isempty{#1} \AND  \isempty{#2} \AND \isempty{#3}}
  {% No  action
    \LetLtxMacro\emph\SVDemph%
    \microtypecontext{expansion=default,protrusion=en-default}%
    #9%
  }{% 
    \ifthenelse{\NOT\isempty{#1}}{% if #1=1
      \renewcommand{\emph}[1]{% temporary change \spaceskip for \emph
        \SVDemph{\setlength{\spaceskip}{#2\fontdimen2\font plus #2\fontdimen3\font minus #2\fontdimen4\font}##1}}%
            \textls*[#1]{%
        \ifthenelse{\NOT\isempty{#3}}{\setFontExpansion{#3}}{}%
        \renewcommand{\footnote}[1]{%
            \SVDfootnote{\microtypesetup{tracking=false}\microtypecontext{expansion=default}##1}}%
                \ifthenelse{\NOT\isempty{#2}}% #2=1
                    {\interword{#2}{#9}}%
                    {\interword{\ParaInwAmByTypeface}{#9}}% #2=0
        \LetLtxMacro\emph\SVDemph% 
        \LetLtxMacro\footnote\SVDfootnote%                   
       }% end textls
       \ifthenelse{\NOT\isempty{#3}}{\setFontExpansion{#3}}{}%   
        }{% #1=0
      \ifthenelse{\NOT\isempty{#3}}{\setFontExpansion{#3}}{}%
            \renewcommand{\emph}[1]{%% temporary change \spaceskip for \emph
                \SVDemph{\setlength{\spaceskip}{#2\fontdimen2\font plus #2\fontdimen3\font minus #2\fontdimen4\font}##1}}%
        \renewcommand{\footnote}[1]{%
            \SVDfootnote{\microtypesetup{tracking=false}\microtypecontext{expansion=default}##1}}%
            \ifthenelse{\NOT\isempty{#2}}% 
                {\interword{#2}{#9}}% #2=1
                {\interword{\ParaInwAmByTypeface}{#9}}% #2=0
      \LetLtxMacro\emph\SVDemph%  
      \LetLtxMacro\footnote\SVDfootnote%                   
      \microtypecontext{expansion=default,protrusion=en-default}%
    }%
  }%
    \ifthenelse{\NOT\isempty{#4}}{\looseness=+1}{}% increase to one line if possible
    \ifthenelse{\NOT\isempty{#5}}{\looseness=-1}{}% decrease to one  line if possible
    \ifthenelse{\NOT\isempty{#6}}{{\parfillskip=0pt plus .9\textwidth \par}}{}% ParIndentStart
    \ifthenelse{\NOT\isempty{#7}}{{\parfillskip=1.5\parindent plus .9\textwidth \par}}{}% ParIndentEnd
    \ifthenelse{\NOT\isempty{#8}}{{\parfillskip=0pt \par}}{}% ParIndentFull
}


\begin{document}

\parx[][][]{}{}{}{}{}{%
Hello, here is some text without a meaning. This text should
show what a printed text will look like at this place. If you
read this text, you will get no information. Really? Is there no
information? \emph{Is there a difference between this text and some
nonsense like “Huardest gefburn”? Kjift – not at all! A blind
text like this gives you information about the selected font, how
the letters are written and an impression of the look.} This text
should contain all letters of the alphabet and it should be written
in of the original language\footnote{There is no need for special content,
but the length of words should match the language. Hello, here
is some text without a meaning. This text should show what
a printed text will look like at this place. If you read this text,
you will get no information. Really? Is there no information?
Is there a difference between this text and some nonsense like
“Huardest gefburn”?}. Kjift – not at all! A blind text like this
gives you information about the selected font, how the letters
are written and an impression of the look. This text should
contain all letters of the alphabet and it should be written in of
the original language. There is no need for special content, but
the length of words should match should match  special the language.
}

\end{document}

Here macros for TexStudio:

(excuse me for my JS and for my english)

using:
select paragraph, run preview, run macros.
set values, press OK. repeat (or Cancel for exit)
For Inter word you must set 120 in dialog to set 1.2 in text (we can scroll for whole numbers)

//SCRIPT

/*
GUI-script for changing options of \parx macros    
*/

var s;// selected text
var p;// regex match

bye: 
while (true){
  if ( s = cursor.selectedText() ) {
  // search in selected - \parx...
  var r = /\\parx\[([0-9-]+)?\]\[([0-9.-]+)?\]\[([0-9-]+)?\]\{(.)?\}\{(.)?\}\{(.)?\}\{(.)?\}\{(.)?\}/;
   p = s.match(r);

   if ( p != -1 && p != null) {
     //get \parx options
      var cur_tr        = p[1] ? p[1]*1     : 0;// N (+\-)1,2,3 ,,,
      var cur_iw        = p[2] ? p[2]*100   : 0;// float [+-] 1.112134  (x100 in dialog)
      var cur_fe        = p[3] ? p[3]*1     : 0;// N (+\-)1,2,3 ,,,
      var cur_lp        = p[4] ? true : false;// 1 or empty
      var cur_lm        = p[5] ? true : false;// 1 or empty
      var cur_pfs   = p[6] ? true : false;// 1 or empty
      var cur_pfe   = p[7] ? true : false;// 1 or empty
      var cur_pfil  = p[8] ? true : false;// 1 or empty
      var reset         = false; 

      // create and init dialog window
      dlg = new UniversalInputDialog();
      dlg.add( reset,   "Reset all", "chbx_reset" );
      dlg.add( cur_tr,  "Tracking", "nbx_track" );
      dlg.add( cur_iw,  "Inter word x100", "nbx_iw" );// divide 100 
      dlg.add( cur_fe,  "Font expansion", "nbx_fexp" );// 
      dlg.add( cur_lp,  "Add line", "chbx_lplus" );
      dlg.add( cur_lm,  "Delete line", "chbx_lminus" );
      dlg.add( cur_pfs, "Fill - at start", "chbx_pf_start" );
      dlg.add( cur_pfe, "Fill - at end", "chbx_pf_end" );
      dlg.add( cur_pfil,"Fill - full line", "chbx_pf_fill" );      

      //run dialog
      a= dlg.exec();

      if ( a === undefined ) {
      // if pressed Cancel or close
        //alert('Bye');
        break bye;
      } else if ( a != false ) {
      // if pressed OK
        var tr = dlg.get("nbx_track") ? dlg.get("nbx_track")    : 0 ;
        var iw = dlg.get("nbx_iw")      ? dlg.get("nbx_iw")         : 0 ;
        var fe = dlg.get("nbx_fexp")    ? dlg.get("nbx_fexp")   : 0 ;
        var lp;
        var lm;
        var pfs;
        var pfe;
        var pfil; 
        if (dlg.get("chbx_lplus") == false ){lp = ''}else {lp = '1'};         
        if (dlg.get("chbx_lminus") == false ){lm = ''}else {lm = '1'};         
        if (dlg.get("chbx_pf_start") == false ){pfs = ''}else {pfs = '1'}; 
        if (dlg.get("chbx_pf_end") == false ){pfe = ''}else {pfe = '1'}; 
        if (dlg.get("chbx_pf_fill") == false ){pfil = ''}else {pfil = '1'}; 

        // set to 0 into dialog -> set empty in text
        if (tr == 0){tr=''};
        if (iw == 0){iw=''};
        if (fe == 0){fe=''};

        if (iw != 0){iw = iw / 100};

        // if 'Reset all' checked 
        if ( dlg.get("chbx_reset") == true ) {
          //alert ('Reset all');
          tr    =   '';
          iw    =   '';
          fe    =   '';
          lp    = '';
          lm    = '';
          pfs = '';
          pfe = '';
          pfil = '';
        }

        //apply changes
        n = s.replace(/\\parx\[([0-9-]+)?\]\[([0-9.-]+)?\]\[([0-9-]+)?\]\{(.)?\}\{(.)?\}\{(.)?\}\{(.)?\}\{(.)?\}/, 
          '\\parx['+tr+']['+iw+']['+fe+']{'+lp+'}{'+lm+'}{'+pfs+'}{'+pfe+'}{'+pfil+'}' );

        cursor.replaceSelectedText(n);   

      } 
   } else { alert ( "No \\parx founded!" ); break bye;}
  } else { alert( "Nothing selected!" ); break bye;}
} //end while
  • 1
    you load expl3 etc but then use \ifthenelse for your main tests iftenelse is not written for speed, similarly using \renewcommand{\footnote}[1] in your main macro means that you are checking, on every paragraph, whether \footnote is defined, even though you know that's true in every case. (no time to answer with code at the moment, just leaving some hints in case anyone else wants to answer:-) – David Carlisle Jul 13 '18 at 7:00
2

The first thing I noticed was the high number of arguments, three of them optional and in a row. The second is that arguments #4 and #5 are mutually exclusive as are arguments #6, #7 and #8. These could be merged.

But for such complicated collection of arguments, it's IMO better to use a key value interface. This has several advantages:

  • The parameters are obvious (well, if you select good names for them), even if you didn't work with the macro for some time.
  • It is easier to expand (see example in code).
  • No more 9 arguments limit (so you can make it even more complex).

For \emph and \footnote I added hooks, so they don't have to be redefined all the time. There are the macros \set@parx@emph@hook and \set@parx@footnote@hook to set them. And \setEmph and \setFootnote use these to set the hooks similar to the original macro.

I also wrote a script for TeXStudio, but it puts some limits to the macro. The script only recognizes \parx with optional parameter, so there have to be at least empty brackets ([]). The names for the choices of expansion and parfillskip can only contain lower case letters. And I didn't find a way to properly initialize the combo boxes, so the current value, if it exist, is added at the top of the list. Btw., the empty entry is for not setting the key.

The code:

\documentclass[10pt,a5paper, notitlepage,final]{book}

\usepackage[OT1,T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polutonikogreek,english]{babel}

\usepackage[text={3in,5in},centering]{geometry}

\newcommand{\ffamily}{cmr}% default typeface
\newcommand{\ParaInwAm}{1}% default inter-word space coeff. в \parx - 
\newcommand{\ParaInwAmByTypeface}{1}%  inter-word space coeff. 

\usepackage{xkeyval}

\usepackage[babel=true,
protrusion=true, 
kerning=true,
expansion=alltext,
tracking=smallcaps
%spacing=true
]{microtype}

% settings FOR EXAMPLE 

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/{n}/*}% NORMAL
}{
    « = {300,     },
    » = {    , 300}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 120},
    ! = {    , 120}, %120
    ? = {    , 0}, %0
    : = {    , 1000},
    ; = {    , 1000},
    . = {    , 1000},
    - = {    ,  1000},
    {,} = {    , 1000}
}

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/it/*}% NORMAL
}{
    « = {300,     },
    » = {    , 200}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 70},
    ! = {    , 0}, %120
    ? = {    , 0}, %0
    : = {    , 100},
    ; = {    , 100},
    . = {    , 1000},
    - = {    ,  950},
    {,} = {    , 1000}
}

% settings FOR EXAMPLE
\SetExpansion% 
[ context = default,
    stretch = 20,
    shrink = 20,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = small,
    stretch = 25,
    shrink = 25,
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = mid, % sloppy from doc
    stretch = 30,
    shrink = 60,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = soft,
    stretch = 25,
    shrink = 40,
    step = 3% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = hard,
    stretch = 40,
    shrink = 70,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion % for ALL symbols
[ factor = 500, context = half ] { encoding = {OT1,T1,T2A}, shape = * }{ }

\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, size = {large}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape={n,it} }{ 0 }
\SetTracking{encoding={*}, shape={n,it} }{ 0 }

\DeclareMicrotypeSet{t2atext}{%
    encoding={OT1,T1,T2A,LGR},
    family={cmr},
    font={{OT1,T1,T2A,LGR}/*/*/{n,it}/*}}
\UseMicrotypeSet{t2atext}

\microtypecontext{protrusion=en-default}
\microtypecontext{kerning=en-default}
\microtypecontext{expansion=default}
\microtypecontext{tracking=en-default}

%-------------------------------------------------------------------------------
\makeatletter
% tracking
\newif\ifparx@tracking\parx@trackingfalse
\newcommand*{\parx@tracking}{}
\define@key{parx}{tracking}{\def\parx@tracking{#1}\parx@trackingtrue}

% inter-word
\newif\ifparx@interword\parx@interwordfalse
\newcommand*{\parx@interword}{}
\define@key{parx}{interword}{\def\parx@interword{#1}\parx@interwordtrue}

\newcommand*{\interword}[1]{%
    \ifparx@interword
        \@interword{\parx@interword}{#1}%
    \else
        \@interword{\ParaInwAmByTypeface}{#1}%
    \fi
}
\newcommand*{\@interword}[2]{%
    \setlength{\spaceskip}{#1\fontdimen2\font plus #1\fontdimen3\font minus #1\fontdimen4\font}%
    #2%
}

% expansion
\newif\ifparx@expansion\parx@expansionfalse
\newcommand*{\parx@expansion}{}
\define@choicekey+{parx}{expansion}[\parx@expansion]%
  {small,soft,mid,hard,half}{\parx@expansiontrue}{}

\newcommand*{\setFontExpansion}{%
    \ifparx@expansion
        \microtypecontext{expansion=\parx@expansion}%
    \fi
}

% looseness
\newcommand*{\parx@looseness}{}
\define@key{parx}{looseness}{\def\parx@looseness{#1}}

\newcommand*{\setLooseness}{%
    \ifx\parx@looseness\@empty\else
        \global\looseness=\parx@looseness
    \fi
}

% parfillskip
\newcommand*{\parx@parfillskip}{}
\define@choicekey{parx}{parfillskip}[\parx@parfillskip]%
  {increase,decrease,fill}{}

\newcommand*{\parx@parfillskip@increase}{%
    \global\parfillskip=0pt plus .9\textwidth \par
}
\newcommand*{\parx@parfillskip@decrease}{%
    \global\parfillskip=1.5\parindent plus .9\textwidth \par
}
\newcommand*{\parx@parfillskip@fill}{%
    \global\parfillskip=0pt \par
}

\newcommand*{\setParfillskip}{%
    \ifx\parx@parfillskip\@empty\else
        \csname parx@parfillskip@\parx@parfillskip\endcsname
    \fi
}

% expanding parfillskip:
% e.g. add 'half' as \parfillskip=.5\textwidth plus .1\textwidth minus .1\textwidth \par
% change {increase,decrease,fill}{}
% to     {increase,decrease,fill,half}{}
% and add                        v-- note: name of value goes here
% \newcommand*{\parx@parfillskip@half}{%
%     \parfillskip=.5\textwidth plus .1\textwidth minus .1\textwidth \par
% }

% hook for \emph
\let\parx@orig@emph\emph%
\newcommand*{\parx@emph@hook}{}
\newcommand*{\set@parx@emph@hook}[1]{%
    \def\parx@emph@hook{#1}%
}
\renewcommand*{\emph}[1]{\parx@orig@emph{\parx@emph@hook #1}}
\newcommand*{\setEmph}{%
    \set@parx@emph@hook{%
        \setlength{\spaceskip}{\parx@interword\fontdimen2\font
                               plus \parx@interword\fontdimen3\font
                               minus \parx@interword\fontdimen4\font}%
    }%
}

% hook for \footnote
\let\parx@orig@footnote\footnote%
\newcommand*{\parx@footnote@hook}{}
\newcommand*{\set@parx@footnote@hook}[1]{%
    \def\parx@footnote@hook{#1}%
}
\renewcommand*{\footnote}[2][]{%
    \def\@tempa{#1}\ifx\@tempa\@empty
        \parx@orig@footnote{\parx@footnote@hook #2}%
    \else
        \parx@orig@footnote[#1]{\parx@footnote@hook #2}%
    \fi
}
\newcommand*{\setFootnote}{%
    \set@parx@footnote@hook{\microtypesetup{tracking=false}\microtypecontext{expansion=default}}%
}

% main macro
% \parx
%  tracking    - whole number.
%  interword   - float number
%  expansion   - choice: small,soft,mid,hard,half
%  looseness   - whole number
%  parfillskip - choice: increase,decrease,fill

\newcommand*{\parx}[2][]{%
    \begingroup
    \setkeys{parx}{#1}%
    \ifparx@tracking
        \@parx{#2}%
    \else
        \ifparx@interword
            \@parx{#2}%
        \else
            \ifparx@expansion
                \@parx{#2}%
            \else
                \set@parx@emph@hook{}%
                \microtypecontext{expansion=default,protrusion=en-default}%
                #2%
            \fi
        \fi
    \fi
    \setLooseness
    \setParfillskip
    \endgroup
}

\newcommand*{\@parx}[1]{%
    \ifparx@tracking
        \setEmph
        \textls*[\parx@tracking]{%
            \setFontExpansion
            \setFootnote
            \interword{#1}%
        }% end textls
        \setFontExpansion
    \else
        \setFontExpansion
        \setEmph
        \setFootnote
        \interword{#1}%
        \microtypecontext{expansion=default,protrusion=en-default}%
    \fi
}

\makeatother
%-------------------------------------------------------------------------------

\begin{document}

\parx[]{%
Hello, here is some text without a meaning. This text should
show what a printed text will look like at this place. If you
read this text, you will get no information. Really? Is there no
information? \emph{Is there a difference between this text and some
nonsense like “Huardest gefburn”? Kjift – not at all! A blind
text like this gives you information about the selected font, how
the letters are written and an impression of the look.} This text
should contain all letters of the alphabet and it should be written
in of the original language\footnote{There is no need for special content,
but the length of words should match the language. Hello, here
is some text without a meaning. This text should show what
a printed text will look like at this place. If you read this text,
you will get no information. Really? Is there no information?
Is there a difference between this text and some nonsense like
“Huardest gefburn”?}. Kjift – not at all! A blind text like this
gives you information about the selected font, how the letters
are written and an impression of the look. This text should
contain all letters of the alphabet and it should be written in of
the original language. There is no need for special content, but
the length of words should match should match  special the language.
}

\end{document}

The script:

//SCRIPT

/*
GUI-script for changing options of \parx macros    
*/

var s;// selected text
var p;// regex match

bye: 
while (true){
    if ( s = cursor.selectedText() ) {
        // search in selected - \parx[...]
        var r = /\\parx\[(.)*\]/;   
        p = s.match(r);

        if ( p != -1 && p != null) {
            // create dialog window
            dlg = new UniversalInputDialog();
            dlg.add( false,   "Reset all", "chbx_reset" );

            // get \parx options
            // tracking
            var key = /tracking=([0-9-]+)?[,\]]+/;
            var opt = p[0].match(key);
            var cur_tr = 0;
            if ( opt != -1 && opt != null ) {
                cur_tr = opt[1]*1;
            }
            dlg.add( cur_tr,  "Tracking", "nbx_tracking" );

            // interword
            var key = /interword=([0-9.-]+)?[,\]]+/;
            var opt = p[0].match(key);
            var cur_iw = 0;
            if ( opt != -1 && opt != null ) {
                cur_iw = opt[1]*100;
            }
            dlg.add( cur_iw,  "Inter word x100", "nbx_interword" );

            // expansion
            var key = /expansion=([a-z]+)?[,\]]+/;
            var opt = p[0].match(key);
            var cur_fe = '';
            if ( opt != -1 && opt != null ) {
                cur_fe = opt[1];
            }
            var cur_fe_items;
            if ( cur_fe == '' ) {
                cur_fe_items = ['','small','soft','mid','hard','half'];
            } else {
                cur_fe_items = [cur_fe,'','small','soft','mid','hard','half'];
            }
            dlg.add( cur_fe_items,  "Font expansion", "cobx_expansion" );

            // looseness
            var key = /looseness=([0-9-]+)?[,\]]+/;
            var opt = p[0].match(key);
            var cur_lp = 0;
            if ( opt != -1 && opt != null ) {
                cur_lp = opt[1]*1;
            }
            dlg.add( cur_lp,  "Add/delete line", "nbx_looseness" );

            // parfillskip            
            var key = /parfillskip=([a-z]+)?[,\]]+/;
            var opt = p[0].match(key);
            var cur_pf = '';
            if ( opt != -1 && opt != null ) {
                cur_pf = opt[1];
            }
            var cur_pf_items;
            if ( cur_pf == '' ) {
                cur_pf_items = ['','increase','decrease','fill'];
            } else {
                cur_pf_items = [cur_pf,'','increase','decrease','fill','test'];
            }
            dlg.add( cur_pf_items,  "Parfillskip", "cobx_parfill" );

            //run dialog
            a = dlg.exec();

            if ( a === undefined ) {
                // if pressed Cancel or close
                //alert('Bye');
                break bye;
            } else if ( a != false ) {
            // if pressed OK
                var tr = '';
                var iw = '';
                var fe = '';
                var lp = '';
                var pf = '';
                // if not 'Reset all' checked 
                if ( dlg.get("chbx_reset") == false ) {
                    tr = dlg.get("nbx_tracking")   ? dlg.get("nbx_tracking")   : 0;
                    iw = dlg.get("nbx_interword")  ? dlg.get("nbx_interword") / 100 : 0;
                    fe = dlg.get("cobx_expansion") ? dlg.get("cobx_expansion") : '';
                    lp = dlg.get("nbx_looseness")  ? dlg.get("nbx_looseness")  : 0;
                    pf = dlg.get("cobx_parfill")   ? dlg.get("cobx_parfill")   : '';

                    // set to 0 into dialog -> not include key
                    if ( tr == 0 ) { tr = '' };
                    if ( iw == 0 ) { iw = '' }; // else { iw = iw / 100 };
                    if ( lp == 0 ) { lp = '' };
                }

                var param = '';
                if ( tr != '' ) {                                            param = param + 'tracking='    + tr };
                if ( iw != '' ) { if( param != '' ) { param = param + ',' }; param = param + 'interword='   + iw };
                if ( fe != '' ) { if( param != '' ) { param = param + ',' }; param = param + 'expansion='   + fe };
                if ( lp != '' ) { if( param != '' ) { param = param + ',' }; param = param + 'looseness='   + lp };
                if ( pf != '' ) { if( param != '' ) { param = param + ',' }; param = param + 'parfillskip=' + pf };


                //apply changes
                n = s.replace(/\\parx\[.*\]/, 
                    '\\parx['+param+']' );
                cursor.replaceSelectedText(n);

            }
        } else { alert ( "No \\parx founded!" ); break bye;}
    } else { alert( "Nothing selected!" ); break bye;}
} //end while
  • Why I choice this form of arguments - from the beginning I have not GUI dialog, and for speedy editing of text I elect this way. Without GUI dialog I can quick set\unset 1 into {} in text. There is GUI dialog now and goodest choice perhaps - set keys, as you do. (But without GUI this way may be slow for editing). – Elisey Jul 15 '18 at 23:44
  • If I try use greek text (via begingrek), I get: pdfTeX error (font expansion): using fonts with different step of expansion in one paragraph is not allowed. In original version, I set \microtypesetup{expansion=false} for greek text in macro \gr{\foreignlanguage{greek}{\greektxt{#1}}}. But it not work here. May be you can help about this? – Elisey Jul 16 '18 at 6:08
  • @Elisey for the greek: I can't test this, because I don't know where \gr and \greektxt come from. And since I usually don't use greek, it's impossible for me to guess. But I think the error message is quite clear. – Mike Jul 16 '18 at 20:38
  • @Elisey for the form of arguments: I rewrote the code and made it a new answer. Now you can choose. – Mike Jul 16 '18 at 22:07
1

Although I think, a key-value interface is more flexible and easier to read, your are right, it's not for fast editing.

So here I modified my previous answer. \parx now has 6 parameters, the first 5 for the tuning. They are all numbers. Their meaning is given in the comment before the macro in the code. The arguments are all mandatory here, but if you want you can easily convert some of them to optional ones.

For \emph and \footnote the same hooks as in the previous answer are used.

And I also wrote a new script.

I leave the old answer, so you (and may be other people) can choose the one you like more.

Code:

\documentclass[10pt,a5paper, notitlepage,final]{book}

\usepackage[OT1,T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polutonikogreek,english]{babel}

\usepackage[text={3in,5in},centering]{geometry}

\newcommand{\ffamily}{cmr}% default typeface
\newcommand{\ParaInwAm}{1}% default inter-word space coeff. в \parx - 
\newcommand{\ParaInwAmByTypeface}{1}%  inter-word space coeff. 

\usepackage[babel=true,
protrusion=true, 
kerning=true,
expansion=alltext,
tracking=smallcaps
%spacing=true
]{microtype}

% settings FOR EXAMPLE 

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/{n}/*}% NORMAL
}{
    « = {300,     },
    » = {    , 300}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 120},
    ! = {    , 120}, %120
    ? = {    , 0}, %0
    : = {    , 1000},
    ; = {    , 1000},
    . = {    , 1000},
    - = {    ,  1000},
    {,} = {    , 1000}
}

\SetProtrusion
[context=en-default]
{% NORMAL - 
    encoding = {T2A},
    family = cmr,
    font={T2A/cmr/*/it/*}% NORMAL
}{
    « = {300,     },
    » = {    , 200}, %1000
    „ = {0,     },
    “ = {    , 0},
    ( = {120,     },% 1000
    ) = {    , 70},
    ! = {    , 0}, %120
    ? = {    , 0}, %0
    : = {    , 100},
    ; = {    , 100},
    . = {    , 1000},
    - = {    ,  950},
    {,} = {    , 1000}
}

% settings FOR EXAMPLE
\SetExpansion% 
[ context = default,
    stretch = 20,
    shrink = 20,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = small,
    stretch = 25,
    shrink = 25,
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = mid, % sloppy from doc
    stretch = 30,
    shrink = 60,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = soft,
    stretch = 25,
    shrink = 40,
    step = 3% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion% 
[ context = hard,
    stretch = 40,
    shrink = 70,
    step = 5% 
]{ encoding = {OT1,T1,T2A} }{ }

\SetExpansion % for ALL symbols
[ factor = 500, context = half ] { encoding = {OT1,T1,T2A}, shape = * }{ }

\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, size = {large}, shape=sc}{0}
\SetTracking[context = en-default]{encoding={OT1,T1,T2A}, shape={n,it} }{ 0 }
\SetTracking{encoding={*}, shape={n,it} }{ 0 }

\DeclareMicrotypeSet{t2atext}{%
    encoding={OT1,T1,T2A,LGR},
    family={cmr},
    font={{OT1,T1,T2A,LGR}/*/*/{n,it}/*}}
\UseMicrotypeSet{t2atext}

\microtypecontext{protrusion=en-default}
\microtypecontext{kerning=en-default}
\microtypecontext{expansion=default}
\microtypecontext{tracking=en-default}

%-------------------------------------------------------------------------------
\makeatletter
% tracking
\newif\ifparx@tracking\parx@trackingfalse
\newcommand*{\parx@tracking}{}
\newcommand*{\parx@handle@tracking}[1]{%
    \def\parx@tracking{#1}\parx@trackingtrue
}

% inter-word
\newif\ifparx@interword\parx@interwordfalse
\newcommand*{\parx@interword}{}
\newcommand*{\parx@handle@interword}[1]{%
    \def\parx@interword{#1}\parx@interwordtrue
}

\newcommand*{\interword}[1]{%
    \ifparx@interword
        \@interword{\parx@interword}{#1}%
    \else
        \@interword{\ParaInwAmByTypeface}{#1}%
    \fi
}
\newcommand*{\@interword}[2]{%
    \setlength{\spaceskip}{#1\fontdimen2\font plus #1\fontdimen3\font minus #1\fontdimen4\font}%
    #2%
}

% expansion
\newif\ifparx@expansion\parx@expansionfalse
\newcommand*{\parx@expansion}{}
\newcommand*{\parx@handle@expansion}[1]{%
    \def\@tempa{#1}\ifx\@tempa\@empty\else
        \ifcase#1
        \or\def\parx@expansion{small}% #1 = 1
        \or\def\parx@expansion{soft}%  #1 = 2
        \or\def\parx@expansion{mid}%   #1 = 3
        \or\def\parx@expansion{hard}%  #1 = 4
        \or\def\parx@expansion{half}%  #1 = 5
        \fi
        \parx@expansiontrue
    \fi
}

\newcommand*{\setFontExpansion}{%
    \ifparx@expansion
        \microtypecontext{expansion=\parx@expansion}%
    \fi
}

% looseness
\newcommand*{\parx@looseness}{}
\newcommand*{\parx@handle@looseness}[1]{%
    \def\parx@looseness{#1}%
}

\newcommand*{\setLooseness}{%
    \ifx\parx@looseness\@empty\else
        \global\looseness=\parx@looseness
    \fi
}

% parfillskip
\newcommand*{\parx@parfillskip}{}
\newcommand*{\parx@handle@parfillskip}[1]{%
    \def\@tempa{#1}\ifx\@tempa\@empty\else
        \ifcase#1
        \or\def\parx@parfillskip{increase}% #1 = 1
        \or\def\parx@parfillskip{decrease}% #1 = 2
        \or\def\parx@parfillskip{fill}%     #1 = 3
        \fi
    \fi
}

\newcommand*{\parx@parfillskip@increase}{%
    \global\parfillskip=0pt plus .9\textwidth \par
}
\newcommand*{\parx@parfillskip@decrease}{%
    \global\parfillskip=1.5\parindent plus .9\textwidth \par
}
\newcommand*{\parx@parfillskip@fill}{%
    \global\parfillskip=0pt \par
}

\newcommand*{\setParfillskip}{%
    \ifx\parx@parfillskip\@empty\else
        \csname parx@parfillskip@\parx@parfillskip\endcsname
    \fi
}

% expanding parfillskip:
% e.g. add 'half' as \parfillskip=.5\textwidth plus .1\textwidth minus .1\textwidth \par
% add   \or\def\parx@parfillskip{half}%     #1 = 4
% after \or\def\parx@parfillskip{fill}%     #1 = 3
% and add                        v-- note: name of value goes here
% \newcommand*{\parx@parfillskip@half}{%
%     \parfillskip=.5\textwidth plus .1\textwidth minus .1\textwidth \par
% }

% hook for \emph
\let\parx@orig@emph\emph%
\newcommand*{\parx@emph@hook}{}
\newcommand*{\set@parx@emph@hook}[1]{%
    \def\parx@emph@hook{#1}%
}
\renewcommand*{\emph}[1]{\parx@orig@emph{\parx@emph@hook #1}}
\newcommand*{\setEmph}{%
    \set@parx@emph@hook{%
        \setlength{\spaceskip}{\parx@interword\fontdimen2\font
                               plus \parx@interword\fontdimen3\font
                               minus \parx@interword\fontdimen4\font}%
    }%
}

% hook for \footnote
\let\parx@orig@footnote\footnote%
\newcommand*{\parx@footnote@hook}{}
\newcommand*{\set@parx@footnote@hook}[1]{%
    \def\parx@footnote@hook{#1}%
}
\renewcommand*{\footnote}[2][]{%
    \def\@tempa{#1}\ifx\@tempa\@empty
        \parx@orig@footnote{\parx@footnote@hook #2}%
    \else
        \parx@orig@footnote[#1]{\parx@footnote@hook #2}%
    \fi
}
\newcommand*{\setFootnote}{%
    \set@parx@footnote@hook{\microtypesetup{tracking=false}\microtypecontext{expansion=default}}%
}

% main macro
% \parx
% #1: tracking    - whole number, -10 - 10
% #2: interword   - float number, 0 - 1.25
% #3: expansion   - whole number, 1: small, 2: soft, 3: mid, 4: hard, 5: half
% #4: looseness   - whole number, -1, 1
% #5: parfillskip - whole number, 1: increase, 2: decrease, 3: fill
% #6: text

\newcommand*{\parx}[6]{%
    \begingroup
    \parx@handle@tracking{#1}%
    \parx@handle@interword{#2}%
    \parx@handle@expansion{#3}%
    \parx@handle@looseness{#4}%
    \parx@handle@parfillskip{#5}%
    \ifparx@tracking
        \@parx{#6}%
    \else
        \ifparx@interword
            \@parx{#6}%
        \else
            \ifparx@expansion
                \@parx{#6}%
            \else
                \set@parx@emph@hook{}%
                \microtypecontext{expansion=default,protrusion=en-default}%
                #6%
            \fi
        \fi
    \fi
    \setLooseness
    \setParfillskip
    \endgroup
}

\newcommand*{\@parx}[1]{%
    \ifparx@tracking
        \setEmph
        \textls*[\parx@tracking]{%
            \setFontExpansion
            \setFootnote
            \interword{#1}%
        }% end textls
        \setFontExpansion
    \else
        \setFontExpansion
        \setEmph
        \setFootnote
        \interword{#1}%
        \microtypecontext{expansion=default,protrusion=en-default}%
    \fi
}

\makeatother
%-------------------------------------------------------------------------------

\begin{document}

\parx{}{}{}{}{}{%
Hello, here is some text without a meaning. This text should
show what a printed text will look like at this place. If you
read this text, you will get no information. Really? Is there no
information? \emph{Is there a difference between this text and some
nonsense like “Huardest gefburn”? Kjift – not at all! A blind
text like this gives you information about the selected font, how
the letters are written and an impression of the look.} This text
should contain all letters of the alphabet and it should be written
in of the original language\footnote{There is no need for special content,
but the length of words should match the language. Hello, here
is some text without a meaning. This text should show what
a printed text will look like at this place. If you read this text,
you will get no information. Really? Is there no information?
Is there a difference between this text and some nonsense like
“Huardest gefburn”?}. Kjift – not at all! A blind text like this
gives you information about the selected font, how the letters
are written and an impression of the look. This text should
contain all letters of the alphabet and it should be written in of
the original language. There is no need for special content, but
the length of words should match should match  special the language.
}

\end{document}

Script:

//SCRIPT

/*
GUI-script for changing options of \parx macros    
*/

var s;// selected text
var p;// regex match

bye: 
while (true){
    if ( s = cursor.selectedText() ) {
        // search in selected - \parx[...]
        var r = /\\parx\{([0-9-]+)?\}\{([0-9.-]+)?\}\{([0-9-]+)?\}\{([0-9-]+)?\}\{([0-9-]+)?\}/;   
        p = s.match(r);

        if ( p != -1 && p != null) {
            // get \parx options
            var cur_tr = p[1] ? p[1]*1   : 0;
            var cur_iw = p[2] ? p[2]*100 : 0;
            var cur_fe = p[3] ? p[3]*1   : 0;
            var cur_lp = p[4] ? p[4]*1   : 0;
            var cur_pf = p[5] ? p[5]*1   : 0;

            // create dialog window
            dlg = new UniversalInputDialog();
            dlg.add( false,   "Reset all", "chbx_reset" );
            dlg.add( cur_tr,  "Tracking", "nbx_tracking" );
            dlg.add( cur_iw,  "Inter word x100", "nbx_interword" );
            dlg.add( cur_fe,  "Font expansion", "cobx_expansion" );
            dlg.add( cur_lp,  "Add/delete line", "nbx_looseness" );
            dlg.add( cur_pf,  "Parfillskip", "cobx_parfill" );

            //run dialog
            a = dlg.exec();

            if ( a === undefined ) {
                // if pressed Cancel or close
                //alert('Bye');
                break bye;
            } else if ( a != false ) {
            // if pressed OK
                var tr = '';
                var iw = '';
                var fe = '';
                var lp = '';
                var pf = '';
                // if not 'Reset all' checked 
                if ( dlg.get("chbx_reset") == false ) {
                    tr = dlg.get("nbx_tracking")   ? dlg.get("nbx_tracking")   : 0;
                    iw = dlg.get("nbx_interword")  ? dlg.get("nbx_interword") / 100 : 0;
                    fe = dlg.get("cobx_expansion") ? dlg.get("cobx_expansion") : 0;
                    lp = dlg.get("nbx_looseness")  ? dlg.get("nbx_looseness")  : 0;
                    pf = dlg.get("cobx_parfill")   ? dlg.get("cobx_parfill")   : 0;

                    // set to 0 into dialog -> empty parameternot include key
                    if ( tr == 0 ) { tr = '' };
                    if ( iw == 0 ) { iw = '' };
                    if ( fe == 0 ) { fe = '' };
                    if ( lp == 0 ) { lp = '' };
                    if ( pf == 0 ) { pf = '' };
                }

                //apply changes
                n = s.replace(/\\parx\{([0-9-]+)?\}\{([0-9.-]+)?\}\{([0-9-]+)?\}\{([0-9-]+)?\}\{([0-9-]+)?\}/, 
                    '\\parx{'+tr+'}{'+iw+'}{'+fe+'}{'+lp+'}{'+pf+'}' );
                cursor.replaceSelectedText(n);

            }
        } else { alert ( "No \\parx founded!" ); break bye;}
    } else { alert( "Nothing selected!" ); break bye;}
} //end while
  • Thank you for nice code! As about grrek text: here I add strings for using small pieces of greek text. (I remove them from MWE in my first post.) – Elisey Jul 16 '18 at 23:49
  • @Elisey sorry, but I can't test this here, some problems with the greek fonts, which I can't figure out right now. Does the same error appear, if you set font expansion for a paragraph containing greek, which is not in \parx? Oh, and the grouping in \gr seems to be wrong, you set expansion=false before the group. – Mike Jul 17 '18 at 20:41
  • Can you email me, Mike - shtangencirkul AT gmail.com? Not very convenient to write here all details. – Elisey Jul 18 '18 at 0:46

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.