diff options
author | Marius <mariausol@gmail.com> | 2010-07-04 15:32:09 +0300 |
---|---|---|
committer | Marius <mariausol@gmail.com> | 2010-07-04 15:32:09 +0300 |
commit | 85b7bc695629926641c7cb752fd478adfdf374f3 (patch) | |
tree | 80293f5aaa7b95a500a78392c39688d8ee7a32fc /tex/context/base/mlib-pps.lua | |
download | context-85b7bc695629926641c7cb752fd478adfdf374f3.tar.gz |
stable 2010-05-24 13:10
Diffstat (limited to 'tex/context/base/mlib-pps.lua')
-rw-r--r-- | tex/context/base/mlib-pps.lua | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/tex/context/base/mlib-pps.lua b/tex/context/base/mlib-pps.lua new file mode 100644 index 000000000..8b36660d3 --- /dev/null +++ b/tex/context/base/mlib-pps.lua @@ -0,0 +1,897 @@ +if not modules then modules = { } end modules ['mlib-pps'] = { -- prescript, postscripts and specials + version = 1.001, + comment = "companion to mlib-ctx.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files", +} + +-- current limitation: if we have textext as well as a special color then due to +-- prescript/postscript overload we can have problems +-- +-- todo: report max textexts + +local format, gmatch, concat, round, match = string.format, string.gmatch, table.concat, math.round, string.match +local sprint = tex.sprint +local tonumber, type = tonumber, type +local lpegmatch = lpeg.match +local texbox = tex.box +local copy_list = node.copy_list + +local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming + +local ctxcatcodes = tex.ctxcatcodes + +local trace_textexts = false trackers.register("metapost.textexts", function(v) trace_textexts = v end) + +colors = colors or { } + +local rgbtocmyk = colors.rgbtocmyk or function() return 0,0,0,1 end +local cmyktorgb = colors.cmyktorgb or function() return 0,0,0 end +local rgbtogray = colors.rgbtogray or function() return 0 end +local cmyktogray = colors.cmyktogray or function() return 0 end + +metapost = metapost or { } +metapost.specials = metapost.specials or { } +metapost.specials.data = metapost.specials.data or { } +metapost.externals = metapost.externals or { n = 0 } + +local data = metapost.specials.data + +local colordata = { {}, {}, {}, {}, {} } + +--~ (r,g,b) => cmyk : r=123 g= 1 b=hash +--~ => spot : r=123 g= 2 b=hash +--~ => transparent rgb : r=123 g= 3 b=hash +--~ => transparent cmyk : r=123 g= 4 b=hash +--~ => transparent spot : r=123 g= 5 b=hash +--~ => rest : r=123 g=n>10 b=whatever + +local nooutercolor = "0 g 0 G" +local nooutertransparency = "/Tr0 gs" -- only when set +local outercolormode = 0 +local outercolor = nooutercolor +local outertransparency = nooutertransparency +local innercolor = nooutercolor +local innertransparency = nooutertransparency + +local pdfcolor, pdftransparency = lpdf.color, lpdf.transparency +local registercolor, registerspotcolor = colors.register, colors.registerspotcolor +local registertransparency = transparencies.register + +function metapost.set_outer_color(mode,colormodel,colorattribute,transparencyattribute) + -- has always to be called before conversion + -- todo: transparency (not in the mood now) + outercolormode = mode + if mode == 1 or mode == 3 then + -- inherit from outer (registered color) + outercolor = pdfcolor(colormodel,colorattribute) or nooutercolor + outertransparency = pdftransparency(transparencyattribute) or nooutertransparency + elseif mode == 2 then + -- stand alone (see m-punk.tex) + outercolor = "" + outertransparency = "" + else -- 0 + outercolor = nooutercolor + outertransparency = nooutertransparency + end + innercolor = outercolor + innertransparency = outertransparency -- not yet used +end + +local function checked_color_pair(color) + if not color then + return innercolor, outercolor + elseif outercolormode == 3 then + innercolor = color + return innercolor, innercolor + else + return color, outercolor + end +end + +metapost.checked_color_pair = checked_color_pair + +function metapost.colorinitializer() + innercolor = outercolor + innertransparency = outertransparency + return outercolor, outertransparency +end + +function metapost.specials.register(str) -- only colors + local size, content, n, class = match(str,"^%%%%MetaPostSpecial: (%d+) (.*) (%d+) (%d+)$") + if class then + -- use lpeg splitter + local data = { } + for s in gmatch(content,"[^ ]+") do + data[#data+1] = s + end + class, n = tonumber(class), tonumber(n) + if class == 3 or class == 4 or class == 5 then + -- hm, weird + else + n = tonumber(data[1]) + end + if n then + local cc = colordata[class] + if cc then + cc[n] = data + else + logs.report("mplib","problematic special: %s (no colordata class %s)", str or "?",class) + end + else + -- there is some bug to be solved, so we issue a message + logs.report("mplib","problematic special: %s", str or "?") + end + end +--~ if match(str,"^%%%%MetaPostOption: multipass") then +--~ metapost.multipass = true +--~ end +end + +local function spotcolorconverter(parent, n, d, p) + registerspotcolor(parent) + return pdfcolor(colors.model,registercolor(nil,'spot',parent,n,d,p)) +end + +function metapost.colorhandler(cs, object, result, colorconverter) -- handles specials + local cr = outercolor + local what = round(cs[2]*10000) + local data = colordata[what] + if data then + data = data[round(cs[3]*10000)] + end + if not data then + -- + elseif what == 1 then + result[#result+1], cr = colorconverter({ data[2], data[3], data[4], data[5] }) + elseif what == 2 then + result[#result+1] = spotcolorconverter(data[2],data[3],data[4],data[5]) + else + if what == 3 then + result[#result+1], cr = colorconverter({ data[3], data[4], data[5]}) + elseif what == 4 then + result[#result+1], cr = colorconverter({ data[3], data[4], data[5], data[6]}) + elseif what == 5 then + result[#result+1] = spotcolorconverter(data[3],data[4],data[5],data[6]) + end + object.prescript = "tr" + object.postscript = data[1] .. "," .. data[2] + end + object.color = nil + return object, cr +end + +function metapost.colorspec(cs) -- used for shades ... returns table (for checking) or string (spot) + local what = round(cs[2]*10000) + local data = colordata[what][round(cs[3]*10000)] + if not data then + return { 0 } + elseif what == 1 then + return { tonumber(data[2]), tonumber(data[3]), tonumber(data[4]), tonumber(data[5]) } + elseif what == 2 then + return spotcolorconverter(data[2],data[3],data[4],data[5]) + elseif what == 3 then + return { tonumber(data[3]), tonumber(data[4]), tonumber(data[5]) } + elseif what == 4 then + return { tonumber(data[3]), tonumber(data[4]), tonumber(data[5]), tonumber(data[6]) } + elseif what == 5 then + return spotcolorconverter(data[3],data[4],data[5],data[6]) + end +end + +function metapost.specials.tr(specification,object,result) + local a, t = match(specification,"^(.+),(.+)$") + local before = a and t and function() + result[#result+1] = format("/Tr%s gs",registertransparency(nil,a,t,true)) -- maybe nil instead of 'mp' + return object, result + end + local after = before and function() + result[#result+1] = outertransparency -- here we could revert to the outer color + return object, result + end + return object, before, nil, after +end + +local specificationsplitter = lpeg.Ct(lpeg.splitat(" ")) +local colorsplitter = lpeg.Ct(lpeg.splitat(":")) +local colorsplitter = lpeg.Ct(lpeg.splitter(":",tonumber)) + +-- Unfortunately we cannot use cmyk colors natively because there is no +-- generic color allocation primitive ... it's just an rgbcolor color.. This +-- means that we cannot pass colors in either cmyk or rgb form. +-- +-- def cmyk(expr c,m,y,k) = +-- 1 withprescript "cc" withpostscript ddddecimal (c,m,y,k) +-- enddef ; +-- +-- This is also an example of a simple plugin. + +--~ function metapost.specials.cc(specification,object,result) +--~ object.color = lpegmatch(specificationsplitter,specification) +--~ return object, nil, nil, nil +--~ end +--~ function metapost.specials.cc(specification,object,result) +--~ local c = lpegmatch(specificationsplitter,specification) +--~ local o = object.color[1] +--~ c[1],c[2],c[3],c[4] = o*c[1],o*c[2],o*c[3],o*c[4] +--~ return object, nil, nil, nil +--~ end + +-- thanks to taco's reading of the postscript manual: +-- +-- x' = sx * x + ry * y + tx +-- y' = rx * x + sy * y + ty + +function metapost.specials.fg(specification,object,result,flusher) -- graphics + local op = object.path + local first, second, fourth = op[1], op[2], op[4] + local tx, ty = first.x_coord , first.y_coord + local sx, sy = second.x_coord - tx, fourth.y_coord - ty + local rx, ry = second.y_coord - ty, fourth.x_coord - tx + if sx == 0 then sx = 0.00001 end + if sy == 0 then sy = 0.00001 end + local before = specification and function() + flusher.flushfigure(result) + sprint(ctxcatcodes,format("\\MPLIBfigure{%f}{%f}{%f}{%f}{%f}{%f}{%s}",sx,rx,ry,sy,tx,ty,specification)) + object.path = nil + return object, { } + end + return { } , before, nil, nil -- replace { } by object for tracing +end + +function metapost.specials.ps(specification,object,result) -- positions + local op = object.path + local first, third = op[1], op[3] + local x, y = first.x_coord, first.y_coord + local w, h = third.x_coord - x, third.y_coord - y + local label = specification + x = x - metapost.llx + y = metapost.ury - y + -- logs.report("mplib", "todo: position '%s' at (%s,%s) with (%s,%s)",label,x,y,w,h) + sprint(ctxcatcodes,format("\\dosavepositionwhd{%s}{0}{%sbp}{%sbp}{%sbp}{%sbp}{0pt}",label,x,y,w,h)) + return { }, nil, nil, nil +end + +local nofshades = 0 -- todo: hash resources, start at 1000 in order not to clash with older + +local function normalize(ca,cb) + if #cb == 1 then + if #ca == 4 then + cb[1], cb[2], cb[3], cb[4] = 0, 0, 0, 1-cb[1] + else + cb[1], cb[2], cb[3] = cb[1], cb[1], cb[1] + end + elseif #cb == 3 then + if #ca == 4 then + cb[1], cb[2], cb[3], cb[4] = rgbtocmyk(cb[1],cb[2],cb[3]) + else + cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4]) + end + end +end + +-- todo: check for the same colorspace (actually a backend issue), now we can +-- have several similar resources +-- +-- normalize(ca,cb) fails for spotcolors + +function metapost.specials.cs(specification,object,result,flusher) -- spot colors? + -- a mess, not dynamic anyway + nofshades = nofshades + 1 + flusher.flushfigure(result) + result = { } + local t = lpegmatch(specificationsplitter,specification) + -- we need a way to move/scale + local ca = lpegmatch(colorsplitter,t[4]) + local cb = lpegmatch(colorsplitter,t[8]) + if round(ca[1]*10000) == 123 then ca = metapost.colorspec(ca) end + if round(cb[1]*10000) == 123 then cb = metapost.colorspec(cb) end + local name = format("MplSh%s",nofshades) + local domain = { tonumber(t[1]), tonumber(t[2]) } + local coordinates = { tonumber(t[5]), tonumber(t[6]), tonumber(t[7]), tonumber(t[9]), tonumber(t[10]), tonumber(t[11]) } + if type(ca) == "string" then + -- backend specific (will be renamed) + lpdf.circularshade(name,domain,{ 0 },{ 1 },1,"DeviceGray",coordinates) + else + if #ca > #cb then + normalize(ca,cb) + elseif #ca < #cb then + normalize(cb,ca) + end + local model = colors.model + if model == "all" then + model= (#ca == 4 and "cmyk") or (#ca == 3 and "rgb") or "gray" + end + if model == "rgb" then + if #ca == 4 then + ca[1], ca[2], ca[3] = cmyktorgb(ca[1],ca[2],ca[3],ca[4]) + cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4]) + ca[4], cb[4] = nil, nil + elseif #ca == 1 then + local a, b = 1-ca[1], 1-cb[1] + ca[1], ca[2], ca[3] = a, a, a + cb[1], cb[2], cb[3] = b, b, b + end + -- backend specific (will be renamed) + lpdf.circularshade(name,domain,ca,cb,1,"DeviceRGB",coordinates) + elseif model == "cmyk" then + if #ca == 3 then + ca[1], ca[2], ca[3], ca[4] = rgbtocmyk(ca[1],ca[2],ca[3]) + cb[1], cb[2], cb[3], ca[4] = rgbtocmyk(cb[1],cb[2],cb[3]) + elseif #ca == 1 then + ca[1], ca[2], ca[3], ca[4] = 0, 0, 0, ca[1] + cb[1], cb[2], cb[3], ca[4] = 0, 0, 0, ca[1] + end + -- backend specific (will be renamed) + lpdf.circularshade(name,domain,ca,cb,1,"DeviceCMYK",coordinates) + else + if #ca == 4 then + ca[1] = cmyktogray(ca[1],ca[2],ca[3],ca[4]) + cb[1] = cmyktogray(cb[1],cb[2],cb[3],cb[4]) + elseif #ca == 3 then + ca[1] = rgbtogray(ca[1],ca[2],ca[3]) + cb[1] = rgbtogray(cb[1],cb[2],cb[3]) + end + -- backend specific (will be renamed) + lpdf.circularshade(name,domain,ca,cb,1,"DeviceGRAY",coordinates) + end + end + local before = function() + result[#result+1] = "q /Pattern cs" + return object, result + end + local after = function() + result[#result+1] = format("W n /%s sh Q", name) + return object, result + end + object.color, object.type = nil, nil + return object, before, nil, after +end + +function metapost.specials.ls(specification,object,result,flusher) + nofshades = nofshades + 1 + flusher.flushfigure(result) + result = { } + local t = lpegmatch(specificationsplitter,specification) + -- we need a way to move/scale + local ca = lpegmatch(colorsplitter,t[4]) + local cb = lpegmatch(colorsplitter,t[7]) + if round(ca[1]*10000) == 123 then ca = metapost.colorspec(ca) end + if round(cb[1]*10000) == 123 then cb = metapost.colorspec(cb) end + local name = format("MpSh%s",nofshades) + local domain = { tonumber(t[1]), tonumber(t[2]) } + local coordinates = { tonumber(t[5]), tonumber(t[6]), tonumber(t[8]), tonumber(t[9]) } + if type(ca) == "string" then + -- backend specific (will be renamed) + lpdf.linearshade(name,domain,{ 0 },{ 1 },1,"DeviceGray",coordinates) + else + if #ca > #cb then + normalize(ca,cb) + elseif #ca < #cb then + normalize(cb,ca) + end + local model = colors.model + if model == "all" then + model= (#ca == 4 and "cmyk") or (#ca == 3 and "rgb") or "gray" + end + if model == "rgb" then + if #ca == 4 then + ca[1], ca[2], ca[3] = cmyktorgb(ca[1],ca[2],ca[3],ca[4]) + cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4]) + elseif #ca == 1 then + local a, b = 1-ca[1], 1-cb[1] + ca[1], ca[2], ca[3] = a, a, a + cb[1], cb[2], cb[3] = b, b, b + end + -- backend specific (will be renamed) + lpdf.linearshade(name,domain,ca,cb,1,"DeviceRGB",coordinates) + elseif model == "cmyk" then + if #ca == 3 then + ca[1], ca[2], ca[3], ca[4] = rgbtocmyk(ca[1],ca[2],ca[3]) + cb[1], cb[2], cb[3], ca[4] = rgbtocmyk(cb[1],cb[2],cb[3]) + elseif #ca == 1 then + ca[1], ca[2], ca[3], ca[4] = 0, 0, 0, ca[1] + cb[1], cb[2], cb[3], ca[4] = 0, 0, 0, ca[1] + end + -- backend specific (will be renamed) + lpdf.linearshade(name,domain,ca,cb,1,"DeviceCMYK",coordinates) + else + if #ca == 4 then + ca[1] = cmyktogray(ca[1],ca[2],ca[3],ca[4]) + cb[1] = cmyktogray(cb[1],cb[2],cb[3],cb[4]) + elseif #ca == 3 then + ca[1] = rgbtogray(ca[1],ca[2],ca[3]) + cb[1] = rgbtogray(cb[1],cb[2],cb[3]) + end + -- backend specific (will be renamed) + lpdf.linearshade(name,domain,ca,cb,1,"DeviceGRAY",coordinates) + end + end + local before = function() + result[#result+1] = "q /Pattern cs" + return object, result + end + local after = function() + result[#result+1] = format("W n /%s sh Q", name) + return object, result + end + object.color, object.type = nil, nil + return object, before, nil, after +end + +-- no need for a before here + +local current_format, current_graphic, current_initializations + +-- metapost.first_box = metapost.first_box or 1000 +-- metapost.last_box = metapost.last_box or 1100 +--~ metapost.textext_current = metapost.first_box +metapost.multipass = false + +local textexts = { } + +function metapost.free_boxes() -- todo: mp direct list ipv box + -- for i = metapost.first_box,metapost.last_box do + -- local b = texbox[i] + -- if b then + -- texbox[i] = nil -- no node.flush_list(b) needed, else double free error + -- else + -- break + -- end + -- end + for n, box in next, textexts do + local tn = textexts[n] + if tn then + -- somehow not flushed (used) + textexts[n] = nil + end + end + textexts = { } +end + +function metapost.settext(box,slot) + textexts[slot] = copy_list(texbox[box]) + texbox[box] = nil + -- this will become + -- textexts[slot] = texbox[box] + -- unsetbox(box) +end + +function metapost.gettext(box,slot) + texbox[box] = copy_list(textexts[slot]) +-- textexts[slot] = nil -- no, pictures can be placed several times +end + +function metapost.specials.tf(specification,object) +--~ print("setting", metapost.textext_current) + local n, str = match(specification,"^(%d+):(.+)$") + if n and str then + n = tonumber(n) + -- if metapost.textext_current < metapost.last_box then + -- metapost.textext_current = metapost.first_box + n - 1 + -- end + if trace_textexts then + -- logs.report("metapost","first pass: order %s, box %s",n,metapost.textext_current) + logs.report("metapost","first pass: order %s",n) + end + -- sprint(ctxcatcodes,format("\\MPLIBsettext{%s}{%s}",metapost.textext_current,str)) + sprint(ctxcatcodes,format("\\MPLIBsettext{%s}{%s}",n,str)) + metapost.multipass = true + end + return { }, nil, nil, nil +end + +function metapost.specials.ts(specification,object,result,flusher) + -- print("getting", metapost.textext_current) + local n, str = match(specification,"^(%d+):(.+)$") + if n and str then + n = tonumber(n) + if trace_textexts then + -- logs.report("metapost","second pass: order %s, box %s",n,metapost.textext_current) + logs.report("metapost","second pass: order %s",n) + end + local op = object.path + local first, second, fourth = op[1], op[2], op[4] + local tx, ty = first.x_coord , first.y_coord + local sx, sy = second.x_coord - tx, fourth.y_coord - ty + local rx, ry = second.y_coord - ty, fourth.x_coord - tx + if sx == 0 then sx = 0.00001 end + if sy == 0 then sy = 0.00001 end + if not trace_textexts then + object.path = nil + end + local before = function() -- no need for before function (just do it directly) + --~ flusher.flushfigure(result) + --~ sprint(ctxcatcodes,format("\\MPLIBgettext{%f}{%f}{%f}{%f}{%f}{%f}{%s}",sx,rx,ry,sy,tx,ty,metapost.textext_current)) + --~ result = { } + result[#result+1] = format("q %f %f %f %f %f %f cm", sx,rx,ry,sy,tx,ty) + flusher.flushfigure(result) + -- if metapost.textext_current < metapost.last_box then + -- metapost.textext_current = metapost.first_box + n - 1 + -- end + -- local b = metapost.textext_current + -- local box = texbox[b] + local box = textexts[n] + if box then + -- sprint(ctxcatcodes,format("\\MPLIBgettextscaled{%s}{%s}{%s}",b,metapost.sxsy(box.width,box.height,box.depth))) + sprint(ctxcatcodes,format("\\MPLIBgettextscaled{%s}{%s}{%s}",n,metapost.sxsy(box.width,box.height,box.depth))) + else + -- error + end + result = { "Q" } + return object, result + end + return { }, before, nil, nil -- replace { } by object for tracing + else + return { }, nil, nil, nil -- replace { } by object for tracing + end +end + +-- rather generic pdf, so use this elsewhere too it no longer pays +-- off to distinguish between outline and fill (we now have both +-- too, e.g. in arrows) + +metapost.reducetogray = true + +local models = { } + +function models.all(cr) + local n = #cr + if n == 0 then + return checked_color_pair() + elseif metapost.reducetogray then + if n == 1 then + local s = cr[1] + return checked_color_pair(format("%.3f g %.3f G",s,s)) + elseif n == 3 then + local r, g, b = cr[1], cr[2], cr[3] + if r == g and g == b then + return checked_color_pair(format("%.3f g %.3f G",r,r)) + else + return checked_color_pair(format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b)) + end + else + local c, m, y, k = cr[1], cr[2], cr[3], cr[4] + if c == m and m == y and y == 0 then + k = 1 - k + return checked_color_pair(format("%.3f g %.3f G",k,k)) + else + return checked_color_pair(format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k)) + end + end + elseif n == 1 then + local s = cr[1] + return checked_color_pair(format("%.3f g %.3f G",s,s)) + elseif n == 3 then + local r, g, b = cr[1], cr[2], cr[3] + return checked_color_pair(format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b)) + else + local c, m, y, k = cr[1], cr[2], cr[3], cr[4] + return checked_color_pair(format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k)) + end +end + +function models.rgb(cr) + local n = #cr + if n == 0 then + return checked_color_pair() + elseif metapost.reducetogray then + if n == 1 then + local s = cr[1] + checked_color_pair(format("%.3f g %.3f G",s,s)) + elseif n == 3 then + local r, g, b = cr[1], cr[2], cr[3] + if r == g and g == b then + return checked_color_pair(format("%.3f g %.3f G",r,r)) + else + return checked_color_pair(format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b)) + end + else + local c, m, y, k = cr[1], cr[2], cr[3], cr[4] + if c == m and m == y and y == 0 then + k = 1 - k + return checked_color_pair(format("%.3f g %.3f G",k,k)) + else + local r, g, b = cmyktorgb(c,m,y,k) + return checked_color_pair(format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b)) + end + end + elseif n == 1 then + local s = cr[1] + return checked_color_pair(format("%.3f g %.3f G",s,s)) + else + local r, g, b + if n == 3 then + r, g, b = cmyktorgb(cr[1],cr[2],cr[3],cr[4]) + else + r, g, b = cr[1], cr[2], cr[3] + end + return checked_color_pair(format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b)) + end +end + +function models.cmyk(cr) + local n = #cr + if n == 0 then + return checked_color_pair() + elseif metapost.reducetogray then + if n == 1 then + local s = cr[1] + return checked_color_pair(format("%.3f g %.3f G",s,s)) + elseif n == 3 then + local r, g, b = cr[1], cr[2], cr[3] + if r == g and g == b then + return checked_color_pair(format("%.3f g %.3f G",r,r)) + else + local c, m, y, k = rgbtocmyk(r,g,b) + return checked_color_pair(format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k)) + end + else + local c, m, y, k = cr[1], cr[2], cr[3], cr[4] + if c == m and m == y and y == 0 then + k = 1 - k + return checked_color_pair(format("%.3f g %.3f G",k,k)) + else + return checked_color_pair(format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k)) + end + end + elseif n == 1 then + local s = cr[1] + return checked_color_pair(format("%.3f g %.3f G",s,s)) + else + local c, m, y, k + if n == 3 then + c, m, y, k = rgbtocmyk(cr[1],cr[2],cr[3]) + else + c, m, y, k = cr[1], cr[2], cr[3], cr[4] + end + return checked_color_pair(format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k)) + end +end + +function models.gray(cr) + local n, s = #cr, 0 + if n == 0 then + return checked_color_pair() + elseif n == 4 then + s = cmyktogray(cr[1],cr[2],cr[3],cr[4]) + elseif n == 3 then + s = rgbtogray(cr[1],cr[2],cr[3]) + else + s = cr[1] + end + return checked_color_pair(format("%.3f g %.3f G",s,s)) +end + +function metapost.colorconverter() + return models[colors.model] or gray +end + +do + + local P, S, V, Cs = lpeg.P, lpeg.S, lpeg.V, lpeg.Cs + + local btex = P("btex") + local etex = P(" etex") + local vtex = P("verbatimtex") + local ttex = P("textext") + local gtex = P("graphictext") + local multipass = P("forcemultipass") + local spacing = S(" \n\r\t\v")^0 + local dquote = P('"') + + local found, forced = false, false + + local function convert(str) + found = true + return "rawtextext(\"" .. str .. "\")" -- centered + end + local function ditto(str) + return "\" & ditto & \"" + end + local function register() + found = true + end + local function force() + forced = true + end + + local texmess = (dquote/ditto + (1 - etex))^0 + + local function ignore(s) + logs.report("mplib","ignoring verbatim tex: %s",s) + return "" + end + + local parser = P { + [1] = Cs((V(2)/register + V(4)/ignore + V(3)/convert + V(5)/force + 1)^0), + [2] = ttex + gtex, + [3] = btex * spacing * Cs(texmess) * etex, + [4] = vtex * spacing * Cs(texmess) * etex, + [5] = multipass, -- experimental, only for testing + } + + -- currently a a one-liner produces less code + + local parser = Cs(( + (ttex + gtex)/register + + (btex * spacing * Cs(texmess) * etex)/convert + + (vtex * spacing * Cs(texmess) * etex)/ignore + + 1 + )^0) + + function metapost.check_texts(str) + found, forced = false, false + return lpegmatch(parser,str), found, forced + end + +end + +local factor = 65536*(7227/7200) + +function metapost.edefsxsy(wd,ht,dp) -- helper for figure + local hd = ht + dp + commands.edef("sx",(wd ~= 0 and factor/wd) or 0) + commands.edef("sy",(hd ~= 0 and factor/hd) or 0) +end + +function metapost.sxsy(wd,ht,dp) -- helper for text + local hd = ht + dp + return (wd ~= 0 and factor/wd) or 0, (hd ~= 0 and factor/hd) or 0 +end + +local no_trial_run = "_trial_run_ := false ;" +local do_trial_run = "if unknown _trial_run_ : boolean _trial_run_ fi ; _trial_run_ := true ;" +local text_data_template = "_tt_w_[%i]:=%f;_tt_h_[%i]:=%f;_tt_d_[%i]:=%f;" +local do_begin_fig = "; beginfig(1); " +local do_end_fig = "; endfig ;" +local do_safeguard = ";" + +function metapost.text_texts_data() + local t, n = { }, 0 +--~ for i = metapost.first_box, metapost.last_box do +--~ n = n + 1 +--~ local box = texbox[i] + for n, box in next, textexts do + if trace_textexts then + logs.report("metapost","passed data: order %s, box %s",n,i) + end + if box then + t[#t+1] = format(text_data_template,n,box.width/factor,n,box.height/factor,n,box.depth/factor) + else + break + end + end +--~ print(table.serialize(t)) + return t +end + +metapost.intermediate = metapost.intermediate or {} +metapost.intermediate.actions = metapost.intermediate.actions or {} +metapost.intermediate.needed = false + +metapost.method = 1 -- 1:dumb 2:clever + +function metapost.graphic_base_pass(mpsformat,str,initializations,preamble,askedfig) + local nofig = (askedfig and "") or false + local done_1, done_2, forced_1, forced_2 + str, done_1, forced_1 = metapost.check_texts(str) + if not preamble or preamble == "" then + preamble, done_2, forced_2 = "", false, false + else + preamble, done_2, forced_2 = metapost.check_texts(preamble) + end + -- metapost.textext_current = metapost.first_box + metapost.intermediate.needed = false + metapost.multipass = false -- no needed here + current_format, current_graphic, current_initializations = mpsformat, str, initializations or "" + if metapost.method == 1 or (metapost.method == 2 and (done_1 or done_2)) then + -- first true means: trialrun, second true means: avoid extra run if no multipass + local flushed = metapost.process(mpsformat, { + preamble, + nofig or do_begin_fig, + do_trial_run, + current_initializations, + do_safeguard, + current_graphic, + nofig or do_end_fig + -- }, true, nil, true ) + }, true, nil, not (forced_1 or forced_2), false, askedfig) + if metapost.intermediate.needed then + for _, action in next, metapost.intermediate.actions do + action() + end + end + if not flushed or not metapost.optimize then + -- tricky, we can only ask once for objects and therefore + -- we really need a second run when not optimized + sprint(ctxcatcodes,format("\\ctxlua{metapost.graphic_extra_pass(%s)}",askedfig or "false")) + end + else + metapost.process(mpsformat, { + preamble, + nofig or do_begin_fig, + no_trial_run, + current_initializations, + do_safeguard, + current_graphic, + nofig or do_end_fig + }, false, nil, false, false, askedfig ) + end + -- here we could free the textext boxes + metapost.free_boxes() +end + +function metapost.graphic_extra_pass(askedfig) + local nofig = (askedfig and "") or false + -- metapost.textext_current = metapost.first_box + metapost.process(current_format, { + nofig or do_begin_fig, + no_trial_run, + concat(metapost.text_texts_data()," ;\n"), + current_initializations, + do_safeguard, + current_graphic, + nofig or do_end_fig + }, false, nil, false, true, askedfig ) +end + +metapost.tex = metapost.tex or { } + +do -- only used in graphictexts + + local environments = { } + + function metapost.tex.set(str) + environments[#environments+1] = str + end + function metapost.tex.reset() + environments = { } + end + function metapost.tex.get() + return concat(environments,"\n") + end + +end + +local graphics = { } +local start = [[\starttext]] +local preamble = [[\long\def\MPLIBgraphictext#1{\startTEXpage[scale=10000]#1\stopTEXpage}]] +local stop = [[\stoptext]] + +function metapost.specials.gt(specification,object) -- number, so that we can reorder + graphics[#graphics+1] = format("\\MPLIBgraphictext{%s}",specification) + metapost.intermediate.needed = true + metapost.multipass = true + return { }, nil, nil, nil +end + +function metapost.intermediate.actions.makempy() + if #graphics > 0 then + local externals = metapost.externals + externals.n = externals.n + 1 + starttiming(externals) + local mpofile = tex.jobname .. "-mpgraph" + local mpyfile = file.replacesuffix(mpofile,"mpy") + local pdffile = file.replacesuffix(mpofile,"pdf") + local texfile = file.replacesuffix(mpofile,"tex") + io.savedata(texfile, { start, preamble, metapost.tex.get(), concat(graphics,"\n"), stop }, "\n") + local command = format("context --once %s %s", (tex.interactionmode == 0 and "--batchmode") or "", texfile) + os.execute(command) + if io.exists(pdffile) then + command = format("pstoedit -ssp -dt -f mpost %s %s", pdffile, mpyfile) + os.execute(command) + local result = { } + if io.exists(mpyfile) then + local data = io.loaddata(mpyfile) + for figure in gmatch(data,"beginfig(.-)endfig") do + result[#result+1] = format("begingraphictextfig%sendgraphictextfig ;\n", figure) + end + io.savedata(mpyfile,concat(result,"")) + end + end + stoptiming(externals) + graphics = { } -- ? + end +end |