cld-somemoreexamples.tex /size: 19 Kb    last modification: 2021-10-28 13:50
1% language=us runpath=texruns:manuals/cld
2
3\startcomponent cld-somemoreexamples
4
5\environment cld-environment
6
7\usemodule[morse]
8
9\startchapter[title=Some more examples]
10
11\startsection[title=Appetizer]
12
13Before we give some more examples, we will have a look at the way the title page
14is made. This way you get an idea what more is coming.
15
16\typefile {cld-mkiv-simple-titlepage.cld}
17
18This does not look that bad, does it? Of course in pure \TEX\ code it looks
19mostly the same but loops and calculations feel a bit more natural in \LUA\ then
20in \TEX. The result is shown in \in {figure} [fig:cover]. The actual cover page
21was derived from this.
22
23\startplacefigure[location=here,reference=fig:cover,title={The simplified cover page.}]
24    \doiffileexistselse {cld-mkiv-simple-titlepage.pdf} {
25        \externalfigure
26            [cld-mkiv-simple-titlepage.pdf]
27            [height=.5\textheight]
28    } {
29        \scale
30            [height=.5\textheight]
31            {\cldprocessfile{cld-mkiv-simple-titlepage.cld}}
32    }
33\stopplacefigure
34
35\stopsection
36
37\startsection[title=A few examples]
38
39As it makes most sense to use the \LUA\ interface for generated text, here is
40another example with a loop:
41
42\startbuffer
43context.startitemize { "a", "packed", "two" }
44  for i=1,10 do
45    context.startitem()
46      context("this is item %i",i)
47    context.stopitem()
48  end
49context.stopitemize()
50\stopbuffer
51
52\typebuffer
53
54\ctxluabuffer
55
56Just as you can mix \TEX\ with \XML\ and \METAPOST, you can define bits and
57pieces of a document in \LUA. Tables are good candidates:
58
59\startbuffer
60local one = {
61  align = "middle",
62  style = "type",
63}
64local two = {
65  align = "middle",
66  style = "type",
67  background = "color",
68  backgroundcolor = "darkblue",
69  foregroundcolor = "white",
70}
71local random = math.random
72context.bTABLE { framecolor = "darkblue" }
73    for i=1,10 do
74      context.bTR()
75      for i=1,20 do
76          local r = random(99)
77          context.bTD(r < 50 and one or two)
78          context("%2i",r)
79          context.eTD()
80      end
81      context.eTR()
82    end
83context.eTABLE()
84\stopbuffer
85
86\typebuffer
87
88\placetable[top][tab:random]{A table generated by \LUA.}{\ctxluabuffer}
89
90Here we see a function call to \type {context} in the most indented line. The
91first argument is a format that makes sure that we get two digits and the random
92number is substituted into this format. The result is shown in
93\in{table}[tab:random]. The line correction is ignored when we use this table as
94a float, otherwise it assures proper vertical spacing around the table. Watch how
95we define the tables \type {one} and \type {two} beforehand. This saves 198
96redundant table constructions.
97
98Not all code will look as simple as this. Consider the following:
99
100\starttyping
101context.placefigure(
102  "caption",
103  function() context.externalfigure( { "cow.pdf" } ) end
104)
105\stoptyping
106
107Here we pass an argument wrapped in a function. If we would not do that, the
108external figure would end up wrong, as arguments to functions are evaluated
109before the function that gets them (we already showed some alternative approaches
110in previous chapters). A function argument is treated as special and in this case
111the external figure ends up right. Here is another example:
112
113\startbuffer
114context.placefigure("Two cows!",function()
115  context.bTABLE()
116    context.bTR()
117      context.bTD()
118        context.externalfigure(
119            { "cow.pdf" },
120            { width = "3cm", height = "3cm" }
121        )
122      context.eTD()
123      context.bTD { align = "{lohi,middle}" }
124        context("and")
125      context.eTD()
126      context.bTD()
127        context.externalfigure(
128            { "cow.pdf" },
129            { width = "4cm", height = "3cm" }
130        )
131      context.eTD()
132    context.eTR()
133  context.eTABLE()
134end)
135\stopbuffer
136
137\typebuffer
138
139In this case the figure is not an argument so it gets flushed sequentially
140with the rest.
141
142\ctxluabuffer
143
144\stopsection
145
146\startsection[title=Styles]
147
148Say that you want to typeset a word in a bold font. You can do
149that this way:
150
151\starttyping
152context("This is ")
153context.bold("important")
154context("!")
155\stoptyping
156
157Now imagine that you want this important word to be in red too. As we have
158a nested command, we end up with a nested call:
159
160\starttyping
161context("This is ")
162context.bold(function() context.color( { "red" }, "important") end)
163context("!")
164\stoptyping
165
166or
167
168\starttyping
169context("This is ")
170context.bold(context.delayed.color( { "red" }, "important"))
171context("!")
172\stoptyping
173
174In that case it's good to know that there is a command that combines both
175features:
176
177\starttyping
178context("This is ")
179context.style( { style = "bold", color = "red" }, "important")
180context("!")
181\stoptyping
182
183But that is still not convenient when we have to do that often. So, you can wrap
184the style switch in a function.
185
186\starttyping
187local function mycommands.important(str)
188    context.style( { style = "bold", color = "red" }, str )
189end
190
191context("This is ")
192mycommands.important( "important")
193context(", and ")
194mycommands.important( "this")
195context(" too !")
196\stoptyping
197
198Or you can setup a named style:
199
200\starttyping
201context.setupstyle( { "important" }, { style = "bold", color = "red" } )
202
203context("This is ")
204context.style( { "important" }, "important")
205context(", and ")
206context.style( { "important" }, "this")
207context(" too !")
208\stoptyping
209
210Or even define one:
211
212\starttyping
213context.definestyle( { "important" }, { style = "bold", color = "red" } )
214
215context("This is ")
216context.important("important")
217context(", and ")
218context.important("this")
219context(" too !")
220\stoptyping
221
222This last solution is especially handy for more complex cases:
223
224\startbuffer
225context.definestyle( { "important" }, { style = "bold", color = "red" } )
226
227context("This is ")
228context.startimportant()
229context.inframed("important")
230context.stopimportant()
231context(", and ")
232context.important("this")
233context(" too !")
234\stopbuffer
235
236\typebuffer
237
238\ctxluabuffer
239
240\stopsection
241
242\startsection[title=A complete example]
243
244One day my 6 year old niece Lorien was at the office and wanted to know what I
245was doing. As I knew she was practicing arithmetic at school I wrote a quick and
246dirty script to generate sheets with exercises. The most impressive part was that
247the answers were included. It was a rather braindead bit of \LUA, written in a
248few minutes, but the weeks after I ended up running it a few more times, for her
249and her friends, every time a bit more difficult and also using different
250arithmetic. It was that script that made me decide to extend the basic cld manual
251into this more extensive document.
252
253We generate three columns of exercises. Each exercise is a row in a table. The
254last argument to the function determines if answers are shown.
255
256\starttyping
257local random = math.random
258
259local function ForLorien(n,maxa,maxb,answers)
260  context.startcolumns { n = 3 }
261  context.starttabulate { "|r|c|r|c|r|" }
262  for i=1,n do
263    local sign = random(0,1) > 0.5
264    local a, b = random(1,maxa or 99), random(1,max or maxb or 99)
265    if b > a and not sign then a, b = b, a end
266    context.NC()
267    context(a)
268    context.NC()
269    context.mathematics(sign and "+" or "-")
270    context.NC()
271    context(b)
272    context.NC()
273    context("=")
274    context.NC()
275    context(answers and (sign and a+b or a-b))
276    context.NC()
277    context.NR()
278  end
279  context.stoptabulate()
280  context.stopcolumns()
281  context.page()
282end
283\stoptyping
284
285This is a typical example of where it's more convenient to write the code in
286\LUA\ that in \TEX's macro language. As a consequence setting up the page also
287happens in \LUA:
288
289\starttyping
290context.setupbodyfont {
291  "palatino",
292  "14pt"
293}
294
295context.setuplayout {
296  backspace = "2cm",
297  topspace  = "2cm",
298  header    = "1cm",
299  footer    = "0cm",
300  height    = "middle",
301  width     = "middle",
302}
303\stoptyping
304
305This leave us to generate the document. There is a pitfall here: we need to use
306the same random number for the exercises and the answers, so we freeze and
307defrost it. Functions in the \type {commands} namespace implement functionality
308that is used at the \TEX\ end but better can be done in \LUA\ than in \TEX\ macro
309code. Of course these functions can also be used at the \LUA\ end.
310
311\starttyping
312context.starttext()
313
314  local n = 120
315
316  commands.freezerandomseed()
317
318  ForLorien(n,10,10)
319  ForLorien(n,20,20)
320  ForLorien(n,30,30)
321  ForLorien(n,40,40)
322  ForLorien(n,50,50)
323
324  commands.defrostrandomseed()
325
326  ForLorien(n,10,10,true)
327  ForLorien(n,20,20,true)
328  ForLorien(n,30,30,true)
329  ForLorien(n,40,40,true)
330  ForLorien(n,50,50,true)
331
332context.stoptext()
333\stoptyping
334
335\ctxlua{document.checkcldresource("cld-005.cld")}
336
337\placefigure
338  [here]
339  [fig:lorien]
340  {Lorien's challenge.}
341  {\startcombination
342     {\externalfigure[cld-005.pdf][page=1,width=.45\textwidth,frame=on]} {exercises}
343     {\externalfigure[cld-005.pdf][page=6,width=.45\textwidth,frame=on]} {answers}
344   \stopcombination}
345
346A few pages of the result are shown in \in {figure} [fig:lorien]. In the
347\CONTEXT\ distribution a more advanced version can be found in \type
348{s-edu-01.cld} as I was also asked to generate multiplication and table
349exercises. In the process I had to make sure that there were no duplicates on a
350page as she complained that was not good. There a set of sheets is generated
351with:
352
353\starttyping
354moduledata.educational.arithematic.generate {
355  name     = "Bram Otten",
356  fontsize = "12pt",
357  columns  = 2,
358  run      = {
359    { method = "bin_add_and_subtract", maxa =   8, maxb =   8 },
360    { method = "bin_add_and_subtract", maxa =  16, maxb =  16 },
361    { method = "bin_add_and_subtract", maxa =  32, maxb =  32 },
362    { method = "bin_add_and_subtract", maxa =  64, maxb =  64 },
363    { method = "bin_add_and_subtract", maxa = 128, maxb = 128 },
364  },
365}
366\stoptyping
367
368\stopsection
369
370\startsection[title=Interfacing]
371
372The fact that we can define functionality using \LUA\ code does not mean that we
373should abandon the \TEX\ interface. As an example of this we use a relatively
374simple module for typesetting morse code.\footnote {The real module is a bit
375larger and can format verbose morse.} First we create a proper namespace:
376
377\starttyping
378
379moduledata.morse = moduledata.morse or { }
380local morse      = moduledata.morse
381\stoptyping
382
383We will use a few helpers and create shortcuts for them. The first helper loops
384over each \UTF\ character in a string. The other two helpers map a character onto
385an uppercase (because morse only deals with uppercase) or onto an similar shaped
386character (because morse only has a limited character set).
387
388\starttyping
389local utfcharacters = string.utfcharacters
390local ucchars, shchars = characters.ucchars, characters.shchars
391\stoptyping
392
393The morse codes are stored in a table.
394
395\starttyping
396local codes = {
397
398    ["A"] = "·—",     ["B"] = "—···",
399    ["C"] = "—·—·",   ["D"] = "—··",
400    ["E"] = "·",      ["F"] = "··—·",
401    ["G"] = "——·",    ["H"] = "····",
402    ["I"] = "··",     ["J"] = "·———",
403    ["K"] = "—·—",    ["L"] = "·—··",
404    ["M"] = "——",     ["N"] = "—·",
405    ["O"] = "———",    ["P"] = "·——·",
406    ["Q"] = "——·—",   ["R"] = "·—·",
407    ["S"] = "···",    ["T"] = "",
408    ["U"] = "··—",    ["V"] = "···—",
409    ["W"] = "·——",    ["X"] = "—··—",
410    ["Y"] = "—·——",   ["Z"] = "——··",
411
412    ["0"] = "—————",  ["1"] = "·————",
413    ["2"] = "··———",  ["3"] = "···——",
414    ["4"] = "····—",  ["5"] = "·····",
415    ["6"] = "—····",  ["7"] = "——···",
416    ["8"] = "———··",  ["9"] = "————·",
417
418    ["."] = "·—·—·—", [","] = "——··——",
419    [":"] = "———···", [";"] = "—·—·—",
420    ["?"] = "··——··", ["!"] = "—·—·——",
421    ["-"] = "—····—", ["/"] = "—··—· ",
422    ["("] = "—·——·",  [")"] = "—·——·—",
423    ["="] = "—···—",  ["@"] = "·——·—·",
424    ["'"] = "·————·", ['"'] = "·—··—·",
425
426    ["À"] = "·——·—",
427    ["Å"] = "·——·—",
428    ["Ä"] = "·—·—",
429    ["Æ"] = "·—·—",
430    ["Ç"] = "—·—··",
431    ["É"] = "··—··",
432    ["È"] = "·—··—",
433    ["Ñ"] = "——·——",
434    ["Ö"] = "———·",
435    ["Ø"] = "———·",
436    ["Ü"] = "··——",
437    ["ß"] = "··· ···",
438
439}
440
441morse.codes = codes
442\stoptyping
443
444As you can see, there are a few non \ASCII\ characters supported as well. There
445will never be full \UNICODE\ support simply because morse is sort of obsolete.
446Also, in order to support \UNICODE\ one could as well use the bits of \UTF\
447characters, although \unknown\ memorizing the whole \UNICODE\ table is not much
448fun.
449
450We associate a metatable index function with this mapping. That way we can not
451only conveniently deal with the casing, but also provide a fallback based on the
452shape. Once found, we store the representation so that only one lookup is needed
453per character.
454
455\starttyping
456local function resolvemorse(t,k)
457    if k then
458        local u = ucchars[k]
459        local v = rawget(t,u) or rawget(t,shchars[u]) or false
460        t[k] = v
461        return v
462    else
463        return false
464    end
465end
466
467setmetatable(codes, { __index = resolvemorse })
468\stoptyping
469
470Next comes some rendering code. As we can best do rendering at the \TEX\ end we
471just use macros.
472
473\starttyping
474local MorseBetweenWords      = context.MorseBetweenWords
475local MorseBetweenCharacters = context.MorseBetweenCharacters
476local MorseLong              = context.MorseLong
477local MorseShort             = context.MorseShort
478local MorseSpace             = context.MorseSpace
479local MorseUnknown           = context.MorseUnknown
480\stoptyping
481
482The main function is not that complex. We need to keep track of spaces and
483newlines. We have a nested loop because a fallback to shape can result in
484multiple characters.
485
486\starttyping
487function morse.tomorse(str)
488    local inmorse = false
489    for s in utfcharacters(str) do
490        local m = codes[s]
491        if m then
492            if inmorse then
493                MorseBetweenWords()
494            else
495                inmorse = true
496            end
497            local done = false
498            for m in utfcharacters(m) do
499                if done then
500                    MorseBetweenCharacters()
501                else
502                    done = true
503                end
504                if m == "·" then
505                    MorseShort()
506                elseif m == "" then
507                    MorseLong()
508                elseif m == " " then
509                    MorseBetweenCharacters()
510                end
511            end
512            inmorse = true
513        elseif s == "\n" or s == " " then
514            MorseSpace()
515            inmorse = false
516        else
517            if inmorse then
518                MorseBetweenWords()
519            else
520                inmorse = true
521            end
522            MorseUnknown(s)
523        end
524    end
525end
526\stoptyping
527
528We use this function in two additional functions. One typesets a file, the other
529a table of available codes.
530
531\starttyping
532function morse.filetomorse(name,verbose)
533    morse.tomorse(resolvers.loadtexfile(name),verbose)
534end
535
536function morse.showtable()
537    context.starttabulate { "|l|l|" }
538    for k, v in table.sortedpairs(codes) do
539        context.NC() context(k)
540        context.NC() morse.tomorse(v,true)
541        context.NC() context.NR()
542    end
543    context.stoptabulate()
544end
545\stoptyping
546
547We're done with the \LUA\ code that we can either put in an external file or put
548in the module file. The \TEX\ file has two parts. The typesetting macros that we
549use at the \LUA\ end are defined first. These can be overloaded.
550
551\starttyping
552\def\MorseShort
553  {\dontleavehmode
554   \vrule
555      width  \MorseWidth
556      height \MorseHeight
557      depth  \zeropoint
558   \relax}
559
560\def\MorseLong
561  {\dontleavehmode
562   \vrule
563      width 3\dimexpr\MorseWidth
564      height         \MorseHeight
565      depth          \zeropoint
566   \relax}
567
568\def\MorseBetweenCharacters
569  {\kern\MorseWidth}
570
571\def\MorseBetweenWords
572  {\hskip3\dimexpr\MorseWidth\relax}
573
574\def\MorseSpace
575  {\hskip7\dimexpr\MorseWidth\relax}
576
577\def\MorseUnknown#1
578  {[\detokenize{#1}]}
579\stoptyping
580
581The dimensions are stored in macros as well. Of course we could provide a proper
582setup command, but it hardly makes sense.
583
584\starttyping
585\def\MorseWidth {0.4em}
586\def\MorseHeight{0.2em}
587\stoptyping
588
589Finally we have arrived at the macros that interface to the \LUA\ functions.
590
591\starttyping
592\def\MorseString#1{\ctxlua{moduledata.morse.tomorse(\!!bs#1\!!es)}}
593\def\MorseFile  #1{\ctxlua{moduledata.morse.filetomorse("#1")}}
594\def\MorseTable   {\ctxlua{moduledata.morse.showtable()}}
595\stoptyping
596
597\startbuffer
598\Morse{A more advanced solution would be to convert a node list. That
599way we can deal with weird input.}
600\stopbuffer
601
602A string is converted to morse with the first command.
603
604\typebuffer
605
606This shows up as:
607
608\startalignment[flushleft,tolerant]\getbuffer\stopalignment
609
610Reduction and uppercasing is demonstrated in the next example:
611
612\startbuffer
613\MorseString{ÀÁÂÃÄÅàáâãäå}
614\stopbuffer
615
616\typebuffer
617
618This gives:
619
620\startalignment[flushleft,tolerant]\getbuffer\stopalignment
621
622\stopsection
623
624\startsection[title=Using helpers]
625
626The next example shows a bit of \LPEG. On top of the standard functionality
627a few additional functions are provided. Let's start with a pure \TEX\
628example:
629
630\startbuffer
631\defineframed
632  [coloredframed]
633  [foregroundcolor=red,
634   foregroundstyle=\underbar,
635   offset=.1ex,
636   location=low]
637\stopbuffer
638
639\typebuffer \getbuffer
640
641\startbuffer
642\processisolatedwords {\input ward \relax} \coloredframed
643\stopbuffer
644
645\typebuffer \blank \getbuffer \blank
646
647Because this processor macro operates at the \TEX\ end it has some limitations.
648The content is collected in a very narrow box and from that a regular paragraph
649is constructed. It is for this reason that no color is applied: the snippets that
650end up in the box are already typeset.
651
652An alternative is to delegate the task to \LUA:
653
654\startbuffer
655\startluacode
656local function process(data)
657
658  local words = lpeg.split(lpeg.patterns.spacer,data or "")
659
660  for i=1,#words do
661    if i == 1 then
662        context.dontleavehmode()
663    else
664        context.space()
665    end
666    context.coloredframed(words[i])
667  end
668
669end
670
671process(io.loaddata(resolvers.findfile("ward.tex")))
672\stopluacode
673\stopbuffer
674
675\typebuffer \blank \getbuffer \blank
676
677The function splits the loaded data into a table with individual words. We use a
678splitter that splits on spacing tokens. The special case for \type {i = 1} makes
679sure that we end up in horizontal mode (read: properly start a paragraph). This
680time we do get color because the typesetting is done directly. Here is an
681alternative implementation:
682
683\starttyping
684local done = false
685
686local function reset()
687    done = false
688    return true
689end
690
691local function apply(s)
692  if done then
693    context.space()
694  else
695    done = true
696    context.dontleavehmode()
697  end
698  context.coloredframed(s)
699end
700
701local splitter = lpeg.P(reset)
702               * lpeg.splitter(lpeg.patterns.spacer,apply)
703
704local function process(data)
705  lpeg.match(splitter,data)
706end
707\stoptyping
708
709This version is more efficient as it does not create an intermediate table. The
710next one is comaprable:
711
712\starttyping
713local function apply(s)
714  context.coloredframed("%s ",s)
715end
716
717local splitter lpeg.splitter(lpeg.patterns.spacer,apply)
718
719local function process(data)
720  context.dontleavevmode()
721  lpeg.match(splitter,data)
722  context.removeunwantedspaces()
723end
724\stoptyping
725
726\stopsection
727
728\startsection[title=Formatters]
729
730Sometimes can save a bit of work by using formatters. By default, the \type {context}
731command, when called directly, applies a given formatter. But when called as table
732this feature is lost because then we want to process non|-|strings as well. The next
733example shows a way out:
734
735\startbuffer
736context("the current emwidth is %p",\number\emwidth)
737context.par()
738context.formatted("the current emwidth is %p",\number\emwidth)
739context.par()
740context.bold(string.formatters["the current emwidth is %p"](\number\emwidth))
741context.par()
742context.formatted.bold("the current emwidth is %p",\number\emwidth)
743\stopbuffer
744
745The last one is the most interesting one here: in the subnamespace \type
746{formatted} (watch the \type {d}) a format specification with extra arguments is
747expected.
748
749\ctxluabuffer
750
751\stopsection
752
753\stopchapter
754
755\stopcomponent
756