From 0a5f59a9aa25b3de7e9659b39ad201aaf7eb5a67 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Fri, 27 Sep 2019 20:24:34 +0200 Subject: 2019-09-27 18:10:00 --- tex/context/base/mkii/cont-new.mkii | 2 +- tex/context/base/mkii/context.mkii | 2 +- tex/context/base/mkii/mult-en.mkii | 1 + tex/context/base/mkii/mult-it.mkii | 8 + tex/context/base/mkii/mult-ro.mkii | 1 + tex/context/base/mkiv/back-ini.lua | 23 + tex/context/base/mkiv/back-pdf.mkiv | 8 +- tex/context/base/mkiv/back-pdf.mkxl | 8 + tex/context/base/mkiv/cont-new.mkiv | 2 +- tex/context/base/mkiv/context.mkiv | 2 +- tex/context/base/mkiv/context.mkxl | 2 +- tex/context/base/mkiv/driv-shp.lua | 28 + tex/context/base/mkiv/font-con.lua | 7 +- tex/context/base/mkiv/font-lib.mkvi | 16 +- tex/context/base/mkiv/font-ocl.lua | 89 +- tex/context/base/mkiv/font-ogr.lua | 381 +++++ tex/context/base/mkiv/font-otl.lua | 1 + tex/context/base/mkiv/font-tpk.lua | 28 +- tex/context/base/mkiv/grph-chk.lua | 2 +- tex/context/base/mkiv/grph-img.lua | 50 +- tex/context/base/mkiv/lpdf-emb.lua | 397 +++-- tex/context/base/mkiv/lpdf-img.lua | 26 +- tex/context/base/mkiv/lpdf-ini.lua | 79 +- tex/context/base/mkiv/lpdf-lmt.lua | 111 +- tex/context/base/mkiv/lxml-css.lua | 17 +- tex/context/base/mkiv/meta-fig.mkiv | 7 +- tex/context/base/mkiv/meta-ini.mkiv | 3 + tex/context/base/mkiv/meta-ini.mkxl | 3 + tex/context/base/mkiv/mlib-cnt.lua | 1770 ++++++++++++++++++++ tex/context/base/mkiv/mlib-ctx.lua | 2 +- tex/context/base/mkiv/mlib-ctx.mkiv | 3 +- tex/context/base/mkiv/mlib-ctx.mkxl | 3 + tex/context/base/mkiv/mlib-lmp.lua | 8 + tex/context/base/mkiv/mlib-lua.lua | 14 +- tex/context/base/mkiv/mlib-pdf.lua | 13 +- tex/context/base/mkiv/mlib-pps.lua | 6 +- tex/context/base/mkiv/mlib-run.lua | 105 +- tex/context/base/mkiv/mlib-scn.lua | 19 + tex/context/base/mkiv/mlib-svg.lua | 1635 ++++++++++++++++++ tex/context/base/mkiv/mult-def.lua | 4 + tex/context/base/mkiv/mult-fun.lua | 1 + tex/context/base/mkiv/mult-mps.lua | 2 +- tex/context/base/mkiv/status-files.pdf | Bin 26674 -> 26690 bytes tex/context/base/mkiv/status-lua.pdf | Bin 268804 -> 267983 bytes tex/context/base/mkiv/strc-itm.mklx | 26 +- tex/context/base/mkiv/strc-itm.mkvi | 26 +- tex/context/base/mkiv/strc-ref.lua | 16 +- tex/context/base/mkiv/syst-aux.mkiv | 2 +- tex/context/base/mkiv/syst-aux.mkxl | 2 +- tex/context/base/mkiv/trac-deb.lua | 5 +- tex/context/base/mkiv/util-lua.lua | 10 +- tex/context/base/mkiv/util-sac.lua | 57 + tex/context/interface/mkii/keys-en.xml | 1 + tex/context/interface/mkii/keys-it.xml | 8 + tex/context/interface/mkii/keys-ro.xml | 1 + tex/context/interface/mkiv/context-en.xml | 2 + tex/context/interface/mkiv/i-context.pdf | Bin 893466 -> 893435 bytes tex/context/interface/mkiv/i-itemgroup.xml | 1 + tex/context/interface/mkiv/i-readme.pdf | Bin 61165 -> 61165 bytes tex/context/modules/mkiv/m-scite.mkiv | 7 +- tex/context/modules/mkiv/s-fonts-variable.mkiv | 34 +- tex/context/modules/mkiv/s-present-luatex.mkiv | 118 ++ tex/context/modules/mkiv/s-tugboat.mkiv | 818 ++++----- tex/generic/context/luatex/luatex-basics-nod.lua | 3 - tex/generic/context/luatex/luatex-fonts-merged.lua | 65 +- 65 files changed, 5283 insertions(+), 808 deletions(-) create mode 100644 tex/context/base/mkiv/font-ogr.lua create mode 100644 tex/context/base/mkiv/mlib-cnt.lua create mode 100644 tex/context/base/mkiv/mlib-svg.lua create mode 100644 tex/context/modules/mkiv/s-present-luatex.mkiv (limited to 'tex') diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index eced6e05b..a1f67933b 100644 --- a/tex/context/base/mkii/cont-new.mkii +++ b/tex/context/base/mkii/cont-new.mkii @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2019.09.10 20:03} +\newcontextversion{2019.09.27 17:59} %D This file is loaded at runtime, thereby providing an %D excellent place for hacks, patches, extensions and new diff --git a/tex/context/base/mkii/context.mkii b/tex/context/base/mkii/context.mkii index b3afbbceb..2a4b1588d 100644 --- a/tex/context/base/mkii/context.mkii +++ b/tex/context/base/mkii/context.mkii @@ -20,7 +20,7 @@ %D your styles an modules. \edef\contextformat {\jobname} -\edef\contextversion{2019.09.10 20:03} +\edef\contextversion{2019.09.27 17:59} %D For those who want to use this: diff --git a/tex/context/base/mkii/mult-en.mkii b/tex/context/base/mkii/mult-en.mkii index 65721ddaa..77f45f832 100644 --- a/tex/context/base/mkii/mult-en.mkii +++ b/tex/context/base/mkii/mult-en.mkii @@ -234,6 +234,7 @@ \setinterfacevariable{hanging}{hanging} \setinterfacevariable{head}{head} \setinterfacevariable{header}{header} +\setinterfacevariable{headintext}{headintext} \setinterfacevariable{height}{height} \setinterfacevariable{helptext}{helptext} \setinterfacevariable{hencefore}{hencefore} diff --git a/tex/context/base/mkii/mult-it.mkii b/tex/context/base/mkii/mult-it.mkii index 432917617..a5d956ba4 100644 --- a/tex/context/base/mkii/mult-it.mkii +++ b/tex/context/base/mkii/mult-it.mkii @@ -234,6 +234,7 @@ \setinterfacevariable{hanging}{sospeso} \setinterfacevariable{head}{testa} \setinterfacevariable{header}{intestazione} +\setinterfacevariable{headintext}{headintext} \setinterfacevariable{height}{altezza} \setinterfacevariable{helptext}{testoaiuto} \setinterfacevariable{hencefore}{precedente} @@ -867,6 +868,7 @@ \setinterfaceconstant{hfil}{hfil} \setinterfaceconstant{hidenumber}{hidenumber} \setinterfaceconstant{hoffset}{hoffset} +\setinterfaceconstant{horizontal}{orizzontale} \setinterfaceconstant{horoffset}{horoffset} \setinterfaceconstant{hyphen}{hyphen} \setinterfaceconstant{hyphens}{hyphens} @@ -967,9 +969,12 @@ \setinterfaceconstant{menu}{menu} \setinterfaceconstant{method}{metodo} \setinterfaceconstant{middle}{centro} +\setinterfaceconstant{middlecolor}{middlecolor} \setinterfaceconstant{middlecommand}{middlecommand} \setinterfaceconstant{middlespeech}{middlespeech} +\setinterfaceconstant{middlestyle}{middlestyle} \setinterfaceconstant{middletext}{testocentro} +\setinterfaceconstant{middlewidth}{middlewidth} \setinterfaceconstant{midsentence}{midsentence} \setinterfaceconstant{min}{min} \setinterfaceconstant{mindepth}{mindepth} @@ -1287,6 +1292,7 @@ \setinterfaceconstant{vcompact}{vcompact} \setinterfaceconstant{vector}{vector} \setinterfaceconstant{veroffset}{veroffset} +\setinterfaceconstant{vertical}{verticale} \setinterfaceconstant{vfactor}{vfactor} \setinterfaceconstant{vfil}{vfil} \setinterfaceconstant{viewerprefix}{viewerprefix} @@ -1297,6 +1303,7 @@ \setinterfaceconstant{white}{bianco} \setinterfaceconstant{width}{ampiezza} \setinterfaceconstant{words}{words} +\setinterfaceconstant{xanchor}{xanchor} \setinterfaceconstant{xfactor}{xfactor} \setinterfaceconstant{xhtml}{xhtml} \setinterfaceconstant{xmax}{xmax} @@ -1304,6 +1311,7 @@ \setinterfaceconstant{xoffset}{xoffset} \setinterfaceconstant{xscale}{xscale} \setinterfaceconstant{xstep}{xstep} +\setinterfaceconstant{yanchor}{yanchor} \setinterfaceconstant{yfactor}{yfactor} \setinterfaceconstant{ymax}{ymax} \setinterfaceconstant{yoffset}{yoffset} diff --git a/tex/context/base/mkii/mult-ro.mkii b/tex/context/base/mkii/mult-ro.mkii index 248cf8b18..ccdce5dcc 100644 --- a/tex/context/base/mkii/mult-ro.mkii +++ b/tex/context/base/mkii/mult-ro.mkii @@ -234,6 +234,7 @@ \setinterfacevariable{hanging}{suspendat} \setinterfacevariable{head}{antet} \setinterfacevariable{header}{antet} +\setinterfacevariable{headintext}{headintext} \setinterfacevariable{height}{inaltime} \setinterfacevariable{helptext}{textajutator} \setinterfacevariable{hencefore}{precedent} diff --git a/tex/context/base/mkiv/back-ini.lua b/tex/context/base/mkiv/back-ini.lua index bc4a67bc0..b0af61edc 100644 --- a/tex/context/base/mkiv/back-ini.lua +++ b/tex/context/base/mkiv/back-ini.lua @@ -182,6 +182,29 @@ implement { end } + +local page_x_origin = 0 +local page_y_origin = 0 + +function codeinjections.setpageorigin(x,y) + page_x_origin = x + page_y_origin = y +end + +function codeinjections.getpageorigin() + local x = page_x_origin + local y = page_y_origin + page_x_origin = 0 + page_y_origin = 0 + return x, y, (x ~= 0 or y ~= 0) +end + +implement { + name = "setpageorigin", + arguments = { "dimension", "dimension" }, + actions = codeinjections.setpageorigin, +} + -- could also be codeinjections function backends.noflatelua() diff --git a/tex/context/base/mkiv/back-pdf.mkiv b/tex/context/base/mkiv/back-pdf.mkiv index ff05e647f..ec1c641e6 100644 --- a/tex/context/base/mkiv/back-pdf.mkiv +++ b/tex/context/base/mkiv/back-pdf.mkiv @@ -145,8 +145,10 @@ %D Bah, this is also needed for tikz: -\let\pdfsavepos \savepos -\let\pdflastxpos\lastxpos -\let\pdflastypos\lastypos +\ifdefined\pdfsavepos\else + \let\pdfsavepos \savepos + \let\pdflastxpos\lastxpos + \let\pdflastypos\lastypos +\fi \protect \endinput diff --git a/tex/context/base/mkiv/back-pdf.mkxl b/tex/context/base/mkiv/back-pdf.mkxl index 2b0c4d0b7..ab65458c5 100644 --- a/tex/context/base/mkiv/back-pdf.mkxl +++ b/tex/context/base/mkiv/back-pdf.mkxl @@ -145,4 +145,12 @@ \let\pdfactualtext\pdfbackendactualtext +%D Bah, this is also needed for tikz: + +\ifdefined\pdfsavepos\else + \let\pdfsavepos \savepos + \let\pdflastxpos\lastxpos + \let\pdflastypos\lastypos +\fi + \protect \endinput diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index 0a68310a0..6b05f4174 100644 --- a/tex/context/base/mkiv/cont-new.mkiv +++ b/tex/context/base/mkiv/cont-new.mkiv @@ -13,7 +13,7 @@ % \normalend % uncomment this to get the real base runtime -\newcontextversion{2019.09.10 20:03} +\newcontextversion{2019.09.27 17:59} %D This file is loaded at runtime, thereby providing an excellent place for %D hacks, patches, extensions and new features. diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index 2c17d4b91..907985beb 100644 --- a/tex/context/base/mkiv/context.mkiv +++ b/tex/context/base/mkiv/context.mkiv @@ -45,7 +45,7 @@ %D {YYYY.MM.DD HH:MM} format. \edef\contextformat {\jobname} -\edef\contextversion{2019.09.10 20:03} +\edef\contextversion{2019.09.27 17:59} \edef\contextkind {beta} %D Kind of special: diff --git a/tex/context/base/mkiv/context.mkxl b/tex/context/base/mkiv/context.mkxl index 39f647cc2..6651995c2 100644 --- a/tex/context/base/mkiv/context.mkxl +++ b/tex/context/base/mkiv/context.mkxl @@ -29,7 +29,7 @@ %D {YYYY.MM.DD HH:MM} format. \edef\contextformat {\jobname} -\edef\contextversion{2019.09.10 20:03} +\edef\contextversion{2019.09.27 17:59} \edef\contextkind {beta} %D Kind of special: diff --git a/tex/context/base/mkiv/driv-shp.lua b/tex/context/base/mkiv/driv-shp.lua index ae99d84ce..8fe2b8483 100644 --- a/tex/context/base/mkiv/driv-shp.lua +++ b/tex/context/base/mkiv/driv-shp.lua @@ -150,6 +150,7 @@ local updatefontstate local pushorientation local poporientation local flushcharacter +local flushfontchar local flushrule local flushliteral local flushsetmatrix @@ -241,6 +242,32 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand pos_h = pos_h + flushchar(font,char,font,chr,f,e) end end + elseif command == "use" then + local index = packet[2] + if index then + local fnt + if index == 0 then + fnt = font + else + local okay = fonts and fonts[index] + if okay then + fnt = okay.id + end + end + if fnt then + -- not efficient but ok for now as experiment + local d = characters[fnt] + if d then + for i=3,#packet do + local chr = packet[i] + local dat = d[chr] + if dat then + flushfontchar(fnt,chr,dat) + end + end + end + end + end elseif command == "right" then local h = packet[2] -- * siz if factor ~= 0 and h ~= 0 then @@ -1118,6 +1145,7 @@ function drivers.converters.lmtx(driver,box,smode,objnum,specification) poporientation = flushers.poporientation flushcharacter = flushers.character + flushfontchar = flushers.fontchar flushrule = flushers.rule flushsimplerule = flushers.simplerule flushspecial = flushers.special diff --git a/tex/context/base/mkiv/font-con.lua b/tex/context/base/mkiv/font-con.lua index 9c1e825fa..652794142 100644 --- a/tex/context/base/mkiv/font-con.lua +++ b/tex/context/base/mkiv/font-con.lua @@ -444,9 +444,10 @@ function constructors.scale(tfmdata,specification) -- boundarychar = 65536, -- there is now a string 'right_boundary' -- false_boundarychar = 65536, -- produces invalid tfm in luatex -- - targetproperties.language = properties.language or "dflt" -- inherited - targetproperties.script = properties.script or "dflt" -- inherited - targetproperties.mode = properties.mode or "base" -- inherited + targetproperties.language = properties.language or "dflt" -- inherited + targetproperties.script = properties.script or "dflt" -- inherited + targetproperties.mode = properties.mode or "base" -- inherited + targetproperties.method = properties.method -- local askedscaledpoints = scaledpoints local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined diff --git a/tex/context/base/mkiv/font-lib.mkvi b/tex/context/base/mkiv/font-lib.mkvi index 081ae0f38..c9050f61b 100644 --- a/tex/context/base/mkiv/font-lib.mkvi +++ b/tex/context/base/mkiv/font-lib.mkvi @@ -28,7 +28,6 @@ % helpers - \registerctxluafile{font-otr}{optimize} % opentype fontloader \registerctxluafile{font-web}{} % opentype fontloader \registerctxluafile{font-cff}{optimize} % quadratic outlines @@ -51,7 +50,12 @@ \registerctxluafile{font-oth}{} \registerctxluafile{font-osd}{} % \doifelsefileexists{font-osm.lua}{\registerctxluafile{font-osm}{}}{} -\registerctxluafile{font-ocl}{} + +\ifcase\contextlmtxmode + \registerctxluafile{font-ocl}{} +\else + \registerctxluafile{font-ogr}{} +\fi % we use otf code for type one @@ -61,12 +65,8 @@ % tfm -\doifelsefileexists {font-tpk.lua} { - \registerctxluafile{font-tpk}{optimize} - \registerctxluafile{font-tfm}{} -} { - \registerctxluafile{font-tfm}{} -} +\registerctxluafile{font-tpk}{optimize} +\registerctxluafile{font-tfm}{} % name database diff --git a/tex/context/base/mkiv/font-ocl.lua b/tex/context/base/mkiv/font-ocl.lua index 10d2df270..9ce6982a8 100644 --- a/tex/context/base/mkiv/font-ocl.lua +++ b/tex/context/base/mkiv/font-ocl.lua @@ -8,6 +8,10 @@ if not modules then modules = { } end modules ['font-ocl'] = { -- todo : user list of colors +if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then + return +end + local tostring, tonumber, next = tostring, tonumber, next local round, max = math.round, math.round local gsub, find = string.gsub, string.find @@ -17,8 +21,6 @@ local setmetatableindex = table.setmetatableindex local formatters = string.formatters local tounicode = fonts.mappings.tounicode -local bpfactor = number.dimenfactors.bp - local helpers = fonts.helpers local charcommand = helpers.commands.char @@ -367,19 +369,25 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) -- kind = png|svg for unicode, character in sortedhash(characters) do -- sort is nicer for svg local index = character.index if index then - local pdf = pdfshapes[index] - local typ = type(pdf) - local data = nil - local dx = nil - local dy = nil + local pdf = pdfshapes[index] + local typ = type(pdf) + local data = nil + local dx = nil + local dy = nil + local scale = 1 if typ == "table" then - data = pdf.data - dx = pdf.x or pdf.dx or 0 - dy = pdf.y or pdf.dy or 0 + data = pdf.data + dx = pdf.x or pdf.dx or 0 + dy = pdf.y or pdf.dy or 0 + scale = pdf.scale or 1 elseif typ == "string" then data = pdf dx = 0 dy = 0 + elseif typ == "number" then + data = pdf + dx = 0 + dy = 0 end if data then -- We can delay storage by a lua function in commands: but then we need to @@ -402,7 +410,7 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) -- kind = png|svg -- mkiv downcommand [dp + dy * hfactor], rightcommand[ dx * hfactor], - vfimage(wd,ht,dp,data,name), + vfimage(scale*wd,ht,dp,data,pdfshapes.filename or ""), actuale, } character[kind] = true @@ -457,12 +465,17 @@ do -- poor mans variant for generic: -- runner = function() - return io.open("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w") + return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w") end end -- There are svg out there with bad viewBox specifications where shapes lay outside that area, - -- but trying to correct that didin'w work out well enough so I discarded that code. + -- but trying to correct that didn't work out well enough so I discarded that code. BTW, we + -- decouple the inskape run and the loading run because inkscape is working in the background + -- in the files so we need to have unique files. + -- + -- Because a generic setup can be flawed we need to catch bad inkscape runs which add a bit of + -- ugly overhead. Bah. function otfsvg.topdf(svgshapes,tfmdata) local pdfshapes = { } @@ -476,6 +489,7 @@ do local f_convert = formatters["%s --export-pdf=%s\n"] local filterglyph = otfsvg.filterglyph local nofdone = 0 + local processed = { } report_svg("processing %i svg containers",nofshapes) statistics.starttiming() for i=1,nofshapes do @@ -487,30 +501,55 @@ do local pdffile = f_pdffile(index) savedata(svgfile,data) inkscape:write(f_convert(svgfile,pdffile)) - pdfshapes[index] = true + processed[index] = true nofdone = nofdone + 1 - if nofdone % 100 == 0 then - report_svg("%i shapes processed",nofdone) + if nofdone % 25 == 0 then + report_svg("%i shapes submitted",nofdone) end end end end + if nofdone % 25 ~= 0 then + report_svg("%i shapes submitted",nofdone) + end + report_svg("processing can be going on for a while") inkscape:write("quit\n") inkscape:close() report_svg("processing %i pdf results",nofshapes) - for index in next, pdfshapes do + for index in next, processed do local svgfile = f_svgfile(index) local pdffile = f_pdffile(index) -- local fntdata = descriptions[indices[index]] -- local bounds = fntdata and fntdata.boundingbox - pdfshapes[index] = { - data = loaddata(pdffile), - -- x = bounds and bounds[1] or 0, - -- y = bounds and bounds[2] or 0, - } + local pdfdata = loaddata(pdffile) + if pdfdata and pdfdata ~= "" then + pdfshapes[index] = { + data = pdfdata, + -- x = bounds and bounds[1] or 0, + -- y = bounds and bounds[2] or 0, + } + end remove(svgfile) remove(pdffile) end +local characters = tfmdata.characters +for k, v in next, characters do + local d = descriptions[k] + local i = d.index + if i then + local p = pdfshapes[i] + if p then + local w = d.width + local l = d.boundingbox[1] + local r = d.boundingbox[3] + p.scale = (r - l) / w + p.x = l + end + end +end + if not next(pdfshapes) then + report_svg("there are no converted shapes, fix your setup") + end statistics.stoptiming() if statistics.elapsedseconds then report_svg("svg conversion time %s",statistics.elapsedseconds() or "-") @@ -531,10 +570,12 @@ local function initializesvg(tfmdata,kind,value) -- hm, always value end local pdffile = containers.read(otf.pdfcache,hash) local pdfshapes = pdffile and pdffile.pdfshapes - if not pdfshapes or pdffile.timestamp ~= timestamp then + if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) then + -- the next test tries to catch errors in generic usage but of course can result + -- in running again and again local svgfile = containers.read(otf.svgcache,hash) local svgshapes = svgfile and svgfile.svgshapes - pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata) or { } + pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or { } containers.write(otf.pdfcache, hash, { pdfshapes = pdfshapes, timestamp = timestamp, diff --git a/tex/context/base/mkiv/font-ogr.lua b/tex/context/base/mkiv/font-ogr.lua new file mode 100644 index 000000000..c441ddbf7 --- /dev/null +++ b/tex/context/base/mkiv/font-ogr.lua @@ -0,0 +1,381 @@ +if not modules then modules = { } end modules ['font-ogr'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- Here we deal with graphic variants and for now also color support ends up here +-- but that might change. It's lmtx only code. + +if not context then + return +elseif CONTEXTLMTXMODE == 0 then + return +end + +local tostring, tonumber, next = tostring, tonumber, next +local round, max, mod, div = math.round, math.round, math.mod, math.div +local concat, setmetatableindex = table.concat, table.setmetatableindex +local formatters = string.formatters + +local otf = fonts.handlers.otf +local otfregister = otf.features.register +otf.svgenabled = true +otf.pngenabled = true + +-- Just to remind me ... rewritten around the time this was posted on YT which +-- was also around the 2019 ctx meeting: +-- +-- Gavin Harrison - "Threatening War" by The Pineapple Thief +-- https://www.youtube.com/watch?v=ENF9wth4kwM + +-- todo: svg color plugin +-- todo: get rid of double cm in svg (tricky as also elsewhere) +-- todo: png offsets (depth) +-- todo: maybe collapse indices so that we have less files (harder to debug) +-- todo: manage (read: assign) font id's in lua so we know in advance + +do + + -- This is a prelude to something better but I'm still experimenting. + + local dropins = { } + fonts.dropins = dropins + local droppedin = 0 + local identifiers = fonts.hashes.identifiers + + function dropins.nextid() + droppedin = droppedin - 1 + return droppedin + end + + function dropins.provide(method,clone,t_tfmdata,indexdata,...) + droppedin = dropins.nextid() + local t_characters = t_tfmdata.characters + local t_descriptions = t_tfmdata.descriptions + local t_properties = t_tfmdata.properties + local d_tfmdata = setmetatableindex({ },t_tfmdata) + local d_properties = setmetatableindex({ },t_properties) + d_tfmdata.properties = d_properties + if clone then + local d_characters = setmetatableindex({ },t_characters) + local d_descriptions = setmetatableindex({ },t_descriptions) + d_tfmdata.characters = d_characters + d_tfmdata.descriptions = d_descriptions + end + d_properties.instance = - droppedin -- will become an extra element in the hash + t_properties.virtualized = true + identifiers[droppedin] = d_tfmdata + local fonts = t_tfmdata.fonts or { } + t_tfmdata.fonts = fonts + d_properties.format = "type3" + d_properties.method = method + d_properties.indexdata = { indexdata, ... } -- can take quite some memory + local slot = #fonts + 1 + fonts[slot] = { id = droppedin } + if not clone then + for k, v in next, t_characters do + local index = v.index + if index and indexdata[index] then + -- todo: use extender + v.commands = { { "slot", slot, k } } + end + end + else + -- make sure that the drop characters should get a copy with no commands + -- (false will do) + end + return slot, droppedin, d_tfmdata, d_properties + end + +end + +-- This sits here for historcal reasons so for now we keep it here. + +local startactualtext = nil +local stopactualtext = nil + +function otf.getactualtext(s) + if not startactualtext then + startactualtext = backends.codeinjections.startunicodetoactualtextdirect + stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect + end + return startactualtext(s), stopactualtext() +end + +-- This is also somewhat specific. + +local sharedpalettes do + + sharedpalettes = { } + + local colors = attributes.list[attributes.private('color')] or { } + local transparencies = attributes.list[attributes.private('transparency')] or { } + + function otf.registerpalette(name,values) + sharedpalettes[name] = values + local color = lpdf.color + local transparency = lpdf.transparency + local register = colors.register + for i=1,#values do + local v = values[i] + if v == "textcolor" then + values[i] = false + else + local c = nil + local t = nil + if type(v) == "table" then + c = register(name,"rgb", + max(round((v.r or 0)*255),255)/255, + max(round((v.g or 0)*255),255)/255, + max(round((v.b or 0)*255),255)/255 + ) + else + c = colors[v] + t = transparencies[v] + end + if c and t then + values[i] = color(1,c) .. " " .. transparency(t) + elseif c then + values[i] = color(1,c) + elseif t then + values[i] = color(1,t) + end + end + end + end + +end + +do + + local f_color = formatters["%.3f %.3f %.3f rg"] + local f_gray = formatters["%.3f g"] + + local hash = setmetatableindex(function(t,k) + local v = k + t[k] = v + return v + end) + + local function convert(t,k) + local v = { } + for i=1,#k do + local p = k[i] + local r, g, b = p[1], p[2], p[3] + if r == g and g == b then + v[i] = hash[f_gray(r/255)] + else + v[i] = hash[f_color(r/255,g/255,b/255)] + end + end + t[k] = v + return v + end + + local function initialize(tfmdata,kind,value) -- we really need the id ... todo + if value then + local resources = tfmdata.resources + local palettes = resources.colorpalettes + if palettes then + -- + local converted = resources.converted + if not converted then + converted = setmetatableindex(convert) + resources.converted = converted + end + local colorvalues = sharedpalettes[value] + local default = false -- so the text color (bad for icon overloads) + if colorvalues then + default = colorvalues[#colorvalues] + else + colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { } + end + local classes = #colorvalues + if classes == 0 then + return + end + -- + -- todo: delay with lua function or plugin in vf handler, saves a lot of + -- overhead + -- + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local droppedin, tfmdrop, dropchars, dropdescs, colrshapes + -- alternative 1: + -- local slots = { } + -- alternative 2: + local idx = 255 + local slot = 0 + -- + for k, v in next, characters do + local index = v.index + if index then + local description = descriptions[k] + if description then + local colorlist = description.colors + if colorlist then + -- alternative 1: + -- local fnt = div(index,256) + -- local idx = mod(index,256) + -- local slt = slots[fnt] + -- if not slt then + -- colrshapes = { } + -- slot, droppedin, tfmdrop = fonts.dropins.provide("color",true,tfmdata,colrshapes,colorvalues) + -- slt = { slot, colrshapes, tfmdrop.characters, tfmdrop.descriptions } + -- slots[fnt] = slt + -- end + -- slot = slt[1] + -- colrshapes = slt[2] + -- dropchars = slt[3] + -- dropdescs = slt[4] + -- alternative 2: + if idx >= 255 then + idx = 1 + colrshapes = { } + slot, droppedin, tfmdrop = fonts.dropins.provide("color",true,tfmdata,colrshapes,colorvalues) + dropchars = tfmdrop.characters + dropdescs = tfmdrop.descriptions + else + idx = idx + 1 + end + -- + colrshapes[idx] = description + -- todo: use extender + local u = { "use", 0 } + for i=1,#colorlist do + u[i+2] = colorlist[i].slot + end + v.commands = { u, { "slot", slot, idx } } + -- hack to prevent that type 3 also gets 'use' flags .. todo + local c = { commands = false, index = idx, dropin = tfmdata } + local d = { index = idx, dropin = tfmdata } + setmetatableindex(c,v) + setmetatableindex(d,description) + dropchars[idx] = c + dropdescs[idx] = d + end + end + end + end + end + end + end + + fonts.handlers.otf.features.register { + name = "colr", + description = "color glyphs", + manipulators = { + base = initialize, + node = initialize, + } + } + +end + +do + + local report_svg = logs.reporter("fonts","svg") + + local cached = true directives.register("fonts.svg.cached", function(v) cached = v end) + + local function initializesvg(tfmdata,kind,value) -- hm, always value + if value then + local properties = tfmdata.properties + local svg = properties.svg + local hash = svg and svg.hash + local timestamp = svg and svg.timestamp + if not hash then + return + end + if cached then + -- we need a different hash than for mkiv, so we append: + local pdfhash = hash .. "-svg" + local pdffile = containers.read(otf.pdfcache,pdfhash) + local pdfshapes = pdffile and pdffile.pdfshapes + local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf")) + if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg) or { } + containers.write(otf.pdfcache, pdfhash, { + pdfshapes = pdfshapes, + timestamp = timestamp, + }) + end + -- this can be delayed, no need to keep this in mem + fonts.dropins.provide("pdf",false,tfmdata,pdfshapes) + else + local mpsfile = containers.read(otf.mpscache,hash) + local mpsshapes = mpsfile and mpsfile.mpsshapes + if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + -- still suboptimal + mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg) or { } + containers.write(otf.mpscache, hash, { + mpsshapes = mpsshapes, + timestamp = timestamp, + }) + end + fonts.dropins.provide("mps",false,tfmdata,mpsshapes) + end + end + end + + otfregister { + name = "svg", + description = "svg glyphs", + manipulators = { + base = initializesvg, + node = initializesvg, + } + } + +end + +do + + -- If this is really critical we can also use a pdf file as cache but I don't expect + -- png fonts to remain used. + + local report_png = logs.reporter("fonts","png conversion") + + local function initializepng(tfmdata,kind,value) -- hm, always value + if value then + local properties = tfmdata.properties + local png = properties.png + local hash = png and png.hash + local timestamp = png and png.timestamp + if not hash then + return + end + local pngfile = containers.read(otf.pngcache,hash) + local pngshapes = pngfile and pngfile.pngshapes + if pngshapes then + fonts.dropins.provide("png",false,tfmdata,pngshapes) + end + end + end + + otfregister { + name = "sbix", + description = "sbix glyphs", + manipulators = { + base = initializepng, + node = initializepng, + } + } + + otfregister { + name = "cblc", + description = "cblc glyphs", + manipulators = { + base = initializepng, + node = initializepng, + } + } + +end diff --git a/tex/context/base/mkiv/font-otl.lua b/tex/context/base/mkiv/font-otl.lua index f8cd8217c..8bf44d79e 100644 --- a/tex/context/base/mkiv/font-otl.lua +++ b/tex/context/base/mkiv/font-otl.lua @@ -57,6 +57,7 @@ otf.cache = containers.define("fonts", "otl", otf.version, true) otf.svgcache = containers.define("fonts", "svg", otf.version, true) otf.pngcache = containers.define("fonts", "png", otf.version, true) otf.pdfcache = containers.define("fonts", "pdf", otf.version, true) +otf.mpscache = containers.define("fonts", "mps", otf.version, true) otf.svgenabled = false otf.pngenabled = false diff --git a/tex/context/base/mkiv/font-tpk.lua b/tex/context/base/mkiv/font-tpk.lua index 2a0810a6b..26e3fc0f0 100644 --- a/tex/context/base/mkiv/font-tpk.lua +++ b/tex/context/base/mkiv/font-tpk.lua @@ -269,18 +269,18 @@ do end local template = formatters [ [[ - %.3N 0 %i %i %i %i d1 - q - %i 0 0 %i %i %i cm - BI - /W %i - /H %i - /IM true - /BPC 1 - /D [1 0] - ID %t - EI - Q]] ] +%.3N 0 %i %i %i %i d1 +q +%i 0 0 %i %i %i cm +BI + /W %i + /H %i + /IM true + /BPC 1 + /D [1 0] +ID %t +EI +Q]] ] function readers.pktopdf(glyph,width) local xsize = glyph.xsize or 0 @@ -319,9 +319,7 @@ do r = r + 1 ; result[r] = char(extract(b,8,8)) end end - return - template(width,llx,lly,urx,ury,xdpi,ydpi,llx,lly,xsize,ysize,result), - llx, lly, urx, ury + return template(width,llx,lly,urx,ury,xdpi,ydpi,llx,lly,xsize,ysize,result) end function readers.loadpk(filename) diff --git a/tex/context/base/mkiv/grph-chk.lua b/tex/context/base/mkiv/grph-chk.lua index fedd8d3ca..f286dedd6 100644 --- a/tex/context/base/mkiv/grph-chk.lua +++ b/tex/context/base/mkiv/grph-chk.lua @@ -207,7 +207,7 @@ function checkers.png(data) -- same as jpg (for now) local used = data.used if request and used and not request.scanimage then local identify = graphics.identify - local inject = lpdf.injectors.png + local inject = lpdf.injectors.png -- currently pdf specific local found = false request.scanimage = function(t) local result = wrappedidentify(identify,t.filename) diff --git a/tex/context/base/mkiv/grph-img.lua b/tex/context/base/mkiv/grph-img.lua index cde9c8442..795ea4827 100644 --- a/tex/context/base/mkiv/grph-img.lua +++ b/tex/context/base/mkiv/grph-img.lua @@ -29,6 +29,8 @@ local skipbytes = files.skip local setposition = files.setposition local getposition = files.getposition +local newreader = io.newreader + local setmetatableindex = table.setmetatableindex local setmetatablecall = table.setmetatablecall @@ -543,8 +545,8 @@ do if once then for i=1,#t do local l = t[i] - setposition(f,l.offset) - t[i] = readstring(f,l.length) + f:setposition(l.offset) + t[i] = f:readstring(l.length) end local data = concat(t) -- t wiped in caller @@ -553,14 +555,14 @@ do local data = { } for i=1,#t do local l = t[i] - setposition(f,l.offset) - data[i] = readstring(f,l.length) + f:setposition(l.offset) + data[i] = f:readstring(l.length) end return concat(data) end end - function identifiers.png(filename) + function identifiers.png(filename,method) local specification = { filename = filename, filetype = "png", @@ -569,7 +571,7 @@ do specification.error = "invalid filename" return specification -- error end - local f = io.open(filename,"rb") + local f = newreader(filename,method) if not f then specification.error = "unable to open file" return specification -- error @@ -581,44 +583,44 @@ do specification.pagenum = 1 specification.offset = 0 specification.length = 0 - local filesize = getsize(f) -- seek end + local filesize = f:getsize() -- seek end local tables = { } - local banner = readstring(f,8) + local banner = f:readstring(8) if banner ~= "\137PNG\013\010\026\010" then specification.error = "no png file" return specification -- error end while true do - local position = getposition(f) + local position = f:getposition() if position >= filesize then break end - local length = readcardinal4(f) + local length = f:readcardinal4() if not length then break end - local kind = readstring(f,4) + local kind = f:readstring(4) if kind then kind = lower(kind) else break end if kind == "ihdr" then -- metadata - specification.xsize = readcardinal4(f) - specification.ysize = readcardinal4(f) - specification.colordepth = readcardinal(f) - specification.colorspace = readcardinal(f) - specification.compression = readcardinal(f) - specification.filter = readcardinal(f) - specification.interlace = readcardinal(f) + specification.xsize = f:readcardinal4() + specification.ysize = f:readcardinal4() + specification.colordepth = f:readcardinal() + specification.colorspace = f:readcardinal() + specification.compression = f:readcardinal() + specification.filter = f:readcardinal() + specification.interlace = f:readcardinal() tables[kind] = true elseif kind == "iend" then tables[kind] = true break elseif kind == "phys" then - local x = readcardinal4(f) - local y = readcardinal4(f) - local u = readcardinal(f) + local x = f:readcardinal4() + local y = f:readcardinal4() + local u = f:readcardinal() if u == 1 then -- meters -- x = round(0.0254 * x) -- y = round(0.0254 * y) @@ -633,14 +635,16 @@ do tables[kind] = t end t[#t+1] = { - offset = getposition(f), +-- offset = getposition(f), + offset = f:getposition(), length = length, } else tables[kind] = true end - setposition(f,position+length+12) -- #size #kind #crc + f:setposition(position+length+12) -- #size #kind #crc end + f:close() specification.tables = tables return specification end diff --git a/tex/context/base/mkiv/lpdf-emb.lua b/tex/context/base/mkiv/lpdf-emb.lua index dbf6fd4d3..1cdcac1ae 100644 --- a/tex/context/base/mkiv/lpdf-emb.lua +++ b/tex/context/base/mkiv/lpdf-emb.lua @@ -45,7 +45,9 @@ local report_fonts = logs.reporter("backend","fonts") local trace_fonts = false local trace_details = false -local dimenfactors = number.dimenfactors +local dimenfactors = number.dimenfactors +local bpfactor = dimenfactors.bp +local ptfactor = dimenfactors.pt trackers.register("backend.pdf.fonts",function(v) trace_fonts = v end) @@ -1360,7 +1362,7 @@ do -- This is a hack, we have no basepoints. correction = details.fontdata.parameters.size / 1000 -- And this needs checking. - correction = correction * dimenfactors.bp / dimenfactors.pt + correction = correction * bpfactor / ptfactor metadata = { } end @@ -1510,114 +1512,296 @@ do mainwriters["opentype"](details) end - -- todo: map pdf glyphs onto companion type 3 font .. can be set of small - -- ones. maybe only private codes with proper tounicode + do + + -- The methods might become plugins. + + local methods = { } + + local pdfimage = lpdf.epdf.image + local openpdf = pdfimage.open + local closepdf = pdfimage.close + local copypage = pdfimage.copy + + local embedimage = images.embed + + local f_glyph = formatters["G%d"] + local f_char = formatters["BT /V%d 1 Tf [<%04X>] TJ ET"] + local f_width = formatters["%.6N 0 d0"] + local f_index = formatters["I%d"] + local f_image = formatters["%.6N 0 d0 /%s Do"] + local f_stream = formatters["%.6N 0 d0 %s"] - local methods = { } + -- A type 3 font has at most 256 characters and Acrobat also wants a zero slot + -- to be filled. We can share a mandate zero slot character. - function methods.pk(filename) - local resolution = 600 - local widthfactor = resolution / 72 - local scalefactor = 72 / resolution / 10 - local pkfullname = resolvers.findpk(basedfontname,resolution) - if not pkfullname or pkfullname == "" then - return + local c_notdef = nil + local r_notdef = nil + local w_notdef = nil + local fontbbox = nil + + -- pk inclusion (not really tested but not really used either) + + function methods.pk(filename) + local resolution = 600 + local widthfactor = resolution / 72 + local scalefactor = 72 / resolution / 10 + local pkfullname = resolvers.findpk(basedfontname,resolution) + if not pkfullname or pkfullname == "" then + return + end + local readers = fonts.handlers.tfm.readers + local result = readers.loadpk(pkfullname) + if not result or result.error then + return + end + return result.glyphs, widthfactor / 65536, scalefactor, readers.pktopdf end - local readers = fonts.handlers.tfm.readers - local result = readers.loadpk(pkfullname) - if not result or result.error then - return + + -- pdf inclusion + + function methods.pdf(filename,details) + local properties = details.properties + local pdfshapes = properties.indexdata[1] + local pdfdoc = openpdf(pdfshapes.filename) + local xforms = pdfdictionary() + local nofglyphs = 0 + if pdfdoc then + local function pdftopdf(glyph,width,data) + local image = copypage(pdfdoc,glyph) + width = 100 * width * bpfactor + embedimage(image) + nofglyphs = nofglyphs + 1 + local name = f_glyph(nofglyphs) + xforms[name] = pdfreference(image.objnum) + local pdf = f_image(width,name) + return pdf, width + end + local function closepdf() + -- closepdf(pdfdoc) + end + local function getresources() + return pdfdictionary { XObject = xforms } + end + return pdfshapes, 1, 0.001, pdftopdf, closepdf, getresources + end end - return result.glyphs, widthfactor / 65536, scalefactor, readers.pktopdf - end - mainwriters["type3"] = function(details) - local properties = details.properties - local basefontname = details.basefontname or properties.name - local askedmethod = "pk" - local method = methods[askedmethod] - if not method then - return + -- mps inclusion + + function methods.mps(filename,details) + local properties = details.properties + local mpshapes = properties.indexdata[1] + local function mpstopdf(mp,width,data) + local pdf = metapost.simple("metafun",mp,true) -- can be sped up, minifun + local width = 100 * width * bpfactor + local stream = f_stream(width,pdf) + return stream, width + end + local function getresources() + return lpdf.collectedresources { + serialize = false, + } + end + return mpshapes, 1, 0.001, mpstopdf, nil, getresources end - local glyphs, widthfactor, scalefactor, glyphtopdf = method(basedfontname) - if not glyphs then - return + + -- png inclusion + + -- With d1 the image mask is used when given and obeys color. So it is needed for pure bw + -- bitmap fonts, so here we really need d0. + -- + -- Acrobat X pro only seems to see the image mask but other viewers are doing it ok. Acrobat + -- reader crashes. We really need to add a notdef! + + function methods.png(filename,details) + local properties = details.properties + local png = properties.png + local hash = png.hash + local pngshapes = properties.indexdata[1] + local xforms = pdfdictionary() + local nofglyphs = 0 + if pngshapes then + local function pngtopdf(glyph,width,data) + local info = graphics.identifiers.png(glyph.data,"string") + local image = lpdf.injectors.png(info,"string") + local bbox = image.bbox + local llx = bbox[1] * bpfactor + local lly = bbox[2] * bpfactor + local urx = bbox[3] * bpfactor + local ury = bbox[4] * bpfactor + width = width * bpfactor / 10 + embedimage(image) + nofglyphs = nofglyphs + 1 + local name = f_glyph(nofglyphs) + xforms[name] = pdfreference(image.objnum) + local pdf = f_image(width,name) + return pdf, width + end + local function closepng() + pngshapes = nil + end + local function getresources() + return pdfdictionary { XObject = xforms } + end + return pngshapes, 1, 1, pngtopdf, closepng, getresources + end end - local parameters = details.parameters - local object = details.objectnumber - local factor = parameters.factor -- normally 1 - local f_name = formatters["I%05i"] - local fontmatrix = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 } - local indices, - minindex, - maxindex = collectindices(details.fontdata.characters,details.indices) - local widths = pdfarray() - local differences = pdfarray() - local charprocs = pdfdictionary() - local basefont = pdfconstant(basefontname) - local llx, lly, urx, ury = 0, 0, 0, 0 - for i=1,maxindex-minindex+1 do - widths[i] = 0 + + function methods.color(filename,details) + local colrshapes = details.properties.indexdata[1] + local colrvalues = details.properties.indexdata[2] + local usedfonts = { } + local dd = details.fontdata.descriptions -- temp hack + local function colrtopdf(description,wd,data) -- todo: chardata instead of descriptions + -- descriptions by index + local colorlist = description.colors + if colorlist then + local dropdata = data.dropin + local dropid = dropdata.properties.id + local dropunits = dropdata.parameters.units + usedfonts[dropid] = dropid + + local w = description.width or 0 + local s = #colorlist + local l = nil + local t = { f_width(w) } + local n = 1 + local d = colrvalues[#colrvalues] + + for i=1,s do + local entry = colorlist[i] + local v = colrvalues[entry.class] or d + if v and l ~= v then + n = n + 1 ; t[n] = v + l = v + end + local e = dd[entry.slot] + if e then + n = n + 1 ; t[n] = f_char(dropid,e.index) + end + end + return concat(t," "), w / dropunits + end + end + local function getresources() + return lpdf.collectedresources { + serialize = false, + fonts = usedfonts, + fontprefix = "V", + } + end + return colrshapes, 1, 1, colrtopdf, false, getresources end - local d = 0 - local lastindex = -0xFFFF - for index, data in sortedhash(indices) do - local name = f_name(index) - local glyph = glyphs[index] - if glyph then - local width = widthfactor * data.width - local stream, lx, ly, ux, uy = glyphtopdf(glyph,width) - if stream then - if index - 1 ~= lastindex then - d = d + 1 ; differences[d] = index + + mainwriters["type3"] = function(details) + local properties = details.properties + local basefontname = details.basefontname or properties.name + local askedmethod = properties.method or "pk" + local method = methods[askedmethod] or methods.pk + if not method then + return + end + local glyphs, widthfactor, scalefactor, + glyphtopdf, reset, getresources = method(basedfontname,details) + if not glyphs then + return + end + local parameters = details.parameters + local object = details.objectnumber + local factor = parameters.factor -- normally 1 + local fontmatrix = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 } + local indices, + minindex, + maxindex = collectindices(details.fontdata.characters,details.indices) + local widths = pdfarray() + local differences = pdfarray() + local charprocs = pdfdictionary() + local basefont = pdfconstant(basefontname) + local d = 0 + local forcenotdef = minindex > 0 + local lastindex = -0xFF + + if forcenotdef then + widths[0] = 0 + minindex = 0 + lastindex = 0 + d = 2 + if not c_notdef then + w_notdef = 0 + c_notdef = pdfconstant(".notdef") + r_notdef = pdfreference(pdfflushstreamobject("0 0 d0")) + end + differences[1] = w_notdef + differences[2] = c_notdef + charprocs[".notdef"] = r_notdef + end + + for i=1,maxindex-minindex+1 do + widths[i] = 0 + end + + for index, data in sortedhash(indices) do + local name = f_index(index) + local glyph = glyphs[index] + if glyph then + local width = widthfactor * data.width + local stream, wd = glyphtopdf(glyph,width,data) + if stream then + if wd then + width = wd + end + if index - 1 ~= lastindex then + d = d + 1 differences[d] = index + end + lastindex = index + d = d + 1 differences[d] = pdfconstant(name) + charprocs[name] = pdfreference(pdfflushstreamobject(stream)) + widths[index-minindex+1] = width end - lastindex = index - d = d + 1 ; differences[d] = pdfconstant(name) - charprocs[name] = pdfreference(pdfflushstreamobject(stream)) - widths[index-minindex+1] = width - if lx < llx then llx = lx end - if ux > urx then urx = ux end - if ly < lly then lly = ly end - if uy > ury then ury = uy end + else + report_fonts("missing glyph %i in type3 font %a",index,basefontname) end end + if not fontbbox then + -- The specification permits zero values and these are actually also more + -- robust as then there are no assumptions and no accuracy is needed. + fontbbox = pdfarray { 0, 0, 0, 0 } + end + local encoding = pdfdictionary { + Type = pdfconstant("Encoding"), + Differences = differences, + } + local tounicode = tounicodedictionary(details,indices,maxindex,basefontname) + local resources = getresources and getresources() or lpdf.procset(true) + local descriptor = pdfdictionary { + -- most is optional in type3 + Type = pdfconstant("FontDescriptor"), + FontName = basefont, + Flags = 4, + ItalicAngle = 0, + } + local parent = pdfdictionary { + Type = pdfconstant("Font"), + Subtype = pdfconstant("Type3"), + Name = basefont, + FontBBox = fontbbox, + FontMatrix = fontmatrix, + CharProcs = pdfreference(pdfflushobject(charprocs)), + Encoding = pdfreference(pdfflushobject(encoding)), + FirstChar = minindex, + LastChar = maxindex, + Widths = pdfreference(pdfflushobject(widths)), + FontDescriptor = pdfreference(pdfflushobject(descriptor)), + Resources = resources, + ToUnicode = pdfreference(pdfflushstreamobject(tounicode)), + } + pdfflushobject(object,parent) + if reset then + reset() + end end - local fontbbox = pdfarray { llx, lly, urx, ury } - local encoding = pdfdictionary { - Type = pdfconstant("Encoding"), - Differences = differences, - } - local tounicode = tounicodedictionary(details,indices,maxindex,basefontname) - local descriptor = pdfdictionary { - Type = pdfconstant("FontDescriptor"), - FontName = basefont, - Flags = 4, - FontBBox = fontbbox, - -- Ascent = scale(ascender), - -- Descent = scale(descender), - -- ItalicAngle = round(italicangle or 0), - -- CapHeight = scale(capheight), - -- StemV = scale(stemv), - -- XHeight = scale(xheight), - -- Metadata = fontmeta and pdfreference(pdfflushstreamobject(fontmeta)) or nil, - } - local parent = pdfdictionary { - Type = pdfconstant("Font"), - Subtype = pdfconstant("Type3"), - Name = basefont, - FontBBox = fontbbox, - FontMatrix = fontmatrix, - CharProcs = pdfreference(pdfflushobject(charprocs)), - Encoding = pdfreference(pdfflushobject(encoding)), - FirstChar = minindex, - LastChar = maxindex, - Widths = pdfreference(pdfflushobject(widths)), - FontDescriptor = pdfreference(pdfflushobject(descriptor)), - Resources = lpdf.procset(true), - ToUnicode = pdfreference(pdfflushstreamobject(tounicode)), - } - pdfflushobject(reserved,descriptor) - pdfflushobject(object,parent) + end end @@ -1650,7 +1834,8 @@ local objects = setmetatableindex(function(t,k) report_fonts("font id %i bound to hash %s and object %i",k,h,v) end else - report_fonts("fatal error, hash %s asked but not used",k,h,v) + -- no problem as it can be svg only + -- report_fonts("fatal error, hash %s asked but not used",k,h,v) v = pdfreserveobject() t[k] = v end @@ -1752,7 +1937,8 @@ function lpdf.flushfonts() end local properties = details.properties local bitmap = properties.usedbitmap - if bitmap then + local method = properties.method -- will be pk | pdf | svg | ... + if bitmap or method then local format = "type3" local writer = mainwriters[format] if writer then @@ -1786,7 +1972,7 @@ function lpdf.flushfonts() local vector = encoding.vector local indices = details.indices local remapped = { } - local factor = dimenfactors.bp * size / 65536 + local factor = bpfactor * size / 65536 for k, v in next, indices do local name = vector[k] local index = reverse[name] or 0 @@ -1826,7 +2012,7 @@ function lpdf.flushfonts() local vector = encoding.vector local indices = details.indices local remapped = { } - local factor = dimenfactors.bp * size / 65536 + local factor = bpfactor * size / 65536 for k, v in next, indices do local name = vector[k] local index = reverse[name] or 0 @@ -1868,8 +2054,11 @@ function lpdf.flushfonts() report_fonts("no %a writer for %a",format,filename) end end - else - report_fonts("no indices for %a",filename) + else -- not problem for svg ... + -- report_fonts("no indices for %a",filename) + end + if trace_fonts then + report_fonts("embedded indices: % t",table.sortedkeys(details.indices)) end mainfonts[details.hash] = false -- done end diff --git a/tex/context/base/mkiv/lpdf-img.lua b/tex/context/base/mkiv/lpdf-img.lua index 64ed642fa..6b19f0cfd 100644 --- a/tex/context/base/mkiv/lpdf-img.lua +++ b/tex/context/base/mkiv/lpdf-img.lua @@ -33,6 +33,8 @@ local openstring = streams.openstring local readstring = streams.readstring local readbytetable = streams.readbytetable +local newreader = io.newreader + local tobytetable = string.bytetable local lpdf = lpdf or { } @@ -871,7 +873,7 @@ do end end) - function injectors.png(specification) + function injectors.png(specification,method) -- todo: method in specification -- inspect(specification) if specification.error then return @@ -901,7 +903,7 @@ do if not idat then return end - local pngfile = io.open(filename,"rb") -- todo: in-mem too + local pngfile = newreader(filename,method) if not pngfile then return end @@ -1094,8 +1096,10 @@ do if specification.colorref then xobject.ColorSpace = pdfreference(specification.colorref) end + local width = specification.width or xsize * 65536 + local height = specification.height or ysize * 65536 return createimage { - bbox = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate + bbox = { 0, 0, width/xsize, height/ysize }, -- mandate transform = specification.transform, nolength = true, nobbox = true, @@ -1204,14 +1208,14 @@ do local content = pack(specification,index and "indexed" or "data") local mask = specification.mask local colorspace = pdfconstant(colorspace) -if index then - colorspace = pdfarray { - pdfconstant("Indexed"), - colorspace, - #index + (index[0] and 0 or -1), -- upper index - pdfverbose(pack(specification,"index")) - } -end + if index then + colorspace = pdfarray { + pdfconstant("Indexed"), + colorspace, + #index + (index[0] and 0 or -1), -- upper index + pdfverbose(pack(specification,"index")) + } + end local xobject = pdfdictionary { Type = pdfconstant("XObject"), Subtype = pdfconstant("Image"), diff --git a/tex/context/base/mkiv/lpdf-ini.lua b/tex/context/base/mkiv/lpdf-ini.lua index 19654a3d9..fd1e52aad 100644 --- a/tex/context/base/mkiv/lpdf-ini.lua +++ b/tex/context/base/mkiv/lpdf-ini.lua @@ -12,7 +12,7 @@ local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setme local char, byte, format, gsub, concat, match, sub, gmatch = string.char, string.byte, string.format, string.gsub, table.concat, string.match, string.sub, string.gmatch local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values local sind, cosd, max, min = math.sind, math.cosd, math.max, math.min -local sort = table.sort +local sort, sortedhash = table.sort, table.sortedhash local P, C, R, S, Cc, Cs, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs, lpeg.V local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns local formatters = string.formatters @@ -170,16 +170,16 @@ end local pdfgetpagereference updaters.register("backend.update.lpdf",function() - pdfreserveobject = pdf.reserveobj - pdfimmediateobject = pdf.immediateobj - pdfdeferredobject = pdf.obj - pdfreferenceobject = pdf.refobj + pdfreserveobject = pdf.reserveobj + pdfimmediateobject = pdf.immediateobj + pdfdeferredobject = pdf.obj + pdfreferenceobject = pdf.refobj - pdfgetpagereference = pdf.getpageref + pdfgetpagereference = pdf.getpageref - pdfsetpageresources = pdf.setpageresources - pdfsetpageattributes = pdf.setpageattributes - pdfsetpagesattributes = pdf.setpagesattributes + pdfsetpageresources = pdf.setpageresources + pdfsetpageattributes = pdf.setpageattributes + pdfsetpagesattributes = pdf.setpagesattributes end) local jobpositions = job.positions @@ -1253,32 +1253,47 @@ do -- todo: share them -- todo: force when not yet set + local f_font = formatters["%s%d"] + function lpdf.collectedresources(options) local ExtGState = d_extgstates and next(d_extgstates ) and p_extgstates local ColorSpace = d_colorspaces and next(d_colorspaces) and p_colorspaces local Pattern = d_patterns and next(d_patterns ) and p_patterns local Shading = d_shades and next(d_shades ) and p_shades + local Font if options and options.patterns == false then Pattern = nil end - if ExtGState or ColorSpace or Pattern or Shading then + local fonts = options and options.fonts + if fonts and next(fonts) then + local pdfgetfontobjnumber = lpdf.getfontobjnumber + if pdfgetfontobjnumber then + local prefix = options.fontprefix or "F" + Font = pdfdictionary { } + for k, v in sortedhash(fonts) do + Font[f_font(prefix,v)] = pdfreference(pdfgetfontobjnumber(k)) + end + end + end + if ExtGState or ColorSpace or Pattern or Shading or Fonts then local collected = pdfdictionary { ExtGState = ExtGState, ColorSpace = ColorSpace, Pattern = Pattern, Shading = Shading, + Font = Font, } if options and options.serialize == false then return collected else return collected() end + elseif options and options.notempty then + return nil + elseif options and options.serialize == false then + return pdfdictionary { } else - if options and options.serialize == false then - return pdfdictionary { } - else - return "" - end + return "" end end @@ -1487,41 +1502,23 @@ end do - local f_actual_text_one = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] - local f_actual_text_two = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] - local f_actual_text_one_b = formatters["BT /Span << /ActualText >> BDC"] - local f_actual_text_two_b = formatters["BT /Span << /ActualText >> BDC"] + local f_actual_text_p = formatters["BT /Span << /ActualText >> BDC %s EMC ET"] local f_actual_text_b = formatters["BT /Span << /ActualText >> BDC"] local s_actual_text_e = "EMC ET" local f_actual_text_b_not = formatters["/Span << /ActualText >> BDC"] - local f_actual_text_one_b_not = formatters["/Span << /ActualText >> BDC"] - local f_actual_text_two_b_not = formatters["/Span << /ActualText >> BDC"] local s_actual_text_e_not = "EMC" local f_actual_text = formatters["/Span <> BDC"] local context = context local pdfdirect = nodes.pool.directliteral -- we can use nuts.write deep down - - -- todo: use tounicode from the font mapper - - -- floor(unicode/1024) => rshift(unicode,10) -- better for 5.3 + local tounicode = fonts.mappings.tounicode function codeinjections.unicodetoactualtext(unicode,pdfcode) - if unicode < 0x10000 then - return f_actual_text_one(unicode,pdfcode) - else - return f_actual_text_two(rshift(unicode,10)+0xD800,unicode%1024+0xDC00,pdfcode) - end + return f_actual_text_p(type(unicode) == "string" and unicode or tounicode(unicode),pdfcode) end function codeinjections.startunicodetoactualtext(unicode) - if type(unicode) == "string" then - return f_actual_text_b(unicode) - elseif unicode < 0x10000 then - return f_actual_text_one_b(unicode) - else - return f_actual_text_two_b(rshift(unicode,10)+0xD800,unicode%1024+0xDC00) - end + return f_actual_text_b(type(unicode) == "string" and unicode or tounicode(unicode)) end function codeinjections.stopunicodetoactualtext() @@ -1529,13 +1526,7 @@ do end function codeinjections.startunicodetoactualtextdirect(unicode) - if type(unicode) == "string" then - return f_actual_text_b_not(unicode) - elseif unicode < 0x10000 then - return f_actual_text_one_b_not(unicode) - else - return f_actual_text_two_b_not(rshift(unicode,10)+0xD800,unicode%1024+0xDC00) - end + return f_actual_text_b_not(type(unicode) == "string" and unicode or tounicode(unicode)) end function codeinjections.stopunicodetoactualtextdirect() diff --git a/tex/context/base/mkiv/lpdf-lmt.lua b/tex/context/base/mkiv/lpdf-lmt.lua index 709a9d48a..8b40ee15c 100644 --- a/tex/context/base/mkiv/lpdf-lmt.lua +++ b/tex/context/base/mkiv/lpdf-lmt.lua @@ -37,6 +37,7 @@ local formatters, splitupstring = string.formatters, string.splitup local band, extract = bit32.band, bit32.extract local concat, sortedhash = table.concat, table.sortedhash local setmetatableindex = table.setmetatableindex +local loaddata = io.loaddata local bpfactor = number.dimenfactors.bp @@ -49,7 +50,6 @@ local tonut = nodes.tonut local getdata = nuts.getdata local getsubtype = nuts.getsubtype -local getfield = nuts.getfield local getwhd = nuts.getwhd local flushlist = nuts.flush_list @@ -583,6 +583,15 @@ local flushcharacter do end + flushfontchar = function(font,char,data) + local dummy = usedfonts[font] + local index = data.index or char + if not pdfcharacters[index] then + pdfcharacters[index] = true + end + return dummy + end + end -- literals @@ -1410,18 +1419,26 @@ local function finalize(driver,details) pageresources.XObject = xforms pageresources.ProcSet = lpdf.procset() + local xorigin, yorigin, relocated = backends.codeinjections.getpageorigin() -- for now here + + local bbox = pdfarray { + (boundingbox[1] + xorigin) * bpfactor, + (boundingbox[2] + yorigin) * bpfactor, + (boundingbox[3] + xorigin) * bpfactor, + (boundingbox[4] + yorigin) * bpfactor, + } + + if relocated then + content = formatters["1 0 0 1 %.6N %.6N cm\n%s"](bbox[1],bbox[2],content) + end + local contentsobj = pdfflushstreamobject(content,false,false) pageattributes.Type = pdf_page pageattributes.Contents = pdfreference(contentsobj) pageattributes.Resources = pageresources -- pageattributes.Resources = pdfreference(pdfflushobject(pageresources)) - pageattributes.MediaBox = pdfarray { - boundingbox[1] * bpfactor, - boundingbox[2] * bpfactor, - boundingbox[3] * bpfactor, - boundingbox[4] * bpfactor, - } + pageattributes.MediaBox = bbox pageattributes.Parent = nil -- precalculate pageattributes.Group = nil -- todo @@ -1431,6 +1448,12 @@ local function finalize(driver,details) lpdf.finalizepage(true) + if relocated then + if pageattributes.TrimBox then pageattributes.TrimBox = box end + if pageattributes.CropBox then pageattributes.CropBox = box end + if pageattributes.BleedBox then pageattributes.BleedBox = box end + end + else local xformtype = specification.type or 0 @@ -1850,18 +1873,18 @@ local function obj(a,b,c,d) nolength = a.nolength if kind == "stream" then if filename then - data = io.loaddata(filename) or "" + data = loaddata(filename) or "" end elseif kind == "raw"then if filename then - data = io.loaddata(filename) or "" + data = loaddata(filename) or "" end elseif kind == "file"then kind = "raw" - data = filename and io.loaddata(filename) or "" + data = filename and loaddata(filename) or "" elseif kind == "streamfile" then kind = "stream" - data = filename and io.loaddata(filename) or "" + data = filename and loaddata(filename) or "" end else if argtype == "number" then @@ -1877,10 +1900,10 @@ local function obj(a,b,c,d) data = b elseif a == "file" then -- kind = "raw" - data = io.loaddata(b) + data = loaddata(b) elseif a == "streamfile" then kind = "stream" - data = io.loaddata(b) + data = loaddata(b) else data = "" -- invalid object end @@ -2312,10 +2335,13 @@ end) updaters.register("backend.update.lpdf",function() - -- for the moment here, todo: an md5 or sha2 hash can save space + -- todo: an md5 or sha2 hash can save space + -- todo: make a type 3 font instead + -- todo: move to lpdf namespace local pdfimage = lpdf.epdf.image local newpdf = pdfimage.new + local openpdf = pdfimage.open local closepdf = pdfimage.close local copypage = pdfimage.copy @@ -2325,7 +2351,7 @@ updaters.register("backend.update.lpdf",function() local topdf = { } local toidx = { } - local function storedata(pdf) + local function storedata_s(pdf) local idx = toidx[pdf] if not idx then nofstreams = nofstreams + 1 @@ -2336,11 +2362,7 @@ updaters.register("backend.update.lpdf",function() return idx end - -- todo: make a type 3 font instead - - -- move to lpdf namespace - - local function vfimage(id,wd,ht,dp,pos_h,pos_v) + local function vfimage_s(id,wd,ht,dp,pos_h,pos_v) local index = topdf[id] if type(index) == "string" then local pdfdoc = newpdf(index,#index) @@ -2356,11 +2378,51 @@ updaters.register("backend.update.lpdf",function() flushimage(index,wd,ht,dp,pos_h,pos_v) end + local function storedata_n(name,page) + local idx = toidx[pdf] + if not idx then + nofstreams = nofstreams + 1 + idx = nofstreams + toidx[pdf] = nofstreams + topdf[idx] = pdf + end + return idx + end + + -- We need to have a way to close such a pdf ... esp for fonts. + + local pdfdocs = { } + + local function vfimage_n(name,page,wd,ht,dp,pos_h,pos_v) + local d = pdfdocs[name] + if not d then + d = { doc = openpdf(name), pages = { } } + pdfdocs[name] = d + end + local index = d.pages[page] + if not index then + local image = copypage(d.doc,page) + local bbox = image.bbox + image.width = bbox[3] - bbox[1] + image.height = bbox[4] - bbox[2] + embedimage(image) + index = image.index + d.pages[page] = index + end + flushimage(index,wd,ht,dp,pos_h,pos_v) + end + local function pdfvfimage(wd,ht,dp,data,name) - return { "lua", function(font,char,pos_h,pos_v) - local id = storedata(data) - vfimage(id,wd,ht,dp,pos_h,pos_v) - end } + if type(data) == "number" then + return { "lua", function(font,char,pos_h,pos_v) + vfimage_n(name,data,wd,ht,dp,pos_h,pos_v) + end } + else + return { "lua", function(font,char,pos_h,pos_v) + local id = storedata_s(data) + vfimage_s(id,wd,ht,dp,pos_h,pos_v) + end } + end end lpdf.vfimage = pdfvfimage @@ -2482,6 +2544,7 @@ do name = "pdf", flushers = { character = flushcharacter, + fontchar = flushfontchar, rule = flushrule, simplerule = flushsimplerule, pushorientation = pushorientation, diff --git a/tex/context/base/mkiv/lxml-css.lua b/tex/context/base/mkiv/lxml-css.lua index b0f5c9b72..8f7f19e84 100644 --- a/tex/context/base/mkiv/lxml-css.lua +++ b/tex/context/base/mkiv/lxml-css.lua @@ -146,9 +146,11 @@ if context then end +local p_digit = lpeg.patterns.digit + local pattern = Cf( Ct("") * ( Cg( - Cc("style") * ( + Cc("style") * ( C("italic") + C("oblique") + C("slanted") / "oblique" @@ -156,12 +158,17 @@ local pattern = Cf( Ct("") * ( + Cc("variant") * ( (C("smallcaps") + C("caps")) / "small-caps" ) - + Cc("weight") * + + Cc("weight") * ( C("bold") - + Cc("family") * ( - (C("mono") + C("type")) / "monospace" -- just ignore the "space(d)" + ) + + Cc("family") * ( + (C("mono") + C("type")) / "monospace" -- just ignore the "space(d)" + (C("sansserif") + C("sans")) / "sans-serif" -- match before serif - + C("serif") + + C("serif") + ) + + Cc("size") * Ct ( + (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1)) / tonumber + * C(P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in") + P("%")) ) ) --+ P("\\") * ( diff --git a/tex/context/base/mkiv/meta-fig.mkiv b/tex/context/base/mkiv/meta-fig.mkiv index bf37aa7bf..e89cb1442 100644 --- a/tex/context/base/mkiv/meta-fig.mkiv +++ b/tex/context/base/mkiv/meta-fig.mkiv @@ -44,12 +44,17 @@ \definefittingpage [MPpage] [\c!align=, - \c!command=\meta_process_graphic_instance{\fittingpageparameter\c!instance}, + \c!command=\meta_process_graphic_command, \c!instance=] \unexpanded\def\setupMPpage {\setupfittingpage[MPpage]} +\unexpanded\def\meta_process_graphic_command + {\doif{\fittingpageparameter\c!alternative}\v!offset + {\def\meta_relocate_graphic{\clf_setpageorigin\MPllx\MPlly\relax}}% + \meta_process_graphic_instance{\fittingpageparameter\c!instance}} + %D \macros %D {MPfigure} %D diff --git a/tex/context/base/mkiv/meta-ini.mkiv b/tex/context/base/mkiv/meta-ini.mkiv index fa5aa92c6..97d2e1a7d 100644 --- a/tex/context/base/mkiv/meta-ini.mkiv +++ b/tex/context/base/mkiv/meta-ini.mkiv @@ -278,9 +278,12 @@ {\pushMPboundingbox \setbox\b_meta_graphic\hpack\bgroup} +\let\meta_relocate_graphic\relax % experimental hook + \def\meta_process_graphic_stop {\egroup \meta_place_graphic + \meta_relocate_graphic \popMPboundingbox} \unexpanded\def\meta_process_graphic_instance#1% diff --git a/tex/context/base/mkiv/meta-ini.mkxl b/tex/context/base/mkiv/meta-ini.mkxl index 31117420c..745bd9268 100644 --- a/tex/context/base/mkiv/meta-ini.mkxl +++ b/tex/context/base/mkiv/meta-ini.mkxl @@ -277,9 +277,12 @@ {\pushMPboundingbox \setbox\b_meta_graphic\hpack\bgroup} +\let\meta_relocate_graphic\relax % experimental hook + \def\meta_process_graphic_stop {\egroup \meta_place_graphic + \meta_relocate_graphic \popMPboundingbox} \unexpanded\def\meta_process_graphic_instance#1% diff --git a/tex/context/base/mkiv/mlib-cnt.lua b/tex/context/base/mkiv/mlib-cnt.lua new file mode 100644 index 000000000..5bd27dbfd --- /dev/null +++ b/tex/context/base/mkiv/mlib-cnt.lua @@ -0,0 +1,1770 @@ +if not modules then modules = { } end modules ['mlib-cnt'] = { + 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", +} + +-- The only useful reference that I could find about this topic is in wikipedia: +-- +-- https://en.wikipedia.org/wiki/Marching_squares +-- +-- I derived the edge code from: +-- +-- https://physiology.arizona.edu/people/secomb/contours +-- +-- and also here: +-- +-- https://github.com/secomb/GreensV4 +-- +-- which has the banner: +-- +-- TWS, November 1989. Converted to C September 2007. Revised February 2009. +-- +-- and has a liberal licence. I optimized the code so that it runs a bit faster in Lua and +-- there's probably more room for optimization (I tried several variants). For instance I +-- don't use that many tables because access is costly. We don't have a compiler that does +-- some optimizing (even then the c code can probably be made more efficient). +-- +-- We often have the same node so it's cheaper to reuse the wsp tables and reconstruct +-- the point in the path then to alias the original point. We can be more clever: +-- straighten, but it's more work so maybe I will do that later; for now I only added +-- a test for equality. There are some experiments in this file too and not all might +-- work. It's a playground for me. +-- +-- The code is meant for metafun so it is not general purpose. The bitmap variant is +-- relatively fast and bitmaps compress well. When all is settled I might make a couple +-- of helpers in C for this purpose but not now. +-- +-- I removed optimization code (more aggressive flattening and such because it didn't really +-- pay off now that we use combined paths with just line segments. I also moved some other +-- code to a experimental copy. So we now only have the bare helpers needed here. + +-- Todo: look into zero case (lavel 1) for shapes ... omiting the background calculation +-- speeds up quite a bit. + +local next, type, tostring = next, type, tostring +local round, abs, min, max, floor = math.round, math.abs, math.min, math.max, math.floor +local concat, move = table.concat, table.move + +local starttiming = statistics.starttiming +local stoptiming = statistics.stoptiming +local elapsedtime = statistics.elapsedtime + +local formatters = string.formatters +local setmetatableindex = table.setmetatableindex +local sortedkeys = table.sortedkeys +local sequenced = table.sequenced + +local metapost = metapost or { } +local mp = mp or { } + +local getparameterset = metapost.getparameterset + +local mpflush = mp.flush +local mptriplet = mp.triplet +local mpstring = mp.string +local mpdraw = mp.draw +local mpfill = mp.fill +local mpflatten = mp.flatten + +local report = logs.reporter("mfun contour") + +local version = 0.007 + +-- we iterate using integers so that we get a better behaviour at zero + +local f_function_n = formatters [ [[ + local math = math + local round = math.round + %s + return function(data,nx,ny,nxmin,nxmax,xstep,nymin,nymax,ystep) + local sx = nxmin + for mx=1,nx do + local dx = data[mx] + local x = sx * xstep + local sy = nymin + for my=1,ny do + local y = sy * ystep + dx[my] = %s + sy = sy + 1 + end + sx = sx + 1 + end + return 0 + end +]] ] + +local f_function_y = formatters [ [[ + local math = math + local round = math.round + local nan = NaN + local inf = math.huge + %s + return function(data,nx,ny,nxmin,nxmax,xstep,nymin,nymax,ystep,dnan,dinf,report) + local sx = nxmin + local er = 0 + for mx=nxmin,nxmax do + local dx = data[mx] + local x = sx * xstep + local sy = nymin + for my=nymin,nymax do + local y = sy * ystep + local n = %s + if n == nan then + er = er + 1 + if er < 10 then + report("nan at (%s,%s) -> ",x,y,mx,my) + end + n = dnan + elseif n == inf then + er = er + 1 + if er < 10 then + report("inf at (%s,%s) -> ",x,y,mx,my) + end + n = dinf + end + dx[my] = n + sy = sy + 1 + end + sx = sx + 1 + end + return er + end +]] ] + +local f_color = formatters [ [[ + local math = math + local min = math.min + local max = math.max + local n = %s + local minz = %s + local maxz = %s + + local color_value = 0 + local color_step = mp.lmt_color_functions.step + local color_shade = mp.lmt_color_functions.shade + + local function step(...) + return color_step(color_value,n,...) + end + local function shade(...) + return color_shade(color_value,n,...) + end + local function lin(l) + return l/n + end + %s + return function(l) + color_value = l + return %s + end +]] ] + +local inbetween = attributes and attributes.colors.helpers.inbetween + +mp.lmt_color_functions = { + step = function(l,n,r,g,b,s) + if not s then + s = 1 + end + local f = l / n + local r = s * 1.5 - 4 * abs(f-r) + local g = s * 1.5 - 4 * abs(f-g) + local b = s * 1.5 - 4 * abs(f-b) + return min(max(r,0),1), min(max(g,0),1), min(max(b,0),1) + end, + shade = function(l,n,one,two) + local f = l / n + local r = inbetween(one,two,1,f) + local g = inbetween(one,two,2,f) + local b = inbetween(one,two,3,f) + return min(max(r,0),1), min(max(g,0),1), min(max(b,0),1) + end, +} + +local f_box = formatters [ [[draw rawtexbox("contour",%s) xysized (%s,%s) ;]] ] + +local n_box = 0 + +-- todo: remove old one, so we need to store old hashes + +local nofcontours = 0 + +-- We don't want cosmetics like axis and labels to trigger a calculation, +-- especially a slow one. + +local hashfields = { + "xmin", "xmax", "xstep", "ymin", "ymax", "ystep", + "levels", "colors", "level", "preamble", "function", "functions", "color", "values", + "background", "foreground", "linewidth", "backgroundcolor", "linecolor", +} + +local function prepare(p) + local h = { } + for i=1,#hashfields do + local f = hashfields[i] + h[f] = p[f] + end + local hash = md5.HEX(sequenced(h)) + local name = formatters["%s-m-c-%03i.lua"](tex.jobname,nofcontours) + return name, hash +end + +local function getcache(p) + local cache = p.cache + if cache then + local name, hash = prepare(p) + local data = table.load(name) + if data and data.hash == hash and data.version == version then + p.result = data + return true + else + return false, hash, name + end + end + return false, nil, nil +end + +local function setcache(p) + local result = p.result + local name = result.name + if name then + if result.bitmap then + result.bitmap = nil + else + result.data = nil + end + result.color = nil + result.action = nil + result.cached = nil + io.savedata(name, table.fastserialize(result)) + else + os.remove((prepare(p))) + end +end + +function mp.lmt_contours_start() + + starttiming("lmt_contours") + + nofcontours = nofcontours + 1 + + local p = getparameterset() + + local xmin = p.xmin + local xmax = p.xmax + local ymin = p.ymin + local ymax = p.ymax + local xstep = p.xstep + local ystep = p.ystep + local levels = p.levels + local colors = p.colors + local nx = 0 + local ny = 0 + local means = setmetatableindex("number") + local values = setmetatableindex("number") + local data = setmetatableindex("table") + local minmean = false + local maxmean = false + + local cached, hash, name = getcache(p) + + local function setcolors(preamble,levels,minz,maxz,color,nofvalues) + if colors then + local function f(k) + return #colors[1] == 1 and 0 or { 0, 0, 0 } + end + setmetatableindex(colors, function(t,k) + local v = f(k) + t[k] = v + return v + end) + local c = { } + local n = 1 + for i=1,nofvalues do + c[i] = colors[n] + n = n + 1 + end + return c, f + else + local tcolor = f_color(levels,minz,maxz,preamble,color) + local colors = { } + local fcolor = load(tcolor) + if type(fcolor) ~= "function" then + report("error in color function, case %i: %s",1,color) + fcolor = false + else + fcolor = fcolor() + if type(fcolor) ~= "function" then + report("error in color function, case %i: %s",2,color) + fcolor = false + end + end + if not fcolor then + local color_step = mp.lmt_color_functions.step + fcolor = function(l) + return color_step(l,levels,0.25, 0.50, 0.75) + end + end + for i=1,nofvalues do + colors[i] = { fcolor(i) } + end + return colors, fcolor + end + end + + if cached then + local result = p.result + local colors, color = setcolors( + p.preamble, + p.levels, + result.minz, + result.maxz, + p.color, + result.nofvalues + ) + result.color = color + result.colors = colors + result.cached = true + return + end + + local functioncode = p["function"] + local functionrange = p.range + local functionlist = functionrange and p.functions + local preamble = p.preamble + + if xstep == 0 then xstep = (xmax - xmin)/200 end + if ystep == 0 then ystep = (ymax - ymin)/200 end + + local nxmin = round(xmin/xstep) + local nxmax = round(xmax/xstep) + local nymin = round(ymin/ystep) + local nymax = round(ymax/ystep) + local nx = nxmax - nxmin + 1 + local ny = nymax - nymin + 1 + + local function executed(data,code) + local fcode = p.check and f_function_y or f_function_n + fcode = fcode(preamble,code) + fcode = load(fcode) + if type(fcode) == "function" then + fcode = fcode() + end + if type(fcode) == "function" then + local er = fcode( + data, nx, ny, + nxmin, nxmax, xstep, + nymin, nymax, ystep, + p.defaultnan, p.defaultinf, report + ) + if er > 0 then + report("%i errors in: %s",er,code) + end + return true + else + return false -- fatal error + end + end + + + -- for i=1,nx do + -- data[i] = lua.newtable(ny,0) + -- end + + if functionlist then + + local datalist = { } + local minr = functionrange[1] + local maxr = functionrange[2] or minr + local size = #functionlist + + for l=1,size do + + local func = setmetatableindex("table") + if not executed(func,functionlist[l]) then + report("error in executing function %i from functionlist",l) + return + end + + local bit = l -- + 1 + + if l == 1 then + for i=1,nx do + local di = data[i] + local fi = func[i] + for j=1,ny do + local f = fi[j] + if f >= minr and f <= maxr then + di[j] = bit + else + di[j] = 0 + end + end + end + else + for i=1,nx do + local di = data[i] + local fi = func[i] + for j=1,ny do + local f = fi[j] + if f >= minr and f <= maxr then + di[j] = di[j] | bit + end + end + end + end + + end + + -- we can simplify the value mess below + + elseif functioncode then + + if not executed(data,functioncode) then + report("error in executing function") + return -- fatal error + end + + else + + report("no valid function(s)") + return -- fatal error + + end + + minz = data[1][1] + maxz = minz + + for i=1,nx do + local d = data[i] + for j=1,ny do + local v = d[j] + if v < minz then + minz = v + elseif v > maxz then + maxz = v + end + end + end + + if functionlist then + + for i=minz,maxz do + values[i] = i + end + + levels = maxz + + minmean = minz + maxmean = maxz + + else + + local snap = (maxz - minz + 1) / levels + + for i=1,nx do + local d = data[i] + for j=1,ny do + local dj = d[j] + local v = round((dj - minz) / snap) + values[v] = values[v] + 1 + means [v] = means [v] + dj + d[j] = v + end + end + + local list = sortedkeys(values) + local count = values + local remap = { } + + values = { } + + for i=1,#list do + local v = list[i] + local m = means[v] / count[v] + remap [v] = i + values[i] = m + if not minmean then + minmean = m + maxmean = m + elseif m < minmean then + minmean = m + elseif m > maxmean then + maxmean = m + end + end + + for i=1,nx do + local d = data[i] + for j=1,ny do + d[j] = remap[d[j]] + end + end + + end + + -- due to rounding we have values + 1 so we can have a floor, ceil, round + -- option as well as levels -1 + + local nofvalues = #values + + local colors = setcolors( + p.preamble,levels,minz,maxz,p.color,nofvalues + ) + + p.result = { + version = version, + values = values, + nofvalues = nofvalues, + minz = minz, + maxz = maxz, + minmean = minmean, + maxmean = maxmean, + data = data, + color = color, + nx = nx, + ny = ny, + colors = colors, + name = name, + hash = hash, + islist = functionlist and true or false, + } + + report("index %i, nx %i, ny %i, levels %i", nofcontours, nx, ny, nofvalues) +end + +function mp.lmt_contours_stop() + local p = getparameterset() + local e = stoptiming("lmt_contours") + setcache(p) + p.result = nil + local f = p["function"] + local l = p.functions + report("index %i, %0.3f seconds for: %s", + nofcontours, e, "[ " .. concat(l or { f } ," ] [ ") .. " ]" + ) +end + +function mp.lmt_contours_bitmap_set() + local p = getparameterset() + local result = p.result + + local values = result.values + local nofvalues = result.nofvalues + local rawdata = result.data + local nx = result.nx + local ny = result.ny + local colors = result.colors + local depth = #colors[1] -- == 3 and "rgb" or "gray" + + -- i need to figure out this offset of + 1 + + local bitmap = graphics.bitmaps.new(nx,ny,depth == 3 and "rgb" or "gray",1,false,true) + + local palette = bitmap.index or { } -- has to start at 0 + local data = bitmap.data + local p = 0 + + if depth == 3 then + for i=1,nofvalues do + local c = colors[i] + local r = round((c[1] or 0) * 255) + local g = round((c[2] or 0) * 255) + local b = round((c[3] or 0) * 255) + palette[p] = { + (r > 255 and 255) or (r < 0 and 0) or r, + (g > 255 and 255) or (g < 0 and 0) or g, + (b > 255 and 255) or (b < 0 and 0) or b, + } + p = p + 1 + end + else + for i=1,nofvalues do + local s = colors[i][1] + local s = round((s or 0) * 255) + palette[p] = ( + (s > 255 and 255) or (s < 0 and 0) or s + ) + p = p + 1 + end + end + + -- As (1,1) is the left top corner so we need to flip of we start in + -- the left bottom (we cannot loop reverse because we want a properly + -- indexed table. + + local k = 0 + for y=ny,1,-1 do + k = k + 1 + local d = data[k] + for x=1,nx do + d[x] = rawdata[x][y] - 1 + end + end + + result.bitmap = bitmap +end + +function mp.lmt_contours_bitmap_get() + local p = getparameterset() + local result = p.result + local bitmap = result.bitmap + local box = nodes.hpack(graphics.bitmaps.flush(bitmap)) + n_box = n_box + 1 + nodes.boxes.savenode("contour",tostring(n_box),box) + return f_box(n_box,bitmap.xsize,bitmap.ysize) +end + +function mp.lmt_contours_cleanup() + nodes.boxes.reset("contour") + n_box = 0 +end + +function mp.lmt_contours_edge_set() + local p = getparameterset() + local result = p.result + + if result.cached then return end + + local values = result.values + local nofvalues = result.nofvalues + local data = result.data + local nx = result.nx + local ny = result.ny + + local xmin = p.xmin + local xmax = p.xmax + local ymin = p.ymin + local ymax = p.ymax + local xstep = p.xstep + local ystep = p.ystep + + local wsp = { } + local edges = { } + + for value=1,nofvalues do + + local iwsp = 0 + local di = data[1] + local dc + local edge = { } + local e = 0 + -- the next loop is fast + for i=1,nx do + local di1 = data[i+1] + local dij = di[1] + local d = dij - value + local dij1 + for j=1,ny do + if j < ny then + dij1 = di[j+1] + dc = dij1 - value + if (d >= 0 and dc < 0) or (d < 0 and dc >= 0) then + iwsp = iwsp + 1 + local y = (d * (j+1) - dc * j) / (d - dc) + if i == 1 then + wsp[iwsp] = { i, y, 0, (i + (j-1)*nx) } + elseif i == nx then + wsp[iwsp] = { i, y, (i - 1 + (j-1)*nx), 0 } + else + local jx = (i + (j-1)*nx) + wsp[iwsp] = { i, y, jx - 1, jx } + end + end + end + if i < nx then + local dc = di1[j] - value + if (d >= 0 and dc < 0) or (d < 0 and dc >= 0) then + iwsp = iwsp + 1 + local x = (d * (i+1) - dc * i) / (d - dc) + if j == 1 then + wsp[iwsp] = { x, j, 0, (i + (j-1)*nx) } + elseif j == ny then + wsp[iwsp] = { x, j, (i + (j-2)*nx), 0 } + else + local jx = i + (j-1)*nx + wsp[iwsp] = { x, j, jx - nx, jx } + end + end + end + dij = dij1 + d = dc + end + di = di1 + end + -- the next loop takes time + for i=1,iwsp do + local wspi = wsp[i] + for isq=3,4 do + local nsq = wspi[isq] + if nsq ~= 0 then + local px = wspi[1] + local py = wspi[2] + local p = { px, py } + local pn = 2 + wspi[isq] = 0 + while true do + for ii=1,iwsp do + local w = wsp[ii] + local n1 = w[3] + local n2 = w[4] + if n1 == nsq then + local x = w[1] + local y = w[2] + if x ~= px or y ~= py then + pn = pn + 1 + p[pn] = x + pn = pn + 1 + p[pn] = y + px = x + py = y + end + nsq = n2 + w[3] = 0 + w[4] = 0 + if nsq == 0 then + if pn == 1 then + pn = pn + 1 + p[pn] = w + end + goto flush + end + elseif n2 == nsq then + local x = w[1] + local y = w[2] + if x ~= px or y ~= py then + pn = pn + 1 + p[pn] = x + pn = pn + 1 + p[pn] = y + px = x + py = y + end + nsq = n1 + w[3] = 0 + w[4] = 0 + if nsq == 0 then + goto flush + end + end + end + end + ::flush:: + e = e + 1 + edge[e] = p + if mpflatten then + mpflatten(p) + end + end + end + end + + + edges[value] = edge + + end + + result.edges = edges + +end + +function mp.lmt_contours_shade_set(edgetoo) + local p = getparameterset() + local result = p.result + + if result.cached then return end + + local values = result.values + local nofvalues = result.nofvalues + local data = result.data + local nx = result.nx + local ny = result.ny + local color = result.color + + local edges = setmetatableindex("table") + local shades = setmetatableindex("table") + + local sqtype = setmetatableindex("table") + + local xspoly = { 0, 0, 0, 0, 0, 0 } + local yspoly = { 0, 0, 0, 0, 0, 0 } + local xrpoly = { } + local yrpoly = { } + + local xrpoly = { } -- lua.newtable(2000,0) + local yrpoly = { } -- lua.newtable(2000,0) + + -- for i=1,2000 do + -- xrpoly[i] = 0 + -- yrpoly[i] = 0 + -- end + + -- Unlike a c compiler lua will not optimize loops to run in parallel so we expand + -- some of the loops and make sure we don't calculate when not needed. Not that nice + -- but not that bad either. Maybe I should just write this from scratch. + +-- local i = 0 +-- local j = 0 + + -- Analyze each rectangle separately. Overwrite lower colors + + -- Unrolling the loops and copying code and using constants is faster and doesn't + -- produce much more code in the end, also because we then can leave out the not + -- seen branches. One can argue about the foundit2* blobs but by stepwise optimizing + -- this is the result. + + shades[1] = { { 0, 0, nx - 1, 0, nx - 1, ny - 1, 0, ny - 1 } } + edges [1] = { { } } + + -- this is way too slow ... i must have messed up some loop .. what is this with value 1 + + for value=1,nofvalues do +-- for value=2,nofvalues do + + local edge = { } + local nofe = 0 + local shade = { } + local nofs = 0 + + for i=1,nx-1 do + local s = sqtype[i] + for j=1,ny-1 do + s[j] = 0 + end + end + + local nrp = 0 + + local function addedge(a,b,c,d) + nofe = nofe + 1 edge[nofe] = a + nofe = nofe + 1 edge[nofe] = b + nofe = nofe + 1 edge[nofe] = c + nofe = nofe + 1 edge[nofe] = d + end + while true do + -- search for a square of type 0 with >= 1 corner above contour level + local i + local j + local d0 = data[1] + local d1 = data[2] + for ii=1,nx do + local s = sqtype[ii] + for jj=1,ny do + if s[jj] == 0 then + if d0[jj] > value then i = ii j = jj goto foundit end + if d1[jj] > value then i = ii j = jj goto foundit end + local j1 = jj + 1 + if d1[j1] > value then i = ii j = jj goto foundit end + if d0[j1] > value then i = ii j = jj goto foundit end + end + end + d0 = d1 + d1 = data[ii+1] + end + break + ::foundit:: + -- initialize r-polygon (nrp seems to be 1 or 2) + nrp = nrp + 1 + + local first = true + local nrpoly = 0 + local nspoly = 0 + local nrpm = -nrp + -- this is the main loop + while true do + -- search for a square of type -nrp + if first then + first = false + if sqtype[i][j] == 0 then -- true anyway + goto foundit1 + end + end + for ii=1,nx do + local s = sqtype[ii] + for jj=1,ny do + if s[jj] == nrpm then + i = ii + j = jj + goto foundit1 + end + end + end + break + ::foundit1:: + while true do + + -- search current then neighboring squares for square type 0, with a corner in common with current square above contour level + + -- top/bottom ... a bit cheating here + + local i_l, i_c, i_r -- i left current right + local j_b, j_c, j_t -- j bottom current top + + local i_n = i + 1 -- i next (right) + local j_n = j + 1 -- j next (top) + + local i_p = i - 1 -- i previous (bottom) + local j_p = j - 1 -- j previous (right) + + local d_c = data[i] + local d_r = data[i_n] + + local sq + + i_c = i ; j_c = j ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then + if d_c[j_c] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit21 end + if d_c[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit22 end + if d_r[j_c] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit23 end + if d_r[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit24 end + end end + + i_c = i_n ; j_c = j ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then + if d_r[j_c] > value then i_l = i ; i_r = i_n + 1 ; j_b = j_p ; j_t = j_n ; d_c = d_r ; d_r = data[i_r] ; goto foundit21 end + if d_r[j_n] > value then i_l = i ; i_r = i_n + 1 ; j_b = j_p ; j_t = j_n ; d_c = d_r ; d_r = data[i_r] ; goto foundit22 end + end end + + i_c = i ; j_c = j_n ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then + if d_c[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j ; j_t = j_n + 1 ; goto foundit21 end + if d_r[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j ; j_t = j_n + 1 ; goto foundit23 end + end end + + i_c = i_p ; j_c = j ; if i_c > 0 and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then + if d_c[j_c] > value then i_l = i_p - 1 ; i_r = i ; j_b = j_p ; j_t = j_n ; d_r = d_c ; d_c = data[i_p] ; goto foundit23 end + if d_c[j_n] > value then i_l = i_p - 1 ; i_r = i ; j_b = j_p ; j_t = j_n ; d_r = d_c ; d_c = data[i_p] ; goto foundit24 end + end end + + i_c = i ; j_c = j_p ; if i < nx and j_c > 0 then sq = sqtype[i_c] if sq[j_c] == 0 then + if d_c[j] > value then i_l = i_p ; i_r = i_n ; j_b = j_p - 1 ; j_t = j ; goto foundit22 end + if d_r[j] > value then i_l = i_p ; i_r = i_n ; j_b = j_p - 1 ; j_t = j ; goto foundit24 end + end end + + -- not found + + sqtype[i][j] = nrp + + break + + -- define s-polygon for found square (i_c,j_c) - may have up to 6 sides + + ::foundit21:: -- 1 2 3 4 + + sq[j_c] = nrpm + + xspoly[1] = i_l ; yspoly[1] = j_b + xspoly[2] = i_c ; yspoly[2] = j_b + if d_r[j_c] > value then -- dd2 + xspoly[3] = i_c ; yspoly[3] = j_c + if d_r[j_t] > value then -- dd3 + xspoly[4] = i_l ; yspoly[4] = j_c + if d_c[j_t] > value then -- dd4 + nspoly = 4 + else + xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5 + end + elseif d_c[j_t] > value then -- dd4 + xspoly[4] = i_c ; yspoly[4] = j_c ; + xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5 + else + xspoly[4] = i_l ; yspoly[4] = j_c ; nspoly = 4 + if edgetoo then addedge(i_c, j_c, i_l, j_c) end + end + elseif d_r[j_t] > value then -- dd3 + xspoly[3] = i_c ; yspoly[3] = j_b + xspoly[4] = i_c ; yspoly[4] = j_c + if d_c[j_t] > value then -- dd4 + xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5 + else + xspoly[5] = i_l ; yspoly[5] = j_c ; + xspoly[6] = i_l ; yspoly[6] = j_c ; nspoly = 6 + end + elseif d_c[j_t] > value then -- dd4 + if edgetoo then addedge(i_c, j_b, i_c, j_c) end + xspoly[3] = i_c ; yspoly[3] = j_c ; + xspoly[4] = i_l ; yspoly[4] = j_c ; nspoly = 4 + else + if edgetoo then addedge(i_c, j_b, i_l, j_c) end + xspoly[3] = i_l ; yspoly[3] = j_c ; nspoly = 3 + end + goto done + + ::foundit22:: -- 4 1 2 3 + + sq[j_c] = nrpm + + xspoly[1] = i_l ; yspoly[1] = j_c + xspoly[2] = i_l ; yspoly[2] = j_b + if d_c[j_c] > value then -- dd2 + xspoly[3] = i_c ; yspoly[3] = j_b + if d_r[j_c] > value then -- dd3 + xspoly[4] = i_c ; yspoly[4] = j_c + if d_r[j_t] > value then -- dd4 + nspoly = 4 + else + xspoly[5] = i_c ; yspoly[5] = j_c ; nspoly = 5 -- suspicious, the same + end + elseif d_r[j_t] > value then -- dd4 + xspoly[4] = i_c ; yspoly[4] = j_b ; + xspoly[5] = i_c ; yspoly[5] = j_c ; nspoly = 5 + else + if edgetoo then addedge(i_c, j_b, i_c, j_c) end + xspoly[4] = i_c ; yspoly[4] = j_c ; nspoly = 4 + end + elseif d_r[j_c] > value then -- dd3 + xspoly[3] = i_l ; yspoly[3] = j_b + xspoly[4] = i_c ; yspoly[4] = j_b + xspoly[5] = i_c ; yspoly[5] = j_c + if d_r[j_t] > value then -- dd4 + nspoly = 5 + else + xspoly[6] = i_c ; yspoly[6] = j_c ; nspoly = 6 + end + elseif d_r[j_t] > value then -- dd4 + if edgetoo then addedge(i_l, j_b, i_c, j_b) end + xspoly[3] = i_c ; yspoly[3] = j_b + xspoly[4] = i_c ; yspoly[4] = j_c ; nspoly = 4 + else + if edgetoo then addedge(i_l, j_b, i_c, j_c) end + xspoly[3] = i_c ; yspoly[3] = j_c ; nspoly = 3 + end + goto done + + ::foundit23:: -- 2 3 4 1 + + sq[j_c] = nrpm + + xspoly[1] = i_c ; yspoly[1] = j_b + xspoly[2] = i_c ; yspoly[2] = j_c + if d_r[j_t] > value then -- dd2 + xspoly[3] = i_l ; yspoly[3] = j_c + if d_c[j_t] > value then -- dd3 + xspoly[4] = i_l ; yspoly[4] = j_b + if d_c[j_c] > value then -- dd4 + nspoly = 4 + else + xspoly[5] = i_l ; yspoly[5] = j_b ; nspoly = 5 + end + elseif d_c[j_c] > value then -- dd4 + xspoly[4] = i_l ; yspoly[4] = j_c + xspoly[5] = i_l ; yspoly[5] = j_b ; nspoly = 5 + else + if edgetoo then addedge(i_l, j_c, i_l, j_b) end + xspoly[4] = i_l ; yspoly[4] = j_b ; nspoly = 4 + end + elseif d_c[j_t] > value then -- dd3 + xspoly[3] = i_c ; yspoly[3] = j_c + xspoly[4] = i_l ; yspoly[4] = j_c + xspoly[5] = i_l ; yspoly[5] = j_b + if d_c[j_c] > value then -- dd4 + nspoly = 5 + else + xspoly[6] = i_l ; yspoly[6] = j_b ; nspoly = 6 + end + elseif d_c[j_c] > value then -- dd4 + if edgetoo then addedge(i_c, j_c, i_l, j_c) end + xspoly[3] = i_l ; yspoly[3] = j_c ; + xspoly[4] = i_l ; yspoly[4] = j_b ; nspoly = 4 + else + if edgetoo then addedge(i_c, j_c, i_l, j_b) end + xspoly[3] = i_l ; yspoly[3] = j_b ; nspoly = 3 + end + goto done + + ::foundit24:: -- 3 4 1 2 + + sq[j_c] = nrpm + + xspoly[1] = i_c ; yspoly[1] = j_c + xspoly[2] = i_l ; yspoly[2] = j_c + if d_c[j_t] > value then -- dd2 + if d_c[j_c] > value then -- dd3 + xspoly[3] = i_l ; yspoly[3] = j_b + xspoly[4] = i_c ; yspoly[4] = j_b + if d_r[j_c] > value then -- dd4 + nspoly = 4 + else + xspoly[5] = i_c ; yspoly[5] = j_b ; nspoly = 5 + end + else + xspoly[3] = i_l ; yspoly[3] = j_b + if d_r[j_c] > value then -- dd4 + + local xv34 = (dd3*i_c-dd4*i_l)/(dd3 - dd4) -- probably i_l + print("4.4 : xv34",xv34,i_c,i_l) + + -- if edgetoo then addedge(i_l, j_b, xv34, j_b) end + xspoly[4] = xv34 ; yspoly[4] = j_b ; + xspoly[5] = i_c ; yspoly[5] = j_b ; nspoly = 5 + else + if edgetoo then addedge(i_l, j_b, i_c, j_b) end + xspoly[4] = i_c ; yspoly[4] = j_b ; nspoly = 4 + end + end + elseif d_c[j_c] > value then -- dd3 + xspoly[3] = i_l ; yspoly[3] = j_b + xspoly[4] = i_l ; yspoly[4] = j_b + xspoly[5] = i_c ; yspoly[5] = j_b + if d_r[j_c] > value then -- dd4 + nspoly = 5 + else + xspoly[6] = i_c ; yspoly[6] = j_b ; nspoly = 6 + end + elseif d_r[j_c] > value then -- dd4 + if edgetoo then addedge(i_l, j_c, i_l, j_b) end + xspoly[3] = i_l ; yspoly[3] = j_b + xspoly[4] = i_c ; yspoly[4] = j_b ; nspoly = 4 + else + if edgetoo then addedge(i_l, j_c, i_c, j_b) end + xspoly[3] = i_c ; yspoly[3] = j_b ; nspoly = 3 + end + -- goto done + + ::done:: + -- combine s-polygon with existing r-polygon, eliminating redundant segments + + if nrpoly == 0 then + -- initiate r-polygon + for i=1,nspoly do + xrpoly[i] = xspoly[i] + yrpoly[i] = yspoly[i] + end + nrpoly = nspoly + else + -- search r-polygon and s-polygon for one side that matches + -- + -- this is a bottleneck ... we keep this variant here but next go for a faster + -- alternative + -- + -- local ispoly, irpoly + -- for r=nrpoly,1,-1 do + -- local r1 + -- for s=1,nspoly do + -- local s1 = s % nspoly + 1 + -- if xrpoly[r] == xspoly[s1] and yrpoly[r] == yspoly[s1] then + -- if not r1 then + -- r1 = r % nrpoly + 1 + -- end + -- if xrpoly[r1] == xspoly[s] and yrpoly[r1] == yspoly[s] then + -- ispoly = s + -- irpoly = r + -- goto foundit3 + -- end + -- end + -- end + -- end + -- + -- local ispoly, irpoly + -- local xr1 = xrpoly[1] + -- local yr1 = yrpoly[1] + -- for r0=nrpoly,1,-1 do + -- for s0=1,nspoly do + -- if xr1 == xspoly[s0] and yr1 == yspoly[s0] then + -- if s0 == nspoly then + -- if xr0 == xspoly[1] and yr0 == yspoly[1] then + -- ispoly = s0 + -- irpoly = r0 + -- goto foundit3 + -- end + -- else + -- local s1 = s0 + 1 + -- if xr0 == xspoly[s1] and yr0 == yspoly[s1] then + -- ispoly = s0 + -- irpoly = r0 + -- goto foundit3 + -- end + -- end + -- end + -- end + -- xr1 = xrpoly[r0] + -- yr1 = yrpoly[r0] + -- end + -- + -- but ... + -- + local minx = xspoly[1] + local miny = yspoly[1] + local maxx = xspoly[1] + local maxy = yspoly[1] + for i=1,nspoly do + local y = yspoly[i] + if y < miny then + miny = y + elseif y > maxy then + maxy = y + end + local x = xspoly[i] + if x < minx then + minx = y + elseif x > maxx then + maxx = x + end + end + -- we can delay accessing y ... + local ispoly, irpoly + local xr1 = xrpoly[1] + local yr1 = yrpoly[1] + for r0=nrpoly,1,-1 do + if xr1 >= minx and xr1 <= maxx and yr1 >= miny and yr1 <= maxy then + local xr0 = xrpoly[r0] + local yr0 = yrpoly[r0] + for s0=1,nspoly do + if xr1 == xspoly[s0] and yr1 == yspoly[s0] then + if s0 == nspoly then + if xr0 == xspoly[1] and yr0 == yspoly[1] then + ispoly = s0 + irpoly = r0 + goto foundit3 + end + else + local s1 = s0 + 1 + if xr0 == xspoly[s1] and yr0 == yspoly[s1] then + ispoly = s0 + irpoly = r0 + goto foundit3 + end + end + end + end + xr1 = xr0 + yr1 = yr0 + else + xr1 = xrpoly[r0] + yr1 = yrpoly[r0] + end + end + -- + goto nomatch3 + ::foundit3:: + local match1 = 0 + local rpoly1 = irpoly + nrpoly + local spoly1 = ispoly - 1 + for i=2,nspoly-1 do + -- search for further matches nearby + local ir = (rpoly1 - i) % nrpoly + 1 + local is = (spoly1 + i) % nspoly + 1 + if xrpoly[ir] == xspoly[is] and yrpoly[ir] == yspoly[is] then + match1 = match1 + 1 + else + break -- goto nomatch1 + end + end + ::nomatch1:: + local match2 = 0 + local rpoly2 = irpoly - 1 + local spoly2 = ispoly + nspoly + for i=2,nspoly-1 do + -- search other way for further matches nearby + local ir = (rpoly2 + i) % nrpoly + 1 + local is = (spoly2 - i) % nspoly + 1 + if xrpoly[ir] == xspoly[is] and yrpoly[ir] == yspoly[is] then + match2 = match2 + 1 + else + break -- goto nomatch2 + end + end + ::nomatch2:: + -- local dnrpoly = nspoly - 2 - 2*match1 - 2*match2 + local dnrpoly = nspoly - 2*(match1 + match2 + 1) + local ispolystart = (ispoly + match1) % nspoly + 1 -- first node of s-polygon to include + local irpolyend = (rpoly1 - match1 - 1) % nrpoly + 1 -- last node of s-polygon to include + if dnrpoly ~= 0 then + local irpolystart = (irpoly + match2) % nrpoly + 1 -- first node of s-polygon to include + if irpolystart > irpolyend then + -- local ispolyend = (spoly1 - match2 + nspoly)%nspoly + 1 -- last node of s-polygon to include + if dnrpoly > 0 then + -- expand the arrays xrpoly and yrpoly + for i=nrpoly,irpolystart,-1 do + local k = i + dnrpoly + xrpoly[k] = xrpoly[i] + yrpoly[k] = yrpoly[i] + end + else -- if dnrpoly < 0 then + -- contract the arrays xrpoly and yrpoly + for i=irpolystart,nrpoly do + local k = i + dnrpoly + xrpoly[k] = xrpoly[i] + yrpoly[k] = yrpoly[i] + end + end + end + nrpoly = nrpoly + dnrpoly + end + if nrpoly < irpolyend then + for i=irpolyend,nrpoly+1,-1 do + -- otherwise these values get lost! + local k = i - nrpoly + xrpoly[k] = xrpoly[i] + yrpoly[k] = yrpoly[i] + end + end + local n = nspoly - 2 - match1 - match2 + if n == 1 then + local irpoly1 = irpolyend % nrpoly + 1 + local ispoly1 = ispolystart % nspoly + 1 + xrpoly[irpoly1] = xspoly[ispoly1] + yrpoly[irpoly1] = yspoly[ispoly1] + elseif n > 0 then + -- often 2 + for i=1,n do + local ii = i - 1 + local ir = (irpolyend + ii) % nrpoly + 1 + local is = (ispolystart + ii) % nspoly + 1 + xrpoly[ir] = xspoly[is] + yrpoly[ir] = yspoly[is] + end + end + ::nomatch3:: + end + end + end + + if nrpoly > 0 then + local t = { } + local n = 0 + for i=1,nrpoly do + n = n + 1 t[n] = xrpoly[i] + n = n + 1 t[n] = yrpoly[i] + end + if mpflatten then + mpflatten(t) -- maybe integrate + end + nofs = nofs + 1 + shade[nofs] = t + -- print(value,nrpoly,#t,#t-nrpoly*2) + end + + end + + edges [value+1] = edge + shades[value+1] = shade +-- edges [value] = edge +-- shades[value] = shade + end + + result.shades = shades + result.shapes = edges + +end + +-- accessors + +function mp.lmt_contours_nx (i) return getparameterset().result.nx end +function mp.lmt_contours_ny (i) return getparameterset().result.ny end + +function mp.lmt_contours_nofvalues() return getparameterset().result.nofvalues end +function mp.lmt_contours_value (i) return getparameterset().result.values[i] end + +function mp.lmt_contours_minz (i) return getparameterset().result.minz end +function mp.lmt_contours_maxz (i) return getparameterset().result.maxz end + +function mp.lmt_contours_minmean (i) return getparameterset().result.minmean end +function mp.lmt_contours_maxmean (i) return getparameterset().result.maxmean end + +function mp.lmt_contours_xrange () local p = getparameterset() mpstring(formatters["x = [%.3N,%.3N] ;"](p.xmin,p.xmax)) end +function mp.lmt_contours_yrange () local p = getparameterset() mpstring(formatters["y = [%.3N,%.3N] ;"](p.ymin,p.ymax)) end + +function mp.lmt_contours_format() + local p = getparameterset() + return mpstring(p.result.islist and "@i" or p.zformat or p.format) +end + +function mp.lmt_contours_function() + local p = getparameterset() + return mpstring(p.result.islist and concat(p["functions"], ", ") or p["function"]) +end + +function mp.lmt_contours_range() + local p = getparameterset() + local r = p.result.islist and p.range + if not r or #r == 0 then + return mpstring("") + elseif #r == 1 then + return mpstring(r[1]) + else + return mpstring(formatters["z = [%s,%s]"](r[1],r[2])) + end +end + +function mp.lmt_contours_edge_paths(value) + mpdraw(getparameterset().result.edges[value],true) + mpflush() +end + +function mp.lmt_contours_shape_paths(value) + mpdraw(getparameterset().result.shapes[value],false) + mpflush() +end + +function mp.lmt_contours_shade_paths(value) + mpfill(getparameterset().result.shades[value],true) + mpflush() +end + +function mp.lmt_contours_color(value) + local p = getparameterset() + local color = p.result.colors[value] + if #color == 3 then + mptriplet(color) + else + return color[1] + end +end + +-- The next code is based on the wikipedia page. It was a bit tedius job to define the +-- coordinates but hupefully I made no errors. I rendered all shapes independently and +-- tripple checked bit one never knows ... + +-- maybe some day write from scatch, like this (axis are swapped): + +local d = 1/2 + +local paths = { + { 0, d, d, 0 }, + { 1, d, d, 0 }, + { 0, d, 1, d }, + { 1, d, d, 1 }, + { 0, d, d, 1, d, 0, 1, d }, -- saddle + { d, 0, d, 1 }, + { 0, d, d, 1 }, + { 0, d, d, 1 }, + { d, 0, d, 1 }, + { 0, d, d, 0, 1, d, d, 1 }, -- saddle + { 1, d, d, 1 }, + { 0, d, 1, d }, + { d, 0, 1, d }, + { d, 0, 0, d }, +} + +local function whatever(data,nx,ny,threshold) + local edges = { } + local e = 0 + local d0 = data[1] + for j=1,ny-1 do + local d1 = data[j+1] + local k = j + 1 + for i=1,nx-1 do + local v = 0 + local l = i + 1 + local c1 = d0[i] + if c1 < threshold then + v = v + 8 + end + local c2 = d0[l] + if c2 < threshold then + v = v + 4 + end + local c3 = d1[l] + if c3 < threshold then + v = v + 2 + end + local c4 = d1[i] + if c4 < threshold then + v = v + 1 + end + if v > 0 and v < 15 then + if v == 5 or v == 10 then + local a = (c1 + c2 + c3 + c4) / 4 + if a < threshold then + v = v == 5 and 10 or 5 + end + local p = paths[v] + e = e + 1 edges[e] = k - p[2] + e = e + 1 edges[e] = i + p[1] + e = e + 1 edges[e] = k - p[4] + e = e + 1 edges[e] = i + p[3] + e = e + 1 edges[e] = k - p[6] + e = e + 1 edges[e] = i + p[5] + e = e + 1 edges[e] = k - p[8] + e = e + 1 edges[e] = i + p[7] + else + local p = paths[v] + e = e + 1 edges[e] = k - p[2] + e = e + 1 edges[e] = i + p[1] + e = e + 1 edges[e] = k - p[4] + e = e + 1 edges[e] = i + p[3] + end + end + end + d0 = d1 + end + return edges +end + +-- todo: just fetch when needed, no need to cache + +function mp.lmt_contours_edge_set_by_cell() + local p = getparameterset() + local result = p.result + + if result.cached then return end + + local values = result.values + local nofvalues = result.nofvalues + local data = result.data + local nx = result.nx + local ny = result.ny + local lines = { } + result.lines = lines + for value=1,nofvalues do + lines[value] = whatever(data,ny,nx,value) + end +end + +function mp.lmt_contours_edge_get_cell(value) + mpdraw(getparameterset().result.lines[value]) + mpflush() +end + +local singles = { + { d, 0, 0, 0, 0, d }, -- 1 0001 + { d, 0, 0, d }, -- 2 0002 + { 1, d, 1, 0, d, 0 }, -- 3 0010 + { 1, d, 1, 0, 0, 0, 0, d }, -- 4 0011 + { 1, d, 1, 0, d, 0, 0, d }, -- 5 0012 + { 1, d, d, 0 }, -- 6 0020 + { 1, d, d, 0, 0, 0, 0, d }, -- 7 0021 + { 1, d, 0, d }, -- 8 0022 + { d, 1, 1, 1, 1, d }, -- 9 0100 + false, -- 10 0101 + false, -- 11 0102 + { d, 1, 1, 1, 1, 0, d, 0 }, -- 12 0110 + { d, 1, 1, 1, 1, 0, 0, 0, 0, d }, -- 13 0111 + { d, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 14 0112 + { d, 1, 1, 1, 1, d, d, 0 }, -- 15 0120 + { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, -- 16 0121 + { d, 1, 1, 1, 1, d, 0, d }, -- 17 0122 + { d, 1, 1, d }, -- 18 0200 + false, -- 19 0201 + false, -- 20 0202 + { d, 1, 1, d, 1, 0, d, 0 }, -- 21 0210 + { d, 1, 1, d, 1, 0, 0, 0, 0, d }, -- 22 0211 + false, -- 23 0212 + { d, 1, d, 0 }, -- 24 0220 + { d, 1, d, 0, 0, 0, 0, d }, -- 25 0221 + { d, 1, 0, d }, -- 26 0222 + { 0, 1, d, 1, 0, d }, -- 27 1000 + { 0, 1, d, 1, d, 0, 0, 0 }, -- 28 1001 + { 0, 1, d, 1, d, 0, 0, d }, -- 29 1002 + false, -- 30 1010 + { 0, 1, d, 1, 1, d, 1, 0, 0, 0 }, -- 31 1011 + { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, -- 32 1012 + false, -- 33 1020 + { 0, 1, d, 1, 1, d, d, 0, 0, 0 }, -- 34 1021 + { 0, 1, d, 1, 1, d, 0, d }, -- 35 1022 + { 0, 1, 1, 1, 1, d, 0, d }, -- 36 1100 + { 0, 1, 1, 1, 1, d, d, 0, 0, 0 }, -- 37 1101 + { 0, 1, 1, 1, 1, d, d, 0, 0, d }, -- 38 1102 + { 0, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 39 1110 + { 0, 1, 1, 1, 1, 0, 0, 0 }, -- 40 1111 + { 0, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 41 1112 + { 0, 1, 1, 1, 1, d, d, 0, 0, d }, -- 42 1120 + { 0, 1, 1, 1, 1, d, d, 0, 0, 0 }, -- 43 1121 + { 0, 1, 1, 1, 1, d, 0, d }, -- 44 1122 + { 0, 1, d, 1, 1, d, 0, d }, -- 45 1200 + { 0, 1, d, 1, 1, d, d, 0, 0, 0 }, -- 46 1201 + false, -- 47 1202 + { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, -- 48 1210 + { 0, 1, d, 1, 1, d, 1, 0, 0, 0 }, -- 49 1211 + false, -- 50 1212 + { 0, 1, d, 1, d, 0, 0, d }, -- 51 1220 + { 0, 1, d, 1, d, 0, 0, 0 }, -- 52 1221 + { 0, 1, d, 1, 0, d }, -- 53 1222 + { d, 1, 0, d }, -- 54 2000 + { d, 1, d, 0, 0, 0, 0, d }, -- 55 2001 + { d, 1, d, 0 }, -- 56 2002 + false, -- 57 2010 + { d, 1, 1, d, 1, 0, 0, 0, 0, d }, -- 58 2011 + { d, 1, 1, d, 1, 0, d, 0 }, -- 59 2012 + false, -- 60 2020 + false, -- 61 2021 + { d, 1, 1, d }, -- 62 2022 + { d, 1, 1, 1, 1, d, 0, d }, -- 63 2100 + { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, -- 64 2101 + { d, 1, 1, 1, 1, d, d, 0 }, -- 65 2102 + { d, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 66 2110 + { d, 1, 1, 1, 1, 0, 0, 0, 0, d }, -- 67 2111 + { d, 1, 1, 1, 1, 0, d, 0 }, -- 68 2112 + false, -- 69 2120 + false, -- 70 2121 + { d, 1, 1, 1, 1, d }, -- 71 2122 + { 1, d, 0, d }, -- 72 2200 + { 1, d, d, 0, 0, 0, 0, d }, -- 73 2201 + { 1, d, d, 0 }, -- 74 2202 + { 1, d, 1, 0, d, 0, 0, d }, -- 75 2210 + { 1, d, 1, 0, 0, 0, 0, d }, -- 76 2211 + { 1, d, 1, 0, d, 0 }, -- 77 2212 + { d, 0, 0, d }, -- 78 2220 + { 0, d, 0, 0, d, 0 }, -- 79 2221 +} + +local sadles = { + false, false, false, false, false, false, false, false, false, + { { d, 1, 1, 1, 1, d }, { d, 0, 0, 0, 0, d }, { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, false, false, false }, -- 10 0101 + { { d, 1, 1, 1, 1, d }, { d, 0, 0, d }, { d, 1, 1, 1, 1, d, d, 0, 0, d }, false, false, false }, -- 11 0102 + false, false, false, false, false, false, false, + { { d, 1, 1, d }, { d, 0, 0, 0, 0, d }, { d, 1, 1, d, d, 0, 0, 0, 0, d }, false, false, false }, -- 19 0201 + { { d, 1, 1, d }, { d, 0, 0, d }, { d, 1, 1, d, d, 0, 0, d }, false, { d, 1, 0, d }, { 1, d, d, 0 } }, -- 20 0202 + false, false, + { false, false, { d, 1, 1, d, 1, 0, d, 0, 0, d }, false, { d, 1, 0,d, }, { 1, d, 1, 0,d, 0 } }, -- 23 0212 + false, false, false, false, false, false, + { { 0, 1, d, 1, 0, d }, { 1, d, 1, 0, d, 0 }, { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, false, false, false }, -- 30 1010 + false, false, + { { 1, 0, d, 0, 0, d, }, { 1, d, d, 0 }, { 0, 1, d, 1, 1, d, d, 0, 0, d }, false, false, false }, -- 33 1020 + false, false, false, false, false, false, false, false, false, false, false, false, false, + { false, false, { 0,1, d, 1, 1, d, d, 0, 0, d }, false, { 0,1, d, 1, 0, d }, {1, d, d, 0 } }, -- 47 1202 + false, false, + { false, false, { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, false, { 0, 1, d, 1, 0, d }, { 1, d, 1, 0, d, 0 } }, -- 50 1212 + false, false, false, false, false, false, + { { d, 1, 0, d }, { 1, d, 1, 0, 0, d }, { d, 1, 1, d, 1, 0, d, 0, 0, d }, false, false, false }, -- 57 2010 + false, false, + { { d, 1, 0,d }, { 1, d, d, 0 }, { d, 1, 1, d, d, 0, 0, d }, false, { d, 1, 1, d }, { d, 0, 0, d } }, -- 60 2020 + { false, false, { d, 1, 1, d, d, 0, 0, 0, 0, d }, false, { d, 1, 1, d }, { d, 0, 0, 0, 0, d } }, -- 61 2021 + false, false, false, false, false, false, false, + { false, false, { d, 1, 1, 1, 1, d, d, 0, 0, d }, false, { d, 1, 1, 1, 1, d }, { d, 0,0,d } }, -- 69 2120 + { false, false, { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, false, { d, 1, 1, 1, 1, d }, { d, 0, 0, 0, 0, d } }, -- 70 2121 +} + +local function whatever(data,nx,ny,threshold,background) + + if background then + + local llx = 1/2 + local lly = llx + local urx = ny + llx + local ury = nx + lly + + return { { llx, lly, urx, 0, urx, ury, 0, ury } } + + else + + local bands = { } + local b = 0 + + local function band(s,n,x,y) -- simple. no closure so fast + if n == 6 then + return { + x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5], + } + elseif n == 8 then + return { + x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5], + x - s[ 8], y + s[ 7], + } + elseif n == 10 then + return { + x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5], + x - s[ 8], y + s[ 7], x - s[10], y + s[ 9], + } + elseif n == 4 then + return { + x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], + } + else -- 12 + return { + x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5], + x - s[ 8], y + s[ 7], x - s[10], y + s[ 9], x - s[12], y + s[11], + } + end + end + + local pp = { } + + local d0 = data[1] + for j=1,ny-1 do + local d1 = data[j+1] + local k = j + 1 + local p = false + for i=1,nx-1 do + local v = 0 + local l = i + 1 + local c1 = d0[i] + if c1 == threshold then + v = v + 27 + elseif c1 > threshold then + v = v + 54 + end + local c2 = d0[l] + if c2 == threshold then + v = v + 9 + elseif c2 > threshold then + v = v + 18 + end + local c3 = d1[l] + if c3 == threshold then + v = v + 3 + elseif c3 > threshold then + v = v + 6 + end + local c4 = d1[i] + if c4 == threshold then + v = v + 1 + elseif c4 > threshold then + v = v + 2 + end + if v > 0 and v < 80 then + if v == 40 then + -- a little optimization: full areas appended horizontally + if p then + p[4] = l -- i + 1 + p[6] = l -- i + 1 + else + -- x-0 y+1 x-1 y+1 x-1 y+0 x-0 y+0 + p = { j, i, j, l, k, l, k, i } + b = b + 1 ; bands[b] = p + end + else + local s = singles[v] + if s then + b = b + 1 ; bands[b] = band(s,#s,k,i) + else + local s = sadles[v] + if s then + local m = (c1 + c2 + c3 + c4) / 4 + if m < threshold then + local s1 = s[1] if s1 then b = b + 1 ; bands[b] = band(s1,#s1,i,j) end + local s2 = s[2] if s2 then b = b + 1 ; bands[b] = band(s2,#s2,i,j) end + elseif m == threshold then + local s3 = s[3] if s3 then b = b + 1 ; bands[b] = band(s3,#s3,i,j) end + local s4 = s[4] if s4 then b = b + 1 ; bands[b] = band(s4,#s4,i,j) end + else + local s5 = s[5] if s5 then b = b + 1 ; bands[b] = band(s5,#s5,i,j) end + local s6 = s[6] if s6 then b = b + 1 ; bands[b] = band(s6,#s6,i,j) end + end + end + end + p = false + end + else + p = false + end + end + d0 = d1 + end + return bands + end +end + +function mp.lmt_contours_edge_set_by_band(value) + local p = getparameterset() + local result = p.result + + if result.cached then return end + + local values = result.values + local nofvalues = result.nofvalues + local data = result.data + local nx = result.nx + local ny = result.ny + local bands = { } + result.bands = bands + for value=1,nofvalues do + bands[value] = whatever(data,ny,nx,value,value == 1) + end +end + +function mp.lmt_contours_edge_get_band(value) + mpfill(getparameterset().result.bands[value],true) + mpflush() +end diff --git a/tex/context/base/mkiv/mlib-ctx.lua b/tex/context/base/mkiv/mlib-ctx.lua index e19f111b4..88ef98f58 100644 --- a/tex/context/base/mkiv/mlib-ctx.lua +++ b/tex/context/base/mkiv/mlib-ctx.lua @@ -332,7 +332,7 @@ implement { } statistics.register("metapost", function() - local n = metapost.n + local n = metapost.nofruns if n and n > 0 then local elapsedtime = statistics.elapsedtime local elapsed = statistics.elapsed diff --git a/tex/context/base/mkiv/mlib-ctx.mkiv b/tex/context/base/mkiv/mlib-ctx.mkiv index 78a26ad1c..c90536937 100644 --- a/tex/context/base/mkiv/mlib-ctx.mkiv +++ b/tex/context/base/mkiv/mlib-ctx.mkiv @@ -19,7 +19,8 @@ \registerctxluafile{mlib-lmp}{} \registerctxluafile{mlib-int}{} -\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-int}{}}{} +\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{}}{} +\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{}}{} \unprotect diff --git a/tex/context/base/mkiv/mlib-ctx.mkxl b/tex/context/base/mkiv/mlib-ctx.mkxl index c66e6f968..bb2460627 100644 --- a/tex/context/base/mkiv/mlib-ctx.mkxl +++ b/tex/context/base/mkiv/mlib-ctx.mkxl @@ -22,6 +22,9 @@ \registerctxluafile{mlib-int}{} \registerctxluafile{mlib-lmt}{} +\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{}}{} +\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{}}{} + \unprotect \protect \endinput diff --git a/tex/context/base/mkiv/mlib-lmp.lua b/tex/context/base/mkiv/mlib-lmp.lua index 93a758681..22607be8d 100644 --- a/tex/context/base/mkiv/mlib-lmp.lua +++ b/tex/context/base/mkiv/mlib-lmp.lua @@ -134,3 +134,11 @@ do end end + +function mp.lmt_svg_include() + local name = metapost.getparameter { "svg", "filename" } + local mps = metapost.svgtomp { + data = name and name ~= "" and io.loaddata(name) or "", + } + mp.direct(mps) +end diff --git a/tex/context/base/mkiv/mlib-lua.lua b/tex/context/base/mkiv/mlib-lua.lua index 9e3802396..29ef334f1 100644 --- a/tex/context/base/mkiv/mlib-lua.lua +++ b/tex/context/base/mkiv/mlib-lua.lua @@ -75,6 +75,7 @@ do local scan_cmykcolor = mplib.scan_cmykcolor local scan_transform = mplib.scan_transform local scan_path = mplib.scan_path + local scan_pen = mplib.scan_pen scan.next = function(k) return scan_next (currentmpx,k) end scan.expression = function(k) return scan_expression(currentmpx,k) end @@ -90,6 +91,7 @@ do scan.cmykcolor = function(t) return scan_cmykcolor (currentmpx,t) end scan.transform = function(t) return scan_transform (currentmpx,t) end scan.path = function(t) return scan_path (currentmpx,t) end + scan.pen = function(t) return scan_pen (currentmpx,t) end else @@ -148,6 +150,7 @@ do local f_triplet = formatters["(%F,%F,%F)"] local f_quadruple = formatters["(%F,%F,%F,%F)"] local f_transform = formatters["totransform(%F,%F,%F,%F,%F,%F)"] + local f_pen = formatters["(pencircle transformed totransform(%F,%F,%F,%F,%F,%F))"] local f_points = formatters["%p"] local f_pair_pt = formatters["(%p,%p)"] @@ -516,7 +519,12 @@ do local tn = #t if tn == 1 then local t1 = t[1] - n = n + 1 ; buffer[n] = f2(t1[1],t1[2]) + n = n + 1 + if t.pen then + buffer[n] = f_pen(unpack(t1)) + else + buffer[n] = f2(t1[1],t1[2]) + end elseif tn > 0 then if connector == true or connector == nil then connector = ".." @@ -534,6 +542,8 @@ do local a = t[1] local b = t[2] n = n + 1 + buffer[n] = "(" + n = n + 1 if six and #a == 6 and #b == 6 then buffer[n] = f6(a[1],a[2],a[5],a[6],b[3],b[4]) controls = ".." @@ -575,6 +585,8 @@ do else buffer[n] = f2(a[1],a[2]) end + n = n + 1 + buffer[n] = ")" end end end diff --git a/tex/context/base/mkiv/mlib-pdf.lua b/tex/context/base/mkiv/mlib-pdf.lua index 524e930a9..ece332d84 100644 --- a/tex/context/base/mkiv/mlib-pdf.lua +++ b/tex/context/base/mkiv/mlib-pdf.lua @@ -400,7 +400,7 @@ function metapost.flush(specification,result) local linecap = -1 local linejoin = -1 local dashed = false -local linewidth = false + local linewidth = false local llx = properties.llx local lly = properties.lly local urx = properties.urx @@ -533,10 +533,10 @@ local linewidth = false if pen then if pen.type == "elliptical" then transformed, penwidth = pen_characteristics(original) -- boolean, value -if penwidth ~= linewidth then - result[#result+1] = f_w(penwidth) -- todo: only if changed - linewidth = penwidth -end + if penwidth ~= linewidth then + result[#result+1] = f_w(penwidth) + linewidth = penwidth + end if objecttype == "fill" then objecttype = "both" end @@ -624,8 +624,7 @@ end end if object.grouped then -- can be qQ'd so changes can end up in groups - miterlimit, linecap, linejoin, dashed = -1, -1, -1, "" -- was false -linewidth = false + miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false end end end diff --git a/tex/context/base/mkiv/mlib-pps.lua b/tex/context/base/mkiv/mlib-pps.lua index 1457020ef..46d436466 100644 --- a/tex/context/base/mkiv/mlib-pps.lua +++ b/tex/context/base/mkiv/mlib-pps.lua @@ -691,7 +691,7 @@ metapost.splitprescript = splitprescript -- end function metapost.pluginactions(what,t,flushfigure) -- before/after object, depending on what - if top.plugmode then -- hm, what about other features + if top and top.plugmode then -- hm, what about other features for i=1,#what do local wi = what[i] if type(wi) == "function" then @@ -708,14 +708,14 @@ function metapost.pluginactions(what,t,flushfigure) -- before/after object, depe end function metapost.resetplugins(t) -- intialize plugins, before figure - if top.plugmode then + if top and top.plugmode then outercolormodel = colors.currentmodel() -- currently overloads the one set at the tex end resetteractions.runner(t) end end function metapost.processplugins(object) -- each object (second pass) - if top.plugmode then + if top and top.plugmode then local prescript = object.prescript -- specifications if prescript and #prescript > 0 then local before = { } diff --git a/tex/context/base/mkiv/mlib-run.lua b/tex/context/base/mkiv/mlib-run.lua index fb1367151..bf7c8a796 100644 --- a/tex/context/base/mkiv/mlib-run.lua +++ b/tex/context/base/mkiv/mlib-run.lua @@ -57,6 +57,7 @@ metapost.showlog = false metapost.lastlog = "" metapost.texerrors = false metapost.exectime = metapost.exectime or { } -- hack +metapost.nofruns = 0 local mpxformats = { } local mpxterminals = { } @@ -100,6 +101,7 @@ local function executempx(mpx,data) elseif type(data) == "table" then data = prepareddata(data,collapse) end + metapost.nofruns = metapost.nofruns + 1 return mpx:execute(data) end @@ -822,70 +824,13 @@ function metapost.directrun(formatname,filename,outputformat,astable,mpdata) report_metapost("producing postscript and svg is no longer supported") end --- goodie - -function metapost.quickanddirty(mpxformat,data,incontext) - if not data then - mpxformat = "metafun" - data = mpxformat - end - local code, bbox - local flusher = { - startfigure = function(n,llx,lly,urx,ury) - code = { } - bbox = { llx, lly, urx, ury } - end, - flushfigure = function(t) - for i=1,#t do - code[#code+1] = t[i] - end - end, - stopfigure = function() - end - } - local data = formatters["; beginfig(1) ;\n %s\n ; endfig ;"](data) - metapost.process { - mpx = mpxformat, - flusher = flusher, - askedfig = "all", - useplugins = incontext, - incontext = incontext, - data = { data }, - } - if code then - return { - bbox = bbox or { 0, 0, 0, 0 }, - code = code, - data = data, - } - else - report_metapost("invalid quick and dirty run") - end -end - -function metapost.getstatistics(memonly) - if memonly then - local n, m = 0, 0 - for name, mpx in next, mpxformats do - n = n + 1 - m = m + mpx:statistics().memory - end - return n, m - else - local t = { } - for name, mpx in next, mpxformats do - t[name] = mpx:statistics() - end - return t - end -end - do local result = { } local width = 0 local height = 0 local depth = 0 + local bbox = { 0, 0, 0, 0 } local flusher = { startfigure = function(n,llx,lly,urx,ury) @@ -893,35 +838,59 @@ do width = urx - llx height = ury depth = -lly + bbox = { llx, lly, urx, ury } end, flushfigure = function(t) + local r = #result for i=1,#t do - result[#result+1] = t[i] + r = r + 1 + result[r] = t[i] end end, stopfigure = function() - end + end, } - function metapost.simple(format,code) -- even less than metapost.quickcanddirty - local mpx = metapost.pushformat { } -- takes defaults - -- metapost.setoutercolor(2) + function metapost.simple(format,code,useextensions) + local mpx = metapost.pushformat { + instance = "simplefun", + format = "metafun", + method = "double", + } metapost.process { mpx = mpx, flusher = flusher, askedfig = 1, - useplugins = false, - incontext = false, + useplugins = useextensions, data = { "beginfig(1);", code, "endfig;" }, + incontext = false, } metapost.popformat() if result then local stream = concat(result," ") - result = nil -- cleanup - return stream, width, height, depth + result = { } -- nil -- cleanup .. weird, we can have a dangling q + return stream, width, height, depth, bbox else - return "", 0, 0, 0 + return "", 0, 0, 0, { 0, 0, 0, 0 } end end end + +function metapost.getstatistics(memonly) + if memonly then + local n, m = 0, 0 + for name, mpx in next, mpxformats do + n = n + 1 + m = m + mpx:statistics().memory + end + return n, m + else + local t = { } + for name, mpx in next, mpxformats do + t[name] = mpx:statistics() + end + return t + end +end + diff --git a/tex/context/base/mkiv/mlib-scn.lua b/tex/context/base/mkiv/mlib-scn.lua index a85c861bd..8867455b6 100644 --- a/tex/context/base/mkiv/mlib-scn.lua +++ b/tex/context/base/mkiv/mlib-scn.lua @@ -60,6 +60,7 @@ local scancolor = scanners.color local scancmykcolor = scanners.cmykcolor local scantransform = scanners.transform local scanpath = scanners.path +local scanpen = scanners.pen local mpprint = mp.print local mpnumeric = mp.numeric @@ -164,6 +165,7 @@ typescanners = { [types.cmykcolor] = scan_cmykcolor, [types.transform] = scan_transform, [types.path] = scanpath, + [types.pen] = scanpen, } table.setmetatableindex(tokenscanners,function() @@ -603,6 +605,22 @@ local function getparameterpath() end end +local function getparameterpen() + local list, n = collectnames() + local v = namespaces + for i=1,n do + v = v[list[i]] + if not v then + break + end + end + if type(v) == "table" then + return mppath(v,"..",true) + else + return mppair(0,0) + end +end + local function getparametertext() local list, n = collectnames() local strut = list[n] @@ -660,6 +678,7 @@ metapost.registerscript("getparameterdefault", getparameterdefault) metapost.registerscript("getparametercount", getparametercount) metapost.registerscript("getmaxparametercount",getmaxparametercount) metapost.registerscript("getparameterpath", getparameterpath) +metapost.registerscript("getparameterpen", getparameterpen) metapost.registerscript("getparametertext", getparametertext) --------.registerscript("getparameteroption", getparameteroption) metapost.registerscript("pushparameters", pushparameters) diff --git a/tex/context/base/mkiv/mlib-svg.lua b/tex/context/base/mkiv/mlib-svg.lua new file mode 100644 index 000000000..c3635480d --- /dev/null +++ b/tex/context/base/mkiv/mlib-svg.lua @@ -0,0 +1,1635 @@ +if not modules then modules = { } end modules ['mlib-svg'] = { + 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", +} + +-- As usual with these standards, things like a path can be very compact while the rest is +-- very verbose which defeats the point. This is a first attempt. There will be a converter +-- to MP as well as directly to PDF. This module was made for one of the dangerous curves +-- talks at the 2019 CTX meeting. I will do the font when I need it (not that hard). +-- +-- There is no real need to boost performance here .. we can always make a fast variant +-- when really needed. I will also do some of the todo's when I run into proper fonts. + +-- Written with Anne Clark on speakers as distraction. + +-- TODO: + +-- optimize +-- test for gzip header 0x1F 0x8B 0x08 +-- var() +-- color hash +-- currentColor +-- instances +-- --color +-- glyph +-- shading +-- "none" -> false +-- clip = [ auto | rect(llx,lly,urx,ury) ] (in svg) +-- xlink url ... whatever +-- mp svg module + shortcuts +-- withpen -> pickup + +-- The fact that in the more recent versions of SVG the older text related elements +-- are depricated and not even supposed to be supported, combined with the fact that +-- the text element assumes css styling, demonstrates that there is not so much as a +-- standard. It basically means that whatever technology dominates at some point +-- (probably combined with some libraries that at that point exist) determine what +-- is standard. Anyway, it probably also means that these formats are not that +-- suitable for long term archival purposes. So don't take the next implementation +-- too serious. + +-- We can do a direct conversion to PDF but then we also loose the abstraction which +-- in the future will be used. + +local type, tonumber = type, tonumber + +local P, S, R, C, Ct, Cs, Cc, Cp, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Carg + +local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns +local pi, sin, cos, asin, sind, cosd, tan, abs, sqrt = math.pi, math.sin, math.cos, math.asin, math.sind, math.cosd, math.tan, math.abs, math.sqrt +local concat, setmetatableindex = table.concat, table.setmetatableindex +local gmatch, gsub, find, match, rep = string.gmatch, string.gsub, string.find, string.match, string.rep +local formatters = string.formatters + +local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot, xmltext = xml.convert, xml.collected, xml.count, xml.first, xml.root, xml.text + +metapost = metapost or { } +local metapost = metapost +local context = context + +local report = logs.reporter("metapost","svg") + +local trace = false +-- local trace = true + +-- todo: also a high res mode +-- todo: optimize (no hurry) + +local f_rectangle = formatters['unitsquare xyscaled (%.3N,%.3N) shifted (%.3N,%.3N)'] +local f_rounded = formatters['roundedsquarexy(%.3N,%.3N,%.3N,%.3N) shifted (%.3N,%.3N)'] +local f_ellipse = formatters['(fullcircle xyscaled (%.3N,%.3N) shifted (%.3N,%.3N))'] +local f_circle = formatters['(fullcircle scaled %.3N shifted (%.3N,%.3N))'] +local f_line = formatters['((%.3N,%.3N)--(%.3N,%.3N))'] +local f_fill = formatters['fill %s(%s--cycle)%s%s%s ;'] -- play safe +local f_fill_cycle_c = formatters['fill %s(%s..cycle)%s%s%s ;'] +local f_fill_cycle_l = formatters['fill %s(%s--cycle)%s%s%s ;'] +local f_eofill = formatters['eofill %s(%s--cycle)%s%s%s ;'] -- play safe +local f_eofill_cycle_c = formatters['eofill %s(%s..cycle)%s%s%s ;'] +local f_eofill_cycle_l = formatters['eofill %s(%s--cycle)%s%s%s ;'] +local f_nofill = formatters['nofill %s(%s--cycle)%s ;'] -- play safe +local f_nofill_cycle_c = formatters['nofill %s(%s..cycle)%s ;'] +local f_nofill_cycle_l = formatters['nofill %s(%s--cycle)%s ;'] +local f_draw = formatters['draw %s(%s)%s%s%s%s%s ;'] +local f_nodraw = formatters['nodraw %s(%s)%s ;'] + +-- local f_fill = formatters['F %s(%s--C)%s%s%s ;'] -- play safe +-- local f_fill_cycle_c = formatters['F %s(%s..C)%s%s%s ;'] +-- local f_fill_cycle_l = formatters['F %s(%s--C)%s%s%s ;'] +-- local f_eofill = formatters['E %s(%s--C)%s%s%s ;'] -- play safe +-- local f_eofill_cycle_c = formatters['E %s(%s..C)%s%s%s ;'] +-- local f_eofill_cycle_l = formatters['E %s(%s--C)%s%s%s ;'] +-- local f_nofill = formatters['f %s(%s--C)%s ;'] -- play safe +-- local f_nofill_cycle_c = formatters['f %s(%s..C)%s ;'] +-- local f_nofill_cycle_l = formatters['f %s(%s--C)%s ;'] +-- local f_draw = formatters['D %s(%s)%s%s%s%s%s ;'] +-- local f_nodraw = formatters['d %s(%s)%s ;'] + +local f_color = formatters[' withcolor "%s"'] +local f_rgb = formatters[' withcolor (%.3N,%.3N,%.3N)'] +local f_rgba = formatters[' withcolor (%.3N,%.3N,%.3N) withtransparency (1,%3N)'] +local f_triplet = formatters['(%.3N,%.3N,%.3N)'] +local f_gray = formatters[' withcolor %.3N'] +local f_opacity = formatters[' withtransparency (1,%.3N)'] +local f_pen = formatters[' withpen pencircle scaled %.3N'] + +local f_dashed_n = formatters[" dashed dashpattern (%s ) "] +local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%.3N,0) "] + +local f_moveto = formatters['(%N,%n)'] +local f_curveto_z = formatters[' controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)'] +local f_curveto_n = formatters['.. controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)'] +local f_lineto_z = formatters['(%.3N,%.3N)'] +local f_lineto_n = formatters['-- (%.3N,%.3N)'] + +local f_rotatedaround = formatters[" ) rotatedaround((%.3N,%.3N),%.3N)"] +local f_rotated = formatters[" ) rotated(%.3N)"] +local f_shifted = formatters[" ) shifted(%.3N,%.3N)"] +local f_slanted_x = formatters[" ) xslanted(%.3N)"] +local f_slanted_y = formatters[" ) yslanted(%.3N)"] +local f_scaled = formatters[" ) scaled(%.3N)"] +local f_xyscaled = formatters[" ) xyscaled(%.3N,%.3N)"] +local f_matrix = formatters[" ) transformed bymatrix(%.3N,%.3N,%.3N,%.3N,%.3N,%.3N)"] + +-- penciled n -> withpen pencircle scaled n +-- applied (...) -> transformed bymatrix (...) +-- withopacity n -> withtransparency (1,n) + +local s_clip_start = 'draw image (' +local f_clip_stop = formatters[') ; clip currentpicture to (%s) ;'] +local f_eoclip_stop = formatters[') ; eoclip currentpicture to (%s) ;'] + +local f_transform_start = formatters["draw %s image ( "] +local f_transform_stop = formatters[") %s ;"] + +local s_offset_start = "draw image ( " +local f_offset_stop = formatters[") shifted (%.3N,%.3N) ;"] + +local f_viewport_start = "draw image (" +local f_viewport_stop = ") ;" +local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%03N,%03N);"] +local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%03N,%03N));"] + +local f_linecap = formatters["interim linecap := %s ;"] +local f_linejoin = formatters["interim linejoin := %s ;"] +local f_miterlimit = formatters["interim miterlimit := %s ;"] + +local s_begingroup = "begingroup;" +local s_endgroup = "endgroup;" + +-- make dedicated macro + +local s_shade_linear = ' withshademethod "linear" ' +local s_shade_circular = ' withshademethod "circular" ' +local f_shade_step = formatters['withshadestep ( withshadefraction %.3N withshadecolors(%s,%s) )'] +local f_shade_one = formatters['withprescript "sh_center_a=%.3N %.3N"'] +local f_shade_two = formatters['withprescript "sh_center_b=%.3N %.3N"'] + +local f_text_scaled = formatters['(textext.drt("%s") scaled %.3N shifted (%.3N,%.3N))'] +local f_text_normal = formatters['(textext.drt("%s") shifted (%.3N,%.3N))'] + +local p_digit = lpegpatterns.digit +local p_hexdigit = lpegpatterns.hexdigit +local p_space = lpegpatterns.whitespace + +local p_hexcolor = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) + r = tonumber(r,16)/255 + if g then + g = tonumber(g,16)/255 + end + if b then + b = tonumber(b,16)/255 + end + if b then + return f_rgb(r,g,b) + else + return f_gray(r) + end +end + +local p_hexcolor3 = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) + r = tonumber(r,16)/255 + g = g and tonumber(g,16)/255 or r + b = b and tonumber(b,16)/255 or g + return f_triplet(r,g,b) +end + +local function hexcolor(c) + return lpegmatch(p_hexcolor,c) +end + +local function hexcolor3(c) + return lpegmatch(p_hexcolor3,c) +end + +-- gains a little: + +-- local hexhash = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor, k) t[k] = v return v end) -- per file +-- local hexhash3 = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor3,k) t[k] = v return v end) -- per file +-- +-- local function hexcolor (c) return hexhash [c] end -- directly do hexhash [c] +-- local function hexcolor3(c) return hexhash3[c] end -- directly do hexhash3[c] + +-- Most of the conversion is rather trivial code till I ran into a file with arcs. A bit +-- of searching lead to the a2c javascript function but it has some puzzling thingies +-- (like sin and cos definitions that look like leftovers). Anyway, we can if needed +-- optimize it a bit more. Here does it come from: + +-- http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes +-- https://github.com/adobe-webplatform/Snap.svg/blob/b242f49e6798ac297a3dad0dfb03c0893e394464/src/path.js + +local d120 = (pi * 120) / 180 +local pi2 = 2 * pi + +local function a2c(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy) + + local recursive = f1 + + if rx == 0 or ry == 0 then + return { x1, y1, x2, y2, x2, y2 } + end + + if x1 == x2 and y1 == y2 then + return { x1, y1, x2, y2, x2, y2 } + end + + local rad = pi / 180 * angle + local res = nil + + local cosrad = cos(-rad) -- local cosrad = cosd(angle) + local sinrad = sin(-rad) -- local sinrad = sind(angle) + + if not recursive then + + x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad + x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad + + local x = (x1 - x2) / 2 + local y = (y1 - y2) / 2 + local xx = x * x + local yy = y * y + local h = xx / (rx * rx) + yy / (ry * ry) + + if h > 1 then + h = sqrt(h) + rx = h * rx + ry = h * ry + end + + local rx2 = rx * rx + local ry2 = ry * ry + local ry2xx = ry2 * xx + local rx2yy = rx2 * yy + local total = rx2yy + ry2xx -- otherwise overflow + + local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total)) + + if large == sweep then + k = -k + end + + cx = k * rx * y / ry + (x1 + x2) / 2 + cy = k * -ry * x / rx + (y1 + y2) / 2 + + f1 = (y1 - cy) / ry -- otherwise crash on a tiny eps + f2 = (y2 - cy) / ry -- otherwise crash on a tiny eps + + f1 = asin((f1 < -1.0 and -1.0) or (f1 > 1.0 and 1.0) or f1) + f2 = asin((f2 < -1.0 and -1.0) or (f2 > 1.0 and 1.0) or f2) + + if x1 < cx then f1 = pi - f1 end + if x2 < cx then f2 = pi - f2 end + + if f1 < 0 then f1 = pi2 + f1 end + if f2 < 0 then f2 = pi2 + f2 end + + if sweep ~= 0 and f1 > f2 then + f1 = f1 - pi2 + end + if sweep == 0 and f2 > f1 then + f2 = f2 - pi2 + end + + end + + if abs(f2 - f1) > d120 then + local f2old = f2 + local x2old = x2 + local y2old = y2 + f2 = f1 + d120 * ((sweep ~= 0 and f2 > f1) and 1 or -1) + x2 = cx + rx * cos(f2) + y2 = cy + ry * sin(f2) + res = a2c(x2, y2, rx, ry, angle, 0, sweep, x2old, y2old, f2, f2old, cx, cy) + end + + local c1 = cos(f1) + local s1 = sin(f1) + local c2 = cos(f2) + local s2 = sin(f2) + + local t = tan((f2 - f1) / 4) + local hx = 4 * rx * t / 3 + local hy = 4 * ry * t / 3 + + local r = { x1 - hx * s1, y1 + hy * c1, x2 + hx * s2, y2 - hy * c2, x2, y2, unpack(res or { }) } + + if not recursive then -- we can also check for sin/cos being 0/1 + cosrad = cos(rad) + sinrad = sin(rad) + -- cosrad = cosd(angle) + -- sinrad = sind(angle) + for i0=1,#r,2 do + local i1 = i0 + 1 + local x = r[i0] + local y = r[i1] + r[i0] = x * cosrad - y * sinrad + r[i1] = x * sinrad + y * cosrad + end + end + + return r +end + +-- incredible: we can find .123.456 => 0.123 0.456 ... + +local factors = { + ["pt"] = 1.25, + ["mm"] = 3.543307, + ["cm"] = 35.43307, + ["px"] = 1, + ["pc"] = 15, + ["in"] = 90, + ["em"] = 12 * 1.25, + ["ex"] = 8 * 1.25, +} + +local percentage_r = 1/100 +local percentage_x = percentage_r +local percentage_y = percentage_r + +local p_command_x = C(S("Hh")) +local p_command_y = C(S("Vv")) +local p_command_xy = C(S("CcLlMmQqSsTt")) +local p_command_a = C(S("Aa")) +local p_command = C(S("Zz")) + +local p_separator = S("\t\n\r ,")^1 +local p_number = (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1) ) + +local function convert (n) n = tonumber(n) return n end +local function convert_r (n,u) n = tonumber(n) if u == true then return percentage_r * n elseif u then return u * n else return n end end +local function convert_x (n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end +local function convert_y (n,u) n = tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end +local function convert_vx(n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end +local function convert_vy(n,u) n = - tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end + +local p_unit = ( P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in")) / factors +local p_percent = P("%") * Cc(true) + +local c_number_n = C(p_number) +local c_number_u = C(p_number) * (p_unit + p_percent)^-1 + +local p_number_n = c_number_n / convert +local p_number_x = c_number_u / convert_x +local p_number_vx = c_number_u / convert_vx +local p_number_y = c_number_u / convert_y +local p_number_vy = c_number_u / convert_vy +local p_number_r = c_number_u / convert_r + +-- local p_number = p_number_r -- maybe no percent here + +local function asnumber (s) return lpegmatch(p_number, s) end +local function asnumber_r (s) return lpegmatch(p_number_r, s) end +local function asnumber_x (s) return lpegmatch(p_number_x, s) end +local function asnumber_y (s) return lpegmatch(p_number_y, s) end +local function asnumber_vx(s) return lpegmatch(p_number_vx,s) end +local function asnumber_vy(s) return lpegmatch(p_number_vy,s) end + +local p_numbersep = p_number_n + p_separator +local p_numbers = p_separator^0 * P("(") * p_numbersep^0 * p_separator^0 * P(")") +local p_four = p_numbersep^4 + +-- local p_path = Ct((p_command + (p_number_x * p_separator^0 * p_number_y * p_separator^0) + p_separator)^1) + +local p_path = Ct ( ( + p_command_xy * (p_separator^0 * p_number_vx * + p_separator^0 * p_number_vy )^1 + + p_command_x * (p_separator^0 * p_number_vx )^1 + + p_command_y * (p_separator^0 * p_number_vy )^1 + + p_command_a * (p_separator^0 * p_number_vx * + p_separator^0 * p_number_vy * + p_separator^0 * p_number_r * + p_separator^0 * p_number_n * -- flags + p_separator^0 * p_number_n * -- flags + p_separator^0 * p_number_vx * + p_separator^0 * p_number_vy )^1 + + p_command + + p_separator +)^1 ) + +local p_rgbacolor = P("rgba(") * (C(p_number) + p_separator)^1 * P(")") + +local function rgbacolor(s) + local r, g, b, a = lpegmatch(p_rgbacolor,s) + if a then + return f_rgba(r/255,g/255,b/255,a) + end +end + +local function viewbox(v) + local x, y, w, h = lpegmatch(p_four,v) + if h then + return x, y, w, h + end +end + +-- actually we can loop faster because we can go to the last one + +local function grabpath(str) + local p = lpegmatch(p_path,str) + local np = #p + local t = { } -- no real saving here if we share + local n = 0 + local all = { entries = np, closed = false, curve = false } + local a = 0 + local i = 0 + local last = "M" + local prev = last + local kind = "L" + local x = 0 + local y = 0 + local x1 = 0 + local y1 = 0 + local x2 = 0 + local y2 = 0 + local rx = 0 + local ry = 0 + local ar = 0 + local al = 0 + local as = 0 + local ac = nil + while i < np do + i = i + 1 + local pi = p[i] + if type(pi) ~= "number" then + last = pi + i = i + 1 + pi = p[i] + end + -- most often + ::restart:: + if last == "c" then + x1 = x + pi + i = i + 1 ; y1 = y + p[i] + i = i + 1 ; x2 = x + p[i] + i = i + 1 ; y2 = y + p[i] + i = i + 1 ; x = x + p[i] + i = i + 1 ; y = y + p[i] + goto curveto + elseif last == "l" then + x = x + pi + i = i + 1 ; y = y + p[i] + goto lineto + elseif last == "h" then + x = x + pi + goto lineto + elseif last == "v" then + y = y + pi + goto lineto + elseif last == "a" then + x1 = x + y1 = y + rx = pi + i = i + 1 ; ry = p[i] + i = i + 1 ; ar = p[i] + i = i + 1 ; al = p[i] + i = i + 1 ; as = p[i] + i = i + 1 ; x = x + p[i] + i = i + 1 ; y = y + p[i] + goto arc + elseif last == "s" then + if prev == "C" then + x1 = 2 * x - x2 + y1 = 2 * y - y2 + else + x1 = x + y1 = y + end + x2 = x + pi + i = i + 1 ; y2 = y + p[i] + i = i + 1 ; x = x + p[i] + i = i + 1 ; y = y + p[i] + goto curveto + elseif last == "m" then + if n > 0 then + a = a + 1 + all[a] = concat(t,"",1,n) + n = 0 + end + x = x + pi + i = i + 1 ; y = y + p[i] + goto moveto + elseif last == "z" then + goto close + -- less frequent + elseif last == "C" then + x1 = pi + i = i + 1 ; y1 = p[i] + i = i + 1 ; x2 = p[i] + i = i + 1 ; y2 = p[i] + i = i + 1 ; x = p[i] + i = i + 1 ; y = p[i] + goto curveto + elseif last == "L" then + x = pi + i = i + 1 ; y = p[i] + goto lineto + elseif last == "H" then + x = pi + goto lineto + elseif last == "V" then + y = pi + goto lineto + elseif last == "A" then + x1 = x + y1 = y + rx = pi + i = i + 1 ; ry = p[i] + i = i + 1 ; ar = p[i] + i = i + 1 ; al = p[i] + i = i + 1 ; as = p[i] + i = i + 1 ; x = p[i] + i = i + 1 ; y = p[i] + goto arc + elseif last == "S" then + if prev == "C" then + x1 = 2 * x - x2 + y1 = 2 * y - y2 + else + x1 = x + y1 = y + end + x2 = pi + i = i + 1 ; y2 = p[i] + i = i + 1 ; x = p[i] + i = i + 1 ; y = p[i] + goto curveto + elseif last == "M" then + if n > 0 then + a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 + end + x = pi ; + i = i + 1 ; y = p[i] + goto moveto + elseif last == "Z" then + goto close + -- very seldom + elseif last == "q" then + x1 = x + pi + i = i + 1 ; y1 = y + p[i] + i = i + 1 ; x2 = x + p[i] + i = i + 1 ; y2 = y + p[i] + goto quadratic + elseif last == "t" then + if prev == "C" then + x1 = 2 * x - x1 + y1 = 2 * y - y1 + else + x1 = x + y1 = y + end + x2 = x + pi + i = i + 1 ; y2 = y + p[i] + goto quadratic + elseif last == "Q" then + x1 = pi + i = i + 1 ; y1 = p[i] + i = i + 1 ; x2 = p[i] + i = i + 1 ; y2 = p[i] + goto quadratic + elseif last == "T" then + if prev == "C" then + x1 = 2 * x - x1 + y1 = 2 * y - y1 + else + x1 = x + y1 = y + end + x2 = pi + i = i + 1 ; y2 = p[i] + goto quadratic + else + goto continue + end + ::moveto:: + n = n + 1 ; t[n] = f_moveto(x,y) + last = last == "M" and "L" or "l" + prev = "M" + goto continue + ::lineto:: + n = n + 1 ; t[n] = (n > 0 and f_lineto_n or f_lineto_z)(x,y) + prev = "L" + goto continue + ::curveto:: + n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(x1,y1,x2,y2,x,y) + prev = "C" + goto continue + ::arc:: + ac = a2c(x1,y1,rx,ry,ar,al,as,x,y) + for i=1,#ac,6 do + n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)( + ac[i],ac[i+1],ac[i+2],ac[i+3],ac[i+4],ac[i+5] + ) + end + prev = "A" + goto continue + ::quadratic:: + n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)( + x + 2/3 * (x1-x ), y + 2/3 * (y1-y ), + x2 + 2/3 * (x1-x2), y2 + 2/3 * (y1-y2), + x2, y2 + ) + x = x2 + y = y2 + prev = "C" + goto continue + ::close:: + n = n + 1 ; t[n] = prev == "C" and "..cycle" or "--cycle" + if n > 0 then + a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 + end + if i == n then + break + else + i = i - 1 + end + kind = prev + prev = "Z" + ::continue:: + end + if n > 0 then + a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 + end + if prev == "Z" then + all.closed = true + end + all.curve = (kind == "C" or kind == "A") + return all +end + +local transform do + + --todo: better lpeg + + local n = 0 + + local function rotate(r,x,y) + if x then + n = n + 1 + return r and f_rotatedaround(x,-(y or x),-r) + elseif r then + n = n + 1 + return f_rotated(-r) + else + return "" + end + end + + local function translate(x,y) + if y then + n = n + 1 + return f_shifted(x,-y) + elseif x then + n = n + 1 + return f_shifted(x,0) + else + return "" + end + end + + local function scale(x,y) + if y then + n = n + 1 + return f_xyscaled(x,y) + elseif x then + n = n + 1 + return f_scaled(x) + else + return "" + end + end + + local function skewx(x) + if x then + n = n + 1 + return f_slanted_x(math.sind(-x)) + else + return "" + end + end + + local function skewy(y) + if y then + n = n + 1 + return f_slanted_y(math.sind(-y)) + else + return "" + end + end + + local function matrix(rx,sx,sy,ry,tx,ty) + n = n + 1 + return f_matrix(rx or 1, sx or 0, sy or 0, ry or 1, tx or 0, - (ty or 0)) + end + + -- how to deal with units here? + + local p_transform = Cs ( ( + P("translate") * p_numbers / translate -- maybe xy + + P("scale") * p_numbers / scale + + P("rotate") * p_numbers / rotate + + P("matrix") * p_numbers / matrix + + P("skewX") * p_numbers / skewx + + P("skewY") * p_numbers / skewy + -- + p_separator + + P(1)/"" + )^1) + + transform = function(t,image) + n = 0 + local e = lpegmatch(p_transform,t) + local b = rep("( ",n) + if image then + return f_transform_start(b), f_transform_stop(e) + else + return b, e + end + end + +end + +local dashed do + + -- actually commas are mandate but we're tolerant + + local p_number = p_separator^0/"" * p_number_r + local p_on = Cc(" on ") * p_number + local p_off = Cc(" off ") * p_number + local p_dashed = Cs((p_on * p_off^-1)^1) + + dashed = function(s,o) + if not find(s,",") then + -- a bit of a hack: + s = s .. " " .. s + end + return (o and f_dashed_y or f_dashed_n)(lpegmatch(p_dashed,s),o) + end + +end + +do + + local defaults = { + x = 0, x1 = 0, x2 = 0, cx = 0, rx = 0, + y = 0, y1 = 0, y2 = 0, cy = 0, ry = 0, + r = 0, + + width = 0, + height = 0, + + stroke = "none", + fill = "black", + opacity = "none", + + ["stroke-width"] = 1, + ["stroke-linecap"] = "none", + ["stroke-linejoin"] = "none", + ["stroke-dasharray"] = "none", + ["stroke-dashoffset"] = "none", + ["stroke-miterlimit"] = "none", + ["stroke-opacity"] = "none", + + ["fill-opacity"] = "none", + -- ["fill-rule"] = "nonzero", + -- ["clip-rule"] = "nonzero", + } + + local handlers = { } + local process = false + local root = false + local result = false + local r = false + local definitions = false + local styles = false + local bodyfont = false + + -- todo: check use ... and make definitions[id] self resolving + + local function locate(id) + local ref = gsub(id,"^url%(#(.-)%)$","%1") + local ref = gsub(ref,"^#","") + local res = xmlfirst(root,"**[@id='"..ref.."']") + if res then + definitions[id] = res + return res + else + -- warning + end + end + + local function clippath(id,a) + local spec = definitions[id] or locate(id) + if spec then + local kind = spec.tg + if kind == "clipPath" then + ::again:: + for c in xmlcollected(spec,"/(path|use|g)") do + local tg = c.tg + if tg == "use" then + local ca = c.at + local id = ca["xlink:href"] + if id then + spec = locate(id) + if spec then + local sa = spec.at + setmetatableindex(sa,ca) + if spec.tg == "path" then + local d = sa.d + if d then + local p = grabpath(d) + p.evenodd = sa["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd" + return p + else + goto done + end + else + goto again + end + end + end + -- break + elseif tg == "path" then + local ca = c.at + local d = ca.d + if d then + local p = grabpath(d) + p.evenodd = ca["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd" + return p + else + goto done + end + else + -- inherit? + end + end + ::done:: + else + report("unknown clip %a",id) + end + end + end + + local function gradient(id) + local spec = definitions[id] + if spec then + local kind = spec.tg + local shade = nil + local n = 1 + local a = spec.at + if kind == "linearGradient" then + shade = { s_shade_linear } + -- + local x1 = a.x1 + local y1 = a.y1 + local x2 = a.x2 + local y2 = a.y2 + if x1 and y1 then + n = n + 1 shade[n] = f_shade_one(asnumber_vx(x1),asnumber_vy(y1)) + end + if x2 and y2 then + n = n + 1 shade[n] = f_shade_one(asnumber_vx(x2),asnumber_vy(y2)) + end + -- + elseif kind == "radialGradient" then + shade = { s_shade_circular } + -- + local cx = a.cx -- x center + local cy = a.cy -- y center + local r = a.r -- radius + local fx = a.fx -- focal points + local fy = a.fy -- focal points + -- + if cx and cy then + -- todo + end + if r then + -- todo + end + if fx and fy then + -- todo + end + else + report("unknown gradient %a",id) + return + end + -- local gu = a.gradientUnits + -- local gt = a.gradientTransform + -- local sm = a.spreadMethod + local colora, colorb + for c in xmlcollected(spec,"/stop") do + local a = c.at + local offset = a.offset + local colorb = a["stop-color"] + local opacity = a["stop-opacity"] + + colorb = colorb and hexcolor3(colorb) or colorb + colora = colora or colorb + + -- what if no percentage + + local fraction = offset and asnumber_r(offset) + + if not fraction then + -- offset = tonumber(offset) + -- for now + fraction = xmlcount(spec,"/stop")/100 + end + + n = n + 1 shade[n] = f_shade_step(fraction,colora,colorb) + + prevcolor = color + end + return concat(shade) + end + end + + local function drawproperties(stroke,a) + local w = a["stroke-width"] + if w then + w = f_pen(asnumber_r(w)) + else + w = "" + end + local d = a["stroke-dasharray"] + if d ~= "none" then + local o = a["stroke-dashoffset"] + if o ~= "none" then + o = asnumber_r(o) + else + o = false + end + d = dashed(d,o) + else + d = "" + end + local c = hexcolor(stroke) + if not c then + c = rgbacolor(stroke) + if not c then + c = f_color(stroke) + end + end + local o = a["stroke-opacity"] or a.opacity + if o ~= "none" then + o = asnumber_r(o) + if o and o ~= 1 then + o = f_opacity(o) + else + o = "" + end + else + o = "" + end + return w, d, c, o + end + + local function fillproperties(fill,a) + local c = gradient(fill) + if not c then + c = hexcolor(fill) + if not c then + c = f_color(fill) + end + end + local o = a["fill-opacity"] or a.opacity + if o == "none" then + o = asnumber_r(o) + if o and o ~= 1 then + o = f_opacity(asnumber_r(o)) + else + o = "" + end + else + o = "" + end + return c, o + end + + -- todo: clip = [ auto | rect(llx,lly,urx,ury) ] + + local function offset(a) + local x = a.x + local y = a.y + if x then x = asnumber_vx(x) end + if y then y = asnumber_vy(y) end + if not x then x = 0 end + if not y then y = 0 end + if x ~= 0 or y ~= 0 then + r = r + 1 ; result[r] = s_offset_start + return function() + r = r + 1 ; result[r] = f_offset_stop(x,-y) + end + end + end + + local function viewport(x,y,w,h,noclip) + r = r + 1 ; result[r] = f_viewport_start + return function() + r = r + 1 ; result[r] = f_viewport_shift(-x,y) + if not noclip then + r = r + 1 ; result[r] = f_viewport_clip(w,-h) + end + r = r + 1 ; result[r] = f_viewport_stop + end + end + + function handlers.defs(c,top) + for c in xmlcollected(c,"/*") do + local a = c.at + local id = a.id + -- setmetatableindex(a,top) + if id then + definitions["#" .. id ] = c + definitions["url(#" .. id .. ")"] = c + end + end + end + + function handlers.use(c,top) + local a = setmetatableindex(c.at,top) + local id = a["xlink:href"] -- better a rawget + if id then + local d = definitions[id] + if d then + local h = handlers[d.tg] + if h then + h(d,a) + end + else + local res = locate(id) + if res then + local wrapup = offset(a) + process(res,"/*",top) + if wrapup then + wrapup() + end + else + report("unknown definition %a",id) + end + end + end + end + + local linecaps = { butt = "butt", square = "squared", round = "rounded" } + local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" } + + local function stoplineproperties() + r = r + 1 result[r] = s_endgroup + end + + local function startlineproperties(a) + local cap = a["stroke-linecap"] + local join = a["stroke-linejoin"] + local limit = a["stroke-miterlimit"] + cap = cap ~= "none" and linecaps [cap] or false + join = join ~= "none" and linejoins[join] or false + limit = limit ~= "none" and asnumber_r(limit) or false + if cap or join or limit then + r = r + 1 result[r] = s_begingroup + if cap then + r = r + 1 result[r] = f_linecap(cap) + end + if join then + r = r + 1 result[r] = f_linejoin(join) + end + if limit then + r = r + 1 result[r] = f_miterlimit(limit) + end + return stoplineproperties + end + end + + local function flush(a,shape,nofill) + local fill = not nofill and a.fill + local stroke = a.stroke + local cpath = a["clip-path"] + local trans = a.transform + local b, e = "", "" + if cpath then + cpath = clippath(cpath,a) + end + if cpath then + r = r + 1 result[r] = s_clip_start + end + if trans then + b, e = transform(trans) + end + if fill and fill ~= "none" then + r = r + 1 result[r] = (a["fill-rule"] == "evenodd" and f_eofill or f_fill)(b,shape,e,fillproperties(fill,a)) + end + if stroke and stroke ~= "none" and stroke ~= 0 then + local wrapup = startlineproperties(a) + r = r + 1 result[r] = f_draw(b,shape,e,drawproperties(stroke,a)) + if wrapup then + wrapup() + end + end + if cpath then + r = r + 1 result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1]) + end + end + + -- todo: strokes in: + + local function flushpath(a,shape) + local fill = a.fill + local stroke = a.stroke + local cpath = a["clip-path"] + local trans = a.transform + local b, e = "", "" + if cpath then + cpath = definitions[cpath] + end + if cpath then + r = r + 1 result[r] = s_clip_start + end + -- todo: image (nicer for transform too) + if trans then + b, e = transform(trans) + end + if fill and fill ~= "none" then + local n = #shape + for i=1,n do + r = r + 1 + if i == n then + local f = a["fill-rule"] == "evenodd" + if shape.closed then + f = f and f_eofill or f_fill + elseif shape.curve then + f = f and f_eofill_cycle_c or f_fill_cycle_c + else + f = f and f_eofill_cycle_l or f_fill_cycle_l + end + result[r] = f(b,shape[i],e,fillproperties(fill,a)) + else + result[r] = f_nofill(b,shape[i],e) + end + end + end + if stroke and stroke ~= "none" and stroke ~= 0 then + local wrapup = startlineproperties(a) + local n = #shape + for i=1,n do + r = r + 1 + if i == n then + result[r] = f_draw(b,shape[i],e,drawproperties(stroke,a)) + else + result[r] = f_nodraw(b,shape[i],e) + end + end + if wrapup then + wrapup() + end + end + if cpath then + r = r + 1 result[r] = f_clip_stop(cpath[1]) + end + end + + function handlers.line(c,top) + local a = setmetatableindex(c.at,top) + local x1 = asnumber_vx(a.x1) + local y1 = asnumber_vy(a.y1) + local x2 = asnumber_vx(a.x2) + local y2 = asnumber_vy(a.y2) + if trace then + report("line: x1 %.3N, y1 %.3N, x2 %.3N, x3 %.3N",x1,y1,x2,y2) + end + flush(a,f_line(x1,y1,x2,y2),true) + end + + function handlers.rect(c,top) + local a = setmetatableindex(c.at,top) + local w = asnumber_x(a.width) + local h = asnumber_y(a.height) + local x = asnumber_vx(a.x) + local y = asnumber_vy(a.y) - h + local rx = a.rx + local ry = a.ry + if rx == 0 then rx = false end -- maybe no default 0 + if ry == 0 then ry = false end -- maybe no default 0 + if rx or ry then + rx = asnumber_x(rx or ry) + ry = asnumber_y(ry or rx) + if trace then + report("rect: x %.3N, y %.3N, w %.3N, h %.3N, rx %.3N, ry %.3N",x,y,w,h,rx,ry) + end + flush(a,f_rounded(w,h,rx,ry,x,y)) + else + if trace then + report("rect: x %.3N, y %.3N, w %.3N, h %.3N",x,y,w,h) + end + flush(a,f_rectangle(w,h,x,y)) + end + end + + function handlers.ellipse(c,top) + local a = setmetatableindex(c.at,top) + local x = asnumber_vx(a.cx) + local y = asnumber_vy(a.cy) + local rx = asnumber_x (a.rx) + local ry = asnumber_y (a.ry) + if trace then + report("ellipse: x %.3N, y %.3N, rx %.3N, ry %.3N",x,y,rx,ry) + end + flush(a,f_ellipse(2*rx,2*ry,x,y)) + end + + function handlers.circle(c,top) + local a = setmetatableindex(c.at,top) + local x = asnumber_vx(a.cx) + local y = asnumber_vy(a.cy) + local r = asnumber_r (a.r) + if trace then + report("circle: x %.3N, y %.3N, r %.3N",x,y,r) + end + flush(a,f_circle(2*r,x,y)) + end + + function handlers.path(c,top) + local a = setmetatableindex(c.at,top) + local d = a.d + if d then + local p = grabpath(d) + if trace then + report("path: %i entries, %sclosed",p.entries,p.closed and "" or "not ") + end + flushpath(a,p) + end + end + + do + + local p_pair = p_separator^0 * p_number_vx * p_separator^0 * p_number_vy + local p_pair_z = p_pair / f_lineto_z + local p_pair_n = p_pair / f_lineto_n + local p_open = Cc("(") + local p_close = Carg(1) * P(true) / function(s) return s end + local p_polyline = Cs(p_open * p_pair_z * p_pair_n^0 * p_close) + + function handlers.polyline(c,top) + local a = setmetatableindex(c.at,top) + local p = a.points + if p then + flush(a,lpegmatch(p_polyline,p,1,")"),true) + end + end + + function handlers.polygon(c,top) + local a = setmetatableindex(c.at,top) + local p = a.points + if p then + flush(a,lpegmatch(p_polyline,p,1,"--cycle)")) + end + end + + end + + function handlers.g(c,top) + local a = c.at + setmetatableindex(a,top) + process(c,"/*",a) + end + + function handlers.symbol(c,top) + report("todo: %s","symbol") + end + + -- We only need to filter classes .. I will complete this when I really need + -- it. Not hard to do but kind of boring. + + local p_class = P(".") * C((R("az","AZ","09","__","--")^1)) + local p_spec = P("{") * C((1-P("}"))^1) * P("}") + + local p_grab = ((Carg(1) * p_class * p_space^0 * p_spec / function(t,k,v) t[k] = v end) + p_space^1 + P(1))^1 + + function handlers.style(c,top) + local s = xmltext(c) + lpegmatch(p_grab,s,1,styles) + for k, v in next, styles do + -- todo: lpeg + local t = { } + for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);") do + if k == "font" then + v = xml.css.fontspecification(v) + end + t[k] = v + end + styles[k] = t + end + bodyfont = tex.getdimen("bodyfontsize") / 65536 + end + + -- this will never really work out + + function handlers.text(c,top) + local a = c.at + local x = asnumber_vx(a.x) + local y = asnumber_vy(a.y) + local text = xmltext(c) + local size = false + local fill = a.fill + -- escape text or keep it at the tex end + local class = a.class + if class then + local style = styles[class] + if style and next(style) then + local font = style.font + local s_family = font and font.family + local s_style = font and font.style + local s_weight = font and font.weight + local s_size = font and font.size + if s_size then + local f = factors[s_size[2]] + if f then + size = f * s_size[1] + end + end + -- todo: family: serif | sans-serif | monospace : use mapping + if s_family then + if s_family == "serif" then + text = "\\rm " .. text + elseif s_family == "sans-serif" then + text = "\\ss " .. text + else + text = "\\tt " .. text + end + end + if s_style or s_weight then + text = formatters['\\style[%s%s]{%s}'](s_weight or "",s_style or "",text) + end + fill = style.fill or fill + end + end + if size then + -- bodyfontsize + size = size/bodyfont + text = f_text_scaled(text,size,x,y) + else + text = f_text_normal(text,x,y) + end + if fill and fill ~= "none" then + text = f_draw("",text,"",fillproperties(fill,a)) + else + text = f_draw("",text,"","","","","") + end + if trace then + report("text: x %.3N, y %.3N, text %s",x,y,text) + end + r = r + 1 ; result[r] = text + end + + function handlers.svg(c,top,x,y,w,h,noclip) + local a = setmetatableindex(c.at,top) + local v = a.viewBox + local t = a.transform + local wrapupoffset + local wrapupviewport + -- local ex, em + local xpct, ypct, rpct + if trace then + report("view: %s, xpct %.3N, ypct %.3N","before",percentage_x,percentage_y) + end + if v then + x, y, w, h = viewbox(v) + if trace then + report("viewbox: x %.3N, y %.3N, width %.3N, height %.3N",x,y,w,h) + end + end + if h then + -- + -- em = factors["em"] + -- ex = factors["ex"] + -- factors["em"] = em + -- factors["ex"] = ex + -- + xpct = percentage_x + ypct = percentage_y + rpct = percentage_r + percentage_x = w / 100 + percentage_y = h / 100 + percentage_r = (sqrt(w^2 + h^2) / sqrt(2)) / 100 + if trace then + report("view: %s, xpct %.3N, ypct %.3N","inside",percentage_x,percentage_y) + end + wrapupviewport = viewport(x,y,w,h,noclip) + end + -- todo: combine transform and offset here + if t then + btransform, etransform = transform(t,true) + end + if btransform then + r = r + 1 ; result[r] = btransform + end + wrapupoffset = offset(a) + process(c,"/*",top or defaults) + if wrapupoffset then + wrapupoffset() + end + if etransform then + r = r + 1 ; result[r] = etransform + end + if h then + -- + -- factors["em"] = em + -- factors["ex"] = ex + -- + percentage_x = xpct + percentage_y = ypct + percentage_r = rpct + if wrapupviewport then + wrapupviewport() + end + end + if trace then + report("view: %s, xpct %.3N, ypct %.3N","after",percentage_x,percentage_y) + end + end + + process = function(x,p,top) + for c in xmlcollected(x,p) do + local h = handlers[c.tg] + if h then + h(c,top) + end + end + end + + function metapost.svgtomp(specification,pattern) + local mps = "" + local svg = specification.data + if type(svg) == "string" then + svg = xmlconvert(svg) + end + if svg then + local c = xmlfirst(svg,pattern or "/svg") + if c then + root, result, r, definitions, styles, bodyfont = svg, { }, 0, { }, { }, 12 + -- print("default",x,y,w,h) + handlers.svg ( + c, + nil, + specification.x, + specification.y, + specification.w, + specification.h, + specification.noclip + ) + mps = concat(result," ") + root, result, r, definitions, styles, bodyfont = false, false, false, false, false, false + else + report("missing svg root element") + end + else + report("bad svg blob") + end + return mps + end + +end + +function metapost.showsvgpage(data) + local dd = data.data + if type(dd) == "table" then + local comment = dd.comment + local offset = dd.pageoffset + for i=1,#dd do + local d = setmetatableindex( { + data = dd[i], + comment = comment and i or false, + pageoffset = offset or nil, + }, data) + metapost.showsvgpage(d) + end + else + context.startMPpage { instance = "doublefun", offset = data.pageoffset or nil } + context(metapost.svgtomp(data)) + local comment = data.comment + if comment then + context("draw boundingbox currentpicture withcolor .6red ;") + context('draw textext.bot("\\strut\\tttf %s") ysized (10pt) shifted center bottomboundary currentpicture ;',comment) + end + context.stopMPpage() + end +end + +function metapost.svgtopdf(data,...) + local mps = metapost.svgtomp(data,...) + if mps then + local pdf = metapost.simple("metafun",mps,true) -- todo: special instance, only basics needed + if pdf then + return pdf + else + -- message + end + else + -- message + end +end + +do + + local runner = sandbox.registerrunner { + name = "otfsvg2pdf", + program = "context", + template = "--batchmode --purgeall --runs=2 %filename%", + reporter = report_svg, + } + + -- By using an independent pdf file instead of pdf streams we can use resources and still + -- cache. This is the old method updated. Maybe a future version will just do this runtime + -- but for now this is the most efficient method. + + function metapost.svgshapestopdf(svgshapes,pdftarget,report_svg) + local texname = "temp-otf-svg-to-pdf.tex" + local pdfname = "temp-otf-svg-to-pdf.pdf" + local tucname = "temp-otf-svg-to-pdf.tuc" + local nofshapes = #svgshapes + local pdfpages = { filename = pdftarget } + local pdfpage = 0 + local t = { } + local n = 0 + -- + os.remove(texname) + os.remove(pdfname) + os.remove(tucname) + -- + if report_svg then + report_svg("processing %i svg containers",nofshapes) + statistics.starttiming(pdfpages) + end + -- + -- can be option: + -- + n = n + 1 ; t[n] = "\\enabledirectives[pdf.stripzeros,metapost.stripzeros]" + -- + -- can be option: + -- + -- n = n + 1 ; t[n] = "\\nopdfcompression" + -- + n = n + 1 ; t[n] = "\\starttext" + n = n + 1 ; t[n] = "\\setupMPpage[alternative=offset,instance=doublefun]" + -- + for i=1,nofshapes do + local entry = svgshapes[i] + local data = entry.data + local specification = { + data = xmlconvert(data), + x = 0, + y = 1000, + width = 1000, + height = 1000, + noclip = true, + } + for index=entry.first,entry.last do + if not pdfpages[index] then + pdfpage = pdfpage + 1 + pdfpages[index] = pdfpage + local pattern = "/svg[@id='glyph" .. index .. "']" + n = n + 1 ; t[n] = "\\startMPpage" + n = n + 1 ; t[n] = metapost.svgtomp(specification,pattern) or "" + n = n + 1 ; t[n] = "\\stopMPpage" + end + end + end + n = n + 1 ; t[n] = "\\stoptext" + io.savedata(texname,concat(t,"\n")) + runner { filename = texname } + os.remove(pdftarget) + file.copy(pdfname,pdftarget) + if report_svg then + statistics.stoptiming(pdfpages) + report_svg("svg conversion time %s",statistics.elapsedseconds(pdfpages)) + end + os.remove(texname) + os.remove(pdfname) + os.remove(tucname) + return pdfpages + end + + function metapost.svgshapestomp(svgshapes,report_svg) + local nofshapes = #svgshapes + local mpshapes = { } + if report_svg then + report_svg("processing %i svg containers",nofshapes) + statistics.starttiming(mpshapes) + end + for i=1,nofshapes do + local entry = svgshapes[i] + local data = entry.data + local specification = { + data = xmlconvert(data), + x = 0, + y = 1000, + width = 1000, + height = 1000, + noclip = true, + } + for index=entry.first,entry.last do + if not mpshapes[index] then + local pattern = "/svg[@id='glyph" .. index .. "']" + mpshapes[index] = metapost.svgtomp(specification,pattern) or "" + end + end + end + if report_svg then + statistics.stoptiming(mpshapes) + report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes)) + end + return mpshapes + end + +end diff --git a/tex/context/base/mkiv/mult-def.lua b/tex/context/base/mkiv/mult-def.lua index cb3b4f4e5..3c6ccceaf 100644 --- a/tex/context/base/mkiv/mult-def.lua +++ b/tex/context/base/mkiv/mult-def.lua @@ -14336,6 +14336,10 @@ return { ["pe"]="درون‌متن", ["ro"]="intext", }, + ["headintext"]={ + ["en"]="headintext", + ["nl"]="kopintekst", + }, ["intro"]={ ["cs"]="uvod", ["de"]="intro", diff --git a/tex/context/base/mkiv/mult-fun.lua b/tex/context/base/mkiv/mult-fun.lua index d18d1bc0e..99700f474 100644 --- a/tex/context/base/mkiv/mult-fun.lua +++ b/tex/context/base/mkiv/mult-fun.lua @@ -30,6 +30,7 @@ return { "getparametercount", "getmaxparametercount", "getparameterpath", + "getparameterpen", "getparametertext", -- "getparameteroption", "applyparameters", diff --git a/tex/context/base/mkiv/mult-mps.lua b/tex/context/base/mkiv/mult-mps.lua index 35035308e..625e8ca65 100644 --- a/tex/context/base/mkiv/mult-mps.lua +++ b/tex/context/base/mkiv/mult-mps.lua @@ -117,7 +117,7 @@ return { -- "mm", "pt", "dd", "bp", "cm", "pc", "cc", "in", -- - "triplet", "quadruplet", "totransform", + "triplet", "quadruplet", "totransform", "bymatrix", -- }, internals = { -- we need to remove duplicates above diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf index 75ea1077e..410f87641 100644 Binary files a/tex/context/base/mkiv/status-files.pdf and b/tex/context/base/mkiv/status-files.pdf differ diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf index 726657bc3..8008a61ee 100644 Binary files a/tex/context/base/mkiv/status-lua.pdf and b/tex/context/base/mkiv/status-lua.pdf differ diff --git a/tex/context/base/mkiv/strc-itm.mklx b/tex/context/base/mkiv/strc-itm.mklx index eeeb5ce00..e5458b5dd 100644 --- a/tex/context/base/mkiv/strc-itm.mklx +++ b/tex/context/base/mkiv/strc-itm.mklx @@ -198,6 +198,7 @@ \newconditional\c_strc_itemgroups_txt \newconditional\c_strc_itemgroups_extra \newconditional\c_strc_itemgroups_repeat +\newconditional\c_strc_itemgroups_inline_head % 0 = before/after % 1 = between unless before @@ -440,6 +441,7 @@ \setitemgroupparameter\c!width{0em}% \fi} % signal \setvalue{\??itemgroupkeyword\v!intext }{\settrue\c_strc_itemgroups_inline} +\setvalue{\??itemgroupkeyword\v!headintext }{\settrue\c_strc_itemgroups_inline_head} \setvalue{\??itemgroupkeyword\v!loose }{\setfalse\c_strc_itemgroups_optimize} \setvalue{\??itemgroupkeyword\v!fit }{\settrue\c_strc_itemgroups_fitting} \setvalue{\??itemgroupkeyword\v!nofit }{\setfalse\c_strc_itemgroups_fitting} @@ -1211,19 +1213,25 @@ \unexpanded\def\strc_itemgroups_stop_head_indeed {\removeunwantedspaces \dostoptagged - \ifconditional\c_strc_itemgroups_text - \space - \ignorespaces + \ifconditional\c_strc_itemgroups_inline_head + \space + \ignorespaces \else - \par - \fi - \strc_itemgroups_insert_breakno - \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi - \strc_itemgroups_insert_breakno} + \ifconditional\c_strc_itemgroups_text + \space + \ignorespaces + \else + \par + \fi + \strc_itemgroups_insert_breakno + \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi + \strc_itemgroups_insert_breakno + \fi} \unexpanded\def\strc_itemgroups_head_body_indeed {\dostarttagged\t!itembody\empty - \noindentation} + \noindentation + \ignorespaces} % Simple commands. diff --git a/tex/context/base/mkiv/strc-itm.mkvi b/tex/context/base/mkiv/strc-itm.mkvi index bc140d65b..f09b24cdf 100644 --- a/tex/context/base/mkiv/strc-itm.mkvi +++ b/tex/context/base/mkiv/strc-itm.mkvi @@ -198,6 +198,7 @@ \newconditional\c_strc_itemgroups_txt \newconditional\c_strc_itemgroups_extra \newconditional\c_strc_itemgroups_repeat +\newconditional\c_strc_itemgroups_inline_head % 0 = before/after % 1 = between unless before @@ -442,6 +443,7 @@ \setitemgroupparameter\c!width{0em}% \fi} % signal \setvalue{\??itemgroupkeyword\v!intext }{\settrue\c_strc_itemgroups_inline} +\setvalue{\??itemgroupkeyword\v!headintext }{\settrue\c_strc_itemgroups_inline_head} \setvalue{\??itemgroupkeyword\v!loose }{\setfalse\c_strc_itemgroups_optimize} \setvalue{\??itemgroupkeyword\v!fit }{\settrue\c_strc_itemgroups_fitting} \setvalue{\??itemgroupkeyword\v!nofit }{\setfalse\c_strc_itemgroups_fitting} @@ -1232,19 +1234,25 @@ \unexpanded\def\strc_itemgroups_stop_head_indeed {\removeunwantedspaces \dostoptagged - \ifconditional\c_strc_itemgroups_text - \space - \ignorespaces + \ifconditional\c_strc_itemgroups_inline_head + \space + \ignorespaces \else - \par - \fi - \strc_itemgroups_insert_breakno - \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi - \strc_itemgroups_insert_breakno} + \ifconditional\c_strc_itemgroups_text + \space + \ignorespaces + \else + \par + \fi + \strc_itemgroups_insert_breakno + \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi + \strc_itemgroups_insert_breakno + \fi} \unexpanded\def\strc_itemgroups_head_body_indeed {\dostarttagged\t!itembody\empty - \noindentation} + \noindentation + \ignorespaces} % Simple commands. diff --git a/tex/context/base/mkiv/strc-ref.lua b/tex/context/base/mkiv/strc-ref.lua index ec5ed5e03..a4f31ec0c 100644 --- a/tex/context/base/mkiv/strc-ref.lua +++ b/tex/context/base/mkiv/strc-ref.lua @@ -2613,14 +2613,14 @@ implement { -- actions = { referencerealpage, context }, -- -- arguments = "string" -- hm, weird -- } --- --- implement { --- name = "referencerealpage", --- actions = function() --- local actions = references.currentset --- context(not actions and 0 or actions.realpage or setreferencerealpage(actions)) --- end --- } + +implement { + name = "referencerealpage", + actions = function() + local actions = references.currentset + context(not actions and 0 or actions.realpage or setreferencerealpage(actions)) + end +} local function referencepos(key) local actions = references.currentset diff --git a/tex/context/base/mkiv/syst-aux.mkiv b/tex/context/base/mkiv/syst-aux.mkiv index 1cade869c..255f0746c 100644 --- a/tex/context/base/mkiv/syst-aux.mkiv +++ b/tex/context/base/mkiv/syst-aux.mkiv @@ -5250,7 +5250,7 @@ \unexpanded\def\pickupgroupedcommand#1#2#3% {\def\m_syst_helpers_handle_group_b{#1}% \def\m_syst_helpers_handle_group_a{#2}% - \def\m_syst_helpers_handle_group_p{#2}% + \def\m_syst_helpers_handle_group_p{#3}% \doifelsenextbgroupcs\syst_helpers_handle_group_pickup\syst_helpers_handle_group_nop} \unexpanded\def\triggergroupedcommand#1% diff --git a/tex/context/base/mkiv/syst-aux.mkxl b/tex/context/base/mkiv/syst-aux.mkxl index b2fc08487..c14e03008 100644 --- a/tex/context/base/mkiv/syst-aux.mkxl +++ b/tex/context/base/mkiv/syst-aux.mkxl @@ -4007,7 +4007,7 @@ \unexpanded\def\pickupgroupedcommand#1#2#3% {\def\m_syst_helpers_handle_group_b{#1}% \def\m_syst_helpers_handle_group_a{#2}% - \def\m_syst_helpers_handle_group_p{#2}% + \def\m_syst_helpers_handle_group_p{#3}% \futureexpandis\bgroup\syst_helpers_handle_group_pickup\syst_helpers_handle_group_nop} \unexpanded\def\triggergroupedcommand#1% diff --git a/tex/context/base/mkiv/trac-deb.lua b/tex/context/base/mkiv/trac-deb.lua index 5101119d7..72294e587 100644 --- a/tex/context/base/mkiv/trac-deb.lua +++ b/tex/context/base/mkiv/trac-deb.lua @@ -242,7 +242,10 @@ function tracers.printerror(specification) local offset = specification.offset local report = errorreporter(luaerrorline) if not filename then - report("error not related to input file: %s ...",lasttexerror) + report("error not related to input file:") + report(" tex: %s",lasttexerror or "-") + report(" lua: %s",lastluaerror or "-") + report(" mps: %s",lastmpserror or "-") elseif type(filename) == "number" then report("error on line %s of filehandle %s: %s ...",linenumber,lasttexerror) else diff --git a/tex/context/base/mkiv/util-lua.lua b/tex/context/base/mkiv/util-lua.lua index f7e6e4f31..fb4088e7b 100644 --- a/tex/context/base/mkiv/util-lua.lua +++ b/tex/context/base/mkiv/util-lua.lua @@ -28,6 +28,13 @@ luautilities.nofstrippedbytes = 0 local strippedchunks = { } -- allocate() luautilities.strippedchunks = strippedchunks +if not LUATEXENGINE then + --- probably mtxrun ... + LUATEXENGINE = status.luatex_engine and string.lower(status.luatex_engine) + JITSUPPORTED = LUATEXENGINE == "luajittex" or jit + CONTEXTLMTXMODE = CONTEXTLMTXMODE or (LUATEXENGINE == "luametatex" and 1) or 0 +end + luautilities.suffixes = { tma = "tma", tmc = (CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 and "tmd") or (jit and "tmb") or "tmc", @@ -37,7 +44,8 @@ luautilities.suffixes = { luv = "luv", luj = "luj", tua = "tua", - tuc = "tuc", + -- tuc = "tuc", + tuc = (CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 and "tud") or (jit and "tub") or "tuc", } -- environment.loadpreprocessedfile can be set to a preprocessor diff --git a/tex/context/base/mkiv/util-sac.lua b/tex/context/base/mkiv/util-sac.lua index 976989a77..19dc0eb0f 100644 --- a/tex/context/base/mkiv/util-sac.lua +++ b/tex/context/base/mkiv/util-sac.lua @@ -43,6 +43,8 @@ function streams.size(f) return f and f[3] or 0 end +streams.getsize = streams.size + function streams.setposition(f,i) if f[4] then -- zerobased @@ -485,3 +487,58 @@ else end end + +-- For practical reasons we put this here. It's less efficient but ok when we don't +-- have much access. + +do + + local files = utilities.files + + if files then + + local openfile = files.open + local openstream = streams.open + local openstring = streams.openstring + + local setmetatable = setmetatable + + function io.newreader(str,method) + local f, m + if method == "string" then + f = openstring(str) + m = streams + elseif method == "stream" then + f = openstream(str) + m = streams + else + f = openfile(str,"rb") + m = files + end + if f then + local t = { } + setmetatable(t, { + __index = function(t,k) + local r = m[k] + if k == "close" then + if f then + m.close(f) + f = nil + end + return function() end + elseif r then + local v = function(_,a,b) return r(f,a,b) end + t[k] = v + return v + else + print("unknown key",k) + end + end + } ) + return t + end + end + + end + +end diff --git a/tex/context/interface/mkii/keys-en.xml b/tex/context/interface/mkii/keys-en.xml index 3b2b69e0b..a621f03ab 100644 --- a/tex/context/interface/mkii/keys-en.xml +++ b/tex/context/interface/mkii/keys-en.xml @@ -237,6 +237,7 @@ + diff --git a/tex/context/interface/mkii/keys-it.xml b/tex/context/interface/mkii/keys-it.xml index ac2816578..f6982abca 100644 --- a/tex/context/interface/mkii/keys-it.xml +++ b/tex/context/interface/mkii/keys-it.xml @@ -237,6 +237,7 @@ + @@ -873,6 +874,7 @@ + @@ -973,9 +975,12 @@ + + + @@ -1293,6 +1298,7 @@ + @@ -1303,6 +1309,7 @@ + @@ -1310,6 +1317,7 @@ + diff --git a/tex/context/interface/mkii/keys-ro.xml b/tex/context/interface/mkii/keys-ro.xml index 12c7a8606..ca739d2b2 100644 --- a/tex/context/interface/mkii/keys-ro.xml +++ b/tex/context/interface/mkii/keys-ro.xml @@ -237,6 +237,7 @@ + diff --git a/tex/context/interface/mkiv/context-en.xml b/tex/context/interface/mkiv/context-en.xml index 17bc09c8e..3953a8a04 100644 --- a/tex/context/interface/mkiv/context-en.xml +++ b/tex/context/interface/mkiv/context-en.xml @@ -17651,6 +17651,7 @@ + @@ -18310,6 +18311,7 @@ + diff --git a/tex/context/interface/mkiv/i-context.pdf b/tex/context/interface/mkiv/i-context.pdf index a6e83085f..5c0fba19a 100644 Binary files a/tex/context/interface/mkiv/i-context.pdf and b/tex/context/interface/mkiv/i-context.pdf differ diff --git a/tex/context/interface/mkiv/i-itemgroup.xml b/tex/context/interface/mkiv/i-itemgroup.xml index 416cf1ccd..810024351 100644 --- a/tex/context/interface/mkiv/i-itemgroup.xml +++ b/tex/context/interface/mkiv/i-itemgroup.xml @@ -20,6 +20,7 @@ + diff --git a/tex/context/interface/mkiv/i-readme.pdf b/tex/context/interface/mkiv/i-readme.pdf index 4d002f1b0..db5adf50d 100644 Binary files a/tex/context/interface/mkiv/i-readme.pdf and b/tex/context/interface/mkiv/i-readme.pdf differ diff --git a/tex/context/modules/mkiv/m-scite.mkiv b/tex/context/modules/mkiv/m-scite.mkiv index 5d192a5f6..315cb3bed 100644 --- a/tex/context/modules/mkiv/m-scite.mkiv +++ b/tex/context/modules/mkiv/m-scite.mkiv @@ -334,9 +334,12 @@ moduledata.scite = scite \newdimen\scitespaceskip \unexpanded\def\buff_scite_slxb#1% - {\hangindent\numexpr#1+2\relax\scitespaceskip + {% we can have a side float + \advance\hangindent\numexpr#1+2\relax\scitespaceskip \begstrut\hskip#1\scitespaceskip - \hangafter 1\relax} + \ifcase\hangafter + \hangafter\plusone\relax + \fi} \unexpanded\def\buff_scite_slxe {\endstrut\par} diff --git a/tex/context/modules/mkiv/s-fonts-variable.mkiv b/tex/context/modules/mkiv/s-fonts-variable.mkiv index 2cd612c75..5336f6024 100644 --- a/tex/context/modules/mkiv/s-fonts-variable.mkiv +++ b/tex/context/modules/mkiv/s-fonts-variable.mkiv @@ -49,6 +49,8 @@ % \enabletrackers[modules.fonts.variables.glyphs] % \enabletrackers[modules.fonts.variables.kerns] +\nopdfcompression + \starttext \startbuffer[zycon] @@ -81,9 +83,9 @@ % \showfontvariations % [font=file:DecoVar-VF.ttf] - % \showfontvariations - % [font=file:VotoSerifGX.ttf, - % max=15] +% \showfontvariations +% [font=file:VotoSerifGX.ttf, +% max=15] % \showfontvariations % [font=file:Selawik-Variable.ttf] @@ -91,23 +93,23 @@ % \showfontvariations % [font=file:LibreFranklinGX-Romans.ttf] - % \showfontvariations - % [font=file:Zycon.ttf, - % sample={\getbuffer[zycon]}] +% \showfontvariations +% [font=file:Zycon.ttf, +% sample={\getbuffer[zycon]}] - % \showfontvariations - % [font=file:kairossansvariable.ttf] +% \showfontvariations +% [font=file:kairossansvariable.ttf] - % \showfontvariations - % [font=file:sourcecode-regular.otf] +% \showfontvariations +% [font=file:sourcecode-regular.otf] - % \showfontvariations - % [font=file:AmstelvarAlpha-VF.ttf] +% \showfontvariations +% [font=file:AmstelvarAlpha-VF.ttf] - % \showfontvariations - % [font=file:bahnschrift.ttf] + \showfontvariations + [font=file:bahnschrift.ttf] - % \showfontvariations - % [font=file:sitka.ttc] +% \showfontvariations +% [font=file:sitka.ttc] \stoptext diff --git a/tex/context/modules/mkiv/s-present-luatex.mkiv b/tex/context/modules/mkiv/s-present-luatex.mkiv new file mode 100644 index 000000000..079860bf9 --- /dev/null +++ b/tex/context/modules/mkiv/s-present-luatex.mkiv @@ -0,0 +1,118 @@ +%D \module +%D [ file=s-present-luatex, +%D version=2016.04.30, % around +%D title=\CONTEXT\ Style File, +%D subtitle=Presentation Environment Banner, +%D author=Hans Hagen, +%D date=\currentdate, +%D copyright={PRAGMA ADE \& \CONTEXT\ Development Team}] +%C +%C This module is part of the \CONTEXT\ macro||package and is +%C therefore copyrighted by \PRAGMA. See mreadme.pdf for +%C details. + +%D As with older presentations we use the \LUA\ moon as page counter. The rest +%D of the style is simple because I got bored of all this fancy stepping for +%D simple presentations. + +%D Beware: this presentation is meant for my own use and not a general purpose +%D presentation style. It is a rather dumb module but served its purpose well. + +\startmodule[present-luatex] + +\useMPlibrary[lua] + +\setupbodyfont + [dejavu,17.3pt] + +\startuseMPgraphic{page} + StartPage ; + luaorbitcolor := white ; + fill Page withcolor (.5white - luaplanetcolor) ; + draw anchored.urt(image(drawluapagenumbergoround) xsized 150pt,urcorner Page shifted (-15pt,-10pt)) ; + if RealPageNumber > 1 : + begingroup; + save p, q; picture p, q; + p := textext("\ssbf\setstrut\strut \documentvariable{mp:title}") xsized 150pt ; + q := textext("\ssbf\setstrut\strut \documentvariable{mp:subtitle}") xsized 150pt ; + setbounds q to boundingbox(p); + draw anchored.lrt(p,lrcorner Page shifted (-15pt, 90pt)) withcolor luaplanetcolor ; + draw anchored.lrt(q,lrcorner Page shifted (-15pt, 55pt)) withcolor luaholecolor ; + endgroup; + fi ; + fill Field[Text][Text] enlarged 10pt withcolor (.5white - luaplanetcolor)/1.5 ; + StopPage ; +\stopuseMPgraphic + +\defineoverlay + [page] + [\useMPgraphic{page}] + +\setupbackgrounds + [page] + [background=page] + +\setuppapersize + [S6] + +\setuplayout + [backspace=50pt, + cutspace=200pt, + topspace=40pt, + bottomspace=40pt, + margin=0pt, + footer=0pt, + header=0pt, + width=middle, + height=middle] + +\setupmakeup + [standard] + [top=\vfil, + bottom=\vfilll, + setups=standardmakeup, + align=middle, + color=white] + +\setupcolors + [textcolor=white] + +\startsetups standardmakeup + \setupwhitespace + [big] +\stopsetups + +\setupdocument + [title=Title, + subtitle=Subtitle, + location=\currentdate, + mp:title=title, + mp:subtitle=subtitle] + +\startsetups document:start + + \startstandardmakeup[bottom=,top=] + + \raggedcenter + + \vfil\vfil + \dontleavehmode\scale[width=1.00\textwidth]{\ssbf\documentvariable{title}} + \vfil + \dontleavehmode\scale[width=0.75\textwidth]{\ssbf\documentvariable{subtitle}} + \vfil + \dontleavehmode\scale[width=0.65\textwidth]{\ssbf\documentvariable{location}} + \vfil + \dontleavehmode\scale[width=0.45\textwidth]{\ssbf\documentvariable{author}} + \vfil\vfil\vfil + + \stopstandardmakeup + +\stopsetups + +\stopmodule + +\continueifinputfile{s-present-luatex.mkiv} + +\usemodule[present-common] + +\inputpresentationfile{context/2016/context-2016-luatex.tex} diff --git a/tex/context/modules/mkiv/s-tugboat.mkiv b/tex/context/modules/mkiv/s-tugboat.mkiv index d3d15c3e7..e243dc487 100644 --- a/tex/context/modules/mkiv/s-tugboat.mkiv +++ b/tex/context/modules/mkiv/s-tugboat.mkiv @@ -1,32 +1,33 @@ %D \module %D [ file=t-tugboat -%D version=$Id: t-tugboat.tex 111 2013-02-25 17:56:22Z bnb $ +%D version=$Id: s-tugboat.mkiv 246 2019-09-09 18:22:19Z karl $ %D title=\CONTEXT\ Style File, %D subtitle=\TUGBOAT\ base style, -%D author=Aditya Mahajan, +%D author=Aditya Mahajan and others, %D date=\currentdate, %D copyright=Public Domain] -%D This file is derived from \filename{s-tug-02} written by Hans Hagen -%D and Steve Grathwohl. Karl Berry asked me for some changes in the -%D \TUGBOAT\ style files. The original file had an option of typesetting -%D on a grid. That was too clever for me to understand, and hence to -%D modify. Since \TUGBOAT\ usually does not typeset on a grid, I redid the -%D style, borrowing parts from \filename{s-tug-02} and redoing some -%D parts from scratch. +%D \filename {s-tug-02} was originally written by Hans Hagen and Steve Grathwohl, +%D and used grid typesetting. \filename {t-tugboat} was a major revision by Aditya +%D Mahajan, no grid. \filename {s-tugboat.mkiv} was another major revision by Taco +%D Hoekwater and Alan Braslau, now with pagecolumns. The latest version has been +%D adapted by Karl Berry. -%D \section Variables %<<<1 +%D HH: most articles are not that demanding, otherwise I'd probably redefine some +%D of the code below. So, for now things gets adapted when needed. + +%D \section{Variables} %D %D We store the information about the article in variables. \setvariables [tugboat] [type=article, - columns=no, - grid=yes] + columns=yes, + grid=no] \setvariables - [tugboat] + [tugboat] % overridden from ../tugboat.dates if present. [year=1900, volume=0, number=0, @@ -38,45 +39,44 @@ subtitle=, keywords=, author=T. Boat, - address=Pragmatically Advanced tugboats \\ - 314 Pi Ave. \\ - 8061GH Hasselt NL, - email={tugboat@tug.org}] + address=Pragmatically Advanced tugboats\\314 Pi Ave.\\8061GH Hasselt NL, + email={pragboat@tug.org}] -%D \section Font Setup %<<<1 +%D \section{Font Setup} %D -%D \TUGBOAT\ uses slightly different interline space than the default. -%D So we change the interline space. +%D \TUGBOAT\ uses standard CM interline spacing. -\definebodyfontenvironment [8pt] [interlinespace=9.5pt, big=9pt, small=7pt] -\definebodyfontenvironment [9pt] [interlinespace=11pt, big=10pt, small=8pt] -\definebodyfontenvironment [10pt] [interlinespace=12pt, big=12pt, small=9pt] -\definebodyfontenvironment [12pt] [interlinespace=14pt,big=14.4pt,small=10pt] -\definebodyfontenvironment [14.4pt] [interlinespace=18pt,big=14.4pt,small=12pt] -\definebodyfontenvironment [9.8pt] % Because 9.8pt is used in typing environments +\definebodyfontenvironment [8pt] [interlinespace=9.5pt, big=9pt, small=7pt] +\definebodyfontenvironment [9pt] [interlinespace=11pt, big=10pt, small=8pt] +\definebodyfontenvironment [10pt] [interlinespace=12pt, big=12pt, small=9pt] +\definebodyfontenvironment [12pt] [interlinespace=14pt, big=14.4pt,small=10pt] +\definebodyfontenvironment [14.4pt] [interlinespace=18pt, big=14.4pt,small=12pt] -%D \TUGBOAT\ uses Computer Modern fonts, and \CONTEXT\ uses Latin Modern -%D by default. So, we just specify the font size. +%D \TUGBOAT\ uses Computer Modern fonts, and \CONTEXT\ uses Latin Modern by default, +%D which is fine. So, we just specify the font size. \setupbodyfont [10pt] -% Italic rather than slanted for emphasis. -\setupbodyfontenvironment[default][em=italic] +%D \TUGBOAT\ uses italic rather than slanted for emphasis. + +\setupbodyfontenvironment + [default] + [em=italic] + +%D Break after these chars in urls, not before. -% Break after these chars in urls, not before. \sethyphenatedurlafter / \sethyphenatedurlafter . \sethyphenatedurlafter _ -%D \section Layout Setup %<<<1 +%D \section{Layout Setup} %D -%D The original layout used in the \LATEX\ style for \TUGBOAT\ is a bit -%D ambiguous. It uses low|-|level \TEX\ syntax, rather than changing the -%D layout in a human understandable way (for example, by using the -%D \mono{geometry} package. I have tried to translate it to \CONTEXT\ as -%D far as I understand. +%D The original layout used in the \LATEX\ style for \TUGBOAT\ uses low|-|level +%D \TEX\ syntax. -\setuppapersize[letter][letter] +\setuppapersize + [letter] + [letter] \setuplayout [topspace=3.8pc,% was 3.5pc @@ -95,32 +95,43 @@ margindistance=1pc, ] -\setupcolumns[distance=1.5pc] +%D With pagecolumns, seemingly can't get single-column floats. (HH: not sure +%D what is meant but probably that it can't be done in the running text.) + +\enablemode [pagecolumns] + +%D KB: With columnsets, get unwanted gridsetting and blank last pages. HH: can +%D be solved but columnsets normally get set up beforehand (figures and such). + +% \enablemode [columnsets] +% \startmode [columnsets] +% \usemodule [newcolumnsets] +% \stopmode \setuppagenumbering [location=, alternative=doublesided] -%D In \TUGBOAT\ different articles are glued together to form the final -%D journal, so we do not want each article to occupy even number of -%D pages. +%D In \TUGBOAT\ different articles are glued together to form the final journal, so +%D we do not want each article to occupy an even number of pages. (Except this seems +%D to have no effect with pagecolumns.) HH: page columns are actually just single +%D columns but combined, so it supports side floats and such. \installpagebreakhandler {last} {} -%D \section Indentation %<<<1 +%D \section{Indentation} %D -%D We use automatic indentation control, that is: no -%D indentation after titles and skips. +%D We use automatic indentation control, that is: no indentation after titles and +%D skips. \setupindenting[20pt,yes] -%D We do not want indentation after lists. +%D We do not usually want indentation after lists. \setupenumerations [indentnext=no] - \setupdescriptions [indentnext=no] -%D \section Itemize +%D \section{Itemize} %D %D And these. We typeset itemizations ragged right. @@ -144,70 +155,112 @@ [1] [packed] -%D \section Section Headings %<<<1 +%D \section{Section Headings} %D -%D We follow the \TUGBOAT\ style for sections. I do not know if -%D \type{align=right} also disables hyphenation. Lets wait and see on -%D this. Rest all is straight forward. It took me a while to realize -%D that in \LATEX\ \type{\@startsection} the absolute value of before -%D skip (fourth argument) is important and not the sign. +%D We follow the \TUGBOAT\ style for sections. I do not know if \type {align=right} +%D also disables hyphenation (HH: no that is independent). Let's wait and see on +%D this. The rest is straightforward. It took me a while to realize that in \LATEX\ +%D \type {\@startsection} the absolute value of before skip (fourth argument) is +%D important and not the sign. \setuphead - [section,subsection, subsubsection, - subject, subsubject, subsubsubject] + [section,subsection,subsubsection, + subject,subsubject,subsubsubject] [style=bold, align=right, before={\blank[8pt]}, after={\blank[4pt]}] -%D \section Spacing <<<1 +%D \section{Spacing} %D -%D We define a logical skip. This is equal to the \tex{topsep} in latex, -%D and most environments should have this skip. +%D We define a logical skip. This is equal to the \type {\topsep} in \LATEX, and +%D most environments should have this skip. + +\defineblank + [tugblank] + [3pt] -\defineblank[tugblank][3pt] -\setupblank[3pt] +%D HH: In fact, \type \setupblank[tugblank] would deal with most of the blanks +%D anyway, but let's not change this now. +\setupblank + [3pt] -\setupitemize[1][before={\blank[tugblank]},after={\blank[tugblank]}, - inbetween={\blank[tugblank]}] +\setupitemize + [1] + [before={\blank[tugblank]}, + after={\blank[tugblank]}, + inbetween={\blank[tugblank]}] -\setuplines[before={\blank[tugblank]},after={\blank[tugblank]}, - inbetween={\blank[tugblank]}] +\setuplines + [before={\blank[tugblank]}, + after={\blank[tugblank]}, + inbetween={\blank[tugblank]}] %D \section Typing %<<<1 %D -%D \TUGBOAT\ uses a smaller font size for verbatim typesetting. +%D \TUGBOAT\ uses a smaller font size for verbatim typesetting. Not sure why +%D \type {nowhite} is used here. \setuptyping [option=none, - % mkiv arbitrarily scales lm somehow randomly; 10pt gets 9pt back, - % we make it a little smaller. - bodyfont=9.8pt, - before={\blank[nowhite]}, % AM: If this is too small, use \blank[nowhite,tugblank]} + bodyfont=9pt, + before={\blank[nowhite,tugblank]}, after={\blank[back,nowhite,tugblank]}] -%D \section Footnotes %<<<1 +%D Do not colorize code. + +\setuptyping[LUA][option=] +\setuptyping[TEX][option=] +\setuptyping[MP] [option=] + +%D Tiny line numbers in numbered code (not text size) + +\setuplinenumbering[style=\tfxx] % \infofont would be really tiny + +%D \section{Figure captions} +%D +%D \TUGBOAT\ uses a smaller font size for captions, too; \type {\small} gives 8pt, +%D and we want 9pt, so we have to manually fix the baselines. + +\setupcaptions + [headstyle=\AcroFontBold, + style={\AcroFont\advance\baselineskip by -1pt}, + align=right] + +%D We like a colon after the caption label. + +\let\floatcaptionsuffix=: + +% Prefer floats in text. + +\setupfloat[figure][align=middle,default=here] +\setupfloat[table] [align=middle,default=here] + +%D \section{Footnotes} %D %D Not entirely a la \TUGBOAT: +% rule={\hrule width 5pc height .4pt depth 0pt\relax \kern \strutdepth}, + \setupfootnotes - [bodyfont=9pt, - location=columns%, -% rule={\hrule width 5pc height .4pt depth 0pt\relax \kern \strutdepth} -] + [bodyfont=9pt] + +\doif {\getvariable{tugboat}{columns}} {yes} { + \setupfootnotes + [location=columns] +} -\setupnotation[footnote] +\setupnotation + [footnote] [location=joinedup, width=fit, headstyle=normal, distance=.5em] -%>>> -%D \section List %<<< +%D \section{List} %D -%D We define a standard description and enumeration -%D environment. +%D We define a standard description and enumeration environment. \definedescription [description] @@ -223,31 +276,26 @@ before={\blank[tugblank]}, after={\blank[tugblank]}] - -% >>> -%D \section References %<<< +%D \section{References} %D -%D The bib does not handle urls nicely. So we provide a stop gap solution. - -\definereferenceformat[cite][left={[},right={]}] -\defineitemgroup [bibliography] [levels=1] -\setupitemgroup [bibliography] - [symbol=n, - left={[}, - right={]}, - width=1.5em, - stopper=, - itemalign=flushright, - inbetween={\blank[small]}] +%D More or less like plain BibTeX + ltugboat. + +\setupbtx + [default:list:numbering] + [left={[}, + right={]}] + +\setupbtxlist + [default] + [width=2em, + style={\frenchspacing}, + align={raggedright,verystrict}] %D Instead of color, we use weighted gray scales: + \setupcolors [conversion=always] -%D English it is. -\mainlanguage - [en] - %D We define some logical skips \defineblank [tugbefore] [big] @@ -261,26 +309,28 @@ \startsetups tugboat:abstract:setup - \setuptolerance - [horizontal, tolerant] + % \setuptolerance + % [horizontal, tolerant] + + \setupnarrower + [before={\blank[.5\baselineskip]}, + % after={\blank[10pt plus4pt minus4pt]}, + after={\blank[10pt]}, + middle=4.875pc] - \setupnarrower - [before={\blank[.5\baselineskip]}, -% after={\blank[10pt plus4pt minus4pt]}, - after={\blank[10pt]}, - middle=4.875pc] + \setupindenting + [20pt,yes] \stopsetups -%D Headers and footers are different for normal issues -%D and proceedings. +%D Headers and footers. \startsetups tugboat:banner:text:article - {\sl TUGboat},\space - Volume \getvariable{tugboat}{volume}\space - (\getvariable{tugboat}{year}),\space - No.\space\getvariable{tugboat}{number} + {\sl TUGboat},\space + Volume \getvariable{tugboat}{volume}\space + (\getvariable{tugboat}{year}),\space + No.\space\getvariable{tugboat}{number} \stopsetups @@ -288,9 +338,9 @@ \startsetups tugboat:banner:text:proceedings - \setups{tugboat:banner:text:article} - \thinspace---\thinspace - \postissno + \setups{tugboat:banner:text:article} + \thinspace---\thinspace + \postissno \stopsetups @@ -300,26 +350,15 @@ [\setups{tugboat:banner:text:article}] [\pagenumber] -% no footer in regular articles -% \setupfootertexts -% [][\getvariable{tugboat}{author}] -% [\getvariable{tugboat}{title}][] - -\stopsetups - -\startsetups tugboat:banner:setup:proceedings - - \setupheadertexts - [][\getvariable{tugboat}{title}] - [\getvariable{tugboat}{author}][] + % no footer in regular articles \setupfootertexts - [\setups{tugboat:banner:text:proceedings}] - [\pagenumber] + [][\getvariable{tugboat}{author}] + [\getvariable{tugboat}{title}][] \stopsetups -%D article is default so, +%D article is default. \setups{tugboat:banner:setup:article} @@ -330,333 +369,334 @@ \startsetups tugboat:grid:setup:settings:yes - \setupblank - [line] + \setupblank + [line] - \defineblank [tugbefore] [halfline] - \defineblank [tuginbetween] [halfline] + \defineblank [tugbefore] [halfline] + \defineblank [tuginbetween] [halfline] \stopsetups -\startsetups tugboat:grid:setup:no - +% \startsetups tugboat:grid:setup:no +% % \setupblank % [halfline] - +% % \defineblank [medium] [halfline] % \defineblank [tugbefore] [halfline] % \defineblank [tuginbetween] [halfline] - -\stopsetups +% +% \stopsetups \startsetups tugboat:introduction:article -% \blank[halfline] - - \start - \def\\{\unskip\space\&\space\ignorespaces} - \hbox{\indent\getvariable{tugboat}{author}} - \par - \stop - - -\stopsetups - -\startsetups tugboat:introduction:proceedings - - \blank[20pt] - - \start - \switchtobodyfont[12pt] - \def\\{\unskip\space\&\space\ignorespaces} - \getvariable{tugboat}{author} - \par - \stop + % \blank[halfline] \start - \switchtobodyfont[9pt] - \def\\{\unskip,\space\ignorespaces} - \getvariable{tugboat}{address} - \par - {\tt\getvariable{tugboat}{email}} - \par - \stop - + \def\\{\unskip\space\&\space\ignorespaces} + \hbox{\indent\getvariable{tugboat}{author}} + \par + \stop \stopsetups \startsetups tugboat:article:start - \starttext - - \setups{tugboat:columns:\getvariable{tugboat}{columns}} - \setups{tugboat:banner:setup:\getvariable{tugboat}{type}} - - \doif{\getvariable{tugboat}{columns}}{yes}{\startcolumns} + \starttext + + \setups{tugboat:columns:\getvariable{tugboat}{columns}} + \setups{tugboat:banner:setup:\getvariable{tugboat}{type}} + + \doif {\getvariable{tugboat}{columns}} {yes} { + \doifmodeelse {columnset} { + \startcolumnset + } { + \doifmodeelse {pagecolumns} { + \startpagecolumns + } { + \startcolumns + } + } + } + + \setcounter + [userpage] + [\getvariable{tugboat}{page}] + + \setups{tugboat:grid:setup:\getvariable{tugboat}{grid}} + + \snaptogrid \vbox \bgroup + + \forgetall + \hrule height .6pt + \blank[halfline] + \start + \let\\=\par + {\bf\getvariable{tugboat}{title}} + \par + \blank[halfline] + \hskip20pt\getvariable{tugboat}{author} + \stop + + \blank[line] + + \doiftext {\getbuffer[abstract]} { + \let\\=\endgraf + \setups{tugboat:abstract:setup} + \subject{Abstract} + \getbuffer[abstract] + } + + \egroup +\stopsetups - % AM: Why set these again? - % \setupheadertexts - % [\setups{tugboat:banner:text:article}] - % [pagenumber] - - % \setuppagenumber - % [number=\getvariable{tugboat}{page}] - \setcounter[userpage][\getvariable{tugboat}{page}] - - % \setuplayout - % [grid=\getvariable{tugboat}{grid}] - - % % instead of \startmode [*grid] ... \stopmode - % % instead of \startnotmode[*grid] ... \stopnotmode - - \setups{tugboat:grid:setup:\getvariable{tugboat}{grid}} - - \snaptogrid \vbox \bgroup - - \forgetall -\hrule height .6pt -\blank[halfline] - \start - \let\\=\par - {\bf\getvariable{tugboat}{title}} - \par - \blank[halfline] - \hskip20pt\getvariable{tugboat}{author} - \stop - -% \setups{tugboat:introduction:\getvariable{tugboat}{type}} +\startsetups tugboat:affiliation:article \blank[line] + \begingroup + \leftskip=5.7pc + \noindent + \llap{\sym{\diamond}\enspace} + \getvariable{tugboat}{author} + \\ + \getvariable{tugboat}{address} + \\ + {\tt\getvariable{tugboat}{email}} + \par + \endgroup - \doiftext{\getbuffer[abstract]} - {\let\\=\endgraf - \setups{tugboat:abstract:setup} - \subject{Abstract} - \getbuffer[abstract]} - \egroup \stopsetups -\startsetups tugboat:proceedings:start - - \starttext - - \setups{tugboat:columns:\getvariable{tugboat}{columns}} - \setups{tugboat:banner:setup:\getvariable{tugboat}{type}} - - \setupheader - [state=empty] - - \setuppagenumber - [number=\getvariable{tugboat}{page}] - - \setuplayout - [grid=\getvariable{tugboat}{grid}] - - % instead of \startmode [*grid] ... \stopmode - % instead of \startnotmode[*grid] ... \stopnotmode - \doifmodeelse{*grid}{\setups{tugboat:grid:setup:yes}}{\setups{tugboat:grid:setup:no}} -% \setups{tugboat:grid:setup:\systemmodevalue{grid}{yes}{no}} - - \snaptogrid \vbox \bgroup - - \forgetall - - \start - \switchtobodyfont[14.4pt] - \let\\=\par - \getvariable{tugboat}{title} - \par - \stop - - \setups{tugboat:introduction:\getvariable{tugboat}{type}} - - \blank[9pt]% plus3pt minus3pt] - - \let\\=\par - \setups{tugboat:abstract:setup} - \midaligned{\bf Abstract} +\startsetups tugboat:article:stop - \startnarrower[middle] - \getbuffer[abstract] - \stopnarrower + \setups{tugboat:affiliation:\getvariable{tugboat}{type}} - % \blank[10pt plus4pt minus4pt] + % article joining in the issue. - \egroup + \ifx\writelastpage\undefined \else + \begingroup + \count0=\count1 % because context uses \count0 + \writelastpage{+1}% + \endgroup + \fi - \doif{\getvariable{tugboat}{columns}}{yes}{\startcolumns} + \stoptext \stopsetups -\def\signaturewidth{13pc} +%D Normal word spacing, please. -\definefontsynonym[LMSY][lmsy10] -\def\mydiamond{\getglyph{LMSY}{\char5}} +\setuptolerance + [strict] -\startsetups tugboat:affiliation:article +%D Hz is fine but hanging can hang quotes out too far. - \blank[line] +\setupalign[hz] - \snaptogrid \vbox \bgroup +%D Let's see boxes overfull by >.5pt - \forgetall +\overfullrule = 2pt % yes, I want to see +\hfuzz = .5pt - \leftskip=\textwidth \advance\leftskip by -\signaturewidth +%D One can use the following setups (in the article) to collect settings specific to +%D normal and|/|or multi column typesetting. - \let\\=\par - \leavevmode\llap{\mydiamond\enspace}\getvariable{tugboat}{author} - \par - \getvariable{tugboat}{address} - \par - {\tt\getvariable{tugboat}{email}} +\startsetups tugboat:columns:yes - \egroup + \doifmodeelse {columnset} { + \setuplayout + [grid=no] + \setupcolumnset + [distance=1.5pc] + } { + \doifmodeelse {pagecolumns} { + \setuplayout + [grid=no] + \setuppagecolumns + [distance=1.5pc] + } { + \setupcolumns + [distance=1.5pc] + } + } \stopsetups -\startsetups tugboat:affiliation:proceedings - - % nothing fancy at the end - -\stopsetups +% \startsetups tugboat:columns:no +% +% \stopsetups -\startsetups tugboat:article:stop +%D Logos, abbreviations: lots could be better here. - \setups{tugboat:affiliation:\getvariable{tugboat}{type}} +\font\mflogo=logo10 % hm, better use \definefont - \doif{\getvariable{tugboat}{columns}}{yes}{\stopcolumns} +%D HH: I'm pretty sure that this doesn't really work as it probably assumes some +%D specific math family setup and that's not how \MKIV\ works. The TB definition +%D doesn't scale with font changes and also doesn't adapt itself to style changes. - \page +\newcount\TestCount +\newbox \TestBox + +\def\La + {\dontleavehmode + \begingroup + \TestCount=\the\fam % no meaningful fams in text mode in ConTeXt + \setbox\TestBox=\hbox{$\fam\TestCount\scriptstyle A$}% + L\kern-.5\wd\TestBox\raise.42ex\box\TestBox + \endgroup} + +%D Probably this will do a better job: + +% \def\La +% {\dontleavehmode +% \begingroup +% \setbox\scratchbox\hbox{\tx A}% % or \txx +% L\kern-.5\wd\scratchbox\raise.42ex\scratchbox +% \endgroup} + +\def\ALEPH {Aleph} +\def\API {\acro{API}} +\def\CCODE {C} +\def\CD {\acro{CD}} +\def\CMYK {\acro{CMYK}} +\def\CONTEXT {C\kern-.0333emon\-\kern-.0667em\TeX\kern-.0333emt} +\def\Cplusplus {C\plusplus} +\def\CPU {\acro{CPU}} +\def\DVI {\acro{DVI}} +\def\DVIPDFMX {dvipdfmx} +\def\DVIPS {dvips} +\def\ETEX {$\varepsilon$-\kern-.125em\TeX} +\def\FTP {\acro{FTP}} +\def\HTTP {\acro{HTTP}} +\def\IO {\acro{I/O}} +\def\ISO {\acro{ISO}} +\def\KPSE {\acro{KPSE}} +\def\KPSEWHICH {kpsewhich} +\def\LATEX {\La\kern-.15em\TeX} +\def\LATEXE {\LaTeX{}\kern.05em2$_{\textstyle\varepsilon}$} +\def\LINUX {Linux} +\def\LPEG {Lpeg} +\def\LUA {Lua} +\def\LUAJIT {Lua\acro{JIT}} +\def\LUATEX {Lua\-\TeX} +\def\LUATOOLS {lua\-tools} +\def\MATHML {Math\acro{ML}} +\def\METAFUN {Metafun} +\def\METAPOST {MetaPost} +\def\METATEX {Meta\TeX{}} +\def\MF {{\mflogo META\-FONT}} +\def\MKII {Mk\acro{II}} +\def\MKIV {Mk\acro{IV}} +\def\MPLIB {\acro{MP}lib} +\def\MPTOPDF {mp\-to\-pdf} +\def\MSWINDOWS {Windows} +\def\MTXTOOLS {mtx\-tools} +\def\NFSS {\acro{NFSS}} +\def\OPENMATH {Open\-Math} +\def\OPENTYPE {Open\-Type} +\def\PASCAL {Pascal} +\def\PDF {\acro{PDF}} +\def\PDFTEX {pdf\/\-\TeX} +\def\plusplus {\lower.45ex\hbox{$^{++}$}} +\def\POSIX {\acro{POSIX}} +\def\POSTSCRIPT{Post\-Script} +\def\PRAGMA {Pragma \acro{ADE}} +\def\RGB {\acro{RGB}} +\def\RUBY {Ruby} +\def\SCITE {Sci\acro{TE}} +\def\TDS {\acro{TDS}} +\def\TEX {\TeX} +\def\TEXBOOK {{\sl The \TeX book}} +\def\TEXEXEC {\TeX exec} +\def\TFM {\acro{TFM}} +\def\TRUETYPE {True\-Type} +\def\TYPEONE {Type~1} +\def\UTF {\acro{UTF}} +\def\WEBC {Web2C} +\def\XETEX {X\lower.5ex\hbox{\kern-.15em\mirror{E}}\kern-.1667em\TeX} +\def\XML {\acro{XML}} +\def\XPATH {\acro{XP}ath} +\def\XSLT {\acro{XSLT}} +\def\XSLTPROC {\acro{XSLTPROC}} +\def\ZIP {zip} + +\def\Dash {\unskip\nobreak\thinspace---\thinspace\ignorespaces} +\def\slash {/\penalty0 \hskip0pt \relax} + +% \acro for one point size smaller. Don't know how to do this for real. + +\definefont[AcroFont] [Serif*default sa .91] +\definefont[AcroFontBold] [SerifBold*default sa .91] +\definefont[AcroFontSmall][Serif*default sa .80] + +\unexpanded\def\acro #1{{\AcroFont #1}} % could be a style definition +\unexpanded\def\acrobf #1{{\AcroFontBold #1}} % could be a style definition +\unexpanded\def\acrosmall#1{{\AcroFontSmall #1}} % could be a style definition + +%D This is English; disallow x- or -xx breaks. (These are already the defaults +%D but let's keep this anyway.) + +\setuplanguage + [en] + [lefthyphenmin=2, + righthyphenmin=3] - \stoptext +% \lefthyphenmin = 2 +% \righthyphenmin = 3 -\stopsetups +\mainlanguage + [en] -%D Normal word spacing, please. +\hyphenation { + Post-Script + data-base + data-bases +} -\setuptolerance - [strict] +%D A hack to read \type {tugboat.dates} settings. -%D One can use the following setups (in the article) to -%D collect settings specific to normal and/or multi -%D column typesetting. +\doifelsefileexists {../tugboat.dates} { -\startsetups tugboat:columns:yes + \newif \ifPrelimDraft + \newcount\issueseqno -\stopsetups + \def\vol #1, #2.{\gdef\tubvol {#1}% + \gdef\tubnum {#2}} + \def\issyear #1.{\gdef\tubyear{#1}} -\startsetups tugboat:columns:no + \input ../tugboat.dates -\stopsetups + \setevariables + [tugboat] + [year=\tubyear, + volume=\tubvol, + number=\tubnum, + page=\the\count0] -% >>> Logos, abbreviations: TODO: Clean up <<< -\font\mflogo = logo10 -\def\MF{{\mflogo META\-FONT}} - -\def\ALEPH{Aleph} -\def\API{\acro{API}} -\def\CCODE{C} -\def\CD{\acro{CD}} -\def\CMYK{\acro{CMYK}} -\def\CONTEXT{C\kern-.0333emon\-\kern-.0667em\TeX\kern-.0333emt} -\def\CPU{\acro{CPU}} -\def\DVI{\acro{DVI}} -\def\DVIPDFMX{dvipdfmx} -\def\DVIPS{dvips} -\def\ETEX{$\varepsilon$-\kern-.125em\TeX} -\def\FTP{\acro{FTP}} -\def\HTTP{\acro{HTTP}} -\def\IO{\acro{I/O}} -\def\ISO{\acro{ISO}} -\def\KPSEWHICH{kpsewhich} -\def\KPSE{\acro{KPSE}} -\newcount\TestCount -\newbox\TestBox -\def\La{\TestCount=\the\fam \leavevmode L% - \setbox\TestBox=\hbox{$\fam\TestCount\scriptstyle A$}% - \kern-.5\wd\TestBox\raise.42ex\box\TestBox} -\def\LATEX{\La\kern-.15em\TeX} -\def\LATEXE{\LaTeX{}\kern.05em2$_{\textstyle\varepsilon}$} -\def\LINUX{Linux} -\def\LPEG{Lpeg} -\def\LUAJIT{Lua\acro{JIT}} -\def\LUATEX{Lua\-\TeX} -\def\LUATOOLS{lua\-tools} -\def\LUA{Lua} -\def\MATHML{Math\acro{ML}} -\def\METAFUN{Metafun} -\def\METAPOST{MetaPost} -\def\METATEX{Meta\TeX{}} -\def\MKII{Mk\acro{II}} -\def\MKIV{Mk\acro{IV}} -\def\MPLIB{\acro{MP}lib} -\def\MPTOPDF{mp\-to\-pdf} -\def\MSWINDOWS{Windows} -\def\MTXTOOLS{mtx\-tools} -\def\NFSS{\acro{NFSS}} -\def\OPENMATH{Open\-Math} -\def\OPENTYPE{Open\-Type} -\def\PASCAL{Pascal} -\def\PDFTEX{pdf\/\-\TeX} -\def\PDF{\acro{PDF}} -\def\POSIX{\acro{POSIX}} -\def\PRAGMA{Pragma \acro{ADE}} -\def\POSTSCRIPT{Post\-Script} -\def\RGB{\acro{RGB}} -\def\RUBY{Ruby} -\def\SCITE{Sci\acro{TE}} -\def\TDS{\acro{TDS}} -\def\TEXBOOK{{\sl The \TeX book}} -\def\TEXEXEC{\TeX exec} -\def\TEX{\TeX} -\def\TFM{\acro{TFM}} -\def\TRUETYPE{True\-Type} -\def\TYPEONE{Type~1} -\def\UTF{\acro{UTF}} -\def\WEBC{Web2C} -\def\XETEX{X\lower.5ex\hbox{\kern-.15em\mirror{E}}\kern-.1667em\TeX} -\def\XML{\acro{XML}} -\def\XPATH{\acro{XP}ath} -\def\XSLT{\acro{XSLT}} -\def\XSLTPROC{\acro{XSLTPROC}} -\def\ZIP{zip} - -\def\Dash{\unskip\thinspace---\thinspace\ignorespaces} -\def\slash{/\penalty0 \hskip0pt \relax} - -\definefont[AcroFont][Serif sa .91] -\def\acro#1{{\AcroFont #1}} - -\lefthyphenmin=2 \righthyphenmin=3 % disallow x- or -xx breaks - -\hyphenation{Post-Script data-base data-bases} - -% hack to read tugboat.dates settings. -\def\vol#1, #2.{\def\tubvol{#1}\def\tubnum{#2}} -\def\issyear#1.{\def\tubyear{#1}} -\newcount\issueseqno +} { + % nothing +} -% >>> -%D Good bye. <<< +%D Good bye. -\doifnotmode{demo}{\endinput} +\continueifinputfile{s-tugboat.mkiv} -\showgrid +% \showgrid \starttext \StartArticle \StartAbstract - \input bryson + \samplefile{bryson} \StopAbstract \dorecurse{30}{\input ward \endgraf} \page \startitemize -\item \input ward -\item \input ward + \startitem \samplefile{ward} \stopitem + \startitem \samplefile{ward} \stopitem \stopitemize \StopArticle @@ -666,15 +706,15 @@ \StartArticle \StartAbstract - \input bryson + \samplefile{bryson} \StopAbstract -\dorecurse{30}{\input ward \endgraf} \page +\dorecurse {30} { + \samplefile{ward}\endgraf +} \page \StopArticle \stoptext % >>> - -% vim: foldmethod=marker foldmarker=<<<,>>> diff --git a/tex/generic/context/luatex/luatex-basics-nod.lua b/tex/generic/context/luatex/luatex-basics-nod.lua index f2f884abf..172fcc7f7 100644 --- a/tex/generic/context/luatex/luatex-basics-nod.lua +++ b/tex/generic/context/luatex/luatex-basics-nod.lua @@ -279,6 +279,3 @@ if not nuts.setreplace then end end - - -end diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 47bfe470f..b538d1f3b 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date : 09/10/19 20:03:16 +-- merge date : 09/27/19 17:59:57 do -- begin closure to overcome local limits and interference @@ -5024,7 +5024,6 @@ if not nuts.setreplace then setfield(n,"replace",h) end end -end end -- closure @@ -9016,7 +9015,8 @@ function constructors.scale(tfmdata,specification) local units=parameters.units or 1000 targetproperties.language=properties.language or "dflt" targetproperties.script=properties.script or "dflt" - targetproperties.mode=properties.mode or "base" + targetproperties.mode=properties.mode or "base" + targetproperties.method=properties.method local askedscaledpoints=scaledpoints local scaledpoints,delta=constructors.calculatescale(tfmdata,scaledpoints,nil,specification) local hdelta=delta @@ -23081,6 +23081,7 @@ otf.cache=containers.define("fonts","otl",otf.version,true) otf.svgcache=containers.define("fonts","svg",otf.version,true) otf.pngcache=containers.define("fonts","png",otf.version,true) otf.pdfcache=containers.define("fonts","pdf",otf.version,true) +otf.mpscache=containers.define("fonts","mps",otf.version,true) otf.svgenabled=false otf.pngenabled=false local otfreaders=otf.readers @@ -32098,6 +32099,9 @@ if not modules then modules={} end modules ['font-ocl']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } +if CONTEXTLMTXMODE and CONTEXTLMTXMODE>0 then + return +end local tostring,tonumber,next=tostring,tonumber,next local round,max=math.round,math.round local gsub,find=string.gsub,string.find @@ -32105,7 +32109,6 @@ local sortedkeys,sortedhash,concat=table.sortedkeys,table.sortedhash,table.conca local setmetatableindex=table.setmetatableindex local formatters=string.formatters local tounicode=fonts.mappings.tounicode -local bpfactor=number.dimenfactors.bp local helpers=fonts.helpers local charcommand=helpers.commands.char local rightcommand=helpers.commands.right @@ -32292,14 +32295,20 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) local data=nil local dx=nil local dy=nil + local scale=1 if typ=="table" then data=pdf.data dx=pdf.x or pdf.dx or 0 dy=pdf.y or pdf.dy or 0 + scale=pdf.scale or 1 elseif typ=="string" then data=pdf dx=0 dy=0 + elseif typ=="number" then + data=pdf + dx=0 + dy=0 end if data then local bt=unicode and getactualtext(unicode) @@ -32310,7 +32319,7 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) not unicode and actualb or { "pdf","page",(getactualtext(unicode)) }, downcommand [dp+dy*hfactor], rightcommand[ dx*hfactor], - vfimage(wd,ht,dp,data,name), + vfimage(scale*wd,ht,dp,data,pdfshapes.filename or ""), actuale, } character[kind]=true @@ -32349,7 +32358,7 @@ do } if not runner then runner=function() - return io.open("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w") + return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w") end end function otfsvg.topdf(svgshapes,tfmdata) @@ -32364,6 +32373,7 @@ do local f_convert=formatters["%s --export-pdf=%s\n"] local filterglyph=otfsvg.filterglyph local nofdone=0 + local processed={} report_svg("processing %i svg containers",nofshapes) statistics.starttiming() for i=1,nofshapes do @@ -32375,26 +32385,51 @@ do local pdffile=f_pdffile(index) savedata(svgfile,data) inkscape:write(f_convert(svgfile,pdffile)) - pdfshapes[index]=true + processed[index]=true nofdone=nofdone+1 - if nofdone%100==0 then - report_svg("%i shapes processed",nofdone) + if nofdone%25==0 then + report_svg("%i shapes submitted",nofdone) end end end end + if nofdone%25~=0 then + report_svg("%i shapes submitted",nofdone) + end + report_svg("processing can be going on for a while") inkscape:write("quit\n") inkscape:close() report_svg("processing %i pdf results",nofshapes) - for index in next,pdfshapes do + for index in next,processed do local svgfile=f_svgfile(index) local pdffile=f_pdffile(index) - pdfshapes[index]={ - data=loaddata(pdffile), - } + local pdfdata=loaddata(pdffile) + if pdfdata and pdfdata~="" then + pdfshapes[index]={ + data=pdfdata, + } + end remove(svgfile) remove(pdffile) end +local characters=tfmdata.characters +for k,v in next,characters do + local d=descriptions[k] + local i=d.index + if i then + local p=pdfshapes[i] + if p then + local w=d.width + local l=d.boundingbox[1] + local r=d.boundingbox[3] + p.scale=(r-l)/w + p.x=l + end + end +end + if not next(pdfshapes) then + report_svg("there are no converted shapes, fix your setup") + end statistics.stoptiming() if statistics.elapsedseconds then report_svg("svg conversion time %s",statistics.elapsedseconds() or "-") @@ -32413,10 +32448,10 @@ local function initializesvg(tfmdata,kind,value) end local pdffile=containers.read(otf.pdfcache,hash) local pdfshapes=pdffile and pdffile.pdfshapes - if not pdfshapes or pdffile.timestamp~=timestamp then + if not pdfshapes or pdffile.timestamp~=timestamp or not next(pdfshapes) then local svgfile=containers.read(otf.svgcache,hash) local svgshapes=svgfile and svgfile.svgshapes - pdfshapes=svgshapes and otfsvg.topdf(svgshapes,tfmdata) or {} + pdfshapes=svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or {} containers.write(otf.pdfcache,hash,{ pdfshapes=pdfshapes, timestamp=timestamp, -- cgit v1.2.3