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)
pic 1: Original position. \parx without options
pic 2: Run TexStudio GUI dialog
pic 3: Decrease last line
pic 4: Trying add line. (We get short last line)
pic 5: Try increase last line. (Not enough)
pic 6: Get it.
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
\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:-)