diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mlib-pps.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mlib-pps.lua | 1058 |
1 files changed, 569 insertions, 489 deletions
diff --git a/Master/texmf-dist/tex/context/base/mlib-pps.lua b/Master/texmf-dist/tex/context/base/mlib-pps.lua index 8b36660d350..27269a14e49 100644 --- a/Master/texmf-dist/tex/context/base/mlib-pps.lua +++ b/Master/texmf-dist/tex/context/base/mlib-pps.lua @@ -1,4 +1,4 @@ -if not modules then modules = { } end modules ['mlib-pps'] = { -- prescript, postscripts and specials +if not modules then modules = { } end modules ['mlib-pps'] = { version = 1.001, comment = "companion to mlib-ctx.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -6,60 +6,65 @@ if not modules then modules = { } end modules ['mlib-pps'] = { -- prescript, pos license = "see context related readme files", } +-- pps: prescript, postscripts and specials (although specials are dropped) +-- -- 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 format, gmatch, match = string.format, string.gmatch, string.match local tonumber, type = tonumber, type +local round = math.round +local insert, concat = table.insert, table.concat +local Cs, Cf, C, Cg, Ct, P, S, V, Carg = lpeg.Cs, lpeg.Cf, lpeg.C, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.S, lpeg.V, lpeg.Carg local lpegmatch = lpeg.match -local texbox = tex.box -local copy_list = node.copy_list -local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming +local mplib, metapost, lpdf, context = mplib, metapost, lpdf, context -local ctxcatcodes = tex.ctxcatcodes +local texbox = tex.box +local copy_list = node.copy_list +local free_list = node.flush_list +local setmetatableindex = table.setmetatableindex +local sortedhash = table.sortedhash -local trace_textexts = false trackers.register("metapost.textexts", function(v) trace_textexts = v end) +local starttiming = statistics.starttiming +local stoptiming = statistics.stoptiming -colors = colors or { } +local trace_textexts = false trackers.register("metapost.textexts", function(v) trace_textexts = v end) +local trace_scripts = false trackers.register("metapost.scripts", function(v) trace_scripts = v end) -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 +local report_metapost = logs.reporter("metapost") +local report_textexts = logs.reporter("metapost","textexts") +local report_scripts = logs.reporter("metapost","scripts") -metapost = metapost or { } -metapost.specials = metapost.specials or { } -metapost.specials.data = metapost.specials.data or { } -metapost.externals = metapost.externals or { n = 0 } +local colors = attributes.colors -local data = metapost.specials.data +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 -local colordata = { {}, {}, {}, {}, {} } +metapost.makempy = metapost.makempy or { nofconverted = 0 } +local makempy = metapost.makempy ---~ (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 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 = lpdf.color +local pdftransparency = lpdf.transparency +local registercolor = colors.register +local registerspotcolor = colors.registerspotcolor -local pdfcolor, pdftransparency = lpdf.color, lpdf.transparency -local registercolor, registerspotcolor = colors.register, colors.registerspotcolor +local transparencies = attributes.transparencies local registertransparency = transparencies.register -function metapost.set_outer_color(mode,colormodel,colorattribute,transparencyattribute) +function metapost.setoutercolor(mode,colormodel,colorattribute,transparencyattribute) -- has always to be called before conversion -- todo: transparency (not in the mood now) outercolormode = mode @@ -90,169 +95,25 @@ local function checked_color_pair(color) 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 +local specificationsplitter = Ct(lpeg.splitat(" ")) +local colorsplitter = Ct(lpeg.splitter(":",tonumber)) -- no need for : +local domainsplitter = Ct(lpeg.splitter(" ",tonumber)) +local centersplitter = domainsplitter +local coordinatesplitter = domainsplitter -- 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) @@ -276,95 +137,30 @@ end -- -- 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) +local function spotcolorconverter(parent, n, d, p) + registerspotcolor(parent) + return pdfcolor(colors.model,registercolor(nil,'spot',parent,n,d,p)), outercolor +end + +local function checkandconvertspot(n_a,f_a,c_a,v_a,n_b,f_b,c_b,v_b) + -- must be the same but we don't check + local name = format("MpSh%s",nofshades) + local ca = string.split(v_a,",") + local cb = string.split(v_b,",") + if #ca == 0 or #cb == 0 then + return { 0 }, { 1 }, "DeviceGray", name 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 + for i=1,#ca do ca[i] = tonumber(ca[i]) or 0 end + for i=1,#cb do cb[i] = tonumber(cb[i]) or 1 end + --~ spotcolorconverter(n_a,f_a,c_a,v_a) -- not really needed + return ca, cb, n_a or n_b, name 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 +end + +local function checkandconvert(ca,cb) 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) + if not ca or not cb or type(ca) == "string" then + return { 0 }, { 1 }, "DeviceGray", name else if #ca > #cb then normalize(ca,cb) @@ -377,79 +173,61 @@ function metapost.specials.ls(specification,object,result,flusher) 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 = { cmyktorgb(ca[1],ca[2],ca[3],ca[4]) } + cb = { 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 + ca = { a, a, a } + cb = { b, b, b } end - -- backend specific (will be renamed) - lpdf.linearshade(name,domain,ca,cb,1,"DeviceRGB",coordinates) + return ca, cb, "DeviceRGB", name 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]) + ca = { rgbtocmyk(ca[1],ca[2],ca[3]) } + cb = { 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] + ca = { 0, 0, 0, ca[1] } + cb = { 0, 0, 0, ca[1] } end - -- backend specific (will be renamed) - lpdf.linearshade(name,domain,ca,cb,1,"DeviceCMYK",coordinates) + return ca, cb, "DeviceCMYK", name 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]) + ca = { cmyktogray(ca[1],ca[2],ca[3],ca[4]) } + cb = { 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]) + ca = { rgbtogray(ca[1],ca[2],ca[3]) } + cb = { rgbtogray(cb[1],cb[2],cb[3]) } end -- backend specific (will be renamed) - lpdf.linearshade(name,domain,ca,cb,1,"DeviceGRAY",coordinates) + return ca, cb, "DeviceGray", name 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 +metapost.multipass = false + +local textexts = { } +local scratchbox = 0 + +local function freeboxes() -- todo: mp direct list ipv box for n, box in next, textexts do local tn = textexts[n] if tn then - -- somehow not flushed (used) - textexts[n] = nil + free_list(tn) + -- texbox[scratchbox] = tn + -- texbox[scratchbox] = nil -- this frees too + if trace_textexts then + report_textexts("freeing %s",n) + end end end textexts = { } end +metapost.resettextexts = freeboxes + function metapost.settext(box,slot) textexts[slot] = copy_list(texbox[box]) texbox[box] = nil @@ -460,72 +238,10 @@ 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 + if trace_textexts then + report_textexts("putting %s in box %s",slot,box) end + -- textexts[slot] = nil -- no, pictures can be placed several times end -- rather generic pdf, so use this elsewhere too it no longer pays @@ -664,79 +380,85 @@ function models.gray(cr) return checked_color_pair(format("%.3f g %.3f G",s,s)) end -function metapost.colorconverter() - return models[colors.model] or gray +setmetatableindex(models, function(t,k) + local v = models.gray + t[k] = v + return v +end) + +local function colorconverter(cs) + return models[colors.model](cs) end -do +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 P, S, V, Cs = lpeg.P, lpeg.S, lpeg.V, lpeg.Cs +local found, forced = false, false - 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 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 found, forced = false, false +local texmess = (dquote/ditto + (1 - etex))^0 - 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 function ignore(s) + report_metapost("ignoring verbatim tex: %s",s) + return "" +end - local texmess = (dquote/ditto + (1 - etex))^0 +-- 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 +-- } - local function ignore(s) - logs.report("mplib","ignoring verbatim tex: %s",s) - return "" - end +-- currently a a one-liner produces less code - 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 +-- textext.*(".*") can have "'s but tricky parsing as we can have concatenated strings +-- so this is something for a boring plain or train trip and we might assume proper mp +-- input anyway + +local parser = Cs(( + (ttex + gtex)/register + + (btex * spacing * Cs(texmess) * etex)/convert + + (vtex * spacing * Cs(texmess) * etex)/ignore + + 1 +)^0) +local function checktexts(str) + found, forced = false, false + return lpegmatch(parser,str), found, forced end +metapost.checktexts = checktexts + 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) + context.setvalue("sx",wd ~= 0 and factor/wd or 0) + context.setvalue("sy",hd ~= 0 and factor/hd or 0) end -function metapost.sxsy(wd,ht,dp) -- helper for text +local function 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 @@ -748,22 +470,20 @@ 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] +function metapost.texttextsdata() + local t, nt, n = { }, 0, 0 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) + local wd, ht, dp = box.width/factor, box.height/factor, box.depth/factor + if trace_textexts then + report_textexts("passed data %s: (%0.4f,%0.4f,%0.4f)",n,wd,ht,dp) + end + nt = nt + 1 + t[nt] = format(text_data_template,n,wd,n,ht,n,dp) else break end end ---~ print(table.serialize(t)) return t end @@ -773,16 +493,17 @@ metapost.intermediate.needed = false metapost.method = 1 -- 1:dumb 2:clever +-- maybe we can latelua the texts some day + 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) + str, done_1, forced_1 = checktexts(str) if not preamble or preamble == "" then preamble, done_2, forced_2 = "", false, false else - preamble, done_2, forced_2 = metapost.check_texts(preamble) + preamble, done_2, forced_2 = checktexts(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 "" @@ -806,7 +527,7 @@ function metapost.graphic_base_pass(mpsformat,str,initializations,preamble,asked 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")) + context.MPLIBextrapass(askedfig or "false") end else metapost.process(mpsformat, { @@ -819,59 +540,30 @@ function metapost.graphic_base_pass(mpsformat,str,initializations,preamble,asked 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"), + concat(metapost.texttextsdata()," ;\n"), current_initializations, do_safeguard, current_graphic, nofig or do_end_fig }, false, nil, false, true, askedfig ) + context.MPLIBresettexts() -- must happen afterwards 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() +function makempy.processgraphics(graphics) if #graphics > 0 then - local externals = metapost.externals - externals.n = externals.n + 1 - starttiming(externals) + makempy.nofconverted = makempy.nofconverted + 1 + starttiming(makempy) local mpofile = tex.jobname .. "-mpgraph" local mpyfile = file.replacesuffix(mpofile,"mpy") local pdffile = file.replacesuffix(mpofile,"pdf") @@ -882,16 +574,404 @@ function metapost.intermediate.actions.makempy() if io.exists(pdffile) then command = format("pstoedit -ssp -dt -f mpost %s %s", pdffile, mpyfile) os.execute(command) - local result = { } + local result, r = { }, 0 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) + r = r + 1 + result[r] = format("begingraphictextfig%sendgraphictextfig ;\n", figure) end io.savedata(mpyfile,concat(result,"")) end end - stoptiming(externals) + stoptiming(makempy) + end +end + +-- -- the new plugin handler -- -- + +local sequencers = utilities.sequencers +local appendgroup = sequencers.appendgroup +local appendaction = sequencers.appendaction + +local resetter = nil +local analyzer = nil +local processor = nil + +local resetteractions = sequencers.reset { arguments = "" } +local analyzeractions = sequencers.reset { arguments = "object,prescript" } +local processoractions = sequencers.reset { arguments = "object,prescript,before,after" } + +appendgroup(resetteractions, "system") +appendgroup(analyzeractions, "system") +appendgroup(processoractions,"system") + +-- later entries come first + +--~ local scriptsplitter = Cf(Ct("") * ( +--~ Cg(C((1-S("= "))^1) * S("= ")^1 * C((1-S("\n\r"))^0) * S("\n\r")^0) +--~ )^0, rawset) + +local scriptsplitter = Ct ( Ct ( + C((1-S("= "))^1) * S("= ")^1 * C((1-S("\n\r"))^0) * S("\n\r")^0 +)^0 ) + +local function splitscript(script) + local hash = lpegmatch(scriptsplitter,script) + for i=#hash,1,-1 do + local h = hash[i] + hash[h[1]] = h[2] + end + if trace_scripts then + report_scripts(table.serialize(hash,"prescript")) + end + return hash +end + +function metapost.pluginactions(what,t,flushfigure) -- to be checked: too many 0 g 0 G + for i=1,#what do + local wi = what[i] + if type(wi) == "function" then + -- assume injection + flushfigure(t) + t = { } + wi() + else + t[#t+1] = wi + end + end + return t +end + +function metapost.resetplugins() + resetter() +end + +function metapost.analyzeplugins(object) + local prescript = object.prescript -- specifications + if prescript and #prescript > 0 then + return analyzer(object,splitscript(prescript)) + end +end + +function metapost.processplugins(object) -- maybe environment table + local prescript = object.prescript -- specifications + if prescript and #prescript > 0 then + local before = { } + local after = { } + processor(object,splitscript(prescript),before,after) + return #before > 0 and before, #after > 0 and after + else + local c = object.color + if c and #c > 0 then + local b, a = colorconverter(c) + return { b }, { a } + end + end +end + +-- helpers + +local basepoints = number.dimenfactors["bp"] + +local cm = function(object) + 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 + return sx,rx,ry,sy,tx,ty +end + +-- text + +local tx_done = { } + +local function tx_reset() + tx_done = { } +end + +local function tx_analyze(object,prescript) -- todo: hash content and reuse them + local tx_stage = prescript.tx_stage + if tx_stage then + local tx_number = tonumber(prescript.tx_number) + if not tx_done[tx_number] then + tx_done[tx_number] = true + if trace_textexts then + report_textexts("setting %s %s (first pass)",tx_stage,tx_number) + end + local s = object.postscript or "" + local c = object.color -- only simple ones, no transparency + local a = prescript.tr_alternative + local t = prescript.tr_transparency + if not c then + -- no color + elseif #c == 1 then + if a and t then + s = format("\\colored[s=%f,a=%f,t=%f]%s",c[1],a,t,s) + else + s = format("\\colored[s=%f]%s",c[1],s) + end + elseif #c == 3 then + if a and t then + s = format("\\colored[r=%f,g=%f,b=%f,a=%f,t=%f]%s",c[1],c[2],c[3],a,t,s) + else + s = format("\\colored[r=%f,g=%f,b=%f]%s",c[1],c[2],c[3],s) + end + elseif #c == 4 then + if a and t then + s = format("\\colored[c=%f,m=%f,y=%f,k=%f,a=%f,t=%f]%s",c[1],c[2],c[3],c[4],a,t,s) + else + s = format("\\colored[c=%f,m=%f,y=%f,k=%f]%s",c[1],c[2],c[3],c[4],s) + end + end + context.MPLIBsettext(tx_number,s) + metapost.multipass = true + end + end +end + +local function tx_process(object,prescript,before,after) + local tx_number = prescript.tx_number + if tx_number then + tx_number = tonumber(tx_number) + local tx_stage = prescript.tx_stage + if tx_stage == "final" then -- redundant test + if trace_textexts then + report_textexts("processing %s (second pass)",tx_number) + end + before[#before+1] = format("q %f %f %f %f %f %f cm",cm(object)) + before[#before+1] = function() + -- flush always happens, we can have a special flush function injected before + local box = textexts[tx_number] + if box then + context.MPLIBgettextscaled(tx_number,sxsy(box.width,box.height,box.depth)) + else + report_textexts("unknown %s",tx_number) + end + end + before[#before+1] = "Q" + if not trace_textexts then + object.path = false -- else: keep it + end + object.color = false + object.grouped = true + end + end +end + +-- graphics + +local graphics = { } + +function metapost.intermediate.actions.makempy() + if #graphics > 0 then + makempy.processgraphics(graphics) graphics = { } -- ? end end + +local function gt_analyze(object,prescript) + local gt_stage = prescript.gt_stage + if gt_stage == "trial" then + graphics[#graphics+1] = format("\\MPLIBgraphictext{%s}",object.postscript or "") + metapost.intermediate.needed = true + metapost.multipass = true + end +end + +-- local function gt_process(object,prescript,before,after) +-- local gt_stage = prescript.gt_stage +-- if gt_stage == "final" then +-- end +-- end + +-- shades + +local function sh_process(object,prescript,before,after) + local sh_type = prescript.sh_type + if sh_type then + nofshades = nofshades + 1 + local domain = lpegmatch(domainsplitter,prescript.sh_domain) + local centera = lpegmatch(centersplitter,prescript.sh_center_a) + local centerb = lpegmatch(centersplitter,prescript.sh_center_b) + -- + local sh_color_a = prescript.sh_color_a or "1" + local sh_color_b = prescript.sh_color_b or "1" + local ca, cb, colorspace, name, separation + if prescript.sh_color == "into" and prescript.sp_name then + -- some spotcolor + local value_a, components_a, fractions_a, name_a + local value_b, components_b, fractions_b, name_b + for i=1,#prescript do + local tag = prescript[i][1] + if not name_a and tag == "sh_color_a" then + value_a = prescript[i-4][2] + components_a = prescript[i-3][2] + fractions_a = prescript[i-2][2] + name_a = prescript[i-1][2] + elseif not name_b and tag == "sh_color_b" then + value_b = prescript[i-4][2] + components_b = prescript[i-3][2] + fractions_b = prescript[i-2][2] + name_b = prescript[i-1][2] + end + if name_a and name_b then + break + end + end + ca, cb, separation, name = checkandconvertspot( + name_a,fractions_a,components_a,value_a, + name_b,fractions_b,components_b,value_b + ) + else + local colora = lpegmatch(colorsplitter,sh_color_a) + local colorb = lpegmatch(colorsplitter,sh_color_b) + ca, cb, colorspace, name = checkandconvert(colora,colorb) + end + if not ca or not cb then + ca, cb, colorspace, name = checkandconvert() + end + if sh_type == "linear" then + local coordinates = { centera[1], centera[2], centerb[1], centerb[2] } + lpdf.linearshade(name,domain,ca,cb,1,colorspace,coordinates,separation) -- backend specific (will be renamed) + elseif sh_type == "circular" then + local radiusa = tonumber(prescript.sh_radius_a) + local radiusb = tonumber(prescript.sh_radius_b) + local coordinates = { centera[1], centera[2], radiusa, centerb[1], centerb[2], radiusb } + lpdf.circularshade(name,domain,ca,cb,1,colorspace,coordinates,separation) -- backend specific (will be renamed) + else + -- fatal error + end + before[#before+1], after[#after+1] = "q /Pattern cs", format("W n /%s sh Q",name) + object.color, object.type, object.grouped = false, false, true -- not nil, otherwise mt + end +end + +-- bitmaps + +local function bm_process(object,prescript,before,after) + local bm_xresolution = prescript.bm_xresolution + if bm_xresolution then + before[#before+1] = format("q %f %f %f %f %f %f cm",cm(object)) + before[#before+1] = function() + figures.bitmapimage { + xresolution = tonumber(bm_xresolution), + yresolution = tonumber(prescript.bm_yresolution), + width = 1/basepoints, + height = 1/basepoints, + data = object.postscript + } + end + before[#before+1] = "Q" + object.path = false + object.color = false + object.grouped = true + end +end + +-- positions + +local function ps_process(object,prescript,before,after) + local ps_label = prescript.ps_label + if ps_label then + 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 + x = x - metapost.llx + y = metapost.ury - y + before[#before+1] = function() + context.MPLIBpositionwhd(ps_label,x,y,w,h) + end + end +end + +-- figures + +local function fg_process(object,prescript,before,after) + local fg_name = prescript.fg_name + if fg_name then + before[#before+1] = format("q %f %f %f %f %f %f cm",cm(object)) + before[#before+1] = function() + context.MPLIBfigure(fg_name,prescript.fg_mask or "") + end + before[#before+1] = "Q" + object.path = false + object.grouped = true + end +end + +-- color and transparency + +local value = Cs ( ( + (Carg(1) * C((1-P(","))^1)) / function(a,b) return format("%0.3f",a * tonumber(b)) end + + P(","))^1 +) + +local function tr_process(object,prescript,before,after) + -- before can be shortcut to t + local tr_alternative = prescript.tr_alternative + if tr_alternative then + tr_alternative = tonumber(tr_alternative) + local tr_transparency = tonumber(prescript.tr_transparency) + before[#before+1], after[#after+1] = format("/Tr%s gs",registertransparency(nil,tr_alternative,tr_transparency,true)), "/Tr0 gs" -- outertransparency + end + local cs = object.color + if cs and #cs > 0 then + local sp_name = prescript.sp_name + local b, a + if sp_name then + local sp_fractions = prescript.sp_fractions or 1 + local sp_components = prescript.sp_components or "" + local sp_value = prescript.sp_value or "1" + local cf = cs[1] + if cf ~= 1 then + -- beware, we do scale the spotcolors but not the alternative representation + sp_value = lpeg.match(value,sp_value,1,cf) or sp_value + end + -- should be codeinjections + b, a = spotcolorconverter(sp_name,sp_fractions,sp_components,sp_value) + else + -- should be codeinjections + b, a = colorconverter(cs) + end + before[#before+1] = b + after[#after+1] = a + end +end + +-- layers (nasty: we need to keep the 'grouping' right + +local function la_process(object,prescript,before,after) + local la_name = prescript.la_name + if la_name then + before[#before+1] = backends.codeinjections.startlayer(la_name) + insert(after,1,backends.codeinjections.stoplayer()) + end +end + +-- definitions + +appendaction(resetteractions,"system",tx_reset) + +appendaction(analyzeractions,"system",tx_analyze) +appendaction(analyzeractions,"system",gt_analyze) + +appendaction(processoractions,"system",sh_process) +-- (processoractions,"system",gt_process) +appendaction(processoractions,"system",bm_process) +appendaction(processoractions,"system",tx_process) +appendaction(processoractions,"system",ps_process) +appendaction(processoractions,"system",fg_process) +appendaction(processoractions,"system",tr_process) -- last, as color can be reset + +appendaction(processoractions,"system",la_process) + +-- no auto here + +resetter = sequencers.compile(resetteractions ) +analyzer = sequencers.compile(analyzeractions ) +processor = sequencers.compile(processoractions) |