meta-pdf.lua /size: 19 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['meta-pdf'] = {
2    version   = 1.001,
3    comment   = "companion to meta-pdf.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
9-- This module is not used in practice but we keep it around for historic
10-- reasons.
11
12-- Finally we used an optimized version. The test code can be found in
13-- meta-pdh.lua but since we no longer want to overload functione we use
14-- more locals now. This module keeps changing as it is also a testbed.
15--
16-- We can make it even more efficient if needed, but as we don't use this
17-- code often in \MKIV\ it makes no sense.
18
19local tonumber = tonumber
20local concat, unpack = table.concat, table.unpack
21local gsub, find, byte, gmatch, match = string.gsub, string.find, string.byte, string.gmatch, string.match
22local lpegmatch = lpeg.match
23local round = math.round
24local formatters, format = string.formatters, string.format
25
26local mplib                     = mplib
27local metapost                  = metapost
28local lpdf                      = lpdf
29local context                   = context
30
31local report_mptopdf            = logs.reporter("graphics","mptopdf")
32
33local texgetattribute           = tex.getattribute
34
35local pdfrgbcode                = lpdf.rgbcode
36local pdfcmykcode               = lpdf.cmykcode
37local pdfgraycode               = lpdf.graycode
38local pdfspotcode               = lpdf.spotcode
39local pdftransparencycode       = lpdf.transparencycode
40local pdffinishtransparencycode = lpdf.finishtransparencycode
41
42metapost.mptopdf = metapost.mptopdf or { }
43local mptopdf    = metapost.mptopdf
44
45mptopdf.nofconverted = 0
46
47local f_translate = formatters["1 0 0 0 1 %.6N %.6N cm"]
48local f_concat    = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
49
50local m_path, m_stack, m_texts, m_version, m_date, m_shortcuts = { }, { }, { }, 0, 0, false
51
52local m_stack_close, m_stack_path, m_stack_concat = false, { }, nil
53local extra_path_data, ignore_path = nil, false
54local specials = { }
55
56local function resetpath()
57    m_stack_close, m_stack_path, m_stack_concat = false, { }, nil
58end
59
60local function resetall()
61    m_path, m_stack, m_texts, m_version, m_shortcuts = { }, { }, { }, 0, false
62    extra_path_data, ignore_path = nil, false
63    specials = { }
64    resetpath()
65end
66
67resetall()
68
69local pdfcode = context.pdfliteral
70
71local function mpscode(str)
72    if ignore_path then
73        pdfcode("h W n")
74        if extra_path_data then
75            pdfcode(extra_path_data)
76            extra_path_data = nil
77        end
78        ignore_path = false
79    else
80        pdfcode(str)
81    end
82end
83
84-- auxiliary functions
85
86local function flushconcat()
87    if m_stack_concat then
88        mpscode(f_concat(unpack(m_stack_concat)))
89        m_stack_concat = nil
90    end
91end
92
93local function flushpath(cmd)
94    if #m_stack_path > 0 then
95        local path = { }
96        if m_stack_concat then
97            local sx = m_stack_concat[1]
98            local sy = m_stack_concat[4]
99            local rx = m_stack_concat[2]
100            local ry = m_stack_concat[3]
101            local tx = m_stack_concat[5]
102            local ty = m_stack_concat[6]
103            local d = (sx*sy) - (rx*ry)
104            for k=1,#m_stack_path do
105                local v  = m_stack_path[k]
106                local px = v[1]
107                local py = v[2]
108                v[1] = (sy*(px-tx)-ry*(py-ty))/d
109                v[2] = (sx*(py-ty)-rx*(px-tx))/d
110                if #v == 7 then
111                    px = v[3]
112                    py = v[4]
113                    v[3] = (sy*(px-tx)-ry*(py-ty))/d
114                    v[4] = (sx*(py-ty)-rx*(px-tx))/d
115                    px = v[5]
116                    py = v[6]
117                    v[5] = (sy*(px-tx)-ry*(py-ty))/d
118                    v[6] = (sx*(py-ty)-rx*(px-tx))/d
119                end
120                path[k] = concat(v," ")
121            end
122        else
123            for k=1,#m_stack_path do
124                path[k] = concat(m_stack_path[k]," ")
125            end
126        end
127        flushconcat()
128        pdfcode(concat(path," "))
129        if m_stack_close then
130            mpscode("h " .. cmd)
131        else
132            mpscode(cmd)
133        end
134    end
135    resetpath()
136end
137
138-- mp interface
139
140local mps = { }
141
142function mps.creator(a, b, c)
143    m_version = tonumber(b)
144end
145
146function mps.creationdate(a)
147    m_date = a
148end
149
150function mps.newpath()
151    m_stack_path = { }
152end
153
154function mps.boundingbox(llx, lly, urx, ury)
155    context.setMPboundingbox(llx,lly,urx,ury)
156end
157
158function mps.moveto(x,y)
159    m_stack_path[#m_stack_path+1] = { x, y, "m" }
160end
161
162function mps.curveto(ax, ay, bx, by, cx, cy)
163    m_stack_path[#m_stack_path+1] = { ax, ay, bx, by, cx, cy, "c" }
164end
165
166function mps.lineto(x,y)
167    m_stack_path[#m_stack_path+1] = { x, y, "l" }
168end
169
170function mps.rlineto(x,y)
171    local dx = 0
172    local dy = 0
173    local topofstack = #m_stack_path
174    if topofstack > 0 then
175        local msp = m_stack_path[topofstack]
176        dx = msp[1]
177        dy = msp[2]
178    end
179    m_stack_path[topofstack+1] = { dx, dy, "l" }
180end
181
182function mps.translate(tx,ty)
183    mpscode(f_translate(tx,ty))
184end
185
186function mps.scale(sx,sy)
187    m_stack_concat = { sx, 0, 0, sy, 0, 0 }
188end
189
190function mps.concat(sx, rx, ry, sy, tx, ty)
191    m_stack_concat = { sx, rx, ry, sy, tx, ty }
192end
193
194function mps.setlinejoin(d)
195    mpscode(d .. " j")
196end
197
198function mps.setlinecap(d)
199    mpscode(d .. " J")
200end
201
202function mps.setmiterlimit(d)
203    mpscode(d .. " M")
204end
205
206function mps.gsave()
207    mpscode("q")
208end
209
210function mps.grestore()
211    mpscode("Q")
212end
213
214function mps.setdash(...) -- can be made faster, operate on t = { ... }
215    local n = select("#",...)
216    mpscode("[" .. concat({...}," ",1,n-1) .. "] " .. select(n,...) .. " d")
217 -- mpscode("[" .. concat({select(1,n-1)}," ") .. "] " .. select(n,...) .. " d")
218end
219
220function mps.resetdash()
221    mpscode("[ ] 0 d")
222end
223
224function mps.setlinewidth(d)
225    mpscode(d .. " w")
226end
227
228function mps.closepath()
229    m_stack_close = true
230end
231
232function mps.fill()
233    flushpath('f')
234end
235
236function mps.stroke()
237    flushpath('S')
238end
239
240function mps.both()
241    flushpath('B')
242end
243
244function mps.clip()
245    flushpath('W n')
246end
247
248function mps.textext(font, scale, str) -- old parser
249    local dx = 0
250    local dy = 0
251    if #m_stack_path > 0 then
252        dx, dy = m_stack_path[1][1], m_stack_path[1][2]
253    end
254    flushconcat()
255    context.MPtextext(font,scale,str,dx,dy)
256    resetpath()
257end
258
259local handlers = { }
260
261handlers[1] = function(s)
262    pdfcode(pdffinishtransparencycode())
263    pdfcode(pdfcmykcode(mps.colormodel,s[3],s[4],s[5],s[6]))
264end
265handlers[2] = function(s)
266    pdfcode(pdffinishtransparencycode())
267    pdfcode(pdfspotcode(mps.colormodel,s[3],s[4],s[5],s[6]))
268end
269handlers[3] = function(s)
270    pdfcode(pdfrgbcode(mps.colormodel,s[4],s[5],s[6]))
271    pdfcode(pdftransparencycode(s[2],s[3]))
272end
273handlers[4] = function(s)
274    pdfcode(pdfcmykcode(mps.colormodel,s[4],s[5],s[6],s[7]))
275    pdfcode(pdftransparencycode(s[2],s[3]))
276end
277handlers[5] = function(s)
278    pdfcode(pdfspotcode(mps.colormodel,s[4],s[5],s[6],s[7]))
279    pdfcode(pdftransparencycode(s[2],s[3]))
280end
281
282-- todo: color conversion
283
284local nofshades, tn = 0, tonumber
285
286local function linearshade(colorspace,domain,ca,cb,coordinates)
287    pdfcode(pdffinishtransparencycode())
288    nofshades = nofshades + 1
289    local name = formatters["MpsSh%s"](nofshades)
290    lpdf.linearshade(name,domain,ca,cb,1,colorspace,coordinates)
291    extra_path_data, ignore_path = formatters["/%s sh Q"](name), true
292    pdfcode("q /Pattern cs")
293end
294
295local function circularshade(colorspace,domain,ca,cb,coordinates)
296    pdfcode(pdffinishtransparencycode())
297    nofshades = nofshades + 1
298    local name = formatters["MpsSh%s"](nofshades)
299    lpdf.circularshade(name,domain,ca,cb,1,colorspace,coordinates)
300    extra_path_data, ignore_path = formatters["/%s sh Q"](name), true
301    pdfcode("q /Pattern cs")
302end
303
304handlers[30] = function(s)
305    linearshade("DeviceRGB", { tn(s[ 2]), tn(s[ 3]) },
306        { tn(s[ 5]), tn(s[ 6]), tn(s[ 7]) }, { tn(s[10]), tn(s[11]), tn(s[12]) },
307        { tn(s[ 8]), tn(s[ 9]), tn(s[13]), tn(s[14]) } )
308end
309
310handlers[31] = function(s)
311    circularshade("DeviceRGB", { tn(s[ 2]), tn(s[ 3]) },
312        { tn(s[ 5]), tn(s[ 6]), tn(s[ 7]) }, { tn(s[11]), tn(s[12]), tn(s[13]) },
313        { tn(s[ 8]), tn(s[ 9]), tn(s[10]), tn(s[14]), tn(s[15]), tn(s[16]) } )
314end
315
316handlers[32] = function(s)
317    linearshade("DeviceCMYK", { tn(s[ 2]), tn(s[ 3]) },
318        { tn(s[ 5]), tn(s[ 6]), tn(s[ 7]), tn(s[ 8]) }, { tn(s[11]), tn(s[12]), tn(s[13]), tn(s[14]) },
319        { tn(s[ 9]), tn(s[10]), tn(s[15]), tn(s[16]) } )
320end
321
322handlers[33] = function(s)
323    circularshade("DeviceCMYK", { tn(s[ 2]), tn(s[ 3]) },
324        { tn(s[ 5]), tn(s[ 6]), tn(s[ 7]), tn(s[ 8]) }, { tn(s[12]), tn(s[13]), tn(s[14]), tn(s[15]) },
325        { tn(s[ 9]), tn(s[10]), tn(s[11]), tn(s[16]), tn(s[17]), tn(s[18]) } )
326end
327
328handlers[34] = function(s) -- todo (after further cleanup)
329    linearshade("DeviceGray", { tn(s[ 2]), tn(s[ 3]) }, { 0 }, { 1 }, { tn(s[9]), tn(s[10]), tn(s[15]), tn(s[16]) } )
330end
331
332handlers[35] = function(s) -- todo (after further cleanup)
333    circularshade("DeviceGray",  { tn(s[ 2]), tn(s[ 3]) }, { 0 }, { 1 }, { tn(s[9]), tn(s[10]), tn(s[15]), tn(s[16]) } )
334end
335
336-- not supported in mkiv , use mplib instead
337
338handlers[10] = function() report_mptopdf("skipping special %s",10) end
339handlers[20] = function() report_mptopdf("skipping special %s",20) end
340handlers[50] = function() report_mptopdf("skipping special %s",50) end
341
342--end of not supported
343
344function mps.setrgbcolor(r,g,b) -- extra check
345    r = tonumber(r) -- needed when we use lpeg
346    g = tonumber(g) -- needed when we use lpeg
347    b = tonumber(b) -- needed when we use lpeg
348    if r == 0.0123 and g < 0.1 then
349        g = round(g*10000)
350        b = round(b*10000)
351        local s = specials[b]
352        local h = round(s[#s])
353        local handler = handlers[h]
354        if handler then
355            handler(s)
356        else
357            report_mptopdf("unknown special handler %s (1)",h)
358        end
359    elseif r == 0.123 and g < 0.1 then
360        g = round(g*1000)
361        b = round(b*1000)
362        local s = specials[b]
363        local h = round(s[#s])
364        local handler = handlers[h]
365        if handler then
366            handler(s)
367        else
368            report_mptopdf("unknown special handler %s (2)",h)
369        end
370    else
371        pdfcode(pdffinishtransparencycode())
372        pdfcode(pdfrgbcode(mps.colormodel,r,g,b))
373    end
374end
375
376function mps.setcmykcolor(c,m,y,k)
377    pdfcode(pdffinishtransparencycode())
378    pdfcode(pdfcmykcode(mps.colormodel,c,m,y,k))
379end
380
381function mps.setgray(s)
382    pdfcode(pdffinishtransparencycode())
383    pdfcode(pdfgraycode(mps.colormodel,s))
384end
385
386function mps.specials(version,signal,factor) -- 2.0 123 1000
387end
388
389function mps.special(...) -- 7 1 0.5 1 0 0 1 3
390    local t = { ... }
391    local n = tonumber(t[#t-1])
392    specials[n] = t
393end
394
395function mps.begindata()
396end
397
398function mps.enddata()
399end
400
401function mps.showpage()
402end
403
404-- lpeg parser
405
406-- The lpeg based parser is rather optimized for the kind of output
407-- that MetaPost produces. It's my first real lpeg code, which may
408-- show. Because the parser binds to functions, we define it last.
409
410local lpegP, lpegR, lpegS, lpegC, lpegCc, lpegCs = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cs
411
412local digit    = lpegR("09")
413local eol      = lpegS('\r\n')^1
414local sp       = lpegP(' ')^1
415local space    = lpegS(' \r\n')^1
416local number   = lpegS('0123456789.-+')^1
417local nonspace = lpegP(1-lpegS(' \r\n'))^1
418
419local spec  = digit^2 * lpegP("::::") * digit^2
420local text  = lpegCc("{") * (
421        lpegP("\\") * ( (digit * digit * digit) / function(n) return "c" .. tonumber(n,8) end) +
422                         lpegP(" ")             / function(n) return "\\c32" end + -- never in new mp
423                         lpegP(1)               / function(n) return "\\c" .. byte(n) end
424    ) * lpegCc("}")
425local package = lpegCs(spec + text^0)
426
427function mps.fshow(str,font,scale) -- lpeg parser
428    mps.textext(font,scale,lpegmatch(package,str))
429end
430
431----- cnumber = lpegC(number)
432local cnumber = number/tonumber -- we now expect numbers (feeds into %F)
433local cstring = lpegC(nonspace)
434
435local specials           = (lpegP("%%MetaPostSpecials:") * sp * (cstring * sp^0)^0 * eol) / mps.specials
436local special            = (lpegP("%%MetaPostSpecial:")  * sp * (cstring * sp^0)^0 * eol) / mps.special
437local boundingbox        = (lpegP("%%BoundingBox:")      * sp * (cnumber * sp^0)^4 * eol) / mps.boundingbox
438local highresboundingbox = (lpegP("%%HiResBoundingBox:") * sp * (cnumber * sp^0)^4 * eol) / mps.boundingbox
439
440local setup              = lpegP("%%BeginSetup")  * (1 - lpegP("%%EndSetup") )^1
441local prolog             = lpegP("%%BeginProlog") * (1 - lpegP("%%EndProlog"))^1
442local comment            = lpegP('%')^1 * (1 - eol)^1
443
444local curveto            = ((cnumber * sp)^6 * lpegP("curveto")            ) / mps.curveto
445local lineto             = ((cnumber * sp)^2 * lpegP("lineto")             ) / mps.lineto
446local rlineto            = ((cnumber * sp)^2 * lpegP("rlineto")            ) / mps.rlineto
447local moveto             = ((cnumber * sp)^2 * lpegP("moveto")             ) / mps.moveto
448local setrgbcolor        = ((cnumber * sp)^3 * lpegP("setrgbcolor")        ) / mps.setrgbcolor
449local setcmykcolor       = ((cnumber * sp)^4 * lpegP("setcmykcolor")       ) / mps.setcmykcolor
450local setgray            = ((cnumber * sp)^1 * lpegP("setgray")            ) / mps.setgray
451local newpath            = (                   lpegP("newpath")            ) / mps.newpath
452local closepath          = (                   lpegP("closepath")          ) / mps.closepath
453local fill               = (                   lpegP("fill")               ) / mps.fill
454local stroke             = (                   lpegP("stroke")             ) / mps.stroke
455local clip               = (                   lpegP("clip")               ) / mps.clip
456local both               = (                   lpegP("gsave fill grestore")) / mps.both
457local showpage           = (                   lpegP("showpage")           )
458local setlinejoin        = ((cnumber * sp)^1 * lpegP("setlinejoin")        ) / mps.setlinejoin
459local setlinecap         = ((cnumber * sp)^1 * lpegP("setlinecap")         ) / mps.setlinecap
460local setmiterlimit      = ((cnumber * sp)^1 * lpegP("setmiterlimit")      ) / mps.setmiterlimit
461local gsave              = (                   lpegP("gsave")              ) / mps.gsave
462local grestore           = (                   lpegP("grestore")           ) / mps.grestore
463
464local setdash            = (lpegP("[") * (cnumber * sp^0)^0 * lpegP("]") * sp * cnumber * sp * lpegP("setdash")) / mps.setdash
465local concat             = (lpegP("[") * (cnumber * sp^0)^6 * lpegP("]")                * sp * lpegP("concat") ) / mps.concat
466local scale              = (             (cnumber * sp^0)^6                             * sp * lpegP("concat") ) / mps.concat
467
468local fshow              = (lpegP("(") * lpegC((1-lpegP(")"))^1) * lpegP(")") * space * cstring * space * cnumber * space * lpegP("fshow")) / mps.fshow
469local fshow              = (lpegP("(") * lpegCs( ( lpegP("\\(")/"\\050" + lpegP("\\)")/"\\051" + (1-lpegP(")")) )^1 )
470                            * lpegP(")") * space * cstring * space * cnumber * space * lpegP("fshow")) / mps.fshow
471
472local setlinewidth_x     = (lpegP("0") * sp * cnumber * sp * lpegP("dtransform truncate idtransform setlinewidth pop")) / mps.setlinewidth
473local setlinewidth_y     = (cnumber * sp * lpegP("0 dtransform exch truncate exch idtransform pop setlinewidth")  ) / mps.setlinewidth
474
475local c   = ((cnumber * sp)^6 * lpegP("c")  ) / mps.curveto -- ^6 very inefficient, ^1 ok too
476local l   = ((cnumber * sp)^2 * lpegP("l")  ) / mps.lineto
477local r   = ((cnumber * sp)^2 * lpegP("r")  ) / mps.rlineto
478local m   = ((cnumber * sp)^2 * lpegP("m")  ) / mps.moveto
479local vlw = ((cnumber * sp)^1 * lpegP("vlw")) / mps.setlinewidth
480local hlw = ((cnumber * sp)^1 * lpegP("hlw")) / mps.setlinewidth
481
482local R   = ((cnumber * sp)^3 * lpegP("R")  ) / mps.setrgbcolor
483local C   = ((cnumber * sp)^4 * lpegP("C")  ) / mps.setcmykcolor
484local G   = ((cnumber * sp)^1 * lpegP("G")  ) / mps.setgray
485
486local lj  = ((cnumber * sp)^1 * lpegP("lj") ) / mps.setlinejoin
487local ml  = ((cnumber * sp)^1 * lpegP("ml") ) / mps.setmiterlimit
488local lc  = ((cnumber * sp)^1 * lpegP("lc") ) / mps.setlinecap
489
490local n   = lpegP("n") / mps.newpath
491local p   = lpegP("p") / mps.closepath
492local S   = lpegP("S") / mps.stroke
493local F   = lpegP("F") / mps.fill
494local B   = lpegP("B") / mps.both
495local W   = lpegP("W") / mps.clip
496local P   = lpegP("P") / mps.showpage
497
498local q   = lpegP("q") / mps.gsave
499local Q   = lpegP("Q") / mps.grestore
500
501local sd  = (lpegP("[") * (cnumber * sp^0)^0 * lpegP("]") * sp * cnumber * sp * lpegP("sd")) / mps.setdash
502local rd  = (                                                                   lpegP("rd")) / mps.resetdash
503
504local s   = (             (cnumber * sp^0)^2                   * lpegP("s") ) / mps.scale
505local t   = (lpegP("[") * (cnumber * sp^0)^6 * lpegP("]") * sp * lpegP("t") ) / mps.concat
506
507-- experimental
508
509local preamble = (
510    prolog + setup +
511    boundingbox + highresboundingbox + specials + special +
512    comment
513)
514
515local procset = (
516    lj + ml + lc +
517    c + l + m + n + p + r +
518    R + C + G +
519    S + F + B + W +
520    vlw + hlw +
521    Q + q +
522    sd + rd +
523    t + s +
524    fshow +
525    P
526)
527
528local verbose = (
529    curveto + lineto + moveto + newpath + closepath + rlineto +
530    setrgbcolor + setcmykcolor + setgray +
531    setlinejoin + setmiterlimit + setlinecap +
532    stroke + fill + clip + both +
533    setlinewidth_x + setlinewidth_y +
534    gsave + grestore +
535    concat + scale +
536    fshow +
537    setdash + -- no resetdash
538    showpage
539)
540
541-- order matters in terms of speed / we could check for procset first
542
543local captures_old = ( space + verbose + preamble           )^0
544local captures_new = ( space + verbose + procset + preamble )^0
545
546local function parse(m_data)
547    if find(m_data,"%%BeginResource: procset mpost",1,true) then
548     -- report_mptopdf("using sparse scanner, case 1")
549        lpegmatch(captures_new,m_data)
550    elseif find(m_data,"%%%%BeginProlog%s*%S+(.-)%%%%EndProlog") then
551     -- report_mptopdf("using sparse scanner, case 2")
552        lpegmatch(captures_new,m_data)
553    else
554     -- report_mptopdf("using verbose ps scanner")
555        lpegmatch(captures_old,m_data)
556    end
557end
558
559-- main converter
560
561local a_colormodel = attributes.private('colormodel')
562
563function mptopdf.convertmpstopdf(name)
564    resetall()
565    local ok, m_data, n = resolvers.loadbinfile(name, 'tex') -- we need a binary load !
566    if ok then
567        mps.colormodel = texgetattribute(a_colormodel)
568        statistics.starttiming(mptopdf)
569        mptopdf.nofconverted = mptopdf.nofconverted + 1
570        pdfcode(formatters["\\letterpercent\\space mptopdf begin: n=%s, file=%s"](mptopdf.nofconverted,file.basename(name)))
571        pdfcode("q 1 0 0 1 0 0 cm")
572        parse(m_data)
573        pdfcode(pdffinishtransparencycode())
574        pdfcode("Q")
575        pdfcode("\\letterpercent\\space mptopdf end")
576        resetall()
577        statistics.stoptiming(mptopdf)
578    else
579        report_mptopdf("file %a not found",name)
580    end
581end
582
583-- status info
584
585statistics.register("mps conversion time",function()
586    local n = mptopdf.nofconverted
587    if n > 0 then
588        return format("%s seconds, %s conversions", statistics.elapsedtime(mptopdf),n)
589    else
590        return nil
591    end
592end)
593
594-- interface
595
596interfaces.implement {
597    name      = "convertmpstopdf",
598    arguments = "string",
599    actions   = mptopdf.convertmpstopdf
600}
601