diff options
Diffstat (limited to 'doc/context/sources/general/manuals/cld/cld-somemoreexamples.tex')
-rw-r--r-- | doc/context/sources/general/manuals/cld/cld-somemoreexamples.tex | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/doc/context/sources/general/manuals/cld/cld-somemoreexamples.tex b/doc/context/sources/general/manuals/cld/cld-somemoreexamples.tex new file mode 100644 index 000000000..a282be4e9 --- /dev/null +++ b/doc/context/sources/general/manuals/cld/cld-somemoreexamples.tex @@ -0,0 +1,753 @@ +% language=uk + +\startcomponent cld-somemoreexamples + +\environment cld-environment + +\usemodule[morse] + +\startchapter[title=Some more examples] + +\startsection[title=Appetizer] + +Before we give some more examples, we will have a look at the way the title page +is made. This way you get an idea what more is coming. + +\typefile {cld-mkiv-simple-titlepage.cld} + +This does not look that bad, does it? Of course in pure \TEX\ code it looks +mostly the same but loops and calculations feel a bit more natural in \LUA\ then +in \TEX. The result is shown in \in {figure} [fig:cover]. The actual cover page +was derived from this. + +\startplacefigure[location=here,reference=fig:cover,title={The simplified cover page.}] + \doiffileexistselse {cld-mkiv-simple-titlepage.pdf} { + \externalfigure + [cld-mkiv-simple-titlepage.pdf] + [height=.5\textheight] + } { + \scale + [height=.5\textheight] + {\cldprocessfile{cld-mkiv-simple-titlepage.cld}} + } +\stopplacefigure + +\stopsection + +\startsection[title=A few examples] + +As it makes most sense to use the \LUA\ interface for generated text, here is +another example with a loop: + +\startbuffer +context.startitemize { "a", "packed", "two" } + for i=1,10 do + context.startitem() + context("this is item %i",i) + context.stopitem() + end +context.stopitemize() +\stopbuffer + +\typebuffer + +\ctxluabuffer + +Just as you can mix \TEX\ with \XML\ and \METAPOST, you can define bits and +pieces of a document in \LUA. Tables are good candidates: + +\startbuffer +local one = { + align = "middle", + style = "type", +} +local two = { + align = "middle", + style = "type", + background = "color", + backgroundcolor = "darkblue", + foregroundcolor = "white", +} +local random = math.random +context.bTABLE { framecolor = "darkblue" } + for i=1,10 do + context.bTR() + for i=1,20 do + local r = random(99) + context.bTD(r < 50 and one or two) + context("%2i",r) + context.eTD() + end + context.eTR() + end +context.eTABLE() +\stopbuffer + +\typebuffer + +\placetable[top][tab:random]{A table generated by \LUA.}{\ctxluabuffer} + +Here we see a function call to \type {context} in the most indented line. The +first argument is a format that makes sure that we get two digits and the random +number is substituted into this format. The result is shown in +\in{table}[tab:random]. The line correction is ignored when we use this table as +a float, otherwise it assures proper vertical spacing around the table. Watch how +we define the tables \type {one} and \type {two} beforehand. This saves 198 +redundant table constructions. + +Not all code will look as simple as this. Consider the following: + +\starttyping +context.placefigure( + "caption", + function() context.externalfigure( { "cow.pdf" } ) end +) +\stoptyping + +Here we pass an argument wrapped in a function. If we would not do that, the +external figure would end up wrong, as arguments to functions are evaluated +before the function that gets them (we already showed some alternative approaches +in previous chapters). A function argument is treated as special and in this case +the external figure ends up right. Here is another example: + +\startbuffer +context.placefigure("Two cows!",function() + context.bTABLE() + context.bTR() + context.bTD() + context.externalfigure( + { "cow.pdf" }, + { width = "3cm", height = "3cm" } + ) + context.eTD() + context.bTD { align = "{lohi,middle}" } + context("and") + context.eTD() + context.bTD() + context.externalfigure( + { "cow.pdf" }, + { width = "4cm", height = "3cm" } + ) + context.eTD() + context.eTR() + context.eTABLE() +end) +\stopbuffer + +\typebuffer + +In this case the figure is not an argument so it gets flushed sequentially +with the rest. + +\ctxluabuffer + +\stopsection + +\startsection[title=Styles] + +Say that you want to typeset a word in a bold font. You can do +that this way: + +\starttyping +context("This is ") +context.bold("important") +context("!") +\stoptyping + +Now imagine that you want this important word to be in red too. As we have +a nested command, we end up with a nested call: + +\starttyping +context("This is ") +context.bold(function() context.color( { "red" }, "important") end) +context("!") +\stoptyping + +or + +\starttyping +context("This is ") +context.bold(context.delayed.color( { "red" }, "important")) +context("!") +\stoptyping + +In that case it's good to know that there is a command that combines both +features: + +\starttyping +context("This is ") +context.style( { style = "bold", color = "red" }, "important") +context("!") +\stoptyping + +But that is still not convenient when we have to do that often. So, you can wrap +the style switch in a function. + +\starttyping +local function mycommands.important(str) + context.style( { style = "bold", color = "red" }, str ) +end + +context("This is ") +mycommands.important( "important") +context(", and ") +mycommands.important( "this") +context(" too !") +\stoptyping + +Or you can setup a named style: + +\starttyping +context.setupstyle( { "important" }, { style = "bold", color = "red" } ) + +context("This is ") +context.style( { "important" }, "important") +context(", and ") +context.style( { "important" }, "this") +context(" too !") +\stoptyping + +Or even define one: + +\starttyping +context.definestyle( { "important" }, { style = "bold", color = "red" } ) + +context("This is ") +context.important("important") +context(", and ") +context.important("this") +context(" too !") +\stoptyping + +This last solution is especially handy for more complex cases: + +\startbuffer +context.definestyle( { "important" }, { style = "bold", color = "red" } ) + +context("This is ") +context.startimportant() +context.inframed("important") +context.stopimportant() +context(", and ") +context.important("this") +context(" too !") +\stopbuffer + +\typebuffer + +\ctxluabuffer + +\stopsection + +\startsection[title=A complete example] + +One day my 6 year old niece Lorien was at the office and wanted to know what I +was doing. As I knew she was practicing arithmetic at school I wrote a quick and +dirty script to generate sheets with exercises. The most impressive part was that +the answers were included. It was a rather braindead bit of \LUA, written in a +few minutes, but the weeks after I ended up running it a few more times, for her +and her friends, every time a bit more difficult and also using different +arithmetic. It was that script that made me decide to extend the basic cld manual +into this more extensive document. + +We generate three columns of exercises. Each exercise is a row in a table. The +last argument to the function determines if answers are shown. + +\starttyping +local random = math.random + +local function ForLorien(n,maxa,maxb,answers) + context.startcolumns { n = 3 } + context.starttabulate { "|r|c|r|c|r|" } + for i=1,n do + local sign = random(0,1) > 0.5 + local a, b = random(1,maxa or 99), random(1,max or maxb or 99) + if b > a and not sign then a, b = b, a end + context.NC() + context(a) + context.NC() + context.mathematics(sign and "+" or "-") + context.NC() + context(b) + context.NC() + context("=") + context.NC() + context(answers and (sign and a+b or a-b)) + context.NC() + context.NR() + end + context.stoptabulate() + context.stopcolumns() + context.page() +end +\stoptyping + +This is a typical example of where it's more convenient to write the code in +\LUA\ that in \TEX's macro language. As a consequence setting up the page also +happens in \LUA: + +\starttyping +context.setupbodyfont { + "palatino", + "14pt" +} + +context.setuplayout { + backspace = "2cm", + topspace = "2cm", + header = "1cm", + footer = "0cm", + height = "middle", + width = "middle", +} +\stoptyping + +This leave us to generate the document. There is a pitfall here: we need to use +the same random number for the exercises and the answers, so we freeze and +defrost it. Functions in the \type {commands} namespace implement functionality +that is used at the \TEX\ end but better can be done in \LUA\ than in \TEX\ macro +code. Of course these functions can also be used at the \LUA\ end. + +\starttyping +context.starttext() + + local n = 120 + + commands.freezerandomseed() + + ForLorien(n,10,10) + ForLorien(n,20,20) + ForLorien(n,30,30) + ForLorien(n,40,40) + ForLorien(n,50,50) + + commands.defrostrandomseed() + + ForLorien(n,10,10,true) + ForLorien(n,20,20,true) + ForLorien(n,30,30,true) + ForLorien(n,40,40,true) + ForLorien(n,50,50,true) + +context.stoptext() +\stoptyping + +\placefigure + [here] + [fig:lorien] + {Lorien's challenge.} + {\startcombination + {\externalfigure[cld-005.pdf][page=1,width=.45\textwidth,frame=on]} {exercises} + {\externalfigure[cld-005.pdf][page=6,width=.45\textwidth,frame=on]} {answers} + \stopcombination} + +A few pages of the result are shown in \in {figure} [fig:lorien]. In the +\CONTEXT\ distribution a more advanced version can be found in \type +{s-edu-01.cld} as I was also asked to generate multiplication and table +exercises. In the process I had to make sure that there were no duplicates on a +page as she complained that was not good. There a set of sheets is generated +with: + +\starttyping +moduledata.educational.arithematic.generate { + name = "Bram Otten", + fontsize = "12pt", + columns = 2, + run = { + { method = "bin_add_and_subtract", maxa = 8, maxb = 8 }, + { method = "bin_add_and_subtract", maxa = 16, maxb = 16 }, + { method = "bin_add_and_subtract", maxa = 32, maxb = 32 }, + { method = "bin_add_and_subtract", maxa = 64, maxb = 64 }, + { method = "bin_add_and_subtract", maxa = 128, maxb = 128 }, + }, +} +\stoptyping + +\stopsection + +\startsection[title=Interfacing] + +The fact that we can define functionality using \LUA\ code does not mean that we +should abandon the \TEX\ interface. As an example of this we use a relatively +simple module for typesetting morse code.\footnote {The real module is a bit +larger and can format verbose morse.} First we create a proper namespace: + +\starttyping + +moduledata.morse = moduledata.morse or { } +local morse = moduledata.morse +\stoptyping + +We will use a few helpers and create shortcuts for them. The first helper loops +over each \UTF\ character in a string. The other two helpers map a character onto +an uppercase (because morse only deals with uppercase) or onto an similar shaped +character (because morse only has a limited character set). + +\starttyping +local utfcharacters = string.utfcharacters +local ucchars, shchars = characters.ucchars, characters.shchars +\stoptyping + +The morse codes are stored in a table. + +\starttyping +local codes = { + + ["A"] = "·—", ["B"] = "—···", + ["C"] = "—·—·", ["D"] = "—··", + ["E"] = "·", ["F"] = "··—·", + ["G"] = "——·", ["H"] = "····", + ["I"] = "··", ["J"] = "·———", + ["K"] = "—·—", ["L"] = "·—··", + ["M"] = "——", ["N"] = "—·", + ["O"] = "———", ["P"] = "·——·", + ["Q"] = "——·—", ["R"] = "·—·", + ["S"] = "···", ["T"] = "—", + ["U"] = "··—", ["V"] = "···—", + ["W"] = "·——", ["X"] = "—··—", + ["Y"] = "—·——", ["Z"] = "——··", + + ["0"] = "—————", ["1"] = "·————", + ["2"] = "··———", ["3"] = "···——", + ["4"] = "····—", ["5"] = "·····", + ["6"] = "—····", ["7"] = "——···", + ["8"] = "———··", ["9"] = "————·", + + ["."] = "·—·—·—", [","] = "——··——", + [":"] = "———···", [";"] = "—·—·—", + ["?"] = "··——··", ["!"] = "—·—·——", + ["-"] = "—····—", ["/"] = "—··—· ", + ["("] = "—·——·", [")"] = "—·——·—", + ["="] = "—···—", ["@"] = "·——·—·", + ["'"] = "·————·", ['"'] = "·—··—·", + + ["À"] = "·——·—", + ["Å"] = "·——·—", + ["Ä"] = "·—·—", + ["Æ"] = "·—·—", + ["Ç"] = "—·—··", + ["É"] = "··—··", + ["È"] = "·—··—", + ["Ñ"] = "——·——", + ["Ö"] = "———·", + ["Ø"] = "———·", + ["Ü"] = "··——", + ["ß"] = "··· ···", + +} + +morse.codes = codes +\stoptyping + +As you can see, there are a few non \ASCII\ characters supported as well. There +will never be full \UNICODE\ support simply because morse is sort of obsolete. +Also, in order to support \UNICODE\ one could as well use the bits of \UTF\ +characters, although \unknown\ memorizing the whole \UNICODE\ table is not much +fun. + +We associate a metatable index function with this mapping. That way we can not +only conveniently deal with the casing, but also provide a fallback based on the +shape. Once found, we store the representation so that only one lookup is needed +per character. + +\starttyping +local function resolvemorse(t,k) + if k then + local u = ucchars[k] + local v = rawget(t,u) or rawget(t,shchars[u]) or false + t[k] = v + return v + else + return false + end +end + +setmetatable(codes, { __index = resolvemorse }) +\stoptyping + +Next comes some rendering code. As we can best do rendering at the \TEX\ end we +just use macros. + +\starttyping +local MorseBetweenWords = context.MorseBetweenWords +local MorseBetweenCharacters = context.MorseBetweenCharacters +local MorseLong = context.MorseLong +local MorseShort = context.MorseShort +local MorseSpace = context.MorseSpace +local MorseUnknown = context.MorseUnknown +\stoptyping + +The main function is not that complex. We need to keep track of spaces and +newlines. We have a nested loop because a fallback to shape can result in +multiple characters. + +\starttyping +function morse.tomorse(str) + local inmorse = false + for s in utfcharacters(str) do + local m = codes[s] + if m then + if inmorse then + MorseBetweenWords() + else + inmorse = true + end + local done = false + for m in utfcharacters(m) do + if done then + MorseBetweenCharacters() + else + done = true + end + if m == "·" then + MorseShort() + elseif m == "—" then + MorseLong() + elseif m == " " then + MorseBetweenCharacters() + end + end + inmorse = true + elseif s == "\n" or s == " " then + MorseSpace() + inmorse = false + else + if inmorse then + MorseBetweenWords() + else + inmorse = true + end + MorseUnknown(s) + end + end +end +\stoptyping + +We use this function in two additional functions. One typesets a file, the other +a table of available codes. + +\starttyping +function morse.filetomorse(name,verbose) + morse.tomorse(resolvers.loadtexfile(name),verbose) +end + +function morse.showtable() + context.starttabulate { "|l|l|" } + for k, v in table.sortedpairs(codes) do + context.NC() context(k) + context.NC() morse.tomorse(v,true) + context.NC() context.NR() + end + context.stoptabulate() +end +\stoptyping + +We're done with the \LUA\ code that we can either put in an external file or put +in the module file. The \TEX\ file has two parts. The typesetting macros that we +use at the \LUA\ end are defined first. These can be overloaded. + +\starttyping +\def\MorseShort + {\dontleavehmode + \vrule + width \MorseWidth + height \MorseHeight + depth \zeropoint + \relax} + +\def\MorseLong + {\dontleavehmode + \vrule + width 3\dimexpr\MorseWidth + height \MorseHeight + depth \zeropoint + \relax} + +\def\MorseBetweenCharacters + {\kern\MorseWidth} + +\def\MorseBetweenWords + {\hskip3\dimexpr\MorseWidth\relax} + +\def\MorseSpace + {\hskip7\dimexpr\MorseWidth\relax} + +\def\MorseUnknown#1 + {[\detokenize{#1}]} +\stoptyping + +The dimensions are stored in macros as well. Of course we could provide a proper +setup command, but it hardly makes sense. + +\starttyping +\def\MorseWidth {0.4em} +\def\MorseHeight{0.2em} +\stoptyping + +Finally we have arrived at the macros that interface to the \LUA\ functions. + +\starttyping +\def\MorseString#1{\ctxlua{moduledata.morse.tomorse(\!!bs#1\!!es)}} +\def\MorseFile #1{\ctxlua{moduledata.morse.filetomorse("#1")}} +\def\MorseTable {\ctxlua{moduledata.morse.showtable()}} +\stoptyping + +\startbuffer +\Morse{A more advanced solution would be to convert a node list. That +way we can deal with weird input.} +\stopbuffer + +A string is converted to morse with the first command. + +\typebuffer + +This shows up as: + +\startalignment[flushleft,tolerant]\getbuffer\stopalignment + +Reduction and uppercasing is demonstrated in the next example: + +\startbuffer +\MorseString{ÀÁÂÃÄÅàáâãäå} +\stopbuffer + +\typebuffer + +This gives: + +\startalignment[flushleft,tolerant]\getbuffer\stopalignment + +\stopsection + +\startsection[title=Using helpers] + +The next example shows a bit of \LPEG. On top of the standard functionality +a few additional functions are provided. Let's start with a pure \TEX\ +example: + +\startbuffer +\defineframed + [colored] + [foregroundcolor=red, + foregroundstyle=\underbar, + offset=.1ex, + location=low] +\stopbuffer + +\typebuffer \getbuffer + +\startbuffer +\processisolatedwords {\input ward \relax} \colored +\stopbuffer + +\typebuffer \blank \getbuffer \blank + +Because this processor macro operates at the \TEX\ end it has some limitations. +The content is collected in a very narrow box and from that a regular paragraph +is constructed. It is for this reason that no color is applied: the snippets that +end up in the box are already typeset. + +An alternative is to delegate the task to \LUA: + +\startbuffer +\startluacode +local function process(data) + + local words = lpeg.split(lpeg.patterns.spacer,data or "") + + for i=1,#words do + if i == 1 then + context.dontleavehmode() + else + context.space() + end + context.colored(words[i]) + end + +end + +process(io.loaddata(resolvers.findfile("ward.tex"))) +\stopluacode +\stopbuffer + +\typebuffer \blank \getbuffer \blank + +The function splits the loaded data into a table with individual words. We use a +splitter that splits on spacing tokens. The special case for \type {i = 1} makes +sure that we end up in horizontal mode (read: properly start a paragraph). This +time we do get color because the typesetting is done directly. Here is an +alternative implementation: + +\starttyping +local done = false + +local function reset() + done = false + return true +end + +local function apply(s) + if done then + context.space() + else + done = true + context.dontleavehmode() + end + context.colored(s) +end + +local splitter = lpeg.P(reset) + * lpeg.splitter(lpeg.patterns.spacer,apply) + +local function process(data) + lpeg.match(splitter,data) +end +\stoptyping + +This version is more efficient as it does not create an intermediate table. The +next one is comaprable: + +\starttyping +local function apply(s) + context.colored("%s ",s) +end + +local splitter lpeg.splitter(lpeg.patterns.spacer,apply) + +local function process(data) + context.dontleavevmode() + lpeg.match(splitter,data) + context.removeunwantedspaces() +end +\stoptyping + +\stopsection + +\startsection[title=Formatters] + +Sometimes can save a bit of work by using formatters. By default, the \type {context} +command, when called directly, applies a given formatter. But when called as table +this feature is lost because then we want to process non|-|strings as well. The next +example shows a way out: + +\startbuffer +context("the current emwidth is %p",\number\emwidth) +context.par() +context.formatted("the current emwidth is %p",\number\emwidth) +context.par() +context.bold(string.formatters["the current emwidth is %p"](\number\emwidth)) +context.par() +context.formatted.bold("the current emwidth is %p",\number\emwidth) +\stopbuffer + +The last one is the most interesting one here: in the subnamespace \type +{formatted} (watch the \type {d}) a format specification with extra arguments is +expected. + +\ctxluabuffer + +\stopsection + +\stopchapter + +\stopcomponent |