grph-rul.lua /size: 17 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['grph-rul'] = {
2    version   = 1.001,
3    comment   = "companion to grph-rul.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local tonumber, next, type = tonumber, next, type
10local concat = table.concat
11
12local attributes       = attributes
13local nodes            = nodes
14local context          = context
15
16local bpfactor         = number.dimenfactors.bp
17
18local nuts             = nodes.nuts
19local nodepool         = nuts.pool
20local userrule         = nuts.rules.userrule
21local outlinerule      = nuts.pool.outlinerule
22local ruleactions      = nuts.rules.ruleactions
23
24local setattrlist      = nuts.setattrlist
25local setattr          = nuts.setattr
26local tonode           = nuts.tonode
27
28local getattribute     = tex.getattribute
29local getwhd           = nuts.getwhd
30local setwhd           = nuts.setwhd
31
32local lefttoright_code = nodes.dirvalues.lefttoright
33
34local a_color          = attributes.private('color')
35local a_transparency   = attributes.private('transparency')
36local a_colormodel     = attributes.private('colormodel')
37
38local mpcolor          = attributes.colors.mpcolor
39
40local trace_mp         = false  trackers.register("rules.mp", function(v) trace_mp = v end)
41
42local report_mp        = logs.reporter("rules","mp")
43
44local floor            = math.floor
45local getrandom        = utilities.randomizer.get
46local formatters       = string.formatters
47
48-- This is very pdf specific. Maybe move some to lpdf-rul.lua some day.
49
50local pdfprint
51
52pdfprint = function(...) pdfprint = lpdf.print return pdfprint(...) end
53
54updaters.register("backend.update",function()
55    pdfprint = lpdf.print
56end)
57
58do
59
60    local simplemetapost = metapost.simple
61    local cachesize      = 0
62    local maxcachesize   = 256*1024
63    local cachethreshold = 1024
64    local caching        = false -- otherwise random issues so we need a dedicated randomizer first
65
66 -- local maxcachesize   = 8*1024
67 -- local cachethreshold = 1024/2
68
69    local cache = table.setmetatableindex(function(t,k)
70        local v = simplemetapost("rulefun",k) -- w, h, d
71        cachesize = cachesize + #v
72        if cachesize > maxcachesize then
73         -- print("old",cachesize)
74            for k, v in next, t do
75                local n = #v
76                if n > cachethreshold then
77                    t[k] = nil
78                    cachesize = cachesize - n
79                end
80            end
81         -- print("new",cachesize)
82        end
83     -- print(cachesize,maxcachesize,cachethreshold,#v)
84        t[k] = v
85        return v
86    end)
87
88    local replacer = utilities.templates.replacer
89
90    -- todo: RuleColor -> just string ?
91
92    local predefined = {
93        ["fake:word"] = replacer [[
94FakeWord(%width%,%height%,%depth%,%line%,%color%);
95        ]],
96        ["fake:rule"] = replacer[[
97%initializations%
98FakeRule(%width%,%height%,%depth%,%line%,%color%);
99        ]],
100        ["fake:rest"] = replacer [[
101RuleDirection := "%direction%" ;
102RuleOption := "%option%" ;
103RuleWidth := %width% ;
104RuleHeight := %height% ;
105RuleDepth := %depth% ;
106RuleH := %h% ;
107RuleV := %v% ;
108RuleThickness := %line% ;
109RuleFactor := %factor% ;
110RuleOffset := %offset% ;
111def RuleColor = %color% enddef ;
112%data%;
113        ]]
114    }
115
116    local initialized = false ;
117
118    ruleactions.mp = function(p,h,v,i,n)
119        local name = p.name or "fake:rest"
120        local code = (predefined[name] or predefined["fake:rest"]) {
121            data      = p.data or "",
122            width     = p.width * bpfactor,
123            height    = p.height * bpfactor,
124            depth     = p.depth * bpfactor,
125            factor    = (p.factor or 0) * bpfactor, -- needs checking
126            offset    = p.offset or 0,
127            line      = (p.line or 65536) * bpfactor,
128            color     = mpcolor(p.ma,p.ca,p.ta),
129            option    = p.option or "",
130            direction = p.direction or lefttoright_code,
131            h         = h * bpfactor,
132            v         = v * bpfactor,
133        }
134        if not initialized then
135            initialized = true
136            simplemetapost("rulefun",formatters["randomseed := %s;"](getrandom("rulefun",0,4095)))
137        end
138        -- we enable extensions but we could also consider to delegate colors
139        -- to the node finalizer
140        local pdf = caching and cache[code] or simplemetapost("rulefun",code,true)
141        if trace_mp then
142            report_mp("code: %s",code)
143            report_mp("pdf : %s",pdf)
144        end
145        if pdf and pdf ~= "" then
146            pdfprint("direct",pdf)
147        end
148    end
149
150end
151
152do
153
154    -- This is the old oval method that we keep it for compatibility reasons. Of
155    -- course one can use mp instead. It could be improved but at the cost of more
156    -- code than I'm willing to add for something hardly used.
157
158    local function round(p,kind)
159        local method = tonumber(p.corner) or 0
160        if method < 0 or method > 27 then
161            method = 0
162        end
163        local width  = p.width or 0
164        local height = p.height or 0
165        local depth  = p.depth or 0
166        local total  = height + depth
167        local radius = p.radius or 655360
168        local line   = p.line or 65536
169        local how    = (method > 8 or kind ~= "fill") and "S" or "f"
170        local half   = line / 2
171        local xmin   =            half  * bpfactor
172        local xmax   = ( width  - half) * bpfactor
173        local ymax   = ( height - half) * bpfactor
174        local ymin   = (-depth  + half) * bpfactor
175        local full   = ( radius + half)
176        local xxmin  =            full  * bpfactor
177        local xxmax  = ( width  - full) * bpfactor
178        local yymax  = ( height - full) * bpfactor
179        local yymin  = (-depth  + full) * bpfactor
180              line   =            line  * bpfactor
181        if xxmin <= xxmax and yymin <= yymax then
182            local list = nil
183            if method == 0 then
184                list = {
185                    "q", line, "w", xxmin, ymin, "m", xxmax, ymin, "l", xmax, ymin, xmax, yymin, "y",
186                    xmax, yymax, "l", xmax, ymax, xxmax, ymax, "y", xxmin, ymax, "l", xmin, ymax,
187                    xmin, yymax, "y", xmin, yymin, "l", xmin, ymin, xxmin, ymin, "y", "h", how, "Q",
188                }
189            elseif method == 1 then
190                list = {
191                    "q", line, "w", xxmin, ymin, "m", xxmax, ymin, "l", xmax, ymin, xmax, yymin, "y",
192                    xmax, ymax, "l", xmin, ymax, "l", xmin, yymin, "l", xmin, ymin, xxmin, ymin, "y",
193                    "h", how, "Q",
194                }
195            elseif method == 2 then
196                list = {
197                    "q", line, "w", xxmin, ymin, "m", xmax, ymin, "l", xmax, ymax, "l", xxmin, ymax,
198                    "l", xmin, ymax, xmin, yymax, "y", xmin, yymin, "l", xmin, ymin, xxmin, ymin,
199                    "y", "h", how, "Q",
200                }
201            elseif method == 3 then
202                list = {
203                    "q", line, "w", xmin, ymin, "m", xmax, ymin, "l", xmax, yymax, "l", xmax, ymax,
204                    xxmax, ymax, "y", xxmin, ymax, "l", xmin, ymax, xmin, yymax, "y", xmin, ymin,
205                    "l", "h", how, "Q",
206                }
207
208            elseif method == 4 then
209                list = {
210                    "q", line, "w", xmin, ymin, "m", xxmax, ymin, "l", xmax, ymin, xmax, yymin, "y",
211                    xmax, yymax, "l", xmax, ymax, xxmax, ymax, "y", xmin, ymax, "l", xmin, ymin, "l",
212                    "h", how, "Q",
213                }
214            elseif method == 5 then
215                list = {
216                    "q", line, "w", xmin, ymin, "m", xmax, ymin, "l", xmax, yymax, "l", xmax, ymax,
217                    xxmax, ymax, "y", xmin, ymax, "l", xmin, ymin, "l", "h", how, "Q",
218                }
219            elseif method == 6 then
220                list = {
221                    "q", line, "w", xmin, ymin, "m", xxmax, ymin, "l", xmax, ymin, xmax, yymin, "y",
222                    xmax, ymax, "l", xmin, ymax, "l", xmin, ymin, "l", "h", how, "Q",
223                }
224            elseif method == 7 then
225                list = {
226                    "q", line, "w", xxmin, ymin, "m", xmax, ymin, "l", xmax, ymax, "l", xmin, ymax,
227                    "l", xmin, yymin, "l", xmin, ymin, xxmin, ymin, "y", "h", how, "Q",
228                }
229            elseif method == 8 then
230                list = {
231                    "q", line, "w", xmin, ymin, "m", xmax, ymin, "l", xmax, ymax, "l", xxmin, ymax,
232                    "l", xmin, ymax, xmin, yymax, "y", xmin, ymin, "l", "h", how, "Q",
233                }
234            elseif method == 9 then
235                list = {
236                    "q", line, "w", xmin, ymax, "m", xmin, yymin, "l", xmin, ymin, xxmin, ymin, "y",
237                    xxmax, ymin, "l", xmax, ymin, xmax, yymin, "y", xmax, ymax, "l", how, "Q",
238                }
239            elseif method == 10 then
240                list = {
241                    "q", line, "w", xmax, ymax, "m", xxmin, ymax, "l", xmin, ymax, xmin, yymax, "y",
242                    xmin, yymin, "l", xmin, ymin, xxmin, ymin, "y", xmax, ymin, "l", how, "Q",
243                }
244            elseif method == 11 then
245                list = {
246                    "q", line, "w", xmax, ymin, "m", xmax, yymax, "l", xmax, ymax, xxmax, ymax, "y",
247                    xxmin, ymax, "l", xmin, ymax, xmin, yymax, "y", xmin, ymin, "l", how, "Q",
248                }
249            elseif method == 12 then
250                list = {
251                    "q", line, "w", xmin, ymax, "m", xxmax, ymax, "l", xmax, ymax, xmax, yymax, "y",
252                    xmax, yymin, "l", xmax, ymin, xxmax, ymin, "y", xmin, ymin, "l", how, "Q",
253                }
254            elseif method == 13 then
255                list = {
256                    "q", line, "w", xmin, ymax, "m", xxmax, ymax, "l", xmax, ymax, xmax, yymax, "y",
257                    xmax, ymin, "l", how, "Q",
258                }
259            elseif method == 14 then
260                list = {
261                    "q", line, "w", xmax, ymax, "m", xmax, yymin, "l", xmax, ymin, xxmax, ymin, "y",
262                    xmin, ymin, "l", how, "Q",
263                }
264            elseif method == 15 then
265                list = {
266                    "q", line, "w", xmax, ymin, "m", xxmin, ymin, "l", xmin, ymin, xmin, yymin, "y",
267                    xmin, ymax, "l", how, "Q",
268                }
269            elseif method == 16 then
270                list = {
271                    "q", line, "w", xmin, ymin, "m", xmin, yymax, "l", xmin, ymax, xxmin, ymax, "y",
272                    xmax, ymax, "l", how, "Q",
273                }
274            elseif method == 17 then
275                list = {
276                    "q", line, "w", xxmax, ymax, "m", xmax, ymax, xmax, yymax, "y", how, "Q",
277                }
278            elseif method == 18 then
279                list = {
280                    "q", line, "w", xmax, yymin, "m", xmax, ymin, xxmax, ymin, "y", how, "Q",
281                }
282            elseif method == 19 then
283                list = {
284                    "q", line, "w", xxmin, ymin, "m", xmin, ymin, xmin, yymin, "y", how, "Q",
285                }
286            elseif method == 20 then
287                list = {
288                    "q", line, "w", xmin, yymax, "m", xmin, ymax, xxmin, ymax, "y", how, "Q",
289                }
290            elseif method == 21 then
291                list = {
292                    "q", line, "w", xxmax, ymax, "m", xmax, ymax, xmax, yymax, "y", xmin, yymax, "m",
293                    xmin, ymax, xxmin, ymax, "y", how, "Q",
294                }
295            elseif method == 22 then
296                list = {
297                    "q", line, "w", xxmax, ymax, "m", xmax, ymax, xmax, yymax, "y", xmax, yymin, "m",
298                    xmax, ymin, xxmax, ymin, "y", how, "Q",
299                }
300            elseif method == 23 then
301                list = {
302                    "q", line, "w", xmax, yymin, "m", xmax, ymin, xxmax, ymin, "y", xxmin, ymin, "m",
303                    xmin, ymin, xmin, yymin, "y", how, "Q",
304                }
305            elseif method == 24 then
306                list = {
307                    "q", line, "w", xxmin, ymin, "m", xmin, ymin, xmin, yymin, "y", xmin, yymax, "m",
308                    xmin, ymax, xxmin, ymax, "y", how, "Q",
309                }
310            elseif method == 25 then
311                list = {
312                    "q", line, "w", xxmax, ymax, "m", xmax, ymax, xmax, yymax, "y", xmax, yymin, "m",
313                    xmax, ymin, xxmax, ymin, "y", xxmin, ymin, "m", xmin, ymin, xmin, yymin, "y",
314                    xmin, yymax, "m", xmin, ymax, xxmin, ymax, "y", how, "Q",
315                }
316            elseif method == 26 then
317                list = {
318                    "q", line, "w", xmax, yymin, "m", xmax, ymin, xxmax, ymin, "y", xmin, yymax, "m",
319                    xmin, ymax, xxmin, ymax, "y", how, "Q",
320                }
321
322            elseif method == 27 then
323                list = {
324                    "q", line, "w", xxmax, ymax, "m", xmax, ymax, xmax, yymax, "y", xxmin, ymin, "m",
325                    xmin, ymin, xmin, yymin, "y", how, "Q",
326                }
327            end
328            pdfprint("direct",concat(list," "))
329        end
330    end
331
332    local f_rectangle = formatters["%.6N w %.6N %.6N %.6N %.6N re %s"]
333    local f_baselined = formatters["%.6N w %.6N %.6N %.6N %.6N re s %.6N %.6N m %.6N %.6N l s"]
334    local f_dashlined = formatters["%.6N w %.6N %.6N %.6N %.6N re s [%.6N %.6N] 2 d %.6N %.6N m %.6N %.6N l s"]
335    local f_radtangle = formatters[
336[[%.6N w %.6N %.6N m
337%.6N %.6N l %.6N %.6N %.6N %.6N y
338%.6N %.6N l %.6N %.6N %.6N %.6N y
339%.6N %.6N l %.6N %.6N %.6N %.6N y
340%.6N %.6N l %.6N %.6N %.6N %.6N y
341h %s]]
342        ]
343
344    ruleactions.fill = function(p,h,v,i,n)
345        if p.corner then
346            return round(p,i)
347        else
348            local l = (p.line or 65536)*bpfactor
349            local r = p and (p.radius or 0)*bpfactor or 0
350            local w = h * bpfactor
351            local h = v * bpfactor
352            local m = nil
353            local t = i == "fill" and "f" or "s"
354            local o = l / 2
355            if r > 0 then
356                w = w - o
357                h = h - o
358                m = f_radtangle(l, r,o, w-r,o, w,o,w,r, w,h-r, w,h,w-r,h, r,h, o,h,o,h-r, o,r, o,o,r,o, t)
359            else
360                w = w - l
361                h = h - l
362                m = f_rectangle(l,o,o,w,h,t)
363            end
364            pdfprint("direct",m)
365        end
366    end
367
368    ruleactions.draw   = ruleactions.fill
369    ruleactions.stroke = ruleactions.fill
370
371    ruleactions.box = function(p,h,v,i,n)
372        local w, h, d = getwhd(n)
373        local line = p.line or 65536
374        local l = line *bpfactor
375        local w = w * bpfactor
376        local h = h * bpfactor
377        local d = d * bpfactor
378        local o = l / 2
379        if (d >= 0 and h >= 0) or (d <= 0 and h <= 0) then
380            local dashed = tonumber(p.dashed)
381            if dashed and dashed > 5*line then
382                dashed = dashed * bpfactor
383                local delta = (w - 2*dashed*floor(w/(2*dashed)))/2
384                pdfprint("direct",f_dashlined(l,o,o,w-l,h+d-l,dashed,dashed,delta,d,w-delta,d))
385            else
386                pdfprint("direct",f_baselined(l,o,o,w-l,h+d-l,0,d,w,d))
387            end
388        else
389            pdfprint("direct",f_rectangle(l,o,o,w-l,h+d-l))
390        end
391    end
392
393end
394
395interfaces.implement {
396    name      = "frule",
397    arguments = { {
398        { "width",  "dimension" },
399        { "height", "dimension" },
400        { "depth",  "dimension" },
401        { "radius", "dimension" },
402        { "line",   "dimension" },
403        { "type",   "string" },
404        { "data",   "string" },
405        { "name",   "string" },
406        { "radius", "dimension" },
407        { "corner", "string" },
408    } } ,
409    actions = function(t)
410        local rule = userrule(t)
411        if t.type == "mp" then
412            t.ma = getattribute(a_colormodel) or 1
413            t.ca = getattribute(a_color)
414            t.ta = getattribute(a_transparency)
415        else
416            setattrlist(rule,true)
417        end
418        context(tonode(rule)) -- will become context.nodes.flush
419    end
420}
421
422interfaces.implement {
423    name      = "outlinerule",
424    public    = true,
425    protected = true,
426    arguments = { {
427        { "width",  "dimension" },
428        { "height", "dimension" },
429        { "depth",  "dimension" },
430        { "line",   "dimension" },
431    } } ,
432    actions = function(t)
433        local rule = outlinerule(t.width,t.height,t.depth,t.line)
434        setattrlist(rule,true)
435        context(tonode(rule)) -- will become context.nodes.flush
436    end
437}
438
439interfaces.implement {
440    name      = "framedoutline",
441    arguments = { "dimension", "dimension", "dimension", "dimension" },
442    actions   = function(w,h,d,l)
443        local rule = outlinerule(w,h,d,l)
444        setattrlist(rule,true)
445        context(tonode(rule)) -- will become context.nodes.flush
446    end
447}
448
449interfaces.implement {
450    name      = "fakeword",
451    arguments = { {
452        { "factor", "dimension" },
453        { "name",   "string" }, -- can be type
454        { "min",    "dimension" },
455        { "max",    "dimension" },
456        { "n",      "integer" },
457    } } ,
458    actions = function(t)
459        local factor = t.factor or 0
460        local amount = getrandom("fakeword",t.min,t.max)
461        local rule   = userrule {
462            height = 1.25*factor,
463            depth  = 0.25*factor,
464            width  = floor(amount/10000) * 10000,
465            line   = 0.10*factor,
466            ma     = getattribute(a_colormodel) or 1,
467            ca     = getattribute(a_color),
468            ta     = getattribute(a_transparency),
469            type   = "mp",
470            name   = t.name,
471        }
472        setattrlist(rule,true)
473        context(tonode(rule))
474    end
475}
476