From aacdde41ef02392949aee16b2e428a8913d27efe Mon Sep 17 00:00:00 2001
From: Hans Hagen
Date: Tue, 7 Aug 2007 01:37:00 +0200
Subject: stable 2007.08.07 01:37
---
context/data/context.properties | 59 +-
metapost/context/base/mp-base.mp | 5 +-
scripts/context/lua/luatools.cmd | 5 +
scripts/context/lua/luatools.lua | 5155 ++++++++++
scripts/context/lua/mtx-cache.lua | 92 +
scripts/context/lua/mtx-fonts.lua | 90 +
scripts/context/lua/mtxrun.cmd | 5 +
scripts/context/lua/mtxrun.lua | 4625 +++++++++
scripts/context/lua/x-ldx.lua | 310 +
scripts/context/ruby/base/merge.rb | 2 +-
scripts/context/ruby/ctxtools.rb | 17 +-
scripts/context/ruby/mtxtools.rb | 475 +
scripts/context/ruby/texmfstart.rb | 502 +-
tex/context/base/attr-ini.lua | 669 ++
tex/context/base/attr-ini.tex | 199 +
tex/context/base/char-cmp.lua | 260 +
tex/context/base/char-def.lua | 17736 +++++++++++++++++++++++++++++++++++
tex/context/base/char-def.tex | 47 +
tex/context/base/char-ini.lua | 280 +
tex/context/base/char-ini.tex | 20 +
tex/context/base/char-utf.lua | 298 +
tex/context/base/char-utf.tex | 54 +
tex/context/base/colo-ini.tex | 20 +-
tex/context/base/colo-run.tex | 29 +-
tex/context/base/cont-mtx.tex | 25 +
tex/context/base/cont-new.mkiv | 93 +
tex/context/base/context-debug.lmx | 108 +
tex/context/base/context-error.lmx | 55 +
tex/context/base/context.css | 226 +
tex/context/base/context.tex | 11 +-
tex/context/base/core-buf.lua | 377 +
tex/context/base/core-buf.mkiv | 107 +
tex/context/base/core-con.lua | 164 +
tex/context/base/core-con.mkiv | 90 +
tex/context/base/core-des.tex | 19 +-
tex/context/base/core-itm.tex | 38 +-
tex/context/base/core-lst.tex | 2 +
tex/context/base/core-ntb.tex | 8 +-
tex/context/base/core-obj.lua | 36 +
tex/context/base/core-obj.mkiv | 55 +
tex/context/base/core-pos.lua | 95 +
tex/context/base/core-pos.mkiv | 65 +
tex/context/base/core-reg.lua | 237 +
tex/context/base/core-reg.mkiv | 57 +
tex/context/base/core-sec.tex | 3 +
tex/context/base/core-spa.lua | 526 ++
tex/context/base/core-spa.mkiv | 126 +
tex/context/base/core-syn.lua | 116 +
tex/context/base/core-syn.mkiv | 51 +
tex/context/base/core-sys.mkiv | 23 +
tex/context/base/core-two.lua | 83 +
tex/context/base/core-two.mkiv | 74 +
tex/context/base/core-uti.lua | 29 +
tex/context/base/core-uti.mkiv | 103 +
tex/context/base/core-ver.mkiv | 207 +
tex/context/base/core-ver.tex | 4 +-
tex/context/base/enco-def.tex | 16 +
tex/context/base/enco-ini.mkiv | 69 +
tex/context/base/enco-pfr.mkiv | 20 +
tex/context/base/font-afm.lua | 574 ++
tex/context/base/font-def.lua | 477 +
tex/context/base/font-enc.lua | 98 +
tex/context/base/font-fbk.lua | 219 +
tex/context/base/font-ini.lua | 57 +
tex/context/base/font-ini.mkii | 57 -
tex/context/base/font-ini.mkiv | 72 +
tex/context/base/font-ini.tex | 818 +-
tex/context/base/font-map.lua | 172 +
tex/context/base/font-otf.lua | 3416 +++++++
tex/context/base/font-syn.lua | 285 +
tex/context/base/font-tfm.lua | 479 +
tex/context/base/font-vf.lua | 213 +
tex/context/base/l-aux.lua | 39 +
tex/context/base/l-boolean.lua | 41 +
tex/context/base/l-dir.lua | 94 +
tex/context/base/l-file.lua | 101 +
tex/context/base/l-io.lua | 173 +
tex/context/base/l-math.lua | 25 +
tex/context/base/l-md5.lua | 18 +
tex/context/base/l-number.lua | 10 +
tex/context/base/l-os.lua | 27 +
tex/context/base/l-string.lua | 307 +
tex/context/base/l-table.lua | 492 +
tex/context/base/l-tex.lua | 110 +
tex/context/base/l-unicode.lua | 148 +
tex/context/base/l-utils.lua | 121 +
tex/context/base/lang-ini.lua | 327 +
tex/context/base/lang-ini.mkiv | 23 +
tex/context/base/lang-ini.tex | 4 +-
tex/context/base/lang-sla.mkiv | 17 +
tex/context/base/luat-cbk.lua | 150 +
tex/context/base/luat-crl.lua | 53 +
tex/context/base/luat-deb.lua | 154 +
tex/context/base/luat-deb.tex | 49 +
tex/context/base/luat-env.lua | 172 +
tex/context/base/luat-env.tex | 169 +
tex/context/base/luat-exe.lua | 68 +
tex/context/base/luat-ini.lua | 11 +
tex/context/base/luat-ini.tex | 104 +
tex/context/base/luat-inp.lua | 1865 ++++
tex/context/base/luat-iop.lua | 157 +
tex/context/base/luat-kps.lua | 102 +
tex/context/base/luat-lib.lua | 144 +
tex/context/base/luat-lib.tex | 86 +
tex/context/base/luat-lmx.lua | 139 +
tex/context/base/luat-lmx.tex | 16 +
tex/context/base/luat-log.lua | 123 +
tex/context/base/luat-tex.lua | 389 +
tex/context/base/luat-tmp.lua | 457 +
tex/context/base/luat-tra.lua | 82 +
tex/context/base/luat-uni.lua | 23 +
tex/context/base/luat-uni.tex | 33 +
tex/context/base/luat-zip.lua | 221 +
tex/context/base/meta-pdf.lua | 585 ++
tex/context/base/meta-pdf.mkiv | 155 +
tex/context/base/mult-sys.tex | 1 +
tex/context/base/node-ini.lua | 905 ++
tex/context/base/node-ini.tex | 46 +
tex/context/base/page-imp.tex | 4 +-
tex/context/base/regi-8859-1.lua | 26 +
tex/context/base/regi-8859-10.lua | 26 +
tex/context/base/regi-8859-11.lua | 26 +
tex/context/base/regi-8859-13.lua | 26 +
tex/context/base/regi-8859-14.lua | 26 +
tex/context/base/regi-8859-15.lua | 26 +
tex/context/base/regi-8859-16.lua | 26 +
tex/context/base/regi-8859-2.lua | 26 +
tex/context/base/regi-8859-3.lua | 26 +
tex/context/base/regi-8859-4.lua | 26 +
tex/context/base/regi-8859-5.lua | 26 +
tex/context/base/regi-8859-6.lua | 26 +
tex/context/base/regi-8859-7.lua | 26 +
tex/context/base/regi-8859-8.lua | 26 +
tex/context/base/regi-8859-9.lua | 26 +
tex/context/base/regi-cp1250.lua | 26 +
tex/context/base/regi-cp1251.lua | 26 +
tex/context/base/regi-cp1252.lua | 26 +
tex/context/base/regi-cp1253.lua | 26 +
tex/context/base/regi-cp1254.lua | 26 +
tex/context/base/regi-cp1255.lua | 26 +
tex/context/base/regi-cp1256.lua | 26 +
tex/context/base/regi-cp1257.lua | 26 +
tex/context/base/regi-cp1258.lua | 26 +
tex/context/base/regi-ini.lua | 107 +
tex/context/base/regi-ini.mkiv | 39 +
tex/context/base/regi-utf.tex | 4 +-
tex/context/base/s-abr-01.tex | 140 +-
tex/context/base/s-mag-01.tex | 7 +
tex/context/base/sort-def.mkiv | 16 +
tex/context/base/sort-ini.lua | 176 +
tex/context/base/sort-ini.mkiv | 21 +
tex/context/base/sort-lan.lua | 193 +
tex/context/base/sort-lan.mkiv | 16 +
tex/context/base/spec-def.mkiv | 19 +
tex/context/base/spec-fdf.mkiv | 19 +
tex/context/base/spec-fdf.tex | 4 +-
tex/context/base/spec-pdf.lua | 28 +
tex/context/base/supp-fil.lua | 78 +
tex/context/base/supp-fil.mkiv | 32 +
tex/context/base/syst-cat.mkiv | 116 +
tex/context/base/syst-con.lua | 30 +
tex/context/base/syst-con.mkiv | 27 +
tex/context/base/syst-new.tex | 15 +
tex/context/base/syst-prm.tex | 44 +-
tex/context/base/syst-rtp.mkiv | 18 +
tex/context/base/syst-str.mkiv | 27 +
tex/context/base/toks-ini.lua | 232 +
tex/context/base/toks-ini.tex | 25 +
tex/context/base/type-buy.tex | 1082 +--
tex/context/base/type-cbg.tex | 145 +-
tex/context/base/type-def.tex | 122 +-
tex/context/base/type-exa.tex | 241 +-
tex/context/base/type-fsf.tex | 130 +-
tex/context/base/type-ghz.tex | 50 +-
tex/context/base/type-gyr.tex | 189 +-
tex/context/base/type-ini.tex | 51 +-
tex/context/base/type-map.tex | 146 +-
tex/context/base/type-msw.tex | 50 +-
tex/context/base/type-one.tex | 1154 +++
tex/context/base/type-otf.tex | 720 ++
tex/context/base/type-siz.tex | 21 +-
tex/context/base/type-syn.tex | 38 -
tex/context/base/type-tmf.tex | 1073 +++
tex/context/base/type-xtx.tex | 2 +-
tex/context/base/verb-lua.lua | 212 +
tex/context/base/verb-mp.lua | 234 +
tex/context/base/verb-tex.lua | 122 +
tex/context/base/x-ldx.ctx | 23 +
tex/context/base/x-ldx.tex | 133 +
tex/context/interface/keys-cz.xml | 2 +-
tex/context/interface/keys-de.xml | 2 +-
tex/context/interface/keys-en.xml | 2 +-
tex/context/interface/keys-fr.xml | 2 +-
tex/context/interface/keys-it.xml | 2 +-
tex/context/interface/keys-nl.xml | 2 +-
tex/context/interface/keys-ro.xml | 2 +-
196 files changed, 55421 insertions(+), 2337 deletions(-)
create mode 100644 scripts/context/lua/luatools.cmd
create mode 100644 scripts/context/lua/luatools.lua
create mode 100644 scripts/context/lua/mtx-cache.lua
create mode 100644 scripts/context/lua/mtx-fonts.lua
create mode 100644 scripts/context/lua/mtxrun.cmd
create mode 100644 scripts/context/lua/mtxrun.lua
create mode 100644 scripts/context/lua/x-ldx.lua
create mode 100644 scripts/context/ruby/mtxtools.rb
create mode 100644 tex/context/base/attr-ini.lua
create mode 100644 tex/context/base/attr-ini.tex
create mode 100644 tex/context/base/char-cmp.lua
create mode 100644 tex/context/base/char-def.lua
create mode 100644 tex/context/base/char-def.tex
create mode 100644 tex/context/base/char-ini.lua
create mode 100644 tex/context/base/char-ini.tex
create mode 100644 tex/context/base/char-utf.lua
create mode 100644 tex/context/base/char-utf.tex
create mode 100644 tex/context/base/cont-mtx.tex
create mode 100644 tex/context/base/cont-new.mkiv
create mode 100644 tex/context/base/context-debug.lmx
create mode 100644 tex/context/base/context-error.lmx
create mode 100644 tex/context/base/context.css
create mode 100644 tex/context/base/core-buf.lua
create mode 100644 tex/context/base/core-buf.mkiv
create mode 100644 tex/context/base/core-con.lua
create mode 100644 tex/context/base/core-con.mkiv
create mode 100644 tex/context/base/core-obj.lua
create mode 100644 tex/context/base/core-obj.mkiv
create mode 100644 tex/context/base/core-pos.lua
create mode 100644 tex/context/base/core-pos.mkiv
create mode 100644 tex/context/base/core-reg.lua
create mode 100644 tex/context/base/core-reg.mkiv
create mode 100644 tex/context/base/core-spa.lua
create mode 100644 tex/context/base/core-spa.mkiv
create mode 100644 tex/context/base/core-syn.lua
create mode 100644 tex/context/base/core-syn.mkiv
create mode 100644 tex/context/base/core-sys.mkiv
create mode 100644 tex/context/base/core-two.lua
create mode 100644 tex/context/base/core-two.mkiv
create mode 100644 tex/context/base/core-uti.lua
create mode 100644 tex/context/base/core-uti.mkiv
create mode 100644 tex/context/base/core-ver.mkiv
create mode 100644 tex/context/base/enco-ini.mkiv
create mode 100644 tex/context/base/enco-pfr.mkiv
create mode 100644 tex/context/base/font-afm.lua
create mode 100644 tex/context/base/font-def.lua
create mode 100644 tex/context/base/font-enc.lua
create mode 100644 tex/context/base/font-fbk.lua
create mode 100644 tex/context/base/font-ini.lua
create mode 100644 tex/context/base/font-ini.mkiv
create mode 100644 tex/context/base/font-map.lua
create mode 100644 tex/context/base/font-otf.lua
create mode 100644 tex/context/base/font-syn.lua
create mode 100644 tex/context/base/font-tfm.lua
create mode 100644 tex/context/base/font-vf.lua
create mode 100644 tex/context/base/l-aux.lua
create mode 100644 tex/context/base/l-boolean.lua
create mode 100644 tex/context/base/l-dir.lua
create mode 100644 tex/context/base/l-file.lua
create mode 100644 tex/context/base/l-io.lua
create mode 100644 tex/context/base/l-math.lua
create mode 100644 tex/context/base/l-md5.lua
create mode 100644 tex/context/base/l-number.lua
create mode 100644 tex/context/base/l-os.lua
create mode 100644 tex/context/base/l-string.lua
create mode 100644 tex/context/base/l-table.lua
create mode 100644 tex/context/base/l-tex.lua
create mode 100644 tex/context/base/l-unicode.lua
create mode 100644 tex/context/base/l-utils.lua
create mode 100644 tex/context/base/lang-ini.lua
create mode 100644 tex/context/base/lang-ini.mkiv
create mode 100644 tex/context/base/lang-sla.mkiv
create mode 100644 tex/context/base/luat-cbk.lua
create mode 100644 tex/context/base/luat-crl.lua
create mode 100644 tex/context/base/luat-deb.lua
create mode 100644 tex/context/base/luat-deb.tex
create mode 100644 tex/context/base/luat-env.lua
create mode 100644 tex/context/base/luat-env.tex
create mode 100644 tex/context/base/luat-exe.lua
create mode 100644 tex/context/base/luat-ini.lua
create mode 100644 tex/context/base/luat-ini.tex
create mode 100644 tex/context/base/luat-inp.lua
create mode 100644 tex/context/base/luat-iop.lua
create mode 100644 tex/context/base/luat-kps.lua
create mode 100644 tex/context/base/luat-lib.lua
create mode 100644 tex/context/base/luat-lib.tex
create mode 100644 tex/context/base/luat-lmx.lua
create mode 100644 tex/context/base/luat-lmx.tex
create mode 100644 tex/context/base/luat-log.lua
create mode 100644 tex/context/base/luat-tex.lua
create mode 100644 tex/context/base/luat-tmp.lua
create mode 100644 tex/context/base/luat-tra.lua
create mode 100644 tex/context/base/luat-uni.lua
create mode 100644 tex/context/base/luat-uni.tex
create mode 100644 tex/context/base/luat-zip.lua
create mode 100644 tex/context/base/meta-pdf.lua
create mode 100644 tex/context/base/meta-pdf.mkiv
create mode 100644 tex/context/base/node-ini.lua
create mode 100644 tex/context/base/node-ini.tex
create mode 100644 tex/context/base/regi-8859-1.lua
create mode 100644 tex/context/base/regi-8859-10.lua
create mode 100644 tex/context/base/regi-8859-11.lua
create mode 100644 tex/context/base/regi-8859-13.lua
create mode 100644 tex/context/base/regi-8859-14.lua
create mode 100644 tex/context/base/regi-8859-15.lua
create mode 100644 tex/context/base/regi-8859-16.lua
create mode 100644 tex/context/base/regi-8859-2.lua
create mode 100644 tex/context/base/regi-8859-3.lua
create mode 100644 tex/context/base/regi-8859-4.lua
create mode 100644 tex/context/base/regi-8859-5.lua
create mode 100644 tex/context/base/regi-8859-6.lua
create mode 100644 tex/context/base/regi-8859-7.lua
create mode 100644 tex/context/base/regi-8859-8.lua
create mode 100644 tex/context/base/regi-8859-9.lua
create mode 100644 tex/context/base/regi-cp1250.lua
create mode 100644 tex/context/base/regi-cp1251.lua
create mode 100644 tex/context/base/regi-cp1252.lua
create mode 100644 tex/context/base/regi-cp1253.lua
create mode 100644 tex/context/base/regi-cp1254.lua
create mode 100644 tex/context/base/regi-cp1255.lua
create mode 100644 tex/context/base/regi-cp1256.lua
create mode 100644 tex/context/base/regi-cp1257.lua
create mode 100644 tex/context/base/regi-cp1258.lua
create mode 100644 tex/context/base/regi-ini.lua
create mode 100644 tex/context/base/regi-ini.mkiv
create mode 100644 tex/context/base/sort-def.mkiv
create mode 100644 tex/context/base/sort-ini.lua
create mode 100644 tex/context/base/sort-ini.mkiv
create mode 100644 tex/context/base/sort-lan.lua
create mode 100644 tex/context/base/sort-lan.mkiv
create mode 100644 tex/context/base/spec-def.mkiv
create mode 100644 tex/context/base/spec-fdf.mkiv
create mode 100644 tex/context/base/spec-pdf.lua
create mode 100644 tex/context/base/supp-fil.lua
create mode 100644 tex/context/base/supp-fil.mkiv
create mode 100644 tex/context/base/syst-cat.mkiv
create mode 100644 tex/context/base/syst-con.lua
create mode 100644 tex/context/base/syst-con.mkiv
create mode 100644 tex/context/base/syst-rtp.mkiv
create mode 100644 tex/context/base/syst-str.mkiv
create mode 100644 tex/context/base/toks-ini.lua
create mode 100644 tex/context/base/toks-ini.tex
create mode 100644 tex/context/base/type-one.tex
create mode 100644 tex/context/base/type-otf.tex
create mode 100644 tex/context/base/type-tmf.tex
create mode 100644 tex/context/base/verb-lua.lua
create mode 100644 tex/context/base/verb-mp.lua
create mode 100644 tex/context/base/verb-tex.lua
create mode 100644 tex/context/base/x-ldx.ctx
create mode 100644 tex/context/base/x-ldx.tex
diff --git a/context/data/context.properties b/context/data/context.properties
index 7668e1320..1f46de94a 100644
--- a/context/data/context.properties
+++ b/context/data/context.properties
@@ -306,40 +306,51 @@ command.10.subsystem.$(file.patterns.example)=1
# 11: make
-command.name.11.$(file.patterns.context)=Generate Formats
+command.name.11.$(file.patterns.context)=Generate Formats (pdfTeX)
command.name.11.$(file.patterns.metafun)=Generate Formats
command.name.11.$(file.patterns.example)=Generate Formats
-command.11.$(file.patterns.context)=$(name.context.texexec) --make --all
+command.11.$(file.patterns.context)=$(name.context.texexec) --make --all --pdftex
command.11.$(file.patterns.metafun)=$(name.context.texexec) --make --all
command.11.$(file.patterns.example)=$(name.context.texexec) --make --all
command.11.subsystem.$(file.patterns.context)=1
command.11.subsystem.$(file.patterns.metafun)=1
command.11.subsystem.$(file.patterns.example)=1
-# 12: update
+# 12: make
-command.name.12=Update ConTeXt
-command.name.12.$(file.patterns.context)=Update ConTeXt
-command.name.12.$(file.patterns.metafun)=Update ConTeXt
-command.name.12.$(file.patterns.example)=Update ConTeXt
+command.name.12.$(file.patterns.context)=Generate Formats (luaTeX)
+command.12.$(file.patterns.context)=$(name.context.texexec) --make --all --luatex
+command.12.subsystem.$(file.patterns.context)=1
-command.12=$(name.context.update)
-command.12.$(file.patterns.context)=$(name.context.update)
-command.12.$(file.patterns.metafun)=$(name.context.update)
-command.12.$(file.patterns.example)=$(name.context.update)
+# 13: make
+command.name.13.$(file.patterns.context)=Generate Formats (XeTeX)
+command.13.$(file.patterns.context)=$(name.context.texexec) --make --all --xetex
+command.13.subsystem.$(file.patterns.context)=1
-# 12: example
+# 14: update
-command.name.13=Example Service
-command.name.13.$(file.patterns.context)=Example Service
-command.name.13.$(file.patterns.metafun)=Example Service
-command.name.13.$(file.patterns.example)=Example Service
+command.name.14=Update ConTeXt
+command.name.14.$(file.patterns.context)=Update ConTeXt
+command.name.14.$(file.patterns.metafun)=Update ConTeXt
+command.name.14.$(file.patterns.example)=Update ConTeXt
-command.13=$(name.context.wwwserver)
-command.13.$(file.patterns.context)=$(name.context.wwwserver)
-command.13.$(file.patterns.metafun)=$(name.context.wwwserver)
-command.13.$(file.patterns.example)=$(name.context.wwwserver)
+command.14=$(name.context.update)
+command.14.$(file.patterns.context)=$(name.context.update)
+command.14.$(file.patterns.metafun)=$(name.context.update)
+command.14.$(file.patterns.example)=$(name.context.update)
+
+# 15: example
+
+command.name.15=Example Service
+command.name.15.$(file.patterns.context)=Example Service
+command.name.15.$(file.patterns.metafun)=Example Service
+command.name.15.$(file.patterns.example)=Example Service
+
+command.15=$(name.context.wwwserver)
+command.15.$(file.patterns.context)=$(name.context.wwwserver)
+command.15.$(file.patterns.metafun)=$(name.context.wwwserver)
+command.15.$(file.patterns.example)=$(name.context.wwwserver)
# Editor: syntax highlighting
@@ -359,12 +370,8 @@ Alt+F12|IDM_STOPEXECUTE|\
# install: fonts/truetype/hoekwater/lm/LMTypewriter10-Regular.ttf
if PLAT_WIN
- font.monospace=font:LMTypewriter10,size:16
- font.errorfont=font:LMTypewriter10,size:12
-
-if PLAT_WIN
- font.monospace=font:lmtypewriter10 regular,size:16
- font.errorfont=font:lmtypewriter10 regular,size:12
+ font.monospace=font:LMTypewriter10-Regular,size:16
+ font.errorfont=font:LMTypewriter10-Regular,size:12
if PLAT_GTK
font.monospace=font:!lmtypewriter10 regular,size:16
diff --git a/metapost/context/base/mp-base.mp b/metapost/context/base/mp-base.mp
index 87f3fd2f7..41eb7aa96 100644
--- a/metapost/context/base/mp-base.mp
+++ b/metapost/context/base/mp-base.mp
@@ -477,10 +477,11 @@ enddef;
def label = draw thelabel enddef;
newinternal dotlabeldiam; dotlabeldiam:=3bp;
-vardef dotlabel@#(expr s,z) =
+vardef dotlabel@#(expr s,z) text t_ =
+ label@#(s,z) t_;
label@#(s,z);
interim linecap:=rounded;
- draw z withpen pencircle scaled dotlabeldiam;
+ draw z withpen pencircle scaled dotlabeldiam t_;
enddef;
def makelabel = dotlabel enddef;
diff --git a/scripts/context/lua/luatools.cmd b/scripts/context/lua/luatools.cmd
new file mode 100644
index 000000000..4bc998d65
--- /dev/null
+++ b/scripts/context/lua/luatools.cmd
@@ -0,0 +1,5 @@
+@echo off
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%luatools.lua" %*
+endlocal
diff --git a/scripts/context/lua/luatools.lua b/scripts/context/lua/luatools.lua
new file mode 100644
index 000000000..0eb63c873
--- /dev/null
+++ b/scripts/context/lua/luatools.lua
@@ -0,0 +1,5155 @@
+#!/usr/bin/env texlua
+
+-- one can make a stub:
+--
+-- #!/bin/sh
+-- env LUATEXDIR=/....../texmf/scripts/context/lua luatex --luaonly=luatools.lua "$@"
+-- filename : luatools.lua
+-- comment : companion to context.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+-- Although this script is part of the ConTeXt distribution it is
+-- relatively indepent of ConTeXt. The same is true for some of
+-- the luat files. We may may make them even less dependent in
+-- the future. As long as Luatex is under development the
+-- interfaces and names of functions may change.
+
+banner = "version 1.1.1 - 2006+ - PRAGMA ADE / CONTEXT"
+texlua = true
+
+-- For the sake of independence we optionally can merge the library
+-- code here. It's too much code, but that does not harm. Much of the
+-- library code is used elsewhere. We don't want dependencies on
+-- Lua library paths simply because these scripts are located in the
+-- texmf tree and not in some Lua path. Normally this merge is not
+-- needed when texmfstart is used, or when the proper stub is used or
+-- when (windows) suffix binding is active.
+
+-- begin library merge
+
+-- filename : l-string.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-string'] = 1.001
+
+--~ function string.split(str, pat) -- taken from the lua wiki
+--~ local t = {n = 0} -- so this table has a length field, traverse with ipairs then!
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ table.insert(t,cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end<=string.len(str) then
+--~ table.insert(t,(string.sub(str,last_end)))
+--~ end
+--~ return t
+--~ end
+
+--~ function string:split(pat) -- taken from the lua wiki but adapted
+--~ local t = { } -- self and colon usage (faster)
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = self:find(fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ t[#t+1] = cap
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = self:find(fpat, last_end)
+--~ end
+--~ if last_end <= #self then
+--~ t[#t+1] = self:sub(last_end)
+--~ end
+--~ return t
+--~ end
+
+--~ a piece of brilliant code by Rici Lake (posted on lua list) -- only names changed
+--~
+--~ function string:splitter(pat)
+--~ local st, g = 1, self:gmatch("()"..pat.."()")
+--~ local function splitter(self)
+--~ if st then
+--~ local s, f = g()
+--~ local rv = self:sub(st, (s or 0)-1)
+--~ st = f
+--~ return rv
+--~ end
+--~ end
+--~ return splitter, self
+--~ end
+
+function string:splitter(pat)
+ -- by Rici Lake (posted on lua list) -- only names changed
+ -- p 79 ref man: () returns position of match
+ local st, g = 1, self:gmatch("()("..pat..")")
+ local function strgetter(self, segs, seps, sep, cap1, ...)
+ st = sep and seps + #sep
+ return self:sub(segs, (seps or 0) - 1), cap1 or sep, ...
+ end
+ local function strsplitter(self)
+ if st then return strgetter(self, st, g()) end
+ end
+ return strsplitter, self
+end
+
+function string:split(separator)
+ local t = {}
+ for k in self:splitter(separator) do t[#t+1] = k end
+ return t
+end
+
+-- faster than a string:split:
+
+function string:splitchr(chr)
+ if #self > 0 then
+ local t = { }
+ for s in string.gmatch(self..chr,"(.-)"..chr) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+end
+
+--~ function string.piecewise(str, pat, fnc) -- variant of split
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ fnc(cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end <= #str then
+--~ fnc((string.sub(str,last_end)))
+--~ end
+--~ end
+
+function string.piecewise(str, pat, fnc) -- variant of split
+ for k in string.splitter(str,pat) do fnc(k) end
+end
+
+--~ do if lpeg then
+
+--~ -- this alternative is 30% faster esp when we cache them
+--~ -- problem: no expressions
+
+--~ splitters = { }
+
+--~ function string:split(separator)
+--~ if #self > 0 then
+--~ local split = splitters[separator]
+--~ if not split then
+--~ -- based on code by Roberto
+--~ local p = lpeg.P(separator)
+--~ local c = lpeg.C((1-p)^0)
+--~ split = lpeg.Ct(c*(p*c)^0)
+--~ splitters[separator] = split
+--~ end
+--~ return lpeg.match(split,self)
+--~ else
+--~ return { }
+--~ end
+--~ end
+
+--~ string.splitchr = string.split
+
+--~ function string:piecewise(separator,fnc)
+--~ for _,v in pairs(self:split(separator)) do
+--~ fnc(v)
+--~ end
+--~ end
+
+--~ end end
+
+string.chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+function string:esc() -- variant 2
+ return (self:gsub("(.)",string.chr_to_esc))
+end
+
+function string.unquote(str)
+ return (str:gsub("^([\"\'])(.*)%1$","%2"))
+end
+
+function string.quote(str)
+ return '"' .. str:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in self:gmatch(pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return self:sub(1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+--~ function string.strip(str) -- slightly different
+--~ return (string.gsub(string.gsub(str,"^%s*(.-)%s*$","%1"),"%s+"," "))
+--~ end
+
+function string:is_empty()
+ return not self:find("%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = self:gsub(pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+--~ function string:enhance(pattern,action)
+--~ local ok, n = 0, 0
+--~ repeat
+--~ self, ok = self:gsub(pattern, function(...)
+--~ n = n + 1
+--~ return action(...)
+--~ end)
+--~ until ok == 0
+--~ return self, n
+--~ end
+
+--~ function string:to_hex()
+--~ if self then
+--~ return (self:gsub("(.)",function(c)
+--~ return string.format("%02X",c:byte())
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then
+--~ return (self:gsub("(..)",function(c)
+--~ return string.char(tonumber(c,16))
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+string.chr_to_hex = { }
+string.hex_to_chr = { }
+
+for i=0,255 do
+ local c, h = string.char(i), string.format("%02X",i)
+ string.chr_to_hex[c], string.hex_to_chr[h] = h, c
+end
+
+--~ function string:to_hex()
+--~ if self then return (self:gsub("(.)",string.chr_to_hex)) else return "" end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then return (self:gsub("(..)",string.hex_to_chr)) else return "" end
+--~ end
+
+function string:to_hex()
+ return ((self or ""):gsub("(.)",string.chr_to_hex))
+end
+
+function string:from_hex()
+ return ((self or ""):gsub("(..)",string.hex_to_chr))
+end
+
+if not string.characters then
+
+ local function nextchar(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, str:sub(index,index)
+ end
+ function string:characters()
+ return nextchar, self, 0
+ end
+ local function nextbyte(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, string.byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+--~ function string:padd(n,chr)
+--~ return self .. self.rep(chr or " ",n-#self)
+--~ end
+
+function string:padd(n,chr)
+ local m = n-#self
+ if m > 0 then
+ return self .. self.rep(chr or " ",m)
+ else
+ return self
+ end
+end
+
+function is_number(str)
+ return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+end
+
+--~ print(is_number("1"))
+--~ print(is_number("1.1"))
+--~ print(is_number(".1"))
+--~ print(is_number("-0.1"))
+--~ print(is_number("+0.1"))
+--~ print(is_number("-.1"))
+--~ print(is_number("+.1"))
+
+
+-- filename : l-table.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-table'] = 1.001
+
+table.join = table.concat
+
+function table.strip(tab)
+ local lst = { }
+ for k, v in ipairs(tab) do
+ -- s = string.gsub(v, "^%s*(.-)%s*$", "%1")
+ s = v:gsub("^%s*(.-)%s*$", "%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+--~ function table.sortedkeys(tab)
+--~ local srt = { }
+--~ for key,_ in pairs(tab) do
+--~ srt[#srt+1] = key
+--~ end
+--~ table.sort(srt)
+--~ return srt
+--~ end
+
+function table.sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in pairs(tab) do
+ srt[#srt+1] = key
+ if kind == 3 then
+ -- no further check
+ elseif type(key) == "string" then
+ if kind == 2 then kind = 3 else kind = 1 end
+ elseif type(key) == "number" then
+ if kind == 1 then kind = 3 else kind = 2 end
+ else
+ kind = 3
+ end
+ end
+ if kind == 0 or kind == 3 then
+ table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+ else
+ table.sort(srt)
+ end
+ return srt
+end
+
+function table.append(t, list)
+ for _,v in pairs(list) do
+ table.insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in pairs(list) do
+ table.insert(t,k,v)
+ end
+end
+
+if not table.fastcopy then
+
+ function table.fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in pairs(old) do
+ if type(v) == "table" then
+ new[k] = table.copy(v)
+ else
+ new[k] = v
+ end
+ end
+ return new
+ else
+ return { }
+ end
+ end
+
+end
+
+if not table.copy then
+
+ function table.copy(t, _lookup_table) -- taken from lua wiki
+ _lookup_table = _lookup_table or { }
+ local tcopy = {}
+ if not _lookup_table[t] then
+ _lookup_table[t] = tcopy
+ end
+ for i,v in pairs(t) do
+ if type(i) == "table" then
+ if _lookup_table[i] then
+ i = _lookup_table[i]
+ else
+ i = table.copy(i, _lookup_table)
+ end
+ end
+ if type(v) ~= "table" then
+ tcopy[i] = v
+ else
+ if _lookup_table[v] then
+ tcopy[i] = _lookup_table[v]
+ else
+ tcopy[i] = table.copy(v, _lookup_table)
+ end
+ end
+ end
+ return tcopy
+ end
+
+end
+
+-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
+
+function table.sub(t,i,j)
+ return { unpack(t,i,j) }
+end
+
+function table.replace(a,b)
+ for k,v in pairs(b) do
+ a[k] = v
+ end
+end
+
+-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
+
+function table.is_empty(t)
+ return not t or not next(t)
+end
+
+function table.one_entry(t)
+ local n = next(t)
+ return n and not next(t,n)
+end
+
+function table.starts_at(t)
+ return ipairs(t,1)(t,0)
+end
+
+do
+
+ -- 34.055.092 32.403.326 arabtype.tma
+ -- 1.620.614 1.513.863 lmroman10-italic.tma
+ -- 1.325.585 1.233.044 lmroman10-regular.tma
+ -- 1.248.157 1.158.903 lmsans10-regular.tma
+ -- 194.646 153.120 lmtypewriter10-regular.tma
+ -- 1.771.678 1.658.461 palatinosanscom-bold.tma
+ -- 1.695.251 1.584.491 palatinosanscom-regular.tma
+ -- 13.736.534 13.409.446 zapfinoextraltpro.tma
+
+ -- 13.679.038 11.774.106 arabtype.tmc
+ -- 886.248 754.944 lmroman10-italic.tmc
+ -- 729.828 466.864 lmroman10-regular.tmc
+ -- 688.482 441.962 lmsans10-regular.tmc
+ -- 128.685 95.853 lmtypewriter10-regular.tmc
+ -- 715.929 582.985 palatinosanscom-bold.tmc
+ -- 669.942 540.126 palatinosanscom-regular.tmc
+ -- 1.560.588 1.317.000 zapfinoextraltpro.tmc
+
+ table.serialize_functions = true
+ table.serialize_compact = true
+ table.serialize_inline = true
+
+ local function key(k)
+ if type(k) == "number" then -- or k:find("^%d+$") then
+ return "["..k.."]"
+ elseif noquotes and k:find("^%a[%a%d%_]*$") then
+ return k
+ else
+ return '["'..k..'"]'
+ end
+ end
+
+ local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in pairs(t) do
+ n = n + 1
+ end
+ if n == #t then
+ local tt = { }
+ for _,v in ipairs(t) do
+ local tv = type(v)
+ if tv == "number" or tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = ("%q"):format(v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+ end
+
+ local function serialize(root,name,handle,depth,level,reduce,noquotes,indexed)
+ handle = handle or print
+ reduce = reduce or false
+ if depth then
+ depth = depth .. " "
+ if indexed then
+ handle(("%s{"):format(depth))
+ else
+ handle(("%s%s={"):format(depth,key(name)))
+ end
+ else
+ depth = ""
+ if type(name) == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif type(name) == "number" then
+ handle("[" .. name .. "]={")
+ elseif type(name) == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ end
+ if root and next(root) then
+ local compact = table.serialize_compact
+ local inline = compact and table.serialize_inline
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ for k,v in ipairs(root) do
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ for _,k in pairs(table.sortedkeys(root)) do
+ local v = root[k]
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ handle(("%s %s,"):format(depth,v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s,"):format(depth,v))
+ else
+ handle(("%s %q,"):format(depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s {},"):format(depth))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s { %s },"):format(depth,table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s,"):format(depth,tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s loadstring(%q),'):format(depth,string.dump(v)))
+ else
+ handle(('%s "function",'):format(depth))
+ end
+ else
+ handle(("%s %q,"):format(depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(("%s __p__=nil,"):format(depth))
+ end
+ elseif t == "number" then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s %s={},"):format(depth,key(k)))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s %s={ %s },"):format(depth,key(k),table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s=%s,"):format(depth,key(k),tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(v)))
+ else
+ handle(('%s %s="function",'):format(depth,key(k)))
+ end
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),tostring(v)))
+ -- handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(function() return v end)))
+ end
+ end
+ if level > 0 then
+ handle(("%s},"):format(depth))
+ else
+ handle(("%s}"):format(depth))
+ end
+ else
+ handle(("%s}"):format(depth))
+ end
+ end
+
+ --~ name:
+ --~
+ --~ true : return { }
+ --~ false : { }
+ --~ nil : t = { }
+ --~ string : string = { }
+ --~ 'return' : return { }
+ --~ number : [number] = { }
+
+ function table.serialize(root,name,reduce,noquotes)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ return table.concat(t,"\n")
+ end
+
+ function table.tohandle(handle,root,name,reduce,noquotes)
+ serialize(root, name, handle, nil, 0, reduce, noquotes)
+ end
+
+ -- sometimes tables are real use (zapfino extra pro is some 85M) in which
+ -- case a stepwise serialization is nice; actually, we could consider:
+ --
+ -- for line in table.serializer(root,name,reduce,noquotes) do
+ -- ...(line)
+ -- end
+ --
+ -- so this is on the todo list
+
+ table.tofile_maxtab = 2*1024
+
+ function table.tofile(filename,root,name,reduce,noquotes)
+ local f = io.open(filename,'w')
+ if f then
+ local concat = table.concat
+ local maxtab = table.tofile_maxtab
+ if maxtab > 1 then
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ if #t > maxtab then
+ f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
+ t = { }
+ end
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ end
+ f:close()
+ end
+ end
+
+end
+
+--~ t = {
+--~ b = "123",
+--~ a = "x",
+--~ c = 1.23,
+--~ d = "1.23",
+--~ e = true,
+--~ f = {
+--~ d = "1.23",
+--~ a = "x",
+--~ b = "123",
+--~ c = 1.23,
+--~ e = true,
+--~ f = {
+--~ e = true,
+--~ f = {
+--~ e = true
+--~ },
+--~ },
+--~ },
+--~ g = function() end
+--~ }
+
+--~ print(table.serialize(t), "\n")
+--~ print(table.serialize(t,"name"), "\n")
+--~ print(table.serialize(t,false), "\n")
+--~ print(table.serialize(t,true), "\n")
+--~ print(table.serialize(t,"name",true), "\n")
+--~ print(table.serialize(t,"name",true,true), "\n")
+
+do
+
+ local function flatten(t,f,complete)
+ for _,v in ipairs(t) do
+ if type(v) == "table" then
+ if complete or type(v[1]) == "table" then
+ flatten(v,f,complete)
+ else
+ f[#f+1] = v
+ end
+ else
+ f[#f+1] = v
+ end
+ end
+ end
+
+ function table.flatten(t)
+ local f = { }
+ flatten(t,f,true)
+ return f
+ end
+
+ function table.unnest(t) -- bad name
+ local f = { }
+ flatten(t,f,false)
+ return f
+ end
+
+ table.flatten_one_level = table.unnest
+
+end
+
+function table.insert_before_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i,str)
+ return
+ end
+ end
+ table.insert(t,1,str)
+end
+
+function table.insert_after_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i+1,str)
+ return
+ end
+ end
+ t[#t+1] = str
+end
+
+function table.are_equal(a,b,n,m)
+ if #a == #b then
+ n = n or 1
+ m = m or #a
+ for i=n,m do
+ local ai, bi = a[i], b[i]
+ if (ai==bi) or (type(ai)=="table" and type(bi)=="table" and table.are_equal(ai,bi)) then
+ -- continue
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+--~ function table.are_equal(a,b)
+--~ return table.serialize(a) == table.serialize(b)
+--~ end
+
+
+
+-- filename : l-io.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-io'] = 1.001
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename)
+ local f = io.open(filename)
+ if f then
+ local data = f:read('*all')
+ f:close()
+ return data
+ else
+ return nil
+ end
+end
+
+function io.savedata(filename,data,joiner)
+ local f = io.open(filename, "wb")
+ if f then
+ if type(data) == "table" then
+ f:write(table.join(data,joiner or ""))
+ elseif type(data) == "function" then
+ data(f)
+ else
+ f:write(data)
+ end
+ f:close()
+ end
+end
+
+function io.exists(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return false
+ else
+ assert(f:close())
+ return true
+ end
+end
+
+function io.size(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return 0
+ else
+ local s = f:seek("end")
+ assert(f:close())
+ return s
+ end
+end
+
+function io.noflines(f)
+ local n = 0
+ for _ in f:lines() do
+ n = n + 1
+ end
+ f:seek('set',0)
+ return n
+end
+
+--~ t, f, n = os.clock(), io.open("testbed/sample-utf16-bigendian-big.txt",'rb'), 0
+--~ for a in io.characters(f) do n = n + 1 end
+--~ print(string.format("characters: %s, time: %s", n, os.clock()-t))
+
+do
+
+ local nextchar = {
+ [ 4] = function(f)
+ return f:read(1), f:read(1), f:read(1), f:read(1)
+ end,
+ [ 2] = function(f)
+ return f:read(1), f:read(1)
+ end,
+ [ 1] = function(f)
+ return f:read(1)
+ end,
+ [-2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ return b, a
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local c = f:read(1)
+ return d, c, b, a
+ end
+ }
+
+ function io.characters(f,n)
+ local sb = string.byte
+ if f then
+ return nextchar[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
+
+do
+
+ local nextbyte = {
+ [4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(a), sb(b), sb(c), sb(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(a), sb(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return sb(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(b), sb(a)
+ else
+ return nil, nil
+ end
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(d), sb(c), sb(b), sb(a)
+ else
+ return nil, nil, nil, nil
+ end
+ end
+ }
+
+ function io.bytes(f,n)
+ local sb = string.byte
+ if f then
+ return nextbyte[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
+
+
+-- filename : l-number.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-number'] = 1.001
+
+if not number then number = { } end
+
+
+
+-- filename : l-os.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-os'] = 1.001
+
+function os.resultof(command)
+ return io.popen(command,"r"):read("*all")
+end
+
+--~ if not os.exec then -- still not ok
+ os.exec = os.execute
+--~ end
+
+function os.launch(str)
+ if os.platform == "windows" then
+ os.execute("start " .. str)
+ else
+ os.execute(str .. " &")
+ end
+end
+
+if not os.setenv then
+ function os.setenv() return false end
+end
+
+
+-- filename : l-md5.lua
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-md5'] = 1.001
+
+if md5 then do
+
+ local function convert(str,fmt)
+ return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
+ end
+
+ if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end
+ if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end
+ if not md5.dec then function md5.dec(str) return convert(stt,"%03i") end end
+
+end end
+
+
+-- filename : l-file.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-file'] = 1.001
+
+if not file then file = { } end
+
+function file.removesuffix(filename)
+ return filename:gsub("%.%a+$", "")
+end
+
+function file.addsuffix(filename, suffix)
+ if not filename:find("%.%a-$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return filename:gsub("%.%a+$", "." .. suffix)
+end
+
+function file.dirname(name)
+ return name:match("^(.+)[/\\].-$") or ""
+end
+
+function file.basename(name)
+ return name:match("^.+[/\\](.-)$") or name
+end
+
+function file.extname(name)
+ return name:match("^.+%.(.-)$") or ""
+end
+
+function file.join(...) -- args
+ return (string.gsub(table.concat({...},"/"),"\\","/"))
+end
+
+function file.is_writable(name)
+ local f = io.open(name, 'w')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.is_readable(name)
+ local f = io.open(name,'r')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.split_path(str)
+ if str:find(';') then
+ return str:splitchr(";")
+ else
+ return str:splitchr(io.pathseparator)
+ end
+end
+
+function file.join_path(tab)
+ return table.concat(tab,io.pathseparator)
+end
+
+--~ print('test' .. " == " .. file.collapse_path("test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/test"))
+--~ print("test/test/test" .. " == " .. file.collapse_path("test/test/test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/../test/test"))
+--~ print("test" .. " == " .. file.collapse_path("test/../test"))
+--~ print("../test" .. " == " .. file.collapse_path("../test"))
+--~ print("../test/" .. " == " .. file.collapse_path("../test/"))
+--~ print("a/a" .. " == " .. file.collapse_path("a/b/c/../../a"))
+
+function file.collapse_path(str)
+ local ok = false
+ while not ok do
+ ok = true
+ str, n = str:gsub("[^%./]+/%.%./", function(s)
+ ok = false
+ return ""
+ end)
+ end
+ return (str:gsub("/%./","/"))
+end
+
+function file.robustname(str)
+ return (str:gsub("[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+
+-- filename : l-dir.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-dir'] = 1.001
+
+dir = { }
+
+-- optimizing for no string.find (*) does not save time
+
+if lfs then
+
+ function dir.glob_pattern(path,patt,recurse,action)
+ for name in lfs.dir(path) do
+ local full = path .. '/' .. name
+ local mode = lfs.attributes(full,'mode')
+ if mode == 'file' then
+ if name:find(patt) then
+ action(full)
+ end
+ elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then
+ dir.glob_pattern(full,patt,recurse,action)
+ end
+ end
+ end
+
+ function dir.glob(pattern, action)
+ local t = { }
+ local action = action or function(name) table.insert(t,name) end
+ local path, patt = pattern:match("^(.*)/*%*%*/*(.-)$")
+ local recurse = path and patt
+ if not recurse then
+ path, patt = pattern:match("^(.*)/(.-)$")
+ if not (path and patt) then
+ path, patt = '.', pattern
+ end
+ end
+ patt = patt:gsub("([%.%-%+])", "%%%1")
+ patt = patt:gsub("%*", ".*")
+ patt = patt:gsub("%?", ".")
+ patt = "^" .. patt .. "$"
+ -- print('path: ' .. path .. ' | pattern: ' .. patt .. ' | recurse: ' .. tostring(recurse))
+ dir.glob_pattern(path,patt,recurse,action)
+ return t
+ end
+
+ -- t = dir.glob("c:/data/develop/context/sources/**/????-*.tex")
+ -- t = dir.glob("c:/data/develop/tex/texmf/**/*.tex")
+ -- t = dir.glob("c:/data/develop/context/texmf/**/*.tex")
+ -- t = dir.glob("f:/minimal/tex/**/*")
+ -- print(dir.ls("f:/minimal/tex/**/*"))
+ -- print(dir.ls("*.tex"))
+
+ function dir.ls(pattern)
+ return table.concat(dir.glob(pattern),"\n")
+ end
+
+ --~ mkdirs("temp")
+ --~ mkdirs("a/b/c")
+ --~ mkdirs(".","/a/b/c")
+ --~ mkdirs("a","b","c")
+
+ function dir.mkdirs(...) -- root,... or ... ; root is not split
+ local pth, err = "", false
+ for k,v in pairs({...}) do
+ if k == 1 then
+ if not lfs.isdir(v) then
+ -- print("no root path " .. v)
+ err = true
+ else
+ pth = v
+ end
+ elseif lfs.isdir(pth .. "/" .. v) then
+ pth = pth .. "/" .. v
+ else
+ for _,s in pairs(v:split("/")) do
+ pth = pth .. "/" .. s
+ if not lfs.isdir(pth) then
+ ok = lfs.mkdir(pth)
+ if not lfs.isdir(pth) then
+ err = true
+ end
+ end
+ if err then break end
+ end
+ end
+ if err then break end
+ end
+ return pth, not err
+ end
+
+end
+
+
+-- filename : l-boolean.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-boolean'] = 1.001
+if not boolean then boolean = { } end
+
+function boolean.tonumber(b)
+ if b then return 1 else return 0 end
+end
+
+function toboolean(str)
+ if type(str) == "string" then
+ return str == "true" or str == "yes" or str == "on" or str == "1"
+ elseif type(str) == "number" then
+ return tonumber(str) ~= 0
+ else
+ return str
+ end
+end
+
+function string.is_boolean(str)
+ if type(str) == "string" then
+ if str == "true" or str == "yes" or str == "on" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+-- filename : l-unicode.lua
+-- comment : split off from luat-inp
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-unicode'] = 1.001
+if not unicode then unicode = { } end
+
+if not garbagecollector then
+ garbagecollector = {
+ push = function() collectgarbage("stop") end,
+ pop = function() collectgarbage("restart") end,
+ }
+end
+
+-- 0 EF BB BF UTF-8
+-- 1 FF FE UTF-16-little-endian
+-- 2 FE FF UTF-16-big-endian
+-- 3 FF FE 00 00 UTF-32-little-endian
+-- 4 00 00 FE FF UTF-32-big-endian
+
+unicode.utfname = {
+ [0] = 'utf-8',
+ [1] = 'utf-16-le',
+ [2] = 'utf-16-be',
+ [3] = 'utf-32-le',
+ [4] = 'utf-32-be'
+}
+
+function unicode.utftype(f) -- \000 fails !
+ local str = f:read(4)
+ if not str then
+ f:seek('set')
+ return 0
+ elseif str:find("^%z%z\254\255") then
+ return 4
+ elseif str:find("^\255\254%z%z") then
+ return 3
+ elseif str:find("^\254\255") then
+ f:seek('set',2)
+ return 2
+ elseif str:find("^\255\254") then
+ f:seek('set',2)
+ return 1
+ elseif str:find("^\239\187\191") then
+ f:seek('set',3)
+ return 0
+ else
+ f:seek('set')
+ return 0
+ end
+end
+
+function unicode.utf16_to_utf8(str, endian)
+ garbagecollector.push()
+ local result = { }
+ local tc, uc = table.concat, unicode.utf8.char
+ local tmp, n, m, p = { }, 0, 0, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = uc(n)
+ p = 0
+ end
+ end
+ for l,r in str:bytepairs() do
+ if endian then
+ n = l*256 + r
+ else
+ n = r*256 + l
+ end
+ if m > 0 then
+ n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
+ m = 0
+ doit()
+ elseif n >= 0xD800 and n <= 0xDBFF then
+ m = n
+ else
+ doit()
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = tc(tmp,"")
+ end
+ garbagecollector.pop()
+ return result
+end
+
+function unicode.utf32_to_utf8(str, endian)
+ garbagecollector.push()
+ local result = { }
+ local tc, uc = table.concat, unicode.utf8.char
+ local tmp, n, m, p = { }, 0, -1, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = uc(n)
+ p = 0
+ end
+ end
+ for a,b in str:bytepairs() do
+ if a and b then
+ if m < 0 then
+ if endian then
+ m = a*256*256*256 + b*256*256
+ else
+ m = b*256 + a
+ end
+ else
+ if endian then
+ n = m + a*256 + b
+ else
+ n = m + b*256*256*256 + a*256*256
+ end
+ m = -1
+ doit()
+ end
+ else
+ break
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = tc(tmp,"")
+ end
+ garbagecollector.pop()
+ return result
+end
+
+
+-- filename : l-utils.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-utils'] = 1.001
+
+if not utils then utils = { } end
+if not utils.merger then utils.merger = { } end
+if not utils.lua then utils.lua = { } end
+
+utils.merger.m_begin = "begin library merge"
+utils.merger.m_end = "end library merge"
+utils.merger.pattern =
+ "%c+" ..
+ "%-%-%s+" .. utils.merger.m_begin ..
+ "%c+(.-)%c+" ..
+ "%-%-%s+" .. utils.merger.m_end ..
+ "%c+"
+
+function utils.merger._self_fake_()
+ return
+ "-- " .. "created merged file" .. "\n\n" ..
+ "-- " .. utils.merger.m_begin .. "\n\n" ..
+ "-- " .. utils.merger.m_end .. "\n\n"
+end
+
+function utils.report(...)
+ print(...)
+end
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ data = f:read("*all")
+ f:close()
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ f:write(data)
+ f:close()
+ end
+ end
+end
+
+function utils.merger._self_swap_(data,code)
+ if data ~= "" then
+ return (data:gsub(utils.merger.pattern, function(s)
+ return "\n\n" .. "-- "..utils.merger.m_begin .. "\n" .. code .. "\n" .. "-- "..utils.merger.m_end .. "\n\n"
+ end, 1))
+ else
+ return ""
+ end
+end
+
+function utils.merger._self_libs_(libs,list)
+ local result, f = "", nil
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ local name = string.gsub(pth .. "/" .. lib,"\\","/")
+ f = io.open(name)
+ if f then
+ -- utils.report("merging library",name)
+ result = result .. "\n" .. f:read("*all") .. "\n"
+ f:close()
+ list = { pth } -- speed up the search
+ break
+ else
+ -- utils.report("no library",name)
+ end
+ end
+ end
+ return result or ""
+end
+
+function utils.merger.selfcreate(libs,list,target)
+ if target then
+ utils.merger._self_save_(
+ target,
+ utils.merger._self_swap_(
+ utils.merger._self_fake_(),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+ end
+end
+
+function utils.merger.selfmerge(name,libs,list,target)
+ utils.merger._self_save_(
+ target or name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+end
+
+function utils.merger.selfclean(name)
+ utils.merger._self_save_(
+ name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ ""
+ )
+ )
+end
+
+function utils.lua.compile(luafile, lucfile)
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ return (os.execute("luac -s -o " .. string.quote(lucfile) .. " " .. string.quote(luafile)) == 0)
+end
+
+
+
+-- filename : luat-lib.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-lib'] = 1.001
+
+-- mostcode moved to the l-*.lua and other luat-*.lua files
+
+-- os / io
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+-- os.platform
+
+-- mswin|bccwin|mingw|cygwin windows
+-- darwin|rhapsody|nextstep macosx
+-- netbsd|unix unix
+-- linux linux
+
+if not io.fileseparator then
+ if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator, os.platform = "\\", ";", "windows"
+ else
+ io.fileseparator, io.pathseparator, os.platform = "/" , ":", "unix"
+ end
+end
+
+if not os.platform then
+ if io.pathseparator == ";" then
+ os.platform = "windows"
+ else
+ os.platform = "unix"
+ end
+end
+
+-- arg normalization
+--
+-- for k,v in pairs(arg) do print(k,v) end
+
+if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
+ arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
+end
+
+-- environment
+
+if not environment then environment = { } end
+
+environment.arguments = { }
+environment.files = { }
+environment.sorted_argument_keys = nil
+
+environment.platform = os.platform
+
+function environment.initialize_arguments(arg)
+ environment.arguments = { }
+ environment.files = { }
+ environment.sorted_argument_keys = nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ environment.arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ environment.arguments[flag] = true
+ else
+ environment.files[#environment.files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.showarguments()
+ for k,v in pairs(environment.arguments) do
+ print(k .. " : " .. tostring(v))
+ end
+ if #environment.files > 0 then
+ print("files : " .. table.concat(environment.files, " "))
+ end
+end
+
+function environment.argument(name)
+ if environment.arguments[name] then
+ return environment.arguments[name]
+ else
+ if not environment.sorted_argument_keys then
+ environment.sorted_argument_keys = { }
+ for _,v in pairs(table.sortedkeys(environment.arguments)) do
+ table.insert(environment.sorted_argument_keys, "^" .. v)
+ end
+ end
+ for _,v in pairs(environment.sorted_argument_keys) do
+ if name:find(v) then
+ return environment.arguments[v:sub(2,#v)]
+ end
+ end
+ end
+ return nil
+end
+
+function environment.split_arguments(separator) -- rather special, cut-off before separator
+ local done, before, after = false, { }, { }
+ for _,v in ipairs(environment.original_arguments) do
+ if not done and v == separator then
+ done = true
+ elseif done then
+ after[#after+1] = v
+ else
+ before[#before+1] = v
+ end
+ end
+ return before, after
+end
+
+function environment.reconstruct_commandline(arg)
+ if not arg then arg = environment.original_arguments end
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ local kk, vv = a:match("^(%-+.-)=(.+)$")
+ if kk and vv then
+ if vv:find(" ") then
+ result[#result+1] = kk .. "=" .. string.quote(vv)
+ else
+ result[#result+1] = a
+ end
+ elseif a:find(" ") then
+ result[#result+1] = string.quote(a)
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+end
+
+if arg then
+ environment.initialize_arguments(arg)
+ environment.original_arguments = arg
+ arg = { } -- prevent duplicate handling
+end
+
+
+-- filename : luat-inp.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- This lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split this
+-- module in components when we're done with prototyping.
+
+-- This is the first code I wrote for LuaTeX, so it needs some cleanup.
+
+-- To be considered: hash key lowercase, first entry in table filename
+-- (any case), rest paths (so no need for optimization). Or maybe a
+-- separate table that matches lowercase names to mixed case when
+-- present. In that case the lower() cases can go away. I will do that
+-- only when we run into problems with names.
+
+if not versions then versions = { } end versions['luat-inp'] = 1.001
+if not environment then environment = { } end
+if not file then file = { } end
+
+if environment.aleph_mode == nil then environment.aleph_mode = true end -- temp hack
+
+if not input then input = { } end
+if not input.suffixes then input.suffixes = { } end
+if not input.formats then input.formats = { } end
+if not input.aux then input.aux = { } end
+
+if not input.suffixmap then input.suffixmap = { } end
+
+if not input.locators then input.locators = { } end -- locate databases
+if not input.hashers then input.hashers = { } end -- load databases
+if not input.generators then input.generators = { } end -- generate databases
+if not input.filters then input.filters = { } end -- conversion filters
+
+input.locators.notfound = { nil }
+input.hashers.notfound = { nil }
+input.generators.notfound = { nil }
+
+input.cacheversion = '1.0.1'
+input.banner = nil
+input.verbose = false
+input.debug = false
+input.cnfname = 'texmf.cnf'
+input.lsrname = 'ls-R'
+input.luasuffix = '.tma'
+input.lucsuffix = '.tmc'
+
+-- we use a cleaned up list / format=any is a wildcard, as is *name
+
+input.formats['afm'] = 'AFMFONTS' input.suffixes['afm'] = { 'afm' }
+input.formats['enc'] = 'ENCFONTS' input.suffixes['enc'] = { 'enc' }
+input.formats['fmt'] = 'TEXFORMATS' input.suffixes['fmt'] = { 'fmt' }
+input.formats['map'] = 'TEXFONTMAPS' input.suffixes['map'] = { 'map' }
+input.formats['mp'] = 'MPINPUTS' input.suffixes['mp'] = { 'mp' }
+input.formats['ocp'] = 'OCPINPUTS' input.suffixes['ocp'] = { 'ocp' }
+input.formats['ofm'] = 'OFMFONTS' input.suffixes['ofm'] = { 'ofm', 'tfm' }
+input.formats['otf'] = 'OPENTYPEFONTS' input.suffixes['otf'] = { 'otf' } -- 'ttf'
+input.formats['opl'] = 'OPLFONTS' input.suffixes['opl'] = { 'opl' }
+input.formats['otp'] = 'OTPINPUTS' input.suffixes['otp'] = { 'otp' }
+input.formats['ovf'] = 'OVFFONTS' input.suffixes['ovf'] = { 'ovf', 'vf' }
+input.formats['ovp'] = 'OVPFONTS' input.suffixes['ovp'] = { 'ovp' }
+input.formats['tex'] = 'TEXINPUTS' input.suffixes['tex'] = { 'tex' }
+input.formats['tfm'] = 'TFMFONTS' input.suffixes['tfm'] = { 'tfm' }
+input.formats['ttf'] = 'TTFONTS' input.suffixes['ttf'] = { 'ttf', 'ttc' }
+input.formats['pfb'] = 'T1FONTS' input.suffixes['pfb'] = { 'pfb', 'pfa' }
+input.formats['vf'] = 'VFFONTS' input.suffixes['vf'] = { 'vf' }
+
+input.formats['fea'] = 'FONTFEATURES' input.suffixes['fea'] = { 'fea' }
+
+input.formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+input.suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+input.formats ['lua'] = 'LUAINPUTS' -- new
+input.suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- here we catch a few new thingies
+
+function input.checkconfigdata(instance)
+ if input.env(instance,"LUAINPUTS") == "" then
+ instance.environment["LUAINPUTS"] = ".;$TEXINPUTS;$TEXMFSCRIPTS"
+ end
+ if input.env(instance,"FONTFEATURES") == "" then
+ instance.environment["FONTFEATURES"] = ".;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS"
+ end
+end
+
+-- backward compatible ones
+
+input.alternatives = { }
+
+input.alternatives['map files'] = 'map'
+input.alternatives['enc files'] = 'enc'
+input.alternatives['opentype fonts'] = 'otf'
+input.alternatives['truetype fonts'] = 'ttf'
+input.alternatives['truetype collections'] = 'ttc'
+input.alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+input.formats ['misc fonts'] = ''
+input.suffixes['misc fonts'] = { }
+
+input.formats ['sfd'] = 'SFDFONTS'
+input.suffixes ['sfd'] = { 'sfd' }
+input.alternatives['subfont definition files'] = 'sfd'
+
+function input.reset()
+
+ local instance = { }
+
+ instance.rootpath = ''
+ instance.treepath = ''
+ instance.progname = environment.progname or 'context'
+ instance.engine = environment.engine or 'luatex'
+ instance.format = ''
+ instance.environment = { }
+ instance.variables = { }
+ instance.expansions = { }
+ instance.files = { }
+ instance.configuration = { }
+ instance.found = { }
+ instance.foundintrees = { }
+ instance.kpsevars = { }
+ instance.hashes = { }
+ instance.cnffiles = { }
+ instance.lists = { }
+ instance.remember = true
+ instance.diskcache = true
+ instance.renewcache = false
+ instance.scandisk = true
+ instance.cachepath = nil
+ instance.loaderror = false
+ instance.smallcache = false
+ instance.savelists = true
+ instance.cleanuppaths = true
+ instance.allresults = false
+ instance.pattern = nil -- lists
+ instance.kpseonly = false -- lists
+ instance.cachefile = 'tmftools'
+ instance.loadtime = 0
+ instance.starttime = 0
+ instance.stoptime = 0
+ instance.validfile = function(path,name) return true end
+ instance.data = { } -- only for loading
+ instance.sortdata = false
+ instance.force_suffixes = true
+ instance.dummy_path_expr = "^!*unset/*$"
+ instance.fakepaths = { }
+ instance.lsrmode = false
+
+ if os.env then
+ -- store once, freeze and faster
+ for k,v in pairs(os.env) do
+ instance.environment[k] = input.bare_variable(v)
+ end
+ else
+ -- we will access os.env frequently
+ for k,v in pairs({'HOME','TEXMF','TEXMFCNF','SELFAUTOPARENT'}) do
+ local e = os.getenv(v)
+ if e then
+ -- input.report("setting",v,"to",input.bare_variable(e))
+ instance.environment[v] = input.bare_variable(e)
+ end
+ end
+ end
+
+ -- cross referencing
+
+ for k, v in pairs(input.suffixes) do
+ for _, vv in pairs(v) do
+ if vv then
+ input.suffixmap[vv] = k
+ end
+ end
+ end
+
+ return instance
+
+end
+
+function input.bare_variable(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"%s+$",""),'^"(.+)"$',"%1"),"^'(.+)'$","%1")
+ return str:gsub("\s*([\"\']?)(.+)%1\s*", "%2")
+end
+
+if texio then
+ input.log = texio.write_nl
+else
+ input.log = print
+end
+
+function input.simple_logger(kind, name)
+ if name and name ~= "" then
+ if input.banner then
+ input.log(input.banner..kind..": "..name)
+ else
+ input.log("<<"..kind..": "..name..">>")
+ end
+ else
+ if input.banner then
+ input.log(input.banner..kind..": no name")
+ else
+ input.log("<<"..kind..": no name>>")
+ end
+ end
+end
+
+function input.dummy_logger()
+end
+
+function input.settrace(n)
+ input.trace = tonumber(n or 0)
+ if input.trace > 0 then
+ input.logger = input.simple_logger
+ input.verbose = true
+ else
+ input.logger = function() end
+ end
+end
+
+function input.report(...) -- inefficient
+ if input.verbose then
+ if input.banner then
+ input.log(input.banner .. table.concat({...},' '))
+ elseif input.logmode() == 'xml' then
+ input.log(""..table.concat({...},' ').."")
+ else
+ input.log("<<"..table.concat({...},' ')..">>")
+ end
+ end
+end
+
+function input.reportlines(str)
+ if type(str) == "string" then
+ str = str:split("\n")
+ end
+ for _,v in pairs(str) do input.report(v) end
+end
+
+input.settrace(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0)
+
+-- These functions can be used to test the performance, especially
+-- loading the database files.
+
+function input.start_timing(instance)
+ if instance then
+ instance.starttime = os.clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+end
+
+function input.stop_timing(instance, report)
+ if instance and instance.starttime then
+ instance.stoptime = os.clock()
+ local loadtime = instance.stoptime - instance.starttime
+ instance.loadtime = instance.loadtime + loadtime
+ if report then
+ input.report('load time', string.format("%0.3f",loadtime))
+ end
+ return loadtime
+ else
+ return 0
+ end
+end
+
+input.stoptiming = input.stop_timing
+input.starttiming = input.start_timing
+
+function input.elapsedtime(instance)
+ return string.format("%0.3f",instance.loadtime or 0)
+end
+
+function input.report_loadtime(instance)
+ if instance then
+ input.report('total load time', input.elapsedtime(instance))
+ end
+end
+
+function input.loadtime(instance)
+ tex.print(input.elapsedtime(instance))
+end
+
+function input.env(instance,key)
+ return instance.environment[key] or input.osenv(instance,key)
+end
+
+function input.osenv(instance,key)
+ if instance.environment[key] == nil then
+ local e = os.getenv(key)
+ if e == nil then
+ instance.environment[key] = "" -- false
+ else
+ instance.environment[key] = input.bare_variable(e)
+ end
+ end
+ return instance.environment[key] or ""
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in TEXMF/web2c
+--
+-- for the moment we don't expect a configuration file in a zip
+
+function input.identify_cnf(instance)
+ if #instance.cnffiles == 0 then
+ if instance.treepath ~= "" then
+ if instance.rootpath ~= "" then
+ local t = instance.treepath:splitchr(',')
+ for k,v in ipairs(t) do
+ t[k] = file.join(instance.rootpath,v)
+ end
+ instance.treepath = table.concat(t,',')
+ end
+ local t = instance.treepath:splitchr(',')
+ instance.environment['TEXMF'] = input.bare_variable(instance.treepath)
+ instance.environment['TEXMFCNF'] = file.join(t[1] or '.','texmf/web2c')
+ end
+ if instance.rootpath ~= "" then
+ instance.environment['TEXMFCNF'] = file.join(instance.rootpath,'texmf/web2c')
+ instance.environment['SELFAUTOPARENT'] = instance.rootpath
+ end
+ if input.env(instance,'TEXMFCNF') ~= "" then
+ local t = input.split_path(input.env(instance,'TEXMFCNF'))
+ t = input.aux.expanded_path(instance,t)
+ input.aux.expand_vars(instance,t)
+ for _,v in ipairs(t) do
+ table.insert(instance.cnffiles,file.join(v,input.cnfname))
+ end
+ elseif input.env(instance,'SELFAUTOPARENT') == '.' then
+ table.insert(instance.cnffiles,file.join('.',input.cnfname))
+ else
+ for _,v in ipairs({'texmf-local','texmf'}) do
+ table.insert(instance.cnffiles,file.join(input.env(instance,'SELFAUTOPARENT'),v,'web2c',input.cnfname))
+ end
+ end
+ end
+end
+
+function input.load_cnf(instance)
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = fname:gsub("\\",'/')
+ end
+ for i = 1, 3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ if instance.lsrmode then
+ input.loadconfigdata(instance,instance.cnffiles)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadconfig(instance,instance.cnffiles)
+ if instance.loaderror then
+ input.loadconfigdata(instance,instance.cnffiles)
+ input.saveconfig(instance)
+ end
+ else
+ input.loadconfigdata(instance,instance.cnffiles)
+ if instance.renewcache then
+ input.saveconfig(instance)
+ end
+ end
+ input.aux.collapse_cnf_data(instance)
+ end
+ input.checkconfigdata(instance)
+end
+
+function input.loadconfigdata(instance)
+ for _, fname in pairs(instance.cnffiles) do
+ input.aux.load_cnf(instance,fname)
+ end
+end
+
+if os.env then
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ if instance.environment[k] then
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.kpsevars[k] = true
+ instance.variables[k] = input.bare_variable(v)
+ end
+ end
+ end
+ end
+ end
+else
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ local e = os.getenv(k)
+ if e then
+ instance.environment[k] = input.bare_variable(e)
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.variables[k] = input.bare_variable(v)
+ instance.kpsevars[k] = true
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.aux.load_cnf(instance,fname)
+ fname = input.clean_path(fname)
+ local lname = fname:gsub("%.%a+$",input.luasuffix)
+ local f = io.open(lname)
+ if f then
+ f:close()
+ input.aux.load_data(instance,file.dirname(lname),'configuration',file.basename(lname))
+ else
+ f = io.open(fname)
+ if f then
+ input.report("loading", fname)
+ local line, data, n, k, v
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ instance.configuration[dname] = { }
+ end
+ local data = instance.configuration[dname]
+ while true do
+ line = f:read()
+ if line then
+ while true do -- join lines
+ line, n = line:gsub("\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not line:find("^[%%#]") then
+ k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ else
+ input.report("skipping", fname)
+ end
+ end
+end
+
+-- database loading
+
+function input.load_hash(instance)
+ input.locatelists(instance)
+ if instance.lsrmode then
+ input.loadlists(instance)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadfiles(instance)
+ if instance.loaderror then
+ input.loadlists(instance)
+ input.savefiles(instance)
+ end
+ else
+ input.loadlists(instance)
+ if instance.renewcache then
+ input.savefiles(instance)
+ end
+ end
+end
+
+function input.aux.append_hash(instance,type,tag,name)
+ input.logger("= hash append",tag)
+ table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.prepend_hash(instance,type,tag,name)
+ input.logger("= hash prepend",tag)
+ table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.extend_texmf_var(instance,specification) -- crap
+ if instance.environment['TEXMF'] then
+ input.report("extending environment variable TEXMF with", specification)
+ instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ elseif instance.variables['TEXMF'] then
+ input.report("extending configuration variable TEXMF with", specification)
+ instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ else
+ input.report("setting configuration variable TEXMF to", specification)
+ instance.variables['TEXMF'] = "{" .. specification .. "}"
+ end
+ if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
+ input.report("adding {} to complex TEXMF variable, best do that yourself")
+ instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
+ end
+ input.expand_variables(instance)
+end
+
+-- locators
+
+function input.locatelists(instance)
+ for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
+ input.report("locating list of",path)
+ input.locatedatabase(instance,input.normalize_name(path))
+ end
+end
+
+function input.locatedatabase(instance,specification)
+ return input.methodhandler('locators', instance, specification)
+end
+
+function input.locators.tex(instance,specification)
+ if specification and specification ~= '' then
+ local files = {
+ file.join(specification,'files'..input.lucsuffix),
+ file.join(specification,'files'..input.luasuffix),
+ file.join(specification,input.lsrname)
+ }
+ for _, filename in pairs(files) do
+ local f = io.open(filename)
+ if f then
+ input.logger('! tex locator', specification..' found')
+ input.aux.append_hash(instance,'file',specification,filename)
+ f:close()
+ return
+ end
+ end
+ input.logger('? tex locator', specification..' not found')
+ end
+end
+
+-- hashers
+
+function input.hashdatabase(instance,tag,name)
+ return input.methodhandler('hashers',instance,tag,name)
+end
+
+function input.loadfiles(instance)
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ input.hashdatabase(instance,hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function input.hashers.tex(instance,tag,name)
+ input.aux.load_data(instance,tag,'files')
+end
+
+-- generators:
+
+function input.loadlists(instance)
+ for _, hash in ipairs(instance.hashes) do
+ input.generatedatabase(instance,hash.tag)
+ end
+end
+
+function input.generatedatabase(instance,specification)
+ return input.methodhandler('generators', instance, specification)
+end
+
+function input.generators.tex(instance,specification)
+ local tag = specification
+ if not instance.lsrmode and lfs and lfs.dir then
+ input.report("scanning path",specification)
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local n, m = 0, 0
+ local spec = specification .. '/'
+ local attributes = lfs.attributes
+ local directory = lfs.dir
+ local small = instance.smallcache
+ local function action(path)
+ local mode, full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if name == '.' or name == ".." then
+ -- skip
+ else
+ mode = attributes(full..name,'mode')
+ if mode == "directory" then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ elseif path and mode == 'file' then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if not small then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ end
+ else
+ files[name] = path
+ end
+ end
+ end
+ end
+ end
+ action()
+ input.report(n,"files found on",m,"directories")
+ else
+ local fullname = file.join(specification,input.lsrname)
+ local path = '.'
+ local f = io.open(fullname)
+ if f then
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local small = instance.smallcache
+ input.report("loading lsr file",fullname)
+ -- for line in f:lines() do -- much slower then the next one
+ for line in (f:read("*a")):gmatch("(.-)\n") do
+ if line:find("^[%a%d]") then
+ local fl = files[line]
+ if fl then
+ if not small then
+ if type(fl) == 'string' then
+ files[line] = { fl, path } -- table
+ else
+ fl[#fl+1] = path
+ end
+ end
+ else
+ files[line] = path -- string
+ end
+ else
+ path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ end
+ end
+ f:close()
+ end
+ end
+end
+
+-- savers, todo
+
+function input.savefiles(instance)
+ input.aux.save_data(instance, 'files', function(k,v)
+ return instance.validfile(k,v) -- path, name
+ end)
+end
+
+-- A config (optionally) has the paths split in tables. Internally
+-- we join them and split them after the expansion has taken place. This
+-- is more convenient.
+
+function input.splitconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'string' then
+ local t = file.split_path(v)
+ if #t > 1 then
+ c[k] = t
+ end
+ end
+ end
+ end
+end
+function input.joinconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function input.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function input.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t = file.split_path(v)
+ if #t > 1 then
+ instance.expansions[k] = t
+ end
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t, h = { }, { }
+ for _,vv in pairs(file.split_path(v)) do
+ if vv ~= "" and not h[vv] then
+ t[#t+1] = vv
+ h[vv] = true
+ end
+ end
+ if #t > 1 then
+ instance.expansions[k] = t
+ else
+ instance.expansions[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function input.saveconfig(instance)
+ input.splitconfig(instance)
+ input.aux.save_data(instance, 'configuration', nil)
+ input.joinconfig(instance)
+end
+
+input.configbanner = [[
+-- This is a Luatex configuration file created by 'luatools.lua' or
+-- 'luatex.exe' directly. For comment, suggestions and questions you can
+-- contact the ConTeXt Development Team. This configuration file is
+-- not copyrighted. [HH & TH]
+]]
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name = file.join(cachename,dataname)
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ f:write(input.configbanner)
+ f:write("\n")
+ f:write("if not texmf then texmf = { } end\n")
+ f:write("if not texmf.data then texmf.data = { } end\n")
+ f:write("\n")
+ f:write("texmf.data.type = '" .. dataname .. "'\n")
+ f:write("texmf.data.version = '" .. input.cacheversion .. "'\n")
+ f:write("texmf.data.date = '" .. os.date("%Y-%m-%d") .. "'\n")
+ f:write("texmf.data.time = '" .. os.date("%H:%M:%S") .. "'\n")
+ f:write('texmf.data.content = {\n')
+ local function dump(k,v)
+ if not check or check(v,k) then -- path, name
+ if type(v) == 'string' then
+ f:write("\t['" .. k .. "'] = '" .. v .. "',\n")
+ elseif #v == 1 then
+ f:write("\t['" .. k .. "'] = '" .. v[1] .. "',\n")
+ else
+ f:write("\t['" .. k .. "'] = {'" .. table.concat(v,"','").. "'},\n")
+ end
+ end
+ end
+ if instance.sortdata then
+ for _, k in pairs(table.sortedkeys(files)) do
+ dump(k,files[k])
+ end
+ else
+ for k, v in pairs(files) do
+ dump(k,v)
+ end
+ end
+ f:write('}\n')
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.loadconfig(instance)
+ instance.configuration, instance.loaderror = { }, false
+ if not instance.renewcache then
+ for _, cnf in pairs(instance.cnffiles) do
+ input.aux.load_data(instance,file.dirname(cnf),'configuration')
+ if instance.loaderror then break end
+ end
+ end
+ input.joinconfig(instance)
+end
+
+if not texmf then texmf = {} end
+if not texmf.data then texmf.data = {} end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ if not filename or (filename == "") then
+ filename = dataname .. input.lucsuffix
+ end
+ local blob = loadfile(file.join(pathname,filename))
+ if not blob then
+ filename = dataname .. input.luasuffix
+ blob = loadfile(file.join(pathname,filename))
+ end
+ if blob then
+ blob()
+ if (texmf.data.type == dataname) and (texmf.data.version == input.cacheversion) and texmf.data.content then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = texmf.data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ end
+ texmf.data.content = { }
+end
+
+function input.expand_variables(instance)
+ instance.expansions = { }
+ if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
+ if instance.progname ~= "" then instance.environment['progname'] = instance.engine end
+ for k,v in pairs(instance.environment) do
+ local a, b = k:match("^(%a+)%_(.*)%s*$")
+ if a and b then
+ instance.expansions[a..'.'..b] = v
+ else
+ instance.expansions[k] = v
+ end
+ end
+ for k,v in pairs(instance.environment) do -- move environment to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ for k,v in pairs(instance.variables) do -- move variables to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ while true do
+ local busy = false
+ for k,v in pairs(instance.expansions) do
+ local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ if n > 0 or m > 0 then
+ instance.expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in pairs(instance.expansions) do
+ instance.expansions[k] = v:gsub("\\", '/')
+ end
+ input.splitexpansions(instance)
+end
+
+function input.aux.expand_vars(instance,lst) -- simple vars
+ for k,v in pairs(lst) do
+ lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+ end
+end
+
+function input.aux.expanded_var(instance,var) -- simple vars
+ return var:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+end
+
+function input.aux.entry(instance,entries,name)
+ if name and (name ~= "") then
+ name = name:gsub('%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = input.env(instance,name)
+ if result then
+ instance.variables[name] = result
+ input.expand_variables(instance)
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+function input.variable(instance,name)
+ return input.aux.entry(instance,instance.variables,name)
+end
+function input.expansion(instance,name)
+ return input.aux.entry(instance,instance.expansions,name)
+end
+
+function input.aux.is_entry(instance,entries,name)
+ if name and name ~= "" then
+ name = name:gsub('%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+end
+
+function input.is_variable(instance,name)
+ return input.aux.is_entry(instance,instance.variables,name)
+end
+function input.is_expansion(instance,name)
+ return input.aux.is_entry(instance,instance.expansions,name)
+end
+
+function input.aux.list(instance,list)
+ local pat = string.upper(instance.pattern or "","")
+ for _,key in pairs(table.sortedkeys(list)) do
+ if (instance.pattern=="") or string.find(key:upper(),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ print(key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ elseif instance.kpsevars[key] then
+ print('K ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ else
+ print('E ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ end
+ end
+end
+
+function input.list_variables(instance)
+ input.aux.list(instance,instance.variables)
+end
+function input.list_expansions(instance)
+ input.aux.list(instance,instance.expansions)
+end
+
+function input.list_configurations(instance)
+ for _,key in pairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or key:find(instance.pattern) then
+ print(key.."\n")
+ for i,c in pairs(instance.configuration) do
+ local str = c[key]
+ if str then
+ print("\t" .. i .. "\t\t" .. input.aux.tabstr(str))
+ end
+ end
+ print()
+ end
+ end
+end
+
+function input.aux.tabstr(str)
+ if type(str) == 'table' then
+ return table.concat(str," | ")
+ else
+ return str
+ end
+end
+
+function input.simplified_list(str)
+ if type(str) == 'table' then
+ return str -- troubles ; ipv , in texmf
+ elseif str == '' then
+ return { }
+ else
+ local t = { }
+ for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
+ t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
+ end
+ return t
+ end
+end
+
+function input.unexpanded_path_list(instance,str)
+ local pth = input.variable(instance,str)
+ local lst = input.split_path(pth)
+ return input.aux.expanded_path(instance,lst)
+end
+function input.unexpanded_path(instance,str)
+ return file.join_path(input.unexpanded_path_list(instance,str))
+end
+
+function input.expanded_path_list(instance,str)
+ if not str then
+ return { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = str:gsub("%$","")
+ if not instance.lists[str] then -- cached
+ local lst = input.split_path(input.expansion(instance,str))
+ instance.lists[str] = input.aux.expanded_path(instance,lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = input.split_path(input.expansion(instance,str))
+ return input.aux.expanded_path(instance,lst)
+ end
+end
+function input.expand_path(instance,str)
+ return file.join_path(input.expanded_path_list(instance,str))
+end
+
+--~ function input.first_writable_path(instance,name)
+--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
+--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
+--~ return v
+--~ end
+--~ end
+--~ return "."
+--~ end
+
+function input.expanded_path_list_from_var(instance,str) -- brrr
+ local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
+ if tmp ~= "" then
+ return input.expanded_path_list(instance,str)
+ else
+ return input.expanded_path_list(instance,tmp)
+ end
+end
+function input.expand_path_from_var(instance,str)
+ return file.join_path(input.expanded_path_list_from_var(instance,str))
+end
+
+function input.format_of_var(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+function input.format_of_suffix(str)
+ return input.suffixmap[file.extname(str)] or 'tex'
+end
+
+function input.variable_of_format(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+
+function input.var_of_format_or_suffix(str)
+ local v = input.formats[str]
+ if v then
+ return v
+ end
+ v = input.formats[input.alternatives[str]]
+ if v then
+ return v
+ end
+ v = input.suffixmap[file.extname(str)]
+ if v then
+ return input.formats[isf]
+ end
+ return ''
+end
+
+function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
+ local ori = input.variable(instance,str)
+ local pth = input.aux.expanded_path(instance,input.split_path(ori))
+ return file.join_path(pth)
+end
+
+-- {a,b,c,d}
+-- a,b,c/{p,q,r},d
+-- a,b,c/{p,q,r}/d/{x,y,z}//
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+
+function input.aux.expanded_path(instance,pathlist)
+ -- a previous version fed back into pathlist
+ local i, n, oldlist, newlist, ok = 0, 0, { }, { }, false
+ for _,v in ipairs(pathlist) do
+ if v:find("[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ for _,v in ipairs(pathlist) do
+ oldlist[#oldlist+1] = (v:gsub("([\{\}])", function(p)
+ if p == "{" then
+ i = i + 1
+ if i > n then n = i end
+ return "<" .. (i-1) .. ">"
+ else
+ i = i - 1
+ return "" .. i .. ">"
+ end
+ end))
+ end
+ for i=1,n do
+ while true do
+ local more = false
+ local pattern = "^(.-)<"..(n-i)..">(.-)"..(n-i)..">(.-)$"
+ local t = { }
+ for _,v in ipairs(oldlist) do
+ local pre, mid, post = v:match(pattern)
+ if pre and mid and post then
+ more = true
+ -- for _,vv in ipairs(mid:splitchr(',')) do
+ for vv in string.gmatch(mid..',',"(.-),") do
+ if vv == '.' then
+ t[#t+1] = pre..post
+ else
+ t[#t+1] = pre..vv..post
+ end
+ end
+ else
+ t[#t+1] = v
+ end
+ end
+ oldlist = t
+ if not more then break end
+ end
+ end
+ for _,v in pairs(oldlist) do
+ v = file.collapse_path(v)
+ if v ~= "" and not v:find(instance.dummy_path_expr) then newlist[#newlist+1] = v end
+ end
+ else
+ for _,v in pairs(pathlist) do
+ -- for _,vv in pairs(v:split(",")) do
+ for vv in string.gmatch(v..',',"(.-),") do
+ vv = file.collapse_path(v)
+ if vv ~= "" then newlist[#newlist+1] = vv end
+ end
+ end
+ end
+ return newlist
+end
+
+--~ function input.is_readable(name) -- brrr, get rid of this
+--~ return name:find("^zip##") or file.is_readable(name)
+--~ end
+
+input.is_readable = { }
+
+function input.aux.is_readable(readable, name)
+ if input.trace > 2 then
+ if readable then
+ input.logger("+ readable", name)
+ else
+ input.logger("- readable", name)
+ end
+ end
+ return readable
+end
+
+function input.is_readable.file(name)
+ -- return input.aux.is_readable(file.is_readable(name), name)
+ return input.aux.is_readable(input.aux.is_file(name), name)
+end
+
+input.is_readable.tex = input.is_readable.file
+
+-- name
+-- name/name
+
+function input.aux.collect_files(instance,names)
+ local filelist = nil
+ for _, fname in pairs(names) do
+ if fname then
+ if input.trace > 2 then
+ input.logger("? blobpath asked",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or dname:find("^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ if blobpath and instance.files[blobpath] then
+ if input.trace > 2 then
+ input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
+ end
+ local blobfile = instance.files[blobpath][bname]
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or blobfile:find(dname) then
+ if not filelist then filelist = { } end
+ -- input.logger('= collected', blobpath.." | "..blobfile.." | "..bname)
+ filelist[#filelist+1] = file.join(blobpath,blobfile,bname)
+ end
+ else
+ for _, vv in pairs(blobfile) do
+ if not dname or vv:find(dname) then
+ if not filelist then filelist = { } end
+ filelist[#filelist+1] = file.join(blobpath,vv,bname)
+ end
+ end
+ end
+ end
+ elseif input.trace > 1 then
+ input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
+ end
+ end
+ end
+ end
+ return filelist
+end
+
+function input.suffix_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function input.suffixes_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str]
+ else
+ return {}
+ end
+end
+
+function input.aux.qualified_path(filename) -- make platform dependent / not good yet
+ return
+ filename:find("^%.+/") or
+ filename:find("^/") or
+ filename:find("^%a+%:") or
+ filename:find("^%a+##")
+end
+
+function input.normalize_name(original)
+ -- internally we use type##spec##subspec ; this hackery slightly slows down searching
+ local str = original or ""
+ str = str:gsub("::", "##") -- :: -> ##
+ str = str:gsub("^(%a+)://" ,"%1##") -- zip:// -> zip##
+ str = str:gsub("(.+)##(.+)##/(.+)","%1##%2##%3") -- ##/spec -> ##spec
+ if (input.trace>1) and (original ~= str) then
+ input.logger('= normalizer',original.." -> "..str)
+ end
+ return str
+end
+
+-- split the next one up, better for jit
+
+function input.aux.register_in_trees(instance,name)
+ if not name:find("^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
+ local result = { }
+ local stamp = nil
+ filename = input.normalize_name(filename)
+ filename = file.collapse_path(filename:gsub("\\","/"))
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ input.logger('! remembered', filename)
+ return instance.found[stamp]
+ end
+ end
+ if filename:find('%*') then
+ input.logger('! wildcard', filename)
+ result = input.find_wildcard_files(instance,filename)
+ elseif input.aux.qualified_path(filename) then
+ if input.is_readable.file(filename) then
+ input.logger('! qualified', filename)
+ result = { filename }
+ else
+ local forcedname, ok = "", false
+ if file.extname(filename) == "" then
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing standard filetype tex')
+ result, ok = { forcedname }, true
+ end
+ else
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ forcedname = filename .. "." .. s
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing format filetype', s)
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok then
+ input.logger('? qualified', filename)
+ end
+ end
+ else
+ -- search spec
+ local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
+ if ext == "" then
+ if not instance.force_suffixes then
+ table.insert(wantedfiles, filename)
+ end
+ else
+ table.insert(wantedfiles, filename)
+ end
+ if instance.format == "" then
+ if ext == "" then
+ local forcedname = filename .. '.tex'
+ table.insert(wantedfiles, forcedname)
+ filetype = input.format_of_suffix(forcedname)
+ input.logger('! forcing filetype',filetype)
+ else
+ filetype = input.format_of_suffix(filename)
+ input.logger('! using suffix based filetype',filetype)
+ end
+ else
+ if ext == "" then
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ table.insert(wantedfiles, filename .. "." .. s)
+ end
+ end
+ filetype = instance.format
+ input.logger('! using given filetype',filetype)
+ end
+ local typespec = input.variable_of_format(filetype)
+ local pathlist = input.expanded_path_list(instance,typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ input.logger('? filetype',filetype or '?')
+ input.logger('? wanted files',table.concat(wantedfiles," | "))
+ end
+ for _, fname in pairs(wantedfiles) do
+ if fname and input.is_readable.file(fname) then
+ filename, done = fname, true
+ table.insert(result, file.join('.',fname))
+ break
+ end
+ end
+ -- this is actually 'other text files' or 'any' or 'whatever'
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ filename = filelist and filelist[1]
+ if filename then
+ table.insert(result, filename)
+ done = true
+ end
+ else
+ -- list search
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ local doscan, recurse
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
+ if filelist then input.logger('? file list',table.concat(filelist," | ")) end
+ end
+ -- a bit messy ... esp the doscan setting here
+ for _, path in pairs(pathlist) do
+ if path:find("^!!") then doscan = false else doscan = true end
+ if path:find("//$") then recurse = true else recurse = false end
+ local pathname = path:gsub("^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
+ pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
+ pathname = pathname:gsub("//", '/.-/')
+ expr = "^" .. pathname
+ -- input.debug('?',expr)
+ for _, f in pairs(filelist) do
+ if f:find(expr) then
+ -- input.debug('T',' '..f)
+ if input.trace > 2 then
+ input.logger('= found in hash',f)
+ end
+ table.insert(result,f)
+ input.aux.register_in_trees(instance,f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ else
+ -- input.debug('F',' '..f)
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all
+ if input.method_is_file(pathname) then -- ?
+ local pname = pathname:gsub("%.%*$",'')
+ if not pname:find("%*") then
+ local ppname = pname:gsub("/+$","")
+ if input.aux.can_be_dir(instance,ppname) then
+ for _, w in pairs(wantedfiles) do
+ local fname = file.join(ppname,w)
+ if input.is_readable.file(fname) then
+ if input.trace > 2 then
+ input.logger('= found by scanning',fname)
+ end
+ table.insert(result,fname)
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ else
+ -- no access needed for non existing path, speedup (esp in large tree with lots of fake)
+ end
+ end
+ end
+ end
+ if not done and doscan then
+ -- todo: slow path scanning
+ end
+ if done and not instance.allresults then break end
+ end
+ end
+ end
+ for k,v in pairs(result) do
+ result[k] = file.collapse_path(v)
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+input.aux._find_file_ = input.aux.find_file
+
+function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
+ local result = input.aux._find_file_(instance,filename)
+ if #result == 0 then
+ local lowered = filename:lower()
+ if filename ~= lowered then
+ return input.aux._find_file_(instance,lowered)
+ end
+ end
+ return result
+end
+
+if lfs and lfs.isfile then
+ input.aux.is_file = lfs.isfile -- to be done: use this
+else
+ input.aux.is_file = file.is_readable
+end
+
+if lfs and lfs.isdir then
+ function input.aux.can_be_dir(instance,name)
+ if not instance.fakepaths[name] then
+ if lfs.isdir(name) then
+ instance.fakepaths[name] = 1 -- directory
+ else
+ instance.fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (instance.fakepaths[name] == 1)
+ end
+else
+ function input.aux.can_be_dir()
+ return true
+ end
+end
+
+if not input.concatinators then input.concatinators = { } end
+
+function input.concatinators.tex(tag,path,name)
+ return tag .. '/' .. path .. '/' .. name
+end
+
+input.concatinators.file = input.concatinators.tex
+
+function input.find_files(instance,filename,filetype,mustexist)
+ if type(mustexist) == boolean then
+ -- all set
+ elseif type(filetype) == 'boolean' then
+ filetype, mustexist = nil, false
+ elseif type(filetype) ~= 'string' then
+ filetype, mustexist = nil, false
+ end
+ instance.format = filetype or ''
+ local t = input.aux.find_file(instance,filename,true)
+ instance.format = ''
+ return t
+end
+
+function input.find_file(instance,filename,filetype,mustexist)
+ return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+end
+
+function input.find_given_files(instance,filename)
+ local bname, result = file.basename(filename), { }
+ for k, hash in pairs(instance.hashes) do
+ local blist = instance.files[hash.tag][bname]
+ if blist then
+ if type(blist) == 'string' then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ if not instance.allresults then break end
+ else
+ for kk,vv in pairs(blist) do
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function input.find_given_file(instance,filename)
+ return (input.find_given_files(instance,filename)[1] or "")
+end
+
+--~ function input.find_wildcard_files(instance,filename)
+--~ local result = { }
+--~ local bname, dname = file.basename(filename), file.dirname(filename)
+--~ local expr = dname:gsub("^*/","")
+--~ expr = expr:gsub("*",".*")
+--~ expr = expr:gsub("-","%-")
+--~ for k, hash in pairs(instance.hashes) do
+--~ local blist = instance.files[hash.tag][bname]
+--~ if blist then
+--~ if type(blist) == 'string' then
+--~ -- make function and share code
+--~ if blist:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ else
+--~ for kk,vv in pairs(blist) do
+--~ if vv:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ return result
+--~ end
+
+function input.find_wildcard_files(instance,filename)
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = dname:gsub("^*/","")
+ path = path:gsub("*",".*")
+ path = path:gsub("-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = name:gsub("*",".*")
+ name = name:gsub("-","%%-")
+ path = path:lower()
+ name = name:lower()
+ local function doit(blist,bname,hash,allresults)
+ local done = false
+ if blist then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if (blist:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ done = true
+ end
+ else
+ for kk,vv in pairs(blist) do
+ if (vv:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+ end
+ local files, allresults, done = instance.files, instance.allresults, false
+ if name:find("%*") then
+ for k, hash in pairs(instance.hashes) do
+ for kk, hh in pairs(files[hash.tag]) do
+ if (kk:lower()):find(name) then
+ if doit(hh,kk,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ else
+ for k, hash in pairs(instance.hashes) do
+ if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ return result
+end
+
+function input.find_wildcard_file(instance,filename)
+ return (input.find_wildcard_files(instance,filename)[1] or "")
+end
+
+-- main user functions
+
+function input.save_used_files_in_trees(instance, filename,jobname)
+ if not filename then filename = 'luatex.jlg' end
+ local f = io.open(filename,'w')
+ if f then
+ f:write("\n")
+ f:write("\n")
+ if jobname then
+ f:write("\t" .. jobname .. "\n")
+ end
+ f:write("\t\n")
+ for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
+ f:write("\t\t" .. v .. "\n")
+ end
+ f:write("\t\n")
+ f:write("\n")
+ f:close()
+ end
+end
+
+function input.automount(instance)
+ -- implemented later
+end
+
+function input.load(instance)
+ input.start_timing(instance)
+ input.identify_cnf(instance)
+ input.load_cnf(instance)
+ input.expand_variables(instance)
+ input.load_hash(instance)
+ input.automount(instance)
+ input.stop_timing(instance)
+end
+
+function input.for_files(instance, command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if input.verbose then
+ input.report(str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if input.verbose then
+ report('')
+ end
+ for _, file in pairs(files) do
+ local result = command(instance,file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in pairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+function input.var_value(instance,str) -- output the value of variable $STRING.
+ return input.variable(instance,str)
+end
+function input.expand_var(instance,str) -- output variable expansion of STRING.
+ return input.expansion(instance,str)
+end
+function input.show_path(instance,str) -- output search path for file type NAME
+ return file.join_path(input.expanded_path_list(instance,input.format_of_var(str)))
+end
+
+-- input.find_file(filename)
+-- input.find_file(filename, filetype, mustexist)
+-- input.find_file(filename, mustexist)
+-- input.find_file(filename, filetype)
+
+function input.aux.register_file(files, name, path)
+ if files[name] then
+ if type(files[name]) == 'string' then
+ files[name] = { files[name], path }
+ else
+ files[name] = path
+ end
+ else
+ files[name] = path
+ end
+end
+
+-- zip:: zip## zip://
+-- zip::pathtozipfile::pathinzipfile (also: pathtozipfile/pathinzipfile)
+-- file::name
+-- tex::name
+-- kpse::name
+-- kpse::format::name
+-- parent::n::name
+-- parent::name (default 2)
+
+if not input.finders then input.finders = { } end
+if not input.openers then input.openers = { } end
+if not input.loaders then input.loaders = { } end
+
+input.finders.notfound = { nil }
+input.openers.notfound = { nil }
+input.loaders.notfound = { false, nil, 0 }
+
+function input.splitmethod(filename)
+ local method, specification = filename:match("^(.-)##(.+)$")
+ if method and specification then
+ return method, specification
+ else
+ return 'tex', filename
+ end
+end
+
+function input.method_is_file(filename)
+ local method, specification = input.splitmethod(filename)
+ return method == 'tex' or method == 'file'
+end
+
+function input.methodhandler(what, instance, filename, filetype) -- ...
+ local method, specification = input.splitmethod(filename)
+ if method and specification then -- redundant
+ if input[what][method] then
+ input.logger('= handler',filename.." -> "..what.." | "..method.." | "..specification)
+ return input[what][method](instance,specification,filetype)
+ else
+ return nil
+ end
+ else
+ return input[what].tex(instance,filename,filetype)
+ end
+end
+
+-- also inside next test?
+
+function input.findtexfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.opentexfile(instance,filename)
+ return input.methodhandler('openers',instance, input.normalize_name(filename))
+end
+
+function input.findbinfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.openbinfile(instance,filename)
+ return input.methodhandler('loaders',instance, input.normalize_name(filename))
+end
+
+function input.loadbinfile(instance, filename, filetype)
+ local fname = input.findbinfile(instance, input.normalize_name(filename), filetype)
+ if fname and fname ~= "" then
+ return input.openbinfile(instance,fname)
+ else
+ return unpack(input.loaders.notfound)
+ end
+end
+
+function input.texdatablob(instance, filename, filetype)
+ local ok, data, size = input.loadbinfile(instance, filename, filetype)
+ return data or ""
+end
+
+function input.openfile(filename) -- brrr texmf.instance here / todo ! ! ! ! !
+ local fullname = input.findtexfile(texmf.instance, filename)
+ if fullname and (fullname ~= "") then
+ return input.opentexfile(texmf.instance, fullname)
+ else
+ return nil
+ end
+end
+
+function input.logmode()
+ return (os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"):lower()
+end
+
+-- this is a prelude to engine/progname specific configuration files
+-- in which case we can omit files meant for other programs and
+-- packages
+
+--- ctx
+
+-- maybe texinputs + font paths
+-- maybe positive selection tex/context fonts/tfm|afm|vf|opentype|type1|map|enc
+
+input.validators = { }
+input.validators.visibility = { }
+
+function input.validators.visibility.default(path, name)
+ return true
+end
+
+function input.validators.visibility.context(path, name)
+ path = path[1] or path -- some day a loop
+ return not (
+ path:find("latex") or
+ path:find("doc") or
+ path:find("tex4ht") or
+ path:find("source") or
+-- path:find("config") or
+-- path:find("metafont") or
+ path:find("lists$") or
+ name:find("%.tpm$") or
+ name:find("%.bak$")
+ )
+end
+
+-- todo: describe which functions are public (maybe input.private. ... )
+
+-- beware: i need to check where we still need a / on windows:
+
+function input.clean_path(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"\\","/"),"^!+",""),"//$","/")
+ return (string.gsub(string.gsub(str,"\\","/"),"^!+",""))
+end
+function input.do_with_path(name,func)
+ for _, v in pairs(input.expanded_path_list(instance,name)) do
+ func("^"..input.clean_path(v))
+ end
+end
+function input.do_with_var(name,func)
+ func(input.aux.expanded_var(name))
+end
+
+function input.with_files(instance,pattern,handle)
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ local blobtype = hash.type
+ if blobpath and instance.files[blobpath] then -- sort them?
+ for k,v in pairs(instance.files[blobpath]) do
+ if k:find(pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ newname = file.addsuffix(newname,"lua")
+ newscript = input.clean_path(input.find_file(instance, newname))
+ oldscript = input.clean_path(oldname)
+ input.report("old script", oldscript)
+ input.report("new script", newscript)
+ if oldscript ~= newscript and (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
+ newdata = io.loaddata(newscript)
+ if newdata then
+ input.report("old script content replaced by new content")
+ io.savedata(oldscript,newdata)
+ end
+ end
+end
+
+
+if not modules then modules = { } end modules ['luat-tmp'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module deals with caching data. It sets up the paths and
+implements loaders and savers for tables. Best is to set the
+following variable. When not set, the usual paths will be
+checked. Personally I prefer the (users) temporary path.
Currently we do no locking when we write files. This is no real
+problem because most caching involves fonts and the chance of them
+being written at the same time is small. We also need to extend
+luatools with a recache feature.
+--ldx]]--
+
+cache = cache or { }
+dir = dir or { }
+texmf = texmf or { }
+
+cache.path = nil
+cache.base = cache.base or "luatex-cache"
+cache.more = cache.more or "context"
+cache.direct = false -- true is faster but may need huge amounts of memory
+cache.trace = false
+cache.tree = false
+cache.temp = os.getenv("TEXMFCACHE") or os.getenv("HOME") or os.getenv("HOMEPATH") or os.getenv("VARTEXMF") or os.getenv("TEXMFVAR") or os.getenv("TMP") or os.getenv("TEMP") or os.getenv("TMPDIR") or nil
+cache.paths = { cache.temp }
+
+if not cache.temp then
+ print("\nFATAL ERROR: NO VALID TEMPORARY PATH\n")
+ os.exit()
+end
+
+function cache.configpath(instance)
+ return input.expand_var(instance,"TEXMFCNF")
+end
+
+function cache.treehash(instance)
+ local tree = cache.configpath(instance)
+ if not tree or tree == "" then
+ return false
+ else
+ return md5.hex(tree)
+ end
+end
+
+function cache.setpath(instance,...)
+ if not cache.path then
+ if lfs and instance then
+ for _,v in pairs(cache.paths) do
+ for _,vv in pairs(input.expanded_path_list(instance,v)) do
+ if lfs.isdir(vv) then
+ cache.path = vv
+ break
+ end
+ end
+ if cache.path then break end
+ end
+ end
+ if not cache.path then
+ cache.path = cache.temp
+ end
+ if lfs then
+ cache.tree = cache.tree or cache.treehash(instance)
+ if cache.tree then
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more,cache.tree)
+ else
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more)
+ end
+ end
+ end
+ if not cache.path then
+ cache.path = '.'
+ end
+ cache.path = input.clean_path(cache.path)
+ if lfs and not table.is_empty({...}) then
+ local pth = dir.mkdirs(cache.path,...)
+ return pth
+ end
+ return cache.path
+end
+
+function cache.setluanames(path,name)
+ return path .. "/" .. name .. ".tma", path .. "/" .. name .. ".tmc"
+end
+
+function cache.loaddata(path,name)
+ local tmaname, tmcname = cache.setluanames(path,name)
+ local loader = loadfile(tmcname) or loadfile(tmaname)
+ if loader then
+ return loader()
+ else
+ return false
+ end
+end
+
+function cache.is_writable(filepath,filename)
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ return file.is_writable(tmaname)
+end
+
+function cache.savedata(filepath,filename,data,raw) -- raw needed for file cache
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ local reduce, simplify = true, true
+ if raw then
+ reduce, simplify = false, false
+ end
+ if cache.direct then
+ file.savedata(tmaname, table.serialize(data,'return',true,true))
+ else
+ table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
+ end
+ utils.lua.compile(tmaname, tmcname)
+end
+
+-- here we use the cache for format loading (texconfig.[formatname|jobname])
+
+if tex and texconfig and texconfig.formatname and texconfig.formatname == "" then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end
+ texconfig.formatname = cache.setpath(instance,"format") .. "/" .. texconfig.luaname:gsub("%.lu.$",".fmt")
+end
+
+--[[ldx--
+
Once we found ourselves defining similar cache constructs
+several times, containers were introduced. Containers are used
+to collect tables in memory and reuse them when possible based
+on (unique) hashes (to be provided by the calling function).
+
+
Caching to disk is disabled by default. Version numbers are
+stored in the saved table which makes it possible to change the
+table structures without bothering about the disk cache.
+
+
Examples of usage can be found in the font related code.
+--ldx]]--
+
+containers = { }
+containers.trace = false
+
+do -- local report
+
+ local function report(container,tag,name)
+ if cache.trace or containers.trace or container.trace then
+ logs.report(string.format("%s cache",container.subcategory),string.format("%s: %s",tag,name or 'invalid'))
+ end
+ end
+
+ function containers.define(category, subcategory, version, enabled)
+ if category and subcategory then
+ return {
+ category = category,
+ subcategory = subcategory,
+ storage = { },
+ enabled = enabled,
+ version = version or 1.000,
+ trace = false,
+ path = cache.setpath(texmf.instance,category,subcategory),
+ }
+ else
+ return nil
+ end
+ end
+
+ function containers.is_usable(container, name)
+ return container.enabled and cache.is_writable(container.path, name)
+ end
+
+ function containers.is_valid(container, name)
+ if name and name ~= "" then
+ local cs = container.storage[name]
+ return cs and not table.is_empty(cs) and cs.cache_version == container.version
+ else
+ return false
+ end
+ end
+
+ function containers.read(container,name)
+ if container.enabled and not container.storage[name] then
+ container.storage[name] = cache.loaddata(container.path,name)
+ if containers.is_valid(container,name) then
+ report(container,"loaded",name)
+ else
+ container.storage[name] = nil
+ end
+ end
+ if container.storage[name] then
+ report(container,"reusing",name)
+ end
+ return container.storage[name]
+ end
+
+ function containers.write(container, name, data)
+ if data then
+ data.cache_version = container.version
+ if container.enabled then
+ local unique, shared = data.unique, data.shared
+ data.unique, data.shared = nil, nil
+ cache.savedata(container.path, name, data)
+ report(container,"saved",name)
+ data.unique, data.shared = unique, shared
+ end
+ report(container,"stored",name)
+ container.storage[name] = data
+ end
+ return data
+ end
+
+ function containers.content(container,name)
+ return container.storage[name]
+ end
+
+end
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+input.usecache = true
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name
+ if input.usecache then
+ name = file.join(cache.setpath(instance,"trees"),md5.hex(cachename))
+ else
+ name = file.join(cachename,dataname)
+ end
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ input.report("preparing " .. dataname .. " in", luaname)
+ for k, v in pairs(files) do
+ if not check or check(v,k) then -- path, name
+ if #v == 1 then
+ files[k] = v[1]
+ end
+ else
+ files[k] = nil -- false
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = input.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ -- f:write(table.serialize(data,'return'))
+ f:write(input.serialize(data))
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.serialize(files)
+ -- This version is somewhat optimized for the kind of
+ -- tables that we deal with, so it's much faster than
+ -- the generic serializer. This makes sense because
+ -- luatools and mtxtools are called frequently. Okay,
+ -- we pay a small price for properly tabbed tables.
+ local t = { }
+ local concat = table.concat
+ local sorted = table.sortedkeys
+ local function dump(k,v,m)
+ if type(v) == 'string' then
+ return m .. "['" .. k .. "']='" .. v .. "',"
+ elseif #v == 1 then
+ return m .. "['" .. k .. "']='" .. v[1] .. "',"
+ else
+ return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
+ end
+ end
+ t[#t+1] = "return {"
+ if instance.sortdata then
+ for _, k in pairs(sorted(files)) do
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sorted(fk)) do
+ t[#t+1] = dump(kk,fk[kk],"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,fk,"\t")
+ end
+ end
+ else
+ for k, v in pairs(files) do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in pairs(v) do
+ t[#t+1] = dump(kk,vv,"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,v,"\t")
+ end
+ end
+ end
+ t[#t+1] = "}"
+ return concat(t,"\n")
+end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ local luaname, lucname, pname, fname
+ if input.usecache then
+ pname, fname = cache.setpath(instance,"trees"), md5.hex(pathname)
+ filename = file.join(pname,fname)
+ else
+ if not filename or (filename == "") then
+ filename = dataname
+ end
+ pname, fname = pathname, filename
+ end
+ luaname = file.join(pname,fname) .. input.luasuffix
+ lucname = file.join(pname,fname) .. input.lucsuffix
+ local blob = loadfile(lucname)
+ if not blob then
+ blob = loadfile(luaname)
+ end
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == input.cacheversion then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ end
+end
+
+-- we will make a better format, maybe something xml or just text
+
+input.automounted = input.automounted or { }
+
+function input.automount(instance,usecache)
+ local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { cache.setpath(instance,"mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ input.starttiming(instance)
+ for k, root in pairs(mountpaths) do
+ local f = io.open(root.."/url.tmi")
+ if f then
+ for line in f:lines() do
+ if line then
+ if line:find("^[%%#%-]") then -- or %W
+ -- skip
+ elseif line:find("^zip://") then
+ input.report("mounting",line)
+ table.insert(input.automounted,line)
+ input.usezipfile(instance,line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ input.stoptiming(instance)
+ end
+end
+
+-- store info in format
+
+input.storage = { }
+input.storage.data = { }
+input.storage.min = 0 -- 500
+input.storage.max = input.storage.min - 1
+input.storage.trace = false -- true
+input.storage.done = 0
+input.storage.evaluators = { }
+-- (evaluate,message,names)
+
+function input.storage.register(...)
+ input.storage.data[#input.storage.data+1] = { ... }
+end
+
+function input.storage.evaluate(name)
+ input.storage.evaluators[#input.storage.evaluators+1] = name
+end
+
+function input.storage.finalize() -- we can prepend the string with "evaluate:"
+ for _, t in ipairs(input.storage.evaluators) do
+ for i, v in pairs(t) do
+ if type(v) == "string" then
+ t[i] = loadstring(v)()
+ elseif type(v) == "table" then
+ for _, vv in pairs(v) do
+ if type(vv) == "string" then
+ t[i] = loadstring(vv)()
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.storage.dump()
+ for name, data in ipairs(input.storage.data) do
+ local evaluate, message, original, target = data[1], data[2], data[3] ,data[4]
+ local name, initialize, finalize = nil, "", ""
+ for str in string.gmatch(target,"([^%.]+)") do
+ if name then
+ name = name .. "." .. str
+ else
+ name = str
+ end
+ initialize = string.format("%s %s = %s or {} ", initialize, name, name)
+ end
+ if evaluate then
+ finalize = "input.storage.evaluate(" .. name .. ")"
+ end
+ input.storage.max = input.storage.max + 1
+ if input.storage.trace then
+ logs.report('storage',string.format('saving %s in slot %s',message,input.storage.max))
+ lua.bytecode[input.storage.max] = loadstring(
+ initialize ..
+ string.format("logs.report('storage','restoring %s from slot %s') ",message,input.storage.max) ..
+ table.serialize(original,name) ..
+ finalize
+ )
+ else
+ lua.bytecode[input.storage.max] = loadstring(initialize .. table.serialize(original,name) .. finalize)
+ end
+ end
+end
+
+if lua.bytecode then -- from 0 upwards
+ local i = input.storage.min
+ while lua.bytecode[i] do
+ lua.bytecode[i]()
+ lua.bytecode[i] = nil
+ i = i + 1
+ end
+ input.storage.done = i
+end
+
+
+-- filename : luat-zip.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-zip'] = 1.001
+
+if zip and input then
+ zip.supported = true
+else
+ zip = { }
+ zip.supported = false
+end
+
+if not zip.supported then
+
+ if not input then input = { } end -- will go away
+
+ function zip.openarchive (...) return nil end -- needed ?
+ function zip.closenarchive (...) end -- needed ?
+ function input.registerzipfile (...) end -- needed ?
+ function input.usezipfile (...) end -- needed ?
+
+else
+
+ function input.locators.zip(instance,specification)
+ local name, spec = specification:match("^(.-)##(.-)$")
+ local f = io.open(name or specification)
+ if f then -- todo: reuse code
+ input.logger('! zip locator', specification..' found')
+ if name and spec then
+ input.aux.append_hash(instance,'zip',"zip##"..specification,name)
+ input.aux.extend_texmf_var(instance, "zip##"..specification)
+ else
+ input.aux.append_hash(instance,'zip',"zip##"..specification.."##",specification)
+ input.aux.extend_texmf_var(instance, "zip##"..specification.."##")
+ end
+ f:close()
+ else
+ input.logger('? zip locator', specification..' not found')
+ end
+ end
+
+ function input.hashers.zip(instance,tag,name)
+ input.report("loading zip file",name,"as",tag)
+ input.registerzipfile(instance,name,tag)
+ end
+
+ function input.concatinators.zip(tag,path,name)
+ return tag .. path .. '/' .. name
+ end
+
+ function input.is_readable.zip(name)
+ return true
+ end
+
+ function input.finders.zip(instance,filename,filetype)
+ local archive, dataname = filename:match("^(.+)##/*(.+)$")
+ if archive and dataname then
+ local zfile = zip.openarchive(archive)
+ if not zfile then
+ archive = input.find_file(instance,archive,filetype)
+ zfile = zip.openarchive(archive)
+ end
+ if zfile then
+ input.logger('! zip finder',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ dfile = zfile:close()
+ input.logger('+ zip finder',filename)
+ return 'zip##' .. filename
+ end
+ else
+ input.logger('? zip finder',archive)
+ end
+ end
+ input.logger('- zip finder',filename)
+ return unpack(input.finders.notfound)
+ end
+
+ function input.openers.zip(instance,filename)
+ if filename and filename ~= "" then
+ local archive, dataname = filename:match("^(.-)##/*(.+)$")
+ if archive and dataname then
+ local zfile= zip.openarchive(archive)
+ if zfile then
+ input.logger('+ zip starter',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ input.show_open(filename)
+ return input.openers.text_opener(filename,dfile,'zip')
+ end
+ else
+ input.logger('- zip starter',archive)
+ end
+ end
+ end
+ input.logger('- zip opener',filename)
+ return unpack(input.openers.notfound)
+ end
+
+ function input.loaders.zip(instance, filename) -- we could use input.openers.zip
+ if filename and filename ~= "" then
+ input.logger('= zip loader',filename)
+ local archive, dataname = filename:match("^(.+)##/*(.+)$")
+ if archive and dataname then
+ local zfile = zip.openarchive(archive)
+ if zfile then
+ input.logger('= zip starter',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ input.show_load(filename)
+ input.logger('+ zip loader',filename)
+ local s = dfile:read("*all")
+ dfile:close()
+ return true, s, #s
+ end
+ else
+ input.logger('- zip starter',archive)
+ end
+ end
+ end
+ input.logger('- zip loader',filename)
+ return unpack(input.loaders.notfound)
+ end
+
+ zip.archives = { }
+ zip.registeredfiles = { }
+
+ function zip.openarchive(name)
+ if name and name ~= "" and not zip.archives[name] then
+ zip.archives[name] = zip.open(name)
+ end
+ return zip.archives[name]
+ end
+
+ function zip.closearchive(name)
+ if zip.archives[name] then
+ zip.close(archives[name])
+ zip.archives[name] = nil
+ end
+ end
+
+ -- aparte register maken voor user (register tex / zip), runtime tree register
+ -- todo: alleen url syntax toestaan
+ -- user function: also handle zip::name::path
+
+ function input.usezipfile(instance,zipname) -- todo zip://
+ zipname = input.normalize_name(zipname)
+ if not zipname:find("^zip##") then
+ zipname = "zip##"..zipname
+ end
+ input.logger('! zip user','file '..zipname)
+ if not zipname:find("^zip##(.+)##(.-)$") then
+ zipname = zipname .. "##" -- dummy spec
+ end
+ local tag = zipname
+ local name = zipname:match("zip##(.+)##.-")
+ input.aux.prepend_hash(instance,'zip',tag,name)
+ input.aux.extend_texmf_var(instance, tag)
+ input.registerzipfile(instance,name,tag)
+ end
+
+ function input.registerzipfile(instance,zipname,tag)
+ if not zip.registeredfiles[zipname] then
+ input.start_timing(instance)
+ local z = zip.open(zipname)
+ if not z then
+ zipname = input.find_file(instance,zipname)
+ z = zip.open(zipname)
+ end
+ if z then
+ input.logger("= zipfile","registering "..zipname)
+ zip.registeredfiles[zipname] = z
+ input.aux.register_zip_file(instance,zipname,tag)
+ else
+ input.logger("? zipfile","unknown "..zipname)
+ end
+ input.stop_timing(instance)
+ end
+ end
+
+ function input.aux.register_zip_file(instance,zipname,tagname)
+ if zip.registeredfiles[zipname] then
+ if not tagname:find("^zip##") then
+ tagname = "zip##" .. tagname
+ end
+ local path, name, n = nil, nil, 0
+ if not instance.files[tagname] then
+ instance.files[tagname] = { }
+ end
+ local files, filter = instance.files[tagname], ""
+ local subtree = tagname:match("^zip##.+##(.+)$")
+ if subtree then
+ filter = "^"..subtree.."/(.+)/(.-)$"
+ else
+ filter = "^(.+)/(.-)$"
+ end
+ input.logger('= zip filter',filter)
+ -- we can consider putting a files.luc in the file
+ local register = input.aux.register_file
+ for i, _ in zip.registeredfiles[zipname]:files() do
+ path, name = i.filename:match(filter)
+ if path then
+ if name and name ~= '' then
+ register(files, name, path)
+ n = n + 1
+ else
+ -- directory
+ end
+ else
+ register(files, i.filename, '')
+ n = n + 1
+ end
+ end
+ input.report(n, 'entries in', zipname)
+ end
+ end
+
+end
+
+
+-- filename : luat-zip.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-tex'] = 1.001
+
+-- special functions that deal with io
+
+if texconfig and not texlua then
+
+ input.level = input.level or 0
+
+ if input.logmode() == 'xml' then
+ function input.show_open(name)
+ input.level = input.level + 1
+ texio.write_nl("")
+ end
+ function input.show_close(name)
+ texio.write(" ")
+ input.level = input.level - 1
+ end
+ function input.show_load(name)
+ texio.write_nl("") -- level?
+ end
+ else
+ function input.show_open () end
+ function input.show_close() end
+ function input.show_load () end
+ end
+
+ function input.finders.generic(instance,tag,filename,filetype)
+ local foundname = input.find_file(instance,filename,filetype)
+ if foundname and foundname ~= "" then
+ input.logger('+ ' .. tag .. ' finder',filename,'filetype')
+ return foundname
+ else
+ input.logger('- ' .. tag .. ' finder',filename,'filetype')
+ return unpack(input.finders.notfound)
+ end
+ end
+
+ input.filters.dynamic_translator = nil
+ input.filters.frozen_translator = nil
+ input.filters.utf_translator = nil
+
+ function input.openers.text_opener(filename,file_handle,tag)
+ local u = unicode.utftype(file_handle)
+ local t = { }
+ if u > 0 then
+ input.logger('+ ' .. tag .. ' opener (' .. unicode.utfname[u] .. ')',filename)
+ local l
+ if u > 2 then
+ l = unicode.utf32_to_utf8(file_handle:read("*a"),u==4)
+ else
+ l = unicode.utf16_to_utf8(file_handle:read("*a"),u==2)
+ end
+ file_handle:close()
+ t = {
+ utftype = u, -- may go away
+ lines = l,
+ current = 0,
+ handle = nil,
+ noflines = #l,
+ close = function()
+ input.logger('= ' .. tag .. ' closer (' .. unicode.utfname[u] .. ')',filename)
+ input.show_close(filename)
+ end,
+ reader = function(self)
+ if not self then self = t end
+ if self.current >= #self.lines then
+ return nil
+ else
+ self.current = self.current + 1
+ if input.filters.utf_translator then
+ return input.filters.utf_translator(self.lines[t.current])
+ else
+ return self.lines[self.current]
+ end
+ end
+ end
+ }
+ else
+ input.logger('+ ' .. tag .. ' opener',filename)
+ -- todo: file;name -> freeze / eerste regel scannen -> freeze
+ t = {
+ reader = function(self)
+ if not self then self = t end -- not used
+ if input.filters.dynamic_translator then
+ return input.filters.dynamic_translator(file_handle:read())
+ elseif input.filters.utf_translator then
+ return input.filters.utf_translator(file_handle:read())
+ else
+ return file_handle:read()
+ end
+ end,
+ close = function()
+ input.logger('= ' .. tag .. ' closer',filename)
+ input.show_close(filename)
+ file_handle:close()
+ end,
+ handle = function()
+ return file_handle
+ end,
+ noflines = function()
+ t.noflines = io.noflines(file_handle)
+ return t.noflines
+ end
+ }
+ end
+ return t
+ end
+
+ function input.openers.generic(instance,tag,filename)
+ if filename and filename ~= "" then
+ local f = io.open(filename,"r")
+ if f then
+ input.show_open(filename)
+ return input.openers.text_opener(filename,f,tag)
+ end
+ end
+ input.logger('- ' .. tag .. ' opener',filename)
+ return unpack(input.openers.notfound)
+ end
+
+ function input.loaders.generic(instance,tag,filename)
+ if filename and filename ~= "" then
+ local f = io.open(filename,"rb")
+ if f then
+ input.show_load(filename)
+ input.logger('+ ' .. tag .. ' loader',filename)
+ local s = f:read("*a")
+ f:close()
+ if s then
+ return true, s, #s
+ end
+ end
+ end
+ input.logger('- ' .. tag .. ' loader',filename)
+ return unpack(input.loaders.notfound)
+ end
+
+ function input.finders.tex(instance,filename,filetype)
+ return input.finders.generic(instance,'tex',filename,filetype)
+ end
+ function input.openers.tex(instance,filename)
+ return input.openers.generic(instance,'tex',filename)
+ end
+ function input.loaders.tex(instance,filename)
+ return input.loaders.generic(instance,'tex',filename)
+ end
+
+end
+
+-- callback into the file io and related things; disabling kpse
+
+if texconfig and not texlua then
+
+ texconfig.kpse_init = false
+ texconfig.trace_file_names = input.logmode() == 'tex'
+ texconfig.max_print_line = 100000
+
+ -- if still present, we overload kpse (put it off-line so to say)
+
+ if not texmf then texmf = { } end
+
+ if not texmf.instance then
+
+ if not texmf.instance then -- prevent a second loading
+
+ texmf.instance = input.reset()
+ texmf.instance.progname = environment.progname or 'context'
+ texmf.instance.engine = environment.engine or 'luatex'
+ texmf.instance.validfile = input.validctxfile
+
+ input.load(texmf.instance)
+
+ end
+
+ if callback then
+ callback.register('find_read_file' , function(id,name) return input.findtexfile(texmf.instance,name) end)
+ callback.register('open_read_file' , function( name) return input.opentexfile(texmf.instance,name) end)
+ end
+
+ if callback then
+ callback.register('find_data_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
+ callback.register('find_enc_file' , function(name) return input.findbinfile(texmf.instance,name,"enc") end)
+ callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"tfm") end)
+ callback.register('find_format_file' , function(name) return input.findbinfile(texmf.instance,name,"fmt") end)
+ callback.register('find_image_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
+ callback.register('find_map_file' , function(name) return input.findbinfile(texmf.instance,name,"map") end)
+ callback.register('find_ocp_file' , function(name) return input.findbinfile(texmf.instance,name,"ocp") end)
+ callback.register('find_opentype_file' , function(name) return input.findbinfile(texmf.instance,name,"otf") end)
+ callback.register('find_output_file' , function(name) return name end)
+ callback.register('find_pk_file' , function(name) return input.findbinfile(texmf.instance,name,"pk") end)
+ callback.register('find_sfd_file' , function(name) return input.findbinfile(texmf.instance,name,"sfd") end)
+ callback.register('find_truetype_file' , function(name) return input.findbinfile(texmf.instance,name,"ttf") end)
+ callback.register('find_type1_file' , function(name) return input.findbinfile(texmf.instance,name,"pfb") end)
+ callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"vf") end)
+
+ callback.register('read_data_file' , function(file) return input.loadbinfile(texmf.instance,file,"tex") end)
+ callback.register('read_enc_file' , function(file) return input.loadbinfile(texmf.instance,file,"enc") end)
+ callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"tfm") end)
+ -- format
+ -- image
+ callback.register('read_map_file' , function(file) return input.loadbinfile(texmf.instance,file,"map") end)
+ callback.register('read_ocp_file' , function(file) return input.loadbinfile(texmf.instance,file,"ocp") end)
+ callback.register('read_opentype_file' , function(file) return input.loadbinfile(texmf.instance,file,"otf") end)
+ -- output
+ callback.register('read_pk_file' , function(file) return input.loadbinfile(texmf.instance,file,"pk") end)
+ callback.register('read_sfd_file' , function(file) return input.loadbinfile(texmf.instance,file,"sfd") end)
+ callback.register('read_truetype_file' , function(file) return input.loadbinfile(texmf.instance,file,"ttf") end)
+ callback.register('read_type1_file' , function(file) return input.loadbinfile(texmf.instance,file,"pfb") end)
+ callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"vf" ) end)
+ end
+
+ if callback and environment.aleph_mode then
+ callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"ofm") end)
+ callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"ofm") end)
+ callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"ovf") end)
+ callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"ovf") end)
+ end
+
+ if callback then
+ callback.register('find_write_file' , function(id,name) return name end)
+ end
+
+ if callback and (not config or (#config == 0)) then
+ callback.register('find_format_file' , function(name) return name end)
+ end
+
+ if callback and false then
+ for k, v in pairs(callback.list()) do
+ if not v then texio.write_nl("callback "..k.." is not set") end
+ end
+ end
+
+ if callback then
+
+ input.start_actions = { }
+ input.stop_actions = { }
+
+ function input.register_start_actions(f) table.insert(input.start_actions, f) end
+ function input.register_stop_actions (f) table.insert(input.stop_actions, f) end
+
+--~ callback.register('start_run', function() for _, a in pairs(input.start_actions) do a() end end)
+--~ callback.register('stop_run' , function() for _, a in pairs(input.stop_actions ) do a() end end)
+
+ end
+
+ if callback and (input.logmode() == 'xml') then
+
+ function input.start_page_number()
+ texio.write_nl("")
+ texio.write_nl("")
+ end
+
+ callback.register('start_page_number' , input.start_page_number)
+ callback.register('stop_page_number' , input.stop_page_number )
+
+ function input.report_output_pages(p,b)
+ texio.write_nl(""..p.."")
+ texio.write_nl(""..b.."")
+ texio.write_nl("")
+ end
+ function input.report_output_log()
+ end
+
+ callback.register('report_output_pages', input.report_output_pages)
+ callback.register('report_output_log' , input.report_output_log )
+
+ function input.start_run()
+ texio.write_nl("")
+ texio.write_nl("")
+ texio.write_nl("")
+ end
+ function input.stop_run()
+ texio.write_nl("")
+ end
+ function input.show_statistics()
+ for k,v in pairs(status.list()) do
+ texio.write_nl("log",""..tostring(v).."")
+ end
+ end
+
+ table.insert(input.start_actions, input.start_run)
+
+ table.insert(input.stop_actions, input.show_statistics)
+ table.insert(input.stop_actions, input.stop_run)
+
+ function input.start_run() for _, a in pairs(input.start_actions) do a() end end
+ function input.stop_run () for _, a in pairs(input.stop_actions ) do a() end end
+
+ callback.register('start_run', input.start_run)
+ callback.register('stop_run' , input.stop_run )
+
+ end
+
+ end
+
+ if kpse then
+
+ function kpse.find_file(filename,filetype,mustexist)
+ return input.find_file(texmf.instance,filename,filetype,mustexist)
+ end
+ function kpse.expand_path(variable)
+ return input.expand_path(texmf.instance,variable)
+ end
+ function kpse.expand_var(variable)
+ return input.expand_var(texmf.instance,variable)
+ end
+ function kpse.expand_braces(variable)
+ return input.expand_braces(texmf.instance,variable)
+ end
+
+ end
+
+end
+
+-- program specific configuration (memory settings and alike)
+
+if texconfig and not texlua then
+
+ if not luatex then luatex = { } end
+
+ luatex.variablenames = {
+ 'main_memory', 'extra_mem_bot', 'extra_mem_top',
+ 'buf_size',
+ 'font_max', 'font_mem_size',
+ 'hash_extra', 'max_strings', 'pool_free', 'pool_size', 'string_vacancies',
+ 'obj_tab_size', 'pdf_mem_size', 'dest_names_size',
+ 'nest_size', 'param_size', 'save_size', 'stack_size',
+ 'trie_size', 'hyph_size',
+ 'ocp_stack_size', 'ocp_list_size', 'ocp_buf_size'
+ }
+
+ function luatex.variables()
+ local t, x = { }, nil
+ for _,v in pairs(luatex.variablenames) do
+ x = input.var_value(texmf.instance,v)
+ if x and x:find("^%d+$") then
+ t[v] = tonumber(x)
+ end
+ end
+ return t
+ end
+
+ function luatex.setvariables(tab)
+ for k,v in pairs(luatex.variables()) do
+ tab[k] = v
+ end
+ end
+
+ if not luatex.variables_set then
+ luatex.setvariables(texconfig)
+ luatex.variables_set = true
+ end
+
+ texconfig.max_print_line = 100000
+
+end
+
+-- some tex basics
+
+if not cs then cs = { } end
+
+function cs.def(k,v)
+ tex.sprint(tex.texcatcodes, "\\def\\" .. k .. "{" .. v .. "}")
+end
+
+function cs.chardef(k,v)
+ tex.sprint(tex.texcatcodes, "\\chardef\\" .. k .. "=" .. v .. "\\relax")
+end
+
+function cs.boolcase(b)
+ if b then tex.write(1) else tex.write(0) end
+end
+
+function cs.testcase(b)
+ if b then
+ tex.sprint(tex.texcatcodes, "\\firstoftwoarguments")
+ else
+ tex.sprint(tex.texcatcodes, "\\secondoftwoarguments")
+ end
+end
+
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This file is used when we want the input handlers to behave like
+kpsewhich. What to do with the following:
If you wondered abou tsome of the previous mappings, how about
+the next bunch:
+--ldx]]--
+
+input.formats['bib'] = ''
+input.formats['bst'] = ''
+input.formats['mft'] = ''
+input.formats['ist'] = ''
+input.formats['web'] = ''
+input.formats['cweb'] = ''
+input.formats['MetaPost support'] = ''
+input.formats['TeX system documentation'] = ''
+input.formats['TeX system sources'] = ''
+input.formats['Troff fonts'] = ''
+input.formats['dvips config'] = ''
+input.formats['graphic/figure'] = ''
+input.formats['ls-R'] = ''
+input.formats['other text files'] = ''
+input.formats['other binary files'] = ''
+
+input.formats['gf'] = ''
+input.formats['pk'] = ''
+input.formats['base'] = 'MFBASES'
+input.formats['cnf'] = ''
+input.formats['mem'] = 'MPMEMS'
+input.formats['mf'] = 'MFINPUTS'
+input.formats['mfpool'] = 'MFPOOL'
+input.formats['mppool'] = 'MPPOOL'
+input.formats['texpool'] = 'TEXPOOL'
+input.formats['PostScript header'] = 'TEXPSHEADERS'
+input.formats['cmap files'] = 'CMAPFONTS'
+input.formats['type42 fonts'] = 'T42FONTS'
+input.formats['web2c files'] = 'WEB2C'
+input.formats['pdftex config'] = 'PDFTEXCONFIG'
+input.formats['texmfscripts'] = 'TEXMFSCRIPTS'
+input.formats['bitmap font'] = ''
+input.formats['lig files'] = 'LIGFONTS'
+
+
+-- end library merge
+
+-- We initialize some characteristics of this program. We need to
+-- do this before we load the libraries, else own.name will not be
+-- properly set (handy for selfcleaning the file). It's an ugly
+-- looking piece of code.
+
+own = { }
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-number.lua',
+ 'l-os.lua',
+ 'l-md5.lua',
+ 'l-file.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+ 'l-unicode.lua',
+ 'l-utils.lua',
+ 'luat-lib.lua',
+ 'luat-inp.lua',
+ 'luat-tmp.lua',
+ 'luat-zip.lua',
+ 'luat-tex.lua',
+ 'luat-kps.lua',
+}
+
+-- We need this hack till luatex is fixed.
+
+if arg and arg[0] == 'luatex' and arg[1] == "--luaonly" then
+ arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
+end
+
+-- End of hack.
+
+own.name = (environment and environment.ownname) or arg[0] or 'luatools.lua'
+own.path = string.match(own.name,"^(.+)[\\/].-$") or "."
+own.list = { '.' }
+
+if own.path ~= '.' then
+ table.insert(own.list,own.path)
+end
+
+table.insert(own.list,own.path.."/../../../tex/context/base")
+table.insert(own.list,own.path.."/mtx")
+table.insert(own.list,own.path.."/../sources")
+
+function locate_libs()
+ for _, lib in pairs(own.libs) do
+ for _, pth in pairs(own.list) do
+ local filename = string.gsub(pth .. "/" .. lib,"\\","/")
+ local codeblob = loadfile(filename)
+ if codeblob then
+ codeblob()
+ own.list = { pth } -- speed up te search
+ break
+ end
+ end
+ end
+end
+
+if not input then
+ locate_libs()
+end
+
+if not input then
+ print("")
+ print("Luatools is unable to start up due to lack of libraries. You may")
+ print("try to run 'lua luatools.lua --selfmerge' in the path where this")
+ print("script is located (normally under ..../scripts/context/lua) which")
+ print("will make luatools library independent.")
+ os.exit()
+end
+
+instance = input.reset()
+input.verbose = environment.arguments["verbose"] or false
+input.banner = 'LuaTools | '
+utils.report = input.report
+
+input.defaultlibs = { -- not all are needed
+ 'l-string.lua', 'l-table.lua', 'l-boolean.lua', 'l-number.lua', 'l-unicode.lua',
+ 'l-md5.lua', 'l-os.lua', 'l-io.lua', 'l-file.lua', 'l-dir.lua', 'l-utils.lua', 'l-tex.lua',
+ 'luat-lib.lua', 'luat-inp.lua', 'luat-tmp.lua', 'luat-zip.lua', 'luat-tex.lua'
+}
+
+-- todo: use environment.argument() instead of environment.arguments[]
+
+instance.engine = environment.arguments["engine"] or 'luatex'
+instance.progname = environment.arguments["progname"] or 'context'
+instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
+instance.lualibs = environment.arguments["lualibs"] or table.concat(input.defaultlibs,",")
+instance.allresults = environment.arguments["all"] or false
+instance.pattern = environment.arguments["pattern"] or nil
+instance.sortdata = environment.arguments["sort"] or false
+instance.kpseonly = not environment.arguments["all"] or false
+instance.my_format = environment.arguments["format"] or instance.format
+instance.lsrmode = environment.arguments["lsr"] or false
+
+if environment.arguments["trace"] then input.settrace(environment.arguments["trace"]) end
+
+if environment.arguments["minimize"] then
+ if input.validators.visibility[instance.progname] then
+ instance.validfile = input.validators.visibility[instance.progname]
+ end
+end
+
+function input.my_prepare_a(instance)
+ input.identify_cnf(instance)
+ input.load_cnf(instance)
+ input.expand_variables(instance)
+end
+
+function input.my_prepare_b(instance)
+ input.my_prepare_a(instance)
+ input.load_hash(instance)
+ input.automount(instance)
+end
+
+-- barename
+
+if not messages then messages = { } end
+
+messages.no_ini_file = [[
+There is no lua initialization file found. This file can be forced by the
+"--progname" directive, or specified with "--luaname", or it is derived
+automatically from the formatname (aka jobname). It may be that you have
+to regenerate the file database using "luatools --generate".
+]]
+
+messages.help = [[
+--generate generate file database
+--variables show configuration variables
+--expansions show expanded variables
+--configurations show configuration order
+--expand-braces expand complex variable
+--expand-path expand variable (resolve paths)
+--expand-var expand variable (resolve references)
+--show-path show path expansion of ...
+--var-value report value of variable
+--find-file report file location
+--make or --ini make luatex format
+--run or --fmt= run luatex format
+--luafile=str lua inifile (default is .lua)
+--lualibs=list libraries to assemble (optional when --compile)
+--compile assemble and compile lua inifile
+--mkii force context mkii mode (only for testing, not usable!)
+--verbose give a bit more info
+--minimize optimize lists for format
+--all show all found files
+--sort sort cached data
+--engine=str target engine
+--progname=str format or backend
+--pattern=str filter variables
+--lsr use lsr and cnf directly
+]]
+
+function input.my_make_format(instance,texname)
+ if texname and texname ~= "" then
+ if input.usecache then
+ local path = file.join(cache.setpath(instance,"formats")) -- maybe platform
+ if path and lfs then
+ lfs.chdir(path)
+ end
+ end
+ local barename = texname:gsub("%.%a+$","")
+ if barename == texname then
+ texname = texname .. ".tex"
+ end
+ local fullname = input.find_files(instance,texname)[1] or ""
+ if fullname == "" then
+ input.report("no tex file with name",texname)
+ else
+ local luaname, lucname, luapath, lualibs = "", "", "", { }
+ -- the following is optional, since context.lua can also
+ -- handle this collect and compile business
+ if environment.arguments["compile"] then
+ if luaname == "" then luaname = barename end
+ input.report("creating initialization file " .. luaname)
+ luapath = file.dirname(luaname)
+ if luapath == "" then
+ luapath = file.dirname(texname)
+ end
+ if luapath == "" then
+ luapath = file.dirname(input.find_files(instance,texname)[1] or "")
+ end
+ lualibs = string.split(instance.lualibs,",")
+ luaname = file.basename(barename .. ".lua")
+ lucname = file.basename(barename .. ".luc")
+ -- todo: when this fails, we can just copy the merged libraries from
+ -- luatools since they are normally the same, at least for context
+ if lualibs[1] then
+ local firstlib = file.join(luapath,lualibs[1])
+ if not lfs.isfile(firstlib) then
+ local foundname = input.find_files(instance,lualibs[1])[1]
+ if foundname then
+ input.report("located library path : " .. luapath)
+ luapath = file.dirname(foundname)
+ end
+ end
+ end
+ input.report("using library path : " .. luapath)
+ input.report("using lua libraries: " .. table.join(lualibs," "))
+ utils.merger.selfcreate(lualibs,luapath,luaname)
+ if utils.lua.compile(luaname, lucname) and io.exists(lucname) then
+ luaname = lucname
+ input.report("using compiled initialization file " .. lucname)
+ else
+ input.report("using uncompiled initialization file " .. luaname)
+ end
+ else
+ for _, v in pairs({instance.luaname, instance.progname, barename}) do
+ v = string.gsub(v..".lua","%.lua%.lua$",".lua")
+ if v and (v ~= "") then
+ luaname = input.find_files(instance,v)[1] or ""
+ if luaname ~= "" then
+ break
+ end
+ end
+ end
+ end
+ if luaname == "" then
+ input.reportlines(messages.no_ini_file)
+ input.report("texname : " .. texname)
+ input.report("luaname : " .. instance.luaname)
+ input.report("progname : " .. instance.progname)
+ input.report("barename : " .. barename)
+ else
+ input.report("using lua initialization file " .. luaname)
+ local flags = { "--ini" }
+ if environment.arguments["mkii"] then
+ -- flags[#flags+1] = "--mkii" -- web2c error
+ flags[#flags+1] = "--progname=" .. instance.progname
+ else
+ flags[#flags+1] = "--lua=" .. string.quote(luaname)
+ -- flags[#flags+1] = "--progname=" .. instance.progname -- potential fallback
+ end
+ local command = "luatex ".. table.concat(flags," ") .. " " .. string.quote(fullname) .. " \\dump"
+ input.report("running command: " .. command .. "\n")
+ os.exec(command)
+ end
+ end
+ else
+ input.report("no tex file given")
+ end
+end
+
+function input.my_run_format(instance,name,data)
+ if name and (name ~= "") then
+ local barename = name:gsub("%.%a+$","")
+ local fmtname = ""
+ if input.usecache then
+ local path = file.join(cache.setpath(instance,"formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = input.find_files(instance,barename..".fmt")[1] or ""
+ end
+ fmtname = input.clean_path(fmtname)
+ local barename = fmtname:gsub("%.%a+$","")
+ if fmtname == "" then
+ input.report("no format with name",name)
+ else
+ local luaname = barename .. ".luc"
+ local f = io.open(luaname)
+ if not f then
+ luaname = barename .. ".lua"
+ f = io.open(luaname)
+ end
+ if f then
+ f:close()
+ -- bug, no .fmt !
+ local command = "luatex --fmt=" .. string.quote(barename) .. " --lua=" .. string.quote(luaname) .. " " .. string.quote(data)
+ input.report("running command: " .. command)
+ os.exec(command)
+ else
+ input.report("using format name",fmtname)
+ input.report("no luc/lua with name",barename)
+ end
+ end
+ end
+end
+
+input.report(banner,"\n")
+
+local ok = true
+
+if environment.arguments["selfmerge"] then
+ utils.merger.selfmerge(own.name,own.libs,own.list)
+elseif environment.arguments["selfclean"] then
+ utils.merger.selfclean(own.name)
+elseif environment.arguments["selfupdate"] then
+ input.my_prepare_b(instance)
+ input.verbose = true
+ input.update_script(own.name,"luatools")
+elseif environment.arguments["generate"] then
+ instance.renewcache = true
+ input.verbose = true
+ input.my_prepare_b(instance)
+elseif environment.arguments["make"] or environment.arguments["ini"] or environment.arguments["compile"] then
+ input.my_prepare_b(instance)
+ input.verbose = true
+ input.my_make_format(instance,environment.files[1] or "")
+elseif environment.arguments["run"] then
+ input.my_prepare_a(instance) -- ! no need for loading databases
+ input.verbose = true
+ input.my_run_format(instance,environment.files[1] or "",environment.files[2] or "")
+elseif environment.arguments["fmt"] then
+ input.my_prepare_a(instance) -- ! no need for loading databases
+ input.verbose = true
+ input.my_run_format(instance,environment.arguments["fmt"], environment.files[1] or "")
+elseif environment.arguments["variables"] or environment.arguments["show-variables"] then
+ input.my_prepare_a(instance)
+ input.list_variables(instance)
+elseif environment.arguments["expansions"] or environment.arguments["show-expansions"] then
+ input.my_prepare_a(instance)
+ input.list_expansions(instance)
+elseif environment.arguments["configurations"] or environment.arguments["show-configurations"] then
+ input.my_prepare_a(instance)
+ input.list_configurations(instance)
+elseif environment.arguments["expand-braces"] then
+ input.my_prepare_a(instance)
+ input.for_files(instance, input.expand_braces, environment.files)
+elseif environment.arguments["expand-path"] then
+ input.my_prepare_a(instance)
+ input.for_files(instance, input.expand_path, environment.files)
+elseif environment.arguments["expand-var"] or environment.arguments["expand-variable"] then
+ input.my_prepare_a(instance)
+ input.for_files(instance, input.expand_var, environment.files)
+elseif environment.arguments["show-path"] or environment.arguments["path-value"] then
+ input.my_prepare_a(instance)
+ input.for_files(instance, input.show_path, environment.files)
+elseif environment.arguments["var-value"] or environment.arguments["show-value"] then
+ input.my_prepare_a(instance)
+ input.for_files(instance, input.var_value, environment.files)
+elseif environment.arguments["find-file"] then
+ input.my_prepare_b(instance)
+ instance.format = environment.arguments["format"] or instance.format
+ if environment.arguments["pattern"] then
+ instance.allresults = true
+ input.for_files(instance, input.find_files, { environment.arguments["pattern"] }, instance.my_format)
+ else
+ input.for_files(instance, input.find_files, environment.files, instance.my_format)
+ end
+--~ elseif environment.arguments["first-writable-path"] then
+--~ input.my_prepare_b(instance)
+--~ input.report(input.first_writable_path(instance,environment.files[1] or "."))
+elseif environment.arguments["format-path"] then
+ input.my_prepare_b(instance)
+ input.report(cache.setpath(instance,"format"))
+elseif environment.arguments["pattern"] then
+ input.my_prepare_b(instance)
+ instance.format = environment.arguments["format"] or instance.format
+ instance.allresults = true
+ input.for_files(instance, input.find_files, { environment.arguments["pattern"] }, instance.my_format)
+elseif environment.arguments["help"] or (environment.files[1]=='help') or (#environment.files==0) then
+ if not input.verbose then
+ input.verbose = true
+ input.report(banner,"\n")
+ end
+ input.reportlines(messages.help)
+else
+ input.my_prepare_b(instance)
+ input.for_files(instance, input.find_files, environment.files, instance.my_format)
+end
+
+if input.verbose then
+ input.report("")
+ input.report("runtime: " .. os.clock() .. " seconds")
+end
+
+--~ if ok then
+--~ input.report("exit code: 0") os.exit(0)
+--~ else
+--~ input.report("exit code: 1") os.exit(1)
+--~ end
+
+if environment.platform == "unix" then
+ io.write("\n")
+end
diff --git a/scripts/context/lua/mtx-cache.lua b/scripts/context/lua/mtx-cache.lua
new file mode 100644
index 000000000..0b0983d1b
--- /dev/null
+++ b/scripts/context/lua/mtx-cache.lua
@@ -0,0 +1,92 @@
+dofile(input.find_file(instance,"luat-log.lua"))
+
+texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+
+scripts = scripts or { }
+scripts.cache = scripts.cache or { }
+
+function scripts.cache.collect_one(...)
+ local path = cache.setpath(instance,...)
+ local tmas = dir.glob(path .. "/*tma")
+ local tmcs = dir.glob(path .. "/*tmc")
+ return path, tmas, tmcs
+end
+
+function scripts.cache.collect_two(...)
+ local path = cache.setpath(instance,...)
+ local rest = dir.glob(path .. "/**/*")
+ return path, rest
+end
+
+function scripts.cache.process_one(action)
+ action("fonts", "afm")
+ action("fonts", "tfm")
+ action("fonts", "def")
+ action("fonts", "enc")
+ action("fonts", "otf")
+ action("fonts", "data")
+end
+
+function scripts.cache.process_two(action)
+ action("curl")
+end
+
+-- todo: recursive delete of paths
+
+function scripts.cache.remove(list,keep)
+ local keepsuffixes = { }
+ for _, v in ipairs(keep or {}) do
+ keepsuffixes[v] = true
+ end
+ local n = 0
+ for _,filename in ipairs(list) do
+ if filename:find("luatex%-cache") then -- safeguard
+ if not keepsuffixes[file.extname(filename) or ""] then
+ os.remove(filename)
+ n = n + 1
+ end
+ end
+ end
+ return n
+end
+
+function scripts.cache.delete(all,keep)
+ local function action(...)
+ local path, rest = scripts.cache.collect_two(...)
+ local n = scripts.cache.remove(rest,keep)
+ logs.report("cache path",string.format("%4i files out of %4i deleted on %s",n,#rest,path))
+ end
+ scripts.cache.process_one(action)
+ scripts.cache.process_two(action)
+end
+
+function scripts.cache.list(all)
+ scripts.cache.process_one(function(...)
+ local path, tmas, tmcs = scripts.cache.collect_one(...)
+ logs.report("cache path",string.format("tma:%4i tmc:%4i %s",#tmas,#tmcs,path))
+ end)
+ scripts.cache.process_two(function(...)
+ local path, rest = scripts.cache.collect_two("curl")
+ logs.report("cache path",string.format("all:%4i %s",#rest,path))
+ end)
+end
+
+banner = banner .. " | cache tools "
+
+messages.help = [[
+--purge remove not used files
+--erase completely remove cache
+--list show cache
+
+--all all (not yet implemented)
+]]
+
+if environment.argument("purge") then
+ scripts.cache.delete(environment.argument("all"),{"tmc"})
+elseif environment.argument("erase") then
+ scripts.cache.delete(environment.argument("all"))
+elseif environment.argument("list") then
+ scripts.cache.list(environment.argument("all"))
+else
+ input.help(banner,messages.help)
+end
diff --git a/scripts/context/lua/mtx-fonts.lua b/scripts/context/lua/mtx-fonts.lua
new file mode 100644
index 000000000..10211fe22
--- /dev/null
+++ b/scripts/context/lua/mtx-fonts.lua
@@ -0,0 +1,90 @@
+dofile(input.find_file(instance,"luat-log.lua"))
+dofile(input.find_file(instance,"font-syn.lua"))
+
+texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+
+scripts = scripts or { }
+scripts.fonts = scripts.fonts or { }
+
+function scripts.fonts.list(pattern,reload,all)
+ if reload then
+ logs.report("fontnames","reloading font database")
+ end
+ local t = fonts.names.list(pattern,reload)
+ if reload then
+ logs.report("fontnames","done\n\n")
+ end
+ if t then
+ local s, w = table.sortedkeys(t), { 0, 0, 0 }
+ local function action(f)
+ for k,v in pairs(s) do
+ if all or v == t[v][2]:lower() then
+ local type, name, file, sub = unpack(t[v])
+ f(v,name,file,sub)
+ end
+ end
+ end
+ action(function(v,n,f,s)
+ if #v > w[1] then w[1] = #v end
+ if #n > w[2] then w[2] = #n end
+ if #f > w[3] then w[3] = #f end
+ end)
+ action(function(v,n,f,s)
+ if s then s = "(sub)" else s = "" end
+ print(string.format("%s %s %s %s",v:padd(w[1]," "),n:padd(w[2]," "),f:padd(w[3]," "), s))
+ end)
+ end
+end
+
+function scripts.fonts.save(name,sub)
+ local function save(savename,fontblob)
+ if fontblob then
+ savename = savename:lower() .. ".lua"
+ logs.report("fontsave","saving data in " .. savename)
+ table.tofile(savename,fontforge.to_table(fontblob),"return")
+ fontforge.close(fontblob)
+ end
+ end
+ if name and name ~= "" then
+ local filename = input.find_file(texmf.instance,name) -- maybe also search for opentype
+ if filename and filename ~= "" then
+ local suffix = file.extname(filename)
+ if suffix == 'ttf' or suffix == 'otf' or suffix == 'ttc' then
+ local fontinfo = fontforge.info(filename)
+ if fontinfo then
+ if fontinfo[1] then
+ for _, v in ipairs(fontinfo) do
+ save(v.fontname,fontforge.open(filename,v.fullname))
+ end
+ else
+ save(fontinfo.fullname,fontforge.open(filename))
+ end
+ end
+ end
+ end
+ end
+end
+
+banner = banner .. " | font tools "
+
+messages.help = [[
+--list list installed fonts
+--save save open type font in raw table
+
+--pattern=str filter files
+--reload generate new font database
+--all provide alternatives
+]]
+
+if environment.argument("list") then
+ local pattern = environment.argument("pattern") or environment.files[1] or ""
+ local all = environment.argument("all")
+ local reload = environment.argument("reload")
+ scripts.fonts.list(pattern,reload,all)
+elseif environment.argument("save") then
+ local name = environment.files[1] or ""
+ local sub = environment.files[2] or ""
+ scripts.fonts.save(name,sub)
+else
+ input.help(banner,messages.help)
+end
diff --git a/scripts/context/lua/mtxrun.cmd b/scripts/context/lua/mtxrun.cmd
new file mode 100644
index 000000000..f30148ddb
--- /dev/null
+++ b/scripts/context/lua/mtxrun.cmd
@@ -0,0 +1,5 @@
+@echo off
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" %*
+endlocal
diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua
new file mode 100644
index 000000000..d5a0701c9
--- /dev/null
+++ b/scripts/context/lua/mtxrun.lua
@@ -0,0 +1,4625 @@
+#!/usr/bin/env texlua
+
+-- one can make a stub:
+--
+-- #!/bin/sh
+-- env LUATEXDIR=/....../texmf/scripts/context/lua luatex --luaonly mtxrun.lua "$@"
+
+-- filename : mtxrun.lua
+-- comment : companion to context.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- This script is based on texmfstart.rb but does not use kpsewhich to
+-- locate files. Although kpse is a library it never came to opening up
+-- its interface to other programs (esp scripting languages) and so we
+-- do it ourselves. The lua variant evolved out of an experimental ruby
+-- one. Interesting is that using a scripting language instead of c does
+-- not have a speed penalty. Actually the lua variant is more efficient,
+-- especially when multiple calls to kpsewhich are involved. The lua
+-- library also gives way more ocntrol.
+
+-- to be done / considered
+--
+-- support for --exec or make it default
+-- support for jar files (or maybe not, never used, too messy)
+-- support for $RUBYINPUTS cum suis (if still needed)
+-- remember for subruns: _CTX_K_V_#{original}_
+-- remember for subruns: _CTX_K_S_#{original}_
+-- remember for subruns: TEXMFSTART.#{original} [tex.rb texmfstart.rb]
+
+banner = "version 1.0.1 - 2007+ - PRAGMA ADE / CONTEXT"
+texlua = true
+
+-- begin library merge
+
+-- filename : l-string.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-string'] = 1.001
+
+--~ function string.split(str, pat) -- taken from the lua wiki
+--~ local t = {n = 0} -- so this table has a length field, traverse with ipairs then!
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ table.insert(t,cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end<=string.len(str) then
+--~ table.insert(t,(string.sub(str,last_end)))
+--~ end
+--~ return t
+--~ end
+
+--~ function string:split(pat) -- taken from the lua wiki but adapted
+--~ local t = { } -- self and colon usage (faster)
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = self:find(fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ t[#t+1] = cap
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = self:find(fpat, last_end)
+--~ end
+--~ if last_end <= #self then
+--~ t[#t+1] = self:sub(last_end)
+--~ end
+--~ return t
+--~ end
+
+--~ a piece of brilliant code by Rici Lake (posted on lua list) -- only names changed
+--~
+--~ function string:splitter(pat)
+--~ local st, g = 1, self:gmatch("()"..pat.."()")
+--~ local function splitter(self)
+--~ if st then
+--~ local s, f = g()
+--~ local rv = self:sub(st, (s or 0)-1)
+--~ st = f
+--~ return rv
+--~ end
+--~ end
+--~ return splitter, self
+--~ end
+
+function string:splitter(pat)
+ -- by Rici Lake (posted on lua list) -- only names changed
+ -- p 79 ref man: () returns position of match
+ local st, g = 1, self:gmatch("()("..pat..")")
+ local function strgetter(self, segs, seps, sep, cap1, ...)
+ st = sep and seps + #sep
+ return self:sub(segs, (seps or 0) - 1), cap1 or sep, ...
+ end
+ local function strsplitter(self)
+ if st then return strgetter(self, st, g()) end
+ end
+ return strsplitter, self
+end
+
+function string:split(separator)
+ local t = {}
+ for k in self:splitter(separator) do t[#t+1] = k end
+ return t
+end
+
+-- faster than a string:split:
+
+function string:splitchr(chr)
+ if #self > 0 then
+ local t = { }
+ for s in string.gmatch(self..chr,"(.-)"..chr) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+end
+
+--~ function string.piecewise(str, pat, fnc) -- variant of split
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ fnc(cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end <= #str then
+--~ fnc((string.sub(str,last_end)))
+--~ end
+--~ end
+
+function string.piecewise(str, pat, fnc) -- variant of split
+ for k in string.splitter(str,pat) do fnc(k) end
+end
+
+--~ do if lpeg then
+
+--~ -- this alternative is 30% faster esp when we cache them
+--~ -- problem: no expressions
+
+--~ splitters = { }
+
+--~ function string:split(separator)
+--~ if #self > 0 then
+--~ local split = splitters[separator]
+--~ if not split then
+--~ -- based on code by Roberto
+--~ local p = lpeg.P(separator)
+--~ local c = lpeg.C((1-p)^0)
+--~ split = lpeg.Ct(c*(p*c)^0)
+--~ splitters[separator] = split
+--~ end
+--~ return lpeg.match(split,self)
+--~ else
+--~ return { }
+--~ end
+--~ end
+
+--~ string.splitchr = string.split
+
+--~ function string:piecewise(separator,fnc)
+--~ for _,v in pairs(self:split(separator)) do
+--~ fnc(v)
+--~ end
+--~ end
+
+--~ end end
+
+string.chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+function string:esc() -- variant 2
+ return (self:gsub("(.)",string.chr_to_esc))
+end
+
+function string.unquote(str)
+ return (str:gsub("^([\"\'])(.*)%1$","%2"))
+end
+
+function string.quote(str)
+ return '"' .. str:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in self:gmatch(pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return self:sub(1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+--~ function string.strip(str) -- slightly different
+--~ return (string.gsub(string.gsub(str,"^%s*(.-)%s*$","%1"),"%s+"," "))
+--~ end
+
+function string:is_empty()
+ return not self:find("%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = self:gsub(pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+--~ function string:enhance(pattern,action)
+--~ local ok, n = 0, 0
+--~ repeat
+--~ self, ok = self:gsub(pattern, function(...)
+--~ n = n + 1
+--~ return action(...)
+--~ end)
+--~ until ok == 0
+--~ return self, n
+--~ end
+
+--~ function string:to_hex()
+--~ if self then
+--~ return (self:gsub("(.)",function(c)
+--~ return string.format("%02X",c:byte())
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then
+--~ return (self:gsub("(..)",function(c)
+--~ return string.char(tonumber(c,16))
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+string.chr_to_hex = { }
+string.hex_to_chr = { }
+
+for i=0,255 do
+ local c, h = string.char(i), string.format("%02X",i)
+ string.chr_to_hex[c], string.hex_to_chr[h] = h, c
+end
+
+--~ function string:to_hex()
+--~ if self then return (self:gsub("(.)",string.chr_to_hex)) else return "" end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then return (self:gsub("(..)",string.hex_to_chr)) else return "" end
+--~ end
+
+function string:to_hex()
+ return ((self or ""):gsub("(.)",string.chr_to_hex))
+end
+
+function string:from_hex()
+ return ((self or ""):gsub("(..)",string.hex_to_chr))
+end
+
+if not string.characters then
+
+ local function nextchar(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, str:sub(index,index)
+ end
+ function string:characters()
+ return nextchar, self, 0
+ end
+ local function nextbyte(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, string.byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+--~ function string:padd(n,chr)
+--~ return self .. self.rep(chr or " ",n-#self)
+--~ end
+
+function string:padd(n,chr)
+ local m = n-#self
+ if m > 0 then
+ return self .. self.rep(chr or " ",m)
+ else
+ return self
+ end
+end
+
+function is_number(str)
+ return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+end
+
+--~ print(is_number("1"))
+--~ print(is_number("1.1"))
+--~ print(is_number(".1"))
+--~ print(is_number("-0.1"))
+--~ print(is_number("+0.1"))
+--~ print(is_number("-.1"))
+--~ print(is_number("+.1"))
+
+
+-- filename : l-table.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-table'] = 1.001
+
+table.join = table.concat
+
+function table.strip(tab)
+ local lst = { }
+ for k, v in ipairs(tab) do
+ -- s = string.gsub(v, "^%s*(.-)%s*$", "%1")
+ s = v:gsub("^%s*(.-)%s*$", "%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+--~ function table.sortedkeys(tab)
+--~ local srt = { }
+--~ for key,_ in pairs(tab) do
+--~ srt[#srt+1] = key
+--~ end
+--~ table.sort(srt)
+--~ return srt
+--~ end
+
+function table.sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in pairs(tab) do
+ srt[#srt+1] = key
+ if kind == 3 then
+ -- no further check
+ elseif type(key) == "string" then
+ if kind == 2 then kind = 3 else kind = 1 end
+ elseif type(key) == "number" then
+ if kind == 1 then kind = 3 else kind = 2 end
+ else
+ kind = 3
+ end
+ end
+ if kind == 0 or kind == 3 then
+ table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+ else
+ table.sort(srt)
+ end
+ return srt
+end
+
+function table.append(t, list)
+ for _,v in pairs(list) do
+ table.insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in pairs(list) do
+ table.insert(t,k,v)
+ end
+end
+
+if not table.fastcopy then
+
+ function table.fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in pairs(old) do
+ if type(v) == "table" then
+ new[k] = table.copy(v)
+ else
+ new[k] = v
+ end
+ end
+ return new
+ else
+ return { }
+ end
+ end
+
+end
+
+if not table.copy then
+
+ function table.copy(t, _lookup_table) -- taken from lua wiki
+ _lookup_table = _lookup_table or { }
+ local tcopy = {}
+ if not _lookup_table[t] then
+ _lookup_table[t] = tcopy
+ end
+ for i,v in pairs(t) do
+ if type(i) == "table" then
+ if _lookup_table[i] then
+ i = _lookup_table[i]
+ else
+ i = table.copy(i, _lookup_table)
+ end
+ end
+ if type(v) ~= "table" then
+ tcopy[i] = v
+ else
+ if _lookup_table[v] then
+ tcopy[i] = _lookup_table[v]
+ else
+ tcopy[i] = table.copy(v, _lookup_table)
+ end
+ end
+ end
+ return tcopy
+ end
+
+end
+
+-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
+
+function table.sub(t,i,j)
+ return { unpack(t,i,j) }
+end
+
+function table.replace(a,b)
+ for k,v in pairs(b) do
+ a[k] = v
+ end
+end
+
+-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
+
+function table.is_empty(t)
+ return not t or not next(t)
+end
+
+function table.one_entry(t)
+ local n = next(t)
+ return n and not next(t,n)
+end
+
+function table.starts_at(t)
+ return ipairs(t,1)(t,0)
+end
+
+do
+
+ -- 34.055.092 32.403.326 arabtype.tma
+ -- 1.620.614 1.513.863 lmroman10-italic.tma
+ -- 1.325.585 1.233.044 lmroman10-regular.tma
+ -- 1.248.157 1.158.903 lmsans10-regular.tma
+ -- 194.646 153.120 lmtypewriter10-regular.tma
+ -- 1.771.678 1.658.461 palatinosanscom-bold.tma
+ -- 1.695.251 1.584.491 palatinosanscom-regular.tma
+ -- 13.736.534 13.409.446 zapfinoextraltpro.tma
+
+ -- 13.679.038 11.774.106 arabtype.tmc
+ -- 886.248 754.944 lmroman10-italic.tmc
+ -- 729.828 466.864 lmroman10-regular.tmc
+ -- 688.482 441.962 lmsans10-regular.tmc
+ -- 128.685 95.853 lmtypewriter10-regular.tmc
+ -- 715.929 582.985 palatinosanscom-bold.tmc
+ -- 669.942 540.126 palatinosanscom-regular.tmc
+ -- 1.560.588 1.317.000 zapfinoextraltpro.tmc
+
+ table.serialize_functions = true
+ table.serialize_compact = true
+ table.serialize_inline = true
+
+ local function key(k)
+ if type(k) == "number" then -- or k:find("^%d+$") then
+ return "["..k.."]"
+ elseif noquotes and k:find("^%a[%a%d%_]*$") then
+ return k
+ else
+ return '["'..k..'"]'
+ end
+ end
+
+ local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in pairs(t) do
+ n = n + 1
+ end
+ if n == #t then
+ local tt = { }
+ for _,v in ipairs(t) do
+ local tv = type(v)
+ if tv == "number" or tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = ("%q"):format(v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+ end
+
+ local function serialize(root,name,handle,depth,level,reduce,noquotes,indexed)
+ handle = handle or print
+ reduce = reduce or false
+ if depth then
+ depth = depth .. " "
+ if indexed then
+ handle(("%s{"):format(depth))
+ else
+ handle(("%s%s={"):format(depth,key(name)))
+ end
+ else
+ depth = ""
+ if type(name) == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif type(name) == "number" then
+ handle("[" .. name .. "]={")
+ elseif type(name) == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ end
+ if root and next(root) then
+ local compact = table.serialize_compact
+ local inline = compact and table.serialize_inline
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ for k,v in ipairs(root) do
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ for _,k in pairs(table.sortedkeys(root)) do
+ local v = root[k]
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ handle(("%s %s,"):format(depth,v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s,"):format(depth,v))
+ else
+ handle(("%s %q,"):format(depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s {},"):format(depth))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s { %s },"):format(depth,table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s,"):format(depth,tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s loadstring(%q),'):format(depth,string.dump(v)))
+ else
+ handle(('%s "function",'):format(depth))
+ end
+ else
+ handle(("%s %q,"):format(depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(("%s __p__=nil,"):format(depth))
+ end
+ elseif t == "number" then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s %s={},"):format(depth,key(k)))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s %s={ %s },"):format(depth,key(k),table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s=%s,"):format(depth,key(k),tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(v)))
+ else
+ handle(('%s %s="function",'):format(depth,key(k)))
+ end
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),tostring(v)))
+ -- handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(function() return v end)))
+ end
+ end
+ if level > 0 then
+ handle(("%s},"):format(depth))
+ else
+ handle(("%s}"):format(depth))
+ end
+ else
+ handle(("%s}"):format(depth))
+ end
+ end
+
+ --~ name:
+ --~
+ --~ true : return { }
+ --~ false : { }
+ --~ nil : t = { }
+ --~ string : string = { }
+ --~ 'return' : return { }
+ --~ number : [number] = { }
+
+ function table.serialize(root,name,reduce,noquotes)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ return table.concat(t,"\n")
+ end
+
+ function table.tohandle(handle,root,name,reduce,noquotes)
+ serialize(root, name, handle, nil, 0, reduce, noquotes)
+ end
+
+ -- sometimes tables are real use (zapfino extra pro is some 85M) in which
+ -- case a stepwise serialization is nice; actually, we could consider:
+ --
+ -- for line in table.serializer(root,name,reduce,noquotes) do
+ -- ...(line)
+ -- end
+ --
+ -- so this is on the todo list
+
+ table.tofile_maxtab = 2*1024
+
+ function table.tofile(filename,root,name,reduce,noquotes)
+ local f = io.open(filename,'w')
+ if f then
+ local concat = table.concat
+ local maxtab = table.tofile_maxtab
+ if maxtab > 1 then
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ if #t > maxtab then
+ f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
+ t = { }
+ end
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ end
+ f:close()
+ end
+ end
+
+end
+
+--~ t = {
+--~ b = "123",
+--~ a = "x",
+--~ c = 1.23,
+--~ d = "1.23",
+--~ e = true,
+--~ f = {
+--~ d = "1.23",
+--~ a = "x",
+--~ b = "123",
+--~ c = 1.23,
+--~ e = true,
+--~ f = {
+--~ e = true,
+--~ f = {
+--~ e = true
+--~ },
+--~ },
+--~ },
+--~ g = function() end
+--~ }
+
+--~ print(table.serialize(t), "\n")
+--~ print(table.serialize(t,"name"), "\n")
+--~ print(table.serialize(t,false), "\n")
+--~ print(table.serialize(t,true), "\n")
+--~ print(table.serialize(t,"name",true), "\n")
+--~ print(table.serialize(t,"name",true,true), "\n")
+
+do
+
+ local function flatten(t,f,complete)
+ for _,v in ipairs(t) do
+ if type(v) == "table" then
+ if complete or type(v[1]) == "table" then
+ flatten(v,f,complete)
+ else
+ f[#f+1] = v
+ end
+ else
+ f[#f+1] = v
+ end
+ end
+ end
+
+ function table.flatten(t)
+ local f = { }
+ flatten(t,f,true)
+ return f
+ end
+
+ function table.unnest(t) -- bad name
+ local f = { }
+ flatten(t,f,false)
+ return f
+ end
+
+ table.flatten_one_level = table.unnest
+
+end
+
+function table.insert_before_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i,str)
+ return
+ end
+ end
+ table.insert(t,1,str)
+end
+
+function table.insert_after_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i+1,str)
+ return
+ end
+ end
+ t[#t+1] = str
+end
+
+function table.are_equal(a,b,n,m)
+ if #a == #b then
+ n = n or 1
+ m = m or #a
+ for i=n,m do
+ local ai, bi = a[i], b[i]
+ if (ai==bi) or (type(ai)=="table" and type(bi)=="table" and table.are_equal(ai,bi)) then
+ -- continue
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+--~ function table.are_equal(a,b)
+--~ return table.serialize(a) == table.serialize(b)
+--~ end
+
+
+
+-- filename : l-io.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-io'] = 1.001
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename)
+ local f = io.open(filename)
+ if f then
+ local data = f:read('*all')
+ f:close()
+ return data
+ else
+ return nil
+ end
+end
+
+function io.savedata(filename,data,joiner)
+ local f = io.open(filename, "wb")
+ if f then
+ if type(data) == "table" then
+ f:write(table.join(data,joiner or ""))
+ elseif type(data) == "function" then
+ data(f)
+ else
+ f:write(data)
+ end
+ f:close()
+ end
+end
+
+function io.exists(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return false
+ else
+ assert(f:close())
+ return true
+ end
+end
+
+function io.size(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return 0
+ else
+ local s = f:seek("end")
+ assert(f:close())
+ return s
+ end
+end
+
+function io.noflines(f)
+ local n = 0
+ for _ in f:lines() do
+ n = n + 1
+ end
+ f:seek('set',0)
+ return n
+end
+
+--~ t, f, n = os.clock(), io.open("testbed/sample-utf16-bigendian-big.txt",'rb'), 0
+--~ for a in io.characters(f) do n = n + 1 end
+--~ print(string.format("characters: %s, time: %s", n, os.clock()-t))
+
+do
+
+ local nextchar = {
+ [ 4] = function(f)
+ return f:read(1), f:read(1), f:read(1), f:read(1)
+ end,
+ [ 2] = function(f)
+ return f:read(1), f:read(1)
+ end,
+ [ 1] = function(f)
+ return f:read(1)
+ end,
+ [-2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ return b, a
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local c = f:read(1)
+ return d, c, b, a
+ end
+ }
+
+ function io.characters(f,n)
+ local sb = string.byte
+ if f then
+ return nextchar[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
+
+do
+
+ local nextbyte = {
+ [4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(a), sb(b), sb(c), sb(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(a), sb(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return sb(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(b), sb(a)
+ else
+ return nil, nil
+ end
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(d), sb(c), sb(b), sb(a)
+ else
+ return nil, nil, nil, nil
+ end
+ end
+ }
+
+ function io.bytes(f,n)
+ local sb = string.byte
+ if f then
+ return nextbyte[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
+
+
+-- filename : l-md5.lua
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-md5'] = 1.001
+
+if md5 then do
+
+ local function convert(str,fmt)
+ return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
+ end
+
+ if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end
+ if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end
+ if not md5.dec then function md5.dec(str) return convert(stt,"%03i") end end
+
+end end
+
+
+-- filename : l-number.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-number'] = 1.001
+
+if not number then number = { } end
+
+
+
+-- filename : l-os.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-os'] = 1.001
+
+function os.resultof(command)
+ return io.popen(command,"r"):read("*all")
+end
+
+--~ if not os.exec then -- still not ok
+ os.exec = os.execute
+--~ end
+
+function os.launch(str)
+ if os.platform == "windows" then
+ os.execute("start " .. str)
+ else
+ os.execute(str .. " &")
+ end
+end
+
+if not os.setenv then
+ function os.setenv() return false end
+end
+
+
+-- filename : l-file.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-file'] = 1.001
+
+if not file then file = { } end
+
+function file.removesuffix(filename)
+ return filename:gsub("%.%a+$", "")
+end
+
+function file.addsuffix(filename, suffix)
+ if not filename:find("%.%a-$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return filename:gsub("%.%a+$", "." .. suffix)
+end
+
+function file.dirname(name)
+ return name:match("^(.+)[/\\].-$") or ""
+end
+
+function file.basename(name)
+ return name:match("^.+[/\\](.-)$") or name
+end
+
+function file.extname(name)
+ return name:match("^.+%.(.-)$") or ""
+end
+
+function file.join(...) -- args
+ return (string.gsub(table.concat({...},"/"),"\\","/"))
+end
+
+function file.is_writable(name)
+ local f = io.open(name, 'w')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.is_readable(name)
+ local f = io.open(name,'r')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.split_path(str)
+ if str:find(';') then
+ return str:splitchr(";")
+ else
+ return str:splitchr(io.pathseparator)
+ end
+end
+
+function file.join_path(tab)
+ return table.concat(tab,io.pathseparator)
+end
+
+--~ print('test' .. " == " .. file.collapse_path("test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/test"))
+--~ print("test/test/test" .. " == " .. file.collapse_path("test/test/test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/../test/test"))
+--~ print("test" .. " == " .. file.collapse_path("test/../test"))
+--~ print("../test" .. " == " .. file.collapse_path("../test"))
+--~ print("../test/" .. " == " .. file.collapse_path("../test/"))
+--~ print("a/a" .. " == " .. file.collapse_path("a/b/c/../../a"))
+
+function file.collapse_path(str)
+ local ok = false
+ while not ok do
+ ok = true
+ str, n = str:gsub("[^%./]+/%.%./", function(s)
+ ok = false
+ return ""
+ end)
+ end
+ return (str:gsub("/%./","/"))
+end
+
+function file.robustname(str)
+ return (str:gsub("[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+
+-- filename : l-dir.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-dir'] = 1.001
+
+dir = { }
+
+-- optimizing for no string.find (*) does not save time
+
+if lfs then
+
+ function dir.glob_pattern(path,patt,recurse,action)
+ for name in lfs.dir(path) do
+ local full = path .. '/' .. name
+ local mode = lfs.attributes(full,'mode')
+ if mode == 'file' then
+ if name:find(patt) then
+ action(full)
+ end
+ elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then
+ dir.glob_pattern(full,patt,recurse,action)
+ end
+ end
+ end
+
+ function dir.glob(pattern, action)
+ local t = { }
+ local action = action or function(name) table.insert(t,name) end
+ local path, patt = pattern:match("^(.*)/*%*%*/*(.-)$")
+ local recurse = path and patt
+ if not recurse then
+ path, patt = pattern:match("^(.*)/(.-)$")
+ if not (path and patt) then
+ path, patt = '.', pattern
+ end
+ end
+ patt = patt:gsub("([%.%-%+])", "%%%1")
+ patt = patt:gsub("%*", ".*")
+ patt = patt:gsub("%?", ".")
+ patt = "^" .. patt .. "$"
+ -- print('path: ' .. path .. ' | pattern: ' .. patt .. ' | recurse: ' .. tostring(recurse))
+ dir.glob_pattern(path,patt,recurse,action)
+ return t
+ end
+
+ -- t = dir.glob("c:/data/develop/context/sources/**/????-*.tex")
+ -- t = dir.glob("c:/data/develop/tex/texmf/**/*.tex")
+ -- t = dir.glob("c:/data/develop/context/texmf/**/*.tex")
+ -- t = dir.glob("f:/minimal/tex/**/*")
+ -- print(dir.ls("f:/minimal/tex/**/*"))
+ -- print(dir.ls("*.tex"))
+
+ function dir.ls(pattern)
+ return table.concat(dir.glob(pattern),"\n")
+ end
+
+ --~ mkdirs("temp")
+ --~ mkdirs("a/b/c")
+ --~ mkdirs(".","/a/b/c")
+ --~ mkdirs("a","b","c")
+
+ function dir.mkdirs(...) -- root,... or ... ; root is not split
+ local pth, err = "", false
+ for k,v in pairs({...}) do
+ if k == 1 then
+ if not lfs.isdir(v) then
+ -- print("no root path " .. v)
+ err = true
+ else
+ pth = v
+ end
+ elseif lfs.isdir(pth .. "/" .. v) then
+ pth = pth .. "/" .. v
+ else
+ for _,s in pairs(v:split("/")) do
+ pth = pth .. "/" .. s
+ if not lfs.isdir(pth) then
+ ok = lfs.mkdir(pth)
+ if not lfs.isdir(pth) then
+ err = true
+ end
+ end
+ if err then break end
+ end
+ end
+ if err then break end
+ end
+ return pth, not err
+ end
+
+end
+
+
+-- filename : l-boolean.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-boolean'] = 1.001
+if not boolean then boolean = { } end
+
+function boolean.tonumber(b)
+ if b then return 1 else return 0 end
+end
+
+function toboolean(str)
+ if type(str) == "string" then
+ return str == "true" or str == "yes" or str == "on" or str == "1"
+ elseif type(str) == "number" then
+ return tonumber(str) ~= 0
+ else
+ return str
+ end
+end
+
+function string.is_boolean(str)
+ if type(str) == "string" then
+ if str == "true" or str == "yes" or str == "on" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+-- filename : l-utils.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-utils'] = 1.001
+
+if not utils then utils = { } end
+if not utils.merger then utils.merger = { } end
+if not utils.lua then utils.lua = { } end
+
+utils.merger.m_begin = "begin library merge"
+utils.merger.m_end = "end library merge"
+utils.merger.pattern =
+ "%c+" ..
+ "%-%-%s+" .. utils.merger.m_begin ..
+ "%c+(.-)%c+" ..
+ "%-%-%s+" .. utils.merger.m_end ..
+ "%c+"
+
+function utils.merger._self_fake_()
+ return
+ "-- " .. "created merged file" .. "\n\n" ..
+ "-- " .. utils.merger.m_begin .. "\n\n" ..
+ "-- " .. utils.merger.m_end .. "\n\n"
+end
+
+function utils.report(...)
+ print(...)
+end
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ data = f:read("*all")
+ f:close()
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ f:write(data)
+ f:close()
+ end
+ end
+end
+
+function utils.merger._self_swap_(data,code)
+ if data ~= "" then
+ return (data:gsub(utils.merger.pattern, function(s)
+ return "\n\n" .. "-- "..utils.merger.m_begin .. "\n" .. code .. "\n" .. "-- "..utils.merger.m_end .. "\n\n"
+ end, 1))
+ else
+ return ""
+ end
+end
+
+function utils.merger._self_libs_(libs,list)
+ local result, f = "", nil
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ local name = string.gsub(pth .. "/" .. lib,"\\","/")
+ f = io.open(name)
+ if f then
+ -- utils.report("merging library",name)
+ result = result .. "\n" .. f:read("*all") .. "\n"
+ f:close()
+ list = { pth } -- speed up the search
+ break
+ else
+ -- utils.report("no library",name)
+ end
+ end
+ end
+ return result or ""
+end
+
+function utils.merger.selfcreate(libs,list,target)
+ if target then
+ utils.merger._self_save_(
+ target,
+ utils.merger._self_swap_(
+ utils.merger._self_fake_(),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+ end
+end
+
+function utils.merger.selfmerge(name,libs,list,target)
+ utils.merger._self_save_(
+ target or name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+end
+
+function utils.merger.selfclean(name)
+ utils.merger._self_save_(
+ name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ ""
+ )
+ )
+end
+
+function utils.lua.compile(luafile, lucfile)
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ return (os.execute("luac -s -o " .. string.quote(lucfile) .. " " .. string.quote(luafile)) == 0)
+end
+
+
+
+-- filename : luat-lib.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-lib'] = 1.001
+
+-- mostcode moved to the l-*.lua and other luat-*.lua files
+
+-- os / io
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+-- os.platform
+
+-- mswin|bccwin|mingw|cygwin windows
+-- darwin|rhapsody|nextstep macosx
+-- netbsd|unix unix
+-- linux linux
+
+if not io.fileseparator then
+ if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator, os.platform = "\\", ";", "windows"
+ else
+ io.fileseparator, io.pathseparator, os.platform = "/" , ":", "unix"
+ end
+end
+
+if not os.platform then
+ if io.pathseparator == ";" then
+ os.platform = "windows"
+ else
+ os.platform = "unix"
+ end
+end
+
+-- arg normalization
+--
+-- for k,v in pairs(arg) do print(k,v) end
+
+if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
+ arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
+end
+
+-- environment
+
+if not environment then environment = { } end
+
+environment.arguments = { }
+environment.files = { }
+environment.sorted_argument_keys = nil
+
+environment.platform = os.platform
+
+function environment.initialize_arguments(arg)
+ environment.arguments = { }
+ environment.files = { }
+ environment.sorted_argument_keys = nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ environment.arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ environment.arguments[flag] = true
+ else
+ environment.files[#environment.files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.showarguments()
+ for k,v in pairs(environment.arguments) do
+ print(k .. " : " .. tostring(v))
+ end
+ if #environment.files > 0 then
+ print("files : " .. table.concat(environment.files, " "))
+ end
+end
+
+function environment.argument(name)
+ if environment.arguments[name] then
+ return environment.arguments[name]
+ else
+ if not environment.sorted_argument_keys then
+ environment.sorted_argument_keys = { }
+ for _,v in pairs(table.sortedkeys(environment.arguments)) do
+ table.insert(environment.sorted_argument_keys, "^" .. v)
+ end
+ end
+ for _,v in pairs(environment.sorted_argument_keys) do
+ if name:find(v) then
+ return environment.arguments[v:sub(2,#v)]
+ end
+ end
+ end
+ return nil
+end
+
+function environment.split_arguments(separator) -- rather special, cut-off before separator
+ local done, before, after = false, { }, { }
+ for _,v in ipairs(environment.original_arguments) do
+ if not done and v == separator then
+ done = true
+ elseif done then
+ after[#after+1] = v
+ else
+ before[#before+1] = v
+ end
+ end
+ return before, after
+end
+
+function environment.reconstruct_commandline(arg)
+ if not arg then arg = environment.original_arguments end
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ local kk, vv = a:match("^(%-+.-)=(.+)$")
+ if kk and vv then
+ if vv:find(" ") then
+ result[#result+1] = kk .. "=" .. string.quote(vv)
+ else
+ result[#result+1] = a
+ end
+ elseif a:find(" ") then
+ result[#result+1] = string.quote(a)
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+end
+
+if arg then
+ environment.initialize_arguments(arg)
+ environment.original_arguments = arg
+ arg = { } -- prevent duplicate handling
+end
+
+
+-- filename : luat-inp.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- This lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split this
+-- module in components when we're done with prototyping.
+
+-- This is the first code I wrote for LuaTeX, so it needs some cleanup.
+
+-- To be considered: hash key lowercase, first entry in table filename
+-- (any case), rest paths (so no need for optimization). Or maybe a
+-- separate table that matches lowercase names to mixed case when
+-- present. In that case the lower() cases can go away. I will do that
+-- only when we run into problems with names.
+
+if not versions then versions = { } end versions['luat-inp'] = 1.001
+if not environment then environment = { } end
+if not file then file = { } end
+
+if environment.aleph_mode == nil then environment.aleph_mode = true end -- temp hack
+
+if not input then input = { } end
+if not input.suffixes then input.suffixes = { } end
+if not input.formats then input.formats = { } end
+if not input.aux then input.aux = { } end
+
+if not input.suffixmap then input.suffixmap = { } end
+
+if not input.locators then input.locators = { } end -- locate databases
+if not input.hashers then input.hashers = { } end -- load databases
+if not input.generators then input.generators = { } end -- generate databases
+if not input.filters then input.filters = { } end -- conversion filters
+
+input.locators.notfound = { nil }
+input.hashers.notfound = { nil }
+input.generators.notfound = { nil }
+
+input.cacheversion = '1.0.1'
+input.banner = nil
+input.verbose = false
+input.debug = false
+input.cnfname = 'texmf.cnf'
+input.lsrname = 'ls-R'
+input.luasuffix = '.tma'
+input.lucsuffix = '.tmc'
+
+-- we use a cleaned up list / format=any is a wildcard, as is *name
+
+input.formats['afm'] = 'AFMFONTS' input.suffixes['afm'] = { 'afm' }
+input.formats['enc'] = 'ENCFONTS' input.suffixes['enc'] = { 'enc' }
+input.formats['fmt'] = 'TEXFORMATS' input.suffixes['fmt'] = { 'fmt' }
+input.formats['map'] = 'TEXFONTMAPS' input.suffixes['map'] = { 'map' }
+input.formats['mp'] = 'MPINPUTS' input.suffixes['mp'] = { 'mp' }
+input.formats['ocp'] = 'OCPINPUTS' input.suffixes['ocp'] = { 'ocp' }
+input.formats['ofm'] = 'OFMFONTS' input.suffixes['ofm'] = { 'ofm', 'tfm' }
+input.formats['otf'] = 'OPENTYPEFONTS' input.suffixes['otf'] = { 'otf' } -- 'ttf'
+input.formats['opl'] = 'OPLFONTS' input.suffixes['opl'] = { 'opl' }
+input.formats['otp'] = 'OTPINPUTS' input.suffixes['otp'] = { 'otp' }
+input.formats['ovf'] = 'OVFFONTS' input.suffixes['ovf'] = { 'ovf', 'vf' }
+input.formats['ovp'] = 'OVPFONTS' input.suffixes['ovp'] = { 'ovp' }
+input.formats['tex'] = 'TEXINPUTS' input.suffixes['tex'] = { 'tex' }
+input.formats['tfm'] = 'TFMFONTS' input.suffixes['tfm'] = { 'tfm' }
+input.formats['ttf'] = 'TTFONTS' input.suffixes['ttf'] = { 'ttf', 'ttc' }
+input.formats['pfb'] = 'T1FONTS' input.suffixes['pfb'] = { 'pfb', 'pfa' }
+input.formats['vf'] = 'VFFONTS' input.suffixes['vf'] = { 'vf' }
+
+input.formats['fea'] = 'FONTFEATURES' input.suffixes['fea'] = { 'fea' }
+
+input.formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+input.suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+input.formats ['lua'] = 'LUAINPUTS' -- new
+input.suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- here we catch a few new thingies
+
+function input.checkconfigdata(instance)
+ if input.env(instance,"LUAINPUTS") == "" then
+ instance.environment["LUAINPUTS"] = ".;$TEXINPUTS;$TEXMFSCRIPTS"
+ end
+ if input.env(instance,"FONTFEATURES") == "" then
+ instance.environment["FONTFEATURES"] = ".;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS"
+ end
+end
+
+-- backward compatible ones
+
+input.alternatives = { }
+
+input.alternatives['map files'] = 'map'
+input.alternatives['enc files'] = 'enc'
+input.alternatives['opentype fonts'] = 'otf'
+input.alternatives['truetype fonts'] = 'ttf'
+input.alternatives['truetype collections'] = 'ttc'
+input.alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+input.formats ['misc fonts'] = ''
+input.suffixes['misc fonts'] = { }
+
+input.formats ['sfd'] = 'SFDFONTS'
+input.suffixes ['sfd'] = { 'sfd' }
+input.alternatives['subfont definition files'] = 'sfd'
+
+function input.reset()
+
+ local instance = { }
+
+ instance.rootpath = ''
+ instance.treepath = ''
+ instance.progname = environment.progname or 'context'
+ instance.engine = environment.engine or 'luatex'
+ instance.format = ''
+ instance.environment = { }
+ instance.variables = { }
+ instance.expansions = { }
+ instance.files = { }
+ instance.configuration = { }
+ instance.found = { }
+ instance.foundintrees = { }
+ instance.kpsevars = { }
+ instance.hashes = { }
+ instance.cnffiles = { }
+ instance.lists = { }
+ instance.remember = true
+ instance.diskcache = true
+ instance.renewcache = false
+ instance.scandisk = true
+ instance.cachepath = nil
+ instance.loaderror = false
+ instance.smallcache = false
+ instance.savelists = true
+ instance.cleanuppaths = true
+ instance.allresults = false
+ instance.pattern = nil -- lists
+ instance.kpseonly = false -- lists
+ instance.cachefile = 'tmftools'
+ instance.loadtime = 0
+ instance.starttime = 0
+ instance.stoptime = 0
+ instance.validfile = function(path,name) return true end
+ instance.data = { } -- only for loading
+ instance.sortdata = false
+ instance.force_suffixes = true
+ instance.dummy_path_expr = "^!*unset/*$"
+ instance.fakepaths = { }
+ instance.lsrmode = false
+
+ if os.env then
+ -- store once, freeze and faster
+ for k,v in pairs(os.env) do
+ instance.environment[k] = input.bare_variable(v)
+ end
+ else
+ -- we will access os.env frequently
+ for k,v in pairs({'HOME','TEXMF','TEXMFCNF','SELFAUTOPARENT'}) do
+ local e = os.getenv(v)
+ if e then
+ -- input.report("setting",v,"to",input.bare_variable(e))
+ instance.environment[v] = input.bare_variable(e)
+ end
+ end
+ end
+
+ -- cross referencing
+
+ for k, v in pairs(input.suffixes) do
+ for _, vv in pairs(v) do
+ if vv then
+ input.suffixmap[vv] = k
+ end
+ end
+ end
+
+ return instance
+
+end
+
+function input.bare_variable(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"%s+$",""),'^"(.+)"$',"%1"),"^'(.+)'$","%1")
+ return str:gsub("\s*([\"\']?)(.+)%1\s*", "%2")
+end
+
+if texio then
+ input.log = texio.write_nl
+else
+ input.log = print
+end
+
+function input.simple_logger(kind, name)
+ if name and name ~= "" then
+ if input.banner then
+ input.log(input.banner..kind..": "..name)
+ else
+ input.log("<<"..kind..": "..name..">>")
+ end
+ else
+ if input.banner then
+ input.log(input.banner..kind..": no name")
+ else
+ input.log("<<"..kind..": no name>>")
+ end
+ end
+end
+
+function input.dummy_logger()
+end
+
+function input.settrace(n)
+ input.trace = tonumber(n or 0)
+ if input.trace > 0 then
+ input.logger = input.simple_logger
+ input.verbose = true
+ else
+ input.logger = function() end
+ end
+end
+
+function input.report(...) -- inefficient
+ if input.verbose then
+ if input.banner then
+ input.log(input.banner .. table.concat({...},' '))
+ elseif input.logmode() == 'xml' then
+ input.log(""..table.concat({...},' ').."")
+ else
+ input.log("<<"..table.concat({...},' ')..">>")
+ end
+ end
+end
+
+function input.reportlines(str)
+ if type(str) == "string" then
+ str = str:split("\n")
+ end
+ for _,v in pairs(str) do input.report(v) end
+end
+
+input.settrace(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0)
+
+-- These functions can be used to test the performance, especially
+-- loading the database files.
+
+function input.start_timing(instance)
+ if instance then
+ instance.starttime = os.clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+end
+
+function input.stop_timing(instance, report)
+ if instance and instance.starttime then
+ instance.stoptime = os.clock()
+ local loadtime = instance.stoptime - instance.starttime
+ instance.loadtime = instance.loadtime + loadtime
+ if report then
+ input.report('load time', string.format("%0.3f",loadtime))
+ end
+ return loadtime
+ else
+ return 0
+ end
+end
+
+input.stoptiming = input.stop_timing
+input.starttiming = input.start_timing
+
+function input.elapsedtime(instance)
+ return string.format("%0.3f",instance.loadtime or 0)
+end
+
+function input.report_loadtime(instance)
+ if instance then
+ input.report('total load time', input.elapsedtime(instance))
+ end
+end
+
+function input.loadtime(instance)
+ tex.print(input.elapsedtime(instance))
+end
+
+function input.env(instance,key)
+ return instance.environment[key] or input.osenv(instance,key)
+end
+
+function input.osenv(instance,key)
+ if instance.environment[key] == nil then
+ local e = os.getenv(key)
+ if e == nil then
+ instance.environment[key] = "" -- false
+ else
+ instance.environment[key] = input.bare_variable(e)
+ end
+ end
+ return instance.environment[key] or ""
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in TEXMF/web2c
+--
+-- for the moment we don't expect a configuration file in a zip
+
+function input.identify_cnf(instance)
+ if #instance.cnffiles == 0 then
+ if instance.treepath ~= "" then
+ if instance.rootpath ~= "" then
+ local t = instance.treepath:splitchr(',')
+ for k,v in ipairs(t) do
+ t[k] = file.join(instance.rootpath,v)
+ end
+ instance.treepath = table.concat(t,',')
+ end
+ local t = instance.treepath:splitchr(',')
+ instance.environment['TEXMF'] = input.bare_variable(instance.treepath)
+ instance.environment['TEXMFCNF'] = file.join(t[1] or '.','texmf/web2c')
+ end
+ if instance.rootpath ~= "" then
+ instance.environment['TEXMFCNF'] = file.join(instance.rootpath,'texmf/web2c')
+ instance.environment['SELFAUTOPARENT'] = instance.rootpath
+ end
+ if input.env(instance,'TEXMFCNF') ~= "" then
+ local t = input.split_path(input.env(instance,'TEXMFCNF'))
+ t = input.aux.expanded_path(instance,t)
+ input.aux.expand_vars(instance,t)
+ for _,v in ipairs(t) do
+ table.insert(instance.cnffiles,file.join(v,input.cnfname))
+ end
+ elseif input.env(instance,'SELFAUTOPARENT') == '.' then
+ table.insert(instance.cnffiles,file.join('.',input.cnfname))
+ else
+ for _,v in ipairs({'texmf-local','texmf'}) do
+ table.insert(instance.cnffiles,file.join(input.env(instance,'SELFAUTOPARENT'),v,'web2c',input.cnfname))
+ end
+ end
+ end
+end
+
+function input.load_cnf(instance)
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = fname:gsub("\\",'/')
+ end
+ for i = 1, 3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ if instance.lsrmode then
+ input.loadconfigdata(instance,instance.cnffiles)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadconfig(instance,instance.cnffiles)
+ if instance.loaderror then
+ input.loadconfigdata(instance,instance.cnffiles)
+ input.saveconfig(instance)
+ end
+ else
+ input.loadconfigdata(instance,instance.cnffiles)
+ if instance.renewcache then
+ input.saveconfig(instance)
+ end
+ end
+ input.aux.collapse_cnf_data(instance)
+ end
+ input.checkconfigdata(instance)
+end
+
+function input.loadconfigdata(instance)
+ for _, fname in pairs(instance.cnffiles) do
+ input.aux.load_cnf(instance,fname)
+ end
+end
+
+if os.env then
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ if instance.environment[k] then
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.kpsevars[k] = true
+ instance.variables[k] = input.bare_variable(v)
+ end
+ end
+ end
+ end
+ end
+else
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ local e = os.getenv(k)
+ if e then
+ instance.environment[k] = input.bare_variable(e)
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.variables[k] = input.bare_variable(v)
+ instance.kpsevars[k] = true
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.aux.load_cnf(instance,fname)
+ fname = input.clean_path(fname)
+ local lname = fname:gsub("%.%a+$",input.luasuffix)
+ local f = io.open(lname)
+ if f then
+ f:close()
+ input.aux.load_data(instance,file.dirname(lname),'configuration',file.basename(lname))
+ else
+ f = io.open(fname)
+ if f then
+ input.report("loading", fname)
+ local line, data, n, k, v
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ instance.configuration[dname] = { }
+ end
+ local data = instance.configuration[dname]
+ while true do
+ line = f:read()
+ if line then
+ while true do -- join lines
+ line, n = line:gsub("\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not line:find("^[%%#]") then
+ k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ else
+ input.report("skipping", fname)
+ end
+ end
+end
+
+-- database loading
+
+function input.load_hash(instance)
+ input.locatelists(instance)
+ if instance.lsrmode then
+ input.loadlists(instance)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadfiles(instance)
+ if instance.loaderror then
+ input.loadlists(instance)
+ input.savefiles(instance)
+ end
+ else
+ input.loadlists(instance)
+ if instance.renewcache then
+ input.savefiles(instance)
+ end
+ end
+end
+
+function input.aux.append_hash(instance,type,tag,name)
+ input.logger("= hash append",tag)
+ table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.prepend_hash(instance,type,tag,name)
+ input.logger("= hash prepend",tag)
+ table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.extend_texmf_var(instance,specification) -- crap
+ if instance.environment['TEXMF'] then
+ input.report("extending environment variable TEXMF with", specification)
+ instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ elseif instance.variables['TEXMF'] then
+ input.report("extending configuration variable TEXMF with", specification)
+ instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ else
+ input.report("setting configuration variable TEXMF to", specification)
+ instance.variables['TEXMF'] = "{" .. specification .. "}"
+ end
+ if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
+ input.report("adding {} to complex TEXMF variable, best do that yourself")
+ instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
+ end
+ input.expand_variables(instance)
+end
+
+-- locators
+
+function input.locatelists(instance)
+ for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
+ input.report("locating list of",path)
+ input.locatedatabase(instance,input.normalize_name(path))
+ end
+end
+
+function input.locatedatabase(instance,specification)
+ return input.methodhandler('locators', instance, specification)
+end
+
+function input.locators.tex(instance,specification)
+ if specification and specification ~= '' then
+ local files = {
+ file.join(specification,'files'..input.lucsuffix),
+ file.join(specification,'files'..input.luasuffix),
+ file.join(specification,input.lsrname)
+ }
+ for _, filename in pairs(files) do
+ local f = io.open(filename)
+ if f then
+ input.logger('! tex locator', specification..' found')
+ input.aux.append_hash(instance,'file',specification,filename)
+ f:close()
+ return
+ end
+ end
+ input.logger('? tex locator', specification..' not found')
+ end
+end
+
+-- hashers
+
+function input.hashdatabase(instance,tag,name)
+ return input.methodhandler('hashers',instance,tag,name)
+end
+
+function input.loadfiles(instance)
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ input.hashdatabase(instance,hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function input.hashers.tex(instance,tag,name)
+ input.aux.load_data(instance,tag,'files')
+end
+
+-- generators:
+
+function input.loadlists(instance)
+ for _, hash in ipairs(instance.hashes) do
+ input.generatedatabase(instance,hash.tag)
+ end
+end
+
+function input.generatedatabase(instance,specification)
+ return input.methodhandler('generators', instance, specification)
+end
+
+function input.generators.tex(instance,specification)
+ local tag = specification
+ if not instance.lsrmode and lfs and lfs.dir then
+ input.report("scanning path",specification)
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local n, m = 0, 0
+ local spec = specification .. '/'
+ local attributes = lfs.attributes
+ local directory = lfs.dir
+ local small = instance.smallcache
+ local function action(path)
+ local mode, full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if name == '.' or name == ".." then
+ -- skip
+ else
+ mode = attributes(full..name,'mode')
+ if mode == "directory" then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ elseif path and mode == 'file' then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if not small then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ end
+ else
+ files[name] = path
+ end
+ end
+ end
+ end
+ end
+ action()
+ input.report(n,"files found on",m,"directories")
+ else
+ local fullname = file.join(specification,input.lsrname)
+ local path = '.'
+ local f = io.open(fullname)
+ if f then
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local small = instance.smallcache
+ input.report("loading lsr file",fullname)
+ -- for line in f:lines() do -- much slower then the next one
+ for line in (f:read("*a")):gmatch("(.-)\n") do
+ if line:find("^[%a%d]") then
+ local fl = files[line]
+ if fl then
+ if not small then
+ if type(fl) == 'string' then
+ files[line] = { fl, path } -- table
+ else
+ fl[#fl+1] = path
+ end
+ end
+ else
+ files[line] = path -- string
+ end
+ else
+ path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ end
+ end
+ f:close()
+ end
+ end
+end
+
+-- savers, todo
+
+function input.savefiles(instance)
+ input.aux.save_data(instance, 'files', function(k,v)
+ return instance.validfile(k,v) -- path, name
+ end)
+end
+
+-- A config (optionally) has the paths split in tables. Internally
+-- we join them and split them after the expansion has taken place. This
+-- is more convenient.
+
+function input.splitconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'string' then
+ local t = file.split_path(v)
+ if #t > 1 then
+ c[k] = t
+ end
+ end
+ end
+ end
+end
+function input.joinconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function input.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function input.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t = file.split_path(v)
+ if #t > 1 then
+ instance.expansions[k] = t
+ end
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t, h = { }, { }
+ for _,vv in pairs(file.split_path(v)) do
+ if vv ~= "" and not h[vv] then
+ t[#t+1] = vv
+ h[vv] = true
+ end
+ end
+ if #t > 1 then
+ instance.expansions[k] = t
+ else
+ instance.expansions[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function input.saveconfig(instance)
+ input.splitconfig(instance)
+ input.aux.save_data(instance, 'configuration', nil)
+ input.joinconfig(instance)
+end
+
+input.configbanner = [[
+-- This is a Luatex configuration file created by 'luatools.lua' or
+-- 'luatex.exe' directly. For comment, suggestions and questions you can
+-- contact the ConTeXt Development Team. This configuration file is
+-- not copyrighted. [HH & TH]
+]]
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name = file.join(cachename,dataname)
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ f:write(input.configbanner)
+ f:write("\n")
+ f:write("if not texmf then texmf = { } end\n")
+ f:write("if not texmf.data then texmf.data = { } end\n")
+ f:write("\n")
+ f:write("texmf.data.type = '" .. dataname .. "'\n")
+ f:write("texmf.data.version = '" .. input.cacheversion .. "'\n")
+ f:write("texmf.data.date = '" .. os.date("%Y-%m-%d") .. "'\n")
+ f:write("texmf.data.time = '" .. os.date("%H:%M:%S") .. "'\n")
+ f:write('texmf.data.content = {\n')
+ local function dump(k,v)
+ if not check or check(v,k) then -- path, name
+ if type(v) == 'string' then
+ f:write("\t['" .. k .. "'] = '" .. v .. "',\n")
+ elseif #v == 1 then
+ f:write("\t['" .. k .. "'] = '" .. v[1] .. "',\n")
+ else
+ f:write("\t['" .. k .. "'] = {'" .. table.concat(v,"','").. "'},\n")
+ end
+ end
+ end
+ if instance.sortdata then
+ for _, k in pairs(table.sortedkeys(files)) do
+ dump(k,files[k])
+ end
+ else
+ for k, v in pairs(files) do
+ dump(k,v)
+ end
+ end
+ f:write('}\n')
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.loadconfig(instance)
+ instance.configuration, instance.loaderror = { }, false
+ if not instance.renewcache then
+ for _, cnf in pairs(instance.cnffiles) do
+ input.aux.load_data(instance,file.dirname(cnf),'configuration')
+ if instance.loaderror then break end
+ end
+ end
+ input.joinconfig(instance)
+end
+
+if not texmf then texmf = {} end
+if not texmf.data then texmf.data = {} end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ if not filename or (filename == "") then
+ filename = dataname .. input.lucsuffix
+ end
+ local blob = loadfile(file.join(pathname,filename))
+ if not blob then
+ filename = dataname .. input.luasuffix
+ blob = loadfile(file.join(pathname,filename))
+ end
+ if blob then
+ blob()
+ if (texmf.data.type == dataname) and (texmf.data.version == input.cacheversion) and texmf.data.content then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = texmf.data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ end
+ texmf.data.content = { }
+end
+
+function input.expand_variables(instance)
+ instance.expansions = { }
+ if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
+ if instance.progname ~= "" then instance.environment['progname'] = instance.engine end
+ for k,v in pairs(instance.environment) do
+ local a, b = k:match("^(%a+)%_(.*)%s*$")
+ if a and b then
+ instance.expansions[a..'.'..b] = v
+ else
+ instance.expansions[k] = v
+ end
+ end
+ for k,v in pairs(instance.environment) do -- move environment to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ for k,v in pairs(instance.variables) do -- move variables to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ while true do
+ local busy = false
+ for k,v in pairs(instance.expansions) do
+ local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ if n > 0 or m > 0 then
+ instance.expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in pairs(instance.expansions) do
+ instance.expansions[k] = v:gsub("\\", '/')
+ end
+ input.splitexpansions(instance)
+end
+
+function input.aux.expand_vars(instance,lst) -- simple vars
+ for k,v in pairs(lst) do
+ lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+ end
+end
+
+function input.aux.expanded_var(instance,var) -- simple vars
+ return var:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+end
+
+function input.aux.entry(instance,entries,name)
+ if name and (name ~= "") then
+ name = name:gsub('%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = input.env(instance,name)
+ if result then
+ instance.variables[name] = result
+ input.expand_variables(instance)
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+function input.variable(instance,name)
+ return input.aux.entry(instance,instance.variables,name)
+end
+function input.expansion(instance,name)
+ return input.aux.entry(instance,instance.expansions,name)
+end
+
+function input.aux.is_entry(instance,entries,name)
+ if name and name ~= "" then
+ name = name:gsub('%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+end
+
+function input.is_variable(instance,name)
+ return input.aux.is_entry(instance,instance.variables,name)
+end
+function input.is_expansion(instance,name)
+ return input.aux.is_entry(instance,instance.expansions,name)
+end
+
+function input.aux.list(instance,list)
+ local pat = string.upper(instance.pattern or "","")
+ for _,key in pairs(table.sortedkeys(list)) do
+ if (instance.pattern=="") or string.find(key:upper(),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ print(key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ elseif instance.kpsevars[key] then
+ print('K ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ else
+ print('E ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ end
+ end
+end
+
+function input.list_variables(instance)
+ input.aux.list(instance,instance.variables)
+end
+function input.list_expansions(instance)
+ input.aux.list(instance,instance.expansions)
+end
+
+function input.list_configurations(instance)
+ for _,key in pairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or key:find(instance.pattern) then
+ print(key.."\n")
+ for i,c in pairs(instance.configuration) do
+ local str = c[key]
+ if str then
+ print("\t" .. i .. "\t\t" .. input.aux.tabstr(str))
+ end
+ end
+ print()
+ end
+ end
+end
+
+function input.aux.tabstr(str)
+ if type(str) == 'table' then
+ return table.concat(str," | ")
+ else
+ return str
+ end
+end
+
+function input.simplified_list(str)
+ if type(str) == 'table' then
+ return str -- troubles ; ipv , in texmf
+ elseif str == '' then
+ return { }
+ else
+ local t = { }
+ for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
+ t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
+ end
+ return t
+ end
+end
+
+function input.unexpanded_path_list(instance,str)
+ local pth = input.variable(instance,str)
+ local lst = input.split_path(pth)
+ return input.aux.expanded_path(instance,lst)
+end
+function input.unexpanded_path(instance,str)
+ return file.join_path(input.unexpanded_path_list(instance,str))
+end
+
+function input.expanded_path_list(instance,str)
+ if not str then
+ return { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = str:gsub("%$","")
+ if not instance.lists[str] then -- cached
+ local lst = input.split_path(input.expansion(instance,str))
+ instance.lists[str] = input.aux.expanded_path(instance,lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = input.split_path(input.expansion(instance,str))
+ return input.aux.expanded_path(instance,lst)
+ end
+end
+function input.expand_path(instance,str)
+ return file.join_path(input.expanded_path_list(instance,str))
+end
+
+--~ function input.first_writable_path(instance,name)
+--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
+--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
+--~ return v
+--~ end
+--~ end
+--~ return "."
+--~ end
+
+function input.expanded_path_list_from_var(instance,str) -- brrr
+ local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
+ if tmp ~= "" then
+ return input.expanded_path_list(instance,str)
+ else
+ return input.expanded_path_list(instance,tmp)
+ end
+end
+function input.expand_path_from_var(instance,str)
+ return file.join_path(input.expanded_path_list_from_var(instance,str))
+end
+
+function input.format_of_var(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+function input.format_of_suffix(str)
+ return input.suffixmap[file.extname(str)] or 'tex'
+end
+
+function input.variable_of_format(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+
+function input.var_of_format_or_suffix(str)
+ local v = input.formats[str]
+ if v then
+ return v
+ end
+ v = input.formats[input.alternatives[str]]
+ if v then
+ return v
+ end
+ v = input.suffixmap[file.extname(str)]
+ if v then
+ return input.formats[isf]
+ end
+ return ''
+end
+
+function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
+ local ori = input.variable(instance,str)
+ local pth = input.aux.expanded_path(instance,input.split_path(ori))
+ return file.join_path(pth)
+end
+
+-- {a,b,c,d}
+-- a,b,c/{p,q,r},d
+-- a,b,c/{p,q,r}/d/{x,y,z}//
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+
+function input.aux.expanded_path(instance,pathlist)
+ -- a previous version fed back into pathlist
+ local i, n, oldlist, newlist, ok = 0, 0, { }, { }, false
+ for _,v in ipairs(pathlist) do
+ if v:find("[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ for _,v in ipairs(pathlist) do
+ oldlist[#oldlist+1] = (v:gsub("([\{\}])", function(p)
+ if p == "{" then
+ i = i + 1
+ if i > n then n = i end
+ return "<" .. (i-1) .. ">"
+ else
+ i = i - 1
+ return "" .. i .. ">"
+ end
+ end))
+ end
+ for i=1,n do
+ while true do
+ local more = false
+ local pattern = "^(.-)<"..(n-i)..">(.-)"..(n-i)..">(.-)$"
+ local t = { }
+ for _,v in ipairs(oldlist) do
+ local pre, mid, post = v:match(pattern)
+ if pre and mid and post then
+ more = true
+ -- for _,vv in ipairs(mid:splitchr(',')) do
+ for vv in string.gmatch(mid..',',"(.-),") do
+ if vv == '.' then
+ t[#t+1] = pre..post
+ else
+ t[#t+1] = pre..vv..post
+ end
+ end
+ else
+ t[#t+1] = v
+ end
+ end
+ oldlist = t
+ if not more then break end
+ end
+ end
+ for _,v in pairs(oldlist) do
+ v = file.collapse_path(v)
+ if v ~= "" and not v:find(instance.dummy_path_expr) then newlist[#newlist+1] = v end
+ end
+ else
+ for _,v in pairs(pathlist) do
+ -- for _,vv in pairs(v:split(",")) do
+ for vv in string.gmatch(v..',',"(.-),") do
+ vv = file.collapse_path(v)
+ if vv ~= "" then newlist[#newlist+1] = vv end
+ end
+ end
+ end
+ return newlist
+end
+
+--~ function input.is_readable(name) -- brrr, get rid of this
+--~ return name:find("^zip##") or file.is_readable(name)
+--~ end
+
+input.is_readable = { }
+
+function input.aux.is_readable(readable, name)
+ if input.trace > 2 then
+ if readable then
+ input.logger("+ readable", name)
+ else
+ input.logger("- readable", name)
+ end
+ end
+ return readable
+end
+
+function input.is_readable.file(name)
+ -- return input.aux.is_readable(file.is_readable(name), name)
+ return input.aux.is_readable(input.aux.is_file(name), name)
+end
+
+input.is_readable.tex = input.is_readable.file
+
+-- name
+-- name/name
+
+function input.aux.collect_files(instance,names)
+ local filelist = nil
+ for _, fname in pairs(names) do
+ if fname then
+ if input.trace > 2 then
+ input.logger("? blobpath asked",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or dname:find("^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ if blobpath and instance.files[blobpath] then
+ if input.trace > 2 then
+ input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
+ end
+ local blobfile = instance.files[blobpath][bname]
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or blobfile:find(dname) then
+ if not filelist then filelist = { } end
+ -- input.logger('= collected', blobpath.." | "..blobfile.." | "..bname)
+ filelist[#filelist+1] = file.join(blobpath,blobfile,bname)
+ end
+ else
+ for _, vv in pairs(blobfile) do
+ if not dname or vv:find(dname) then
+ if not filelist then filelist = { } end
+ filelist[#filelist+1] = file.join(blobpath,vv,bname)
+ end
+ end
+ end
+ end
+ elseif input.trace > 1 then
+ input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
+ end
+ end
+ end
+ end
+ return filelist
+end
+
+function input.suffix_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function input.suffixes_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str]
+ else
+ return {}
+ end
+end
+
+function input.aux.qualified_path(filename) -- make platform dependent / not good yet
+ return
+ filename:find("^%.+/") or
+ filename:find("^/") or
+ filename:find("^%a+%:") or
+ filename:find("^%a+##")
+end
+
+function input.normalize_name(original)
+ -- internally we use type##spec##subspec ; this hackery slightly slows down searching
+ local str = original or ""
+ str = str:gsub("::", "##") -- :: -> ##
+ str = str:gsub("^(%a+)://" ,"%1##") -- zip:// -> zip##
+ str = str:gsub("(.+)##(.+)##/(.+)","%1##%2##%3") -- ##/spec -> ##spec
+ if (input.trace>1) and (original ~= str) then
+ input.logger('= normalizer',original.." -> "..str)
+ end
+ return str
+end
+
+-- split the next one up, better for jit
+
+function input.aux.register_in_trees(instance,name)
+ if not name:find("^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
+ local result = { }
+ local stamp = nil
+ filename = input.normalize_name(filename)
+ filename = file.collapse_path(filename:gsub("\\","/"))
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ input.logger('! remembered', filename)
+ return instance.found[stamp]
+ end
+ end
+ if filename:find('%*') then
+ input.logger('! wildcard', filename)
+ result = input.find_wildcard_files(instance,filename)
+ elseif input.aux.qualified_path(filename) then
+ if input.is_readable.file(filename) then
+ input.logger('! qualified', filename)
+ result = { filename }
+ else
+ local forcedname, ok = "", false
+ if file.extname(filename) == "" then
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing standard filetype tex')
+ result, ok = { forcedname }, true
+ end
+ else
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ forcedname = filename .. "." .. s
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing format filetype', s)
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok then
+ input.logger('? qualified', filename)
+ end
+ end
+ else
+ -- search spec
+ local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
+ if ext == "" then
+ if not instance.force_suffixes then
+ table.insert(wantedfiles, filename)
+ end
+ else
+ table.insert(wantedfiles, filename)
+ end
+ if instance.format == "" then
+ if ext == "" then
+ local forcedname = filename .. '.tex'
+ table.insert(wantedfiles, forcedname)
+ filetype = input.format_of_suffix(forcedname)
+ input.logger('! forcing filetype',filetype)
+ else
+ filetype = input.format_of_suffix(filename)
+ input.logger('! using suffix based filetype',filetype)
+ end
+ else
+ if ext == "" then
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ table.insert(wantedfiles, filename .. "." .. s)
+ end
+ end
+ filetype = instance.format
+ input.logger('! using given filetype',filetype)
+ end
+ local typespec = input.variable_of_format(filetype)
+ local pathlist = input.expanded_path_list(instance,typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ input.logger('? filetype',filetype or '?')
+ input.logger('? wanted files',table.concat(wantedfiles," | "))
+ end
+ for _, fname in pairs(wantedfiles) do
+ if fname and input.is_readable.file(fname) then
+ filename, done = fname, true
+ table.insert(result, file.join('.',fname))
+ break
+ end
+ end
+ -- this is actually 'other text files' or 'any' or 'whatever'
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ filename = filelist and filelist[1]
+ if filename then
+ table.insert(result, filename)
+ done = true
+ end
+ else
+ -- list search
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ local doscan, recurse
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
+ if filelist then input.logger('? file list',table.concat(filelist," | ")) end
+ end
+ -- a bit messy ... esp the doscan setting here
+ for _, path in pairs(pathlist) do
+ if path:find("^!!") then doscan = false else doscan = true end
+ if path:find("//$") then recurse = true else recurse = false end
+ local pathname = path:gsub("^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
+ pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
+ pathname = pathname:gsub("//", '/.-/')
+ expr = "^" .. pathname
+ -- input.debug('?',expr)
+ for _, f in pairs(filelist) do
+ if f:find(expr) then
+ -- input.debug('T',' '..f)
+ if input.trace > 2 then
+ input.logger('= found in hash',f)
+ end
+ table.insert(result,f)
+ input.aux.register_in_trees(instance,f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ else
+ -- input.debug('F',' '..f)
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all
+ if input.method_is_file(pathname) then -- ?
+ local pname = pathname:gsub("%.%*$",'')
+ if not pname:find("%*") then
+ local ppname = pname:gsub("/+$","")
+ if input.aux.can_be_dir(instance,ppname) then
+ for _, w in pairs(wantedfiles) do
+ local fname = file.join(ppname,w)
+ if input.is_readable.file(fname) then
+ if input.trace > 2 then
+ input.logger('= found by scanning',fname)
+ end
+ table.insert(result,fname)
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ else
+ -- no access needed for non existing path, speedup (esp in large tree with lots of fake)
+ end
+ end
+ end
+ end
+ if not done and doscan then
+ -- todo: slow path scanning
+ end
+ if done and not instance.allresults then break end
+ end
+ end
+ end
+ for k,v in pairs(result) do
+ result[k] = file.collapse_path(v)
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+input.aux._find_file_ = input.aux.find_file
+
+function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
+ local result = input.aux._find_file_(instance,filename)
+ if #result == 0 then
+ local lowered = filename:lower()
+ if filename ~= lowered then
+ return input.aux._find_file_(instance,lowered)
+ end
+ end
+ return result
+end
+
+if lfs and lfs.isfile then
+ input.aux.is_file = lfs.isfile -- to be done: use this
+else
+ input.aux.is_file = file.is_readable
+end
+
+if lfs and lfs.isdir then
+ function input.aux.can_be_dir(instance,name)
+ if not instance.fakepaths[name] then
+ if lfs.isdir(name) then
+ instance.fakepaths[name] = 1 -- directory
+ else
+ instance.fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (instance.fakepaths[name] == 1)
+ end
+else
+ function input.aux.can_be_dir()
+ return true
+ end
+end
+
+if not input.concatinators then input.concatinators = { } end
+
+function input.concatinators.tex(tag,path,name)
+ return tag .. '/' .. path .. '/' .. name
+end
+
+input.concatinators.file = input.concatinators.tex
+
+function input.find_files(instance,filename,filetype,mustexist)
+ if type(mustexist) == boolean then
+ -- all set
+ elseif type(filetype) == 'boolean' then
+ filetype, mustexist = nil, false
+ elseif type(filetype) ~= 'string' then
+ filetype, mustexist = nil, false
+ end
+ instance.format = filetype or ''
+ local t = input.aux.find_file(instance,filename,true)
+ instance.format = ''
+ return t
+end
+
+function input.find_file(instance,filename,filetype,mustexist)
+ return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+end
+
+function input.find_given_files(instance,filename)
+ local bname, result = file.basename(filename), { }
+ for k, hash in pairs(instance.hashes) do
+ local blist = instance.files[hash.tag][bname]
+ if blist then
+ if type(blist) == 'string' then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ if not instance.allresults then break end
+ else
+ for kk,vv in pairs(blist) do
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function input.find_given_file(instance,filename)
+ return (input.find_given_files(instance,filename)[1] or "")
+end
+
+--~ function input.find_wildcard_files(instance,filename)
+--~ local result = { }
+--~ local bname, dname = file.basename(filename), file.dirname(filename)
+--~ local expr = dname:gsub("^*/","")
+--~ expr = expr:gsub("*",".*")
+--~ expr = expr:gsub("-","%-")
+--~ for k, hash in pairs(instance.hashes) do
+--~ local blist = instance.files[hash.tag][bname]
+--~ if blist then
+--~ if type(blist) == 'string' then
+--~ -- make function and share code
+--~ if blist:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ else
+--~ for kk,vv in pairs(blist) do
+--~ if vv:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ return result
+--~ end
+
+function input.find_wildcard_files(instance,filename)
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = dname:gsub("^*/","")
+ path = path:gsub("*",".*")
+ path = path:gsub("-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = name:gsub("*",".*")
+ name = name:gsub("-","%%-")
+ path = path:lower()
+ name = name:lower()
+ local function doit(blist,bname,hash,allresults)
+ local done = false
+ if blist then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if (blist:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ done = true
+ end
+ else
+ for kk,vv in pairs(blist) do
+ if (vv:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+ end
+ local files, allresults, done = instance.files, instance.allresults, false
+ if name:find("%*") then
+ for k, hash in pairs(instance.hashes) do
+ for kk, hh in pairs(files[hash.tag]) do
+ if (kk:lower()):find(name) then
+ if doit(hh,kk,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ else
+ for k, hash in pairs(instance.hashes) do
+ if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ return result
+end
+
+function input.find_wildcard_file(instance,filename)
+ return (input.find_wildcard_files(instance,filename)[1] or "")
+end
+
+-- main user functions
+
+function input.save_used_files_in_trees(instance, filename,jobname)
+ if not filename then filename = 'luatex.jlg' end
+ local f = io.open(filename,'w')
+ if f then
+ f:write("\n")
+ f:write("\n")
+ if jobname then
+ f:write("\t" .. jobname .. "\n")
+ end
+ f:write("\t\n")
+ for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
+ f:write("\t\t" .. v .. "\n")
+ end
+ f:write("\t\n")
+ f:write("\n")
+ f:close()
+ end
+end
+
+function input.automount(instance)
+ -- implemented later
+end
+
+function input.load(instance)
+ input.start_timing(instance)
+ input.identify_cnf(instance)
+ input.load_cnf(instance)
+ input.expand_variables(instance)
+ input.load_hash(instance)
+ input.automount(instance)
+ input.stop_timing(instance)
+end
+
+function input.for_files(instance, command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if input.verbose then
+ input.report(str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if input.verbose then
+ report('')
+ end
+ for _, file in pairs(files) do
+ local result = command(instance,file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in pairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+function input.var_value(instance,str) -- output the value of variable $STRING.
+ return input.variable(instance,str)
+end
+function input.expand_var(instance,str) -- output variable expansion of STRING.
+ return input.expansion(instance,str)
+end
+function input.show_path(instance,str) -- output search path for file type NAME
+ return file.join_path(input.expanded_path_list(instance,input.format_of_var(str)))
+end
+
+-- input.find_file(filename)
+-- input.find_file(filename, filetype, mustexist)
+-- input.find_file(filename, mustexist)
+-- input.find_file(filename, filetype)
+
+function input.aux.register_file(files, name, path)
+ if files[name] then
+ if type(files[name]) == 'string' then
+ files[name] = { files[name], path }
+ else
+ files[name] = path
+ end
+ else
+ files[name] = path
+ end
+end
+
+-- zip:: zip## zip://
+-- zip::pathtozipfile::pathinzipfile (also: pathtozipfile/pathinzipfile)
+-- file::name
+-- tex::name
+-- kpse::name
+-- kpse::format::name
+-- parent::n::name
+-- parent::name (default 2)
+
+if not input.finders then input.finders = { } end
+if not input.openers then input.openers = { } end
+if not input.loaders then input.loaders = { } end
+
+input.finders.notfound = { nil }
+input.openers.notfound = { nil }
+input.loaders.notfound = { false, nil, 0 }
+
+function input.splitmethod(filename)
+ local method, specification = filename:match("^(.-)##(.+)$")
+ if method and specification then
+ return method, specification
+ else
+ return 'tex', filename
+ end
+end
+
+function input.method_is_file(filename)
+ local method, specification = input.splitmethod(filename)
+ return method == 'tex' or method == 'file'
+end
+
+function input.methodhandler(what, instance, filename, filetype) -- ...
+ local method, specification = input.splitmethod(filename)
+ if method and specification then -- redundant
+ if input[what][method] then
+ input.logger('= handler',filename.." -> "..what.." | "..method.." | "..specification)
+ return input[what][method](instance,specification,filetype)
+ else
+ return nil
+ end
+ else
+ return input[what].tex(instance,filename,filetype)
+ end
+end
+
+-- also inside next test?
+
+function input.findtexfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.opentexfile(instance,filename)
+ return input.methodhandler('openers',instance, input.normalize_name(filename))
+end
+
+function input.findbinfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.openbinfile(instance,filename)
+ return input.methodhandler('loaders',instance, input.normalize_name(filename))
+end
+
+function input.loadbinfile(instance, filename, filetype)
+ local fname = input.findbinfile(instance, input.normalize_name(filename), filetype)
+ if fname and fname ~= "" then
+ return input.openbinfile(instance,fname)
+ else
+ return unpack(input.loaders.notfound)
+ end
+end
+
+function input.texdatablob(instance, filename, filetype)
+ local ok, data, size = input.loadbinfile(instance, filename, filetype)
+ return data or ""
+end
+
+function input.openfile(filename) -- brrr texmf.instance here / todo ! ! ! ! !
+ local fullname = input.findtexfile(texmf.instance, filename)
+ if fullname and (fullname ~= "") then
+ return input.opentexfile(texmf.instance, fullname)
+ else
+ return nil
+ end
+end
+
+function input.logmode()
+ return (os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"):lower()
+end
+
+-- this is a prelude to engine/progname specific configuration files
+-- in which case we can omit files meant for other programs and
+-- packages
+
+--- ctx
+
+-- maybe texinputs + font paths
+-- maybe positive selection tex/context fonts/tfm|afm|vf|opentype|type1|map|enc
+
+input.validators = { }
+input.validators.visibility = { }
+
+function input.validators.visibility.default(path, name)
+ return true
+end
+
+function input.validators.visibility.context(path, name)
+ path = path[1] or path -- some day a loop
+ return not (
+ path:find("latex") or
+ path:find("doc") or
+ path:find("tex4ht") or
+ path:find("source") or
+-- path:find("config") or
+-- path:find("metafont") or
+ path:find("lists$") or
+ name:find("%.tpm$") or
+ name:find("%.bak$")
+ )
+end
+
+-- todo: describe which functions are public (maybe input.private. ... )
+
+-- beware: i need to check where we still need a / on windows:
+
+function input.clean_path(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"\\","/"),"^!+",""),"//$","/")
+ return (string.gsub(string.gsub(str,"\\","/"),"^!+",""))
+end
+function input.do_with_path(name,func)
+ for _, v in pairs(input.expanded_path_list(instance,name)) do
+ func("^"..input.clean_path(v))
+ end
+end
+function input.do_with_var(name,func)
+ func(input.aux.expanded_var(name))
+end
+
+function input.with_files(instance,pattern,handle)
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ local blobtype = hash.type
+ if blobpath and instance.files[blobpath] then -- sort them?
+ for k,v in pairs(instance.files[blobpath]) do
+ if k:find(pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ newname = file.addsuffix(newname,"lua")
+ newscript = input.clean_path(input.find_file(instance, newname))
+ oldscript = input.clean_path(oldname)
+ input.report("old script", oldscript)
+ input.report("new script", newscript)
+ if oldscript ~= newscript and (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
+ newdata = io.loaddata(newscript)
+ if newdata then
+ input.report("old script content replaced by new content")
+ io.savedata(oldscript,newdata)
+ end
+ end
+end
+
+
+if not modules then modules = { } end modules ['luat-tmp'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module deals with caching data. It sets up the paths and
+implements loaders and savers for tables. Best is to set the
+following variable. When not set, the usual paths will be
+checked. Personally I prefer the (users) temporary path.
Currently we do no locking when we write files. This is no real
+problem because most caching involves fonts and the chance of them
+being written at the same time is small. We also need to extend
+luatools with a recache feature.
+--ldx]]--
+
+cache = cache or { }
+dir = dir or { }
+texmf = texmf or { }
+
+cache.path = nil
+cache.base = cache.base or "luatex-cache"
+cache.more = cache.more or "context"
+cache.direct = false -- true is faster but may need huge amounts of memory
+cache.trace = false
+cache.tree = false
+cache.temp = os.getenv("TEXMFCACHE") or os.getenv("HOME") or os.getenv("HOMEPATH") or os.getenv("VARTEXMF") or os.getenv("TEXMFVAR") or os.getenv("TMP") or os.getenv("TEMP") or os.getenv("TMPDIR") or nil
+cache.paths = { cache.temp }
+
+if not cache.temp then
+ print("\nFATAL ERROR: NO VALID TEMPORARY PATH\n")
+ os.exit()
+end
+
+function cache.configpath(instance)
+ return input.expand_var(instance,"TEXMFCNF")
+end
+
+function cache.treehash(instance)
+ local tree = cache.configpath(instance)
+ if not tree or tree == "" then
+ return false
+ else
+ return md5.hex(tree)
+ end
+end
+
+function cache.setpath(instance,...)
+ if not cache.path then
+ if lfs and instance then
+ for _,v in pairs(cache.paths) do
+ for _,vv in pairs(input.expanded_path_list(instance,v)) do
+ if lfs.isdir(vv) then
+ cache.path = vv
+ break
+ end
+ end
+ if cache.path then break end
+ end
+ end
+ if not cache.path then
+ cache.path = cache.temp
+ end
+ if lfs then
+ cache.tree = cache.tree or cache.treehash(instance)
+ if cache.tree then
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more,cache.tree)
+ else
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more)
+ end
+ end
+ end
+ if not cache.path then
+ cache.path = '.'
+ end
+ cache.path = input.clean_path(cache.path)
+ if lfs and not table.is_empty({...}) then
+ local pth = dir.mkdirs(cache.path,...)
+ return pth
+ end
+ return cache.path
+end
+
+function cache.setluanames(path,name)
+ return path .. "/" .. name .. ".tma", path .. "/" .. name .. ".tmc"
+end
+
+function cache.loaddata(path,name)
+ local tmaname, tmcname = cache.setluanames(path,name)
+ local loader = loadfile(tmcname) or loadfile(tmaname)
+ if loader then
+ return loader()
+ else
+ return false
+ end
+end
+
+function cache.is_writable(filepath,filename)
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ return file.is_writable(tmaname)
+end
+
+function cache.savedata(filepath,filename,data,raw) -- raw needed for file cache
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ local reduce, simplify = true, true
+ if raw then
+ reduce, simplify = false, false
+ end
+ if cache.direct then
+ file.savedata(tmaname, table.serialize(data,'return',true,true))
+ else
+ table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
+ end
+ utils.lua.compile(tmaname, tmcname)
+end
+
+-- here we use the cache for format loading (texconfig.[formatname|jobname])
+
+if tex and texconfig and texconfig.formatname and texconfig.formatname == "" then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end
+ texconfig.formatname = cache.setpath(instance,"format") .. "/" .. texconfig.luaname:gsub("%.lu.$",".fmt")
+end
+
+--[[ldx--
+
Once we found ourselves defining similar cache constructs
+several times, containers were introduced. Containers are used
+to collect tables in memory and reuse them when possible based
+on (unique) hashes (to be provided by the calling function).
+
+
Caching to disk is disabled by default. Version numbers are
+stored in the saved table which makes it possible to change the
+table structures without bothering about the disk cache.
+
+
Examples of usage can be found in the font related code.
+--ldx]]--
+
+containers = { }
+containers.trace = false
+
+do -- local report
+
+ local function report(container,tag,name)
+ if cache.trace or containers.trace or container.trace then
+ logs.report(string.format("%s cache",container.subcategory),string.format("%s: %s",tag,name or 'invalid'))
+ end
+ end
+
+ function containers.define(category, subcategory, version, enabled)
+ if category and subcategory then
+ return {
+ category = category,
+ subcategory = subcategory,
+ storage = { },
+ enabled = enabled,
+ version = version or 1.000,
+ trace = false,
+ path = cache.setpath(texmf.instance,category,subcategory),
+ }
+ else
+ return nil
+ end
+ end
+
+ function containers.is_usable(container, name)
+ return container.enabled and cache.is_writable(container.path, name)
+ end
+
+ function containers.is_valid(container, name)
+ if name and name ~= "" then
+ local cs = container.storage[name]
+ return cs and not table.is_empty(cs) and cs.cache_version == container.version
+ else
+ return false
+ end
+ end
+
+ function containers.read(container,name)
+ if container.enabled and not container.storage[name] then
+ container.storage[name] = cache.loaddata(container.path,name)
+ if containers.is_valid(container,name) then
+ report(container,"loaded",name)
+ else
+ container.storage[name] = nil
+ end
+ end
+ if container.storage[name] then
+ report(container,"reusing",name)
+ end
+ return container.storage[name]
+ end
+
+ function containers.write(container, name, data)
+ if data then
+ data.cache_version = container.version
+ if container.enabled then
+ local unique, shared = data.unique, data.shared
+ data.unique, data.shared = nil, nil
+ cache.savedata(container.path, name, data)
+ report(container,"saved",name)
+ data.unique, data.shared = unique, shared
+ end
+ report(container,"stored",name)
+ container.storage[name] = data
+ end
+ return data
+ end
+
+ function containers.content(container,name)
+ return container.storage[name]
+ end
+
+end
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+input.usecache = true
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name
+ if input.usecache then
+ name = file.join(cache.setpath(instance,"trees"),md5.hex(cachename))
+ else
+ name = file.join(cachename,dataname)
+ end
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ input.report("preparing " .. dataname .. " in", luaname)
+ for k, v in pairs(files) do
+ if not check or check(v,k) then -- path, name
+ if #v == 1 then
+ files[k] = v[1]
+ end
+ else
+ files[k] = nil -- false
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = input.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ -- f:write(table.serialize(data,'return'))
+ f:write(input.serialize(data))
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.serialize(files)
+ -- This version is somewhat optimized for the kind of
+ -- tables that we deal with, so it's much faster than
+ -- the generic serializer. This makes sense because
+ -- luatools and mtxtools are called frequently. Okay,
+ -- we pay a small price for properly tabbed tables.
+ local t = { }
+ local concat = table.concat
+ local sorted = table.sortedkeys
+ local function dump(k,v,m)
+ if type(v) == 'string' then
+ return m .. "['" .. k .. "']='" .. v .. "',"
+ elseif #v == 1 then
+ return m .. "['" .. k .. "']='" .. v[1] .. "',"
+ else
+ return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
+ end
+ end
+ t[#t+1] = "return {"
+ if instance.sortdata then
+ for _, k in pairs(sorted(files)) do
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sorted(fk)) do
+ t[#t+1] = dump(kk,fk[kk],"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,fk,"\t")
+ end
+ end
+ else
+ for k, v in pairs(files) do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in pairs(v) do
+ t[#t+1] = dump(kk,vv,"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,v,"\t")
+ end
+ end
+ end
+ t[#t+1] = "}"
+ return concat(t,"\n")
+end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ local luaname, lucname, pname, fname
+ if input.usecache then
+ pname, fname = cache.setpath(instance,"trees"), md5.hex(pathname)
+ filename = file.join(pname,fname)
+ else
+ if not filename or (filename == "") then
+ filename = dataname
+ end
+ pname, fname = pathname, filename
+ end
+ luaname = file.join(pname,fname) .. input.luasuffix
+ lucname = file.join(pname,fname) .. input.lucsuffix
+ local blob = loadfile(lucname)
+ if not blob then
+ blob = loadfile(luaname)
+ end
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == input.cacheversion then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ end
+end
+
+-- we will make a better format, maybe something xml or just text
+
+input.automounted = input.automounted or { }
+
+function input.automount(instance,usecache)
+ local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { cache.setpath(instance,"mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ input.starttiming(instance)
+ for k, root in pairs(mountpaths) do
+ local f = io.open(root.."/url.tmi")
+ if f then
+ for line in f:lines() do
+ if line then
+ if line:find("^[%%#%-]") then -- or %W
+ -- skip
+ elseif line:find("^zip://") then
+ input.report("mounting",line)
+ table.insert(input.automounted,line)
+ input.usezipfile(instance,line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ input.stoptiming(instance)
+ end
+end
+
+-- store info in format
+
+input.storage = { }
+input.storage.data = { }
+input.storage.min = 0 -- 500
+input.storage.max = input.storage.min - 1
+input.storage.trace = false -- true
+input.storage.done = 0
+input.storage.evaluators = { }
+-- (evaluate,message,names)
+
+function input.storage.register(...)
+ input.storage.data[#input.storage.data+1] = { ... }
+end
+
+function input.storage.evaluate(name)
+ input.storage.evaluators[#input.storage.evaluators+1] = name
+end
+
+function input.storage.finalize() -- we can prepend the string with "evaluate:"
+ for _, t in ipairs(input.storage.evaluators) do
+ for i, v in pairs(t) do
+ if type(v) == "string" then
+ t[i] = loadstring(v)()
+ elseif type(v) == "table" then
+ for _, vv in pairs(v) do
+ if type(vv) == "string" then
+ t[i] = loadstring(vv)()
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.storage.dump()
+ for name, data in ipairs(input.storage.data) do
+ local evaluate, message, original, target = data[1], data[2], data[3] ,data[4]
+ local name, initialize, finalize = nil, "", ""
+ for str in string.gmatch(target,"([^%.]+)") do
+ if name then
+ name = name .. "." .. str
+ else
+ name = str
+ end
+ initialize = string.format("%s %s = %s or {} ", initialize, name, name)
+ end
+ if evaluate then
+ finalize = "input.storage.evaluate(" .. name .. ")"
+ end
+ input.storage.max = input.storage.max + 1
+ if input.storage.trace then
+ logs.report('storage',string.format('saving %s in slot %s',message,input.storage.max))
+ lua.bytecode[input.storage.max] = loadstring(
+ initialize ..
+ string.format("logs.report('storage','restoring %s from slot %s') ",message,input.storage.max) ..
+ table.serialize(original,name) ..
+ finalize
+ )
+ else
+ lua.bytecode[input.storage.max] = loadstring(initialize .. table.serialize(original,name) .. finalize)
+ end
+ end
+end
+
+if lua.bytecode then -- from 0 upwards
+ local i = input.storage.min
+ while lua.bytecode[i] do
+ lua.bytecode[i]()
+ lua.bytecode[i] = nil
+ i = i + 1
+ end
+ input.storage.done = i
+end
+
+
+-- end library merge
+
+own = { }
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-md5.lua',
+ 'l-number.lua',
+ 'l-os.lua',
+ 'l-file.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+-- 'l-unicode.lua',
+ 'l-utils.lua',
+-- 'l-tex.lua',
+ 'luat-lib.lua',
+ 'luat-inp.lua',
+-- 'luat-zip.lua',
+-- 'luat-tex.lua',
+-- 'luat-kps.lua',
+ 'luat-tmp.lua',
+}
+
+-- We need this hack till luatex is fixed.
+--
+-- for k,v in pairs(arg) do print(k,v) end
+
+if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
+ arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
+end
+
+-- End of hack.
+
+own.name = (environment and environment.ownname) or arg[0] or 'luatools.lua'
+
+own.path = string.match(own.name,"^(.+)[\\/].-$") or "."
+own.list = { '.' }
+if own.path ~= '.' then
+ table.insert(own.list,own.path)
+end
+table.insert(own.list,own.path.."/../../../tex/context/base")
+table.insert(own.list,own.path.."/mtx")
+table.insert(own.list,own.path.."/../sources")
+
+function locate_libs()
+ for _, lib in pairs(own.libs) do
+ for _, pth in pairs(own.list) do
+ local filename = string.gsub(pth .. "/" .. lib,"\\","/")
+ local codeblob = loadfile(filename)
+ if codeblob then
+ codeblob()
+ own.list = { pth } -- speed up te search
+ break
+ end
+ end
+ end
+end
+
+if not input then
+ locate_libs()
+end
+
+if not input then
+ print("")
+ print("Mtxrun is unable to start up due to lack of libraries. You may")
+ print("try to run 'lua mtxrun.lua --selfmerge' in the path where this")
+ print("script is located (normally under ..../scripts/context/lua) which")
+ print("will make this script library independent.")
+ os.exit()
+end
+
+instance = input.reset()
+input.verbose = environment.argument("verbose") or false
+input.banner = 'MtxRun | '
+utils.report = input.report
+
+instance.engine = environment.argument("engine") or 'luatex'
+instance.progname = environment.argument("progname") or 'context'
+instance.lsrmode = environment.argument("lsr") or false
+
+-- use os.env or environment when available
+
+function os.setenv(key,value)
+ -- todo
+end
+
+function input.check_environment(tree)
+ input.report('')
+ os.setenv('TMP', os.getenv('TMP') or os.getenv('TEMP') or os.getenv('TMPDIR') or os.getenv('HOME'))
+ if os.platform == 'linux' then
+ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-linux')
+ elseif os.platform == 'windows' then
+ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-windows')
+ elseif os.platform == 'macosx' then
+ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-macosx')
+ end
+ os.setenv('TEXOS', string.gsub(string.gsub(os.getenv('TEXOS'),"^[\\\/]*", ''),"[\\\/]*$", ''))
+ os.setenv('TEXPATH', string.gsub(tree,"\/+$",''))
+ os.setenv('TEXMFOS', os.getenv('TEXPATH') .. "/" .. os.getenv('TEXOS'))
+ input.report('')
+ input.report("preset : TEXPATH => " .. os.getenv('TEXPATH'))
+ input.report("preset : TEXOS => " .. os.getenv('TEXOS'))
+ input.report("preset : TEXMFOS => " .. os.getenv('TEXMFOS'))
+ input.report("preset : TMP => " .. os.getenv('TMP'))
+ input.report('')
+end
+
+function input.load_environment(name) -- todo: key=value as well as lua
+ local f = io.open(name)
+ if f then
+ for line in f:lines() do
+ if line:find("^[%%%#]") then
+ -- skip comment
+ else
+ local key, how, value = line:match("^(.-)%s*([%<%=%>%?]+)%s*(.*)%s*$")
+ value = value:gsub("^%%(.+)%%$", function(v) return os.getenv(v) or "" end)
+ if how == "=" or how == "<<" then
+ os.setenv(key,value)
+ elseif how == "?" or how == "??" then
+ os.setenv(key,os.getenv(key) or value)
+ elseif how == "<" or how == "+=" then
+ if os.getenv(key) then
+ os.setenv(key,os.getenv(key) .. io.fileseparator .. value)
+ else
+ os.setenv(key,value)
+ end
+ elseif how == ">" or how == "=+" then
+ if os.getenv(key) then
+ os.setenv(key,value .. io.pathseparator .. os.getenv(key))
+ else
+ os.setenv(key,value)
+ end
+ end
+ end
+ end
+ f:close()
+ end
+end
+
+function input.load_tree(tree)
+ if tree and tree ~= "" then
+ local setuptex = 'setuptex.tmf'
+ if lfs.attributes(tree, mode) == "directory" then -- check if not nil
+ setuptex = tree .. "/" .. setuptex
+ else
+ setuptex = tree
+ end
+ if io.exists(setuptex) then
+ input.check_environment(tree)
+ input.load_environment(setuptex)
+ end
+ end
+end
+
+-- md5 extensions
+
+-- maybe md.md5 md.md5hex md.md5HEX
+
+if not md5 then md5 = { } end
+
+if not md5.sum then
+ function md5.sum(k)
+ return string.rep("x",16)
+ end
+end
+
+function md5.hexsum(k)
+ return (string.gsub(md5.sum(k), ".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+function md5.HEXsum(k)
+ return (string.gsub(md5.sum(k), ".", function(c) return string.format("%02X", string.byte(c)) end))
+end
+
+-- file extensions
+
+file.needs_updating_threshold = 1
+
+function file.needs_updating(oldname,newname) -- size modification access change
+ local oldtime = lfs.attributes(oldname, modification)
+ local newtime = lfs.attributes(newname, modification)
+ if newtime >= oldtime then
+ return false
+ elseif oldtime - newtime < file.needs_updating_threshold then
+ return false
+ else
+ return true
+ end
+end
+
+function file.mdchecksum(name)
+ if md5 then
+ local data = io.loadall(name)
+ if data then
+ return md5.HEXsum(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md then
+ local data = io.loadall(name .. ".md5")
+ if data then
+ return string.gsub(md5.HEXsum(data),"%s$","")
+ end
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.mdchecksum(name) end
+ if checksum then
+ local f = io.open(name .. ".md5","w")
+ if f then
+ f:write(checksum)
+ f:close()
+ return checksum
+ end
+ end
+ return nil
+end
+
+-- it starts here
+
+input.runners = { }
+input.runners.applications = { }
+
+input.runners.applications.lua = "luatex --luaonly"
+input.runners.applications.pl = "perl"
+input.runners.applications.py = "python"
+input.runners.applications.rb = "ruby"
+
+input.runners.suffixes = {
+ 'rb', 'lua', 'py', 'pl'
+}
+
+input.runners.registered = {
+ texexec = { 'texexec.rb', true },
+ texutil = { 'texutil.rb', true },
+ texfont = { 'texfont.pl', true },
+ texshow = { 'texshow.pl', false },
+
+ makempy = { 'makempy.pl', true },
+ mptopdf = { 'mptopdf.pl', true },
+ pstopdf = { 'pstopdf.rb', true },
+
+ examplex = { 'examplex.rb', false },
+ concheck = { 'concheck.rb', false },
+
+ runtools = { 'runtools.rb', true },
+ textools = { 'textools.rb', true },
+ tmftools = { 'tmftools.rb', true },
+ ctxtools = { 'ctxtools.rb', true },
+ rlxtools = { 'rlxtools.rb', true },
+ pdftools = { 'pdftools.rb', true },
+ mpstools = { 'mpstools.rb', true },
+ exatools = { 'exatools.rb', true },
+ xmltools = { 'xmltools.rb', true },
+ luatools = { 'luatools.lua', true },
+ mtxtools = { 'mtxtools.rb', true },
+
+ pdftrimwhite = { 'pdftrimwhite.pl', false }
+}
+
+if not messages then messages = { } end
+
+messages.help = [[
+--execute run a script or program
+--resolve resolve prefixed arguments
+--ctxlua run internally (using preloaded libs)
+--locate locate given filename
+
+--autotree use texmf tree cf. env 'texmfstart_tree' or 'texmfstarttree'
+--tree=pathtotree use given texmf tree (default file: 'setuptex.tmf')
+--environment=name use given (tmf) environment file
+--path=runpath go to given path before execution
+--ifchanged=filename only execute when given file has changed (md checksum)
+--iftouched=old,new only execute when given file has changed (time stamp)
+
+--make create stubs for (context related) scripts
+--remove remove stubs (context related) scripts
+--stubpath=binpath paths where stubs wil be written
+--windows create windows (mswin) stubs
+--unix create unix (linux) stubs
+
+--verbose give a bit more info
+--engine=str target engine
+--progname=str format or backend
+
+--edit launch editor with found file
+--launch (--all) launch files (assume os support)
+
+--intern run script using built in libraries
+]]
+
+function input.runners.my_prepare_a(instance)
+ input.identify_cnf(instance)
+ input.load_cnf(instance)
+ input.expand_variables(instance)
+end
+
+function input.runners.my_prepare_b(instance)
+ input.runners.my_prepare_a(instance)
+ input.load_hash(instance)
+end
+
+function input.runners.prepare(instance)
+ local checkname = environment.argument("ifchanged")
+ if checkname and checkname ~= "" then
+ local oldchecksum = file.loadchecksum(checkname)
+ local newchecksum = file.checksum(checkname)
+ if oldchecksum == newchecksum then
+ report("file '" .. checkname .. "' is unchanged")
+ return "skip"
+ else
+ report("file '" .. checkname .. "' is changed, processing started")
+ end
+ file.savechecksum(checkname)
+ end
+ local oldname, newname = string.split(environment.argument("iftouched") or "", ",")
+ if oldname and newname and oldname ~= "" and newname ~= "" then
+ if not file.needs_updating(oldname,newname) then
+ report("file '" .. oldname .. "' and '" .. newname .. "'have same age")
+ return "skip"
+ else
+ report("file '" .. newname .. "' is older than '" .. oldname .. "'")
+ end
+ end
+ local tree = environment.argument('tree') or ""
+ if environment.argument('autotree') then
+ tree = os.getenv('TEXMFSTART_TREE') or os.getenv('TEXMFSTARTTREE') or tree
+ end
+ if tree and tree ~= "" then
+ input.load_tree(tree)
+ end
+ local env = environment.argument('environment') or ""
+ if env and env ~= "" then
+ for _,e in pairs(string.split(env)) do
+ -- maybe force suffix when not given
+ input.load_tree(e)
+ end
+ end
+ local runpath = environment.argument("path")
+ if runpath and not dir.chdir(runpath) then
+ input.report("unable to change to path '" .. runpath .. "'")
+ return "error"
+ end
+ return "run"
+end
+
+function input.runners.execute_script(instance,fullname,internal)
+ if fullname and fullname ~= "" then
+ local state = input.runners.prepare(instance)
+ if state == 'error' then
+ return false
+ elseif state == 'skip' then
+ return true
+ elseif state == "run" then
+ instance.progname = environment.argument("progname") or instance.progname
+ instance.format = environment.argument("format") or instance.format
+ local path, name, suffix, result = file.dirname(fullname), file.basename(fullname), file.extname(fullname), ""
+ if path ~= "" then
+ result = fullname
+ elseif name then
+ name = name:gsub("^int[%a]*:",function()
+ internal = true
+ return ""
+ end )
+ name = name:gsub("^script:","")
+ if suffix == "" and input.runners.registered[name] and input.runners.registered[name][1] then
+ name = input.runners.registered[name][1]
+ suffix = file.extname(name)
+ end
+ if suffix == "" then
+ -- loop over known suffixes
+ for _,s in pairs(input.runners.suffixes) do
+ result = input.find_file(instance, name .. "." .. s, 'texmfscripts')
+ if result ~= "" then
+ break
+ end
+ end
+ elseif input.runners.applications[suffix] then
+ result = input.find_file(instance, name, 'texmfscripts')
+ else
+ -- maybe look on path
+ result = input.find_file(instance, name, 'other text files')
+ end
+ end
+ if result and result ~= "" then
+ if internal then
+ local before, after = environment.split_arguments(fullname)
+ arg = { } for _,v in pairs(after) do arg[#arg+1] = v end
+ dofile(result)
+ else
+ local binary = input.runners.applications[file.extname(result)]
+ if binary and binary ~= "" then
+ result = binary .. " " .. result
+ end
+ local before, after = environment.split_arguments(fullname)
+ -- environment.initialize_arguments(after)
+ -- local command = result .. " " .. environment.reconstruct_commandline(environment.original_arguments) -- bugged
+ local command = result .. " " .. environment.reconstruct_commandline(after)
+ input.report("")
+ input.report("executing: " .. command)
+ input.report("\n \n")
+ local code = os.exec(command)
+ return code == 0
+ end
+ end
+ end
+ end
+ return false
+end
+
+function input.runners.execute_program(instance,fullname)
+ if fullname and fullname ~= "" then
+ local state = input.runners.prepare(instance)
+ if state == 'error' then
+ return false
+ elseif state == 'skip' then
+ return true
+ elseif state == "run" then
+ local before, after = environment.split_arguments(fullname)
+ environment.initialize_arguments(after)
+ fullname = fullname:gsub("^bin:","")
+ local command = fullname .. " " .. environment.reconstruct_commandline(environment.original_arguments)
+ input.report("")
+ input.report("executing: " .. command)
+ input.report("\n \n")
+ local code = os.exec(command)
+ return code == 0
+ end
+ end
+ return false
+end
+
+function input.runners.handle_stubs(instance,create)
+ local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer supported
+ local windows = environment.argument('windows') or environment.argument('mswin') or false
+ local unix = environment.argument('unix') or environment.argument('linux') or false
+ if not windows and not unix then
+ if environment.platform == "unix" then
+ unix = true
+ else
+ windows = true
+ end
+ end
+ for _,v in pairs(input.runners.registered) do
+ local name, doit = v[1], v[2]
+ if doit then
+ local base = string.gsub(file.basename(name), "%.(.-)$", "")
+ if create then
+ -- direct local command = input.runners.applications[file.extname(name)] .. " " .. name
+ local command = "luatex --luaonly mtxrun.lua " .. name
+ if windows then
+ io.savedata(base..".bat", {"@echo off", command.." %*"}, "\013\010")
+ input.report("windows stub for '" .. base .. "' created")
+ end
+ if unix then
+ io.savedata(base, {"#!/bin/sh", command..' "$@"'}, "\010")
+ input.report("unix stub for '" .. base .. "' created")
+ end
+ else
+ if windows and (os.remove(base..'.bat') or os.remove(base..'.cmd')) then
+ input.report("windows stub for '" .. base .. "' removed")
+ end
+ if unix and (os.remove(base) or os.remove(base..'.sh')) then
+ input.report("unix stub for '" .. base .. "' removed")
+ end
+ end
+ end
+ end
+end
+
+function input.resolve_prefixes(instance,str)
+ -- [env|environment|rel|relative|loc|locate|kpse|path|file]:
+ return (str:gsub("(%a+)%:(%S+)", function(k,v)
+ if k == "env" or k == "environment" then
+ v = os.getenv(k) or ""
+ elseif k == "rel" or k == "relative" then
+ for _,p in pairs({".","..","../.."}) do
+ local f = p .. "/" .. v
+ if file.exist(v) then break end
+ end
+ elseif k == "kpse" or k == "loc" or k == "locate" or k == "file" or k == "path" then
+instance.progname = environment.argument("progname") or instance.progname
+instance.format = environment.argument("format") or instance.format
+ v = input.find_given_file(instance, v)
+ if k == "path" then v = file.dirname(v) end
+ else
+ v = ""
+ end
+ return v
+ end))
+end
+
+function input.runners.resolve_string(instance)
+ instance.progname = environment.argument("progname") or environment.argument("program") or instance.progname
+ instance.format = environment.argument("format") or instance.format
+ input.runners.report_location(instance,input.resolve_prefixes(instance,table.join(environment.files, " ")))
+end
+
+function input.runners.locate_file(instance)
+ instance.progname = environment.argument("progname") or instance.progname
+ instance.format = environment.argument("format") or instance.format
+ input.runners.report_location(instance,input.find_given_file(instance, environment.files[1] or ""))
+end
+
+function input.runners.report_location(instance,result)
+ if input.verbose then
+ input.report("")
+ if result and result ~= "" then
+ input.report(result)
+ else
+ input.report("not found")
+ end
+ else
+ io.write(result)
+ end
+end
+
+function input.runners.edit_script(instance,filename)
+ local editor = os.getenv("MTXRUN_EDITOR") or os.getenv("TEXMFSTART_EDITOR") or os.getenv("EDITOR") or 'scite'
+ local rest = input.resolve_prefixes(instance,filename)
+ if rest ~= "" then
+ os.launch(editor .. " " .. rest)
+ end
+end
+
+input.runners.launchers = {
+ windows = { },
+ unix = { }
+}
+
+function input.launch(str)
+ -- maybe we also need to test on mtxrun.launcher.suffix environment
+ -- variable or on windows consult the assoc and ftype vars and such
+ local launchers = input.runners.launchers[os.platform] if launchers then
+ local suffix = file.extname(str) if suffix then
+ local runner = launchers[suffix] if runner then
+ str = runner .. " " .. str
+ end
+ end
+ end
+ os.launch(str)
+end
+
+function input.runners.launch_file(instance,filename)
+ instance.allresults = true
+ input.verbose = true
+ local pattern = environment.arguments["pattern"]
+ if not pattern or pattern == "" then
+ pattern = filename
+ end
+ if not pattern or pattern == "" then
+ input.report("provide name or --pattern=")
+ else
+ local t = input.find_files(instance,environment.arguments["pattern"])
+ -- local t = input.aux.find_file(instance,"*/" .. pattern,true)
+ if t and #t > 0 then
+ if environment.arguments["all"] then
+ for _, v in pairs(t) do
+ input.report("launching", v)
+ input.launch(v)
+ end
+ else
+ input.report("launching", t[1])
+ input.launch(t[1])
+ end
+ else
+ input.report("no match for", pattern)
+ end
+ end
+end
+
+function input.runners.execute_ctx_script(instance,filename)
+ local before, after = environment.split_arguments(filename)
+ local suffix = ""
+ if not filename:find("%.lua$") then suffix = ".lua" end
+ local fullname = filename
+ -- just
+ fullname = filename .. suffix
+ fullname = input.find_file(instance,fullname)
+ -- mtx-
+ if not fullname or fullname == "" then
+ fullname = "mtx-" .. filename .. suffix
+ fullname = input.find_file(instance,fullname)
+ end
+ -- mtx-s
+ if not fullname or fullname == "" then
+ fullname = "mtx-" .. filename .. "s" .. suffix
+ fullname = input.find_file(instance,fullname)
+ end
+ -- mtx-
+ if not fullname or fullname == "" then
+ fullname = "mtx-" .. filename:gsub("s$","") .. suffix
+ fullname = input.find_file(instance,fullname)
+ end
+ -- that should do it
+ if fullname and fullname ~= "" then
+ local state = input.runners.prepare(instance)
+ if state == 'error' then
+ return false
+ elseif state == 'skip' then
+ return true
+ elseif state == "run" then
+ arg = { } for _,v in pairs(after) do arg[#arg+1] = v end
+ environment.initialize_arguments(arg)
+ filename = environment.files[1]
+ dofile(fullname)
+ return true
+ end
+ else
+ return false
+ end
+end
+
+input.report(banner,"\n")
+
+function input.help(banner,message)
+ if not input.verbose then
+ input.verbose = true
+ input.report(banner,"\n")
+ end
+ input.reportlines(message)
+end
+
+-- this is a bit dirty ... first we store the first filename and next we
+-- split the arguments so that we only see the ones meant for this script
+-- ... later we will use the second half
+
+local filename = environment.files[1] or ""
+local ok = true
+
+local before, after = environment.split_arguments(filename)
+environment.initialize_arguments(before)
+
+if environment.argument("selfmerge") then
+ -- embed used libraries
+ utils.merger.selfmerge(own.name,own.libs,own.list)
+elseif environment.argument("selfclean") then
+ -- remove embedded libraries
+ utils.merger.selfclean(own.name)
+elseif environment.arguments["selfupdate"] then
+ input.my_prepare_b(instance)
+ input.verbose = true
+ input.update_script(own.name,"mtxrun")
+elseif environment.argument("ctxlua") or environment.argument("internal") then
+ -- run a script by loading it (using libs)
+ input.runners.my_prepare_b(instance)
+ ok = input.runners.execute_script(instance,filename,true)
+elseif environment.argument("script") then
+ -- run a script by loading it (using libs), pass args
+ input.runners.my_prepare_b(instance)
+ ok = input.runners.execute_ctx_script(instance,filename)
+elseif environment.argument("execute") then
+ -- execute script
+ input.runners.my_prepare_b(instance)
+ ok = input.runners.execute_script(instance,filename)
+elseif environment.argument("direct") then
+ -- equals bin:
+ input.runners.my_prepare_b(instance)
+ ok = input.runners.execute_program(instance,filename)
+elseif environment.argument("edit") then
+ -- edit file
+ input.runners.my_prepare_b(instance)
+ input.runners.edit_script(instance,filename)
+elseif environment.argument("launch") then
+ input.runners.my_prepare_b(instance)
+ input.runners.launch_file(instance,filename)
+elseif environment.argument("make") then
+ -- make stubs
+ input.runners.handle_stubs(instance,true)
+elseif environment.argument("remove") then
+ -- remove stub
+ input.runners.handle_stubs(instance,false)
+elseif environment.argument("resolve") then
+ -- resolve string
+ input.runners.my_prepare_b(instance)
+ input.runners.resolve_string(instance)
+elseif environment.argument("locate") then
+ -- locate file
+ input.runners.my_prepare_b(instance)
+ input.runners.locate_file(instance)
+elseif environment.argument("help") or filename=='help' or filename == "" then
+ input.help(banner,messages.help)
+else
+ -- execute script
+ input.runners.my_prepare_b(instance)
+ if filename:find("^bin:") then
+ ok = input.runners.execute_program(instance,filename)
+ else
+ ok = input.runners.execute_script(instance,filename)
+ end
+end
+
+--~ if input.verbose then
+--~ input.report("")
+--~ input.report("runtime: " .. os.clock() .. " seconds")
+--~ end
+
+--~ if ok then
+--~ input.report("exit code: 0") os.exit(0)
+--~ else
+--~ input.report("exit code: 1") os.exit(1)
+--~ end
+
+if environment.platform == "unix" then
+ io.write("\n")
+end
diff --git a/scripts/context/lua/x-ldx.lua b/scripts/context/lua/x-ldx.lua
new file mode 100644
index 000000000..3914b2907
--- /dev/null
+++ b/scripts/context/lua/x-ldx.lua
@@ -0,0 +1,310 @@
+--[[ldx--
+
+
+This file is part of the documentation suite and
+itself serves as an example of using in combination
+with .
+
+I will rewrite this using lpeg once I have the time to study that nice new
+subsystem.
+--ldx]]--
+
+banner = "version 1.0.1 - 2007+ - PRAGMA ADE / CONTEXT"
+
+--[[
+This script needs a few libraries. Instead of merging the code here
+we can use
+
+
+mtxrun --internal x-ldx.lua
+
+
+That way, the libraries included in the runner will be used.
+]]--
+
+-- libraries l-string.lua l-table.lua l-io.lua l-file.lua
+
+-- begin library merge
+-- end library merge
+
+--[[
+Just a demo comment line. We will handle such multiline comments but
+only when they start and end at the beginning of a line. More rich
+comments are tagged differently.
+]]--
+
+--[[ldx--
+First we define a proper namespace for this module. The l stands for
+, the d for documentation and the x for
+.
+--ldx]]--
+
+if not ldx then ldx = { } end
+
+--[[ldx--
+We load the lua file into a table. The entries in this table themselves are
+tables and have keys like code and comment.
+--ldx]]--
+
+function ldx.load(filename)
+ local data = file.readdata(filename)
+ local expr = "%s*%-%-%[%[ldx%-*%s*(.-)%s*%-%-ldx%]%]%-*%s*"
+ local i, j, t = 0, 0, { }
+ while true do
+ local comment, ni
+ ni, j, comment = data:find(expr, j)
+ if not ni then break end
+ t[#t+1] = { code = data:sub(i, ni-1) }
+ t[#t+1] = { comment = comment }
+ i = j + 1
+ end
+ local str = data:sub(i, #data)
+ str = str:gsub("^%s*(.-)%s*$", "%1")
+ if #str > 0 then
+ t[#t+1] = { code = str }
+ end
+ return t
+end
+
+--[[ldx--
+We will tag keywords so that we can higlight them using a special font
+or color. Users can extend this list when needed.
+--ldx]]--
+
+ldx.keywords = { }
+
+--[[ldx--
+Here come the reserved words:
+--ldx]]--
+
+ldx.keywords.reserved = {
+ ["and"] = 1,
+ ["break"] = 1,
+ ["do"] = 1,
+ ["else"] = 1,
+ ["elseif"] = 1,
+ ["end"] = 1,
+ ["false"] = 1,
+ ["for"] = 1,
+ ["function"] = 1,
+ ["if"] = 1,
+ ["in"] = 1,
+ ["local"] = 1,
+ ["nil"] = 1,
+ ["not"] = 1,
+ ["or"] = 1,
+ ["repeat"] = 1,
+ ["return"] = 1,
+ ["then"] = 1,
+ ["true"] = 1,
+ ["until"] = 1,
+ ["while"] = 1
+}
+
+--[[ldx--
+We need to escape a few tokens. We keep the hash local to the
+definition but set it up only once, hence the do
+construction.
+--ldx]]--
+
+do
+ local e = { [">"] = ">", ["<"] = "<", ["&"] = "&" }
+ function ldx.escape(str)
+ return str:gsub("([><&])",e)
+ end
+end
+
+--[[ldx--
+Enhancing the code is a bit tricky due to the fact that we have to
+deal with strings and escaped quotes within these strings. Before we
+mess around with the code, we hide the strings, and after that we
+insert them again. Single and double quoted strings are tagged so
+that we can use a different font to highlight them.
+--ldx]]--
+
+ldx.make_index = true
+
+function ldx.enhance(data)
+ local e = ldx.escape
+ for _,v in pairs(data) do
+ if v.code then
+ local dqs, sqs, com, cmt, cod = { }, { }, { }, { }, e(v.code)
+ cod = cod:gsub("%-%-%[%[.-%]%]%-%-", function(s)
+ cmt[#cmt+1] = s
+ return "[[[[".. #cmt .."]]]]"
+ end)
+ cod = cod:gsub("%-%-([^\n]*)", function(s)
+ com[#com+1] = s
+ return "[[".. #com .."]]"
+ end)
+--~ cod = cod:gsub('\\"', "<<>>")
+--~ cod = cod:gsub("\\'", "<>")
+ cod = cod:gsub("(%b\"\")", function(s)
+ dqs[#dqs+1] = s:sub(2,-2)
+ return "<<<<".. #dqs ..">>>>"
+ end)
+ cod = cod:gsub("(%b\'\')", function(s)
+ sqs[#sqs+1] = s:sub(2,-2)
+ return "<<".. #sqs ..">>"
+ end)
+ cod = cod:gsub("(%a+)",function(key)
+ local class = ldx.keywords.reserved[key]
+ if class then
+ return "" .. key .. ""
+ else
+ return key
+ end
+ end)
+ cod = cod:gsub("%[%[%[%[(%d+)%]%]%]%]", function(s)
+ return cmt[tonumber(s)]
+ end)
+ cod = cod:gsub("%[%[(%d+)%]%]", function(s)
+ return "" .. com[tonumber(s)] .. ""
+ end)
+ cod = cod:gsub("<<<<(%d+)>>>>", function(s)
+ return "" .. dqs[tonumber(s)] .. ""
+ end)
+ cod = cod:gsub("<<(%d+)>>", function(s)
+ return "" .. sqs[tonumber(s)] .. ""
+ end)
+--~ cod = cod:gsub("<<>>", "\\\"")
+--~ cod = cod:gsub("<>" , "\\\'")
+ if ldx.make_index then
+ local lines = cod:split("\n")
+ local f = "(function)%s+([%w%.]+)%s*%("
+ for k,v in pairs(lines) do
+ -- functies
+ v = v:gsub(f,function(key, str)
+ return "" .. str .. "("
+ end)
+ -- variables
+ v = v:gsub("^([%w][%w%,%s]-)(=[^=])",function(str, rest)
+ local t = string.split(str, ",%s*")
+ for k,v in pairs(t) do
+ t[k] = "" .. v .. ""
+ end
+ return table.join(t,", ") .. rest
+ end)
+ -- so far
+ lines[k] = v
+ end
+ v.code = table.concat(lines,"\n")
+ else
+ v.code = cod
+ end
+ end
+ end
+end
+
+--[[ldx--
+We're now ready to save the file in format. This boils
+down to wrapping the code and comment as well as the whole document. We tag
+lines in the code as such so that we don't need messy CDATA constructs
+and by calculating the indentation we also avoid space troubles. It also makes
+it possible to change the indentation afterwards.
+--ldx]]--
+
+function ldx.as_xml(data)
+ local t, cmode = { }, false
+ t[#t+1] = "\n"
+ t[#t+1] = "\n\n"
+ for _,v in pairs(data) do
+ if v.code and not v.code:is_empty() then
+ t[#t+1] = "\n\n"
+ for k,v in pairs(v.code:split("\n")) do -- make this faster
+ local a, b = v:find("^(%s+)")
+ if a and b then
+ if cmode then
+ t[#t+1] = "" .. v:sub(b+1,#v) .. "\n"
+ else
+ t[#t+1] = "" .. v:sub(b+1,#v) .. "\n"
+ end
+ elseif v:is_empty() then
+ if cmode then
+ t[#t+1] = "\n"
+ else
+ t[#t+1] = "\n"
+ end
+ elseif v:find("^%-%-%[%[") then
+ t[#t+1] = "" .. v .. "\n"
+ cmode= true
+ elseif v:find("^%]%]%-%-") then
+ t[#t+1] = "" .. v .. "\n"
+ cmode= false
+ elseif cmode then
+ t[#t+1] = "" .. v .. "\n"
+ else
+ t[#t+1] = "" .. v .. "\n"
+ end
+ end
+ t[#t+1] = "\n"
+ elseif v.comment then
+ t[#t+1] = "\n\n" .. v.comment .. "\n\n"
+ else
+ -- cannot happen
+ end
+ end
+ t[#t+1] = "\n\n"
+ return table.concat(t,"")
+end
+
+--[[ldx--
+Saving the result is a trivial effort.
+--ldx]]--
+
+function ldx.save(filename,data)
+ file.savedata(filename,ldx.as_xml(data))
+end
+
+--[[ldx--
+The next function wraps it all in one call:
+--ldx]]--
+
+function ldx.convert(luaname,ldxname)
+ if not file.is_readable(luaname) then
+ luaname = luaname .. ".lua"
+ end
+ if file.is_readable(luaname) then
+ if not ldxname then
+ ldxname = file.replacesuffix(luaname,"ldx")
+ end
+ local data = ldx.load(luaname)
+ if data then
+ ldx.enhance(data)
+ if ldxname ~= luaname then
+ ldx.save(ldxname,data)
+ end
+ end
+ end
+end
+
+--[[ldx--
+This module can be used directly:
+
+
+mtxrun --internal x-ldx somefile.lua
+
+
+will produce an ldx file that can be processed with
+by running:
+
+
+texexec --use=x-ldx --forcexml somefile.ldx
+
+
+You can do this in one step by saying:
+
+
+texmfstart texexec --ctx=x-ldx somefile.lua
+
+
+This will trigger into loading the mentioned
+ file. That file describes the conversion as well
+as the module to be used.
+
+The main conversion call is:
+--ldx]]--
+
+if arg and arg[1] then
+ ldx.convert(arg[1],arg[2])
+end
diff --git a/scripts/context/ruby/base/merge.rb b/scripts/context/ruby/base/merge.rb
index ad4b9c9e6..a66b97e91 100644
--- a/scripts/context/ruby/base/merge.rb
+++ b/scripts/context/ruby/base/merge.rb
@@ -38,7 +38,7 @@ module SelfMerge
@@modules = $".collect do |file| File.expand_path(file) end
@@modules.delete_if do |file|
- file !~ /^#{@@ownpath}\/#{@@modroot}.*$/
+ file !~ /^#{@@ownpath}\/#{@@modroot}.*$/i
end
def SelfMerge::ok?
diff --git a/scripts/context/ruby/ctxtools.rb b/scripts/context/ruby/ctxtools.rb
index 85b079ee1..724f5593f 100644
--- a/scripts/context/ruby/ctxtools.rb
+++ b/scripts/context/ruby/ctxtools.rb
@@ -334,7 +334,7 @@ class Commands
report("generating #{keyfile}")
begin
- one = "texexec --make --alone --all #{interface}"
+ one = "texexec --make --all #{interface}"
two = "texexec --batch --silent --interface=#{interface} x-set-01"
if @commandline.option("force") then
system(one)
@@ -1639,23 +1639,22 @@ class Commands
def dpxmapfiles
- force = @commandline.option("force")
+ force = @commandline.option("force")
texmfroot = @commandline.argument('first')
texmfroot = '.' if texmfroot.empty?
- if @commandline.option('maproot') then
+ if @commandline.option('maproot') != "" then
maproot = @commandline.option('maproot')
else
maproot = "#{texmfroot.gsub(/\\/,'/')}/fonts/map/pdftex/context"
end
-
if File.directory?(maproot) then
files = Dir.glob("#{maproot}/*.map")
if files.size > 0 then
files.each do |pdffile|
next if File.basename(pdffile) == 'pdftex.map'
pdffile = File.expand_path(pdffile)
- dpxfile = File.expand_path(pdffile.sub(/pdftex/i,'dvipdfm'))
+ dpxfile = File.expand_path(pdffile.sub(/(dvips|pdftex)/i,'dvipdfm'))
unless pdffile == dpxfile then
begin
if data = File.read(pdffile) then
@@ -2654,7 +2653,13 @@ class Commands
end
def remakeformats
- return system("texmfstart texexec --make --all")
+ return system("mktexlsr")
+ return system("luatools --selfupdate")
+ return system("mtxrun --selfupdate")
+ return system("luatools --generate")
+ return system("texmfstart texexec --make --all --fast --pdftex")
+ return system("texmfstart texexec --make --all --fast --luatex")
+ return system("texmfstart texexec --make --all --fast --xetex")
end
if localtree = locatedlocaltree then
diff --git a/scripts/context/ruby/mtxtools.rb b/scripts/context/ruby/mtxtools.rb
new file mode 100644
index 000000000..41d0f7085
--- /dev/null
+++ b/scripts/context/ruby/mtxtools.rb
@@ -0,0 +1,475 @@
+#!/usr/bin/env ruby
+
+# program : mtxtools
+# copyright : PRAGMA Advanced Document Engineering
+# version : 2004-2005
+# author : Hans Hagen
+#
+# info : j.hagen@xs4all.nl
+# www : www.pragma-ade.com
+
+# This script hosts MetaTeX related features.
+
+banner = ['MtxTools', 'version 1.0.0', '2006', 'PRAGMA ADE/POD']
+
+$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
+
+require 'base/switch'
+require 'base/logger'
+require 'base/system'
+require 'base/kpse'
+
+class Reporter
+ def report(str)
+ puts(str)
+ end
+end
+
+module ConTeXt
+
+ def ConTeXt::banner(filename,companionname,compact=false)
+ "-- filename : #{File.basename(filename)}\n" +
+ "-- comment : companion to #{File.basename(companionname)} (in ConTeXt)\n" +
+ "-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL\n" +
+ "-- copyright: PRAGMA ADE / ConTeXt Development Team\n" +
+ "-- license : see context related readme files\n" +
+ if compact then "\n-- remark : compact version\n" else "" end
+ end
+
+end
+
+class UnicodeTables
+
+ @@version = "1.001"
+
+ @@shape_a = /^((GREEK|LATIN|HEBREW)\s*(SMALL|CAPITAL|)\s*LETTER\s*[A-Z]+)$/
+ @@shape_b = /^((GREEK|LATIN|HEBREW)\s*(SMALL|CAPITAL|)\s*LETTER\s*[A-Z]+)\s*(.+)$/
+
+ @@shape_a = /^(.*\s*LETTER\s*[A-Z]+)$/
+ @@shape_b = /^(.*\s*LETTER\s*[A-Z]+)\s+WITH\s+(.+)$/
+
+ attr_accessor :context, :comment
+
+ def initialize(logger=Reporter.new)
+ @data = Array.new
+ @logger = logger
+ @error = false
+ @context = true
+ @comment = true
+ @shapes = Hash.new
+ end
+
+ def load_unicode_data(filename='unicodedata.txt')
+ # beware, the unicodedata table is bugged, sometimes ending
+ @logger.report("reading base data from #{filename}") if @logger
+ begin
+ IO.readlines(filename).each do |line|
+ if line =~ /^[0-9A-F]{4,4}/ then
+ d = line.chomp.sub(/\;$/, '').split(';')
+ if d then
+ while d.size < 15 do d << '' end
+ n = d[0].hex
+ @data[n] = d
+ if d[1] =~ @@shape_a then
+ @shapes[$1] = d[0]
+ end
+ end
+ end
+ end
+ rescue
+ @error = true
+ @logger.report("error while reading base data from #{filename}") if @logger
+ end
+ end
+
+ def load_context_data(filename='contextnames.txt')
+ @logger.report("reading data from #{filename}") if @logger
+ begin
+ IO.readlines(filename).each do |line|
+ if line =~ /^[0-9A-F]{4,4}/ then
+ d = line.chomp.split(';')
+ if d then
+ n = d[0].hex
+ if @data[n] then
+ @data[d[0].hex] << d[1] # adobename == 15
+ @data[d[0].hex] << d[2] # contextname == 16
+ else
+ @logger.report("missing information about #{d} in #{filename}") if @logger
+ end
+ end
+ end
+ end
+ rescue
+ @error = true
+ @logger.report("error while reading context data from #{filename}") if @logger
+ end
+ end
+
+ def save_metatex_data(filename='char-def.lua',compact=false)
+ if not @error then
+ begin
+ File.open(filename,'w') do |f|
+ @logger.report("saving data in #{filename}") if @logger
+ f << ConTeXt::banner(filename,'char-def.tex',compact)
+ f << "\n"
+ f << "\nif not versions then versions = { } end versions['#{filename.gsub(/\..*?$/,'')}'] = #{@@version}\n"
+ f << "\n"
+ f << "if not characters then characters = { } end\n"
+ f << "if not characters.data then characters.data = { } end\n"
+ f << "\n"
+ f << "characters.data = {\n" if compact
+ @data.each do |d|
+ if d then
+ r = metatex_data(d)
+ if compact then
+ f << "\t" << "[0x#{d[0]}]".rjust(8,' ') << " = { #{r.join(", ").gsub(/\t/,'')} }, \n"
+ else
+ f << "characters.define { -- #{d[0].hex}" << "\n"
+ f << r.join(",\n") << "\n"
+ f << "}" << "\n"
+ end
+ end
+ end
+ f << "}\n" if compact
+ end
+ rescue
+ @logger.report("error while saving data in #{filename}") if @logger
+ else
+ @logger.report("#{@data.size} (#{sprintf('%X',@data.size)}) entries saved in #{filename}") if @logger
+ end
+ else
+ @logger.report("not saving data in #{filename} due to previous error") if @logger
+ end
+ end
+
+ def metatex_data(d)
+ r = Array.new
+ r << "\tunicodeslot=0x#{d[0]}"
+ if d[2] && ! d[2].empty? then
+ r << "\tcategory='#{d[2].downcase}'"
+ end
+ if @context then
+ if d[15] && ! d[15].empty? then
+ r << "\tadobename='#{d[15]}'"
+ end
+ if d[16] && ! d[16].empty? then
+ r << "\tcontextname='#{d[16]}'"
+ end
+ end
+ if @comment then
+ if d[1] == "" then
+ r << "\tdescription='#{d[10]}'" unless d[10].empty?
+ else
+ r << "\tdescription='#{d[1]}'" unless d[1].empty?
+ end
+ end
+ if d[1] =~ @@shape_b then
+ r << "\tshcode=0x#{@shapes[$1]}" if @shapes[$1]
+ end
+ if d[12] && ! d[12].empty? then
+ r << "\tuccode=0x#{d[12]}"
+ elsif d[14] && ! d[14].empty? then
+ r << "\tuccode=0x#{d[14]}"
+ end
+ if d[13] && ! d[13].empty? then
+ r << "\tlccode=0x#{d[13]}"
+ end
+ if d[5] && ! d[5].empty? then
+ special, specials = '', Array.new
+ c = d[5].split(/\s+/).collect do |cc|
+ if cc =~ /^\<(.*)\>$/io then
+ special = $1.downcase
+ else
+ specials << "0x#{cc}"
+ end
+ end
+ if specials.size > 0 then
+ special = 'char' if special.empty?
+ r << "\tspecials={'#{special}',#{specials.join(',')}}"
+ end
+ end
+ return r
+ end
+
+ def save_xetex_data(filename='enco-utf.tex')
+ if not @error then
+ begin
+ minnumber, maxnumber, n = 0x001F, 0xFFFF, 0
+ File.open(filename,'w') do |f|
+ @logger.report("saving data in #{filename}") if @logger
+ f << "% filename : #{filename}\n"
+ f << "% comment : poor man's alternative for a proper enco file\n"
+ f << "% this file is generated by mtxtools and can be\n"
+ f << "% used in xetex and luatex mkii mode\n"
+ f << "% author : Hans Hagen, PRAGMA-ADE, Hasselt NL\n"
+ f << "% copyright: PRAGMA ADE / ConTeXt Development Team\n"
+ f << "% license : see context related readme files\n"
+ f << "\n"
+ f << "\\ifx\\setcclcucx\\undefined\n"
+ f << "\n"
+ f << " \\def\\setcclcucx #1 #2 #3 %\n"
+ f << " {\\global\\catcode\"#1=11 \n"
+ f << " \\global\\lccode \"#1=\"#2 \n"
+ f << " \\global\\uccode \"#1=\"#3 }\n"
+ f << "\n"
+ f << "\\fi\n"
+ f << "\n"
+ @data.each do |d|
+ if d then
+ number, type = d[0], d[2].downcase
+ if number.hex >= minnumber && number.hex <= maxnumber && type =~ /^l(l|u|t)$/o then
+ if d[13] && ! d[13].empty? then
+ lc = d[13]
+ else
+ lc = number
+ end
+ if d[12] && ! d[12].empty? then
+ uc = d[12]
+ elsif d[14] && ! d[14].empty? then
+ uc = d[14]
+ else
+ uc = number
+ end
+ if @comment then
+ f << "\\setcclcuc #{number} #{lc} #{uc} % #{d[1]}\n"
+ else
+ f << "\\setcclcuc #{number} #{lc} #{uc} \n"
+ end
+ n += 1
+ end
+ end
+ end
+ f << "\n"
+ f << "\\endinput\n"
+ end
+ rescue
+ @logger.report("error while saving data in #{filename}") if @logger
+ else
+ @logger.report("#{n} entries saved in #{filename}") if @logger
+ end
+ else
+ @logger.report("not saving data in #{filename} due to previous error") if @logger
+ end
+ end
+
+end
+
+class RegimeTables
+
+ @@version = "1.001"
+
+ def initialize(logger=Reporter.new)
+ @logger = logger
+ reset
+ end
+
+ def reset
+ @code, @regime, @filename, @loaded = Array.new(256), '', '', false
+ (32..127).each do |i|
+ @code[i] = [sprintf('%04X',i), i.chr]
+ end
+ end
+
+ def load(filename)
+ begin
+ reset
+ if filename =~ /regi\-(ini|run|uni|utf|syn)/ then
+ report("skipping #{filename}")
+ else
+ report("loading file #{filename}")
+ @regime, unicodeset = File.basename(filename).sub(/\..*?$/,''), false
+ IO.readlines(filename).each do |line|
+ case line
+ when /^\#/ then
+ # skip
+ when /^(0x[0-9A-F]+)\s+(0x[0-9A-F]+)\s+\#\s+(.*)$/ then
+ @code[$1.hex], unicodeset = [$2, $3], true
+ when /^(0x[0-9A-F]+)\s+(0x[0-9A-F]+)\s+/ then
+ @code[$1.hex], unicodeset = [$2, ''], true
+ end
+ end
+ reset if not unicodeset
+ end
+ rescue
+ report("problem in loading file #{filename}")
+ reset
+ else
+ if ! @regime.empty? then
+ @loaded = true
+ else
+ reset
+ end
+ end
+ end
+
+ def save(filename,compact=false)
+ begin
+ if @loaded && ! @regime.empty? then
+ if File.expand_path(filename) == File.expand_path(@filename) then
+ report("saving in #{filename} is blocked")
+ else
+ report("saving file #{filename}")
+ File.open(filename,'w') do |f|
+ f << ConTeXt::banner(filename,'regi-ini.tex',compact)
+ f << "\n"
+ f << "\nif not versions then versions = { } end versions['#{filename.gsub(/\..*?$/,'')}'] = #{@@version}\n"
+ f << "\n"
+ f << "if not regimes then regimes = { } end\n"
+ f << "if not regimes.data then regimes.data = { } end\n"
+ f << "\n"
+ if compact then
+ f << "regimes.data[\"#{@regime}\"] = { [0] = \n\t"
+ i = 17
+ @code.each_index do |c|
+ if (i-=1) == 0 then
+ i = 16
+ f << "\n\t"
+ end
+ if @code[c] then
+ f << @code[c][0].rjust(6,' ')
+ else
+ f << "0x0000".rjust(6,' ')
+ end
+ f << ', ' if c<@code.length-1
+ end
+ f << "\n}\n"
+ else
+ @code.each_index do |c|
+ if @code[c] then
+ f << someregimeslot(@regime,c,@code[c][0],@code[c][1])
+ else
+ f << someregimeslot(@regime,c,'','')
+ end
+ end
+ end
+ end
+ end
+ end
+ rescue
+ report("problem in saving file #{filename} #{$!}")
+ end
+ end
+
+ def report(str)
+ @logger.report(str)
+ end
+
+ private
+
+ def someregimeslot(regime,slot,unicodeslot,comment)
+ "regimes.define { #{if comment.empty? then '' else '-- ' end} #{comment}\n" +
+ "\tregime='#{regime}',\n" +
+ "\tslot='#{sprintf('0x%02X',slot)}',\n" +
+ "\tunicodeslot='#{if unicodeslot.empty? then '0x0000' else unicodeslot end}'\n" +
+ "}\n"
+ end
+
+ public
+
+ def RegimeTables::convert(filenames,compact=false)
+ filenames.each do |filename|
+ txtfile = File.expand_path(filename)
+ luafile = File.join(File.dirname(txtfile),'regi-'+File.basename(txtfile.sub(/\..*?$/, '.lua')))
+ unless txtfile == luafile then
+ regime = RegimeTables.new
+ regime.load(txtfile)
+ regime.save(luafile,compact)
+ end
+ end
+ end
+
+end
+
+class Commands
+
+ include CommandBase
+
+ def unicodetable
+ unicode = UnicodeTables.new(logger)
+ unicode.load_unicode_data
+ unicode.load_context_data
+ unicode.save_metatex_data('char-def.lua',@commandline.option('compact'))
+ end
+
+ def xetextable
+ unicode = UnicodeTables.new(logger)
+ unicode.load_unicode_data
+ unicode.load_context_data
+ # unicode.comment = false
+ unicode.save_xetex_data
+ end
+
+ def regimetable
+ if @commandline.arguments.length > 0 then
+ RegimeTables::convert(@commandline.arguments, @commandline.option('compact'))
+ else
+ RegimeTables::convert(Dir.glob("cp*.txt") , @commandline.option('compact'))
+ RegimeTables::convert(Dir.glob("8859*.txt") , @commandline.option('compact'))
+ end
+ end
+
+ def pdftextable
+ # instead of directly saving the data, we use luatex (kind of test)
+ pdfrdef = 'pdfr-def.tex'
+ tmpfile = 'mtxtools.tmp'
+ File.delete(pdfrdef) rescue false
+ if f = File.open(tmpfile,'w') then
+ f << "\\starttext\n"
+ f << "\\ctxlua{characters.pdftex.make_pdf_to_unicodetable('#{pdfrdef}')}\n"
+ f << "\\stoptext\n"
+ f.close()
+ system("texmfstart texexec --luatex --once --purge mtxtools.tmp")
+ report("vecor saved in #{pdfrdef}")
+ end
+ File.delete(tmpfile) rescue false
+ end
+
+ def xmlmapfile
+ # instead of directly saving the data, we use luatex (kind of test)
+ tmpfile = 'mtxtools.tmp'
+ xmlsuffix = 'frx'
+ @commandline.arguments.each do |mapname|
+ if f = File.open(tmpfile,'w') then
+ xmlname = mapname.gsub(/\.map$/,".#{xmlsuffix}")
+ File.delete(xmlname) rescue false
+ f << "\\starttext\n"
+ f << "\\ctxlua{\n"
+ f << " mapname = input.find_file(texmf.instance,'#{mapname}') or ''\n"
+ f << " xmlname = '#{xmlname}'\n"
+ f << " if mapname and not mapname:is_empty() then\n"
+ f << " ctx.fonts.map.convert_file(mapname,xmlname)\n"
+ f << " end\n"
+ f << "}\n"
+ f << "\\stoptext\n"
+ f.close()
+ system("texmfstart texexec --luatex --once --purge mtxtools.tmp")
+ if FileTest.file?(xmlname) then
+ report("map file #{mapname} converted to #{xmlname}")
+ else
+ report("no valid map file #{mapname}")
+ end
+ end
+ end
+ File.delete(tmpfile) rescue false
+ end
+
+end
+
+logger = Logger.new(banner.shift)
+commandline = CommandLine.new
+
+commandline.registeraction('unicodetable', 'create unicode table for metatex/luatex')
+commandline.registeraction('regimetable' , 'create regime table(s) for metatex/luatex [--compact]')
+commandline.registeraction('xetextable' , 'create unicode table for xetex')
+commandline.registeraction('pdftextable' , 'create unicode table for xetex')
+commandline.registeraction('xmlmapfile' , 'convert traditional mapfile to xml font resourse')
+
+# general
+
+commandline.registeraction('help')
+commandline.registeraction('version')
+commandline.registerflag('compact')
+
+commandline.expand
+
+Commands.new(commandline,logger,banner).send(commandline.action || 'help')
diff --git a/scripts/context/ruby/texmfstart.rb b/scripts/context/ruby/texmfstart.rb
index 4273de7bc..02d17d8f8 100644
--- a/scripts/context/ruby/texmfstart.rb
+++ b/scripts/context/ruby/texmfstart.rb
@@ -36,6 +36,8 @@ $: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.u
require "rbconfig"
require "md5"
+# funny, selfmergs was suddenly broken to case problems
+
# kpse_merge_done: require 'base/kpseremote'
# kpse_merge_done: require 'base/kpsedirect'
# kpse_merge_done: require 'base/kpsefast'
@@ -43,127 +45,7 @@ require "md5"
# kpse_merge_start
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/kpseremote.rb'
-
-# kpse_merge_done: require 'base/kpsefast'
-
-case ENV['KPSEMETHOD']
- when /soap/o then # kpse_merge_done: require 'base/kpse/soap'
- when /drb/o then # kpse_merge_done: require 'base/kpse/drb'
- else # kpse_merge_done: require 'base/kpse/drb'
-end
-
-class KpseRemote
-
- @@port = ENV['KPSEPORT'] || 7000
- @@method = ENV['KPSEMETHOD'] || 'drb'
-
- def KpseRemote::available?
- @@method && @@port
- end
-
- def KpseRemote::start_server(port=nil)
- kpse = KpseServer.new(port || @@port)
- kpse.start
- end
-
- def KpseRemote::start_client(port=nil) # keeps object in server
- kpseclient = KpseClient.new(port || @@port)
- kpseclient.start
- kpse = kpseclient.object
- tree = kpse.choose(KpseUtil::identify, KpseUtil::environment)
- [kpse, tree]
- end
-
- def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object
- kpseclient = KpseClient.new(port || @@port)
- kpseclient.start
- kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil
- end
-
- def initialize(port=nil)
- if KpseRemote::available? then
- begin
- @kpse, @tree = KpseRemote::start_client(port)
- rescue
- @kpse, @tree = nil, nil
- end
- else
- @kpse, @tree = nil, nil
- end
- end
-
- def progname=(value)
- @kpse.set(@tree,'progname',value)
- end
- def format=(value)
- @kpse.set(@tree,'format',value)
- end
- def engine=(value)
- @kpse.set(@tree,'engine',value)
- end
-
- def progname
- @kpse.get(@tree,'progname')
- end
- def format
- @kpse.get(@tree,'format')
- end
- def engine
- @kpse.get(@tree,'engine')
- end
-
- def load
- @kpse.load(KpseUtil::identify, KpseUtil::environment)
- end
- def okay?
- @kpse && @tree
- end
- def set(key,value)
- @kpse.set(@tree,key,value)
- end
- def load_cnf
- @kpse.load_cnf(@tree)
- end
- def load_lsr
- @kpse.load_lsr(@tree)
- end
- def expand_variables
- @kpse.expand_variables(@tree)
- end
- def expand_braces(str)
- clean_name(@kpse.expand_braces(@tree,str))
- end
- def expand_path(str)
- clean_name(@kpse.expand_path(@tree,str))
- end
- def expand_var(str)
- clean_name(@kpse.expand_var(@tree,str))
- end
- def show_path(str)
- clean_name(@kpse.show_path(@tree,str))
- end
- def var_value(str)
- clean_name(@kpse.var_value(@tree,str))
- end
- def find_file(filename)
- clean_name(@kpse.find_file(@tree,filename))
- end
- def find_files(filename,first=false)
- # dodo: each filename
- @kpse.find_files(@tree,filename,first)
- end
-
- private
-
- def clean_name(str)
- str.gsub(/\\/,'/')
- end
-
-end
-
-
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/kpsefast.rb'
+# kpse_merge_file: 't:/ruby/base/kpsefast.rb'
# module : base/kpsefast
# copyright : PRAGMA Advanced Document Engineering
@@ -1097,67 +979,7 @@ end
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/kpse/drb.rb'
-
-require 'drb'
-# kpse_merge_done: require 'base/kpse/trees'
-
-class KpseServer
-
- attr_accessor :port
-
- def initialize(port=7000)
- @port = port
- end
-
- def start
- puts "starting drb service at port #{@port}"
- DRb.start_service("druby://localhost:#{@port}", KpseTrees.new)
- trap(:INT) do
- DRb.stop_service
- end
- DRb.thread.join
- end
-
- def stop
- # todo
- end
-
-end
-
-class KpseClient
-
- attr_accessor :port
-
- def initialize(port=7000)
- @port = port
- @kpse = nil
- end
-
- def start
- # only needed when callbacks are used / slow, due to Socket::getaddrinfo
- # DRb.start_service
- end
-
- def object
- @kpse = DRbObject.new(nil,"druby://localhost:#{@port}")
- end
-
-end
-
-
-# SERVER_URI="druby://localhost:8787"
-#
-# # Start a local DRbServer to handle callbacks.
-# #
-# # Not necessary for this small example, but will be required
-# # as soon as we pass a non-marshallable object as an argument
-# # to a dRuby call.
-# DRb.start_service
-#
-
-
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/kpse/trees.rb'
+# kpse_merge_file: 't:/ruby/base/kpse/trees.rb'
require 'monitor'
# kpse_merge_done: require 'base/kpsefast'
@@ -1245,179 +1067,219 @@ class KpseTrees < Monitor
end
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/kpsedirect.rb'
+# kpse_merge_file: 't:/ruby/base/kpse/drb.rb'
-class KpseDirect
+require 'drb'
+# kpse_merge_done: require 'base/kpse/trees'
- attr_accessor :progname, :format, :engine
+class KpseServer
- def initialize
- @progname, @format, @engine = '', '', ''
- end
+ attr_accessor :port
- def expand_path(str)
- clean_name(`kpsewhich -expand-path=#{str}`.chomp)
+ def initialize(port=7000)
+ @port = port
end
- def expand_var(str)
- clean_name(`kpsewhich -expand-var=#{str}`.chomp)
+ def start
+ puts "starting drb service at port #{@port}"
+ DRb.start_service("druby://localhost:#{@port}", KpseTrees.new)
+ trap(:INT) do
+ DRb.stop_service
+ end
+ DRb.thread.join
end
- def find_file(str)
- clean_name(`kpsewhich #{_progname_} #{_format_} #{str}`.chomp)
+ def stop
+ # todo
end
- def _progname_
- if @progname.empty? then '' else "-progname=#{@progname}" end
- end
- def _format_
- if @format.empty? then '' else "-format=\"#{@format}\"" end
+end
+
+class KpseClient
+
+ attr_accessor :port
+
+ def initialize(port=7000)
+ @port = port
+ @kpse = nil
end
- private
+ def start
+ # only needed when callbacks are used / slow, due to Socket::getaddrinfo
+ # DRb.start_service
+ end
- def clean_name(str)
- str.gsub(/\\/,'/')
+ def object
+ @kpse = DRbObject.new(nil,"druby://localhost:#{@port}")
end
end
-# kpse_merge_file: 'c:/data/develop/context/ruby/base/merge.rb'
-
-# module : base/merge
-# copyright : PRAGMA Advanced Document Engineering
-# version : 2006
-# author : Hans Hagen
+# SERVER_URI="druby://localhost:8787"
+#
+# # Start a local DRbServer to handle callbacks.
+# #
+# # Not necessary for this small example, but will be required
+# # as soon as we pass a non-marshallable object as an argument
+# # to a dRuby call.
+# DRb.start_service
#
-# project : ConTeXt / eXaMpLe
-# concept : Hans Hagen
-# info : j.hagen@xs4all.nl
-# this module will package all the used modules in the file itself
-# so that we can relocate the file at wish, usage:
-#
-# merge:
-#
-# unless SelfMerge::ok? && SelfMerge::merge then
-# puts("merging should happen on the path were the base inserts reside")
-# end
-#
-# cleanup:
-#
-# unless SelfMerge::cleanup then
-# puts("merging should happen on the path were the base inserts reside")
+# kpse_merge_file: 't:/ruby/base/kpseremote.rb'
-module SelfMerge
+# kpse_merge_done: require 'base/kpsefast'
- @@kpsemergestart = "\# kpse_merge_start"
- @@kpsemergestop = "\# kpse_merge_stop"
- @@kpsemergefile = "\# kpse_merge_file: "
- @@kpsemergedone = "\# kpse_merge_done: "
+case ENV['KPSEMETHOD']
+ when /soap/o then # kpse_merge_done: require 'base/kpse/soap'
+ when /drb/o then # kpse_merge_done: require 'base/kpse/drb'
+ else # kpse_merge_done: require 'base/kpse/drb'
+end
- @@filename = File.basename($0)
- @@ownpath = File.expand_path(File.dirname($0))
- @@modroot = '(base|graphics|rslb|www)' # needed in regex in order not to mess up SelfMerge
- @@modules = $".collect do |file| File.expand_path(file) end
+class KpseRemote
- @@modules.delete_if do |file|
- file !~ /^#{@@ownpath}\/#{@@modroot}.*$/
+ @@port = ENV['KPSEPORT'] || 7000
+ @@method = ENV['KPSEMETHOD'] || 'drb'
+
+ def KpseRemote::available?
+ @@method && @@port
end
- def SelfMerge::ok?
- begin
- @@modules.each do |file|
- return false unless FileTest.file?(file)
- end
- rescue
- return false
- else
- return true
- end
+ def KpseRemote::start_server(port=nil)
+ kpse = KpseServer.new(port || @@port)
+ kpse.start
end
- def SelfMerge::merge
- begin
- if SelfMerge::ok? && rbfile = IO.read(@@filename) then
- begin
- inserts = "#{@@kpsemergestart}\n\n"
- @@modules.each do |file|
- inserts << "#{@@kpsemergefile}'#{file}'\n\n"
- inserts << IO.read(file).gsub(/^#.*?\n$/,'')
- inserts << "\n\n"
- end
- inserts << "#{@@kpsemergestop}\n\n"
- # no gsub! else we end up in SelfMerge
- rbfile.sub!(/#{@@kpsemergestart}\s*#{@@kpsemergestop}/moi) do
- inserts
- end
- rbfile.gsub!(/^(.*)(require [\"\'].*?#{@@modroot}.*)$/) do
- pre, post = $1, $2
- if pre =~ /#{@@kpsemergedone}/ then
- "#{pre}#{post}"
- else
- "#{pre}#{@@kpsemergedone}#{post}"
- end
- end
- rescue
- return false
- else
- begin
- File.open(@@filename,'w') do |f|
- f << rbfile
- end
- rescue
- return false
- end
- end
- end
- rescue
- return false
- else
- return true
- end
+ def KpseRemote::start_client(port=nil) # keeps object in server
+ kpseclient = KpseClient.new(port || @@port)
+ kpseclient.start
+ kpse = kpseclient.object
+ tree = kpse.choose(KpseUtil::identify, KpseUtil::environment)
+ [kpse, tree]
end
- def SelfMerge::cleanup
- begin
- if rbfile = IO.read(@@filename) then
- begin
- rbfile.sub!(/#{@@kpsemergestart}(.*)#{@@kpsemergestop}\s*/moi) do
- "#{@@kpsemergestart}\n\n#{@@kpsemergestop}\n\n"
- end
- rbfile.gsub!(/^(.*#{@@kpsemergedone}.*)$/) do
- str = $1
- if str =~ /require [\"\']/ then
- str.gsub(/#{@@kpsemergedone}/, '')
- else
- str
- end
- end
- rescue
- return false
- else
- begin
- File.open(@@filename,'w') do |f|
- f << rbfile
- end
- rescue
- return false
- end
- end
+ def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object
+ kpseclient = KpseClient.new(port || @@port)
+ kpseclient.start
+ kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil
+ end
+
+ def initialize(port=nil)
+ if KpseRemote::available? then
+ begin
+ @kpse, @tree = KpseRemote::start_client(port)
+ rescue
+ @kpse, @tree = nil, nil
end
- rescue
- return false
else
- return true
+ @kpse, @tree = nil, nil
end
end
- def SelfMerge::replace
- if SelfMerge::ok? then
- SelfMerge::cleanup
- SelfMerge::merge
- end
+ def progname=(value)
+ @kpse.set(@tree,'progname',value)
+ end
+ def format=(value)
+ @kpse.set(@tree,'format',value)
+ end
+ def engine=(value)
+ @kpse.set(@tree,'engine',value)
+ end
+
+ def progname
+ @kpse.get(@tree,'progname')
+ end
+ def format
+ @kpse.get(@tree,'format')
+ end
+ def engine
+ @kpse.get(@tree,'engine')
+ end
+
+ def load
+ @kpse.load(KpseUtil::identify, KpseUtil::environment)
+ end
+ def okay?
+ @kpse && @tree
+ end
+ def set(key,value)
+ @kpse.set(@tree,key,value)
+ end
+ def load_cnf
+ @kpse.load_cnf(@tree)
+ end
+ def load_lsr
+ @kpse.load_lsr(@tree)
+ end
+ def expand_variables
+ @kpse.expand_variables(@tree)
+ end
+ def expand_braces(str)
+ clean_name(@kpse.expand_braces(@tree,str))
+ end
+ def expand_path(str)
+ clean_name(@kpse.expand_path(@tree,str))
+ end
+ def expand_var(str)
+ clean_name(@kpse.expand_var(@tree,str))
+ end
+ def show_path(str)
+ clean_name(@kpse.show_path(@tree,str))
+ end
+ def var_value(str)
+ clean_name(@kpse.var_value(@tree,str))
+ end
+ def find_file(filename)
+ clean_name(@kpse.find_file(@tree,filename))
+ end
+ def find_files(filename,first=false)
+ # dodo: each filename
+ @kpse.find_files(@tree,filename,first)
+ end
+
+ private
+
+ def clean_name(str)
+ str.gsub(/\\/,'/')
+ end
+
+end
+
+
+# kpse_merge_file: 't:/ruby/base/kpsedirect.rb'
+
+class KpseDirect
+
+ attr_accessor :progname, :format, :engine
+
+ def initialize
+ @progname, @format, @engine = '', '', ''
+ end
+
+ def expand_path(str)
+ clean_name(`kpsewhich -expand-path=#{str}`.chomp)
+ end
+
+ def expand_var(str)
+ clean_name(`kpsewhich -expand-var=#{str}`.chomp)
+ end
+
+ def find_file(str)
+ clean_name(`kpsewhich #{_progname_} #{_format_} #{str}`.chomp)
+ end
+
+ def _progname_
+ if @progname.empty? then '' else "-progname=#{@progname}" end
+ end
+ def _format_
+ if @format.empty? then '' else "-format=\"#{@format}\"" end
+ end
+
+ private
+
+ def clean_name(str)
+ str.gsub(/\\/,'/')
end
end
@@ -2468,7 +2330,7 @@ def show_environment
end
end
-def execute(arguments)
+def execute(arguments) # br global
arguments = arguments.split(/\s+/) if arguments.class == String
$directives = hashed(arguments)
diff --git a/tex/context/base/attr-ini.lua b/tex/context/base/attr-ini.lua
new file mode 100644
index 000000000..4469608c4
--- /dev/null
+++ b/tex/context/base/attr-ini.lua
@@ -0,0 +1,669 @@
+if not modules then modules = { } end modules ['attr-ini'] = {
+ version = 1.001,
+ comment = "companion to attr-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--
+-- attributes
+--
+
+nodes = nodes or { }
+
+-- We can distinguish between rules and glyphs but it's not worth the trouble. A
+-- first implementation did that and while it saves a bit for glyphs and rules, it
+-- costs more resourses for transparencies. So why bother.
+
+-- namespace for all those features / plural becomes singular
+
+-- i will do the resource stuff later, when we have an interface to pdf (ok, i can
+-- fake it with tokens but it will take some coding
+
+function totokens(str)
+ local t = { }
+ for c in string.bytes(str) do
+ t[#t+1] = { 12, c }
+ end
+ return t
+end
+
+function pdfliteral(str)
+ local t = node.new('whatsit',8)
+ t.mode = 1 -- direct
+ t.data = str -- totokens(str)
+ return t
+end
+
+-- shipouts
+
+shipouts = shipouts or { }
+shipouts.plugins = shipouts.plugins or { }
+
+do
+
+ local hlist, vlist = node.id('hlist'), node.id('vlist')
+
+ local function do_process_page(attribute,processor,head) -- maybe work with ranges
+ local previous, stack = nil, head
+ while stack do
+ local id = stack.id
+ if id == hlist or id == vlist then
+ local content = stack.list
+ if content then
+ stack.list = do_process_page(attribute,processor,content)
+ end
+ else
+ stack, previous, head = processor(attribute,stack,previous,head)
+ end
+ previous = stack
+ stack = stack.next
+ end
+ return head
+ end
+
+ function nodes.process_page(head)
+ if head then
+ input.start_timing(nodes)
+ local done, used = false, { }
+ for name, plugin in pairs(shipouts.plugins) do
+ local attribute = attributes.numbers[name]
+ if attribute then
+ local initializer = plugin.initializer
+ local processor = plugin.processor
+ local finalizer = plugin.finalizer
+ if initializer then
+ initializer(attribute,head)
+ end
+ if processor then
+ head = do_process_page(attribute,processor,head)
+ end
+ if finalizer then
+ local ok
+ ok, head, used[attribute] = finalizer(attribute,head)
+ done = done or ok
+ end
+ else
+ texio.write_nl(string.format("undefined attribute %s",name))
+ end
+ end
+ if done then
+ for name, plugin in pairs(shipouts.plugins) do
+ local attribute = attributes.numbers[name]
+ if used[attribute] then
+ local flusher = plugin.flusher
+ if flusher then
+ head = flusher(attribute,head,used[attribute])
+ end
+ end
+ end
+ end
+ input.stop_timing(nodes)
+ end
+ return head
+ end
+
+end
+
+--
+-- attributes
+--
+
+attributes = attributes or { }
+
+attributes.names = attributes.names or { }
+attributes.numbers = attributes.numbers or { }
+attributes.list = attributes.list or { }
+
+input.storage.register(false,"attributes/names", attributes.names, "attributes.names")
+input.storage.register(false,"attributes/numbers", attributes.numbers, "attributes.numbers")
+input.storage.register(false,"attributes/list", attributes.list, "attributes.list")
+
+function attributes.define(name,number)
+ attributes.numbers[name], attributes.names[number], attributes.list[number] = number, name, { }
+end
+
+--
+-- generic handlers
+--
+
+states = { }
+
+do
+
+ local glyph, rule, whatsit = node.id('glyph'), node.id('rule'), node.id('whatsit')
+
+ local current, used, done = 0, { }, false
+
+ function states.initialize(what, attribute, stack)
+ current, used, done = 0, { }, false
+ end
+
+ local contains = node.has_attribute
+
+ function insert(n,stack,previous,head)
+ if n then
+ n = node.copy(n)
+ n.next = stack
+ if previous then
+ previous.next = n
+ else
+ head = n
+ end
+ previous = n
+ end
+ return stack, previous, head
+ end
+
+ function states.finalize(what,attribute,head)
+ if what.enabled and what.none and current > 0 and head.list then
+ local head = head.list
+ stack, previous, head = insert(what.none,list,nil,list)
+ end
+ return done, head, used
+ end
+
+--~ function states.process(what,attribute,stack,previous,head) -- one attribute
+--~ if what.enabled then
+--~ local c = contains(stack,attribute)
+--~ if c then
+--~ if current ~= c then
+--~ local id = stack.id
+--~ if id == glyph or id == rule or id == whatsit then
+--~ stack, previous, head = insert(what.data[c],stack,previous,head)
+--~ current, done, used[c] = c, true, true
+--~ end
+--~ end
+--~ elseif current > 0 then
+--~ stack, previous, head = insert(what.none,stack,previous,head)
+--~ current, done, used[0] = 0, true, true
+--~ end
+--~ end
+--~ return stack, previous, head
+--~ end
+
+ function states.process(what,attribute,stack,previous,head) -- one attribute
+ if what.enabled then
+ local id = stack.id
+ if id == glyph or id == rule then -- or id == whatsit then
+ local c = contains(stack,attribute)
+ if c then
+ if current ~= c then
+ stack, previous, head = insert(what.data[c],stack,previous,head)
+ current, done, used[c] = c, true, true
+ end
+ elseif current > 0 then
+ stack, previous, head = insert(what.none,stack,previous,head)
+ current, done, used[0] = 0, true, true
+ end
+ end
+ end
+ return stack, previous, head
+ end
+
+--~ function states.selective(what,attribute,stack,previous,head) -- two attributes
+--~ if what.enabled then
+--~ local c = contains(stack,attribute)
+--~ if c then
+--~ if current ~= c then
+--~ local id = stack.id
+--~ if id == glyph or id == rule then -- or id == whatsit then
+--~ stack, previous, head = insert(what.data[c][contains(stack,what.selector) or what.default],stack,previous,head)
+--~ current, done, used[c] = c, true, true
+--~ end
+--~ end
+--~ elseif current > 0 then
+--~ local id = stack.id
+--~ if id == glyph or id == rule then -- or id == whatsit then
+--~ stack, previous, head = insert(what.none,stack,previous,head)
+--~ current, done, used[0] = 0, true, true
+--~ end
+--~ end
+--~ end
+--~ return stack, previous, head
+--~ end
+
+ function states.selective(what,attribute,stack,previous,head) -- two attributes
+ if what.enabled then
+ local id = stack.id
+ if id == glyph or id == rule then -- or id == whatsit then
+ local c = contains(stack,attribute)
+ if c then
+ if current ~= c then
+ stack, previous, head = insert(what.data[c][contains(stack,what.selector) or what.default],stack,previous,head)
+ current, done, used[c] = c, true, true
+ end
+ elseif current > 0 then
+ stack, previous, head = insert(what.none,stack,previous,head)
+ current, done, used[0] = 0, true, true
+ end
+ end
+ end
+ return stack, previous, head
+ end
+
+ collected = { }
+
+ function states.collect(str)
+ collected[#collected+1] = str
+ end
+
+ function states.flush()
+ for _, c in ipairs(collected) do
+ tex.sprint(tex.ctxcatcodes,c)
+ end
+ collected = { }
+ end
+
+end
+
+--
+-- colors
+--
+
+-- we can also collapse the two attributes: n, n+1, n+2 and then
+-- at the tex end add 0, 1, 2, but this is not faster and less
+-- flexible (since sometimes we freeze color attribute values at
+-- the lua end of the game
+
+-- we also need to store the colorvalues because we need then in mp
+
+colors = colors or { }
+colors.enabled = true
+colors.data = colors.data or { }
+colors.strings = colors.strings or { }
+colors.registered = colors.registered or { }
+colors.weightgray = true
+colors.attribute = 0
+colors.selector = 0
+colors.default = 1
+
+input.storage.register(true,"colors/data", colors.strings, "colors.data")
+input.storage.register(false,"colors/registered", colors.registered, "colors.registered")
+
+colors.stamps = {
+ rgb = "r:%s:%s:%s",
+ cmyk = "c:%s:%s:%s:%s",
+ gray = "s:%s",
+ spot = "p:%s:%s"
+}
+
+colors.models = {
+ all = 1,
+ gray = 2,
+ rgb = 3,
+ cmyk = 4
+}
+
+do
+
+ local min = math.min
+ local max = math.max
+ local format = string.format
+ local concat = table.concat
+
+ local function rgbdata(r,g,b)
+ return pdfliteral(format("%s %s %s rg %s %s %s RG",r,g,b,r,g,b))
+ end
+
+ local function cmykdata(c,m,y,k)
+ return pdfliteral(format("%s %s %s %s k %s %s %s %s K",c,m,y,k,c,m,y,k))
+ end
+
+ local function graydata(s)
+ return pdfliteral(format("%s g %s G",s,s))
+ end
+
+ local function spotdata(n,p) -- name, parent, ratio
+ if type(p) == "string" then
+ p = p:gsub(","," ") -- brr misuse of spot
+ end
+ return pdfliteral(format("/%s cs /%s CS %s SCN %s scn",n,n,p,p))
+ end
+
+ local function rgbtocmyk(r,g,b)
+ return 1-r, 1-g, 1-b, 0
+ end
+
+ local function cmyktorgb(c,m,y,k)
+ return 1.0 - min(1.0,c+k), 1.0 - min(1.0,m+k), 1.0 - min(1.0,y+k)
+ end
+
+ local function rgbtogray(r,g,b)
+ if colors.weightgray then
+ return .30*r+.59*g+.11*b
+ else
+ return r/3+g/3+b/3
+ end
+ end
+
+ local function cmyktogray(c,m,y,k)
+ return rgbtogray(cmyktorgb(c,m,y,k))
+ end
+
+ -- we can share some *data by using s, rgb and cmyk hashes, but
+ -- normally the amount of colors is not that large; storing the
+ -- components costs a bit of extra runtime, but we expect to gain
+ -- some back because we have them at hand; the number indicates the
+ -- default color space
+
+ function colors.gray(s)
+ local gray = graydata(s)
+ return { gray, gray, gray, gray, 2, s, 0, 0, 0, 0, 0, 0, 1 }
+ end
+
+ function colors.rgb(r,g,b)
+ local s = rgbtogray(r,g,b)
+ local c, m, y, k = rgbtocmyk(r,g,b)
+ local gray, rgb, cmyk = graydata(s), rgbdata(r,g,b), cmykdata(c,m,y,k)
+ return { rgb, gray, rgb, cmyk, 3, s, r, g, b, c, m, y, k }
+ end
+
+ function colors.cmyk(c,m,y,k)
+ local s = cmyktogray(c,m,y,k)
+ local r, g, b = cmyktorgb(c,m,y,k)
+ local gray, rgb, cmyk = graydata(s), rgbdata(r,g,b), cmykdata(c,m,y,k)
+ return { cmyk, gray, rgb, cmyk, 4, s, r, g, b, c, m, y, k }
+ end
+
+ function colors.spot(parent,p) -- parent, ratio
+ local spot = spotdata(parent,p)
+ if type(p) == "string" and p:find(",") then
+ -- use converted replacement (combination color)
+ else
+ -- todo: map gray, rgb, cmyk onto fraction*parent
+ end
+ local gray, rgb, cmyk = graydata(.5), rgbdata(.5,.5,.5), cmykdata(0,0,0,.5)
+ return { spot, gray, rgb, cmyk, 5 }
+ end
+
+ function colors.filter(n)
+ return concat(colors.data[n],":",5)
+ end
+
+ colors.none = graydata(0)
+
+end
+
+-- conversion models
+
+function colors.setmodel(attribute,name)
+ colors.selector = attributes.numbers[attribute]
+ colors.default = colors.models[name] or 1
+ return colors.default
+end
+
+function colors.register(attribute, name, colorspace, ...)
+ local stamp = string.format(colors.stamps[colorspace], ...)
+ local color = colors.registered[stamp]
+ if not color then
+ local cd = colors.data
+ color = #cd+1
+ cd[color] = colors[colorspace](...)
+ if environment.initex then
+ colors.strings[color] = "return colors." .. colorspace .. "(" .. table.concat({...},",") .. ")"
+ end
+ colors.registered[stamp] = color
+ end
+ attributes.list[attributes.numbers[attribute]][name] = color
+ return colors.registered[stamp]
+end
+
+shipouts.plugins.color = {
+ initializer = function(...) return states.initialize(colors,...) end,
+ finalizer = function(...) return states.finalize (colors,...) end,
+ processor = function(...) return states.selective (colors,...) end,
+}
+
+--- overprint / knockout
+
+overprints = { enabled = true , data = { } }
+
+overprints.none = pdfliteral(string.format("/GSoverprint gs"))
+overprints.data[1] = pdfliteral(string.format("/GSknockout gs"))
+
+overprints.registered = {
+ overprint = 0,
+ knockout = 1,
+}
+
+function overprints.register(stamp)
+ return overprints.registered[stamp] or overprints.registered.overprint
+end
+
+shipouts.plugins.overprint = {
+ initializer = function(...) return states.initialize(overprints,...) end,
+ finalizer = function(...) return states.finalize (overprints,...) end,
+ processor = function(...) return states.process (overprints,...) end,
+}
+
+--- negative / positive
+
+negatives = { enabled = true, data = { } }
+
+negatives.none = pdfliteral(string.format("/GSpositive gs"))
+negatives.data[1] = pdfliteral(string.format("/GSnegative gs"))
+
+negatives.registered = {
+ positive = 0,
+ negative = 1,
+}
+
+function negatives.register(stamp)
+ return negatives.registered[stamp] or negatives.registered.positive
+end
+
+shipouts.plugins.negative = {
+ initializer = function(...) return states.initialize(negatives,...) end,
+ finalizer = function(...) return states.finalize (negatives,...) end,
+ processor = function(...) return states.process (negatives,...) end,
+}
+
+-- effects
+
+effects = { enabled = true, data = { } }
+
+effects.none = pdfliteral(string.format("0 Tr"))
+effects.data[1] = pdfliteral(string.format("1 Tr"))
+effects.data[2] = pdfliteral(string.format("2 Tr"))
+effects.data[3] = pdfliteral(string.format("3 Tr"))
+
+effects.registered = {
+ normal = 0,
+ inner = 0,
+ outer = 1,
+ both = 2,
+ hidden = 3,
+}
+
+function effects.register(stamp)
+ return effects.registered[stamp] or effects.registered.normal
+end
+
+shipouts.plugins.effect = {
+ initializer = function(...) return states.initialize(effects,...) end,
+ finalizer = function(...) return states.finalize (effects,...) end,
+ processor = function(...) return states.process (effects,...) end,
+}
+
+-- layers
+
+--~ /OC /somename BDC
+--~ EMC
+
+-- transparencies
+
+-- for the moment we manage transparencies in the pdf driver because
+-- first we need a nice interface to some pdf things
+
+transparencies = {
+ enabled = true,
+ data = { },
+ registered = { },
+ hack = { }
+}
+
+input.storage.register(false, "transparencies/registed", transparencies.registered, "transparencies.registered")
+input.storage.register(false, "transparencies/data", transparencies.data, "transparencies.data")
+input.storage.register(false, "transparencies/hack", transparencies.hack, "transparencies.hack")
+
+function transparencies.reference(n)
+ return pdfliteral(string.format("/Tr%s gs",n))
+end
+
+transparencies.none = transparencies.reference(0)
+
+transparencies.stamp = "%s:%s"
+
+function transparencies.register(...)
+ local stamp = string.format(transparencies.stamp, ...)
+ if not transparencies.registered[stamp] then
+ local n = #transparencies.data+1
+ transparencies.data[n] = transparencies.reference(n)
+ transparencies.registered[stamp] = n
+ states.collect(string.format("\\presetPDFtransparency{%s}{%s}",...)) -- too many, but experimental anyway
+ end
+ return transparencies.registered[stamp]
+end
+
+shipouts.plugins.transparency = {
+ initializer = function(...) return states.initialize(transparencies,...) end,
+ finalizer = function(...) return states.finalize (transparencies,...) end,
+ processor = function(...) return states.process (transparencies,...) end,
+}
+
+--~ shipouts.plugins.transparency.flusher = function(attribute,head,used)
+--~ local max = 0
+--~ for k,v in pairs(used) do
+--~ end
+--~ return head
+--~ end
+
+--~ from the time that node lists were tables and not userdata ...
+--~
+--~ local function do_collapse_page(stack,existing_t)
+--~ if stack then
+--~ local t = existing_t or { }
+--~ for _, node in ipairs(stack) do
+--~ if node then
+--~ local kind = node[1]
+--~ node[3] = nil
+--~ if kind == 'hlist' or kind == 'vlist' then
+--~ node[8] = do_collapse_page(node[8]) -- maybe here check for nil
+--~ t[#t+1] = node
+--~ elseif kind == 'inline' then -- appending literals cost too much time
+--~ local nodes = node[4]
+--~ if #nodes == 1 then
+--~ t[#t+1] = nodes[1]
+--~ else
+--~ do_collapse_page(nodes,t)
+--~ end
+--~ else
+--~ t[#t+1] = node
+--~ end
+--~ end
+--~ end
+--~ return t
+--~ else
+--~ return nil
+--~ end
+--~ end
+--~
+--~ local function do_process_page(attribute,processor,stack)
+--~ if stack then
+--~ for i, node in ipairs(stack) do
+--~ if node then
+--~ local kind = node[1]
+--~ if kind == 'hlist' or kind == "vlist" then
+--~ local content = node[8]
+--~ if not content then
+--~ -- nil node
+--~ elseif type(content) == "table" then
+--~ node[8] = do_process_page(attribute,processor,content)
+--~ else
+--~ node[8] = do_process_page(attribute,processor,tex.get_node_list(content))
+--~ end
+--~ elseif kind == 'inline' then
+--~ node[4] = do_process_page(attribute,processor,node[4])
+--~ else
+--~ processor(attribute,stack,i,node,kind)
+--~ end
+--~ end
+--~ end
+--~ end
+--~ return stack
+--~ end
+--~
+--~ function nodes.process_page(stack,...)
+--~ if stack then
+--~ input.start_timing(nodes)
+--~ local done, used = false, { }
+--~ for name, plugin in pairs(shipouts.plugins) do
+--~ local attribute = attributes.numbers[name]
+--~ if attribute then
+--~ local initializer = plugin.initializer
+--~ local processor = plugin.processor
+--~ local finalizer = plugin.finalizer
+--~ if initializer then
+--~ initializer(attribute,stack)
+--~ end
+--~ if processor then
+--~ do_process_page(attribute,processor,stack)
+--~ end
+--~ if finalizer then
+--~ local ok
+--~ ok, used[attribute] = finalizer(attribute,stack)
+--~ done = done or ok
+--~ end
+--~ else
+--~ texio.write_nl(string.format("undefined attribute %s",name))
+--~ end
+--~ end
+--~ if done then
+--~ stack = do_collapse_page(stack)
+--~ for name, plugin in pairs(shipouts.plugins) do
+--~ local attribute = attributes.numbers[name]
+--~ if used[attribute] then
+--~ local flusher = plugin.flusher
+--~ if flusher then
+--~ flusher(attribute,stack,used[attribute])
+--~ end
+--~ end
+--~ end
+--~ else
+--~ stack = true
+--~ end
+--~ input.stop_timing(nodes)
+--~ end
+--~ return stack
+--~ end
+--~
+--~ function states.finalize(what,attribute,stack)
+--~ if what.enabled then
+--~ if current > 0 then
+--~ local list = stack
+--~ if #stack == 1 then
+--~ list = stack[#stack][8]
+--~ end
+--~ list[#list+1], current, done, used[0] = what.none, 0, true, true
+--~ end
+--~ end
+--~ return done, used
+--~ end
+--~
+--~ function states.process(what,attribute,stack,i,node,kind)
+--~ if what.enabled then
+--~ local a = node[3]
+--~ if a then
+--~ local c = a[attribute]
+--~ if c then
+--~ if current ~= c and (kind == 'glyph' or kind == 'rule') then
+--~ stack[i], current, done, used[c] = nodes.inline(what.data[c], node), c, true, true
+--~ end
+--~ elseif current > 0 then
+--~ stack[i], current, done, used[0] = nodes.inline(what.none, node), 0, true, true
+--~ end
+--~ end
+--~ end
+--~ end
diff --git a/tex/context/base/attr-ini.tex b/tex/context/base/attr-ini.tex
new file mode 100644
index 000000000..60746d560
--- /dev/null
+++ b/tex/context/base/attr-ini.tex
@@ -0,0 +1,199 @@
+%D \module
+%D [ file=attr-ini,
+%D version=2007.06.06,
+%D title=\CONTEXT\ Attribute Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA-ADE]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Context Attribute Macros / initialization}
+
+%D Although it's still somewhat experimental, here we introduce code
+%D related to attributes.
+
+\unprotect
+
+\registerctxluafile{attr-ini}{1.001}
+
+\newcount\attdefcounter
+
+% \def\newattribute#1%
+% {\global\advance\attdefcounter\plusone
+% \global\attributedef#1\attdefcounter
+% \ctxlua{attributes.define("\strippedcsname#1",\number\attdefcounter)}}
+
+% \newattribute\statusattribute
+% \newattribute\colorattribute
+% \newattribute\skipattribute
+% \newattribute\penaltyattribute
+
+\newtoks \attributesresetlist
+
+\def\defineattribute[#1]% alternatively we can let lua do the housekeeping
+ {\global\advance\attdefcounter\plusone
+ \expandafter \xdef\csname :attr:#1\endcsname{\number\attdefcounter}%
+ \global\expandafter\attributedef\csname @attr@#1\endcsname \attdefcounter
+ \writestatus\m!systems{defining attribute #1 with number \the\attdefcounter}%
+ \appendetoks\csname @attr@#1\endcsname\minusone\to\attributesresetlist
+ \ctxlua{attributes.define("#1",\number\attdefcounter)}}
+
+\def\dosetattribute#1#2{\csname @attr@#1\endcsname#2\relax}
+\def\doresetattribute#1{\csname @attr@#1\endcsname\minusone}
+\def\dogetattribute #1{\number\csname @attr@#1\endcsname}
+\def\dogetattributeid#1{\csname :attr:#1\endcsname}
+
+\let\dompattribute\gobbletwoarguments
+
+\defineattribute[mark]
+\defineattribute[status]
+% \defineattribute[language]
+\defineattribute[skip]
+\defineattribute[penalty]
+
+% \dosetattribute{status}{1}
+
+% temp here / will be indirect ! just for testing
+
+\defineattribute[colormodel]
+\defineattribute[color]
+
+% todo: no need for 'color' argument, we can set that once at startup; currently
+% a bit inconsistent
+
+% 1=off 2=gray 3=spot 4=rgb 5=cmyk 6=cmy % only 1/2/4/5 are supported
+%
+% we could combine this in one attribute but this is not faster and also
+% less flexible because sometimes we want to freeze the attribute bit
+
+\newcount\currentcolormodel
+
+\def\setcolormodel#1%
+ {\currentcolormodel\ctxlua{tex.print(colors.setmodel('colormodel','#1'))}%
+ \dosetattribute{colormodel}{\the\currentcolormodel}}
+
+\setcolormodel{all} % when no color, reset ! !
+
+\def\registerrgbcolor#1#2#3#4% r g b -- print or sprint
+ {\scratchcounter\ctxlua{tex.print(colors.register('color','#1','rgb',#2,#3,#4))}%
+ \setevalue{(ca:#1)}{\number\scratchcounter}%
+ \setevalue{(cv:#1)}{\ctxlua{tex.print(colors.filter(\number\scratchcounter))}}%
+ \setevalue{(cs:#1)}{\dosetattribute{color}{\the\scratchcounter}}}
+
+\def\registercmykcolor#1#2#3#4#5% c m y k
+ {\scratchcounter\ctxlua{tex.print(colors.register('color','#1','cmyk',#2,#3,#4,#5))}%
+ \setevalue{(ca:#1)}{\number\scratchcounter}%
+ \setevalue{(cv:#1)}{\ctxlua{tex.print(colors.filter(\number\scratchcounter))}}%
+ \setevalue{(cs:#1)}{\dosetattribute{color}{\the\scratchcounter}}}
+
+\def\registergraycolor#1#2% s
+ {\scratchcounter\ctxlua{tex.print(colors.register('color','#1','gray',#2))}%
+ \setevalue{(ca:#1)}{\number\scratchcounter}%
+ \setevalue{(cv:#1)}{\ctxlua{tex.print(colors.filter(\number\scratchcounter))}}%
+ \setevalue{(cs:#1)}{\dosetattribute{color}{\the\scratchcounter}}}
+
+\def\registerspotcolor#1#2#3% p name
+ {\scratchcounter\ctxlua{tex.print(colors.register('color','#1','spot',#2,#3))}%
+ \setevalue{(ca:#1)}{\number\scratchcounter}%
+ \setevalue{(cv:#1)}{\ctxlua{tex.print(colors.filter(\number\scratchcounter))}}%
+ \setevalue{(cs:#1)}{\dosetattribute{color}{\the\scratchcounter}}}
+
+\def\somecolorvalue #1{\csname(cv:#1)\endcsname}
+\def\somecolorswitch #1{\csname(cs:#1)\endcsname}
+\def\somecolorattribute#1{\csname(ca:#1)\endcsname}
+
+\def\getMPcolorspec#1%
+ {\expandafter\expandafter\expandafter\dogetMPcolorspec\csname (cv:#1)\endcsname\relax}
+
+\def\dogetMPcolorspec#1:#2:#3:#4:#5:#6:#7:#8:#9\relax % some day we will just pass a attribute number in pre/post
+ {\ifcase\currentcolormodel\or
+ \ifcase#1\or#2\or(#3,#4,#5)\or(#6,#7,#8,#9)\fi\or#2\or(#3,#4,#5)\or(#6,#7,#8,#9)%
+ \fi}
+
+% \registerrgbcolor {red} {1}{0}{0}
+% \registerrgbcolor {green} {0}{1}{0}
+% \registerrgbcolor {blue} {0}{0}{1}
+% \registercmykcolor {cyan} {1}{0}{0}{0}
+% \registercmykcolor {magenta} {0}{1}{0}{0}
+% \registercmykcolor {yellow} {0}{0}{1}{0}
+% \registergraycolor {black} {0}
+
+% transparency
+
+\defineattribute[transparency]
+
+\def\registertransparency#1#2#3% we need to fake a name in the current setup, same as color
+ {\setevalue{(ts:#1)}{\dosetattribute{transparency}{\ctxlua{tex.print(transparencies.register(#2,#3))}}}}
+
+\def\sometransparencyswitch#1{\csname(ts:#1)\endcsname}
+
+% \registertransparency {one} {1} {.5}
+% \registertransparency {two} {1} {.6}
+
+% overprint
+
+\defineattribute[overprint]
+
+\def\registeroverprint#1#2%
+ {\initializePDFoverprint % temp here
+ \setvalue{(os:#1)}{\dosetattribute{overprint}{\ctxlua{tex.print(overprints.register('#2'))}}}}
+
+% \registeroverprint{knockout} {knockout}
+% \registeroverprint{overprint}{overprint}
+
+% negative
+
+\defineattribute[negative]
+
+\def\registernegative#1#2%
+ {\initializePDFnegative % temp here
+ \setvalue{(ns:#1)}{\dosetattribute{negative}{\ctxlua{tex.print(negatives.register('#2'))}}}}
+
+% \registernegative{positive}{positive}
+% \registernegative{negative}{negative}
+
+% effect
+
+\defineattribute[effect]
+
+\def\registereffect#1%
+ {\setevalue{(es:#1)}{\dosetattribute{effect}{\ctxlua{tex.print(effects.register('#1'))}}}}
+
+% \registereffect{normal}
+% \registereffect{inner}
+% \registereffect{outer}
+% \registereffect{both}
+% \registereffect{hidden}
+
+% ugly solution
+%
+% \def\shipout
+% {%\writestatus{SHIPOUT}{CALLED AT PAGE \realfolio}%
+% \dowithnextbox
+% {\ctxlua{callbacks.push('hpack_filter',nodes.process_page)}%
+% %\writestatus{SHIPOUT}{START PACKAGING}%
+% \setbox\nextbox\hbox{\box\nextbox}%
+% %\writestatus{SHIPOUT}{STOP PACKAGING}%
+% \ctxlua{callbacks.pop('hpack_filter')}%
+% \primitive\shipout\box\nextbox}}
+
+% \def\shipout
+% {\dowithnextbox
+% {\ctxlua{nodes.process_page(tex.box[\number\nextbox])}%
+% \primitive\shipout\box\nextbox}}
+
+\def\processshipoutbox#1% % hack till we have access to pdf backend
+ {\setbox\nextbox\hbox{#1}%
+ \ctxlua{nodes.process_page(tex.box[\number\nextbox])}%
+ \hbox{\ctxlua{states.flush()}\box\nextbox}}
+
+% \def\shipout
+% {\dowithnextbox{\ctxlua{tex.primitive('shipout', nodes.process_page(tex.nextbox)}}}
+
+\let\normalshipout\shipout
+
+\protect \endinput
diff --git a/tex/context/base/char-cmp.lua b/tex/context/base/char-cmp.lua
new file mode 100644
index 000000000..42f66143c
--- /dev/null
+++ b/tex/context/base/char-cmp.lua
@@ -0,0 +1,260 @@
+if not modules then modules = { } end modules ['char-cmp'] = {
+ version = 1.001,
+ comment = "companion to char-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+characters = characters or { }
+characters.uncomposed = characters.uncomposed or { }
+
+--[[ldx--
+
The code defined here may move to the big character table.
The next three tables can for instance be be used to enhance
+kerning tables that lack kerning pairs for these special characters.
+Of course they may come in handy elsewhere too
The following function is used in the indexing code, where
+we need some sort of default fallback mapping.
+--ldx]]--
+
+function characters.uncompose(n) -- n == string|number, returns string
+ local cdn
+ if type(n) == "string" then
+ cdn = characters.data[utf.byte(n)]
+ else
+ cdn = characters.data[n]
+ end
+ -- return characters.shape(n)
+ if cdn then
+ if cdn.shcode then
+ return utf.char(cdn.shcode)
+ else
+ return characters.uncomposed.both[cdn.contextname] or n
+ end
+ end
+ return n
+end
+
+--[[ldx--
+
Only characters with a code smaller than 128 make sense,
+anything larger is encoding dependent. An interesting complication
+is that a character can be in an encoding twice but is hashed
+once.
This module implements some methods and creates additional datastructured
+from the big character table that we use for all kind of purposes:
+char-def.lua.
+--ldx]]--
+
+characters = characters or { }
+characters.data = characters.data or { }
+characters.context = characters.context or { }
+
+_empty_table_ = { }
+_empty_table_.__index = function(t,k) return "" end
+
+setmetatable(characters.data,_empty_table_)
+
+--[[ldx--
+
At this point we assume that the big data table is loaded. From this
+table we derive a few more.
+--ldx]]--
+
+characters.context.unicodes = characters.context.unicodes or { }
+characters.context.utfcodes = characters.context.utfcodes or { }
+characters.context.enccodes = characters.context.enccodes or { }
+
+function characters.context.rehash()
+ local unicodes, utfcodes, enccodes = characters.context.unicodes, characters.context.utfcodes, characters.context.enccodes
+ for k,v in pairs(characters.data) do
+ local contextname, adobename = v.contextname, v.adobename
+ if contextname then
+ unicodes[contextname] = v.unicodeslot
+ utfcodes[contextname] = utf.char(v.unicodeslot)
+ end
+ local encname = adobename or contextname
+ if encname then
+ enccodes[encname] = k
+ end
+ end
+end
+
+--[[ldx--
+
The context namespace is used to store methods and data
+which is rather specific to .
+--ldx]]--
+
+function characters.context.show(n)
+ local n = characters.number(n)
+ local d = characters.data[n]
+ if d then
+ local function entry(label,name)
+ tex.ctxprint(string.format("\\NC %s\\NC %s\\NC\\NR",label,characters.valid(d[name])))
+ end
+ tex.ctxprint("\\starttabulate[|Tl|Tl|]")
+ entry("unicode index" , "unicodeslot")
+ entry("context name" , "contextname")
+ entry("adobe name" , "adobename")
+ entry("category" , "category")
+ entry("description" , "description")
+ entry("uppercase code", "uccode")
+ entry("lowercase code", "lccode")
+ entry("specials" , "specials")
+ tex.ctxprint("\\stoptabulate ")
+ end
+end
+
+--[[ldx--
+
Instead of using a file to define the named glyphs, we
+use the table. After all, we have this information available anyway.
+--ldx]]--
+
+function characters.context.define()
+ local unicodes, utfcodes = characters.context.unicodes, characters.context.utfcodes
+ local flush, tc = tex.sprint, tex.ctxcatcodes
+ for _, chr in pairs(characters.data) do
+ local contextname = chr.contextname
+ if contextname then
+ -- by this time, we're still in normal catcode mode
+ if chr.unicodeslot < 128 then
+ flush(tc, "\\chardef\\" .. contextname .. "=" .. unicodes[contextname])
+ else
+ flush(tc, "\\let\\" .. contextname .. "=" .. utfcodes[contextname])
+ end
+ end
+ end
+end
+
+--[[ldx--
+
Setting the lccodes is also done in a loop over the data table.
+--ldx]]--
+
+
+function characters.setcodes()
+ local flush, tc = tex.sprint, tex.ctxcatcodes
+ for code, chr in pairs(characters.data) do
+ local cc = chr.category
+ if cc == 'll' or cc == 'lu' or cc == 'lt' then
+ if not chr.lccode then chr.lccode = code end
+ if not chr.uccode then chr.uccode = code end
+ flush(tc, '\\setcclcuc '.. code .. ' ' .. chr.lccode .. ' ' .. chr.uccode .. ' ')
+ end
+ end
+end
+
+--[[ldx--
+
Next comes a whole series of helper methods. These are (will be) part
+of the official .
+--ldx]]--
+
+--[[ldx--
+
This converts a string (if given) into a number.
+--ldx]]--
+
+function characters.number(n)
+ if type(n) == "string" then return tonumber(n,16) else return n end
+end
+
+--[[ldx--
+
Checking for valid characters.
+--ldx]]--
+
+function characters.is_valid(s)
+ return s or ""
+end
+
+function characters.checked(s, default)
+ return s or default
+end
+
+characters.valid = characters.is_valid
+
+--[[ldx--
+
The next method is used when constructing the main table, although nowadays
+we do this in one step. The index can be a string or a number.
+--ldx]]--
+
+function characters.define(c)
+ characters.data[characters.number(c.unicodeslot)] = c
+end
+
+--[[ldx--
+
+--ldx]]--
+-- set a table entry; index is number (can be different from unicodeslot)
+
+function characters.set(n, c)
+ characters.data[characters.number(n)] = c
+end
+
+--[[ldx--
+
Get a table entry happens by number. Keep in mind that the unicodeslot
+can be different (not likely).
+--ldx]]--
+
+function characters.uccode(n) return characters.data[n].uccode or n end
+function characters.lccode(n) return characters.data[n].lccode or n end
+
+function characters.flush(n)
+ if characters.data[n].contextname then
+ tex.sprint(tex.texcatcodes, "\\"..characters.data[n].contextname)
+ else
+ tex.sprint(unicode.utf8.char(n))
+ end
+end
+
+function characters.shape(n)
+ return characters.data[n].shcode or n
+end
+
+--[[ldx--
+
Categories play an important role, so here are some checkers.
+--ldx]]--
+
+function characters.is_of_category(token,category)
+ if type(token) == "string" then
+ return characters.data[utf.byte(token)].category == category
+ else
+ return characters.data[token].category == category
+ end
+end
+
+function characters.i_is_of_category(i,category) -- by index (number)
+ local cd = characters.data[i]
+ return cd and cd.category == category
+end
+
+function characters.n_is_of_category(n,category) -- by name (string)
+ local cd = characters.data[utf.byte(n)]
+ return cd and cd.category == category
+end
+
+--[[ldx--
+
The following code is kind of messy. It is used to generate the right
+unicode reference tables.
+--ldx]]--
+
+function characters.setpdfunicodes()
+ local flush, tc, sf = tex.sprint, tex.ctxcatcodes, string.format
+ for _,v in pairs(characters.data) do
+ if v.adobename then
+ flush(tc,sf("\\pdfglyphtounicode{%s}{%04X}", v.adobename, v.unicodeslot))
+ end
+ end
+end
+
+--[[ldx--
+
The next method generates a table for good old .
+
+
+characters.pdftex.make_pdf_to_unicodetable("pdfr-def.tex")
+
+--ldx]]--
+
+characters.pdftex = characters.pdftex or { }
+
+function characters.pdftex.make_pdf_to_unicodetable(filename)
+ local sf = string.format
+ f = io.open(filename,'w')
+ if f then
+ f:write("% This file is generated with Luatex using the\n")
+ f:write("% character tables that come with ConTeXt MkIV.\n")
+ f:write("%\n")
+ f:write("\\ifx\\pdfglyphtounicode\\undefined\\endinput\\fi\n") -- just to be sure
+ for _, v in pairs(characters.data) do
+ if v.adobename then
+ f:write(sf("\\pdfglyphtounicode{%s}{%04X}", v.adobename, v.unicodeslot))
+ end
+ end
+ f:write("%\n")
+ f:write("%\n")
+ f:write("\\endinput")
+ f:close()
+ end
+end
diff --git a/tex/context/base/char-ini.tex b/tex/context/base/char-ini.tex
new file mode 100644
index 000000000..4b4c55ef1
--- /dev/null
+++ b/tex/context/base/char-ini.tex
@@ -0,0 +1,20 @@
+%D \module
+%D [ file=char-ini,
+%D version=2006.08.20,
+%D title=\CONTEXT\ Character Macros,
+%D subtitle=Character Support (Initialization),
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Character Support (initialization)}
+
+\registerctxluafile{char-ini}{1.001}
+\registerctxluafile{char-cmp}{1.001} % maybe we will load this someplace else
+\registerctxluafile{char-tok}{1.001} % maybe we will load this someplace else
+
+\endinput
diff --git a/tex/context/base/char-utf.lua b/tex/context/base/char-utf.lua
new file mode 100644
index 000000000..c30a160bc
--- /dev/null
+++ b/tex/context/base/char-utf.lua
@@ -0,0 +1,298 @@
+if not modules then modules = { } end modules ['char-utf'] = {
+ version = 1.001,
+ comment = "companion to char-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
When a sequence of characters enters the application, it may
+be neccessary to collapse subsequences into their composed variant.
+
+
This module implements methods for collapsing and expanding
+sequences. We also provide means to deal with characters that are
+special to as well as 8-bit characters that need to end up
+in special kinds of output (for instance ).
+
+
We implement these manipulations as filters. One can run multiple filters
+over a string.
+--ldx]]--
+
+utf = utf or unicode.utf8
+
+characters = characters or { }
+characters.graphemes = characters.graphemes or { }
+characters.filters = characters.filters or { }
+characters.filters.utf = characters.filters.utf or { }
+
+characters.filters.utf.initialized = false
+characters.filters.utf.collapsing = true
+characters.filters.utf.expanding = true
+
+--[[ldx--
+
It only makes sense to collapse at runtime, since we don't expect
+source code to depend on collapsing:
+
+
+characters.filters.utf.collapsing = true
+input.filters.utf_translator = characters.filters.utf.collapse
+
+--ldx]]--
+
+function characters.filters.utf.initialize()
+ if characters.filters.utf.collapsing and not characters.filters.utf.initialized then
+ characters.graphemes = { }
+ local cg = characters.graphemes
+ local uc = utf.char
+ for k,v in pairs(characters.data) do
+ -- using vs and first testing for length is faster (.02->.01 s)
+ local vs = v.specials
+ if vs and #vs == 3 and vs[1] == 'char' then
+ local first, second = uc(vs[2]), uc(vs[3])
+ if not cg[first] then
+ cg[first] = { }
+ end
+ cg[first][second] = uc(k)
+ end
+ end
+ characters.filters.utf.initialized = true
+ end
+end
+
+function characters.filters.utf.collapse(str)
+ if characters.filters.utf.collapsing and str and #str > 1 then
+ characters.filters.utf.initialize()
+ local tokens, first, done = { }, false, false
+ local cg = characters.graphemes
+ for second in string.utfcharacters(str) do
+ local cgf = cg[first]
+ if cgf and cgf[second] then
+ first, done = cgf[second], true
+ elseif first then
+ tokens[#tokens+1] = first
+ first = second
+ else
+ first = second
+ end
+ end
+ if done then
+ tokens[#tokens+1] = first
+ return table.concat(tokens,"")
+ end
+ end
+ return str
+end
+
+--[[ldx--
+
In order to deal with 8-bit output, we need to find a way to
+go from to 8-bit. This is handled in the
+ engine itself.
+
+
This leaves us problems with characters that are specific to
+ like {}, $ 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.
+--ldx]]--
+
+characters.filters.utf.private = { }
+characters.filters.utf.private.high = { }
+characters.filters.utf.private.low = { }
+
+do
+
+ local ub, uc, ug = utf.byte, utf.char, utf.gsub
+ local cfup = characters.filters.utf.private
+
+ function characters.filters.utf.private.set(ch)
+ local cb = ub(ch)
+ if cb < 256 then
+ cfup.low[ch] = uc(0x0F0000 + cb)
+ cfup.high[uc(0x0F0000 + cb)] = ch
+ end
+ end
+
+ function characters.filters.utf.private.replace(str)
+ ug("(.)", cfup.low)
+ end
+
+ function characters.filters.utf.private.revert(str)
+ ug("(.)", cfup.high)
+ end
+
+ for _, ch in pairs({ '~', '#', '$', '%', '^', '&', '_', '{', '}' }) do
+ cfup.set(ch)
+ end
+
+end
+
+--[[ldx--
+
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.
+
+
+[x] [$x$]
+
+--ldx]]--
+
+do
+
+ local cg = characters.graphemes
+ local cr = characters.filters.utf.private.high
+
+ function characters.filters.utf.collapse(str)
+ if characters.filters.utf.collapsing and str then
+ if #str > 1 then
+ characters.filters.utf.initialize()
+ local tokens, first, done = { }, false, false
+ for second in string.utfcharacters(str) do
+ if cr[second] then
+ if first then
+ tokens[#tokens+1] = first
+ end
+ first, done = cr[second], true
+ else
+ local cgf = cg[first]
+ if cgf and cgf[second] then
+ first, done = cgf[second], true
+ elseif first then
+ tokens[#tokens+1] = first
+ first = second
+ else
+ first = second
+ end
+ end
+ end
+ if done then
+ tokens[#tokens+1] = first
+ return table.concat(tokens,"")
+ end
+ elseif #str > 0 then
+ return cr[str] or str
+ end
+ end
+ return str
+ end
+
+end
+
+--[[ldx--
+
In the beginning of we experimented with a sequence
+of filters so that we could manipulate the input stream. However, since
+this is a partial solution (not taking macro expansion into account)
+and since it may interfere with non-text, we will not use this feature
+by default.
The following helper functions may disappear (or become optional)
+in the future.
+--ldx]]--
+
+characters.filters.sequences = { }
+characters.filters.activated = false
+
+function characters.filters.append(name)
+ table.insert(characters.filters.sequences,name)
+end
+
+function characters.filters.prepend(name)
+ table.insert(characters.filters.sequences,1,name)
+end
+
+function characters.filters.remove(name)
+ for k,v in pairs(characters.filters.sequences) do
+ if v == name then
+ table.remove(characters.filters.sequences,k)
+ end
+ end
+end
+
+function characters.filters.replace(name_1,name_2)
+ for k,v in pairs(characters.filters.sequences) do
+ if v == name then
+ characters.filters.sequences[k] = name_2
+ end
+ end
+end
+
+function characters.filters.insert_before(name_1,name_2)
+ for k,v in pairs(characters.filters.sequences) do
+ if v == name_1 then
+ table.insert(characters.filters.sequences,k,name_2)
+ end
+ end
+end
+
+function characters.filters.insert_after(name_1,name_2)
+ for k,v in pairs(characters.filters.sequences) do
+ if v == name_1 then
+ table.insert(characters.filters.sequences,k+1,name_2)
+ end
+ end
+end
+
+function characters.filters.list(separator)
+ table.concat(characters.filters.sequences,seperator or ' ')
+end
+
+function characters.filters.process(str)
+ if characters.filters.activated then
+ for _,v in pairs(characters.filters.sequences) do
+ str = v(str)
+ end
+ return str
+ else
+ return nil -- luatex callback optimalisation
+ end
+end
+
+--[[ldx--
+
The following code is no longer needed and replaced by token
+collectors somehwere else.
+--ldx]]--
+
+--[[obsolete--
+
+characters.filters.collector = { }
+characters.filters.collector.data = { }
+characters.filters.collector.collecting = false
+
+function characters.filters.collector.reset()
+ characters.filters.collector.data = { }
+end
+
+function characters.filters.collector.flush(separator)
+ tex.sprint(table.concat(characters.filters.collector.data,separator))
+end
+
+function characters.filters.collector.prune(n)
+ for i=1,n do
+ table.remove(characters.filters.collector.data,-1)
+ end
+end
+
+function characters.filters.collector.numerate(str)
+ if characters.filters.collector.collecting then
+ table.insert(characters.filters.collector.data,(unicode.utf8.gsub(str,"(.)", function(c)
+ return string.format("0x%04X ",unicode.utf8.byte(c))
+ end)))
+ end
+ return str
+end
+
+--obsolete]]--
diff --git a/tex/context/base/char-utf.tex b/tex/context/base/char-utf.tex
new file mode 100644
index 000000000..cb6eceea1
--- /dev/null
+++ b/tex/context/base/char-utf.tex
@@ -0,0 +1,54 @@
+%D \module
+%D [ file=char-utf,
+%D version=2006.12.05,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=Unicode Support (UTF),
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Unicode Support (utf)}
+
+%D After a bit of experimenting we reached a clean state where \UTF\
+%D 8, 16 and 32 were supported as well as collapsing (combining
+%D sequences). Writing the code was a relaxed experience, not in the
+%D last place because it was accompanied by listening to those nice
+%D Vienna Teng cd's (who decided that making music was more fun than
+%D programming, but then, she may not know \TEX\ and \LUA).
+
+\unprotect
+
+\registerctxluafile{char-utf}{1.001}
+
+%D We enable collapsing (combining characters) by default, but
+%D since the source files are rather simple, we postpone the
+%D initialization till runtime.
+
+\appendtoks
+ \ctxlua {
+ characters.filters.utf.collapsing = true
+ input.filters.utf_translator = characters.filters.utf.collapse
+ }%
+\to \everyjob
+
+%D This is a hack, and only meant for special situations. We don't
+%D support this in for instance verbatim. The active characters map
+%D onto the \CONTEXT\ names and font handling etc. is up to the user.
+
+\registerctxluafile{char-act}{1.001}
+
+\def\enableactiveutf {\ctxlua{characters.active.enable()}}
+\def\disableactiveutf{\ctxlua{characters.active.disable()}}
+\def\testactiveutf #1{\ctxlua{characters.active.test("#1")}}
+
+%D Usage:
+%D
+%D \starttyping
+%D \enableactiveutf \testactiveutf{eacute}
+%D \stoptyping
+
+\protect
diff --git a/tex/context/base/colo-ini.tex b/tex/context/base/colo-ini.tex
index 81e7d1cf7..687d41da3 100644
--- a/tex/context/base/colo-ini.tex
+++ b/tex/context/base/colo-ini.tex
@@ -359,8 +359,7 @@
{% this way we can freeze \definecolor[somecolor][somecolor]
% and still prevent cyclic definitions
\iffreezecolors#3{\??cr#5}{\csname\??cr#6\endcsname}\fi}
- {\iffreezecolors\@EA#3\else\@EA#2\fi
- {\??cr#5}{\csname\??cr#6\endcsname}}}
+ {\iffreezecolors\@EA#3\else\@EA#2\fi{\??cr#5}{\csname\??cr#6\endcsname}}}
{\showmessage\m!colors3{#5 (def)}}%
\fi}%
\ifcase#4\or
@@ -527,6 +526,8 @@
%D We now redefine the color definition macro so that you
%D can define both normal and spotcolors.
+%D This messy method will become obsolete in mkiv.
+
\def\definecolor
{\dotripleempty\dodefinewhatevercolor}
@@ -1101,14 +1102,14 @@
\def\predefinecolor[#1]%
{\bgroup
- \flushatshipout{\hbox{\localcolortrue\color[#1]}}% real ones
+ \flushatshipout{\hbox{\localcolortrue\color[#1]{}}}% real ones
\egroup}
\def\predefineindexcolor[#1]%
{\bgroup
- \flushatshipout{\hbox{\localcolortrue\color[#1]}}% real ones
+ \flushatshipout{\hbox{\localcolortrue\color[#1]{}}}% real ones
\let\doexeccolorP\doexeccolorPindex
- \flushatshipout{\hbox{\localcolortrue\color[#1]}}% index one
+ \flushatshipout{\hbox{\localcolortrue\color[#1]{}}}% index one
\egroup}
% \def\checkpredefinedcolor[#1]%
@@ -1151,15 +1152,6 @@
\expandafter\noexectransparency
\fi}
-%\def\doexectransparency#1:#2\od
-% {\global\@EA\chardef\csname\@@currenttransparent\endcsname % nasty
-% \ifcase#1\space
-% \zerocount
-% \else
-% \plusone
-% \dostarttransparency{#1}{#2}%
-% \fi}
-
\def\doexectransparency#1:#2\od
{\ifcase#1\space
\global\intransparentfalse
diff --git a/tex/context/base/colo-run.tex b/tex/context/base/colo-run.tex
index 8d21430e1..0b44f2ee5 100644
--- a/tex/context/base/colo-run.tex
+++ b/tex/context/base/colo-run.tex
@@ -239,24 +239,29 @@
% \def\execcolorRCSP#1:{\csname execcolor#1\endcsname} -> \execcolorR
\gdef\dogetcolorcomponents#1%
- {\startnointerference
- \localcolortrue
- \def\doexeccolorR ##1:##2:##3:##4:##5\od{\global\globalscratchtoks{\NC\Od#1 \NC#1\NC a=\Do##4 \enspace t=\Do##5 \NC r=\Do##1 \enspace g=\Do##2 \enspace b=\Do##3 \NC\NR}}%
- \def\doexeccolorC##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC#1\NC a=\Do##5 \enspace t=\Do##6 \NC c=\Do##1 \enspace m=\Do##2 \enspace y=\Do##3 \enspace k=\Do##4 \NC\NR}}%
- \def\doexeccolorS ##1:##2:##3\od{\global\globalscratchtoks{\NC\Od#1 \NC#1\NC a=\Do##2 \enspace t=\Do##3 \NC s=\Do##1 \NC\NR}}%
- %\def\doexeccolorP##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC#1\NC a=\Do##5 \enspace t=\Do##6 \NC p=\Do##4 \enspace f=\Do##2 \enspace d=\Do##3 \enspace n=##1 \NC\NR}}%
- \def\doexeccolorP##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC#1\NC a=\Do##5 \enspace t=\Do##6 \NC p=\Do##4 \enspace n=##1 \NC\NR}}%
- \let\doexeccolorPindex\doexeccolorP
- \backgroundline[#1]{}%
- \stopnointerference
+ {\doifelsenothing{#1}
+ {\global\globalscratchtoks{\TB}}
+ {\startnointerference
+ \localcolortrue
+ \def\doexeccolorR ##1:##2:##3:##4:##5\od{\global\globalscratchtoks{\NC\Od#1 \NC\Nm #1 \NC a=\Do##4 \enspace t=\Do##5 \NC r=\Do##1 \enspace g=\Do##2 \enspace b=\Do##3 \NC\NR}}%
+ \def\doexeccolorC##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC\Nm #1 \NC a=\Do##5 \enspace t=\Do##6 \NC c=\Do##1 \enspace m=\Do##2 \enspace y=\Do##3 \enspace k=\Do##4 \NC\NR}}%
+ \def\doexeccolorS ##1:##2:##3\od{\global\globalscratchtoks{\NC\Od#1 \NC\Nm #1 \NC a=\Do##2 \enspace t=\Do##3 \NC s=\Do##1 \NC\NR}}%
+ %\def\doexeccolorP##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC\Nm #1 \NC a=\Do##5 \enspace t=\Do##6 \NC p=\Do##4 \enspace f=\Do##2 \enspace d=\Do##3 \enspace n=##1 \NC\NR}}%
+ \def\doexeccolorP##1:##2:##3:##4:##5:##6\od{\global\globalscratchtoks{\NC\Od#1 \NC\Nm #1 \NC a=\Do##5 \enspace t=\Do##6 \NC p=\Do##4 \enspace n=##1 \NC\NR}}%
+ \let\doexeccolorPindex\doexeccolorP
+ \backgroundline[#1]{}%
+ \stopnointerference}%
\appendetoks\the\globalscratchtoks\to\scratchtoks}
+\newdimen\colorcomponentwidth % for my eyes only
+
\gdef\showcolorcomponents[#1]%
{\bgroup
- \def\Od##1 {\backgroundline[##1]{\strut\quad \color[white]{white}\quad\color[black]{black}\quad\quad}}%
+ \def\Od##1 {\backgroundline[##1]{\strut\enspace\color[white]{white}\enspace\color[black]{black}\enspace}}%
\def\Do##1 {\twodigitrounding{##1}}%
+ \def\Nm##1 {\ifdim\colorcomponentwidth>\zeropoint\hbox to \colorcomponentwidth\fi{##1}}%
\scratchtoks\emptytoks
- \appendtoks\starttabulate[|l|l|l|l|]\to\scratchtoks
+ \appendtoks\starttabulate[|l|l|l|p|]\to\scratchtoks
\appendtoks\NC color \NC name \NC transparency \NC specification \NC\NR\TB\to\scratchtoks
\processcommacommand[#1]\dogetcolorcomponents
\appendtoks\stoptabulate\to\scratchtoks
diff --git a/tex/context/base/cont-mtx.tex b/tex/context/base/cont-mtx.tex
new file mode 100644
index 000000000..833785a7f
--- /dev/null
+++ b/tex/context/base/cont-mtx.tex
@@ -0,0 +1,25 @@
+%D \module
+%D [ file=cont-mtx,
+%D version=2006.01.01, % no date -)
+%D title=\CONTEXT\ Miscellaneous Macros,
+%D subtitle=Experimental MetaTeX Macros,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+% format generation : texexec --make --all --luatex
+%
+% pdftex compatible run : texexec --luatex filename
+% aleph compatible run : texexec --luatex --output=dvipdfmx filename
+%
+% or, at top of tex file : % engine=luatex
+
+% \loadcorefile{meta-xxx.tex}
+
+%D Here we load files that are not yet part of the core.
+
+\endinput
diff --git a/tex/context/base/cont-new.mkiv b/tex/context/base/cont-new.mkiv
new file mode 100644
index 000000000..b7b069b45
--- /dev/null
+++ b/tex/context/base/cont-new.mkiv
@@ -0,0 +1,93 @@
+%D \module
+%D [ file=cont-new,
+%D version=2006.10.04,
+%D title=\CONTEXT\ Miscellaneous Macros,
+%D subtitle=New Macros,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\ctxlua { fonts.define.method = 2 }
+
+\unprotect
+
+\appendtoks
+ \ctxlua{garbagecollector.update()}%
+\to \everyshipout
+
+% texmf.instance will become just texmf
+
+\appendtoks
+ \writestatus\m!lua{input load time - \ctxlua{input.loadtime(texmf.instance)} seconds}%
+ \writestatus\m!lua{fonts load time - \ctxlua{input.loadtime(fonts)} seconds}%
+ \writestatus\m!lua{mps conversion time - \ctxlua{input.loadtime(mptopdf)} seconds}%
+ \writestatus\m!lua{node processing time - \ctxlua{input.loadtime(nodes)} second}%
+ \writestatus\m!lua{used config path - \ctxlua{tex.print(cache.configpath(texmf.instance))}}%
+ \writestatus\m!lua{used cache path - \ctxlua{tex.print(cache.path)}}%
+ \writestatus\m!lua{modules/dumps/instances - \ctxlua{tex.print((status.luabytecodes-500).."/"..input.storage.done.."/"..status.luastates)}}%
+ \writestatus\m!lua{current memory usage - \ctxlua{tex.print(status.luastate_bytes)} bytes}%
+ \writestatus\m!lua{loaded fonts - \ctxlua{tex.print(fonts.logger.report())}}%
+\to \everystoptext
+
+\def\resettimer {\ctxlua{environment.starttime = os.clock()}}
+\def\elapsedtime {\ctxlua{tex.sprint(os.clock()-environment.starttime)}}
+\let\elapsedseconds \elapsedtime
+
+%D Fonts (experimental AFM loading}
+
+% \ctxlua {
+% remapper.define('encoding','^lm' ,'^(.*)$','lm-\letterpercent1')
+% remapper.define('encoding','^qbk','^(.*)$','q-\letterpercent1')
+% remapper.define('encoding','^qcs','^(.*)$','q-\letterpercent1')
+% remapper.define('encoding','^qpl','^(.*)$','q-\letterpercent1')
+% remapper.define('encoding','^qtm','^(.*)$','q-\letterpercent1')
+% }
+
+\appendtoksonce \loadallXfontmapfiles \to \everyPDFxform
+\appendtoksonce \loadallXfontmapfiles \to \everyPDFximage
+\appendtoksonce \loadallXfontmapfiles \to \everystarttext
+\appendtoksonce \loadallXfontmapfiles \to \everybeforepagebody
+
+\def\loadallXfontmapfiles{\ctxlua {
+ local s = fonts.map.flushlines("pdftex","")
+ tex.sprint(tex.ctxcatcodes,s)
+}}
+
+% \ctxlua{
+% do
+% local pth = "." .. io.fileseparator .. "tmp" .. io.fileseparator .. "\jobname"
+% texio.write_nl("CREATING "..pth)
+% os.execute("mkdir " .. pth)
+% end
+% input.output_files = { }
+% callback.register('find_write_file', function(id,name)
+% input.output_files[name] = file.join(".","tmp","\jobname",name)
+% texio.write_nl("REDIRECTING OUTPUT "..name.. " TO " .. input.output_files[name])
+% return input.output_files[name]
+% end)
+% callback.register('find_read_file', function(id,name)
+% local sname = string.gsub(name,"^\letterpercent./","")
+% if input.output_files[sname] then
+% return input.output_files[name]
+% elseif string.find(sname,"^\jobname[\letterpercent.\letterpercent-]") then
+% local n = file.join(".","tmp","\jobname",sname)
+% local f = io.open(n)
+% if f then
+% input.output_files[name] = n
+% texio.write_nl("REDIRECTING INPUT "..sname.. " TO " .. n)
+% f:close()
+% return n
+% else
+% return input.findtexfile(texmf.instance,name)
+% end
+% else
+% return input.findtexfile(texmf.instance,name)
+% end
+% end)
+% }
+
+\protect \endinput
diff --git a/tex/context/base/context-debug.lmx b/tex/context/base/context-debug.lmx
new file mode 100644
index 000000000..88ca0b450
--- /dev/null
+++ b/tex/context/base/context-debug.lmx
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tex/context/base/context.css b/tex/context/base/context.css
new file mode 100644
index 000000000..ef4a44cea
--- /dev/null
+++ b/tex/context/base/context.css
@@ -0,0 +1,226 @@
+body {
+ color: #FFFFFF ;
+ background-color: ;
+ font-family: optima, verdana, futura, "lucida sans", arial, geneva, helvetica, sans ;
+ font-size: 12px ;
+ line-height: 18px ;
+}
+a:link, a:active, a:visited {
+ color: #FFFFFF ;
+}
+a.dir-view:link, a.dir-view:active, a.dir-view:visited {
+ color: #FFFFFF ;
+ text-decoration: underline ;
+}
+h1, .title {
+ font-style: normal ;
+ font-weight: normal ;
+ font-size: 18px ;
+ line-height: 18px ;
+ margin-bottom: 20px ;
+}
+h2, .subtitle {
+ font-style: normal ;
+ font-weight: normal ;
+ font-size: 12px ;
+ margin-top: 18px ;
+ margin-bottom: 18px ;
+}
+table {
+ line-height: 18px ;
+ font-size: 12px ;
+ margin: 0 ;
+}
+p, li {
+ max-width: 60em ;
+}
+.empty-line {
+ margin-top: 4px ;
+}
+.more-room {
+ margin-right: 1.5em ;
+}
+.much-more-room {
+ margin-right: 3em ;
+}
+#main {
+ position: absolute;
+ left: 10% ;
+ top: 10% ;
+ right: 10% ;
+ bottom: 10% ;
+ z-index: 2 ;
+ width: 80% ;
+ height: 80% ;
+ padding: 0% ;
+ margin: 0% ;
+ overflow: auto ;
+ border-style: none ;
+ border-width: 0 ;
+ background-color: ;
+}
+#main-settings {
+ margin: 12px ;
+ x_max-width: 60em ;
+ line-height: 18px ;
+ font-size: 12px ;
+}
+#left {
+ position: absolute;
+ top : 10% ;
+ left: 0% ;
+ bottom: 0%;
+ right: 90% ;
+ z-index: 1 ;
+ width: 10% ;
+ height: 90% ;
+ padding: 0% ;
+ margin: 0% ;
+ font-size: 16px ;
+ border-style: none ;
+ border-width: 0 ;
+ background-color: ;
+}
+#right {
+ position: absolute;
+ top : 0% ;
+ left: 90% ;
+ bottom: 10% ;
+ right: 0% ;
+ z-index: 1 ;
+ width: 10% ;
+ height: 90% ;
+ padding: 0% ;
+ margin: 0% ;
+ font-size: 16px ;
+ border-style: none ;
+ border-width: 0 ;
+ background-color: ;
+ _margin-left: -15px ;
+}
+#bottom {
+ position: absolute ;
+ left: 10% ;
+ right: 0% ;
+ top: 90% ;
+ bottom: 0% ;
+ z-index: 1 ;
+ width: 90% ;
+ height: 10% ;
+ padding: 0% ;
+ margin: 0% ;
+ font-size: 16px ;
+ border-style: none ;
+ border-width: 0 ;
+ background-color: ;
+}
+#top {
+ position: absolute ;
+ left: 0% ;
+ right: 10% ;
+ top: 0% ;
+ bottom: 90% ;
+ z-index: 1 ;
+ width: 90% ;
+ height: 10% ;
+ padding: 0% ;
+ margin: 0% ;
+ font-size: 16px ;
+ border-style: none ;
+ border-width: 0 ;
+ background-color: ;
+}
+#top-one {
+ position: absolute ;
+ bottom: 50% ;
+ width: 100% ;
+ buggedheight: 100% ;
+}
+#top-two {
+ position: relative ;
+ margin-bottom: -9px ;
+ margin-left: 12px ;
+ margin-right: 12px ;
+ line-height: 18px ;
+ text-align: right ;
+ vertical-align: middle ;
+}
+#bottom-one {
+ position: absolute ;
+ bottom: 50% ;
+ width: 100% ;
+ buggedheight: 100% ;
+}
+#bottom-two {
+ position: relative ;
+ margin-bottom: -9px ;
+ margin-left: 12px ;
+ margin-right: 12px ;
+ line-height: 18px ;
+ text-align: left ;
+ vertical-align: middle ;
+}
+#left-one {
+ position: absolute ;
+ width: 100% ;
+ buggedheight: 100% ;
+}
+#left-two {
+ position: relative ;
+ margin-top: 12px ;
+ line-height: 18px ;
+ text-align: center ;
+ vertical-align: top ;
+}
+#right-one {
+ display: table ;
+ height: 100% ;
+ width: 100% ;
+}
+#right-two {
+ display: table-row ;
+ height: 100% ;
+ width: 100% ;
+}
+#right-three {
+ display: table-cell ;
+ width: 100% ;
+ vertical-align: bottom ;
+ _position: absolute ;
+ _top: 100% ;
+}
+#right-four {
+ text-align: center ;
+ margin-bottom: 2ex ;
+ _position: relative ;
+ _top: -100% ;
+}
+#more-top {
+ position: absolute;
+ top: 0% ;
+ left: 90% ;
+ bottom: 90%;
+ right: 0% ;
+ z-index: 3 ;
+ width: 10% ;
+ height: 10% ;
+ padding: 0% ;
+ margin: 0% ;
+ border-style: none ;
+ border-width: 0 ;
+}
+#more-top-settings {
+ text-align: center ;
+}
+#more-right-settings {
+ margin-right: 12px ;
+ margin-left: 12px ;
+ line-height: 18px ;
+ font-size: 10px ;
+ text-align: center ;
+}
+#right-safari {
+ display: table ;
+ width: 100% ;
+ height: 100% ;
+}
diff --git a/tex/context/base/context.tex b/tex/context/base/context.tex
index 70e4dc67a..8a412745b 100644
--- a/tex/context/base/context.tex
+++ b/tex/context/base/context.tex
@@ -42,7 +42,7 @@
%D your styles an modules.
\edef\contextformat {\jobname}
-\edef\contextversion{2007.07.25 12:35}
+\edef\contextversion{2007.08.07 01:37}
%D For those who want to use this:
@@ -622,6 +622,15 @@
\writeline\writebanner{\copyrightversion}\writeline
+\unprotect
+\beginLUATEX
+ \appendtoks
+ \writestatus\m!lua{used config path - \ctxlua{tex.print(cache.configpath(texmf.instance))}}%
+ \writestatus\m!lua{used cache path - \ctxlua{tex.print(cache.path)}}%
+ \to \everydump
+\endLUATEX
+\protect
+
% %D Except from english, no hyphenation patterns are loaded
% %D yet. Users can specify their needs in the next module:
%
diff --git a/tex/context/base/core-buf.lua b/tex/context/base/core-buf.lua
new file mode 100644
index 000000000..57b5274ff
--- /dev/null
+++ b/tex/context/base/core-buf.lua
@@ -0,0 +1,377 @@
+-- filename : core-buf.lua
+-- comment : companion to core-buf.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- ctx lua reference model / hooks and such
+-- to be optimized
+
+if not versions then versions = { } end versions['core-buf'] = 1.001
+
+if unicode and not utf then utf = unicode.utf8 end
+
+buffers = { }
+buffers.data = { }
+buffers.hooks = { }
+buffers.flags = { }
+buffers.commands = { }
+
+function buffers.erase(name)
+ buffers.data[name] = nil
+end
+
+function buffers.set(name, str)
+ buffers.data[name] = { str } -- CHECK THIS
+end
+
+function buffers.append(name, str)
+ buffers.data[name] = (buffers.data[name] or "") .. str
+end
+
+buffers.flags.store_as_table = true
+
+-- to be sorted out: crlf + \ ; slow now
+
+function buffers.grab(name,begintag,endtag,data)
+ if not buffers.data[name] or buffers.data[name] == "" then
+ buffers.data[name] = ""
+ buffers.level = 0
+ end
+ buffers.level = buffers.level + data:count("\\"..begintag) - data:count("\\"..endtag)
+ local more = buffers.level>0
+ if more then
+ buffers.data[name] = buffers.data[name] .. data .. endtag
+ buffers.level = buffers.level - 1
+ else
+ if buffers.data[name] == "" then
+ buffers.data[name] = data:sub(1,#data-1)
+ else
+ buffers.data[name] = buffers.data[name] .. "\n" .. data:sub(1,#data-1)
+ end
+ buffers.data[name] = buffers.data[name]:gsub("[\010\013]$","")
+ if buffers.flags.store_as_table then
+ -- todo: specific splitter, do we really want to erase the spaces?
+ buffers.data[name] = string.split(buffers.data[name]," *[\010\013]")
+ end
+ end
+ cs.testcase(more)
+end
+
+function buffers.doifelsebuffer(name)
+ cs.testcase(buffers.data[name] ~= nil)
+end
+
+buffers.flags.optimize_verbatim = true
+buffers.flags.count_empty_lines = false
+
+buffers.commands.no_break = "\\doverbatimnobreak"
+buffers.commands.do_break = "\\doverbatimgoodbreak"
+buffers.commands.begin_of_line_command = "\\doverbatimbeginofline"
+buffers.commands.end_of_line_command = "\\doverbatimendofline"
+buffers.commands.empty_line_command = "\\doverbatimemptyline"
+
+function buffers.verbatimbreak(n,m)
+ if buffers.flags.optimize_verbatim then
+ if (n==2) or (n==m) then
+ tex.sprint(buffers.commands.no_break)
+ else
+ tex.sprint(buffers.commands.do_break)
+ end
+ end
+end
+
+function buffers.type(name)
+ if buffers.data[name] then
+ if type(buffers.data[name]) == "string" then
+ -- todo: use linestepper (no need for a table)
+ local lines = string.split(buffers.data[name]," *[\010\013]")
+ local line, n, m = 0, 0, #lines
+ for _,str in ipairs(lines) do
+ n, line = buffers.typeline(str, n, m, line)
+ end
+ else
+ local line, n, m = 0, 0, #buffers.data[name]
+ for _,str in ipairs(buffers.data[name]) do
+ n, line = buffers.typeline(str, n, m, line)
+ end
+ end
+ end
+end
+
+function buffers.typefile(name)
+ local t = input.openfile(name)
+ if t then
+ local line, n, m = 0, 0, t.noflines
+ while true do
+ str = t.reader(t)
+ if str then
+ n, line = buffers.typeline(str, n, m, line)
+ else
+ break
+ end
+ end
+ t.close()
+ end
+end
+
+function buffers.typeline(str,n,m,line)
+ n = n + 1
+ buffers.verbatimbreak(n,m)
+ if str:find("%S") then
+ line = line + 1
+ buffers.hooks.begin_of_line(line)
+ buffers.hooks.flush_line(buffers.hooks.line(str))
+ buffers.hooks.end_of_line()
+ else
+ if buffers.flags.count_empty_lines then
+ line = line + 1
+ end
+ buffers.hooks.empty_line(line)
+ end
+ return n, line
+end
+
+function buffers.get(name)
+ if buffers.data[name] then
+ if type(buffers.data[name]) == "table" then
+ for _,v in ipairs(buffers.data[name]) do
+ tex.print(v)
+ end
+ else
+ string.piecewise(buffers.data[name], " *[\010\013]", tex.print)
+ end
+ end
+end
+
+function buffers.inspect(name)
+ if buffers.data[name] then
+ if type(buffers.data[name]) == "table" then
+ for _,v in ipairs(buffers.data[name]) do
+ if v == "" then
+ tex.sprint(tex.ctxcatcodes,"[crlf]\\par ")
+ else
+ tex.sprint(tex.ctxcatcodes,(string.gsub("(.)",function(c)
+ return " [" .. string.byte(c) .. "] "
+ end)) .. "\\par")
+ end
+ end
+ else
+ tex.sprint(tex.ctxcatcodes,(string.gsub(buffers.data[name],"(.)",function(c)
+ return " [" .. string.byte(c) .. "] "
+ end)))
+ end
+ end
+end
+
+-- maybe just line(n,str) empty(n,str)
+
+buffers.visualizers = { }
+buffers.visualizers.default = { }
+buffers.visualizers.tex = { }
+buffers.visualizers.mp = { }
+
+buffers.visualizers.escapetoken = nil
+buffers.visualizers.tablength = 7
+
+buffers.visualizers.enabletab = false
+buffers.visualizers.enableescape = false
+
+function buffers.visualizers.reset()
+ buffers.visualizers.enabletab = false
+ buffers.visualizers.enableescape = false
+ buffers.currentvisualizer = 'default'
+end
+
+buffers.currentvisualizer = 'default'
+
+function buffers.setvisualizer(str)
+ buffers.currentvisualizer = string.lower(str)
+ if not buffers.visualizers[buffers.currentvisualizer] then
+ buffers.currentvisualizer = 'default'
+ end
+end
+
+function buffers.doifelsevisualizer(str)
+ cs.testcase((str ~= "") and (buffers.visualizers[string.lower(str)] ~= nil))
+end
+
+-- calling routines, don't change
+
+function buffers.hooks.flush_line(str,nesting)
+ if buffers.visualizers[buffers.currentvisualizer].flush_line then
+ buffers.visualizers[buffers.currentvisualizer].flush_line(str,nesting)
+--~ elseif nesting then
+--~ buffers.visualizers.flush_nested(str,false) -- no real nesting
+ else
+ buffers.visualizers.default.flush_line(str,nesting)
+ end
+end
+
+function buffers.hooks.begin_of_line(n)
+ if buffers.visualizers[buffers.currentvisualizer].begin_of_line then
+ buffers.visualizers[buffers.currentvisualizer].begin_of_line(n)
+ else
+ buffers.visualizers.default.begin_of_line(n)
+ end
+end
+
+function buffers.hooks.end_of_line()
+ if buffers.visualizers[buffers.currentvisualizer].end_of_line then
+ buffers.visualizers[buffers.currentvisualizer].end_of_line()
+ else
+ buffers.visualizers.default.end_of_line(str)
+ end
+end
+
+function buffers.hooks.empty_line()
+ if buffers.visualizers[buffers.currentvisualizer].empty_line then
+ buffers.visualizers[buffers.currentvisualizer].empty_line()
+ else
+ buffers.visualizers.default.empty_line()
+ end
+end
+
+function buffers.hooks.line(str)
+ if buffers.visualizers[buffers.currentvisualizer].line then
+ return buffers.visualizers[buffers.currentvisualizer].line(str)
+ else
+ return buffers.visualizers.default.line(str)
+ end
+end
+
+-- defaults
+
+function buffers.visualizers.default.flush_line(str)
+ tex.sprint(tex.ctxcatcodes,buffers.escaped(str))
+end
+
+function buffers.visualizers.default.begin_of_line(n)
+ tex.sprint(tex.ctxcatcodes, buffers.commands.begin_of_line_command .. "{" .. n .. "}")
+end
+
+function buffers.visualizers.default.end_of_line()
+ tex.sprint(tex.ctxcatcodes,buffers.commands.end_of_line_command)
+end
+
+function buffers.visualizers.default.empty_line()
+ tex.sprint(tex.ctxcatcodes,buffers.commands.empty_line_command)
+end
+
+function buffers.visualizers.default.line(str)
+ return str
+end
+
+-- special one
+
+buffers.commands.nested = "\\switchslantedtype "
+
+-- todo : utf + faster
+
+function buffers.visualizers.flush_nested(str, enable) -- no utf, kind of obsolete mess
+ local result, c, nested, i = "", "", 0, 1
+ local sb, ss, sf = string.byte, string.sub, string.find
+ while i < #str do -- slow
+ c = ss(str,i,i+1)
+ if c == "<<" then
+ nested = nested + 1
+ if enable then
+ result = result .. "{" .. buffers.commands.nested
+ else
+ result = result .. "{"
+ end
+ i = i + 2
+ elseif c == ">>" then
+ if nested > 0 then
+ nested = nested - 1
+ result = result .. "}"
+ end
+ i = i + 2
+ else
+ c = ss(str,i,i)
+ if c == " " then
+ result = result .. "\\obs "
+ elseif sf(c,"%a") then
+ result = result .. c
+ else
+ result = result .. "\\char" .. sb(c) .. " "
+ end
+ i = i + 1
+ end
+ end
+ result = result .. "\\char" .. sb(ss(str,i,i)) .. " " .. string.rep("}",nested)
+ tex.sprint(tex.ctxcatcodes,result)
+end
+
+-- handy helpers
+--
+-- \sop[color] switch_of_pretty
+-- \bop[color] begin_of_pretty
+-- \eop end_of_pretty
+-- \obs obeyedspace
+-- \char special characters
+
+buffers.currentcolors = { }
+
+function buffers.change_state(n, state, result)
+ if n then
+ if state ~= n then
+ if state > 0 then
+ result[#result+1] = "\\sop[" .. buffers.currentcolors[n] .. "]"
+ else
+ result[#result+1] = "\\bop[" .. buffers.currentcolors[n] .. "]"
+ end
+ return n
+ end
+ elseif state > 0 then
+ result[#result+1] = "\\eop "
+ return 0
+ end
+ return state
+end
+
+function buffers.finish_state(state, result)
+ if state > 0 then
+ result[#result+1] = "\\eop "
+ return 0
+ else
+ return state
+ end
+end
+
+buffers.open_nested = string.rep("\\char"..string.byte('<').." ",2)
+buffers.close_nested = string.rep("\\char"..string.byte('>').." ",2)
+
+function buffers.replace_nested(result)
+ return (string.gsub(string.gsub(result,buffers.open_nested,"{"),buffers.close_nested,"}"))
+end
+
+function buffers.flush_result(result,nested)
+ if nested then
+ tex.sprint(tex.ctxcatcodes,buffers.replace_nested(table.concat(result,"")))
+ else
+ tex.sprint(tex.ctxcatcodes,table.concat(result,""))
+ end
+end
+
+function buffers.escaped(str)
+ local sb, sf = utf.byte, utf.find
+ return (utf.gsub(str,"(.)", function(c)
+ if sf(c,"^(%a%d)$") then
+ return c
+ elseif c == " " then
+ return "\\obs "
+ else
+ return "\\char" .. sb(c) .. " "
+ end
+ end))
+end
+
+function buffers.escaped_chr(ch)
+ local b = utf.byte(ch)
+ if b == 32 then
+ return "\\obs "
+ else
+ return "\\char" .. b .. " "
+ end
+end
diff --git a/tex/context/base/core-buf.mkiv b/tex/context/base/core-buf.mkiv
new file mode 100644
index 000000000..09f4f552e
--- /dev/null
+++ b/tex/context/base/core-buf.mkiv
@@ -0,0 +1,107 @@
+%D \module
+%D [ file=core-buf, % blocks are moved to core-blk
+%D version=2000.01.05,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Buffers,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-buf}{1.001}
+
+\def\mkresetbuffer
+ {\ctxlua{buffers.erase("\currentbuffer")}}
+
+\long\def\mksetbuffer#1%
+ {\ctxlua{buffers.set("\currentbuffer", \!!bs\detokenize{#1}\!!es)}}
+
+\long\def\mkstartbuffer#1#2#3#4#5%
+ {\doifelsenothing{#4}
+ {\expanded{\setbuffercapsules{\e!start\v!buffer}{\e!stop\v!buffer}}%
+ \letvalue\bufferstop\relax}
+ {\setbuffercapsules{#3}{#4}}% not expanded, can be \cs \cs !
+ \expanded{\dodowithbuffer
+ {\currentbuffer}
+ {\bufferstart}
+ {\bufferstop}
+ {\donothing}
+ {#5% \egroup
+ \noexpand\getvalue{\bufferstop}}}}
+
+\def\mkdobuffer#1%
+ {#1}
+
+\def\mkdoifelsebuffer#1%
+ {\ctxlua{buffers.doifelsebuffer("#1")}}
+
+\def\mkgetbuffer
+ {\ctxlua{buffers.get("\currentbuffer")}}
+
+% will move
+
+\ifx\mkinitializeverbatim\undefined \def\mkinitializeverbatim{\tttf} \fi
+
+\def\mktypebuffer
+ {\mkdotypebuffer{\v!file}{}{\currentbuffer}}
+
+\def\mkprocessbufferverbatim
+ {\mkinitializeverbatim
+ \ctxlua{buffers.type("\currentbuffer")}}
+
+\def\mkprocessbufferlinesverbatim#1#2#3%
+ {#2%
+ % todo, set up numbers
+ \mkinitializeverbatim
+ \ctxlua{buffers.type("\currentbuffer")}
+ #3}
+
+\def\mkdotypebuffer#1#2#3% see dodotypefile
+ {\mkdoifelsebuffer{#3}
+ {\dosometyping{#1}{#2}{#3}\mkprocessbufferverbatim\mkprocessbufferlinesverbatim}
+ {\reporttypingerror{#3}}}
+
+\def\setbuffercapsules#1#2%
+ {\edef\bufferstart{\strippedcsname#1}\edef\bufferstart{\scantextokens\expandafter{\bufferstart}}%
+ \edef\bufferstop {\strippedcsname#2}\edef\bufferstop {\scantextokens\expandafter{\bufferstop }}}
+
+\def\dowithbuffer#1#2#3% name, startsequence, stopsequence, before, after
+ {\setbuffercapsules{#2}{#3}%
+ \expanded{\dodowithbuffer{#1}{\bufferstart}{\bufferstop}}}
+
+\long\def\dodowithbuffer#1#2#3#4#5% name, startsequence, stopsequence, before, after
+ {#4%
+ \bgroup
+ \setcatcodetable \vrbcatcodes
+ \catcode`\\=12
+ \ctxlua{buffers.erase("#1")}%
+ \long\def\nododowithbuffer
+ {\egroup
+ #5}%
+ \long\def\dododowithbuffer##1#3% is detokenize needed? TEST
+ {\ctxlua
+ {buffers.grab("#1","#2","#3",\!!bs\detokenize{##1}\!!es)}
+ \dododowithbuffer
+ \nododowithbuffer}%
+ \dododowithbuffer}
+
+% kind of redundant in mkiv
+
+\let\mkstartmemorybuffer\startbuffer
+\let\mkstartfilebuffer \startbuffer
+
+% bonus
+
+\def\inspectbuffer
+ {\dosingleempty\doinspectbuffer}
+
+\def\doinspectbuffer[#1]%
+ {\setcurrentbuffer{#1}%
+ \ctxlua{buffers.inspect("\currentbuffer")}}
+
+\protect \endinput
diff --git a/tex/context/base/core-con.lua b/tex/context/base/core-con.lua
new file mode 100644
index 000000000..349d33688
--- /dev/null
+++ b/tex/context/base/core-con.lua
@@ -0,0 +1,164 @@
+if not modules then modules = { } end modules ['char-con'] = {
+ version = 1.001,
+ comment = "companion to core-con.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module implements a bunch of conversions. Some are more
+efficient than their counterpart, some are even
+slower but look nicer this way.
+
+
Some code may move to a module in the language namespace.
+--ldx]]--
+
+convert = convert or { }
+language = language or { }
+
+language.counters = {
+ ['**'] = {
+ 0x0061, 0x0062, 0x0063, 0x0064, 0x0065,
+ 0x0066, 0x0067, 0x0068, 0x0069, 0x006A,
+ 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
+ 0x0075, 0x0076, 0x0077, 0x0078, 0x0079,
+ 0x007A
+ },
+ ['sl'] = {
+ 0x0061, 0x0062, 0x0063, 0x010D, 0x0064,
+ 0x0065, 0x0066, 0x0067, 0x0068, 0x0069,
+ 0x006A, 0x006B, 0x006C, 0x006D, 0x006E,
+ 0x006F, 0x0070, 0x0072, 0x0073, 0x0161,
+ 0x0074, 0x0075, 0x0076, 0x007A, 0x017E
+ },
+ ['gr'] = {
+ 0x0391, 0x0392, 0x0393, 0x0394, 0x0395,
+ 0x0396, 0x0397, 0x0398, 0x0399, 0x039A,
+ 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
+ 0x03A0, 0x03A1, 0x03A3, 0x03A4, 0x03A5,
+ 0x03A6, 0x03A7, 0x03A8, 0x03A9
+ },
+ ['arabic'] = {
+ 0x0660, 0x0661, 0x0662, 0x0663, 0x0664,
+ 0x0665, 0x0666, 0x0667, 0x0668, 0x0669
+ },
+ ['persian'] = {
+ 0x06F0, 0x06F1, 0x06F2, 0x06F3, 0x06F4,
+ 0x06F5, 0x06F6, 0x06F7, 0x06F8, 0x06F9
+ },
+ ['thai'] = {
+ 0xE050, 0xE051, 0xE052, 0xE053, 0xE054,
+ 0xE055, 0xE056, 0xE057, 0xE058, 0xE059
+ },
+ ['devangari'] = {
+ 0x0966, 0x0967, 0x0968, 0x0969, 0x096A,
+ 0x096B, 0x096C, 0x096D, 0x096E, 0x096F
+ },
+ ['gurmurkhi'] = {
+ 0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A,
+ 0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F
+ },
+ ['gujarati'] = {
+ 0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA,
+ 0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF
+ },
+ ['tibetan'] = {
+ 0x0F20, 0x0F21, 0x0F22, 0x0F23, 0x0F24,
+ 0x0F25, 0x0F26, 0x0F27, 0x0F28, 0x0F29
+ }
+}
+
+function convert.chr(n, m)
+ if n > 0 and n < 27 then
+ tex.sprint(string.char(n+m))
+ end
+end
+
+function convert.maxchrs(n,m,cmd)
+ if n <= m then
+ tex.sprint(tex.texcatcodes, cmd .. "{" .. n .. "}")
+ else
+ convert.maxchrs(math.floor((n-1)/m),m,cmd)
+ tex.sprint(tex.texcatcodes, cmd .. "{" .. ((n-1)%m + 1) .. "}")
+ end
+end
+function convert.chrs(n,m)
+ if n <= 26 then
+ tex.sprint(string.char(n+m))
+ else
+ convert.chrs(math.floor((n-1)/26),m)
+ tex.sprint(string.char(((n-1)%26 + 1)+m))
+ end
+end
+
+function convert.alphabetic(n,code)
+ local code = language.counters[code] or language.counters['**']
+ convert.do_alphabetic(n,#code,function(n) return code[n] end)
+end
+
+function convert.Alphabetic(n,code)
+ local code = language.counters[code] or language.counters['**']
+ convert.do_alphabetic(n,#code,function(n) return characters.uccode(code[n]) end)
+end
+
+function convert.do_alphabetic(n,max,chr)
+ if n <= max then
+ characters.flush(chr(n))
+ else
+ convert.do_alphabetic(math.floor((n-1)/max),max,chr)
+ characters.flush(chr((n-1)%max+1))
+ end
+end
+
+function convert.character(n) convert.chr (n,96) end
+function convert.Character(n) convert.chr (n,64) end
+function convert.characters(n) convert.chrs(n,96) end
+function convert.Characters(n) convert.chrs(n,64) end
+
+function convert.weekday(year,month,day)
+ tex.sprint(os.date("%w",os.time{year=year,month=month,day=day})+1)
+end
+
+function convert.lpy(year)
+ return (year % 400 == 0) or ((year % 100 ~= 0) and (year % 4 == 0))
+end
+
+function convert.leapyear(year)
+ if convert.lpy(year) then tex.sprint(1) else tex.sprint(0) end
+end
+
+convert.mth = {
+ [false] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ [true] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+}
+
+function convert.nofdays(year,month)
+ tex.sprint(convert.mth[convert.lpy(year)][month])
+end
+
+function convert.year () tex.sprint(os.date("%Y")) end
+function convert.month () tex.sprint(os.date("%m")) end
+function convert.hour () tex.sprint(os.date("%H")) end
+function convert.minute () tex.sprint(os.date("%M")) end
+function convert.second () tex.sprint(os.date("%S")) end
+function convert.textime() tex.sprint(tonumber(os.date("%H"))*60+tonumber(os.date("%M"))) end
+
+convert.rom = {
+ { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' },
+ { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' },
+ { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' },
+}
+
+function convert.toroman(n)
+ if n >= 4000 then
+ return convert.toroman(math.floor(n/1000)) .. " " .. convert.toroman(n%1000)
+ else
+ return string.rep("M",math.floor(n/1000)) .. convert.rom[3][math.floor((n%1000)/100)] ..
+ convert.rom[2][math.floor((n%100)/10)] .. convert.rom[1][math.floor((n% 10)/1)]
+ end
+end
+
+function convert.romannumerals(n) return tex.sprint(string.tolower(convert.toroman(n))) end
+function convert.Romannumerals(n) return tex.sprint( convert.toroman(n) ) end
diff --git a/tex/context/base/core-con.mkiv b/tex/context/base/core-con.mkiv
new file mode 100644
index 000000000..afed14f1d
--- /dev/null
+++ b/tex/context/base/core-con.mkiv
@@ -0,0 +1,90 @@
+%D \module
+%D [ file=core-con,
+%D version=2006.09.16,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Conversion Macros,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-con}{1.001}
+
+\def\romannumerals#1{\ctxlua{convert.romannumerals(\number#1)}}
+\def\Romannumerals#1{\ctxlua{convert.Romannumerals(\number#1)}}
+
+\def\greeknumerals#1{\ctxlua{convert.alphabetic(\number#1,"gr")}}
+\def\Greeknumerals#1{\ctxlua{convert.Alphabetic(\number#1,"gr")}}
+
+\def\character #1{\ctxlua{convert.character (\number#1)}}
+\def\Character #1{\ctxlua{convert.Character (\number#1)}}
+\def\characters#1{\ctxlua{convert.characters(\number#1)}}
+\def\Characters#1{\ctxlua{convert.Characters(\number#1)}}
+
+\def\languagecharacters#1{\ctxlua{convert.alphabetic(\number#1,"\currentlanguage")}} % new
+\def\languageCharacters#1{\ctxlua{convert.Alphabetic(\number#1,"\currentlanguage")}} % new
+
+\def\getdayoftheweek#1#2#3{\normalweekday\ctxlua{convert.weekday(\number#1,\number#2,\number#3)}}
+\def\dayoftheweek #1#2#3{\doconvertday{\ctxlua{convert.weekday(\number#1,\number#2,\number#3)}}}
+
+\def\doifleapyearelse#1%
+ {\ifcase\ctxlua{convert.leapyear(\number#1)}
+ \@EA\secondoftwoarguments
+ \else
+ \@EA\firstoftwoarguments
+ \fi}
+
+\def\getdayspermonth#1#2%
+ {\edef\numberofdays{\ctxlua{convert.nofdays(\number#1,\number#2)}}}
+
+\def\dayspermonth#1#2%
+ {\ctxlua{convert.nofdays(\number#1,\number#2)}}
+
+\def\calculatecurrenttime
+ {\edef\currenthour {\ctxlua{convert.hour ()}}%
+ \edef\currentminute{\ctxlua{convert.minute()}}%
+ \edef\currentsecond{\ctxlua{convert.second()}}}
+
+% problem is that we calculate with those numbers
+%
+% \def\time {\numexpr\ctxlua{convert.textime()}\relax}
+% \def\year {\numexpr\ctxlua{convert.year ()}\relax}
+% \def\month{\numexpr\ctxlua{convert.month ()}\relax}
+% \def\day {\numexpr\ctxlua{convert.day ()}\relax}
+
+% \dayoftheweek{2006}{9}{15}
+% \doifleapyearelse{2000}{OK}{NOT OK}
+% \doifleapyearelse{2100}{NOT OK}{OK}
+% \doifleapyearelse{2004}{OK}{NOT OK}
+% \doifleapyearelse{2003}{NOT OK}{OK}
+% \dayspermonth{2000}{2}
+% [\the\normaltime=\the\time]
+
+% we could use an auxiliary macro to save some bytes in the format
+%
+% \def\dolanguagecharacters#1#2{\ctxlua{convert.alphabetic(\number#2,"#1")}}
+
+% this does not belong here, but in a lang-module
+
+\def\arabicnumerals #1{\ctxlua{convert.alphabetic(\number#1,"arabic")}}
+\def\persiannumerals #1{\ctxlua{convert.alphabetic(\number#1,"persian")}}
+\def\thainumerals #1{\ctxlua{convert.alphabetic(\number#1,"thai")}}
+\def\devanagarinumerals#1{\ctxlua{convert.alphabetic(\number#1,"devanagari")}}
+\def\gurmurkhinumerals #1{\ctxlua{convert.alphabetic(\number#1,"gurmurkhi")}}
+\def\gujaratinumerals #1{\ctxlua{convert.alphabetic(\number#1,"gujarati")}}
+\def\tibetannumerals #1{\ctxlua{convert.alphabetic(\number#1,"tibetan")}}
+
+\defineconversion[arabicnumerals] [\arabicnumerals]
+\defineconversion[persiannumerals] [\persiannumerals]
+\defineconversion[thainumerals] [\thainumerals]
+\defineconversion[devanagarinumerals][\devanagarinumerals]
+\defineconversion[gurmurkhinumerals] [\gurmurkhinumerals]
+\defineconversion[gujaratinumerals] [\gujaratinumerals]
+\defineconversion[tibetannumerals] [\tibetannumerals]
+
+\protect \endinput
diff --git a/tex/context/base/core-des.tex b/tex/context/base/core-des.tex
index 1c37b655c..d0152fddc 100644
--- a/tex/context/base/core-des.tex
+++ b/tex/context/base/core-des.tex
@@ -423,7 +423,7 @@
[\c!location,\c!headstyle,\c!style,\c!color,\c!headcolor,\c!title,
\c!width,\c!hang,\c!sample,\c!before,\c!inbetween,\c!after,\c!margin,
\c!indenting,\c!indentnext,\c!align,\c!text,\c!distance,\c!titledistance,\c!command,
- \c!titleleft,\c!titleright,\c!closesymbol,\c!closecommand]%
+ \c!titleleft,\c!titleright,\c!titlecommand,\c!closesymbol,\c!closecommand]%
\getparameters[\??dd#1]
[\c!title=\v!yes,\s!do\c!command=\normal@@descriptionhandler,
\c!type=\v!description,\c!list=,\c!listtext=,
@@ -520,9 +520,10 @@
{\doifsomething{#1}
{\doattributes{\??dd\currentdescription}\c!titlestyle\c!titlecolor
{\hskip\descriptionparameter\c!titledistance
- \descriptionparameter\c!titleleft
- \begstrut#1\endstrut
- \descriptionparameter\c!titleright}}}}
+ \descriptionparameter\c!titlecommand
+ {\descriptionparameter\c!titleleft
+ \begstrut#1\endstrut
+ \descriptionparameter\c!titleright}}}}}
\def\showdnpuretext
@@ -653,7 +654,7 @@
\c!sample,\c!hang,\c!align,\c!before,\c!inbetween,\c!after,
\c!levels,\c!way,\c!blockway,\c!separator,\c!margin,
\c!indenting,\c!indentnext,\c!stopper,\c!sectionnumber,
- \c!title,\c!titleleft,\c!titleright,\c!closesymbol,\c!closecommand]%
+ \c!title,\c!titleleft,\c!titleright,\c!titlecommand,\c!closesymbol,\c!closecommand]%
\doifassignmentelse{#4}
{\getparameters[\??dd#3#1]%
[\c!text=#1,\??dd\c!number=#1,\c!conversion=,\c!listtext=#1\space,
@@ -671,7 +672,7 @@
\c!sample,\c!hang,\c!align,\c!before,\c!inbetween,\c!after,
\c!stopper,\c!indenting,\c!indentnext,\c!left,\c!right,
\c!coupling,\c!couplingway,
- \c!title,\c!titleleft,\c!titleright,\c!closesymbol,\c!closecommand]%
+ \c!title,\c!titleleft,\c!titleright,\c!titlecommand,\c!closesymbol,\c!closecommand]%
\getparameters[\??dd#3#1]
[\c!text=#1,\??dd\c!number=#4,\c!conversion=,#5]%
%docheckenumerationnumber{#1}{#3#1}{#4}}}%
@@ -859,7 +860,8 @@
\c!titleright=),
\c!closesymbol=,
\c!closecommand=\wordright,
- \c!command=]
+ \c!command=,
+ \c!titlecommand=]
\setupenumerations
[\c!location=\v!top,
@@ -893,7 +895,8 @@
\c!closesymbol=,
\c!closecommand=\wordright,
\c!number=,
- \c!command=]
+ \c!command=,
+ \c!titlecommand=]
\setupindentations
[\c!style=\v!normal,
diff --git a/tex/context/base/core-itm.tex b/tex/context/base/core-itm.tex
index 2171ff837..42d45a5df 100644
--- a/tex/context/base/core-itm.tex
+++ b/tex/context/base/core-itm.tex
@@ -87,6 +87,10 @@
\let\currentitemoffset \!!zerocount
\def\currentitemnumber{\countervalue{\@@itemcounter\itemlevel}}
+% tricky ... we cannot use trialtypesetting here because there can be
+% multiple itemizes in e.g. a table, so we need something more advanced
+% where counters etc are reset to pre-outertrial values
+
\def\dolistreference
{\ifconditional\continuelistitems
\savetaggedtwopassdata\s!list\currentlist\currentlist{\itemlevel:\noflistelements:c:\getitemparameter\itemlevel\c!maxwidth}%
@@ -104,9 +108,9 @@
\def\checkcurrentnofitems
{\splititemtwopassdata\noflists
\iftwopassdatafound
- \ifcase\scratchcounter
+ \ifcase\itemdatan\relax % \scratchcounter
\let\currentnofitems \!!zerocount
- \let\currentminnofitems\!!zerocount
+ \let\currentminnofitems\!!plusone
\let\currentmaxnofitems\!!zerocount
\else
\scratchcounter\itemdatan\relax
@@ -615,16 +619,16 @@
\ifconditional\textlistitem\else\doifnotinset\v!text{#1}\par\fi % suboptimal
\fi
\begingroup
-% new where, ok or not / we should integrate random, intro, continue here
-% beware, the following no longer inherit from the previous level, is this ok?
-\setfalse\reverselistitem
-\setfalse\introlistitem
-\setfalse\autointrolistitem
-\setfalse\beforelistitem
-\setfalse\afterlistitem
-\setfalse\nowhitelistitem
-\setfalse\randomizeitems
-%
+ % new where, ok or not / we should integrate random, intro, continue here
+ % beware, the following no longer inherit from the previous level, is this ok?
+ \setfalse\reverselistitem
+ \setfalse\introlistitem
+ \setfalse\autointrolistitem
+ \setfalse\beforelistitem
+ \setfalse\afterlistitem
+ \setfalse\nowhitelistitem
+ \setfalse\randomizeitems
+ %
\doifinsetelse\v!intro {#1}{\settrue\introlistitem }{\setfalse\introlistitem }%
\doifinsetelse\v!random {#1}{\settrue\randomizeitems }{\setfalse\randomizeitems }%
\doifinsetelse\v!continue{#1}{\settrue\continuelistitems}{\setfalse\continuelistitems}%
@@ -636,7 +640,7 @@
\setfalse\sublistitem
\setfalse\symbollistitem
\let\marsymbol\relax
- \globallet\somdestination\empty
+ \globallet\doitemdestination\empty
\let\symsymbol\empty
\the\itemgroupcommands
\checkcurrentnofitems
@@ -781,7 +785,7 @@
{\doitemgroupitem}
\def\itemgroupbutton[#1]%
- {\gdef\somdestination{#1}%
+ {\gdef\doitemdestination{#1}%
\itemgroupitem}
\def\itemgroupdummy
@@ -974,9 +978,9 @@
\setbox8\simplealignedbox{\getitemparameter\itemlevel\c!itemalign}{\itemdataw sp}{\box8}%
\fi
\fi
- \doifsomething\somdestination
- {\setbox8\hbox{\goto{\box8}[\somdestination]}}%
- \globallet\somdestination\empty
+ \doifsomething\doitemdestination
+ {\setbox8\hbox{\goto{\box8}[\doitemdestination]}}%
+ \globallet\doitemdestination\empty
\dimen2=\getitemparameter\itemlevel\c!width\relax
% new, prevents loops when symbol is (not yet found) graphic
\ht8=\strutheight
diff --git a/tex/context/base/core-lst.tex b/tex/context/base/core-lst.tex
index 359e9b8ce..7c26b97f7 100644
--- a/tex/context/base/core-lst.tex
+++ b/tex/context/base/core-lst.tex
@@ -33,6 +33,8 @@
% number uses the text container. We use reference mapping
% (define reference) to keep track of the current ref.
+% \@@sectie == current level
+
\def\dowritetolist#1%
{\doifelsevalue{\??li#1\c!state}\v!start
\dodowritetolist\gobblefourarguments{#1}}
diff --git a/tex/context/base/core-ntb.tex b/tex/context/base/core-ntb.tex
index e8534b714..aa1ace99f 100644
--- a/tex/context/base/core-ntb.tex
+++ b/tex/context/base/core-ntb.tex
@@ -884,11 +884,11 @@
\fi
\else\ifautoTBLrowspan\ifnum\maximumrowspan>1 % max ?
% added jan 2002 because nx=* did no longer work
-\edef\savedhsize{\the\hsize}%
-\hsize\wd0\relax % new per 17/04/2006
+ \edef\savedhsize{\the\hsize}%
+ \hsize\wd0\relax % new per 17/04/2006
\checktblwidthsone % trial run
\checktblwidthstwo % real run
-\hsize\savedhsize
+ \hsize\savedhsize
%
\let\handleTBLcell\dohandleTBLcellC
\setbox\scratchbox\vbox{\trialtypesettingtrue \the\tbltoks}%
@@ -901,7 +901,7 @@
\dorecurse{\gettblcol{##1}{##2}}
{\advance\dimen2 \gettblwid\colTBL
\advance\dimen2 \tbltblcolumndistance
-\ifnum\recurselevel<\gettblcol{##1}{##2}\relax \advance\dimen2 \gettbldis\colTBL\fi
+ \ifnum\recurselevel<\gettblcol{##1}{##2}\relax \advance\dimen2 \gettbldis\colTBL\fi
\increment\colTBL}%
\advance\dimen2 -\tbltblcolumndistance
\edef\widthTBL{\the\dimen2}%
diff --git a/tex/context/base/core-obj.lua b/tex/context/base/core-obj.lua
new file mode 100644
index 000000000..b94719993
--- /dev/null
+++ b/tex/context/base/core-obj.lua
@@ -0,0 +1,36 @@
+if not modules then modules = { } end modules ['core-obj'] = {
+ version = 1.001,
+ comment = "companion to core-obj.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
We save object references in the main utility table. Objects are
+reusable components.
+--ldx]]--
+
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.objects then job.objects = { } end
+
+function job.getobjectreference(tag,default)
+ if job.objects[tag] then
+ tex.sprint(job.objects[tag][1] or default)
+ else
+ tex.sprint(default)
+ end
+end
+
+function job.getobjectreferencepage(tag,default)
+ if job.objects[tag] then
+ tex.sprint(job.objects[tag][2] or default)
+ else
+ tex.sprint(default)
+ end
+end
+
+function job.doifobjectreference(tag)
+ cs.testcase(job.objects[tag])
+end
diff --git a/tex/context/base/core-obj.mkiv b/tex/context/base/core-obj.mkiv
new file mode 100644
index 000000000..022b6908d
--- /dev/null
+++ b/tex/context/base/core-obj.mkiv
@@ -0,0 +1,55 @@
+%D \module
+%D [ file=core-obj,
+%D version=2006.10.16,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Object Handling,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\let\objectreference\gobblefourarguments % catch mkii tuo stuff
+
+\registerctxluafile{core-obj}{1.001}
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.objects then job.objects = { } end}%
+ \immediatewriteutilitytua{local ob = job.objects}%
+\to \everyopenutilities
+
+\def\mkregisterobjectreference#1#2#3%
+ {\blabelgroup
+ \expanded{\writeutilitytua{ob['#1::#2']={"#3","\noexpand\realfolio"}}}%
+ \expanded{\ctxlua{job.objects['#1::#2']={"#3","\noexpand\realfolio"}}}%
+ \elabelgroup}
+
+\def\mkoverloadobjectreference#1#2#3%
+ {\blabelgroup
+ \expanded{\ctxlua{job.objects['#1::#2']={"#3","\noexpand\realfolio"}}}%
+ \elabelgroup}
+
+\def\mkgetobjectreference#1#2#3%
+ {\blabelgroup
+ \xdef#3{\ctxlua{job.getobjectreference ('#1::#2',"\defaultobjectreference{#1}{#2}")}}%
+ \elabelgroup}
+
+\def\mkgetobjectreferencepage#1#2#3%
+ {\blabelgroup
+ \xdef#3{\ctxlua{job.getobjectreferencepage('#1::#2',"\defaultobjectpage{#1}{#2}")}}%
+ \elabelgroup}
+
+% \def\doifobjectreferencefoundelse#1#2
+% {\ctxlua{job.doifobjectreference('#1::#2')}}
+
+\def\doifobjectreferencefoundelse#1#2%
+ {\blabelgroup
+ \ctxlua{job.doifobjectreference('#1::#2')}%
+ {\elabelgroup\firstoftwoarguments}
+ {\elabelgroup\secondoftwoarguments}}
+
+\protect \endinput
diff --git a/tex/context/base/core-pos.lua b/tex/context/base/core-pos.lua
new file mode 100644
index 000000000..d5c365031
--- /dev/null
+++ b/tex/context/base/core-pos.lua
@@ -0,0 +1,95 @@
+if not modules then modules = { } end modules ['core-pos'] = {
+ version = 1.001,
+ comment = "companion to core-pos.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
We save positional information in the main utility table. Not only
+can we store much more information in but it's also
+more efficient.
+--ldx]]--
+
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.positions then job.positions = { } end
+
+function job.MPp(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[1]) else tex.sprint('0' ) end end
+function job.MPx(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[2]) else tex.sprint('0pt') end end
+function job.MPy(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[3]) else tex.sprint('0pt') end end
+function job.MPw(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[4]) else tex.sprint('0pt') end end
+function job.MPh(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[5]) else tex.sprint('0pt') end end
+function job.MPd(id) local jpi = job.positions[id] if jpi then tex.sprint(jpi[6]) else tex.sprint('0pt') end end
+
+function job.MPxy(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint('('..jpi[2]..','..jpi[3]..')')
+ else
+ tex.sprint('(0pt,0pt)')
+ end
+end
+
+function job.MPll(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint('('..jpi[2]..'-'..-jpi[3]..','..jpi[6]..')')
+ else
+ tex.sprint('(0pt,0pt)')
+ end
+end
+function job.MPlr(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint('('..jpi[2]..'+'..jpi[4]..','..jpi[3]..'-'..jpi[6]..')')
+ else
+ tex.sprint('(0pt,0pt)')
+ end
+end
+function job.MPur(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint('('..jpi[2]..'+'..jpi[4]..','..jpi[3]..'+'..jpi[5]..')')
+ else
+ tex.sprint('(0pt,0pt)')
+ end
+end
+function job.MPul(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint('('..jpi[2]..','..jpi[3]..'+'..jpi[5]..')')
+ else
+ tex.sprint('(0pt,0pt)')
+ end
+end
+
+-- todo
+
+function job.MPpos(id)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint(table.concat(jpi,',',1,6))
+ else
+ tex.sprint('0,0pt,0pt,0pt,0pt,0pt')
+ end
+end
+
+function job.MPplus(id,n)
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint(jpi[n] or '0pt')
+ else
+ tex.sprint('0pt')
+ end
+end
+
+function job.MPrest(id,default) -- 7 or 8 ?
+ local jpi = job.positions[id]
+ if jpi then
+ tex.sprint(jpi[7] or default)
+ else
+ tex.sprint(default)
+ end
+end
diff --git a/tex/context/base/core-pos.mkiv b/tex/context/base/core-pos.mkiv
new file mode 100644
index 000000000..7c80984dc
--- /dev/null
+++ b/tex/context/base/core-pos.mkiv
@@ -0,0 +1,65 @@
+%D \module
+%D [ file=core-pos,
+%D version=2006.09.18,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Positioning Support,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-pos}{1.001}
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.positions then job.positions = { } end}%
+ \immediatewriteutilitytua{local jp = job.positions}%
+\to \everyopenutilities
+
+\def\replacepospxywhd#1#2#3#4#5#6#7%
+ {\ctxlua{job.positions['#1']={\number#2,"\the\dimexpr#3\relax","\the\dimexpr#4\relax",%
+ "\the\dimexpr#5\relax","\the\dimexpr#6\relax","\the\dimexpr#7\relax"}}}
+
+\def\dolazysaveposition#1#2#3#4%
+ {\expanded{\writeutilitytua{jp['#1']={#2,"#3","#4"}}}}
+
+\def\dolazysavepositionwhd#1#2#3#4#5#6#7%
+ {\expanded{\writeutilitytua{jp['#1']={#2,"#3","#4","#5","#6","#7"}}}}
+
+\def\dolazysavepositionplus#1#2#3#4#5#6#7#8%
+ {\expanded{\writeutilitytua{jp['#1']={#2,"#3","#4","#5","#6","#7","#8"}}}}
+
+\def\dosaveposition#1#2#3#4% tag page x y
+ {\expanded{\immediatewriteutilitytua{jp['#1']={#2,"#3","#4"}}}}
+
+\def\dosavepositionwhd#1#2#3#4#5#6#7% tag page x y w h d
+ {\expanded{\immediatewriteutilitytua{jp['#1']={#2,"#3","#4","#5","#6","#7"}}}}
+
+\def\dosavepositionplus#1#2#3#4#5#6#7#8% tag page x y w h d list
+ {\expanded{\immediatewriteutilitytua{jp['#1']={#2,"#3","#4","#5","#6","#7","#8"}}}}
+
+\def\MPp #1{\ctxlua{job.MPp("#1")}}
+\def\MPx #1{\ctxlua{job.MPx("#1")}}
+\def\MPy #1{\ctxlua{job.MPy("#1")}}
+\def\MPw #1{\ctxlua{job.MPw("#1")}}
+\def\MPh #1{\ctxlua{job.MPh("#1")}}
+\def\MPd #1{\ctxlua{job.MPd("#1")}}
+\def\MPxy #1{\ctxlua{job.MPxy("#1")}}
+\def\MPll #1{\ctxlua{job.MPll("#1")}}
+\def\MPlr #1{\ctxlua{job.MPlr("#1")}}
+\def\MPur #1{\ctxlua{job.MPur("#1")}}
+\def\MPul #1{\ctxlua{job.MPul("#1")}}
+\def\MPpos#1{\ctxlua{job.MPpos("#1")}}
+
+\def\MPplus#1#2{\ctxlua{job.MPplus("#1",#2)}}
+\def\MPrest#1#2{\ctxlua{job.MPrest("#1","#2")}}
+
+\def\doifpositionelse#1{\ctxlua{cs.testcase(job.positions['#1'])}}
+
+\def\copyposition#1#2{\ctxlua{job.positions['#1']=job.positions['#2']}}
+
+\protect \endinput
diff --git a/tex/context/base/core-reg.lua b/tex/context/base/core-reg.lua
new file mode 100644
index 000000000..b5ca260f8
--- /dev/null
+++ b/tex/context/base/core-reg.lua
@@ -0,0 +1,237 @@
+-- filename : core-reg.lua
+-- comment : companion to core-reg.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['core-reg'] = 1.001
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.registers then job.registers = { } end
+
+function job.defineregister(id)
+ if not job.registers[id] then
+ job.registers[id] = { }
+ end
+end
+
+-- {'e','3','','test+test+test','2--0-0-0-0-0-0-0--1','1'}
+
+-- load index (we could rease the original entry afterwards, freeing memory)
+
+-- index sorting
+
+sorters = sorters or { }
+sorters.index = sorters.index or { }
+sorters.index.data = sorters.index.data or { }
+
+do
+
+ function sorters.index.compare(a,b)
+ local result = 0
+ for i=1,3 do
+ if result == 0 then
+ result = sorters.comparers.basic(a,b,i)
+ else
+ return result
+ end
+ end
+ if a[1] ~= 's' then -- e/f/t
+ local page_a, page_b = a[3], b[3]
+ if page_a < page_b then
+ return -1
+ elseif page_a > page_b then
+ return 1
+ end
+ end
+ return 0
+ end
+
+ function sorters.index.prepare(data)
+ sorters.prepare(data,sorters.splitters.utf,3)
+ end
+
+ function sorters.index.sort(data)
+ sorters.sort(data,sorters.index.compare)
+ end
+
+ function sorters.index.unique(data)
+ sorters.unique(data)
+ end
+
+ function sorters.index.cleanup(data)
+ sorters.cleanup(data)
+ end
+
+ function sorters.index.finalize(data)
+ local split = { }
+ for k,v in ipairs(data) do
+ local entry, tag = v[2][1][3][1], ""
+ local se = sorters.entries[sorters.language]
+ if se and se[entry] then
+ if type(se[entry]) == "number" then
+ entry = se[entry]
+ end
+ tag = se[entry]
+ else
+ entry = 0
+ tag = "unknown"
+ end
+ split[entry] = split[entry] or { tag = tag, data = { } }
+ split[entry].data[#split[entry].data+1] = v
+ end
+ return split
+ end
+
+--~ local template = {
+--~ page = "\\pageentry{%s}{%s}{%s}{%s}",
+--~ start = {
+--~ [0] = "\\startletter{%s}",
+--~ [1] = "\\startentry{%s}",
+--~ [2] = "\\startsubentry{%s}",
+--~ [3] = "\\startsubsubentry{%s}"
+--~ },
+--~ stop = {
+--~ [0] = "\\stopletter",
+--~ [1] = "\\stopentry",
+--~ [2] = "\\stopsubentry",
+--~ [3] = "\\stopsubsubentry"
+--~ }
+--~ }
+
+--~ function sorters.index.flush(sorted,class,flush)
+--~ flush = flush or print
+--~ class = class or 'index'
+--~ local function flushpage(v)
+--~ flush(string.format(template.page,v[2],v[3] or "",v[4] or "",v[5] or ""))
+--~ end
+--~ for _,v in ipairs(table.sortedkeys(sorted)) do
+--~ local s = sorted[v]
+--~ flush(string.format(template.start[0],s.tag))
+--~ local done = { false, false, false }
+--~ for kk,vv in ipairs(s.data) do
+--~ if vv[1][1] then
+--~ local e = { false, false, false }
+--~ for i=1,3,1 do
+--~ if vv[1][i] then e[i] = vv[1][i][1] end
+--~ end
+--~ for i=3,1,-1 do
+--~ if done[i] and e[i] ~= done[i] then
+--~ flush(template.stop[i])
+--~ end
+--~ end
+--~ for i=1,3,1 do
+--~ if e[i] ~= done[i] then
+--~ if e[i] and e[i] ~= "" then
+--~ done[i] = e[i]
+--~ flush(string.format(template.start[i],e[i]))
+--~ else
+--~ done[i] = false
+--~ end
+--~ end
+--~ end
+--~ flushpage(vv)
+--~ end
+--~ end
+--~ for i=3,1,-1 do
+--~ if done[i] then flush(template.stop[i]) end
+--~ end
+--~ flush(template.stop[0])
+--~ end
+--~ end
+
+ -- \registerpage{index}{,}{6}{2--0-0-0-0-0-0-0--1}{1}
+
+ -- for the moment we use the old structure, some day mmiv code
+ -- will be different: more structure, less mess
+
+ local template = {
+ page = "\\registerpage{%s}{%s}{%s}{%s}{%s}",
+ letter = "\\registerentry{%s}{%s}",
+ entry = {
+ "\\registerentrya{%s}{%s}",
+ "\\registerentryb{%s}{%s}",
+ "\\registerentryc{%s}{%s}",
+ },
+ }
+
+ function sorters.index.flush(sorted,class,flush)
+ flush = flush or print
+ class = class or 'index'
+ for k,v in ipairs(table.sortedkeys(sorted)) do
+ local s = sorted[v]
+ flush(string.format(template.letter,class,s.tag))
+ local done = { false, false, false }
+ for kk,vv in ipairs(s.data) do
+ if vv[2][1] then
+ local e = { false, false, false }
+ for i=1,3,1 do
+ if vv[2][i] then
+ e[i] = vv[2][i][1]
+ end
+ if e[i] ~= done[i] then
+ if e[i] and e[i] ~= "" then
+ done[i] = e[i]
+ flush(string.format(template.entry[i],class,e[i]))
+ else
+ done[i] = false
+ end
+ end
+ end
+ if vv[1] == 'e' then
+ -- format reference pagespec realpage
+ flush(string.format(template.page,class,",",vv[4],vv[5],vv[3]))
+ end
+ end
+ end
+ end
+ end
+
+ function sorters.index.process(data)
+ return sorters.process('index',data)
+ end
+
+end
+
+-- { { entry, key }, { entry, key }, { entry, key } }, kind, realpage|see, reference, pagespec
+
+function job.loadregister(class)
+ if job.registers[class] then
+ if not sorters.index.data[class] then
+ sorters.index.data[class] = {
+ language = 'en',
+ entries = { },
+ flush = function(s) tex.sprint(tex.ctxcatcodes,s) end,
+ sorted = false,
+ class = class
+ }
+ local entries = sorters.index.data[class].entries
+ for k,v in ipairs(job.registers[class]) do
+ if v[1] == 'l' then -- language
+ sorters.index.data[class].language = v[2]
+ else
+ local key, entry = v[3], v[4]
+ if type(entry) == 'string' then
+ entry = entry:splitchr('+')
+ end
+ if type(key) == 'string' then
+ key = key:splitchr('+')
+ end
+ entries[#entries+1] = {
+ v[1], -- kind (e, f, t, s)
+ {
+ { entry[1] or "", key[1] or "" },
+ { entry[2] or "", key[2] or "" },
+ { entry[3] or "", key[3] or "" }
+ },
+ v[6], -- realpage or seeword (check see)
+ v[2], -- reference
+ v[5], -- pagespec
+ }
+ end
+ end
+ end
+ -- maybe we should also save the register result stream
+ sorters.index.process(sorters.index.data[class])
+ end
+end
diff --git a/tex/context/base/core-reg.mkiv b/tex/context/base/core-reg.mkiv
new file mode 100644
index 000000000..d7dc9a9cb
--- /dev/null
+++ b/tex/context/base/core-reg.mkiv
@@ -0,0 +1,57 @@
+%D \module
+%D [ file=core-reg,
+%D version=2007.05.07,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Register Management,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-reg}{1.001}
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.registers then job.registers = { } end}%
+ \immediatewriteutilitytua{local jr = job.registers}%
+\to \everyopenutilities
+
+\let\allregisters\empty
+
+\appendtoks
+ \processcommacommand[\allregisters]\mkdodefineregister
+ \globallet\allregisters\empty
+\to \everyopenutilities
+
+\def\mkdodefineregister#1% class
+ {\ctxlua{job.defineregister('#1')}%
+ \immediatewriteutilitytua{job.defineregister('#1')}}
+
+\def\mkdefineregister#1% class
+ {\mkdodefineregister{#1}%
+ \doglobal\addtocommalist{#1}\allregisters}
+
+\def\mksaveregisterentry#1#2#3#4#5#6#7% class type reference key entry pagespec realpage
+ {\expanded{\writeutilitytua{table.insert(jr['#1'],{'#2','#3',\!!bs#4\!!es,\!!bs#5\!!es,'#6','#7'})}}}
+
+\def\mksaveregistersee#1#2#3#4#5#6#7% class type reference key entry see pagespec
+ {\expanded{\writeutilitytua{table.insert(jr['#1'],{'#2','#3',\!!bs#4\!!es,\!!bs#5\!!es,'#6','#7'})}}}
+
+\def\mksaveregistervariable#1#2#3% class type value
+ {\expanded{\immediatewriteutilitytua{table.insert(jr['#1'],{'#2','#3'})}}}
+
+% Beware, we have no filename support here. For that we need to save the resulting
+% tex code in a file. No big deal.
+
+\def\mkloadregister#1% class, todo: loader macro just like mkii
+ {\bgroup
+ \getvalue{\s!set#1}%
+ \ctxlua{job.loadregister('#1')}\par % par needed for hanging indentation
+ \getvalue{\s!reset#1}%
+ \egroup}
+
+\protect \endinput
diff --git a/tex/context/base/core-sec.tex b/tex/context/base/core-sec.tex
index 467b0cf50..b92fbb21c 100644
--- a/tex/context/base/core-sec.tex
+++ b/tex/context/base/core-sec.tex
@@ -1416,6 +1416,9 @@
\def\headparameter#1% to do: everywhere in core-sec
{\executeifdefined{\??ko\currenthead#1}\empty}
+% todo: write to list etc in both args or in enclosing h/vbox else it gets
+% lost when no #1 or #2 is typeset
+
\def\dodododoconstructhead#1[#2]#3#4% [ref] {number} {title}
{\def\currenthead{#1}% dus #1 overal vervangen
\let\finalsectionnumber\dofinalsectionnumber % overloaded ungrouped -)
diff --git a/tex/context/base/core-spa.lua b/tex/context/base/core-spa.lua
new file mode 100644
index 000000000..c6090ab13
--- /dev/null
+++ b/tex/context/base/core-spa.lua
@@ -0,0 +1,526 @@
+if not modules then modules = { } end modules ['core-spa'] = {
+ version = 1.001,
+ comment = "companion to core-spa.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+do
+
+ local glyph, disc, kern, glue, hlist, vlist = node.id('glyph'), node.id('disc'), node.id('kern'), node.id('glue'), node.id('hlist'), node.id('vlist')
+
+ local kernnode = node.new('kern')
+ local stretch = attributes.numbers['kern-chars'] or 141
+
+ function nodes.kern_chars(head)
+ local fti, scale, has_attribute = fonts.tfm.id, tex.scale, node.has_attribute
+ local fnt, pchar, pfont, p, n = nil, nil, nil, nil, head
+-- local marks = fti[font].shared.otfdata.luatex.marks
+ while n do
+ local id = n.id
+ local extra = has_attribute(n,stretch)
+ if id == glyph then
+ if pfont == n.font then
+ -- check for mark
+ local tchar = n.char
+-- if not marks[tchar] then
+ local pdata = fti[pfont].characters[pchar]
+ local pkern = pdata.kerns
+ if pkern and pkern[tchar] then
+ local k = node.copy(kernnode)
+ if extra then
+ k.kern = pkern[tchar] + extra
+ if p.id == disc then
+ p.replace = p.replace + 1
+ end
+ else
+ k.kern = pkern[tchar]
+ end
+ k.attr = n.attr
+ k.next = p.next
+ p.next = k
+ elseif extra then
+ local k = node.copy(kernnode)
+ k.kern = extra
+ k.attr = n.attr
+ k.next = p.next
+ p.next = k
+ if p.id == disc then
+ p.replace = p.replace + 1
+ end
+-- end
+ end
+ else
+ pfont, pchar = n.font, n.char
+ end
+ elseif id == disc then
+ local pre, post = n.pre, n.post
+ if pre then
+ local nn, pp = p.prev, p.next
+ p.prev, p.next = nil, pre -- hijack node
+ pre = nodes.kern_chars(p)
+ pre = pre.next
+ pre.prev = nil
+ p.prev, p.next = nn, pp
+ n.pre = pre
+ end
+ if post and n.next then
+ local tail = node.slide(post)
+ local nn, pp = n.next.prev, n.next.next
+ n.next.next, n.next.prev = nil, tail
+ tail.next = n.next -- hijack node
+ post = nodes.kern_chars(post)
+ tail.next = nil
+ n.next.prev, n.next.next = nn, pp
+ n.post = post
+ end
+ if n.next and n.next.id == glyph then
+ local tchar = n.next.char
+ local pdata = fti[pfont].characters[pchar]
+ local pkern = pdata.kerns
+ if pkern and pkern[tchar] then
+ local k = node.copy(kernnode)
+ if extra then
+ k.kern = pkern[tchar] + extra
+ else
+ k.kern = pkern[tchar]
+ end
+ k.attr = n.attr
+ k.next = n.next
+ n.next = k
+ n.replace = n.replace + 1
+ n = n.next
+ end
+ end
+ else
+ pfont = nil
+ if extra then
+ if id == glue and n.subtype == 0 then
+ local g = n.spec
+ if g.width > 0 then
+ g = node.copy(g)
+ n.spec = g
+ local w = g.width
+ g.width = w + 2*extra
+ local f = g.width/w
+ g.stretch = scale(g.stretch,f)
+ g.shrink = scale(g.shrink, f)
+ end
+ elseif id == kern and n.subtype == 0 then
+ if n.width > 0 then
+ n.width = n.width + extra
+ end
+ elseif id == hlist or id == vlist then
+ if n.width > 0 then -- else parindent etc
+ if p then
+ local k = node.copy(kernnode)
+ k.kern = extra
+ k.attr = n.attr
+ k.next = n
+ k.prev = p
+ p.next = k
+ n.prev = k
+ end
+ if n.next then
+ local k = node.copy(kernnode)
+ k.kern = extra
+ k.attr = n.attr
+ k.next = n.next
+ k.prev = n
+ n.prev = k
+ n.next = k
+ p = k
+ n = n.next
+ end
+ end
+ end
+ end
+ end
+ n.prev = p
+ p = n
+ n = n.next
+ end
+ return head, p
+ end
+
+end
+
+-- vertical space handler
+
+nodes.snapvalues = { }
+
+function nodes.setsnapvalue(n,ht,dp)
+ nodes.snapvalues[n] = { ht, dp, ht+dp }
+end
+
+do
+
+ nodes.trace_collapse = false
+
+ local kern, glue, penalty, hlist = node.id('kern'), node.id('glue'), node.id('penalty'), node.id('hlist')
+
+ local penalty_node = node.new('penalty')
+
+ local has_attribute = node.has_attribute
+ local has_field = node.has_field
+
+ local trace_list = { }
+
+ local function reset_tracing()
+ trace_list = { }
+ end
+ local function trace_skip(str,sc,so,sp,data)
+ trace_list[#trace_list+1] = string.format("%s %8s %8s %8s %8s", str:padd(8), data.spec.width, sc or "-", so or "-", sp or "-")
+ end
+ local function trace_done(str,data)
+ if data.id == penalty then
+ trace_list[#trace_list+1] = string.format("%s %8s penalty", str:padd(8), data.penalty)
+ else
+ trace_list[#trace_list+1] = string.format("%s %8s glue", str:padd(8), data.spec.width)
+ end
+ end
+ local function show_tracing()
+ texio.write_nl(table.concat(trace_list,"\n"))
+ end
+
+ -- we assume that these are defined
+
+ local skip_category = attributes.numbers['skip-category'] or 101
+ local skip_penalty = attributes.numbers['skip-penalty'] or 102
+ local skip_order = attributes.numbers['skip-order'] or 103
+ local snap_category = attributes.numbers['snap-category'] or 111
+ local display_math = attributes.numbers['display-math'] or 121
+
+ -- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display
+
+ function nodes.is_display_math(head)
+ n = head.prev
+ while n do
+ local id = n.id
+ if id == penalty then
+ elseif id == glue then
+ if n.subtype == 6 then
+ return true
+ end
+ else
+ break
+ end
+ n = n.prev
+ end
+ n = head.next
+ while n do
+ local id = n.id
+ if id == penalty then
+ elseif id == glue then
+ if n.subtype == 7 then
+ return true
+ end
+ else
+ break
+ end
+ n = n.next
+ end
+ return false
+ end
+
+ -- helpers
+
+ function nodes.snapline(current,where)
+ local sn = has_attribute(current.list,snap_category)
+ if sn then
+ local sv = nodes.snapvalues[sn]
+ if sv then
+ local height, depth, lineheight = sv[1], sv[2], sv[3]
+ current.height = math.ceil((current.height-height)/lineheight)*lineheight + height
+ current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth
+ end
+ end
+ end
+
+ function collapser(head,where)
+ if head then
+ local trace = nodes.trace_collapse
+ local current, tail = head, nil
+ local glue_order, glue_data = 0, nil
+ local penalty_order, penalty_data, natural_penalty = 0, nil, nil
+ if trace then reset_tracing() end
+ while current do
+ local id = current.id
+ if id == glue and current.subtype == 0 then -- todo, other subtypes, like math
+ local sc = has_attribute(current,skip_category)
+ local so = has_attribute(current,skip_order ) or 1
+ local sp = has_attribute(current,skip_penalty )
+ if not sc then
+ if glue_data then
+ if trace then trace_done("before",glue_data) end
+ head, current = nodes.before(head,current,glue_data)
+ end
+ if trace then trace_skip("natural",sc,so,sp,current) end
+ glue_order, glue_data = 0, nil
+ current = current.next
+ elseif sc < 1 or sc > 4 then -- 0 = discard, > 3 = unsupported
+ if trace then trace_skip("ignore",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ else
+ if sp then
+ if not penalty_data then
+ penalty_data = sp
+ elseif penalty_order < so then
+ penalty_order, penalty_data = so, sp
+ elseif penalty_order == so and sp > penalty_data then
+ penalty_data = sp
+ end
+ end
+ if not glue_data then
+ if trace then trace_skip("assign",sc,so,sp,current) end
+ glue_order = so
+ head, current, glue_data = nodes.remove(head, current)
+ elseif glue_order < so then
+ if trace then trace_skip("force",sc,so,sp,current) end
+ glue_order = so
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ elseif glue_order == so then
+ if sc == 1 then
+ if current.spec.width > glue_data.spec.width then
+ if trace then trace_skip("larger",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ else
+ if trace then trace_skip("smaller",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ elseif sc == 2 then
+ if trace then trace_skip("force",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ elseif sc == 3 then
+ if trace then trace_skip("penalty",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current = nodes.remove(head, current, true)
+ elseif sc == 4 then
+ if trace then trace_skip("add",sc,so,sp,current) end
+ local old, new = glue_data.spec, current.spec
+ old.width = old.width + new.width
+ old.stretch = old.stretch + new.stretch
+ old.shrink = old.shrink + new.shrink
+ head, current = nodes.remove(head, current, true)
+ else
+ if trace then trace_skip("unknown",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ else
+ if trace then trace_skip("unknown",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ end
+ -- elseif id == penalty then
+ -- natural_penalty = current.penalty
+ -- head, current = nodes.remove(head, current, true)
+ elseif id == glue and current.subtype == 2 then
+ local sn = has_attribute(current,snap_category)
+ if sn then
+ -- local sv = nodes.snapvalues[sn]
+ -- if sv then
+ head, current = nodes.remove(head, current, true)
+ -- else
+ -- current = current.next
+ -- end
+ else
+ current = current.next
+ end
+ else
+ if glue_data then
+ head, current = nodes.before(head,current,glue_data)
+ if trace then trace_done("before",glue_data) end
+ glue_order, glue_data = 0, nil
+ end
+ if id == hlist and where == 'hmode_par' and current.list then
+ nodes.snapline(current,where) -- will be inline later
+ end
+ current = current.next
+ end
+ tail = current
+ end
+ -- if natural_penalty and (not penalty_data or natural_penalty > penalty_data) then
+ -- penalty_data = natural_penalty
+ -- end
+ if penalty_data then
+ local p = node.copy(penalty_node)
+ p.penalty = penalty_data
+ if trace then trace_done("before",p) end
+ head, head = nodes.before(head,head,p)
+ end
+ if glue_data then
+ if trace then trace_done("after",glue_data) end
+ head, tail = nodes.after(head,tail,glue_data)
+ end
+ if trace then show_tracing() end
+ end
+ return head
+ end
+
+ local head, tail = nil, nil
+
+ function nodes.flush_vertical_spacing()
+ input.start_timing(nodes)
+ if head then
+ t = collapser(head)
+ head = nil
+ -- tail = nil
+ else
+ t = nil
+ end
+ input.stop_timing(nodes)
+ return t
+ end
+
+ function nodes.handle_page_spacing(t, where)
+ -- we need to add the latest t too, else we miss skips and such
+ if t then
+ local tt = node.slide(t)
+ local id = tt.id
+ if id == glue then -- or id == penalty then -- or maybe: if not hlist or vlist
+ if head then
+ t.prev = tail
+ tail.next = t
+ else
+ head = t
+ end
+ tail = tt
+ t = nil
+ else
+ input.start_timing(nodes)
+ if head then
+ t.prev = tail
+ tail.next = t
+ -- tail = tt
+ t = collapser(head,where)
+ head = nil
+ -- tail = nil
+ else
+ t = collapser(t,where)
+ end
+ input.stop_timing(nodes,where)
+ end
+ end
+ return t
+ end
+
+ function nodes.handle_vbox_spacing(t)
+ local tail = node.slide(t)
+ return collapser(t,'whole')
+ end
+
+end
+
+-- experimental!
+
+callback.register('vpack_filter', nodes.handle_vbox_spacing)
+callback.register('buildpage_filter', nodes.handle_page_spacing)
+
+-- educational: snapper
+
+--~ function demo_snapper(head,where) -- snap_category 105 / nodes.snapvalue = { [1] = { 8*65536, 4*65536, 12*65536 } }
+--~ if head then
+--~ local current, tail, dummy = head, nil, nil
+--~ while current do
+--~ local id = current.id
+--~ if id == glue and current.subtype == 2 then
+--~ local sn = has_attribute(current,snap_category)
+--~ if sn then
+--~ local sv = nodes.snapvalues[sn]
+--~ if sv then
+--~ head, current, dummy = node.delete(head, current)
+--~ node.free(dummy)
+--~ else
+--~ current = current.next
+--~ end
+--~ else
+--~ current = current.next
+--~ end
+--~ else
+--~ if id == hlist and where == 'hmode_par' and current.list then
+--~ local sn = has_attribute(current.list,snap_category)
+--~ if sn then
+--~ local sv = nodes.snapvalues[sn]
+--~ if sv then
+--~ local height, depth, lineheight = sv[1], sv[2], sv[3]
+--~ current.height = math.ceil((current.height-height)/lineheight)*lineheight + height
+--~ current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth
+--~ end
+--~ end
+--~ end
+--~ current = current.next
+--~ end
+--~ tail = current
+--~ end
+--~ end
+--~ return head
+--~ end
+
+--~ callback.register('buildpage_filter', demo_snapper)
+
+ --~ function nodes.kern_chars(head)
+ --~ local fti = fonts.tfm.id
+ --~ local fnt, pchar, pfont, p, n = nil, nil, nil, nil, head
+ --~ while n do
+ --~ local id = n.id
+ --~ if id == glyph then
+ --~ if pfont == n.font then
+ --~ local tchar = n.char
+ --~ local pdata = fti[pfont].characters[pchar]
+ --~ local pkern = pdata.kerns
+ --~ if pkern and pkern[tchar] then
+ --~ local k = node.copy(kernnode)
+ --~ k.kern = pkern[tchar]
+ --~ k.attr = n.attr
+ --~ k.next = p.next
+ --~ p.next = k
+ --~ -- texio.write_nl(string.format("KERN = %s %s %s",utf.char(pchar), utf.char(tchar), pkern[tchar]))
+ --~ end
+ --~ else
+ --~ pfont, pchar = n.font, n.char
+ --~ end
+ --~ elseif id == disc then
+ --~ if pre then
+ --~ local nn, pp = p.prev, p.next
+ --~ p.prev, p.next = nil, pre -- hijack node
+ --~ pre = nodes.kern_chars(p)
+ --~ pre = pre.next
+ --~ pre.prev = nil
+ --~ p.prev, p.next = nn, pp
+ --~ n.pre = pre
+ --~ end
+ --~ if post then
+ --~ local tail = node.slide(post)
+ --~ local nn, pp = n.next.prev, n.next.next
+ --~ n.next.next, n.next.prev = nil, tail
+ --~ tail.next = n.next -- hijack node
+ --~ post = nodes.kern_chars(post)
+ --~ tail.next = nil
+ --~ n.next.prev, n.next.next = nn, pp
+ --~ n.post = post
+ --~ end
+ --~ local tchar = n.next.char
+ --~ local pdata = fti[pfont].characters[pchar]
+ --~ local pkern = pdata.kerns
+ --~ if pkern and pkern[tchar] then
+ --~ local k = node.copy(kernnode)
+ --~ k.kern = pkern[tchar]
+ --~ k.attr = n.attr
+ --~ k.next = n.next
+ --~ n.next = k
+ --~ n.replace = n.replace + 1
+ --~ n = n.next
+ --~ end
+ --~ else
+ --~ pfont = nil
+ --~ end
+ --~ n.prev = p
+ --~ p = n
+ --~ n = n.next
+ --~ end
+ --~ return head, p
+ --~ end
diff --git a/tex/context/base/core-spa.mkiv b/tex/context/base/core-spa.mkiv
new file mode 100644
index 000000000..03d3c5595
--- /dev/null
+++ b/tex/context/base/core-spa.mkiv
@@ -0,0 +1,126 @@
+%D \module
+%D [ file=core-spa,
+%D version=1997.03.31,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Spacing,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Context Core Macros / Buffers}
+
+\unprotect
+
+% category:
+%
+% 0 == discard
+% 1 == only if larger
+% 2 == force even if smaller
+% 3 == only take penalty component
+% 4 == add to existing skip
+
+% penalty:
+%
+% larger wins
+
+% order:
+%
+% larger wins
+
+\defineattribute[kern-chars]
+
+\defineattribute[skip-category]
+\defineattribute[skip-penalty]
+\defineattribute[skip-order]
+
+\defineattribute[snap-category]
+
+\defineattribute[display-math]
+
+\registerctxluafile{core-spa}{1.001}
+
+% \start \dosetstretch{.25em} \setuptolerance[tolerant,stretch] \input tufte \endgraf \stop
+% \start \dosetstretch{.5em} effe flink doorfietsen \stop
+
+\def\dosetstretch#1% to be interfaces
+ {\relax\ifdim#1>\zeropoint
+ \dosetattribute{kern-chars}{\number\dimexpr#1\relax}%
+ \else
+ \doresetattribute{kern-chars}%
+ \fi}
+
+\appendtoks\doresetattribute{kern-chars}\to\everyforgetall
+
+\def\mksetupgridsnapping
+ {\ctxlua{nodes.setsnapvalue(1,\number\openstrutheight,\number\openstrutdepth)}}
+
+\def\mkenablegridsnapping
+ {\dosetattribute{snap-category}{1}%
+ \topskip\strutht
+ \offinterlineskip}
+
+\def\mkdisablegridsnapping
+ {\doresetattribute{snap-category}%
+ % reset topskip
+ \oninterlineskip}
+
+\protect \endinput
+
+\starttext
+
+\dorecurse{2}{
+ $2^{2^{2^{2}}}$ $2_{2_{2_{2}}}^{2^{2^{2^{2^{2^{2^{2^{2^{2}}}}}}}}}$
+ \input tufte \inframed {tufte}
+ \par
+}
+
+\dorecurse{100} {
+
+ \kern \recurselevel pt
+
+ \vbox {
+ \endgraf \strut first \endgraf
+ {\dosetattribute{skip-category}{1}\vskip10pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip20pt}
+ {\dosetattribute{skip-category}{2}\vskip10pt}
+ \endgraf \strut second \endgraf
+ }
+
+ \endgraf \strut first \endgraf
+ {\dosetattribute{skip-category}{1}\vskip10pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip20pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ \endgraf \strut second \endgraf
+
+ {\dosetattribute{skip-category}{0}\vskip10pt} % remove
+ {\dosetattribute{skip-category}{1}\vskip10pt} % take largest
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\vskip40pt}
+ {\dosetattribute{skip-category}{1}\dosetattribute{skip-order}{10}\vskip20pt}
+ {\dosetattribute{skip-category}{4}\dosetattribute{skip-order}{10}\vskip20pt}
+ {\dosetattribute{skip-category}{1}\vskip60pt}
+ {\dosetattribute{skip-category}{1}\vskip20pt}
+ {\dosetattribute{skip-category}{0}\vskip10pt}
+
+ third (no break after this)
+
+ {\dosetattribute{skip-category}{1}\dosetattribute{skip-penalty}{100000}\vskip10pt}
+ {\dosetattribute{skip-category}{1}\dosetattribute{skip-penalty}{100000}\vskip20pt}
+ {\dosetattribute{skip-category}{1}\vskip10pt}
+ {\dosetattribute{skip-category}{1}\vskip20pt}
+
+ fourth
+ \vskip10pt
+ fifth
+}
+
+\stoptext
diff --git a/tex/context/base/core-syn.lua b/tex/context/base/core-syn.lua
new file mode 100644
index 000000000..fc7b72b5d
--- /dev/null
+++ b/tex/context/base/core-syn.lua
@@ -0,0 +1,116 @@
+-- filename : core-syn.lua
+-- comment : companion to core-syn.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['core-syn'] = 1.001
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.sortedlists then job.sortedlists = { } end
+
+function job.definesortedlist(id)
+ if not job.sortedlists[id] then
+ job.sortedlists[id] = { }
+ end
+end
+
+sorters = sorters or { }
+sorters.list = sorters.list or { }
+sorters.list.data = sorters.list.data or { }
+
+do
+
+ function sorters.list.compare(a,b)
+ return sorters.comparers.basic(a,b,1)
+ end
+
+ function sorters.list.prepare(data)
+ sorters.prepare(data,sorters.splitters.utf,1)
+ end
+
+ function sorters.list.sort(data)
+ sorters.sort(data,sorters.list.compare)
+ end
+
+ function sorters.list.unique(data)
+ sorters.unique(data)
+ end
+
+ function sorters.list.cleanup(data)
+ sorters.cleanup(data)
+ end
+
+ function sorters.list.finalize(data)
+ -- we use the same splitter as with indices
+ local split = { }
+ for k,v in ipairs(data) do
+ local entry, tag = v[2][1][3][1], ""
+ local se = sorters.entries[sorters.language]
+ if se and se[entry] then
+ if type(se[entry]) == "number" then
+ entry = se[entry]
+ end
+ tag = se[entry]
+ else
+ entry = 0
+ tag = "unknown"
+ end
+ split[entry] = split[entry] or { tag = tag, data = { } }
+ split[entry].data[#split[entry].data+1] = v
+ end
+ return split
+ end
+
+ -- for the moment we use the old structure, some day mmiv code
+ -- will be different: more structure, less mess
+
+ local template = {
+ entry = "\\synonymentry{%s}{%s}{%s}{%s}"
+ }
+
+ function sorters.list.flush(sorted,class,flush)
+ -- for the moment we don't add split data (letters) yet
+ flush = flush or print
+ class = class or 'abbreviation'
+ for k,v in ipairs(table.sortedkeys(sorted)) do
+ for _, vv in ipairs(sorted[v].data) do
+ flush(string.format(template.entry,class,vv[2][1][1],vv[2][1][2],vv[3]))
+ end
+ end
+ end
+
+ function sorters.list.process(data)
+ return sorters.process('list',data)
+ end
+
+end
+
+-- { { entry, key } }, meaning
+
+function job.loadsortedlist(class)
+ if job.sortedlists[class] then
+ if not sorters.list.data[class] then
+ sorters.list.data[class] = {
+ language = 'en',
+ entries = { },
+ flush = function(s) tex.sprint(tex.ctxcatcodes,s) end,
+ sorted = false,
+ class = class
+ }
+ local entries = sorters.list.data[class].entries
+ for k,v in ipairs(job.sortedlists[class]) do
+ if v[1] == 'l' then -- language
+ sorters.list.data[class].language = v[2]
+ else
+ entries[#entries+1] = {
+ v[1], -- kind (e)
+ { { v[3], v[2] } }, -- entry, key
+ v[4] -- optional meaning
+ }
+ end
+ end
+ end
+ sorters.list.process(sorters.list.data[class])
+ end
+end
diff --git a/tex/context/base/core-syn.mkiv b/tex/context/base/core-syn.mkiv
new file mode 100644
index 000000000..8996940dd
--- /dev/null
+++ b/tex/context/base/core-syn.mkiv
@@ -0,0 +1,51 @@
+%D \module
+%D [ file=core-syn,
+%D version=1997.03.31,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Synonyms and Sorts,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-syn}{1.001}
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.sortedlists then job.sortedlists = { } end}%
+ \immediatewriteutilitytua{local js = job.sortedlists}%
+\to \everyopenutilities
+
+\let\allsortedlists\empty
+
+\appendtoks
+ \processcommacommand[\allsortedlists]\mkdodefinesortedlist
+ \globallet\allsortedlists\empty
+\to \everyopenutilities
+
+\def\mkdodefinesortedlist#1% class
+ {\ctxlua{job.definesortedlist('#1')}%
+ \immediatewriteutilitytua{job.definesortedlist('#1')}}
+
+\def\mkdefinesortedlist#1% class
+ {\mkdodefinesortedlist{#1}%
+ \doglobal\addtocommalist{#1}\allsortedlists}
+
+\def\mksavesortedlistentry#1#2#3#4% class key entry meaning
+ {\immediatewriteutilitytua{table.insert(js['#1'],{'e','#1',\!!bs#2\!!es,\!!bs#3\!!es})}}
+
+\def\mksavesortedlistvariable#1#2#3% class type value
+ {\immediatewriteutilitytua{table.insert(js['#1'],{'#2','#3'})}}
+
+\def\mkloadsortedlist#1% class
+ {\bgroup
+ \getvalue{\s!set#1}%
+ \ctxlua{job.loadsortedlist('#1')}%
+ \getvalue{\s!reset#1}%
+ \egroup}
+
+\protect \endinput
diff --git a/tex/context/base/core-sys.mkiv b/tex/context/base/core-sys.mkiv
new file mode 100644
index 000000000..0cef6c236
--- /dev/null
+++ b/tex/context/base/core-sys.mkiv
@@ -0,0 +1,23 @@
+%D \module
+%D [ file=core-sys,
+%D version=2006.09.18,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=System,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\startruntimeluacode
+ \ctxlua {
+ environment.inputfilename = "\inputfilename"
+ environment.outputfilename = "\outputfilename"
+ environment.jobfilename = "\jobfilename"
+ environment.jobfilesuffix = "\jobfilesuffix"
+ }
+\stopruntimeluacode
+
+\endinput
diff --git a/tex/context/base/core-two.lua b/tex/context/base/core-two.lua
new file mode 100644
index 000000000..45f1d0f46
--- /dev/null
+++ b/tex/context/base/core-two.lua
@@ -0,0 +1,83 @@
+if not modules then modules = { } end modules ['core-two'] = {
+ version = 1.001,
+ comment = "companion to core-two.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
We save multi-pass information in the main utility table.
+--ldx]]--
+
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.twopass then job.twopass = { } end
+
+function job.definetwopassdata(id)
+ job.twopass[id] = job.twopass[id] or { }
+end
+
+function job.gettwopassdata(id)
+ local jti = job.twopass[id]
+ if jti and #jti > 0 then
+ tex.print(jti[1])
+ table.remove(jti,1)
+ end
+end
+
+function job.checktwopassdata(id)
+ local jti = job.twopass[id]
+ if jti and #jti > 0 then
+ tex.print(jti[1])
+ end
+end
+
+function job.getfromtwopassdata(id,n)
+ local jti = job.twopass[id]
+ if jti and jti[n] then
+ tex.print(jti[n])
+ end
+end
+
+job.findtwopassdata = job.getfromtwopassdata
+job.getfirstpassdata = job.checktwopassdata
+
+function job.getlasttwopassdata(id)
+ local jti = job.twopass[id]
+ if jti and #jti > 0 then
+ tex.print(jti[#jti])
+ end
+end
+
+function job.noftwopassitems(id)
+ local jti = job.twopass[id]
+ if jti then
+ tex.print(#jti)
+ else
+ tex.print('0')
+ end
+end
+
+function job.twopassdatalist(id)
+ local jti = job.twopass[id]
+ if jti then
+ tex.print(table.concat(jti,','))
+ end
+end
+
+function job.doifelseintwopassdata(id,str)
+ local jti = job.twopass[id]
+ if jti then
+ local found = false
+ for _, v in pairs(jti) do
+ if v == str then
+ found = true
+ break
+ end
+ end
+ cs.testcase(found)
+ else
+ cs.testcase(false)
+ end
+end
diff --git a/tex/context/base/core-two.mkiv b/tex/context/base/core-two.mkiv
new file mode 100644
index 000000000..bbe00be92
--- /dev/null
+++ b/tex/context/base/core-two.mkiv
@@ -0,0 +1,74 @@
+%D \module
+%D [ file=core-two, % moved from core-uti
+%D version=2006.09.24,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Two Pass Data,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-two}{1.001}
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.twopass then job.twopass = { } end}%
+ \immediatewriteutilitytua{local tp = job.twopass}%
+\to \everyopenutilities
+
+\def\immediatesavetwopassdata #1#2#3{\expanded{\immediatewriteutilitytua{table.insert(tp['#1'],"#3")}}}
+\def\savetwopassdata #1#2#3{\expanded{\writeutilitytua {table.insert(tp['#1'],"#3")}}}
+\def\immediatesavetaggedtwopassdata#1#2#3#4{\expanded{\immediatewriteutilitytua{tp['#1']['#3']="#4"}}}
+\def\savetaggedtwopassdata #1#2#3#4{\expanded{\writeutilitytua {tp['#1']['#3']="#4"}}}
+
+% temp hack: needs a proper \starteverytimeluacode
+
+\def\dodefinetwopasslist#1%
+ {\ctxlua{job.definetwopassdata('#1')}%
+ \immediatewriteutilitytua{job.definetwopassdata('#1')}}
+
+\def\definetwopasslist#1%
+ {\dodefinetwopasslist{#1}%
+ \doglobal\addtocommalist{#1}\alltwopasslists}
+
+\appendtoks
+ \processcommacommand[\alltwopasslists]\dodefinetwopasslist
+ \globallet\alltwopasslists\empty
+\to \everyopenutilities
+
+\def\testtwopassdata
+ {\ifx\twopassdata\empty\twopassdatafoundfalse\else\twopassdatafoundtrue\fi}
+
+\def\gettwopassdata#1%
+ {\edef\twopassdata{\ctxlua{job.gettwopassdata("#1")}}\testtwopassdata}
+
+\def\checktwopassdata#1%
+ {\edef\twopassdata{\ctxlua{job.checktwopassdata("#1")}}\testtwopassdata}
+
+\def\findtwopassdata#1#2%
+ {\edef\twopassdata{\ctxlua{job.findtwopassdata("#1","#2")}}\testtwopassdata}
+
+\let\getfirsttwopassdata\checktwopassdata
+
+\def\getlasttwopassdata#1%
+ {\edef\noftwopassitems{\ctxlua{job.noftwopassitems("#1")}}%
+ \edef\twopassdata {\ctxlua{job.getlasttwopassdata("#1")}}%
+ \testtwopassdata}
+
+\def\getfromtwopassdata#1#2%
+ {\edef\twopassdata{\ctxlua{job.getfromtwopassdata("#1",#2)}}\testtwopassdata}
+
+\def\gettwopassdatalist
+ {\getnamedtwopassdatalist\twopassdatalist}
+
+\def\getnamedtwopassdatalist#1#2% \cs tag
+ {\edef#1{\ctxlua{job.twopassdatalist("#2")}}}
+
+\def\doifelseintwopassdata#1#2% tag dat
+ {\ctxlua{job.doifelseintwopassdata("#1","#2"))}}
+
+\protect \endinput
diff --git a/tex/context/base/core-uti.lua b/tex/context/base/core-uti.lua
new file mode 100644
index 000000000..00811e528
--- /dev/null
+++ b/tex/context/base/core-uti.lua
@@ -0,0 +1,29 @@
+if not modules then modules = { } end modules ['core-uti'] = {
+ version = 1.001,
+ comment = "companion to core-uti.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
A utility file has always been part of and with
+the move to we also moved a lot of multi-pass info
+to a table. Instead of loading a based
+utility file under different setups, we now load a table once. This
+saves much runtime but at the cost of more memory usage.
+--ldx]]--
+
+if not jobs then jobs = { } end
+if not job then jobs['main'] = { } end job = jobs['main']
+if not job.variables then job.variables = { } end
+
+--[[ldx--
+
Variables are saved using in the previously defined table and passed
+onto using the following method. Of course one can also
+directly access the variable using a call.
+--ldx]]--
+
+function job.initializevariable(cs,value)
+ tex.sprint("\\xdef\\"..cs.."{"..value.."}")
+end
diff --git a/tex/context/base/core-uti.mkiv b/tex/context/base/core-uti.mkiv
new file mode 100644
index 000000000..6883f2227
--- /dev/null
+++ b/tex/context/base/core-uti.mkiv
@@ -0,0 +1,103 @@
+%D \module
+%D [ file=core-uti,
+%D version=2006.09.19,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Utility File Handling,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{core-uti}{1.001}
+
+%D We need a way to pass strings safely to \LUA\ without the
+%D need for tricky escaping. Compare:
+%D
+%D \starttyping
+%D \ctxlua {something("anything tricky can go here")}
+%D \ctxlua {something([\luastringsep[anything tricky can go here]\luastringsep])}
+%D \stoptyping
+
+\def\luastringsep{===} % this permits \typefile{self} otherwise nested b/e sep problems
+
+\edef\!!bs{[\luastringsep[}
+\edef\!!es{]\luastringsep]}
+
+%D We have a the following available as primitive so there is no need
+%D for it:
+%D
+%D \starttyping
+%D \long\edef\luaescapestring#1{\!!bs#1\!!es}
+%D \stoptyping
+
+%D Next we implement a few helpers:
+
+\newwrite\utility@tua
+
+\prependtoks
+ \def \writeutilitytua {\write\utility@tua}
+ \def\immediatewriteutilitytua{\immediate\write\utility@tua}
+\to \everyjob
+
+\let \writeutilitytua\gobbleoneargument
+\let\immediatewriteutilitytua\gobbleoneargument
+
+\appendtoks
+ \immediate\openout\utility@tua=\jobname.tua
+ \immediatewriteutilitytua{-- file\space\space\space: \jobname}%
+ \immediatewriteutilitytua{-- format\space: \contextformat}%
+ \immediatewriteutilitytua{-- stamp\space\space: \contextversion}%
+ \immediatewriteutilitytua{-- escape\space: \!!bs\space...\space\!!es}%
+ \immediatewriteutilitytua{-- version: \utilityversion}%
+ \immediatewriteutilitytua{}%
+ \immediatewriteutilitytua{do}%
+ \immediatewriteutilitytua{if job and job.version and not job.version == "\utilityversion" then return end}%
+ \immediatewriteutilitytua{if not job then job = { } end}%
+ \immediatewriteutilitytua{job.version = "\utilityversion"}%
+\to \everyopenutilities
+
+\appendtoks
+ \immediatewriteutilitytua{end}%
+ %immediate\closeout\utility@tua
+\to \everycloseutilities
+
+% The next file can be in lua or luc format:
+
+\appendtoks
+ \ctxlua { do
+ if not job then job = { } end
+ job.version = "\utilityversion"
+ local settings = loadfile("\jobname.tuc")
+ if settings then settings() end
+ end}%
+\to \everyjob
+
+% variables
+
+\appendtoks
+ \immediatewriteutilitytua{if not job.variables then job.variables = { } end}%
+\to \everyopenutilities
+
+\def\savecurrentvalue#1#2%
+ {\immediatewriteutilitytua{job.initializevariable("\strippedcsname#1","#2")}}
+
+% temp
+
+\let\thisisbytesequence\gobbleoneargument
+
+% wrong place but we need to have it someplace
+
+\appendtoks
+ \ctxlua{input.storage.dump()}%
+\to \everydump
+
+\appendtoks
+ \ctxlua{input.storage.finalize()}%
+\to \everyfinalizeluacode
+
+\protect \endinput
diff --git a/tex/context/base/core-ver.mkiv b/tex/context/base/core-ver.mkiv
new file mode 100644
index 000000000..01e46f925
--- /dev/null
+++ b/tex/context/base/core-ver.mkiv
@@ -0,0 +1,207 @@
+%D \module
+%D [ file=core-ver,
+%D version=2000.10.13,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Verbatim,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\def\mksetupprettiesintype
+ {\begingroup
+ \lowercasestring verb-\prettyidentifier\to\filename
+ \doonlyonce\filename{\ctxluafileload\filename\empty}%
+ \endgroup}
+
+% todo: obeytabs|spaces|lines|pages
+
+\def\mksetupprettytype % todo check
+ {\processingverbatimtrue % will move
+ \ctxlua{buffers.doifelsevisualizer("\prettyidentifier")}
+ {\ctxlua{buffers.setvisualizer("\prettyidentifier")}%
+ \localcolortrue % tricky, maybe not here
+ \def\obs{\obeyedspace}%
+ \def\bop{\bgroup\beginofpretty}%
+ \def\eop{\endofpretty\egroup}%
+ \def\sop{\endofpretty\egroup\bgroup\beginofpretty}}
+ {\def\obs{\obeyedspace}}}
+
+\def\mkinitializeverbatim
+ {\ctxlua{buffers.visualizers.reset()}%
+ \def\obs{\obeyedspace}%
+ \verbatimfont
+ \obeycharacters}
+
+% \ctxluafileload{verb-tex}{}
+% \ctxluafileload{verb-mp} {}
+
+% \registerctxluafile{core-buf-tex}{}
+% \registerctxluafile{core-buf-mp} {}
+
+% \def\mktype#1%
+% {\bgroup
+% \begstrut % new, enables leading space in \type { abc } at par start
+% \let\currenttypingclass\??ty
+% \edef\currenttyping{#1}%
+% \initializetype % probably too much
+% \verbatimcolor
+% \setcatcodetable \vrbcatcodes
+% \catcode`\{=\plusone
+% \catcode`\}=\plustwo
+% \dodotype}
+
+% \def\dodotype#1%
+% {\obeycharacters % everyinitializeverbatim
+% \ctxlua{buffers.hooks.flush_line(\!!bs\detokenize{#1}\!!es)}%
+% \egroup}
+
+\let\mksetupslantedtype \relax
+
+\def\mksetupcommandsintype% can also be \string\
+ {\ctxlua{
+ buffers.visualizers.enableescape = true
+ buffers.visualizers.escapetoken = \!!bs\typingparameter\c!escape\!!es
+ }%
+ \setevalue{\typingparameter\c!escape}{\typingparameter\c!escape}}
+
+\def\mktype#1% was \dotype
+ {\bgroup
+ \begstrut % new, enables leading space in \type { abc } at par start / begstrut else no hyphenation
+ \let\currenttypingclass\??ty
+ \edef\currenttyping{#1}%
+ \catcode`\<=\@@other
+ \catcode`\>=\@@other
+ \futurelet\next\dodotype}
+
+\def\dodotype
+ {\ifx\next\bgroup
+ \@EA\dodotypeA
+ \else\if\next<%
+ \doifelse{\typingparameter\c!option}\v!none
+ {\@EAEAEA\dodotypeB}{\@EAEAEA\dodotypeC}%
+ \else
+ \@EAEAEA\dodotypeD
+ \fi\fi}
+
+\def\dodotypeA
+ {\initializetype % probably too much
+ \verbatimcolor
+ \setcatcodetable \vrbcatcodes
+ \catcode`\{=\plusone
+ \catcode`\}=\plustwo
+ \dodotypeAA}
+
+\def\dodotypeAA#1%
+ {\mkinitializeverbatim
+\def\obs{\obeyedspace}%
+ \ctxlua{buffers.hooks.flush_line(\!!bs\detokenize{#1}\!!es)}%
+ \egroup}
+
+\def\dodotypeB#1%
+ {\initializetype
+ \verbatimcolor
+ \setcatcodetable \vrbcatcodes
+ \catcode`\<=\plusone
+ \catcode`\>=\plustwo
+ \dodotypeBB}
+
+\def\dodotypeBB#1%
+ {\mkinitializeverbatim
+ \ctxlua{buffers.visualizers.flush_nested(\!!bs\detokenize{#1}\!!es,false)}%
+ \egroup
+ \gobbleoneargument} % grab last >
+
+\def\dodotypeC#1%
+ {\initializetype
+ \verbatimcolor
+ \setcatcodetable \vrbcatcodes
+ \catcode`\<=\plusone
+ \catcode`\>=\plustwo
+ \dodotypeCC}
+
+\def\dodotypeCC#1%
+ {\mkinitializeverbatim
+ \ifx\obeycharacters\setupprettytype % temp hack, we need a proper signal
+ \ctxlua{buffers.hooks.flush_line([\!!bs\detokenize{#1}\!!es,true)}%
+ \else
+ \def\obs{\obeyedspace}%
+ \ctxlua{buffers.visualizers.flush_nested(\!!bs\detokenize{#1}\!!es,true)}%
+ \fi
+ \egroup
+ \gobbleoneargument} % grab last >
+
+\def\dodotypeD#1%
+ {\initializetype
+ \verbatimcolor
+ \setcatcodetable \vrbcatcodes
+ \def\dodotypeDD##1#1{\dodotypeAA{##1}}%
+ \dodotypeDD}
+
+\def\dodotypeDD#1%
+ {\mkinitializeverbatim
+ \ctxlua{buffers.hooks.flush_line(\!!bs\detokenize{#1}\!!es,true)}%
+ \egroup
+ \gobbleoneargument} % grab last >
+
+% \typing:
+
+\def\mktypeblockverbatim#1#2%
+ {\dowithbuffer{_typing_}{#1}{#2}
+ {}
+ {\mkinitializeverbatim
+ \beginofverbatimlines
+ \ctxlua{buffers.type("_typing_")}%
+ \endofverbatimlines
+ \getvalue{\strippedcsname#2}}}
+
+% \typefile:
+
+\def\mktypefileverbatim
+ {\mkinitializeverbatim
+ \ctxlua{buffers.typefile("\readfilename")}}
+
+\def\mktypefilelinesverbatim#1#2%
+ {#1%
+ \mkinitializeverbatim
+ \ctxlua{buffers.typefile("\readfilename")}%
+ #2}
+
+% patched from verb-ini (todo)
+
+\let\beginverbatimline \relax
+\let\endverbatimline \relax
+\let\doopenupverbatimline\empty
+
+\def\doverbatimbeginofline#1% linenumber
+ {\bgroup % due to pretty status
+ \iflinepar\else\EveryPar{}\fi
+ \dontleavehmode % \leavevmode
+ \xdef\dokeepverbatimlinedata % hm, still needed?
+ {\parindent \the\parindent
+ \hangindent\the\hangindent
+ \hangafter \the\hangafter
+ \leftskip \the\leftskip
+ \rightskip \the\rightskip}%
+ \egroup
+ \dokeepverbatimlinedata
+ \doopenupverbatimline
+ \the\everyline\strut
+ \beginverbatimline}
+
+\def\doverbatimendofline
+ {\endverbatimline
+ \global\lineparfalse
+ \obeyedline\par}
+
+\def\doverbatimemptyline
+ {\strut
+ \par
+ \global\linepartrue}
+
+\protect \endinput
diff --git a/tex/context/base/core-ver.tex b/tex/context/base/core-ver.tex
index 8623dffef..259d018a6 100644
--- a/tex/context/base/core-ver.tex
+++ b/tex/context/base/core-ver.tex
@@ -293,7 +293,7 @@
\def\obeyhyphens
{\def\obeyedspace {\hskip\interwordspace}% better than spaceskip
- \def\controlspace{\hskip\zeropoint\hbox{\char32}\hskip\zeropoint}%
+ \def\controlspace{\hskip\zeropoint\hbox{\normalcontrolspace}\hskip\zeropoint}%
\spaceskip.25em\relax} % hm a bit of stretch !
\def\obeybreakpoints
@@ -302,7 +302,7 @@
\def\ignorehyphens
{\def\obeyedspace {\null\hskip\interwordspace\null}% better than spaceskip
- \def\controlspace{\null\hskip\zeropoint\hbox{\char32}\hskip\zeropoint\null}%
+ \def\controlspace{\null\hskip\zeropoint\hbox{\normalcontrolspace}\hskip\zeropoint\null}%
\spaceskip.5em\relax}
\unexpanded\def\typ
diff --git a/tex/context/base/enco-def.tex b/tex/context/base/enco-def.tex
index c111b6c44..9511f0f58 100644
--- a/tex/context/base/enco-def.tex
+++ b/tex/context/base/enco-def.tex
@@ -895,4 +895,20 @@
\stopencoding
+%D Goodie (makes more sense):
+
+\def\normalcontrolspace
+ {\getglyph{ComputerModernMono}{\char32}}
+
+\def\fakedcontrolspace % can be virtual in luatex
+ {\dontleavehmode\hbox
+ {\scratchdimen.1ex%
+ \kern\scratchdimen
+ \vrule \!!width\scratchdimen \!!height5.5\scratchdimen \!!depth3\scratchdimen
+ \vrule \!!width\dimexpr.5em-4\scratchdimen\relax \!!height-2\scratchdimen \!!depth3\scratchdimen
+ \vrule \!!width\scratchdimen \!!height5.5\scratchdimen \!!depth3\scratchdimen
+ \kern\scratchdimen}}
+
+\def\fakecontrolspace{\let\normalcontrolspace\fakedcontrolspace}
+
\endinput
diff --git a/tex/context/base/enco-ini.mkiv b/tex/context/base/enco-ini.mkiv
new file mode 100644
index 000000000..a676c46aa
--- /dev/null
+++ b/tex/context/base/enco-ini.mkiv
@@ -0,0 +1,69 @@
+%D \module
+%D [ file=enco-ini,
+%D version=2007.02.19,
+%D title=\CONTEXT\ Encoding Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=\PRAGMA]
+%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 In the end we will cleanup enco-ini.tex!
+
+\unprotect
+
+% could also be a new kind of table \definecharacter {name} {char} {fallback}
+
+\startruntimectxluacode
+ characters.context.rehash()
+\stopruntimectxluacode
+
+\ctxlua { characters.context.define() } % redefines all \characters
+
+\useencoding[032,033,037] % fallbacks for some unicode chars, todo
+
+\setupencoding[\s!default=ec] % for the moment keep it this way, till fonts are there
+
+\protect \endinput
+
+When dealing with characters we have four cases to take into account when moving
+from mkii to mkiv:
+
+1. => ref to slot 200 in current font
+2. \char 200 => ref to slot 200 in current font
+3. => can (e.g.) map to another slot in current font
+4. \namedglyph => can map to some slot in some font
+
+Using case 2 for special characters is doomed to fail because we are not going
+to intercept these on the fly as happens automatically with traditional font
+encoding handling. We could do that in a node pass but it's not worth the effort
+because we seldom use this case in a document source.
+
+We can consider using utf as internal format for mkii. The main reason for not
+doing this before was that it was slow. On the other hand, it would make dealing
+with utility files easier.
+
+These are the only cases where char references are used:
+
+enco-def.tex : 46 : \definecharacter dotlessi {\char"10 }
+enco-def.tex : 47 : \definecharacter dotlessj {\char"11 }
+enco-def.tex : 54 : \definecharacter aeligature {\char26 } % "1A
+enco-def.tex : 55 : \definecharacter AEligature {\char29 } % "1D
+enco-def.tex : 58 : \definecharacter oeligature {\char27 } % "1B
+enco-def.tex : 59 : \definecharacter OEligature {\char30 } % "1E
+enco-def.tex : 61 : \definecharacter ssharp {\char25 } % "19
+enco-def.tex : 336 : \definecharacter Lstroke {\hsmash{\char32}L}
+enco-def.tex : 337 : \definecharacter lstroke {\hsmash{\char32}l}
+enco-def.tex : 338 : \definecharacter Ostroke {\char31 } % "1F
+enco-def.tex : 339 : \definecharacter ostroke {\char28 } % "1C
+enco-il2.tex : 147 : {\dontleavehmode{\char32l}}
+enco-il2.tex : 152 : \hbox to\wd0{\hss\char32L}%
+symb-eur.tex : 37 : \definesymbol [euro] [\getglyph{Euro}{\char160}]
+symb-glm.tex : 61 : \definesymbol [xleftguillemot] [\getglyph{Guil}{\char19}]
+symb-glm.tex : 62 : \definesymbol [xrightguillemot] [\getglyph{Guil}{\char20}]
+symb-glm.tex : 64 : \definesymbol [xguilsingleleft] [\getglyph{Guil}{\char14}]
+symb-glm.tex : 65 : \definesymbol [xguilsingleright] [\getglyph{Guil}{\char15}]
+
diff --git a/tex/context/base/enco-pfr.mkiv b/tex/context/base/enco-pfr.mkiv
new file mode 100644
index 000000000..2db8670f0
--- /dev/null
+++ b/tex/context/base/enco-pfr.mkiv
@@ -0,0 +1,20 @@
+%D \module
+%D [ file=enco-pfr,
+%D version=2000.12.10, % adapted 2005.08.14 to more delayed loading
+%D title=\CONTEXT\ Encoding Macros,
+%D subtitle=PDF Font Resource Inclusion,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\ifx\pdfglyphtounicode\undefined \else
+ \appendtoks
+ \doif\jobsuffix{pdf}{\ctxlua{characters.setpdfunicodes()}}% pdftounicode mappings can only be done runtime
+ \to \everystarttext
+\fi
+
+\endinput
diff --git a/tex/context/base/font-afm.lua b/tex/context/base/font-afm.lua
new file mode 100644
index 000000000..7f4107386
--- /dev/null
+++ b/tex/context/base/font-afm.lua
@@ -0,0 +1,574 @@
+if not modules then modules = { } end modules ['font-afm'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Some code may look a bit obscure but this has to do with the
+fact that we also use this code for testing and much code evolved
+in the transition from to to .
+
+
The following code still has traces of intermediate font support
+where we handles font encodings. Eventually font encoding goes
+away.
+--ldx]]--
+
+fonts = fonts or { }
+fonts.afm = fonts.afm or { }
+fonts.afm.version = 1.10 -- incrementing this number one up will force a re-cache
+fonts.afm.syncspace = true -- when true, nicer stretch values
+fonts.afm.enhance_data = true -- best leave this set to true
+fonts.afm.trace_features = false
+fonts.afm.features = { }
+fonts.afm.features.aux = { }
+fonts.afm.features.data = { }
+fonts.afm.features.list = { }
+fonts.afm.features.default = { }
+fonts.afm.cache = containers.define("fonts", "afm", fonts.afm.version, true)
+
+--[[ldx--
+
We start with the basic reader which we give a name similar to the
+built in and reader.
+--ldx]]--
+
+do
+
+ local keys = { }
+
+ function keys.FontName (data,line) data.fullname = line:strip() end
+ function keys.ItalicAngle (data,line) data.italicangle = tonumber (line) end
+ function keys.IsFixedPitch(data,line) data.isfixedpitch = toboolean(line) end
+ function keys.CharWidth (data,line) data.charwidth = tonumber (line) end
+ function keys.XHeight (data,line) data.xheight = tonumber (line) end
+ function keys.Descender (data,line) data.descender = tonumber (line) end
+ function keys.Ascender (data,line) data.ascender = tonumber (line) end
+ function keys.Comment (data,line)
+ -- Comment DesignSize 12 (pts)
+ -- Comment TFM designsize: 12 (in points)
+ line = line:lower()
+ local designsize = line:match("designsize[^%d]*(%d+)")
+ if designsize then data.designsize = tonumber(designsize) end
+ end
+
+ local function get_charmetrics(characters,charmetrics)
+ local characters = data.characters
+ local chr, str, ind = { }, "", 0
+ for k,v in charmetrics:gmatch("([%a]+) +(.-) *;") do
+ if k == 'C' then
+ if str ~= "" then characters[str] = chr end
+ chr = { }
+ str = ""
+ v = tonumber(v)
+ if v < 0 then
+ ind = ind + 1
+ else
+ ind = v
+ end
+ chr.index = ind
+ elseif k == 'WX' then
+ chr.wx = v
+ elseif k == 'N' then
+ str = v
+ elseif k == 'B' then
+ local llx, lly, urx, ury = v:match("^ *(.-) +(.-) +(.-) +(.-)$")
+ chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) }
+ elseif k == 'L' then
+ local plus, becomes = v:match("^(.-) +(.-)$")
+ if not chr.ligatures then chr.ligatures = { } end
+ chr.ligatures[plus] = becomes
+ end
+ end
+ if str ~= "" then characters[str] = chr end
+ end
+
+ local function get_kernpairs(characters,kernpairs)
+ local characters = data.characters
+ for one, two, value in kernpairs:gmatch("KPX +(.-) +(.-) +(.-)\n") do
+ local chr = characters[one]
+ if chr then
+ if not chr.kerns then chr.kerns = { } end
+ chr.kerns[two] = tonumber(value)
+ end
+ end
+ end
+
+ local function get_variables(data,fontmetrics)
+ for key, rest in fontmetrics:gmatch("(%a+) *(.-)[\n\r]") do
+ if keys[key] then keys[key](data,rest) end
+ end
+ end
+
+ function fonts.afm.read_afm(filename)
+ local ok, afmblob, size = input.loadbinfile(texmf.instance,filename) -- has logging
+ -- local ok, afmblob = true, file.readdata(filename)
+ if ok and afmblob then
+ data = {
+ version = version or '0',
+ characters = { },
+ filename = file.removesuffix(file.basename(filename))
+ }
+ afmblob = afmblob:gsub("StartCharMetrics(.-)EndCharMetrics", function(charmetrics)
+ get_charmetrics(data,charmetrics)
+ return ""
+ end)
+ afmblob = afmblob:gsub("StartKernPairs(.-)EndKernPairs", function(kernpairs)
+ get_kernpairs(data,kernpairs)
+ return ""
+ end)
+ afmblob = afmblob:gsub("StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics)
+ data.afmversion = version
+ get_variables(data,fontmetrics)
+ return ""
+ end)
+ return data
+ else
+ return nil
+ end
+ end
+
+end
+
+--[[ldx--
+
We cache files. Caching is taken care of in the loader. We cheat a bit
+by adding ligatures and kern information to the afm derived data. That
+way we can set them faster when defining a font.
+--ldx]]--
+
+function fonts.afm.load(filename)
+ local name = file.removesuffix(filename)
+ local data = containers.read(fonts.afm.cache,name)
+ if not data then
+ local foundname = input.find_file(texmf.instance,filename,'afm')
+ if foundname and foundname ~= "" then
+ data = fonts.afm.read_afm(foundname)
+ if data then
+ fonts.afm.unify(data,filename)
+ if fonts.afm.enhance_data then
+ fonts.afm.add_ligatures(data,'ligatures') -- easier this way
+ fonts.afm.add_ligatures(data,'texligatures') -- easier this way
+ fonts.afm.add_kerns(data) -- faster this way
+ end
+ data = containers.write(fonts.afm.cache, name, data)
+ end
+ end
+ end
+ return data
+end
+
+function fonts.afm.unify(data, filename)
+ local unicode, private, unicodes = containers.content(fonts.enc.cache,'unicode').hash, 0x0F0000, { }
+ for name, blob in pairs(data.characters) do
+ local code = unicode[name]
+ if not code then
+ code = private
+ private = private + 1
+ end
+ blob.unicode = code
+ unicodes[name] = code
+ end
+ data.luatex = {
+ filename = file.basename(filename),
+ -- version = fonts.afm.version,
+ unicodes = unicodes
+ }
+end
+
+--[[ldx--
+
These helpers extend the basic table with extra ligatures, texligatures
+and extra kerns. This saves quite some lookups later.
+--ldx]]--
+
+function fonts.afm.add_ligatures(afmdata,ligatures)
+ local chars = afmdata.characters
+ for k,v in pairs(characters[ligatures]) do
+ local one = chars[k]
+ if one then
+ for _, b in pairs(v) do
+ two, three = b[1], b[2]
+ if two and three and chars[two] and chars[three] then
+ if one[ligatures] then
+ if not one.ligatures[two] then
+ one[ligatures][two] = three
+ end
+ else
+ one[ligatures] = { [two] = three }
+ end
+ end
+ end
+ end
+ end
+end
+
+--[[ldx--
+
We keep the extra kerns in separate kerning tables so that we can use
+them selectively.
+--ldx]]--
+
+function fonts.afm.add_kerns(afmdata)
+ local chars = afmdata.characters
+ -- add complex with values of simplified when present
+ local function do_it_left(what)
+ for _,v in pairs(chars) do
+ if v.kerns then
+ local k = { }
+ for complex,simple in pairs(characters.uncomposed[what]) do
+ if k[simple] and not k[complex] then
+ k[complex] = k[simple]
+ end
+ end
+ if not table.is_empty(k) then
+ v.extrakerns = k
+ end
+ end
+ end
+ end
+ do_it_left("left")
+ do_it_left("both")
+ -- copy kerns from simple char to complex char unless set
+ local function do_it_copy(what)
+ for complex,simple in pairs(characters.uncomposed[what]) do
+ local c = chars[complex]
+ if c then -- optional
+ local s = chars[simple]
+ if s and s.kerns then
+ c.extrakerns = s.kerns -- ok ? no merge ?
+ end
+ end
+ end
+ end
+ do_it_copy("both")
+ do_it_copy("right")
+end
+
+--[[ldx--
+
The copying routine looks messy (and is indeed a bit messy).
+--ldx]]--
+
+-- once we have otf sorted out (new format) we can try to make the afm
+-- cache similar to it
+
+function fonts.afm.copy_to_tfm(data)
+ if data and data.characters then
+ local tfm = { characters = { }, parameters = { } }
+ local characters = data.characters
+ if characters then
+ for k, v in pairs(characters) do
+ local t = { }
+ t.height = v.boundingbox[4]
+ t.depth = - v.boundingbox[2]
+ t.width = v.wx
+ t.boundingbox = v.boundingbox
+ t.index = v.index
+ t.name = k
+ t.unicode = v.unicode
+ tfm.characters[t.unicode] = t
+ end
+ end
+ tfm.encodingbytes = 2
+ tfm.units = 1000
+ tfm.name = data.filename
+ tfm.type = "real"
+ tfm.fullname = data.fullname
+ tfm.stretch = stretch
+ tfm.slant = slant
+ tfm.direction = 0
+ tfm.boundarychar_label = 0
+ tfm.boundarychar = 65536
+ --~ tfm.false_boundarychar = 65536 -- produces invalid tfm in luatex
+ tfm.designsize = (data.designsize or 10)*65536
+ local spaceunits = 500
+ tfm.spacer = "500 units"
+ if data.isfixedpitch then
+ if characters['space'] and characters['space'].wx then
+ spaceunits, tfm.spacer = characters['space'].wx, "space"
+ elseif characters['emdash'] and characters['emdash'].wx then -- funny default
+ spaceunits, tfm.spacer = characters['emdash'].wx, "emdash"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth"
+ end
+ elseif characters['space'] and characters['space'].wx then
+ spaceunits, tfm.spacer = characters['space'].wx, "space"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth variable"
+ end
+ spaceunits = tonumber(spaceunits)
+ tfm.parameters[1] = 0 -- slant
+ tfm.parameters[2] = spaceunits -- space
+ tfm.parameters[3] = 500 -- space_stretch
+ tfm.parameters[4] = 333 -- space_shrink
+ tfm.parameters[5] = 400 -- x_height
+ tfm.parameters[6] = 1000 -- quad
+ tfm.parameters[7] = 0 -- extra_space (todo)
+ if spaceunits < 200 then
+ -- todo: warning
+ end
+ tfm.italicangle = data.italicangle
+ tfm.ascender = math.abs(data.ascender or 0)
+ tfm.descender = math.abs(data.descender or 0)
+ if data.italicangle then
+ tfm.parameters[1] = tfm.parameters[1] - math.round(math.tan(data.italicangle*math.pi/180))
+ end
+ if data.isfixedpitch then
+ tfm.parameters[3] = 0
+ tfm.parameters[4] = 0
+ elseif fonts.afm.syncspace then
+ -- too little
+ -- tfm.parameters[3] = .2*spaceunits -- space_stretch
+ -- tfm.parameters[4] = .1*spaceunits -- space_shrink
+ -- taco's suggestion:
+ -- tfm.parameters[3] = .4*spaceunits -- space_stretch
+ -- tfm.parameters[4] = .1*spaceunits -- space_shrink
+ -- knuthian values: (for the moment compatible)
+ tfm.parameters[3] = spaceunits/2 -- space_stretch
+ tfm.parameters[4] = spaceunits/3 -- space_shrink
+ end
+ if data.xheight and data.xheight > 0 then
+ tfm.parameters[5] = data.xheight
+ elseif tfm.characters['x'] and tfm.characters['x'].height then
+ tfm.parameters[5] = tfm.characters['x'].height
+ end
+ if table.is_empty(tfm.characters) then
+ return nil
+ else
+ return tfm
+ end
+ else
+ return nil
+ end
+end
+
+
+--~ function set_x(w,h) return h*slant+w*stretch end
+--~ function set_y(h) return h end
+
+--[[ldx--
+
Originally we had features kind of hard coded for
+files but since I expect to support more font formats, I decided
+to treat this fontformat like any other and handle features in a
+more configurable way.
+--ldx]]--
+
+function fonts.afm.features.register(name,default)
+ fonts.afm.features.list[#fonts.afm.features.list+1] = name
+ fonts.afm.features.default[name] = default
+end
+
+function fonts.afm.set_features(tfmdata)
+ local shared = tfmdata.shared
+ local afmdata = shared.afmdata
+ shared.features = fonts.define.check(shared.features,fonts.afm.features.default)
+ local features = shared.features
+--~ texio.write_nl(table.serialize(features))
+ if not table.is_empty(features) then
+ local mode = tfmdata.mode or fonts.mode
+ local fi = fonts.initializers[mode]
+ if fi and fi.afm then
+ function initialize(list) -- using tex lig and kerning
+ if list then
+ for _, f in ipairs(list) do
+ local value = features[f]
+ if value and fi.afm[f] then -- brr
+ if fonts.afm.trace_features then
+ logs.report("define afm",string.format("initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown',tfmdata.name or 'unknown'))
+ end
+ fi.afm[f](tfmdata,value)
+ mode = tfmdata.mode or fonts.mode
+ fi = fonts.initializers[mode]
+ end
+ end
+ end
+ end
+ initialize(fonts.triggers)
+ initialize(fonts.afm.features.list)
+ end
+ local fm = fonts.methods[mode]
+ if fm and fm.afm then
+ function register(list) -- node manipulations
+ if list then
+ for _, f in ipairs(list) do
+ if features[f] and fm.afm[f] then -- brr
+ if not shared.processors then -- maybe also predefine
+ shared.processors = { fm.afm[f] }
+ else
+ shared.processors[#shared.processors+1] = fm.afm[f]
+ end
+ end
+ end
+ end
+ end
+ register(fonts.afm.features.list)
+ end
+ end
+end
+
+function fonts.afm.afm_to_tfm(specification)
+ local afmfile = specification.filename
+ local features = specification.features.normal
+ local cache_id = specification.hash
+ local tfmdata = containers.read(fonts.tfm.cache, cache_id) -- cache with features applied
+ if not tfmdata then
+ local afmdata = fonts.afm.load(afmfile)
+ if not table.is_empty(afmdata) then
+ tfmdata = fonts.afm.copy_to_tfm(afmdata)
+ if not table.is_empty(tfmdata) then
+ tfmdata.shared = tfmdata.shared or { }
+ tfmdata.unique = tfmdata.unique or { }
+ tfmdata.shared.afmdata = afmdata
+ tfmdata.shared.features = features
+ fonts.afm.set_features(tfmdata)
+ end
+ end
+ tfmdata = containers.write(fonts.tfm.cache,cache_id,tfmdata)
+ end
+ return tfmdata
+end
+
+--[[ldx--
+
As soon as we could intercept the reader, I implemented an
+ reader. Since traditional could use
+fonts with companions, the following method also could handle
+those cases, but now that we can handle directly we no longer
+need this features.
+--ldx]]--
+
+fonts.tfm.default_encoding = 'unicode'
+
+function fonts.tfm.set_normal_feature(specification,name,value)
+ if specification and name then
+ specification.features = specification.features or { }
+ specification.features.normal = specification.features.normal or { }
+ specification.features.normal[name] = value
+ end
+end
+
+function fonts.tfm.read_from_afm(specification)
+ local name, size, tfmtable = specification.name, specification.size, nil
+ local encoding, filename = name:match("^(.-)%-(.*)$") -- context: encoding-name.*
+ if filename and encoding and fonts.enc.known[encoding] then
+ fonts.tfm.set_normal_feature(specification,'encoding',encoding)
+ else
+ encoding = nil -- fonts.tfm.default_encoding
+ filename = name
+ end
+ if filename ~= "" then
+ specification.filename = filename .. ".afm"
+ tfmtable = fonts.afm.afm_to_tfm(specification)
+ if tfmtable then
+ tfmtable.name = name
+ tfmtable = fonts.tfm.scale(tfmtable, size)
+ filename = input.findbinfile(texmf.instance,filename,"pfb")
+ if filename then
+ tfmtable.format, tfmtable.filename = 'type1', filename
+ else
+ tfmtable.format, tfmtable.filename = 'pk', nil
+ end
+ if fonts.dontembed[filename] then
+ tfmtable.file = nil
+ end
+ -- begin of map hack
+ local mapentry = {
+ name = tfmtable.name,
+ fullname = tfmtable.fullname,
+ stretch = tfmtable.stretch,
+ slant = tfmtable.slant,
+ file = tfmtable.filename,
+ }
+ -- end of map hack
+ fonts.map.data[name] = mapentry
+ end
+ end
+ return tfmtable
+end
+
+--[[ldx--
+
Here comes the implementation of a few features. We only implement
+those that make sense for this format.
+--ldx]]--
+
+function fonts.afm.features.prepare_ligatures(tfmdata,ligatures,value)
+ if value then
+ local charlist = tfmdata.shared.afmdata.characters
+ for k,v in pairs(tfmdata.characters) do
+ local ac = charlist[v.name]
+ if ac then
+ local al = ac[ligatures]
+ if al then
+ local ligatures = { }
+ for k,v in pairs(al) do
+ ligatures[charlist[k].index] = {
+ char = charlist[v].index,
+ type = 0
+ }
+ end
+ v.ligatures = ligatures
+ end
+ end
+ end
+ end
+end
+
+function fonts.afm.features.prepare_kerns(tfmdata,kerns,value)
+ if value then
+ local charlist = tfmdata.shared.afmdata.characters
+ for _, chr in pairs(tfmdata.characters) do
+ local newkerns = charlist[chr.name][kerns]
+ if newkerns then
+ local t = chr.kerns or { }
+ for k,v in pairs(newkerns) do
+ t[charlist[k].index] = v
+ end
+ chr.kerns = t
+ end
+ end
+ end
+end
+
+function fonts.initializers.base.afm.ligatures(tfmdata,value)
+ fonts.afm.features.prepare_ligatures(tfmdata,'ligatures',value)
+end
+
+function fonts.initializers.base.afm.texligatures(tfmdata,value)
+ fonts.afm.features.prepare_ligatures(tfmdata,'texligatures',value)
+end
+
+function fonts.initializers.base.afm.kerns(tfmdata,value)
+ fonts.afm.features.prepare_kerns(tfmdata,'kerns',value)
+end
+
+function fonts.initializers.base.afm.extrakerns(tfmdata,value)
+ fonts.afm.features.prepare_kerns(tfmdata,'extrakerns',value)
+end
+
+fonts.afm.features.register('liga',true)
+fonts.afm.features.register('kerns',true)
+fonts.afm.features.register('extrakerns')
+
+fonts.initializers.node.afm.ligatures = fonts.initializers.base.afm.ligatures
+fonts.initializers.node.afm.texligatures = fonts.initializers.base.afm.texligatures
+fonts.initializers.node.afm.kerns = fonts.initializers.base.afm.kerns
+fonts.initializers.node.afm.extrakerns = fonts.initializers.base.afm.extrakerns
+
+fonts.initializers.base.afm.liga = fonts.initializers.base.afm.ligatures
+fonts.initializers.node.afm.liga = fonts.initializers.base.afm.ligatures
+fonts.initializers.base.afm.tlig = fonts.initializers.base.afm.texligatures
+fonts.initializers.node.afm.tlig = fonts.initializers.base.afm.texligatures
+
+-- tfm features
+
+fonts.initializers.base.afm.equaldigits = fonts.initializers.common.equaldigits
+fonts.initializers.node.afm.equaldigits = fonts.initializers.common.equaldigits
+fonts.initializers.base.afm.lineheight = fonts.initializers.common.lineheight
+fonts.initializers.node.afm.lineheight = fonts.initializers.common.lineheight
+
+-- afm specific, encodings ...kind of obsolete
+
+fonts.afm.features.register('encoding')
+
+fonts.initializers.base.afm.encoding = fonts.initializers.common.encoding
+fonts.initializers.node.afm.encoding = fonts.initializers.common.encoding
+
+-- todo: oldstyle smallcaps as features for afm files
diff --git a/tex/context/base/font-def.lua b/tex/context/base/font-def.lua
new file mode 100644
index 000000000..cb8e6f75b
--- /dev/null
+++ b/tex/context/base/font-def.lua
@@ -0,0 +1,477 @@
+if not modules then modules = { } end modules ['font-def'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- check reuse of lmroman1o-regular vs lmr10
+
+--[[ldx--
+
Here we deal with defining fonts. We do so by intercepting the
+default loader that only handles .
+--ldx]]--
+
+fonts = fonts or { }
+fonts.define = fonts.define or { }
+fonts.tfm = fonts.tfm or { }
+fonts.vf = fonts.vf or { }
+fonts.used = fonts.used or { }
+
+fonts.tfm.version = 1.01
+fonts.tfm.cache = containers.define("fonts", "tfm", fonts.tfm.version, false)
+
+--[[ldx--
+
Choosing a font by name and specififying its size is only part of the
+game. In order to prevent complex commands, introduced
+a method to pass feature information as part of the font name. At the
+risk of introducing nasty parsing and compatinility problems, this
+syntax was expanded over time.
+
+
For the sake of users who have defined fonts using that syntax, we
+will support it, but we will provide additional methods as well.
+Normally users will not use this direct way, but use a more abstract
+interface.
+ --ldx]]--
+
+--~ name, kind, features = fonts.features.split_xetex("blabla / B : + lnum ; foo = bar ; - whatever ; whow ; + hans ; test = yes")
+
+fonts.define.method = 3 -- 1: tfm 2: tfm and if not then afm 3: afm and if not then tfm
+fonts.define.auto_afm = true
+fonts.define.auto_otf = true
+fonts.define.specify = { }
+fonts.define.splitsymbols = ""
+fonts.define.methods = fonts.define.methods or { }
+
+fonts.tfm.fonts = fonts.tfm.fonts or { }
+fonts.tfm.readers = fonts.tfm.readers or { }
+fonts.tfm.internalized = fonts.tfm.internalized or { } -- internal tex numbers
+fonts.tfm.id = fonts.tfm.id or { } -- font data, maybe use just fonts.ids (faster lookup)
+
+fonts.tfm.readers.sequence = { 'otf', 'ttf', 'afm', 'tfm' }
+
+--[[ldx--
+
We hardly gain anything when we cache the final (pre scaled)
+ table. But it can be handy for debugging.
The following function split the font specification into components
+and prepares a table that will move along as we proceed.
+--ldx]]--
+
+function fonts.define.analyze(name, size, id)
+ local specification = name or 'unknown'
+ local lookup, rest = name:match("^(.-):(.+)$")
+ local sub = ""
+ if lookup == 'file' or lookup == 'name' then
+ name = rest
+ else
+ lookup = 'file'
+ end
+ local font, method, detail = name:match("^(.-)(["..fonts.define.splitsymbols.."])(.+)$")
+ if method and detail then
+ name = font
+ else
+ method, detail = "", ""
+ end
+ local mainfont, subfont = name:match("^(.*-)(%(.*-)(%)$")
+ if mainfont and subfont then
+ name, sub = mainfont, subfont
+ end
+ size = size or (65536*10)
+ return {
+ lookup = lookup, -- forced type
+ specification = specification, -- full specification
+ size = size, -- size in scaled points or -1000*n
+ name = name, -- font or filename
+ sub = sub, -- subfont (eg in ttc)
+ method = method, -- specification method
+ detail = detail, -- specification
+ resolved = "", -- resolved font name
+ forced = "", -- forced loader
+ id = id, -- font id
+ features = { }, -- preprocessed features
+ -- hash = nil
+ -- filename = nil,
+ -- encoding = nil,
+ -- format = nil,
+ }
+end
+
+function fonts.define.register_split(symbol,action)
+ fonts.define.splitsymbols = fonts.define.splitsymbols .. "%" .. symbol
+ fonts.define.specify[symbol] = action
+end
+
+--[[ldx--
+
A unique hash value is generated by:
+--ldx]]--
+
+function fonts.tfm.hash_features(specification)
+ if specification.features then
+ local normal = specification.features.normal
+ if not table.is_empty(normal) then
+ local t = { }
+ for _, v in pairs(table.sortedkeys(normal)) do
+ t[#t+1] = v .. '=' .. tostring(normal[v])
+ end
+ return table.concat(t,"+")
+ end
+ end
+ return "unknown"
+end
+
+function fonts.tfm.hash_instance(specification)
+ if not specification.hash then
+ specification.hash = fonts.tfm.hash_features(specification)
+ end
+ return specification.hash .. ' @ ' .. tostring(specification.size)
+end
+
+--[[ldx--
+
We can resolve the filename using the next function:
+--ldx]]--
+
+function fonts.define.resolve(specification)
+ if specification.lookup == 'name' then
+ specification.resolved, specification.sub = fonts.names.resolve(specification.name,specification.sub)
+ if specification.resolved then
+ specification.forced = file.extname(specification.resolved)
+ specification.name = file.removesuffix(specification.resolved)
+ end
+ elseif specification.lookup == 'file' then
+ specification.forced = file.extname(specification.name)
+ specification.name = file.removesuffix(specification.name)
+ end
+ if specification.forced == "" then
+ specification.forced = nil
+ end
+ specification.hash = specification.name .. ' @ ' .. fonts.tfm.hash_features(specification)
+ if specification.sub and specification.sub ~= "" then
+ specification.hash = specification.sub .. ' @ ' .. specification.hash
+ end
+ return specification
+end
+
+--[[ldx--
+
The main read function either uses a forced reader (as determined by
+a lookup) or tries to resolve the name using the list of readers.
+
+
We need to cache when possible. We do cache raw tfm data (from , or ). After that we can cache based
+on specificstion (name) and size, that is, only needs a number
+for an already loaded fonts. However, it may make sense to cache fonts
+before they're scaled as well (store 's with applied methods
+and features). However, there may be a relation between the size and
+features (esp in virtual fonts) so let's not do that now.
+
+
Watch out, here we do load a font, but we don't prepare the
+specification yet.
+--ldx]]--
+
+function fonts.tfm.read(specification)
+ garbagecollector.push()
+ input.start_timing(fonts)
+ local hash = fonts.tfm.hash_instance(specification)
+ local tfmtable = fonts.tfm.fonts[hash] -- hashes by size !
+ if not tfmtable then
+ if specification.forced and specification.forced ~= "" then
+ tfmtable = fonts.tfm.readers[specification.forced](specification)
+ else
+ for _, reader in ipairs(fonts.tfm.readers.sequence) do
+ if fonts.tfm.readers[reader] then -- not really needed
+ tfmtable = fonts.tfm.readers[reader](specification)
+ if tfmtable then break end
+ end
+ end
+ end
+ if tfmtable then
+ if tfmtable.filename and fonts.dontembed[tfmtable.filename] then
+ tfmtable.embedding = "no"
+ else
+ tfmtable.embedding = "subset"
+ end
+ end
+ fonts.tfm.fonts[hash] = tfmtable
+ end
+ input.stop_timing(fonts)
+ garbagecollector.pop()
+ return tfmtable
+end
+
+--[[ldx--
+
For virtual fonts we need a slightly different approach:
+--ldx]]--
+
+function fonts.tfm.read_and_define(name,size) -- no id
+ local specification = fonts.define.analyze(name,size,nil)
+ if specification.method and fonts.define.specify[specification.method] then
+ specification = fonts.define.specify[specification.method](specification)
+ end
+ specification = fonts.define.resolve(specification)
+ local hash = fonts.tfm.hash_instance(specification)
+ local id = fonts.tfm.internalized[hash]
+ if not id then
+ local fontdata = fonts.tfm.read(specification)
+ id = font.define(fontdata)
+ fonts.tfm.id[id] = fontdata
+ fonts.tfm.internalized[hash] = id
+ end
+ return fonts.tfm.id[id], id
+end
+
+--[[ldx--
+
Next follow the readers. This code was written while
+evolved. Each one has its own way of dealing with its format.
+--ldx]]--
+
+function fonts.tfm.readers.opentype(specification,suffix,what)
+ if fonts.define.auto_otf then
+ local fullname, tfmtable = nil, nil
+ fullname = input.findbinfile(texmf.instance,specification.name,suffix)
+ if fullname and fullname ~= "" then
+ specification.filename, specification.format = fullname, what
+ tfmtable = fonts.tfm.read_from_open_type(specification)
+ fonts.logger.save(tfmtable,suffix,specification)
+ end
+ return tfmtable
+ else
+ return nil
+ end
+end
+
+function fonts.tfm.readers.otf(specification) return fonts.tfm.readers.opentype(specification,"otf","opentype") end
+function fonts.tfm.readers.ttf(specification) return fonts.tfm.readers.opentype(specification,"ttf","truetype") end
+function fonts.tfm.readers.ttc(specification) return fonts.tfm.readers.opentype(specification,"ttf","truetype") end -- !!
+
+function fonts.tfm.readers.afm(specification)
+ local fullname, tfmtable = nil, nil
+ if fonts.define.method == 2 then
+ fullname = input.findbinfile(texmf.instance,specification.name,"ofm") -- ?
+ if not (fullname and fullname ~= "") then
+ specification.filename = fullname
+ tfmtable = fonts.tfm.read_from_afm(specification)
+ fonts.logger.save(tfmtable,'afm',specification)
+ end
+ elseif fonts.define.method == 3 then
+-- maybe also findbinfile here
+ if fonts.define.auto_afm then
+ tfmtable = fonts.tfm.read_from_afm(specification)
+ fonts.logger.save(tfmtable,'afm',specification)
+ end
+ elseif fonts.define.method == 4 then
+-- maybe also findbinfile here
+ tfmtable = fonts.tfm.read_from_afm(specification)
+ fonts.logger.save(tfmtable,'afm',specification)
+ end
+ return tfmtable
+end
+
+function fonts.tfm.readers.tfm(specification)
+ local fullname, tfmtable = nil, nil
+ tfmtable = fonts.tfm.read_from_tfm(specification)
+ fonts.logger.save(tfmtable,'tfm',specification)
+ return tfmtable
+end
+
+--[[ldx--
+
So far we haven't really dealt with features (or whatever we want
+to pass along with the font definition. We distinguish the following
+situations:
+
+
+name:xetex like specs
+name@virtual font spec
+name*context specification
+
+
+
Of course one can always define more.
+--ldx]]--
+
+function fonts.define.specify.predefined(specification)
+ if specification.detail ~= "" and fonts.define.methods[specification.detail] then
+ specification.features.vtf = { preset = specification.detail }
+ end
+ return specification
+end
+
+fonts.define.register_split("@", fonts.define.specify.predefined)
+
+function fonts.define.specify.colonized(specification)
+ local list = { }
+ if specification.detail and specification.detail ~= "" then
+ local expanded_features = { }
+ local function expand(features)
+ for _,v in pairs(features:split(";")) do
+ expanded_features[#expanded_features+1] = v
+ end
+ end
+ expand(specification.detail)
+ for _,v in pairs(expanded_features) do
+ local a, b = v:match("^%s*(%S+)%s*=%s*(%S+)%s*$")
+ if a and b then
+ list[a] = b:is_boolean()
+ if type(list[a]) == "nil" then
+ list[a] = b
+ end
+ else
+ local a, b = v:match("^%s*([%+%-]?)%s*(%S+)%s*$")
+ if a and b then
+ list[b] = a ~= "-"
+ end
+ end
+ end
+ end
+ specification.features.normal = list
+ return specification
+end
+
+function fonts.tfm.make(specification)
+ local fvm = fonts.define.methods[specification.features.vtf.preset]
+ if fvm then
+ return fvm(specification)
+ else
+ return nil
+ end
+end
+
+fonts.define.register_split(":", fonts.define.specify.colonized)
+
+fonts.define.specify.context_setups = fonts.define.specify.context_setups or { }
+
+input.storage.register(false,"fonts/setups", fonts.define.specify.context_setups, "fonts.define.specify.context_setups")
+
+function fonts.define.specify.preset_context(name,features)
+ local t = aux.settings_to_hash(features)
+ for k,v in pairs(t) do
+ t[k] = v:is_boolean()
+ if type(t[k]) == "nil" then
+ t[k] = v
+ end
+ end
+ fonts.define.specify.context_setups[name] = t
+end
+
+function fonts.define.specify.split_context(features)
+ if fonts.define.specify.context_setups[features] then
+ return fonts.define.specify.context_setups[features]
+ else
+ return fonts.define.specify.preset_context("***",features)
+ end
+end
+
+function fonts.define.specify.starred(features)
+ if features.detail and features.detail ~= "" then
+ features.features.normal = fonts.define.specify.split_context(features.detail)
+ else
+ features.features.normal = { }
+ end
+ return features
+end
+
+fonts.define.register_split('*',fonts.define.specify.starred)
+
+--[[ldx--
+
We need to check for default features. For this we provide
+a helper function.
+--ldx]]--
+
+function fonts.define.check(features,defaults)
+ if table.is_empty(features) then
+ features = table.fastcopy(defaults) -- we could do without copy
+ else
+ for k,v in pairs(defaults) do
+ if features[k] == nil then
+ features[k] = v
+ end
+ end
+ end
+ return features
+end
+
+--[[ldx--
+
So far the specifyers. Now comes the real definer. Here we cache
+based on id's. Here we also intercept the virtual font handler. Since
+it evolved stepwise I may rewrite this bit (combine code).
+
+In the previously defined reader (the one resulting in a
+table) we cached the (scaled) instances. Here we cache them again, but
+this time based on id. We could combine this in one cache but this does
+not gain much. By the way, passing id's back to in the callback was
+introduced later in the development.
+--ldx]]--
+
+function fonts.define.read(name,size,id)
+ local specification = fonts.define.analyze(name,size,id)
+ if specification.method and fonts.define.specify[specification.method] then
+ specification = fonts.define.specify[specification.method](specification)
+ end
+ specification = fonts.define.resolve(specification)
+ local hash = fonts.tfm.hash_instance(specification)
+ if true then
+ local fontdata = containers.read(fonts.cache,hash) -- for tracing purposes
+ end
+ local fontdata = fonts.tfm.internalized[hash]
+ if not fontdata then
+ if specification.features.vtf and specification.features.vtf.preset then
+ fontdata = fonts.tfm.make(specification)
+ else
+ fontdata = fonts.tfm.read(specification)
+ end
+ if true then
+ fontdata = containers.write(fonts.cache,hash,fontdata) -- for tracing purposes
+ end
+ fonts.tfm.id[id] = fontdata
+ fonts.tfm.internalized[hash] = id
+ end
+ return fontdata
+end
+
+--~ table.insert(fonts.tfm.readers.sequence,1,'vtf')
+
+--~ function fonts.tfm.readers.vtf(specification)
+--~ if specification.features.vtf and specification.features.vtf.preset then
+--~ return fonts.tfm.make(specification)
+--~ else
+--~ return nil
+--~ end
+--~ end
+
+function fonts.vf.find(name)
+ if fonts.logger.format(name) == 'tfm' then
+ return input.findbinfile(texmf.instance,name,"ovf")
+ else
+ return ""
+ end
+end
+
+--[[ldx--
+
We overload both the and readers.
+--ldx]]--
+
+callback.register('define_font' , fonts.define.read)
+callback.register('find_vf_file', fonts.vf.find )
diff --git a/tex/context/base/font-enc.lua b/tex/context/base/font-enc.lua
new file mode 100644
index 000000000..a29ed83d3
--- /dev/null
+++ b/tex/context/base/font-enc.lua
@@ -0,0 +1,98 @@
+if not modules then modules = { } end modules ['font-enc'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Because encodings are going to disappear, we don't bother defining
+them in tables. But we may do so some day, for consistency.
Beware! The generic encoding files don't always apply to the ones that
+ship with fonts. This has to do with the fact that names follow (slightly)
+different standards. However, the fonts where this applies to (for instance
+Latin Modern or Gyre) come in OpenType variants too, so these
+will be used.
+--ldx]]--
+
+function fonts.enc.load(filename)
+ local name = file.removesuffix(filename)
+ local data = containers.read(fonts.enc.cache,name)
+ if data then
+ local vector, tag, hash = { }, "", { }
+ local foundname = input.find_file(texmf.instance,filename,'enc')
+ if foundname and foundname ~= "" then
+ local ok, encoding, size = input.loadbinfile(texmf.instance,foundname)
+ if ok and encoding then
+ encoding = encoding:gsub("%%(.-)\n","")
+ local tag, vec = encoding:match("/(%w+)%s*%[(.*)%]%s*def")
+ local i = 0
+ for ch in vec:gmatch("/([%a%d%.]+)") do
+ if ch ~= ".notdef" then
+ vector[i] = ch
+ if not hash[ch] then
+ hash[ch] = i
+ else
+ -- duplicate, play safe for tex ligs and take first
+ end
+ end
+ i = i + 1
+ end
+ end
+ end
+ data = containers.write(fonts.enc.cache, name, { name=name, tag=tag, vector=vector, hash=hash })
+ end
+ return data
+end
+
+--[[ldx--
+
There is no unicode encoding but for practical purposed we define
+one.
+--ldx]]--
+
+do
+ local vector, hash = { }, { }
+ for k,v in pairs(characters.data) do
+ local a = v.adobename
+ if a then
+ vector[k], hash[a] = a, k
+ else
+ vector[k] = '.notdef'
+ end
+ end
+ containers.write(fonts.enc.cache, 'unicode', { name='unicode', tag='unicode', vector=vector, hash=hash })
+end
diff --git a/tex/context/base/font-fbk.lua b/tex/context/base/font-fbk.lua
new file mode 100644
index 000000000..b81b94309
--- /dev/null
+++ b/tex/context/base/font-fbk.lua
@@ -0,0 +1,219 @@
+if not modules then modules = { } end modules ['font-fbk'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This is very experimental code!
+--ldx]]--
+
+fonts.fallbacks = { }
+fonts.vf.aux.combine.trace = false
+
+fonts.vf.aux.combine.commands["enable-tracing"] = function(g,v)
+ fonts.vf.aux.combine.trace = true
+end
+
+fonts.vf.aux.combine.commands["disable-tracing"] = function(g,v)
+ fonts.vf.aux.combine.trace = false
+end
+
+fonts.vf.aux.combine.commands["set-tracing"] = function(g,v)
+ if v[2] == nil then
+ fonts.vf.aux.combine.trace = true
+ else
+ fonts.vf.aux.combine.trace = v[2]
+ end
+end
+
+function fonts.vf.aux.combine.initialize_trace()
+ if fonts.vf.aux.combine.trace then
+ return "special", "pdf: .8 0 0 rg .8 0 0 RG", "pdf: 0 .8 0 rg 0 .8 0 RG", "pdf: 0 0 .8 rg 0 0 .8 RG", "pdf: 0 g 0 G"
+ else
+ return "comment", "", "", "", ""
+ end
+end
+
+fonts.vf.aux.combine.force_fallback = false
+
+fonts.vf.aux.combine.commands["fake-character"] = function(g,v) -- g, nr, fallback_id
+ local index, fallback = v[2], v[3]
+ if fonts.vf.aux.combine.force_fallback or not g.characters[index] then
+ if fonts.fallbacks[fallback] then
+ g.characters[index] = fonts.fallbacks[fallback](g)
+ end
+ end
+end
+
+fonts.fallbacks['textcent'] = function (g)
+ local c = string.byte("c")
+ local t = table.fastcopy(g.characters[c])
+--~ local s = fonts.tfm.scaled(g.specification.size or g.size or g.private.size)
+--~ local s = fonts.tfm.scaled(g.size or g.private.size)
+ local s = fonts.tfm.scaled(g.specification.size or g.size)
+ local a = - math.tan(math.rad(g.italicangle))
+ local special, red, green, blue, black = fonts.vf.aux.combine.initialize_trace()
+ if a == 0 then
+ t.commands = {
+ {"push"}, {"slot", 1, c}, {"pop"},
+ {"right", .5*t.width},
+ {"down", .2*t.height},
+ {special, green},
+ {"rule", 1.4*t.height, .02*s},
+ {special, black},
+ }
+ else
+ t.commands = {
+ {"push"},
+ {"right", .5*t.width-.025*s},
+ {"down", .2*t.height},
+ {"special",string.format("pdf: q 1 0 %s 1 0 0 cm",a)},
+ {special, green},
+ {"rule", 1.4*t.height, .025*s},
+ {special, black},
+ {"special","pdf: Q"},
+ {"pop"},
+ {"slot", 1, c} -- last else problems with cm
+ }
+ end
+ -- somehow the width is messed up now
+ -- todo: set height
+ t.height = 1.2*t.height
+ t.depth = 0.2*t.height
+ return t
+end
+
+fonts.fallbacks['texteuro'] = function (g)
+ local c = string.byte("C")
+ local t = table.fastcopy(g.characters[c])
+--~ local s = fonts.tfm.scaled(g.specification.size or g.size or g.private.size)
+--~ local s = fonts.tfm.scaled(g.size or g.private.size)
+ local s = fonts.tfm.scaled(g.specification.size or g.size)
+ local d = math.cos(math.rad(90+g.italicangle))
+ local special, red, green, blue, black = fonts.vf.aux.combine.initialize_trace()
+ t.width = 1.05*t.width
+ t.commands = {
+ {"right", .05*t.width},
+ {"push"}, {"slot", 1, c}, {"pop"},
+ {"right", .5*t.width*d},
+ {"down", -.5*t.height},
+ {special, green},
+ {"rule", .05*s, .4*s},
+ {special, black},
+ }
+ return t
+end
+
+-- maybe store llx etc instead of bbox in tfm blob / more efficient
+
+fonts.vf.aux.combine.force_composed = false
+
+fonts.vf.aux.combine.commands["complete-composed-characters"] = function(g,v)
+ local chars = g.characters
+ local cap_lly = chars[string.byte("X")].boundingbox[4]
+ local ita_cor = math.cos(math.rad(90+g.italicangle))
+ local force = fonts.vf.aux.combine.force_composed
+ local special, red, green, blue, black = fonts.vf.aux.combine.initialize_trace()
+ for i,c in pairs(characters.data) do
+ if force or not chars[i] then
+ local s = c.specials
+ if s and s[1] == 'char' then
+ local chr = s[2]
+ if chars[chr] then
+ local cc = c.category
+ if (cc == 'll') or (cc == 'lu') or (cc == 'lt') then
+ local acc = s[3]
+ chars[i] = table.fastcopy(chars[chr])
+ if chars[acc] then
+ local cb = chars[chr].boundingbox
+ local ab = chars[acc].boundingbox
+ local c_llx, c_lly, c_urx, c_ury = cb[1], cb[2], cb[3], cb[4]
+ local a_llx, a_lly, a_urx, a_ury = ab[1], ab[2], ab[3], ab[4]
+ -- local dx = (c_urx-a_urx) - (c_urx-c_llx-a_urx+a_llx)/2
+ -- local dx = (c_urx-a_urx) - (c_urx-a_urx-c_llx+a_llx)/2
+ local dx = (c_urx - a_urx - a_llx + c_llx)/2
+ -- local dd = chars[chr].width*ita_cor
+ local dd = (c_urx-c_llx)*ita_cor
+ if a_ury < 0 then
+ local dy = cap_lly-a_lly
+ chars[i].commands = {
+ {"push"},
+ {"right", dx-dd},
+ {special, red},
+ {"slot", 1, acc},
+ {special, black},
+ {"pop"},
+ {"slot", 1, chr},
+ }
+ elseif c_ury > a_lly then
+ local dy = cap_lly-a_lly
+ chars[i].commands = {
+ {"push"},
+ {"right", dx+dd},
+ {"down", -dy},
+ {special, green},
+ {"slot", 1, acc},
+ {special, black},
+ {"pop"},
+ {"slot", 1, chr},
+ }
+ else
+ chars[i].commands = {
+ {"push"},
+ {"right", dx+dd},
+ {special, blue},
+ {"slot", 1, acc},
+ {special, black},
+ {"pop"},
+ {"slot", 1, chr},
+ }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+--~ {'special', 'pdf: q ' .. s .. ' 0 0 '.. s .. ' 0 0 cm'},
+--~ {'special', 'pdf: q 1 0 0 1 ' .. -w .. ' ' .. -h .. ' cm'},
+--~ -- {'special', 'pdf: /Fm\XX\space Do'},
+--~ {'special', 'pdf: Q'},
+--~ {'special', 'pdf: Q'},
+
+-- for documentation purposes we provide:
+
+fonts.define.methods.install("fallback", { -- todo: auto-fallback with loop over data.characters
+ { "fake-character", 0x00A2, 'textcent' },
+ { "fake-character", 0x20AC, 'texteuro' }
+})
+
+fonts.vf.aux.combine.commands["enable-force"] = function(g,v)
+ fonts.vf.aux.combine.force_composed = true
+ fonts.vf.aux.combine.force_fallback = true
+end
+fonts.vf.aux.combine.commands["disable-force"] = function(g,v)
+ fonts.vf.aux.combine.force_composed = false
+ fonts.vf.aux.combine.force_fallback = false
+end
+
+fonts.define.methods.install("demo-2", {
+ { "enable-tracing" },
+ { "enable-force" },
+ { "initialize" },
+ { "include-method", "fallback" },
+ { "complete-composed-characters" },
+ { "disable-tracing" },
+ { "disable-force" },
+})
+
+fonts.define.methods.install("demo-3", {
+ { "enable-tracing" },
+ { "initialize" },
+ { "complete-composed-characters" },
+ { "disable-tracing" },
+})
diff --git a/tex/context/base/font-ini.lua b/tex/context/base/font-ini.lua
new file mode 100644
index 000000000..68cb49439
--- /dev/null
+++ b/tex/context/base/font-ini.lua
@@ -0,0 +1,57 @@
+if not modules then modules = { } end modules ['font-ini'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Eventually this code will disappear because map files are kind
+of obsolete. Some code may move to runtime or auxiliary modules.
+--ldx]]--
+
+fonts = fonts or { }
+fonts.map = fonts.map or { }
+fonts.map.data = fonts.map.data or { }
+fonts.map.done = fonts.map.done or { }
+fonts.map.line = fonts.map.line or { }
+fonts.map.loaded = fonts.map.loaded or { }
+fonts.map.direct = fonts.map.direct or { }
+
+function fonts.map.line.pdfmapline(tag,str)
+ return "\\loadmapline[" .. tag .. "][" .. str .. "]"
+end
+
+function fonts.map.line.pdftex(e) -- so far no combination of slant and stretch
+ if e.name and e.file then
+ local fullname = e.fullname or ""
+ if e.slant and tonumber(e.slant) ~= 0 then
+ if e.encoding then
+ return fonts.map.line.pdfmapline("=",string.format('%s %s "%g SlantFont" <%s <%s',e.name,fullname,e.slant,e.encoding,e.file))
+ else
+ return fonts.map.line.pdfmapline("=",string.format('%s %s "%g SlantFont" <%s',e.name,fullname,e.slant,e.file))
+ end
+ elseif e.stretch and tonumber(e.stretch) ~= 1 and tonumber(e.stretch) ~= 0 then
+ if e.encoding then
+ return fonts.map.line.pdfmapline("=",string.format('%s %s "%g ExtendFont" <%s <%s',e.name,fullname,e.stretch,e.encoding,e.file))
+ else
+ return fonts.map.line.pdfmapline("=",string.format('%s %s "%g ExtendFont" <%s',e.name,fullname,e.stretch,e.file))
+ end
+ else
+ if e.encoding then
+ return fonts.map.line.pdfmapline("=",string.format('%s %s <%s <%s',e.name,fullname,e.encoding,e.file))
+ else
+ return fonts.map.line.pdfmapline("=",string.format('%s %s <%s',e.name,fullname,e.file))
+ end
+ end
+ else
+ return nil
+ end
+end
+
+function fonts.map.flushlines(backend,separator)
+ local t = { }
+ for k,v in pairs(fonts.map.data) do
+ if not fonts.map.done[k] then
+ local str = fonts.map.line[backend](v)
+ if str then
+ t[#t+1] = str
+ end
+ fonts.map.done[k] = true
+ end
+ end
+ return table.join(t,separator or "")
+end
+
+fonts.map.line.dvips = fonts.map.line.pdftex
+fonts.map.line.dvipdfmx = function() end
+
+function fonts.map.process_entries(filename, backend, handle)
+ local root = xml.load(filename,true)
+ if root then
+ if not handle then handle = texio.write_nl end
+ xml.process_attributes(root, "/fontlist/font", function(e,k)
+ local str = fonts.map.line[backend](e)
+ if str then
+ handle(str)
+ end
+ end)
+ end
+end
+
+function fonts.map.convert_entries(filename)
+ if not fonts.map.loaded[filename] then
+ local root = xml.load(filename,true) -- todo: stop garbage collector
+ if root then
+ garbagecollector.push()
+ xml.process_attributes(root, "/fontlist/font", function(e,k)
+ if e.name and e.file then
+ fonts.map.data[e.name] = e
+ -- fonts.map.data[e.name].name = nil -- beware, deletes xml entry as well
+ end
+ end)
+ garbagecollector.pop()
+ end
+ fonts.map.loaded[filename] = true
+ end
+end
+
+function fonts.map.direct.pdftex(filename)
+ fonts.map.process_entries(filename,'pdftex',function(str)
+ tex.sprint(tex.ctxcatcodes,str)
+ end)
+end
+
+-- the next one will go to a runtime module, no need to put this in the format
+
+function fonts.map.convert_file(filename, handle) -- when handle is string, then assume file
+ local f, g = io.open(filename), nil
+ if f then
+ if not handle then
+ handle = print
+ elseif type(handle) == "string" then
+ g = io.open(handle,"w")
+ function handle(str)
+ g:write(str .. "\n")
+ end
+ end
+ handle("\n")
+ handle(string.format("\n", "generated by context"))
+ handle(string.format("\n", filename))
+ for line in f:lines() do
+ local comment = line:match("^[%#%%]%s*(.*)%s*$")
+ if comment then -- todo: optional
+ handle(string.format(" ", comment))
+ elseif line:find("^\s*$") then
+ handle("")
+ else
+ name, fullname, spec, encoding, file = line:match("^(%S+)%s+(%S-)%s*\"([^\"]-)\"%s*<%s*(%S-)%s*<%s*(%S-)%s*$")
+ if not name then
+ name, fullname, spec, file = line:match("^(%S+)%s+(%S-)%s*\"([^\"]-)\"%s*<%s*(%S-)%s*$")
+ end
+ if not name then
+ name, fullname, encoding, file = line:match("^(%S+)%s+(%S-)%s*<%s*(%S-)%s*<%s*(%S-)%s*$")
+ end
+ if not name then
+ name, fullname, file = line:match("^(%S+)%s+(%S-)%s*<%s*(%S-)%s*$")
+ end
+ if name and name ~= "" and file and file ~= "" then
+ t = { }
+ if name then t[#t+1] = string.format("name='%s'" , name) end
+ if fullname and fullname ~= "" then t[#t+1] = string.format("fullname='%s'", fullname) end
+ if encoding and encoding ~= "" then t[#t+1] = string.format("encoding='%s'", encoding) end
+ if file then t[#t+1] = string.format("file='%s'" , file) end
+ if spec then
+ local a, b = spec:match("^([%d%.])%s+(%a+)$")
+ if a and b and b == "ExtendFont" then t[#t+1] = string.format("slant='%s'" , a) end
+ if a and b and b == "SlantFont" then t[#t+1] = string.format("stretch='%s'", a) end
+ end
+ handle(string.format(" ",table.concat(t," ")))
+ end
+ end
+ end
+ handle("\n")
+ f:close()
+ if g then g:close() end
+ end
+end
+
+--~ fonts.map.convert_file("c:/data/develop/tex/texmf/fonts/map/dvips/lm/lm-ec.map")
+--~ fonts.map.convert_file("maptest.map")
+--~ fonts.map.convert_file("maptest.map", "maptest-1.xml")
+--~ fonts.map.convert_file("c:/data/develop/tex/texmf/fonts/map/pdftex/updmap/pdftex.map")
+--~ fonts.map.convert_file("c:/data/develop/tex/texmf/fonts/map/pdftex/updmap/pdftex.map", "maptest-2.xml")
+
+--~ fonts.map.convert_entries('maptest-2.xml')
+--~ fonts.map.process_entries('maptest.xml','pdftex')
+
+--~ print(table.serialize(fonts.map.data))
+
+--~ tex.sprint(fonts.map.flushlines("pdftex","\n"))
+--~ str = fonts.map.flushlines("pdftex")
diff --git a/tex/context/base/font-otf.lua b/tex/context/base/font-otf.lua
new file mode 100644
index 000000000..c6a100566
--- /dev/null
+++ b/tex/context/base/font-otf.lua
@@ -0,0 +1,3416 @@
+if not modules then modules = { } end modules ['font-otf'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- otfdata zit in tfmdata / check
+
+--~ function string:split_at_space()
+--~ local t = { }
+--~ for s in self:gmatch("(%S+)") do
+--~ t[#t+1] = s
+--~ end
+--~ return t
+--~ end
+
+-- beware, the node related functions need to return head, current -- todo
+-- we may move marks to components so that parsing is faster
+
+-- using for i=1,#t do ... t[i] ... end is much faster than using ipairs
+-- copying some functions is faster than sharing code chunks esp here
+
+--[[ldx--
+
This module is sparesely documented because it is a moving target.
+The table format of the reader changes and we experiment a lot with
+different methods for supporting features.
+
+
As with the code, we may decide to store more information
+in the table.
+--ldx]]--
+
+fonts = fonts or { }
+fonts.otf = fonts.otf or { }
+fonts.otf.version = 1.56 -- incrementing this number one up will force a re-cache
+fonts.otf.tables = fonts.otf.tables or { }
+fonts.otf.meanings = fonts.otf.meanings or { }
+fonts.otf.enhance_data = false
+fonts.otf.syncspace = true
+fonts.otf.features = { }
+fonts.otf.features.aux = { }
+fonts.otf.features.data = { }
+fonts.otf.features.list = { } -- not (yet) used, oft fonts have gpos/gsub lists
+fonts.otf.features.default = { }
+fonts.otf.trace_features = false
+fonts.otf.trace_replacements = false
+fonts.otf.trace_contexts = false
+fonts.otf.trace_anchors = false
+fonts.otf.trace_ligatures = false
+fonts.otf.trace_kerns = false
+fonts.otf.notdef = false
+fonts.otf.cache = containers.define("fonts", "otf", fonts.otf.version, true)
+
+--[[ldx--
+
We start with a lot of tables and related functions.
+--ldx]]--
+
+fonts.otf.enhance = { }
+
+function fonts.otf.load(filename,format,sub,featurefile)
+ local name = file.basename(file.removesuffix(filename))
+ if featurefile then
+ name = name .. "@" .. file.removesuffix(file.basename(featurefile))
+ end
+ local data = containers.read(fonts.otf.cache, name)
+ if not data then
+ local ff
+ if sub and sub ~= "" then
+ ff = fontforge.open(filename,sub)
+ else
+ ff = fontforge.open(filename)
+ end
+ if ff then
+ logs.report("load otf","loading: " .. filename)
+ if featurefile then
+ featurefile = input.find_file(texmf.instance,file.addsuffix(featurefile,'fea'),"FONTFEATURES")
+ if featurefile and featurefile ~= "" then
+ logs.report("load otf", "featurefile: " .. featurefile)
+ fontforge.apply_featurefile(ff, featurefile)
+ end
+ end
+ data = fontforge.to_table(ff)
+ fontforge.close(ff)
+ if data then
+ logs.report("load otf","enhance: before")
+ fonts.otf.enhance.before(data,filename)
+ logs.report("load otf","enhance: analyze")
+ fonts.otf.enhance.analyze(data,filename)
+ logs.report("load otf","enhance: after")
+ fonts.otf.enhance.after(data,filename)
+ logs.report("load otf","save in cache")
+ data = containers.write(fonts.otf.cache, name, data)
+ else
+ logs.error("load otf","loading failed")
+ end
+ end
+ end
+ if data then
+ local map = data.map.map
+ local backmap = data.map.backmap
+ local unicodes = data.luatex.unicodes
+ local glyphs = data.glyphs
+ -- maybe handy some day, not used
+ data.name_to_unicode = function (n) return unicodes[n] end
+ data.name_to_index = function (n) return map[unicodes[n]] end
+ data.index_to_name = function (i) return glyphs[i].name end
+ data.unicode_to_name = function (u) return glyphs[map[u]].name end
+ data.index_to_unicode = function (u) return backmap[u] end
+ data.unicode_to_index = function (u) return map[u] end
+ end
+ return data
+end
+
+function fonts.otf.enhance.analyze(data,filename)
+ local t = {
+ filename = file.basename(filename),
+ version = fonts.otf.version,
+ creator = "context mkiv",
+ unicodes = fonts.otf.analyze_unicodes(data),
+ gposfeatures = fonts.otf.analyze_features(data.gpos),
+ gsubfeatures = fonts.otf.analyze_features(data.gsub),
+ marks = fonts.otf.analyze_class(data,'mark'),
+ }
+ t.subtables, t.name_to_type, t.internals, t.always_valid, t.ignore_flags = fonts.otf.analyze_subtables(data)
+ data.luatex = t
+end
+
+function fonts.otf.enhance.before(data,filename)
+ local private = 0xE000
+ local uni_to_int = data.map.map
+ local int_to_uni = data.map.backmap
+ for index, glyph in pairs(data.glyphs) do
+ if index > 0 and glyph.unicodeenc == -1 then
+ while uni_to_int[private] do
+ private = private + 1
+ end
+ uni_to_int[private] = index
+ int_to_uni[index] = private
+ glyph.unicodeenc = private
+ logs.report("load otf",string.format("enhance: glyph %s at index %s is moved to private unicode slot %s",glyph.name,index,private))
+ end
+ end
+ if data.ttf_tables then
+ for _, v in ipairs(data.ttf_tables) do
+ if v.data then v.data = "deleted" end
+ --~ if v.data then v.data = v.data:gsub("\026","\\026") end -- does not work out well
+ end
+ end
+end
+
+function fonts.otf.enhance.after(data,filename)
+ local unicodes = data.luatex.unicodes
+ for index, glyph in pairs(data.glyphs) do
+ if glyph.kerns then
+ local mykerns = { }
+ for k,v in ipairs(glyph.kerns) do
+ local mkl = mykerns[v.lookup]
+ if not mkl then
+ mkl = { [unicodes[v.char]] = v.off }
+ mykerns[v.lookup] = mkl
+ else
+ mkl[unicodes[v.char]] = v.off
+ end
+ end
+ glyph.mykerns = mykerns
+ end
+ end
+end
+
+function fonts.otf.analyze_class(data,class)
+ local classes = { }
+ for index, glyph in pairs(data.glyphs) do
+ if glyph.class == class then
+ classes[glyph.unicodeenc] = true
+ end
+ end
+ return classes
+end
+
+function fonts.otf.analyze_subtables(data)
+ local subtables, name_to_type, internals, always_valid, ignore_flags = { }, { }, { }, { }, { }
+ local function collect(g)
+ if g then
+ for k,v in ipairs(g) do
+ if v.features then
+ local ignored = { false, false, false }
+ if v.flags.ignorecombiningmarks then ignored[1] = 'mark' end
+ if v.flags.ignorebasechars then ignored[2] = 'base' end
+ if v.flags.ignoreligatures then ignored[3] = 'ligature' end
+ if v.subtables then
+ local type = v.type
+ for _, feature in ipairs(v.features) do
+ local ft = feature.tag:lower()
+ subtables[ft] = subtables[ft] or { }
+ for _, script in ipairs(feature.scripts) do
+ local ss = script.script
+ ss = ss:lower()
+ ss = ss:strip()
+ sft = subtables[ft]
+ sft[ss] = sft[ss] or { }
+ local sfts = sft[ss]
+ for _, language in ipairs(script.langs) do
+ language = language:lower()
+ language = language:strip()
+ sfts[language] = sfts[language] or { }
+ local sftsl = sfts[language]
+ local lookups, valid = sftsl.lookups or { }, sftsl.valid or { }
+ for n, subtable in ipairs(v.subtables) do
+ local stl = subtable.name
+ if stl then
+ lookups[#lookups+1] = stl
+ valid[stl] = true
+ name_to_type[stl] = type
+ ignore_flags[stl] = ignored
+ end
+ end
+ sftsl.lookups, sftsl.valid = lookups, valid
+ end
+ end
+ end
+ end
+ else
+ -- we have an internal feature, say ss_l_83 that resolves to
+ -- subfeatures like ss_l_83_s which we find in the glyphs
+ name_to_type[v.name] = v.type
+ local lookups, valid = { }, { }
+ for n, subtable in ipairs(v.subtables) do
+ local stl = subtable.name
+ if stl then
+ lookups[#lookups+1] = stl
+ valid[stl] = true
+ -- name_to_type[stl] = type
+ always_valid[stl] = true
+ end
+ end
+ internals[v.name] = {
+ lookups = lookups,
+ valid = valid
+ }
+ always_valid[v.name] = true -- bonus
+ end
+ end
+ end
+ end
+ collect(data.gsub)
+ collect(data.gpos)
+ return subtables, name_to_type, internals, always_valid, ignore_flags
+end
+
+function fonts.otf.analyze_unicodes(data)
+ local unicodes = { }
+ for _, blob in pairs(data.glyphs) do
+ if blob.name then
+ unicodes[blob.name] = blob.unicodeenc or 0
+ end
+ end
+ unicodes['space'] = unicodes['space'] or 32 -- handly later on
+ return unicodes
+end
+
+function fonts.otf.analyze_features(g)
+ if g then
+ local t, done = { }, { }
+ for k,v in ipairs(g) do
+ local f = v.features
+ if f then
+ for k, v in ipairs(f) do
+ -- scripts and tag
+ local tag = v.tag
+ if not done[tag] then
+ t[#t+1] = tag
+ done[tag] = true
+ end
+ end
+ end
+ end
+ if #t > 0 then
+ return t
+ end
+ end
+ return nil
+end
+
+function fonts.otf.valid_subtable(otfdata,language,script,kind)
+ local t = otfdata.luatex.subtables
+ return t[kind] and t[kind][script] and t[kind][script][language] and t[kind][script][language].lookups
+end
+
+function fonts.otf.features.register(name,default)
+ fonts.otf.features.list[#fonts.otf.features.list+1] = name
+ fonts.otf.features.default[name] = default
+end
+
+function fonts.otf.set_features(tfmdata)
+ local shared = tfmdata.shared
+ local otfdata = shared.otfdata
+ shared.features = fonts.define.check(shared.features,fonts.otf.features.default)
+ local features = shared.features
+ if not table.is_empty(features) then
+ local gposlist = otfdata.luatex.gposfeatures
+ local gsublist = otfdata.luatex.gsubfeatures
+ local mode = tfmdata.mode or fonts.mode
+ local fi = fonts.initializers[mode]
+ if fi and fi.otf then
+ function initialize(list) -- using tex lig and kerning
+ if list then
+ for _, f in ipairs(list) do
+ local value = features[f]
+ if value and fi.otf[f] then -- brr
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown', tfmdata.name or 'unknown'))
+ end
+ fi.otf[f](tfmdata,value) -- can set mode (no need to pass otf)
+ mode = tfmdata.mode or fonts.mode
+ fi = fonts.initializers[mode]
+ end
+ end
+ end
+ end
+ initialize(fonts.triggers)
+ initialize(gsublist)
+ initialize(gposlist)
+ end
+ local fm = fonts.methods[mode]
+ if fm and fm.otf then
+ function register(list) -- node manipulations
+ if list then
+ for _, f in ipairs(list) do
+ if features[f] and fm.otf[f] then -- brr
+ if not shared.processors then -- maybe also predefine
+ shared.processors = { fm.otf[f] }
+ else
+ shared.processors[#shared.processors+1] = fm.otf[f]
+ end
+ end
+ end
+ end
+ end
+ register(fonts.triggers)
+ register(gsublist)
+ register(gposlist)
+ end
+ end
+end
+
+function fonts.otf.otf_to_tfm(specification)
+ local name = specification.name
+ local sub = specification.sub
+ local filename = specification.filename
+ local format = specification.format
+ local features = specification.features.normal
+ local cache_id = specification.hash
+ local tfmdata = containers.read(fonts.tfm.cache,cache_id)
+ if not tfmdata then
+ local otfdata = fonts.otf.load(filename,format,sub,features and features.featurefile)
+ if not table.is_empty(otfdata) then
+ tfmdata = fonts.otf.copy_to_tfm(otfdata)
+ if not table.is_empty(tfmdata) then
+ tfmdata.shared = tfmdata.shared or { }
+ tfmdata.unique = tfmdata.unique or { }
+ tfmdata.shared.otfdata = otfdata
+ tfmdata.shared.features = features
+ fonts.otf.set_features(tfmdata)
+ end
+ end
+ containers.write(fonts.tfm.cache,cache_id,tfmdata)
+ end
+ return tfmdata
+end
+
+function fonts.otf.features.prepare_base_kerns(tfmdata,kind,value) -- todo what kind of kerns, currently all
+ if value then
+ local otfdata = tfmdata.shared.otfdata
+ local charlist = otfdata.glyphs
+ local unicodes = otfdata.luatex.unicodes
+ local somevalid = fonts.otf.some_valid_feature(otfdata,tfmdata.language,tfmdata.script,kind)
+ for _, chr in pairs(tfmdata.characters) do
+ local d = charlist[chr.index]
+ if d and d.kerns then
+ local t, done = chr.kerns or { }, false
+ for _, v in pairs(d.kerns) do
+ if somevalid[v.lookup] then
+ local k = unicodes[v.char]
+ if k > 0 then
+ t[k], done = v.off, true
+ end
+ end
+ end
+ if done then
+ chr.kerns = t
+ end
+ end
+ end
+ end
+end
+
+function fonts.otf.copy_to_tfm(data)
+ if data then
+ local tfm = { characters = { }, parameters = { } }
+ local unicodes = data.luatex.unicodes
+ local characters = tfm.characters
+ local force = fonts.otf.notdef
+ for k,v in pairs(data.map.map) do
+ -- k = unicode, v = slot
+ local d = data.glyphs[v]
+ -- if d then
+ if d and (force or d.name) then
+ local t = { }
+ t.index = v
+ t.unicode = k
+ t.name = d.name or ".notdef"
+ t.boundingbox = d.boundingbox or nil
+ t.width = d.width or 0
+ t.height = d.boundingbox[4] or 0
+ t.depth = - d.boundingbox[2] or 0
+ t.class = d.class
+ if d.class == "mark" then
+ t.width = -t.width
+ end
+ characters[k] = t
+ end
+ end
+ tfm.units = data.units_per_em or 1000
+ -- we need a runtime lookup because of running from cdrom or zip
+ tfm.filename = input.findbinfile(texmf.instance,data.luatex.filename,"") or data.luatex.filename
+ tfm.fullname = data.fullname or data.fontname
+ tfm.cidinfo = data.cidinfo
+ -- if not tfm.cidinfo.registry then tfm.cidinfo.registry = "" end
+ tfm.cidinfo.registry = tfm.cidinfo.registry or ""
+ tfm.name = file.removesuffix(file.basename(tfm.filename))
+ tfm.type = "real"
+ tfm.stretch = 0 -- stretch
+ tfm.slant = 0 -- slant
+ tfm.direction = 0
+ tfm.boundarychar_label = 0
+ tfm.boundarychar = 65536
+ tfm.designsize = ((data.designsize or 100)/10)*65536
+ local spaceunits = 500
+ tfm.spacer = "500 units"
+ data.isfixedpitch = data.pfminfo and data.pfminfo.panose and data.pfminfo.panose["proportion"] == "Monospaced"
+ data.charwidth = nil
+ if data.pfminfo then
+ data.charwidth = data.pfminfo.avgwidth
+ end
+ if data.isfixedpitch then
+ if data.glyphs[unicodes['space']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['space']].width, "space"
+ elseif data.glyphs[unicodes['emdash']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['emdash']].width, "emdash"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth"
+ end
+ elseif data.glyphs[unicodes['space']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['space']].width, "space"
+ elseif data.glyphs[unicodes['emdash']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['emdash']].width/2, "emdash/2"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth"
+ end
+ spaceunits = tonumber(spaceunits)
+ tfm.parameters[1] = 0 -- slant
+ tfm.parameters[2] = spaceunits -- space
+ tfm.parameters[3] = 500 -- space_stretch
+ tfm.parameters[4] = 333 -- space_shrink
+ tfm.parameters[5] = 400 -- x_height
+ tfm.parameters[6] = 1000 -- quad
+ tfm.parameters[7] = 0 -- extra_space (todo)
+ if spaceunits < 200 then
+ -- todo: warning
+ end
+ tfm.italicangle = data.italicangle
+ tfm.ascender = math.abs(data.ascent or 0)
+ tfm.descender = math.abs(data.descent or 0)
+ if data.italicangle then -- maybe also in afm _
+ tfm.parameters[1] = tfm.parameters[1] - math.round(math.tan(data.italicangle*math.pi/180))
+ end
+ if data.isfixedpitch then
+ tfm.parameters[3] = 0
+ tfm.parameters[4] = 0
+ elseif fonts.otf.syncspace then
+ tfm.parameters[3] = spaceunits/2 -- space_stretch
+ tfm.parameters[4] = spaceunits/3 -- space_shrink
+ end
+ if data.pfminfo and data.pfminfo.os2_xheight and data.pfminfo.os2_xheight > 0 then
+ tfm.parameters[5] = data.pfminfo.os2_xheight
+ elseif data.glyphs[unicodes['x']] and data.glyphs[unicodes['x']].height then
+ tfm.parameters[5] = data.glyphs[unicodes['x']].height
+ end
+ return tfm
+ else
+ return nil
+ end
+end
+
+function fonts.tfm.read_from_open_type(specification)
+ local tfmtable = fonts.otf.otf_to_tfm(specification)
+ if tfmtable then
+ tfmtable.name = specification.name
+ tfmtable.sub = specification.sub
+ tfmtable = fonts.tfm.scale(tfmtable, specification.size)
+ tfmtable.file = file.basename(specification.filename)
+ tfmtable.format = specification.format
+ end
+ return tfmtable
+end
+
+-- scripts
+
+fonts.otf.default_language = 'latn'
+fonts.otf.default_script = 'dflt'
+
+function fonts.otf.valid_feature(otfdata,language,script) -- return hash is faster
+ local language = language or fonts.otf.default_language
+ local script = script or fonts.otf.default_script
+ if not (script and language) then
+ return boolean.alwaystrue
+ else
+ language = string.padd(language:lower(),4)
+ script = string.padd(script:lower (),4)
+ local t = { }
+ for k,v in pairs(otfdata.luatex.subtables) do
+ local vv = v[script]
+ if vv and vv[language] then
+ t[k] = vv[language].valid
+ end
+ end
+ local always = otfdata.luatex.always_valid -- for the moment not per feature
+ return function(kind,tag) -- is the kind test needed
+ return always[tag] or kind and t[kind] and t[kind][tag]
+ end
+ end
+end
+function fonts.otf.some_valid_feature(otfdata,language,script,kind)
+ local language = language or fonts.otf.default_language
+ local script = script or fonts.otf.default_script
+ if not (script and language) then
+ return boolean.alwaystrue
+ else
+ language = string.padd(language:lower(),4)
+ script = string.padd(script:lower (),4)
+ local t = otfdata.luatex.subtables[kind]
+ if t and t[script] and t[script][language] and t[script][language].valid then
+ return t[script][language].valid
+ else
+ return { }
+ end
+--~ return (t and t[script][language] and t[script][language].valid) or { }
+ end
+end
+
+function fonts.otf.features.aux.resolve_ligatures(tfmdata,ligatures,kind)
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local chars = tfmdata.characters
+ local changed = tfmdata.changed or { }
+ local done = { }
+ kind = kind or "unknown"
+ while true do
+ local ok = false
+ for k,v in pairs(ligatures) do
+ local lig = v[1]
+ if not done[lig] then
+ local ligs = lig:split(" ")
+ if #ligs == 2 then
+ local c, f, s = chars[v[2]], ligs[1], ligs[2]
+ local uf, us = unicodes[f], unicodes[s]
+ if changed[uf] or changed[us] then
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("%s: %s (%s) + %s (%s) ignored",kind,f,uf,s,us))
+ end
+ else
+ local first, second = chars[uf], us
+ if first and second then
+ if not first.ligatures then first.ligatures = { } end
+ first.ligatures[second] = {
+ char = unicodes[c.name],
+ type = 0
+ }
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("%s: %s (%s) + %s (%s) = %s (%s)",kind,f,uf,s,us,c.name,unicodes[c.name]))
+ end
+ end
+ end
+ ok, done[lig] = true, c.name
+ end
+ end
+ end
+ if ok then
+ for d,n in pairs(done) do
+ local pattern = "^(" .. d .. ") "
+ for k,v in pairs(ligatures) do
+ v[1] = v[1]:gsub(pattern, function(str)
+ return n .. " "
+ end)
+ end
+ end
+ else
+ break
+ end
+ end
+end
+
+function fonts.otf.features.prepare_base_substitutions(tfmdata,kind,value) -- we can share some code with the node features
+ if value then
+ local ligatures = { }
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ local chars = tfmdata.characters
+ local somevalid = fonts.otf.some_valid_feature(otfdata,tfmdata.language,tfmdata.script,kind)
+ tfmdata.changed = tfmdata.changed or { }
+ local changed = tfmdata.changed
+ for k,c in pairs(chars) do
+ local o = otfdata.glyphs[c.index]
+ if o and o.lookups then
+ for lookup,ps in pairs(o.lookups) do
+--~ if valid(kind,lookup) then -- can be optimized for #p = 1
+if somevalid[lookup] then -- can be optimized for #p = 1
+ for i=1,#ps do
+ local p = ps[i]
+ local t = p.type
+ if t == 'substitution' then
+ local ps = p.specification
+ if ps and ps.variant then
+ local pv = ps.variant
+ if pv then
+ local upv = unicodes[pv]
+ if upv and chars[upv] then
+ if trace then
+ logs.report("define otf",string.format("%s: %s (%s) => %s (%s)",kind,chars[k].name,k,chars[upv].name,upv))
+ end
+ chars[k] = chars[upv]
+ changed[k] = true
+ end
+ end
+ end
+ elseif t == 'alternate' then
+ local pa = p.specification if pa and pa.components then
+ local pc = pa.components:match("(%S+)")
+ if pc then
+ local upc = unicodes[pc]
+ if upc and chars[upc] then
+ if trace then
+ logs.report("define otf",string.format("%s: %s (%s) => %s (%s)",kind,chars[k].name,k,chars[upc].name,upc))
+ end
+ chars[k] = chars[upc]
+ changed[k] = true
+ end
+ end
+ end
+ elseif t == 'ligature' and not changed[k] then
+ local pl = p.specification
+ if pl and pl.components then
+ if trace then
+ logs.report("define otf",string.format("%s: %s => %s (%s)",kind,pl.components,chars[k].name,k))
+ end
+ ligatures[#ligatures+1] = { pl.components, k }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ fonts.otf.features.aux.resolve_ligatures(tfmdata,ligatures,kind)
+ else
+ tfmdata.ligatures = tfmdata.ligatures or { }
+ end
+end
+
+function fonts.initializers.base.otf.liga(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'liga',value) end
+function fonts.initializers.base.otf.dlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'dlig',value) end
+function fonts.initializers.base.otf.rlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'rlig',value) end
+function fonts.initializers.base.otf.hlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'hlig',value) end
+function fonts.initializers.base.otf.pnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'pnum',value) end
+function fonts.initializers.base.otf.onum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'onum',value) end
+function fonts.initializers.base.otf.tnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'tnum',value) end
+function fonts.initializers.base.otf.lnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'lnum',value) end
+function fonts.initializers.base.otf.zero(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'zero',value) end
+function fonts.initializers.base.otf.smcp(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'smcp',value) end
+function fonts.initializers.base.otf.cpsp(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'cpsp',value) end
+function fonts.initializers.base.otf.c2sc(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'c2sc',value) end
+function fonts.initializers.base.otf.ornm(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'ornm',value) end
+function fonts.initializers.base.otf.aalt(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'aalt',value) end
+
+fonts.otf.features.data.tex = {
+ { "endash", "hyphen hyphen" },
+ { "emdash", "hyphen hyphen hyphen" },
+ { "quotedblleft", "quoteleft quoteleft" },
+ { "quotedblright", "quoteright quoteright" },
+ { "quotedblleft", "grave grave" },
+ { "quotedblright", "quotesingle quotesingle" },
+ { "quotedblbase", "comma comma" }
+}
+
+--~ 0x201C 0x2018 0x2018
+--~ 0x201D 0x2019 0x2019
+--~ 0x201E 0X002C 0x002C
+
+function fonts.initializers.base.otf.texligatures(tfm,value)
+ local otfdata = tfm.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local ligatures = { }
+ for _,v in pairs(fonts.otf.features.data.tex) do
+ if type(v[1]) == "string" then
+ local c = unicodes[v[1]]
+ if c then
+ ligatures[#ligatures+1] = { v[2], c }
+ end
+ else
+ ligatures[#ligatures+1] = { v[2], v[1] }
+ end
+ end
+ fonts.otf.features.aux.resolve_ligatures(tfm,ligatures)
+end
+
+function fonts.initializers.base.otf.texquotes(tfm,value)
+ tfm.characters[0x0022] = table.fastcopy(tfm.characters[0x201D])
+ tfm.characters[0x0027] = table.fastcopy(tfm.characters[0x2019])
+ tfm.characters[0x0060] = table.fastcopy(tfm.characters[0x2018])
+end
+
+fonts.initializers.base.otf.trep = fonts.initializers.base.otf.texquotes
+fonts.initializers.base.otf.tlig = fonts.initializers.base.otf.texligatures
+
+table.insert(fonts.triggers,"texquotes")
+table.insert(fonts.triggers,"texligatures")
+table.insert(fonts.triggers,"tlig")
+
+-- Here comes the real thing ... node processing! The next session prepares
+-- things. The main features (unchained by rules) have their own caches,
+-- while the private ones cache locally.
+
+do
+
+ fonts.otf.features.prepare = { }
+
+ -- also share vars
+
+ function fonts.otf.features.prepare.feature(tfmdata,kind,value) -- check BASE VS NODE
+ if value then
+ tfmdata.unique = tfmdata.unique or { }
+ tfmdata.shared = tfmdata.shared or { }
+ local shared = tfmdata.shared
+ shared.featuredata = shared.featuredata or { }
+ shared.featuredata[kind] = shared.featuredata[kind] or { }
+ shared.featurecache = shared.featurecache or { }
+ shared.featurecache[kind] = false -- signal
+ local otfdata = shared.otfdata
+ local lookuptable = fonts.otf.valid_subtable(otfdata,tfmdata.language,tfmdata.script,kind)
+ shared.lookuptable = shared.lookuptable or { }
+ shared.lookuptable[kind] = lookuptable
+ if lookuptable then
+ shared.processes = shared.processes or { }
+ shared.processes[kind] = shared.processes[kind] or { }
+ local processes = shared.processes[kind]
+ local types = otfdata.luatex.name_to_type
+ local flags = otfdata.luatex.ignore_flags
+ local preparers = fonts.otf.features.prepare
+ local process = fonts.otf.features.process
+ for noflookups, lookupname in ipairs(lookuptable) do
+ local lookuptype = types[lookupname]
+ local prepare = preparers[lookuptype]
+ if prepare then
+ local processdata = prepare(tfmdata,kind,lookupname)
+ if processdata then
+ local processflags = flags[lookupname] or {false,false,false}
+ processes[#processes+1] = { process[lookuptype], lookupname, processdata, processflags }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- helper: todo, we don't need to store non local ones for chains so we can pass the
+ -- validator as parameter
+
+ function fonts.otf.features.collect_ligatures(tfmdata,kind,internal) -- ligs are spread all over the place
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = tfmdata.shared.otfdata.luatex.unicodes -- actually the char index is ok too
+ local trace = fonts.otf.trace_features
+ local ligatures = { }
+ local function collect(lookup,o,ps)
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'ligature' then
+ if trace then
+ logs.report("define otf",string.format("feature %s ligature %s => %s",kind,p.specification.components,o.name))
+ end
+ local t = ligatures[lookup]
+ if not t then
+ t = { }
+ ligatures[lookup] = t
+ end
+ local first = true
+ for s in p.specification.components:gmatch("(%S+)") do
+ local u = unicodes[s]
+ if first then
+ if not t[u] then
+ t[u] = { { } }
+ end
+ t = t[u]
+ first = false
+ else
+ if not t[1][u] then
+ t[1][u] = { { } }
+ end
+ t = t[1][u]
+ end
+ end
+ t[2] = o.unicodeenc
+ end
+ end
+ end
+ if internal then
+ local always = otfdata.luatex.always_valid
+ for _,o in pairs(otfdata.glyphs) do
+ if o.lookups then
+ for lookup, ps in pairs(o.lookups) do
+ if always[lookup] then
+ collect(lookup,o,ps)
+ end
+ end
+ end
+ end
+ else -- check if this valid is still ok
+ local valid = fonts.otf.valid_feature(otfdata,tfmdata.language,tfmdata.script)
+ for _,o in pairs(otfdata.glyphs) do
+ if o.lookups then
+ for lookup, ps in pairs(o.lookups) do
+ if valid(kind,lookup) then
+ collect(lookup,o,ps)
+ end
+ end
+ end
+ end
+ end
+ return ligatures
+ end
+
+ -- gsub_single -> done
+ -- gsub_multiple -> done
+ -- gsub_alternate -> done
+ -- gsub_ligature -> done
+ -- gsub_context -> todo
+ -- gsub_contextchain -> done
+ -- gsub_reversechain -> todo
+
+ -- we used to share code in the following functions but that was relatively
+ -- due to extensive calls to functions (easily hundreds of thousands per
+ -- document)
+
+ function fonts.otf.features.prepare.gsub_single(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ substitutions = { }
+ featuredata[lookupname] = substitutions
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _, o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'substitution' then
+ local old, new = o.unicodeenc, unicodes[p.specification.variant]
+ substitutions[old] = new
+ if trace then
+ logs.report("define otf",string.format("%s:%s substitution %s => %s",kind,lookupname,old,new))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_multiple(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ substitutions = { }
+ featuredata[lookupname] = substitutions
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _,o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'multiple' then
+ local old, new = o.unicodeenc, { }
+ substitutions[old] = new
+ for pc in p.specification.components:gmatch("(%S+)") do
+ new[#new+1] = unicodes[pc]
+ end
+ if trace then
+ logs.report("define otf",string.format("%s:%s multiple %s => %s",kind,lookupname,old,table.concat(new," ")))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_alternate(tfmdata,kind,lookupname)
+ -- todo: configurable preference list
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ featuredata[lookupname] = { }
+ substitutions = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _,o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'alternate' then
+ local old = o.unicodeenc
+ local t = { }
+ for pc in p.specification.components:gmatch("(%S+)") do
+ t[#t+1] = unicodes[pc]
+ end
+ substitutions[old] = t
+ if trace then
+ logs.report("define otf",string.format("%s:%s alternate %s => %s",kind,lookupname,old,table.concat(substitutions,"|")))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_ligature(tfmdata,kind,lookupname)
+ -- we collect them for all lookups, this saves loops, we only use the
+ -- lookupname for testing, we need to check if this leads to redundant
+ -- collections
+ local ligatures = tfmdata.shared.featuredata[kind]
+ if not ligatures[lookupname] then
+ ligatures = fonts.otf.features.collect_ligatures(tfmdata,kind)
+ tfmdata.shared.featuredata[kind] = ligatures
+ end
+ return ligatures[lookupname]
+ end
+
+ function fonts.otf.features.prepare.contextchain(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local contexts = featuredata[lookupname]
+ if not contexts then
+ featuredata[lookupname] = { }
+ contexts = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local internals = otfdata.luatex.internals
+ local flags = otfdata.luatex.ignore_flags
+ local types = otfdata.luatex.name_to_type
+ otfdata.luatex.covers = otfdata.luatex.covers or { }
+ local cache = otfdata.luatex.covers
+ local characters = tfmdata.characters
+ local function uncover(covers)
+ if covers then
+ local result = { }
+ for n, c in ipairs(covers) do
+ local cc = cache[c]
+ if not cc then
+ local t = { }
+ for s in c:gmatch("(%S+)") do
+ t[unicodes[s]] = true
+ end
+ cache[c] = t
+ result[n] = t
+ else
+ result[n] = cc
+ end
+ end
+ return result
+ else
+ return { }
+ end
+ end
+ local lookupdata = otfdata.lookups[lookupname]
+ if not lookupdata then
+ texio.write_nl(string.format("error, missing lookupdata table %s",lookupname))
+ elseif lookupdata.rules then
+ for nofrules, rule in ipairs(lookupdata.rules) do
+ local coverage = rule.coverage
+ if coverage and coverage.current then
+ local current = uncover(coverage.current)
+ local before = uncover(coverage.before)
+ local after = uncover(coverage.after)
+ if current[1] then
+ local lookups, lookuptype = rule.lookups, 'self'
+ -- for the moment only lookup index 1
+ if lookups then
+ if #lookups > 1 then
+ logs.report("otf process","WARNING: more than one lookup in rule")
+ end
+ lookuptype = types[lookups[1]]
+ end
+ for unic, _ in pairs(current[1]) do
+ local t = contexts[unic]
+ if not t then
+ contexts[unic] = { lookups={}, flags=flags[lookupname] }
+ t = contexts[unic].lookups
+ end
+ t[#t+1] = { nofrules, lookuptype, current, before, after, lookups }
+ end
+ end
+ end
+ end
+ end
+ end
+ return contexts
+ end
+
+ fonts.otf.features.prepare.gsub_context = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gsub_contextchain = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gsub_reversecontextchain = fonts.otf.features.prepare.contextchain
+
+ -- ruled->lookup=ks_latn_l_27_c_4 => internal[ls_l_84] => valid[ls_l_84_s]
+
+ -- gpos_mark2base -> done
+ -- gpos_mark2ligature -> done
+ -- gpos_mark2mark -> done
+ -- gpos_single -> not done
+ -- gpos_pair -> not done
+ -- gpos_cursive -> not done
+ -- gpos_context -> not done
+ -- gpos_contextchain -> not done
+
+ function fonts.otf.features.prepare.anchors(tfmdata,kind,lookupname) -- tracing
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local anchors = featuredata[lookupname]
+ if not anchors then
+ featuredata[lookupname] = { }
+ anchors = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local validanchors = { }
+ local glyphs = otfdata.glyphs
+ if otfdata.anchor_classes then
+ for k,v in ipairs(otfdata.anchor_classes) do
+ if v.lookup == lookupname then
+ validanchors[v.name] = true
+ end
+ end
+ end
+ for _,o in pairs(glyphs) do
+ local oanchor = o.anchors
+ if oanchor then
+ local t, ok = { }, false
+ for type, anchors in pairs(oanchor) do -- types
+ local tt = false
+ for name, anchor in pairs(anchors) do
+ if validanchors[name] then
+ if not tt then
+ tt = { [name] = anchor }
+ t[type] = tt
+ ok = true
+ else
+ tt[name] = anchor
+ end
+ end
+ end
+ end
+ if ok then
+ anchors[o.unicodeenc] = t
+ end
+ end
+ end
+ end
+ return anchors
+ end
+
+ fonts.otf.features.prepare.gpos_mark2base = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_mark2ligature = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_mark2mark = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_cursive = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_context = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gpos_contextchain = fonts.otf.features.prepare.contextchain
+
+ function fonts.otf.features.prepare.gpos_single(tfmdata,kind,lookupname)
+ logs.report("otf define","gpos_single not yet supported")
+ end
+
+ function fonts.otf.features.prepare.gpos_pair(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local kerns = featuredata[lookupname]
+ if not kerns then
+ featuredata[lookupname] = { }
+ kerns = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local glyphs = otfdata.glyphs
+ for k,o in pairs(glyphs) do
+ local list = o.lookups
+ if list then
+ local one = o.unicodeenc
+ for lookup,ps in pairs(list) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.type == 'pair' then
+ local specification = p.specification
+ local two = unicodes[specification.paired]
+ local krn = kerns[one]
+ if krn then
+ krn[two] = specification.offsets
+ else
+ kerns[one] = { two = specification.offsets }
+ end
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two))
+ end
+ end
+ end
+ end
+ end
+ else
+ list = o.kerns
+ if list then
+ local one = o.unicodeenc
+ for lookup,ps in pairs(list) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ local char = p.char
+ if char then
+ local two = unicodes[char]
+ local krn = kerns[one]
+ if krn then
+ krn[two] = p.off
+ else
+ kerns[one] = { two = p.off }
+ end
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return kerns
+ end
+
+ fonts.otf.features.prepare.gpos_contextchain = fonts.otf.features.prepare.contextchain
+
+end
+
+-- can be generalized: one loop in main
+
+do
+
+ prepare = fonts.otf.features.prepare.feature
+
+ function fonts.initializers.node.otf.afrc(tfm,value) return prepare(tfm,'afrc',value) end
+ function fonts.initializers.node.otf.akhn(tfm,value) return prepare(tfm,'akhn',value) end
+ function fonts.initializers.node.otf.c2pc(tfm,value) return prepare(tfm,'c2pc',value) end
+ function fonts.initializers.node.otf.c2sc(tfm,value) return prepare(tfm,'c2sc',value) end
+ function fonts.initializers.node.otf.calt(tfm,value) return prepare(tfm,'calt',value) end
+ function fonts.initializers.node.otf.case(tfm,value) return prepare(tfm,'case',value) end
+ function fonts.initializers.node.otf.ccmp(tfm,value) return prepare(tfm,'ccmp',value) end
+ function fonts.initializers.node.otf.clig(tfm,value) return prepare(tfm,'clig',value) end
+ function fonts.initializers.node.otf.cpsp(tfm,value) return prepare(tfm,'cpsp',value) end
+ function fonts.initializers.node.otf.cswh(tfm,value) return prepare(tfm,'cswh',value) end
+ function fonts.initializers.node.otf.curs(tfm,value) return prepare(tfm,'curs',value) end
+ function fonts.initializers.node.otf.dlig(tfm,value) return prepare(tfm,'dlig',value) end
+ function fonts.initializers.node.otf.dnom(tfm,value) return prepare(tfm,'dnom',value) end
+ function fonts.initializers.node.otf.expt(tfm,value) return prepare(tfm,'expt',value) end
+ function fonts.initializers.node.otf.fin2(tfm,value) return prepare(tfm,'fin2',value) end
+ function fonts.initializers.node.otf.fin3(tfm,value) return prepare(tfm,'fin3',value) end
+ function fonts.initializers.node.otf.fina(tfm,value) return prepare(tfm,'fina',value) end
+ function fonts.initializers.node.otf.frac(tfm,value) return prepare(tfm,'frac',value) end
+ function fonts.initializers.node.otf.haln(tfm,value) return prepare(tfm,'haln',value) end
+ function fonts.initializers.node.otf.hist(tfm,value) return prepare(tfm,'hist',value) end
+ function fonts.initializers.node.otf.hkna(tfm,value) return prepare(tfm,'hkna',value) end
+ function fonts.initializers.node.otf.hlig(tfm,value) return prepare(tfm,'hlig',value) end
+ function fonts.initializers.node.otf.hngl(tfm,value) return prepare(tfm,'hngl',value) end
+ function fonts.initializers.node.otf.init(tfm,value) return prepare(tfm,'init',value) end
+ function fonts.initializers.node.otf.isol(tfm,value) return prepare(tfm,'isol',value) end
+ function fonts.initializers.node.otf.ital(tfm,value) return prepare(tfm,'ital',value) end
+ function fonts.initializers.node.otf.jp78(tfm,value) return prepare(tfm,'jp78',value) end
+ function fonts.initializers.node.otf.jp83(tfm,value) return prepare(tfm,'jp83',value) end
+ function fonts.initializers.node.otf.jp90(tfm,value) return prepare(tfm,'jp90',value) end
+ function fonts.initializers.node.otf.kern(tfm,value) return prepare(tfm,'kern',value) end
+ function fonts.initializers.node.otf.liga(tfm,value) return prepare(tfm,'liga',value) end
+ function fonts.initializers.node.otf.lnum(tfm,value) return prepare(tfm,'lnum',value) end
+ function fonts.initializers.node.otf.locl(tfm,value) return prepare(tfm,'locl',value) end
+ function fonts.initializers.node.otf.mark(tfm,value) return prepare(tfm,'mark',value) end
+ function fonts.initializers.node.otf.med2(tfm,value) return prepare(tfm,'med2',value) end
+ function fonts.initializers.node.otf.medi(tfm,value) return prepare(tfm,'medi',value) end
+ function fonts.initializers.node.otf.mgrk(tfm,value) return prepare(tfm,'mgrk',value) end
+ function fonts.initializers.node.otf.mkmk(tfm,value) return prepare(tfm,'mkmk',value) end
+ function fonts.initializers.node.otf.nalt(tfm,value) return prepare(tfm,'nalt',value) end
+ function fonts.initializers.node.otf.nlck(tfm,value) return prepare(tfm,'nlck',value) end
+ function fonts.initializers.node.otf.nukt(tfm,value) return prepare(tfm,'nukt',value) end
+ function fonts.initializers.node.otf.numr(tfm,value) return prepare(tfm,'numr',value) end
+ function fonts.initializers.node.otf.onum(tfm,value) return prepare(tfm,'onum',value) end
+ function fonts.initializers.node.otf.ordn(tfm,value) return prepare(tfm,'ordn',value) end
+ function fonts.initializers.node.otf.ornm(tfm,value) return prepare(tfm,'ornm',value) end
+ function fonts.initializers.node.otf.pnum(tfm,value) return prepare(tfm,'pnum',value) end
+ function fonts.initializers.node.otf.pref(tfm,value) return prepare(tfm,'pref',value) end
+ function fonts.initializers.node.otf.pres(tfm,value) return prepare(tfm,'pres',value) end
+ function fonts.initializers.node.otf.pstf(tfm,value) return prepare(tfm,'pstf',value) end
+ function fonts.initializers.node.otf.rlig(tfm,value) return prepare(tfm,'rlig',value) end
+ function fonts.initializers.node.otf.rphf(tfm,value) return prepare(tfm,'rphf',value) end
+ function fonts.initializers.node.otf.salt(tfm,value) return prepare(tfm,'salt',value) end
+ function fonts.initializers.node.otf.sinf(tfm,value) return prepare(tfm,'sinf',value) end
+ function fonts.initializers.node.otf.smcp(tfm,value) return prepare(tfm,'smcp',value) end
+ function fonts.initializers.node.otf.smpl(tfm,value) return prepare(tfm,'smpl',value) end
+ function fonts.initializers.node.otf.ss01(tfm,value) return prepare(tfm,'ss01',value) end
+ function fonts.initializers.node.otf.ss02(tfm,value) return prepare(tfm,'ss02',value) end
+ function fonts.initializers.node.otf.ss03(tfm,value) return prepare(tfm,'ss03',value) end
+ function fonts.initializers.node.otf.ss04(tfm,value) return prepare(tfm,'ss04',value) end
+ function fonts.initializers.node.otf.ss05(tfm,value) return prepare(tfm,'ss05',value) end
+ function fonts.initializers.node.otf.ss06(tfm,value) return prepare(tfm,'ss06',value) end
+ function fonts.initializers.node.otf.ss07(tfm,value) return prepare(tfm,'ss07',value) end
+ function fonts.initializers.node.otf.ss08(tfm,value) return prepare(tfm,'ss08',value) end
+ function fonts.initializers.node.otf.ss09(tfm,value) return prepare(tfm,'ss09',value) end
+ function fonts.initializers.node.otf.subs(tfm,value) return prepare(tfm,'subs',value) end
+ function fonts.initializers.node.otf.sups(tfm,value) return prepare(tfm,'sups',value) end
+ function fonts.initializers.node.otf.swsh(tfm,value) return prepare(tfm,'swsh',value) end
+ function fonts.initializers.node.otf.titl(tfm,value) return prepare(tfm,'titl',value) end
+ function fonts.initializers.node.otf.tnam(tfm,value) return prepare(tfm,'tnam',value) end
+ function fonts.initializers.node.otf.tnum(tfm,value) return prepare(tfm,'tnum',value) end
+ function fonts.initializers.node.otf.trad(tfm,value) return prepare(tfm,'trad',value) end
+ function fonts.initializers.node.otf.unic(tfm,value) return prepare(tfm,'unic',value) end
+ function fonts.initializers.node.otf.zero(tfm,value) return prepare(tfm,'zero',value) end
+
+end
+
+do
+
+ local glyph = node.id('glyph')
+ local glue = node.id('glue')
+ local kern_node = node.new("kern")
+ local glue_node = node.new("glue")
+ local glyph_node = node.new("glyph")
+
+ local fontdata = fonts.tfm.id
+ local has_attribute = node.has_attribute
+ local set_attribute = node.set_attribute
+ local state = attributes.numbers['state'] or 100
+ local marknumber = attributes.numbers['mark'] or 200
+ local format = string.format
+ local report = logs.report
+
+ fonts.otf.features.process = { }
+
+ -- we share aome vars here, after all, we have no nested lookups and
+ -- less code
+
+ local tfmdata = false
+ local otfdata = false
+ local characters = false
+ local marks = false
+ local glyphs = false
+ local currentfont = false
+
+ function fonts.otf.features.process.feature(head,font,kind,attribute)
+ tfmdata = fontdata[font]
+ otfdata = tfmdata.shared.otfdata
+ characters = tfmdata.characters
+ marks = otfdata.luatex.marks
+ glyphs = otfdata.glyphs
+ currentfont = font
+ local lookuptable = tfmdata.shared.lookuptable[kind]
+ if lookuptable then
+ local types = otfdata.luatex.name_to_type
+ local start, done, ok = head, false, false
+ local processes = tfmdata.shared.processes[kind]
+ if #processes == 1 then
+ local p = processes[1]
+ while start do
+ if start.id == glyph and start.font == font and (not attribute or has_attribute(start,state,attribute)) then
+ -- we can make the p vars also global to this closure
+ local pp = p[3] -- all lookups
+ local pc = pp[start.char]
+ if pc then
+ start, ok = p[1](start,kind,p[2],pc,pp,p[4])
+ done = done or ok
+ if start then start = start.next end
+ else
+ start = start.next
+ end
+ else
+ start = start.next
+ end
+ end
+ else
+ while start do
+ if start.id == glyph and start.font == font and (not attribute or has_attribute(start,state,attribute)) then
+ for i=1,#processes do local p = processes[i]
+ local pp = p[3]
+ local pc = pp[start.char]
+ if pc then
+ start, ok = p[1](start,kind,p[2],pc,pp,p[4])
+ if ok then
+ done = true
+ break
+ elseif not start then
+ break
+ end
+ end
+ end
+ if start then start = start.next end
+ else
+ start = start.next
+ end
+ end
+ end
+ return head, done
+ else
+ return head, false
+ end
+ end
+
+ -- todo: components / else subtype 0 / maybe we should be able to force this
+
+ function toligature(start,stop,char,markflag)
+ if start ~= stop then
+ local deletemarks = markflag ~= "mark"
+ start.subtype = 1
+ start.char = char
+ local marknum = 1
+ local next = start.next
+ while true do
+ if marks[next.char] then
+ if not deletemarks then
+ set_attribute(next,marknumber,marknum)
+ end
+ else
+ marknum = marknum + 1
+ end
+ if next == stop then
+ break
+ else
+ next = next.next
+ end
+ end
+ next = stop.next
+ while next do
+ if next.id == glyph and next.font == currentfont and marks[next.char] then
+ set_attribute(next,marknumber,marknum)
+ next = next.next
+ else
+ break
+ end
+ end
+ local next = start.next
+ while true do
+ if next == stop or deletemarks or marks[next.char] then
+ local crap = next
+ next.prev.next = next.next
+ if next.next then
+ next.next.prev = next.prev
+ end
+ if next == stop then
+ stop = crap.prev
+ node.free(crap)
+ break
+ else
+ next = next.next
+ node.free(crap)
+ end
+ else
+ next = next.next
+ end
+ end
+ end
+ return start
+ end
+
+ function fonts.otf.features.process.gsub_single(start,kind,lookupname,replacements)
+ if replacements then
+ start.char = replacements
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s replacing %s by %s",kind,lookupname,start.char,replacements))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_alternate(start,kind,lookupname,alternatives)
+ if alternatives then
+ start.char = alternatives[1] -- will be preference
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s alternative %s => %s",kind,lookupname,start.char,table.concat(alternatives,"|")))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_multiple(start,kind,lookupname,multiples)
+ if multiples then
+ start.char = multiples[1]
+ if #multiples > 1 then
+ for k=2,#multiples do
+ local n = node.copy(start)
+ n.char = multiples[k]
+ n.next = start.next
+ n.prev = start
+ if start.next then
+ start.next.prev = n
+ end
+ start.next = n
+ start = n
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s alternative %s => %s",kind,lookupname,start.char,table.concat(multiples," ")))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_ligature(start,kind,lookupname,ligatures,alldata,flags)
+ local s, stop = start.next, nil
+ while s and s.id == glyph and s.subtype == 0 and s.font == currentfont do
+ if marks[s.char] then
+ s = s.next
+ else
+ local lg = ligatures[1][s.char]
+ if not lg then
+ break
+ else
+ stop = s
+ ligatures = lg
+ s = s.next
+ end
+ end
+ end
+ if stop and ligatures[2] then
+ start = toligature(start,stop,ligatures[2],flags[1])
+ if fonts.otf.trace_ligatures then
+ report("process otf",format("%s: inserting ligature %s (%s)",kind,start.char,utf.char(start.char)))
+ end
+ return start, true
+ end
+ return start, false
+ end
+
+ -- again, using copies is more efficient than sharing code
+
+ function fonts.otf.features.process.gpos_mark2base(start,kind,lookupname,baseanchors,anchors) -- maybe use copies
+ local bases = baseanchors['basechar']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s: anchoring mark %s to basechar %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ end
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_mark2ligature(start,kind,lookupname,baseanchors,anchors)
+ local bases = baseanchors['baselig']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local n = has_attribute(component,marknumber)
+ local ban = ba[n]
+ if ban then
+ local dx = tex.scale(ban.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ban.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s:%s:%s anchoring mark %s to baselig %s => (%s,%s) => (%s,%s)",kind,anchor,n,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_mark2mark(start,kind,lookupname,baseanchors,anchors)
+ -- we can stay in the loop for all anchors
+ local bases = baseanchors['basemark']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local baseattr = has_attribute(start,marknumber) or 1
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markattr = has_attribute(component,marknumber) or 1
+ if baseattr == markattr then
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s:%s:%s anchoring mark %s to basemark %s => (%s,%s) => (%s,%s)",kind,anchor,n,start.char,component.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_cursive(start,kind,lookupname,exitanchors,anchors)
+ local trace = fonts.otf.trace_anchors
+ local next, done, x, y, total, t, first = start.next, false, 0, 0, 0, { }, nil
+ local function finish()
+ local i = 0
+ while first do
+ if characters[first.char].class == 'mark' then
+ first = first.next
+ else
+ first.yoffset = tex.scale(total, tfmdata.factor)
+ if first == next then
+ break
+ else
+ i = i + 1
+ total = total - (t[i] or 0)
+ first = first.next
+ end
+ end
+ end
+ x, y, total, t, first = 0, 0, 0, { }, nil
+ end
+ while next do
+ if next.id == glyph and next.font == currentfont then
+ local nextchar = next.char
+ if marks[nextchar] then
+ next = next.next
+ else
+ local entryanchors, exitanchors = anchors[nextchar], anchors[start.char]
+ if entryanchors and exitanchors then
+ local centry, cexit = entryanchors['centry'], exitanchors['cexit']
+ if centry and cexit then
+ for anchor, entry in pairs(centry) do
+ local exit = cexit[anchor]
+ if exit then
+ if not first then first = start end
+ t[#t+1] = exit.y + entry.y
+ total = total + t[#t]
+ done = true
+ break
+ end
+ end
+ else
+ finish()
+ end
+ else
+ finish()
+ end
+ start = next
+ next = start.next
+ end
+ else
+ finish()
+ break
+ end
+ end
+ return start, done
+ end
+
+ function fonts.otf.features.process.gpos_single(start,kind,lookupname,basekerns,kerns)
+ report("otf process","gpos_single not yet supported")
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_pair(start,kind,lookupname,basekerns,kerns)
+ local next, prev, done = start.next, start, false
+ while next and next.id == glyph and next.font == currentfont do
+ if characters[next.char].class == 'mark' then
+ prev = next
+ next = next.next
+ else
+ local krn = basekerns[next.char]
+ if krn then
+ local a, b = krn[1], krn[2]
+ if a and a.x then
+ local k = node.copy(kern_node)
+ k.kern = tex.scale(a.x,fontdata[currentfont].factor) -- tfmdata.factor
+ if b and b.x then
+ report("otf process","we need to do something with the second kern xoff " .. b.x)
+ end
+ k.next = next
+ k.prev = prev
+ prev.next = k
+ next.prev = k
+ if fonts.otf.trace_kerns then
+ -- todo
+ end
+ end
+ -- skip over marks
+ end
+ break
+ end
+ end
+ return start, done
+ end
+
+ chainprocs = { } -- we can probably optimize this because they're all internal lookups
+
+ -- For the moment we save each looked up glyph in the sequence, which is ok because
+ -- each lookup in the chain has its own sequence. This saves memory. Only ligatures
+ -- are stored in the featurecache, because we don't want to loop over all characters
+ -- in order to locate them.
+
+ -- We had a version that shared code, but it was too much a slow down
+ -- todo n x n.
+
+ function chainprocs.gsub_single(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = unicodes[lv[1].specification.variant] or char
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement, cacheslot[char] = char, char
+ end
+ else
+ replacement, cacheslot[char] = char, char
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by single %s",kind,char,replacement))
+ end
+ start.char = replacement
+ return start
+ end
+
+ function chainprocs.gsub_multiple(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = { }
+ for c in lv[1].specification.components:gmatch("(%S+)") do
+ replacement[#replacement+1] = unicodes[c]
+ end
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by multiple",kind,char))
+ end
+ start.char = replacement[1]
+ if #replacement > 1 then
+ for k=2,#replacement do
+ local n = node.copy(start)
+ n.char = replacement[k]
+ n.next = start.next
+ n.prev = start
+ if start.next then
+ start.next.prev = n
+ end
+ start.next = n
+ start = n
+ end
+ end
+ return start
+ end
+
+ function chainprocs.gsub_alternate(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = { }
+ for c in lv[1].specification.components:gmatch("(%S+)") do
+ replacement[#replacement+1] = unicodes[c]
+ end
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by alternate",kind,char))
+ end
+ start.char = replacement[1]
+ return start
+ end
+
+ function chainprocs.gsub_ligature(start,stop,kind,lookupname,sequence,lookups,flags)
+ if lookups then
+ local featurecache = fontdata[currentfont].shared.featurecache
+ if not featurecache[kind] then
+ featurecache[kind] = fonts.otf.features.collect_ligatures(tfmdata,kind)
+ -- to be tested: only collect internal
+ -- featurecache[kind] = fonts.otf.features.collect_ligatures(tfmdata,kind,true) --
+ end
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local ligaturecache = featurecache[kind]
+ for i=1,#lookups do
+ local ligatures = ligaturecache[lookups[i]]
+ if ligatures and ligatures[start.char] then
+ ligatures = ligatures[start.char]
+ local s = start.next
+ while s do
+ if characters[s.char].class == 'mark' then
+ s = s.next
+ else
+ local lg = ligatures[1][s.char]
+ if not lg then
+ break
+ else
+ ligatures = lg
+ if s == stop then
+ break
+ else
+ s = s.next
+ end
+ end
+ end
+ end
+ if ligatures[2] then
+ if fonts.otf.trace_ligatures then
+ report("otf chain",format("%s: replacing character %s by ligature",kind,start.char))
+ end
+ return toligature(start,stop,ligatures[2],flags[1])
+ end
+ break
+ end
+ end
+ end
+ return stop
+ end
+
+ function chainprocs.gpos_mark2base(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ if anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ local baseanchors = glyph.anchors['basechar'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to basechar %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_mark2ligature(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ if anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local done = false
+ local last = start
+ local baseanchors = glyph.anchors['baselig'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local n = has_attribute(component,marknumber)
+ local ban = ba[n]
+ if ban then
+ local dx = tex.scale(ban.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ban.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to baselig %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_mark2mark(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ local baseattr = has_attribute(start,marknumber)
+ local markattr = has_attribute(component,marknumber)
+ if baseattr == markattr and anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = false
+ local baseanchors = glyph.anchors['basemark'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to basemark %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ markattr = has_attribute(component,marknumber)
+ if baseattr ~= markattr then
+ break
+ end
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_cursive(start,stop,kind,lookupname,sequence,lookups)
+ report("otf chain","chainproc gpos_cursive not yet supported")
+ return start
+ end
+ function chainprocs.gpos_single(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","chainproc gpos_single not yet supported")
+ return start
+ end
+ function chainprocs.gpos_pair(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","chainproc gpos_pair not yet supported")
+ return start
+ end
+
+ function chainprocs.self(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","self refering lookup cannot happen")
+ return stop
+ end
+
+ function fonts.otf.features.process.contextchain(start,kind,lookupname,contextdata)
+ local done = false
+ local contexts = contextdata.lookups
+ local flags = contextdata.flags
+ local skipmark, skipligature, skipbase = unpack(flags)
+ for k=1,#contexts do
+ local match, stop = true, start
+ local rule, lookuptype, sequence, before, after, lookups = unpack(contexts[k])
+ if #sequence > 0 then
+ if #sequence == 1 then
+ match = sequence[1][start.char]
+ else -- n = #sequence -> faster
+ for n=1,#sequence do
+ if stop and stop.id == glyph and stop.font == currentfont then
+ local char = stop.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif sequence[n][char] then
+ if n < #sequence then
+ stop = stop.next
+ end
+ else
+ match = false break
+ end
+ else
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #before > 0 then
+ local prev = start.prev
+ if prev then
+ if #before == 1 then
+ match = prev.id == glyph and prev.font == currentfont and before[1][prev.char]
+ else
+ for n=#before,1 do
+ if prev then
+ if prev.id == glyph and prev.font == currentfont then -- normal char
+ local char = prev.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not before[n][char] then
+ match = false break
+ end
+ elseif not before[n][32] then
+ match = false break
+ end
+ prev = prev.prev
+ elseif not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #before == 1 then
+ match = before[1][32]
+ else
+ for n=#before,1 do
+ if not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #after > 0 then
+ local next = stop.next
+ if next then
+ if #after == 1 then
+ match = next.id == glyph and next.font == currentfont and after[1][next.char]
+ else
+ for n=1,#after do
+ if next then
+ if next.id == glyph and next.font == currentfont then -- normal char
+ local char = next.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not after[n][char] then
+ match = false break
+ end
+ elseif not after[n][32] then -- brrr
+ match = false break
+ end
+ next = next.next
+ elseif not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #after == 1 then
+ match = after[1][32]
+ else
+ for n=1,#after do
+ if not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match then
+ local trace = fonts.otf.trace_contexts
+ if trace then
+ report("otf chain",format("%s: rule %s of %s matches %s times at char %s (%s) lookuptype %s",kind,rule,lookupname,#sequence,char,utf.char(char),lookuptype))
+ end
+ if lookups then
+ local cp = chainprocs[lookuptype]
+ if cp then
+ start = cp(start,stop,kind,lookupname,sequence,lookups,flags)
+ else
+ report("otf chain",format("%s: lookuptype %s not supported yet for %s",kind,lookuptype,lookupname))
+ end
+ elseif trace then
+ report("otf chain",format("%s: skipping match for %s",kind,lookupname))
+ end
+ done = true
+ break
+ end
+ end
+ return start, done
+ end
+
+ function fonts.otf.features.process.reversecontextchain(start,kind,lookupname,contextdata)
+ -- there is only a single substitution here so it is a simple case of the normal one
+ -- sequence is one character here and we swap the rest
+ local done = false
+ local contexts = contextdata.lookups
+ local flags = contextdata.flags
+ local skipmark, skipligature, skipbase = unpack(flags)
+ for k=1,#contexts do
+ local match, stop = true, start
+ local rule, lookuptype, sequence, before, after, lookups = unpack(contexts[k])
+ match = sequence[1][start.char]
+ if match and #after > 0 then
+ local prev = start.prev
+ if prev then
+ if #after == 1 then
+ match = prev.id == glyph and prev.font == currentfont and after[1][prev.char]
+ else
+ for n=1,#after do
+ if prev then
+ if prev.id == glyph and prev.font == currentfont then -- normal char
+ local char = prev.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not after[n][char] then
+ match = false break
+ end
+ elseif not after[n][32] then
+ match = false break
+ end
+ prev = prev.prev
+ elseif not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #after == 1 then
+ match = after[1][32]
+ else
+ for n=#after,1 do
+ if not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #before > 0 then
+ local next = stop.next
+ if next then
+ if #after == 1 then
+ match = next.id == glyph and next.font == currentfont and before[1][next.char]
+ else
+ for n=#before,1 do
+ if next then
+ if next.id == glyph and next.font == currentfont then -- normal char
+ local char = next.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not before[n][char] then
+ match = false break
+ end
+ elseif not before[n][32] then -- brrr
+ match = false break
+ end
+ next = next.next
+ elseif not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #before == 1 then
+ match = before[1][32]
+ else
+ for n=1,#before do
+ if not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match then
+ local trace = fonts.otf.trace_contexts
+ if trace then
+ report("otf reverse chain",format("%s: rule %s of %s matches %s times at char %s (%s) lookuptype %s",kind,rule,lookupname,#sequence,char,utf.char(char),lookuptype))
+ end
+ if lookups then
+ local cp = chainprocs[lookuptype]
+ if cp then
+ start = cp(start,stop,kind,lookupname,sequence,lookups,flags)
+ else
+ report("otf reverse chain",format("%s: lookuptype %s not supported yet for %s",kind,lookuptype,lookupname))
+ end
+ elseif trace then
+ report("otf reverse chain",format("%s: skipping match for %s",kind,lookupname))
+ end
+ done = true
+ break
+ end
+ end
+ return start, done
+ end
+
+ fonts.otf.features.process.gsub_context = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gsub_contextchain = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gsub_reversecontextchain = fonts.otf.features.process.reversecontextchain
+
+ fonts.otf.features.process.gpos_contextchain = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gpos_context = fonts.otf.features.process.contextchain
+
+end
+
+-- aalt abvf abvs blwf blwm blws dist falt fwid half halt hwid jalt lfbd ljmo
+-- mset opbd palt pwid qwid rand rtbd rtla ruby size tjmo twid valt vatu vert
+-- vhal vjmo vkna vkrn vpal vrt2
+
+do
+
+ local process = fonts.otf.features.process.feature
+
+ function fonts.methods.node.otf.afrc(head,font) return process(head,font,'afrc') end
+ function fonts.methods.node.otf.akhn(head,font) return process(head,font,'akhn') end
+ function fonts.methods.node.otf.c2pc(head,font) return process(head,font,'c2pc') end
+ function fonts.methods.node.otf.c2sc(head,font) return process(head,font,'c2sc') end
+ function fonts.methods.node.otf.calt(head,font) return process(head,font,'calt') end
+ function fonts.methods.node.otf.case(head,font) return process(head,font,'case') end
+ function fonts.methods.node.otf.ccmp(head,font) return process(head,font,'ccmp') end
+ function fonts.methods.node.otf.clig(head,font) return process(head,font,'clig') end
+ function fonts.methods.node.otf.cpsp(head,font) return process(head,font,'cpsp') end
+ function fonts.methods.node.otf.cswh(head,font) return process(head,font,'cswh') end
+ function fonts.methods.node.otf.curs(head,font) return process(head,font,'curs') end
+ function fonts.methods.node.otf.dlig(head,font) return process(head,font,'dlig') end
+ function fonts.methods.node.otf.dnom(head,font) return process(head,font,'dnom') end
+ function fonts.methods.node.otf.expt(head,font) return process(head,font,'expt') end
+ function fonts.methods.node.otf.fin2(head,font) return process(head,font,'fin2') end
+ function fonts.methods.node.otf.fin3(head,font) return process(head,font,'fin3') end
+ function fonts.methods.node.otf.fina(head,font) return process(head,font,'fina',3) end
+ function fonts.methods.node.otf.frac(head,font) return process(head,font,'frac') end
+ function fonts.methods.node.otf.haln(head,font) return process(head,font,'haln') end
+ function fonts.methods.node.otf.hist(head,font) return process(head,font,'hist') end
+ function fonts.methods.node.otf.hkna(head,font) return process(head,font,'hkna') end
+ function fonts.methods.node.otf.hlig(head,font) return process(head,font,'hlig') end
+ function fonts.methods.node.otf.hngl(head,font) return process(head,font,'hngl') end
+ function fonts.methods.node.otf.init(head,font) return process(head,font,'init',1) end
+ function fonts.methods.node.otf.isol(head,font) return process(head,font,'isol',4) end
+ function fonts.methods.node.otf.ital(head,font) return process(head,font,'ital') end
+ function fonts.methods.node.otf.jp78(head,font) return process(head,font,'jp78') end
+ function fonts.methods.node.otf.jp83(head,font) return process(head,font,'jp83') end
+ function fonts.methods.node.otf.jp90(head,font) return process(head,font,'jp90') end
+ function fonts.methods.node.otf.kern(head,font) return process(head,font,'kern') end
+ function fonts.methods.node.otf.liga(head,font) return process(head,font,'liga') end
+ function fonts.methods.node.otf.lnum(head,font) return process(head,font,'lnum') end
+ function fonts.methods.node.otf.locl(head,font) return process(head,font,'locl') end
+ function fonts.methods.node.otf.mark(head,font) return process(head,font,'mark') end
+ function fonts.methods.node.otf.med2(head,font) return process(head,font,'med2') end
+ function fonts.methods.node.otf.medi(head,font) return process(head,font,'medi',2) end
+ function fonts.methods.node.otf.mgrk(head,font) return process(head,font,'mgrk') end
+ function fonts.methods.node.otf.mkmk(head,font) return process(head,font,'mkmk') end
+ function fonts.methods.node.otf.nalt(head,font) return process(head,font,'nalt') end
+ function fonts.methods.node.otf.nlck(head,font) return process(head,font,'nlck') end
+ function fonts.methods.node.otf.nukt(head,font) return process(head,font,'nukt') end
+ function fonts.methods.node.otf.numr(head,font) return process(head,font,'numr') end
+ function fonts.methods.node.otf.onum(head,font) return process(head,font,'onum') end
+ function fonts.methods.node.otf.ordn(head,font) return process(head,font,'ordn') end
+ function fonts.methods.node.otf.ornm(head,font) return process(head,font,'ornm') end
+ function fonts.methods.node.otf.pnum(head,font) return process(head,font,'pnum') end
+ function fonts.methods.node.otf.pref(head,font) return process(head,font,'pref') end
+ function fonts.methods.node.otf.pres(head,font) return process(head,font,'pres') end
+ function fonts.methods.node.otf.pstf(head,font) return process(head,font,'pstf') end
+ function fonts.methods.node.otf.rlig(head,font) return process(head,font,'rlig') end
+ function fonts.methods.node.otf.rphf(head,font) return process(head,font,'rphf') end
+ function fonts.methods.node.otf.salt(head,font) return process(head,font,'calt') end
+ function fonts.methods.node.otf.sinf(head,font) return process(head,font,'sinf') end
+ function fonts.methods.node.otf.smcp(head,font) return process(head,font,'smcp') end
+ function fonts.methods.node.otf.smpl(head,font) return process(head,font,'smpl') end
+ function fonts.methods.node.otf.ss01(head,font) return process(head,font,'ss01') end
+ function fonts.methods.node.otf.ss02(head,font) return process(head,font,'ss02') end
+ function fonts.methods.node.otf.ss03(head,font) return process(head,font,'ss03') end
+ function fonts.methods.node.otf.ss04(head,font) return process(head,font,'ss04') end
+ function fonts.methods.node.otf.ss05(head,font) return process(head,font,'ss05') end
+ function fonts.methods.node.otf.ss06(head,font) return process(head,font,'ss06') end
+ function fonts.methods.node.otf.ss07(head,font) return process(head,font,'ss07') end
+ function fonts.methods.node.otf.ss08(head,font) return process(head,font,'ss08') end
+ function fonts.methods.node.otf.ss09(head,font) return process(head,font,'ss09') end
+ function fonts.methods.node.otf.subs(head,font) return process(head,font,'subs') end
+ function fonts.methods.node.otf.sups(head,font) return process(head,font,'sups') end
+ function fonts.methods.node.otf.swsh(head,font) return process(head,font,'swsh') end
+ function fonts.methods.node.otf.titl(head,font) return process(head,font,'titl') end
+ function fonts.methods.node.otf.tnam(head,font) return process(head,font,'tnam') end
+ function fonts.methods.node.otf.tnum(head,font) return process(head,font,'tnum') end
+ function fonts.methods.node.otf.trad(head,font) return process(head,font,'trad') end
+ function fonts.methods.node.otf.unic(head,font) return process(head,font,'unic') end
+ function fonts.methods.node.otf.zero(head,font) return process(head,font,'zero') end
+
+end
+
+--~ function fonts.initializers.node.otf.install(feature,attribute)
+--~ function fonts.initializers.node.otf[feature](tfm,value) return fonts.otf.features.prepare.feature(tfm,feature,value) end
+--~ function fonts.methods.node.otf[feature] (head,font) return fonts.otf.features.process.feature(head,font,feature,attribute) end
+--~ end
+
+-- common stuff
+
+function fonts.otf.features.language(tfm,value)
+ if value then
+ value = value:lower()
+ if fonts.otf.tables.languages[value] then
+ tfm.language = value
+ end
+ end
+end
+
+function fonts.otf.features.script(tfm,value)
+ if value then
+ value = value:lower()
+ if fonts.otf.tables.scripts[value] then
+ tfm.script = value
+ end
+ end
+end
+
+function fonts.otf.features.mode(tfm,value)
+ if value then
+ tfm.mode = value:lower()
+ end
+end
+
+fonts.initializers.base.otf.language = fonts.otf.features.language
+fonts.initializers.base.otf.script = fonts.otf.features.script
+fonts.initializers.base.otf.mode = fonts.otf.features.mode
+fonts.initializers.base.otf.method = fonts.otf.features.mode
+
+fonts.initializers.node.otf.language = fonts.otf.features.language
+fonts.initializers.node.otf.script = fonts.otf.features.script
+fonts.initializers.node.otf.mode = fonts.otf.features.mode
+fonts.initializers.node.otf.method = fonts.otf.features.mode
+
+fonts.initializers.node.otf.trep = fonts.initializers.base.otf.trep
+fonts.initializers.node.otf.tlig = fonts.initializers.base.otf.tlig
+fonts.initializers.node.otf.texquotes = fonts.initializers.base.otf.texquotes
+fonts.initializers.node.otf.texligatures = fonts.initializers.base.otf.texligatures
+
+-- we need this because fonts can be bugged
+
+-- \definefontfeature[calt][language=nld,script=latn,mode=node,calt=yes,clig=yes,rlig=yes]
+-- \definefontfeature[dflt][language=nld,script=latn,mode=node,calt=no, clig=yes,rlig=yes]
+-- \definefontfeature[fixd][language=nld,script=latn,mode=node,calt=no, clig=yes,rlig=yes,ignoredrules={44,45,47}]
+
+-- \starttext
+
+-- {\type{dflt:}\font\test=ZapfinoExtraLTPro*dflt at 24pt \test \char57777\char57812 c/o} \endgraf
+-- {\type{calt:}\font\test=ZapfinoExtraLTPro*calt at 24pt \test \char57777\char57812 c/o} \endgraf
+-- {\type{fixd:}\font\test=ZapfinoExtraLTPro*fixd at 24pt \test \char57777\char57812 c/o} \endgraf
+
+-- \stoptext
+
+--~ table.insert(fonts.triggers,"ignoredrules")
+
+--~ function fonts.initializers.node.otf.ignoredrules(tfmdata,value)
+--~ if value then
+--~ -- these tests must move !
+--~ tfmdata.unique = tfmdata.unique or { }
+--~ tfmdata.unique.ignoredrules = tfmdata.unique.ignoredrules or { }
+--~ local ignored = tfmdata.unique.ignoredrules
+--~ -- value is already ok now
+--~ for s in string.gmatch(value:gsub("[{}]","")..",", "%s*(.-),") do
+--~ ignored[tonumber(s)] = true
+--~ end
+--~ end
+--~ end
+
+fonts.initializers.base.otf.equaldigits = fonts.initializers.common.equaldigits
+fonts.initializers.node.otf.equaldigits = fonts.initializers.common.equaldigits
+
+fonts.initializers.base.otf.lineheight = fonts.initializers.common.lineheight
+fonts.initializers.node.otf.lineheight = fonts.initializers.common.lineheight
+
+-- temp hack, may change
+
+function fonts.initializers.base.otf.kern(tfmdata,value)
+ fonts.otf.features.prepare_base_kerns(tfmdata,'kern',value)
+end
+
+--~ fonts.initializers.node.otf.kern = fonts.initializers.base.otf.kern
+
+-- there is no real need to register features here, only the defaults; supported
+-- features are part of the font data
+
+-- fonts.otf.features.register('tlig',true)
+-- fonts.otf.features.register('liga',true)
+-- fonts.otf.features.register('kern',true)
+
+-- bonus function
+
+function fonts.otf.name_to_slot(name) -- todo: afm en tfm
+ local tfmdata = fonts.tfm.id[font.current()]
+ if tfmdata and tfmdata.shared then
+ local otfdata = tfmdata.shared.otfdata
+ if otfdata and otfdata.luatex then
+ return otfdata.luatex.unicodes[name]
+ end
+ end
+ return nil
+end
+
+function fonts.otf.char(n) -- todo: afm en tfm
+ if type(n) == "string" then
+ n = fonts.otf.name_to_slot(n)
+ end
+ if n then
+ tex.sprint(tex.ctxcatcodes,string.format("\\char%s ",n))
+ end
+end
+
+--~ function fonts.otf.name_to_table(name)
+--~ lcoal temp, result = { }
+--~ local tfmdata = fonts.tfm.id[font.current()]
+--~ if tfmdata and tfmdata.shared then
+--~ local otfdata = tfmdata.shared.otfdata
+--~ if otfdata and otfdata.luatex then
+--~ for k,v in pairs(otfdata.glyphs) do
+--~ if v.name:find(name) then
+--~ temp[v.name] = v.unicodeenc
+--~ end
+--~ end
+--~ end
+--~ end
+--~ for k,v in pairs(table.sortedkeys(temp)) do
+--~ result[#result+1] = { v, temp[v] }
+--~ end
+--~ return result
+--~ end
+
+-- Here we plug in some analyzing code
+
+
+do
+
+ local glyph = node.id('glyph')
+ local fontdata = fonts.tfm.id
+ local set_attribute = node.set_attribute
+ local has_attribute = node.has_attribute
+ local state = attributes.numbers['state'] or 100
+
+ -- in the future we will use language/script attributes instead of the
+ -- font related value, but then we also need dynamic features which is
+ -- somewhat slower; and .. we need a chain of them
+
+ function fonts.initializers.node.otf.analyze(tfm,value)
+ local script, language = tfm.script, tfm.language
+ local action = fonts.analyzers.initializers[script]
+ if action then
+ if type(action) == "function" then
+ return action(tfm,value)
+ elseif action[language] then
+ return action[language](tfm,value)
+ end
+ end
+ return nil
+ end
+
+ function fonts.methods.node.otf.analyze(head,font)
+ local tfmdata = fontdata[font]
+ local script, language = fontdata[font].script, fontdata[font].language
+ local action = fonts.analyzers.methods[script]
+ if action then
+ if type(action) == "function" then
+ return action(head,font)
+ elseif action[language] then
+ return action[language](head,font)
+ end
+ end
+ return head, false
+ end
+
+ fonts.otf.features.register("analyze",true) -- we always analyze
+ table.insert(fonts.triggers,"analyze") -- we need a proper function for doing this
+
+ -- latin
+
+ fonts.analyzers.methods.latn = fonts.analyzers.aux.setstate
+
+ -- arab / todo: 0640 tadwil
+
+ local isol = {
+ [0x0621] = true,
+ }
+
+ local isol_fina = {
+ [0x0622] = true, [0x0623] = true, [0x0624] = true, [0x0625] = true, [0x0627] = true, [0x062F] = true,
+ [0x0630] = true, [0x0631] = true, [0x0632] = true,
+ [0x0648] = true,
+ [0xFEF5] = true, [0xFEF7] = true, [0xFEF9] = true, [0xFEFB] = true,
+ }
+
+ local isol_fina_medi_init = {
+ [0x0626] = true, [0x0628] = true, [0x0629] = true, [0x062A] = true, [0x062B] = true, [0x062C] = true, [0x062D] = true, [0x062E] = true,
+ [0x0633] = true, [0x0634] = true, [0x0635] = true, [0x0636] = true, [0x0637] = true, [0x0638] = true, [0x0639] = true, [0x063A] = true,
+ [0x0641] = true, [0x0642] = true, [0x0643] = true, [0x0644] = true, [0x0645] = true, [0x0646] = true, [0x0647] = true, [0x0649] = true, [0x064A] = true,
+ [0x067E] = true,
+ [0x0686] = true,
+ }
+
+ local arab_warned = { }
+
+ local function warning(current,what)
+ local char = current.char
+ if not arab_warned[char] then
+ log.report("analyze",string.format("arab: character %s (0x%04X) has no %s class", char, char, what))
+ arab_warned[char] = true
+ end
+ end
+
+ local fcs = fonts.color.set
+ local fcr = fonts.color.reset
+
+ function fonts.analyzers.methods.nocolor(head,font)
+ for n in nodes.traverse(glyph) do
+ if not font or n.font == font then
+ fcr(n)
+ end
+ end
+ return head, true
+ end
+
+ function fonts.analyzers.methods.arab(head,font) -- maybe make a special version with no trace
+ local characters = fontdata[font].characters
+ local first, last, current, done = nil, nil, head, false
+ local trace = fonts.color.trace
+ --~ local laststate = 0
+ local function finish()
+ if last then
+ if first == last then
+ if isol_fina_medi_init[first.char] or isol_fina[first.char] then
+ set_attribute(first,state,4) -- isol
+ if trace then fcs(first,"font:isol") end
+ else
+ warning(first,"isol")
+ set_attribute(first,state,0) -- error
+ if trace then fcr(first) end
+ end
+ else
+ if isol_fina_medi_init[last.char] or isol_fina[last.char] then -- why isol here ?
+ -- if laststate == 1 or laststate == 2 or laststate == 4 then
+ set_attribute(last,state,3) -- fina
+ if trace then fcs(last,"font:fina") end
+ else
+ warning(last,"fina")
+ set_attribute(last,state,0) -- error
+ if trace then fcr(last) end
+ end
+ end
+ first, last = nil, nil
+ elseif first then
+ -- first and last are either both set so we never com here
+ if isol_fina_medi_init[first.char] or isol_fina[first.char] then
+ set_attribute(first,state,4) -- isol
+ if trace then fcs(first,"font:isol") end
+ else
+ warning(first,"isol")
+ set_attribute(first,state,0) -- error
+ if trace then fcr(first) end
+ end
+ first = nil
+ end
+ --~ laststate = 0
+ end
+ while current do
+ if current.id == glyph and current.font == font then
+ done = true
+ local char = current.char
+ if characters[char].class == "mark" then -- marks are now in components
+ set_attribute(current,state,5) -- mark
+ if trace then fcs(current,"font:mark") end
+ elseif isol[char] then
+ finish()
+ set_attribute(current,state,4) -- isol
+ if trace then fcs(current,"font:isol") end
+ first, last = nil, nil
+ --~ laststate = 0
+ elseif not first then
+ if isol_fina_medi_init[char] then
+ set_attribute(current,state,1) -- init
+ if trace then fcs(current,"font:init") end
+ first, last = first or current, current
+ --~ laststate = 1
+ elseif isol_fina[char] then
+ set_attribute(current,state,4) -- isol
+ if trace then fcs(current,"font:isol") end
+ first, last = nil, nil
+ --~ laststate = 0
+ else -- no arab
+ finish()
+ end
+ elseif isol_fina_medi_init[char] then
+ first, last = first or current, current
+ set_attribute(current,state,2) -- medi
+ if trace then fcs(current,"font:medi") end
+ --~ laststate = 2
+ elseif isol_fina[char] then
+ -- if not laststate == 1 then
+ if not has_attribute(last,state,1) then
+ -- tricky, we need to check what last may be !
+ set_attribute(last,state,2) -- medi
+ if trace then fcs(last,"font:medi") end
+ end
+ set_attribute(current,state,3) -- fina
+ if trace then fcs(current,"font:fina") end
+ first, last = nil, nil
+ --~ laststate = 0
+ elseif char >= 0x0600 and char <= 0x06FF then
+ if trace then fcs(current,"font:rest") end
+ finish()
+ else --no
+ finish()
+ end
+ else
+ finish()
+ end
+ current = current.next
+ end
+ finish()
+ return head, done
+ end
+
+end
+
+-- experimental and will probably change
+
+function fonts.install_feature(type,...)
+ if fonts[type] and fonts[type].install_feature then
+ fonts[type].install_feature(...)
+ end
+end
+function fonts.otf.install_feature(tag)
+ fonts.methods.node.otf [tag] = function(head,font) return fonts.otf.features.process.feature(head,font,tag) end
+ fonts.initializers.node.otf[tag] = function(tfm,value) return fonts.otf.features.prepare.feature(tfm,tag,value) end
+end
+
+
+--~ exclam + quoteleft => exclamdown
+--~ question + quoteleft => questiondown
+
+--~ less + less => guillemotleft
+--~ greater + greater => guillemotright
+
+--~ I + J => IJ
+--~ f + f => ff
+--~ f + i => fi
+--~ f + l => fl
+--~ f + k => fk
+--~ ff + i => ffi
+--~ ff + l => ffl
+--~ i + j => ij
+
+--~ comma + comma => quotedblbase
+--~ quoteleft + quoteleft => quotedblleft
+--~ quoteright + quoteright => quotedblright
+
+--~ hyphen + hyphen => endash
+--~ endash + hyphen => emdash
+
diff --git a/tex/context/base/font-syn.lua b/tex/context/base/font-syn.lua
new file mode 100644
index 000000000..77898a4a2
--- /dev/null
+++ b/tex/context/base/font-syn.lua
@@ -0,0 +1,285 @@
+if not modules then modules = { } end modules ['font-syn'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module implements a name to filename resolver. Names are resolved
+using a table that has keys filtered from the font related files.
It would make sense to implement the filters in the related modules,
+but to keep the overview, we define them here.
+--ldx]]--
+
+fonts.names.filters.otf = fontforge.info
+fonts.names.filters.ttf = fontforge.info
+fonts.names.filters.ttc = fontforge.info
+
+function fonts.names.filters.afm(name)
+ local f = io.open(name)
+ if f then
+ local hash = { }
+ for line in f:lines() do
+ local key, value = line:match("^(.+)%s+(.+)%s*$")
+ if key and #key > 0 then
+ hash[key:lower()] = value
+ end
+ if line:find("StartCharMetrics") then
+ break
+ end
+ end
+ f:close()
+ return hash
+ else
+ return nil
+ end
+end
+
+--[[ldx--
+
The scanner loops over the filters using the information stored in
+the file databases. Watch how we check not only for the names, but also
+for combination with the weight of a font.
+--ldx]]--
+
+fonts.names.filters.list = {
+ "otf", "ttf", "ttc", "afm"
+}
+
+fonts.names.filters.fixes = {
+ { "reg$", "regular", },
+ { "ita$", "italic", },
+ { "ital$", "italic", },
+ { "cond$", "condensed", },
+}
+
+function fonts.names.identify()
+ fonts.names.data = {
+ mapping = { },
+ version = fonts.names.version
+ }
+ local done, mapping, nofread, nofok = { }, fonts.names.data.mapping, 0, 0
+ local function add(n,fontname,filename,suffix, sub)
+ n = n:lower()
+ if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end
+ n = n:gsub("[^%a%d]","")
+ if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end
+ end
+ local function check(result, filename, suffix, is_sub)
+ local fontname = result.fullname
+ if fontname then
+ add(result.fullname, fontname, filename, suffix, is_sub)
+ end
+ if result.fontname then
+ fontname = fontname or result.fontname
+ add(result.fontname, fontname, filename, suffix, is_sub)
+ end
+ if result.familyname and result.weight then
+ local madename = result.familyname .. " " .. result.weight
+ fontname = fontname or madename
+ add(madename, fontname, filename, suffix, is_sub)
+ end
+ end
+ local function identify(completename,name,suffix)
+ if not done[name] then
+ nofread = nofread + 1
+ logs.info("fontnames", "identifying " .. suffix .. " font " .. completename)
+ logs.push()
+ local result = fonts.names.filters[suffix](completename)
+ logs.pop()
+ if result then
+ if not result[1] then
+ check(result,name,suffix,false)
+ else for _, r in ipairs(result) do
+ check(r,name,suffix,true)
+ end end
+ end
+ done[name] = true
+ end
+ end
+ local function traverse(what, method)
+ for n, suffix in pairs(fonts.names.filters.list) do
+ nofread, nofok = 0, 0
+ local t = os.clock()
+ logs.report("fontnames", string.format("identifying %s font files with suffix %s",what,suffix))
+ method(suffix)
+ logs.report("fontnames", string.format("%s %s files identified, %s hash entries added, runtime %s seconds", nofread, what,nofok, os.clock()-t))
+ end
+ end
+ traverse("tree", function(suffix)
+ input.with_files(texmf.instance,".*%." .. suffix .. "$", function(method,root,path,name)
+ if method == "file" then
+ identify(root .."/" .. path .. "/" .. name,name,suffix)
+ end
+ end)
+ end)
+ traverse("system", function(suffix)
+ local pathlist = input.expanded_path_list(texmf.instance,"osfontdir")
+ if pathlist then
+ for _, path in ipairs(pathlist) do
+ -- not that much needed
+ path = input.clean_path(path .. "/")
+ path = path:gsub("/+","/")
+ local pattern = path .. "*." .. suffix
+ logs.info("fontnames", "globbing path " .. pattern)
+ local t = dir.glob(pattern)
+ for _, name in pairs(t) do
+ local mode = lfs.attributes(name,'mode')
+ if mode == "file" then
+ identify(name,file.basename(name),suffix)
+ end
+ end
+ end
+ end
+ end)
+ local t = { }
+ for _, f in ipairs(fonts.names.filters.fixes) do
+ local expression, replacement = f[1], f[2]
+ for k,v in pairs(mapping) do
+ local fix, pos = k:gsub(expression,replacement)
+ if pos > 0 and not mapping[fix] then
+ t[fix] = v
+ end
+ end
+ end
+ for k,v in pairs(t) do
+ mapping[k] = v
+ end
+end
+
+function fonts.names.load(reload)
+ if not fonts.names.loaded then
+ if reload then
+ if containers.is_usable(fonts.names.cache, "names") then
+ fonts.names.identify()
+ containers.write(fonts.names.cache, "names", fonts.names.data)
+ end
+ fonts.names.saved = true
+ else
+ fonts.names.data = containers.read(fonts.names.cache, "names")
+ if not fonts.names.saved then
+ if table.is_empty(fonts.names.data) or table.is_empty(fonts.names.data.mapping) then
+ fonts.names.load(true)
+ end
+ fonts.names.saved = true
+ end
+ end
+ fonts.names.loaded = true
+ end
+end
+
+function fonts.names.list(pattern,reload)
+ fonts.names.load(reload)
+ if fonts.names.loaded then
+ local t = { }
+ for k,v in pairs(fonts.names.data.mapping) do
+ if k:find(pattern) then
+ t[k] = v
+ end
+ end
+ return t
+ else
+ return nil
+ end
+end
+
+--[[ldx--
+
The resolver also checks if the cached names are loaded. Being clever
+here is for testing purposes only (it deals with names prefixed by an
+encoding name).
+--ldx]]--
+
+do
+
+ local function found(name)
+ if fonts.names.data then
+ local result, mapping = nil, fonts.names.data.mapping
+ local mn = mapping[name]
+ if mn then
+ return mn[2], mn[3], mn[4]
+ end
+ if fonts.names.be_clever then -- this will become obsolete
+ local encoding, tag = name:match("^(.-)[%-%:](.+)$")
+ local mt = mapping[tag]
+ if tag and fonts.enc.is_known(encoding) and mt then
+ return mt[1], encoding .. "-" .. mt[3], mt[4]
+ end
+ end
+ -- name, type, file
+ for k,v in pairs(mapping) do
+ if k:find(name) then
+ return v[2], v[3], v[4]
+ end
+ end
+ local condensed = name:gsub("[^%a%d]","")
+ local mc = mapping[condensed]
+ if mc then
+ return mc[2], mc[3], mc[4]
+ end
+ for k,v in pairs(mapping) do
+ if k:find(condensed) then
+ return v[2], v[3], v[4]
+ end
+ end
+ end
+ return nil, nil, nil
+ end
+
+ function fonts.names.resolve(name, sub)
+ if not name then
+ return nil, nil
+ elseif fonts.names.enabled then
+ fonts.names.load()
+ local name, filename, is_sub = found(name:lower())
+ if is_sub then
+ return filename, name
+ else
+ return filename, sub
+ end
+ else
+ return filename, sub
+ end
+ end
+
+end
+
+--[[ldx--
+
A handy helper.
+--ldx]]--
+
+function fonts.names.table(pattern,reload,all)
+ local t = fonts.names.list(pattern,reload)
+ if t then
+ tex.sprint(tex.ctxcatcodes,"\\start\\nonknuthmode\\starttabulate[|T|T|T|T|T|]")
+ tex.sprint(tex.ctxcatcodes,"\\NC hashname\\NC type\\NC fontname\\NC filename\\NC\\NR\\HL")
+ for k,v in pairs(table.sortedkeys(t)) do
+ if all or v == t[v][2]:lower() then
+ local type, name, file = unpack(t[v])
+ if type and name and file then
+ tex.sprint(tex.ctxcatcodes,string.format("\\NC %s\\NC %s\\NC %s\\NC %s\\NC\\NR",v,type, name, file))
+ else
+ logs.report("font table", "skipping ".. v)
+ end
+ end
+ end
+ tex.sprint(tex.ctxcatcodes,"\\stoptabulate\\stop")
+ end
+end
diff --git a/tex/context/base/font-tfm.lua b/tex/context/base/font-tfm.lua
new file mode 100644
index 000000000..b07f0a062
--- /dev/null
+++ b/tex/context/base/font-tfm.lua
@@ -0,0 +1,479 @@
+if not modules then modules = { } end modules ['font-tfm'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Here we only implement a few helper functions.
+--ldx]]--
+
+fonts = fonts or { }
+fonts.loaded = fonts.loaded or { }
+fonts.dontembed = fonts.dontembed or { }
+fonts.logger = fonts.logger or { }
+fonts.loadtime = 0
+fonts.tfm = fonts.tfm or { }
+fonts.triggers = fonts.triggers or { } -- brrr
+
+--[[ldx--
+
The next function encapsulates the standard loader as
+supplied by .
We need to normalize the scale factor (in scaled points). This has to
+do with the fact that uses a negative multiple of 1000 as
+a signal for a font scaled based on the design size.
+--ldx]]--
+
+function fonts.tfm.scaled(scaledpoints, designsize) -- handles designsize in sp as well
+ if scaledpoints < 0 then
+ if designsize then
+ if designsize > 65536 then -- or just 1000
+ return (- scaledpoints/1000) * designsize -- sp's
+ else
+ return (- scaledpoints/1000) * designsize * 65536
+ end
+ else
+ return (- scaledpoints/1000) * 10 * 65536
+ end
+ else
+ return scaledpoints
+ end
+end
+
+--[[ldx--
+
Before a font is passed to we scale it.
+--ldx]]--
+
+function fonts.tfm.scale(tfmtable, scaledpoints)
+ -- 65536 = 1pt
+ -- 1000 units per designsize (not always)
+ local scale, round = tex.scale, tex.round -- replaces math.floor(n*m+0.5)
+ local delta
+ if scaledpoints < 0 then
+ scaledpoints = (- scaledpoints/1000) * tfmtable.designsize -- already in sp
+ end
+ delta = scaledpoints/(tfmtable.units or 1000) -- brr, some open type fonts have 2048
+ local t = { }
+ t.factor = delta
+ for k,v in pairs(tfmtable) do
+ if type(v) == "table" then
+ t[k] = { }
+ else
+ t[k] = v
+ end
+ end
+ local tc = t.characters
+ for k,v in pairs(tfmtable.characters) do
+ local chr = {
+ unicode = v.unicode,
+ name = v.name,
+ index = v.index or k,
+ width = scale(v.width , delta),
+ height = scale(v.height, delta),
+ depth = scale(v.depth , delta),
+ class = v.class
+ }
+ local b = v.boundingbox -- maybe faster to have llx etc not in table
+ if b then
+ chr.boundingbox = scale(v.boundingbox,delta)
+ end
+ if v.italic then
+ chr.italic = scale(v.italic,delta)
+ end
+ if v.kerns then
+ chr.kerns = scale(v.kerns,delta)
+ end
+ if v.ligatures then
+ local tt = { }
+ for kk,vv in pairs(v.ligatures) do
+ tt[kk] = vv
+ end
+ chr.ligatures = tt
+ end
+ tc[k] = chr
+ end
+ local tp = t.parameters
+ for k,v in pairs(tfmtable.parameters) do
+ if k == 1 then
+ tp[k] = round(v)
+ else
+ tp[k] = scale(v,delta)
+ end
+ end
+--~ t.encodingbytes = tfmtable.encodingbytes or 2
+ t.size = scaledpoints
+ t.italicangle = tfmtable.italicangle
+ t.ascender = scale(tfmtable.ascender or 0,delta)
+ t.descender = scale(tfmtable.descender or 0,delta)
+ -- new / some data will move here
+ t.shared = tfmtable.shared or { }
+ if t.unique then
+ t.unique = table.fastcopy(tfmtable.unique)
+ else
+ t.unique = { }
+ end
+ return t
+end
+
+--[[ldx--
+
The following functions are used for reporting about the fonts
+used. The message itself is not that useful in regular runs but since
+we now have several readers it may be handy to know what reader is
+used for which font.
+--ldx]]--
+
+function fonts.logger.save(tfmtable,source,specification)
+ if tfmtable and specification and specification.specification then
+ specification.source = source
+ fonts.loaded[specification.specification] = specification
+ fonts.used[specification.name] = source
+ end
+end
+
+function fonts.logger.report(separator)
+ local t = { }
+ for _,v in pairs(table.sortedkeys(fonts.loaded)) do
+ t[#t+1] = v .. ":" .. fonts.loaded[v].source
+ end
+ return table.concat(t,separator or " ")
+end
+
+function fonts.logger.format(name)
+ return fonts.used[name] or "unknown"
+end
+
+--[[ldx--
+
When we implement functions that deal with features, most of them
+will depend of the font format. Here we define the few that are kind
+of neutral.
+--ldx]]--
+
+fonts.initializers = fonts.initializers or { }
+fonts.initializers.common = fonts.initializers.common or { }
+
+--[[ldx--
+
This feature will remove inter-digit kerns.
+--ldx]]--
+
+table.insert(fonts.triggers,"equaldigits")
+
+function fonts.initializers.common.equaldigits(tfmdata,value)
+ if value then
+ local chr = tfmdata.characters
+ for i = utf.byte('0'), utf.byte('9') do
+ local c = chr[i]
+ if c then
+ c.kerns = nil
+ end
+ end
+ end
+end
+
+--[[ldx--
+
This feature will give all glyphs an equal height and/or depth. Valid
+values are none, height, depth and
+both.
+--ldx]]--
+
+table.insert(fonts.triggers,"lineheight")
+
+function fonts.initializers.common.lineheight(tfmdata,value)
+ if value and type(value) == "string" then
+ if value == "none" then
+ for _,v in pairs(tfmdata.characters) do
+ v.height, v.depth = 0, 0
+ end
+ else
+ local ascender, descender = tfmdata.ascender, tfmdata.descender
+ if ascender and descender then
+ local ht, dp = ascender or 0, descender or 0
+ if value == "height" then
+ dp = 0
+ elseif value == "depth" then
+ ht = 0
+ end
+ if ht > 0 then
+ if dp > 0 then
+ for _,v in pairs(tfmdata.characters) do
+ v.height, v.depth = ht, dp
+ end
+ else
+ for _,v in pairs(tfmdata.characters) do
+ v.height = ht
+ end
+ end
+ elseif dp > 0 then
+ for _,v in pairs(tfmdata.characters) do
+ v.depth = dp
+ end
+ end
+ end
+ end
+ end
+end
+
+--[[ldx--
+
It does not make sense any more to support messed up encoding vectors
+so we stick to those that implement oldstyle and small caps. After all,
+we move on. We can extend the next function on demand. This features is
+only used with files.
+--ldx]]--
+
+do
+
+ local smallcaps = lpeg.P(".sc") + lpeg.P(".smallcaps") + lpeg.P(".caps") + lpeg.P("small")
+ local oldstyle = lpeg.P(".os") + lpeg.P(".oldstyle") + lpeg.P(".onum")
+
+ smallcaps = lpeg.Cs((1-smallcaps)^1) * smallcaps^1
+ oldstyle = lpeg.Cs((1-oldstyle )^1) * oldstyle ^1
+
+ function fonts.initializers.common.encoding(tfmdata,value)
+ if value then
+ local encodingfile = value .. '.enc'
+ local encoding = fonts.enc.load(encodingfile)
+ if encoding then
+ -- tfmdata.encoding = value
+ local vector = encoding.vector
+ local afmdata = tfmdata.shared.afmdata
+ local characters = tfmdata.characters
+ local unicodes = afmdata.luatex.unicodes
+ local function remap(pattern,name)
+ local p = lpeg.match(pattern,name)
+ if p then
+ local oldchr, newchr = unicodes[p], unicodes[name]
+ if oldchr and newchr then
+ -- texio.write_nl(string.format("%s (%s) -> %s (%s)",p,oldchr or -1,name,newchr or -1))
+ characters[oldchr] = characters[newchr]
+ end
+ end
+ return p
+ end
+ for _, name in pairs(vector) do
+ local ok = remap(smallcaps,name) or remap(oldstyle,name)
+ end
+ if fonts.map.data[tfmdata.name] then
+ fonts.map.data[tfmdata.name].encoding = encodingfile
+ end
+ end
+ end
+ end
+
+ -- when needed we can provide this as features in e.g. afm files
+
+ function fonts.initializers.common.remap(tfmdata,value,pattern)
+ if value then
+ local afmdata = tfmdata.shared.afmdata
+ local characters = tfmdata.characters
+ local unicodes = afmdata.luatex.unicodes
+ local function remap(pattern,name)
+ local p = lpeg.match(pattern,name)
+ if p then
+ local oldchr, newchr = unicodes[p], unicodes[name]
+ if oldchr and newchr then
+ characters[oldchr] = characters[newchr]
+ end
+ end
+ return p
+ end
+ for _, blob in pairs(characters) do
+ remap(pattern,blob.name)
+ end
+ end
+ end
+
+ function fonts.initializers.common.oldstyle(tfmdata,value)
+ fonts.initializers.common.remap(tfmdata,value,oldstyle)
+ end
+ function fonts.initializers.common.smallcaps(tfmdata,value)
+ fonts.initializers.common.remap(tfmdata,value,smallcaps)
+ end
+
+end
+
+--~ function fonts.initializers.common.install(format,feature) -- 'afm','lineheight'
+--~ fonts.initializers.base[format][feature] = fonts.initializers.common[feature]
+--~ fonts.initializers.node[format][feature] = fonts.initializers.common[feature]
+--~ end
+
+--[[ldx--
+
Analyzers run per script and/or language and are needed in order to
+process features right.
+--ldx]]--
+
+fonts.analyzers = fonts.analyzers or { }
+fonts.analyzers.aux = fonts.analyzers.aux or { }
+fonts.analyzers.methods = fonts.analyzers.methods or { }
+fonts.analyzers.initializers = fonts.analyzers.initializers or { }
+
+do
+
+ local glyph = node.id('glyph')
+ local fontdata = fonts.tfm.id
+ local set_attribute = node.set_attribute
+ local unset_attribute = node.unset_attribute
+ local has_attribute = node.has_attribute
+
+ local state = attributes.numbers['state'] or 100
+
+ -- todo: analyzers per script/lang, cross font, so we need an font id hash -> script
+ -- e.g. latin -> hyphenate, arab -> 1/2/3 analyze
+
+ -- an example analyzer
+
+ function fonts.analyzers.aux.setstate(head,font)
+ local characters = fontdata[font].characters
+ local first, last, current, n, done = nil, nil, head, 0, false -- maybe make n boolean
+ local function finish()
+ if first and first == last then
+ set_attribute(last,state,4) -- isol
+ elseif last then
+ set_attribute(last,state,3) -- fina
+ end
+ first, last, n = nil, nil, 0
+ end
+ while current do
+ if current.id == glyph and current.font == font then
+ if characters[current.char].class == "mark" then
+ done = true
+ set_attribute(current,state,5) -- mark
+ elseif n == 0 then
+ first, last, n = current, current, 1
+ set_attribute(current,state,1) -- init
+ else
+ last, n = current, n+1
+ set_attribute(current,state,2) -- medi
+ end
+ else
+ finish()
+ end
+ current = current.next
+ end
+ finish()
+ return head, done
+ end
+
+end
+
+
+--[[ldx--
+
We move marks into the components list. This saves much nasty testing later on.
+--ldx]]--
+
+do
+
+ local glyph = node.id('glyph')
+ local fontdata = fonts.tfm.id
+ local marknumber = attributes.numbers['mark'] or 200
+ local set_attribute = node.set_attribute
+
+ function fonts.pushmarks(head,font)
+ local tfmdata = fontdata[font]
+ local characters = tfmdata.characters
+ local current, last, done, n = head, nil, false, 0
+ while current do
+ if current.id == glyph and current.font == font then
+ if characters[current.char].class == "mark" then
+ -- check if head
+ if last and not last.components then
+ last.components = current
+ last.components.prev = nil
+ done = true
+ n = 1
+ else
+ n = n + 1
+ end
+ set_attribute(current,marknumber,n)
+ current = current.next
+ elseif last and last.components then
+ -- finish 'm
+ current.prev.next = nil
+ current.prev = last
+ last.next = current
+ last = current
+ last = nil
+ else
+ last = current
+ current = current.next
+ end
+ elseif last and last.components then
+ current.prev.next = nil
+ current.prev = last
+ last.next = current
+ last = nil
+ else
+ last = nil
+ current = current.next
+ end
+ end
+ if last and last.components then
+ last.next = nil
+ end
+ tfmdata.shared.markspushed = done
+ return head, done
+ end
+
+ function fonts.removemarks(head,font)
+ local current, done, characters = head, false, tfmdata.characters
+ while current do
+ if current.id == glyph and current.font == font and characters[current.char].class == "mark" then
+ local next, prev = current.next, current.prev
+ if next then
+ next.prev = prev
+ end
+ if prev then
+ prev.next = next
+ else
+ head = next
+ end
+ node.free(current)
+ current = next
+ done = true
+ else
+ current = current.next
+ end
+ end
+ return head, done
+ end
+
+ function fonts.popmarks(head,font)
+ local tfmdata = fontdata[font]
+ if tfmdata.shared.markspushed then
+ local current, done, characters = head, false, tfmdata.characters
+ while current do
+ if current.id == glyph and current.font == font then
+ local components = current.components
+ if components then
+ local last, next = components, current.next
+ while last.next do last = last.next end
+ if next then
+ next.prev = last
+ end
+ last.next= next
+ current.next = components
+ components.prev = current
+ current.components = nil
+ current = last.next
+ done = true
+ else
+ current = current.next
+ end
+ else
+ current = current.next
+ end
+ end
+ return head, done
+ else
+ return head, false
+ end
+ end
+
+end
diff --git a/tex/context/base/font-vf.lua b/tex/context/base/font-vf.lua
new file mode 100644
index 000000000..f0258e281
--- /dev/null
+++ b/tex/context/base/font-vf.lua
@@ -0,0 +1,213 @@
+if not modules then modules = { } end modules ['font-vf'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This is very experimental code! Not yet adapted to recent changes.
+--ldx]]--
+
+-- define.methods elsewhere !
+
+fonts = fonts or { }
+
+fonts.define = fonts.define or { }
+fonts.define.methods = fonts.define.methods or { }
+
+fonts.vf = fonts.vf or { }
+fonts.vf.combinations = fonts.vf.combinations or { }
+fonts.vf.aux = fonts.vf.aux or { }
+fonts.vf.aux.combine = fonts.vf.aux.combine or { }
+
+function fonts.define.methods.install(tag, rules)
+ fonts.vf.combinations[tag] = rules
+ fonts.define.methods[tag] = function(specification)
+ return fonts.vf.combine(specification,tag)
+ end
+end
+
+function fonts.vf.aux.combine.assign(g, name, from, to, start, force)
+ local f, id = fonts.vf.aux.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, gc = f.characters, g.characters
+ g.fonts[#g.fonts+1] = { id = id } -- no need to be sparse
+ local hn = #g.fonts
+ for i=from,to do
+ if fc[i] and (force or not gc[i]) then
+ gc[i] = table.fastcopy(fc[i])
+ gc[i].commands = { { 'slot', hn, start } }
+ end
+ start = start + 1
+ end
+ if not g.parameters and #g.fonts > 0 then -- share this code !
+ g.parameters = table.fastcopy(f.parameters)
+ g.italicangle = f.italicangle
+ g.ascender = f.ascender
+ g.descender = f.descender
+ end
+ end
+end
+
+function fonts.vf.aux.combine.process(g,list)
+ if list then
+ for _,v in pairs(list) do
+ (fonts.vf.aux.combine.commands[v[1]] or nop)(g,v)
+ end
+ end
+end
+
+function fonts.vf.aux.combine.load(g,name)
+ return fonts.tfm.read_and_define(name or g.specification.name,g.specification.size)
+end
+
+fonts.vf.aux.combine.commands = {
+ ["initialize"] = function(g,v) fonts.vf.aux.combine.assign(g, g.name) end,
+ ["include-method"] = function(g,v) fonts.vf.aux.combine.process(g,fonts.vf.combinations[v[2]]) end, -- name
+ ["copy-parameters"] = function(g,v) fonts.vf.aux.combine.parameters(g,v[2]) end, -- name
+ ["copy-range"] = function(g,v) fonts.vf.aux.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) fonts.vf.aux.combine.assign(g,v[2],v[3],v[3],v[4],true) end, -- name, from, to
+ ["fallback-range"] = function(g,v) fonts.vf.aux.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) fonts.vf.aux.combine.assign(g,v[2],v[3],v[3],v[4],false) end, -- name, from, to
+}
+
+function fonts.vf.combine(specification,tag)
+ local g = {
+ name = specification.name,
+ type = 'virtual',
+ fonts = { },
+ characters = { },
+ specification = table.fastcopy(specification)
+ }
+ fonts.vf.aux.combine.process(g,fonts.vf.combinations[tag])
+ return g
+end
+
+fonts.vf.aux.combine.commands["feature"] = function(g,v)
+ local key, value = v[2], v[3]
+ if key then
+ if value == nil then
+ value = true
+ end
+ if g.specification and g.specification.features.normal then
+ g.specification.features.normal[key] = value -- otf?
+ end
+ end
+end
+
+-- simple example with features
+
+fonts.define.methods.install(
+ "ligatures", {
+ { "feature", "liga" } ,
+ { "feature", "dlig" } ,
+ { "initialize" } ,
+ }
+)
+
+--~ fonts.define.methods.install (
+--~ "ligatures-x", {
+--~ { "feature", "liga" } ,
+--~ { "feature", "dlig" } ,
+--~ { "initialize" } ,
+--~ { "lineheight" }
+--~ }
+--~ )
+
+-- docu case
+
+--~ fonts.define.methods.install(
+--~ "weird", {
+--~ { "copy-range", "lmroman10-regular" } ,
+--~ { "copy-char", "lmroman10-regular", 65, 66 } ,
+--~ { "copy-range", "lmsans10-regular", 0x0100, 0x01FF } ,
+--~ { "copy-range", "lmtypewriter10-regular", 0x0200, 0xFF00 } ,
+--~ { "fallback-range", "lmtypewriter10-regular", 0x0000, 0x0200 }
+--~ }
+--~ )
+
+-- demo case -> move to module
+
+fonts.define.methods["demo-1"] = function(specification)
+ local name = specification.name -- symbolic name
+ local size = specification.size -- given size
+--~ specification.name = 'lmroman10-regular' -- forced base name
+--~ specification.features.vtf = { }
+ local f, id = fonts.tfm.read_and_define('lmroman10-regular',size)
+ if f and id then
+ local capscale, digscale = 0.85, 0.75
+ f.name, f.type = name, 'virtual'
+ f.fonts = {
+ {id=id},
+ {name='lmsans10-regular' , size=size*capscale}, -- forced extra name
+ {name='lmtypewriter10-regular', size=size*digscale} -- forced extra name
+ }
+ for k,v in pairs(f.characters) do
+ local u = v.unicode
+ if u and characters.i_is_of_category(u,'lu') then
+ v.width = capscale*v.width
+ v.commands = {
+ {'special','pdf: 1 0 0 rg'},
+ {'slot',2, k},
+ {'special','pdf: 0 g'},
+ }
+ elseif u and characters.i_is_of_category(u,'nd') then
+ v.width = digscale*v.width
+ v.commands = {
+ {'special','pdf: 0 0 1 rg'},
+ {'slot',3,k},
+ {'special','pdf: 0 g'},
+ }
+ else
+ v.commands = {
+ {'special','pdf: 0 1 0 rg'},
+ {'slot',1,k},
+ {'special','pdf: 0 g'},
+ }
+ end
+ end
+ end
+ return f
+end
+
+-- keep as example, now tfm feature
+
+--~ fonts.vf.aux.combine.commands["lineheight"] = function(g,v)
+--~ if g.ascender and g.descender then
+--~ local ht, dp = g.ascender or 0, g.descender or 0
+--~ if v[2] == "none" then
+--~ for _,v in pairs(g.characters) do
+--~ v.height = 0
+--~ v.depth = 0
+--~ end
+--~ else
+--~ if v[2] == "height" then
+--~ dp = 0
+--~ elseif v[2] == "depth" then
+--~ ht = 0
+--~ end
+--~ if ht > 0 then
+--~ if dp > 0 then
+--~ for _,v in pairs(g.characters) do
+--~ v.height = ht
+--~ v.depth = dp
+--~ end
+--~ else
+--~ for _,v in pairs(g.characters) do
+--~ v.height = ht
+--~ end
+--~ end
+--~ elseif dp > 0 then
+--~ for _,v in pairs(g.characters) do
+--~ v.depth = dp
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
diff --git a/tex/context/base/l-aux.lua b/tex/context/base/l-aux.lua
new file mode 100644
index 000000000..59ff42539
--- /dev/null
+++ b/tex/context/base/l-aux.lua
@@ -0,0 +1,39 @@
+-- filename : l-aux.lua
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-aux'] = 1.001
+if not aux then aux = { } end
+
+do
+
+ hash = { }
+
+ function set(key,value)
+ hash[key] = value
+ end
+
+ local space = lpeg.S(' ')^0
+ local equal = lpeg.S("=")^1
+ local comma = lpeg.S(",")^0
+ local nonspace = lpeg.P(1-lpeg.S(' '))^1
+ local nonequal = lpeg.P(1-lpeg.S('='))^1
+ local noncomma = lpeg.P(1-lpeg.S(','))^1
+ local nonbrace = lpeg.P(1-lpeg.S('{}'))^1
+ local nested = lpeg.S('{') * lpeg.C(nonbrace^1) * lpeg.S('}')
+
+ local key = lpeg.C(nonequal)
+ local value = nested + lpeg.C(noncomma)
+
+ local pattern = ((space * key * equal * value * comma) / set)^1
+
+ function aux.settings_to_hash(str)
+ hash = { }
+ lpeg.match(pattern,str)
+ return hash
+ end
+
+end
+
+--~ print(table.serialize(aux.settings_to_hash("aaa=bbb, ccc={d,d,d}")))
diff --git a/tex/context/base/l-boolean.lua b/tex/context/base/l-boolean.lua
new file mode 100644
index 000000000..128e1d069
--- /dev/null
+++ b/tex/context/base/l-boolean.lua
@@ -0,0 +1,41 @@
+-- filename : l-boolean.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-boolean'] = 1.001
+if not boolean then boolean = { } end
+
+function boolean.tonumber(b)
+ if b then return 1 else return 0 end
+end
+
+function toboolean(str)
+ if type(str) == "string" then
+ return str == "true" or str == "yes" or str == "on" or str == "1"
+ elseif type(str) == "number" then
+ return tonumber(str) ~= 0
+ else
+ return str
+ end
+end
+
+function string.is_boolean(str)
+ if type(str) == "string" then
+ if str == "true" or str == "yes" or str == "on" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
diff --git a/tex/context/base/l-dir.lua b/tex/context/base/l-dir.lua
new file mode 100644
index 000000000..df241d221
--- /dev/null
+++ b/tex/context/base/l-dir.lua
@@ -0,0 +1,94 @@
+-- filename : l-dir.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-dir'] = 1.001
+
+dir = { }
+
+-- optimizing for no string.find (*) does not save time
+
+if lfs then
+
+ function dir.glob_pattern(path,patt,recurse,action)
+ for name in lfs.dir(path) do
+ local full = path .. '/' .. name
+ local mode = lfs.attributes(full,'mode')
+ if mode == 'file' then
+ if name:find(patt) then
+ action(full)
+ end
+ elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then
+ dir.glob_pattern(full,patt,recurse,action)
+ end
+ end
+ end
+
+ function dir.glob(pattern, action)
+ local t = { }
+ local action = action or function(name) table.insert(t,name) end
+ local path, patt = pattern:match("^(.*)/*%*%*/*(.-)$")
+ local recurse = path and patt
+ if not recurse then
+ path, patt = pattern:match("^(.*)/(.-)$")
+ if not (path and patt) then
+ path, patt = '.', pattern
+ end
+ end
+ patt = patt:gsub("([%.%-%+])", "%%%1")
+ patt = patt:gsub("%*", ".*")
+ patt = patt:gsub("%?", ".")
+ patt = "^" .. patt .. "$"
+ -- print('path: ' .. path .. ' | pattern: ' .. patt .. ' | recurse: ' .. tostring(recurse))
+ dir.glob_pattern(path,patt,recurse,action)
+ return t
+ end
+
+ -- t = dir.glob("c:/data/develop/context/sources/**/????-*.tex")
+ -- t = dir.glob("c:/data/develop/tex/texmf/**/*.tex")
+ -- t = dir.glob("c:/data/develop/context/texmf/**/*.tex")
+ -- t = dir.glob("f:/minimal/tex/**/*")
+ -- print(dir.ls("f:/minimal/tex/**/*"))
+ -- print(dir.ls("*.tex"))
+
+ function dir.ls(pattern)
+ return table.concat(dir.glob(pattern),"\n")
+ end
+
+ --~ mkdirs("temp")
+ --~ mkdirs("a/b/c")
+ --~ mkdirs(".","/a/b/c")
+ --~ mkdirs("a","b","c")
+
+ function dir.mkdirs(...) -- root,... or ... ; root is not split
+ local pth, err = "", false
+ for k,v in pairs({...}) do
+ if k == 1 then
+ if not lfs.isdir(v) then
+ -- print("no root path " .. v)
+ err = true
+ else
+ pth = v
+ end
+ elseif lfs.isdir(pth .. "/" .. v) then
+ pth = pth .. "/" .. v
+ else
+ for _,s in pairs(v:split("/")) do
+ pth = pth .. "/" .. s
+ if not lfs.isdir(pth) then
+ ok = lfs.mkdir(pth)
+ if not lfs.isdir(pth) then
+ err = true
+ end
+ end
+ if err then break end
+ end
+ end
+ if err then break end
+ end
+ return pth, not err
+ end
+
+end
diff --git a/tex/context/base/l-file.lua b/tex/context/base/l-file.lua
new file mode 100644
index 000000000..ca983402b
--- /dev/null
+++ b/tex/context/base/l-file.lua
@@ -0,0 +1,101 @@
+-- filename : l-file.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-file'] = 1.001
+
+if not file then file = { } end
+
+function file.removesuffix(filename)
+ return filename:gsub("%.%a+$", "")
+end
+
+function file.addsuffix(filename, suffix)
+ if not filename:find("%.%a-$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return filename:gsub("%.%a+$", "." .. suffix)
+end
+
+function file.dirname(name)
+ return name:match("^(.+)[/\\].-$") or ""
+end
+
+function file.basename(name)
+ return name:match("^.+[/\\](.-)$") or name
+end
+
+function file.extname(name)
+ return name:match("^.+%.(.-)$") or ""
+end
+
+function file.join(...) -- args
+ return (string.gsub(table.concat({...},"/"),"\\","/"))
+end
+
+function file.is_writable(name)
+ local f = io.open(name, 'w')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.is_readable(name)
+ local f = io.open(name,'r')
+ if f then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+function file.split_path(str)
+ if str:find(';') then
+ return str:splitchr(";")
+ else
+ return str:splitchr(io.pathseparator)
+ end
+end
+
+function file.join_path(tab)
+ return table.concat(tab,io.pathseparator)
+end
+
+--~ print('test' .. " == " .. file.collapse_path("test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/test"))
+--~ print("test/test/test" .. " == " .. file.collapse_path("test/test/test"))
+--~ print("test/test" .. " == " .. file.collapse_path("test/../test/test"))
+--~ print("test" .. " == " .. file.collapse_path("test/../test"))
+--~ print("../test" .. " == " .. file.collapse_path("../test"))
+--~ print("../test/" .. " == " .. file.collapse_path("../test/"))
+--~ print("a/a" .. " == " .. file.collapse_path("a/b/c/../../a"))
+
+function file.collapse_path(str)
+ local ok = false
+ while not ok do
+ ok = true
+ str, n = str:gsub("[^%./]+/%.%./", function(s)
+ ok = false
+ return ""
+ end)
+ end
+ return (str:gsub("/%./","/"))
+end
+
+function file.robustname(str)
+ return (str:gsub("[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
diff --git a/tex/context/base/l-io.lua b/tex/context/base/l-io.lua
new file mode 100644
index 000000000..e53b8fb6a
--- /dev/null
+++ b/tex/context/base/l-io.lua
@@ -0,0 +1,173 @@
+-- filename : l-io.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-io'] = 1.001
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename)
+ local f = io.open(filename)
+ if f then
+ local data = f:read('*all')
+ f:close()
+ return data
+ else
+ return nil
+ end
+end
+
+function io.savedata(filename,data,joiner)
+ local f = io.open(filename, "wb")
+ if f then
+ if type(data) == "table" then
+ f:write(table.join(data,joiner or ""))
+ elseif type(data) == "function" then
+ data(f)
+ else
+ f:write(data)
+ end
+ f:close()
+ end
+end
+
+function io.exists(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return false
+ else
+ assert(f:close())
+ return true
+ end
+end
+
+function io.size(filename)
+ local f = io.open(filename)
+ if f == nil then
+ return 0
+ else
+ local s = f:seek("end")
+ assert(f:close())
+ return s
+ end
+end
+
+function io.noflines(f)
+ local n = 0
+ for _ in f:lines() do
+ n = n + 1
+ end
+ f:seek('set',0)
+ return n
+end
+
+--~ t, f, n = os.clock(), io.open("testbed/sample-utf16-bigendian-big.txt",'rb'), 0
+--~ for a in io.characters(f) do n = n + 1 end
+--~ print(string.format("characters: %s, time: %s", n, os.clock()-t))
+
+do
+
+ local nextchar = {
+ [ 4] = function(f)
+ return f:read(1), f:read(1), f:read(1), f:read(1)
+ end,
+ [ 2] = function(f)
+ return f:read(1), f:read(1)
+ end,
+ [ 1] = function(f)
+ return f:read(1)
+ end,
+ [-2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ return b, a
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local c = f:read(1)
+ return d, c, b, a
+ end
+ }
+
+ function io.characters(f,n)
+ local sb = string.byte
+ if f then
+ return nextchar[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
+
+do
+
+ local nextbyte = {
+ [4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(a), sb(b), sb(c), sb(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(a), sb(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return sb(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a = f:read(1)
+ local b = f:read(1)
+ if b then
+ return sb(b), sb(a)
+ else
+ return nil, nil
+ end
+ end,
+ [-4] = function(f)
+ local a = f:read(1)
+ local b = f:read(1)
+ local c = f:read(1)
+ local d = f:read(1)
+ if d then
+ return sb(d), sb(c), sb(b), sb(a)
+ else
+ return nil, nil, nil, nil
+ end
+ end
+ }
+
+ function io.bytes(f,n)
+ local sb = string.byte
+ if f then
+ return nextbyte[n or 1], f
+ else
+ return nil, nil
+ end
+ end
+
+end
diff --git a/tex/context/base/l-math.lua b/tex/context/base/l-math.lua
new file mode 100644
index 000000000..b2090143b
--- /dev/null
+++ b/tex/context/base/l-math.lua
@@ -0,0 +1,25 @@
+-- filename : l-math.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-math'] = 1.001
+
+if not math.round then
+ function math.round(x)
+ return math.floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ math.floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
diff --git a/tex/context/base/l-md5.lua b/tex/context/base/l-md5.lua
new file mode 100644
index 000000000..2a24f4169
--- /dev/null
+++ b/tex/context/base/l-md5.lua
@@ -0,0 +1,18 @@
+-- filename : l-md5.lua
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-md5'] = 1.001
+
+if md5 then do
+
+ local function convert(str,fmt)
+ return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
+ end
+
+ if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end
+ if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end
+ if not md5.dec then function md5.dec(str) return convert(stt,"%03i") end end
+
+end end
diff --git a/tex/context/base/l-number.lua b/tex/context/base/l-number.lua
new file mode 100644
index 000000000..e463cb973
--- /dev/null
+++ b/tex/context/base/l-number.lua
@@ -0,0 +1,10 @@
+-- filename : l-number.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-number'] = 1.001
+
+if not number then number = { } end
+
diff --git a/tex/context/base/l-os.lua b/tex/context/base/l-os.lua
new file mode 100644
index 000000000..0c1d92911
--- /dev/null
+++ b/tex/context/base/l-os.lua
@@ -0,0 +1,27 @@
+-- filename : l-os.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-os'] = 1.001
+
+function os.resultof(command)
+ return io.popen(command,"r"):read("*all")
+end
+
+--~ if not os.exec then -- still not ok
+ os.exec = os.execute
+--~ end
+
+function os.launch(str)
+ if os.platform == "windows" then
+ os.execute("start " .. str)
+ else
+ os.execute(str .. " &")
+ end
+end
+
+if not os.setenv then
+ function os.setenv() return false end
+end
diff --git a/tex/context/base/l-string.lua b/tex/context/base/l-string.lua
new file mode 100644
index 000000000..9b594ff8a
--- /dev/null
+++ b/tex/context/base/l-string.lua
@@ -0,0 +1,307 @@
+-- filename : l-string.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-string'] = 1.001
+
+--~ function string.split(str, pat) -- taken from the lua wiki
+--~ local t = {n = 0} -- so this table has a length field, traverse with ipairs then!
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ table.insert(t,cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end<=string.len(str) then
+--~ table.insert(t,(string.sub(str,last_end)))
+--~ end
+--~ return t
+--~ end
+
+--~ function string:split(pat) -- taken from the lua wiki but adapted
+--~ local t = { } -- self and colon usage (faster)
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = self:find(fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ t[#t+1] = cap
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = self:find(fpat, last_end)
+--~ end
+--~ if last_end <= #self then
+--~ t[#t+1] = self:sub(last_end)
+--~ end
+--~ return t
+--~ end
+
+--~ a piece of brilliant code by Rici Lake (posted on lua list) -- only names changed
+--~
+--~ function string:splitter(pat)
+--~ local st, g = 1, self:gmatch("()"..pat.."()")
+--~ local function splitter(self)
+--~ if st then
+--~ local s, f = g()
+--~ local rv = self:sub(st, (s or 0)-1)
+--~ st = f
+--~ return rv
+--~ end
+--~ end
+--~ return splitter, self
+--~ end
+
+function string:splitter(pat)
+ -- by Rici Lake (posted on lua list) -- only names changed
+ -- p 79 ref man: () returns position of match
+ local st, g = 1, self:gmatch("()("..pat..")")
+ local function strgetter(self, segs, seps, sep, cap1, ...)
+ st = sep and seps + #sep
+ return self:sub(segs, (seps or 0) - 1), cap1 or sep, ...
+ end
+ local function strsplitter(self)
+ if st then return strgetter(self, st, g()) end
+ end
+ return strsplitter, self
+end
+
+function string:split(separator)
+ local t = {}
+ for k in self:splitter(separator) do t[#t+1] = k end
+ return t
+end
+
+-- faster than a string:split:
+
+function string:splitchr(chr)
+ if #self > 0 then
+ local t = { }
+ for s in string.gmatch(self..chr,"(.-)"..chr) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+end
+
+--~ function string.piecewise(str, pat, fnc) -- variant of split
+--~ local fpat = "(.-)"..pat
+--~ local last_end = 1
+--~ local s, e, cap = string.find(str, fpat, 1)
+--~ while s ~= nil do
+--~ if s~=1 or cap~="" then
+--~ fnc(cap)
+--~ end
+--~ last_end = e+1
+--~ s, e, cap = string.find(str, fpat, last_end)
+--~ end
+--~ if last_end <= #str then
+--~ fnc((string.sub(str,last_end)))
+--~ end
+--~ end
+
+function string.piecewise(str, pat, fnc) -- variant of split
+ for k in string.splitter(str,pat) do fnc(k) end
+end
+
+--~ do if lpeg then
+
+--~ -- this alternative is 30% faster esp when we cache them
+--~ -- problem: no expressions
+
+--~ splitters = { }
+
+--~ function string:split(separator)
+--~ if #self > 0 then
+--~ local split = splitters[separator]
+--~ if not split then
+--~ -- based on code by Roberto
+--~ local p = lpeg.P(separator)
+--~ local c = lpeg.C((1-p)^0)
+--~ split = lpeg.Ct(c*(p*c)^0)
+--~ splitters[separator] = split
+--~ end
+--~ return lpeg.match(split,self)
+--~ else
+--~ return { }
+--~ end
+--~ end
+
+--~ string.splitchr = string.split
+
+--~ function string:piecewise(separator,fnc)
+--~ for _,v in pairs(self:split(separator)) do
+--~ fnc(v)
+--~ end
+--~ end
+
+--~ end end
+
+string.chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+function string:esc() -- variant 2
+ return (self:gsub("(.)",string.chr_to_esc))
+end
+
+function string.unquote(str)
+ return (str:gsub("^([\"\'])(.*)%1$","%2"))
+end
+
+function string.quote(str)
+ return '"' .. str:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in self:gmatch(pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return self:sub(1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+--~ function string.strip(str) -- slightly different
+--~ return (string.gsub(string.gsub(str,"^%s*(.-)%s*$","%1"),"%s+"," "))
+--~ end
+
+function string:is_empty()
+ return not self:find("%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = self:gsub(pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+--~ function string:enhance(pattern,action)
+--~ local ok, n = 0, 0
+--~ repeat
+--~ self, ok = self:gsub(pattern, function(...)
+--~ n = n + 1
+--~ return action(...)
+--~ end)
+--~ until ok == 0
+--~ return self, n
+--~ end
+
+--~ function string:to_hex()
+--~ if self then
+--~ return (self:gsub("(.)",function(c)
+--~ return string.format("%02X",c:byte())
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then
+--~ return (self:gsub("(..)",function(c)
+--~ return string.char(tonumber(c,16))
+--~ end))
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+string.chr_to_hex = { }
+string.hex_to_chr = { }
+
+for i=0,255 do
+ local c, h = string.char(i), string.format("%02X",i)
+ string.chr_to_hex[c], string.hex_to_chr[h] = h, c
+end
+
+--~ function string:to_hex()
+--~ if self then return (self:gsub("(.)",string.chr_to_hex)) else return "" end
+--~ end
+
+--~ function string:from_hex()
+--~ if self then return (self:gsub("(..)",string.hex_to_chr)) else return "" end
+--~ end
+
+function string:to_hex()
+ return ((self or ""):gsub("(.)",string.chr_to_hex))
+end
+
+function string:from_hex()
+ return ((self or ""):gsub("(..)",string.hex_to_chr))
+end
+
+if not string.characters then
+
+ local function nextchar(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, str:sub(index,index)
+ end
+ function string:characters()
+ return nextchar, self, 0
+ end
+ local function nextbyte(str, index)
+ index = index + 1
+ return (index <= #str) and index or nil, string.byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+--~ function string:padd(n,chr)
+--~ return self .. self.rep(chr or " ",n-#self)
+--~ end
+
+function string:padd(n,chr)
+ local m = n-#self
+ if m > 0 then
+ return self .. self.rep(chr or " ",m)
+ else
+ return self
+ end
+end
+
+function is_number(str)
+ return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+end
+
+--~ print(is_number("1"))
+--~ print(is_number("1.1"))
+--~ print(is_number(".1"))
+--~ print(is_number("-0.1"))
+--~ print(is_number("+0.1"))
+--~ print(is_number("-.1"))
+--~ print(is_number("+.1"))
diff --git a/tex/context/base/l-table.lua b/tex/context/base/l-table.lua
new file mode 100644
index 000000000..79d87139b
--- /dev/null
+++ b/tex/context/base/l-table.lua
@@ -0,0 +1,492 @@
+-- filename : l-table.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-table'] = 1.001
+
+table.join = table.concat
+
+function table.strip(tab)
+ local lst = { }
+ for k, v in ipairs(tab) do
+ -- s = string.gsub(v, "^%s*(.-)%s*$", "%1")
+ s = v:gsub("^%s*(.-)%s*$", "%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+--~ function table.sortedkeys(tab)
+--~ local srt = { }
+--~ for key,_ in pairs(tab) do
+--~ srt[#srt+1] = key
+--~ end
+--~ table.sort(srt)
+--~ return srt
+--~ end
+
+function table.sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in pairs(tab) do
+ srt[#srt+1] = key
+ if kind == 3 then
+ -- no further check
+ elseif type(key) == "string" then
+ if kind == 2 then kind = 3 else kind = 1 end
+ elseif type(key) == "number" then
+ if kind == 1 then kind = 3 else kind = 2 end
+ else
+ kind = 3
+ end
+ end
+ if kind == 0 or kind == 3 then
+ table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+ else
+ table.sort(srt)
+ end
+ return srt
+end
+
+function table.append(t, list)
+ for _,v in pairs(list) do
+ table.insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in pairs(list) do
+ table.insert(t,k,v)
+ end
+end
+
+if not table.fastcopy then
+
+ function table.fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in pairs(old) do
+ if type(v) == "table" then
+ new[k] = table.copy(v)
+ else
+ new[k] = v
+ end
+ end
+ return new
+ else
+ return { }
+ end
+ end
+
+end
+
+if not table.copy then
+
+ function table.copy(t, _lookup_table) -- taken from lua wiki
+ _lookup_table = _lookup_table or { }
+ local tcopy = {}
+ if not _lookup_table[t] then
+ _lookup_table[t] = tcopy
+ end
+ for i,v in pairs(t) do
+ if type(i) == "table" then
+ if _lookup_table[i] then
+ i = _lookup_table[i]
+ else
+ i = table.copy(i, _lookup_table)
+ end
+ end
+ if type(v) ~= "table" then
+ tcopy[i] = v
+ else
+ if _lookup_table[v] then
+ tcopy[i] = _lookup_table[v]
+ else
+ tcopy[i] = table.copy(v, _lookup_table)
+ end
+ end
+ end
+ return tcopy
+ end
+
+end
+
+-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
+
+function table.sub(t,i,j)
+ return { unpack(t,i,j) }
+end
+
+function table.replace(a,b)
+ for k,v in pairs(b) do
+ a[k] = v
+ end
+end
+
+-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
+
+function table.is_empty(t)
+ return not t or not next(t)
+end
+
+function table.one_entry(t)
+ local n = next(t)
+ return n and not next(t,n)
+end
+
+function table.starts_at(t)
+ return ipairs(t,1)(t,0)
+end
+
+do
+
+ -- 34.055.092 32.403.326 arabtype.tma
+ -- 1.620.614 1.513.863 lmroman10-italic.tma
+ -- 1.325.585 1.233.044 lmroman10-regular.tma
+ -- 1.248.157 1.158.903 lmsans10-regular.tma
+ -- 194.646 153.120 lmtypewriter10-regular.tma
+ -- 1.771.678 1.658.461 palatinosanscom-bold.tma
+ -- 1.695.251 1.584.491 palatinosanscom-regular.tma
+ -- 13.736.534 13.409.446 zapfinoextraltpro.tma
+
+ -- 13.679.038 11.774.106 arabtype.tmc
+ -- 886.248 754.944 lmroman10-italic.tmc
+ -- 729.828 466.864 lmroman10-regular.tmc
+ -- 688.482 441.962 lmsans10-regular.tmc
+ -- 128.685 95.853 lmtypewriter10-regular.tmc
+ -- 715.929 582.985 palatinosanscom-bold.tmc
+ -- 669.942 540.126 palatinosanscom-regular.tmc
+ -- 1.560.588 1.317.000 zapfinoextraltpro.tmc
+
+ table.serialize_functions = true
+ table.serialize_compact = true
+ table.serialize_inline = true
+
+ local function key(k)
+ if type(k) == "number" then -- or k:find("^%d+$") then
+ return "["..k.."]"
+ elseif noquotes and k:find("^%a[%a%d%_]*$") then
+ return k
+ else
+ return '["'..k..'"]'
+ end
+ end
+
+ local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in pairs(t) do
+ n = n + 1
+ end
+ if n == #t then
+ local tt = { }
+ for _,v in ipairs(t) do
+ local tv = type(v)
+ if tv == "number" or tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = ("%q"):format(v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+ end
+
+ local function serialize(root,name,handle,depth,level,reduce,noquotes,indexed)
+ handle = handle or print
+ reduce = reduce or false
+ if depth then
+ depth = depth .. " "
+ if indexed then
+ handle(("%s{"):format(depth))
+ else
+ handle(("%s%s={"):format(depth,key(name)))
+ end
+ else
+ depth = ""
+ if type(name) == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif type(name) == "number" then
+ handle("[" .. name .. "]={")
+ elseif type(name) == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ end
+ if root and next(root) then
+ local compact = table.serialize_compact
+ local inline = compact and table.serialize_inline
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ for k,v in ipairs(root) do
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ for _,k in pairs(table.sortedkeys(root)) do
+ local v = root[k]
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ handle(("%s %s,"):format(depth,v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s,"):format(depth,v))
+ else
+ handle(("%s %q,"):format(depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s {},"):format(depth))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s { %s },"):format(depth,table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s,"):format(depth,tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s loadstring(%q),'):format(depth,string.dump(v)))
+ else
+ handle(('%s "function",'):format(depth))
+ end
+ else
+ handle(("%s %q,"):format(depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(("%s __p__=nil,"):format(depth))
+ end
+ elseif t == "number" then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ elseif t == "string" then
+ if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(("%s %s=%s,"):format(depth,key(k),v))
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(("%s %s={},"):format(depth,key(k)))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ handle(("%s %s={ %s },"):format(depth,key(k),table.concat(st,", ")))
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ else
+ serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ end
+ elseif t == "boolean" then
+ handle(("%s %s=%s,"):format(depth,key(k),tostring(v)))
+ elseif t == "function" then
+ if table.serialize_functions then
+ handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(v)))
+ else
+ handle(('%s %s="function",'):format(depth,key(k)))
+ end
+ else
+ handle(("%s %s=%q,"):format(depth,key(k),tostring(v)))
+ -- handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(function() return v end)))
+ end
+ end
+ if level > 0 then
+ handle(("%s},"):format(depth))
+ else
+ handle(("%s}"):format(depth))
+ end
+ else
+ handle(("%s}"):format(depth))
+ end
+ end
+
+ --~ name:
+ --~
+ --~ true : return { }
+ --~ false : { }
+ --~ nil : t = { }
+ --~ string : string = { }
+ --~ 'return' : return { }
+ --~ number : [number] = { }
+
+ function table.serialize(root,name,reduce,noquotes)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ return table.concat(t,"\n")
+ end
+
+ function table.tohandle(handle,root,name,reduce,noquotes)
+ serialize(root, name, handle, nil, 0, reduce, noquotes)
+ end
+
+ -- sometimes tables are real use (zapfino extra pro is some 85M) in which
+ -- case a stepwise serialization is nice; actually, we could consider:
+ --
+ -- for line in table.serializer(root,name,reduce,noquotes) do
+ -- ...(line)
+ -- end
+ --
+ -- so this is on the todo list
+
+ table.tofile_maxtab = 2*1024
+
+ function table.tofile(filename,root,name,reduce,noquotes)
+ local f = io.open(filename,'w')
+ if f then
+ local concat = table.concat
+ local maxtab = table.tofile_maxtab
+ if maxtab > 1 then
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ if #t > maxtab then
+ f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
+ t = { }
+ end
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root, name, flush, nil, 0, reduce, noquotes)
+ end
+ f:close()
+ end
+ end
+
+end
+
+--~ t = {
+--~ b = "123",
+--~ a = "x",
+--~ c = 1.23,
+--~ d = "1.23",
+--~ e = true,
+--~ f = {
+--~ d = "1.23",
+--~ a = "x",
+--~ b = "123",
+--~ c = 1.23,
+--~ e = true,
+--~ f = {
+--~ e = true,
+--~ f = {
+--~ e = true
+--~ },
+--~ },
+--~ },
+--~ g = function() end
+--~ }
+
+--~ print(table.serialize(t), "\n")
+--~ print(table.serialize(t,"name"), "\n")
+--~ print(table.serialize(t,false), "\n")
+--~ print(table.serialize(t,true), "\n")
+--~ print(table.serialize(t,"name",true), "\n")
+--~ print(table.serialize(t,"name",true,true), "\n")
+
+do
+
+ local function flatten(t,f,complete)
+ for _,v in ipairs(t) do
+ if type(v) == "table" then
+ if complete or type(v[1]) == "table" then
+ flatten(v,f,complete)
+ else
+ f[#f+1] = v
+ end
+ else
+ f[#f+1] = v
+ end
+ end
+ end
+
+ function table.flatten(t)
+ local f = { }
+ flatten(t,f,true)
+ return f
+ end
+
+ function table.unnest(t) -- bad name
+ local f = { }
+ flatten(t,f,false)
+ return f
+ end
+
+ table.flatten_one_level = table.unnest
+
+end
+
+function table.insert_before_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i,str)
+ return
+ end
+ end
+ table.insert(t,1,str)
+end
+
+function table.insert_after_value(t,value,str)
+ for i=1,#t do
+ if t[i] == value then
+ table.insert(t,i+1,str)
+ return
+ end
+ end
+ t[#t+1] = str
+end
+
+function table.are_equal(a,b,n,m)
+ if #a == #b then
+ n = n or 1
+ m = m or #a
+ for i=n,m do
+ local ai, bi = a[i], b[i]
+ if (ai==bi) or (type(ai)=="table" and type(bi)=="table" and table.are_equal(ai,bi)) then
+ -- continue
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+--~ function table.are_equal(a,b)
+--~ return table.serialize(a) == table.serialize(b)
+--~ end
+
diff --git a/tex/context/base/l-tex.lua b/tex/context/base/l-tex.lua
new file mode 100644
index 000000000..3d87fe6e3
--- /dev/null
+++ b/tex/context/base/l-tex.lua
@@ -0,0 +1,110 @@
+-- filename : l-tex.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-tex'] = 1.001
+
+if not number then number = { } end
+
+number.dimenfactors = {
+ ["pt"] = 1/65536,
+ ["in"] = ( 100/ 7227)/65536,
+ ["cm"] = ( 254/ 7227)/65536,
+ ["mm"] = ( 254/72270)/65536,
+ ["sp"] = 1,
+ ["bp"] = ( 7200/ 7227)/65536,
+ ["pc"] = ( 1/ 12)/65536,
+ ["dd"] = ( 1157/ 1238)/65536,
+ ["cc"] = ( 1157/14856)/65536,
+ ["nd"] = (20320/21681)/65536,
+ ["nc"] = ( 5080/65043)/65536
+}
+
+function number.todimen(n,unit,fmt)
+ if type(n) == 'string' then
+ return n
+ else
+ return string.format(fmt or "%.5g%s",n*number.dimenfactors[unit or 'pt'],unit or 'pt')
+ end
+end
+
+function number.topoints (n) return number.todimen(n,"pt") end
+function number.toinches (n) return number.todimen(n,"in") end
+function number.tocentimeters (n) return number.todimen(n,"cm") end
+function number.tomillimeters (n) return number.todimen(n,"mm") end
+function number.toscaledpoints(n) return number.todimen(n,"sp") end
+function number.toscaledpoints(n) return n .. "sp" end
+function number.tobasepoints (n) return number.todimen(n,"bp") end
+function number.topicas (n) return number.todimen(n "pc") end
+function number.todidots (n) return number.todimen(n,"dd") end
+function number.tociceros (n) return number.todimen(n,"cc") end
+function number.tonewdidots (n) return number.todimen(n,"nd") end
+function number.tonewciceros (n) return number.todimen(n,"nc") end
+
+--~ for k,v in pairs{nil, "%.5f%s", "%.8g%s", "%.8f%s"} do
+--~ print(number.todimen(65536))
+--~ print(number.todimen( 256))
+--~ print(number.todimen(65536,'pt',v))
+--~ print(number.todimen( 256,'pt',v))
+--~ end
+
+-- todo: use different scratchdimen
+-- todo: use parser if no tex.dimen
+
+function string.todimen(str)
+ if type(str) == "number" then
+ return str
+ elseif str:find("^[%d%-%+%.]+$") then
+ return tonumber(str)
+--~ elseif tex then
+--~ tex.dimen[0] = str
+--~ return tex.dimen[0] or 0
+ else
+ local n, u = str:match("([%d%-%+%.]+)(%a%a)")
+ if n and u then
+ return n/number.dimenfactors[u]
+ else
+ return 0
+ end
+ end
+end
+
+--~ print(string.todimen("10000"))
+--~ print(string.todimen("10pt"))
+
+--~ See mk.pdf for an explanation of the following code:
+--~
+--~ function test(n)
+--~ lua.delay(function(...)
+--~ tex.sprint(string.format("pi: %s %s %s\\par",...))
+--~ end)
+--~ lua.delay(function(...)
+--~ tex.sprint(string.format("more pi: %s %s %s\\par",...))
+--~ end)
+--~ tex.sprint(string.format("\\setbox0=\\hbox{%s}",math.pi*n))
+--~ lua.flush(tex.wd[0],tex.ht[0],tex.dp[0])
+--~ end
+
+if lua then do
+
+ delayed = { } -- could also be done with closures
+
+ function lua.delay(f)
+ delayed[#delayed+1] = f
+ end
+
+ function lua.flush_delayed(...)
+ local t = delayed
+ delayed = { }
+ for _, fun in ipairs(t) do
+ fun(...)
+ end
+ end
+
+ function lua.flush(...)
+ tex.sprint("\\directlua 0 {lua.flush_delayed(" .. table.concat({...},',') .. ")}")
+ end
+
+end end
diff --git a/tex/context/base/l-unicode.lua b/tex/context/base/l-unicode.lua
new file mode 100644
index 000000000..d0c05bb86
--- /dev/null
+++ b/tex/context/base/l-unicode.lua
@@ -0,0 +1,148 @@
+-- filename : l-unicode.lua
+-- comment : split off from luat-inp
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-unicode'] = 1.001
+if not unicode then unicode = { } end
+
+if not garbagecollector then
+ garbagecollector = {
+ push = function() collectgarbage("stop") end,
+ pop = function() collectgarbage("restart") end,
+ }
+end
+
+-- 0 EF BB BF UTF-8
+-- 1 FF FE UTF-16-little-endian
+-- 2 FE FF UTF-16-big-endian
+-- 3 FF FE 00 00 UTF-32-little-endian
+-- 4 00 00 FE FF UTF-32-big-endian
+
+unicode.utfname = {
+ [0] = 'utf-8',
+ [1] = 'utf-16-le',
+ [2] = 'utf-16-be',
+ [3] = 'utf-32-le',
+ [4] = 'utf-32-be'
+}
+
+function unicode.utftype(f) -- \000 fails !
+ local str = f:read(4)
+ if not str then
+ f:seek('set')
+ return 0
+ elseif str:find("^%z%z\254\255") then
+ return 4
+ elseif str:find("^\255\254%z%z") then
+ return 3
+ elseif str:find("^\254\255") then
+ f:seek('set',2)
+ return 2
+ elseif str:find("^\255\254") then
+ f:seek('set',2)
+ return 1
+ elseif str:find("^\239\187\191") then
+ f:seek('set',3)
+ return 0
+ else
+ f:seek('set')
+ return 0
+ end
+end
+
+function unicode.utf16_to_utf8(str, endian)
+ garbagecollector.push()
+ local result = { }
+ local tc, uc = table.concat, unicode.utf8.char
+ local tmp, n, m, p = { }, 0, 0, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = uc(n)
+ p = 0
+ end
+ end
+ for l,r in str:bytepairs() do
+ if endian then
+ n = l*256 + r
+ else
+ n = r*256 + l
+ end
+ if m > 0 then
+ n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
+ m = 0
+ doit()
+ elseif n >= 0xD800 and n <= 0xDBFF then
+ m = n
+ else
+ doit()
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = tc(tmp,"")
+ end
+ garbagecollector.pop()
+ return result
+end
+
+function unicode.utf32_to_utf8(str, endian)
+ garbagecollector.push()
+ local result = { }
+ local tc, uc = table.concat, unicode.utf8.char
+ local tmp, n, m, p = { }, 0, -1, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = tc(tmp,"")
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = uc(n)
+ p = 0
+ end
+ end
+ for a,b in str:bytepairs() do
+ if a and b then
+ if m < 0 then
+ if endian then
+ m = a*256*256*256 + b*256*256
+ else
+ m = b*256 + a
+ end
+ else
+ if endian then
+ n = m + a*256 + b
+ else
+ n = m + b*256*256*256 + a*256*256
+ end
+ m = -1
+ doit()
+ end
+ else
+ break
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = tc(tmp,"")
+ end
+ garbagecollector.pop()
+ return result
+end
diff --git a/tex/context/base/l-utils.lua b/tex/context/base/l-utils.lua
new file mode 100644
index 000000000..96273106a
--- /dev/null
+++ b/tex/context/base/l-utils.lua
@@ -0,0 +1,121 @@
+-- filename : l-utils.lua
+-- comment : split off from luat-lib
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['l-utils'] = 1.001
+
+if not utils then utils = { } end
+if not utils.merger then utils.merger = { } end
+if not utils.lua then utils.lua = { } end
+
+utils.merger.m_begin = "begin library merge"
+utils.merger.m_end = "end library merge"
+utils.merger.pattern =
+ "%c+" ..
+ "%-%-%s+" .. utils.merger.m_begin ..
+ "%c+(.-)%c+" ..
+ "%-%-%s+" .. utils.merger.m_end ..
+ "%c+"
+
+function utils.merger._self_fake_()
+ return
+ "-- " .. "created merged file" .. "\n\n" ..
+ "-- " .. utils.merger.m_begin .. "\n\n" ..
+ "-- " .. utils.merger.m_end .. "\n\n"
+end
+
+function utils.report(...)
+ print(...)
+end
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ data = f:read("*all")
+ f:close()
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ f:write(data)
+ f:close()
+ end
+ end
+end
+
+function utils.merger._self_swap_(data,code)
+ if data ~= "" then
+ return (data:gsub(utils.merger.pattern, function(s)
+ return "\n\n" .. "-- "..utils.merger.m_begin .. "\n" .. code .. "\n" .. "-- "..utils.merger.m_end .. "\n\n"
+ end, 1))
+ else
+ return ""
+ end
+end
+
+function utils.merger._self_libs_(libs,list)
+ local result, f = "", nil
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ local name = string.gsub(pth .. "/" .. lib,"\\","/")
+ f = io.open(name)
+ if f then
+ -- utils.report("merging library",name)
+ result = result .. "\n" .. f:read("*all") .. "\n"
+ f:close()
+ list = { pth } -- speed up the search
+ break
+ else
+ -- utils.report("no library",name)
+ end
+ end
+ end
+ return result or ""
+end
+
+function utils.merger.selfcreate(libs,list,target)
+ if target then
+ utils.merger._self_save_(
+ target,
+ utils.merger._self_swap_(
+ utils.merger._self_fake_(),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+ end
+end
+
+function utils.merger.selfmerge(name,libs,list,target)
+ utils.merger._self_save_(
+ target or name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ utils.merger._self_libs_(libs,list)
+ )
+ )
+end
+
+function utils.merger.selfclean(name)
+ utils.merger._self_save_(
+ name,
+ utils.merger._self_swap_(
+ utils.merger._self_load_(name),
+ ""
+ )
+ )
+end
+
+function utils.lua.compile(luafile, lucfile)
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ return (os.execute("luac -s -o " .. string.quote(lucfile) .. " " .. string.quote(luafile)) == 0)
+end
+
diff --git a/tex/context/base/lang-ini.lua b/tex/context/base/lang-ini.lua
new file mode 100644
index 000000000..5259a085e
--- /dev/null
+++ b/tex/context/base/lang-ini.lua
@@ -0,0 +1,327 @@
+if not modules then modules = { } end modules ['lang-ini'] = {
+ version = 1.001,
+ comment = "companion to lang-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+languages = languages or { }
+languages.patterns = languages.patterns or { }
+languages.version = 1.009
+languages.template = "words-%s.txt"
+languages.number = nil
+languages.current = nil
+languages.attribute = nil
+
+-- We used to have one list: data[word] = pattern but that overflowed lua's function
+-- mechanism. Then we split the lists and again we had oveflows. So eventually we
+-- ended up with a dedicated reader.
+--
+-- function languages.set(attribute,number,name)
+-- if not languages.patterns[number] then
+-- languages.patterns[number] = containers.define("languages","patterns", languages.version, true)
+-- end
+-- input.start_timing(languages)
+-- local data = containers.read(languages.patterns[number],name)
+-- if not data then
+-- data = { }
+-- local fullname = string.format(languages.template,name)
+-- local foundname = input.find_file(texmf.instance,fullname,'other text file')
+-- if foundname and foundname ~= "" then
+-- local ok, blob, size = input.loadbinfile(texmf.instance,foundname)
+-- for word in utf.gfind(blob,"(.-)[%s]+") do
+-- local key = word:gsub("-","")
+-- if key == word then
+-- -- skip
+-- else
+-- data[word:gsub("-","")] = word
+-- end
+-- end
+-- end
+-- data = containers.write(languages.patterns[number],name,data)
+-- end
+-- input.stop_timing(languages)
+-- languages.attribute = attribute
+-- languages.number = number
+-- languages.current = data
+-- end
+
+function languages.set(attribute,number,name)
+ if not languages.patterns[number] then
+ input.start_timing(languages)
+ local fullname = string.format(languages.template,name)
+ local foundname = input.find_file(texmf.instance,fullname,'other text file')
+ if foundname and foundname ~= "" then
+ -- texio.write_nl(string.format("loading patterns for language %s as %s from %s",name,number,foundname))
+ languages.patterns[number] = tex.load_dict(foundname) or { }
+ else
+ languages.patterns[number] = { }
+ end
+ input.stop_timing(languages)
+ end
+ languages.attribute = attribute
+ languages.number = number
+ languages.current = languages.patterns[number]
+end
+
+function languages.add(word,pattern)
+ if languages.current and word and pattern then
+ languages.current[word] = pattern
+ end
+end
+
+function languages.remove(word)
+ if languages.current and word then
+ languages.current[word] = nil
+ end
+end
+
+function languages.hyphenate(str)
+ if languages.current then
+ local result = languages.current[str]
+ if result then
+ return result
+ else
+ -- todo: be clever
+ end
+ end
+ return str
+end
+
+function languages.found(number, str)
+ local patterns = languages.patterns[number]
+ return patterns and patterns[str]
+end
+
+do
+
+ local discnode = node.new('disc')
+
+ discnode.pre = node.new('glyph')
+ discnode.pre.subtype = 0
+ discnode.pre.char = 45 -- will be configurable
+ discnode.pre.font = 0
+
+ local glyph, disc, kern = node.id('glyph'), node.id('disc'), node.id('kern')
+
+ local bynode = node.traverse
+ local bychar = string.utfcharacters
+
+ function reconstruct(prev,str,fnt)
+ local done = false
+ if #str < 4 then
+ -- too short
+ else
+ local wrd = languages.hyphenate(str)
+ if wrd == str then
+ -- not found
+ else
+ local pre, post, after, comp = nil, nil, false, nil
+ for chr in bychar(wrd) do
+ if prev then
+ if not comp and prev.next and prev.next.subtype > 0 then
+ comp = prev.next.components
+ pre = node.copy(comp)
+ comp = comp.next
+ post, after = nil, false
+ elseif chr == '-' then
+ if not comp then
+ done = true
+ local n = node.copy(discnode)
+ n.pre.font = fnt.font
+ n.pre.attr = fnt.attr
+ if pre then
+ pre.next = n.pre
+ n.pre = pre
+ pre, pos, after = nil, nil, false
+ end
+ n.next = prev.next
+ prev.next = n
+ prev = n
+ else
+ after = true
+ end
+ elseif comp then
+ local g = node.copy(comp)
+ comp = comp.next
+ if after then
+ if post then post.next = g else post = g end
+ else
+ if pre then pre.next = g else pre = g end
+ end
+ if not comp then
+ done = true
+ local n = node.copy(discnode)
+ n.pre.font = fnt.font
+ n.pre.attr = fnt.attr
+ pre.next = n.pre
+ n.pre = pre
+ n.post = post
+ n.replace = 1
+ n.next = prev.next
+ prev.next = n
+ prev = n
+ pre, pos, after = nil, nil, false
+ prev = prev.next -- hm, now we get error 1
+ end
+ else
+ prev = prev.next
+ end
+ else
+ -- print("ERROR 1")
+ end
+ end
+ end
+ end
+ return done
+ end
+
+ function nodes.hyphenate_words(head) -- we forget about the very first, no head stuff here
+ local cd = characters.data
+ local uc = utf.char
+ local n, p = head, nil
+ local done, prev, str, fnt, lan = false, false, "", nil, nil
+ local currentlanguage = languages.current
+ local att = languages.attribute
+ local function action() -- maybe inline
+ if reconstruct(prev,str,fnt) then
+ done = true
+ end
+ str, prev = "", false
+ end
+ while n do
+ local id = n.id
+ if id == glyph then
+ local l = node.has_attribute(n,att)
+ if l then
+ if l ~= lan then
+ if prev then action() end
+ lan = l
+ languages.current = languages.patterns[lan]
+ end
+ elseif prev then
+ action()
+ end
+ if not languages.current then
+ -- skip
+ elseif n.subtype > 0 then
+ if not prev then
+ prev, fnt = p, n
+ end
+ for g in bynode(n.components) do
+ str = str .. uc(g.char)
+ end
+ else
+ local code = n.char
+ if cd[code].lccode then
+ if not prev then
+ prev, fnt = p, n
+ end
+ str = str .. uc(code)
+ elseif prev then
+ action()
+ end
+ end
+ elseif id == kern and n.subtype == 0 and p then
+ p.next = n.next
+ node.free(p,n)
+ n = p
+ elseif prev then
+ action()
+ end
+ p = n
+ n = n.next
+ end
+ if prev then
+ action()
+ end
+ languages.current = currentlanguage
+ return head
+ end
+
+ function nodes.mark_words(head,attribute,found)
+ local cd = characters.data
+ local uc = utf.char
+ local current, start, str, att, n = head, nil, "", nil, 0
+ local function action()
+ local f = found(att,str)
+ if f then
+ for i=1,n do
+ f(start)
+ start = start.next
+ end
+ end
+ str, start, n = "", nil, 0
+ end
+ while current do
+ local id = current.id
+ if id == glyph then
+ local a = node.has_attribute(current,attribute)
+ if a then
+ if a ~= att then
+ if start then
+ action()
+ end
+ att = a
+ end
+ elseif start then
+ action()
+ att = a
+ end
+ if current.subtype > 0 then
+ start = start or current
+ n = n + 1
+ for g in bynode(current.components) do
+ str = str .. uc(g.char)
+ end
+ else
+ local code = current.char
+ if cd[code].lccode then
+ start = start or current
+ n = n + 1
+ str = str .. uc(code)
+ else
+ if start then
+ action()
+ end
+ end
+ end
+ elseif id == disc then
+ -- ok
+ elseif id == kern and current.subtype == 0 and start then
+ -- ok
+ elseif start then
+ action()
+ end
+ current = current.next
+ end
+ if start then
+ action()
+ end
+ return head
+ end
+
+ function languages.check(head, attribute, yes, nop)
+ local set = node.set_attribute
+ local unset = node.unset_attribute
+ local wrong, right = false, false
+ if nop then wrong = function(n) set(n,attribute,nop) end end
+ if yes then right = function(n) set(n,attribute,yes) end end
+ for n in node.traverse(head) do
+ unset(n,attribute)
+ end
+ nodes.mark_words(head, languages.attribute, function(att,str)
+ if #str < 4 then
+ return false
+ elseif languages.found(att,str) then
+ return right
+ else
+ return wrong
+ end
+ end)
+ nodes.hyphenate_words(head)
+ return head
+ end
+
+end
diff --git a/tex/context/base/lang-ini.mkiv b/tex/context/base/lang-ini.mkiv
new file mode 100644
index 000000000..e62166966
--- /dev/null
+++ b/tex/context/base/lang-ini.mkiv
@@ -0,0 +1,23 @@
+%D \module
+%D [ file=lang-ini,
+%D version=1996.01.25,
+%D title=\CONTEXT\ Language Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\registerctxluafile{lang-ini}{1.001}
+
+\def\synchronizepatternswithfont{}
+\def\doloadpatterns #1#2{\dodoloadpatterns{#1}{#2}\s!default\s!default}
+\def\setnormallanguage #1{\dosetnormallanguage{:\s!default:\s!default:}#1\empty}
+\def\setuphyppatencoding {\pathypsettings}
+
+\protect \endinput
diff --git a/tex/context/base/lang-ini.tex b/tex/context/base/lang-ini.tex
index b5cfda0db..6f0352772 100644
--- a/tex/context/base/lang-ini.tex
+++ b/tex/context/base/lang-ini.tex
@@ -353,12 +353,12 @@
\def\setuphyppatencoding {\pathypsettings}
\endXETEX
-\beginMETATEX
+\beginLUATEX
\def\synchronizepatternswithfont{}
\def\doloadpatterns #1#2{\dodoloadpatterns{#1}{#2}\s!default\s!default}
\def\setnormallanguage #1{\dosetnormallanguage{:\s!default:\s!default:}#1\empty}
\def\setuphyppatencoding {\pathypsettings}
-\endMETATEX
+\endLUATEX
\def\dodoloadpatterns#1#2#3#4% beware, loaded language also incr
{\normallanguage\loadedlanguage % when not really needed
diff --git a/tex/context/base/lang-sla.mkiv b/tex/context/base/lang-sla.mkiv
new file mode 100644
index 000000000..dfa56bab5
--- /dev/null
+++ b/tex/context/base/lang-sla.mkiv
@@ -0,0 +1,17 @@
+%D \module
+%D [ file=lang-sla,
+%D version=2006.09.16,
+%D title=\CONTEXT\ Language Macros,
+%D subtitle=Slavic Languages,
+%D author=Hans Hagen / Tobias Burnus,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\def\sloveniancharacters#1{\ctxlua{convert.alphabetic(\number#1,"sl")}}
+\def\slovenianCharacters#1{\ctxlua{convert.Alphabetic(\number#1,"sl")}}
+
+\endinput
diff --git a/tex/context/base/luat-cbk.lua b/tex/context/base/luat-cbk.lua
new file mode 100644
index 000000000..a6145ec3b
--- /dev/null
+++ b/tex/context/base/luat-cbk.lua
@@ -0,0 +1,150 @@
+if not modules then modules = { } end modules ['luat-cbk'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Callbacks are the real asset of . They permit you to hook
+your own code into the engine. Here we implement a few handy
+auxiliary functions.
When you (temporarily) want to install a callback function, and after a
+while wants to revert to the original one, you can use the following two
+functions.
+--ldx]]--
+
+function callbacks.push(name, func)
+ if not callbacks.stack[name] then
+ callbacks.stack[name] = { }
+ end
+ table.insert(callbacks.stack[name],callback.find(name))
+ callback.register(name, func)
+end
+
+function callbacks.pop(name)
+-- this fails: callback.register(name, table.remove(callbacks.stack[name]))
+ local func = table.remove(callbacks.stack[name])
+ callback.register(name, func)
+end
+
+--[[ldx--
+
Callbacks may result in doing some hard work
+which takes time and above all resourses. Sometimes it makes
+sense to disable or tune the garbage collector in order to
+keep the use of resources acceptable.
+
+
At some point in the development we did some tests with counting
+nodes (in thsi case 121049).
+
+
+
setstepmul
seconds
megabytes
+
200
24.0
80.5
+
175
21.0
78.2
+
150
22.0
74.6
+
160
22.0
74.6
+
165
21.0
77.6
+
125
21.5
89.2
+
100
21.5
88.4
+
+
+
The following code is kind of experimental. In the documents
+that describe the development of we report
+on speed tests. One observation is thta it sometimes helps to
+restart the collector.
+--ldx]]--
+
+garbagecollector = { }
+
+do
+ local level = 0
+
+ collectgarbage("setstepmul", 165)
+
+ garbagecollector.trace = false
+ garbagecollector.tune = false -- for the moment
+
+ function report(format)
+ if garbagecollector.trace then
+ -- texio.write_nl(string.format(format,level,status.luastate_bytes))
+ texio.write_nl(string.format(format,level,collectgarbage("count")))
+ end
+ end
+
+ function garbagecollector.update()
+ report("%s: memory before update: %s")
+ collectgarbage("restart")
+ end
+
+ function garbagecollector.push()
+ if garbagecollector.tune then
+ level = level + 1
+ if level == 1 then
+ collectgarbage("stop")
+ end
+ report("%s: memory after push: %s")
+ else
+ garbagecollector.update()
+ end
+ end
+
+ function garbagecollector.pop()
+ if garbagecollector.tune then
+ report("%s: memory before pop: %s")
+ if level == 1 then
+ collectgarbage("restart")
+ end
+ level = level - 1
+ end
+ end
+
+ function garbagecollector.cycle()
+ if garbagecollector.tune then
+ report("%s: memory before collect: %s")
+ collectgarbage("collect")
+ report("%s: memory after collect: %s")
+ end
+ end
+
+end
+
diff --git a/tex/context/base/luat-crl.lua b/tex/context/base/luat-crl.lua
new file mode 100644
index 000000000..9d15b4896
--- /dev/null
+++ b/tex/context/base/luat-crl.lua
@@ -0,0 +1,53 @@
+-- filename : luat-crl.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-crl'] = 1.001
+if not curl then curl = { } end
+
+curl.cachepath = cache.setpath(texmf.instance,"curl")
+
+curl.cached = { }
+
+function curl.fetch(protocol, name)
+ local cachename = curl.cachepath .. "/" .. file.robustname(name)
+ cachename = cachename:gsub("[\\/]", io.fileseparator)
+ if not curl.cached[name] then
+ if not io.exists(cachename) then
+ curl.cached[name] = cachename
+ local command = "curl --silent --create-dirs --output " .. cachename .. " " .. protocol .. "://" .. name
+ os.execute(command)
+ end
+ if io.exists(cachename) then
+ curl.cached[name] = cachename
+ else
+ curl.cached[name] = ""
+ end
+ end
+ return curl.cached[name]
+end
+
+function input.finders.curl(instance,protocol,filename)
+ local foundname = curl.fetch(protocol, filename)
+ return input.finders.generic(instance,protocol,foundname,filetype)
+end
+function input.openers.curl(instance,protocol,filename)
+ return input.openers.generic(instance,protocol,filename)
+end
+function input.loaders.curl(instance,protocol,filename)
+ return input.loaders.generic(instance,protocol,filename)
+end
+
+-- todo: metamethod
+
+function curl.install(protocol)
+ input.finders[protocol] = function (instance,filename,filetype) return input.finders.curl(instance,protocol,filename) end
+ input.openers[protocol] = function (instance,filename) return input.openers.curl(instance,protocol,filename) end
+ input.loaders[protocol] = function (instance,filename) return input.loaders.curl(instance,protocol,filename) end
+end
+
+curl.install('http')
+curl.install('https')
+curl.install('ftp')
diff --git a/tex/context/base/luat-deb.lua b/tex/context/base/luat-deb.lua
new file mode 100644
index 000000000..fc671d6be
--- /dev/null
+++ b/tex/context/base/luat-deb.lua
@@ -0,0 +1,154 @@
+-- filename : luat-deb.lua
+-- comment : companion to luat-deb.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-deb'] = 1.001
+if not lmx then lmx = { } end
+if not lmx.variables then lmx.variables = { } end
+
+lmx.variables['color-background-green'] = '#4F6F6F'
+lmx.variables['color-background-blue'] = '#6F6F8F'
+lmx.variables['color-background-yellow'] = '#8F8F6F'
+lmx.variables['color-background-purple'] = '#8F6F8F'
+
+lmx.variables['color-background-body'] = '#808080'
+lmx.variables['color-background-main'] = '#3F3F3F'
+lmx.variables['color-background-one'] = lmx.variables['color-background-green']
+lmx.variables['color-background-two'] = lmx.variables['color-background-blue']
+
+lmx.variables['title-default'] = 'ConTeXt Status Information'
+lmx.variables['title'] = lmx.variables['title-default']
+
+if not trace then trace = { } end
+if not trace.list then trace.list = { } end
+if not trace.strings then trace.strings = { } end
+
+trace.strings.undefined = "undefined"
+
+function trace.split(csname)
+ return csname:match("^(.+):(.+)$")
+end
+
+function trace.type(csname)
+ tag, name = trace.split(csname)
+ if tag then return tag else return nil end
+end
+
+function trace.name(csname)
+ tag, name = trace.split(csname)
+ if tag then return name else return csname end
+end
+
+function trace.cs(csname)
+ tag, name = trace.split(csname)
+ if trace.types[tag] then
+ return trace.types[tag](name)
+ else
+ return trace.primitive(csname)
+ end
+end
+
+function trace.dimen(name)
+ return (tex.dimen[name] and number.topoints(tex.dimen[name])) or trace.strings.undefined
+end
+
+function trace.count(name)
+ return tex.count[name] or trace.strings.undefined
+end
+
+function trace.toks(name)
+ return (tex.toks[name] and string.limit(tex.toks[name],40)) or trace.strings.undefined
+end
+
+function trace.primitive(name)
+ return tex[name] or trace.strings.undefined
+end
+
+trace.types = {
+ ['d'] = trace.dimen,
+ ['c'] = trace.count,
+ ['t'] = trace.toks,
+ ['p'] = trace.primitive
+}
+
+function trace.knownlist(name)
+ return trace.list[name] and #trace.list[name] > 0
+end
+
+function trace.showdebuginfo()
+ lmx.set('title', 'ConTeXt Debug Information')
+ lmx.set('color-background-one', lmx.get('color-background-green'))
+ lmx.set('color-background-two', lmx.get('color-background-blue'))
+ lmx.show('context-debug.lmx')
+ lmx.restore()
+end
+
+function trace.showerror()
+ lmx.set('title', 'ConTeXt Error Information')
+ lmx.set('errormessage', status.lasterrorstring)
+ lmx.set('linenumber', status.linenumber)
+ lmx.set('color-background-one', lmx.get('color-background-yellow'))
+ lmx.set('color-background-two', lmx.get('color-background-purple'))
+ local filename = status.filename
+ local linenumber = tonumber(status.linenumber or "0")
+ if not filename then
+ lmx.set('filename', 'unknown')
+ lmx.set('errorcontext', 'error in filename')
+ elseif type(filename) == "number" then
+ lmx.set('filename', "")
+ lmx.set('errorcontext', 'unknown error')
+ elseif io.exists(filename) then
+ -- todo: use an input opener so that we also catch utf16 an reencoding
+ lmx.set('filename', filename)
+ lines = io.lines(filename)
+ if lines then
+ local context = { }
+ n, m = 1, linenumber
+ b, e = m-10, m+10
+ s = string.len(tostring(e))
+ for line in lines do
+ if n > e then
+ break
+ elseif n > b then
+ if n == m then
+ context[#context+1] = string.format("%" .. s .. "d",n) .. " >> " .. line
+ else
+ context[#context+1] = string.format("%" .. s .. "d",n) .. " " .. line
+ end
+ end
+ n = n + 1
+ end
+ lmx.set('errorcontext', table.concat(context,"\n"))
+ else
+ lmx.set('errorcontext', "")
+ end
+ else
+ lmx.set('filename', filename)
+ lmx.set('errorcontext', 'file not found')
+ end
+ lmx.show('context-error.lmx')
+ lmx.restore()
+end
+
+function trace.overloaderror()
+--~ callback.register('show_error_hook', function(identifier, filename, linenumber)
+--~ trace.showerror(identifier, filename, linenumber)
+--~ end )
+ callback.register('show_error_hook', trace.showerror)
+end
+
+trace.list['scratch'] = {
+ 0, 2, 4, 6, 8
+}
+
+trace.list['internals'] = {
+ 'p:hsize', 'p:parindent', 'p:leftskip','p:rightskip',
+ 'p:vsize', 'p:parskip', 'p:baselineskip', 'p:lineskip', 'p:topskip'
+}
+
+trace.list['context'] = {
+ 'd:lineheight',
+ 'c:realpageno', 'c:pageno', 'c:subpageno'
+}
diff --git a/tex/context/base/luat-deb.tex b/tex/context/base/luat-deb.tex
new file mode 100644
index 000000000..d76cb212e
--- /dev/null
+++ b/tex/context/base/luat-deb.tex
@@ -0,0 +1,49 @@
+%D \module
+%D [ file=luat-deb,
+%D version=2005.11.06,
+%D title=\CONTEXT\ Communication Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Communication Support (initialization)}
+
+\registerctxluafile{luat-deb}{1.001}
+
+\startruntimeluacode
+ \ctxlua {
+ lmx.htmfile = function(name) return environment.jobname .. "-status.html" end
+ lmx.lmxfile = function(name) return environment.texfile(name) end
+ }
+\stopruntimeluacode
+
+\def\showdebuginfo{\ctxlua{trace.showdebuginfo()}}
+\def\overloaderror{\ctxlua{trace.overloaderror()}}
+
+\def\breakpoint{\showdebuginfo\wait}
+
+\registerctxluafile{luat-tra}{1.001}
+
+\appendtoks
+ \ctxlua {
+ if debugger.tracing() then
+ debugger.enable() ;
+ end
+ }%
+\to \everyjob
+
+\appendtoks
+ \ctxlua {
+ if debugger.tracing() then
+ debugger.disable() ;
+ debugger.savestats("\jobname-luacalls.log") ;
+ end
+ }%
+\to \everybye
+
+\endinput
diff --git a/tex/context/base/luat-env.lua b/tex/context/base/luat-env.lua
new file mode 100644
index 000000000..a3bf6cd11
--- /dev/null
+++ b/tex/context/base/luat-env.lua
@@ -0,0 +1,172 @@
+-- filename : luat-env.lua
+-- comment : companion to luat-env.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- here we don't assume any extra libraries
+
+if not versions then versions = { } end versions['luat-env'] = 1.001
+
+-- environment
+
+if not environment then environment = { } end
+
+environment.useluc = false -- for testing
+--~ environment.silent = true -- for testing
+
+if environment.silent == nil then environment.silent = false end
+if environment.useluc == nil then environment.useluc = true end
+
+-- kpse is overloaded by this time
+
+if environment.formatname == nil then environment.formatname = tex.formatname end
+if environment.formatpath == nil then environment.formatpath = kpse.find_file(tex.formatname,"fmt") or "." end
+if environment.jobname == nil then environment.jobname = tex.jobname end
+if environment.progname == nil then environment.progname = os.getenv("progname") or "luatex" end
+if environment.engine == nil then environment.engine = os.getenv("engine") or "context" end
+if environment.enginepath == nil then environment.enginepath = os.getenv("SELFAUTOLOC") or "." end
+if environment.initex == nil then environment.initex = tex.formatname == "" end
+
+environment.formatpath = string.gsub(environment.formatpath:gsub("\\","/"),"/([^/]-)$","")
+environment.enginepath = string.gsub(environment.enginepath:gsub("\\","/"),"/([^/]-)$","")
+
+if environment.formatname == "" then environment.formatpath = "cont-en" end
+if environment.formatpath == "" then environment.formatpath = '.' end
+if environment.enginepath == "" then environment.enginepath = '.' end
+if environment.version == nil then environment.version = "unknown" end
+
+function environment.get(name)
+ return os.getenv(name) or ""
+end
+
+function environment.cleanname(filename)
+ if filename and filename ~= "" then
+ return filename:gsub( "\\", "/")
+ else -- leave nil and empty untouched
+ return filename
+ end
+end
+
+function environment.texfile(filename)
+ return environment.cleanname(input.find_file(texmf.instance,filename,'tex'))
+end
+
+function environment.ctxfile(filename)
+ return environment.cleanname(input.find_file(texmf.instance,filename,'tex'))
+end
+
+function environment.luafile(filename)
+ return environment.cleanname(input.find_file(texmf.instance,filename,'tex') or input.find_file(texmf.instance,filename,'texmfscripts'))
+end
+
+function environment.showmessage(...) -- todo, cleaner
+ if not environment.silent then
+ if input and input.report then
+ input.report(table.concat({...}," "))
+ elseif texio and texio.write_nl then
+ texio.write_nl("[[" .. table.concat({...}," ") .. "]]")
+ else
+ print("[[" .. table.concat({...}," ") .. "]]")
+ end
+ end
+end
+
+if not environment.jobname then environment.jobname = "unknown" end
+
+function environment.setlucpath()
+ if environment.initex then
+ environment.lucpath = nil
+ else
+ environment.lucpath = environment.formatpath .. "/lua/" .. environment.progname
+ end
+end
+
+function environment.luafilechunk(filename)
+ local filename = filename:gsub("%.%a+$", "") .. ".lua"
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ environment.showmessage("loading file", fullname)
+ return loadfile(fullname)
+ else
+ environment.showmessage("unknown file", filename)
+ return nil
+ end
+end
+
+-- the next ones can use the previous ones
+
+function environment.loadluafile(filename,register)
+ filename = filename:gsub("%.%a+$", "") .. ".lua"
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ environment.showmessage("loading", fullname)
+ if register then
+ if not environment.regfil then
+ environment.regfil = io.open('luafiles.tmp', 'w')
+ end
+ if environment.regfil then
+ environment.regfil:write(fullname .."\n")
+ end
+ end
+ dofile(fullname)
+ else
+ environment.showmessage("unknown file", filename)
+ end
+end
+
+function environment.loadlucfile(filename,version)
+ local filename = filename:gsub("%.%a+$", "")
+ local fullname = nil
+ if environment.initex or not environment.useluc then
+ environment.loadluafile(filename,environment.initex)
+ else
+ if environment.lucpath and environment.lucpath ~= "" then
+ fullname = environment.lucpath .. "/" .. filename .. ".luc"
+ local chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if chunk then
+ environment.showmessage("loading", fullname)
+ assert(chunk)()
+ if version then
+--~ if modules and modules[filename] and modules[filename].version ~= version then
+--~ environment.showmessage("version mismatch", filename,"lua=" .. modules[filename].version, "luc=" ..version)
+--~ environment.loadluafile(filename)
+--~ elseif versions and versions[filename] and versions[filename] ~= version then
+--~ environment.showmessage("version mismatch", filename,"lua=" .. versions[filename], "luc=" ..version)
+--~ environment.loadluafile(filename)
+--~ end
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old
+ end
+ if v ~= version then
+ environment.showmessage("version mismatch", filename,"lua=" .. v, "luc=" ..version)
+ environment.loadluafile(filename)
+ end
+
+ end
+ else
+ environment.loadluafile(filename)
+ end
+ else
+ environment.loadluafile(filename)
+ end
+ end
+end
+
+function environment.loadedctxfile(filename)
+ local fullname = environment.ctxfile(filename)
+ local i = io.open(fullname)
+ if i then
+ local data = i:read('*all')
+ i:close()
+ return data
+ else
+ environment.showmessage("missing",filename)
+ return ""
+ end
+end
+
+environment.setlucpath()
diff --git a/tex/context/base/luat-env.tex b/tex/context/base/luat-env.tex
new file mode 100644
index 000000000..ec2b942eb
--- /dev/null
+++ b/tex/context/base/luat-env.tex
@@ -0,0 +1,169 @@
+%D \module
+%D [ file=luat-env,
+%D version=2005.05.26,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=ConTeXt features,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+% \writestatus{loading}{Lua Support Macros (environment)}
+
+% print (lua.id)
+% print (lua.version)
+% print (lua.startupfile)
+
+%D Allocation of \LUA\ engines.
+
+\newcount\luadefcounter
+
+\ifx\zerocount\undefined \chardef\zerocount=0 \fi
+\ifx\plusone \undefined \chardef\plusone =1 \fi
+
+\def\newlua#1%
+ {\global\advance\luadefcounter\plusone
+ \mathchardef#1\luadefcounter}
+
+%D We use a dedicated instance for \CONTEXT\ core functionality. In
+%D \CONTEXT we also use this as callback instance. Instance 0 is
+%D the scratch instance.
+
+\ifx\luastartup\undefined \newcount\luastartup \fi
+
+\chardef\CTXlua\zerocount \luadefcounter\CTXlua \luastartup\CTXlua
+
+\def\ctxlua {\directlua\CTXlua}
+\def\directctxlua{\directlua\CTXlua}
+\def\latectxlua {\latelua \CTXlua}
+
+%D The simple \type {\lua} command is just a shortcut to the
+%D zero instance. Beware, we don't use the 0--9 range for
+%D scratch purposes as we do with other registers. First of all
+%D we want to avoid the overhead, but mostly, users can just define
+%D their own.
+
+\newlua \luadefault
+
+\def \lua {\directlua\luadefault} % zero is the main one, and reserved for ctx
+\edef\luaversion{\CTXlua{tex.print(_VERSION)}}
+
+%D We want to define \LUA\ related things in the format but
+%D need to reluad code because \LUA\ instances are not dumped
+%D into the format.
+
+\ifx\undefined\normaleveryjob \let\normaleveryjob\everyjob \newtoks\everyjob \fi
+
+\newtoks\everyloadluacode
+\newtoks\everyfinalizeluacode
+
+\normaleveryjob{\the\everyloadluacode\the\everyfinalizeluacode\the\everyjob}
+
+\newif\ifproductionrun
+
+\long\def\startruntimeluacode#1\stopruntimeluacode % only simple code (load +init)
+ {\ifproductionrun
+ \global\let\startruntimeluacode\relax
+ \global\let\stopruntimeluacode \relax
+ \else
+ \global\everyloadluacode\expandafter{\the\everyloadluacode#1}%
+ \fi
+ #1} % maybe no interference
+
+\long\def\startruntimectxluacode#1\stopruntimectxluacode
+ {\startruntimeluacode\ctxlua{#1}\stopruntimeluacode}
+
+%D Next we load the initialization code.
+
+\startruntimectxluacode
+ if not environment then environment = { } end
+ environment.jobname = "\jobname" % tex.jobname
+ environment.formatname = "\contextformat" % tex.formatname
+ environment.initex = \ifproductionrun false \else true \fi % tex.formatname == ""
+ environment.version = "\contextversion"
+ dofile(input.find_file(texmf.instance,"luat-env.lua","tex"))
+\stopruntimectxluacode
+
+% 0 = external compilation and loading
+% 1 = runtime compilation and embedding
+
+\chardef\ctxluaembeddingmode \plusone
+\chardef\ctxluaexecutionmode \zerocount % private
+
+% we start at 500, below this, we store predefined data (dumps)
+
+\newcount\luabytecodecounter \luabytecodecounter=500
+
+\startruntimectxluacode
+ if not lua.bytedata then lua.bytedata = { } end
+\stopruntimectxluacode
+
+%D Handy when we expand:
+
+\let\stopruntimeluacode \relax
+\let\stopruntimectxluacode\relax
+
+\long\def\lastexpanded{} % todo: elsewhere we use \@@expanded
+
+\long\def\expanded#1{\long\xdef\lastexpanded{\noexpand#1}\lastexpanded}
+
+%D More code:
+
+\def\ctxluabytecode#1% executes an already loaded chunk
+ {\ctxlua {
+ do
+ local str = ''
+ if lua.bytedata[#1] then
+ str = " from file " .. lua.bytedata[#1][1] .. " version " .. lua.bytedata[#1][2]
+ end
+ if lua.bytecode[#1] then
+ if environment.initex then
+ environment.showmessage("executing byte code " .. "#1" .. str)
+ assert(lua.bytecode[#1])()
+ else
+ assert(lua.bytecode[#1])()
+ lua.bytecode[#1] = nil
+ end
+ else
+ environment.showmessage("invalid byte code " .. "#1" .. str)
+ end
+ end
+ }}
+
+\def\ctxluabyteload#1#2% registers and compiles chunk
+ {\global\advance\luabytecodecounter \plusone
+ \expanded{\startruntimectxluacode
+ lua.bytedata[\the\luabytecodecounter] = { "#1", "#2" }
+ \stopruntimectxluacode}%
+ \ctxlua {
+ lua.bytedata[\the\luabytecodecounter] = { "#1", "#2" }
+ lua.bytecode[\the\luabytecodecounter] = environment.luafilechunk("#1")}}
+
+\def\ctxluafileload#1#2% load a (either not compiled) chunk at runtime
+ {\doifelsenothing{#2}
+ {\ctxlua{environment.loadlucfile("#1")}}
+ {\ctxlua{environment.loadlucfile("#1",#2)}}}
+
+\def\registerctxluafile#1#2% name version
+ {\ifcase\ctxluaembeddingmode
+ \ifproductionrun \else
+ \global\everyloadluacode\expandafter{\the\everyloadluacode\ctxluafileload{#1}{#2}}%
+ \fi
+ \ifcase\ctxluaexecutionmode\or\ctxluafileload{#1}{#2}\fi
+ \else
+ \ifproductionrun \else
+ \ctxluabyteload{#1}{#2}%
+ \fi
+ \global\everyloadluacode\expandafter\expandafter\expandafter{\expandafter\the\expandafter\everyloadluacode
+ \expandafter\ctxluabytecode\expandafter{\the\luabytecodecounter}}%
+ \ifcase\ctxluaexecutionmode\or\ctxluabytecode{\the\luabytecodecounter}\fi
+ \fi}
+
+\ifcase\ctxluaembeddingmode \else \registerctxluafile{luat-env}{1.001} \fi
+
+\chardef\ctxluaexecutionmode \plusone
+
+\endinput
diff --git a/tex/context/base/luat-exe.lua b/tex/context/base/luat-exe.lua
new file mode 100644
index 000000000..dcb28d35f
--- /dev/null
+++ b/tex/context/base/luat-exe.lua
@@ -0,0 +1,68 @@
+-- filename : luat-exe.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-exe'] = 1.001
+if not executer then executer = { } end
+
+executer.permitted = { }
+executer.execute = os.execute
+
+function executer.register(...)
+ for k,v in pairs({...}) do
+ if v == "*" then
+ table.insert(executer.permitted, ".*")
+ else
+ table.insert(executer.permitted, v)
+ end
+ end
+end
+
+function executer.finalize() -- todo: os.exec
+ do
+ local execute = os.execute
+ function executer.execute(...)
+ local t, name, arguments = {...}, "", ""
+ if #t == 1 then
+ if type(t[1]) == 'table' then
+ name, arguments = t[1], table.concat(t," ",2,#t)
+ else
+ name, arguments = string.match(t[1],"^(.-)%s+(.+)$")
+ if not (name and arguments) then
+ name, arguments = t[1], ""
+ end
+ end
+ else
+ name, arguments = t[1], table.concat(t," ",2,#t)
+ end
+ for _,v in pairs(executer.permitted) do
+ if string.find(name,v) then
+ execute(name .. " " .. arguments)
+ -- print("executed: " .. name .. " " .. arguments)
+ else
+ print("not permitted: " .. name .. " " .. arguments)
+ end
+ end
+ end
+ function executer.finalize()
+ print("executer is already finalized")
+ end
+ function executer.register(name)
+ print("executer is already finalized")
+ end
+ os.execute = executer.execute
+ end
+end
+
+--~ executer.register('.*')
+--~ executer.register('*')
+--~ executer.register('dir','ls')
+--~ executer.register('dir')
+
+--~ executer.finalize()
+--~ executer.execute('dir',"*.tex")
+--~ executer.execute("dir *.tex")
+--~ executer.execute("ls *.tex")
+--~ os.execute('ls')
diff --git a/tex/context/base/luat-ini.lua b/tex/context/base/luat-ini.lua
new file mode 100644
index 000000000..022055e7f
--- /dev/null
+++ b/tex/context/base/luat-ini.lua
@@ -0,0 +1,11 @@
+if not modules then modules = { } end modules ['luat-ini'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
We cannot load anything yet.
+--ldx]]--
diff --git a/tex/context/base/luat-ini.tex b/tex/context/base/luat-ini.tex
new file mode 100644
index 000000000..800950baf
--- /dev/null
+++ b/tex/context/base/luat-ini.tex
@@ -0,0 +1,104 @@
+%D \module
+%D [ file=luat-ini,
+%D version=2005.08.11,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Lua Support Macros (initialization)}
+
+\unprotect
+
+%D We have to load this module in a very early stage. Therefore we
+%D cannot rely on support macros being available.
+
+% \long\def\rescan#1{\expanded{\scantextokens{#1}}}
+
+%D Loading lua code can be done using \type {startup.lua}. The following
+%D method uses the \TEX\ input file locator of kpse. At least we need to
+%D use that way of loading when we haven't yet define our own code, which
+%D we keep outside the format. We will keep code outside \TEX\ files as
+%D much as possible.
+
+\ifx\setnaturalcatcodes\undefined \let\setnaturalcatcodes\relax \fi
+\ifx\obeylualines \undefined \let\obeylualines \relax \fi
+\ifx\obeyluatokens \undefined \let\obeyluatokens \relax \fi
+
+% \def\loadluacode#1#2% instance filename
+% {\bgroup
+% \everyeof{\noexpand}% hack to make \input nicely expandable
+% \setnaturalcatcodes
+% \obeylualines
+% %message{[Lua Load: #2]}%
+% \directlua#1\expandafter{\normalinput#2\space}\relax
+% \egroup}
+
+%D A few more goodies:
+
+\def\dostartlua#1%
+ {\begingroup
+ \obeylualines
+ \directlua#1\iftrue{\else}\fi}
+
+\def\dostoplua
+ {\iffalse{\else}\fi
+ \endgroup}
+
+\def\dostartluacode#1%
+ {\begingroup
+ \obeylualines
+ \obeyluatokens
+ \directlua#1\iftrue{\else}\fi}
+
+\def\dostopluacode % no unexpanded, else no } seen
+ {\iffalse{\else}\fi
+ \endgroup}
+
+\def\startlua {\dostartlua \zerocount} \def\stoplua {\dostoplua}
+\def\startluacode{\dostartluacode\zerocount} \def\stopluacode {\dostopluacode}
+
+%D Some delayed definitions:
+
+\ifx\obeylines \undefined \let\obeylines \relax \fi
+\ifx\obeyedline \undefined \let\obeyedline \relax \fi
+\ifx\obeyspaces \undefined \let\obeyspaces \relax \fi
+\ifx\obeyedspace \undefined \let\obeyedspace \relax \fi
+\ifx\outputnewlinechar\undefined \let\outputnewlinechar\relax \fi
+
+\def\obeylualines
+ {\obeylines \let\obeyedline \outputnewlinechar
+ \obeyspaces \let\obeyedspace\space}
+
+\def\obeyluatokens % todo: make this a proper catcode table
+ {\catcode`\%=11 \catcode`\#=11
+ \catcode`\_=11 \catcode`\^=11
+ \catcode`\&=11 \catcode`\|=11
+ \catcode`\{=11 \catcode`\}=11
+ \def\\{\string\\}\def\|{\string\|}\def\-{\string\-}%
+ \def\({\string\(}\def\){\string\)}\def\{{\string\{}\def\}{\string\}}%
+ \def\'{\string\'}\def\"{\string\"}%
+ \def\n{\string\n}\def\r{\string\r}\def\f{\string\f}\def\t{\string\t}%
+ \def\a{\string\a}\def\b{\string\b}\def\v{\string\v}%
+ \def\1{\string1}\def\2{\string2}\def\3{\string3}\def\4{\string\4}\def\5{\string\5}%
+ \def\6{\string6}\def\7{\string7}\def\8{\string8}\def\9{\string\9}\def\0{\string\0}}
+
+%D We provide an interface for defining instances:
+
+\def\s!lua{lua} \def\v!code{code} \let\@EA\expandafter
+
+\def\definelua[#1]%
+ {\ifcsname#1\s!lua\endcsname\else\expandafter\newlua\csname#1\s!lua\endcsname\fi
+ \setevalue{\e!start#1\s!lua }{\noexpand\dostartlua \csname#1\s!lua\endcsname}%
+ \setevalue{\e!start#1\s!lua\v!code}{\noexpand\dostartluacode\csname#1\s!lua\endcsname}%
+ \setvalue {\e!stop #1\s!lua }{\dostoplua }%
+ \setvalue {\e!stop #1\s!lua\v!code}{\dostopluacode}}
+
+\definelua[CTX]
+
+\protect \endinput
diff --git a/tex/context/base/luat-inp.lua b/tex/context/base/luat-inp.lua
new file mode 100644
index 000000000..798105718
--- /dev/null
+++ b/tex/context/base/luat-inp.lua
@@ -0,0 +1,1865 @@
+-- filename : luat-inp.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- This lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split this
+-- module in components when we're done with prototyping.
+
+-- This is the first code I wrote for LuaTeX, so it needs some cleanup.
+
+-- To be considered: hash key lowercase, first entry in table filename
+-- (any case), rest paths (so no need for optimization). Or maybe a
+-- separate table that matches lowercase names to mixed case when
+-- present. In that case the lower() cases can go away. I will do that
+-- only when we run into problems with names.
+
+if not versions then versions = { } end versions['luat-inp'] = 1.001
+if not environment then environment = { } end
+if not file then file = { } end
+
+if environment.aleph_mode == nil then environment.aleph_mode = true end -- temp hack
+
+if not input then input = { } end
+if not input.suffixes then input.suffixes = { } end
+if not input.formats then input.formats = { } end
+if not input.aux then input.aux = { } end
+
+if not input.suffixmap then input.suffixmap = { } end
+
+if not input.locators then input.locators = { } end -- locate databases
+if not input.hashers then input.hashers = { } end -- load databases
+if not input.generators then input.generators = { } end -- generate databases
+if not input.filters then input.filters = { } end -- conversion filters
+
+input.locators.notfound = { nil }
+input.hashers.notfound = { nil }
+input.generators.notfound = { nil }
+
+input.cacheversion = '1.0.1'
+input.banner = nil
+input.verbose = false
+input.debug = false
+input.cnfname = 'texmf.cnf'
+input.lsrname = 'ls-R'
+input.luasuffix = '.tma'
+input.lucsuffix = '.tmc'
+
+-- we use a cleaned up list / format=any is a wildcard, as is *name
+
+input.formats['afm'] = 'AFMFONTS' input.suffixes['afm'] = { 'afm' }
+input.formats['enc'] = 'ENCFONTS' input.suffixes['enc'] = { 'enc' }
+input.formats['fmt'] = 'TEXFORMATS' input.suffixes['fmt'] = { 'fmt' }
+input.formats['map'] = 'TEXFONTMAPS' input.suffixes['map'] = { 'map' }
+input.formats['mp'] = 'MPINPUTS' input.suffixes['mp'] = { 'mp' }
+input.formats['ocp'] = 'OCPINPUTS' input.suffixes['ocp'] = { 'ocp' }
+input.formats['ofm'] = 'OFMFONTS' input.suffixes['ofm'] = { 'ofm', 'tfm' }
+input.formats['otf'] = 'OPENTYPEFONTS' input.suffixes['otf'] = { 'otf' } -- 'ttf'
+input.formats['opl'] = 'OPLFONTS' input.suffixes['opl'] = { 'opl' }
+input.formats['otp'] = 'OTPINPUTS' input.suffixes['otp'] = { 'otp' }
+input.formats['ovf'] = 'OVFFONTS' input.suffixes['ovf'] = { 'ovf', 'vf' }
+input.formats['ovp'] = 'OVPFONTS' input.suffixes['ovp'] = { 'ovp' }
+input.formats['tex'] = 'TEXINPUTS' input.suffixes['tex'] = { 'tex' }
+input.formats['tfm'] = 'TFMFONTS' input.suffixes['tfm'] = { 'tfm' }
+input.formats['ttf'] = 'TTFONTS' input.suffixes['ttf'] = { 'ttf', 'ttc' }
+input.formats['pfb'] = 'T1FONTS' input.suffixes['pfb'] = { 'pfb', 'pfa' }
+input.formats['vf'] = 'VFFONTS' input.suffixes['vf'] = { 'vf' }
+
+input.formats['fea'] = 'FONTFEATURES' input.suffixes['fea'] = { 'fea' }
+
+input.formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+input.suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+input.formats ['lua'] = 'LUAINPUTS' -- new
+input.suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- here we catch a few new thingies
+
+function input.checkconfigdata(instance)
+ if input.env(instance,"LUAINPUTS") == "" then
+ instance.environment["LUAINPUTS"] = ".;$TEXINPUTS;$TEXMFSCRIPTS"
+ end
+ if input.env(instance,"FONTFEATURES") == "" then
+ instance.environment["FONTFEATURES"] = ".;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS"
+ end
+end
+
+-- backward compatible ones
+
+input.alternatives = { }
+
+input.alternatives['map files'] = 'map'
+input.alternatives['enc files'] = 'enc'
+input.alternatives['opentype fonts'] = 'otf'
+input.alternatives['truetype fonts'] = 'ttf'
+input.alternatives['truetype collections'] = 'ttc'
+input.alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+input.formats ['misc fonts'] = ''
+input.suffixes['misc fonts'] = { }
+
+input.formats ['sfd'] = 'SFDFONTS'
+input.suffixes ['sfd'] = { 'sfd' }
+input.alternatives['subfont definition files'] = 'sfd'
+
+function input.reset()
+
+ local instance = { }
+
+ instance.rootpath = ''
+ instance.treepath = ''
+ instance.progname = environment.progname or 'context'
+ instance.engine = environment.engine or 'luatex'
+ instance.format = ''
+ instance.environment = { }
+ instance.variables = { }
+ instance.expansions = { }
+ instance.files = { }
+ instance.configuration = { }
+ instance.found = { }
+ instance.foundintrees = { }
+ instance.kpsevars = { }
+ instance.hashes = { }
+ instance.cnffiles = { }
+ instance.lists = { }
+ instance.remember = true
+ instance.diskcache = true
+ instance.renewcache = false
+ instance.scandisk = true
+ instance.cachepath = nil
+ instance.loaderror = false
+ instance.smallcache = false
+ instance.savelists = true
+ instance.cleanuppaths = true
+ instance.allresults = false
+ instance.pattern = nil -- lists
+ instance.kpseonly = false -- lists
+ instance.cachefile = 'tmftools'
+ instance.loadtime = 0
+ instance.starttime = 0
+ instance.stoptime = 0
+ instance.validfile = function(path,name) return true end
+ instance.data = { } -- only for loading
+ instance.sortdata = false
+ instance.force_suffixes = true
+ instance.dummy_path_expr = "^!*unset/*$"
+ instance.fakepaths = { }
+ instance.lsrmode = false
+
+ if os.env then
+ -- store once, freeze and faster
+ for k,v in pairs(os.env) do
+ instance.environment[k] = input.bare_variable(v)
+ end
+ else
+ -- we will access os.env frequently
+ for k,v in pairs({'HOME','TEXMF','TEXMFCNF','SELFAUTOPARENT'}) do
+ local e = os.getenv(v)
+ if e then
+ -- input.report("setting",v,"to",input.bare_variable(e))
+ instance.environment[v] = input.bare_variable(e)
+ end
+ end
+ end
+
+ -- cross referencing
+
+ for k, v in pairs(input.suffixes) do
+ for _, vv in pairs(v) do
+ if vv then
+ input.suffixmap[vv] = k
+ end
+ end
+ end
+
+ return instance
+
+end
+
+function input.bare_variable(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"%s+$",""),'^"(.+)"$',"%1"),"^'(.+)'$","%1")
+ return str:gsub("\s*([\"\']?)(.+)%1\s*", "%2")
+end
+
+if texio then
+ input.log = texio.write_nl
+else
+ input.log = print
+end
+
+function input.simple_logger(kind, name)
+ if name and name ~= "" then
+ if input.banner then
+ input.log(input.banner..kind..": "..name)
+ else
+ input.log("<<"..kind..": "..name..">>")
+ end
+ else
+ if input.banner then
+ input.log(input.banner..kind..": no name")
+ else
+ input.log("<<"..kind..": no name>>")
+ end
+ end
+end
+
+function input.dummy_logger()
+end
+
+function input.settrace(n)
+ input.trace = tonumber(n or 0)
+ if input.trace > 0 then
+ input.logger = input.simple_logger
+ input.verbose = true
+ else
+ input.logger = function() end
+ end
+end
+
+function input.report(...) -- inefficient
+ if input.verbose then
+ if input.banner then
+ input.log(input.banner .. table.concat({...},' '))
+ elseif input.logmode() == 'xml' then
+ input.log(""..table.concat({...},' ').."")
+ else
+ input.log("<<"..table.concat({...},' ')..">>")
+ end
+ end
+end
+
+function input.reportlines(str)
+ if type(str) == "string" then
+ str = str:split("\n")
+ end
+ for _,v in pairs(str) do input.report(v) end
+end
+
+input.settrace(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0)
+
+-- These functions can be used to test the performance, especially
+-- loading the database files.
+
+function input.start_timing(instance)
+ if instance then
+ instance.starttime = os.clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+end
+
+function input.stop_timing(instance, report)
+ if instance and instance.starttime then
+ instance.stoptime = os.clock()
+ local loadtime = instance.stoptime - instance.starttime
+ instance.loadtime = instance.loadtime + loadtime
+ if report then
+ input.report('load time', string.format("%0.3f",loadtime))
+ end
+ return loadtime
+ else
+ return 0
+ end
+end
+
+input.stoptiming = input.stop_timing
+input.starttiming = input.start_timing
+
+function input.elapsedtime(instance)
+ return string.format("%0.3f",instance.loadtime or 0)
+end
+
+function input.report_loadtime(instance)
+ if instance then
+ input.report('total load time', input.elapsedtime(instance))
+ end
+end
+
+function input.loadtime(instance)
+ tex.print(input.elapsedtime(instance))
+end
+
+function input.env(instance,key)
+ return instance.environment[key] or input.osenv(instance,key)
+end
+
+function input.osenv(instance,key)
+ if instance.environment[key] == nil then
+ local e = os.getenv(key)
+ if e == nil then
+ instance.environment[key] = "" -- false
+ else
+ instance.environment[key] = input.bare_variable(e)
+ end
+ end
+ return instance.environment[key] or ""
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in TEXMF/web2c
+--
+-- for the moment we don't expect a configuration file in a zip
+
+function input.identify_cnf(instance)
+ if #instance.cnffiles == 0 then
+ if instance.treepath ~= "" then
+ if instance.rootpath ~= "" then
+ local t = instance.treepath:splitchr(',')
+ for k,v in ipairs(t) do
+ t[k] = file.join(instance.rootpath,v)
+ end
+ instance.treepath = table.concat(t,',')
+ end
+ local t = instance.treepath:splitchr(',')
+ instance.environment['TEXMF'] = input.bare_variable(instance.treepath)
+ instance.environment['TEXMFCNF'] = file.join(t[1] or '.','texmf/web2c')
+ end
+ if instance.rootpath ~= "" then
+ instance.environment['TEXMFCNF'] = file.join(instance.rootpath,'texmf/web2c')
+ instance.environment['SELFAUTOPARENT'] = instance.rootpath
+ end
+ if input.env(instance,'TEXMFCNF') ~= "" then
+ local t = input.split_path(input.env(instance,'TEXMFCNF'))
+ t = input.aux.expanded_path(instance,t)
+ input.aux.expand_vars(instance,t)
+ for _,v in ipairs(t) do
+ table.insert(instance.cnffiles,file.join(v,input.cnfname))
+ end
+ elseif input.env(instance,'SELFAUTOPARENT') == '.' then
+ table.insert(instance.cnffiles,file.join('.',input.cnfname))
+ else
+ for _,v in ipairs({'texmf-local','texmf'}) do
+ table.insert(instance.cnffiles,file.join(input.env(instance,'SELFAUTOPARENT'),v,'web2c',input.cnfname))
+ end
+ end
+ end
+end
+
+function input.load_cnf(instance)
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = fname:gsub("\\",'/')
+ end
+ for i = 1, 3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ if instance.lsrmode then
+ input.loadconfigdata(instance,instance.cnffiles)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadconfig(instance,instance.cnffiles)
+ if instance.loaderror then
+ input.loadconfigdata(instance,instance.cnffiles)
+ input.saveconfig(instance)
+ end
+ else
+ input.loadconfigdata(instance,instance.cnffiles)
+ if instance.renewcache then
+ input.saveconfig(instance)
+ end
+ end
+ input.aux.collapse_cnf_data(instance)
+ end
+ input.checkconfigdata(instance)
+end
+
+function input.loadconfigdata(instance)
+ for _, fname in pairs(instance.cnffiles) do
+ input.aux.load_cnf(instance,fname)
+ end
+end
+
+if os.env then
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ if instance.environment[k] then
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.kpsevars[k] = true
+ instance.variables[k] = input.bare_variable(v)
+ end
+ end
+ end
+ end
+ end
+else
+ function input.aux.collapse_cnf_data(instance)
+ for _,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ local e = os.getenv(k)
+ if e then
+ instance.environment[k] = input.bare_variable(e)
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.variables[k] = input.bare_variable(v)
+ instance.kpsevars[k] = true
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.aux.load_cnf(instance,fname)
+ fname = input.clean_path(fname)
+ local lname = fname:gsub("%.%a+$",input.luasuffix)
+ local f = io.open(lname)
+ if f then
+ f:close()
+ input.aux.load_data(instance,file.dirname(lname),'configuration',file.basename(lname))
+ else
+ f = io.open(fname)
+ if f then
+ input.report("loading", fname)
+ local line, data, n, k, v
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ instance.configuration[dname] = { }
+ end
+ local data = instance.configuration[dname]
+ while true do
+ line = f:read()
+ if line then
+ while true do -- join lines
+ line, n = line:gsub("\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not line:find("^[%%#]") then
+ k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ else
+ input.report("skipping", fname)
+ end
+ end
+end
+
+-- database loading
+
+function input.load_hash(instance)
+ input.locatelists(instance)
+ if instance.lsrmode then
+ input.loadlists(instance)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadfiles(instance)
+ if instance.loaderror then
+ input.loadlists(instance)
+ input.savefiles(instance)
+ end
+ else
+ input.loadlists(instance)
+ if instance.renewcache then
+ input.savefiles(instance)
+ end
+ end
+end
+
+function input.aux.append_hash(instance,type,tag,name)
+ input.logger("= hash append",tag)
+ table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.prepend_hash(instance,type,tag,name)
+ input.logger("= hash prepend",tag)
+ table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.extend_texmf_var(instance,specification) -- crap
+ if instance.environment['TEXMF'] then
+ input.report("extending environment variable TEXMF with", specification)
+ instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ elseif instance.variables['TEXMF'] then
+ input.report("extending configuration variable TEXMF with", specification)
+ instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ else
+ input.report("setting configuration variable TEXMF to", specification)
+ instance.variables['TEXMF'] = "{" .. specification .. "}"
+ end
+ if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
+ input.report("adding {} to complex TEXMF variable, best do that yourself")
+ instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
+ end
+ input.expand_variables(instance)
+end
+
+-- locators
+
+function input.locatelists(instance)
+ for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
+ input.report("locating list of",path)
+ input.locatedatabase(instance,input.normalize_name(path))
+ end
+end
+
+function input.locatedatabase(instance,specification)
+ return input.methodhandler('locators', instance, specification)
+end
+
+function input.locators.tex(instance,specification)
+ if specification and specification ~= '' then
+ local files = {
+ file.join(specification,'files'..input.lucsuffix),
+ file.join(specification,'files'..input.luasuffix),
+ file.join(specification,input.lsrname)
+ }
+ for _, filename in pairs(files) do
+ local f = io.open(filename)
+ if f then
+ input.logger('! tex locator', specification..' found')
+ input.aux.append_hash(instance,'file',specification,filename)
+ f:close()
+ return
+ end
+ end
+ input.logger('? tex locator', specification..' not found')
+ end
+end
+
+-- hashers
+
+function input.hashdatabase(instance,tag,name)
+ return input.methodhandler('hashers',instance,tag,name)
+end
+
+function input.loadfiles(instance)
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ input.hashdatabase(instance,hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function input.hashers.tex(instance,tag,name)
+ input.aux.load_data(instance,tag,'files')
+end
+
+-- generators:
+
+function input.loadlists(instance)
+ for _, hash in ipairs(instance.hashes) do
+ input.generatedatabase(instance,hash.tag)
+ end
+end
+
+function input.generatedatabase(instance,specification)
+ return input.methodhandler('generators', instance, specification)
+end
+
+function input.generators.tex(instance,specification)
+ local tag = specification
+ if not instance.lsrmode and lfs and lfs.dir then
+ input.report("scanning path",specification)
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local n, m = 0, 0
+ local spec = specification .. '/'
+ local attributes = lfs.attributes
+ local directory = lfs.dir
+ local small = instance.smallcache
+ local function action(path)
+ local mode, full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if name == '.' or name == ".." then
+ -- skip
+ else
+ mode = attributes(full..name,'mode')
+ if mode == "directory" then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ elseif path and mode == 'file' then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if not small then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ end
+ else
+ files[name] = path
+ end
+ end
+ end
+ end
+ end
+ action()
+ input.report(n,"files found on",m,"directories")
+ else
+ local fullname = file.join(specification,input.lsrname)
+ local path = '.'
+ local f = io.open(fullname)
+ if f then
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local small = instance.smallcache
+ input.report("loading lsr file",fullname)
+ -- for line in f:lines() do -- much slower then the next one
+ for line in (f:read("*a")):gmatch("(.-)\n") do
+ if line:find("^[%a%d]") then
+ local fl = files[line]
+ if fl then
+ if not small then
+ if type(fl) == 'string' then
+ files[line] = { fl, path } -- table
+ else
+ fl[#fl+1] = path
+ end
+ end
+ else
+ files[line] = path -- string
+ end
+ else
+ path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ end
+ end
+ f:close()
+ end
+ end
+end
+
+-- savers, todo
+
+function input.savefiles(instance)
+ input.aux.save_data(instance, 'files', function(k,v)
+ return instance.validfile(k,v) -- path, name
+ end)
+end
+
+-- A config (optionally) has the paths split in tables. Internally
+-- we join them and split them after the expansion has taken place. This
+-- is more convenient.
+
+function input.splitconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'string' then
+ local t = file.split_path(v)
+ if #t > 1 then
+ c[k] = t
+ end
+ end
+ end
+ end
+end
+function input.joinconfig(instance)
+ for i,c in pairs(instance.configuration) do
+ for k,v in pairs(c) do
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function input.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function input.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t = file.split_path(v)
+ if #t > 1 then
+ instance.expansions[k] = t
+ end
+ end
+end
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t, h = { }, { }
+ for _,vv in pairs(file.split_path(v)) do
+ if vv ~= "" and not h[vv] then
+ t[#t+1] = vv
+ h[vv] = true
+ end
+ end
+ if #t > 1 then
+ instance.expansions[k] = t
+ else
+ instance.expansions[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function input.saveconfig(instance)
+ input.splitconfig(instance)
+ input.aux.save_data(instance, 'configuration', nil)
+ input.joinconfig(instance)
+end
+
+input.configbanner = [[
+-- This is a Luatex configuration file created by 'luatools.lua' or
+-- 'luatex.exe' directly. For comment, suggestions and questions you can
+-- contact the ConTeXt Development Team. This configuration file is
+-- not copyrighted. [HH & TH]
+]]
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name = file.join(cachename,dataname)
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ f:write(input.configbanner)
+ f:write("\n")
+ f:write("if not texmf then texmf = { } end\n")
+ f:write("if not texmf.data then texmf.data = { } end\n")
+ f:write("\n")
+ f:write("texmf.data.type = '" .. dataname .. "'\n")
+ f:write("texmf.data.version = '" .. input.cacheversion .. "'\n")
+ f:write("texmf.data.date = '" .. os.date("%Y-%m-%d") .. "'\n")
+ f:write("texmf.data.time = '" .. os.date("%H:%M:%S") .. "'\n")
+ f:write('texmf.data.content = {\n')
+ local function dump(k,v)
+ if not check or check(v,k) then -- path, name
+ if type(v) == 'string' then
+ f:write("\t['" .. k .. "'] = '" .. v .. "',\n")
+ elseif #v == 1 then
+ f:write("\t['" .. k .. "'] = '" .. v[1] .. "',\n")
+ else
+ f:write("\t['" .. k .. "'] = {'" .. table.concat(v,"','").. "'},\n")
+ end
+ end
+ end
+ if instance.sortdata then
+ for _, k in pairs(table.sortedkeys(files)) do
+ dump(k,files[k])
+ end
+ else
+ for k, v in pairs(files) do
+ dump(k,v)
+ end
+ end
+ f:write('}\n')
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.loadconfig(instance)
+ instance.configuration, instance.loaderror = { }, false
+ if not instance.renewcache then
+ for _, cnf in pairs(instance.cnffiles) do
+ input.aux.load_data(instance,file.dirname(cnf),'configuration')
+ if instance.loaderror then break end
+ end
+ end
+ input.joinconfig(instance)
+end
+
+if not texmf then texmf = {} end
+if not texmf.data then texmf.data = {} end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ if not filename or (filename == "") then
+ filename = dataname .. input.lucsuffix
+ end
+ local blob = loadfile(file.join(pathname,filename))
+ if not blob then
+ filename = dataname .. input.luasuffix
+ blob = loadfile(file.join(pathname,filename))
+ end
+ if blob then
+ blob()
+ if (texmf.data.type == dataname) and (texmf.data.version == input.cacheversion) and texmf.data.content then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = texmf.data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ end
+ texmf.data.content = { }
+end
+
+function input.expand_variables(instance)
+ instance.expansions = { }
+ if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
+ if instance.progname ~= "" then instance.environment['progname'] = instance.engine end
+ for k,v in pairs(instance.environment) do
+ local a, b = k:match("^(%a+)%_(.*)%s*$")
+ if a and b then
+ instance.expansions[a..'.'..b] = v
+ else
+ instance.expansions[k] = v
+ end
+ end
+ for k,v in pairs(instance.environment) do -- move environment to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ for k,v in pairs(instance.variables) do -- move variables to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ while true do
+ local busy = false
+ for k,v in pairs(instance.expansions) do
+ local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ if n > 0 or m > 0 then
+ instance.expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in pairs(instance.expansions) do
+ instance.expansions[k] = v:gsub("\\", '/')
+ end
+ input.splitexpansions(instance)
+end
+
+function input.aux.expand_vars(instance,lst) -- simple vars
+ for k,v in pairs(lst) do
+ lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+ end
+end
+
+function input.aux.expanded_var(instance,var) -- simple vars
+ return var:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+end
+
+function input.aux.entry(instance,entries,name)
+ if name and (name ~= "") then
+ name = name:gsub('%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = input.env(instance,name)
+ if result then
+ instance.variables[name] = result
+ input.expand_variables(instance)
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+function input.variable(instance,name)
+ return input.aux.entry(instance,instance.variables,name)
+end
+function input.expansion(instance,name)
+ return input.aux.entry(instance,instance.expansions,name)
+end
+
+function input.aux.is_entry(instance,entries,name)
+ if name and name ~= "" then
+ name = name:gsub('%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+end
+
+function input.is_variable(instance,name)
+ return input.aux.is_entry(instance,instance.variables,name)
+end
+function input.is_expansion(instance,name)
+ return input.aux.is_entry(instance,instance.expansions,name)
+end
+
+function input.aux.list(instance,list)
+ local pat = string.upper(instance.pattern or "","")
+ for _,key in pairs(table.sortedkeys(list)) do
+ if (instance.pattern=="") or string.find(key:upper(),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ print(key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ elseif instance.kpsevars[key] then
+ print('K ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ else
+ print('E ' .. key .. "=" .. input.aux.tabstr(list[key]))
+ end
+ end
+ end
+end
+
+function input.list_variables(instance)
+ input.aux.list(instance,instance.variables)
+end
+function input.list_expansions(instance)
+ input.aux.list(instance,instance.expansions)
+end
+
+function input.list_configurations(instance)
+ for _,key in pairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or key:find(instance.pattern) then
+ print(key.."\n")
+ for i,c in pairs(instance.configuration) do
+ local str = c[key]
+ if str then
+ print("\t" .. i .. "\t\t" .. input.aux.tabstr(str))
+ end
+ end
+ print()
+ end
+ end
+end
+
+function input.aux.tabstr(str)
+ if type(str) == 'table' then
+ return table.concat(str," | ")
+ else
+ return str
+ end
+end
+
+function input.simplified_list(str)
+ if type(str) == 'table' then
+ return str -- troubles ; ipv , in texmf
+ elseif str == '' then
+ return { }
+ else
+ local t = { }
+ for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
+ t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
+ end
+ return t
+ end
+end
+
+function input.unexpanded_path_list(instance,str)
+ local pth = input.variable(instance,str)
+ local lst = input.split_path(pth)
+ return input.aux.expanded_path(instance,lst)
+end
+function input.unexpanded_path(instance,str)
+ return file.join_path(input.unexpanded_path_list(instance,str))
+end
+
+function input.expanded_path_list(instance,str)
+ if not str then
+ return { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = str:gsub("%$","")
+ if not instance.lists[str] then -- cached
+ local lst = input.split_path(input.expansion(instance,str))
+ instance.lists[str] = input.aux.expanded_path(instance,lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = input.split_path(input.expansion(instance,str))
+ return input.aux.expanded_path(instance,lst)
+ end
+end
+function input.expand_path(instance,str)
+ return file.join_path(input.expanded_path_list(instance,str))
+end
+
+--~ function input.first_writable_path(instance,name)
+--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
+--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
+--~ return v
+--~ end
+--~ end
+--~ return "."
+--~ end
+
+function input.expanded_path_list_from_var(instance,str) -- brrr
+ local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
+ if tmp ~= "" then
+ return input.expanded_path_list(instance,str)
+ else
+ return input.expanded_path_list(instance,tmp)
+ end
+end
+function input.expand_path_from_var(instance,str)
+ return file.join_path(input.expanded_path_list_from_var(instance,str))
+end
+
+function input.format_of_var(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+function input.format_of_suffix(str)
+ return input.suffixmap[file.extname(str)] or 'tex'
+end
+
+function input.variable_of_format(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+
+function input.var_of_format_or_suffix(str)
+ local v = input.formats[str]
+ if v then
+ return v
+ end
+ v = input.formats[input.alternatives[str]]
+ if v then
+ return v
+ end
+ v = input.suffixmap[file.extname(str)]
+ if v then
+ return input.formats[isf]
+ end
+ return ''
+end
+
+function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
+ local ori = input.variable(instance,str)
+ local pth = input.aux.expanded_path(instance,input.split_path(ori))
+ return file.join_path(pth)
+end
+
+-- {a,b,c,d}
+-- a,b,c/{p,q,r},d
+-- a,b,c/{p,q,r}/d/{x,y,z}//
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+
+function input.aux.expanded_path(instance,pathlist)
+ -- a previous version fed back into pathlist
+ local i, n, oldlist, newlist, ok = 0, 0, { }, { }, false
+ for _,v in ipairs(pathlist) do
+ if v:find("[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ for _,v in ipairs(pathlist) do
+ oldlist[#oldlist+1] = (v:gsub("([\{\}])", function(p)
+ if p == "{" then
+ i = i + 1
+ if i > n then n = i end
+ return "<" .. (i-1) .. ">"
+ else
+ i = i - 1
+ return "" .. i .. ">"
+ end
+ end))
+ end
+ for i=1,n do
+ while true do
+ local more = false
+ local pattern = "^(.-)<"..(n-i)..">(.-)"..(n-i)..">(.-)$"
+ local t = { }
+ for _,v in ipairs(oldlist) do
+ local pre, mid, post = v:match(pattern)
+ if pre and mid and post then
+ more = true
+ -- for _,vv in ipairs(mid:splitchr(',')) do
+ for vv in string.gmatch(mid..',',"(.-),") do
+ if vv == '.' then
+ t[#t+1] = pre..post
+ else
+ t[#t+1] = pre..vv..post
+ end
+ end
+ else
+ t[#t+1] = v
+ end
+ end
+ oldlist = t
+ if not more then break end
+ end
+ end
+ for _,v in pairs(oldlist) do
+ v = file.collapse_path(v)
+ if v ~= "" and not v:find(instance.dummy_path_expr) then newlist[#newlist+1] = v end
+ end
+ else
+ for _,v in pairs(pathlist) do
+ -- for _,vv in pairs(v:split(",")) do
+ for vv in string.gmatch(v..',',"(.-),") do
+ vv = file.collapse_path(v)
+ if vv ~= "" then newlist[#newlist+1] = vv end
+ end
+ end
+ end
+ return newlist
+end
+
+--~ function input.is_readable(name) -- brrr, get rid of this
+--~ return name:find("^zip##") or file.is_readable(name)
+--~ end
+
+input.is_readable = { }
+
+function input.aux.is_readable(readable, name)
+ if input.trace > 2 then
+ if readable then
+ input.logger("+ readable", name)
+ else
+ input.logger("- readable", name)
+ end
+ end
+ return readable
+end
+
+function input.is_readable.file(name)
+ -- return input.aux.is_readable(file.is_readable(name), name)
+ return input.aux.is_readable(input.aux.is_file(name), name)
+end
+
+input.is_readable.tex = input.is_readable.file
+
+-- name
+-- name/name
+
+function input.aux.collect_files(instance,names)
+ local filelist = nil
+ for _, fname in pairs(names) do
+ if fname then
+ if input.trace > 2 then
+ input.logger("? blobpath asked",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or dname:find("^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ if blobpath and instance.files[blobpath] then
+ if input.trace > 2 then
+ input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
+ end
+ local blobfile = instance.files[blobpath][bname]
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or blobfile:find(dname) then
+ if not filelist then filelist = { } end
+ -- input.logger('= collected', blobpath.." | "..blobfile.." | "..bname)
+ filelist[#filelist+1] = file.join(blobpath,blobfile,bname)
+ end
+ else
+ for _, vv in pairs(blobfile) do
+ if not dname or vv:find(dname) then
+ if not filelist then filelist = { } end
+ filelist[#filelist+1] = file.join(blobpath,vv,bname)
+ end
+ end
+ end
+ end
+ elseif input.trace > 1 then
+ input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
+ end
+ end
+ end
+ end
+ return filelist
+end
+
+function input.suffix_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function input.suffixes_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str]
+ else
+ return {}
+ end
+end
+
+function input.aux.qualified_path(filename) -- make platform dependent / not good yet
+ return
+ filename:find("^%.+/") or
+ filename:find("^/") or
+ filename:find("^%a+%:") or
+ filename:find("^%a+##")
+end
+
+function input.normalize_name(original)
+ -- internally we use type##spec##subspec ; this hackery slightly slows down searching
+ local str = original or ""
+ str = str:gsub("::", "##") -- :: -> ##
+ str = str:gsub("^(%a+)://" ,"%1##") -- zip:// -> zip##
+ str = str:gsub("(.+)##(.+)##/(.+)","%1##%2##%3") -- ##/spec -> ##spec
+ if (input.trace>1) and (original ~= str) then
+ input.logger('= normalizer',original.." -> "..str)
+ end
+ return str
+end
+
+-- split the next one up, better for jit
+
+function input.aux.register_in_trees(instance,name)
+ if not name:find("^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
+ local result = { }
+ local stamp = nil
+ filename = input.normalize_name(filename)
+ filename = file.collapse_path(filename:gsub("\\","/"))
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ input.logger('! remembered', filename)
+ return instance.found[stamp]
+ end
+ end
+ if filename:find('%*') then
+ input.logger('! wildcard', filename)
+ result = input.find_wildcard_files(instance,filename)
+ elseif input.aux.qualified_path(filename) then
+ if input.is_readable.file(filename) then
+ input.logger('! qualified', filename)
+ result = { filename }
+ else
+ local forcedname, ok = "", false
+ if file.extname(filename) == "" then
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing standard filetype tex')
+ result, ok = { forcedname }, true
+ end
+ else
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ forcedname = filename .. "." .. s
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing format filetype', s)
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok then
+ input.logger('? qualified', filename)
+ end
+ end
+ else
+ -- search spec
+ local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
+ if ext == "" then
+ if not instance.force_suffixes then
+ table.insert(wantedfiles, filename)
+ end
+ else
+ table.insert(wantedfiles, filename)
+ end
+ if instance.format == "" then
+ if ext == "" then
+ local forcedname = filename .. '.tex'
+ table.insert(wantedfiles, forcedname)
+ filetype = input.format_of_suffix(forcedname)
+ input.logger('! forcing filetype',filetype)
+ else
+ filetype = input.format_of_suffix(filename)
+ input.logger('! using suffix based filetype',filetype)
+ end
+ else
+ if ext == "" then
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ table.insert(wantedfiles, filename .. "." .. s)
+ end
+ end
+ filetype = instance.format
+ input.logger('! using given filetype',filetype)
+ end
+ local typespec = input.variable_of_format(filetype)
+ local pathlist = input.expanded_path_list(instance,typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ input.logger('? filetype',filetype or '?')
+ input.logger('? wanted files',table.concat(wantedfiles," | "))
+ end
+ for _, fname in pairs(wantedfiles) do
+ if fname and input.is_readable.file(fname) then
+ filename, done = fname, true
+ table.insert(result, file.join('.',fname))
+ break
+ end
+ end
+ -- this is actually 'other text files' or 'any' or 'whatever'
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ filename = filelist and filelist[1]
+ if filename then
+ table.insert(result, filename)
+ done = true
+ end
+ else
+ -- list search
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ local doscan, recurse
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
+ if filelist then input.logger('? file list',table.concat(filelist," | ")) end
+ end
+ -- a bit messy ... esp the doscan setting here
+ for _, path in pairs(pathlist) do
+ if path:find("^!!") then doscan = false else doscan = true end
+ if path:find("//$") then recurse = true else recurse = false end
+ local pathname = path:gsub("^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
+ pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
+ pathname = pathname:gsub("//", '/.-/')
+ expr = "^" .. pathname
+ -- input.debug('?',expr)
+ for _, f in pairs(filelist) do
+ if f:find(expr) then
+ -- input.debug('T',' '..f)
+ if input.trace > 2 then
+ input.logger('= found in hash',f)
+ end
+ table.insert(result,f)
+ input.aux.register_in_trees(instance,f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ else
+ -- input.debug('F',' '..f)
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all
+ if input.method_is_file(pathname) then -- ?
+ local pname = pathname:gsub("%.%*$",'')
+ if not pname:find("%*") then
+ local ppname = pname:gsub("/+$","")
+ if input.aux.can_be_dir(instance,ppname) then
+ for _, w in pairs(wantedfiles) do
+ local fname = file.join(ppname,w)
+ if input.is_readable.file(fname) then
+ if input.trace > 2 then
+ input.logger('= found by scanning',fname)
+ end
+ table.insert(result,fname)
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ else
+ -- no access needed for non existing path, speedup (esp in large tree with lots of fake)
+ end
+ end
+ end
+ end
+ if not done and doscan then
+ -- todo: slow path scanning
+ end
+ if done and not instance.allresults then break end
+ end
+ end
+ end
+ for k,v in pairs(result) do
+ result[k] = file.collapse_path(v)
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+input.aux._find_file_ = input.aux.find_file
+
+function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
+ local result = input.aux._find_file_(instance,filename)
+ if #result == 0 then
+ local lowered = filename:lower()
+ if filename ~= lowered then
+ return input.aux._find_file_(instance,lowered)
+ end
+ end
+ return result
+end
+
+if lfs and lfs.isfile then
+ input.aux.is_file = lfs.isfile -- to be done: use this
+else
+ input.aux.is_file = file.is_readable
+end
+
+if lfs and lfs.isdir then
+ function input.aux.can_be_dir(instance,name)
+ if not instance.fakepaths[name] then
+ if lfs.isdir(name) then
+ instance.fakepaths[name] = 1 -- directory
+ else
+ instance.fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (instance.fakepaths[name] == 1)
+ end
+else
+ function input.aux.can_be_dir()
+ return true
+ end
+end
+
+if not input.concatinators then input.concatinators = { } end
+
+function input.concatinators.tex(tag,path,name)
+ return tag .. '/' .. path .. '/' .. name
+end
+
+input.concatinators.file = input.concatinators.tex
+
+function input.find_files(instance,filename,filetype,mustexist)
+ if type(mustexist) == boolean then
+ -- all set
+ elseif type(filetype) == 'boolean' then
+ filetype, mustexist = nil, false
+ elseif type(filetype) ~= 'string' then
+ filetype, mustexist = nil, false
+ end
+ instance.format = filetype or ''
+ local t = input.aux.find_file(instance,filename,true)
+ instance.format = ''
+ return t
+end
+
+function input.find_file(instance,filename,filetype,mustexist)
+ return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+end
+
+function input.find_given_files(instance,filename)
+ local bname, result = file.basename(filename), { }
+ for k, hash in pairs(instance.hashes) do
+ local blist = instance.files[hash.tag][bname]
+ if blist then
+ if type(blist) == 'string' then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ if not instance.allresults then break end
+ else
+ for kk,vv in pairs(blist) do
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function input.find_given_file(instance,filename)
+ return (input.find_given_files(instance,filename)[1] or "")
+end
+
+--~ function input.find_wildcard_files(instance,filename)
+--~ local result = { }
+--~ local bname, dname = file.basename(filename), file.dirname(filename)
+--~ local expr = dname:gsub("^*/","")
+--~ expr = expr:gsub("*",".*")
+--~ expr = expr:gsub("-","%-")
+--~ for k, hash in pairs(instance.hashes) do
+--~ local blist = instance.files[hash.tag][bname]
+--~ if blist then
+--~ if type(blist) == 'string' then
+--~ -- make function and share code
+--~ if blist:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ else
+--~ for kk,vv in pairs(blist) do
+--~ if vv:find(expr) then
+--~ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+--~ if not instance.allresults then break end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ end
+--~ return result
+--~ end
+
+function input.find_wildcard_files(instance,filename)
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = dname:gsub("^*/","")
+ path = path:gsub("*",".*")
+ path = path:gsub("-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = name:gsub("*",".*")
+ name = name:gsub("-","%%-")
+ path = path:lower()
+ name = name:lower()
+ local function doit(blist,bname,hash,allresults)
+ local done = false
+ if blist then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if (blist:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,blist,bname) or "")
+ done = true
+ end
+ else
+ for kk,vv in pairs(blist) do
+ if (vv:lower()):find(path) then
+ table.insert(result,input.concatinators[hash.type](hash.tag,vv,bname) or "")
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+ end
+ local files, allresults, done = instance.files, instance.allresults, false
+ if name:find("%*") then
+ for k, hash in pairs(instance.hashes) do
+ for kk, hh in pairs(files[hash.tag]) do
+ if (kk:lower()):find(name) then
+ if doit(hh,kk,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ else
+ for k, hash in pairs(instance.hashes) do
+ if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ return result
+end
+
+function input.find_wildcard_file(instance,filename)
+ return (input.find_wildcard_files(instance,filename)[1] or "")
+end
+
+-- main user functions
+
+function input.save_used_files_in_trees(instance, filename,jobname)
+ if not filename then filename = 'luatex.jlg' end
+ local f = io.open(filename,'w')
+ if f then
+ f:write("\n")
+ f:write("\n")
+ if jobname then
+ f:write("\t" .. jobname .. "\n")
+ end
+ f:write("\t\n")
+ for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
+ f:write("\t\t" .. v .. "\n")
+ end
+ f:write("\t\n")
+ f:write("\n")
+ f:close()
+ end
+end
+
+function input.automount(instance)
+ -- implemented later
+end
+
+function input.load(instance)
+ input.start_timing(instance)
+ input.identify_cnf(instance)
+ input.load_cnf(instance)
+ input.expand_variables(instance)
+ input.load_hash(instance)
+ input.automount(instance)
+ input.stop_timing(instance)
+end
+
+function input.for_files(instance, command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if input.verbose then
+ input.report(str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if input.verbose then
+ report('')
+ end
+ for _, file in pairs(files) do
+ local result = command(instance,file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in pairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+function input.var_value(instance,str) -- output the value of variable $STRING.
+ return input.variable(instance,str)
+end
+function input.expand_var(instance,str) -- output variable expansion of STRING.
+ return input.expansion(instance,str)
+end
+function input.show_path(instance,str) -- output search path for file type NAME
+ return file.join_path(input.expanded_path_list(instance,input.format_of_var(str)))
+end
+
+-- input.find_file(filename)
+-- input.find_file(filename, filetype, mustexist)
+-- input.find_file(filename, mustexist)
+-- input.find_file(filename, filetype)
+
+function input.aux.register_file(files, name, path)
+ if files[name] then
+ if type(files[name]) == 'string' then
+ files[name] = { files[name], path }
+ else
+ files[name] = path
+ end
+ else
+ files[name] = path
+ end
+end
+
+-- zip:: zip## zip://
+-- zip::pathtozipfile::pathinzipfile (also: pathtozipfile/pathinzipfile)
+-- file::name
+-- tex::name
+-- kpse::name
+-- kpse::format::name
+-- parent::n::name
+-- parent::name (default 2)
+
+if not input.finders then input.finders = { } end
+if not input.openers then input.openers = { } end
+if not input.loaders then input.loaders = { } end
+
+input.finders.notfound = { nil }
+input.openers.notfound = { nil }
+input.loaders.notfound = { false, nil, 0 }
+
+function input.splitmethod(filename)
+ local method, specification = filename:match("^(.-)##(.+)$")
+ if method and specification then
+ return method, specification
+ else
+ return 'tex', filename
+ end
+end
+
+function input.method_is_file(filename)
+ local method, specification = input.splitmethod(filename)
+ return method == 'tex' or method == 'file'
+end
+
+function input.methodhandler(what, instance, filename, filetype) -- ...
+ local method, specification = input.splitmethod(filename)
+ if method and specification then -- redundant
+ if input[what][method] then
+ input.logger('= handler',filename.." -> "..what.." | "..method.." | "..specification)
+ return input[what][method](instance,specification,filetype)
+ else
+ return nil
+ end
+ else
+ return input[what].tex(instance,filename,filetype)
+ end
+end
+
+-- also inside next test?
+
+function input.findtexfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.opentexfile(instance,filename)
+ return input.methodhandler('openers',instance, input.normalize_name(filename))
+end
+
+function input.findbinfile(instance, filename, filetype)
+ return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
+end
+function input.openbinfile(instance,filename)
+ return input.methodhandler('loaders',instance, input.normalize_name(filename))
+end
+
+function input.loadbinfile(instance, filename, filetype)
+ local fname = input.findbinfile(instance, input.normalize_name(filename), filetype)
+ if fname and fname ~= "" then
+ return input.openbinfile(instance,fname)
+ else
+ return unpack(input.loaders.notfound)
+ end
+end
+
+function input.texdatablob(instance, filename, filetype)
+ local ok, data, size = input.loadbinfile(instance, filename, filetype)
+ return data or ""
+end
+
+function input.openfile(filename) -- brrr texmf.instance here / todo ! ! ! ! !
+ local fullname = input.findtexfile(texmf.instance, filename)
+ if fullname and (fullname ~= "") then
+ return input.opentexfile(texmf.instance, fullname)
+ else
+ return nil
+ end
+end
+
+function input.logmode()
+ return (os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"):lower()
+end
+
+-- this is a prelude to engine/progname specific configuration files
+-- in which case we can omit files meant for other programs and
+-- packages
+
+--- ctx
+
+-- maybe texinputs + font paths
+-- maybe positive selection tex/context fonts/tfm|afm|vf|opentype|type1|map|enc
+
+input.validators = { }
+input.validators.visibility = { }
+
+function input.validators.visibility.default(path, name)
+ return true
+end
+
+function input.validators.visibility.context(path, name)
+ path = path[1] or path -- some day a loop
+ return not (
+ path:find("latex") or
+ path:find("doc") or
+ path:find("tex4ht") or
+ path:find("source") or
+-- path:find("config") or
+-- path:find("metafont") or
+ path:find("lists$") or
+ name:find("%.tpm$") or
+ name:find("%.bak$")
+ )
+end
+
+-- todo: describe which functions are public (maybe input.private. ... )
+
+-- beware: i need to check where we still need a / on windows:
+
+function input.clean_path(str)
+ -- return string.gsub(string.gsub(string.gsub(str,"\\","/"),"^!+",""),"//$","/")
+ return (string.gsub(string.gsub(str,"\\","/"),"^!+",""))
+end
+function input.do_with_path(name,func)
+ for _, v in pairs(input.expanded_path_list(instance,name)) do
+ func("^"..input.clean_path(v))
+ end
+end
+function input.do_with_var(name,func)
+ func(input.aux.expanded_var(name))
+end
+
+function input.with_files(instance,pattern,handle)
+ for _, hash in pairs(instance.hashes) do
+ local blobpath = hash.tag
+ local blobtype = hash.type
+ if blobpath and instance.files[blobpath] then -- sort them?
+ for k,v in pairs(instance.files[blobpath]) do
+ if k:find(pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ newname = file.addsuffix(newname,"lua")
+ newscript = input.clean_path(input.find_file(instance, newname))
+ oldscript = input.clean_path(oldname)
+ input.report("old script", oldscript)
+ input.report("new script", newscript)
+ if oldscript ~= newscript and (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
+ newdata = io.loaddata(newscript)
+ if newdata then
+ input.report("old script content replaced by new content")
+ io.savedata(oldscript,newdata)
+ end
+ end
+end
diff --git a/tex/context/base/luat-iop.lua b/tex/context/base/luat-iop.lua
new file mode 100644
index 000000000..469b7c034
--- /dev/null
+++ b/tex/context/base/luat-iop.lua
@@ -0,0 +1,157 @@
+-- filename : luat-iop.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- this paranoid stuff in web2c ... we cannot hook checks into the
+-- input functions because one can always change the callback but
+-- we can feed back specific patterns and paths into the next
+-- mechanism
+
+if not versions then versions = { } end versions['luat-exe'] = 1.001
+
+if not io.inp then io.inp = { } end
+if not io.out then io.out = { } end
+
+io.inp.blocked = { }
+io.out.blocked = { }
+io.inp.permitted = { }
+io.out.permitted = { }
+io.inp.modes = { } -- functions
+io.out.modes = { } -- functions
+
+io.blocked_openers = { } -- *.open(name,method)
+
+function io.inp.inhibit (name) table.insert(io.inp.blocked, name) end
+function io.out.inhibit (name) table.insert(io.out.blocked, name) end
+function io.inp.permit (name) table.insert(io.inp.permitted, name) end
+function io.out.permit (name) table.insert(io.out.permitted, name) end
+
+function io.register_opener(func) table.insert(io.blocked_openers, func) end
+
+function io.finalize_openers(func)
+ if (#io.out.blocked > 0) or (#io.inp.blocked > 0) then
+ do
+ local open = func
+ local out_permitted = io.out.permitted
+ local inp_permitted = io.inp.permitted
+ local out_blocked = io.out.blocked
+ local inp_blocked = io.inp.blocked
+ return function(name,method)
+ local function checked(blocked, permitted)
+ local n = string.lower(name)
+ for _,b in pairs(blocked) do
+ if string.find(n,b) then
+ for _,p in pairs(permitted) do
+ if string.find(n,p) then
+ return true
+ end
+ end
+ return false
+ end
+ end
+ return true
+ end
+ if method and string.find(method,'[wa]') then
+ if #out.blocked > 0 then
+ if not checked(out_blocked, out_permitted) then
+ -- print("writing to " .. name .. " is not permitted")
+ return nil
+ end
+ end
+ else
+ if #inp.blocked > 0 then
+ if not checked(inp_blocked, inp_permitted) then
+ -- print("reading from " .. name .. " is not permitted")
+ return nil
+ end
+ end
+ end
+ return open(name,method)
+ end
+ end
+ else
+ return func
+ end
+end
+
+--~ io.inp.inhibit('^%.')
+--~ io.inp.inhibit('^/etc')
+--~ io.inp.inhibit('/windows/')
+--~ io.inp.inhibit('/winnt/')
+--~ io.inp.permit('c:/windows/wmsetup.log')
+
+--~ io.open = io.finalize_openers(io.open)
+
+--~ f = io.open('.tex') print(f)
+--~ f = io.open('tufte.tex') print(f)
+--~ f = io.open('t:/sources/tufte.tex') print(f)
+--~ f = io.open('/etc/passwd') print(f)
+--~ f = io.open('c:/windows/crap.log') print(f)
+--~ f = io.open('c:/windows/wmsetup.log') print(f)
+
+function io.set_opener_modes(i,o)
+ for _,v in pairs({'inp','out'}) do
+ if io[v][i] then
+ io[v][i]()
+ elseif io[v][string.sub(i,1,1)] then
+ io[v][string.sub(i,1,1)]()
+ end
+ end
+ io.open = io.finalize_openers(io.open)
+end
+
+function io.set_opener_modes(i,o)
+ local f
+ for _,v in pairs({'inp','out'}) do
+ f = io[v][i] or io[v][string.sub(i,1,1)]
+ if f then f() end
+ end
+ io.open = io.finalize_openers(io.open)
+end
+
+-- restricted
+
+function io.inp.modes.restricted()
+ io.inp.inhibit('^%.[%a]')
+end
+function io.out.modes.restricted()
+ io.out.inhibit('^%.[%a]')
+end
+
+-- paranoid
+
+function io.inp.modes.paranoid()
+ io.inp.inhibit('.*')
+ io.inp.inhibit('%.%.')
+ io.inp.permit('^%./')
+ io.inp.permit('[^/]')
+ input.do_with_path('TEXMF',io.inp.permit)
+end
+function io.out.modes.paranoid()
+ io.out.inhibit('.*')
+ input.do_with_path('TEXMFOUTPUT',io.out.permit)
+end
+
+-- handy
+
+function io.inp.modes.handy()
+ io.inp.inhibit('%.%.')
+ if os.platform == 'linux' then
+ io.inp.inhibit('^/etc')
+ else
+ io.inp.inhibit('/windows/')
+ io.inp.inhibit('/winnt/')
+ end
+end
+function io.out.modes.handy()
+ io.out.inhibit('.*')
+ io.out.permit('%./')
+ io.out.permit('^%./')
+ io.out.permit('[^/]')
+end
+
+--~ io.set_opener_modes('p','p')
+--~ io.set_opener_modes('r','r')
+--~ io.set_opener_modes('h','h')
diff --git a/tex/context/base/luat-kps.lua b/tex/context/base/luat-kps.lua
new file mode 100644
index 000000000..15dadbb84
--- /dev/null
+++ b/tex/context/base/luat-kps.lua
@@ -0,0 +1,102 @@
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This file is used when we want the input handlers to behave like
+kpsewhich. What to do with the following:
If you wondered abou tsome of the previous mappings, how about
+the next bunch:
+--ldx]]--
+
+input.formats['bib'] = ''
+input.formats['bst'] = ''
+input.formats['mft'] = ''
+input.formats['ist'] = ''
+input.formats['web'] = ''
+input.formats['cweb'] = ''
+input.formats['MetaPost support'] = ''
+input.formats['TeX system documentation'] = ''
+input.formats['TeX system sources'] = ''
+input.formats['Troff fonts'] = ''
+input.formats['dvips config'] = ''
+input.formats['graphic/figure'] = ''
+input.formats['ls-R'] = ''
+input.formats['other text files'] = ''
+input.formats['other binary files'] = ''
+
+input.formats['gf'] = ''
+input.formats['pk'] = ''
+input.formats['base'] = 'MFBASES'
+input.formats['cnf'] = ''
+input.formats['mem'] = 'MPMEMS'
+input.formats['mf'] = 'MFINPUTS'
+input.formats['mfpool'] = 'MFPOOL'
+input.formats['mppool'] = 'MPPOOL'
+input.formats['texpool'] = 'TEXPOOL'
+input.formats['PostScript header'] = 'TEXPSHEADERS'
+input.formats['cmap files'] = 'CMAPFONTS'
+input.formats['type42 fonts'] = 'T42FONTS'
+input.formats['web2c files'] = 'WEB2C'
+input.formats['pdftex config'] = 'PDFTEXCONFIG'
+input.formats['texmfscripts'] = 'TEXMFSCRIPTS'
+input.formats['bitmap font'] = ''
+input.formats['lig files'] = 'LIGFONTS'
diff --git a/tex/context/base/luat-lib.lua b/tex/context/base/luat-lib.lua
new file mode 100644
index 000000000..16c573ded
--- /dev/null
+++ b/tex/context/base/luat-lib.lua
@@ -0,0 +1,144 @@
+-- filename : luat-lib.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-lib'] = 1.001
+
+-- mostcode moved to the l-*.lua and other luat-*.lua files
+
+-- os / io
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+-- os.platform
+
+-- mswin|bccwin|mingw|cygwin windows
+-- darwin|rhapsody|nextstep macosx
+-- netbsd|unix unix
+-- linux linux
+
+if not io.fileseparator then
+ if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator, os.platform = "\\", ";", "windows"
+ else
+ io.fileseparator, io.pathseparator, os.platform = "/" , ":", "unix"
+ end
+end
+
+if not os.platform then
+ if io.pathseparator == ";" then
+ os.platform = "windows"
+ else
+ os.platform = "unix"
+ end
+end
+
+-- arg normalization
+--
+-- for k,v in pairs(arg) do print(k,v) end
+
+if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
+ arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
+end
+
+-- environment
+
+if not environment then environment = { } end
+
+environment.arguments = { }
+environment.files = { }
+environment.sorted_argument_keys = nil
+
+environment.platform = os.platform
+
+function environment.initialize_arguments(arg)
+ environment.arguments = { }
+ environment.files = { }
+ environment.sorted_argument_keys = nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ environment.arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ environment.arguments[flag] = true
+ else
+ environment.files[#environment.files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.showarguments()
+ for k,v in pairs(environment.arguments) do
+ print(k .. " : " .. tostring(v))
+ end
+ if #environment.files > 0 then
+ print("files : " .. table.concat(environment.files, " "))
+ end
+end
+
+function environment.argument(name)
+ if environment.arguments[name] then
+ return environment.arguments[name]
+ else
+ if not environment.sorted_argument_keys then
+ environment.sorted_argument_keys = { }
+ for _,v in pairs(table.sortedkeys(environment.arguments)) do
+ table.insert(environment.sorted_argument_keys, "^" .. v)
+ end
+ end
+ for _,v in pairs(environment.sorted_argument_keys) do
+ if name:find(v) then
+ return environment.arguments[v:sub(2,#v)]
+ end
+ end
+ end
+ return nil
+end
+
+function environment.split_arguments(separator) -- rather special, cut-off before separator
+ local done, before, after = false, { }, { }
+ for _,v in ipairs(environment.original_arguments) do
+ if not done and v == separator then
+ done = true
+ elseif done then
+ after[#after+1] = v
+ else
+ before[#before+1] = v
+ end
+ end
+ return before, after
+end
+
+function environment.reconstruct_commandline(arg)
+ if not arg then arg = environment.original_arguments end
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ local kk, vv = a:match("^(%-+.-)=(.+)$")
+ if kk and vv then
+ if vv:find(" ") then
+ result[#result+1] = kk .. "=" .. string.quote(vv)
+ else
+ result[#result+1] = a
+ end
+ elseif a:find(" ") then
+ result[#result+1] = string.quote(a)
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+end
+
+if arg then
+ environment.initialize_arguments(arg)
+ environment.original_arguments = arg
+ arg = { } -- prevent duplicate handling
+end
diff --git a/tex/context/base/luat-lib.tex b/tex/context/base/luat-lib.tex
new file mode 100644
index 000000000..6c5c63227
--- /dev/null
+++ b/tex/context/base/luat-lib.tex
@@ -0,0 +1,86 @@
+%D \module
+%D [ file=luat-lib,
+%D version=2006.09.11,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=Unicode Support,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+% \writestatus{loading}{Lua Support Macros (libs)}
+
+%D For the moment we only load this lib.
+
+%D This will move cq. become configurable. The XML like output is just
+%D an example.
+
+% todo \let\normaleverytoks\everytoks \newtoks\everytoke \normaleverytoks{\the\everytoks}
+
+\chardef\statuswidth=15
+\chardef\statuswrite=16
+
+\newtoks\everywritestring
+
+\def\writedirect {\immediate\write\statuswrite}
+\def\writeline {\writedirect{}}
+\def\writestring#1{\begingroup\the\everywritestring\writedirect{#1}\endgroup}
+
+\ifx\normalmessage \undefined \let\normalmessage \message \fi
+\ifx\normalwritestatus\undefined \def\normalwritestatus#1#2{\writedirect{#1 : #2}} \fi
+
+% this will change once we have proper write overloads
+
+\registerctxluafile{l-string} {1.001}
+\registerctxluafile{l-boolean}{1.001}
+\registerctxluafile{l-number} {1.001}
+\registerctxluafile{l-math} {1.001}
+\registerctxluafile{l-table} {1.001}
+\registerctxluafile{l-md5} {1.001}
+\registerctxluafile{l-aux} {1.001}
+\registerctxluafile{l-io} {1.001}
+\registerctxluafile{l-os} {1.001}
+\registerctxluafile{l-file} {1.001}
+\registerctxluafile{l-dir} {1.001}
+\registerctxluafile{l-unicode}{1.001}
+\registerctxluafile{l-utils} {1.001}
+\registerctxluafile{l-tex} {1.001}
+\registerctxluafile{l-xml} {1.001}
+\registerctxluafile{l-xmlctx} {1.001}
+
+\registerctxluafile{luat-cbk} {1.001}
+\registerctxluafile{luat-lib} {1.001}
+\registerctxluafile{luat-inp} {1.001}
+\registerctxluafile{luat-log} {1.001}
+\registerctxluafile{luat-zip} {1.001}
+\registerctxluafile{luat-tex} {1.001}
+
+\startruntimeluacode
+ \edef\asciia{\ctxlua{tex.sprint(input.logmode())}}
+ \edef\asciib{xml}
+ \ifx\asciia\asciib % brrr
+ \long\def\writebanner #1{\writestring {#1}}
+ \long\def\writestatus#1#2{\writestring {#2}}
+ \long\def\message #1{\normalmessage{#1}}
+ \else
+ \let\writebanner\writestring
+ \let\writestatus\normalwritestatus
+ \let\message \normalmessage
+ \fi
+\stopruntimeluacode
+
+\registerctxluafile{luat-tmp}{1.001}
+\registerctxluafile{luat-crl}{1.001}
+\registerctxluafile{luat-exe}{1.001}
+\registerctxluafile{luat-iop}{1.001}
+
+% trace used files (only from trees)
+%
+% \ctxlua{input.register_stop_actions(function() input.save_used_files_in_trees(texmf.instance) end)}
+% \ctxlua{table.insert(input.stop_actions, function() input.save_used_files_in_trees(texmf.instance) end)}
+% \ctxlua{function input.stop_actions.trace_used_files() input.save_used_files_in_trees(texmf.instance) end}
+
+\endinput
diff --git a/tex/context/base/luat-lmx.lua b/tex/context/base/luat-lmx.lua
new file mode 100644
index 000000000..817506220
--- /dev/null
+++ b/tex/context/base/luat-lmx.lua
@@ -0,0 +1,139 @@
+-- filename : luat-lmx.lua
+-- comment : companion to luat-lmx.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-mlx'] = 1.001
+
+lmx = { }
+
+lmx.escapes = {
+ ['&'] = '&',
+ ['<'] = '<',
+ ['>'] = '>',
+ ['"'] = '"'
+}
+
+-- local function p -> ends up in lmx.p, so we need to cast
+
+lmx.variables = { }
+
+lmx.variables['title-default'] = 'LMX File'
+lmx.variables['title'] = lmx.variables['title-default']
+
+-- demonstrates: local, *all, gsub using tables, nil or value, loadstring
+
+function lmx.loadedfile(filename)
+ return input.texdatablob(texmf.instance, filename)
+end
+
+lmx.converting = false
+
+function lmx.convert(template,result) -- use lpeg instead
+ if not lmx.converting then -- else, if error then again tex error and loop
+ local data = input.texdatablob(texmf.instance, template)
+ local f = false
+ if result then
+ f = io.open(result,"w")
+ function lmx.print(str) f:write(str) end
+ else
+ lmx.print = io.write
+ end
+ function lmx.variable(str)
+ return lmx.variables[str] or ""
+ end
+ function lmx.escape(str)
+ return string.gsub(string.gsub(str,'&','&'),'[<>"]',lmx.escapes)
+ end
+ function lmx.type(str)
+ if str then lmx.print("" .. lmx.escape(str) .. "") end
+ end
+ function lmx.pv(str)
+ lmx.print(lmx.variable(str))
+ end
+ function lmx.tv(str)
+ lmx.type(lmx.variable(str))
+ end
+ data = string.gsub(data, "<%?lmx%-include%s+(.-)%s-%?>", function(filename)
+ return lmx.loadedfile(filename)
+ end)
+ local definitions = { }
+ data = string.gsub(data, "<%?lmx%-define%-begin%s+(%S-)%s-%?>(.-)<%?lmx%-define%-end%s-%?>", function(tag,content)
+ definitions[tag] = content
+ return ""
+ end)
+ data = string.gsub(data, "<%?lmx%-resolve%s+(%S-)%s-%?>", function(tag)
+ return definitions[tag] or ""
+ end)
+ data = string.gsub(data, "%c%s-(<%?lua .-%?>)%s-%c", function(lua)
+ return "\n" .. lua .. " "
+ end)
+ data = string.gsub(data .. "","(.-)<%?lua%s+(.-)%?>", function(txt, lua)
+ txt = txt:gsub("%c+", "\\n")
+ txt = txt:gsub('"' , '\\"')
+ txt = txt:gsub("'" , "\\'")
+ -- txt = string.gsub(txt, "([\'\"])", { ["'"] = '\\"', ['"'] = "\\'" } )
+ return "p(\"" .. txt .. "\")\n" .. lua .. "\n"
+ end)
+ lmx.converting = true
+ data = "local p,v,e,t,pv,tv = lmx.print,lmx.variable,lmx.escape,lmx.type,lmx.pv,lmx.tv " .. data
+ assert(loadstring(data))()
+ lmx.converting = false
+ if f then
+ f:close()
+ end
+ end
+end
+
+-- these can be overloaded; we assume that the os handles filename associations
+
+lmx.lmxfile = function(filename) return filename end
+lmx.htmfile = function(filename) return filename end
+
+if environment.platform == "windows" then
+ lmx.popupfile = function(filename) os.execute("start " .. filename) end
+else
+ lmx.popupfile = function(filename) os.execute(filename) end
+end
+
+function lmx.show(name)
+ local lmxfile = lmx.lmxfile(name)
+ local htmfile = lmx.htmfile(name)
+ if lmxfile == htmfile then
+ htmfile = string.gsub(lmxfile, "%.%a+$", "html")
+ end
+ lmx.convert(lmxfile, htmfile)
+ lmx.popupfile(htmfile)
+end
+
+-- kind of private
+
+lmx.restorables = { }
+
+function lmx.set(key, value)
+ if not lmx.restorables[key] then
+ table.insert(lmx.restorables, key)
+ lmx.variables['@@' .. key] = lmx.variables[key]
+ end
+ lmx.variables[key] = value
+end
+
+function lmx.get(key)
+ return lmx.variables[key] or ""
+end
+
+function lmx.restore()
+ for _,key in pairs(lmx.restorables) do
+ lmx.variables[key] = lmx.variables['@@' .. key]
+ end
+ lmx.restorables = { }
+end
+
+-- command line
+
+if arg then
+ if arg[1] == "--show" then if arg[2] then lmx.show (arg[2]) end
+ elseif arg[1] == "--convert" then if arg[2] then lmx.convert(arg[2], arg[3] or "temp.html") end
+ end
+end
diff --git a/tex/context/base/luat-lmx.tex b/tex/context/base/luat-lmx.tex
new file mode 100644
index 000000000..cc7fa448f
--- /dev/null
+++ b/tex/context/base/luat-lmx.tex
@@ -0,0 +1,16 @@
+%D \module
+%D [ file=luat-lmx,
+%D version=2005.09.02,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=LMX Support,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Lua Support Macros (lmx)}
+
+\registerctxluafile{luat-lmx}{1.001}
diff --git a/tex/context/base/luat-log.lua b/tex/context/base/luat-log.lua
new file mode 100644
index 000000000..faecf7e29
--- /dev/null
+++ b/tex/context/base/luat-log.lua
@@ -0,0 +1,123 @@
+if not modules then modules = { } end modules ['luat-log'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This is a prelude to a more extensive logging module. For the sake
+of parsing log files, in addition to the standard logging we will
+provide an structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.
+--ldx]]--
+
+input = input or { }
+logs = logs or { }
+
+--[[ldx--
+
This looks pretty ugly but we need to speed things up a bit.
+--ldx]]--
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4
+}
+
+logs.functions = {
+ 'error', 'warning', 'info', 'debug', 'report',
+ 'start', 'stop', 'push', 'pop'
+}
+
+logs.callbacks = {
+ 'start_page_number',
+ 'stop_page_number',
+ 'report_output_pages',
+ 'report_output_log'
+}
+
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+logs.level = 0
+
+do
+ local write_nl, write, format = texio.write_nl or print, texio.write or print, string.format
+
+ function logs.xml.debug(category,str)
+ if logs.level > 3 then write_nl(format("%s",category,str)) end
+ end
+ function logs.xml.info(category,str)
+ if logs.level > 2 then write_nl(format("%s",category,str)) end
+ end
+ function logs.xml.warning(category,str)
+ if logs.level > 1 then write_nl(format("%s",category,str)) end
+ end
+ function logs.xml.error(category,str)
+ if logs.level > 0 then write_nl(format("%s",category,str)) end
+ end
+ function logs.xml.report(category,str)
+ write_nl(format("%s",category,str))
+ end
+
+ function logs.xml.start() if logs.level > 0 then tw("<%s>" ) end end
+ function logs.xml.stop () if logs.level > 0 then tw("%s>") end end
+ function logs.xml.push () if logs.level > 0 then tw("" ) end end
+
+ function logs.tex.debug(category,str)
+ if logs.level > 3 then write_nl(format("debug >> %s: %s" ,category,str)) end
+ end
+ function logs.tex.info(category,str)
+ if logs.level > 2 then write_nl(format("info >> %s: %s" ,category,str)) end
+ end
+ function logs.tex.warning(category,str)
+ if logs.level > 1 then write_nl(format("warning >> %s: %s",category,str)) end
+ end
+ function logs.tex.error(category,str)
+ if logs.level > 0 then write_nl(format("error >> %s: %s" ,category,str)) end
+ end
+ function logs.tex.report(category,str)
+ write_nl(format("report >> %s: %s" ,category,str))
+ end
+
+ function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+ end
+
+ function logs.set_method(method)
+ for _, v in pairs(logs.functions) do
+ logs[v] = logs[method][v] or function() end
+ end
+ if callback and input[method] then
+ for _, cb in pairs(logs.callbacks) do
+ callback.register(cb, input[method][cb])
+ end
+ end
+ end
+
+ function logs.xml.start_page_number()
+ write_nl(format("")
+ write_nl("")
+ end
+
+ function logs.xml.report_output_pages(p,b)
+ write_nl(format("", p))
+ write_nl(format("", b))
+ write_nl("")
+ end
+
+ function logs.xml.report_output_log()
+ end
+
+end
+
+logs.set_level('error')
+logs.set_method('tex')
diff --git a/tex/context/base/luat-tex.lua b/tex/context/base/luat-tex.lua
new file mode 100644
index 000000000..8e770f9de
--- /dev/null
+++ b/tex/context/base/luat-tex.lua
@@ -0,0 +1,389 @@
+-- filename : luat-zip.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-tex'] = 1.001
+
+-- special functions that deal with io
+
+if texconfig and not texlua then
+
+ input.level = input.level or 0
+
+ if input.logmode() == 'xml' then
+ function input.show_open(name)
+ input.level = input.level + 1
+ texio.write_nl("")
+ end
+ function input.show_close(name)
+ texio.write(" ")
+ input.level = input.level - 1
+ end
+ function input.show_load(name)
+ texio.write_nl("") -- level?
+ end
+ else
+ function input.show_open () end
+ function input.show_close() end
+ function input.show_load () end
+ end
+
+ function input.finders.generic(instance,tag,filename,filetype)
+ local foundname = input.find_file(instance,filename,filetype)
+ if foundname and foundname ~= "" then
+ input.logger('+ ' .. tag .. ' finder',filename,'filetype')
+ return foundname
+ else
+ input.logger('- ' .. tag .. ' finder',filename,'filetype')
+ return unpack(input.finders.notfound)
+ end
+ end
+
+ input.filters.dynamic_translator = nil
+ input.filters.frozen_translator = nil
+ input.filters.utf_translator = nil
+
+ function input.openers.text_opener(filename,file_handle,tag)
+ local u = unicode.utftype(file_handle)
+ local t = { }
+ if u > 0 then
+ input.logger('+ ' .. tag .. ' opener (' .. unicode.utfname[u] .. ')',filename)
+ local l
+ if u > 2 then
+ l = unicode.utf32_to_utf8(file_handle:read("*a"),u==4)
+ else
+ l = unicode.utf16_to_utf8(file_handle:read("*a"),u==2)
+ end
+ file_handle:close()
+ t = {
+ utftype = u, -- may go away
+ lines = l,
+ current = 0,
+ handle = nil,
+ noflines = #l,
+ close = function()
+ input.logger('= ' .. tag .. ' closer (' .. unicode.utfname[u] .. ')',filename)
+ input.show_close(filename)
+ end,
+ reader = function(self)
+ if not self then self = t end
+ if self.current >= #self.lines then
+ return nil
+ else
+ self.current = self.current + 1
+ if input.filters.utf_translator then
+ return input.filters.utf_translator(self.lines[t.current])
+ else
+ return self.lines[self.current]
+ end
+ end
+ end
+ }
+ else
+ input.logger('+ ' .. tag .. ' opener',filename)
+ -- todo: file;name -> freeze / eerste regel scannen -> freeze
+ t = {
+ reader = function(self)
+ if not self then self = t end -- not used
+ if input.filters.dynamic_translator then
+ return input.filters.dynamic_translator(file_handle:read())
+ elseif input.filters.utf_translator then
+ return input.filters.utf_translator(file_handle:read())
+ else
+ return file_handle:read()
+ end
+ end,
+ close = function()
+ input.logger('= ' .. tag .. ' closer',filename)
+ input.show_close(filename)
+ file_handle:close()
+ end,
+ handle = function()
+ return file_handle
+ end,
+ noflines = function()
+ t.noflines = io.noflines(file_handle)
+ return t.noflines
+ end
+ }
+ end
+ return t
+ end
+
+ function input.openers.generic(instance,tag,filename)
+ if filename and filename ~= "" then
+ local f = io.open(filename,"r")
+ if f then
+ input.show_open(filename)
+ return input.openers.text_opener(filename,f,tag)
+ end
+ end
+ input.logger('- ' .. tag .. ' opener',filename)
+ return unpack(input.openers.notfound)
+ end
+
+ function input.loaders.generic(instance,tag,filename)
+ if filename and filename ~= "" then
+ local f = io.open(filename,"rb")
+ if f then
+ input.show_load(filename)
+ input.logger('+ ' .. tag .. ' loader',filename)
+ local s = f:read("*a")
+ f:close()
+ if s then
+ return true, s, #s
+ end
+ end
+ end
+ input.logger('- ' .. tag .. ' loader',filename)
+ return unpack(input.loaders.notfound)
+ end
+
+ function input.finders.tex(instance,filename,filetype)
+ return input.finders.generic(instance,'tex',filename,filetype)
+ end
+ function input.openers.tex(instance,filename)
+ return input.openers.generic(instance,'tex',filename)
+ end
+ function input.loaders.tex(instance,filename)
+ return input.loaders.generic(instance,'tex',filename)
+ end
+
+end
+
+-- callback into the file io and related things; disabling kpse
+
+if texconfig and not texlua then
+
+ texconfig.kpse_init = false
+ texconfig.trace_file_names = input.logmode() == 'tex'
+ texconfig.max_print_line = 100000
+
+ -- if still present, we overload kpse (put it off-line so to say)
+
+ if not texmf then texmf = { } end
+
+ if not texmf.instance then
+
+ if not texmf.instance then -- prevent a second loading
+
+ texmf.instance = input.reset()
+ texmf.instance.progname = environment.progname or 'context'
+ texmf.instance.engine = environment.engine or 'luatex'
+ texmf.instance.validfile = input.validctxfile
+
+ input.load(texmf.instance)
+
+ end
+
+ if callback then
+ callback.register('find_read_file' , function(id,name) return input.findtexfile(texmf.instance,name) end)
+ callback.register('open_read_file' , function( name) return input.opentexfile(texmf.instance,name) end)
+ end
+
+ if callback then
+ callback.register('find_data_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
+ callback.register('find_enc_file' , function(name) return input.findbinfile(texmf.instance,name,"enc") end)
+ callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"tfm") end)
+ callback.register('find_format_file' , function(name) return input.findbinfile(texmf.instance,name,"fmt") end)
+ callback.register('find_image_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
+ callback.register('find_map_file' , function(name) return input.findbinfile(texmf.instance,name,"map") end)
+ callback.register('find_ocp_file' , function(name) return input.findbinfile(texmf.instance,name,"ocp") end)
+ callback.register('find_opentype_file' , function(name) return input.findbinfile(texmf.instance,name,"otf") end)
+ callback.register('find_output_file' , function(name) return name end)
+ callback.register('find_pk_file' , function(name) return input.findbinfile(texmf.instance,name,"pk") end)
+ callback.register('find_sfd_file' , function(name) return input.findbinfile(texmf.instance,name,"sfd") end)
+ callback.register('find_truetype_file' , function(name) return input.findbinfile(texmf.instance,name,"ttf") end)
+ callback.register('find_type1_file' , function(name) return input.findbinfile(texmf.instance,name,"pfb") end)
+ callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"vf") end)
+
+ callback.register('read_data_file' , function(file) return input.loadbinfile(texmf.instance,file,"tex") end)
+ callback.register('read_enc_file' , function(file) return input.loadbinfile(texmf.instance,file,"enc") end)
+ callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"tfm") end)
+ -- format
+ -- image
+ callback.register('read_map_file' , function(file) return input.loadbinfile(texmf.instance,file,"map") end)
+ callback.register('read_ocp_file' , function(file) return input.loadbinfile(texmf.instance,file,"ocp") end)
+ callback.register('read_opentype_file' , function(file) return input.loadbinfile(texmf.instance,file,"otf") end)
+ -- output
+ callback.register('read_pk_file' , function(file) return input.loadbinfile(texmf.instance,file,"pk") end)
+ callback.register('read_sfd_file' , function(file) return input.loadbinfile(texmf.instance,file,"sfd") end)
+ callback.register('read_truetype_file' , function(file) return input.loadbinfile(texmf.instance,file,"ttf") end)
+ callback.register('read_type1_file' , function(file) return input.loadbinfile(texmf.instance,file,"pfb") end)
+ callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"vf" ) end)
+ end
+
+ if callback and environment.aleph_mode then
+ callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"ofm") end)
+ callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"ofm") end)
+ callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"ovf") end)
+ callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"ovf") end)
+ end
+
+ if callback then
+ callback.register('find_write_file' , function(id,name) return name end)
+ end
+
+ if callback and (not config or (#config == 0)) then
+ callback.register('find_format_file' , function(name) return name end)
+ end
+
+ if callback and false then
+ for k, v in pairs(callback.list()) do
+ if not v then texio.write_nl("callback "..k.." is not set") end
+ end
+ end
+
+ if callback then
+
+ input.start_actions = { }
+ input.stop_actions = { }
+
+ function input.register_start_actions(f) table.insert(input.start_actions, f) end
+ function input.register_stop_actions (f) table.insert(input.stop_actions, f) end
+
+--~ callback.register('start_run', function() for _, a in pairs(input.start_actions) do a() end end)
+--~ callback.register('stop_run' , function() for _, a in pairs(input.stop_actions ) do a() end end)
+
+ end
+
+ if callback and (input.logmode() == 'xml') then
+
+ function input.start_page_number()
+ texio.write_nl("")
+ texio.write_nl("")
+ end
+
+ callback.register('start_page_number' , input.start_page_number)
+ callback.register('stop_page_number' , input.stop_page_number )
+
+ function input.report_output_pages(p,b)
+ texio.write_nl(""..p.."")
+ texio.write_nl(""..b.."")
+ texio.write_nl("")
+ end
+ function input.report_output_log()
+ end
+
+ callback.register('report_output_pages', input.report_output_pages)
+ callback.register('report_output_log' , input.report_output_log )
+
+ function input.start_run()
+ texio.write_nl("")
+ texio.write_nl("")
+ texio.write_nl("")
+ end
+ function input.stop_run()
+ texio.write_nl("")
+ end
+ function input.show_statistics()
+ for k,v in pairs(status.list()) do
+ texio.write_nl("log",""..tostring(v).."")
+ end
+ end
+
+ table.insert(input.start_actions, input.start_run)
+
+ table.insert(input.stop_actions, input.show_statistics)
+ table.insert(input.stop_actions, input.stop_run)
+
+ function input.start_run() for _, a in pairs(input.start_actions) do a() end end
+ function input.stop_run () for _, a in pairs(input.stop_actions ) do a() end end
+
+ callback.register('start_run', input.start_run)
+ callback.register('stop_run' , input.stop_run )
+
+ end
+
+ end
+
+ if kpse then
+
+ function kpse.find_file(filename,filetype,mustexist)
+ return input.find_file(texmf.instance,filename,filetype,mustexist)
+ end
+ function kpse.expand_path(variable)
+ return input.expand_path(texmf.instance,variable)
+ end
+ function kpse.expand_var(variable)
+ return input.expand_var(texmf.instance,variable)
+ end
+ function kpse.expand_braces(variable)
+ return input.expand_braces(texmf.instance,variable)
+ end
+
+ end
+
+end
+
+-- program specific configuration (memory settings and alike)
+
+if texconfig and not texlua then
+
+ if not luatex then luatex = { } end
+
+ luatex.variablenames = {
+ 'main_memory', 'extra_mem_bot', 'extra_mem_top',
+ 'buf_size',
+ 'font_max', 'font_mem_size',
+ 'hash_extra', 'max_strings', 'pool_free', 'pool_size', 'string_vacancies',
+ 'obj_tab_size', 'pdf_mem_size', 'dest_names_size',
+ 'nest_size', 'param_size', 'save_size', 'stack_size',
+ 'trie_size', 'hyph_size',
+ 'ocp_stack_size', 'ocp_list_size', 'ocp_buf_size'
+ }
+
+ function luatex.variables()
+ local t, x = { }, nil
+ for _,v in pairs(luatex.variablenames) do
+ x = input.var_value(texmf.instance,v)
+ if x and x:find("^%d+$") then
+ t[v] = tonumber(x)
+ end
+ end
+ return t
+ end
+
+ function luatex.setvariables(tab)
+ for k,v in pairs(luatex.variables()) do
+ tab[k] = v
+ end
+ end
+
+ if not luatex.variables_set then
+ luatex.setvariables(texconfig)
+ luatex.variables_set = true
+ end
+
+ texconfig.max_print_line = 100000
+
+end
+
+-- some tex basics
+
+if not cs then cs = { } end
+
+function cs.def(k,v)
+ tex.sprint(tex.texcatcodes, "\\def\\" .. k .. "{" .. v .. "}")
+end
+
+function cs.chardef(k,v)
+ tex.sprint(tex.texcatcodes, "\\chardef\\" .. k .. "=" .. v .. "\\relax")
+end
+
+function cs.boolcase(b)
+ if b then tex.write(1) else tex.write(0) end
+end
+
+function cs.testcase(b)
+ if b then
+ tex.sprint(tex.texcatcodes, "\\firstoftwoarguments")
+ else
+ tex.sprint(tex.texcatcodes, "\\secondoftwoarguments")
+ end
+end
diff --git a/tex/context/base/luat-tmp.lua b/tex/context/base/luat-tmp.lua
new file mode 100644
index 000000000..d11ae6329
--- /dev/null
+++ b/tex/context/base/luat-tmp.lua
@@ -0,0 +1,457 @@
+if not modules then modules = { } end modules ['luat-tmp'] = {
+ version = 1.001,
+ comment = "companion to luat-lib.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module deals with caching data. It sets up the paths and
+implements loaders and savers for tables. Best is to set the
+following variable. When not set, the usual paths will be
+checked. Personally I prefer the (users) temporary path.
Currently we do no locking when we write files. This is no real
+problem because most caching involves fonts and the chance of them
+being written at the same time is small. We also need to extend
+luatools with a recache feature.
+--ldx]]--
+
+cache = cache or { }
+dir = dir or { }
+texmf = texmf or { }
+
+cache.path = nil
+cache.base = cache.base or "luatex-cache"
+cache.more = cache.more or "context"
+cache.direct = false -- true is faster but may need huge amounts of memory
+cache.trace = false
+cache.tree = false
+cache.temp = os.getenv("TEXMFCACHE") or os.getenv("HOME") or os.getenv("HOMEPATH") or os.getenv("VARTEXMF") or os.getenv("TEXMFVAR") or os.getenv("TMP") or os.getenv("TEMP") or os.getenv("TMPDIR") or nil
+cache.paths = { cache.temp }
+
+if not cache.temp then
+ print("\nFATAL ERROR: NO VALID TEMPORARY PATH\n")
+ os.exit()
+end
+
+function cache.configpath(instance)
+ return input.expand_var(instance,"TEXMFCNF")
+end
+
+function cache.treehash(instance)
+ local tree = cache.configpath(instance)
+ if not tree or tree == "" then
+ return false
+ else
+ return md5.hex(tree)
+ end
+end
+
+function cache.setpath(instance,...)
+ if not cache.path then
+ if lfs and instance then
+ for _,v in pairs(cache.paths) do
+ for _,vv in pairs(input.expanded_path_list(instance,v)) do
+ if lfs.isdir(vv) then
+ cache.path = vv
+ break
+ end
+ end
+ if cache.path then break end
+ end
+ end
+ if not cache.path then
+ cache.path = cache.temp
+ end
+ if lfs then
+ cache.tree = cache.tree or cache.treehash(instance)
+ if cache.tree then
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more,cache.tree)
+ else
+ cache.path = dir.mkdirs(cache.path,cache.base,cache.more)
+ end
+ end
+ end
+ if not cache.path then
+ cache.path = '.'
+ end
+ cache.path = input.clean_path(cache.path)
+ if lfs and not table.is_empty({...}) then
+ local pth = dir.mkdirs(cache.path,...)
+ return pth
+ end
+ return cache.path
+end
+
+function cache.setluanames(path,name)
+ return path .. "/" .. name .. ".tma", path .. "/" .. name .. ".tmc"
+end
+
+function cache.loaddata(path,name)
+ local tmaname, tmcname = cache.setluanames(path,name)
+ local loader = loadfile(tmcname) or loadfile(tmaname)
+ if loader then
+ return loader()
+ else
+ return false
+ end
+end
+
+function cache.is_writable(filepath,filename)
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ return file.is_writable(tmaname)
+end
+
+function cache.savedata(filepath,filename,data,raw) -- raw needed for file cache
+ local tmaname, tmcname = cache.setluanames(filepath,filename)
+ local reduce, simplify = true, true
+ if raw then
+ reduce, simplify = false, false
+ end
+ if cache.direct then
+ file.savedata(tmaname, table.serialize(data,'return',true,true))
+ else
+ table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
+ end
+ utils.lua.compile(tmaname, tmcname)
+end
+
+-- here we use the cache for format loading (texconfig.[formatname|jobname])
+
+if tex and texconfig and texconfig.formatname and texconfig.formatname == "" then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end
+ texconfig.formatname = cache.setpath(instance,"format") .. "/" .. texconfig.luaname:gsub("%.lu.$",".fmt")
+end
+
+--[[ldx--
+
Once we found ourselves defining similar cache constructs
+several times, containers were introduced. Containers are used
+to collect tables in memory and reuse them when possible based
+on (unique) hashes (to be provided by the calling function).
+
+
Caching to disk is disabled by default. Version numbers are
+stored in the saved table which makes it possible to change the
+table structures without bothering about the disk cache.
+
+
Examples of usage can be found in the font related code.
+--ldx]]--
+
+containers = { }
+containers.trace = false
+
+do -- local report
+
+ local function report(container,tag,name)
+ if cache.trace or containers.trace or container.trace then
+ logs.report(string.format("%s cache",container.subcategory),string.format("%s: %s",tag,name or 'invalid'))
+ end
+ end
+
+ function containers.define(category, subcategory, version, enabled)
+ if category and subcategory then
+ return {
+ category = category,
+ subcategory = subcategory,
+ storage = { },
+ enabled = enabled,
+ version = version or 1.000,
+ trace = false,
+ path = cache.setpath(texmf.instance,category,subcategory),
+ }
+ else
+ return nil
+ end
+ end
+
+ function containers.is_usable(container, name)
+ return container.enabled and cache.is_writable(container.path, name)
+ end
+
+ function containers.is_valid(container, name)
+ if name and name ~= "" then
+ local cs = container.storage[name]
+ return cs and not table.is_empty(cs) and cs.cache_version == container.version
+ else
+ return false
+ end
+ end
+
+ function containers.read(container,name)
+ if container.enabled and not container.storage[name] then
+ container.storage[name] = cache.loaddata(container.path,name)
+ if containers.is_valid(container,name) then
+ report(container,"loaded",name)
+ else
+ container.storage[name] = nil
+ end
+ end
+ if container.storage[name] then
+ report(container,"reusing",name)
+ end
+ return container.storage[name]
+ end
+
+ function containers.write(container, name, data)
+ if data then
+ data.cache_version = container.version
+ if container.enabled then
+ local unique, shared = data.unique, data.shared
+ data.unique, data.shared = nil, nil
+ cache.savedata(container.path, name, data)
+ report(container,"saved",name)
+ data.unique, data.shared = unique, shared
+ end
+ report(container,"stored",name)
+ container.storage[name] = data
+ end
+ return data
+ end
+
+ function containers.content(container,name)
+ return container.storage[name]
+ end
+
+end
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+input.usecache = true
+
+function input.aux.save_data(instance, dataname, check)
+ for cachename, files in pairs(instance[dataname]) do
+ local name
+ if input.usecache then
+ name = file.join(cache.setpath(instance,"trees"),md5.hex(cachename))
+ else
+ name = file.join(cachename,dataname)
+ end
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ input.report("preparing " .. dataname .. " in", luaname)
+ for k, v in pairs(files) do
+ if not check or check(v,k) then -- path, name
+ if #v == 1 then
+ files[k] = v[1]
+ end
+ else
+ files[k] = nil -- false
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = input.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ -- f:write(table.serialize(data,'return'))
+ f:write(input.serialize(data))
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.serialize(files)
+ -- This version is somewhat optimized for the kind of
+ -- tables that we deal with, so it's much faster than
+ -- the generic serializer. This makes sense because
+ -- luatools and mtxtools are called frequently. Okay,
+ -- we pay a small price for properly tabbed tables.
+ local t = { }
+ local concat = table.concat
+ local sorted = table.sortedkeys
+ local function dump(k,v,m)
+ if type(v) == 'string' then
+ return m .. "['" .. k .. "']='" .. v .. "',"
+ elseif #v == 1 then
+ return m .. "['" .. k .. "']='" .. v[1] .. "',"
+ else
+ return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
+ end
+ end
+ t[#t+1] = "return {"
+ if instance.sortdata then
+ for _, k in pairs(sorted(files)) do
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sorted(fk)) do
+ t[#t+1] = dump(kk,fk[kk],"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,fk,"\t")
+ end
+ end
+ else
+ for k, v in pairs(files) do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in pairs(v) do
+ t[#t+1] = dump(kk,vv,"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,v,"\t")
+ end
+ end
+ end
+ t[#t+1] = "}"
+ return concat(t,"\n")
+end
+
+function input.aux.load_data(instance,pathname,dataname,filename)
+ local luaname, lucname, pname, fname
+ if input.usecache then
+ pname, fname = cache.setpath(instance,"trees"), md5.hex(pathname)
+ filename = file.join(pname,fname)
+ else
+ if not filename or (filename == "") then
+ filename = dataname
+ end
+ pname, fname = pathname, filename
+ end
+ luaname = file.join(pname,fname) .. input.luasuffix
+ lucname = file.join(pname,fname) .. input.lucsuffix
+ local blob = loadfile(lucname)
+ if not blob then
+ blob = loadfile(luaname)
+ end
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == input.cacheversion then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ end
+end
+
+-- we will make a better format, maybe something xml or just text
+
+input.automounted = input.automounted or { }
+
+function input.automount(instance,usecache)
+ local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { cache.setpath(instance,"mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ input.starttiming(instance)
+ for k, root in pairs(mountpaths) do
+ local f = io.open(root.."/url.tmi")
+ if f then
+ for line in f:lines() do
+ if line then
+ if line:find("^[%%#%-]") then -- or %W
+ -- skip
+ elseif line:find("^zip://") then
+ input.report("mounting",line)
+ table.insert(input.automounted,line)
+ input.usezipfile(instance,line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ input.stoptiming(instance)
+ end
+end
+
+-- store info in format
+
+input.storage = { }
+input.storage.data = { }
+input.storage.min = 0 -- 500
+input.storage.max = input.storage.min - 1
+input.storage.trace = false -- true
+input.storage.done = 0
+input.storage.evaluators = { }
+-- (evaluate,message,names)
+
+function input.storage.register(...)
+ input.storage.data[#input.storage.data+1] = { ... }
+end
+
+function input.storage.evaluate(name)
+ input.storage.evaluators[#input.storage.evaluators+1] = name
+end
+
+function input.storage.finalize() -- we can prepend the string with "evaluate:"
+ for _, t in ipairs(input.storage.evaluators) do
+ for i, v in pairs(t) do
+ if type(v) == "string" then
+ t[i] = loadstring(v)()
+ elseif type(v) == "table" then
+ for _, vv in pairs(v) do
+ if type(vv) == "string" then
+ t[i] = loadstring(vv)()
+ end
+ end
+ end
+ end
+ end
+end
+
+function input.storage.dump()
+ for name, data in ipairs(input.storage.data) do
+ local evaluate, message, original, target = data[1], data[2], data[3] ,data[4]
+ local name, initialize, finalize = nil, "", ""
+ for str in string.gmatch(target,"([^%.]+)") do
+ if name then
+ name = name .. "." .. str
+ else
+ name = str
+ end
+ initialize = string.format("%s %s = %s or {} ", initialize, name, name)
+ end
+ if evaluate then
+ finalize = "input.storage.evaluate(" .. name .. ")"
+ end
+ input.storage.max = input.storage.max + 1
+ if input.storage.trace then
+ logs.report('storage',string.format('saving %s in slot %s',message,input.storage.max))
+ lua.bytecode[input.storage.max] = loadstring(
+ initialize ..
+ string.format("logs.report('storage','restoring %s from slot %s') ",message,input.storage.max) ..
+ table.serialize(original,name) ..
+ finalize
+ )
+ else
+ lua.bytecode[input.storage.max] = loadstring(initialize .. table.serialize(original,name) .. finalize)
+ end
+ end
+end
+
+if lua.bytecode then -- from 0 upwards
+ local i = input.storage.min
+ while lua.bytecode[i] do
+ lua.bytecode[i]()
+ lua.bytecode[i] = nil
+ i = i + 1
+ end
+ input.storage.done = i
+end
diff --git a/tex/context/base/luat-tra.lua b/tex/context/base/luat-tra.lua
new file mode 100644
index 000000000..90756d359
--- /dev/null
+++ b/tex/context/base/luat-tra.lua
@@ -0,0 +1,82 @@
+-- filename : luat-tra.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-tra'] = 1.001
+
+debugger = { }
+
+debugger.counters = { }
+debugger.names = { }
+
+function debugger.hook()
+ local f = debug.getinfo(2,"f").func
+ if debugger.counters[f] == nil then
+ debugger.counters[f] = 1
+ debugger.names[f] = debug.getinfo(2,"Sn")
+ else
+ debugger.counters[f] = debugger.counters[f] + 1
+ end
+end
+
+function debugger.getname(func)
+ local n = debugger.names[func]
+ if n.what == "C" then
+ return n.name
+ end
+ local lc = string.format("[%s]:%s", n.short_src, n.linedefined)
+ if n.namewhat ~= "" then
+ return string.format("%s (%s)", lc, n.name)
+ else
+ return lc
+ end
+end
+
+function debugger.showstats(printer,threshold)
+ if not printer then printer = print end
+ if not threshold then threshold = 0 end
+ for func, count in pairs(debugger.counters) do
+ if count > threshold then
+ printer(string.format("%8i %s\n", count, debugger.getname(func)))
+ end
+ end
+end
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(debugger.hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ debugger.counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ return tonumber((os.env['MTX.TRACE.CALLS'] or os.env['MTX_TRACE_CALLS'] or 0)) > 0
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
diff --git a/tex/context/base/luat-uni.lua b/tex/context/base/luat-uni.lua
new file mode 100644
index 000000000..06945ed2b
--- /dev/null
+++ b/tex/context/base/luat-uni.lua
@@ -0,0 +1,23 @@
+-- filename : luat-uni.lua
+-- comment : companion to luat-uni.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-uni'] = 1.001
+
+function unicode.utf8.split(str)
+ lst = { }
+ -- for snippet in unicode.utf8.gfind(str,".") do
+ for snippet in string.utfcharacters(str) do
+ table.insert(lst,snippet)
+ end
+ return lst
+end
+
+function unicode.utf8.each(str,fnc)
+ -- for snippet in unicode.utf8.gfind(str,".") do
+ for snippet in string.utfcharacters(str) do
+ fnc(snippet)
+ end
+end
diff --git a/tex/context/base/luat-uni.tex b/tex/context/base/luat-uni.tex
new file mode 100644
index 000000000..453c8e0d8
--- /dev/null
+++ b/tex/context/base/luat-uni.tex
@@ -0,0 +1,33 @@
+%D \module
+%D [ file=luat-uni,
+%D version=2006.04.25,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=Unicode Support,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\writestatus{loading}{Lua Support Macros (unicode)}
+
+\registerctxluafile{luat-uni}{1.001}
+
+% \defconvertedargument\ascii{ÀÁÂÃÄÅàáâãäå}
+%
+% \lua{ tex.print ("\ascii")}
+% \lua{ tex.print(unicode.utf8.reverse ("\ascii"))}
+% \lua{ tex.print(unicode.utf8.lower ("\ascii"))}
+% \lua{ tex.print(unicode.utf8.upper ("\ascii"))}
+% \lua{ tex.print(unicode.utf8.len ("\ascii"))}
+% \lua{ tex.print(table.getn(unicode.utf8.split("\ascii"))}}
+%
+% \lua{unicode.utf8.each("\ascii", function(chr) tex.print("["..chr.."]") end)}
+
+\let\UnicodeOne \gobbleoneargument
+\let\UnicodeTwo \gobbleoneargument
+\let\UnicodeThree\gobbleoneargument
+
+\endinput
diff --git a/tex/context/base/luat-zip.lua b/tex/context/base/luat-zip.lua
new file mode 100644
index 000000000..fbf00a47e
--- /dev/null
+++ b/tex/context/base/luat-zip.lua
@@ -0,0 +1,221 @@
+-- filename : luat-zip.lua
+-- comment : companion to luat-lib.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['luat-zip'] = 1.001
+
+if zip and input then
+ zip.supported = true
+else
+ zip = { }
+ zip.supported = false
+end
+
+if not zip.supported then
+
+ if not input then input = { } end -- will go away
+
+ function zip.openarchive (...) return nil end -- needed ?
+ function zip.closenarchive (...) end -- needed ?
+ function input.registerzipfile (...) end -- needed ?
+ function input.usezipfile (...) end -- needed ?
+
+else
+
+ function input.locators.zip(instance,specification)
+ local name, spec = specification:match("^(.-)##(.-)$")
+ local f = io.open(name or specification)
+ if f then -- todo: reuse code
+ input.logger('! zip locator', specification..' found')
+ if name and spec then
+ input.aux.append_hash(instance,'zip',"zip##"..specification,name)
+ input.aux.extend_texmf_var(instance, "zip##"..specification)
+ else
+ input.aux.append_hash(instance,'zip',"zip##"..specification.."##",specification)
+ input.aux.extend_texmf_var(instance, "zip##"..specification.."##")
+ end
+ f:close()
+ else
+ input.logger('? zip locator', specification..' not found')
+ end
+ end
+
+ function input.hashers.zip(instance,tag,name)
+ input.report("loading zip file",name,"as",tag)
+ input.registerzipfile(instance,name,tag)
+ end
+
+ function input.concatinators.zip(tag,path,name)
+ return tag .. path .. '/' .. name
+ end
+
+ function input.is_readable.zip(name)
+ return true
+ end
+
+ function input.finders.zip(instance,filename,filetype)
+ local archive, dataname = filename:match("^(.+)##/*(.+)$")
+ if archive and dataname then
+ local zfile = zip.openarchive(archive)
+ if not zfile then
+ archive = input.find_file(instance,archive,filetype)
+ zfile = zip.openarchive(archive)
+ end
+ if zfile then
+ input.logger('! zip finder',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ dfile = zfile:close()
+ input.logger('+ zip finder',filename)
+ return 'zip##' .. filename
+ end
+ else
+ input.logger('? zip finder',archive)
+ end
+ end
+ input.logger('- zip finder',filename)
+ return unpack(input.finders.notfound)
+ end
+
+ function input.openers.zip(instance,filename)
+ if filename and filename ~= "" then
+ local archive, dataname = filename:match("^(.-)##/*(.+)$")
+ if archive and dataname then
+ local zfile= zip.openarchive(archive)
+ if zfile then
+ input.logger('+ zip starter',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ input.show_open(filename)
+ return input.openers.text_opener(filename,dfile,'zip')
+ end
+ else
+ input.logger('- zip starter',archive)
+ end
+ end
+ end
+ input.logger('- zip opener',filename)
+ return unpack(input.openers.notfound)
+ end
+
+ function input.loaders.zip(instance, filename) -- we could use input.openers.zip
+ if filename and filename ~= "" then
+ input.logger('= zip loader',filename)
+ local archive, dataname = filename:match("^(.+)##/*(.+)$")
+ if archive and dataname then
+ local zfile = zip.openarchive(archive)
+ if zfile then
+ input.logger('= zip starter',archive)
+ local dfile = zfile:open(dataname)
+ if dfile then
+ input.show_load(filename)
+ input.logger('+ zip loader',filename)
+ local s = dfile:read("*all")
+ dfile:close()
+ return true, s, #s
+ end
+ else
+ input.logger('- zip starter',archive)
+ end
+ end
+ end
+ input.logger('- zip loader',filename)
+ return unpack(input.loaders.notfound)
+ end
+
+ zip.archives = { }
+ zip.registeredfiles = { }
+
+ function zip.openarchive(name)
+ if name and name ~= "" and not zip.archives[name] then
+ zip.archives[name] = zip.open(name)
+ end
+ return zip.archives[name]
+ end
+
+ function zip.closearchive(name)
+ if zip.archives[name] then
+ zip.close(archives[name])
+ zip.archives[name] = nil
+ end
+ end
+
+ -- aparte register maken voor user (register tex / zip), runtime tree register
+ -- todo: alleen url syntax toestaan
+ -- user function: also handle zip::name::path
+
+ function input.usezipfile(instance,zipname) -- todo zip://
+ zipname = input.normalize_name(zipname)
+ if not zipname:find("^zip##") then
+ zipname = "zip##"..zipname
+ end
+ input.logger('! zip user','file '..zipname)
+ if not zipname:find("^zip##(.+)##(.-)$") then
+ zipname = zipname .. "##" -- dummy spec
+ end
+ local tag = zipname
+ local name = zipname:match("zip##(.+)##.-")
+ input.aux.prepend_hash(instance,'zip',tag,name)
+ input.aux.extend_texmf_var(instance, tag)
+ input.registerzipfile(instance,name,tag)
+ end
+
+ function input.registerzipfile(instance,zipname,tag)
+ if not zip.registeredfiles[zipname] then
+ input.start_timing(instance)
+ local z = zip.open(zipname)
+ if not z then
+ zipname = input.find_file(instance,zipname)
+ z = zip.open(zipname)
+ end
+ if z then
+ input.logger("= zipfile","registering "..zipname)
+ zip.registeredfiles[zipname] = z
+ input.aux.register_zip_file(instance,zipname,tag)
+ else
+ input.logger("? zipfile","unknown "..zipname)
+ end
+ input.stop_timing(instance)
+ end
+ end
+
+ function input.aux.register_zip_file(instance,zipname,tagname)
+ if zip.registeredfiles[zipname] then
+ if not tagname:find("^zip##") then
+ tagname = "zip##" .. tagname
+ end
+ local path, name, n = nil, nil, 0
+ if not instance.files[tagname] then
+ instance.files[tagname] = { }
+ end
+ local files, filter = instance.files[tagname], ""
+ local subtree = tagname:match("^zip##.+##(.+)$")
+ if subtree then
+ filter = "^"..subtree.."/(.+)/(.-)$"
+ else
+ filter = "^(.+)/(.-)$"
+ end
+ input.logger('= zip filter',filter)
+ -- we can consider putting a files.luc in the file
+ local register = input.aux.register_file
+ for i, _ in zip.registeredfiles[zipname]:files() do
+ path, name = i.filename:match(filter)
+ if path then
+ if name and name ~= '' then
+ register(files, name, path)
+ n = n + 1
+ else
+ -- directory
+ end
+ else
+ register(files, i.filename, '')
+ n = n + 1
+ end
+ end
+ input.report(n, 'entries in', zipname)
+ end
+ end
+
+end
diff --git a/tex/context/base/meta-pdf.lua b/tex/context/base/meta-pdf.lua
new file mode 100644
index 000000000..56e09bb1f
--- /dev/null
+++ b/tex/context/base/meta-pdf.lua
@@ -0,0 +1,585 @@
+-- filename : meta-pdf.lua
+-- comment : companion to meta-pdf.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- This is the third version. Version 1 converted to Lua code,
+-- version 2 gsubbed the file into TeX code, and version 3 uses
+-- the new lpeg functionality and streams the result into TeX.
+
+--~ old lpeg 0.4 lpeg 0.5
+--~ 100 times test graphic 2.45 (T:1.07) 0.72 (T:0.24) 0.580 (0.560 no table) -- 0.54 optimized for one space (T:0.19)
+--~ 100 times big graphic 10.44 4.30/3.35 nogb 2.914 (2.050 no table) -- 1.99 optimized for one space (T:0.85)
+--~ 500 times test graphic T:1.29 T:1.16 (T:1.10 no table) -- T:1.10
+
+if not versions then versions = { } end versions['meta-pdf'] = 1.003
+
+mptopdf = { }
+mptopdf.parsers = { }
+mptopdf.parser = 'none'
+
+function mptopdf.reset()
+ mptopdf.data = ""
+ mptopdf.path = { }
+ mptopdf.stack = { }
+ mptopdf.texts = { }
+ mptopdf.version = 0
+ mptopdf.shortcuts = false
+ mptopdf.resetpath()
+end
+
+function mptopdf.resetpath()
+ mptopdf.stack.close = false
+ mptopdf.stack.path = { }
+ mptopdf.stack.concat = nil
+ mptopdf.stack.special = false
+end
+
+mptopdf.reset()
+
+function mptopdf.parsers.none()
+ -- no parser set
+end
+
+function mptopdf.parse()
+ mptopdf.parsers[mptopdf.parser]()
+end
+
+-- shared code
+
+mptopdf.steps = { }
+
+mptopdf.descapes = {
+ ['('] = "\\\\char40 ",
+ [')'] = "\\\\char41 ",
+ ['"'] = "\\\\char92 "
+}
+
+function mptopdf.descape(str)
+ str = str:gsub("\\(%d%d%d)",function(n)
+ return "\\char" .. tonumber(n,8) .. " "
+ end)
+ return str:gsub("\\([%(%)\\])",mptopdf.descapes)
+end
+
+-- old code
+
+function mptopdf.steps.descape(str)
+ str = str:gsub("\\(%d%d%d)",function(n)
+ return "\\\\char" .. tonumber(n,8) .. " "
+ end)
+ return str:gsub("\\([%(%)\\])",mptopdf.descapes)
+end
+
+function mptopdf.steps.strip() -- .3 per expr
+ mptopdf.data = mptopdf.data:gsub("^(.-)%%+Page:.-%c+(.*)%s+%a+%s+%%+EOF.*$", function(preamble, graphic)
+ local bbox = "0 0 0 0"
+ for b in preamble:gmatch("%%%%%a+oundingBox: +(.-)%c+") do
+ bbox = b
+ end
+ local name, version = preamble:gmatch("%%%%Creator: +(.-) +(.-) ")
+ mptopdf.version = tostring(version or "0")
+ if preamble:find("/hlw{0 dtransform") then
+ mptopdf.shortcuts = true
+ end
+ -- the boundingbox specification needs to come before data, well, not really
+ return bbox .. " boundingbox\n" .. "\nbegindata\n" .. graphic .. "\nenddata\n"
+ end, 1)
+ mptopdf.data = mptopdf.data:gsub("%%%%MetaPostSpecials: +(.-)%c+", "%1 specials\n", 1)
+ mptopdf.data = mptopdf.data:gsub("%%%%MetaPostSpecial: +(.-)%c+", "%1 special\n")
+ mptopdf.data = mptopdf.data:gsub("%%.-%c+", "")
+end
+
+function mptopdf.steps.cleanup()
+ if not mptopdf.shortcuts then
+ mptopdf.data = mptopdf.data:gsub("gsave%s+fill%s+grestore%s+stroke", "both")
+ mptopdf.data = mptopdf.data:gsub("([%d%.]+)%s+([%d%.]+)%s+dtransform%s+exch%s+truncate%s+exch%s+idtransform%s+pop%s+setlinewidth", function(wx,wy)
+ if tonumber(wx) > 0 then return wx .. " setlinewidth" else return wy .. " setlinewidth" end
+ end)
+ mptopdf.data = mptopdf.data:gsub("([%d%.]+)%s+([%d%.]+)%s+dtransform%s+truncate%s+idtransform%s+setlinewidth%s+pop", function(wx,wy)
+ if tonumber(wx) > 0 then return wx .. " setlinewidth" else return wy .. " setlinewidth" end
+ end)
+ end
+end
+
+function mptopdf.steps.convert()
+ mptopdf.data = mptopdf.data:gsub("%c%((.-)%) (.-) (.-) fshow", function(str,font,scale)
+ table.insert(mptopdf.texts,{mptopdf.steps.descape(str), font, scale})
+ return "\n" .. #mptopdf.texts .. " textext"
+ end)
+ mptopdf.data = mptopdf.data:gsub("%[%s*(.-)%s*%]", function(str)
+ return str:gsub("%s+"," ")
+ end)
+ local t
+ mptopdf.data = mptopdf.data:gsub("%s*([^%a]-)%s*(%a+)", function(args,cmd)
+ if cmd == "textext" then
+ t = mptopdf.texts[tonumber(args)]
+ return "mp.textext(" .. "\"" .. t[2] .. "\"," .. t[3] .. ",\"" .. t[1] .. "\")\n"
+ else
+ return "mp." .. cmd .. "(" .. args:gsub(" +",",") .. ")\n"
+ end
+ end)
+end
+
+function mptopdf.steps.process()
+ assert(loadstring(mptopdf.data))() -- () runs the loaded chunk
+end
+
+function mptopdf.parsers.gsub()
+ mptopdf.steps.strip()
+ mptopdf.steps.cleanup()
+ mptopdf.steps.convert()
+ mptopdf.steps.process()
+end
+
+-- end of old code
+
+-- from lua to tex
+
+function mptopdf.pdfcode(str)
+ tex.sprint(tex.ctxcatcodes,"\\PDFcode{" .. str .. "}")
+end
+
+function mptopdf.texcode(str)
+ tex.sprint(tex.ctxcatcodes,str)
+end
+
+-- auxiliary functions
+
+function mptopdf.flushconcat()
+ if mptopdf.stack.concat then
+ mptopdf.pdfcode(table.concat(mptopdf.stack.concat," ") .. " cm")
+ mptopdf.stack.concat = nil
+ end
+end
+
+function mptopdf.flushpath(cmd)
+ if #mptopdf.stack.path > 0 then
+ local path = { }
+ if mptopdf.stack.concat then
+ local sx, sy = mptopdf.stack.concat[1], mptopdf.stack.concat[4]
+ local rx, ry = mptopdf.stack.concat[2], mptopdf.stack.concat[3]
+ local tx, ty = mptopdf.stack.concat[5], mptopdf.stack.concat[6]
+ local d = (sx*sy) - (rx*ry)
+ local function concat(px, py)
+ return (sy*(px-tx)-ry*(py-ty))/d, (sx*(py-ty)-rx*(px-tx))/d
+ end
+ for _,v in pairs(mptopdf.stack.path) do
+ v[1],v[2] = concat(v[1],v[2])
+ if #v == 7 then
+ v[3],v[4] = concat(v[3],v[4])
+ v[5],v[6] = concat(v[5],v[6])
+ end
+ table.insert(path, table.concat(v," "))
+ end
+ else
+ for _,v in pairs(mptopdf.stack.path) do
+ table.insert(path, table.concat(v," "))
+ end
+ end
+ mptopdf.flushconcat()
+ mptopdf.texcode("\\MPSpath{" .. table.concat(path," ") .. "}")
+ if mptopdf.stack.close then
+ mptopdf.texcode("\\MPScode{h " .. cmd .. "}")
+ else
+ mptopdf.texcode("\\MPScode{" .. cmd .."}")
+ end
+ end
+ mptopdf.resetpath()
+end
+
+if texmf and texmf.instance then
+ function mptopdf.loaded(name)
+ local ok, n
+ mptopdf.reset()
+ ok, mptopdf.data, n = input.loadbinfile(texmf.instance, name, 'tex') -- we need a binary load !
+ return ok
+ end
+else
+ function mptopdf.loaded(name)
+ local f = io.open(name, 'rb')
+ if f then
+ mptopdf.reset()
+ mptopdf.data = f:read('*all')
+ f:close()
+ return true
+ else
+ return false
+ end
+ end
+end
+
+if not mptopdf.parse then
+ function mptopdf.parse() end -- forward declaration
+end
+
+function mptopdf.convertmpstopdf(name)
+ if mptopdf.loaded(name) then
+ garbagecollector.push()
+ input.start_timing(mptopdf)
+ mptopdf.parse()
+ mptopdf.reset()
+ input.stop_timing(mptopdf)
+ garbagecollector.pop()
+ else
+ tex.print("file " .. name .. " not found")
+ end
+end
+
+-- mp interface
+
+mp = { }
+
+function mp.creator(a, b, c)
+ mptopdf.version = tonumber(b)
+end
+
+function mp.creationdate(a)
+ mptopdf.date= a
+end
+
+function mp.newpath()
+ mptopdf.stack.path = { }
+end
+
+function mp.boundingbox(llx, lly, urx, ury)
+ mptopdf.texcode("\\MPSboundingbox{" .. llx .. "}{" .. lly .. "}{" .. urx .. "}{" .. ury .. "}")
+end
+
+function mp.moveto(x,y)
+ mptopdf.stack.path[#mptopdf.stack.path+1] = {x,y,"m"}
+end
+
+function mp.curveto(ax, ay, bx, by, cx, cy)
+ mptopdf.stack.path[#mptopdf.stack.path+1] = {ax,ay,bx,by,cx,cy,"c"}
+end
+
+function mp.lineto(x,y)
+ mptopdf.stack.path[#mptopdf.stack.path+1] = {x,y,"l"}
+end
+
+function mp.rlineto(x,y)
+ local dx, dy = 0, 0
+ if #mptopdf.stack.path > 0 then
+ dx, dy = mptopdf.stack.path[#mptopdf.stack.path][1], mptopdf.stack.path[#mptopdf.stack.path][2]
+ end
+ mptopdf.stack.path[#mptopdf.stack.path+1] = {dx,dy,"l"}
+end
+
+function mp.translate(tx,ty)
+ mptopdf.pdfcode("1 0 0 0 1 " .. tx .. " " .. ty .. " cm")
+end
+
+function mp.scale(sx,sy)
+ mptopdf.stack.concat = {sx,0,0,sy,0,0}
+end
+
+function mp.concat(sx, rx, ry, sy, tx, ty)
+ mptopdf.stack.concat = {sx,rx,ry,sy,tx,ty}
+end
+
+function mp.setlinejoin(d)
+ mptopdf.pdfcode(d .. " j")
+end
+
+function mp.setlinecap(d)
+ mptopdf.pdfcode(d .. " J")
+end
+
+function mp.setmiterlimit(d)
+ mptopdf.pdfcode(d .. " M")
+end
+
+function mp.gsave()
+ mptopdf.pdfcode("q")
+end
+
+function mp.grestore()
+ mptopdf.pdfcode("Q")
+end
+
+function mp.setdash(...)
+ local n = select("#",...)
+ mptopdf.pdfcode("[" .. table.concat({...}," ",1,n-1) .. "] " .. select(n,...) .. " d")
+end
+
+function mp.resetdash()
+ mptopdf.pdfcode("[ ] 0 d")
+end
+
+function mp.setlinewidth(d)
+ mptopdf.pdfcode(d .. " w")
+end
+
+function mp.closepath()
+ mptopdf.stack.close = true
+end
+
+function mp.fill()
+ mptopdf.flushpath('f')
+end
+
+function mp.stroke()
+ mptopdf.flushpath('S')
+end
+
+function mp.both()
+ mptopdf.flushpath('B')
+end
+
+function mp.clip()
+ mptopdf.flushpath('W n')
+end
+
+function mp.textext(font, scale, str) -- old parser
+ local dx, dy = 0, 0
+ if #mptopdf.stack.path > 0 then
+ dx, dy = mptopdf.stack.path[1][1], mptopdf.stack.path[1][2]
+ end
+ mptopdf.flushconcat()
+ mptopdf.texcode("\\MPStextext{"..font.."}{"..scale.."}{"..str.."}{"..dx.."}{"..dy.."}")
+ mptopdf.resetpath()
+end
+
+function mp.fshow(str,font,scale) -- lpeg parser
+ mp.textext(font,scale,mptopdf.descape(str))
+--~ local dx, dy = 0, 0
+--~ if #mptopdf.stack.path > 0 then
+--~ dx, dy = mptopdf.stack.path[1][1], mptopdf.stack.path[1][2]
+--~ end
+--~ mptopdf.flushconcat()
+--~ mptopdf.texcode("\\MPStextext{"..font.."}{"..scale.."}{"..mptopdf.descape(str).."}{"..dx.."}{"..dy.."}")
+--~ mptopdf.resetpath()
+end
+
+
+--~ function mp.handletext(font,scale.str,dx,dy)
+--~ local one, two = string.match(str, "^(%d+)::::(%d+)")
+--~ if one and two then
+--~ mptopdf.texcode("\\MPTOPDFtextext{"..font.."}{"..scale.."}{"..one.."}{"..two.."}{"..dx.."}{"..dy.."}")
+--~ else
+--~ mptopdf.texcode("\\MPTOPDFtexcode{"..font.."}{"..scale.."}{"..str.."}{"..dx.."}{"..dy.."}")
+--~ end
+--~ end
+
+function mp.setrgbcolor(r,g,b) -- extra check
+ r, g = tonumber(r), tonumber(g) -- needed when we use lpeg
+ if r == 0.0123 and g < 0.01 then
+ mptopdf.texcode("\\MPSspecial{" .. g*10000 .. "}{" .. b*10000 .. "}")
+ elseif r == 0.123 and r < 0.1 then
+ mptopdf.texcode("\\MPSspecial{" .. g* 1000 .. "}{" .. b* 1000 .. "}")
+ else
+ mptopdf.texcode("\\MPSrgb{" .. r .. "}{" .. g .. "}{" .. b .. "}")
+ end
+end
+
+function mp.setcmykcolor(c,m,y,k)
+ mptopdf.texcode("\\MPScmyk{" .. c .. "}{" .. m .. "}{" .. y .. "}{" .. k .. "}")
+end
+
+function mp.setgray(s)
+ mptopdf.texcode("\\MPSgray{" .. s .. "}")
+end
+
+function mp.specials(version,signal,factor) -- 2.0 123 1000
+end
+
+function mp.special(...) -- 7 1 0.5 1 0 0 1 3
+ local n = select("#",...)
+ mptopdf.texcode("\\MPSbegin\\MPSset{" .. table.concat({...},"}\\MPSset{",2,n) .. "}\\MPSend")
+end
+
+function mp.begindata()
+end
+
+function mp.enddata()
+end
+
+function mp.showpage()
+end
+
+mp.n = mp.newpath -- n
+mp.p = mp.closepath -- h
+mp.l = mp.lineto -- l
+mp.r = mp.rlineto -- r
+mp.m = mp.moveto -- m
+mp.c = mp.curveto -- c
+mp.hlw = mp.setlinewidth
+mp.vlw = mp.setlinewidth
+
+mp.C = mp.setcmykcolor -- k
+mp.G = mp.setgray -- g
+mp.R = mp.setrgbcolor -- rg
+
+mp.lj = mp.setlinejoin -- j
+mp.ml = mp.setmiterlimit -- M
+mp.lc = mp.setlinecap -- J
+mp.sd = mp.setdash -- d
+mp.rd = mp.resetdash
+
+mp.S = mp.stroke -- S
+mp.F = mp.fill -- f
+mp.B = mp.both -- B
+mp.W = mp.clip -- W
+
+mp.q = mp.gsave -- q
+mp.Q = mp.grestore -- Q
+
+mp.s = mp.scale -- (not in pdf)
+mp.t = mp.concat -- (not the same as pdf anyway)
+
+mp.P = mp.showpage
+
+-- experimental
+
+function mp.attribute(id,value)
+ mptopdf.texcode("\\attribute " .. id .. "=" .. value .. " ")
+-- mptopdf.texcode("\\dompattribute{" .. id .. "}{" .. value .. "}")
+end
+
+-- lpeg parser
+
+-- The lpeg based parser is rather optimized for the kind of output
+-- that MetaPost produces. It's my first real lpeg code, which may
+-- show. Because the parser binds to functions, we define it last.
+
+do
+
+ local eol = lpeg.S('\r\n')^1
+ local sp = lpeg.P(' ')^1
+ local space = lpeg.S(' \r\n')^1
+ local number = lpeg.S('0123456789.-+')^1
+ local nonspace = lpeg.P(1-lpeg.S(' \r\n'))^1
+
+ local cnumber = lpeg.C(number)
+ local cstring = lpeg.C(nonspace)
+
+ local specials = (lpeg.P("%%MetaPostSpecials:") * sp * (cstring * sp^0)^0 * eol) / mp.specials
+ local special = (lpeg.P("%%MetaPostSpecial:") * sp * (cstring * sp^0)^0 * eol) / mp.special
+ local boundingbox = (lpeg.P("%%BoundingBox:") * sp * (cnumber * sp^0)^4 * eol) / mp.boundingbox
+ local highresboundingbox = (lpeg.P("%%HiResBoundingBox:") * sp * (cnumber * sp^0)^4 * eol) / mp.boundingbox
+
+ local setup = lpeg.P("%%BeginSetup") * (1 - lpeg.P("%%EndSetup") )^1
+ local prolog = lpeg.P("%%BeginProlog") * (1 - lpeg.P("%%EndProlog"))^1
+ local comment = lpeg.P('%')^1 * (1 - eol)^1
+
+ local curveto = ((cnumber * sp)^6 * lpeg.P("curveto") ) / mp.curveto
+ local lineto = ((cnumber * sp)^2 * lpeg.P("lineto") ) / mp.lineto
+ local rlineto = ((cnumber * sp)^2 * lpeg.P("rlineto") ) / mp.rlineto
+ local moveto = ((cnumber * sp)^2 * lpeg.P("moveto") ) / mp.moveto
+ local setrgbcolor = ((cnumber * sp)^3 * lpeg.P("setrgbcolor") ) / mp.setrgbcolor
+ local setcmykcolor = ((cnumber * sp)^4 * lpeg.P("setcmykcolor") ) / mp.setcmykcolor
+ local setgray = ((cnumber * sp)^1 * lpeg.P("setgray") ) / mp.setgray
+ local newpath = ( lpeg.P("newpath") ) / mp.newpath
+ local closepath = ( lpeg.P("closepath") ) / mp.closepath
+ local fill = ( lpeg.P("fill") ) / mp.fill
+ local stroke = ( lpeg.P("stroke") ) / mp.stroke
+ local clip = ( lpeg.P("clip") ) / mp.clip
+ local both = ( lpeg.P("gsave fill grestore")) / mp.both
+ local showpage = ( lpeg.P("showpage") )
+ local setlinejoin = ((cnumber * sp)^1 * lpeg.P("setlinejoin") ) / mp.setlinejoin
+ local setlinecap = ((cnumber * sp)^1 * lpeg.P("setlinecap") ) / mp.setlinecap
+ local setmiterlimit = ((cnumber * sp)^1 * lpeg.P("setmiterlimit") ) / mp.setmiterlimit
+ local gsave = ( lpeg.P("gsave") ) / mp.gsave
+ local grestore = ( lpeg.P("grestore") ) / mp.grestore
+
+ local setdash = (lpeg.P("[") * (cnumber * sp^0)^0 * lpeg.P("]") * sp * cnumber * sp * lpeg.P("setdash")) / mp.setdash
+ local concat = (lpeg.P("[") * (cnumber * sp^0)^6 * lpeg.P("]") * sp * lpeg.P("concat") ) / mp.concat
+ local scale = ( (cnumber * sp^0)^6 * sp * lpeg.P("concat") ) / mp.concat
+
+ local fshow = (lpeg.P("(") * lpeg.C((1-lpeg.P(")"))^1) * lpeg.P(")") * space * lpeg.C(lpeg.P((1-space)^1)) * space * cnumber * space * lpeg.P("fshow")) / mp.fshow
+ local fshow = (lpeg.P("(") * lpeg.C((1-lpeg.P(")"))^1) * lpeg.P(")") * space * cstring * space * cnumber * space * lpeg.P("fshow")) / mp.fshow
+
+ local setlinewidth_x = (lpeg.P("0") * sp * cnumber * sp * lpeg.P("dtransform truncate idtransform setlinewidth pop")) / mp.setlinewidth
+ local setlinewidth_y = (cnumber * sp * lpeg.P("0 dtransform exch truncate exch idtransform pop setlinewidth") ) / mp.setlinewidth
+
+ local c = ((cnumber * sp)^6 * lpeg.P("c") ) / mp.curveto -- ^6 very inefficient, ^1 ok too
+ local l = ((cnumber * sp)^2 * lpeg.P("l") ) / mp.lineto
+ local r = ((cnumber * sp)^2 * lpeg.P("r") ) / mp.rlineto
+ local m = ((cnumber * sp)^2 * lpeg.P("m") ) / mp.moveto
+ local vlw = ((cnumber * sp)^1 * lpeg.P("vlw")) / mp.setlinewidth
+ local hlw = ((cnumber * sp)^1 * lpeg.P("hlw")) / mp.setlinewidth
+
+ local R = ((cnumber * sp)^3 * lpeg.P("R") ) / mp.setrgbcolor
+ local C = ((cnumber * sp)^4 * lpeg.P("C") ) / mp.setcmykcolor
+ local G = ((cnumber * sp)^1 * lpeg.P("G") ) / mp.setgray
+
+ local lj = ((cnumber * sp)^1 * lpeg.P("lj") ) / mp.setlinejoin
+ local ml = ((cnumber * sp)^1 * lpeg.P("ml") ) / mp.setmiterlimit
+ local lc = ((cnumber * sp)^1 * lpeg.P("lc") ) / mp.setlinecap
+
+ local n = lpeg.P("n") / mp.newpath
+ local p = lpeg.P("p") / mp.closepath
+ local S = lpeg.P("S") / mp.stroke
+ local F = lpeg.P("F") / mp.fill
+ local B = lpeg.P("B") / mp.both
+ local W = lpeg.P("W") / mp.clip
+ local P = lpeg.P("P") / mp.showpage
+
+ local q = lpeg.P("q") / mp.gsave
+ local Q = lpeg.P("Q") / mp.grestore
+
+ local sd = (lpeg.P("[") * (cnumber * sp^0)^0 * lpeg.P("]") * sp * cnumber * sp * lpeg.P("sd")) / mp.setdash
+ local rd = ( lpeg.P("rd")) / mp.resetdash
+
+ local s = ( (cnumber * sp^0)^2 * sp * lpeg.P("s") ) / mp.scale
+ local t = (lpeg.P("[") * (cnumber * sp^0)^6 * lpeg.P("]") * sp * lpeg.P("t") ) / mp.concat
+
+ -- experimental
+
+ local attribute = ((cnumber * sp)^2 * lpeg.P("attribute")) / mp.attribute
+ local A = ((cnumber * sp)^2 * lpeg.P("A")) / mp.attribute
+
+
+ local preamble = (
+ prolog + setup +
+ boundingbox + highresboundingbox + specials + special +
+ comment
+ )
+
+ local procset = (
+ lj + ml + lc +
+ c + l + m + n + p + r +
+A +
+ R + C + G +
+ S + F + B + W +
+ vlw + hlw +
+ Q + q +
+ sd + rd +
+ t + s +
+ fshow +
+ P
+ )
+
+ local verbose = (
+ curveto + lineto + moveto + newpath + closepath + rlineto +
+ setrgbcolor + setcmykcolor + setgray +
+attribute +
+ setlinejoin + setmiterlimit + setlinecap +
+ stroke + fill + clip + both +
+ setlinewidth_x + setlinewidth_y +
+ gsave + grestore +
+ concat + scale +
+ fshow +
+ setdash + -- no resetdash
+ showpage
+ )
+
+ -- order matters in terms of speed / we could check for procset first
+
+ local captures_old = ( space + verbose + preamble )^0
+ local captures_new = ( space + procset + preamble + verbose )^0
+
+ function mptopdf.parsers.lpeg()
+ if mptopdf.data:find("%%%%BeginResource: procset mpost") then
+--~ if mptopdf.data:find("/bd{bind def}bind def") then -- bug in mp
+ lpeg.match(captures_new,mptopdf.data)
+ else
+ lpeg.match(captures_old,mptopdf.data)
+ end
+ end
+
+end
+
+mptopdf.parser = 'lpeg'
diff --git a/tex/context/base/meta-pdf.mkiv b/tex/context/base/meta-pdf.mkiv
new file mode 100644
index 000000000..dadf760a6
--- /dev/null
+++ b/tex/context/base/meta-pdf.mkiv
@@ -0,0 +1,155 @@
+%D \module
+%D [ file=meta-pdf,
+%D version=2006.29.09,
+%D title=\CONTEXT\ Support Macros,
+%D subtitle=\METAPOST\ to \PDF\ conversion,
+%D author=Hans Hagen \& others (see text),
+%D date=\currentdate,
+%D copyright=\PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+% Using test case at end of meta-pdf.tex:
+%
+% \useMPgraphic{1}
+% \testfeatureonce{250}{\setbox0\hbox{\convertMPtoPDF{test-mps-mpgraph.1}{1}{1}}}
+%
+% 8.4 : mkii, direct parsing by tex
+% 11.8 : mkiv, dirty conversion (10.8 with dirty tricks)
+% 14.5 : mkiv, clean conversion
+% 7.4 : mkiv, simulated clean direct lua from mp
+% 0.3 : time taken by tex to handle converted code
+
+% Maybe delayed load, only at first call of the converter.
+
+\registerctxluafile{meta-pdf}{1.003}
+
+%D Plugin.
+
+\def\mkconvertMPtoPDF
+ {\vbox\bgroup
+ \forgetall
+ \offinterlineskip
+ %\ifcase\blackoutMPgraphic\or\PDFcode{0 g 0 G}\fi % fixed in mp
+ \setbox\scratchbox\vbox\bgroup
+ \setnormalcatcodes % we can be in verbatim or so
+ \message{[MP to PDF]}%
+ \startMPresources
+ \PDFcomment{mps begin}%
+ \PDFcode{q 1 0 0 1 0 0 cm}%
+ \ctxlua{mptopdf.convertmpstopdf("\MPfilename")}\removeunwantedspaces
+ \PDFcode{Q}%
+ \PDFcomment{mps end}%
+ \stopMPresources
+ \egroup
+ \setbox\scratchbox\hbox\bgroup
+ \hskip-\MPllx\onebasepoint
+ \raise-\MPlly\onebasepoint
+ \box\scratchbox
+ \egroup
+ \setbox\scratchbox\vbox to \MPheight\bgroup
+ \vfill
+ \hsize\MPwidth
+ \smashbox\scratchbox
+ \box\scratchbox
+ \egroup
+ \wd\scratchbox\MPwidth
+ \ht\scratchbox\MPheight
+ \dopackageMPgraphic\scratchbox
+ \egroup}
+
+\let\mkprocessMPtoPDFfile\mkconvertMPtoPDF
+
+% \def\TEXcode#1#2#3#4#5%
+% {\setbox\scratchbox\hbox
+% {\font\temp=#1\space at #2\onebasepoint
+% \temp
+% \MPfshowcommand{#3}}%
+% \setbox\scratchbox\hbox
+% {\hskip#4\onebasepoint
+% \raise#5\onebasepoint
+% \box\scratchbox}%
+% \smashbox\scratchbox
+% \box\scratchbox}
+
+% will be done better
+
+\def\MPStextext#1#2#3#4#5% if we clean up this plugin model, we can
+ {\def\MPtextdata{#3}% % delegate the splitter to lua
+ \def\MPtextsize{#2}%
+ \def\lastMPmoveX{#4}%
+ \def\lastMPmoveY{#5}%
+ \convertcommand\MPtextdata\to\MPtextdata
+ \splitstring\MPtextdata\at::::\to\MPtexttag\and\MPtextnumber
+ \executeifdefined{handleMPtext\MPtexttag}
+ {\setbox\scratchbox\hbox
+ {\font\temp=#1\space at #2\onebasepoint
+ \temp
+ \MPfshowcommand{#3}}%
+ \setbox\scratchbox\hbox
+ {\hskip#4\onebasepoint
+ \raise#5\onebasepoint
+ \box\scratchbox}%
+ \smashbox\scratchbox
+ \box\scratchbox}}
+
+%D We save the special variables on a stack. It's not that
+%D fast, but it make implementing the special more convenient.
+
+\def\MPSbegin
+ {\nofMParguments\zerocount}
+
+\def\MPSend
+ {\csname\MPspecial\endcsname}
+
+\def\MPSset
+ {\advance\nofMParguments\plusone
+ \expandafter\def\csname\@@MP\number\nofMParguments\endcsname}
+
+\def\gMPs#1{\csname\@@MP\number#1\endcsname}
+
+%D The boundingbox.
+
+\def\MPSboundingbox#1#2#3#4%
+ {\xdef\MPllx{#1}
+ \xdef\MPlly{#2}
+ \xdef\MPurx{#3}
+ \xdef\MPury{#4}
+ \xdef\MPwidth {\the\dimexpr#3\onebasepoint-#1\onebasepoint\relax}
+ \xdef\MPheight{\the\dimexpr#4\onebasepoint-#2\onebasepoint\relax}}
+
+\MPSboundingbox0000
+
+\def\MPSspecial#1#2%
+ {\csname\@@MPSK#2\endcsname}
+
+%D A path is (in most cases) just a sequence of \PDF\ commands.
+
+% \newcontitional\ignoreMPpath
+
+\def\MPSpath
+ {\PDFcode}
+
+\def\MPScode % hack, will be improved
+ {\ifconditional\ignoreMPpath
+ \PDFcode{h W n}%
+ \ifx\extraMPpathcode\empty\else
+ \PDFcode{\extraMPpathcode}%
+ \let\extraMPpathcode\empty
+ \fi
+ \setfalse\ignoreMPpath
+ \expandafter\gobbleoneargument
+ \else
+ \expandafter\PDFcode
+ \fi}
+
+\let\MPSrgb \dohandleMPrgb
+\let\MPScmyk\dohandleMPcmyk
+\let\MPSgray\dohandleMPgray
+\let\MPSspot\dohandleMPspot
+
+\protect \endinput
diff --git a/tex/context/base/mult-sys.tex b/tex/context/base/mult-sys.tex
index 2b2cefe99..f10e91cfe 100644
--- a/tex/context/base/mult-sys.tex
+++ b/tex/context/base/mult-sys.tex
@@ -108,6 +108,7 @@
\definemessageconstant {structures}
\definemessageconstant {symbols}
\definemessageconstant {systems}
+\definemessageconstant {lua}
\definemessageconstant {textblocks}
\definemessageconstant {verbatims}
\definemessageconstant {versions}
diff --git a/tex/context/base/node-ini.lua b/tex/context/base/node-ini.lua
new file mode 100644
index 000000000..77835e04b
--- /dev/null
+++ b/tex/context/base/node-ini.lua
@@ -0,0 +1,905 @@
+if not modules then modules = { } end modules ['node-ini'] = {
+ version = 1.001,
+ comment = "companion to node-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
Access to nodes is what gives its power. Here we
+implement a few helper functions.
+--ldx]]--
+
+nodes = nodes or { }
+nodes.trace = false
+
+-- handy helpers
+
+do
+
+ local remove, free = node.remove, node.free
+
+ function nodes.remove(head, current, free_too)
+ local t = current
+ head, current = remove(head,current)
+ if t then
+ if free_too then
+ free(t)
+ t = nil
+ else
+ t.next, t.prev = nil, nil
+ end
+ end
+ return head, current, t
+ end
+
+ function nodes.delete(head,current)
+ return nodes.remove(head,current,true)
+ end
+
+ nodes.before = node.insert_before
+ nodes.after = node.insert_after
+
+ function nodes.show_list(head, message)
+ if message then
+ texio.write_nl(message)
+ end
+ for n in node.traverse(head) do
+ texio.write_nl(tostring(n))
+ end
+ end
+
+end
+
+-- will move
+
+nodes.processors = { }
+nodes.processors.char = { }
+nodes.processors.char.proc = { }
+
+function nodes.report(t,done)
+ if nodes.trace then
+ if done then
+ if status.output_active then
+ texio.write(string.format("<++ %s>",nodes.count(t)))
+ else
+ texio.write(string.format("<+ %s>",nodes.count(t)))
+ end
+ else
+ if status.output_active then
+ texio.write(string.format("<-- %s>",nodes.count(t)))
+ else
+ texio.write(string.format("<- %s>",nodes.count(t)))
+ end
+ end
+ end
+end
+
+--~ function nodes.count(stack)
+--~ if stack then
+--~ local n = 0
+--~ for _, node in pairs(stack) do
+--~ if node then
+--~ local kind = node[1]
+--~ if kind == 'hlist' or kind == 'vlist' then
+--~ local content = node[8]
+--~ if type(content) == "table" then
+--~ n = n + 1 + nodes.count(content) -- self counts too
+--~ else
+--~ n = n + 1
+--~ end
+--~ elseif kind == 'inline' then
+--~ n = n + nodes.count(node[4]) -- self does not count
+--~ else
+--~ n = n + 1
+--~ end
+--~ end
+--~ end
+--~ return n
+--~ else
+--~ return 0
+--~ end
+--~ end
+
+do
+
+ local hlist, vlist = node.id('hlist'), node.id('vlist')
+
+ function nodes.count(stack)
+ local n = 0
+ while stack do
+ local id = stack.id
+ if id == hlist or id == vlist then
+ local list = stack.list
+ if list then
+ n = n + 1 + nodes.count(list) -- self counts too
+ else
+ n = n + 1
+ end
+ else
+ n = n + 1
+ end
+ stack = stack.next
+ end
+ return n
+ end
+
+end
+
+--[[ldx--
+
When manipulating node lists in , we will remove
+nodes and insert new ones. While node access was implemented, we did
+quite some experiments in order to find out if manipulating nodes
+in was feasible from the perspective of performance.
+
+
First of all, we noticed that the bottleneck is more with excessive
+callbacks (some gets called very often) and the conversion from and to
+'s datastructures. However, at the end, we
+found that inserting and deleting nodes in a table could become a
+bottleneck.
+
+
This resulted in two special situations in passing nodes back to
+: a table entry with value false is ignored,
+and when instead of a table true is returned, the
+original table is used.
+
+
Insertion is handled (at least in as follows. When
+we need to insert a node at a certain position, we change the node at
+that position by a dummy node, tagged inline which itself
+contains the original node and one or more new nodes. Before we pass
+back the list we collapse the list. Of course collapsing could be built
+into the engine, but this is a not so natural extension.
+
+
When we collapse (something that we only do when really needed), we
+also ignore the empty nodes.
+--ldx]]--
+
+--~ function nodes.inline(...)
+--~ return { 'inline', 0, nil, { ... } }
+--~ end
+
+--~ do
+
+--~ function collapse(stack,existing_t)
+--~ if stack then
+--~ local t = existing_t or { }
+--~ for _, node in pairs(stack) do
+--~ if node then
+--~ -- if node[3] then node[3][1] = nil end -- remove status bit
+--~ local kind = node[1]
+--~ if kind == 'inline' then
+--~ collapse(node[4],t)
+--~ elseif kind == 'hlist' or kind == 'vlist' then
+--~ local content = node[8]
+--~ if type(content) == "table" then
+--~ node[8] = collapse(content)
+--~ end
+--~ t[#t+1] = node
+--~ else
+--~ t[#t+1] = node
+--~ end
+--~ else
+--~ -- deleted node
+--~ end
+--~ end
+--~ return t
+--~ else
+--~ return stack
+--~ end
+--~ end
+
+--~ nodes.collapse = collapse
+
+--~ end
+
+--[[ldx--
+
The following function implements a generic node processor. A
+generic processer is not that much needed, because we often need
+to act differently for horizontal or vertical lists. For instance
+counting nodes needs a different method (ok, we could add a second
+handle for catching them but it would become messy then).
+--ldx]]--
+
+--~ function nodes.each(stack,handle)
+--~ if stack then
+--~ local i = 1
+--~ while true do
+--~ local node = stack[i]
+--~ if node then
+--~ local kind = node[1]
+--~ if kind == 'hlist' or kind == 'vlist' then
+--~ local content = node[8]
+--~ if type(content) == "table" then
+--~ nodes.each(content,handle)
+--~ end
+--~ elseif kind == 'inline' then
+--~ nodes.each(node[4],handle)
+--~ else
+--~ stack[i] = handle(kind,node)
+--~ end
+--~ end
+--~ i = i + 1
+--~ if i > #stack then
+--~ break
+--~ end
+--~ end
+--~ end
+--~ end
+
+--~ function nodes.remove(stack,id,subid) -- "whatsit", 6
+--~ nodes.each(stack, function(kind,node)
+--~ if kind == id and node[2] == subid then
+--~ return false
+--~ else
+--~ return node
+--~ end
+--~ end)
+--~ end
+
+--[[ldx--
+
Serializing nodes can be handy for tracing. Also, saving and
+loading node lists can come in handy as soon we are going to
+use external applications to process node lists.
Regimes take care of converting the input characters into
+ sequences. The conversion tables are loaded at
+runtime.
+--ldx]]--
+
+regimes = regimes or { }
+regimes.data = regimes.data or { }
+regimes.utf = regimes.utf or { }
+regimes.context = regimes.context or { }
+
+-- setmetatable(regimes.data,_empty_table_)
+
+regimes.currentregime = ""
+
+--[[ldx--
+
We will hook regime handling code into the input methods.
+--ldx]]--
+
+input = input or { }
+input.filters = input.filters or { }
+
+function regimes.number(n)
+ if type(n) == "string" then return tonumber(n,16) else return n end
+end
+
+function regimes.define(c) -- is this used at all?
+ local r, u, s = c.regime, c.unicodeslot, c.slot
+ regimes.data[r] = regimes.data[r] or { }
+ if s then
+ if u then
+ regimes.data[r][regimes.number(s)] = regimes.number(u)
+ else
+ regimes.data[r][regimes.number(s)] = 0
+ end
+ else
+ input.report(string.format("regi-ini: invalid regime code %s/%s", r, s))
+ end
+end
+
+function regimes.load(regime)
+ environment.loadlucfile("regi-"..regime, 1.001)
+ if regimes.data[regime] then
+ regimes.utf[regime] = { }
+ for k,v in pairs(regimes.data[regime]) do
+ regimes.utf[regime][string.char(k)] = unicode.utf8.char(v)
+ end
+ end
+end
+
+function regimes.translate(line,regime)
+ if regime and line and regimes.utf[regime] then
+ return line:gsub("(.)", regimes.utf[regime])
+ else
+ return line
+ end
+end
+
+function regimes.enable(regime)
+ if regimes.data[regime] then
+ regimes.currentregime = regime
+ input.filters.dynamic_translator = function(s)
+ return regimes.translate(s,regimes.currentregime)
+ end
+ else
+ regimes.disable()
+ end
+end
+
+function regimes.disable()
+ regimes.currentregime = ""
+ input.filters.dynamic_translator = nil
+end
+
+function input.filters.frozen_translator(regime)
+ return function(s)
+ return regimes.translate(s,regime)
+ end
+end
+
+--[[ldx--
+
The following code is rather specific.
+--ldx]]--
+
+function regimes.context.show(regime)
+ local flush, tc = tex.sprint, tex.ctxcatcodes
+ local r = regimes.data[regime]
+ if r then
+ flush(tc, "\\starttabulate[|rT|T|rT|lT|lT|lT|]")
+ for k, v in ipairs(r) do
+ flush(tc, string.format("\\NC %s\\NC\\getvalue{%s}\\NC %s\\NC %s\\NC %s\\NC %s\\NC\\NR", k,
+ characters.contextname(v), characters.hexindex(v), characters.contextname(v),
+ characters.category(v), characters.description(v)))
+ end
+ flush(tc, "\\stoptabulate")
+ else
+ flush(tc, "unknown regime " .. regime)
+ end
+end
diff --git a/tex/context/base/regi-ini.mkiv b/tex/context/base/regi-ini.mkiv
new file mode 100644
index 000000000..ef269985c
--- /dev/null
+++ b/tex/context/base/regi-ini.mkiv
@@ -0,0 +1,39 @@
+%D \module
+%D [ file=char-reg,
+%D version=2005.04.25,
+%D title=\CONTEXT\ Lua Macros,
+%D subtitle=Regime Support,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright=PRAGMA]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+%D First some initialization code:
+
+\registerctxluafile{regi-ini}{1.001}
+
+\def\mkloadregime #1{\ctxlua{regimes.load("#1")}}
+\def\mkenableregime #1{\ctxlua{regimes.enable("#1")}}
+\def\mkdisableregime {\ctxlua{regimes.disable()}}
+\def\mkshowregime #1{\ctxlua{regimes.context.show("#1")}}
+
+\appendtoks
+ \pushmacro\currentregime
+ \disableregime
+\to \everystartreadingfile
+
+\appendtoks
+ \popmacro\currentregime
+ \enableregime[\currentregime]%
+\to \everystopreadingfile
+
+\protect \endinput
+
+% \starttext
+% \showregimetable{cp1250}
+% \stoptext
diff --git a/tex/context/base/regi-utf.tex b/tex/context/base/regi-utf.tex
index 90e018684..297ad9649 100644
--- a/tex/context/base/regi-utf.tex
+++ b/tex/context/base/regi-utf.tex
@@ -37,9 +37,9 @@
\expandafter \endinput
\endXETEX
-\beginMETATEX
+\beginLUATEX
\expandafter \endinput
-\endMETATEX
+\endLUATEX
\unprotect
diff --git a/tex/context/base/s-abr-01.tex b/tex/context/base/s-abr-01.tex
index dd5160a0d..98e36e2f1 100644
--- a/tex/context/base/s-abr-01.tex
+++ b/tex/context/base/s-abr-01.tex
@@ -24,32 +24,35 @@
\logo [MKIII] {MkIII}
\logo [MKIV] {MkIV}
+%logo [FGA] {fga}
+%logo [FGBBS] {fgbbs}
\logo [ACROBAT] {Acro\-bat}
\logo [AFM] {afm}
\logo [ALEPH] {Aleph} % {\mathematics{\aleph}}
\logo [ALGOL] {ALGOL}
-\logo [ARABTEX] {Arab\TeX}
+\logo [AMS] {ams}
\logo [AMSLATEX] {\AmSLaTeX}
\logo [AMSTEX] {\AmSTeX}
-\logo [AMS] {ams}
\logo [ANSI] {ansi}
-\logo [ASCIITEX] {ascii\TeX}
+\logo [ARABTEX] {Arab\TeX}
\logo [ASCII] {ascii}
+\logo [ASCIITEX] {ascii\TeX}
\logo [BACHOTEX] {Bacho\TeX}
\logo [BIBTEX] {bib\TeX}
\logo [BLUESKY] {BlueSky}
-\logo [BSD] {bsd}
\logo [BMP] {bmp}
-\logo [CDROM] {cdrom}
+\logo [BSD] {bsd}
+\logo [CALCMATH] {CalcMath}
\logo [CD] {cd}
+\logo [CDROM] {cdrom}
+\logo [CID] {cid}
\logo [CMR] {cmr}
\logo [CMYK] {cmyk}
\logo [CODHOST] {CodHost}
-\logo [CALCMATH] {CalcMath}
\logo [CONTEXT] {\ConTeXt}
\logo [CSS] {css}
-\logo [CID] {cid}
\logo [CTAN] {ctan}
+\logo [CTXTOOLS] {ctxtools}
\logo [CWEB] {cweb}
\logo [DANTE] {Dante}
\logo [DISTILLER] {distiller}
@@ -59,41 +62,38 @@
\logo [DTK] {dtk}
\logo [DTP] {dtp}
\logo [DVD] {dvd}
-\logo [DVIPSONE] {dvipsone}
+\logo [DVI] {dvi}
\logo [DVIPDFM] {dvipdfm}
\logo [DVIPDFMX] {dvipdfmx}
\logo [DVIPOS] {dvipos}
\logo [DVIPS] {dvips}
+\logo [DVIPSONE] {dvipsone}
\logo [DVISCR] {dviscr}
\logo [DVIWINDO] {dviwindo}
-\logo [DVI] {dvi}
\logo [EC] {ec}
\logo [EIFFEL] {Eiffel}
\logo [EMACS] {emacs}
\logo [EMTEX] {em\TeX}
+\logo [ENCODING] {enc}
\logo [ENCTEX] {enc\TeX}
\logo [EPS] {eps}
-\logo [EUROBACHOTEX] {EuroBacho\TeX}
-\logo [ENCODING] {enc}
-\logo [MAPPING] {map}
-\logo [EXAMPLEX] {examplex}
-\logo [EXAMPLET] {examplet}
-\logo [EXAMPLER] {exampler}
-\logo [EXAMPLEQ] {exampleq}
-\logo [EXAMPLED] {exampled}
-\logo [EXAMPLE] {eXaMpLe}
-\logo [EXIMPLE] {eXiMpLe}
\logo [ETEX] {\eTeX}
+\logo [EUROBACHOTEX] {EuroBacho\TeX}
\logo [EUROMATH] {EuroMath}
\logo [EUROTEX] {Euro\TeX}
+\logo [EXAMPLE] {eXaMpLe}
+\logo [EXAMPLED] {exampled}
+\logo [EXAMPLEQ] {exampleq}
+\logo [EXAMPLER] {exampler}
+\logo [EXAMPLET] {examplet}
+\logo [EXAMPLEX] {examplex}
+\logo [EXIMPLE] {eXiMpLe}
\logo [FAQ] {faq}
-%logo [FGA] {fga}
\logo [FDF] {fdf}
-%logo [FGBBS] {fgbbs}
+\logo [FONTFORGE] {FontForge}
+\logo [FOXET] {foXet}
\logo [FPTEX] {fp\TeX}
\logo [FREEBSD] {FreeBSD}
-\logo [FOXET] {foXet}
-\logo [FONTFORGE] {FontForge}
\logo [FTP] {ftp}
\logo [GHOSTSCRIPT]{Ghost\-script}
\logo [GHOSTVIEW] {Ghost\-view}
@@ -103,11 +103,10 @@
\logo [GS] {Ghost\-Script}
\logo [GUST] {Gust}
\logo [GWTEX] {gw\TeX}
-\logo [HZ] {hz}
-\logo [HTTP] {http}
\logo [HSB] {hsb}
\logo [HTML] {html}
-\logo [XHTML] {xhtml}
+\logo [HTTP] {http}
+\logo [HZ] {hz}
\logo [IBM] {ibm}
\logo [IMAGEMAGICK]{ImageMagick}
\logo [INITEX] {ini\TeX}
@@ -115,27 +114,28 @@
\logo [IO] {io}
\logo [IRCNET] {IRCnet}
\logo [ISO] {iso}
-\logo [JAVASCRIPT] {Java\-Script}
\logo [JAVA] {Java}
+\logo [JAVASCRIPT] {Java\-Script}
\logo [JPEG] {jpeg}
\logo [JPG] {jpg}
+\logo [KPATHSEA] {kpathsea}
\logo [KPSE] {kpse}
\logo [KPSEWHICH] {kpsewhich}
-\logo [KPATHSEA] {kpathsea}
\logo [LAMSTEX] {\LamSTeX}
+\logo [LATEX] {\LaTeX}
\logo [LATEXTE] {\LaTeX2e}
\logo [LATEXTN] {\LaTeX2.09}
-\logo [LATEX] {\LaTeX}
\logo [LINUX] {linux}
\logo [LISP] {Lisp}
-\logo [LUAJIT] {LuaJIT}
+\logo [LPEG] {lpeg}
\logo [LUA] {Lua}
+\logo [LUAJIT] {LuaJIT}
\logo [LUATEX] {Lua\TeX}
-\logo [TEXLUA] {\TeX Lua}
\logo [LUATOOLS] {luatools}
\logo [MACOSX] {MacOSX}
\logo [MACROTEX] {Macro\TeX}
\logo [MAKEMPY] {MakeMPY}
+\logo [MAPPING] {map}
\logo [MAPS] {Maps}
\logo [MATHML] {MathML}
\logo [METAFONT] {\MetaFont}
@@ -143,33 +143,36 @@
\logo [METATEX] {Meta\TeX}
\logo [MIKTEX] {Mik\TeX}
\logo [MLTEX] {ml\TeX}
-\logo [MTXTOOLS] {mtxtools}
\logo [MODULA] {Modula}
\logo [MOV] {mov}
\logo [MPS] {mps}
\logo [MPTOPDF] {mptopdf}
\logo [MSDOS] {msdos}
\logo [MSWINDOWS] {MS~Windows}
+\logo [MTXRUN] {mtxrun}
+\logo [MTXTOOLS] {mtxtools}
\logo [NETPBM] {NetPBM}
\logo [NTG] {ntg}
\logo [NTS] {nts}
+\logo [OFM] {ofm}
\logo [OMEGA] {Omega}
-\logo [OPI] {opi}
\logo [OPENMATH] {OpenMath}
\logo [OPENTYPE] {OpenType}
-\logo [OTP] {otp}
+\logo [OPI] {opi}
\logo [OTF] {otf}
-\logo [OFM] {ofm}
+\logo [OTP] {otp}
\logo [OVF] {ovf}
\logo [PASCAL] {Pascal}
\logo [PCTEX] {pc\TeX}
+\logo [PDF] {pdf}
\logo [PDFETEX] {pdfe\TeX}
\logo [PDFTEX] {pdf\TeX}
+\logo [PDFTOOLS] {pdftools}
\logo [PDFTOPS] {pdftops}
-\logo [PDF] {pdf}
-\logo [PERLTK] {Perl/Tk}
\logo [PERL] {Perl}
+\logo [PERLTK] {Perl/Tk}
\logo [PICTEX] {\PiCTeX}
+\logo [PK] {pk}
\logo [PLAIN] {Plain}
\logo [PNG] {png}
\logo [POSIX] {posix}
@@ -178,17 +181,18 @@
\logo [PRAGMA] {Pragma ADE}
\logo [PRESS] {press}
\logo [PRIFIL] {prifil}
+\logo [PS] {Post\-Script}
\logo [PSCHECK] {pscheck}
+\logo [PSTOEDIT] {pstoedit}
\logo [PSTOPAGE] {pstopage}
\logo [PSTOPDF] {pstopdf}
\logo [PSTRICKS] {pstricks}
-\logo [PSTOEDIT] {pstoedit}
-\logo [PS] {Post\-Script}
+\logo [RAM] {ram}
\logo [READER] {Acro\-bat Reader}
\logo [RELAXNG] {Relax\kern.125emNG}
-\logo [RAM] {ram}
-\logo [RUBY] {Ruby}
\logo [RGB] {rgb}
+\logo [RLXTOOLS] {rlxtools}
+\logo [RUBY] {Ruby}
\logo [SCITE] {SciTE}
\logo [SGML] {sgml}
\logo [SI] {si}
@@ -197,73 +201,71 @@
\logo [TCPIP] {tcp/ip}
\logo [TDS] {tds} % no sc te
\logo [TETEX] {te\TeX} % no sc te
+\logo [TEX] {\TeX}
\logo [TEXADRES] {\TeX adress}
\logo [TEXBASE] {\TeX base}
\logo [TEXEDIT] {\TeX edit}
\logo [TEXEXEC] {\TeX exec}
+\logo [TEXFONT] {\TeX font}
\logo [TEXFORM] {\TeX form}
\logo [TEXLIVE] {\TeX Live}
+\logo [TEXLUA] {\TeX Lua}
\logo [TEXMF] {texmf}
+\logo [TEXMFSTART] {texmfstart}
\logo [TEXNL] {tex-nl}
-\logo [TEXXET] {\TeX\XeT} \def\XeT{XeT}
\logo [TEXSHOW] {\TeX show}
\logo [TEXSPELL] {\TeX spell}
-\logo [TEXUTIL] {\TeX util}
-\logo [TEXWORK] {\TeX work}
-\logo [TEXFONT] {\TeX font}
+\logo [TEXSYNC] {texsync}
\logo [TEXTMATE] {TextMate}
\logo [TEXTOOLS] {\TeX tools}
-\logo [TRUETYPE] {TrueType}
-\logo [TTF] {ttf}
-\logo [TYPEONE] {Type1}
-\logo [XMLTOOLS] {xmltools}
-\logo [PDFTOOLS] {pdftools}
-\logo [RLXTOOLS] {rlxtools}
-\logo [CTXTOOLS] {ctxtools}
-\logo [TMFTOOLS] {tmftools}
-\logo [TEXMFSTART] {texmfstart}
-\logo [TEXSYNC] {texsync}
-\logo [TEX] {\TeX}
+\logo [TEXUTIL] {\TeX util}
+\logo [TEXWORK] {\TeX work}
+\logo [TEXXET] {\TeX\XeT} \def\XeT{XeT}
\logo [TFM] {tfm}
-\logo [VF] {vf}
-\logo [PK] {pk}
+\logo [TIF] {tif}
+\logo [TIFF] {tiff}
\logo [TIFFINFO] {tiffinfo}
\logo [TIFFTAGS] {tifftags}
-\logo [TIFF] {tiff}
-\logo [TIF] {tif}
+\logo [TMFTOOLS] {tmftools}
\logo [TPIC] {tpic}
\logo [TPM] {tpm}
+\logo [TRUETYPE] {TrueType}
+\logo [TTF] {ttf}
+\logo [TUG] {tug}
\logo [TUGBOAT] {Tug\-Boat}
\logo [TUGNEWS] {Tug\-News}
-\logo [TUG] {tug}
+\logo [TYPEONE] {Type1}
+\logo [UCS] {ucs}
\logo [UNICODE] {Uni\-code}
\logo [UNIX] {Unix}
\logo [URI] {uri}
\logo [URL] {url}
\logo [USA] {usa}
-\logo [UTF] {utf}
-\logo [UCS] {ucs}
\logo [USENET] {usenet}
\logo [UTF] {utf}
+\logo [UTF] {utf}
+\logo [VF] {vf}
\logo [WDT] {wdt}
-\logo [WEBC] {web2c}
\logo [WEB] {web}
-\logo [WINNT] {WinNT}
+\logo [WEBC] {web2c}
\logo [WIKI] {Wiki}
\logo [WINDOWS] {Windows}
+\logo [WINNT] {WinNT}
\logo [WINNX] {Win9x}
\logo [WWW] {www}
\logo [WYSIWYG] {wysiwyg}
\logo [XDVI] {Xdvi}
+\logo [XETEX] {\XeTeX}
+\logo [XFDF] {xfdf}
+\logo [XHTML] {xhtml}
\logo [XINDY] {Xindy}
\logo [XML] {xml}
-\logo [XFDF] {xfdf}
+\logo [XMLTOOLS] {xmltools}
\logo [XPDFETEX] {xpdfe\TeX}
-\logo [XSLT] {xslt}
-\logo [XSLTPROC] {xsltproc}
\logo [XSL] {xsl}
-\logo [XETEX] {\XeTeX}
\logo [XSLFO] {xsl-fo}
+\logo [XSLT] {xslt}
+\logo [XSLTPROC] {xsltproc}
\logo [XYPIC] {XYPIC} % wrong logo
\logo [YandY] {y\&y}
\logo [ZIP] {zip}
diff --git a/tex/context/base/s-mag-01.tex b/tex/context/base/s-mag-01.tex
index 79d944f4c..54d997fc1 100644
--- a/tex/context/base/s-mag-01.tex
+++ b/tex/context/base/s-mag-01.tex
@@ -377,6 +377,13 @@
\definetyping[xtyping] [style=\ttx]
\definetyping[xxtyping][style=\ttxx]
+\definetypeface
+ [narrowtt] [tt]
+ [mono] [modern-cond] [default] [encoding=\defaultencoding]
+
+\definetyping[ntyping] \setuptyping[ntyping][style=\narrowtt]
+\definetype [ntype] \setuptype [ntype] [style=\narrowtt]
+
\doifnotmode{demo}{\endinput}
\startbuffer[abstract]
diff --git a/tex/context/base/sort-def.mkiv b/tex/context/base/sort-def.mkiv
new file mode 100644
index 000000000..8cc92a02e
--- /dev/null
+++ b/tex/context/base/sort-def.mkiv
@@ -0,0 +1,16 @@
+%D \module
+%D [ file=sort-def,
+%D version=2005.08.08,
+%D title=\CONTEXT\ Sort Macros,
+%D subtitle=Defaults,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+% nothing here
+
+\endinput
diff --git a/tex/context/base/sort-ini.lua b/tex/context/base/sort-ini.lua
new file mode 100644
index 000000000..11d1e8c79
--- /dev/null
+++ b/tex/context/base/sort-ini.lua
@@ -0,0 +1,176 @@
+-- filename : sort-ini.lua
+-- comment : companion to sort-ini.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- todo:
+--
+-- out of range
+-- uppercase
+-- texutil compatible
+-- always expand to utf
+
+if not versions then versions = { } end versions['sort-ini'] = 1.001
+
+sorters = { }
+sorters.comparers = { }
+sorters.splitters = { }
+sorters.entries = { }
+sorters.mappings = { }
+sorters.replacements = { }
+sorters.language = 'en'
+
+function sorters.comparers.basic(a,b,i) -- [2] has entry, key, cmp
+ local sort_a, sort_b = a[2][i][3], b[2][i][3]
+ if #sort_a > #sort_b then
+ if #sort_b == 0 then
+ return 1
+ else
+ for i=1,#sort_b do
+ local ai, bi = sort_a[i], sort_b[i]
+ if ai > bi then
+ return 1
+ elseif ai < bi then
+ return -1
+ end
+ end
+ return -1
+ end
+ elseif #sort_a < #sort_b then
+ if #sort_a == 0 then
+ return -1
+ else
+ for i=1,#sort_a do
+ local ai, bi = sort_a[i], sort_b[i]
+ if ai > bi then
+ return 1
+ elseif ai < bi then
+ return -1
+ end
+ end
+ return 1
+ end
+ elseif #sort_a == 0 then
+ return 0
+ else
+ for i=1,#sort_a do
+ local ai, bi = sort_a[i], sort_b[i]
+ if ai > bi then
+ return 1
+ elseif ai < bi then
+ return -1
+ end
+ end
+ sort_a, sort_b = a[2][i][2], b[2][i][2]
+ if sort_a == "" then sort_a = a[2][i][1] end
+ if sort_b == "" then sort_b = b[2][i][1] end
+ if sort_a < sort_b then
+ return -1
+ elseif sort_a > sort_b then
+ return 1
+ else
+ return 0
+ end
+ end
+end
+
+function sorters.prepare(data,split,n)
+ local strip = sorters.strip
+ for k,v in ipairs(data) do
+ for i=1,n do
+ local vv = v[2][i]
+ if vv then
+ if vv[2] then
+ if vv[2] ~= "" then
+ vv[3] = split(strip(vv[2]))
+ else
+ vv[3] = split(strip(vv[1]))
+ end
+ else
+ vv[2] = { }
+ vv[3] = split(strip(vv[1]))
+ end
+ else
+ v[2][i] = { {}, {}, {} }
+ end
+ end
+ end
+end
+
+function sorters.strip(str) -- todo: only letters and such utf.gsub("([^%w%d])","")
+ str = str:gsub("\\%S*","")
+ str = str:gsub("[%s%[%](){}%$\"\']*","")
+ str = str:gsub("(%d+)",function(s) return (" "):rep(10-#s) .. s end) -- sort numbers properly
+ return str
+end
+
+function sorters.splitters.utf(str)
+ local r = sorters.replacements[sorters.language] or { }
+ local m = sorters.mappings[sorters.language] or { }
+ local u = characters.uncompose
+ local t = { }
+ for _,v in pairs(r) do
+ str = str:gsub(v[1],v[2])
+ end
+ for c in string.utfcharacters(str) do
+ if m[c] then
+ t[#t+1] = m[c]
+ else
+ for cc in string.characters(u(c)) do
+ t[#t+1] = m[cc] or cc
+ end
+ end
+ end
+ return t
+end
+
+function sorters.sort(data,cmp)
+ table.sort(data,function(a,b) return cmp(a,b) == -1 end)
+end
+
+function sorters.cleanup(data)
+ for k,v in ipairs(data) do
+ for kk,vv in ipairs(v[2]) do
+ if vv and #vv[1] == 0 then
+ v[1][kk] = nil
+ else
+ vv[3] = nil
+ end
+ end
+ for kk,vv in pairs(v) do
+ if vv == "" then
+ v[kk] = nil
+ end
+ end
+ end
+end
+
+function sorters.unique(data)
+ local prev, last = nil, 0
+ for _,v in ipairs(data) do
+ if not prev or not table.are_equal(prev,v,2,3) then -- check range
+ last = last + 1
+ data[last] = v
+ prev = v
+ end
+ end
+ for i=last+1,#data do
+ data[i] = nil
+ end
+end
+
+function sorters.process(kind,data)
+ if data.entries then
+ if not data.sorted then
+ sorters.language = data.language or sorters.language
+ sorters[kind].prepare(data.entries)
+ sorters[kind].sort(data.entries)
+ sorters[kind].unique(data.entries)
+ data.sorted = true
+ end
+ return sorters[kind].flush(sorters[kind].finalize(data.entries),data.class,data.flush)
+ else
+ return { }
+ end
+end
diff --git a/tex/context/base/sort-ini.mkiv b/tex/context/base/sort-ini.mkiv
new file mode 100644
index 000000000..9c87edb11
--- /dev/null
+++ b/tex/context/base/sort-ini.mkiv
@@ -0,0 +1,21 @@
+%D \module
+%D [ file=sort-ini,
+%D version=2005.08.08,
+%D title=\CONTEXT\ Sort Macros,
+%D subtitle=Initialization,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\registerctxluafile{sort-ini}{1.001}
+
+\let\savesortlanguage\gobbleoneargument
+
+\readfile{sort-def.tex}{}{} % runtime loaded in mkii
+\readfile{sort-lan.tex}{}{} % runtime loaded in mkii
+
+\endinput
diff --git a/tex/context/base/sort-lan.lua b/tex/context/base/sort-lan.lua
new file mode 100644
index 000000000..9802dd315
--- /dev/null
+++ b/tex/context/base/sort-lan.lua
@@ -0,0 +1,193 @@
+-- filename : sort-lan.lua
+-- comment : companion to sort-lan.tex
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+if not versions then versions = { } end versions['sort-lan'] = 1.001
+
+-- this is a rather preliminary and incomplete file
+-- maybe we should load this kind of stuff runtime
+
+-- english
+
+do
+ -- The next one can be more efficient when not indexed this way, but
+ -- other languages are sparse so for the moment we keep this one.
+
+ sorters.entries['en'] = {
+ [ 1] = "a", [ 3] = "b", [ 5] = "c", [ 7] = "d", [ 9] = "e",
+ [11] = "f", [13] = "g", [15] = "h", [17] = "i", [19] = "j",
+ [21] = "k", [23] = "l", [25] = "m", [27] = "n", [29] = "o",
+ [31] = "p", [33] = "q", [35] = "r", [37] = "s", [39] = "t",
+ [41] = "u", [43] = "v", [45] = "w", [47] = "x", [49] = "y",
+ [51] = "z",
+ [ 2] = 1, [ 4] = 3, [ 6] = 5, [ 8] = 7, [10] = 9,
+ [12] = 11, [14] = 13, [16] = 15, [18] = 17, [20] = 19,
+ [22] = 21, [24] = 23, [26] = 25, [28] = 27, [30] = 29,
+ [32] = 31, [34] = 33, [36] = 35, [38] = 37, [40] = 39,
+ [42] = 41, [44] = 43, [46] = 45, [48] = 47, [50] = 49,
+ [52] = 51,
+ }
+ sorters.mappings['en'] = {
+ ["a"] = 1, ["b"] = 3, ["c"] = 5, ["d"] = 7, ["e"] = 9,
+ ["f"] = 11, ["g"] = 13, ["h"] = 15, ["i"] = 17, ["j"] = 19,
+ ["k"] = 21, ["l"] = 23, ["m"] = 25, ["n"] = 27, ["o"] = 29,
+ ["p"] = 31, ["q"] = 33, ["r"] = 35, ["s"] = 37, ["t"] = 39,
+ ["u"] = 41, ["v"] = 43, ["w"] = 45, ["x"] = 47, ["y"] = 49,
+ ["z"] = 51,
+ ["A"] = 2, ["B"] = 4, ["C"] = 6, ["D"] = 8, ["E"] = 10,
+ ["F"] = 12, ["G"] = 14, ["H"] = 16, ["I"] = 18, ["J"] = 20,
+ ["K"] = 22, ["L"] = 24, ["M"] = 26, ["N"] = 28, ["O"] = 30,
+ ["P"] = 32, ["Q"] = 34, ["R"] = 36, ["S"] = 38, ["T"] = 40,
+ ["U"] = 42, ["V"] = 44, ["W"] = 46, ["X"] = 48, ["Y"] = 50,
+ ["Z"] = 52,
+ }
+end
+
+-- dutch
+
+do
+ sorters.replacements['nl'] = { { "ij", 'y' }, { "IJ", 'Y' } }
+ sorters.entries ['nl'] = sorters.entries ['en']
+ sorters.mappings ['nl'] = sorters.mappings['en']
+end
+
+-- czech
+
+do
+
+ local uc = unicode.utf8.char
+ local ub = unicode.utf8.byte
+
+ sorters.replacements['cz'] = {
+ [1] = { "ch", uc(0xFF01) }
+ }
+
+ sorters.entries['cz'] = {
+ [ 1] = "a",
+ [ 2] = 1,
+ [ 3] = "b",
+ [ 4] = "c",
+ [ 5] = uc(0x010D), -- ccaron
+ [ 6] = "d",
+ [ 7] = uc(0x010F), -- dcaron
+ [ 8] = "e",
+ [ 9] = 8,
+ [10] = 8,
+ [11] = "f",
+ [12] = "g",
+ [13] = "h",
+ [14] = "ch",
+ [15] = "i",
+ [16] = 15,
+ [17] = "j",
+ [18] = "k",
+ [19] = "l",
+ [20] = "m",
+ [21] = "n",
+ [22] = uc(0x0147), -- ncaron
+ [23] = "o",
+ [24] = "p",
+ [25] = "q",
+ [26] = "r",
+ [27] = uc(0x0147), -- rcaron
+ [28] = "s",
+ [29] = uc(0x0161), -- scaron
+ [30] = "t",
+ [31] = uc(0x0165), -- tcaron
+ [32] = "u",
+ [33] = 32,
+ [34] = 32,
+ [35] = "v",
+ [36] = "w",
+ [37] = "x",
+ [38] = "y",
+ [49] = "z",
+ [40] = uc(0x017E), -- zcaron
+ }
+
+ sorters.mappings['cz'] = {
+ ['a'] = 1, -- a
+ [uc(0x00E1)] = 2, -- aacute
+ ['b'] = 3, -- b
+ ['c'] = 4, -- c
+ [uc(0x010D)] = 5, -- ccaron
+ ['d'] = 6, -- d
+ [uc(0x010F)] = 7, -- dcaron
+ ['e'] = 8, -- e
+ [uc(0x00E9)] = 9, -- eacute
+ [uc(0x011B)] = 10, -- ecaron
+ ['f'] = 11, -- f
+ ['g'] = 12, -- g
+ ['h'] = 13, -- h
+ [uc(0xFF01)] = 14, -- ch
+ ['i'] = 15, -- i
+ [uc(0x00ED)] = 16, -- iacute
+ ['j'] = 17, -- j
+ ['k'] = 18, -- k
+ ['l'] = 19, -- l
+ ['m'] = 20, -- m
+ ['n'] = 21, -- n
+ [uc(0x0147)] = 22, -- ncaron
+ ['o'] = 23, -- o
+ ['p'] = 24, -- p
+ ['q'] = 25, -- q
+ ['s'] = 26, -- r
+ [uc(0x0147)] = 27, -- rcaron
+ ['s'] = 28, -- s
+ [uc(0x0161)] = 29, -- scaron
+ ['t'] = 30, -- t
+ [uc(0x0165)] = 31, -- tcaron
+ ['u'] = 32, -- u
+ [uc(0x00FA)] = 33, -- uacute
+ [uc(0x01F6)] = 34, -- uring
+ ['v'] = 35, -- v
+ ['w'] = 36, -- w
+ ['x'] = 37, -- x
+ ['y'] = 38, -- y
+ ['z'] = 49, -- z
+ [uc(0x017E)] = 40, -- zcaron
+ }
+
+end
+
+--~ sorters.test = ''
+--~ sorters.test = 'nl'
+--~ sorters.test = 'cz'
+
+--~ if sorters.test == 'nl' then -- dutch test
+
+--~ data = {
+--~ { 'e', { {"ijsco",""} },2,"","","",""},
+--~ { 'e', { {"ysco" ,""} },2,"","","",""},
+--~ { 'e', { {"ijsco",""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"aap" ,""} },2,"","","",""},
+--~ { 'e', { {"$a$" ,""} },2,"","","",""},
+--~ { 'e', { {"aap" ,""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"aap" ,""} },6,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"noot",""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"mies",""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"mies",""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"mies",""}, [3] = {"oeps",""} },2,"","","",""},
+--~ { 'e', { {"hans" ,""}, {"mies",""}, [3] = {"oeps",""} },4,"","","",""},
+--~ }
+--~ sorters.index.process({ entries = data, language = 'nl'})
+
+--~ elseif sorters.test == 'cz' then -- czech test
+
+--~ data = {
+--~ { 'e', { {"blabla",""} },2,"","","",""},
+--~ { 'e', { {"czacza",""} },2,"","","",""},
+--~ { 'e', { {"albalb",""} },2,"","","",""},
+--~ { 'e', { {"azcazc",""} },2,"","","",""},
+--~ { 'e', { {"chacha",""} },2,"","","",""},
+--~ { 'e', { {"hazzah",""} },2,"","","",""},
+--~ { 'e', { {"iaccai",""} },2,"","","",""},
+--~ }
+--~ sorters.index.process({ entries = data, language = 'cz'})
+
+--~ end
+
+
+--~ print(table.serialize(sorters))
diff --git a/tex/context/base/sort-lan.mkiv b/tex/context/base/sort-lan.mkiv
new file mode 100644
index 000000000..918f7f9b7
--- /dev/null
+++ b/tex/context/base/sort-lan.mkiv
@@ -0,0 +1,16 @@
+%D \module
+%D [ file=sort-lan,
+%D version=2005.08.08,
+%D title=\CONTEXT\ Sort Macros,
+%D subtitle=Language Definitions,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\registerctxluafile{sort-lan}{1.001}
+
+\endinput
diff --git a/tex/context/base/spec-def.mkiv b/tex/context/base/spec-def.mkiv
new file mode 100644
index 000000000..82e121864
--- /dev/null
+++ b/tex/context/base/spec-def.mkiv
@@ -0,0 +1,19 @@
+%D \module
+%D [ file=spec-def,
+%D version=2006.09.18,
+%D title=\CONTEXT\ Special Macros,
+%D subtitle=Definitions,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%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 load the lua scripts here because we don't want to load
+%D them multiple times.
+
+\registerctxluafile{spec-pdf}{1.001}
+
+\endinput
diff --git a/tex/context/base/spec-fdf.mkiv b/tex/context/base/spec-fdf.mkiv
new file mode 100644
index 000000000..87a30d0ca
--- /dev/null
+++ b/tex/context/base/spec-fdf.mkiv
@@ -0,0 +1,19 @@
+%D \module
+%D [ file=spec-fdf,
+%D version=2006.09.18,
+%D title=\CONTEXT\ \PDF\ Macros,
+%D subtitle=Support Macros,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\def\setPDFdestination#1{\xdef\PDFdestination{\ctxlua{pdf.cleandestination("\luaescapestring{#1}")}}}
+\def\sanitizePDFstring#1\to#2{\xdef#2{\ctxlua{pdf.sanitizedstring("\luaescapestring{#1}")}}}
+
+\protect \endinput
diff --git a/tex/context/base/spec-fdf.tex b/tex/context/base/spec-fdf.tex
index d6f912df5..7b99dfcc9 100644
--- a/tex/context/base/spec-fdf.tex
+++ b/tex/context/base/spec-fdf.tex
@@ -3111,7 +3111,7 @@
\PDFcode{/GSknockout gs}% wrong
\fi}
-%D Transparency support (experimental):
+%D Transparency support:
\newif\ifPDFtransparencysupported
@@ -3126,11 +3126,9 @@
\def\dodoPDFstarttransparency#1#2%
{\presetPDFtransparency{#1}{#2}%
-% \PDFdirectcode{\PDFtransparencyidentifier\space gs }}
\PDFcode{\PDFtransparencyidentifier\space gs }}
\def\dodoPDFstoptransparency
-% {\PDFdirectcode{/Tr0 gs }}
{\PDFcode{/Tr0 gs }}
\def\doPDFstarttransparency
diff --git a/tex/context/base/spec-pdf.lua b/tex/context/base/spec-pdf.lua
new file mode 100644
index 000000000..04886b724
--- /dev/null
+++ b/tex/context/base/spec-pdf.lua
@@ -0,0 +1,28 @@
+if not modules then modules = { } end modules ['spec-pdf'] = {
+ version = 1.001,
+ comment = "companion to spec-fdf.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
This module implements a couple of cleanup methods. We need these
+in order to meet the specification. Watch the double
+parenthesis; they are needed because otherwise we would pass more
+than one argument to .
+--ldx]]--
+
+pdf = pdf or { }
+
+function pdf.cleandestination(str)
+ tex.sprint((str:gsub("[%/%#%<%>%[%]%(%)%-%s]+","-")))
+end
+
+function pdf.cleandestination(str)
+ tex.sprint((str:gsub("[%/%#%<%>%[%]%(%)%-%s]+","-")))
+end
+
+function pdf.santizedstring(str)
+ tex.sprint((str:gsub("([\\/#<>%[%]%(%)])","\\%1")))
+end
diff --git a/tex/context/base/supp-fil.lua b/tex/context/base/supp-fil.lua
new file mode 100644
index 000000000..ecf118104
--- /dev/null
+++ b/tex/context/base/supp-fil.lua
@@ -0,0 +1,78 @@
+if not modules then modules = { } end modules ['supp-fil'] = {
+ version = 1.001,
+ comment = "companion to supp-fil.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+
It's more convenient to manipulate filenames (paths) in
+ than in . These methods have counterparts
+at the side.
+--ldx]]--
+
+support = support or { }
+environment = environment or { }
+
+environment.outputfilename = environment.outputfilename or environment.jobname
+
+function support.checkfilename(str) -- "/whatever..." "c:..." "http://..."
+ cs.chardef("kindoffile",boolean.tonumber(str:find("^/") or str:find("[%a]:")))
+end
+
+function support.thesanitizedfilename(str)
+ tex.write((str:gsub("\\","/")))
+end
+
+function support.splitfilename(fullname)
+ local path, name, base, suffix, kind = '', fullname, fullname, '', 0
+ local p, n = fullname:match("^(.+)/(.-)$")
+ if p and n then
+ path, name, base = p, n, n
+ end
+ local b, s = base:match("^(.+)%.(.-)$")
+ if b and s then
+ name, suffix = b, s
+ end
+ if path == "" then
+ kind = 0
+ elseif path == '.' then
+ kind = 1
+ else
+ kind = 2
+ end
+ cs.def("splitofffull", fullname)
+ cs.def("splitoffpath", path)
+ cs.def("splitoffbase", base)
+ cs.def("splitoffname", name)
+ cs.def("splitofftype", suffix)
+ cs.chardef("splitoffkind", kind)
+end
+
+function support.splitfiletype(fullname)
+ local name, suffix = fullname, ''
+ local n, s = fullname:match("^(.+)%.(.-)$")
+ if n and s then
+ name, suffix = n, s
+ end
+ cs.def("splitofffull", fullname)
+ cs.def("splitoffpath", "")
+ cs.def("splitoffname", name)
+ cs.def("splitofftype", suffix)
+end
+
+function support.doifparentfileelse(n)
+ cs.testcase(n==environment.jobname or n==environment.jobname..'.tex' or n==environment.outputfilename)
+end
+
+-- saves some .15 sec on 12 sec format generation
+
+function support.doiffileexistelse(name)
+ if not name or name == "" then
+ return cs.testcase(false)
+ else
+ local n = input.findtexfile(texmf.instance,name)
+ return cs.testcase(n and n ~= "")
+ end
+end
diff --git a/tex/context/base/supp-fil.mkiv b/tex/context/base/supp-fil.mkiv
new file mode 100644
index 000000000..586004259
--- /dev/null
+++ b/tex/context/base/supp-fil.mkiv
@@ -0,0 +1,32 @@
+%D \module
+%D [ file=supp-fil,
+%D version=2006.09.18,
+%D title=\CONTEXT\ Support Macros,
+%D subtitle=Files,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details
+
+% \input supp-fil.mkii \endinput
+
+\registerctxluafile{supp-fil}{1.001}
+
+% \def\sanitizefilename#1\to#2%
+% {\edef#2{\ctxlua{support.thesanitizedfilename("\luaescapestring{#1}")}}}
+% \def\checkfilename #1{\ctxlua{support.checkfilename("\luaescapestring{#1}")}}
+% \def\splitfilename #1{\ctxlua{support.splitfilename("\luaescapestring{#1}")}}
+% \def\splitfiletype #1{\ctxlua{support.splitfiletype("\luaescapestring{#1}")}}
+% \def\doifparentfileelse#1{\ctxlua{support.doifparentfileelse("\luaescapestring{#1}")}}
+
+\def\sanitizefilename#1\to#2{\edef#2{\ctxlua{support.thesanitizedfilename([[#1]])}}}
+\def\checkfilename #1{\ctxlua{support.checkfilename([[#1]])}}
+\def\splitfilename #1{\ctxlua{support.splitfilename([[#1]])}}
+\def\splitfiletype #1{\ctxlua{support.splitfiletype([[#1]])}}
+\def\doifparentfileelse #1{\ctxlua{support.doifparentfileelse([[#1]])}}
+\def\doiffileexistselse #1{\ctxlua{support.doiffileexistelse([[#1]])}}
+
+\endinput
diff --git a/tex/context/base/syst-cat.mkiv b/tex/context/base/syst-cat.mkiv
new file mode 100644
index 000000000..6f3f605b8
--- /dev/null
+++ b/tex/context/base/syst-cat.mkiv
@@ -0,0 +1,116 @@
+%D \module
+%D [ file=syst-cat,
+%D version=2006.09.18,
+%D title=\CONTEXT\ System Macros,
+%D subtitle=Catcode Handling,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\def\newcatcodetable#1%
+ {\global\advance\cctdefcounter\plusone
+ \expandafter\xdef\csname @@ccn:\number\cctdefcounter\endcsname{\string#1}% logging
+ \global\mathchardef#1\cctdefcounter}
+
+\newcatcodetable \scratchcatcodetable \initcatcodetable\scratchcatcodetable
+
+\ifx\nilcatcodes \undefined \newcatcodetable \nilcatcodes \fi
+\ifx\texcatcodes \undefined \newcatcodetable \texcatcodes \fi
+\ifx\ctxcatcodes \undefined \newcatcodetable \ctxcatcodes \fi
+\ifx\vrbcatcodes \undefined \newcatcodetable \vrbcatcodes \fi
+\ifx\prtcatcodes \undefined \newcatcodetable \prtcatcodes \fi
+\ifx\xmlcatcodesn\undefined \newcatcodetable \xmlcatcodesn \fi % normal
+\ifx\xmlcatcodese\undefined \newcatcodetable \xmlcatcodese \fi % entitle
+\ifx\xmlcatcodesr\undefined \newcatcodetable \xmlcatcodesr \fi % reduce
+
+\newtoks \setdefaultcatcodes
+
+\setdefaultcatcodes
+ {\catcode`\\ 12
+ \catcode`\^^M 12
+ \catcode`\ 12
+ \catcode`\% 12
+ \catcode127 12 }
+
+\long\def\startcatcodetable#1#2\stopcatcodetable
+ {\bgroup
+ \catcodetable\scratchcatcodetable
+ \the\setdefaultcatcodes
+ #2%
+ \savecatcodetable#1\relax
+ \egroup}
+
+\newcatcodetable\dummycatcodes
+
+% \long\def\startextendcatcodetable#1#2\stopextendcatcodetable
+% {\bgroup
+% \catcodetable#1\relax
+% #2%
+% \savecatcodetable\dummycatcodes
+% \catcodetable\dummycatcodes
+% \savecatcodetable#1\relax
+% \egroup}
+
+\long\def\startextendcatcodetable#1#2\stopextendcatcodetable
+ {\bgroup
+ \catcodetable#1\relax
+ \globaldefs\plusone
+ #2%
+ \globaldefs\zerocount
+ \egroup}
+
+% ==
+%
+% \long\def\startextendcatcodetable#1#2\stopextendcatcodetable
+% {\bgroup
+% \scratchcounter\the\catcodetable
+% \catcodetable #1 #2
+% \catcodetable\scratchcounter
+% \egroup}
+
+\def\letcatcodecommand
+ {\afterassignment\letcatcodecommanda\cctcountera}
+
+\def\letcatcodecommanda
+ {\afterassignment\letcatcodecommandb\cctcounterb}
+
+% construct the definition in lua
+%
+% \def\letcatcodecommandb
+% {\scratchcounter\catcode\cctcounterb \catcode\cctcounterb=13
+% \directlua\CTXlua{tex.print(tex.texcatcodes,"\\xdef " .. string.char(\number\cctcounterb)
+% .. "{\\noexpand\\catcodecommand{\number\cctcounterb}}")}%
+% \catcode\cctcounterb\scratchcounter
+% \expandafter\let\csname cc:\number\cctcountera:\number\cctcounterb\endcsname}
+%
+% or less messy:
+%
+% \def\letcatcodecommandb
+% {\chardef\savedcctcode\catcode\cctcounterb
+% \catcode\cctcounterb=13
+% \expandafter\edef\directlua\CTXlua{tex.sprint(tex.texcatcodes,string.char(\number\cctcounterb))}%
+% {\noexpand\catcodecommand{\number\cctcounterb}}%
+% \catcode\cctcounterb\savedcctcode
+% \expandafter\let\csname cc:\number\cctcountera:\number\cctcounterb\endcsname}
+
+\let\currentcatcodetable\catcodetable
+
+\startruntimectxluacode
+ tex.nilcatcodes = \number\nilcatcodes ;
+ tex.texcatcodes = \number\texcatcodes ;
+ tex.ctxcatcodes = \number\ctxcatcodes ;
+ tex.vrbcatcodes = \number\vrbcatcodes ;
+ tex.prtcatcodes = \number\prtcatcodes ;
+ tex.xmlcatcodes = \number\xmlcatcodesn ;
+ tex.xmlcatcodesn = \number\xmlcatcodesn ; % normal
+ tex.xmlcatcodese = \number\xmlcatcodese ; % entitle
+ tex.xmlcatcodesr = \number\xmlcatcodesr ; % reduce
+\stopruntimectxluacode
+
+\protect \endinput
diff --git a/tex/context/base/syst-con.lua b/tex/context/base/syst-con.lua
new file mode 100644
index 000000000..273bc5b5c
--- /dev/null
+++ b/tex/context/base/syst-con.lua
@@ -0,0 +1,30 @@
+-- filename : syst-con.lua
+-- comment : companion to syst-con.tex (in ConTeXt)
+-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+-- copyright: PRAGMA ADE / ConTeXt Development Team
+-- license : see context related readme files
+
+-- remark : compact version
+
+if not versions then versions = { } end versions['syst-con'] = 1.001
+if not convert then convert = { } end
+
+-- For raw 8 bit characters, the offset is 0x110000 (bottom of plane 18)
+-- at the top of luatex's char range but outside the unicode range.
+
+function convert.lchexnumber (n) tex.sprint(string.format("%x" ,n)) end
+function convert.uchexnumber (n) tex.sprint(string.format("%X" ,n)) end
+function convert.lchexnumbers (n) tex.sprint(string.format("%02x",n)) end
+function convert.uchexnumbers (n) tex.sprint(string.format("%02X",n)) end
+function convert.octnumber (n) tex.sprint(string.format("%03o",n)) end
+function convert.hexstringtonumber(n) tex.sprint(tonumber(n,16)) end
+function convert.octstringtonumber(n) tex.sprint(tonumber(n, 8)) end
+function convert.rawcharacter (n) tex.sprint(unicode.utf8.char(0x110000+n)) end
+
+do
+ local char = unicode.utf8.char
+ local flush = tex.sprint
+
+ function convert.rawcharacter(n) flush(char(0x110000+n)) end
+
+end
diff --git a/tex/context/base/syst-con.mkiv b/tex/context/base/syst-con.mkiv
new file mode 100644
index 000000000..ddc9fe564
--- /dev/null
+++ b/tex/context/base/syst-con.mkiv
@@ -0,0 +1,27 @@
+%D \module
+%D [ file=syst-con,
+%D version=2006.09.16,
+%D title=\CONTEXT\ System Macros,
+%D subtitle=Conversions,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\registerctxluafile{syst-con}{1.001}
+
+\unprotect
+
+\def\lchexnumber #1{\ctxlua{convert.lchexnumber(\number#1)}}
+\def\uchexnumber #1{\ctxlua{convert.uchexnumber(\number#1)}}
+\def\lchexnumbers #1{\ctxlua{convert.lchexnumbers(\number#1)}}
+\def\uchexnumbers #1{\ctxlua{convert.uchexnumbers(\number#1)}}
+\def\octnumber #1{\ctxlua{convert.octnumber(\number#1)}}
+\def\hexstringtonumber#1{\ctxlua{convert.hexstringtonumber("#1")}}
+\def\octstringtonumber#1{\ctxlua{convert.octstringtonumber("#1")}}
+\def\rawcharacter #1{\ctxlua{convert.rawcharacter(\number#1)}}
+
+\protect \endinput
diff --git a/tex/context/base/syst-new.tex b/tex/context/base/syst-new.tex
index dca897ac0..853b2514c 100644
--- a/tex/context/base/syst-new.tex
+++ b/tex/context/base/syst-new.tex
@@ -65,6 +65,21 @@
\def\unspaceafter#1#2%
{\edef\ascii{\dounspaced#2\end}\@EA#1\@EA{\ascii}}
+
+% sometimes handy:
+
+\def\doifhasspaceelse#1%
+ {\edef\!!stringa{#1}%
+ \expanded{\dodoifhasspaceelse#1\space}\empty\relax}
+
+\def\dodoifhasspaceelse#1 #2#3\relax % \space\empty\relax
+ {\ifx\!!stringa\space
+ \@EA\firstoftwoarguments
+ \else\ifx#2\empty
+ \@EAEAEA\secondoftwoarguments
+ \else
+ \@EAEAEA\firstoftwoarguments
+ \fi\fi}
% this will replace loadfile once and alike !!! todo
diff --git a/tex/context/base/syst-prm.tex b/tex/context/base/syst-prm.tex
index ea7b5634c..dc259dff7 100644
--- a/tex/context/base/syst-prm.tex
+++ b/tex/context/base/syst-prm.tex
@@ -102,19 +102,51 @@
\ifx\directlua\undefined
- \long\gdef\beginMETATEX#1\endMETATEX%
+ \long\gdef\beginLUATEX#1\endLUATEX%
{}
\else
- \gdef\beginMETATEX%
- {\bgroup\obeylines\dobeginMETATEX}
+ \gdef\beginLUATEX%
+ {\bgroup\obeylines\dobeginLUATEX}
- \gdef\dobeginMETATEX#1
+ \gdef\dobeginLUATEX#1
{\egroup\immediate\write16%
- {system (METATEX) : [line \the\inputlineno] \detokenize{#1}}}
+ {system (LUATEX) : [line \the\inputlineno] \detokenize{#1}}}
- \global\let\endMETATEX\relax
+ \global\let\endLUATEX\relax
+
+\fi
+
+% traditional tex's vs de utf tex's
+
+\ifx\XeTeXversion\undefined \ifx\directlua\undefined
+
+ \gdef\beginOLDTEX%
+ {\bgroup\obeylines\dobeginOLDTEX}
+
+ \gdef\dobeginOLDTEX#1
+ {\egroup\immediate\write16%
+ {system (OLDTEX) : [line \the\inputlineno] \detokenize{#1}}}
+
+ \global\let\endOLDTEX\relax
+
+ \long\gdef\beginNEWTEX#1\endNEWTEX%
+ {}
+
+\fi \fi \ifx\beginOLDTEX\undefined
+
+ \long\gdef\beginOLDTEX#1\endOLDTEX%
+ {}
+
+ \gdef\beginNEWTEX%
+ {\bgroup\obeylines\dobeginNEWTEX}
+
+ \gdef\dobeginNEWTEX#1
+ {\egroup\immediate\write16%
+ {system (NEWTEX) : [line \the\inputlineno] \detokenize{#1}}}
+
+ \global\let\endNEWTEX\relax
\fi
diff --git a/tex/context/base/syst-rtp.mkiv b/tex/context/base/syst-rtp.mkiv
new file mode 100644
index 000000000..36ce8edb1
--- /dev/null
+++ b/tex/context/base/syst-rtp.mkiv
@@ -0,0 +1,18 @@
+%D \module
+%D [ file=syst-rtp,
+%D version=2006.10.13,
+%D title=\CONTEXT\ Core Macros,
+%D subtitle=Run Time Processes,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+\def\executesystemcommand#1{\ctxlua{os.execute([[#1]])}}
+
+\protect \endinput
diff --git a/tex/context/base/syst-str.mkiv b/tex/context/base/syst-str.mkiv
new file mode 100644
index 000000000..6d0f0565e
--- /dev/null
+++ b/tex/context/base/syst-str.mkiv
@@ -0,0 +1,27 @@
+%D \module
+%D [ file=syst-str,
+%D version=2006.09.18,
+%D title=\CONTEXT\ System Macros,
+%D subtitle=String Processing,
+%D author=Hans Hagen,
+%D date=\currentdate,
+%D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
+%C
+%C This module is part of the \CONTEXT\ macro||package and is
+%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
+%C details.
+
+\unprotect
+
+% todo: escape special chars in expr (\luaescapeexpression)
+
+\def\replacecharacters#1#2#3% macro characters replacement
+ {\dodoglobal\edef#1{\ctxlua{tex.sprint((string.gsub("#1",string.esc(#2),"#3")))}}}
+
+\def\separatestring#1\to#2%
+ {\dodoglobal\def#2{\ctxlua{tex.sprint(string.gsub(("#1","\letterpercent s+",","))})}}
+
+\def\unspacefilename#1\to#2%
+ {\dodoglobal\def#2{\ctxlua{tex.sprint(string.gsub(("#1","\letterpercent s+","-")))}}}
+
+\protect \endinput
diff --git a/tex/context/base/toks-ini.lua b/tex/context/base/toks-ini.lua
new file mode 100644
index 000000000..ad046be50
--- /dev/null
+++ b/tex/context/base/toks-ini.lua
@@ -0,0 +1,232 @@
+if not modules then modules = { } end modules ['toks-ini'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+utf = utf or unicode.utf8
+
+--[[ldx--
+