diff options
Diffstat (limited to 'tex')
44 files changed, 4049 insertions, 848 deletions
diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index 79a8f46d6..01555af45 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{2020.12.10 22:23} +\newcontextversion{2020.12.15 10:10} %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 80a42b727..8b46e9081 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{2020.12.10 22:23} +\edef\contextversion{2020.12.15 10:10} %D For those who want to use this: diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index 5b7f4831b..beded1600 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{2020.12.10 22:23} +\newcontextversion{2020.12.15 10:10} %D This file is loaded at runtime, thereby providing an excellent place for hacks, %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index 8b645e33a..4aaaf492d 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{2020.12.10 22:23} +\edef\contextversion{2020.12.15 10:10} %D Kind of special: diff --git a/tex/context/base/mkiv/data-tex.lua b/tex/context/base/mkiv/data-tex.lua index 2e8642cdd..55383c1fe 100644 --- a/tex/context/base/mkiv/data-tex.lua +++ b/tex/context/base/mkiv/data-tex.lua @@ -149,6 +149,9 @@ local function textopener(tag,filename,filehandle,coding) filename = filename, noflines = noflines, -- currentline = 0, + endoffile = function() + return not lines or currentline >= noflines + end, close = function() local usedname = popinputname() -- should match filename if trace_locating then diff --git a/tex/context/base/mkiv/font-ctx.lua b/tex/context/base/mkiv/font-ctx.lua index 5c93f302f..eead8abff 100644 --- a/tex/context/base/mkiv/font-ctx.lua +++ b/tex/context/base/mkiv/font-ctx.lua @@ -1840,10 +1840,6 @@ function mappings.reset() lpdf.setmapfile("") -- tricky ... backend related end -function mappings.getentry(...) - return lpdf.getmapentry(...) -- tricky ... backend related -end - implement { name = "loadmapfile", actions = mappings.loadfile, diff --git a/tex/context/base/mkiv/mult-fun.lua b/tex/context/base/mkiv/mult-fun.lua index 095900861..41ca68613 100644 --- a/tex/context/base/mkiv/mult-fun.lua +++ b/tex/context/base/mkiv/mult-fun.lua @@ -2,7 +2,7 @@ return { internals = { -- "nocolormodel", "greycolormodel", "graycolormodel", "rgbcolormodel", "cmykcolormodel", - "shadefactor", + "shadefactor", "shadeoffset", "textextoffset", "textextanchor", "normaltransparent", "multiplytransparent", "screentransparent", "overlaytransparent", "softlighttransparent", "hardlighttransparent", "colordodgetransparent", "colorburntransparent", @@ -17,7 +17,7 @@ return { "drawoptionsfactor", "dq", "sq", "crossingscale", "crossingoption", - "contextlmtxmode", + "contextlmtxmode", "metafunversion", -- -- for the moment we put these here as they need to stand out -- @@ -74,6 +74,7 @@ return { "withshadedomain", "withshademethod", "withshadefactor", "withshadevector", "withshadecenter", "withshadedirection", "withshaderadius", "withshadetransform", "withshadestep", "withshadefraction", "withshadeorigin", "shownshadevector", "shownshadeorigin", + "shownshadedirection", "shownshadecenter", "cmyk", "spotcolor", "multitonecolor", "namedcolor", "drawfill", "undrawfill", "inverted", "uncolored", "softened", "grayed", "greyed", @@ -85,6 +86,7 @@ return { "withmask", "bitmapimage", "colordecimals", "ddecimal", "dddecimal", "ddddecimal", "colordecimalslist", "textext", "thetextext", "rawtextext", "textextoffset", "texbox", "thetexbox", "rawtexbox", "istextext", + "rawmadetext", "validtexbox", "onetimetextext", "rawfmttext", "thefmttext", "fmttext", "onetimefmttext", "notcached", "keepcached", "verbatim", "thelabel", "label", @@ -191,10 +193,10 @@ return { -- "isarray", "prefix", "isobject", -- - "comment", "report", "lua", "mp", "MP", "luacall", + "comment", "report", "lua", "lualist", "mp", "MP", "luacall", -- "mirrored", "mirroredabout", -- - "scriptindex", + "scriptindex", "newscriptindex", }, } diff --git a/tex/context/base/mkiv/mult-low.lua b/tex/context/base/mkiv/mult-low.lua index be507394d..fb786231e 100644 --- a/tex/context/base/mkiv/mult-low.lua +++ b/tex/context/base/mkiv/mult-low.lua @@ -50,7 +50,7 @@ return { "inicatcodes", "ctxcatcodes", "texcatcodes", "notcatcodes", "txtcatcodes", "vrbcatcodes", "prtcatcodes", "nilcatcodes", "luacatcodes", "tpacatcodes", "tpbcatcodes", - "xmlcatcodes", "ctdcatcodes", + "xmlcatcodes", "ctdcatcodes", "rlncatcodes", -- "escapecatcode", "begingroupcatcode", "endgroupcatcode", "mathshiftcatcode", "alignmentcatcode", "endoflinecatcode", "parametercatcode", "superscriptcatcode", "subscriptcatcode", "ignorecatcode", @@ -362,8 +362,8 @@ return { "getvalue", "getuvalue", "setvalue", "setevalue", "setgvalue", "setxvalue", "letvalue", "letgvalue", "resetvalue", "undefinevalue", "ignorevalue", "setuvalue", "setuevalue", "setugvalue", "setuxvalue", - -- - "globallet", "glet", "udef", "ugdef", "uedef", "uxdef", "checked", "unique", + -- glet + "globallet", "udef", "ugdef", "uedef", "uxdef", "checked", "unique", -- "getparameters", "geteparameters", "getgparameters", "getxparameters", "forgetparameters", "copyparameters", -- diff --git a/tex/context/base/mkiv/mult-mps.lua b/tex/context/base/mkiv/mult-mps.lua index d2bad606c..d994a3f65 100644 --- a/tex/context/base/mkiv/mult-mps.lua +++ b/tex/context/base/mkiv/mult-mps.lua @@ -27,7 +27,6 @@ return { "shipout", "show", "showdependencies", "showtoken", "showvariable", "special", "begingroup", "endgroup", "of", "curl", "tension", "and", "controls", - "interpath", "on", "off", "def", "vardef", "enddef", "expr", "suffix", "text", "primary", "secondary", "tertiary", "primarydef", "secondarydef", "tertiarydef", "randomseed", "also", "contour", "doublepath", @@ -68,11 +67,14 @@ return { "fontpart", "fontsize", "glyph", "restoreclipcolor", "troffmode", -- "runscript", "maketext", "numbersystem", + "overloadmode", "setproperty", }, commands = { + "on", "off", + "interpath", "upto", "downto", "beginfig", "endfig", - "beginglyph", "endglyph", -- actually a mult-fun one + "beginglyph", "endglyph", "beginfont", "endfont", -- actually a mult-fun one "rotatedaround", "reflectedabout", "arrowhead", "currentpen", "currentpicture", "cuttings", @@ -119,6 +121,7 @@ return { -- "triplet", "quadruplet", "totransform", "bymatrix", -- + "primitive", "permanent", "immutable", "mutable", "frozen", }, 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 Binary files differindex 709b9330d..e4baa5bde 100644 --- a/tex/context/base/mkiv/status-files.pdf +++ b/tex/context/base/mkiv/status-files.pdf diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf Binary files differindex 37f1aff8a..03b142f98 100644 --- a/tex/context/base/mkiv/status-lua.pdf +++ b/tex/context/base/mkiv/status-lua.pdf diff --git a/tex/context/base/mkiv/strc-pag.lua b/tex/context/base/mkiv/strc-pag.lua index 47fa9463c..6f9cdd429 100644 --- a/tex/context/base/mkiv/strc-pag.lua +++ b/tex/context/base/mkiv/strc-pag.lua @@ -35,6 +35,8 @@ local stopapplyprocessor = processors.stopapply local texsetcount = tex.setcount local texgetcount = tex.getcount +local texconditionals = tex.conditionals + local ctx_convertnumber = context.convertnumber -- storage @@ -319,6 +321,10 @@ function pages.on_right(n) end end +function pages.has_changed() + return texconditionals.layouthaschanged +end + function pages.in_body(n) return texgetcount("pagebodymode") > 0 end diff --git a/tex/context/base/mkxl/catc-ctx.mkxl b/tex/context/base/mkxl/catc-ctx.mkxl deleted file mode 100644 index fcaec65d0..000000000 --- a/tex/context/base/mkxl/catc-ctx.mkxl +++ /dev/null @@ -1,162 +0,0 @@ -%D \module -%D [ file=catc-cys, -%D version=2006.09.18, -%D title=\CONTEXT\ Catcode Macros, -%D subtitle=Extra Tables, -%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 We prefer to define relevant catcode tables in this file instead -%D of everywhere around. - -\ifdefined \ctxcatcodes \else \newcatcodetable \ctxcatcodes \fi -\ifdefined \prtcatcodes \else \newcatcodetable \prtcatcodes \fi -\ifdefined \txtcatcodes \else \newcatcodetable \txtcatcodes \fi -%ifdefined \mthcatcodes \else \newcatcodetable \mthcatcodes \fi % math, not used, too tricky -\ifdefined \tpacatcodes \else \newcatcodetable \tpacatcodes \fi % { } -\ifdefined \tpbcatcodes \else \newcatcodetable \tpbcatcodes \fi % < > - -\setnewconstant \doublecommentsignal "10FF25 %% < 110000 (last valid range) - -\startcatcodetable \ctxcatcodes - \catcode\tabasciicode \spacecatcode - \catcode\endoflineasciicode \endoflinecatcode - \catcode\formfeedasciicode \endoflinecatcode - \catcode\spaceasciicode \spacecatcode - \catcode\endoffileasciicode \ignorecatcode - % \catcode\circumflexasciicode\superscriptcatcode - % \catcode\underscoreasciicode\subscriptcatcode - % \catcode\ampersandasciicode \alignmentcatcode - \catcode\underscoreasciicode\othercatcode - \catcode\circumflexasciicode\othercatcode - \catcode\ampersandasciicode \othercatcode - \catcode\backslashasciicode \escapecatcode - \catcode\leftbraceasciicode \begingroupcatcode - \catcode\rightbraceasciicode\endgroupcatcode - \catcode\dollarasciicode \mathshiftcatcode - \catcode\hashasciicode \parametercatcode - \catcode\commentasciicode \commentcatcode - \catcode\tildeasciicode \activecatcode - \catcode\barasciicode \activecatcode -\stopcatcodetable - -\startcatcodetable \prtcatcodes - \catcode\tabasciicode \spacecatcode - \catcode\endoflineasciicode \endoflinecatcode - \catcode\formfeedasciicode \endoflinecatcode - \catcode\spaceasciicode \spacecatcode - \catcode\endoffileasciicode \ignorecatcode - \catcode\circumflexasciicode \superscriptcatcode % candidate - \catcode\underscoreasciicode \lettercatcode - \catcode\ampersandasciicode \alignmentcatcode - \catcode\backslashasciicode \escapecatcode - \catcode\leftbraceasciicode \begingroupcatcode - \catcode\rightbraceasciicode \endgroupcatcode - \catcode\dollarasciicode \mathshiftcatcode - \catcode\hashasciicode \parametercatcode - \catcode\commentasciicode \commentcatcode - \catcode\atsignasciicode \lettercatcode - \catcode\exclamationmarkasciicode\lettercatcode - \catcode\questionmarkasciicode \lettercatcode - \catcode\tildeasciicode \activecatcode - \catcode\barasciicode \activecatcode -\stopcatcodetable - -\startcatcodetable \tpacatcodes - \catcode\tabasciicode \othercatcode - \catcode\endoflineasciicode \othercatcode - \catcode\formfeedasciicode \othercatcode - \catcode\spaceasciicode \othercatcode - \catcode\endoffileasciicode \othercatcode - \catcode\leftbraceasciicode \begingroupcatcode - \catcode\rightbraceasciicode\endgroupcatcode -\stopcatcodetable - -\startcatcodetable \tpbcatcodes - \catcode\tabasciicode \othercatcode - \catcode\endoflineasciicode \othercatcode - \catcode\formfeedasciicode \othercatcode - \catcode\spaceasciicode \othercatcode - \catcode\endoffileasciicode \othercatcode - \catcode\lessthanasciicode \begingroupcatcode - \catcode\morethanasciicode \endgroupcatcode -\stopcatcodetable - -\startcatcodetable \txtcatcodes - \catcode\tabasciicode \spacecatcode - \catcode\endoflineasciicode \endoflinecatcode - \catcode\formfeedasciicode \endoflinecatcode - \catcode\spaceasciicode \spacecatcode - \catcode\endoffileasciicode \ignorecatcode - \catcode\backslashasciicode \escapecatcode - \catcode\leftbraceasciicode \begingroupcatcode - \catcode\rightbraceasciicode\endgroupcatcode - \catcode\doublecommentsignal\commentcatcode -\stopcatcodetable - -\letcatcodecommand \ctxcatcodes \barasciicode \relax -\letcatcodecommand \ctxcatcodes \tildeasciicode \relax - -\pushoverloadmode - - \catcodetable \ctxcatcodes -\let \defaultcatcodetable \ctxcatcodes - -\popoverloadmode - -% for the moment here: - -\permanent\protected\def\starttexcode - {\pushcatcodetable - \catcodetable\prtcatcodes} - -\permanent\protected\def\stoptexcode - {\popcatcodetable} - -\permanent\protected\def\startcontextcode - {\pushcatcodetable - \catcodetable\ctxcatcodes} - -\permanent\protected\def\stopcontextcode - {\popcatcodetable} - -% not visible, only for special cases - -\newcatcodetable \ctdcatcodes % context definitions - -\startcatcodetable \ctdcatcodes - \catcode\tabasciicode \ignorecatcode - \catcode\endoflineasciicode \ignorecatcode - \catcode\formfeedasciicode \ignorecatcode - \catcode\spaceasciicode \ignorecatcode - \catcode\endoffileasciicode \ignorecatcode - \catcode\circumflexasciicode \superscriptcatcode % candidate - \catcode\underscoreasciicode \lettercatcode - \catcode\ampersandasciicode \alignmentcatcode -% \catcode\colonasciicode \lettercatcode % candidate - \catcode\backslashasciicode \escapecatcode - \catcode\leftbraceasciicode \begingroupcatcode - \catcode\rightbraceasciicode \endgroupcatcode - \catcode\dollarasciicode \mathshiftcatcode - \catcode\hashasciicode \parametercatcode - \catcode\commentasciicode \commentcatcode - \catcode\atsignasciicode \lettercatcode - \catcode\exclamationmarkasciicode\lettercatcode - \catcode\questionmarkasciicode \lettercatcode - \catcode\tildeasciicode \activecatcode - \catcode\barasciicode \activecatcode -\stopcatcodetable - -\permanent\protected\def\startcontextdefinitioncode - {\pushcatcodetable - \catcodetable\ctdcatcodes} - -\permanent\protected\def\stopcontextdefinitioncode - {\popcatcodetable} - -\endinput diff --git a/tex/context/base/mkxl/catc-def.mkxl b/tex/context/base/mkxl/catc-def.mkxl index e93345e1c..a67d94e44 100644 --- a/tex/context/base/mkxl/catc-def.mkxl +++ b/tex/context/base/mkxl/catc-def.mkxl @@ -11,14 +11,24 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -%D The following catcode tables are rather \CONTEXT\ independent. +%D Contrary to \MKIV\ we define the catcode tables in one file. The original split +%D was a prelude to a split approach where context would run on top of a basic layer +%D (so that we could make smaller alternative formats) but that idea (from the early +%D days of \LUATEX) was dropped when I realized that there is not common ground to +%D cover between formats. It is simply not worth the trouble. -\ifdefined\nilcatcodes \else \newcatcodetable \nilcatcodes \fi -\ifdefined\texcatcodes \else \newcatcodetable \texcatcodes \fi -\ifdefined\luacatcodes \else \newcatcodetable \luacatcodes \fi -\ifdefined\notcatcodes \else \newcatcodetable \notcatcodes \fi -\ifdefined\vrbcatcodes \else \newcatcodetable \vrbcatcodes \fi -\ifdefined\prtcatcodes \else \newcatcodetable \prtcatcodes \fi +\ifdefined\nilcatcodes \else \newcatcodetable \nilcatcodes \fi +\ifdefined\texcatcodes \else \newcatcodetable \texcatcodes \fi +\ifdefined\luacatcodes \else \newcatcodetable \luacatcodes \fi +\ifdefined\notcatcodes \else \newcatcodetable \notcatcodes \fi +\ifdefined\rlncatcodes \else \newcatcodetable \rlncatcodes \fi +\ifdefined\vrbcatcodes \else \newcatcodetable \vrbcatcodes \fi +\ifdefined\prtcatcodes \else \newcatcodetable \prtcatcodes \fi +\ifdefined\ctxcatcodes \else \newcatcodetable \ctxcatcodes \fi +\ifdefined\txtcatcodes \else \newcatcodetable \txtcatcodes \fi +\ifdefined\tpacatcodes \else \newcatcodetable \tpacatcodes \fi % { } +\ifdefined\tpbcatcodes \else \newcatcodetable \tpbcatcodes \fi % < > +\ifdefined\ctdcatcodes \else \newcatcodetable \ctdcatcodes \fi % context definitions \startcatcodetable \nilcatcodes \catcode\tabasciicode \spacecatcode @@ -88,6 +98,30 @@ \catcode\barasciicode \othercatcode \stopcatcodetable +\startcatcodetable \rlncatcodes + \catcode\tabasciicode \othercatcode + \catcode\endoflineasciicode \othercatcode + \catcode\formfeedasciicode \othercatcode + \catcode\spaceasciicode \spacecatcode % maybe we also need a vector with space being other + \catcode\endoffileasciicode \othercatcode + \catcode\circumflexasciicode \othercatcode + \catcode\underscoreasciicode \othercatcode + \catcode\ampersandasciicode \othercatcode + \catcode\tildeasciicode \othercatcode + \catcode\hashasciicode \othercatcode + \catcode\dollarasciicode \othercatcode + \catcode\commentasciicode \othercatcode + \catcode\lessthanasciicode \othercatcode + \catcode\morethanasciicode \othercatcode + \catcode\leftbraceasciicode \othercatcode + \catcode\rightbraceasciicode \othercatcode + \catcode\doublequoteasciicode \othercatcode + \catcode\singlequoteasciicode \othercatcode + \catcode\forwardslashasciicode\othercatcode + \catcode\backslashasciicode \othercatcode + \catcode\barasciicode \othercatcode +\stopcatcodetable + \startcatcodetable \vrbcatcodes % probably less needed \catcode\tabasciicode \othercatcode \catcode\endoflineasciicode\othercatcode @@ -119,6 +153,104 @@ \catcode\barasciicode \activecatcode \stopcatcodetable +\startcatcodetable \ctxcatcodes + \catcode\tabasciicode \spacecatcode + \catcode\endoflineasciicode \endoflinecatcode + \catcode\formfeedasciicode \endoflinecatcode + \catcode\spaceasciicode \spacecatcode + \catcode\endoffileasciicode \ignorecatcode + % \catcode\circumflexasciicode\superscriptcatcode + % \catcode\underscoreasciicode\subscriptcatcode + % \catcode\ampersandasciicode \alignmentcatcode + \catcode\underscoreasciicode\othercatcode + \catcode\circumflexasciicode\othercatcode + \catcode\ampersandasciicode \othercatcode + \catcode\backslashasciicode \escapecatcode + \catcode\leftbraceasciicode \begingroupcatcode + \catcode\rightbraceasciicode\endgroupcatcode + \catcode\dollarasciicode \mathshiftcatcode + \catcode\hashasciicode \parametercatcode + \catcode\commentasciicode \commentcatcode + \catcode\tildeasciicode \activecatcode + \catcode\barasciicode \activecatcode +\stopcatcodetable + +\startcatcodetable \tpacatcodes + \catcode\tabasciicode \othercatcode + \catcode\endoflineasciicode \othercatcode + \catcode\formfeedasciicode \othercatcode + \catcode\spaceasciicode \othercatcode + \catcode\endoffileasciicode \othercatcode + \catcode\leftbraceasciicode \begingroupcatcode + \catcode\rightbraceasciicode\endgroupcatcode +\stopcatcodetable + +\startcatcodetable \tpbcatcodes + \catcode\tabasciicode \othercatcode + \catcode\endoflineasciicode \othercatcode + \catcode\formfeedasciicode \othercatcode + \catcode\spaceasciicode \othercatcode + \catcode\endoffileasciicode \othercatcode + \catcode\lessthanasciicode \begingroupcatcode + \catcode\morethanasciicode \endgroupcatcode +\stopcatcodetable + +\setnewconstant \doublecommentsignal "10FF25 %% < 110000 (last valid range) + +\startcatcodetable \txtcatcodes + \catcode\tabasciicode \spacecatcode + \catcode\endoflineasciicode \endoflinecatcode + \catcode\formfeedasciicode \endoflinecatcode + \catcode\spaceasciicode \spacecatcode + \catcode\endoffileasciicode \ignorecatcode + \catcode\backslashasciicode \escapecatcode + \catcode\leftbraceasciicode \begingroupcatcode + \catcode\rightbraceasciicode\endgroupcatcode + \catcode\doublecommentsignal\commentcatcode +\stopcatcodetable + +\startcatcodetable \ctdcatcodes + \catcode\tabasciicode \ignorecatcode + \catcode\endoflineasciicode \ignorecatcode + \catcode\formfeedasciicode \ignorecatcode + \catcode\spaceasciicode \ignorecatcode + \catcode\endoffileasciicode \ignorecatcode + \catcode\circumflexasciicode \superscriptcatcode % candidate + \catcode\underscoreasciicode \lettercatcode + \catcode\ampersandasciicode \alignmentcatcode + % \catcode\colonasciicode \lettercatcode % candidate + \catcode\backslashasciicode \escapecatcode + \catcode\leftbraceasciicode \begingroupcatcode + \catcode\rightbraceasciicode \endgroupcatcode + \catcode\dollarasciicode \mathshiftcatcode + \catcode\hashasciicode \parametercatcode + \catcode\commentasciicode \commentcatcode + \catcode\atsignasciicode \lettercatcode + \catcode\exclamationmarkasciicode\lettercatcode + \catcode\questionmarkasciicode \lettercatcode + \catcode\tildeasciicode \activecatcode + \catcode\barasciicode \activecatcode +\stopcatcodetable + +%D From now on we can use the protection mechanisms. + +\permanent\protected\def\unprotect {\pushcatcodetable\catcodetable\prtcatcodes} +\permanent\protected\def\protect {\popcatcodetable} + +\permanent\protected\def\starttexcode {\pushcatcodetable\catcodetable\prtcatcodes} +\permanent\protected\def\stoptexcode {\popcatcodetable} + +\permanent\protected\def\startcontextcode {\pushcatcodetable\catcodetable\ctxcatcodes} +\permanent\protected\def\stopcontextcode {\popcatcodetable} + +\permanent\protected\def\startcontextdefinitioncode {\pushcatcodetable\catcodetable\ctdcatcodes} +\permanent\protected\def\stopcontextdefinitioncode {\popcatcodetable} + +%D We have a few active characters (left): + +\letcatcodecommand \ctxcatcodes \barasciicode \relax +\letcatcodecommand \ctxcatcodes \tildeasciicode \relax + %D Because some characters have a special meaning, we provide shortcuts to their %D character representation. Some will be overloaded (which might change). @@ -133,15 +265,9 @@ \chardef \\ = \backslashasciicode \chardef \| = \barasciicode -% way too wide in lm, so one can also use: -% -% \def\_{\dontleavehmode \kern.06em \vbox{\hrule width.3em}} % this will become a \chardef - -%D From now on we can use the protection mechanisms. - -\permanent\protected\def\unprotect{\pushcatcodetable\setcatcodetable\prtcatcodes} -\permanent\protected\def\protect {\popcatcodetable} - -% \prependtoks \catcodetable\ctxcatcodes \to \everyjob +\pushoverloadmode + \catcodetable \ctxcatcodes + \let \defaultcatcodetable \ctxcatcodes +\popoverloadmode \endinput diff --git a/tex/context/base/mkxl/catc-ini.mkxl b/tex/context/base/mkxl/catc-ini.mkxl index b7844fe89..7d23c37d8 100644 --- a/tex/context/base/mkxl/catc-ini.mkxl +++ b/tex/context/base/mkxl/catc-ini.mkxl @@ -11,35 +11,14 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -%D We've split the functionality of syst-cat.* over more files now so that we can -%D load more selectively. - \registerctxluafile{catc-ini}{} \unprotect %D A long standing wish has been the availability of catcode arrays. Because -%D traditional \TEX\ does not provide this we implement a fake method in the -%D \MKII\ file. There is some overlap in code with \MKII\ but we take that -%D for granted. Also, in \MKIV\ less active characters are used. - -% \begingroup -% -% \catcode\tabasciicode \activecatcode -% \catcode\formfeedasciicode \activecatcode -% \catcode\endoflineasciicode\activecatcode -% -% \letcharcode\tabasciicode \relax -% \letcharcode\newlineasciicode \relax -% \letcharcode\formfeedasciicode \relax -% \letcharcode\endoflineasciicode\relax -% -% \xdef\activetabtoken {\Uchar\tabasciicode } % \gdef\activetabtoken {^^I} -% \xdef\outputnewlinechar {\Uchar\newlineasciicode } % \gdef\outputnewlinechar {^^J} -% \xdef\activeformfeedtoken {\Uchar\formfeedasciicode } % \gdef\activeformfeedtoken {^^L} -% \xdef\activeendoflinetoken{\Uchar\endoflineasciicode} % \gdef\activeendoflinetoken{^^M} -% -% \endgroup +%D traditional \TEX\ does not provide this we implement a fake method in the \MKII\ +%D file. There is some overlap in code with \MKII\ but we take that for granted. +%D Also, in \MKIV\ less active characters are used. \begingroup \letcharcode\newlineasciicode\relax \xdef\outputnewlinechar{\Uchar\newlineasciicode} @@ -58,7 +37,7 @@ \endgroup} %D We predefine some prefixes ahead of syst-aux and mult-sys. We reserve 8 slots for -%D catcodes. +%D catcodes. (This active mess probably needs an update some day.) \installsystemnamespace {catcodelet} % let : \let \installsystemnamespace {catcodedef} % def : \def @@ -225,13 +204,13 @@ \permanent\protected\def\pushcatcodetable {\advance\c_syst_catcodes_level\plusone \syst_catcodes_trace_push - \expandafter\chardef\csname\??catcodetablet\number\c_syst_catcodes_level\endcsname\currentcatcodetable} + \expandafter\integerdef\csname\??catcodetablet\number\c_syst_catcodes_level\endcsname\currentcatcodetable} \permanent\protected\def\popcatcodetable {\ifcase\c_syst_catcodes_level \syst_catcodes_trace_nesting_error \else - \expandafter\catcodetable\csname\??catcodetablet\number\c_syst_catcodes_level\endcsname + \catcodetable\csname\??catcodetablet\number\c_syst_catcodes_level\endcsname \syst_catcodes_trace_pop \advance\c_syst_catcodes_level\minusone \fi} @@ -243,14 +222,14 @@ \permanent\protected\def\restorecatcodes % takes previous level {\ifnum\c_syst_catcodes_level>\plusone - \expandafter\catcodetable\csname\??catcodetablet\number\numexpr\c_syst_catcodes_level-1\relax\endcsname + \catcodetable\csname\??catcodetablet\number\numexpr\c_syst_catcodes_level-1\relax\endcsname \fi} \permanent\protected\def\setcatcodetable#1% {\catcodetable#1% \syst_catcodes_trace_set} -%D Handy for debugging: +%D Handy for debugging (not that we ever use(d) it): %D %D \starttyping %D \tracecatcodetables diff --git a/tex/context/base/mkxl/char-ini.mkxl b/tex/context/base/mkxl/char-ini.mkxl index 1df5d4b01..6965598a9 100644 --- a/tex/context/base/mkxl/char-ini.mkxl +++ b/tex/context/base/mkxl/char-ini.mkxl @@ -15,7 +15,7 @@ \registerctxluafile{char-fio}{} \registerctxluafile{char-map}{} % maybe we will load this someplace else -\registerctxluafile{char-tex}{} +\registerctxluafile{char-tex}{autosuffix} \registerctxluafile{char-ent}{} \unprotect @@ -52,7 +52,6 @@ \permanent \def\checkedchar {\relax\ifmmode\expandafter\checkedmathchar\else\expandafter\checkedtextchar\fi} % #1#2 \permanent \def\checkedmathchar#1#2{#2} -%permanent \def\checkedtextchar #1{\iffontchar\font#1 \expandafter\firstoftwoarguments\else\expandafter\secondoftwoarguments\fi{\char#1}} \permanent\protected\def\checkedtextchar #1{\clf_doifelsecharinfont\numexpr#1\relax{\char#1}} % {#2} \newconditional\prefermathovertextchar @@ -87,29 +86,26 @@ \normalstartimath#1#2\normalstopimath \fi} -%D The codes are stored in the format, so we don't need to reinitialize -%D them (unless of course we have adapted the table). It is on the agenda -%D to do this with \type {tex.lccode} cum suis once they're available. - -% \def\setcclcuc#1#2#3{\global\catcode#1=\lettercatcode\global\lccode#1=#2\global\uccode#1=#3\relax} -% \def\setcclcucself#1{\global\catcode#1=\lettercatcode\global\lccode#1=#1\global\uccode#1=#1\relax } +%D The codes are stored in the format, so we don't need to reinitialize them (unless +%D of course we have adapted the table). It is on the agenda to do this with \type +%D {tex.lccode} cum suis once they're available. % Is setting up vrb tpa and tpb needed? % move to lua side -%clf_setcharactercodes - \clf_setlettercatcodes\texcatcodes \clf_setlettercatcodes\ctxcatcodes \clf_setlettercatcodes\notcatcodes %clf_setlettercatcodes\mthcatcodes -\clf_setlettercatcodes\vrbcatcodes +\clf_setlettercatcodes\vrbcatcodes % hm ... we need to hyphemate \clf_setlettercatcodes\prtcatcodes -\clf_setlettercatcodes\tpacatcodes -\clf_setlettercatcodes\tpbcatcodes +\clf_setlettercatcodes\tpacatcodes % hm +\clf_setlettercatcodes\tpbcatcodes % hm \clf_setlettercatcodes\txtcatcodes +\clf_setothercatcodes \rlncatcodes + \clf_setactivecatcodes\ctxcatcodes \clf_setactivecatcodes\notcatcodes \clf_setactivecatcodes\prtcatcodes diff --git a/tex/context/base/mkxl/char-tex.lmt b/tex/context/base/mkxl/char-tex.lmt new file mode 100644 index 000000000..5d2883f21 --- /dev/null +++ b/tex/context/base/mkxl/char-tex.lmt @@ -0,0 +1,763 @@ +if not modules then modules = { } end modules ['char-tex'] = { + version = 1.001, + comment = "companion to char-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local lpeg = lpeg +local tonumber, next, type = tonumber, next, type +local format, find, gmatch, match = string.format, string.find, string.gmatch, string.match +local utfchar, utfbyte = utf.char, utf.byte +local concat, tohash = table.concat, table.tohash +local P, C, R, S, V, Cs, Cc = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.V, lpeg.Cs, lpeg.Cc + +local lpegpatterns = lpeg.patterns +local lpegmatch = lpeg.match +local utfchartabletopattern = lpeg.utfchartabletopattern + +local allocate = utilities.storage.allocate +local mark = utilities.storage.mark + +local context = context +local commands = commands + +local characters = characters +local texcharacters = { } +characters.tex = texcharacters +local utffilters = characters.filters.utf + +local is_character = characters.is_character +local is_letter = characters.is_letter +local is_command = characters.is_command +local is_spacing = characters.is_spacing +local is_mark = characters.is_mark +local is_punctuation = characters.is_punctuation + +local data = characters.data if not data then return end +local blocks = characters.blocks + +local trace_defining = false trackers.register("characters.defining", function(v) characters_defining = v end) + +local report_defining = logs.reporter("characters") + +--[[ldx-- +<p>In order to deal with 8-bit output, we need to find a way to go from <l n='utf'/> to +8-bit. This is handled in the <l n='luatex'/> engine itself.</p> + +<p>This leaves us problems with characters that are specific to <l n='tex'/> like +<type>{}</type>, <type>$</type> and alike. We can remap some chars that tex input files +are sensitive for to a private area (while writing to a utility file) and revert then +to their original slot when we read in such a file. Instead of reverting, we can (when +we resolve characters to glyphs) map them to their right glyph there. For this purpose +we can use the private planes 0x0F0000 and 0x100000.</p> +--ldx]]-- + +local low = allocate() +local high = allocate() +local escapes = allocate() +local special = "~#$%^&_{}\\|" -- "~#$%{}\\|" + +local private = { + low = low, + high = high, + escapes = escapes, +} + +utffilters.private = private + +for ch in gmatch(special,".") do + local cb + if type(ch) == "number" then + cb, ch = ch, utfchar(ch) + else + cb = utfbyte(ch) + end + if cb < 256 then + escapes[ch] = "\\" .. ch + low[ch] = utfchar(0x0F0000 + cb) + if ch == "%" then + ch = "%%" -- nasty, but we need this as in replacements (also in lpeg) % is interpreted + end + high[utfchar(0x0F0000 + cb)] = ch + end +end + +local tohigh = lpeg.replacer(low) -- frozen, only for basic tex +local tolow = lpeg.replacer(high) -- frozen, only for basic tex + +lpegpatterns.utftohigh = tohigh +lpegpatterns.utftolow = tolow + +function utffilters.harden(str) + return lpegmatch(tohigh,str) +end + +function utffilters.soften(str) + return lpegmatch(tolow,str) +end + +private.escape = utf.remapper(escapes) -- maybe: ,"dynamic" +private.replace = utf.remapper(low) -- maybe: ,"dynamic" +private.revert = utf.remapper(high) -- maybe: ,"dynamic" + +--[[ldx-- +<p>We get a more efficient variant of this when we integrate +replacements in collapser. This more or less renders the previous +private code redundant. The following code is equivalent but the +first snippet uses the relocated dollars.</p> + +<typing> +[x] [$x$] +</typing> +--ldx]]-- + +-- using the tree-lpeg-mapper would be nice but we also need to deal with end-of-string +-- cases: "\"\i" and don't want "\relax" to be seen as \r e lax" (for which we need to mess +-- with spaces + +local accentmapping = allocate { + ['"'] = { [""] = "¨", + A = "Ä", a = "ä", + E = "Ë", e = "ë", + I = "Ï", i = "ï", ["ı"] = "ï", ["\\i"] = "ï", + O = "Ö", o = "ö", + U = "Ü", u = "ü", + Y = "Ÿ", y = "ÿ", + }, + ["'"] = { [""] = "´", + A = "Á", a = "á", + C = "Ć", c = "ć", + E = "É", e = "é", + I = "Í", i = "í", ["ı"] = "í", ["\\i"] = "í", + L = "Ĺ", l = "ĺ", + N = "Ń", n = "ń", + O = "Ó", o = "ó", + R = "Ŕ", r = "ŕ", + S = "Ś", s = "ś", + U = "Ú", u = "ú", + Y = "Ý", y = "ý", + Z = "Ź", z = "ź", + }, + ["."] = { [""] = "˙", + C = "Ċ", c = "ċ", + E = "Ė", e = "ė", + G = "Ġ", g = "ġ", + I = "İ", i = "i", ["ı"] = "i", ["\\i"] = "i", + Z = "Ż", z = "ż", + }, + ["="] = { [""] = "¯", + A = "Ā", a = "ā", + E = "Ē", e = "ē", + I = "Ī", i = "ī", ["ı"] = "ī", ["\\i"] = "ī", + O = "Ō", o = "ō", + U = "Ū", u = "ū", + }, + ["H"] = { [""] = "˝", + O = "Ő", o = "ő", + U = "Ű", u = "ű", + }, + ["^"] = { [""] = "ˆ", + A = "Â", a = "â", + C = "Ĉ", c = "ĉ", + E = "Ê", e = "ê", + G = "Ĝ", g = "ĝ", + H = "Ĥ", h = "ĥ", + I = "Î", i = "î", ["ı"] = "î", ["\\i"] = "î", + J = "Ĵ", j = "ĵ", + O = "Ô", o = "ô", + S = "Ŝ", s = "ŝ", + U = "Û", u = "û", + W = "Ŵ", w = "ŵ", + Y = "Ŷ", y = "ŷ", + }, + ["`"] = { [""] = "`", + A = "À", a = "à", + E = "È", e = "è", + I = "Ì", i = "ì", ["ı"] = "ì", ["\\i"] = "ì", + O = "Ò", o = "ò", + U = "Ù", u = "ù", + Y = "Ỳ", y = "ỳ", + }, + ["c"] = { [""] = "¸", + C = "Ç", c = "ç", + K = "Ķ", k = "ķ", + L = "Ļ", l = "ļ", + N = "Ņ", n = "ņ", + R = "Ŗ", r = "ŗ", + S = "Ş", s = "ş", + T = "Ţ", t = "ţ", + }, + ["k"] = { [""] = "˛", + A = "Ą", a = "ą", + E = "Ę", e = "ę", + I = "Į", i = "į", + U = "Ų", u = "ų", + }, + ["r"] = { [""] = "˚", + A = "Å", a = "å", + U = "Ů", u = "ů", + }, + ["u"] = { [""] = "˘", + A = "Ă", a = "ă", + E = "Ĕ", e = "ĕ", + G = "Ğ", g = "ğ", + I = "Ĭ", i = "ĭ", ["ı"] = "ĭ", ["\\i"] = "ĭ", + O = "Ŏ", o = "ŏ", + U = "Ŭ", u = "ŭ", + }, + ["v"] = { [""] = "ˇ", + C = "Č", c = "č", + D = "Ď", d = "ď", + E = "Ě", e = "ě", + L = "Ľ", l = "ľ", + N = "Ň", n = "ň", + R = "Ř", r = "ř", + S = "Š", s = "š", + T = "Ť", t = "ť", + Z = "Ž", z = "ž", + }, + ["~"] = { [""] = "˜", + A = "Ã", a = "ã", + I = "Ĩ", i = "ĩ", ["ı"] = "ĩ", ["\\i"] = "ĩ", + N = "Ñ", n = "ñ", + O = "Õ", o = "õ", + U = "Ũ", u = "ũ", + }, +} + +texcharacters.accentmapping = accentmapping + +local accent_map = allocate { -- incomplete + ['~'] = "̃" , -- ̃ Ẽ + ['"'] = "̈" , -- ̈ Ë + ["`"] = "̀" , -- ̀ È + ["'"] = "́" , -- ́ É + ["^"] = "̂" , -- ̂ Ê + -- ̄ Ē + -- ̆ Ĕ + -- ̇ Ė + -- ̉ Ẻ + -- ̌ Ě + -- ̏ Ȅ + -- ̑ Ȇ + -- ̣ Ẹ + -- ̧ Ȩ + -- ̨ Ę + -- ̭ Ḙ + -- ̰ Ḛ +} + +-- local accents = concat(table.keys(accentmapping)) -- was _map + +local function remap_accent(a,c,braced) + local m = accentmapping[a] + if m then + local n = m[c] + if n then + return n + end + end +-- local m = accent_map[a] +-- if m then +-- return c .. m +-- elseif braced then -- or #c > 0 + if braced then -- or #c > 0 + return "\\" .. a .. "{" .. c .. "}" + else + return "\\" .. a .. " " .. c + end +end + +local commandmapping = allocate { + ["aa"] = "å", ["AA"] = "Å", + ["ae"] = "æ", ["AE"] = "Æ", + ["cc"] = "ç", ["CC"] = "Ç", + ["i"] = "ı", ["j"] = "ȷ", + ["ij"] = "ij", ["IJ"] = "IJ", + ["l"] = "ł", ["L"] = "Ł", + ["o"] = "ø", ["O"] = "Ø", + ["oe"] = "œ", ["OE"] = "Œ", + ["sz"] = "ß", ["SZ"] = "SZ", ["ss"] = "ß", ["SS"] = "ß", -- uppercase: ẞ +} + +texcharacters.commandmapping = commandmapping + +local ligaturemapping = allocate { + ["''"] = "”", + ["``"] = "“", + ["--"] = "–", + ["---"] = "—", +} + +-- Older accent handling code can be found in char-def.lua but in the meantime +-- we moved on. + +local untex + +local function toutfpattern() + if not untex then + local hash = { } + for k, v in next, accentmapping do + for kk, vv in next, v do + if (k >= "a" and k <= "z") or (k >= "A" and k <= "Z") then + hash[ "\\"..k.." "..kk ] = vv + hash["{\\"..k.." "..kk.."}"] = vv + else + hash["\\" ..k ..kk ] = vv + hash["{\\"..k ..kk.."}"] = vv + end + hash["\\" ..k.."{"..kk.."}" ] = vv + hash["{\\"..k.."{"..kk.."}}"] = vv + end + end + for k, v in next, commandmapping do + hash["\\"..k.." "] = v + hash["{\\"..k.."}"] = v + hash["{\\"..k.." }"] = v + end + for k, v in next, ligaturemapping do + hash[k] = v + end + untex = utfchartabletopattern(hash) / hash + end + return untex +end + +texcharacters.toutfpattern = toutfpattern + +local pattern = nil + +local function prepare() + pattern = Cs((toutfpattern() + P(1))^0) + return pattern +end + +function texcharacters.toutf(str,strip) + if str == "" then + return str + elseif not find(str,"\\",1,true) then + return str + -- elseif strip then + else + return lpegmatch(pattern or prepare(),str) + end +end + +-- print(texcharacters.toutf([[\~{Z}]],true)) +-- print(texcharacters.toutf([[\'\i]],true)) +-- print(texcharacters.toutf([[\'{\i}]],true)) +-- print(texcharacters.toutf([[\"{e}]],true)) +-- print(texcharacters.toutf([[\" {e}]],true)) +-- print(texcharacters.toutf([[{\"{e}}]],true)) +-- print(texcharacters.toutf([[{\" {e}}]],true)) +-- print(texcharacters.toutf([[{\l}]],true)) +-- print(texcharacters.toutf([[{\l }]],true)) +-- print(texcharacters.toutf([[\v{r}]],true)) +-- print(texcharacters.toutf([[fo{\"o}{\ss}ar]],true)) +-- print(texcharacters.toutf([[H{\'a}n Th\^e\llap{\raise 0.5ex\hbox{\'{\relax}}} Th{\'a}nh]],true)) + +function texcharacters.safechar(n) -- was characters.safechar + local c = data[n] + if c and c.contextname then + return "\\" .. c.contextname + else + return utfchar(n) + end +end + +if not context or not commands then + -- used in e.g. mtx-bibtex + return +end + +-- all kind of initializations + +if not interfaces then return end + +local implement = interfaces.implement + +local tex = tex +local texsetlccode = tex.setlccode +local texsetsfcode = tex.setsfcode +local texsetcatcode = tex.setcatcode + +local contextsprint = context.sprint +local ctxcatcodes = catcodes.numbers.ctxcatcodes + +local texsetmacro = tokens.setters.macro +local texsetchar = tokens.setters.char + +function texcharacters.defineaccents() + local ctx_dodefineaccentcommand = context.dodefineaccentcommand + local ctx_dodefineaccent = context.dodefineaccent + local ctx_dodefinecommand = context.dodefinecommand + for accent, group in next, accentmapping do + ctx_dodefineaccentcommand(accent) + for character, mapping in next, group do + ctx_dodefineaccent(accent,character,mapping) + end + end + for command, mapping in next, commandmapping do + ctx_dodefinecommand(command,mapping) + end +end + +implement { -- a waste of scanner but consistent + name = "defineaccents", + actions = texcharacters.defineaccents +} + +--[[ldx-- +<p>Instead of using a <l n='tex'/> file to define the named glyphs, we +use the table. After all, we have this information available anyway.</p> +--ldx]]-- + +local function to_number(s) + local n = tonumber(s) + if n then + return n + end + return tonumber(match(s,'^"(.*)$'),16) or 0 +end + +implement { + name = "utfchar", + actions = { to_number, utfchar, contextsprint }, + arguments = "string" +} + +implement { + name = "safechar", + actions = { to_number, texcharacters.safechar, contextsprint }, + arguments = "string" +} + +implement { + name = "uchar", + arguments = { "integer", "integer" }, + actions = function(h,l) + context(utfchar(h*256+l)) + end +} + +tex.uprint = commands.utfchar + +-- in contect we don't use lc and uc codes (in fact in luatex we should have a hf code) +-- so at some point we might drop this + +-- The following get set at the TeX end: + +local forbidden = tohash { + 0x000A0, -- zs nobreakspace <self> + 0x000AD, -- cf softhyphen <self> + -- 0x00600, -- cf arabicnumber <self> + -- 0x00601, -- cf arabicsanah <self> + -- 0x00602, -- cf arabicfootnotemarker <self> + -- 0x00603, -- cf arabicsafha <self> + -- 0x00604, -- cf arabicsamvat <self> + -- 0x00605, -- cf arabicnumberabove <self> + -- 0x0061C, -- cf arabiclettermark <self> + -- 0x006DD, -- cf arabicendofayah <self> + -- 0x008E2, -- cf arabicdisputedendofayah <self> + 0x02000, -- zs enquad <self> + 0x02001, -- zs emquad <self> + 0x02002, -- zs enspace \kern .5\emwidth + 0x02003, -- zs emspace \hskip \emwidth + 0x02004, -- zs threeperemspace <self> + 0x02005, -- zs fourperemspace <self> + 0x02006, -- zs sixperemspace <self> + 0x02007, -- zs figurespace <self> + 0x02008, -- zs punctuationspace <self> + 0x02009, -- zs breakablethinspace <self> + 0x0200A, -- zs hairspace <self> + 0x0200B, -- cf zerowidthspace <self> + 0x0200C, -- cf zwnj <self> + 0x0200D, -- cf zwj <self> + 0x0202F, -- zs narrownobreakspace <self> + 0x0205F, -- zs medspace \textormathspace +\medmuskip 2 + -- 0x03000, -- zs ideographicspace <self> + -- 0x0FEFF, -- cf zerowidthnobreakspace \penalty \plustenthousand \kern \zeropoint +} + +local csletters = characters.csletters -- also a signal that we have initialized +local activated = { } +local sfmode = "unset" -- unset, traditional, normal +local block_too = false + +directives.register("characters.blockstoo",function(v) block_too = v end) + +-- If this is something that is not documentwide and used a lot, then we +-- need a more clever approach (trivial but not now). + +local function setuppersfcodes(v,n) + if sfstate ~= "unset" then + report_defining("setting uppercase sf codes to %a",n) + for u, chr in next, data do + if chr.category == "lu" then + texsetsfcode(u,n) + end + end + end + sfstate = v +end + +directives.register("characters.spaceafteruppercase",function(v) + if v == "traditional" then + setuppersfcodes(v,999) + elseif v == "normal" then + setuppersfcodes(v,1000) + end +end) + +if not csletters then + + csletters = allocate() + characters.csletters = csletters + + report_defining("setting up character related codes and commands") + + if sfstate == "unset" then + sfstate = "traditional" + end + + local traditional = sfstate == "traditional" + + for u, chr in next, data do -- will move up + local contextname = chr.contextname + local category = chr.category + local isletter = is_letter[category] + if contextname then + if is_character[category] then + if chr.unicodeslot < 128 then + if isletter then + local c = utfchar(u) + csletters[c] = u + end + else + local c = utfchar(u) + if isletter and u >= 32 and u <= 65536 then + csletters[c] = u + end + end + if isletter then + local lc = chr.lccode + local uc = chr.uccode + if not lc then + chr.lccode = u + lc = u + elseif type(lc) == "table" then + lc = u + end + if not uc then + chr.uccode = u + uc = u + elseif type(uc) == "table" then + uc = u + end + texsetlccode(u,lc,uc) + if traditional and category == "lu" then + texsetsfcode(code,999) + end + end + elseif is_command[category] and not forbidden[u] then + -- skip + elseif is_mark[category] then + texsetlccode(u,u,u) -- for hyphenation + end + elseif isletter then + csletters[utfchar(u)] = u + local lc, uc = chr.lccode, chr.uccode + if not lc then + chr.lccode = u + lc = u + elseif type(lc) == "table" then + lc = u + end + if not uc then + chr.uccode = u + uc = u + elseif type(uc) == "table" then + uc = u + end + texsetlccode(u,lc,uc) + if traditional and category == "lu" then + texsetsfcode(code,999) + end + elseif is_mark[category] then + texsetlccode(u,u,u) -- for hyphenation + end + end + + if blocks_too then + -- this slows down format generation by over 10 percent + for k, v in next, blocks do + if v.catcode == "letter" then + local first = v.first + local last = v.last + local gaps = v.gaps + if first and last then + for u=first,last do + csletters[utfchar(u)] = u + -- + -- texsetlccode(u,u,u) -- self self + -- + end + end + if gaps then + for i=1,#gaps do + local u = gaps[i] + csletters[utfchar(u)] = u + -- + -- texsetlccode(u,u,u) -- self self + -- + end + end + end + end + end + + if storage then + storage.register("characters/csletters", csletters, "characters.csletters") + end + + function characters.setcharacternames(ctt) + for u, chr in next, data do -- will move up + local contextname = chr.contextname + local category = chr.category + local isletter = is_letter[category] + if contextname then + if is_character[category] then + if chr.unicodeslot < 128 then + if isletter then + texsetmacro(contextname,utfchar(u),"immutable") + else + texsetchar(contextname,u,"immutable") + end + else + texsetmacro(contextname,utfchar(u),"immutable") + end + elseif is_command[category] and not forbidden[u] then + texsetmacro(contextname,utfchar(u),"immutable") + end + end + end + end + +else + mark(csletters) +end + +lpegpatterns.csletter = utfchartabletopattern(csletters) + +-- The engine presets the letters to 11 (always). + +function characters.setlettercatcodes(cct) + if trace_defining then + report_defining("assigning letter catcodes to catcode table %a",cct) + end + local saved = tex.catcodetable + tex.catcodetable = cct + texsetcatcode(0x200C,11) -- non-joiner + texsetcatcode(0x200D,11) -- joiner + for c, u in next, csletters do + texsetcatcode(u,11) + end + tex.catcodetable = saved +end + +function characters.setothercatcodes(cct) + if trace_defining then + report_defining("assigning other catcodes to catcode table %a",cct) + end + local saved = tex.catcodetable + tex.catcodetable = cct + for u=65,90 do + texsetcatcode(u,12) + end + for u=97,122 do + texsetcatcode(u,12) + end + tex.catcodetable = saved +end + +function characters.setactivecatcodes(cct) + local saved = tex.catcodetable + tex.catcodetable = cct + for i=1,#activated do + local u = activated[i] + texsetcatcode(u,13) + if trace_defining then + report_defining("character %U (%s) is active in set %a",u,data[u].description,cct) + end + end + tex.catcodetable = saved +end + +--[[ldx-- +<p>Setting the lccodes is also done in a loop over the data table.</p> +--ldx]]-- + +implement { + name = "chardescription", + arguments = "integer", + actions = function(slot) + local d = data[slot] + if d then + context(d.description) + end + end, +} + +if characters.setcharacternames then -- only in ini mode + + implement { name = "setlettercatcodes", scope = "private", actions = characters.setlettercatcodes, arguments = "integer" } + implement { name = "setothercatcodes", scope = "private", actions = characters.setothercatcodes, arguments = "integer" } + implement { name = "setactivecatcodes", scope = "private", actions = characters.setactivecatcodes, arguments = "integer" } + implement { name = "setcharacternames", scope = "private", actions = characters.setcharacternames, arguments = "integer" } + +end + +-- experiment (some can move to char-ini.lua) + +local function overload(c,u,code,codes) + local c = tonumber(c) + if not c then + return + end + local u = utilities.parsers.settings_to_array(u) + local n = #u + if n == 0 then + return + end + local t = nil + if n == 1 then + t = tonumber(u[1]) + else + t = { } + for i=1,n do + t[#t+1] = tonumber(u[i]) + end + end + if t then + data[c][code] = t + characters[codes][c] = nil + end +end + +interfaces.implement { + name = "overloaduppercase", + arguments = "2 strings", + actions = function(c,u) + overload(c,u,"uccode","uccodes") + end +} + +interfaces.implement { + name = "overloadlowercase", + arguments = "2 strings", + actions = function(c,u) + overload(c,u,"lccode","lccodes") + end +} diff --git a/tex/context/base/mkxl/cont-new.mkxl b/tex/context/base/mkxl/cont-new.mkxl index 298dbc016..c3af4c00f 100644 --- a/tex/context/base/mkxl/cont-new.mkxl +++ b/tex/context/base/mkxl/cont-new.mkxl @@ -13,7 +13,7 @@ % \normalend % uncomment this to get the real base runtime -\newcontextversion{2020.12.10 22:23} +\newcontextversion{2020.12.15 10:10} %D This file is loaded at runtime, thereby providing an excellent place for hacks, %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkxl/context.mkxl b/tex/context/base/mkxl/context.mkxl index 238271819..39a9490a9 100644 --- a/tex/context/base/mkxl/context.mkxl +++ b/tex/context/base/mkxl/context.mkxl @@ -29,7 +29,7 @@ %D {YYYY.MM.DD HH:MM} format. \immutable\edef\contextformat {\jobname} -\immutable\edef\contextversion{2020.12.10 22:23} +\immutable\edef\contextversion{2020.12.15 10:10} %overloadmode 1 % check frozen / warning %overloadmode 2 % check frozen / error @@ -97,7 +97,6 @@ \loadmkxlfile{catc-ini} \loadmkxlfile{catc-act} \loadmkxlfile{catc-def} -\loadmkxlfile{catc-ctx} \loadmkxlfile{catc-sym} \loadmkxlfile{toks-ini} diff --git a/tex/context/base/mkxl/font-fbk.lmt b/tex/context/base/mkxl/font-fbk.lmt index 0e104aca7..651bca331 100644 --- a/tex/context/base/mkxl/font-fbk.lmt +++ b/tex/context/base/mkxl/font-fbk.lmt @@ -189,7 +189,7 @@ local function composecharacters(tfmdata) chr_t, } end -t.depth = a_ury + t.depth = a_ury elseif c_ury > a_lly then -- messy test local dy if compose then @@ -220,7 +220,7 @@ t.depth = a_ury else dy = - deltaxheight + extraxheight end -t.height = a_ury-dy + t.height = a_ury-dy local right = rightcommand[dx+dd] local down = downcommand[dy] if trace_visualize then @@ -247,7 +247,7 @@ t.height = a_ury-dy chr_t, } end -t.height = a_ury + t.height = a_ury end end else @@ -316,31 +316,25 @@ registerotffeature { description = 'unicode compat specials to ligatures', } -do +-- We now provide the composer in the helpers namespace (too): + +fonts.helpers.composecharacters = composecharacters + +-- We keep this just because we have a few demos but there often are other ways to achieve +-- the same. This code installs the builder into the regular virtual font builder, which +-- only makes sense as demo. It also shows a bit what the evolution is. - -- This installs the builder into the regular virtual font builder, - -- which only makes sense as demo. +do local vf = handlers.vf local commands = vf.combiner.commands vf.helpers.composecharacters = composecharacters - commands["compose.trace.enable"] = function() - trace_visualize = true - end - - commands["compose.trace.disable"] = function() - trace_visualize = false - end - - commands["compose.force.enable"] = function() - force_combining = true - end - - commands["compose.force.disable"] = function() - force_combining = false - end + commands["compose.trace.enable"] = function() trace_visualize = true end + commands["compose.trace.disable"] = function() trace_visualize = false end + commands["compose.force.enable"] = function() force_combining = true end + commands["compose.force.disable"] = function() force_combining = false end commands["compose.trace.set"] = function(g,v) if v[2] == nil then diff --git a/tex/context/base/mkxl/font-ini.lmt b/tex/context/base/mkxl/font-ini.lmt new file mode 100644 index 000000000..45e8f9e18 --- /dev/null +++ b/tex/context/base/mkxl/font-ini.lmt @@ -0,0 +1,40 @@ +if not modules then modules = { } end modules ['font-ini'] = { + 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" +} + +--[[ldx-- +<p>Not much is happening here.</p> +--ldx]]-- + +local allocate = utilities.storage.allocate +local sortedhash = table.sortedhash + +fonts = fonts or { } +local fonts = fonts + +local identifiers = allocate() + +fonts.hashes = fonts.hashes or { identifiers = identifiers } +fonts.tables = fonts.tables or { } +fonts.helpers = fonts.helpers or { } +fonts.tracers = fonts.tracers or { } -- for the moment till we have move to moduledata +fonts.specifiers = fonts.specifiers or { } -- in format ! + +fonts.analyzers = { } -- not needed here +fonts.readers = { } +fonts.definers = { methods = { } } +fonts.loggers = { register = function() end } + +fonts.privateoffsets = { + textbase = 0xF0000, -- used for hidden (opentype features) + textextrabase = 0xFD000, -- used for visible by name + mathextrabase = 0xFE000, -- used for visible by code + mathbase = 0xFF000, -- used for hidden (virtual math) + keepnames = false, -- when set to true names are always kept (not for context) +} + +-- Also here now: diff --git a/tex/context/base/mkxl/font-lib.mklx b/tex/context/base/mkxl/font-lib.mklx index b6daca3c7..1d7d4d1e5 100644 --- a/tex/context/base/mkxl/font-lib.mklx +++ b/tex/context/base/mkxl/font-lib.mklx @@ -15,7 +15,7 @@ \unprotect -\registerctxluafile{font-ini}{} +\registerctxluafile{font-ini}{autosuffix} \registerctxluafile{font-log}{} \registerctxluafile{font-con}{autosuffix} %registerctxluafile{font-cft}{} @@ -33,7 +33,8 @@ \registerctxluafile{font-ttf}{optimize} % cubic outlines \registerctxluafile{font-dsp}{optimize} % ... for this one \registerctxluafile{font-hsh}{} % hashes used by context -\registerctxluafile{font-vfc}{} +\registerctxluafile{font-vir}{} +\registerctxluafile{font-vfc}{autosuffix} \registerctxluafile{font-prv}{} % needs hashes \registerctxluafile{font-nod}{optimize} \registerctxluafile{font-oti}{} % otf initialization @@ -81,7 +82,6 @@ \registerctxluafile{font-lua}{} -\registerctxluafile{font-vir}{} \registerctxluafile{font-enh}{autosuffix} \registerctxluafile{good-ini}{} diff --git a/tex/context/base/mkxl/font-vfc.lmt b/tex/context/base/mkxl/font-vfc.lmt new file mode 100644 index 000000000..e39eca683 --- /dev/null +++ b/tex/context/base/mkxl/font-vfc.lmt @@ -0,0 +1,83 @@ +if not modules then modules = { } end modules ['font-vfc'] = { + version = 1.001, + comment = "companion to font-ini.mkiv and hand-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local select, type = select, type +local insert = table.insert + +local fonts = fonts +local helpers = fonts.helpers + +local setmetatableindex = table.setmetatableindex +----- makeweak = table.makeweak + +-- Helpers dealing with virtual fonts: beware, these are final values so +-- don't change the content of tables fetched from here! + +local push = { "push" } +local pop = { "pop" } +local dummy = { "comment" } + +function helpers.prependcommands(commands,...) + insert(commands,1,push) + for i=select("#",...),1,-1 do + local s = (select(i,...)) + if s then + insert(commands,1,s) + end + end + insert(commands,pop) + return commands +end + +function helpers.appendcommands(commands,...) + insert(commands,1,push) + insert(commands,pop) + for i=1,select("#",...) do + local s = (select(i,...)) + if s then + insert(commands,s) + end + end + return commands +end + +function helpers.prependcommandtable(commands,t) + insert(commands,1,push) + for i=#t,1,-1 do + local s = t[i] + if s then + insert(commands,1,s) + end + end + insert(commands,pop) + return commands +end + +function helpers.appendcommandtable(commands,t) + insert(commands,1,push) + insert(commands,pop) + for i=1,#t do + local s = t[i] + if s then + insert(commands,s) + end + end + return commands +end + +helpers.commands = utilities.storage.allocate { + char = setmetatableindex(function(t,k) local v = { "slot", 0, k } t[k] = v return v end), + right = setmetatableindex(function(t,k) local v = { "right", k } t[k] = v return v end), + left = setmetatableindex(function(t,k) local v = { "right", -k } t[k] = v return v end), -- todo: left + down = setmetatableindex(function(t,k) local v = { "down", k } t[k] = v return v end), + up = setmetatableindex(function(t,k) local v = { "down", -k } t[k] = v return v end), -- todo: up + push = push, + pop = pop, + dummy = dummy, +} + diff --git a/tex/context/base/mkxl/font-vir.lmt b/tex/context/base/mkxl/font-vir.lmt new file mode 100644 index 000000000..f60639d6a --- /dev/null +++ b/tex/context/base/mkxl/font-vir.lmt @@ -0,0 +1,159 @@ +if not modules then modules = { } end modules ['font-vir'] = { + 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" +} + +-- This is actually very experimental code but it has been so since we began with +-- \LUATEX\ so \unknown. We keep it around because it's not that much code and +-- because we have some demo's (in the test suite) that use it. + +local next, setmetatable, getmetatable = next, setmetatable, getmetatable + +local allocate = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex +local fastcopy = table.fastcopy + +local fonts = fonts +local constructors = fonts.constructors +local vf = constructors.handlers.vf +vf.version = 1.000 -- same as tfm + +local definers = fonts.definers +local methods = definers.methods + +local variants = allocate() +local combinations = { } +local combiner = { } +local whatever = allocate() +local helpers = allocate() +local predefined = fonts.helpers.commands + +methods.variants = variants -- todo .. wrong namespace +vf.combinations = combinations +vf.combiner = combiner +vf.whatever = whatever +vf.helpers = helpers +vf.predefined = predefined + +setmetatableindex(whatever, function(t,k) local v = { } t[k] = v return v end) + +local function checkparameters(g,f) + if f and g and not g.parameters and #g.fonts > 0 then + local p = { } + for k,v in next, f.parameters do + p[k] = v + end + g.parameters = p + setmetatable(p, getmetatable(f.parameters)) + end +end + +function methods.install(tag, rules) + vf.combinations[tag] = rules + variants[tag] = function(specification) + return vf.combine(specification,tag) + end +end + +local function combine_load(g,name) + return constructors.readanddefine(name or g.specification.name,g.specification.size) +end + +local function combine_assign(g, name, from, to, start, force) + local f, id = combine_load(g,name) + if f and id then + -- optimize for whole range, then just g = f + if not from then from, to = 0, 0xFF00 end + if not to then to = from end + if not start then start = from end + local fc = f.characters + local gc = g.characters + local fd = f.descriptions + local gd = g.descriptions + local hn = #g.fonts+1 + g.fonts[hn] = { id = id } -- no need to be sparse + for i=from,to do + if fc[i] and (force or not gc[i]) then + gc[i] = fastcopy(fc[i],true) -- can be optimized + gc[i].commands = { { "slot", hn, start } } + gd[i] = fd[i] + end + start = start + 1 + end + checkparameters(g,f) + end +end + +local function combine_process(g,list) + if list then + for _,v in next, list do + (combiner.commands[v[1]] or nop)(g,v) + end + end +end + +local function combine_names(g,name,force) + local f, id = constructors.readanddefine(name,g.specification.size) + if f and id then + local fc = f.characters + local gc = g.characters + local fd = f.descriptions + local gd = g.descriptions + g.fonts[#g.fonts+1] = { id = id } -- no need to be sparse + local hn = #g.fonts + for k, v in next, fc do + if force or not gc[k] then + gc[k] = fastcopy(v,true) + gc[k].commands = { { "slot", hn, k } } + gd[i] = fd[i] + end + end + checkparameters(g,f) + end +end + +local combine_feature = function(g,v) + local key = v[2] + local value = v[3] + if key then + if value == nil then + value = true + end + local specification = g.specification + if specification then + local normalfeatures = specification.features.normal + if normalfeatures then + normalfeatures[key] = value -- otf? + end + end + end +end + +combiner.commands = allocate { + ["initialize"] = function(g,v) combine_assign (g,g.properties.name) end, + ["include-method"] = function(g,v) combine_process (g,combinations[v[2]]) end, -- name + -- ["copy-parameters"] = function(g,v) combine_parameters(g,v[2]) end, -- name + ["copy-range"] = function(g,v) combine_assign (g,v[2],v[3],v[4],v[5],true) end, -- name, from-start, from-end, to-start + ["copy-char"] = function(g,v) combine_assign (g,v[2],v[3],v[3],v[4],true) end, -- name, from, to + ["fallback-range"] = function(g,v) combine_assign (g,v[2],v[3],v[4],v[5],false) end, -- name, from-start, from-end, to-start + ["fallback-char"] = function(g,v) combine_assign (g,v[2],v[3],v[3],v[4],false) end, -- name, from, to + ["copy-names"] = function(g,v) combine_names (g,v[2],true) end, + ["fallback-names"] = function(g,v) combine_names (g,v[2],false) end, + ["feature"] = combine_feature, +} + +function vf.combine(specification,tag) + local g = { + name = specification.name, + properties = { }, + fonts = { }, + characters = { }, + descriptions = { }, + specification = fastcopy(specification), + } + combine_process(g,combinations[tag]) + return g +end diff --git a/tex/context/base/mkxl/grph-inc.lmt b/tex/context/base/mkxl/grph-inc.lmt new file mode 100644 index 000000000..4fa562cb8 --- /dev/null +++ b/tex/context/base/mkxl/grph-inc.lmt @@ -0,0 +1,2160 @@ +if not modules then modules = { } end modules ['grph-inc'] = { + version = 1.001, + comment = "companion to grph-inc.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- todo: in pdfe: pdfe.copyappearance(document,objnum) +-- +-- local im = createimage { filename = fullname } +-- local on = images.flushobject(im,document.__xrefs__[AP]) + +-- todo: files are sometimes located twice +-- todo: empty filename or only suffix always false (not found) +-- lowercase types +-- mps tex tmp svg +-- partly qualified +-- dimensions +-- use metatables +-- figures.boxnumber can go as we now can use names +-- avoid push +-- move some to command namespace + +--[[ +The ConTeXt figure inclusion mechanisms are among the oldest code +in ConTeXt and evolved into a complex whole. One reason is that we +deal with backend in an abstract way. What complicates matters is +that we deal with internal graphics as well: TeX code, MetaPost code, +etc. Later on figure databases were introduced, which resulted in +a plug in model for locating images. On top of that runs a conversion +mechanism (with caching) and resource logging. + +Porting that to Lua is not that trivial because quite some +status information is kept between al these stages. Of course, image +reuse also has some price, and so I decided to implement the graphics +inclusion in several layers: detection, loading, inclusion, etc. + +Object sharing and scaling can happen at each stage, depending on the +way the resource is dealt with. + +The TeX-Lua mix is suboptimal. This has to do with the fact that we cannot +run TeX code from within Lua. Some more functionality will move to Lua. +]]-- + +-- todo: store loaded pages per pdf file someplace + +local tonumber, tostring, next, unpack = tonumber, tostring, next, unpack +local format, lower, find, match, gsub = string.format, string.lower, string.find, string.match, string.gsub +local longtostring = string.longtostring +local contains = table.contains +local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys +local concat, insert, remove = table.concat, table.insert, table.remove +local todimen = string.todimen +local collapsepath = file.collapsepath +local formatters = string.formatters +local odd = math.odd +local isfile, isdir, modificationtime = lfs.isfile, lfs.isdir, lfs.modification + +local P, R, S, Cc, C, Cs, Ct, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cc, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.match + +local settings_to_array = utilities.parsers.settings_to_array +local settings_to_hash = utilities.parsers.settings_to_hash +local allocate = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex +local replacetemplate = utilities.templates.replace + +local bpfactor = number.dimenfactors.bp + +images = images or { } +local images = images + +local hasscheme = url.hasscheme +local urlhashed = url.hashed + +local resolveprefix = resolvers.resolve + +local texgetbox = tex.getbox +local texsetbox = tex.setbox + +local hpack = node.hpack + +local new_latelua = nodes.pool.latelua + +local context = context + +local implement = interfaces.implement +local variables = interfaces.variables + +local codeinjections = backends.codeinjections +local nodeinjections = backends.nodeinjections + +local trace_figures = false trackers.register ("graphics.locating", function(v) trace_figures = v end) +local trace_bases = false trackers.register ("graphics.bases", function(v) trace_bases = v end) +local trace_programs = false trackers.register ("graphics.programs", function(v) trace_programs = v end) +local trace_conversion = false trackers.register ("graphics.conversion", function(v) trace_conversion = v end) +local trace_inclusion = false trackers.register ("graphics.inclusion", function(v) trace_inclusion = v end) +local trace_usage = false trackers.register ("graphics.usage", function(v) trace_usage = v end) + +local extra_check = false directives.register("graphics.extracheck", function(v) extra_check = v end) +local auto_transform = true directives.register("graphics.autotransform", function(v) auto_transform = v end) + +local report = logs.reporter("graphics") +local report_inclusion = logs.reporter("graphics","inclusion") + +local f_hash_part = formatters["%s->%s->%s->%s"] +local f_hash_full = formatters["%s->%s->%s->%s->%s->%s->%s->%s"] + +local v_yes = variables.yes +local v_global = variables["global"] +local v_local = variables["local"] +local v_default = variables.default +local v_auto = variables.auto + +local maxdimen = tex.magicconstants.maxdimen -- 0x3FFFFFFF -- 2^30-1 + +local ctx_doscalefigure = context.doscalefigure +local ctx_relocateexternalfigure = context.relocateexternalfigure +local ctx_startfoundexternalfigure = context.startfoundexternalfigure +local ctx_stopfoundexternalfigure = context.stopfoundexternalfigure +local ctx_dosetfigureobject = context.dosetfigureobject +local ctx_doboxfigureobject = context.doboxfigureobject + +-- extensions + +function checkimage(figure) + if figure then + local width = figure.width or 0 + local height = figure.height or 0 + if width <= 0 or height <= 0 then + report_inclusion("image %a has bad dimensions (%p,%p), discarding",figure.filename or "?",width,height) + return false, "bad dimensions" + end + -- local xres = figure.xres + -- local yres = figure.yres + local changes = false + if height > width then + if height > maxdimen then + figure.height = maxdimen + figure.width = width * maxdimen/height + changed = true + end + elseif width > maxdimen then + figure.width = maxdimen + figure.height = height * maxdimen/width + changed = true + end + if changed then + report_inclusion("limiting natural dimensions of %a, old %p * %p, new %p * %p", + figure.filename,width,height,figure.width,figure.height) + end + if width >= maxdimen or height >= maxdimen then + report_inclusion("image %a is too large (%p,%p), discarding", + figure.filename,width,height) + return false, "dimensions too large" + end + return figure + end +end + +-- This is a bit of abstraction. Fro a while we will follow the luatex image +-- model. + +local imagekeys = { + -- only relevant ones (permitted in luatex) + "width", "height", "depth", "bbox", + "colordepth", "colorspace", + "filename", "filepath", "visiblefilename", + "imagetype", "stream", + "index", "objnum", + "pagebox", "page", "pages", + "rotation", "transform", + "xsize", "ysize", "xres", "yres", +} + +local imagesizes = { + art = true, bleed = true, crop = true, + media = true, none = true, trim = true, +} + +local imagetypes = { [0] = + "none", + "pdf", "png", "jpg", "jp2", "jbig2", + "stream", +} + +imagetypes = table.swapped(imagetypes,imagetypes) + +images.keys = imagekeys +images.types = imagetypes +images.sizes = imagesizes + +local function createimage(specification) + return backends.codeinjections.newimage(specification) +end + +local function copyimage(specification) + return backends.codeinjections.copyimage(specification) +end + +local function scanimage(specification) + return backends.codeinjections.scanimage(specification) +end + +local function embedimage(specification) + -- write the image to file + return backends.codeinjections.embedimage(specification) +end + +local function wrapimage(specification) + -- create an image rule + return backends.codeinjections.wrapimage(specification) +end + +images.create = createimage +images.scan = scanimage +images.copy = copyimage +images.wrap = wrapimage +images.embed = embedimage + +-- If really needed we can provide: +-- +-- img = { +-- new = createimage, +-- scan = scanimage, +-- copy = copyimage, +-- node = wrapimage, +-- write = function(specification) context(wrapimage(specification)) end, +-- immediatewrite = embedimage, +-- immediatewriteobject = function() end, -- not upported, experimental anyway +-- boxes = function() return sortedkeys(imagesizes) end, +-- fields = function() return imagekeys end, +-- types = function() return { unpack(imagetypes,0,#imagetypes) } end, +-- } + +-- end of copies / mapping + +local function imagetotable(imgtable) + if type(imgtable) == "table" then + return copy(imgtable) + end + local result = { } + for k=1,#imagekeys do + local key = imagekeys[k] + result[key] = imgtable[key] + end + return result +end + +function images.serialize(i,...) + return table.serialize(imagetotable(i),...) +end + +function images.print(i,...) + return table.print(imagetotable(i),...) +end + +local function checkimagesize(size) + if size then + size = gsub(size,"box","") + return imagesizes[size] and size or "crop" + else + return "crop" + end +end + +images.check = checkimage +images.checksize = checkimagesize +images.totable = imagetotable + +-- local indexed = { } +-- +-- function images.ofindex(n) +-- return indexed[n] +-- end + +--- we can consider an grph-ini file + +figures = figures or { } +local figures = figures + +figures.images = images +figures.boxnumber = figures.boxnumber or 0 +figures.defaultsearch = true +figures.defaultwidth = 0 +figures.defaultheight = 0 +figures.defaultdepth = 0 +figures.nofprocessed = 0 +figures.nofmissing = 0 +figures.preferquality = true -- quality over location + +local figures_loaded = allocate() figures.loaded = figures_loaded +local figures_used = allocate() figures.used = figures_used +local figures_found = allocate() figures.found = figures_found +local figures_suffixes = allocate() figures.suffixes = figures_suffixes +local figures_patterns = allocate() figures.patterns = figures_patterns +local figures_resources = allocate() figures.resources = figures_resources + +local existers = allocate() figures.existers = existers +local checkers = allocate() figures.checkers = checkers +local includers = allocate() figures.includers = includers +local remappers = allocate() figures.remappers = remappers +local converters = allocate() figures.converters = converters +local identifiers = allocate() figures.identifiers = identifiers +local programs = allocate() figures.programs = programs + +local defaultformat = "pdf" +local defaultprefix = "m_k_i_v_" + +figures.localpaths = allocate { + ".", "..", "../.." +} + +figures.cachepaths = allocate { + prefix = "", + path = ".", + subpath = ".", +} + +local figure_paths = allocate(table.copy(figures.localpaths)) +figures.paths = figure_paths + +local figures_order = allocate { + "pdf", "mps", "jpg", "png", "jp2", "jbig", "svg", "eps", "tif", "gif", "mov", "buffer", "tex", "cld", "auto", +} + +local figures_formats = allocate { -- magic and order will move here + ["pdf"] = { list = { "pdf" } }, + ["mps"] = { patterns = { "^mps$", "^%d+$" } }, -- we need to anchor + ["jpg"] = { list = { "jpg", "jpeg" } }, + ["png"] = { list = { "png" } }, + ["jp2"] = { list = { "jp2" } }, + ["jbig"] = { list = { "jbig", "jbig2", "jb2" } }, + ["svg"] = { list = { "svg", "svgz" } }, + ["eps"] = { list = { "eps", "ai" } }, + ["gif"] = { list = { "gif" } }, + ["tif"] = { list = { "tif", "tiff" } }, + ["mov"] = { list = { "mov", "flv", "mp4" } }, -- "avi" is not supported + ["buffer"] = { list = { "tmp", "buffer", "buf" } }, + ["tex"] = { list = { "tex" } }, + ["cld"] = { list = { "cld" } }, + ["auto"] = { list = { "auto" } }, +} + +local figures_magics = allocate { + { format = "png", pattern = P("\137PNG\013\010\026\010") }, -- 89 50 4E 47 0D 0A 1A 0A, + { format = "jpg", pattern = P("\255\216\255") }, -- FF D8 FF + { format = "jp2", pattern = P("\000\000\000\012\106\080\032\032\013\010"), }, -- 00 00 00 0C 6A 50 20 20 0D 0A }, + { format = "gif", pattern = P("GIF") }, + { format = "pdf", pattern = (1 - P("%PDF"))^0 * P("%PDF") }, +} + +local figures_native = allocate { + pdf = true, + jpg = true, + jp2 = true, + png = true, +} + +figures.formats = figures_formats -- frozen +figures.magics = figures_magics -- frozen +figures.order = figures_order -- frozen + +-- name checker + +local okay = P("m_k_i_v_") + +local pattern = (R("az","AZ") * P(":"))^-1 * ( -- a-z : | A-Z : + (okay + R("az","09") + S("_/") - P("_")^2)^1 * (P(".") * R("az")^1)^0 * P(-1) + -- a-z | single _ | / + (okay + R("az","09") + S("-/") - P("-")^2)^1 * (P(".") * R("az")^1)^0 * P(-1) + -- a-z | single - | / + (okay + R("AZ","09") + S("_/") - P("_")^2)^1 * (P(".") * R("AZ")^1)^0 * P(-1) + -- A-Z | single _ | / + (okay + R("AZ","09") + S("-/") - P("-")^2)^1 * (P(".") * R("AZ")^1)^0 * P(-1) -- A-Z | single - | / +) * Cc(false) + Cc(true) + +function figures.badname(name) + if not name then + -- bad anyway + elseif not hasscheme(name) then + return lpegmatch(pattern,name) + else + return lpegmatch(pattern,file.basename(name)) + end +end + +logs.registerfinalactions(function() + local done = false + if trace_usage and figures.nofprocessed > 0 then + logs.startfilelogging(report,"names") + for _, data in sortedhash(figures_found) do + if done then + report() + else + done = true + end + report("asked : %s",data.askedname) + if data.found then + report("format : %s",data.format) + report("found : %s",data.foundname) + report("used : %s",data.fullname) + if data.badname then + report("comment : %s","bad name") + elseif data.comment then + report("comment : %s",data.comment) + end + else + report("comment : %s","not found") + end + end + logs.stopfilelogging() + end + if figures.nofmissing > 0 and logs.loggingerrors() then + logs.starterrorlogging(report,"missing figures") + for _, data in sortedhash(figures_found) do + report("%w%s",6,data.askedname) + end + logs.stoperrorlogging() + end +end) + +-- We can set the order but only indirectly so that we can check for support. + +function figures.setorder(list) -- can be table or string + if type(list) == "string" then + list = settings_to_array(list) + end + if list and #list > 0 then + figures_order = allocate() + figures.order = figures_order + local done = { } -- just to be sure in case the list is generated + for i=1,#list do + local l = lower(list[i]) + if figures_formats[l] and not done[l] then + figures_order[#figures_order+1] = l + done[l] = true + end + end + report_inclusion("lookup order % a",figures_order) + else + -- invalid list + end +end + +local function guessfromstring(str) + if str then + for i=1,#figures_magics do + local pattern = figures_magics[i] + if lpegmatch(pattern.pattern,str) then + local format = pattern.format + if trace_figures then + report_inclusion("file %a has format %a",filename,format) + end + return format + end + end + end +end + +figures.guessfromstring = guessfromstring + +function figures.guess(filename) + local f = io.open(filename,'rb') + if f then + local str = f:read(100) + f:close() + if str then + return guessfromstring(str) + end + end +end + +local function setlookups() -- tobe redone .. just set locals + figures_suffixes = allocate() + figures_patterns = allocate() + for _, format in next, figures_order do + local data = figures_formats[format] + local list = data.list + if list then + for i=1,#list do + figures_suffixes[list[i]] = format -- hash + end + else + figures_suffixes[format] = format + end + local patterns = data.patterns + if patterns then + for i=1,#patterns do + figures_patterns[#figures_patterns+1] = { patterns[i], format } -- array + end + end + end + figures.suffixes = figures_suffixes + figures.patterns = figures_patterns +end + +setlookups() + +figures.setlookups = setlookups + +function figures.registerresource(t) + local n = #figures_resources + 1 + figures_resources[n] = t + return n +end + +local function register(tag,what,target) + local data = figures_formats[target] -- resolver etc + if not data then + data = { } + figures_formats[target] = data + end + local d = data[tag] -- list or pattern + if d and not contains(d,what) then + d[#d+1] = what -- suffix or patternspec + else + data[tag] = { what } + end + if not contains(figures_order,target) then + figures_order[#figures_order+1] = target + end + setlookups() +end + +function figures.registersuffix (suffix, target) register('list',suffix,target) end +function figures.registerpattern(pattern,target) register('pattern',pattern,target) end + +implement { name = "registerfiguresuffix", actions = register, arguments = { "'list'", "string", "string" } } +implement { name = "registerfigurepattern", actions = register, arguments = { "'pattern'", "string", "string" } } + +local last_locationset = last_locationset or nil +local last_pathlist = last_pathlist or nil + +function figures.setpaths(locationset,pathlist) + if last_locationset == locationset and last_pathlist == pathlist then + -- this function can be called each graphic so we provide this optimization + return + end + local t, h = figure_paths, settings_to_hash(locationset) + if last_locationset ~= locationset then + -- change == reset (actually, a 'reset' would indeed reset + if h[v_local] then + t = table.fastcopy(figures.localpaths or { }) + else + t = { } + end + figures.defaultsearch = h[v_default] + last_locationset = locationset + end + if h[v_global] then + local list = settings_to_array(pathlist) + for i=1,#list do + local s = list[i] + if not contains(t,s) then + t[#t+1] = s + end + end + end + figure_paths = t + last_pathlist = pathlist + figures.paths = figure_paths + if trace_figures then + report_inclusion("using locations %a",last_locationset) + report_inclusion("using paths % a",figure_paths) + end +end + +implement { name = "setfigurepaths", actions = figures.setpaths, arguments = "2 strings" } + +-- check conversions and handle it here + +function figures.hash(data) + local status = data and data.status + return (status and status.hash or tostring(status.private)) or "nohash" -- the <img object> +end + +-- interfacing to tex + +local function new() -- we could use metatables status -> used -> request but it needs testing + local request = { + name = false, + label = false, + format = false, + page = false, + width = false, + height = false, + preview = false, + ["repeat"] = false, + controls = false, + display = false, + mask = false, + conversion = false, + resolution = false, + color = false, + arguments = false, + cache = false, + prefix = false, + size = false, + } + local used = { + fullname = false, + format = false, + name = false, + path = false, + suffix = false, + width = false, + height = false, + } + local status = { + status = 0, + converted = false, + cached = false, + fullname = false, + format = false, + } + -- this needs checking because we might check for nil, the test case + -- is getfiguredimensions which then should return ~= 0 + -- setmetatableindex(status, used) + -- setmetatableindex(used, request) + return { + request = request, + used = used, + status = status, + } +end + +-- use table.insert|remove + +local lastfiguredata = nil -- will be topofstack or last so no { } (else problems with getfiguredimensions) +local callstack = { } + +function figures.initialize(request) + local figuredata = new() + if request then + -- request.width/height are strings and are only used when no natural dimensions + -- can be determined; at some point the handlers might set them to numbers instead + local w = tonumber(request.width) or 0 + local h = tonumber(request.height) or 0 + local p = tonumber(request.page) or 0 + request.width = w > 0 and w or nil + request.height = h > 0 and h or nil + -- + request.page = p > 0 and p or 1 + request.keepopen = p > 0 + request.size = checkimagesize(request.size) + request.object = request.object == v_yes + request["repeat"] = request["repeat"] == v_yes + request.preview = request.preview == v_yes + request.cache = request.cache ~= "" and request.cache + request.prefix = request.prefix ~= "" and request.prefix + request.format = request.format ~= "" and request.format + request.compact = request.compact == v_yes + table.merge(figuredata.request,request) + end + return figuredata +end + +function figures.push(request) + statistics.starttiming(figures) + local figuredata = figures.initialize(request) -- we could use table.sparse but we set them later anyway + insert(callstack,figuredata) + lastfiguredata = figuredata + return figuredata +end + +function figures.pop() + remove(callstack) + lastfiguredata = callstack[#callstack] or lastfiguredata + statistics.stoptiming(figures) +end + +function figures.current() + return callstack[#callstack] or lastfiguredata +end + +local function get(category,tag,default) + local value = lastfiguredata and lastfiguredata[category] + value = value and value[tag] + if not value or value == "" or value == true then + return default or "" + else + return value + end +end + +local function setdimensions(box) + local status = lastfiguredata and lastfiguredata.status + local used = lastfiguredata and lastfiguredata.used + if status and used then + local b = texgetbox(box) + local w = b.width + local h = b.height + b.depth + status.width = w + status.height = h + used.width = w + used.height = h + status.status = 10 + end +end + +figures.get = get +figures.set = setdimensions + +implement { name = "figurestatus", actions = { get, context }, arguments = { "'status'", "string", "string" } } +implement { name = "figurerequest", actions = { get, context }, arguments = { "'request'", "string", "string" } } +implement { name = "figureused", actions = { get, context }, arguments = { "'used'", "string", "string" } } + +implement { name = "figurefilepath", public = true, actions = { get, file.dirname, context }, arguments = { "'used'", "'fullname'" } } +implement { name = "figurefilename", public = true, actions = { get, file.nameonly, context }, arguments = { "'used'", "'fullname'" } } +implement { name = "figurefiletype", public = true, actions = { get, file.extname, context }, arguments = { "'used'", "'fullname'" } } + +implement { name = "figuresetdimensions", actions = setdimensions, arguments = "integer" } + +-- todo: local path or cache path + +local function forbiddenname(filename) + if not filename or filename == "" then + return false + end + local expandedfullname = collapsepath(filename,true) + local expandedinputname = collapsepath(file.addsuffix(environment.jobfilename,environment.jobfilesuffix),true) + if expandedfullname == expandedinputname then + report_inclusion("skipping graphic with same name as input filename %a, enforce suffix",expandedinputname) + return true + end + local expandedoutputname = collapsepath(codeinjections.getoutputfilename(),true) + if expandedfullname == expandedoutputname then + report_inclusion("skipping graphic with same name as output filename %a, enforce suffix",expandedoutputname) + return true + end +end + +local function rejected(specification) + if extra_check then + local fullname = specification.fullname + if fullname and figures_native[file.suffix(fullname)] and not figures.guess(fullname) then + specification.comment = "probably a bad file" + specification.found = false + specification.error = true + report_inclusion("file %a looks bad",fullname) + return true + end + end +end + +local function wipe(str) + if str == "" or str == "default" or str == "unknown" then + return nil + else + return str + end +end + +local function register(askedname,specification) + if not specification then + specification = { askedname = askedname, comment = "invalid specification" } + elseif forbiddenname(specification.fullname) then + specification = { askedname = askedname, comment = "forbidden name" } + elseif specification.internal then + -- no filecheck needed + specification.found = true + if trace_figures then + report_inclusion("format %a internally supported by engine",specification.format) + end + elseif not rejected(specification) then + local format = specification.format + if format then + local conversion = wipe(specification.conversion) + local resolution = wipe(specification.resolution) + local arguments = wipe(specification.arguments) + local newformat = conversion + if not newformat or newformat == "" then + newformat = defaultformat + end + if trace_conversion then + report_inclusion("checking conversion of %a, fullname %a, old format %a, new format %a, conversion %a, resolution %a, arguments %a", + askedname, + specification.fullname, + format, + newformat, + conversion or "default", + resolution or "default", + arguments or "" + ) + end + -- begin of quick hack + local remapper = remappers[format] + if remapper then + remapper = remapper[conversion] + if remapper then + specification = remapper(specification) or specification + format = specification.format + newformat = format + conversion = nil + end + end + -- end of quick hack + local converter = (not remapper) and (newformat ~= format or resolution or arguments) and converters[format] + if converter then + local okay = converter[newformat] + if okay then + converter = okay + else + newformat = defaultformat + converter = converter[newformat] + end + elseif trace_conversion then + report_inclusion("no converter for %a to %a",format,newformat) + end + if converter then + -- todo: make this a function + -- + -- todo: outline as helper function + -- + local oldname = specification.fullname + local newpath = file.dirname(oldname) + local oldbase = file.basename(oldname) + -- + -- problem: we can have weird filenames, like a.b.c (no suffix) and a.b.c.gif + -- so we cannot safely remove a suffix (unless we do that for known suffixes) + -- + -- local newbase = file.removesuffix(oldbase) -- assumes a known suffix + -- + -- so we now have (also see *): + -- + local newbase = oldbase + -- + local fc = specification.cache or figures.cachepaths.path + if fc and fc ~= "" and fc ~= "." then + newpath = gsub(fc,"%*",newpath) -- so cachedir can be "/data/cache/*" + else + newbase = defaultprefix .. newbase + end + local subpath = specification.subpath or figures.cachepaths.subpath + if subpath and subpath ~= "" and subpath ~= "." then + newpath = newpath .. "/" .. subpath + end + if not isdir(newpath) then + dir.makedirs(newpath) + if not file.is_writable(newpath) then + if trace_conversion then + report_inclusion("path %a is not writable, forcing conversion path %a",newpath,".") + end + newpath = "." + end + end + local prefix = specification.prefix or figures.cachepaths.prefix + if prefix and prefix ~= "" then + newbase = prefix .. newbase + end + local hash = "" + if resolution then + hash = hash .. "[r:" .. resolution .. "]" + end + if arguments then + hash = hash .. "[a:" .. arguments .. "]" + end + if hash ~= "" then + newbase = newbase .. "_" .. md5.hex(hash) + end + -- + -- see *, we had: + -- + -- local newbase = file.addsuffix(newbase,newformat) + -- + -- but now have (result of Aditya's web image testing): + -- + -- as a side effect we can now have multiple fetches with different + -- original figures_formats, not that it matters much (apart from older conversions + -- sticking around) + -- + local newbase = newbase .. "." .. newformat + local newname = file.join(newpath,newbase) + oldname = collapsepath(oldname) + newname = collapsepath(newname) + local oldtime = modificationtime(oldname) or 0 + local newtime = modificationtime(newname) or 0 + if newtime == 0 or oldtime > newtime then + if trace_conversion then + report_inclusion("converting %a (%a) from %a to %a",askedname,oldname,format,newformat) + end + converter(oldname,newname,resolution or "", arguments or "") + else + if trace_conversion then + report_inclusion("no need to convert %a (%a) from %a to %a",askedname,oldname,format,newformat) + end + end + if io.exists(newname) and io.size(newname) > 0 then + specification.foundname = oldname + specification.fullname = newname + specification.prefix = prefix + specification.subpath = subpath + specification.converted = true + format = newformat + if not figures_suffixes[format] then + -- maybe the new format is lowres.png (saves entry in suffixes) + -- so let's do this extra check + local suffix = file.suffix(newformat) + if figures_suffixes[suffix] then + if trace_figures then + report_inclusion("using suffix %a as format for %a",suffix,format) + end + format = suffix + end + end + specification.format = format + elseif io.exists(oldname) then + report_inclusion("file %a is bugged",oldname) + if format and imagetypes[format] then + specification.fullname = oldname + end + specification.converted = false + specification.bugged = true + end + end + end + if format then + local found = figures_suffixes[format] + if not found then + specification.found = false + if trace_figures then + report_inclusion("format %a is not supported",format) + end + elseif imagetypes[format] then + specification.found = true + if trace_figures then + report_inclusion("format %a natively supported by backend",format) + end + else + specification.found = true -- else no foo.1 mps conversion + if trace_figures then + report_inclusion("format %a supported by output file format",format) + end + end + else + specification.askedname = askedname + specification.found = false + end + end + if specification.found then + specification.foundname = specification.foundname or specification.fullname + else + specification.foundname = nil + end + specification.badname = figures.badname(askedname) + local askedhash = f_hash_part( + askedname, + specification.conversion or "default", + specification.resolution or "default", + specification.arguments or "" + ) + figures_found[askedhash] = specification + if not specification.found then + figures.nofmissing = figures.nofmissing + 1 + end + return specification +end + +local resolve_too = false -- true + +local internalschemes = { + file = true, + tree = true, + dirfile = true, + dirtree = true, +} + +local function locate(request) -- name, format, cache + -- not resolvers.cleanpath(request.name) as it fails on a!b.pdf and b~c.pdf + -- todo: more restricted cleanpath + local askedname = request.name or "" + local askedcache = request.cache + local askedconversion = request.conversion + local askedresolution = request.resolution + local askedarguments = request.arguments + local askedhash = f_hash_part( + askedname, + askedconversion or "default", + askedresolution or "default", + askedarguments or "" + ) + local foundname = figures_found[askedhash] + if foundname then + return foundname + end + -- + -- + local askedformat = request.format + if not askedformat or askedformat == "" or askedformat == "unknown" then + askedformat = file.suffix(askedname) or "" + elseif askedformat == v_auto then + if trace_figures then + report_inclusion("ignoring suffix of %a",askedname) + end + askedformat = "" + askedname = file.removesuffix(askedname) + end + -- protocol check + local hashed = urlhashed(askedname) + if not hashed then + -- go on + elseif internalschemes[hashed.scheme] then + local path = hashed.path + if path and path ~= "" then + askedname = path + end + else + local foundname = resolvers.findbinfile(askedname) + if not foundname or not isfile(foundname) then -- foundname can be dummy + if trace_figures then + report_inclusion("unknown url %a",askedname) + end + -- url not found + return register(askedname) + end + local guessedformat = figures.guess(foundname) + if askedformat ~= guessedformat then + if trace_figures then + report_inclusion("url %a has unknown format",askedname) + end + -- url found, but wrong format + return register(askedname) + else + if trace_figures then + report_inclusion("url %a is resolved to %a",askedname,foundname) + end + return register(askedname, { + askedname = askedname, + fullname = foundname, + format = askedformat, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + -- we could use the hashed data instead + local askedpath = file.is_rootbased_path(askedname) + local askedbase = file.basename(askedname) + if askedformat ~= "" then + askedformat = lower(askedformat) + if trace_figures then + report_inclusion("forcing format %a",askedformat) + end + local format = figures_suffixes[askedformat] + if not format then + for i=1,#figures_patterns do + local pattern = figures_patterns[i] + if find(askedformat,pattern[1]) then + format = pattern[2] + if trace_figures then + report_inclusion("asked format %a matches %a",askedformat,pattern[1]) + end + break + end + end + end + if format then + local foundname, quitscanning, forcedformat, internal = figures.exists(askedname,format,resolve_too) -- not askedformat + if foundname then + return register(askedname, { + askedname = askedname, + fullname = foundname, -- askedname, + format = forcedformat or format, + cache = askedcache, + -- foundname = foundname, -- no + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + internal = internal, + }) + elseif quitscanning then + return register(askedname) + end + askedformat = format -- new per 2013-08-05 + elseif trace_figures then + report_inclusion("unknown format %a",askedformat) + end + if askedpath then + -- path and type given, todo: strip pieces of path + local foundname, quitscanning, forcedformat = figures.exists(askedname,askedformat,resolve_too) + if foundname then + return register(askedname, { + askedname = askedname, + fullname = foundname, -- askedname, + format = forcedformat or askedformat, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + else + -- type given + for i=1,#figure_paths do + local path = resolveprefix(figure_paths[i]) -- we resolve (e.g. jobfile:) + local check = path .. "/" .. askedname + -- we pass 'true' as it can be an url as well, as the type + -- is given we don't waste much time + local foundname, quitscanning, forcedformat = figures.exists(check,askedformat,resolve_too) + if foundname then + return register(check, { + askedname = askedname, + fullname = foundname, -- check, + format = askedformat, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + if figures.defaultsearch then + local check = resolvers.findfile(askedname) + if check and check ~= "" then + return register(askedname, { + askedname = askedname, + fullname = check, + format = askedformat, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + end + elseif askedpath then + if trace_figures then + report_inclusion("using rootbased path") + end + for i=1,#figures_order do + local format = figures_order[i] + local list = figures_formats[format].list or { format } + for j=1,#list do + local suffix = list[j] + local check = file.addsuffix(askedname,suffix) + local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) + if foundname then + return register(askedname, { + askedname = askedname, + fullname = foundname, -- check, + format = forcedformat or format, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + end + else + if figures.preferquality then + if trace_figures then + report_inclusion("unknown format, quality preferred") + end + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } + for k=1,#list do + local suffix = list[k] + -- local name = file.replacesuffix(askedbase,suffix) + local name = file.replacesuffix(askedname,suffix) + for i=1,#figure_paths do + local path = resolveprefix(figure_paths[i]) -- we resolve (e.g. jobfile:) + local check = path .. "/" .. name + local isfile = internalschemes[urlhashed(check).scheme] + if not isfile then + if trace_figures then + report_inclusion("warning: skipping path %a",path) + end + else + local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) -- true) + if foundname then + return register(askedname, { + askedname = askedname, + fullname = foundname, -- check + format = forcedformat or format, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments + }) + end + end + end + end + end + else -- 'location' + if trace_figures then + report_inclusion("unknown format, using path strategy") + end + for i=1,#figure_paths do + local path = resolveprefix(figure_paths[i]) -- we resolve (e.g. jobfile:) + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } + for k=1,#list do + local suffix = list[k] + local check = path .. "/" .. file.replacesuffix(askedbase,suffix) + local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) + if foundname then + return register(askedname, { + askedname = askedname, + fullname = foudname, -- check, + format = forcedformat or format, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + end + end + end + if figures.defaultsearch then + if trace_figures then + report_inclusion("using default tex path") + end + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } + for k=1,#list do + local suffix = list[k] + local check = resolvers.findfile(file.replacesuffix(askedname,suffix)) + if check and check ~= "" then + return register(askedname, { + askedname = askedname, + fullname = check, + format = format, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) + end + end + end + end + end + return register(askedname, { -- these two are needed for hashing 'found' + conversion = askedconversion, + resolution = askedresolution, + arguments = askedarguments, + }) +end + +-- -- -- plugins -- -- -- + +function identifiers.default(data) + local dr, du, ds = data.request, data.used, data.status + local l = locate(dr) + local foundname = l.foundname + local fullname = l.fullname or foundname + if fullname then + du.format = l.format or false + du.fullname = fullname -- can be cached + ds.fullname = foundname -- original + ds.format = l.format + ds.status = (l.bugged and 0) or (l.found and 10) or 0 + end + return data +end + +function figures.identify(data) + data = data or callstack[#callstack] or lastfiguredata + if data then + local list = identifiers.list -- defined at the end + for i=1,#list do + local identifier = list[i] + local data = identifier(data) +-- if data and (not data.status and data.status.status > 0) then + if data and (not data.status and data.status.status > 0) then + break + end + end + end + return data +end + +function figures.exists(askedname,format,resolve) + return (existers[format] or existers.generic)(askedname,resolve) +end + +function figures.check(data) + data = data or callstack[#callstack] or lastfiguredata + return (checkers[data.status.format] or checkers.generic)(data) +end + +local used_images = { } + +statistics.register("used graphics",function() + if trace_usage then + local filename = file.nameonly(environment.jobname) .. "-figures-usage.lua" + if next(figures_found) then + local found = { } + for _, data in sortedhash(figures_found) do + found[#found+1] = data + for k, v in next, data do + if v == false or v == "" then + data[k] = nil + end + end + end + for i=1,#used_images do + local u = used_images[i] + local s = u.status + if s then + s.status = nil -- doesn't say much here + if s.error then + u.used = { } -- better show that it's not used + end + end + for _, t in next, u do + for k, v in next, t do + if v == false or v == "" or k == "private" then + t[k] = nil + end + end + end + end + table.save(filename,{ + found = found, + used = used_images, + } ) + return format("log saved in '%s'",filename) + else + os.remove(filename) + end + end +end) + +function figures.include(data) + data = data or callstack[#callstack] or lastfiguredata + if trace_usage then + used_images[#used_images+1] = data + end + return (includers[data.status.format] or includers.generic)(data) +end + +function figures.scale(data) -- will become lua code + data = data or callstack[#callstack] or lastfiguredata + ctx_doscalefigure() + return data +end + +function figures.done(data) + figures.nofprocessed = figures.nofprocessed + 1 + data = data or callstack[#callstack] or lastfiguredata + local dr, du, ds, nr = data.request, data.used, data.status, figures.boxnumber + local box = texgetbox(nr) + ds.width = box.width + ds.height = box.height + -- somehow this fails on some of tacos files + -- ds.xscale = ds.width /(du.width or 1) + -- ds.yscale = ds.height/(du.height or 1) + -- du.width and du.height can be false + if du.width and du.height and du.width > 0 and du.height > 0 then + ds.xscale = ds.width /du.width + ds.yscale = ds.height/du.height + elseif du.xsize and du.ysize and du.xsize > 0 and du.ysize > 0 then + ds.xscale = ds.width /du.xsize + ds.yscale = ds.height/du.ysize + else + ds.xscale = 1 + ds.yscale = 1 + end + -- sort of redundant but can be limited + ds.page = ds.page or du.page or dr.page + return data +end + +function figures.dummy(data) + data = data or callstack[#callstack] or lastfiguredata + local dr, du, nr = data.request, data.used, figures.boxnumber + local box = hpack(node.new("hlist")) -- we need to set the dir (luatex 0.60 buglet) + du.width = du.width or figures.defaultwidth + du.height = du.height or figures.defaultheight + du.depth = du.depth or figures.defaultdepth + box.width = du.width + box.height = du.height + box.depth = du.depth + texsetbox(nr,box) -- hm, should be global (to be checked for consistency) +end + +-- -- -- generic -- -- -- + +function existers.generic(askedname,resolve) + -- not findbinfile + local result + if hasscheme(askedname) then + result = resolvers.findbinfile(askedname) + elseif isfile(askedname) then + result = askedname + elseif resolve then + result = resolvers.findbinfile(askedname) + end + if not result or result == "" then + result = false + end + if trace_figures then + if result then + report_inclusion("%a resolved to %a",askedname,result) + else + report_inclusion("%a cannot be resolved",askedname) + end + end + return result +end + +-- pdf : 0-3: 0 90 180 270 +-- jpeg: 0 unset 1-4: 0 90 180 270 5-8: flipped r/c + +local transforms = setmetatableindex ( + { + ["orientation-1"] = 0, ["R0"] = 0, + ["orientation-2"] = 4, ["R0MH"] = 4, + ["orientation-3"] = 2, ["R180"] = 2, + ["orientation-4"] = 6, ["R0MV"] = 6, + ["orientation-5"] = 5, ["R270MH"] = 5, + ["orientation-6"] = 3, ["R90"] = 3, + ["orientation-7"] = 7, ["R90MH"] = 7, + ["orientation-8"] = 1, ["R270"] = 1, + }, + function(t,k) -- transforms are 0 .. 7 + local v = tonumber(k) or 0 + if v < 0 or v > 7 then + v = 0 + end + t[k] = v + return v + end +) + +local function checktransform(figure,forced) + if auto_transform then + + local orientation = (forced ~= "" and forced ~= v_auto and forced) or figure.orientation or 0 + local transform = transforms["orientation-"..orientation] + figure.transform = transform + if odd(transform) then + return figure.height, figure.width + else + return figure.width, figure.height + end + end +end + +local pagecount = { } + +function checkers.generic(data) + local dr, du, ds = data.request, data.used, data.status + local name = du.fullname or "unknown generic" + local page = du.page or dr.page + local size = dr.size or "crop" + local color = dr.color or "natural" + local mask = dr.mask or "none" + local conversion = dr.conversion + local resolution = dr.resolution + local arguments = dr.arguments + local scanimage = dr.scanimage or scanimage + local userpassword = dr.userpassword + local ownerpassword = dr.ownerpassword + if not conversion or conversion == "" then + conversion = "default" + end + if not resolution or resolution == "" then + resolution = "default" + end + if not arguments or arguments == "" then + arguments = "default" + end + local hash = f_hash_full( + name, + page, + size, + color, + mask, + conversion, + resolution, + arguments + ) + -- + local figure = figures_loaded[hash] + if figure == nil then + figure = createimage { + filename = name, + page = page, + pagebox = dr.size, + keepopen = dr.keepopen or false, + userpassword = userpassword, + ownerpassword = ownerpassword, + -- visiblefilename = "", -- this prohibits the full filename ending up in the file + } + codeinjections.setfigurecolorspace(data,figure) + codeinjections.setfiguremask(data,figure) + if figure then + -- new, bonus check (a bogus check in lmtx) + if page and page > 1 then + local f = scanimage { + filename = name, + userpassword = userpassword, + ownerpassword = ownerpassword, + } + if f and f.page and f.pages < page then + report_inclusion("no page %i in %a, using page 1",page,name) + page = 1 + figure.page = page + end + end + -- till here + local f, comment = checkimage(scanimage(figure)) + if not f then + ds.comment = comment + ds.found = false + ds.error = true + end + if figure.attr and not f.attr then + -- tricky as img doesn't allow it + f.attr = figure.attr + end + if dr.cmyk == v_yes then + f.enforcecmyk = true + elseif dr.cmyk == v_auto and attributes.colors.model == "cmyk" then + f.enforcecmyk = true + end + figure = f + end + local f, d = codeinjections.setfigurealternative(data,figure) + figure = f or figure + data = d or data + figures_loaded[hash] = figure + if trace_conversion then + report_inclusion("new graphic, using hash %a",hash) + end + else + if trace_conversion then + report_inclusion("existing graphic, using hash %a",hash) + end + end + if figure then + local width, height = checktransform(figure,dr.transform) + -- + du.width = width + du.height = height + du.pages = figure.pages + du.depth = figure.depth or 0 + du.colordepth = figure.colordepth or 0 + du.xresolution = figure.xres or 0 + du.yresolution = figure.yres or 0 + du.xsize = figure.xsize or 0 + du.ysize = figure.ysize or 0 + du.rotation = figure.rotation or 0 -- in pdf multiples or 90% in jpeg 1 + du.orientation = figure.orientation or 0 -- jpeg 1 2 3 4 (0=unset) + ds.private = figure + ds.hash = hash + end + return data +end + +local nofimages = 0 +local pofimages = { } + +function figures.getrealpage(index) + return pofimages[index] or 0 +end + +local function updatepage(specification) + local n = specification.n + pofimages[n] = pofimages[n] or tex.count.realpageno -- so when reused we register the first one only +end + +function includers.generic(data) + local dr, du, ds = data.request, data.used, data.status + -- here we set the 'natural dimensions' + dr.width = du.width + dr.height = du.height + local hash = figures.hash(data) + local figure = figures_used[hash] + -- figures.registerresource { + -- filename = du.fullname, + -- width = dr.width, + -- height = dr.height, + -- } + if figure == nil then + figure = ds.private -- the img object + if figure then + figure = (dr.copyimage or copyimage)(figure) + if figure then + figure.width = dr.width or figure.width + figure.height = dr.height or figure.height + end + end + figures_used[hash] = figure + end + if figure then + local nr = figures.boxnumber + nofimages = nofimages + 1 + ds.pageindex = nofimages + local image = wrapimage(figure) + local pager = new_latelua { action = updatepage, n = nofimages } + image.next = pager + pager.prev = image + local box = hpack(image) + box.width = figure.width + box.height = figure.height + box.depth = 0 + texsetbox(nr,box) + ds.objectnumber = figure.objnum + -- indexed[figure.index] = figure + ctx_relocateexternalfigure() + end + return data +end + +-- -- -- nongeneric -- -- -- + +local function checkers_nongeneric(data,command) -- todo: macros and context.* + local dr, du, ds = data.request, data.used, data.status + local name = du.fullname or "unknown nongeneric" + local hash = name + if dr.object then + -- hm, bugged ... waiting for an xform interface + if not objects.data["FIG"][hash] then + if type(command) == "function" then + command() + end + ctx_dosetfigureobject("FIG",hash) + end + ctx_doboxfigureobject("FIG",hash) + elseif type(command) == "function" then + command() + end + return data +end + +local function includers_nongeneric(data) + return data +end + +checkers.nongeneric = checkers_nongeneric +includers.nongeneric = includers_nongeneric + +-- -- -- mov -- -- -- + +function checkers.mov(data) + local dr, du, ds = data.request, data.used, data.status + local width = todimen(dr.width or figures.defaultwidth) + local height = todimen(dr.height or figures.defaultheight) + local foundname = du.fullname + dr.width, dr.height = width, height + du.width, du.height, du.foundname = width, height, foundname + if trace_inclusion then + report_inclusion("including movie %a, width %p, height %p",foundname,width,height) + end + -- we need to push the node.write in between ... we could make a shared helper for this + ctx_startfoundexternalfigure(width .. "sp",height .. "sp") + context(function() + nodeinjections.insertmovie { + width = width, + height = height, + factor = bpfactor, + ["repeat"] = dr["repeat"], + controls = dr.controls, + preview = dr.preview, + label = dr.label, + foundname = foundname, + } + end) + ctx_stopfoundexternalfigure() + return data +end + +includers.mov = includers.nongeneric + +-- -- -- mps -- -- -- + +internalschemes.mprun = true + +-- mprun.foo.1 mprun.6 mprun:foo.2 + +local ctx_docheckfiguremprun = context.docheckfiguremprun +local ctx_docheckfiguremps = context.docheckfiguremps + +local function internal(askedname) + local spec, mprun, mpnum = match(lower(askedname),"mprun([:%.]?)(.-)%.(%d+)") + -- mpnum = tonumber(mpnum) or 0 -- can be string or number, fed to context anyway + if spec ~= "" then + return mprun, mpnum + else + return "", mpnum + end +end + +function existers.mps(askedname) + local mprun, mpnum = internal(askedname) + if mpnum then + return askedname, true, "mps", true + else + return existers.generic(askedname) + end +end + +function checkers.mps(data) + local mprun, mpnum = internal(data.used.fullname) + if mpnum then + return checkers_nongeneric(data,function() ctx_docheckfiguremprun(mprun,mpnum) end) + else + return checkers_nongeneric(data,function() ctx_docheckfiguremps(data.used.fullname) end) + end +end + +includers.mps = includers.nongeneric + +-- -- -- tex -- -- -- + +local ctx_docheckfiguretex = context.docheckfiguretex + +function existers.tex(askedname) + askedname = resolvers.findfile(askedname) + return askedname ~= "" and askedname or false, true, "tex", true +end + +function checkers.tex(data) + return checkers_nongeneric(data,function() ctx_docheckfiguretex(data.used.fullname) end) +end + +includers.tex = includers.nongeneric + +-- -- -- buffer -- -- -- + +local ctx_docheckfigurebuffer = context.docheckfigurebuffer + +function existers.buffer(askedname) + local name = file.nameonly(askedname) + local okay = buffers.exists(name) + return okay and name, true, "buffer", true -- always quit scanning +end + +function checkers.buffer(data) + return checkers_nongeneric(data,function() ctx_docheckfigurebuffer(file.nameonly(data.used.fullname)) end) +end + +includers.buffers = includers.nongeneric + +-- -- -- auto -- -- -- + +function existers.auto(askedname) + local name = gsub(askedname, ".auto$", "") + local format = figures.guess(name) + -- if format then + -- report_inclusion("format guess %a for %a",format,name) + -- else + -- report_inclusion("format guess for %a is not possible",name) + -- end + return format and name, true, format +end + +checkers.auto = checkers.generic +includers.auto = includers.generic + +-- -- -- cld -- -- -- + +local ctx_docheckfigurecld = context.docheckfigurecld + +function existers.cld(askedname) + askedname = resolvers.findfile(askedname) + return askedname ~= "" and askedname or false, true, "cld", true +end + +function checkers.cld(data) + return checkers_nongeneric(data,function() ctx_docheckfigurecld(data.used.fullname) end) +end + +includers.cld = includers.nongeneric + +-- -- -- converters -- -- -- + +setmetatableindex(converters,"table") + +-- We keep this helper because it has been around for a while and therefore it can +-- be a depedency in an existing workflow. + +function programs.makeoptions(options) + local to = type(options) + return (to == "table" and concat(options," ")) or (to == "string" and options) or "" +end + +function programs.run(binary,argument,variables) + local found = nil + if type(binary) == "table" then + for i=1,#binary do + local b = binary[i] + found = os.which(b) + if found then + binary = b + break + end + end + if not found then + binary = concat(binary, " | ") + end + elseif binary then + found = os.which(match(binary,"[%S]+")) + end + if type(argument) == "table" then + argument = concat(argument," ") -- for old times sake + end + if not found then + report_inclusion("program %a is not installed",binary or "?") + elseif not argument or argument == "" then + report_inclusion("nothing to run, no arguments for program %a",binary) + else + -- no need to use the full found filename (found) .. we also don't quote the program + -- name any longer as in luatex there is too much messing with these names + local command = format([[%s %s]],binary,replacetemplate(longtostring(argument),variables)) + if trace_conversion or trace_programs then + report_inclusion("running command: %s",command) + end + os.execute(command) + end +end + +-- the rest of the code has been moved to grph-con.lua + +-- -- -- bases -- -- -- + +local bases = allocate() +figures.bases = bases + +local bases_list = nil -- index => { basename, fullname, xmlroot } +local bases_used = nil -- [basename] => { basename, fullname, xmlroot } -- pointer to list +local bases_found = nil +local bases_enabled = false + +local function reset() + bases_list = allocate() + bases_used = allocate() + bases_found = allocate() + bases_enabled = false + bases.list = bases_list + bases.used = bases_used + bases.found = bases_found +end + +reset() + +function bases.use(basename) + if basename == "reset" then + reset() + else + basename = file.addsuffix(basename,"xml") + if not bases_used[basename] then + local t = { basename, nil, nil } + bases_used[basename] = t + bases_list[#bases_list+1] = t + if not bases_enabled then + bases_enabled = true + xml.registerns("rlx","http://www.pragma-ade.com/schemas/rlx") -- we should be able to do this per xml file + end + if trace_bases then + report_inclusion("registering base %a",basename) + end + end + end +end + +implement { name = "usefigurebase", actions = bases.use, arguments = "string" } + +local function bases_find(basename,askedlabel) + if trace_bases then + report_inclusion("checking for %a in base %a",askedlabel,basename) + end + basename = file.addsuffix(basename,"xml") + local t = bases_found[askedlabel] + if t == nil then + local base = bases_used[basename] + local page = 0 + if base[2] == nil then + -- no yet located + for i=1,#figure_paths do + local path = resolveprefix(figure_paths[i]) -- we resolve (e.g. jobfile:) + local xmlfile = path .. "/" .. basename + if io.exists(xmlfile) then + base[2] = xmlfile + base[3] = xml.load(xmlfile) + if trace_bases then + report_inclusion("base %a loaded",xmlfile) + end + break + end + end + end + t = false + if base[2] and base[3] then -- rlx:library + for e in xml.collected(base[3],"/(*:library|figurelibrary)/*:figure/*:label") do + page = page + 1 + if xml.text(e) == askedlabel then + t = { + base = file.replacesuffix(base[2],"pdf"), + format = "pdf", + name = xml.text(e,"../*:file"), -- to be checked + page = page, + } + bases_found[askedlabel] = t + if trace_bases then + report_inclusion("figure %a found in base %a",askedlabel,base[2]) + end + return t + end + end + if trace_bases and not t then + report_inclusion("figure %a not found in base %a",askedlabel,base[2]) + end + end + end + return t +end + +-- we can access sequential or by name + +local function bases_locate(askedlabel) + for i=1,#bases_list do + local entry = bases_list[i] + local t = bases_find(entry[1],askedlabel,1,true) + if t then + return t + end + end + return false +end + +function identifiers.base(data) + if bases_enabled then + local dr, du, ds = data.request, data.used, data.status + local fbl = bases_locate(dr.name or dr.label) + if fbl then + du.page = fbl.page + du.format = fbl.format + du.fullname = fbl.base + ds.fullname = fbl.name + ds.format = fbl.format + ds.page = fbl.page + ds.status = 10 + end + end + return data +end + +bases.locate = bases_locate +bases.find = bases_find + +identifiers.list = { + identifiers.base, + identifiers.default +} + +-- tracing + +statistics.register("graphics processing time", function() + local nofprocessed = figures.nofprocessed + if nofprocessed > 0 then + local nofnames, nofbadnames = 0, 0 + for hash, data in next, figures_found do + nofnames = nofnames + 1 + if data.badname then + nofbadnames = nofbadnames + 1 + end + end + return format("%s seconds including tex, %s processed images, %s unique asked, %s bad names", + statistics.elapsedtime(figures),nofprocessed,nofnames,nofbadnames) + else + return nil + end +end) + +-- helper + +function figures.applyratio(width,height,w,h) -- width and height are strings and w and h are numbers + if not width or width == "" then + if not height or height == "" then + return figures.defaultwidth, figures.defaultheight + else + height = todimen(height) + if w and h then + return height * w/h, height + else + return figures.defaultwidth, height + end + end + else + width = todimen(width) + if not height or height == "" then + if w and h then + return width, width * h/w + else + return width, figures.defaultheight + end + else + return width, todimen(height) + end + end +end + +-- example of simple plugins: +-- +-- figures.converters.png = { +-- png = function(oldname,newname,resolution) +-- local command = string.format('gm convert -depth 1 "%s" "%s"',oldname,newname) +-- logs.report(string.format("running command %s",command)) +-- os.execute(command) +-- end, +-- } + +-- local n = "foo.pdf" +-- local d = figures.getinfo(n) +-- if d then +-- for i=1,d.used.pages do +-- local p = figures.getinfo(n,i) +-- if p then +-- local u = p.used +-- print(u.width,u.height,u.orientation) +-- end +-- end +-- end + +function figures.getinfo(name,page) + if type(name) == "string" then + name = { name = name, page = page } + end + if name.name then + local data = figures.push(name) + data = figures.identify(data) + if data.status and data.status.status > 0 then + data = figures.check(data) + end + figures.pop() + return data + end +end + +function figures.getpdfinfo(name,page,metadata) + -- not that useful but as we have it for detailed inclusion we can as + -- we expose it + if type(name) ~= "table" then + name = { name = name, page = page, metadata = metadata } + end + return codeinjections.getinfo(name) +end + +-- interfacing + +implement { + name = "figure_push", + scope = "private", + actions = figures.push, + arguments = { + { + { "name" }, + { "label" }, + { "page" }, + { "file" }, + { "size" }, + { "object" }, + { "prefix" }, + { "cache" }, + { "format" }, + { "preset" }, + { "controls" }, + { "resources" }, + { "preview" }, + { "display" }, + { "mask" }, + { "conversion" }, + { "resolution" }, + { "color" }, + { "cmyk" }, + { "arguments" }, + { "repeat" }, + { "transform" }, + { "compact" }, + { "width", "dimen" }, + { "height", "dimen" }, + { "userpassword" }, + { "ownerpassword" }, + } + } +} + +-- beware, we get a number passed by default + +implement { name = "figure_pop", scope = "private", actions = figures.pop } +implement { name = "figure_done", scope = "private", actions = figures.done } +implement { name = "figure_dummy", scope = "private", actions = figures.dummy } +implement { name = "figure_identify", scope = "private", actions = figures.identify } +implement { name = "figure_scale", scope = "private", actions = figures.scale } +implement { name = "figure_check", scope = "private", actions = figures.check } +implement { name = "figure_include", scope = "private", actions = figures.include } + +implement { + name = "setfigurelookuporder", + actions = figures.setorder, + arguments = "string" +} + +implement { + name = "figure_reset", + scope = "private", + arguments = { "integer", "dimen", "dimen" }, + actions = function(box,width,height) + figures.boxnumber = box + figures.defaultwidth = width + figures.defaultheight = height + end +} + +-- require("util-lib-imp-gm") +-- +-- figures.converters.tif.pdf = function(oldname,newname,resolution) +-- logs.report("graphics","using gm library to convert %a",oldname) +-- utilities.graphicmagick.convert { +-- inputname = oldname, +-- outputname = newname, +-- } +-- end +-- +-- \externalfigure[t:/sources/hakker1b.tiff] + +-- something relatively new: + +local registered = { } + +local ctx_doexternalfigurerepeat = context.doexternalfigurerepeat + +implement { + name = "figure_register_page", + arguments = "3 strings", + actions = function(a,b,c) + registered[#registered+1] = { a, b, c } + context(#registered) + end +} + +implement { + name = "figure_nof_registered_pages", + actions = function() + context(#registered) + end +} + +implement { + name = "figure_flush_registered_pages", + arguments = "string", + actions = function(n) + local f = registered[tonumber(n)] + if f then + ctx_doexternalfigurerepeat(f[1],f[2],f[3],n) + end + end +} diff --git a/tex/context/base/mkxl/grph-inc.mkxl b/tex/context/base/mkxl/grph-inc.mkxl index 4a6626617..99ceea4d3 100644 --- a/tex/context/base/mkxl/grph-inc.mkxl +++ b/tex/context/base/mkxl/grph-inc.mkxl @@ -21,12 +21,12 @@ \writestatus{loading}{ConTeXt Graphic Macros / Figure Inclusion} \registerctxluafile{grph-img}{} -\registerctxluafile{grph-inc}{} +\registerctxluafile{grph-inc}{autosuffix} \registerctxluafile{grph-bmp}{} \registerctxluafile{grph-chk}{} \registerctxluafile{grph-con}{} \registerctxluafile{grph-fil}{} -\registerctxluafile{grph-mem}{} +%registerctxluafile{grph-mem}{} % obsolete \registerctxluafile{grph-u3d}{} % this will become a module %registerctxluafile{grph-swf}{} % this will become a module diff --git a/tex/context/base/mkxl/grph-trf.lmt b/tex/context/base/mkxl/grph-trf.lmt new file mode 100644 index 000000000..f68d6a6a8 --- /dev/null +++ b/tex/context/base/mkxl/grph-trf.lmt @@ -0,0 +1,126 @@ +if not modules then modules = { } end modules ['grph-trf'] = { + version = 1.001, + comment = "companion to grph-trf.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- see grph-trf-todo.lua for older preliminary code for the rest + +local sind, cosd, tand, abs = math.sind, math.cosd, math.tand, math.abs + +local setdimension = tex.setdimensionvalue + +local function analyzerotate(rotation,width,height,depth,total,notfit,obeydepth) + -- + -- print(rotation,width,height,depth,notfit,obeydepth) + -- + local sin = sind(rotation) + local cos = cosd(rotation) + local abssin = abs(sin) + local abscos = abs(cos) + local xsize = 0 + local ysize = 0 + local xposition = 0 + local yposition = 0 + local xoffset = 0 + local yoffset = 0 + local newwidth = width + local newheight = height + local newdepth = depth + -- print(sin,cos) + if sin > 0 then + if cos > 0 then + xsize = cos * width + sin * total + ysize = sin * width + cos * total + yposition = cos * total + if notfit then + xoffset = - sin * height + newwidth = sin * depth + cos * width + else + newwidth = xsize + xposition - xoffset + end + if obeydepth then + yoffset = cos * depth + end + newheight = ysize - yoffset + newdepth = yoffset + -- print("case 1, done") + else + xsize = abscos * width + sin * total + ysize = sin * width + abscos * total + xposition = abscos * width + if notfit then + xoffset = - xsize + sin * depth + end + if obeydepth then + yoffset = abscos * height + newwidth = abssin * total + abscos * width + xoffset + else + newwidth = xsize + end + newheight = ysize - yoffset + newdepth = yoffset + -- print("case 2, done") + end + else + if cos < 0 then + xsize = abscos * width + abssin * total + ysize = abssin * width + abscos * total + xposition = xsize + yposition = abssin * width + if notfit then + xoffset = - xsize + abssin * height + end + if obeydepth then + yoffset = ysize + cos * depth + end + newwidth = notfit and (abssin * height) or xsize + newheight = ysize - yoffset + newdepth = yoffset + -- print("case 3, done") + else + xsize = cos * width + abssin * total + ysize = abssin * width + cos * total + xposition = abssin * total + yposition = cos * total + if notfit then + xoffset = - abssin * depth + newwidth = xsize + xoffset + else + newwidth = xsize + end + if obeydepth then + yoffset = cos * depth + end + newheight = ysize - yoffset + sin * width + newdepth = yoffset - sin * width + -- print("case 4, done") + end + end + setdimension("d_grph_rotate_x_size", xsize) + setdimension("d_grph_rotate_y_size", ysize) + setdimension("d_grph_rotate_x_position", xposition) + setdimension("d_grph_rotate_y_position", yposition) + setdimension("d_grph_rotate_x_offset", xoffset) + setdimension("d_grph_rotate_y_offset", yoffset) + setdimension("d_grph_rotate_new_width", newwidth) + setdimension("d_grph_rotate_new_height", newheight) + setdimension("d_grph_rotate_new_depth", newdepth) +end + +interfaces.implement { + name = "analyzerotate", + actions = analyzerotate, + arguments = { +-- "integer", + "number", + "dimension", + "dimension", + "dimension", + "dimension", + "conditional", + "conditional", + }, +} diff --git a/tex/context/base/mkxl/grph-trf.mkxl b/tex/context/base/mkxl/grph-trf.mkxl index 87e4da7f4..550eb6f96 100644 --- a/tex/context/base/mkxl/grph-trf.mkxl +++ b/tex/context/base/mkxl/grph-trf.mkxl @@ -24,7 +24,7 @@ %D We could move the calculations to \LUA\ and clean up this lot anyway. On the %D other hand, there is some danger of messing up so it has a real low priority. -\registerctxluafile{grph-trf}{} +\registerctxluafile{grph-trf}{autosuffix} % local: @@ -266,18 +266,18 @@ \glet\finalscaleboxyscale \m_grph_scale_used_y_scale \grph_scale_calculations_report} -\setvalue{\??scalegrid\v!yes }{\getnoflines \d_grph_scale_used_y_size\edef\p_height{\the\noflines\lineheight}} -\setvalue{\??scalegrid\v!height }{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight+\strutdepth}} -\setvalue{\??scalegrid\v!depth }{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight-\strutdepth}} -\setvalue{\??scalegrid\v!halfline}{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight+.5\lineheight}} -\setvalue{\??scalegrid\v!fit }{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\noflines\lineheight}} -\letvalue{\??scalegrid\empty }\donothing +\defcsname\??scalegrid\v!yes \endcsname{\getnoflines \d_grph_scale_used_y_size\edef\p_height{\the\noflines\lineheight}} +\defcsname\??scalegrid\v!height \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight+\strutdepth}} +\defcsname\??scalegrid\v!depth \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight-\strutdepth}} +\defcsname\??scalegrid\v!halfline\endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\dimexpr\noflines\lineheight+.5\lineheight}} +\defcsname\??scalegrid\v!fit \endcsname{\getrawnoflines\d_grph_scale_used_y_size\edef\p_height{\the\noflines\lineheight}} +\letcsname\??scalegrid\empty \endcsname\donothing \def\grph_scale_check_parameters % resolve self referencing loops {\ifempty\p_maxwidth \else \edef\p_maxwidth {\the\dimexpr\p_maxwidth }\fi \ifempty\p_maxheight\else \edef\p_maxheight{\the\dimexpr\p_maxheight }\fi \ifempty\p_lines \else \edef\p_height {\the\dimexpr\p_lines\lineheight}\fi - \csname\??scalegrid\scaleparameter\c!grid\endcsname} + \begincsname\??scalegrid\scaleparameter\c!grid\endcsname} \def\grph_scale_by_nature % where ! ! ! ! ! {\ifempty\p_width \else \global\d_grph_scale_used_x_size\p_width \fi @@ -373,16 +373,11 @@ \global\d_grph_scale_used_y_size\zeropoint % \ifempty\p_maxwidth - \ifempty\p_maxheight - \else - \ifdim\d_grph_scale_y_size>\p_maxheight\relax - \global\d_grph_scale_used_y_size\p_maxheight - \fi - \fi - \else - \ifdim\d_grph_scale_x_size>\p_maxwidth\relax - \global\d_grph_scale_used_x_size\p_maxwidth + \ifempty\p_maxheight\orelse\ifdim\d_grph_scale_y_size>\p_maxheight\relax + \global\d_grph_scale_used_y_size\p_maxheight \fi + \orelse\ifdim\d_grph_scale_x_size>\p_maxwidth\relax + \global\d_grph_scale_used_x_size\p_maxwidth \fi \fi} @@ -441,45 +436,35 @@ \def\grph_scale_by_dimension_indeed#1#2#3% {#1\relax - \ifempty\p_maxwidth \else - \ifdim\d_grph_scale_used_x_size>\p_maxwidth\relax - \global\d_grph_scale_used_x_size\p_maxwidth - #2\relax - \fi + \ifempty\p_maxwidth\orelse\ifdim\d_grph_scale_used_x_size>\p_maxwidth\relax + \global\d_grph_scale_used_x_size\p_maxwidth + #2\relax \fi - \ifempty\p_maxheight \else - \ifdim\d_grph_scale_used_y_size>\p_maxheight\relax - \global\d_grph_scale_used_y_size\p_maxheight - #3\relax - \fi + \ifempty\p_maxheight\orelse\ifdim\d_grph_scale_used_y_size>\p_maxheight\relax + \global\d_grph_scale_used_y_size\p_maxheight + #3\relax \fi} +% we can use \lastnamedcs if we want to squeeze out some more + \def\grph_scale_calculate_norm#1#2% todo: swap 1 and 2 and pass one less {\csname\??scalenorm\ifcsname\??scalenorm#2\endcsname#2\else\s!unknown\fi\endcsname#1#2} \def\grph_scale_calculate_fact#1% {\csname\??scalefact\ifcsname\??scalefact#1\endcsname#1\else\s!unknown\fi\endcsname} -%setvalue{\??scalenorm\v!min }#1#2#3#4#5{\global#1#4} -\setvalue{\??scalenorm\v!max }#1#2#3#4#5{\global#1#4} -\setvalue{\??scalenorm\v!fit }#1#2#3#4#5{\global#1#5} -\setvalue{\??scalenorm\v!broad }#1#2#3#4#5{\global#1\dimexpr#5-4\externalfigureparameter\c!bodyfont\relax} -\setvalue{\??scalenorm\s!unknown}#1#2#3#4#5{\global#1\dimexpr#2\dimexpr\externalfigureparameter\c!bodyfont/10\relax\relax} % brr ex -\setvalue{\??scalenorm\v!auto }#1#2#3#4#5{\ifempty#3\else\global#1#3\fi} -\setvalue{\??scalenorm\empty }#1#2#3#4#5{\ifempty#3\else\global#1#3\fi} -\setvalue{\??scalenorm\s!default}#1#2#3#4#5{\ifempty#3\else\global#1#3\fi} - -\setvalue{\??scalefact\v!min }{\global\settrue \c_grph_scale_swap_factor} -\setvalue{\??scalefact\s!unknown}{\global\setfalse\c_grph_scale_swap_factor} - -% \setvalue{\??scalenorm\v!min }#1#2#3#4#5% an ugly hack -% {\ifdim\d_grph_scale_used_x_size>\d_grph_scale_h_size -% \d_grph_scale_used_y_size\vsize -% \else -% \d_grph_scale_used_x_size\hsize -% \fi} - -\setvalue{\??scalenorm\v!min}#1#2#3#4#5% an ugly hack +\defcsname\??scalenorm\v!max \endcsname#1#-#-#2#-{\global#1#2} +\defcsname\??scalenorm\v!fit \endcsname#1#-#-#-#2{\global#1#2} +\defcsname\??scalenorm\v!broad \endcsname#1#-#-#-#2{\global#1\dimexpr#2-4\externalfigureparameter\c!bodyfont\relax} +\defcsname\??scalenorm\s!unknown\endcsname#1#2#-#-#-{\global#1\dimexpr#2\dimexpr\externalfigureparameter\c!bodyfont/10\relax\relax} % brr ex +\defcsname\??scalenorm\v!auto \endcsname#1#-#2#-#-{\ifempty#2\else\global#1#2\fi} +\defcsname\??scalenorm\empty \endcsname#1#-#2#-#-{\ifempty#2\else\global#1#2\fi} +\defcsname\??scalenorm\s!default\endcsname#1#-#2#-#-{\ifempty#2\else\global#1#2\fi} + +\defcsname\??scalefact\v!min \endcsname{\global\settrue \c_grph_scale_swap_factor} +\defcsname\??scalefact\s!unknown\endcsname{\global\setfalse\c_grph_scale_swap_factor} + +\defcsname\??scalenorm\v!min\endcsname#-#-#-#-#-% an ugly hack {\d_grph_scale_used_x_size\hsize \d_grph_scale_used_y_size\vsize} @@ -769,14 +754,22 @@ \installcorenamespace {rotatelocation} \installcorenamespace {rotatepreset} -% todo: scratchcounters +%D These are set at the \LUA\ end (it's a cheaper operation than setting a dimen +%D register and these are actually kind of constants not used in further +%D calculations): -\newdimen\d_grph_rotate_x_size -\newdimen\d_grph_rotate_y_size -\newdimen\d_grph_rotate_x_offset -\newdimen\d_grph_rotate_y_offset -\newdimen\d_grph_rotate_x_position -\newdimen\d_grph_rotate_y_position +\mutable\dimendef\d_grph_rotate_x_size \zeropoint +\mutable\dimendef\d_grph_rotate_y_size \zeropoint +\mutable\dimendef\d_grph_rotate_x_offset \zeropoint +\mutable\dimendef\d_grph_rotate_y_offset \zeropoint +\mutable\dimendef\d_grph_rotate_x_position\zeropoint +\mutable\dimendef\d_grph_rotate_y_position\zeropoint + +\mutable\dimendef\d_grph_rotate_new_width \zeropoint +\mutable\dimendef\d_grph_rotate_new_height\zeropoint +\mutable\dimendef\d_grph_rotate_new_depth \zeropoint + +%D These aren't: \newdimen\d_grph_rotate_used_height @@ -788,10 +781,6 @@ \newdimen\d_grph_rotate_saved_height \newdimen\d_grph_rotate_saved_depth -\newdimen\d_grph_rotate_new_width -\newdimen\d_grph_rotate_new_height -\newdimen\d_grph_rotate_new_depth - \newconditional\c_grph_rotate_obey_depth \newconditional\c_grph_rotate_not_fit \newconditional\c_grph_rotate_center @@ -816,9 +805,11 @@ \fi \edef\p_rotation_location{\rotateparameter\c!location}% \edef\p_rotation_rotation{\rotateparameter\c!rotation}% - \csname\??rotatelocation - \ifcsname\??rotatelocation\p_rotation_location\endcsname\p_rotation_location\else\v!default\fi - \endcsname} + \ifcsname\??rotatelocation\p_rotation_location\endcsname + \expandafter\lastnamedcs + \else + \expandafter\grph_rotate_default + \fi} \def\grph_rotate_framed {\resetrotateparameter\c!location @@ -832,42 +823,44 @@ {\grph_rotate_finish_indeed \egroup} -\setvalue{\??rotatelocation\v!depth}% +\def\grph_rotate_default + {\setfalse\c_grph_rotate_not_fit + \setfalse\c_grph_rotate_center + \settrue \c_grph_rotate_obey_depth + \grph_rotate_framed} + +\letcsname\??rotatelocation\v!default\endcsname\grph_rotate_default + +\defcsname\??rotatelocation\v!depth\endcsname {\setfalse\c_grph_rotate_not_fit \setfalse\c_grph_rotate_center \settrue \c_grph_rotate_obey_depth \grph_rotate_normal} -\setvalue{\??rotatelocation\v!fit}% +\defcsname\??rotatelocation\v!fit\endcsname {\settrue \c_grph_rotate_not_fit \setfalse\c_grph_rotate_center \settrue \c_grph_rotate_obey_depth \grph_rotate_normal} -\setvalue{\??rotatelocation\v!broad}% +\defcsname\??rotatelocation\v!broad\endcsname {\setfalse\c_grph_rotate_not_fit \setfalse\c_grph_rotate_center \setfalse\c_grph_rotate_obey_depth \grph_rotate_normal} -\setvalue{\??rotatelocation\v!high}% +\defcsname\??rotatelocation\v!high\endcsname {\setfalse\c_grph_rotate_not_fit \setfalse\c_grph_rotate_center \setfalse\c_grph_rotate_obey_depth \grph_rotate_framed} -\setvalue{\??rotatelocation\v!middle}% +\defcsname\??rotatelocation\v!middle\endcsname {\setfalse\c_grph_rotate_not_fit \settrue \c_grph_rotate_center \setfalse\c_grph_rotate_obey_depth % hm, depth ? \grph_rotate_normal} -\setvalue{\??rotatelocation\v!default}% - {\setfalse\c_grph_rotate_not_fit - \setfalse\c_grph_rotate_center - \settrue \c_grph_rotate_obey_depth - \grph_rotate_framed} - \permanent\protected\def\dorotatebox#1% {angle} \hbox/\vbox/\vtop % a fast low level one {\ifcase#1\relax \expandafter\gobbleoneargument @@ -901,22 +894,21 @@ \def\grph_rotate_finish_nop {\boxcursor\box\nextbox} -\setvalue{\??rotatepreset\v!left}% +\defcsname\??rotatepreset\v!left\endcsname {\edef\p_rotation_rotation{90}} -\setvalue{\??rotatepreset\v!right}% +\defcsname\??rotatepreset\v!right\endcsname {\edef\p_rotation_rotation{270}} -\setvalue{\??rotatepreset\v!inner}% +\defcsname\??rotatepreset\v!inner\endcsname {\signalrightpage \doifelserightpage{\def\p_rotation_rotation{270}}{\def\p_rotation_rotation{90}}} -\setvalue{\??rotatepreset\v!outer}% +\defcsname\??rotatepreset\v!outer\endcsname {\signalrightpage \doifelserightpage{\def\p_rotation_rotation{90}}{\def\p_rotation_rotation{270}}} -\setvalue{\??rotatepreset\v!default}% - {} +\letcsname\??rotatepreset\v!default\endcsname\empty \def\grph_rotate_finish_yes {\begincsname\??rotatepreset\p_rotation_rotation\endcsname diff --git a/tex/context/base/mkxl/lpdf-col.lmt b/tex/context/base/mkxl/lpdf-col.lmt index 13f34fe46..778d250c6 100644 --- a/tex/context/base/mkxl/lpdf-col.lmt +++ b/tex/context/base/mkxl/lpdf-col.lmt @@ -777,7 +777,7 @@ do local function startcolor(k) local m, c = colortoattributes(k) local t = transparencytoattribute(k) - if t then + if t and t ~= unsetvalue then return t_cache[t][m][c] else return c_cache[m][c] @@ -793,10 +793,11 @@ do backends.pdf.tables.vfspecials = vfspecials vfspecials.startcolor = startcolor - vfspecials.stopcolor = { "pdf", "text", "Q" } + -- vfspecials.stopcolor = { "pdf", "text", "Q" } -- fails + vfspecials.stopcolor = { "pdf", "page", "Q" } vfspecials.startslant = startslant - vfspecials.stopslant = { "pdf", "text", "Q" } + vfspecials.stopslant = { "pdf", "page", "Q" } -- fails end diff --git a/tex/context/base/mkxl/lpdf-lmt.lmt b/tex/context/base/mkxl/lpdf-lmt.lmt index c02a35fe1..28cfff41d 100644 --- a/tex/context/base/mkxl/lpdf-lmt.lmt +++ b/tex/context/base/mkxl/lpdf-lmt.lmt @@ -1074,11 +1074,10 @@ local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do -- place image also used in vf but we can use a different one if we need it - local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream memstream + local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream local img_none = imagetypes.none local img_pdf = imagetypes.pdf local img_stream = imagetypes.stream - local img_memstream = imagetypes.memstream local one_bp = 65536 * bpfactor @@ -1158,7 +1157,7 @@ local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do -- tricky: xsize and ysize swapped - if kind == img_pdf or kind == img_stream or kind == img_memstream then + if kind == img_pdf or kind == img_stream then rx, ry, tx, ty = 1/xsize, 1/ysize, xorigin/xsize, yorigin/ysize else -- if kind == img_png then @@ -2621,7 +2620,7 @@ do -- updaters.register("backend.update.pdf",function() local codeinjections = backends.pdf.codeinjections - local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream memstream + local imagetypes = images.types -- pdf png jpg jp2 jbig2 stream local img_none = imagetypes.none local rulecodes = nodes.rulecodes diff --git a/tex/context/base/mkxl/lxml-ini.mkxl b/tex/context/base/mkxl/lxml-ini.mkxl index f331718e9..01d0f883f 100644 --- a/tex/context/base/mkxl/lxml-ini.mkxl +++ b/tex/context/base/mkxl/lxml-ini.mkxl @@ -47,86 +47,22 @@ \aliased\let\xmlload\xmlloadfile -% aliased - -%let\xmlall \clf_xmlall -%let\xmlatt \clf_xmlatt -%let\xmlattdef \clf_xmlattdef -%let\xmlattribute \clf_xmlattribute -%let\xmlattributedef \clf_xmlattributedef -%let\xmlbadinclusions \clf_xmlbadinclusions -%let\xmlchainatt \clf_xmlchainatt -%let\xmlchainattdef \clf_xmlchainattdef -%let\xmlchecknamespace \clf_xmlchecknamespace -%let\xmlcommand \clf_xmlcommand -%let\xmlcontext \clf_xmlcontext -%let\xmlcount \clf_xmlcount -%let\xmldelete \clf_xmldelete -%let\xmldirect \clf_xmldirect % in loops, not dt but root -%let\xmldirectives \clf_xmldirectives -%let\xmldirectivesafter \clf_xmldirectivesafter -%let\xmldirectivesbefore \clf_xmldirectivesbefore -%let\xmldisplayverbatim \clf_xmldisplayverbatim -%let\xmlelement \clf_xmlelement -%let\xmlfilter \clf_xmlfilter -%let\xmlfilterlist \clf_xmlfilterlist -%let\xmlfirst \clf_xmlfirst -%let\xmlflush \clf_xmlflush -%let\xmlflushcontext \clf_xmlflushcontext -%let\xmlflushlinewise \clf_xmlflushlinewise -%let\xmlflushpure \clf_xmlflushpure -%let\xmlflushspacewise \clf_xmlflushspacewise -%let\xmlflushtext \clf_xmlflushtext -%let\xmlfunction \clf_xmlfunction -%let\xmlinclude \clf_xmlinclude -%let\xmlincludeoptions \clf_xmlincludeoptions -%let\xmlinclusion \clf_xmlinclusion -%let\xmlinclusionbase \clf_xmlinclusionbase -%let\xmlinclusions \clf_xmlinclusions -%let\xmlindex \clf_xmlindex -%let\xmlinlineverbatim \clf_xmlinlineverbatim -%let\xmllast \clf_xmllast -%let\xmllastatt \clf_xmllastatt -%let\xmllastmatch \clf_xmllastmatch -%let\xmllastpar \clf_xmllastpar -%let\xmlloaddirectives \clf_xmlloaddirectives -%let\xmlmain \clf_xmlmain -%let\xmlmatch \clf_xmlmatch -%let\xmlname \clf_xmlname -%let\xmlnamespace \clf_xmlnamespace -%let\xmlnonspace \clf_xmlnonspace -%let\xmlpar \clf_xmlpar -%let\xmlparam \clf_xmlparam -%let\xmlpath \clf_xmlpath -%let\xmlpopmatch \clf_xmlpopmatch -%let\xmlpos \clf_xmlpos -%let\xmlpure \clf_xmlpure -%let\xmlpushmatch \clf_xmlpushmatch -%let\xmlraw \clf_xmlraw -%let\xmlrefatt \clf_xmlrefatt -%let\xmlregisterns \clf_xmlregisterns % document -%let\xmlremapname \clf_xmlremapname % element -%let\xmlremapnamespace \clf_xmlremapnamespace % document -%let\xmlsave \clf_xmlsave -%let\xmlsetatt \clf_xmlsetatt -%let\xmlsetattribute \clf_xmlsetattribute -%let\xmlsetpar \clf_xmlsetpar -%let\xmlsetparam \clf_xmlsetparam -%let\xmlsetsetup \clf_xmlsetsetup -%let\xmlsnippet \clf_xmlsnippet -%let\xmlstrip \clf_xmlstrip -%let\xmlstripanywhere \clf_xmlstripanywhere -%let\xmlstripnolines \clf_xmlstripnolines -%let\xmlstripped \clf_xmlstripped -%let\xmlstrippednolines \clf_xmlstrippednolines -%let\xmltag \clf_xmltag -%let\xmltext \clf_xmltext -%let\xmltobuffer \clf_xmltobuffer % id pattern name -%let\xmltobuffertextonly \clf_xmltobuffertextonly % id pattern name -%let\xmltobufferverbose \clf_xmltobufferverbose % id pattern name -%let\xmltofile \clf_xmltofile % id pattern filename -%let\xmltoparameters \clf_xmltoparameters -%let\xmlverbatim \clf_xmlverbatim +%D These are defined at the \LUA\ end: + +% \xmlall \xmlatt \xmlattdef \xmlattribute \xmlattributedef \xmlbadinclusions +% \xmlchainatt \xmlchainattdef \xmlchecknamespace \xmlcommand \xmlcontext \xmlcount +% \xmldelete \xmldirect \xmldirectives \xmldirectivesafter \xmldirectivesbefore +% \xmldisplayverbatim \xmlelement \xmlfilter \xmlfilterlist \xmlfirst \xmlflush +% \xmlflushcontext \xmlflushlinewise \xmlflushpure \xmlflushspacewise \xmlflushtext +% \xmlfunction \xmlinclude \xmlincludeoptions \xmlinclusion \xmlinclusionbase +% \xmlinclusions \xmlindex \xmlinlineverbatim \xmllast \xmllastatt \xmllastmatch +% \xmllastpar \xmlloaddirectives \xmlmain \xmlmatch \xmlname \xmlnamespace +% \xmlnonspace \xmlpar \xmlparam \xmlpath \xmlpopmatch \xmlpos \xmlpure +% \xmlpushmatch \xmlraw \xmlrefatt \xmlregisterns \xmlremapname \xmlremapnamespace +% \xmlsave \xmlsetatt \xmlsetattribute \xmlsetpar \xmlsetparam \xmlsetsetup +% \xmlsnippet \xmlstrip \xmlstripanywhere \xmlstripnolines \xmlstripped +% \xmlstrippednolines \xmltag \xmltext \xmltobuffer \xmltobuffertextonly +% \xmltobufferverbose \xmltofile \xmltoparameters \xmlverbatim \aliased\let\xmlposition\xmlindex @@ -184,25 +120,11 @@ \aliased\let\startxmlraw\clf_xmlstartraw \aliased\let\stopxmlraw \clf_xmlstopraw -% these are expandable! todo: \xmldoifelseattribute +% These are expandable and defined at the \LUA\ end: -%let\xmldoif \clf_xmldoif -%let\xmldoifnot \clf_xmldoifnot -%let\xmldoifelse \clf_xmldoifelse -%let\xmldoiftext \clf_xmldoiftext -%let\xmldoifnottext \clf_xmldoifnottext -%let\xmldoifelsetext \clf_xmldoifelsetext - -%let\xmldoifatt \clf_xmldoifatt -%let\xmldoifnotatt \clf_xmldoifnotatt -%let\xmldoifelseatt \clf_xmldoifelseatt - -%let\xmldoifempty \clf_xmldoifempty -%let\xmldoifnotempty \clf_xmldoifnotempty -%let\xmldoifelseempty \clf_xmldoifelseempty -%let\xmldoifselfempty \clf_xmldoifselfempty -%let\xmldoifnotselfempty \clf_xmldoifnotselfempty -%let\xmldoifelseselfempty \clf_xmldoifelseselfempty +% \xmldoif \xmldoifnot \xmldoifelse \xmldoiftext \xmldoifnottext \xmldoifelsetext +% \xmldoifatt \xmldoifnotatt \xmldoifelseatt \xmldoifempty \xmldoifnotempty +% \xmldoifelseempty \xmldoifselfempty \xmldoifnotselfempty \xmldoifelseselfempty \aliased\let\xmldoiftextelse \xmldoifelsetext \aliased\let\xmldoifemptyelse \xmldoifelseempty @@ -519,26 +441,8 @@ \aliased\let\xmlapplyselectors\clf_xmlapplyselectors -% \let\xmlcatcodes\notcatcodes +% In the\MKIV\ file there is some commented code wrt handing entities with active +% characters ... very old and obsolete. \protect \endinput -% \newcount\charactersactiveoffset \charactersactiveoffset="10000 -% -% \startextendcatcodetable\ctxcatcodes -% \catcode\numexpr\charactersactiveoffset+`<\relax=13 -% \catcode\numexpr\charactersactiveoffset+`&\relax=13 -% \catcode\numexpr\charactersactiveoffset+`>\relax=13 -% \stopextendcatcodetable -% -% \startextendcatcodetable\xmlcatcodes % not needed -% \catcode\numexpr\charactersactiveoffset+`<\relax=13 -% \catcode\numexpr\charactersactiveoffset+`&\relax=13 -% \catcode\numexpr\charactersactiveoffset+`>\relax=13 -% \stopextendcatcodetable -% -% \ctxlua { % entities are remembered in the format -% commands.remapentity("<",characters.activeoffset + utf.byte("<")) -% commands.remapentity("&",characters.activeoffset + utf.byte("&")) -% commands.remapentity(">",characters.activeoffset + utf.byte(">")) -% } diff --git a/tex/context/base/mkxl/meta-fnt.lmt b/tex/context/base/mkxl/meta-fnt.lmt index 627dbc746..a0feb0b52 100644 --- a/tex/context/base/mkxl/meta-fnt.lmt +++ b/tex/context/base/mkxl/meta-fnt.lmt @@ -6,257 +6,24 @@ if not modules then modules = { } end modules ['meta-fnt'] = { license = "see context related readme files" } -local next = next -local concat = table.concat -local format = string.format -local formatters = string.formatters -local chardata = characters.data -local fontdata = fonts.hashes.identifiers +-- This was one of the demo modules that never was used for real so the \MKIV\ +-- variant has been replaced by one using the newer \LMTX\ interface, but only +-- because we want to be (sort of) compatible. Instead of doing the work at the +-- \LUA\ end we do it at the \TEX end. -local vffonts = fonts.handlers.vf - -local mpfonts = fonts.mp or { } -fonts.mp = mpfonts - -mpfonts.version = mpfonts.version or 1.20 -mpfonts.inline = true -mpfonts.cache = containers.define("fonts", "mp", mpfonts.version, true) - -metapost.fonts = metapost.fonts or { } - -local function unicodetoactualtext(...) - unicodetoactualtext = backends.codeinjections.unicodetoactualtext - return unicodetoactualtext(...) -end - --- a few glocals - -local characters, descriptions = { }, { } -local factor, code, slot, width, height, depth, total, variants, bbox, llx, lly, urx, ury = 100, { }, 0, 0, 0, 0, 0, 0, true, 0, 0, 0, 0 - -local flusher = { - startfigure = function(_chr_,_llx_,_lly_,_urx_,_ury_) - code = { } - slot = _chr_ - llx = _llx_ - lly = _lly_ - urx = _urx_ - ury = _ury_ - width = urx - llx - height = ury - depth = -lly - total = total + 1 - inline = mpfonts.inline - end, - flushfigure = function(t) - for i=1,#t do - code[#code+1] = t[i] - end - end, - stopfigure = function() - local cd = chardata[n] - local code = unicodetoactualtext(slot,concat(code," ")) or "" - descriptions[slot] = { - -- unicode = slot, - name = cd and cd.adobename, - width = width * 100, - height = height * 100, - depth = depth * 100, - boundingbox = { llx, lly, urx, ury }, - } - if inline then - characters[slot] = { - commands = { - { "pdf", "origin", code }, - } - } - else - characters[slot] = { - commands = { - { - "image", - { - stream = code, - bbox = { 0, -depth * 65536, width * 65536, height * 65536 } - }, - }, - } - } - end - code = nil -- no need to keep that - end -} - -local function process(mpxformat,name,instances,scalefactor) - local filename = resolvers.findfile(name) - local attributes = filename and lfs.isfile(filename) and lfs.attributes(filename) - if attributes then - statistics.starttiming(metapost.fonts) - scalefactor = scalefactor or 1 - instances = instances or metapost.fonts.instances or 1 -- maybe store in liost too - local fontname = file.removesuffix(file.basename(name)) - local modification = attributes.modification - local filesize = attributes.size - local hash = file.robustname(formatters["%s %05i %03i"](fontname,scalefactor*1000,instances)) - local lists = containers.read(mpfonts.cache,hash) - if not lists or lists.modification ~= modification or lists.filesize ~= filesize or lists.instances ~= instances or lists.scalefactor ~= scalefactor then - statistics.starttiming(flusher) - local data = io.loaddata(filename) - metapost.reset(mpxformat) - metapost.setoutercolor(2) -- no outer color and no reset either - lists = { } - for i=1,instances do - characters = { } - descriptions = { } - metapost.process { - mpx = mpxformat, - flusher = flusher, - askedfig = "all", - -- incontext = false, - data = { - formatters["randomseed := %s ;"](i*10), - formatters["charscale := %s ;"](scalefactor), - data, - }, - } - lists[i] = { - characters = characters, - descriptions = descriptions, - parameters = { - designsize = 655360, - slant = 0, - space = 333 * scalefactor, - space_stretch = 166.5 * scalefactor, - space_shrink = 111 * scalefactor, - x_height = 431 * scalefactor, - quad = 1000 * scalefactor, - extra_space = 0, - }, - properties = { - name = formatters["%s-%03i"](hash,i), - spacer = "space", - } - } - end - lists.version = metapost.variables.fontversion or "1.000" - lists.modification = modification - lists.filesize = filesize - lists.instances = instances - lists.scalefactor = scalefactor - metapost.reset(mpxformat) -- saves memory - lists = containers.write(mpfonts.cache, hash, lists) - statistics.stoptiming(flusher) - end - variants = variants + #lists - statistics.stoptiming(metapost.fonts) - return lists - else - return { } - end -end - -metapost.fonts.flusher = flusher -metapost.fonts.instances = 1 -metapost.fonts.process = process - -local function build(g,v) - local size = g.specification.size - local data = process(v[2],v[3],v[4],size/655360,v[6]) - local list = { } - local t = { } - for d=1,#data do - t = fonts.constructors.scale(data[d],-1000) - -- local id = font.nextid() - -- t.fonts = { { id = id } } - fontdata[id] = t - if v[5] then - vffonts.helpers.composecharacters(t) - end - list[d] = font.define(t) - end - for k, v in next, t do -- last t - g[k] = v -- kind of replace, when not present, make nil - end - g.variants = list -end - -vffonts.combiner.commands.metapost = build -vffonts.combiner.commands.metafont = build - -statistics.register("metapost font generation", function() - if total > 0 then - local time = statistics.elapsedtime(flusher) - if total > 0 then - return format("%i glyphs, %s seconds runtime, %.1f glyphs/second", total, time, total/tonumber(time)) - else - return format("%i glyphs, %s seconds runtime", total, time) - end - end -end) - -statistics.register("metapost font loading",function() - if variants > 0 then - local time = statistics.elapsedtime(metapost.fonts) - if variants > 0 then - return format("%s seconds, %i instances, %.3f instances/second", time, variants, variants/tonumber(time)) - else - return format("%s seconds, %i instances", time, variants) - end - end -end) - --- fonts.definers.methods.install( "bidi", { --- { --- "metapost", -- method --- "metafun", -- format --- "fontoeps.mp", -- filename --- 1, -- instances --- false, -- compose --- }, --- } ) - -local report = logs.reporter("metapost","fonts") +metapost.fonts = metapost.fonts or { } function metapost.fonts.define(specification) - local fontname = specification.fontname or "" - local filename = specification.filename or "" - local format = specification.format or "metafun" - if fontname == "" then - report("no fontname given") - return - end - if filename == "" then - report("no filename given for %a",fontname) - return - end - local fullname = resolvers.findfile(filename) - if fullname == "" then - report("unable to locate file %a",filename) - return - end - report("generating font %a using format %a and file %a",fontname,format,filename) - fonts.definers.methods.install(fontname, { - { - specification.engine or "metapost", - format, - filename, - specification.instances or 1, - specification.compose or false, - }, - } ) + local fontname = specification.fontname + local filename = specification.filename + local fontsize = specification.size + context.definemetafont ( + { fontname }, + { filename }, + { size and { "at " .. size .. " sp" } or "" } + ) end -interfaces.implement { - name = "definemetafont", - actions = metapost.fonts.define, - arguments = { - { - { "fontname" }, - { "filename" }, - } - } -} - -- metapost.fonts.define { -- fontname = "bidi", -- filename = "bidi-symbols.mp", diff --git a/tex/context/base/mkxl/meta-fnt.mkxl b/tex/context/base/mkxl/meta-fnt.mkxl index 9efee0ee5..0a2cab991 100644 --- a/tex/context/base/mkxl/meta-fnt.mkxl +++ b/tex/context/base/mkxl/meta-fnt.mkxl @@ -17,17 +17,27 @@ \unprotect -\permanent\tolerant\protected\def\definemetafont[#1]#*[#2]#*[#3]% - {\clf_definemetafont - fontname {#1}% - filename {#2}% - % no #3 settings yet (compose, instances) - \relax} +%D Contrary to the \MKIV\ variant we dont' use the \LUA\ call but the \LUA\ call +%D uses this here: -% \startluacode -% metapost.fonts.define { fontname = "bidi-symbols", filename = "bidi-symbols.mp" } -% \stopluacode +\permanent\tolerant\protected\def\definemetafont[#1]#*[#2]#*[#3]% + {\doonlyonce{#2}{% + \definefontfeature[metafont-#2][metapost=#2] + \startMPcalculation{simplefun} input "#2" ; \stopMPcalculation + }% + \definefont[#1][Mono*metafont-#2 #3]} -% \definemetafont[bidi-symbols][bidi-symbols.mp] +%D Demo: +%D +%D \starttyping +%D \setupbodyfont[dejavu] +%D \starttext +%D \showglyphs +%D \startTEXpage[offset=10pt] +%D \definemetafont[MyDemoA][demo-symbols][at 10pt]\MyDemoA\char9754 +%D \definemetafont[MyDemoB][demo-symbols][at 30pt]\MyDemoB\char9754 +%D \stopTEXpage +%D \stoptext +%D \stoptyping \protect \endinput diff --git a/tex/context/base/mkxl/mlib-fio.lmt b/tex/context/base/mkxl/mlib-fio.lmt index 4290537dd..895178b04 100644 --- a/tex/context/base/mkxl/mlib-fio.lmt +++ b/tex/context/base/mkxl/mlib-fio.lmt @@ -1,4 +1,4 @@ -if not modules then modules = { } end modules ['mlib-run'] = { +if not modules then modules = { } end modules ['mlib-fio'] = { version = 1.001, comment = "companion to mlib-ctx.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -80,7 +80,9 @@ local function writetoterminal(terminaldata,maxterm,d) elseif t == "table" then for i=1,#d do local l = d[i] - if find(l,"[\n\r]") then + if not l then + -- just ignore + elseif find(l,"[\n\r]") then local s = splitlines(l) local m = #s for i=1,m do @@ -177,10 +179,45 @@ local function finder(name,mode,kind) return findtexfile(name,kind) end +local overloadmode = "warning" + +directives.register("metapost.overloadmode",function(v) + if v == "warning" or v == "error" then + overloadmode = v + else + overloadmode= false + end +end) + +local propertycodes = { + [-3] = "mutable", + [ 1] = "primitive", + [ 2] = "permanent", + [ 3] = "immutable", +} + +mplib.propertycodes = propertycodes + +local function overload(property,name) + if overloadmode then + -- turn of warning after format is loaded + report_metapost("overloading %s %a",propertycodes[property] or "unknown", name) + -- no overload permitted + if overloadmode == "error" then + luatex.abort() + end + return false + else + -- overload permitted + return true + end +end + function mplib.new(specification) local openfile = fileopener() specification.find_file = finder specification.run_logger = logger + specification.run_overload = overload specification.open_file = openfile specification.interaction = "silent" specification.halt_on_error = true diff --git a/tex/context/base/mkxl/mlib-int.lmt b/tex/context/base/mkxl/mlib-int.lmt index 86e2e5732..3ea270a79 100644 --- a/tex/context/base/mkxl/mlib-int.lmt +++ b/tex/context/base/mkxl/mlib-int.lmt @@ -18,10 +18,11 @@ local exheights = fonts.hashes.exheights local registerscript = metapost.registerscript -local on_right_page = structures.pages.on_right -local is_odd_page = structures.pages.is_odd -local in_body_page = structures.pages.in_body -local page_fraction = structures.pages.fraction +local on_right_page = structures.pages.on_right +local is_odd_page = structures.pages.is_odd +local in_body_page = structures.pages.in_body +local page_fraction = structures.pages.fraction +local layout_has_changed = structures.pages.has_changed local function defaultcolormodel() -- can be helper local colormethod = getcount("MPcolormethod") @@ -127,9 +128,10 @@ registerscript("OverlayRegion", function() mpstring(getmacro("m_overlay_r -- -- registerscript("PageFraction", page_fraction) -registerscript("PageFraction", function() return mpboolean(page_fraction()) end) -registerscript("OnRightPage", function() return mpboolean(on_right_page()) end) -registerscript("OnOddPage", function() return mpboolean(is_odd_page ()) end) -registerscript("InPageBody", function() return mpboolean(in_body_page ()) end) +registerscript("PageFraction", function() return mpboolean(page_fraction ()) end) +registerscript("OnRightPage", function() return mpboolean(on_right_page ()) end) +registerscript("OnOddPage", function() return mpboolean(is_odd_page ()) end) +registerscript("InPageBody", function() return mpboolean(in_body_page ()) end) +registerscript("LayoutHasChanged", function() return mpboolean(layout_has_changed()) end) registerscript("defaultcolormodel", defaultcolormodel) diff --git a/tex/context/base/mkxl/mlib-lmt.lmt b/tex/context/base/mkxl/mlib-lmt.lmt index f0b59248a..355674582 100644 --- a/tex/context/base/mkxl/mlib-lmt.lmt +++ b/tex/context/base/mkxl/mlib-lmt.lmt @@ -10,15 +10,16 @@ if not modules then modules = { } end modules ['mlib-lmt'] = { local type = type -local aux = mp.aux -local mpdirect = aux.direct -local mppath = mp.path +local aux = mp.aux +local mpdirect = aux.direct +local mppath = mp.path -local scan = mp.scan -local scannumeric = scan.numeric -local scanpath = scan.path +local scan = mp.scan +local scannumeric = scan.numeric +local scanpath = scan.path -local getparameter = metapost.getparameter +local getparameter = metapost.getparameter +local registerscript = metapost.registerscript function mp.lmt_function_x(xmin,xmax,xstep,code,shape) -- experimental local code = "return function(x) return " .. code .. " end" @@ -126,27 +127,21 @@ function mp.lmt_svg_include() end end - -function mp.lmt_do_remaptext() +registerscript("remaptext", function() local parameters = metapost.scanparameters() if parameters and parameters.label then metapost.remaptext(parameters) end -end +end) do local dropins = fonts.dropins - local registerglyphs = dropins.registerglyphs local registerglyph = dropins.registerglyph + local registerglyphs = dropins.registerglyphs - function mp.lmt_register_glyph() - registerglyph(metapost.getparameterset("mpsglyph")) - end - - function mp.lmt_register_glyphs() - registerglyphs(metapost.getparameterset("mpsglyphs")) - end + registerscript("registerglyph", function() registerglyph (metapost.getparameterset("mpsglyph")) end) + registerscript("registerglyphs",function() registerglyphs(metapost.getparameterset("mpsglyphs")) end) end diff --git a/tex/context/base/mkxl/mlib-pdf.lmt b/tex/context/base/mkxl/mlib-pdf.lmt index e737b5d86..c1f5045d7 100644 --- a/tex/context/base/mkxl/mlib-pdf.lmt +++ b/tex/context/base/mkxl/mlib-pdf.lmt @@ -348,17 +348,17 @@ end -- lpegmatch(pattern_key,object.prescript,1,variables) -- end -function metapost.processspecial(str) - local code = loadstring(str) - if code then - if trace_variables then - report_metapost("executing special code: %s",str) - end - code() - else - report_metapost("invalid special code: %s",str) - end -end +-- function metapost.processspecial(str) +-- local code = loadstring(str) +-- if code then +-- if trace_variables then +-- report_metapost("executing special code: %s",str) +-- end +-- code() +-- else +-- report_metapost("invalid special code: %s",str) +-- end +-- end local stack = { } @@ -407,7 +407,7 @@ function metapost.flush(specification,result) local stopfigure = flusher.stopfigure local flushfigure = flusher.flushfigure local textfigure = flusher.textfigure - local processspecial = flusher.processspecial or metapost.processspecial + -- local processspecial = flusher.processspecial or metapost.processspecial metapost.comment = flusher.comment or nocomment for index=1,#figures do local figure = figures[index] @@ -443,58 +443,7 @@ function metapost.flush(specification,result) for o=1,#objects do local object = objects[o] local objecttype = object.type - if objecttype == "text" then - result[#result+1] = "q" - local ot = object.transform -- 3,4,5,6,1,2 - result[#result+1] = f_cm(ot[3],ot[4],ot[5],ot[6],ot[1],ot[2]) - flushfigure(result) -- flush accumulated literals - result = { } - textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth) - result[#result+1] = "Q" - elseif objecttype == "special" then - if processspecial then - processspecial(object.prescript) - end - elseif objecttype == "start_clip" then - local evenodd = not object.istext and object.postscript == "evenodd" - result[#result+1] = "q" - flushnormalpath(object.path,result,false) - result[#result+1] = evenodd and "W* n" or "W n" - elseif objecttype == "stop_clip" then - result[#result+1] = "Q" - miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false - elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then - -- skip - elseif objecttype == "start_group" then - if lpdf.flushgroup then - local before, after = processplugins(object) - if before then - result[#result+1] = "q" - result = pluginactions(before,result,flushfigure) - insert(groupstack, { - after = after, - result = result, - bbox = toboundingbox(object.path), - }) - result = { } - miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false - else - insert(groupstack,false) - end - else - insert(groupstack,false) - end - elseif objecttype == "stop_group" then - local data = remove(groupstack) - if data then - local reference = lpdf.flushgroup(concat(result,"\r"),data.bbox) - result = data.result - result[#result+1] = reference - result = pluginactions(data.after,result,flushfigure) - result[#result+1] = "Q" - miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false - end - else + if objecttype == "fill" or objecttype == "outline" then -- we use an indirect table as we want to overload -- entries but this is not possible in userdata -- @@ -676,6 +625,60 @@ function metapost.flush(specification,result) -- can be qQ'd so changes can end up in groups miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false end + elseif objecttype == "start_clip" then + local evenodd = not object.istext and object.postscript == "evenodd" + result[#result+1] = "q" + flushnormalpath(object.path,result,false) + result[#result+1] = evenodd and "W* n" or "W n" + elseif objecttype == "stop_clip" then + result[#result+1] = "Q" + miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false + elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then + -- skip + elseif objecttype == "start_group" then + if lpdf.flushgroup then + local before, after = processplugins(object) + if before then + result[#result+1] = "q" + result = pluginactions(before,result,flushfigure) + insert(groupstack, { + after = after, + result = result, + bbox = toboundingbox(object.path), + }) + result = { } + miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false + else + insert(groupstack,false) + end + else + insert(groupstack,false) + end + elseif objecttype == "stop_group" then + local data = remove(groupstack) + if data then + local reference = lpdf.flushgroup(concat(result,"\r"),data.bbox) + result = data.result + result[#result+1] = reference + result = pluginactions(data.after,result,flushfigure) + result[#result+1] = "Q" + miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false + end + -- if objecttype == "text" then + -- result[#result+1] = "q" + -- local ot = object.transform -- 3,4,5,6,1,2 + -- result[#result+1] = f_cm(ot[3],ot[4],ot[5],ot[6],ot[1],ot[2]) + -- flushfigure(result) -- flush accumulated literals + -- result = { } + -- textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth) + -- result[#result+1] = "Q" + -- elseif objecttype == "special" then + -- if processspecial then + -- processspecial(object.prescript) + -- end + -- else + else + -- error end end end diff --git a/tex/context/base/mkxl/mlib-run.lmt b/tex/context/base/mkxl/mlib-run.lmt index 602d6f36c..2af504feb 100644 --- a/tex/context/base/mkxl/mlib-run.lmt +++ b/tex/context/base/mkxl/mlib-run.lmt @@ -31,7 +31,7 @@ nears zero.</p> local type, tostring, tonumber, next = type, tostring, tonumber, next local find, striplines = string.find, utilities.strings.striplines -local concat, insert, remove = table.concat, table.insert, table.remove +local concat, insert, remove, sortedkeys = table.concat, table.insert, table.remove, table.sortedkeys local emptystring = string.is_empty @@ -641,6 +641,9 @@ do format = "metafun", -- or: minifun method = "double", } +-- if not code then +-- code = "" +-- end metapost.process { mpx = mpx, flusher = flusher, @@ -661,7 +664,7 @@ do end -local getstatistics = mplib.getstatistics or mplib.statistics +local getstatistics = mplib.getstatistics function metapost.getstatistics(memonly) if memonly then @@ -679,3 +682,26 @@ function metapost.getstatistics(memonly) return t end end + +local gethashentries = mplib.gethashentries + +function metapost.gethashentries(name,full) + if name then + local mpx = mpxformats[name] or mpxformats[name .. ":1"] + if mpx then + return gethashentries(mpx,full) + end + else + local t = { } + for name, mpx in next, mpxformats do + t[name] = gethashtokens(mpx,full) + end + return t + end +end + +local gethashtokens = mplib.gethashtokens + +function metapost.getinstancenames() + return sortedkeys(mpxformats) +end diff --git a/tex/context/base/mkxl/page-lay.mkxl b/tex/context/base/mkxl/page-lay.mkxl index 6cd0d9579..01098ae3b 100644 --- a/tex/context/base/mkxl/page-lay.mkxl +++ b/tex/context/base/mkxl/page-lay.mkxl @@ -123,6 +123,8 @@ \doifnothing{\directlayoutparameter\c!state}{\letlayoutparameter\c!state\v!start}% \to \everydefinelayout +\newconditional \layouthaschanged \settrue\layouthaschanged % used in mp + \appendtoks \ifcase\layoutsetupmode % can't happen @@ -153,8 +155,13 @@ \page_layouts_synchronize \page_layouts_check_next \fi + \global\settrue\layouthaschanged \to \everysetuplayout +\appendtoks + \global\setfalse\layouthaschanged +\to \everyaftershipout + \permanent\def\doifelselayoutdefined#1% {\ifcsname\namedlayouthash{#1}\c!state\endcsname % maybe a helper \expandafter\firstoftwoarguments diff --git a/tex/context/base/mkxl/syst-lua.lmt b/tex/context/base/mkxl/syst-lua.lmt index 673c99ae3..faad64ff0 100644 --- a/tex/context/base/mkxl/syst-lua.lmt +++ b/tex/context/base/mkxl/syst-lua.lmt @@ -313,10 +313,11 @@ end do -- This is some 20% slower than native but we only provide this for compatibility - -- reasons so we don't care that much about it. Eventually we can drop the - -- built-in method. + -- reasons so we don't care that much about it. Eventually we can drop the built-in + -- method. local channels = { } + local lastdone = { } local findbinfile = resolvers.findbinfile local loadbinfile = resolvers.loadbinfile @@ -328,7 +329,20 @@ do local scancsname = tokens.scanners.csname local setmacro = tokens.setters.macro - local vrbcatcodes = tex.vrbcatcodes + local rlncatcodes = tex.rlncatcodes + local texgetcount = tex.getcount + + local bytes = string.bytes + local getcatcode = tex.getcatcode + + local char, concat = string.char, table.concat + + -- This uses the normal bin lookup method that we also use for other files, + -- and in principle there is no limit on the amount of files other than the + -- operating system imposes. + + local t = { } + local l = 0 implement { name = "openin", @@ -345,8 +359,10 @@ do local f = findbinfile(s,"tex") if f then channels[n] = opentexfile(f) + lastdone[n] = false else channels[n] = false + lastdone[n] = true end end, } @@ -362,10 +378,19 @@ do c:close() end channels[n] = false + t = { } end, } - implement { + -- This is not the fastest routine but hardly used, occasionally for line by line + -- input in e.g. tikz which is not that fast anyway so I'll deal with it when it + -- really is a bottleneck. + + local readerror = function(what,n) + tex.error(string.formatters["too many %s brace tokens in read from channel %i"](what,n)) + end + + interfaces.implement { name = "read", public = true, usage = "value", @@ -374,18 +399,67 @@ do scankeyword("to") local m = scancsname(true) local c = channels[n] - local s = c and c:reader() - if s then - s = gsub(s," *$"," ") -- or just "", no need to fake tex here + local g = 0 + local s + l = 0 + if c then + while true do + local s = c and c:reader() + if s then + l = l + 1 + s = gsub(s," *$","") + t[l] = s + for c in bytes(s) do + local cc = getcatcode(c) + if cc == 1 then + g = g + 1 + elseif cc == 2 then + g = g - 1 + end + end + if g <= 0 then + break + end + else + break + end + end + if g > 0 then + readerror("left",n) + s = "" + elseif g < 0 then + readerror("right",n) + s = "" + elseif l == 0 then + if c:endoffile() then + lastdone[n] = true + s = "\\par" + else + s = "" + end + channels[n] = false + else + local e = texgetcount("endlinechar") -- we can have tex.endline if needed + if e < 0 or e > 127 then + e = "" + else + e = " " + l = l + 1 + t[l] = "" + end + s = concat(t, e, 1, l) + end else - channels[n] = false - -- s = "\\par" - s = "" -- no need to fake tex here + s = "" end setmacro(m, s, prefix) -- checks for frozen end, } + -- This is an etex extension. All characters become catcode 12 but a space gets code + -- 10. The etex manual specifies a lineending of catcode 12 too. Should we strip spaces + -- at the end? A bit weird command that we never use anyway. + implement { name = "readline", public = true, @@ -397,15 +471,21 @@ do local c = channels[n] local s = c and c:reader() if s then - -- should we strip spaces at the end? + local e = texgetcount("endlinechar") -- we can have tex.endline if needed + if e > 0 then -- forget about 0 + s = s .. char(e) + end else channels[n] = false s = "" end - setmacro(vrbcatcodes, m, s, prefix) -- checks for frozen + setmacro(rlncatcodes, m, s, prefix) -- checks for frozen end, } + -- This one uses the special lua condition option which is kind of experimental + -- but seems to work fine. + local boolean_value = tokens.values.boolean implement { @@ -418,7 +498,8 @@ do end, } - -- for the moment here: + -- This one doesn't belong here and it might become a real primitive if we need it + -- frequently. So, for the moment we keep it in this file. local getnest = tex.getnest diff --git a/tex/context/base/mkxl/trac-inf.lmt b/tex/context/base/mkxl/trac-inf.lmt index 23000b7d0..301a56e9b 100644 --- a/tex/context/base/mkxl/trac-inf.lmt +++ b/tex/context/base/mkxl/trac-inf.lmt @@ -179,9 +179,10 @@ function statistics.show() end) register("tex properties", function() local t = status.gethashstate() + local l = status.getlookupstate() local m = status.gettexstate() - return format("%s hash slots used of %s, approximate memory usage: %i MB", - t.top, t.max, m.approximate // (1024 * 1024)) + return format("%s hash slots used of %s, %s control sequences, approximate memory usage: %i MB", + t.top, t.max, l.ptr, m.approximate // (1024 * 1024)) end) register("callbacks", statistics.callbacks) -- so far diff --git a/tex/context/modules/mkiv/m-tikz.mkiv b/tex/context/modules/mkiv/m-tikz.mkiv index 73124716b..e04d53156 100644 --- a/tex/context/modules/mkiv/m-tikz.mkiv +++ b/tex/context/modules/mkiv/m-tikz.mkiv @@ -25,8 +25,12 @@ \popcatcodetable +\protected\def\tikzerrormessage#1#2#3% + {\writestatus{#1}{#2}} + \protected\def\starttikzpicture {\begingroup + \ifdefined\PackageError\else \let\PackageError\tikzerrormessage \fi \overloadmode\zerocount \tikzpicture} @@ -34,5 +38,4 @@ {\endtikzpicture \endgroup} - \stopmodule diff --git a/tex/context/modules/mkiv/s-system-macros.mkxl b/tex/context/modules/mkiv/s-system-macros.mkxl index 6dcccd048..63ac17913 100644 --- a/tex/context/modules/mkiv/s-system-macros.mkxl +++ b/tex/context/modules/mkiv/s-system-macros.mkxl @@ -15,8 +15,17 @@ %D to the more robust \LMTX. % todo: \permanent\protected\let\select\directselect : same value (add ref) - -% todo: when mutable, remove immutable +% +% todo: when mutable, remove immutable, decide on how immutable it really is +% +% todo: just for the fun of it we can compare the table generated here to mkiv but it +% needs the same hash exposure as we use in luametatex so i'll do that after all lmtx +% macros are checked (maybe i need to backport some more but it has a low priority) +% +% todo: do we need something similar for lua functions etc (problem: performance hit) +% +% todo: and how about metapost ... i need a mp.hashtokens in order to do that and I also +% need to think about protection there (tricky as we use an avl hash there) \startmodule[system-macros] @@ -106,7 +115,7 @@ local permanent = v.permanent and "permanent" local primitive = v.primitive and "primitive" local instance = v.instance and "instance" - local dealtwith = mutable or immutable or frozen or permanent or primitive -- beware: we can have combinations + local dealtwith = mutable or immutable or mutable or frozen or permanent or primitive -- beware: we can have combinations local whatever = find(k,"^[a-z][a-z][a-z]+_") local cscommand = gsub(v.cmdname or "","_"," ") local undefined = cscommand == "undefined cs" @@ -146,14 +155,17 @@ end context.stoptabulate() end - show(true, function(k) return find(k,"^[a-zA-Z]+$") end) - - context.page() + context.starttitle { title = "Public \\TEX\\ commands" } + show(true, function(k) return find(k,"^[a-zA-Z]+$") end) + context.stoptitle() - show(false, function(k) return not find(k,"^[a-zA-Z]+$") and not find(k,crap) end) + context.starttitle { title = "Private \\TEX\\ commands" } + show(false, function(k) return not find(k,"^[a-zA-Z]+$") and not find(k,crap) end) + context.stoptitle() - tokens.setters.macro("NumberOfVisible", total) + tokens.setters.macro("NumberOfVisible", total) tokens.setters.macro("NumberOfFlagged", flagged) + \stopluacode \page @@ -175,9 +187,97 @@ the core of \CONTEXT. Noaligned macros are special and dealt deep down when scanning for alignment specific primitives. \par \stop -\starttabulate -\BC number of visible macros \NC \NumberOfVisible \NC \NR -\BC number of flagged macros \NC \NumberOfFlagged \NC \NR +\starttabulate[||r|] +\BC number of visible macros \NC \NumberOfVisible \NC \NR +\BC number of flagged macros \NC \NumberOfFlagged \NC \NR +\stoptabulate + +\page + +% \startMPcalculation +% % force a format +% \stopMPcalculation + +\startluacode + + local context = context + -- local ctx_NC = context.NC + -- local ctx_NR = context.NR + -- local ctx_bold = context.bold + + local find = string.find + local gsub = string.gsub + local create = token.create + + context.starttitle { title = "\\METAFUN\\ commands" } + + metapost.simple("prestine") + + local mptotal = 0 + local codes = metapost.codes + local types = metapost.types + local procodes = mplib.propertycodes + + context.startcolumns { n = 5, distance = "1em" } + context.nohyphens(false) + context.obeyspaces(false) + local t = metapost.gethashentries("prestine",true) -- prestine:1 + if t then + table.sort(t,function(a,b) return a[3] < b[3] end) + mptotal = #t + for i=1,mptotal do + local ti = t[i] + local code = codes[ti[1]] + local property = procodes[ti[2]] + local name = ti[3] + local vtype = ti[4] + local vardef = vtype and types[vtype] == "unsuffixedmacro" + context.bgroup() + context("%-15s\\quad",vardef and "(vardef) tag" or code) + if property == "primitive" then + context.bf(false) + context.blue(false) + elseif property == "permanent" then + context.bf(false) + context.lightgreen(false) + elseif property == "immutable" then + context.bf(false) + context.lightcyan(false) + elseif property == "mutable" then + context.bf(false) + context.lightmagenta(false) + elseif not find(name,"^%a+_") then + -- todo: + if vardef then + context.bf(false) + context.lightred(false) + end + end + context.verbatim(name) + context.egroup() + context.par() + end + end + context.stopcolumns() + + context.stoptitle() + + tokens.setters.macro("NumberOfMetafun", mptotal) +\stopluacode + +\page + +\starttabulate[|c|l|] +\HL +\NC \type {-3} \NC \bf \lightmagenta mutable \NC \NR +\NC \type {1} \NC \bf \blue primitive \NC \NR +\NC \type {2} \NC \bf \lightgreen permanent \NC \NR +\NC \type {3} \NC \bf \lightcyan immutable \NC \NR +\HL +\stoptabulate + +\starttabulate[||r|] +\BC number of metafun entries \NC \NumberOfMetafun \NC \NR \stoptabulate \stoptext diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index b40f2c8f2..256bf1175 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 : 2020-12-10 22:23 +-- merge date : 2020-12-15 10:10 do -- begin closure to overcome local limits and interference |