%%% textpath.mp
%%% Copyright 2007 Stephan Hennig <
[email protected]>
%
% This work may be distributed and/or modified under the conditions of
% the LaTeX Project Public License, either version 1.3 of this license
% or (at your option) any later version. The latest version of this
% license is in
http://www.latex-project.org/lppl.txt
%
if known textpath_fileversion: endinput fi;
string textpath_fileversion;
textpath_fileversion := "v1.6 (2007/02/11)";
message "Loading textpath " & textpath_fileversion;
%%% Global variables.
%%% Global variable.
%%% Character coordinates that lay outside path boundaries after
%%% transformation are clipped to the boundaries.
%%% Variable textpathClip determines if such characters should be
%%% output at path boundaries, thereby acting as a visual indicator
%%% that something went wrong, or if text outside path boundaries
%%% should be omitted. Variable textpathClip can be either one (omit)
%%% or zero (don't omit).
newinternal textpathClip;
textpathClip := 0;
%%% Global variable.
%%% Variable textpathLetterSpace determines the amount of space that
%%% is additionally inserted between all characters. This can be
%%% useful, when drawing text along a concacve shape or in general
%%% along paths with a high curvature. Variable textpathLetterSpace
%%% can contain positive or negative values.
newinternal textpathLetterSpace;
textpathLetterSpace := 0bp;
%%% Global variable.
newinternal textpathRotation;
textpathRotation := 0;
%%% Global variable.
newinternal textpathAbsRotation;
textpathAbsRotation := 0;
newinternal textpathRepeat;
textpathRepeat := 1;
newinternal textpathStretch;
textpathStretch := 1;
newinternal textpathHSpace;
textpathHSpace := 0;
newinternal textpathShift;
textpathShift := 0;
newinternal textpathSoulLetterSpace;
textpathSoulLetterSpace := 10bp;
newinternal textpathFancyStrokes;
textpathFancyStrokes := 1;
newinternal textpathStrokePrecision;
textpathStrokePrecision := 10;
newinternal textpathCureSqrt;
textpathCureSqrt := 1;
newinternal textpathAutoScale;
textpathAutoScale := 0;
newinternal textpathDraft;
textpathDraft := 0;
newinternal textpathDebug;
textpathDebug := 1;
pair textpathHookCoord, textpathHookPoint;
newinternal textpathHookRot;
textpathHookCoord := origin;
textpathHookPoint := origin;
textpathHookRot := 0;
%%% User Macros.
%%% User macro.
%%% Set text along a path.
%%% Parameters:
%%% * a text string t that is fed into TeX through latexmp,
%%% * a path p the text should follow.
%%% * a numeric variable j that controls justification:
%%% j=0: text is left aligned on the path,
%%% j=0.5: text is centered on the path,
%%% j=1: text is right aligned on the path,
%%% The general rule is the character at fraction j of the total
%%% text width is transformed to the point at fraction j of the total
%%% path width. j should be from the interval [0, 1].
%%% Return value:
%%% * a picture variable containing text following a path.
def textpath(expr t, p, j)=
textpathFont("", t, p, j)
enddef;
%%% User macro.
%%% Set text in arbitrary font along a path.
%%% Parameters:
%%% * a string f that is a valid LaTeX font selection command, e.g., \itshape,
%%% * a text string t that is fed into TeX through latexmp,
%%% * a path p the text should follow.
%%% * a numeric variable j that controls justification:
%%% j=0: text is left aligned on the path,
%%% j=0.5: text is centered on the path,
%%% j=1: text is right aligned on the path,
%%% The general rule is the character at fraction j of the total
%%% text width is transformed to the point at fraction j of the total
%%% path width. j should be from the interval [0, 1].
%%% Return value:
%%% * a picture variable containing text following a path.
vardef textpathFont(expr f, t, p, j)=
save pre, post;
string pre, post;
pre := f & "\spaced{";
post := "}";
textpathToPic(strToPic(pre & t & post), p, j)
enddef;
%%% User macro.
%%% Set text along a path.
%%% Parameters:
%%% * a text string t that is fed into TeX through latexmp,
%%% * a path p the text should follow.
%%% * a numeric variable j that controls justification:
%%% j=0: text is left aligned on the path,
%%% j=0.5: text is centered on the path,
%%% j=1: text is right aligned on the path,
%%% The general rule is the character at fraction j of the total
%%% text width is transformed to the point at fraction j of the total
%%% path width. j should be from the interval [0, 1].
%%% Return value:
%%% * a picture variable containing text following a path.
vardef textpathRaw(expr t, p, j)=
save pre, post;
string pre, post;
pre := "\rule[-30bp]{0pt}{30bp}";
post := "";
textpathRawToPic(strToPic(pre & t & post), p, j)
enddef;
%%% Internal Macros.
%%% Internal macro.
%%% Convert a string into a picture using TeX via latexmp package.
%%% Parameters:
%%% * a string.
%%% Return value:
%%% * a picture variable.
vardef strToPic(expr s)=
interim labeloffset := 0bp;
thelabel.urt(textext(s), origin)
enddef;
%%% Internal macro.
%%% Computes the length of a textual picture, thereby correcting
%%% for textpathSoulLetterSpace and textpathLetterSpace
vardef getPictureWidth(expr textpic, scale)=
save lenpic, k, w;
numeric lenpic, k, w;
lenpic := 0;
k := 0;
for item within textpic:
if textual item:
w := xpart lrcorner ( item shifted (scale*k*(textpathLetterSpace-textpathSoulLetterSpace), 0bp) );
if w > lenpic:
lenpic := w;
fi
k := k + 1;
fi
endfor
lenpic
enddef;
%%% Internal macro.
%%% Does pre-processing for macros textpath(Raw)ToPic, such as
%%% calculating variables startOffset, autoScale, ltextpathHSpace etc.
def preprocessTextPic(expr rawMode, atextpic, p, j)=
save textpic, pic, lenp, lenpic, startOffset, ltextpathHSpace, autoScale, overfullPath;
interim bboxmargin := 0bp;
numeric lenp, lenpic, startOffset, ltextpathHSpace, autoScale;
picture textpic, pic;
boolean overfullPath;
textpic := atextpic;
pic := nullpicture;
%%% Compute path length.
lenp := arclength(p);
%%% Compute picture length, i.e., correct plain picture dimensions
%%% for soul and textpath letter spacing.
if rawMode:%%% Called from textpathRawToPic.
lenpic := xpart (lrcorner textpic - llcorner textpic);
else:%%% Called from textpathToPic.
lenpic := getPictureWidth(textpic, 1);%%% Get corrected picture with scale = 1.
fi
%%% Compute inter-copy space (for textpathRepeat > 1).
if textpathStretch = 0:
ltextpathHSpace := textpathHSpace;
else:
ltextpathHSpace := lenp/textpathRepeat - lenpic;
fi
%%% Compute startOffset as j * remaining space.
startOffset := j * (lenp - textpathRepeat*lenpic - (textpathRepeat-1)*ltextpathHSpace);
%%% By default, assume path isn't overfull.
overfullPath := false;
autoScale := 1;
%%% Overfull path?
if startOffset < 0:
overfullPath := true;
if textpathAutoScale <> 0:%%% Calculate scaling factor.
if textpathStretch <> 0:
ltextpathHSpace := 0;%%% No inter-copy space when stretching allowed.
fi
autoScale := lenp/(textpathRepeat*lenpic + (textpathRepeat-1)*ltextpathHSpace1);
startOffset := 0;
if textpathDraft = 0:
textpic := atextpic scaled autoScale;
else:
textpic := atextpic xscaled autoScale;%%% Don't scale y in draft mode.
fi
lenpic := getPictureWidth(textpic, autoScale);
%%% Compute inter-copy space (for textpathRepeat > 1).
if textpathStretch = 0:
ltextpathHSpace := textpathHSpace*autoScale;
else:
ltextpathHSpace := lenp/textpathRepeat - lenpic;%%% Should be equal to zero.
fi
%%% Finally, output a warning.
message "Package textpath warning: Overfull path! Scaled to " & decimal autoScale & ".";
else:%%% No scaling.
autoScale := 1;
if textpathClip = 0:%%% Warn the user about overfull paths.
message "Package textpath warning: Overfull path! Not clipped.";
else:
message "Package textpath warning: Overfull path! Clipped.";
fi
fi
fi
enddef;
%%% Internal macro.
def transformSimpleItem(expr rawMode, item, p)=
%%% bitem is a character or straight rule to transform.
%%% banker is its original anchor position.
if rawMode:
bitem := item;
banker := (xpart center bitem, autoScale*30bp);%%% Why 30bp?
else:
bitem := item shifted (k*autoScale*(textpathLetterSpace-textpathSoulLetterSpace), 0bp);
banker := (xpart center bitem, ypart item);
fi
%%% bx is the anchor position of a first item copy.
bx := (xpart banker) + startOffset;
%%% Shift anchor to origin and raise baseline.
bitem := bitem shifted -banker shifted (0bp, textpathShift);
%%% Iterate over all copies of an item.
for i=1 upto textpathRepeat:
%%% cx is the anchor position of an item copy.
cx := (i-1)*(lenpic + ltextpathHSpace) + bx;
%%% Draw items with valid positions or if no clipping.
if ((cx>=0) and (cx<=lenp)) or (textpathClip=0):
%%% Compute path time value.
tb := arctime cx of p;
%%% Compute rotation angle.
if textpathAbsRotation=0:
db := textpathRotation + angle direction tb of p;
else:
db := textpathRotation;
fi
%%% Draw item's bounding box?
if odd (textpathDebug div 2):
addto pic doublepath bbox bitem rotated db shifted point tb of p withpen currentpen;
fi
%%% Draw item?
if odd (textpathDebug div 1):
%%% In draft mode fill bounding box if path is overfull.
if overfullPath and (textpathDraft<>0):
addto pic contour bbox bitem rotated db shifted point tb of p;
else:
addto pic also bitem rotated db shifted point tb of p;
fi
fi
%%% Draw item's anchor point?
if odd (textpathDebug div 4):
addto pic contour fullcircle scaled 3bp shifted point tb of p;
fi
fi
endfor
%%% Save top right corner of supported square root glyphs in variable textpathHookCoord.
if rawMode and (textpathCureSqrt <> 0):
if ((fontpart item="cmex10") and ((ASCII textpart item>=112) and (ASCII textpart item<=116))) or
((fontpart item="cmsy5") and (ASCII textpart item=112)) or
((fontpart item="cmsy6") and (ASCII textpart item=112)) or
((fontpart item="cmsy7") and (ASCII textpart item=112)) or
((fontpart item="cmsy8") and (ASCII textpart item=112)) or
((fontpart item="cmsy9") and (ASCII textpart item=112)) or
((fontpart item="cmsy10") and (ASCII textpart item=112)) or
((fontpart item="cmbsy10") and (ASCII textpart item=112)) or
((fontpart item="fourier-mex") and (ASCII textpart item=114)) or
((fontpart item="fourier-ms") and (ASCII textpart item=112)) or
((fontpart item="MnSymbolE5") and (ASCII textpart item=186)) or
((fontpart item="MnSymbolE5") and (ASCII textpart item=189)) or
((fontpart item="MnSymbolE10") and (ASCII textpart item=186)):%%% \sqrt glyph?
textpathHookCoord := urcorner item shifted (-xpart center item, -autoScale*30bp + textpathShift);
textpathHookRot := db;
textpathHookPoint := point tb of p;
% addto pic contour fullcircle scaled 2bp shifted textpathHookCoord rotated db shifted point tb of p withcolor green;
else:
textpathHookCoord := origin;
fi
fi
enddef;
%%% Internal macro.
def transformStrokedItem(expr rawMode, item, p)=
if textpathFancyStrokes = 0:
transformSimpleItem(true, item, p);
else:
pp := pathpart item;
for i=1 upto textpathRepeat:
if (textpathHookCoord=origin) or (textpathCureSqrt=0):
banker := (0, ypart point 0 of pp - autoScale*30bp + textpathShift);
bx := (xpart point 0 of pp) + startOffset;
dpp := angle direction 0 of pp;
cx := (i-1)*(lenpic + ltextpathHSpace) + bx;
tb := arctime cx of p;
if textpathAbsRotation=0:
db := textpathRotation + angle direction tb of p;
else:
db := textpathRotation;
fi
np := banker rotated db shifted point tb of p%{dir (dpp+db)}
elseif textpathCureSqrt=1:
np := textpathHookCoord shifted (dir(-90)*xpart point 0 of makepath penpart item);
np := np rotated textpathHookRot shifted textpathHookPoint%{dir textpathHookRot}
fi
for h=1 upto textpathStrokePrecision:
hide(
banker := (0, ypart point (h/textpathStrokePrecision) of pp - autoScale*30bp + textpathShift);
bx := (xpart point (h/textpathStrokePrecision) of pp) + startOffset;
cx := (i-1)*(lenpic + ltextpathHSpace) + bx;
dpp := angle direction (h/textpathStrokePrecision) of pp;
tb := arctime cx of p;
if textpathAbsRotation=0:
db := textpathRotation + angle direction tb of p;
else:
db := textpathRotation;
fi
)
..banker rotated db shifted point tb of p%{dir (dpp+db)}
endfor;
if odd (textpathDebug div 2):
addto pic doublepath bbox np withpen currentpen;
fi
if odd (textpathDebug div 1):
addto pic doublepath np withpen penpart item;
fi
if odd (textpathDebug div 4):
%%% Doesn't work.
% addto pic contour fullcircle scaled 3bp shifted point tb of p;
fi
endfor
fi
enddef;
%%% Internal working macro.
%%% Set text from picture variable along a path with justification j.
%%% Parameters:
%%% * a picture that contains the text that has to be transformed onto a path,
%%% * a path p the text should follow,
%%% * a numeric variable that controls justification,
%%% Return value:
%%% * a picture variable containing text following a path.
vardef textpathToPic(expr atextpic, p, j)=
save bitem, banker, k, bx, cx, tb, db;
numeric k, bx, cx, tb, db;
picture bitem;
pair banker;
%%% Calculate variables such as startOffset, autoScale, ltextpathHSpace etc.
preprocessTextPic(false, atextpic, p, j);
%%%
%%% Now iterate over all picture elements.
%%%
k := 0;
for item within textpic:
if textual item:%%% Process textual elements.
transformSimpleItem(false, item, p);
k := k + 1;
else:
message "Package textpath warning: Unsupported picture element found!";
fi
endfor
%%% Return pic.
pic
enddef;
%%% Internal working macro.
%%% Set text from picture variable along a path with justification j.
%%% Parameters:
%%% * a picture that contains the text that has to be transformed onto a path,
%%% * a path p the text should follow,
%%% * a numeric variable that controls justification,
%%% Return value:
%%% * a picture variable containing text following a path.
vardef textpathRawToPic(expr atextpic, p, j)=
save bitem, banker, bx, cx, tb, db, pp, np, dpp;
numeric bx, cx, tb, db, dpp;
picture bitem;
pair banker;
path pp, np;
%%% Calculate variables such as startOffset, autoScale, ltextpathHSpace etc.
preprocessTextPic(true, atextpic, p, j);
%%% Reset last sqrt glyph's top right bounding box corner.
textpathHookCoord := origin;
%%%
%%% Now iterate over all picture elements.
%%%
for item within textpic:
if textual item:%%% Process textual elements.
transformSimpleItem(true, item, p);
elseif stroked item:%%% Process stroked elements.
transformStrokedItem(true, item, p);
else:
message "Package textpath warning: Unsupported picture element found!";
fi
endfor
pic
enddef;