mag-0010-mkiv.tex /size: 15 Kb    last modification: 2023-12-21 09:43
1% language=us
2
3% author    : Hans Hagen
4% copyright : ConTeXt Development Team
5% license   : Creative Commons Attribution ShareAlike 4.0 International
6% reference : pragma-ade.nl | contextgarden.net | texlive (related) distributions
7% origin    : the ConTeXt distribution
8%
9% comment   : Because this manual is distributed with TeX distributions it comes with a rather
10%             liberal license. We try to adapt these documents to upgrades in the (sub)systems
11%             that they describe. Using parts of the content otherwise can therefore conflict
12%             with existing functionality and we cannot be held responsible for that. Many of
13%             the manuals contain characteristic graphics and personal notes or examples that
14%             make no sense when used out-of-context.
15%
16% comment   : Some chapters might have been published in TugBoat, the NTG Maps, the ConTeXt
17%             Group journal or otherwise. Thanks to the editors for corrections. Also thanks
18%             to users for testing, feedback and corrections.
19
20\usemodule[mag-01,abr-02]
21
22\startbuffer[abstract]
23    The content of tenth magazine was written while listening to Tori Amos'
24    latest album, The Beekeeper. In the (nice) booklet the text flows in shapes
25    and here I will demonstrate that \TEX\ can do something similar. It's also a
26    nice example of applying \HZ\ optimization.
27\stopbuffer
28
29\startdocument
30  [title={Good looking shapes},
31   author=Hans Hagen,
32   affiliation=PRAGMA ADE,
33   date=March 2005,
34   number=10 \MKIV]
35
36Just as it takes while to get an understanding what \TEX\ is about, it takes a
37couple of listening loops to get a general picture about Tori Amos' Beekeeper.
38While browsing the rather nicely designed booklet I got puzzled |<|as usual when
39seeing such nice book(let)s|>| why everything looked okay except the text. High
40end design combined with rather low end typography. Don't get me wrong, apart
41from the typesetting it's a pretty good product! Tori being one of my favourite
42artists, you can imagine that I wrote quite some \CONTEXT\ code listening to her
43music.
44
45Now I will not argue that \TEX\ (or \CONTEXT) is the proper system for making
46\CD\ covers, but since most of such a booklet is a matter of pasting graphics
47components together, I can imagine that one should ask someone to typeset the
48text snippets using a proper engine. Anyway, most buyers (fans) won't notice it,
49but anyone familiar with \TEX\ will immediate get distracted by the strange
50intercharacter and interline spacing.
51
52Typesetting in a fixed shape is non||trivial. First of all lines should break in
53a pleasing way. If possible, hyphenation should be avoided. The gaps between
54characters must not become to large and the last line should not be too short.
55Doing this in \TEX\ is non trivial either, not so much because \TEX\ cannot do
56such things, but because one needs to control several mechanisms at once. On the
57other hand, one should know what one's dealing with anyway.
58
59Because the size of the shape is fixed, we can manipulate the number of lines
60and/or the line length and scale afterwards to the desired size. The font size is
61not fixed. This permits us to implement a semi||automated solution. The
62difference between the first version of the solution and current one is that we
63take into account an odd|/|even number of lines. Also, finding the best exit
64condition took some experiments. The final solution is not that complex and also
65shows a couple of tricks.
66
67\startbuffer
68\definecolor[BeeColorA][r=.4,g=.5,b=.6]
69\definecolor[BeeColorB][r=.5,g=.6,b=.4]
70\definecolor[BeeColorC][r=.6,g=.4,b=.5]
71
72\definecolor[BeeColor] [BeeColorA]
73
74\defineoverlay
75  [beecell]
76  [\uniqueMPgraphic{beecell}{offset=3mm,color=BeeColor}]
77
78\startuniqueMPgraphic{beecell}{offset,color}
79  fill
80    for i = 1 upto 6 : (0,OverlayHeight/2)
81      rotatedaround (center OverlayBox,i*60) --
82    endfor cycle
83    withpen pencircle scaled \MPvar{offset}
84    withcolor \MPvar{color} ;
85\stopuniqueMPgraphic
86\stopbuffer
87
88\getbuffer
89
90The shape we are dealing with looks as follows:
91
92\startlinecorrection
93\startMPcode
94  fill
95    for i = 1 upto 6 : (5cm,0)
96      rotatedaround(origin,i*60) --
97    endfor cycle
98    withpen pencircle scaled 2mm
99    withcolor \MPcolor{BeeColorC} ;
100  currentpicture := currentpicture xsized(5cm) ;
101\stopMPcode
102\stoplinecorrection
103
104We will will later put such a shape behind the text for which we define an
105overlay:
106
107\typebuffer
108
109Normally one will not put a shape behind the text, but in our case it illustrates
110the idea. We use an offset in order to get a more pleasing look.
111
112We will use the following two sample texts. The original linebreaks are visible
113in the source:
114
115\startbuffer
116\startbuffer[parasol]
117\title {PARASOL} when I come to
118terms to terms with this when
119I come to terms with this when I
120come to terms to terms with this my
121world will change for me I haven't moved
122since the call came since the call came I
123haven't moved I stare at the wall knowing on the
124other side the storm that waits for me then the
125Seated Woman with a Parasol may be the only one you
126can't Betray if I'm the Seated Women with a Parasol I will
127be safe in my frame I have no need for a sea view for a sea
128view I have no need I have my little pleasures this wall
129being one of these when I come to terms to terms
130with this when I come to terms with this when I
131come to terms with this whip lash of Silk on
132wool embroidery then the Seated Woman
133with a Parasol may be the only one you
134can't betray if I'm the Seated Woman
135with a Parasol I will be safe in my
136frame I will be safe in my frame
137in your House in your frame
138\stopbuffer
139
140\startbuffer[beekeeper]
141\title {THE BEEKEEPER} Flaxen hair
142blowing in the breeze It is time
143for the geese to head south I have
144come with my mustard seed I cannot
145accept that she will be taken from me
146``Do you know who I am'' she said ``I'm the
147one who taps you on the shoulder when it's
148your time Don't be afraid I promise that she
149will awake Tomorrow Somewhere Tomorrow
150Somewhere'' --- wrap yourself around the Tree of
151Life and the Dance of the Infinity of the Hive --- take
152this message to Michael I will comb myself into chains In
153between the tap dance clan and your ballerina gang I have
154come for the Beekeeper I know you want my You want
155my Queen --- Anything but this Can you use me instead?
156In your gown with your breathing mask Plugged into
157a heart machine As if you ever needed one I must
158see the Beekeeper I must see if she'll keep her
159alive Call Engine 49 I have come with my
160mustard seed Maybe I'm passing you by
161On my way On my way I'm just passing
162you by But don't be confused
163One day I'll be coming for you \unknown\space
164I must see the Beekeeper
165I must see the Beekeeper
166\stopbuffer
167\stopbuffer
168
169\typebuffer \getbuffer
170
171We will call these buffers indirectly (using setups is a convenient way to
172collect commands and definitions).
173
174\startbuffer
175\startsetups [beetext]
176  \getbuffer[parasol]
177\stopsetups
178\stopbuffer
179
180\typebuffer \getbuffer
181
182Now comes the dirty code. We assume that you know a bit of \CONTEXT. First of all
183we choose a font, in our case a Termes for the running text. We will use
184Hermann Zapf optimization, which is way more acceptable that intercharacter
185spacing and gives quite good results here.
186
187\startbuffer
188\definefontfeature[hzdefault][default][hz=quality]
189\definefont[BeeFont][file:texgyre-termes*hzdefault]
190\stopbuffer
191
192\typebuffer \getbuffer
193
194The core of the code is a loop wherein we try to figure out what the best width
195is. In principle this method can be used for similar shapes. Beforehand we define
196a few variables.
197
198\startbuffer
199\cldcontext{math.cosd(60)}
200\cldcontext{math.sind(60)}
201
202\newdimen\BeeEdge
203\newdimen\BeeLine
204\newdimen\BeeSize
205
206\newbox  \BeeBox
207
208\def\BeeLines{17}   % choose optimum odd/even
209\def\BeeStart{2cm}  % set automatically
210\def\BeeStep {.5mm} % accurate enough
211\stopbuffer
212
213\typebuffer \getbuffer
214
215The loop starts with a rather small width and with increasing steps tries to find
216the solution where the number of used lines equals the asked number of lines. We
217could have used low level \TEX\ primitives, but using a few \CONTEXT\ wrappers
218makes more sense because that way struts and alike are set as well. In the end we
219stretch the interline spacing to match the height of the cell.
220
221\startbuffer
222
223\startsetups beeloop
224
225\def\title##1%
226  {{\ss\bf\kerncharacters[0.25]##1}%
227    \hskip.5em plus .5em minus .25em\relax
228    \ignorespaces}
229
230\setbox\scratchbox=\hbox{\setups[beetext]}
231
232\edef\BeeStart
233  {\the\dimexpr.5\wd\scratchbox/\BeeLines\relax}
234
235\def\BeeMax
236  {10000}
237
238\def\BeeShapeA
239  {\scratchdimen\numexpr\recurselevel-1\relax
240     \dimexpr\BeeEdge/\BeeLast\relax
241   \appendetoks
242     \the\dimexpr\BeeEdge- \scratchdimen\relax\space
243     \the\dimexpr\hsize  +2\scratchdimen\relax\space
244   \to\scratchtoks}
245
246\def\BeeShapeB
247  {\appendetoks
248     \zeropoint\space
249     \the\dimexpr\hsize+2\BeeEdge\relax\space
250   \to\scratchtoks}
251
252\doloop
253  {\bgroup
254   \forgetall
255   \dontcomplain
256   \edef\BeeLast
257     {\the\numexpr(\BeeLines\ifodd\BeeLines-1\fi)/2\relax}%
258   \hsize\dimexpr\BeeStart+\recurselevel\dimexpr\BeeStep\relax\relax
259   \BeeEdge=\cldcontext{math.cosd(60)}\hsize
260   \BeeSize=\cldcontext{math.sind(60)}\hsize
261   \BeeLine=\dimexpr2\BeeSize/\numexpr2*\BeeLast+1\relax\relax
262   \setupinterlinespace[line=\BeeLine,stretch=.5]%
263   \setuptolerance[verytolerant]%
264   \setupalign[hz]%
265   \parfillskip\zeropoint
266   \scratchtoks\emptytoks
267   \ifodd\BeeLines
268     \dostepwiserecurse{1}{\BeeLast}{+1}{\BeeShapeA}%
269                                         \BeeShapeB
270     \dostepwiserecurse{\BeeLast}{1}{-1}{\BeeShapeA}%
271     \rightskip\zeropoint
272   \else
273     % we want to stay inside the shape, so we need
274     % to compensate the right side
275     \advance\hsize +\dimexpr\BeeEdge/\BeeLast\relax
276     \dostepwiserecurse{1}{\BeeLast}{+1}{\BeeShapeA}%
277     \dostepwiserecurse{\BeeLast}{1}{-1}{\BeeShapeA}%
278     \advance\hsize -\dimexpr\BeeEdge/\BeeLast\relax
279     \rightskip\dimexpr\BeeEdge/\BeeLast\relax
280   \fi
281   \setbox\scratchbox\vbox \bgroup
282     % we set it like this in case grid is turned on
283     \baselineskip=1\baselineskip plus 20pt minus 20pt
284     \parshape\numexpr\BeeLines\relax\the\scratchtoks
285     \begstrut
286     \ignorespaces\setups[beetext]\removeunwantedspaces
287     \endstrut
288     \endgraf
289     \xdef\BeeTotal{\number\prevgraf}%
290     \xdef\BeeRate {\number\badness }%
291   \egroup
292   \writestatus
293     {beestate}
294     {     run: \recurselevel\space
295        target: \BeeLines    \space
296         lines: \BeeTotal    \space
297       badness: \BeeRate}%
298   \CheckBeeLines % sets 'done'
299   \ifdone
300     \vbox to 2\BeeSize
301       {\unvbox\ifvoid\BeeBox\scratchbox\else\BeeBox\fi}%
302     \egroup
303     \exitloop
304   \else
305     \egroup
306   \fi}
307
308\stopsetups
309\stopbuffer
310
311\getbuffer \typebuffer
312
313The end criterium is determined by:
314
315\startbuffer
316\def\CheckBeeLines
317  {\ifnum\BeeTotal>\BeeLines\relax
318     \donefalse
319   \else
320     \donetrue
321   \fi}
322\stopbuffer
323
324\getbuffer \typebuffer
325
326This solution is rather safe and, at the cost of the ugly saving of the number of
327lines as registered in \type {\prevgraf}, works better than measuring the height
328of the box.
329
330We could build the loop out of more isolated pieces of code like this but the
331reason why we do it for the checker is that we now can redefine it. At the cost
332of a few more tests, the following checker is better, because it goes on for a
333while and keeps looking for better solutions. If you have no idea what badness
334is, just skip the following code snippet.
335
336\startbuffer
337\def\CheckBeeLines
338  {\ifnum\BeeTotal>\BeeLines\relax
339     \donefalse
340   \else\ifnum\BeeTotal=\BeeLines\relax
341     \ifnum\BeeRate=\zerocount
342       \global\setbox\BeeBox=\box\scratchbox
343       \donetrue
344     \else\ifnum\BeeRate<\BeeMax\relax
345       \global\let\BeeMax\BeeRate
346       \global\setbox\BeeBox=\box\scratchbox
347       \donefalse
348     \else
349       \donefalse
350     \fi\fi
351   \else
352     \donetrue
353   \fi\fi}
354\stopbuffer
355
356\getbuffer \typebuffer
357
358Well, this is not the kind of code you want a designer to enter, but providing it
359as feature in a desk top publishing application is also non||trivial because each
360case differs and turning many knobs to get things done is not easy either, so
361basically it comes down to manual work (neglectable to the total amount of work
362involved in getting such a musical product done). Of course one can ask someone
363to typeset the text in \TEX\ and provide it as image, but that would make
364coordination the production more complex.
365
366The criterium (here \BeeStep) can be made smaller when you encounter problems. If
367we set it to 1mm, we get one case where the amount of lines jumps~2 and the loop
368is exit unexpected. Of course one can catch such cases but it does not make much
369sense in such a one||shot macro.
370
371The previous setup is applied as follows:
372
373\startbuffer
374\startsetups beeloner
375  \framed
376    [offset=overlay,
377     frame=off,
378     background=beecell,
379     foregroundstyle=\BeeFont]
380    {\setups[beeloop]}
381\stopsetups
382\stopbuffer
383
384\getbuffer \typebuffer
385
386We will now put several variants alongside. For this we use a layer:
387
388\startbuffer
389\startsetups beesample
390
391\definelayer
392  [beekeeper]
393  [width=13cm,
394   height=9cm]
395
396\setlayer
397  [beekeeper]
398  [preset=lefttop]
399  {\scale[width=5cm]{\def\BeeLines{16}\setups[beeloner]}}
400
401\setlayer
402  [beekeeper]
403  [preset=leftbottom]
404  {\scale[width=5cm]{\def\BeeLines{17}\setups[beeloner]}}
405
406\setlayer
407  [beekeeper]
408  [preset=righttop]
409  {\scale[width=5cm]{\def\BeeLines{18}\setups[beeloner]}}
410
411\setlayer
412  [beekeeper]
413  [preset=rightbottom]
414  {\scale[width=5cm]{\def\BeeLines{19}\setups[beeloner]}}
415
416\setlayer
417  [beekeeper]
418  [preset=middle]
419  {\scale[width=5cm]{\def\BeeLines{20}\setups[beeloner]}}
420
421\tightlayer[beekeeper]
422
423\stopsetups
424\stopbuffer
425
426\getbuffer \typebuffer
427
428\startbuffer[a]
429\startsetups [beetext]
430  \getbuffer[parasol]
431\stopsetups
432
433\definecolor[BeeColor][BeeColorA] \setups[beesample]
434\stopbuffer
435
436\startbuffer[b]
437\startsetups [beetext]
438  \getbuffer[beekeeper]
439\stopsetups
440
441\definecolor[BeeColor][BeeColorB] \setups[beesample]
442\stopbuffer
443
444\startpostponing
445
446\placefigure
447  [here]
448  [fig:parasol]
449  {Parasol}
450  {\getbuffer[a]}
451
452\placefigure
453  [here]
454  [fig:beekeeper]
455  {The Beekeeper}
456  {\getbuffer[b]}
457
458\page
459
460\stoppostponing
461
462The first samples, shown in \in {figure} [fig:parasol], will be typeset using:
463
464\typebuffer[a]
465
466The second example, shown in \in {figure} [fig:beekeeper], is done in a similar
467way. We redefine the \type {beetext} setup.
468
469\typebuffer[b]
470
471You can zoom in on cells using your viewer. An enlarged example is shown in \in
472{figure} [fig:big].
473
474\startbuffer
475\definecolor[BeeColor][BeeColorC]%
476\startcombination
477  {\scale
478     [width=.475\textwidth]
479     {\startsetups[beetext]\getbuffer[parasol]\stopsetups
480      \def\BeeLines{17}\setups[beeloner]}}
481  {Parasol}
482  {\scale
483     [width=.475\textwidth]
484     {\startsetups[beetext]\getbuffer[beekeeper]\stopsetups
485      \def\BeeLines{20}\setups[beeloner]}}
486  {The Beekeeper}
487\stopcombination
488\stopbuffer
489
490\typebuffer
491
492Choosing the best alternative is a matter of taste. If you ever get a change to
493see the \CD\ (a good buy anyway) you will note the difference. It is possible to
494improve the spacing at the top and bottom but we leave this as an exercise.
495
496\placefigure
497  [here]
498  [fig:big]
499  {An few enlarged examples.}
500  {\getbuffer}
501
502The downside of this exercise was that in the process my laptop suddenly made
503some funny noises and made me end up with a cracked \CD. So in the end the
504message may be not to bother too much about badly typeset paragraphs in \CD\
505booklets.
506
507\vbox to \vsize \bgroup
508
509  \vfil
510
511  \hbox to \hsize \bgroup \hss
512    \scale
513       [height=.45\textheight]
514       {\startsetups[beetext]\getbuffer[parasol]\stopsetups
515       \defineoverlay[beecell][]\def\BeeLines{17}\setups[beeloner]}%
516  \hss \egroup
517
518  \vfil \vfil
519
520  \hbox to \hsize \bgroup \hss
521    \scale
522       [height=.45\textheight]
523       {\startsetups[beetext]\getbuffer[beekeeper]\stopsetups
524        \defineoverlay[beecell][]\def\BeeLines{20}\setups[beeloner]}%
525  \hss \egroup
526
527  \vfil
528
529\egroup
530
531\stopdocument
532