diff options
Diffstat (limited to 'Master/texmf-dist/doc/context/sources/general/manuals/metafun/metafun-text-lmtx.tex')
-rw-r--r-- | Master/texmf-dist/doc/context/sources/general/manuals/metafun/metafun-text-lmtx.tex | 1519 |
1 files changed, 1519 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/context/sources/general/manuals/metafun/metafun-text-lmtx.tex b/Master/texmf-dist/doc/context/sources/general/manuals/metafun/metafun-text-lmtx.tex new file mode 100644 index 00000000000..abdca3c3ad0 --- /dev/null +++ b/Master/texmf-dist/doc/context/sources/general/manuals/metafun/metafun-text-lmtx.tex @@ -0,0 +1,1519 @@ +% language=uk +% +% copyright=pragma-ade readme=readme.pdf licence=cc-by-nc-sa + +\startcomponent metafun-text + +\environment metafun-environment + +\startchapter[reference=sec:typesetting,title={Typesetting in \METAPOST}] + +\startintro + +It is said that a picture tells more than a thousand words. So you might expect +that text in graphics becomes superfluous. Out of experience we can tell you that +this is not the case. In this chapter we explore the ways to add text to +\METAPOST\ graphics, and let you choose whether or not to have it typeset by +\TEX. + +\stopintro + +\startsection[title={The process}] + +\index{text} + +The \METAPOST\ program is about graphics, not about text. Its ancestor was made +for creating fonts and in order to do so, it actually had some limited +capabilities to add text to a graphic, like coordinates to points. Now, when +you really want to add typeset text, we need something more. The traditional way +to add some text to a \METAPOST\ graphic was to use this: + +\starttyping +btex Some text to be typeset by \TEX etex +\stoptyping + +The machinery was set up in such a way that these snippets got delegated to \TEX, +and the resulting \DVI\ converted back into a a \METAPOST\ picture with objects +that refer to a font and character. Although the \METAPOST\ program could handle +that by calling out to \TEX, we never used that method. In \MKII\ we had two +flows: either direct processing, or collect all texts and process them between +runs. Because the backend (conversion to \PDF) was under \CONTEXT\ control we +could do more than usual. You can find some information in the older manuals. + +In \CONTEXT\ \MKIV\ we started using the library variant of \METAPOST. Its +integration in \LUATEX\ made it possible to do all text rendering runtime at high +speed. This change has an enormous speed gain: when this manual had about 425 +pages, on my laptop with mobile 3840QM processor, one run of this document takes +18 seconds (14.5 with \LUAJITTEX) and that includes loading a bunch of (outline) +fonts and processing some 2200 \METAPOST\ images. While writing the first version +of this manual runtime was upto 50 times slower for half the number of pages so +compared to \MKII\ we have gained a lot. + +In \MKIV\ we still have two internal runs per \METAPOST\ graphic that has text: +the first time the texts snippets are collected and afterwards handed over to +\LUA\ that tells the \TEX\ ending to process it. The dimensions are saved and +used in a second processing of the graphic. Contrary to \MKII\ the user doesn't +see these two runs: they appear as one. In \in {figure} [fig:text:mkiv] this is +illustrated. + +\startFLOWchart[mkiv] + \startFLOWcell + \name {metapost 1} + \location {1,1} + \shape {action} + \text {\METAPOST\ \type{textext 1}} + \connection [rl] {lua 1} + \stopFLOWcell + \startFLOWcell + \name {lua 1} + \location {2,1} + \shape {action} + \text {\LUA} + \connection [rl] {context 1} + \connection [bb] {metapost 2} + \stopFLOWcell + \startFLOWcell + \name {context 1} + \location {3,1} + \shape {action} + \text {\CONTEXT} + \stopFLOWcell + \startFLOWcell + \name {metapost 2} + \location {4,1} + \shape {action} + \text {\METAPOST\ \type{textext 2}} + \connection [bb] {lua 1} + \connection [rl] {backend 1} + \stopFLOWcell + \startFLOWcell + \name {backend 1} + \location {5,1} + \shape {action} + \text {backend} + \stopFLOWcell +\stopFLOWchart + +In the later versions of \MKIV\ it was possible to jump out to \TEX\ immediately +and save the second run, although there were cases where this was not possible +(reliable). In \LMTX\ we always have one run. This is summarized in \in {figure} +[fig:text:mkiv]. All this has to do with the fact that \TEX, \METAPOST\ and \LUA\ +are integrated but still kind of independent. When you expand a macro (that +triggers processing of a graphic) you end up in a call to the \METAPOST\ library +but \TEX\ is still in that macro. In \LUATEX\ and more advanced in \LUAMETATEX\ +you can sort of spawn a subprocess in \TEX\ (called local control) and indeed +process the text immediately and use the calculated dimension after that has been +done. All that \METAPOST\ needs is dimensions as it the backend that eventually +will include the text. + +\startplacefigure[title={How \TEX\ and \METAPOST\ work together (\MKIV).},reference=fig:text:mkiv] + \FLOWchart[mkiv] +\stopplacefigure + +\startFLOWchart[lmtx] + \startFLOWcell + \name {context 1} + \location {1,1} + \shape {action} + \text {\CONTEXT} + \connection [rl] {lua 1} + \stopFLOWcell + \startFLOWcell + \name {lua 1} + \location {2,1} + \shape {action} + \text {\LUA} + \connection [lr] {context 1} + \connection [rl] {metapost 1} + \stopFLOWcell + \startFLOWcell + \name {metapost 1} + \location {3,1} + \shape {action} + \text {\METAPOST\ \type{textext}} + \connection [lr] {lua 1} + \connection [rl] {backend 1} + \stopFLOWcell + \startFLOWcell + \name {backend 1} + \location {5,1} + \shape {action} + \text {backend} + \stopFLOWcell +\stopFLOWchart + +\startplacefigure[title={How \TEX\ and \METAPOST\ work together (\LMTX).},reference=fig:text:lmtx] + \FLOWchart[lmtx] +\stopplacefigure + +\stopsection + +\startsection[title={Environments}] + +\index{environments} + +The \type {textext} command is the main text handling command and it is used in many places +in this manual. It takes a string. + +In \MKII\ text was processed between runs or immediately but still by running an +independent \TEX\ run. That has some consequences when the text was to share its +properties with the main document. This means that in \MKII\ we pass +characteristics of the layout, spacing, fonts, color and optionally an +environment that a user has set up. This is mentioned in the older manual but no +longer relevant in \MKIV\ and \LMTX. Because we handle the text runtime we +automatically use the same style and document properties. + +This means that in code like the following, a Palatino font will be used because +that is what we use in this document. + +\starttyping +\startMPcode +draw btex Meta is a female lion! etex xysized (TextWidth,TextHeight) ; +\stopMPcode +\stoptyping + +But even when we still support \type {btex} we strongly advise users to use this: + +\starttyping +\startMPcode +draw textext("Meta is a female lion!") xysized (TextWidth,TextHeight) ; +\stopMPcode +\stoptyping + +In this example as well in the next one you see that we access \type {TextWidth} +directly. In older examples in the \CONTEXT\ distribution you will notice that we +serialize \TEX\ registers like \type {\textwidth}. This is still valid but less +preferable. Here \type {TextWidth} is actually a macro that calls out to \LUA\ +from where we fetch the value of \type {\textwidth} and pipe it back to +\METAPOST\ in a way that makes it look like we have a variable. + +\startbuffer[lioncode] +\startMPcode +numeric w, h ; w := TextWidth ; h := w/2 ; + +picture p ; p := textext.urt("Meta is a female lion!") xysized (w,h) ; +picture q ; q := textext.urt("Meta is a female lion!") xysized (w,h) ; + +path b ; b := boundingbox p ; draw p withcolor "darkyellow" ; + +draw textextanchor(p) withpen pencircle scaled 5mm withcolor "darkblue" ; + +for i = (.28w,.90h), (.85w,.90h), (w,.05h) : + picture r ; r := q ; + path s ; s := (fullsquare xscaled .05w yscaled .4h) shifted i ; + clip r to s ; + draw r withcolor "darkred"; +endfor ; + +setbounds currentpicture to b ; +\stopMPcode +\stopbuffer + +\typebuffer[lioncode] + +\in {Figure} [lionclip] shows the previous sentence in a slightly different look. +You may consider coloring the dots to be an exercise in clipping. + +\startplacefigure[title={An example of clipping.},reference=lionclip] + \getbuffer[lioncode] +\stopplacefigure + +In this example we show that the origin of the text. By default it gets centered +but the \type {urt} suffix moves it. Here is another example of showing the +origin: + +\startbuffer +\startMPcode +picture p ; p := thetextext.top("foo",(10,1)); +picture q ; q := thetextext.lft("foo",(1,10)); + +draw textextanchor(p) withpen pencircle scaled 2mm withcolor "darkred" ; +draw textextanchor(q) withpen pencircle scaled 1mm withcolor "white" ; + +draw p; +draw q; +\stopMPcode +\stopbuffer + +\typebuffer + +It gives the following: + +\startlinecorrection[blank] +\getbuffer +\stoplinecorrection + +\stopsection + +\startsection[title={Labels}] + +\index{labels} + +In \METAPOST\ you can use the \type {label} macro to position text at certain +points. + +\starttyping +label("x", origin) ; +\stoptyping + +The font and scale are determined by two variables, \type {defaultfont} and \type +{defaultscale}, the former expecting the name of a font in the form of a string, +the latter expecting a numeric to be used in the scaling of the font. Should you +choose not to set these yourself, they default to \type {"Mono"} and \type +{1.0}, respectively. However, you can change the defaults as follows: + +\starttyping +defaultfont := "texgyrepagella-regular*default" ; +defaultscale := 1.2 ; +\stoptyping + +These settings selects Pagella at about 12pt. You can also set these variables +to \CONTEXT\ related values. For \CONTEXT\ graphics they are set to: + +\starttyping +defaultfont := "\truefontname{Regular}*default" ; +defaultscale := \the\bodyfontsize/10 ; +\stoptyping + +This means that they will adapt themselves to the current body font (in this +document we get \truefontname {Regular}) and the current size of the bodyfont +(here \the\bodyfontsize/10). + +Normally you can stick to the default and forget about these details. More important +is how you anchor labels. This is best shown with some examples: + +\startbuffer +\startMPcode + path p ; p := fullcircle scaled 3cm ; + + drawarrow p ; + + dotlabel ("test",point 0 of p) withcolor red ; + dotlabel.rt ("rt", point 1 of p) ; + dotlabel.urt ("urt", point 2 of p) ; + dotlabel.top ("top", point 3 of p) ; + dotlabel.ulft("ulft",point 4 of p) ; + dotlabel.lft ("lft", point 5 of p) ; + dotlabel.llft("llft",point 6 of p) ; + dotlabel.bot ("bot", point 7 of p) ; + dotlabel.lrt ("lrt", point 8 of p) ; +\stopMPcode +\stopbuffer + +\typebuffer \startlinecorrection[blank] \getbuffer \stoplinecorrection + +\startbuffer +\startMPcode + path p ; p := fullcircle scaled 3cm ; + + drawarrow p ; + + dotlabel ("test",p) ; + dotlabel.rt ("rt", p) ; + dotlabel.urt ("urt", p) ; + dotlabel.top ("top", p) ; + dotlabel.ulft("ulft",p) ; + dotlabel.lft ("lft", p) ; + dotlabel.llft("llft",p) ; + dotlabel.bot ("bot", p) ; + dotlabel.lrt ("lrt", p) ; +\stopMPcode +\stopbuffer + +\typebuffer \startlinecorrection[blank] \getbuffer \stoplinecorrection + +\startbuffer +\startMPcode + p := (origin..right..up..left) scaled 3cm ; + + drawarrow p ; + + label ("test",p) ; + label.rt ("rt", p) ; + label.urt ("urt", p) ; + label.top ("top", p) ; + label.ulft("ulft",p) ; + label.lft ("lft", p) ; + label.llft("llft",p) ; + label.bot ("bot", p) ; + label.lrt ("lrt", p) ; +\stopMPcode +\stopbuffer + +\typebuffer \startlinecorrection[blank] \getbuffer \stoplinecorrection + +\startbuffer +\startMPcode + picture p ; p := image(drawarrow (origin..right..up..left) scaled 3cm ;) ; + + draw p ; + draw bbox p dashed evenly ; + + label ("test",p) ; + label.rt ("rt", p) ; + label.urt ("urt", p) ; + label.top ("top", p) ; + label.ulft("ulft",p) ; + label.lft ("lft", p) ; + label.llft("llft",p) ; + label.bot ("bot", p) ; + label.lrt ("lrt", p) ; +\stopMPcode +\stopbuffer + +\typebuffer \startlinecorrection[blank] \getbuffer \stoplinecorrection + +\startbuffer +\startMPcode + picture p ; p := image(drawarrow (origin..right..up..left) scaled 3cm ;) ; + + draw p ; + draw bbox p dashed evenly ; + + draw textext ("test") shifted theoffset (p) ; + draw textext.lft ("rt") shifted theoffset.rt (p) ; + draw textext.llft("urt") shifted theoffset.urt (p) ; + draw textext.bot ("top") shifted theoffset.top (p) ; + draw textext.lrt ("ulft") shifted theoffset.ulft(p) ; + draw textext.rt ("lft") shifted theoffset.lft (p) ; + draw textext.urt ("llft") shifted theoffset.llft(p) ; + draw textext.top ("bot") shifted theoffset.bot (p) ; + draw textext.ulft("lrt") shifted theoffset.lrt (p) ; +\stopMPcode +\stopbuffer + +\typebuffer \startlinecorrection[blank] \getbuffer \stoplinecorrection + +\stopsection + +\startsection[title={Text along a path}] + +\index{text} + +In the next example we will use a special mechanism for building graphics step by +step. The advantage of this method is that we can do intermediate calculations in +\TEX. Our objective is to write a macro that draws text along a circular path. +While doing so we want to achieve the following: + +\startitemize[packed] + \startitem + the text should be properly kerned, i.e.\ the spacing between characters + should be optimal, + \stopitem + \startitem + the position on the circle should vary, and + \stopitem + \startitem + the radius of the circle should vary. + \stopitem +\stopitemize + +This code below is a bit rusted but it illustrates the principles. In \MKIV\ and +\LMTX\ we have typesetting along a curve built in so we no longer show the \TEX\ +code that integrates it. This implementation is not the most straightforward one, +but by doing it step by step, at least we see what is involved. + +We let the bodyfont match the font used in this document, and define \type +{RotFont} to be the regular typeface, the one you are reading right now, but +bold. + +\startbuffer +\definefont[RotFont][RegularBold*default] +\stopbuffer + +\typebuffer \getbuffer + +Since \METAPOST\ is unaware of kerning, we have to use \TEX\ to keep track of the +positions. We will split the text into tokens (often characters) and store the +result in an array of pictures (\type {pic}). We will also store the accumulated +width in an array (\type {len}). The number of characters is stored in~\type {n}. +In a few paragraphs we will see why the other arrays are needed. + +While defining the graphic, we need \TEX\ to do some calculations. Therefore, we +will use \type {\startMPdrawing} to stepwise construct the definition. The basic +pattern we will follow is: + +\starttyping +\resetMPdrawing +\startMPdrawing + metapost code +\stopMPdrawing +tex code +\startMPdrawing + metapost code +\stopMPdrawing +\MPdrawingdonetrue +\getMPdrawing +\stoptyping + +In the process, we will use a few variables. We will store the individual +characters of the text in the variable \type {pic}, its width in \type {wid} and +the length of the string so far in \type {len}. Later we will use the \type {pos} +array to store the position where a character ends up. The variable \type {n} +holds the number of tokens. + +\startbuffer[init] +\resetMPdrawing +\startMPdrawing + picture pic[] ; + numeric wid[], len[], pos[], n ; + wid[0] := len[0] := pos[0] := n := 0 ; +\stopMPdrawing +\stopbuffer + +\typebuffer[init] + +We also started fresh by resetting the drawing. From now on, each start command +will add some more to this graphic. The next macro is responsible for collecting +the data. Each element is passed on to \TEX, using the \type {btex} construct. +So, \METAPOST\ itself will call \TEX ! + +\startbuffer[toks] +\def\whatever#1% + {\appendtoks#1\to\MPtoks + \setbox\MPbox=\hbox{\bfd\the\MPtoks}% + \startMPdrawing + n := n + 1 ; len[n] := \the\wd\MPbox ; + \stopMPdrawing + \startMPdrawing[-] + pic[n] := textext("\bfd\setstrut\strut#1") ; + pic[n] := pic[n] shifted - llcorner pic[n] ; + \stopMPdrawing} + +\handletokens MetaPost is Fun!\with\whatever +\stopbuffer + +\typebuffer[toks] + +We use the low level \CONTEXT\ macro \type {\appendtoks} to extend the token list +\type {\MPtoks}. The \type {\handletokens} macro passes each token (character) of +\typ {MetaPost is Fun!} to the macro \type {\whatever}. The tokens are appended +to the token register \type {\MPtoks} (already defined). Then we typeset the +content of \type {\MPtoks} in \type {\MPbox} (also already defined). The width of +the box is passed to \METAPOST\ and stored in \type {len}. + +By default the content of the drawing is expanded, which means that the macro is +replaced by its current meaning, so the current width ends up in the \METAPOST\ +file. The next part of the drawing, starting with \type {btex}, puts the token in +a picture. This time we don't expand the drawing, since we want to pass font +information. Here, the \type {[-]} suppresses expansion of \typ {btex \bfd #1 +etex}. The process is iterated by \type {\handletokens} for each character of the +text \typ {MetaPost is Fun!}. + +Before we typeset the text, now available in pieces in \type {pic}, in a circle, +we will first demonstrate what they look like. You may like to take a look at the +file \type {mpgraph.mp} to see what is passed to \METAPOST. + +\startbuffer[test] +\startMPdrawing + pair len ; len := origin ; + for i=1 upto n : + draw pic[i] shifted len ; + draw boundingbox pic[i] shifted len + withpen pencircle scaled .25pt withcolor red ; + len := len+(xpart urcorner pic[i]-xpart llcorner pic[i],0) ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[test] + +\startbuffer[show] +\MPdrawingdonetrue\getMPdrawing +\stopbuffer + +We can call up this drawing with \type {\getMPdrawing}, but first we inform the +compiler that our \METAPOST\ drawing is completed. + +\typebuffer[show] + +This results in: + +\startlinecorrection[blank] +\getbuffer[init,toks,test,show] +\stoplinecorrection + +Compare this text with the text as typeset by \TEX: + +\blank \start \bfd MetaPost is Fun!\par \stop \blank + +and you will see that the text produced by \METAPOST\ is not properly kerned. +When putting characters after each other, \TEX\ uses the information available in +the font, to optimize the spacing between characters, while \METAPOST\ looks at +characters as separate entities. But, since we have stored the optimal spacing in +\type {len}, we can let \METAPOST\ do a better job. Let's first calculate the +correction needed. + +\startbuffer[kern] +\startMPdrawing + for i=1 upto n : + wid[i] := abs(xpart urcorner pic[i] - xpart llcorner pic[i]) ; + pos[i] := len[i]-wid[i] ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[kern] + +This compares well to the text as typeset by \TEX: + +\blank \start \bfd MetaPost is Fun!\par \stop \blank + +We can now use the values in \type {pos} to position the pictures according to +what \TEX\ considered to be the best (relative) position. + +\startbuffer[test] +\startMPdrawing + for i=1 upto n : + draw pic[i] shifted (pos[i],0) ; + draw boundingbox pic[i] shifted (pos[i],0) + withpen pencircle scaled .25pt withcolor red ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[test] + +That this correction is adequate, is demonstrated in the next graphic. If you +look closely, you will see that for instance the \quote {o} is moved to the left, +under the capital \quote {P}. + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,test,show] +\stoplinecorrection + +When we want to position the pictures along a circle, we need to apply some +rotations, especially because we want to go clockwise. Since we don't want to use +\quote {complicated} math or more advanced \METAPOST\ code yet, we will do it in +steps. + +\startbuffer[swap] +\startMPdrawing + for i=1 upto n: + pic[i] := pic[i] rotatedaround(origin,-270) ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[swap] + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,swap,test,show] +\stoplinecorrection + +\startbuffer[cent] +\startMPdrawing + for i=1 upto n : + pic[i] := pic[i] + shifted (0,ypart -.5[ulcorner pic[i],llcorner pic[i]]) ; + endfor ; +\stopMPdrawing +\stopbuffer + +We will now center the pictures around the baseline. Centering comes down to +shifting over half the height of the picture. This can be expressed by: + +\starttyping +ypart -.5[ulcorner pic[i],llcorner pic[i]] +\stoptyping + +but different ways of calculating the distance are possible +too. + +\typebuffer[cent] + +So, now we have: + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,swap,cent,test,show] +\stoplinecorrection + +When we typeset on a (half) circle, we should map the actual length onto a +partial circle. We denote the radius with an~\type {r} and shift the pictures to +the left. + +\startbuffer[shif] +\startMPdrawing + numeric r ; r := len[n]/pi ; + for i=1 upto n : + pic[i] := pic[i] shifted (-r,0) ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[shif] + +You can now use the following code to test the current state of the pictures. Of +course this code should not end up in the final definitions. + +\startbuffer[test] +\startMPdrawing + draw origin + withpen pencircle scaled 5pt withcolor red ; + for i=1 upto n : + draw pic[i] ; + draw boundingbox pic[i] + withpen pencircle scaled .25pt withcolor red ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[test] + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,swap,cent,shif,test,show] +\stoplinecorrection + +Later we will write a compact, efficient macro to take care of rotation. However, +for the moment, so as not to overwhelm you with complicated code, we will rotate +each individual picture with the following code fragment. + +\startbuffer[rots] +\startMPdrawing + numeric delta, extra, radius, rot[] ; + + delta := extra := radius := 0 ; + + for i=1 upto n : + rot[i] := extra+delta-((pos[i]+.5wid[i])/len[n])*(180+2delta) ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[rots] + +Here we introduce a few variables that we can use later to tune the result a bit. +With \type {delta}, the space between the characters can be increased, while +\type {extra} rotates the whole string around the origin. The \type {radius} +variable can be used to increase the distance to the origin. Without these +variables, the assignment would have been: + +\starttyping +rot[i] := ((pos[i]+.5wid[i])/len[n])*180 ; +\stoptyping + +Placing the pictures is now rather easy: + +\startbuffer[done] +\startMPdrawing + for i=1 upto n : + draw pic[i] shifted (-radius,0) rotatedaround(origin,rot[i]) ; + endfor ; +\stopMPdrawing +\stopbuffer + +\typebuffer[done] + +The pictures are now positioned on half a circle, properly kerned. + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,swap,cent,shif,rots,done,show] +\stoplinecorrection + +A bit more insight is given in the next picture: + +\startbuffer[test] +\startMPdrawing + def moved(expr i) = + shifted (-radius,0) rotatedaround(origin,rot[i]) + enddef ; + pickup pencircle scaled .5pt ; + for i=1 upto n : + draw pic[i] moved(i) ; + draw boundingbox pic[i] moved(i) withcolor red ; + draw origin -- center pic[i] moved(i) withcolor green ; + endfor ; + draw tcircle scaled 2r withcolor blue ; +\stopMPdrawing +\stopbuffer + +\startlinecorrection[blank] +\getbuffer[init,toks,kern,swap,cent,shif,rots,test,show] +\stoplinecorrection + +This was defined as follows. The path variable \type {tcycle} is predefined to +the top half of a fullcircle. + +\typebuffer[test] + +We will now package all of this into a nice, efficient macro, using, of course, +the predefined scratch registers \type {\MPtoks} and \type {\MPbox}. First we +define the token processor. Note again the expansion inhibition switch \type +{[-]}. This code is presented as an example of how these mechanissm evolved in +\CONTEXT. + +\startbuffer +\def\processrotationtoken#1% + {\appendtoks#1\to\MPtoks + \setbox\MPbox=\hbox{\RotFont\the\MPtoks}% + \startMPdrawing + n := n + 1 ; len[n] := \the\wd\MPbox ; + \stopMPdrawing + \startMPdrawing[-] + pic[n] := textext("\RotFont\setstrut\strut#1") ; + pic[n] := pic[n] shifted - llcorner pic[n] ; + \stopMPdrawing} +\stopbuffer + +\typebuffer + +\getbuffer + +The main macro is a bit more complicated but by using a few scratch numerics, we +can keep it readable. + +\startbuffer +\def\rotatetokens#1#2#3#4% delta extra radius tokens + {\vbox\bgroup + \MPtoks\emptytoks + \resetMPdrawing + \startMPdrawing + picture pic[] ; + numeric wid, len[], rot ; + numeric delta, extra, radius, n, r ; + len[0] := n := 0 ; + delta := #1 ; extra := #2 ; radius := #3 ; + \stopMPdrawing + \handletokens#4\with\processrotationtoken + \startMPdrawing + r := len[n]/pi ; + for i=1 upto n : + wid := abs(xpart lrcorner pic[i] - + xpart llcorner pic[i]) ; + rot := extra + delta - + ((len[i]-.5wid)/len[n]) * (180+2delta) ; + draw pic[i] + rotatedaround (origin,-270) shifted (-r-radius, + ypart -.5[ulcorner pic[i], llcorner pic[i]]) + rotatedaround (origin,rot) ; + endfor ; + \stopMPdrawing + \MPdrawingdonetrue + \getMPdrawing + \resetMPdrawing + \egroup} +\stopbuffer + +\typebuffer + +\getbuffer + +\startbuffer +\startcombination[3*1] + {\rotatetokens {0} {0}{0}{Does it work ok?}} {A} + {\rotatetokens{20} {0}{0}{Does it work ok?}} {B} + {\rotatetokens{20}{30}{0}{Does it work ok?}} {C} +\stopcombination +\stopbuffer + +We can use this macro as follows: + +\typebuffer + +\startlinecorrection[blank] +\getbuffer +\stoplinecorrection + +The previous macro is not really an example of generalization, but we used it for +demonstrating how to build graphics in a stepwise way. If you put the steps in +buffers, you can even combine steps and replace them at will. This is how we made +the previous step by step examples: We put each sub||graphic in a buffer and then +called the ones we wanted. + +Next we show how it is done today in \LMTX. Instead of wrapping it in a macro we +just do all in \METAPOST. Watch how we use the key|/|value interface: + +\startbuffer +\startMPcode + draw lmt_followtext [ + path = halfcircle rotated 45 scaled 4cm, + text = "Does it work ok?", + spread = true, + trace = true, + ] ; + draw lmt_followtext [ + path = halfcircle rotated -30 scaled 3cm, + text = "Does it work ok?", + spread = false, + trace = true, + ] shifted (4cm,0) ; + draw lmt_followtext [ + path = reverse halfcircle scaled 2cm, + text = "Does it work ok?", + spread = true, + trace = true, + ] shifted (8cm,0) ; +\stopMPcode +\stopMPcode +\stopbuffer + +\typebuffer + +\startlinecorrection[blank] +\getbuffer +\stoplinecorrection + +\stopsection + +\startsection[title={Using shapes}] + +Sometimes, others may say oftentimes, we are in need for some fancy typesetting. +If we want to typeset a paragraph of text in a non standard shape, like a circle, +we have to fall back on \type {\parshape}. Unfortunately, \TEX\ is not that +strong in providing the specifications of more complicated shapes, unless you are +willing to do some complicated arithmetic \TEX. Given that \METAPOST\ knows how +to deal with shapes, the question is: \quotation {Can \METAPOST\ be of help?} + +In the process of finding out how to deal with this, we first define a simple +path. Because we are going to replace pieces of code, we will compose the graphic +from components. First, we create the path. + +\startbuffer +\startuseMPgraphic{text path} + path p ; p := ((0,1)..(-1,0)..(1,0)--cycle) scaled 65pt ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +This shape is not that beautiful, but it has a few characteristics that will help +us to identify bordercases. + +\startbuffer +\startuseMPgraphic{text draw} + drawarrow p withpen pencircle scaled 1pt withcolor red ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Now we use \CONTEXT's \type {\includeMPgraphic} command to build our graphic from +the previously defined components. + +\startbuffer +\startuseMPgraphic{text} + \includeMPgraphic{text path} + \includeMPgraphic{text draw} +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +When called with \type {\useMPgraphic{text}}, we get: + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +For the moment we start the path at $(x=0,y>0)$, but later using more complicated +macros, we will see that we can use arbitrary paths. + +We are going to split the path in two, and will use the points that make up the +bounding box as calcutated by \METAPOST. The next graphic shows one of these +points, the lower left corner, available as point \typ {llcorner p}. + +\startbuffer +\startuseMPgraphic{text draw} + draw p withpen pencircle scaled 3pt withcolor red ; + draw boundingbox p withpen pencircle scaled 1pt ; + draw llcorner p withpen pencircle scaled 5pt ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +The five points that \METAPOST\ can report for each path or picture are: + +\starttabulate[|Tl|l|] +\NC llcorner \NC lower left corner \NC \NR +\NC lrcorner \NC lower right corner \NC \NR +\NC urcorner \NC upper right corner \NC \NR +\NC ulcorner \NC upper left corner \NC \NR +\NC center \NC intersection of the diagonals \NC \NR +\stoptabulate + +If we want to typeset text inside this circle, we need to know where a line +starts and ends. Given that lines are horizontal and straight, we therefore need +to calculate the intersection points of the lines and the path. As a first step, +we calculate the top and bottom of the path and after that we split off the left +and right path. + +\startbuffer +\startuseMPgraphic{text split} + pair t, b ; path l, r ; + + t := (ulcorner p -- urcorner p) intersectionpoint p ; + b := (llcorner p -- lrcorner p) intersectionpoint p ; + + l := p cutbefore t ; l := l cutafter b ; + r := p cutbefore b ; r := r cutafter t ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +The \type {intersectionpoint} macro returns the point where two paths cross. If +the paths don't cross, an error is reported, when the paths cross more times, +just one point is returned. The \type {cutafter} and \type {cutbefore} commands +do as their names say and return a path. + +In the \type {text split} code fragment, \type {t} and \type {b} are the top +points of the main path, while \type {l} and \type {r} become the left and right +half of path \type {p}. + +We now draw the original path using a thick pen and both halves with a thinner +pen on top of the original. The arrows show the direction. + +\startbuffer +\startuseMPgraphic{text draw} + draw p withpen pencircle scaled 3pt withcolor red ; + drawarrow l withpen pencircle scaled 1pt withcolor green ; + drawarrow r withpen pencircle scaled 1pt withcolor blue ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +We use \type {\includeMPgraphic} to assemble the components: + +\startbuffer +\startuseMPgraphic{text} + \includeMPgraphic{text path} + \includeMPgraphic{text split} + \includeMPgraphic{text draw} +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +This graphic is typeset with \type {\useMPgraphic{text}}: + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +Before we are going to use them, we define some variables that specify the text. +We use a baseline distance of 8~points. The part of the line above the baseline +is 7.2~points, while the (maximum) depth is 2.8~points. These ratios are the ones +we use in \CONTEXT. Because we don't want the text to touch the circle so we +define an offset too. + +\startbuffer +\startuseMPgraphic{text vars} + MyOffset := LineHeight/2 ; + MyTopSkip := StrutHeight ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +We more or less achieve the offset by scaling the path. In doing so, we use the +width and height, which we call \type {hsize} and \type {vsize}, thereby +conforming to the \TEX\ naming scheme. + +First we calculate both dimensions from the bounding box of the path. Next we +down scale the path to compensate for the offset. When done, we recalculate the +dimensions. + +\startbuffer +\startuseMPgraphic{text move} + pair t, b ; path q, l, r ; + + hsize := xpart lrcorner p - xpart llcorner p ; + vsize := ypart urcorner p - ypart lrcorner p ; + + q := p xscaled ((hsize-2MyOffset)/hsize) + yscaled ((vsize-2MyOffset)/vsize) ; + + hsize := xpart lrcorner q - xpart llcorner q ; + vsize := ypart urcorner q - ypart lrcorner q ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +\startbuffer +\startuseMPgraphic{text split} + t := (ulcorner q -- urcorner q) intersectionpoint q ; + b := (llcorner q -- lrcorner q) intersectionpoint q ; + + l := q cutbefore t ; l := l cutafter b ; + r := q cutbefore b ; r := r cutafter t ; +\stopuseMPgraphic +\stopbuffer + +We adapt the \type {text split} code to use the reduced path +instead of the original. + +\typebuffer \getbuffer + +\startbuffer +\startuseMPgraphic{text draw} + drawarrow p withpen pencircle scaled 1pt withcolor red ; + draw t withpen pencircle scaled 2pt ; + draw b withpen pencircle scaled 2pt ; + drawarrow l withpen pencircle scaled 1pt withcolor green ; + drawarrow r withpen pencircle scaled 1pt withcolor blue ; +\stopuseMPgraphic +\stopbuffer + +In order to test what we have reached so far, we draw the original path, the left +and right part of the reduced path, and both the top and bottom point. + +\typebuffer \getbuffer + +Again we use \type {\includeMPgraphic} to combine the +components into a graphic. + +\startbuffer +\startuseMPgraphic{text} + \includeMPgraphic{text path} \includeMPgraphic{text vars} + \includeMPgraphic{text move} \includeMPgraphic{text split} + \includeMPgraphic{text draw} +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Then we use \type {\useMPgraphic{text}} to call up the picture. + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +The offset is not optimal. Note the funny gap at the top. We could try to fix +this, but there is a better way to optimize both paths. + +We lower the top edge of \type {q}'s bounding box by \type {MyTopSkip}, then cut +any part of the left and right pieces of \type {q} that lie above it. Similarly, +we raise the bottom edge and cut off the pieces that fall below this line. + +\startbuffer +\startuseMPgraphic{text cutoff} + path tt, bb ; + + tt := (ulcorner q -- urcorner q) shifted (0,-MyTopSkip) ; + bb := (llcorner q -- lrcorner q) shifted (0,StrutDepth) ; + + l := l cutbefore (l intersectionpoint tt) ; + l := l cutafter (l intersectionpoint bb) ; + r := r cutbefore (r intersectionpoint bb) ; + r := r cutafter (r intersectionpoint tt) ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Because we use \type {\includeMPgraphic} to construct the graphic, we can +redefine \type {text draw} to show the result of this effort. + +\startbuffer +\startuseMPgraphic{text draw} + drawarrow p withpen pencircle scaled 1pt withcolor red ; + drawarrow l withpen pencircle scaled 1pt withcolor green ; + drawarrow r withpen pencircle scaled 1pt withcolor blue ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +The \type {text} graphic now becomes: + +\startbuffer +\startuseMPgraphic{text} + \includeMPgraphic{text path} \includeMPgraphic{text vars} + \includeMPgraphic{text move} \includeMPgraphic{text split} + \includeMPgraphic{text cutoff} \includeMPgraphic{text draw} +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Or, as graphic: + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +We are now ready for an attempt to calculate the shape of the text. For each +line, we have to calculate the left and right intersection points, and since a +line has a height and depth, we have to determine which part touches first. + +\startbuffer +\startuseMPgraphic{text calc} + vardef found_point (expr lin, pat, sig) = + pair a, b ; + a := pat intersection_point (lin shifted (0,StrutHeight)) ; + if intersection_found : + a := a shifted (0,-StrutHeight) ; + else : + a := pat intersection_point lin ; + fi ; + b := pat intersection_point (lin shifted (0,-StrutDepth)) ; + if intersection_found : + if sig : + if xpart b > xpart a : a := b shifted (0,StrutDepth) fi ; + else : + if xpart b < xpart a : a := b shifted (0,StrutDepth) fi ; + fi ; + fi ; + a + enddef ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Instead of using \METAPOST's \type {intersectionpoint} macro, we use one that +comes with \CONTEXT. That way we don't get an error message when no point is +found, and can use a boolean flag to take further action. Since we use a \type +{vardef}, all calculations are hidden and the~\type {a} at the end is returned, +so that we can use this macro in an assignment. The \type {sig} variable is used +to distinguish between the beginning and end of a line (the left and right +subpath). + +\startbuffer +\startuseMPgraphic{text step} + path line; pair lll, rrr ; + + for i=MyTopSkip step LineHeight until vsize : + + line := (ulcorner q -- urcorner q) shifted (0,-i) ; + + lll := found_point(line,l,true ) ; + rrr := found_point(line,r,false) ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Here we divide the available space in lines. The first line starts at \type +{StrutHeight} from the top. + +We can now finish our graphic by visualizing the lines. Both the height and depth +of the lines are shown. + +\startbuffer +\startuseMPgraphic{text line} + fill (lll--rrr--rrr shifted (0,StrutHeight)--lll + shifted (0,StrutHeight)--cycle) withcolor .5white ; + fill (lll--rrr--rrr shifted (0,-StrutDepth)--lll + shifted (0,-StrutDepth)--cycle) withcolor .7white ; + draw lll withpen pencircle scaled 2pt ; + draw rrr withpen pencircle scaled 2pt ; + draw (lll--rrr) withpen pencircle scaled .5pt ; +\stopuseMPgraphic + +\startuseMPgraphic{text done} + endfor ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +The result is still a bit disappointing. + +\startbuffer +\startuseMPgraphic{text} + \includeMPgraphic{text path} \includeMPgraphic{text vars} + \includeMPgraphic{text move} \includeMPgraphic{text split} + \includeMPgraphic{text cutoff} \includeMPgraphic{text draw} + \includeMPgraphic{text calc} \includeMPgraphic{text step} + \includeMPgraphic{text line} \includeMPgraphic{text done} +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +In order to catch the overflow at the bottom, we need to change the \type +{for}||loop a bit, so that the number of lines does not exceed the available +space. The test that surrounds the assignment of \type {vvsize} makes sure that +we get better results when we (on purpose) take a smaller height. + +\startbuffer +\startuseMPgraphic{text step} + path line; pair lll, rrr ; numeric vvsize ; + + if (StrutHeight+StrutDepth<LineHeight) : + vvsize := vsize ; + else : + vvsize := (vsize div LineHeight) * LineHeight ; + fi ; + + for i=MyTopSkip step LineHeight until vvsize : + + line := (ulcorner q -- urcorner q) shifted (0,-i) ; + + lll := found_point(line,l,true ) ; + rrr := found_point(line,r,false) ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +We can manipulate the heigth and depth of the lines to give different (and maybe +better) results. + +\startbuffer +\startuseMPgraphic{text vars} +MyOffset := .5LineHeight ; +MyTopSkip := StrutHeight ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +\startlinecorrection[blank] +\useMPgraphic{text} +\stoplinecorrection + +Now that we know a bit how this works we demonstrate the implementation in \LMTX. +Where in \MKIV\ and \MKII\ we let \METAPOST\ pass the dimensions to \TEX\ via a +temporary file in \LMTX\ we communicate more directly. You can take a look at the +source code if you want to know how that works. + +We start by defining four shapes. They are not really beautiful, but they +demonstrate what happens in border cases. For instance, too small first lines are +ignored. First we define a circle. Watch how the dimensions are set in the +graphic. The arguments passed to \type {lmt_parshape} are: path, an offset, an +additional horizontal and vertical displacement, the baseline distance, the +height and depth of the line, and the height of the first line (aka topskip). The +height and depth of a line are often called strut height and depth, with a strut +being an invisible character with maximum dimensions. Variables like \type +{StrutHeight} refer to values at the \TEX\ end. We play safe and group so that we +can save variables. + +\startbuffer +\startuseMPgraphic{shapetest-1} + begingroup ; + save p ; path p ; p := fullcircle scaled 6cm ; + lmt_parshape [ + path = p, + offset = BodyFontSize/2, + dx = 0, % default + dy = 0, % default + lineheight = LineHeight, % default + strutheight = StrutHeight, % default + strutdepth = StrutDepth, % default + topskip = StrutHeight, % default + ] ; + draw p withpen pencircle scaled 1pt ; + endgroup ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +The second shape is a diamond. This is a rather useless shape, unless the text +suits the small lines at the top and bottom. + +\startbuffer +\startuseMPgraphic{shapetest-2} + begingroup ; + save p ; path p ; p := fullsquare rotated 45 scaled 5cm ; + lmt_parshape [ + path = p, + offset = BodyFontSize/2, + trace = true, + ] ; + draw p withpen pencircle scaled 1pt ; + endgroup ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +The third and fourth shape demonstrate that providing a suitable offset is not +always trivial. + +\startbuffer +\startuseMPgraphic{shapetest-3} + begingroup ; + save w, h, p ; path p ; w := h := 6cm ; + p := (.5w,h) -- ( 0, h) -- (0,0) -- (w,0) & + ( w,0) .. (.75w,.5h) .. (w,h) & (w,h) -- cycle ; + lmt_parshape [ + path = p, + offset = BodyFontSize/2, + ] ; + draw p withpen pencircle scaled 1pt ; + endgroup ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Contrary to the first three shapes, here we use a different path for the +calculations and the drawing. Watch carefully! If, instead of an offset, we pass +a path, \METAPOST\ is able to calculate the right dimensions and offsets. This is +needed, since we need these later on. + +\startbuffer +\startuseMPgraphic{shapetest-4} + begingroup ; + save d, p, q, shape ; path p, q ; d := BodyFontSize/2; + vardef shape(expr w, h, o) = + (o,o) -- (w-o,o) & (w-o,o) .. (.75w-o,.5h) .. + (w-2o,h-o) & (w-2o,h-o) -- (o,h-o) -- cycle + enddef ; + p := shape(6cm, 6cm, d) ; + q := shape(6cm, 6cm, 0) ; + lmt_parshape [ + path = p, + offsetpath = q, + dx = d, + dy = d, + trace = true, + ] ; + draw q withpen pencircle scaled 1pt ; + endgroup ; +\stopuseMPgraphic +\stopbuffer + +\typebuffer \getbuffer + +Since we also want these graphics as backgrounds, we define them as overlays. If +you don't want to show the graphic, you may omit this step. + +\startbuffer +\defineoverlay[shapetest-1][\useMPgraphic{shapetest-1}] +\defineoverlay[shapetest-2][\useMPgraphic{shapetest-2}] +\defineoverlay[shapetest-3][\useMPgraphic{shapetest-3}] +\defineoverlay[shapetest-4][\useMPgraphic{shapetest-4}] +\stopbuffer + +\typebuffer \getbuffer + +As text, we use a quote from Douglas R.~Hofstadter's book \quotation {Metamagical +Themas, Questing for the Essence of Mind and Pattern}. Watch how we pass a list +of shapes. + +\startbuffer[text] +\startshapetext[shapetest-1,shapetest-2,shapetest-3,shapetest-4] + \forgetall % as it says + \setupalign[verytolerant,stretch,normal]% + \samplefile{douglas}% Douglas R. Hofstadter +\stopshapetext +\stopbuffer + +\typebuffer[text] + +Finally we combine text and shapes. Since we also want a background, we use \type +{\framed}. The normal result is shown in \in {figure} [fig:shapes]. + +\startbuffer[shapes] +\startbuffer +\startcombination[2*2] + {\framed[offset=overlay,frame=off,background=shapetest-1]{\getshapetext}} {shapetest-1} + {\framed[offset=overlay,frame=off,background=shapetest-2]{\getshapetext}} {shapetest-2} + {\framed[offset=overlay,frame=off,background=shapetest-3]{\getshapetext}} {shapetest-3} + {\framed[offset=overlay,frame=off,background=shapetest-4]{\getshapetext}} {shapetest-4} +\stopcombination +\stopbuffer +\stopbuffer + +\typebuffer[shapes] + +\getbuffer[shapes] + +By using a buffer we keep \type {\placefigure} readable. You might have noticed +that for two shapes we turned on tracing. + +\startbuffer[a] +\placefigure + [here][fig:shapes] + {A continuous text, typeset in a non||standard shape, + spread over four areas, and right aligned.} + {\getbuffer} +\stopbuffer + +\startbuffer[b] +\placefigure + [here][fig:shapes] + {A continuous text, typeset in a non||standard shape, + spread over four areas.} + {\scale[factor=max,height=.9\textheight]{\getbuffer}} +\stopbuffer + +\typebuffer[a] + +\doifmodeelse{screen}{\getbuffer[text,b]}{\getbuffer[text,a]} + +\blank + +We can combine all those tricks, although the input is somewhat fuzzy. First we +define a quote typeset in a circular paragraph shape where we use the defaults. + +\startbuffer[shape] +\startuseMPgraphic{center} + lmt_parshape [ + path = fullcircle scaled 8cm, + ] +\stopuseMPgraphic + +\startshapetext[center] + \samplefile{douglas}% +\stopshapetext + +\defineoverlay[center][\useMPgraphic{center}] +\stopbuffer + +\typebuffer[shape] + +We will surround this text with a circular line, that we define as follows. By +using a buffer we keep things organized. + +\startbuffer +\startuseMPgraphic{circle} + save p ; path p ; + p := reverse fullcircle rotatedaround(origin,90) + xscaled OverlayWidth yscaled OverlayHeight ; + + draw lmt_followtext [ + path = p, + text = "This is just a dummy text, kerned by \TeX\ and typeset + in a circle using \MetaPost.\quad", + spread = true, + trace = true, + ] ; + +% draw p ; +\stopuseMPgraphic + +\defineoverlay[edge][\useMPgraphic{circle}] +\stopbuffer + +\typebuffer \getbuffer + +The text and graphics come together in a framed text: + +\startbuffer +\startplacefigure[title={One more time Hofstadter's quotation (normal).}] + \getbuffer[shape,quote]% + \framed + [offset=24pt, + background=edge, + frame=off, + backgroundoffset=-18pt] + {\getshapetext} +\stopplacefigure +\stopbuffer + +\typebuffer \getbuffer + +\stopsection + +\stopchapter + +\stopcomponent |