%%% textpath.mp %%% Copyright 2007 Stephan Hennig % % 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;