Compare commits
67 Commits
e75211162c
...
3ee7a52bdd
Author | SHA1 | Date |
---|---|---|
Marcel Krüger | 3ee7a52bdd | |
Marcel Krüger | 1195e0f201 | |
Marcel Krüger | 2eefb9a14b | |
Marcel Krüger | 56f9d3ce85 | |
Marcel Krüger | 377d147927 | |
Marcel Krüger | b87691c42d | |
Marcel Krüger | 7c392c7575 | |
Marcel Krüger | fe4f3355f3 | |
Marcel Krüger | 2547bc80e3 | |
Marcel Krüger | 463f240670 | |
Marcel Krüger | 7ffc299c16 | |
Marcel Krüger | 06d1efeb55 | |
Marcel Krüger | ad44afdb0b | |
Marcel Krüger | f18f6a3219 | |
Marcel Krüger | aa00de1c9d | |
Marcel Krüger | 3d7380f76a | |
Marcel Krüger | f5b842c30e | |
Marcel Krüger | 8020a70ff1 | |
Marcel Krüger | 446f26cc1d | |
Marcel Krüger | b8dbb5a4c8 | |
Marcel Krüger | f6895d8b50 | |
Marcel Krüger | 780bd4382b | |
Marcel Krüger | 94ba4a2557 | |
Marcel Krüger | d13bbbc5eb | |
Marcel Krüger | 961430ce42 | |
Marcel Krüger | f4f829dbac | |
Marcel Krüger | acc0ad0559 | |
Marcel Krüger | b506fd4357 | |
Marcel Krüger | 01bc7c0f77 | |
Marcel Krüger | 4c76251d2e | |
Marcel Krüger | efedcba3e1 | |
Marcel Krüger | f7a76b69d8 | |
Marcel Krüger | f12fa8a2e6 | |
Marcel Krüger | c9fb95eff3 | |
Marcel Krüger | 2ae166ce65 | |
Marcel Krüger | 50e97985cf | |
Marcel Krüger | 552bbb258c | |
Marcel Krüger | 099d5871d0 | |
Marcel Krüger | 599ae713e3 | |
Marcel Krüger | 9f4c671114 | |
Marcel Krüger | bb955cb1c3 | |
Marcel Krüger | 23d93b55ac | |
Marcel Krüger | 2c68c0223a | |
Marcel Krüger | 88effdc8dd | |
Marcel Krüger | 1be299f4f1 | |
Marcel Krüger | ed130e6ed8 | |
Marcel Krüger | df8930b685 | |
Marcel Krüger | ccafacf7ac | |
Marcel Krüger | c8a96a987d | |
Marcel Krüger | 06b138f1e7 | |
Marcel Krüger | ade568565c | |
Marcel Krüger | d4b1d7f9b4 | |
Marcel Krüger | f4d068e1f3 | |
Marcel Krüger | 06e5dce213 | |
Marcel Krüger | 034a95569b | |
Marcel Krüger | 66e3482be4 | |
Marcel Krüger | b4370f2d1c | |
Marcel Krüger | e1325c6c40 | |
Marcel Krüger | a0fb4684b3 | |
Marcel Krüger | 0d4199ab4a | |
Marcel Krüger | a4b5b89205 | |
Marcel Krüger | b376473f17 | |
Marcel Krüger | 9f7849e108 | |
Marcel Krüger | 19d9ccbd76 | |
Marcel Krüger | 39ed349241 | |
Marcel Krüger | 5c45cbc2e3 | |
Marcel Krüger | 602bcb0583 |
11
README.md
11
README.md
|
@ -6,15 +6,20 @@ This code is in early stages of development and contains more bugs than features
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
You need an up-to-date TeX Live installation and the latest version of LuaMetaTeX.
|
You need an up-to-date TeX Live installation and the latest version of LuaMetaTeX.
|
||||||
|
|
||||||
Additionally a special library version of LuaTeX's kpathsea Lua binding is needed which is provided as a binary for Linux x64. For other platforms you might have to compile it yourself. Drop me a line if you need any instructions.
|
Additionally a special library version of LuaTeX's kpathsea Lua binding is needed which is provided as a binary for Linux x64. For other platforms you might have to compile it yourself. Drop me a line if you need any instructions. (The source can be found under https://github.com/zauguin/luametalatex-kpse)
|
||||||
|
|
||||||
## How to install
|
## How to install (automatically)
|
||||||
|
Obtain `luametatex` from ConTeXt, drop the binary into the same location where your `luatex` binary is installed and then run `install.sh`.
|
||||||
|
|
||||||
|
## How to install (manually)
|
||||||
Obtain `luametatex` from ConTeXt, drop the binary into the same location where your `luatex` binary is installed and copy (or sym-link) the file `luametalatex.lua` into the same directory. Additionally create a sym-link `luametalatex` to `luametatex` in the same directory. Then copy (or sym-link) this entire repo to `.../texmf-local/tex/lualatex/luametalatex`.
|
Obtain `luametatex` from ConTeXt, drop the binary into the same location where your `luatex` binary is installed and copy (or sym-link) the file `luametalatex.lua` into the same directory. Additionally create a sym-link `luametalatex` to `luametatex` in the same directory. Then copy (or sym-link) this entire repo to `.../texmf-local/tex/lualatex/luametalatex`.
|
||||||
|
|
||||||
Finally add the line
|
Finally add the line
|
||||||
```
|
```
|
||||||
luametalatex luametatex language.dat,language.dat.lua --lua="$(kpsewhich luametalatex.lua)" luametalatex.ini
|
luametalatex luametatex language.dat,language.dat.lua --lua="$(kpsewhich luametalatex.lua)" luametalatex.ini
|
||||||
```
|
```
|
||||||
to your local `fmtutil.cnf`. Then you should be able to run `fmtutil-sys --byfmt luametalatex` to generate the format.
|
to your local `fmtutil.cnf` and configure paths for luametalatex in your `texmf.cnf`. Then you should be able to run `fmtutil-sys --byfmt luametalatex` to generate the format.
|
||||||
|
|
||||||
If this worked you can built (simple) LaTeX documents using the command `luametalatex`.
|
If this worked you can built (simple) LaTeX documents using the command `luametalatex`.
|
||||||
|
|
||||||
|
You can then repeat the same instructions with `luametalatex-dev` and `luametaplain` to also get access to development and plain TeX formats
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/bin/sh
|
||||||
|
ENGINE="$(which luametatex)"
|
||||||
|
ENGINE_DIR="$(dirname "$ENGINE")"
|
||||||
|
REPO="$(pwd)"
|
||||||
|
cd "$(dirname "$ENGINE")"
|
||||||
|
ln -s luametatex luametaplain
|
||||||
|
ln -s luametatex luametalatex
|
||||||
|
ln -s luametatex luametalatex-dev
|
||||||
|
ln -s "$REPO/luametaplain.lua" .
|
||||||
|
ln -s "$REPO/luametalatex.lua" .
|
||||||
|
ln -s "$REPO/luametalatex-dev.lua" .
|
||||||
|
while [ ! -d texmf ] && [ ! -d texmf-local ]
|
||||||
|
do
|
||||||
|
LASTDIR="$(pwd)"
|
||||||
|
cd ..
|
||||||
|
if [ "$(pwd)" == "$LASTDIR" ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -d texmf ]
|
||||||
|
then cd texmf
|
||||||
|
else cd texmf-local
|
||||||
|
fi
|
||||||
|
mkdir -p tex/luameta{plain,latex{,-dev}}
|
||||||
|
ln -s "$REPO" tex/luametaplain/base
|
||||||
|
ln -s "$REPO" tex/luametalatex/base
|
||||||
|
ln -s "$REPO" tex/luametalatex-dev/base
|
||||||
|
mkdir -p web2c
|
||||||
|
cat >> web2c/texmf.cnf << "EOF"
|
||||||
|
TEXINPUTS.luametaplain = $TEXMFDOTDIR;$TEXMF/tex/{luametatex,luatex,plain,generic,}//
|
||||||
|
TEXINPUTS.luametalatex = $TEXMFDOTDIR;$TEXMF/tex/{luametalatex,lualatex,latex,luametatex,luatex,generic,}//
|
||||||
|
TEXINPUTS.luametalatex-dev= $TEXMFDOTDIR;$TEXMF/tex/{luametalatex,latex-dev,lualatex,latex,luametatex,luatex,generic,}//
|
||||||
|
|
||||||
|
LUAINPUTS.luametaplain = $TEXMFDOTDIR;$TEXMF/scripts/{$progname,$engine,}/{lua,}//;$TEXMF/tex/{luametatex,luatex,plain,generic,}//
|
||||||
|
LUAINPUTS.luametalatex = $TEXMFDOTDIR;$TEXMF/scripts/{$progname,$engine,}/{lua,}//;$TEXMF/tex/{luametalatex,lualatex,latex,luametatex,luatex,generic,}//
|
||||||
|
LUAINPUTS.luametalatex-dev= $TEXMFDOTDIR;$TEXMF/scripts/{$progname,$engine,}/{lua,}//;$TEXMF/tex/{luametalatex,latex-dev,lualatex,latex,luametatex,luatex,generic,}//
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >> web2c/fmtutil.cnf << "EOF"
|
||||||
|
luametaplain luametatex language.dat,language.dat.lua --lua="$(kpsewhich luametalatex.lua)" luametaplain.ini
|
||||||
|
luametalatex luametatex language.dat,language.dat.lua --lua="$(kpsewhich luametalatex.lua)" luametalatex.ini
|
||||||
|
luametalatex-dev luametatex language.dat,language.dat.lua --lua="$(kpsewhich luametalatex-dev.lua)" luametalatex.ini
|
||||||
|
EOF
|
||||||
|
echo INSTALLED
|
65
ltexpl.ltx
65
ltexpl.ltx
|
@ -1,65 +0,0 @@
|
||||||
%%
|
|
||||||
%% This is file `ltexpl.ltx',
|
|
||||||
%% generated with the docstrip utility.
|
|
||||||
%%
|
|
||||||
%% The original source files were:
|
|
||||||
%%
|
|
||||||
%% ltexpl.dtx (with options: `2ekernel')
|
|
||||||
%%
|
|
||||||
%% This is a generated file.
|
|
||||||
%%
|
|
||||||
%% The source is maintained by the LaTeX Project team and bug
|
|
||||||
%% reports for it can be opened at https://latex-project.org/bugs.html
|
|
||||||
%% (but please observe conditions on bug reports sent to that address!)
|
|
||||||
%%
|
|
||||||
%%
|
|
||||||
%% Copyright (C) 1993-2019
|
|
||||||
%% The LaTeX3 Project and any individual authors listed elsewhere
|
|
||||||
%% in this file.
|
|
||||||
%%
|
|
||||||
%% This file was generated from file(s) of the LaTeX base system.
|
|
||||||
%% --------------------------------------------------------------
|
|
||||||
%%
|
|
||||||
%% It may be distributed and/or modified under the
|
|
||||||
%% conditions of the LaTeX Project Public License, either version 1.3c
|
|
||||||
%% of this license or (at your option) any later version.
|
|
||||||
%% The latest version of this license is in
|
|
||||||
%% https://www.latex-project.org/lppl.txt
|
|
||||||
%% and version 1.3c or later is part of all distributions of LaTeX
|
|
||||||
%% version 2008 or later.
|
|
||||||
%%
|
|
||||||
%% This file has the LPPL maintenance status "maintained".
|
|
||||||
%%
|
|
||||||
%% This file may only be distributed together with a copy of the LaTeX
|
|
||||||
%% base system. You may however distribute the LaTeX base system without
|
|
||||||
%% such generated files.
|
|
||||||
%%
|
|
||||||
%% The list of all files belonging to the LaTeX base distribution is
|
|
||||||
%% given in the file `manifest.txt'. See also `legal.txt' for additional
|
|
||||||
%% information.
|
|
||||||
%%
|
|
||||||
%% The list of derived (unpacked) files belonging to the distribution
|
|
||||||
%% and covered by LPPL is defined by the unpacking scripts (with
|
|
||||||
%% extension .ins) which are part of the distribution.
|
|
||||||
%%% From File: ltexpl.dtx
|
|
||||||
\input luametalatex-baseregisters
|
|
||||||
\IfFileExists{expl3.ltx}
|
|
||||||
{%
|
|
||||||
\ifnum0%
|
|
||||||
\ifdefined\pdffilesize 1\fi
|
|
||||||
\ifdefined\filesize 1\fi
|
|
||||||
\ifdefined\luatexversion\ifnum\luatexversion>94 1\fi\fi
|
|
||||||
>0 %
|
|
||||||
\else
|
|
||||||
\message{Skipping expl3-dependent extensions}
|
|
||||||
\expandafter\endinput
|
|
||||||
\fi
|
|
||||||
}
|
|
||||||
{%
|
|
||||||
\message{Skipping expl3-dependent extensions}%
|
|
||||||
\endinput
|
|
||||||
}%
|
|
||||||
\input{expl3.ltx}
|
|
||||||
\endinput
|
|
||||||
%%
|
|
||||||
%% End of file `ltexpl.ltx'.
|
|
|
@ -1,12 +1,33 @@
|
||||||
local pdf = pdf
|
local pdf = pdf
|
||||||
|
local pdfvariable = pdf.variable
|
||||||
|
|
||||||
local writer = require'luametalatex-nodewriter'
|
local writer = require'luametalatex-nodewriter'
|
||||||
local newpdf = require'luametalatex-pdf'
|
local newpdf = require'luametalatex-pdf'
|
||||||
local nametree = require'luametalatex-pdf-nametree'
|
local nametree = require'luametalatex-pdf-nametree'
|
||||||
|
local build_fontdir = require'luametalatex-pdf-font'
|
||||||
|
local prepare_node_font = require'luametalatex-pdf-font-node'.prepare
|
||||||
|
local fontmap = require'luametalatex-pdf-font-map'
|
||||||
|
|
||||||
|
local utils = require'luametalatex-pdf-utils'
|
||||||
|
local strip_floats = utils.strip_floats
|
||||||
|
local to_bp = utils.to_bp
|
||||||
|
|
||||||
local pdfname, pfile
|
local pdfname, pfile
|
||||||
local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() return t[k] end})
|
local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() return t[k] end})
|
||||||
local usedglyphs = {}
|
local nodefont_meta = {}
|
||||||
|
local usedglyphs = setmetatable({}, {__index=function(t, fid)
|
||||||
|
local v
|
||||||
|
if font.fonts[fid].format == 'type3node' then
|
||||||
|
v = setmetatable({generation = 0, next_generation = 0}, nodefont_meta)
|
||||||
|
else
|
||||||
|
v = {}
|
||||||
|
end
|
||||||
|
t[fid] = v
|
||||||
|
return v
|
||||||
|
end})
|
||||||
local dests = {}
|
local dests = {}
|
||||||
local cur_page
|
local cur_page
|
||||||
|
local declare_whatsit = require'luametalatex-whatsits'.new
|
||||||
local whatsit_id = node.id'whatsit'
|
local whatsit_id = node.id'whatsit'
|
||||||
local whatsits = node.whatsits()
|
local whatsits = node.whatsits()
|
||||||
local colorstacks = {{
|
local colorstacks = {{
|
||||||
|
@ -25,19 +46,32 @@ local function get_pfile()
|
||||||
return pfile
|
return pfile
|
||||||
end
|
end
|
||||||
local outline
|
local outline
|
||||||
|
local build_outline = require'luametalatex-pdf-outline'
|
||||||
local function get_outline()
|
local function get_outline()
|
||||||
if not outline then
|
if not outline then
|
||||||
outline = require'luametalatex-pdf-outline'()
|
outline = build_outline()
|
||||||
end
|
end
|
||||||
return outline
|
return outline
|
||||||
end
|
end
|
||||||
local properties = node.direct.properties
|
local properties = node.direct.properties
|
||||||
|
local immediateassignment = token.new(5, token.command_id'convert')
|
||||||
|
local global = token.new(0, token.command_id'prefix')
|
||||||
|
local deadcycles = token.new(8, token.command_id'set_page_property')
|
||||||
|
local zero_tok = token.create(0x30)
|
||||||
|
local relax = token.new(0, 0)
|
||||||
|
local reset_deadcycles = {
|
||||||
|
immediateassignment,
|
||||||
|
global,
|
||||||
|
deadcycles,
|
||||||
|
zero_tok,
|
||||||
|
relax,
|
||||||
|
}
|
||||||
token.luacmd("shipout", function()
|
token.luacmd("shipout", function()
|
||||||
local pfile = get_pfile()
|
local pfile = get_pfile()
|
||||||
local voff = node.new'kern'
|
local voff = node.new'kern'
|
||||||
voff.kern = tex.voffset + pdf.variable.vorigin
|
voff.kern = tex.voffset + pdfvariable.vorigin
|
||||||
voff.next = token.scan_list()
|
voff.next = token.scan_list()
|
||||||
voff.next.shift = tex.hoffset + pdf.variable.horigin
|
voff.next.shift = tex.hoffset + pdfvariable.horigin
|
||||||
local list = node.direct.tonode(node.direct.vpack(node.direct.todirect(voff)))
|
local list = node.direct.tonode(node.direct.vpack(node.direct.todirect(voff)))
|
||||||
list.height = tex.pageheight
|
list.height = tex.pageheight
|
||||||
list.width = tex.pagewidth
|
list.width = tex.pagewidth
|
||||||
|
@ -46,14 +80,16 @@ token.luacmd("shipout", function()
|
||||||
local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs, colorstacks)
|
local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs, colorstacks)
|
||||||
cur_page = nil
|
cur_page = nil
|
||||||
local content = pfile:stream(nil, '', out)
|
local content = pfile:stream(nil, '', out)
|
||||||
pfile:indirect(page, string.format([[<</Type/Page/Parent %i 0 R/Contents %i 0 R/MediaBox[0 %i %i %i]/Resources%s%s>>]], parent, content, -math.ceil(list.depth/65781.76), math.ceil(list.width/65781.76), math.ceil(list.height/65781.76), resources, annots))
|
pfile:indirect(page, string.format([[<</Type/Page/Parent %i 0 R/Contents %i 0 R/MediaBox[0 %i %i %i]/Resources%s%s%s>>]], parent, content, -math.ceil(to_bp(list.depth)), math.ceil(to_bp(list.width)), math.ceil(to_bp(list.height)), resources(pdfvariable.pageresources), annots, pdfvariable.pageattr))
|
||||||
node.flush_list(list)
|
node.flush_list(list)
|
||||||
token.put_next(token.create'immediateassignment', token.create'global', token.create'deadcycles', token.create(0x30), token.create'relax')
|
token.put_next(reset_deadcycles)
|
||||||
token.scan_token()
|
token.scan_token()
|
||||||
end, 'force', 'protected')
|
end, 'force', 'protected')
|
||||||
|
|
||||||
local infodir = ""
|
local infodir = ""
|
||||||
local namesdir = ""
|
local namesdir = ""
|
||||||
local catalogdir = ""
|
local catalogdir = ""
|
||||||
|
local catalog_openaction
|
||||||
local creationdate = os.date("D:%Y%m%d%H%M%S%z"):gsub("+0000$", "Z"):gsub("%d%d$", "'%0")
|
local creationdate = os.date("D:%Y%m%d%H%M%S%z"):gsub("+0000$", "Z"):gsub("%d%d$", "'%0")
|
||||||
local function write_infodir(p)
|
local function write_infodir(p)
|
||||||
local additional = ""
|
local additional = ""
|
||||||
|
@ -79,22 +115,44 @@ local pdf_escape = require'luametalatex-pdf-escape'
|
||||||
local pdf_bytestring = pdf_escape.escape_bytes
|
local pdf_bytestring = pdf_escape.escape_bytes
|
||||||
local pdf_text = pdf_escape.escape_text
|
local pdf_text = pdf_escape.escape_text
|
||||||
|
|
||||||
|
local function nodefont_newindex(t, k, v)
|
||||||
|
t.generation = t.next_generation
|
||||||
|
return rawset(t, k, v)
|
||||||
|
end
|
||||||
|
|
||||||
callback.register("stop_run", function()
|
callback.register("stop_run", function()
|
||||||
if not pfile then
|
if not pfile then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
do
|
||||||
|
nodefont_meta.__newindex = nodefont_newindex -- Start recording generations
|
||||||
|
local need_new_run = true
|
||||||
|
while need_new_run do
|
||||||
|
need_new_run = nil
|
||||||
|
for fid, glyphs in pairs(usedglyphs) do
|
||||||
|
local next_gen = glyphs.next_generation
|
||||||
|
if next_gen and next_gen == glyphs.generation then
|
||||||
|
glyphs.next_generation = next_gen+1
|
||||||
|
need_new_run = true
|
||||||
|
local f = font.getfont(fid) or font.fonts[fid]
|
||||||
|
prepare_node_font(f, glyphs, pfile, fontdirs, usedglyphs) -- Might become fid, glyphs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
for fid, id in pairs(fontdirs) do
|
for fid, id in pairs(fontdirs) do
|
||||||
local f = font.getfont(fid)
|
local f = font.getfont(fid) or font.fonts[fid]
|
||||||
local psname = f.psname or f.fullname
|
|
||||||
local sorted = {}
|
local sorted = {}
|
||||||
|
local used = usedglyphs[fid]
|
||||||
|
used.generation, used.next_generation = nil, nil
|
||||||
for k,v in pairs(usedglyphs[fid]) do
|
for k,v in pairs(usedglyphs[fid]) do
|
||||||
sorted[#sorted+1] = v
|
sorted[#sorted+1] = v
|
||||||
end
|
end
|
||||||
table.sort(sorted, function(a,b) return a[1] < b[1] end)
|
table.sort(sorted, function(a,b) return a[1] < b[1] end)
|
||||||
pfile:indirect(id, require'luametalatex-pdf-font'(pfile, f, sorted))
|
pfile:indirect(id, build_fontdir(pfile, f, sorted))
|
||||||
end
|
end
|
||||||
pfile.root = pfile:getobj()
|
pfile.root = pfile:getobj()
|
||||||
pfile.version = string.format("%i.%i", pdf.variable.majorversion, pdf.variable.minorversion)
|
pfile.version = string.format("%i.%i", pdfvariable.majorversion, pdfvariable.minorversion)
|
||||||
local destnames = {}
|
local destnames = {}
|
||||||
for k,obj in next, dests do
|
for k,obj in next, dests do
|
||||||
if pfile:written(obj) then
|
if pfile:written(obj) then
|
||||||
|
@ -102,7 +160,7 @@ callback.register("stop_run", function()
|
||||||
destnames[k] = obj .. ' 0 R'
|
destnames[k] = obj .. ' 0 R'
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
texio.write_nl("Warning: Undefined destination %q", tostring(k))
|
texio.write_nl(string.format("Warning: Undefined destination %q", tostring(k)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if next(destnames) then
|
if next(destnames) then
|
||||||
|
@ -115,6 +173,9 @@ callback.register("stop_run", function()
|
||||||
if outline then
|
if outline then
|
||||||
catalogdir = string.format("/Outlines %i 0 R%s", outline:write(pfile), catalogdir)
|
catalogdir = string.format("/Outlines %i 0 R%s", outline:write(pfile), catalogdir)
|
||||||
end
|
end
|
||||||
|
if catalog_openaction then
|
||||||
|
catalogdir = catalogdir .. '/OpenAction' .. catalog_openaction
|
||||||
|
end
|
||||||
pfile:indirect(pfile.root, string.format([[<</Type/Catalog/Version/%s/Pages %i 0 R%s>>]], pfile.version, pfile:writepages(), catalogdir))
|
pfile:indirect(pfile.root, string.format([[<</Type/Catalog/Version/%s/Pages %i 0 R%s>>]], pfile.version, pfile:writepages(), catalogdir))
|
||||||
pfile.info = write_infodir(pfile)
|
pfile.info = write_infodir(pfile)
|
||||||
local size = pfile:close()
|
local size = pfile:close()
|
||||||
|
@ -142,10 +203,9 @@ callback.register("stop_run", function()
|
||||||
texio.write_nl(string.format("Transcript written on %s.\n", status.log_name))
|
texio.write_nl(string.format("Transcript written on %s.\n", status.log_name))
|
||||||
end, "Finish PDF file")
|
end, "Finish PDF file")
|
||||||
token.luacmd("pdfvariable", function()
|
token.luacmd("pdfvariable", function()
|
||||||
for n, t in pairs(pdf.variable_tokens) do
|
for _, n in ipairs(pdf.variable_names) do
|
||||||
if token.scan_keyword(n) then
|
if token.scan_keyword(n) then
|
||||||
token.put_next(t)
|
return token.put_next(token.create('pdfvariable ' .. n))
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- The following error message gobbles the next word as a side effect.
|
-- The following error message gobbles the next word as a side effect.
|
||||||
|
@ -170,9 +230,6 @@ function pdf.newcolorstack(default, mode, page)
|
||||||
}
|
}
|
||||||
return idx
|
return idx
|
||||||
end
|
end
|
||||||
local function sp2bp(sp)
|
|
||||||
return sp/65781.76
|
|
||||||
end
|
|
||||||
local function projected(m, x, y, w)
|
local function projected(m, x, y, w)
|
||||||
w = w or 1
|
w = w or 1
|
||||||
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
||||||
|
@ -196,19 +253,30 @@ local function get_action_attr(p, action, is_link)
|
||||||
error[[FIXME]]
|
error[[FIXME]]
|
||||||
elseif action_type == 1 then -- GoTo
|
elseif action_type == 1 then -- GoTo
|
||||||
local id = action.id
|
local id = action.id
|
||||||
if file then
|
if id then
|
||||||
assert(type(id) == "string")
|
if file then
|
||||||
action_attr = action_attr .. "/S/GoToR/D" .. pdf_bytestring(id) .. ">>"
|
assert(type(id) == "string")
|
||||||
else
|
action_attr = action_attr .. "/S/GoToR/D" .. pdf_bytestring(id) .. ">>"
|
||||||
local dest = dests[id]
|
|
||||||
if not dest then
|
|
||||||
dest = pfile:getobj()
|
|
||||||
dests[id] = dest
|
|
||||||
end
|
|
||||||
if type(id) == "string" then
|
|
||||||
action_attr = action_attr .. "/S/GoTo/D" .. pdf_bytestring(id) .. ">>"
|
|
||||||
else
|
else
|
||||||
action_attr = string.format("%s/S/GoTo/D %i 0 R>>", action_attr, dest)
|
local dest = dests[id]
|
||||||
|
if not dest then
|
||||||
|
dest = pfile:getobj()
|
||||||
|
dests[id] = dest
|
||||||
|
end
|
||||||
|
if type(id) == "string" then
|
||||||
|
action_attr = action_attr .. "/S/GoTo/D" .. pdf_bytestring(id) .. ">>"
|
||||||
|
else
|
||||||
|
action_attr = string.format("%s/S/GoTo/D %i 0 R>>", action_attr, dest)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
id = assert(action.page, 'GoTo action must contain either an id or a page')
|
||||||
|
local tokens = action.tokens
|
||||||
|
if file then
|
||||||
|
action_attr = string.format("%s/S/GoToR/D[%i %s]>>", action_attr, id-1, tokens)
|
||||||
|
else
|
||||||
|
local page_objnum = pfile:reservepage(id)
|
||||||
|
action_attr = string.format("%s/S/GoTo/D[%i 0 R %s]>>", action_attr, page_objnum, tokens)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -222,20 +290,21 @@ local function write_link(p, link)
|
||||||
local quadStr = {}
|
local quadStr = {}
|
||||||
for i=1,#quads,8 do
|
for i=1,#quads,8 do
|
||||||
local x1, y1, x4, y4, x2, y2, x3, y3 = table.unpack(quads, i, i+7)
|
local x1, y1, x4, y4, x2, y2, x3, y3 = table.unpack(quads, i, i+7)
|
||||||
x1, y1, x2, y2, x3, y3, x4, y4 = sp2bp(x1), sp2bp(y1), sp2bp(x2), sp2bp(y2), sp2bp(x3), sp2bp(y3), sp2bp(x4), sp2bp(y4)
|
x1, y1, x2, y2, x3, y3, x4, y4 = to_bp(x1), to_bp(y1), to_bp(x2), to_bp(y2), to_bp(x3), to_bp(y3), to_bp(x4), to_bp(y4)
|
||||||
quadStr[i//8+1] = string.format("%f %f %f %f %f %f %f %f", x1, y1, x2, y2, x3, y3, x4, y4)
|
quadStr[i//8+1] = string.format("%f %f %f %f %f %f %f %f", x1, y1, x2, y2, x3, y3, x4, y4)
|
||||||
minX = math.min(minX, x1, x2, x3, x4)
|
minX = math.min(minX, x1, x2, x3, x4)
|
||||||
minY = math.min(minY, y1, y2, y3, y4)
|
minY = math.min(minY, y1, y2, y3, y4)
|
||||||
maxX = math.max(maxX, x1, x2, x3, x4)
|
maxX = math.max(maxX, x1, x2, x3, x4)
|
||||||
maxY = math.max(maxY, y1, y2, y3, y4)
|
maxY = math.max(maxY, y1, y2, y3, y4)
|
||||||
end
|
end
|
||||||
pfile:indirect(link.objnum, string.format("<</Type/Annot/Rect[%f %f %f %f]/QuadPoints[%s]%s>>", minX-.2, minY-.2, maxX+.2, maxY+.2, table.concat(quadStr, ' '), attr))
|
local boxes = strip_floats(string.format("/Rect[%f %f %f %f]/QuadPoints[%s]", minX-.2, minY-.2, maxX+.2, maxY+.2, table.concat(quadStr, ' ')))
|
||||||
|
pfile:indirect(link.objnum, string.format("<</Type/Annot%s%s>>", boxes, attr))
|
||||||
for i=1,#quads do quads[i] = nil end
|
for i=1,#quads do quads[i] = nil end
|
||||||
link.objnum = nil
|
link.objnum = nil
|
||||||
end
|
end
|
||||||
local function addlinkpoint(p, link, x, y, list, kind)
|
local function addlinkpoint(p, link, x, y, list, kind)
|
||||||
local quads = link.quads
|
local quads = link.quads
|
||||||
local off = pdf.variable.linkmargin
|
local off = pdfvariable.linkmargin
|
||||||
x = kind == 'start' and x-off or x+off
|
x = kind == 'start' and x-off or x+off
|
||||||
if link.annots and link.annots ~= p.annots then -- We started on another page, let's finish that before starting the new page
|
if link.annots and link.annots ~= p.annots then -- We started on another page, let's finish that before starting the new page
|
||||||
write_link(p, link)
|
write_link(p, link)
|
||||||
|
@ -257,12 +326,23 @@ local function addlinkpoint(p, link, x, y, list, kind)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function linkcontext_set(linkcontext, p, x, y, list, level, kind)
|
local function linkcontext_set(linkcontext, p, x, y, list, level, kind)
|
||||||
|
if not p.is_page then return end
|
||||||
for _,l in ipairs(linkcontext) do if l.level == level then
|
for _,l in ipairs(linkcontext) do if l.level == level then
|
||||||
addlinkpoint(p, l, x, y, list, level, kind)
|
addlinkpoint(p, l, x, y, list, level, kind)
|
||||||
end end
|
end end
|
||||||
end
|
end
|
||||||
|
|
||||||
function do_start_link(prop, p, n, x, y, outer, _, level)
|
local start_link_whatsit = declare_whatsit('pdf_start_link', function(prop, p, n, x, y, outer, _, level)
|
||||||
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_start_link whatsit', {"A pdf_start_link whatsit did not contain all necessary \z
|
||||||
|
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not p.is_page then
|
||||||
|
tex.error('pdf_start_link outside of page', {"PDF links are not allowed in Type3 charstrings or Form XObjects. \z
|
||||||
|
The link will be ignored"})
|
||||||
|
return
|
||||||
|
end
|
||||||
local links = p.linkcontext
|
local links = p.linkcontext
|
||||||
if not links then
|
if not links then
|
||||||
links = {set = linkcontext_set}
|
links = {set = linkcontext_set}
|
||||||
|
@ -271,25 +351,45 @@ function do_start_link(prop, p, n, x, y, outer, _, level)
|
||||||
local link = {quads = {}, attr = prop.link_attr, action = prop.action, level = level, force_separate = false} -- force_separate should become an option
|
local link = {quads = {}, attr = prop.link_attr, action = prop.action, level = level, force_separate = false} -- force_separate should become an option
|
||||||
links[#links+1] = link
|
links[#links+1] = link
|
||||||
addlinkpoint(p, link, x, y, outer, 'start')
|
addlinkpoint(p, link, x, y, outer, 'start')
|
||||||
end
|
end)
|
||||||
function do_end_link(prop, p, n, x, y, outer, _, level)
|
local end_link_whatsit = declare_whatsit('pdf_end_link', function(prop, p, n, x, y, outer, _, level)
|
||||||
|
if not p.is_page then
|
||||||
|
tex.error('pdf_start_link outside of page', {"PDF links are not allowed in Type3 charstrings or Form XObjects. \z
|
||||||
|
The link will be ignored"})
|
||||||
|
return
|
||||||
|
end
|
||||||
local links = p.linkcontext
|
local links = p.linkcontext
|
||||||
if not links then error"No link here to end" end
|
if not links then
|
||||||
|
tex.error('No link here to end', {"You asked me to end a link, but currently there is no link active. \z
|
||||||
|
Maybe you forgot to run \\pdfextension startlink first?"})
|
||||||
|
return
|
||||||
|
end
|
||||||
local link = links[#links]
|
local link = links[#links]
|
||||||
|
if link.level ~= level then
|
||||||
|
tex.error('Inconsistent link level', {"You asked me to end a link, but the most recent link had been started at another level. \z
|
||||||
|
I will continue with the link for now."})
|
||||||
|
return
|
||||||
|
end
|
||||||
links[#links] = nil
|
links[#links] = nil
|
||||||
if link.level ~= level then error"Wrong link level" end
|
if not links[1] then p.linkcontext = nil end
|
||||||
addlinkpoint(p, link, x, y, outer, 'final')
|
addlinkpoint(p, link, x, y, outer, 'final')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local do_setmatrix do
|
local setmatrix_whatsit do
|
||||||
local numberpattern = (lpeg.P'-'^-1 * lpeg.R'09'^0 * ('.' * lpeg.R'09'^0)^-1)/tonumber
|
local numberpattern = (lpeg.P'-'^-1 * lpeg.R'09'^0 * ('.' * lpeg.R'09'^0)^-1)/tonumber
|
||||||
local matrixpattern = numberpattern * ' ' * numberpattern * ' ' * numberpattern * ' ' * numberpattern
|
local matrixpattern = numberpattern * ' ' * numberpattern * ' ' * numberpattern * ' ' * numberpattern
|
||||||
function do_setmatrix(prop, p, n, x, y, outer)
|
setmatrix_whatsit = declare_whatsit('pdf_setmatrix', function(prop, p, n, x, y, outer)
|
||||||
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_setmatrix whatsit', {"A pdf_setmatrix whatsit did not contain a matrix value. \z
|
||||||
|
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
|
||||||
|
return
|
||||||
|
end
|
||||||
local m = p.matrix
|
local m = p.matrix
|
||||||
local a, b, c, d = matrixpattern:match(prop.data)
|
local a, b, c, d = matrixpattern:match(prop.data)
|
||||||
if not a then
|
if not a then
|
||||||
print(prop.data)
|
tex.error('Invalid matrix', {"The matrix in this pdf_setmatrix whatsit does not have the expected structure and could not be parsed. \z
|
||||||
error[[No valid matrix found]]
|
Did you provide enough parameters? The matrix needs exactly four decimal entries."})
|
||||||
|
return
|
||||||
end
|
end
|
||||||
local e, f = (1-a)*x-c*y, (1-d)*y-b*x -- Emulate that the origin is at x, y for this transformation
|
local e, f = (1-a)*x-c*y, (1-d)*y-b*x -- Emulate that the origin is at x, y for this transformation
|
||||||
-- (We could also first translate by (-x, -y), then apply the matrix
|
-- (We could also first translate by (-x, -y), then apply the matrix
|
||||||
|
@ -299,66 +399,98 @@ local do_setmatrix do
|
||||||
c, d = projected(m, c, d, 0)
|
c, d = projected(m, c, d, 0)
|
||||||
e, f = projected(m, e, f, 1)
|
e, f = projected(m, e, f, 1)
|
||||||
m[1], m[2], m[3], m[4], m[5], m[6] = a, b, c, d, e, f
|
m[1], m[2], m[3], m[4], m[5], m[6] = a, b, c, d, e, f
|
||||||
end
|
end)
|
||||||
end
|
end
|
||||||
local function do_save(prop, p, n, x, y, outer)
|
local save_whatsit = declare_whatsit('pdf_save', function(prop, p, n, x, y, outer)
|
||||||
pdf.write('page', 'q', x, y, p)
|
pdf.write('page', 'q', x, y, p)
|
||||||
local lastmatrix = p.matrix
|
local lastmatrix = p.matrix
|
||||||
p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)}
|
p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)}
|
||||||
end
|
end)
|
||||||
local function do_restore(prop, p, n, x, y, outer)
|
local restore_whatsit = declare_whatsit('pdf_restore', function(prop, p, n, x, y, outer)
|
||||||
-- TODO: Check x, y
|
-- TODO: Check x, y
|
||||||
pdf.write('page', 'Q', x, y, p)
|
pdf.write('page', 'Q', x, y, p)
|
||||||
p.matrix = p.matrix[0]
|
p.matrix = p.matrix[0]
|
||||||
end
|
end)
|
||||||
local function do_dest(prop, p, n, x, y)
|
local dest_whatsit = declare_whatsit('pdf_dest', function(prop, p, n, x, y)
|
||||||
-- TODO: Apply matrix
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_dest whatsit', {"A pdf_dest whatsit did not contain all necessary \z
|
||||||
|
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
|
||||||
|
end
|
||||||
assert(cur_page, "Destinations can not appear outside of a page")
|
assert(cur_page, "Destinations can not appear outside of a page")
|
||||||
local id = prop.dest_id
|
local id = prop.dest_id
|
||||||
local dest_type = prop.dest_type
|
local dest_type = prop.dest_type
|
||||||
|
local off = pdfvariable.linkmargin
|
||||||
local data
|
local data
|
||||||
if dest_type == "xyz" then
|
if dest_type == "xyz" then
|
||||||
|
local x, y = projected(p.matrix, x, y)
|
||||||
local zoom = prop.xyz_zoom
|
local zoom = prop.xyz_zoom
|
||||||
if zoom then
|
if zoom then
|
||||||
data = string.format("[%i 0 R/XYZ %.5f %.5f %.3f]", cur_page, sp2bp(x), sp2bp(y), prop.zoom/1000)
|
data = string.format("[%i 0 R/XYZ %.5f %.5f %.3f]", cur_page, to_bp(x-off), to_bp(y+off), prop.zoom/1000)
|
||||||
else
|
else
|
||||||
data = string.format("[%i 0 R/XYZ %.5f %.5f null]", cur_page, sp2bp(x), sp2bp(y))
|
data = string.format("[%i 0 R/XYZ %.5f %.5f null]", cur_page, to_bp(x-off), to_bp(y+off))
|
||||||
end
|
end
|
||||||
elseif dest_type == "fitr" then
|
elseif dest_type == "fitr" then
|
||||||
data = string.format("[%i 0 R/FitR %.5f %.5f %.5f %.5f]", cur_page, sp2bp(x), sp2bp(y + prop.depth), sp2bp(x + prop.width), sp2bp(y - prop.height))
|
local m = p.matrix
|
||||||
|
local llx, lly = projected(x, x - prop.depth)
|
||||||
|
local lrx, lry = projected(x+prop.width, x - prop.depth)
|
||||||
|
local ulx, uly = projected(x, x + prop.height)
|
||||||
|
local urx, ury = projected(x+prop.width, x + prop.height)
|
||||||
|
local left, lower, right, upper = math.min(llx, lrx, ulx, urx), math.min(lly, lry, uly, ury),
|
||||||
|
math.max(llx, lrx, ulx, urx), math.max(lly, lry, uly, ury)
|
||||||
|
data = string.format("[%i 0 R/FitR %.5f %.5f %.5f %.5f]", cur_page, to_bp(left-off), to_bp(lower-off), to_bp(right+off), to_bp(upper+off))
|
||||||
elseif dest_type == "fit" then
|
elseif dest_type == "fit" then
|
||||||
data = string.format("[%i 0 R/Fit]", cur_page)
|
data = string.format("[%i 0 R/Fit]", cur_page)
|
||||||
elseif dest_type == "fith" then
|
elseif dest_type == "fith" then
|
||||||
data = string.format("[%i 0 R/FitH %.5f]", cur_page, sp2bp(y))
|
local x, y = projected(p.matrix, x, y)
|
||||||
|
data = string.format("[%i 0 R/FitH %.5f]", cur_page, to_bp(y+off))
|
||||||
elseif dest_type == "fitv" then
|
elseif dest_type == "fitv" then
|
||||||
data = string.format("[%i 0 R/FitV %.5f]", cur_page, sp2bp(x))
|
local x, y = projected(p.matrix, x, y)
|
||||||
|
data = string.format("[%i 0 R/FitV %.5f]", cur_page, to_bp(x-off))
|
||||||
elseif dest_type == "fitb" then
|
elseif dest_type == "fitb" then
|
||||||
data = string.format("[%i 0 R/FitB]", cur_page)
|
data = string.format("[%i 0 R/FitB]", cur_page)
|
||||||
elseif dest_type == "fitbh" then
|
elseif dest_type == "fitbh" then
|
||||||
data = string.format("[%i 0 R/FitBH %.5f]", cur_page, sp2bp(y))
|
local x, y = projected(p.matrix, x, y)
|
||||||
|
data = string.format("[%i 0 R/FitBH %.5f]", cur_page, to_bp(y+off))
|
||||||
elseif dest_type == "fitbv" then
|
elseif dest_type == "fitbv" then
|
||||||
data = string.format("[%i 0 R/FitBV %.5f]", cur_page, sp2bp(x))
|
local x, y = projected(p.matrix, x, y)
|
||||||
|
data = string.format("[%i 0 R/FitBV %.5f]", cur_page, to_bp(x-off))
|
||||||
end
|
end
|
||||||
if pfile:written(dests[id]) then
|
if pfile:written(dests[id]) then
|
||||||
texio.write_nl(string.format("Duplicate destination %q", id))
|
texio.write_nl(string.format("Duplicate destination %q", id))
|
||||||
else
|
else
|
||||||
dests[id] = pfile:indirect(dests[id], data)
|
dests[id] = pfile:indirect(dests[id], strip_floats(data))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local refobj_whatsit = declare_whatsit('pdf_refobj', function(prop, p, n, x, y)
|
||||||
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_refobj whatsit', {"A pdf_refobj whatsit did not reference any object. \z
|
||||||
|
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
|
||||||
local function do_refobj(prop, p, n, x, y)
|
|
||||||
pfile:reference(prop.obj)
|
pfile:reference(prop.obj)
|
||||||
end
|
end)
|
||||||
local function do_literal(prop, p, n, x, y)
|
local literal_whatsit = declare_whatsit('pdf_literal', function(prop, p, n, x, y)
|
||||||
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_literal whatsit', "A pdf_literal whatsit did not contain a literal to be inserted. \z
|
||||||
|
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?")
|
||||||
|
return
|
||||||
|
end
|
||||||
pdf.write(prop.mode, prop.data, x, y, p)
|
pdf.write(prop.mode, prop.data, x, y, p)
|
||||||
end
|
end)
|
||||||
local function do_colorstack(prop, p, n, x, y)
|
local colorstack_whatsit = declare_whatsit('pdf_colorstack', function(prop, p, n, x, y)
|
||||||
|
if not prop then
|
||||||
|
tex.error('Invalid pdf_colorstack whatsit', {"A pdf_colorstack whatsit did not contain all necessary \z
|
||||||
|
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
|
||||||
|
return
|
||||||
|
end
|
||||||
local colorstack = prop.colorstack
|
local colorstack = prop.colorstack
|
||||||
local stack
|
local stack
|
||||||
if p.is_page then
|
if p.is_page then
|
||||||
stack = colorstack.page_stack
|
stack = colorstack.page_stack
|
||||||
elseif prop.last_form == resources then
|
elseif colorstack.last_form == p.resources then
|
||||||
stack = colorstack.form_stack
|
stack = colorstack.form_stack
|
||||||
else
|
else
|
||||||
|
colorstack.last_form = p.resources
|
||||||
stack = {prop.default}
|
stack = {prop.default}
|
||||||
colorstack.form_stack = stack
|
colorstack.form_stack = stack
|
||||||
end
|
end
|
||||||
|
@ -371,28 +503,33 @@ local function do_colorstack(prop, p, n, x, y)
|
||||||
stack[#stack] = prop.data
|
stack[#stack] = prop.data
|
||||||
end
|
end
|
||||||
pdf.write(colorstack.mode, stack[#stack], x, y, p)
|
pdf.write(colorstack.mode, stack[#stack], x, y, p)
|
||||||
end
|
end)
|
||||||
local function write_colorstack()
|
local function write_colorstack()
|
||||||
local idx = token.scan_int()
|
local idx = token.scan_int()
|
||||||
local colorstack = colorstacks[idx + 1]
|
local colorstack = colorstacks[idx + 1]
|
||||||
if not colorstack then
|
if not colorstack then
|
||||||
error[[Undefined colorstack]]
|
tex.error('Undefined colorstack', {"The requested colorstack is not initialized. \z
|
||||||
|
This probably means that you forgot to run \\pdffeedback colorstackinit or \z
|
||||||
|
that you specified the wrong index. I will continue with colorstack 0."})
|
||||||
|
colorstack = colorstacks[1]
|
||||||
end
|
end
|
||||||
local action = token.scan_keyword'pop' and 'pop'
|
local action = token.scan_keyword'pop' and 'pop'
|
||||||
or token.scan_keyword'set' and 'set'
|
or token.scan_keyword'set' and 'set'
|
||||||
or token.scan_keyword'current' and 'current'
|
or token.scan_keyword'current' and 'current'
|
||||||
or token.scan_keyword'push' and 'push'
|
or token.scan_keyword'push' and 'push'
|
||||||
if not action then
|
if not action then
|
||||||
error[[Missing action specifier for colorstack command]]
|
tex.error('Missing action specifier for colorstack', {
|
||||||
|
"I don't know what you want to do with this colorstack. I would have expected pop/set/current or push here. \z
|
||||||
|
I will ignore this command."})
|
||||||
|
return
|
||||||
end
|
end
|
||||||
local text
|
local text
|
||||||
if action == "push" or "set" then
|
if action == "push" or "set" then
|
||||||
text = token.scan_string()
|
text = token.scan_string()
|
||||||
-- text = token.to_string(token.scan_tokenlist()) -- Attention! This should never be executed in an expand-only context
|
-- text = token.to_string(token.scan_tokenlist()) -- Attention! This should never be executed in an expand-only context
|
||||||
end
|
end
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_colorstack)
|
local whatsit = node.new(whatsit_id, colorstack_whatsit)
|
||||||
node.setproperty(whatsit, {
|
node.setproperty(whatsit, {
|
||||||
handle = do_colorstack,
|
|
||||||
colorstack = colorstack,
|
colorstack = colorstack,
|
||||||
action = action,
|
action = action,
|
||||||
data = text,
|
data = text,
|
||||||
|
@ -416,9 +553,15 @@ local function scan_action()
|
||||||
file = token.scan_keyword'file' and token.scan_string(),
|
file = token.scan_keyword'file' and token.scan_string(),
|
||||||
}
|
}
|
||||||
if token.scan_keyword'page' then
|
if token.scan_keyword'page' then
|
||||||
error[[TODO]]
|
assert(action_type == 1)
|
||||||
|
local page = token.scan_int()
|
||||||
|
if page <= 0 then
|
||||||
|
error[[page must be positive in action specified]]
|
||||||
|
end
|
||||||
|
action.page = page
|
||||||
|
action.tokens = token.scan_string()
|
||||||
elseif token.scan_keyword'num' then
|
elseif token.scan_keyword'num' then
|
||||||
if action.file and action_type == 3 then
|
if action.file and action_type == 1 then
|
||||||
error[[num style GoTo actions must be internal]]
|
error[[num style GoTo actions must be internal]]
|
||||||
end
|
end
|
||||||
action.id = token.scan_int()
|
action.id = token.scan_int()
|
||||||
|
@ -476,57 +619,54 @@ token.luacmd("pdfextension", function(_, imm)
|
||||||
elseif token.scan_keyword"literal" then
|
elseif token.scan_keyword"literal" then
|
||||||
local mode = scan_literal_mode()
|
local mode = scan_literal_mode()
|
||||||
local literal = token.scan_string()
|
local literal = token.scan_string()
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_literal)
|
local whatsit = node.new(whatsit_id, literal_whatsit)
|
||||||
node.setproperty(whatsit, {
|
node.setproperty(whatsit, {
|
||||||
handle = do_literal,
|
|
||||||
mode = mode,
|
mode = mode,
|
||||||
data = literal,
|
data = literal,
|
||||||
})
|
})
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"startlink" then
|
elseif token.scan_keyword"startlink" then
|
||||||
local pfile = get_pfile()
|
local pfile = get_pfile()
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_start_link)
|
local whatsit = node.new(whatsit_id, start_link_whatsit)
|
||||||
local attr = token.scan_keyword'attr' and token.scan_string() or ''
|
local attr = token.scan_keyword'attr' and token.scan_string() or ''
|
||||||
local action = scan_action()
|
local action = scan_action()
|
||||||
local objnum = pfile:getobj()
|
local objnum = pfile:getobj()
|
||||||
lastannot = num
|
lastannot = num
|
||||||
node.setproperty(whatsit, {
|
node.setproperty(whatsit, {
|
||||||
handle = do_start_link,
|
|
||||||
link_attr = attr,
|
link_attr = attr,
|
||||||
action = action,
|
action = action,
|
||||||
objnum = objnum,
|
objnum = objnum,
|
||||||
})
|
})
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"endlink" then
|
elseif token.scan_keyword"endlink" then
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_end_link)
|
local whatsit = node.new(whatsit_id, end_link_whatsit)
|
||||||
node.setproperty(whatsit, {
|
|
||||||
handle = do_end_link,
|
|
||||||
})
|
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"save" then
|
elseif token.scan_keyword"save" then
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_save)
|
local whatsit = node.new(whatsit_id, save_whatsit)
|
||||||
node.setproperty(whatsit, {
|
|
||||||
handle = do_save,
|
|
||||||
})
|
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"setmatrix" then
|
elseif token.scan_keyword"setmatrix" then
|
||||||
local matrix = token.scan_string()
|
local matrix = token.scan_string()
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_setmatrix)
|
local whatsit = node.new(whatsit_id, setmatrix_whatsit)
|
||||||
node.setproperty(whatsit, {
|
node.setproperty(whatsit, {
|
||||||
handle = do_setmatrix,
|
|
||||||
data = matrix,
|
data = matrix,
|
||||||
})
|
})
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"restore" then
|
elseif token.scan_keyword"restore" then
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_restore)
|
local whatsit = node.new(whatsit_id, restore_whatsit)
|
||||||
node.setproperty(whatsit, {
|
|
||||||
handle = do_restore,
|
|
||||||
})
|
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"info" then
|
elseif token.scan_keyword"info" then
|
||||||
infodir = infodir .. token.scan_string()
|
infodir = infodir .. token.scan_string()
|
||||||
elseif token.scan_keyword"catalog" then
|
elseif token.scan_keyword"catalog" then
|
||||||
catalogdir = catalogdir .. ' ' .. token.scan_string()
|
catalogdir = catalogdir .. ' ' .. token.scan_string()
|
||||||
|
if token.scan_keyword'openaction' then
|
||||||
|
if catalog_openaction then
|
||||||
|
tex.error("Duplicate openaction", {"Only one use of \\pdfextension catalog is allowed to \z
|
||||||
|
have an openaction."})
|
||||||
|
else
|
||||||
|
local action = scan_action()
|
||||||
|
catalog_openaction = get_action_attr(get_pfile(), action)
|
||||||
|
end
|
||||||
|
end
|
||||||
elseif token.scan_keyword"names" then
|
elseif token.scan_keyword"names" then
|
||||||
namesdir = namesdir .. ' ' .. token.scan_string()
|
namesdir = namesdir .. ' ' .. token.scan_string()
|
||||||
elseif token.scan_keyword"obj" then
|
elseif token.scan_keyword"obj" then
|
||||||
|
@ -555,10 +695,9 @@ token.luacmd("pdfextension", function(_, imm)
|
||||||
end
|
end
|
||||||
elseif token.scan_keyword"refobj" then
|
elseif token.scan_keyword"refobj" then
|
||||||
local num = token.scan_int()
|
local num = token.scan_int()
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_refobj)
|
local whatsit = node.new(whatsit_id, refobj_whatsit)
|
||||||
node.setproperty(whatsit, {
|
node.setproperty(whatsit, {
|
||||||
obj = num,
|
obj = num,
|
||||||
handle = do_refobj,
|
|
||||||
})
|
})
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
elseif token.scan_keyword"outline" then
|
elseif token.scan_keyword"outline" then
|
||||||
|
@ -594,10 +733,9 @@ token.luacmd("pdfextension", function(_, imm)
|
||||||
else
|
else
|
||||||
error[[Unsupported id type]]
|
error[[Unsupported id type]]
|
||||||
end
|
end
|
||||||
local whatsit = node.new(whatsit_id, whatsits.pdf_dest)
|
local whatsit = node.new(whatsit_id, dest_whatsit)
|
||||||
local prop = {
|
local prop = {
|
||||||
dest_id = id,
|
dest_id = id,
|
||||||
handle = do_dest,
|
|
||||||
}
|
}
|
||||||
node.setproperty(whatsit, prop)
|
node.setproperty(whatsit, prop)
|
||||||
if token.scan_keyword'xyz' then
|
if token.scan_keyword'xyz' then
|
||||||
|
@ -640,9 +778,129 @@ token.luacmd("pdfextension", function(_, imm)
|
||||||
error[[Unsupported dest type]]
|
error[[Unsupported dest type]]
|
||||||
end
|
end
|
||||||
node.write(whatsit)
|
node.write(whatsit)
|
||||||
|
elseif token.scan_keyword'mapline' then
|
||||||
|
fontmap.mapline(token.scan_string())
|
||||||
else
|
else
|
||||||
-- The following error message gobbles the next word as a side effect.
|
-- The following error message gobbles the next word as a side effect.
|
||||||
-- This is intentional to make error-recovery easier.
|
-- This is intentional to make error-recovery easier.
|
||||||
error(string.format("Unknown PDF extension %s", token.scan_word()))
|
error(string.format("Unknown PDF extension %s", token.scan_word()))
|
||||||
end
|
end
|
||||||
end, "protected")
|
end, "protected")
|
||||||
|
local imglib = require'luametalatex-pdf-image'
|
||||||
|
local imglib_node = imglib.node
|
||||||
|
local imglib_write = imglib.write
|
||||||
|
local imglib_immediatewrite = imglib.immediatewrite
|
||||||
|
img = {
|
||||||
|
new = imglib.new,
|
||||||
|
scan = imglib.scan,
|
||||||
|
node = function(img, pfile) return imglib_node(pfile or get_pfile(), img) end,
|
||||||
|
write = function(img, pfile) return imglib_write(pfile or get_pfile(), img) end,
|
||||||
|
immediatewrite = function(img, pfile) return imglib_immediatewrite(pfile or get_pfile(), img) end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local lastimage = -1
|
||||||
|
local lastimagepages = -1
|
||||||
|
|
||||||
|
-- These are very minimal right now but LaTeX isn't using the scaling etc. stuff anyway.
|
||||||
|
token.luacmd("saveimageresource", function(imm)
|
||||||
|
local attr = token.scan_keyword'attr' and token.scan_string() or nil
|
||||||
|
local page = token.scan_keyword'page' and token.scan_int() or nil
|
||||||
|
local userpassword = token.scan_keyword'userpassword' and token.scan_string() or nil
|
||||||
|
local ownerpassword = token.scan_keyword'ownerpassword' and token.scan_string() or nil
|
||||||
|
-- local colorspace = token.scan_keyword'colorspace' and token.scan_int() or nil -- Doesn't make sense for PDF
|
||||||
|
local pagebox = token.scan_keyword'mediabox' and 'media'
|
||||||
|
or token.scan_keyword'cropbox' and 'crop'
|
||||||
|
or token.scan_keyword'bleedbox' and 'bleed'
|
||||||
|
or token.scan_keyword'trimbox' and 'trim'
|
||||||
|
or token.scan_keyword'artbox' and 'art'
|
||||||
|
or nil
|
||||||
|
local filename = token.scan_string()
|
||||||
|
local img = imglib.scan{
|
||||||
|
attr = attr,
|
||||||
|
page = page,
|
||||||
|
userpassword = userpassword,
|
||||||
|
ownerpassword = ownerpassword,
|
||||||
|
pagebox = pagebox,
|
||||||
|
filename = filename,
|
||||||
|
}
|
||||||
|
local pfile = get_pfile()
|
||||||
|
lastimage = imglib.get_num(pfile, img)
|
||||||
|
lastimagepages = img.pages or 1
|
||||||
|
if imm == 'immediate' then
|
||||||
|
imglib_immediatewrite(pfile, img)
|
||||||
|
end
|
||||||
|
end, "protected")
|
||||||
|
|
||||||
|
token.luacmd("useimageresource", function()
|
||||||
|
local pfile = get_pfile()
|
||||||
|
local img = assert(imglib.from_num(token.scan_int()))
|
||||||
|
imglib_write(pfile, img)
|
||||||
|
end, "protected")
|
||||||
|
|
||||||
|
local value_values = token.values'value'
|
||||||
|
for i=0,#value_values do
|
||||||
|
value_values[value_values[i]] = i
|
||||||
|
end
|
||||||
|
local integer_code = value_values.integer
|
||||||
|
|
||||||
|
token.luacmd("lastsavedimageresourceindex", function()
|
||||||
|
return integer_code, lastimage
|
||||||
|
end, "protected", "value")
|
||||||
|
|
||||||
|
token.luacmd("lastsavedimageresourcepages", function()
|
||||||
|
return integer_code, lastimagepages
|
||||||
|
end, "protected", "value")
|
||||||
|
|
||||||
|
local savedbox = require'luametalatex-pdf-savedbox'
|
||||||
|
local savedbox_save = savedbox.save
|
||||||
|
function tex.saveboxresource(n, attr, resources, immediate, type, margin, pfile)
|
||||||
|
if not node.type(n) then
|
||||||
|
n = tonumber(n)
|
||||||
|
if not n then
|
||||||
|
error[[Invalid argument to saveboxresource]]
|
||||||
|
end
|
||||||
|
token.put_next(token.create'box', token.new(n, token.command_id'char_given'))
|
||||||
|
n = token.scan_list()
|
||||||
|
end
|
||||||
|
margin = margin or tex.sp'1bp' -- FIXME: default margin variable
|
||||||
|
return savedbox_save(pfile or get_pfile(), n, attr, resources, immediate, type, margin, fontdirs, usedglyphs)
|
||||||
|
end
|
||||||
|
tex.useboxresource = savedbox.use
|
||||||
|
|
||||||
|
local lastbox = -1
|
||||||
|
|
||||||
|
token.luacmd("saveboxresource", function(imm)
|
||||||
|
local type
|
||||||
|
if token.scan_keyword'type' then
|
||||||
|
texio.write_nl('XForm type attribute ignored')
|
||||||
|
type = token.scan_int()
|
||||||
|
end
|
||||||
|
local attr = token.scan_keyword'attr' and token.scan_string() or nil
|
||||||
|
local resources = token.scan_keyword'resources' and token.scan_string() or nil
|
||||||
|
local margin = token.scan_keyword'margin' and token.scan_dimen() or nil
|
||||||
|
local box = token.scan_int()
|
||||||
|
|
||||||
|
local index = tex.saveboxresource(box, attr, resources, imm == 'immediate', type, margin)
|
||||||
|
lastbox = index
|
||||||
|
end, "protected")
|
||||||
|
|
||||||
|
token.luacmd("useboxresource", function()
|
||||||
|
local width, height, depth
|
||||||
|
while true do
|
||||||
|
if token.scan_keyword'width' then
|
||||||
|
width = token.scan_dimen()
|
||||||
|
elseif token.scan_keyword'height' then
|
||||||
|
height = token.scan_dimen()
|
||||||
|
elseif token.scan_keyword'depth' then
|
||||||
|
depth = token.scan_dimen()
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local index = token.scan_int()
|
||||||
|
node.write((tex.useboxresource(index, width, height, depth)))
|
||||||
|
end, "protected")
|
||||||
|
|
||||||
|
token.luacmd("lastsavedboxresourceindex", function()
|
||||||
|
return integer_code, lastbox
|
||||||
|
end, "protected", "value")
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
local value_values = token.values'value'
|
||||||
|
for i=0,#value_values do
|
||||||
|
value_values[value_values[i]] = i
|
||||||
|
end
|
||||||
|
local count_code = value_values.integer
|
||||||
|
local dimen_code = value_values.dimension
|
||||||
|
|
||||||
|
local set_local = require'luametalatex-local'
|
||||||
|
|
||||||
|
local texmeta = getmetatable(tex)
|
||||||
|
local texmetaoldindex = texmeta.__index
|
||||||
|
local texmetaoldnewindex = texmeta.__newindex
|
||||||
|
|
||||||
|
local tex_variables = __luametalatex__preserved_tex_variables or {}
|
||||||
|
__luametalatex__preserved_tex_variables = nil
|
||||||
|
|
||||||
|
function texmeta.__index(t, k)
|
||||||
|
return tex_variables[k] or texmetaoldindex(t, k)
|
||||||
|
end
|
||||||
|
function texmeta.__newindex(t, k, v)
|
||||||
|
if tex_variables[k] then
|
||||||
|
return set_local(tex_variables, k, v)
|
||||||
|
else
|
||||||
|
return texmetaoldnewindex(t, k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tex_variable(value, scanner, name, default)
|
||||||
|
token.luacmd(name, function(_, scanning)
|
||||||
|
if scanning == 'value' then
|
||||||
|
return value, tex_variables[name]
|
||||||
|
else
|
||||||
|
token.scan_keyword'='
|
||||||
|
return set_local(tex_variables, name, scanner(), scanning == 'global')
|
||||||
|
end
|
||||||
|
end, 'global', 'protected', 'value')
|
||||||
|
if status.ini_version then
|
||||||
|
tex_variables[name] = default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local real_pdf_variables = __luametalatex__preserved_real_pdf_variables or {}
|
||||||
|
__luametalatex__preserved_real_pdf_variables = nil
|
||||||
|
local pdf_variable_names = {}
|
||||||
|
local pdf_toks_map = {}
|
||||||
|
local pdf_variables = setmetatable(pdf.variable, {
|
||||||
|
__index = function(_, k)
|
||||||
|
local v = real_pdf_variables[k]
|
||||||
|
if v then return v end
|
||||||
|
v = pdf_toks_map[k]
|
||||||
|
if v then
|
||||||
|
return tex.toks[v]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
__newindex = function(_, k, v)
|
||||||
|
if real_pdf_variables[k] then
|
||||||
|
return set_local(real_pdf_variables, k, v)
|
||||||
|
end
|
||||||
|
local toks = pdf_toks_map[k]
|
||||||
|
if toks then
|
||||||
|
tex.toks[toks] = v
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
pdf.variable_names = pdf_variable_names
|
||||||
|
|
||||||
|
local pdf_toks
|
||||||
|
if status.ini_version then
|
||||||
|
local pdf_toks_list = {}
|
||||||
|
function pdf_toks(name, default)
|
||||||
|
pdf_variable_names[#pdf_variable_names+1] = name
|
||||||
|
pdf_toks_list[#pdf_toks_list+1] = {name, default}
|
||||||
|
end
|
||||||
|
function initialize_pdf_toks()
|
||||||
|
for i=1,#pdf_toks_list do
|
||||||
|
local entry = pdf_toks_list[i]
|
||||||
|
local csname = 'pdfvariable ' .. entry[1]
|
||||||
|
token.set_char(csname, 0) -- Ensure that csname exists
|
||||||
|
local t = token.create(csname)
|
||||||
|
tex.runtoks(function()
|
||||||
|
token.put_next(token.create'newtoks', t)
|
||||||
|
end)
|
||||||
|
pdf_toks_map[entry[1]] = t.index
|
||||||
|
tex.toks[t.index] = entry[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function pdf_toks(name, default)
|
||||||
|
pdf_variable_names[#pdf_variable_names+1] = name
|
||||||
|
local t = token.create('pdfvariable ' .. name)
|
||||||
|
pdf_toks_map[name] = t.index
|
||||||
|
tex.toks[t.index] = default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pdf_variable(value, scanner, name, default, force_default)
|
||||||
|
pdf_variable_names[#pdf_variable_names+1] = name
|
||||||
|
token.luacmd('pdfvariable ' .. name, function(_, scanning)
|
||||||
|
if scanning == 'value' then
|
||||||
|
return value, real_pdf_variables[name]
|
||||||
|
elseif force_default then
|
||||||
|
token.scan_keyword'='
|
||||||
|
local new = scanner()
|
||||||
|
if new ~= default then
|
||||||
|
texio.write_nl('term and log', string.format("Unsupported PDF variable: \z
|
||||||
|
%q is not supported and fixed to %i, but you tried to set it to %i", name, default, new))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
token.scan_keyword'='
|
||||||
|
return set_local(real_pdf_variables, name, scanner(), scanning == 'global')
|
||||||
|
end
|
||||||
|
end, 'global', 'protected', 'value')
|
||||||
|
if status.ini_version then
|
||||||
|
real_pdf_variables[name] = default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tex_variable(count_code, token.scan_int, 'suppressfontnotfounderror', 0)
|
||||||
|
tex_variable(count_code, token.scan_int, 'outputmode', 1) -- The "traditional" default would be 0,
|
||||||
|
-- but we do not actually support that.
|
||||||
|
tex_variable(dimen_code, token.scan_dimen, 'pageheight', tex.sp'297mm')
|
||||||
|
tex_variable(dimen_code, token.scan_dimen, 'pagewidth', tex.sp'210mm')
|
||||||
|
|
||||||
|
tex_variable(count_code, token.scan_int, 'bodydirection', 0)
|
||||||
|
tex_variable(count_code, token.scan_int, 'pagedirection', 0)
|
||||||
|
|
||||||
|
pdf_variable(dimen_code, token.scan_dimen, 'horigin', tex.sp'1in')
|
||||||
|
pdf_variable(dimen_code, token.scan_dimen, 'vorigin', tex.sp'1in')
|
||||||
|
pdf_variable(dimen_code, token.scan_dimen, 'linkmargin', tex.sp'0pt')
|
||||||
|
pdf_variable(dimen_code, token.scan_dimen, 'destmargin', tex.sp'0pt')
|
||||||
|
pdf_variable(dimen_code, token.scan_dimen, 'threadmargin', tex.sp'0pt') -- We don't support threads, so this isn't doing anything
|
||||||
|
pdf_variable(count_code, token.scan_int, 'majorversion', 1)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'minorversion', 7)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'compresslevel', 9)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'objcompresslevel', 3)
|
||||||
|
|
||||||
|
pdf_variable(count_code, token.scan_int, 'decimaldigits', 4, true) -- Will probably stay fixed, but should be more consistent
|
||||||
|
pdf_variable(count_code, token.scan_int, 'gentounicode', 0, true) -- We expect the fontloader to generade tounicode tables. Might change at some point
|
||||||
|
-- These two are ignored, but that is consistent with pdfTeX as long as imageapplygamma is 0:
|
||||||
|
pdf_variable(count_code, token.scan_int, 'gamma', 1000)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'imagegamma', 1000)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'imageapplygamma', 0, true)
|
||||||
|
pdf_variable(count_code, token.scan_int, 'imagehicolor', 1, true) -- We don't consider ancient PDF versions, no no reason to strip images
|
||||||
|
pdf_variable(count_code, token.scan_int, 'imageaddfilename', 0, true) -- Could be added, but I never saw a reason for this anyway.
|
||||||
|
pdf_variable(count_code, token.scan_int, 'inclusionerrorlevel', -1, true) -- FIXME: At least a warning should be supported
|
||||||
|
pdf_variable(count_code, token.scan_int, 'inclusioncopyfonts', 0, true) -- Would be fragile and restrict our ability to use "creative" font constructs
|
||||||
|
pdf_variable(count_code, token.scan_int, 'uniqueresname', 0, true) -- I add this if you show me a usecase
|
||||||
|
pdf_variable(count_code, token.scan_int, 'pagebox', 2, true) -- TODO (1: media, 2: crop, 3: bleed, 4: trim, 5: art
|
||||||
|
pdf_variable(count_code, token.scan_int, 'forcepagebox', 0, true) -- Considered obsolete even in pdfTeX
|
||||||
|
pdf_variable(count_code, token.scan_int, 'imageresolution', 72, true) -- TODO Also 0 should be the same as 72 ?!?!?!?
|
||||||
|
|
||||||
|
pdf_variable(count_code, token.scan_int, 'pkresolution', 1200) -- Original default is 72, but that's crazy
|
||||||
|
pdf_variable(count_code, token.scan_int, 'pkfixeddpi', 0) -- TODO: Implemented, but even when set to one, font sharing doesn't adapt yet.
|
||||||
|
-- Changing that is complicated because it has to be known pretty early.
|
||||||
|
pdf_toks('pkmode', '')
|
||||||
|
|
||||||
|
pdf_toks('pageattr', '')
|
||||||
|
pdf_toks('pagesattr', '')
|
||||||
|
pdf_toks('pageresources', '')
|
||||||
|
|
||||||
|
-- The following two are special: The should have force_default=true because we ignore them,
|
||||||
|
-- but they are token lists so that doesn't really work. Instead, we check during shipout that
|
||||||
|
-- the variables are empty. TODO: Find a nicer solution
|
||||||
|
-- The reason for not implementing them is that XForm specific resources and attributes make
|
||||||
|
-- much more sense, so these two generic ones are useless and error-prone.
|
||||||
|
pdf_toks('xformresources', '')
|
||||||
|
pdf_toks('xformattr', '')
|
||||||
|
|
||||||
|
function tex.getbodydir() return tex.bodydirection end
|
||||||
|
function tex.getpagedir() return tex.pagedirection end
|
||||||
|
function tex.setbodydir(i) tex.bodydirection = i end
|
||||||
|
function tex.setpagedir(i) tex.pagedirection = i end
|
||||||
|
local dir_regs = require 'luametalatex-dir-registers'
|
||||||
|
dir_regs 'textdir'
|
||||||
|
dir_regs 'bodydir'
|
||||||
|
dir_regs 'pagedir'
|
||||||
|
|
||||||
|
if status.ini_version then
|
||||||
|
-- Run in pre_dump callback:
|
||||||
|
lua.prepared_code[#lua.prepared_code+1] = function()
|
||||||
|
local settings = " "
|
||||||
|
for k,v in next, {__luametalatex__preserved_tex_variables = tex_variables,
|
||||||
|
__luametalatex__preserved_real_pdf_variables = real_pdf_variables,} do
|
||||||
|
local entries = {}
|
||||||
|
for kk,vv in next, v do
|
||||||
|
-- entries[#entries+1] = string.format("[%q=%i],", kk, vv) -- If we ever get more compicated names here
|
||||||
|
entries[#entries+1] = string.format("%s=%i,", kk, vv)
|
||||||
|
end
|
||||||
|
settings = string.format("%s%s={%s}", settings, k, table.concat(entries))
|
||||||
|
end
|
||||||
|
return settings
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,124 +0,0 @@
|
||||||
\begingroup
|
|
||||||
\catcode`\^^^^ffff=11
|
|
||||||
\catcode`\@=11
|
|
||||||
\toks0{%
|
|
||||||
do
|
|
||||||
local function frozen(s)
|
|
||||||
local t = token.create(s)
|
|
||||||
return token.new(t.mode, t.command)
|
|
||||||
end
|
|
||||||
local dimen_cmd = token.command_id'assign_dimen'
|
|
||||||
local count_cmd = token.command_id'assign_int'
|
|
||||||
local toks_cmd = token.command_id'assign_toks'
|
|
||||||
local tex_params = {}
|
|
||||||
local texmeta = getmetatable(tex)
|
|
||||||
local texmetaoldindex = texmeta.__index
|
|
||||||
local texmetaoldnewindex = texmeta.__newindex
|
|
||||||
function texmeta.__index(t, k)
|
|
||||||
local v = tex_params[k]
|
|
||||||
if v then
|
|
||||||
if v.command == count_cmd then
|
|
||||||
return tex.count[v.index]
|
|
||||||
elseif v.command == dimen_cmd then
|
|
||||||
return tex.dimen[v.index]
|
|
||||||
elseif v.command == toks_cmd then
|
|
||||||
return tex.toks[v.index]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return texmetaoldindex(t, k)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function texmeta.__newindex(t, k, v)
|
|
||||||
local p = tex_params[k]
|
|
||||||
if p then
|
|
||||||
if p.command == count_cmd then
|
|
||||||
tex.count[p.index] = v
|
|
||||||
elseif p.command == dimen_cmd then
|
|
||||||
tex.dimen[p.index] = v
|
|
||||||
elseif p.command == toks_cmd then
|
|
||||||
tex.toks[p.index] = v
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return texmetaoldnewindex(t, k, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local pdf_params = {}
|
|
||||||
pdf.variable_tokens = pdf_params
|
|
||||||
pdf.variable = setmetatable({}, {
|
|
||||||
__index = function(t, k)
|
|
||||||
local v = pdf_params[k]
|
|
||||||
if v then
|
|
||||||
if v.command == count_cmd then
|
|
||||||
return tex.count[v.index]
|
|
||||||
elseif v.command == dimen_cmd then
|
|
||||||
return tex.dimen[v.index]
|
|
||||||
elseif v.command == toks_cmd then
|
|
||||||
return tex.toks[v.index]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return texmetaoldindex(t, k)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
__newindex = function(t, k, v)
|
|
||||||
local p = pdf_params[k]
|
|
||||||
if p then
|
|
||||||
if p.command == count_cmd then
|
|
||||||
tex.count[p.index] = v
|
|
||||||
elseif p.command == dimen_cmd then
|
|
||||||
tex.dimen[p.index] = v
|
|
||||||
elseif p.command == toks_cmd then
|
|
||||||
tex.toks[p.index] = v
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return texmetaoldnewindex(t, k, v)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
\def\InternalAlloc#1#2#3#4#5{%
|
|
||||||
\csname new#3\endcsname#1%
|
|
||||||
\global#1=#5\relax
|
|
||||||
\etoksapp0{#2_params["\luaescapestring{#4}"] = frozen"\luaescapestring{\csstring#1}"}
|
|
||||||
}
|
|
||||||
\def\internalAlloc#1#2#3{%
|
|
||||||
\expandafter\InternalAlloc\csname ^^^^fffe#3@#1\endcsname{#1}{#2}{#3}%
|
|
||||||
}
|
|
||||||
\def\texAlloc#1#2{%
|
|
||||||
\expandafter\InternalAlloc\csname #2\endcsname{tex}{#1}{#2}%
|
|
||||||
}
|
|
||||||
\def\pdfAlloc{%
|
|
||||||
\internalAlloc{pdf}%
|
|
||||||
}
|
|
||||||
\texAlloc{count}{suppressfontnotfounderror}{0}
|
|
||||||
\texAlloc{count}{outputmode}{1} % The "traditional" default would be 0,
|
|
||||||
% but we do not actually support that.
|
|
||||||
\texAlloc{dimen}{pageheight}{297mm}
|
|
||||||
\texAlloc{dimen}{pagewidth}{210mm}
|
|
||||||
\pdfAlloc{dimen}{horigin}{1in}
|
|
||||||
\pdfAlloc{dimen}{vorigin}{1in}
|
|
||||||
\pdfAlloc{dimen}{linkmargin}{0pt}
|
|
||||||
\pdfAlloc{count}{majorversion}{1}
|
|
||||||
\pdfAlloc{count}{minorversion}{7}
|
|
||||||
\pdfAlloc{count}{compresslevel}{0} % 0 is actually the only supported value right now, so this is basically ignored
|
|
||||||
\pdfAlloc{count}{objcompresslevel}{0} % see above
|
|
||||||
\pdfAlloc{toks}{pageresources}{{}}
|
|
||||||
|
|
||||||
\texAlloc{count}{bodydirection}{0}
|
|
||||||
\texAlloc{count}{pagedirection}{0}
|
|
||||||
\etoksapp0{
|
|
||||||
function tex.getbodydir() return tex.bodydirection end
|
|
||||||
function tex.getpagedir() return tex.pagedirection end
|
|
||||||
function tex.setbodydir(i) tex.bodydirection = i end
|
|
||||||
function tex.setpagedir(i) tex.pagedirection = i end
|
|
||||||
local dir_regs = require 'luametalatex-dir-registers'
|
|
||||||
dir_regs 'textdir'
|
|
||||||
dir_regs 'bodydir'
|
|
||||||
dir_regs 'pagedir'
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
\directlua{
|
|
||||||
lua.prepared_code[\csstring#lua.prepared_code+1] = tex.toks[0]
|
|
||||||
\the\toks0
|
|
||||||
}
|
|
||||||
\endgroup
|
|
|
@ -16,6 +16,6 @@ return {
|
||||||
return (i ^ j) & mask32
|
return (i ^ j) & mask32
|
||||||
end,
|
end,
|
||||||
extract = function(v, shift, count)
|
extract = function(v, shift, count)
|
||||||
return ((bit32 & v) >> shift) & ((1<<count)-1)
|
return ((mask32 & v) >> shift) & ((1<<count)-1)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
local concat = table.concat
|
||||||
|
local format = string.format
|
||||||
|
local ioopen = io.open
|
||||||
|
local assert = assert
|
||||||
|
local ipairs = ipairs
|
||||||
|
|
||||||
|
local _ENV = {}
|
||||||
|
|
||||||
|
local first, later =
|
||||||
|
'local __hidden_local__package_preload__=package.preload',
|
||||||
|
'\n__hidden_local__package_preload__[%q]=function(...)%s\nend'
|
||||||
|
|
||||||
|
first = first .. later
|
||||||
|
|
||||||
|
local list = {}
|
||||||
|
|
||||||
|
return function(t)
|
||||||
|
local length = #t
|
||||||
|
local tmpl = first
|
||||||
|
for i, mod in ipairs(t) do
|
||||||
|
local name, f = mod[1], assert(ioopen(mod[2], 'r'))
|
||||||
|
local data = f:read'a'
|
||||||
|
f:close()
|
||||||
|
list[i] = format(tmpl, name, data)
|
||||||
|
tmpl = later
|
||||||
|
end
|
||||||
|
return concat(list, nil, 1, length)
|
||||||
|
end
|
|
@ -0,0 +1,84 @@
|
||||||
|
-- Two callbacks are defined in other files: pre_dump in lateinit and find_fmt_file in init
|
||||||
|
|
||||||
|
local read_tfm = font.read_tfm
|
||||||
|
local font_define = font.define
|
||||||
|
local callback_register = callback.register
|
||||||
|
|
||||||
|
if status.ini_version then
|
||||||
|
callback_register('define_font', function(name, size)
|
||||||
|
local f = read_tfm(name, size)
|
||||||
|
if not f then return end
|
||||||
|
local id = font_define(f)
|
||||||
|
lua.prepared_code[#lua.prepared_code+1] = string.format("assert(%i == font.define(font.read_tfm(%q, %i)))", id, name, size)
|
||||||
|
return id
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
callback_register('define_font', function(name, size)
|
||||||
|
local f = read_tfm(name, size)
|
||||||
|
if not f then return end
|
||||||
|
return font.define(f)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
callback_register('find_log_file', function(name) return name end)
|
||||||
|
do
|
||||||
|
local function normal_find_data_file(name)
|
||||||
|
return kpse.find_file(name, 'tex', true)
|
||||||
|
end
|
||||||
|
if status.ini_version then
|
||||||
|
function unhook_expl()
|
||||||
|
callback_register('find_data_file', normal_find_data_file)
|
||||||
|
end
|
||||||
|
callback_register('find_data_file', function(name)
|
||||||
|
if name == 'ltexpl.ltx' then
|
||||||
|
name = 'luametalatex-ltexpl-hook'
|
||||||
|
end
|
||||||
|
return normal_find_data_file(name)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
callback_register('find_data_file', normal_find_data_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- callback_register('read_data_file', function(name) error[[TODO]]return kpse.find_file(name, 'tex', true) end)
|
||||||
|
callback_register('open_data_file', function(name)
|
||||||
|
local f = io.open(name)
|
||||||
|
return setmetatable({
|
||||||
|
reader = function()
|
||||||
|
local line = f:read()
|
||||||
|
return line
|
||||||
|
end,
|
||||||
|
close = function()error[[1]] return f:close() end,
|
||||||
|
}, {
|
||||||
|
__gc = function()f:close()end,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
callback_register('handle_error_hook', function()
|
||||||
|
repeat
|
||||||
|
texio.write_nl'? '
|
||||||
|
local line = io.read()
|
||||||
|
if not line then
|
||||||
|
tex.fatalerror'End of line encountered on terminal'
|
||||||
|
end
|
||||||
|
if line == "" then return 3 end
|
||||||
|
local first = line:sub(1,1):upper()
|
||||||
|
if first == 'H' then
|
||||||
|
texio.write(tex.gethelptext() or "Sorry, I don't know how to help in this situation.\n\z
|
||||||
|
Maybe you should try asking a human?")
|
||||||
|
elseif first == 'I' then
|
||||||
|
line = line:sub(2)
|
||||||
|
tex.runtoks(function()
|
||||||
|
tex.sprint(token.scan_token(), line)
|
||||||
|
end)
|
||||||
|
return 3
|
||||||
|
elseif first == 'Q' then texio.write'OK, entering \\batchmode...\n' return 0
|
||||||
|
elseif first == 'R' then texio.write'OK, entering \\nonstopmode...\n' return 1
|
||||||
|
elseif first == 'S' then texio.write'OK, entering \\scrollmode...\n' return 2
|
||||||
|
elseif first == 'X' then return -1
|
||||||
|
else
|
||||||
|
texio.write'Type <return> to proceed, S to scroll future error messages,\
|
||||||
|
\z R to run without stopping, Q to run quietly,\
|
||||||
|
\z I to insert something,\
|
||||||
|
\z H for help, X to quit.'
|
||||||
|
end
|
||||||
|
until false
|
||||||
|
return 3
|
||||||
|
end)
|
|
@ -11,16 +11,13 @@ function tex.gettextdir() return tex.textdirection end
|
||||||
function tex.getlinedir() return tex.linedirection end
|
function tex.getlinedir() return tex.linedirection end
|
||||||
function tex.getmathdir() return tex.mathdirection end
|
function tex.getmathdir() return tex.mathdirection end
|
||||||
function tex.getpardir() return tex.pardirection end
|
function tex.getpardir() return tex.pardirection end
|
||||||
-- local integer_code = value_values.none
|
|
||||||
local integer_code = value_values.integer
|
local integer_code = value_values.integer
|
||||||
local functions = lua.get_functions_table()
|
|
||||||
local lua_call_cmd = token.command_id'lua_call'
|
|
||||||
local function set_xdir(id, scanning)
|
local function set_xdir(id, scanning)
|
||||||
-- local name = names[id]
|
if scanning == 'value' then
|
||||||
if scanning then
|
print(scanning)
|
||||||
return integer_code, getters[id]()
|
return integer_code, getters[id]()
|
||||||
-- return integer_code, tex[name .. 'ection']
|
|
||||||
end
|
end
|
||||||
|
-- local global = scanning == 'global'
|
||||||
local value
|
local value
|
||||||
if token.scan_keyword'tlt' then
|
if token.scan_keyword'tlt' then
|
||||||
value = 0
|
value = 0
|
||||||
|
@ -30,14 +27,12 @@ local function set_xdir(id, scanning)
|
||||||
value = token.scan_int()
|
value = token.scan_int()
|
||||||
end
|
end
|
||||||
setters[id](value)
|
setters[id](value)
|
||||||
-- tex["set" .. name](value)
|
|
||||||
end
|
end
|
||||||
return function(name)
|
return function(name)
|
||||||
local getter = tex["get" .. name]
|
local getter = tex["get" .. name]
|
||||||
local setter = tex["set" .. name]
|
local setter = tex["set" .. name]
|
||||||
assert(getter and setter, "direction parameter undefined")
|
assert(getter and setter, "direction parameter undefined")
|
||||||
local idx = token.luacmd(name, set_xdir, "protected", "global", "value")
|
local idx = token.luacmd(name, set_xdir, "protected", "global", "value")
|
||||||
-- names[idx] = name
|
|
||||||
getters[idx] = getter
|
getters[idx] = getter
|
||||||
setters[idx] = setter
|
setters[idx] = setter
|
||||||
return idx
|
return idx
|
||||||
|
|
|
@ -1,55 +1,4 @@
|
||||||
local functions = lua.getfunctionstable()
|
|
||||||
-- I am not sure why this is necessary, but otherwise LuaMetaTeX resets
|
|
||||||
-- the functions table every time the getter is called
|
|
||||||
function lua.get_functions_table() return functions end
|
|
||||||
local set_lua = token.set_lua
|
|
||||||
-- There are two approaches to manage luafunctions ids without triggering
|
|
||||||
-- issues with ltluatex assigned numbers: Originally we assigned numbers
|
|
||||||
-- starting with 1, then switched to luatexbase ASAP and synchronised both
|
|
||||||
-- numbering schemes. But there is a useful quirk in ltluatex's luafunction
|
|
||||||
-- allocator: It only uses numbers upto 65535, so we can just use bigger
|
|
||||||
-- numbers. (This might have negative repercussins on performance because it
|
|
||||||
-- probably doesn't store the function in the array part of the Lua table.
|
|
||||||
-- Let's reconsider if this ever becomes a problem.
|
|
||||||
-- local new_luafunction = luatexbase.new_luafunction
|
|
||||||
local predefined_luafunctions = status.ini_version and 65535 -- 1<<16 - 1 -- We start with 1<<16
|
|
||||||
local function new_luafunction(name)
|
|
||||||
if predefined_luafunctions then
|
|
||||||
predefined_luafunctions = predefined_luafunctions + 1
|
|
||||||
return predefined_luafunctions
|
|
||||||
else
|
|
||||||
error[[Here only preexisting luafunctions should be set]]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local undefined_cmd = token.command_id'undefined_cs'
|
|
||||||
local lua_call_cmd = token.command_id'lua_call'
|
local lua_call_cmd = token.command_id'lua_call'
|
||||||
local lua_value_cmd = token.command_id'lua_value'
|
|
||||||
local lua_expandable_call_cmd = token.command_id'lua_expandable_call'
|
|
||||||
function token.luacmd(name, func, ...)
|
|
||||||
local idx
|
|
||||||
local tok = token.create(name)
|
|
||||||
local cmd = tok.command
|
|
||||||
if cmd == lua_value_cmd then
|
|
||||||
idx = tok.mode
|
|
||||||
elseif cmd == lua_call_cmd then
|
|
||||||
idx = tok.mode
|
|
||||||
elseif cmd == lua_expandable_call_cmd then
|
|
||||||
idx = tok.mode
|
|
||||||
elseif ... == 'force' then
|
|
||||||
idx = new_luafunction(name)
|
|
||||||
set_lua(name, idx, select(2, ...))
|
|
||||||
elseif cmd == undefined_cmd then
|
|
||||||
idx = new_luafunction(name)
|
|
||||||
set_lua(name, idx, ...)
|
|
||||||
else
|
|
||||||
error(tok.cmdname)
|
|
||||||
end
|
|
||||||
if functions[idx] then
|
|
||||||
error[[Already defined]]
|
|
||||||
end
|
|
||||||
functions[idx] = func
|
|
||||||
return idx
|
|
||||||
end
|
|
||||||
local properties = node.direct.get_properties_table()
|
local properties = node.direct.get_properties_table()
|
||||||
node.direct.properties = properties
|
node.direct.properties = properties
|
||||||
function node.direct.get_properties_table()
|
function node.direct.get_properties_table()
|
||||||
|
@ -63,50 +12,8 @@ end
|
||||||
-- end
|
-- end
|
||||||
-- })
|
-- })
|
||||||
|
|
||||||
|
local new_whatsit = require'luametalatex-whatsits'.new
|
||||||
local whatsit_id = node.id'whatsit'
|
local whatsit_id = node.id'whatsit'
|
||||||
local whatsits = {
|
|
||||||
[0] = "open",
|
|
||||||
"write",
|
|
||||||
"close",
|
|
||||||
"special",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
"save_pos",
|
|
||||||
"late_lua",
|
|
||||||
"user_defined",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
"pdf_literal",
|
|
||||||
"pdf_refobj",
|
|
||||||
"pdf_annot",
|
|
||||||
"pdf_start_link",
|
|
||||||
"pdf_end_link",
|
|
||||||
"pdf_dest",
|
|
||||||
"pdf_action",
|
|
||||||
"pdf_thread",
|
|
||||||
"pdf_start_thread",
|
|
||||||
"pdf_end_thread",
|
|
||||||
"pdf_thread_data",
|
|
||||||
"pdf_link_data",
|
|
||||||
"pdf_colorstack",
|
|
||||||
"pdf_setmatrix",
|
|
||||||
"pdf_save",
|
|
||||||
"pdf_restore",
|
|
||||||
}
|
|
||||||
whatsits[whatsits[0]] = 0
|
|
||||||
for i = 0,#whatsits do
|
|
||||||
local v = whatsits[i]
|
|
||||||
if v then
|
|
||||||
whatsits[v] = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function node.whatsits() return whatsits end
|
|
||||||
function node.subtype(s) return type(s) == "string" and whatsits[s] or nil end
|
|
||||||
local spacer_cmd, relax_cmd = token.command_id'spacer', token.command_id'relax'
|
local spacer_cmd, relax_cmd = token.command_id'spacer', token.command_id'relax'
|
||||||
local function scan_filename()
|
local function scan_filename()
|
||||||
local name = {}
|
local name = {}
|
||||||
|
@ -116,40 +23,43 @@ local function scan_filename()
|
||||||
tok = token.scan_token()
|
tok = token.scan_token()
|
||||||
cmd = tok.command
|
cmd = tok.command
|
||||||
until cmd ~= spacer_cmd and cmd ~= relax_cmd
|
until cmd ~= spacer_cmd and cmd ~= relax_cmd
|
||||||
while (tok.command <= 12 and tok.mode <= token.biggest_char()
|
while (tok.command <= 12 and tok.command > 0 and tok.command ~= 9 and tok.index <= token.biggest_char()
|
||||||
or (token.put_next(tok) and false))
|
or (token.put_next(tok) and false))
|
||||||
and (quoted or tok.mode ~= string.byte' ') do
|
and (quoted or tok.index ~= string.byte' ') do
|
||||||
if tok.mode == string.byte'"' then
|
if tok.index == string.byte'"' then
|
||||||
quoted = not quoted
|
quoted = not quoted
|
||||||
else
|
else
|
||||||
name[#name+1] = tok.mode
|
name[#name+1] = tok.index
|
||||||
end
|
end
|
||||||
tok = token.scan_token()
|
tok = token.scan_token()
|
||||||
end
|
end
|
||||||
return utf8.char(table.unpack(name))
|
return utf8.char(table.unpack(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local l = lpeg or require'lpeg'
|
||||||
|
local add_file_extension = l.Cs((1-('.' * (1-l.S'./\\')^0) * -1)^0 * (l.P(1)^1+l.Cc'.tex'))
|
||||||
local ofiles = {}
|
local ofiles = {}
|
||||||
local function do_openout(p)
|
local function do_openout(p)
|
||||||
if ofiles[p.file] then
|
if ofiles[p.file] then
|
||||||
error[[Existing file]]
|
error[[Existing file]]
|
||||||
else
|
else
|
||||||
local msg
|
local msg
|
||||||
ofiles[p.file], msg = io.open(p.name, 'w')
|
ofiles[p.file], msg = io.open(add_file_extension:match(p.name), 'w')
|
||||||
if not ofiles[p.file] then
|
if not ofiles[p.file] then
|
||||||
error(msg)
|
error(msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local open_whatsit = new_whatsit('open', do_openout)
|
||||||
token.luacmd("openout", function(_, immediate) -- \openout
|
token.luacmd("openout", function(_, immediate) -- \openout
|
||||||
local file = token.scan_int()
|
local file = token.scan_int()
|
||||||
token.scan_keyword'='
|
token.scan_keyword'='
|
||||||
local name = scan_filename()
|
local name = scan_filename()
|
||||||
local props = {file = file, name = name, handle = do_openout}
|
local props = {file = file, name = name}
|
||||||
if immediate == "immediate" then
|
if immediate == "immediate" then
|
||||||
do_openout(props)
|
do_openout(props)
|
||||||
else
|
else
|
||||||
local whatsit = node.direct.new(whatsit_id, whatsits.open)
|
local whatsit = node.direct.new(whatsit_id, open_whatsit)
|
||||||
properties[whatsit] = props
|
properties[whatsit] = props
|
||||||
node.direct.write(whatsit)
|
node.direct.write(whatsit)
|
||||||
end
|
end
|
||||||
|
@ -160,13 +70,14 @@ local function do_closeout(p)
|
||||||
ofiles[p.file] = nil
|
ofiles[p.file] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local close_whatsit = new_whatsit('close', do_closeout)
|
||||||
token.luacmd("closeout", function(_, immediate) -- \closeout
|
token.luacmd("closeout", function(_, immediate) -- \closeout
|
||||||
local file = token.scan_int()
|
local file = token.scan_int()
|
||||||
local props = {file = file, handle = do_closeout}
|
local props = {file = file}
|
||||||
if immediate == "immediate" then
|
if immediate == "immediate" then
|
||||||
do_closeout(props)
|
do_closeout(props)
|
||||||
else
|
else
|
||||||
local whatsit = node.direct.new(whatsit_id, whatsits.close)
|
local whatsit = node.direct.new(whatsit_id, close_whatsit)
|
||||||
properties[whatsit] = props
|
properties[whatsit] = props
|
||||||
node.direct.write(whatsit)
|
node.direct.write(whatsit)
|
||||||
end
|
end
|
||||||
|
@ -180,47 +91,34 @@ local function do_write(p)
|
||||||
texio.write_nl(p.file < 0 and "log" or "term and log", content)
|
texio.write_nl(p.file < 0 and "log" or "term and log", content)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local write_whatsit = new_whatsit('write', do_write)
|
||||||
token.luacmd("write", function(_, immediate) -- \write
|
token.luacmd("write", function(_, immediate) -- \write
|
||||||
local file = token.scan_int()
|
local file = token.scan_int()
|
||||||
local content = token.scan_tokenlist()
|
local content = token.scan_tokenlist()
|
||||||
local props = {file = file, data = content, handle = do_write}
|
local props = {file = file, data = content}
|
||||||
if immediate == "immediate" then
|
if immediate == "immediate" then
|
||||||
do_write(props)
|
do_write(props)
|
||||||
else
|
else
|
||||||
local whatsit = node.direct.new(whatsit_id, whatsits.write)
|
local whatsit = node.direct.new(whatsit_id, write_whatsit)
|
||||||
properties[whatsit] = props
|
properties[whatsit] = props
|
||||||
node.direct.write(whatsit)
|
node.direct.write(whatsit)
|
||||||
end
|
end
|
||||||
end, "protected")
|
end, "protected")
|
||||||
|
|
||||||
|
local functions = lua.get_functions_table()
|
||||||
token.luacmd("immediate", function() -- \immediate
|
token.luacmd("immediate", function() -- \immediate
|
||||||
local next_tok = token.scan_token()
|
local next_tok = token.scan_token()
|
||||||
if next_tok.command ~= lua_call_cmd then
|
if next_tok.command ~= lua_call_cmd then
|
||||||
return token.put_next(next_tok)
|
return token.put_next(next_tok)
|
||||||
end
|
end
|
||||||
local function_id = next_tok.mode
|
local function_id = next_tok.index
|
||||||
functions[function_id](function_id, 'immediate')
|
return functions[function_id](function_id, 'immediate')
|
||||||
end, "protected")
|
end, "protected")
|
||||||
-- functions[43] = function() -- \pdfvariable
|
-- functions[43] = function() -- \pdfvariable
|
||||||
-- local name = token.scan_string()
|
-- local name = token.scan_string()
|
||||||
-- print('Missing \\pdf' .. name)
|
-- print('Missing \\pdf' .. name)
|
||||||
-- end
|
-- end
|
||||||
if status.ini_version then
|
require'luametalatex-baseregisters'
|
||||||
function fixupluafunctions()
|
|
||||||
return predefined_luafunctions
|
|
||||||
end
|
|
||||||
else
|
|
||||||
function fixupluafunctions(i)
|
|
||||||
predefined_luafunctions = i
|
|
||||||
end
|
|
||||||
local prepared_code = lua.bytecode[1]
|
|
||||||
prepared_code()
|
|
||||||
lua.bytecode[1] = nil
|
|
||||||
-- function fixupluafunctions()
|
|
||||||
-- new_luafunction = luatexbase.new_luafunction
|
|
||||||
-- fixupluafunctions = nil
|
|
||||||
-- end
|
|
||||||
end
|
|
||||||
require'luametalatex-back-pdf'
|
require'luametalatex-back-pdf'
|
||||||
require'luametalatex-node-luaotfload'
|
require'luametalatex-node-luaotfload'
|
||||||
|
|
||||||
|
@ -243,4 +141,3 @@ token.luacmd("Umathcodenum", function(_, scanning)
|
||||||
tex.setmathcodes(char, (mathcode >> 21) & 7, mathcode >> 24, mathcode & 0x1FFFFF)
|
tex.setmathcodes(char, (mathcode >> 21) & 7, mathcode >> 24, mathcode & 0x1FFFFF)
|
||||||
end
|
end
|
||||||
end, "force", "global", "value")
|
end, "force", "global", "value")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
local function decode_char(buf)
|
||||||
|
local flag = buf:byte(1)
|
||||||
|
local form = flag & 0x07
|
||||||
|
local tfm, dx, dy, w, h, hoff, voff, off
|
||||||
|
if form < 4 then
|
||||||
|
tfm, dx, w, h, hoff, voff, off = string.unpack(">I3BBBbb", buf, 4)
|
||||||
|
dx, dy = dx * 2^16, 0
|
||||||
|
elseif form < 7 then
|
||||||
|
tfm, dx, w, h, hoff, voff, off = string.unpack(">I3HHHhh", buf, 5)
|
||||||
|
dx, dy = dx * 2^16, 0
|
||||||
|
else
|
||||||
|
tfm, dx, dy, w, h, hoff, voff, off = string.unpack(">I4I4I4I4I4i4i4", buf, 10)
|
||||||
|
end
|
||||||
|
local dyn_f, state = flag >> 4, flag & 8 == 8
|
||||||
|
local data
|
||||||
|
local stride = w+7>>3
|
||||||
|
if dyn_f == 14 then
|
||||||
|
print(state)
|
||||||
|
-- assert(not state)
|
||||||
|
data = lua.newtable(stride*h, 0)
|
||||||
|
local bit_offset, saved = 0
|
||||||
|
local delta_bit_offset = 8 - w%8
|
||||||
|
delta_bit_offset = delta_bit_offset == 8 and 0 or delta_bit_offset
|
||||||
|
for y=0,h-1 do
|
||||||
|
for x=1,stride do
|
||||||
|
if bit_offset == 0 then
|
||||||
|
saved = buf:byte(off)
|
||||||
|
data[y*stride+x] = saved
|
||||||
|
else
|
||||||
|
local saved_mask = (1<<bit_offset)-1
|
||||||
|
local current = (saved&saved_mask) << 8-bit_offset
|
||||||
|
saved = buf:byte(off)
|
||||||
|
data[y*stride+x] = current | (saved & ~saved_mask) >> bit_offset
|
||||||
|
end
|
||||||
|
off = off+1
|
||||||
|
end
|
||||||
|
if delta_bit_offset then
|
||||||
|
data[(y+1)*stride] = data[(y+1)*stride] & (0x100 - (1<<delta_bit_offset))
|
||||||
|
end
|
||||||
|
bit_offset = bit_offset + delta_bit_offset
|
||||||
|
if bit_offset >= 8 then
|
||||||
|
bit_offset = bit_offset-8
|
||||||
|
off = off-1
|
||||||
|
saved = buf:byte(off)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
data = {string.rep('\0', stride*h):byte(1,-1)} -- FIXME: This is probably really slow
|
||||||
|
local nibble, repeat_row = nil, 0
|
||||||
|
local function get_nibble()
|
||||||
|
if nibble then
|
||||||
|
local cur = nibble
|
||||||
|
nibble = nil
|
||||||
|
off = off+1
|
||||||
|
return cur
|
||||||
|
else
|
||||||
|
local cur = buf:byte(off)
|
||||||
|
nibble = cur&0xF
|
||||||
|
return cur >> 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function get_packed()
|
||||||
|
local cur = get_nibble()
|
||||||
|
if cur == 0 then
|
||||||
|
local i = 0
|
||||||
|
repeat
|
||||||
|
cur = get_nibble()
|
||||||
|
i = i+1
|
||||||
|
until cur ~= 0
|
||||||
|
for _=1,i do
|
||||||
|
cur = (cur<<4) + get_nibble()
|
||||||
|
end
|
||||||
|
return cur - 0xF + (13-dyn_f << 4) + dyn_f
|
||||||
|
elseif cur <= dyn_f then
|
||||||
|
return cur
|
||||||
|
elseif cur < 14 then
|
||||||
|
return (cur-dyn_f-1 << 4) + get_nibble() + dyn_f + 1
|
||||||
|
else
|
||||||
|
repeat_row = cur == 14 and get_packed() or 1
|
||||||
|
return get_packed()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local cur_x, cur_y = 0, 0
|
||||||
|
while cur_y < h do
|
||||||
|
local count = get_packed()
|
||||||
|
repeat
|
||||||
|
local this_line = math.min(w - cur_x, count)
|
||||||
|
count = count - this_line
|
||||||
|
if state then
|
||||||
|
local cur_bit_offset = cur_x % 8
|
||||||
|
if cur_bit_offset ~= 0 then -- We are in the middle of a byte
|
||||||
|
cur_bit_offset = 8-cur_bit_offset -- The remaining bits in the byte
|
||||||
|
local off = cur_y*stride+(cur_x>>3)+1
|
||||||
|
if this_line > cur_bit_offset then -- Fill byte with ones
|
||||||
|
data[off] = data[off] + (1<<cur_bit_offset)-1
|
||||||
|
this_line, cur_x = this_line-cur_bit_offset, cur_x+cur_bit_offset
|
||||||
|
else
|
||||||
|
data[off] = data[off] + (1<<cur_bit_offset)-(1<<cur_bit_offset-this_line)
|
||||||
|
this_line, cur_x = 0, cur_x+this_line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
while this_line >= 8 do
|
||||||
|
data[cur_y*stride+(cur_x>>3)+1] = 0xFF
|
||||||
|
this_line, cur_x = this_line-8, cur_x+8
|
||||||
|
end
|
||||||
|
if this_line ~= 0 then
|
||||||
|
data[cur_y*stride+(cur_x>>3)+1] = 0x100-(1<<8-this_line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cur_x = cur_x + this_line
|
||||||
|
if cur_x == w then
|
||||||
|
for i = 1, repeat_row do
|
||||||
|
table.move(data, cur_y*stride+1, (cur_y+1)*stride, (cur_y+i)*stride+1) -- TODO
|
||||||
|
end
|
||||||
|
cur_y, cur_x, repeat_row = cur_y + 1 + repeat_row, 0, 0
|
||||||
|
end
|
||||||
|
until count == 0
|
||||||
|
state = not state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
data = string.char(table.unpack(data))
|
||||||
|
return {
|
||||||
|
data = data,
|
||||||
|
tfm = tfm,
|
||||||
|
dx = dx,
|
||||||
|
dy = dy,
|
||||||
|
hoff = hoff,
|
||||||
|
voff = voff,
|
||||||
|
w = w,
|
||||||
|
h = h,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local commands = {
|
||||||
|
[240] = function(buf, off, t)
|
||||||
|
local xxx xxx, off = string.unpack(">xs1", buf, off)
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
[241] = function(buf, off, t)
|
||||||
|
local xxx xxx, off = string.unpack(">xs2", buf, off)
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
[242] = function(buf, off, t)
|
||||||
|
local xxx xxx, off = string.unpack(">xs3", buf, off)
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
[243] = function(buf, off, t)
|
||||||
|
local xxx xxx, off = string.unpack(">xs4", buf, off)
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
[244] = function(buf, off, t)
|
||||||
|
local yyy yyy, off = string.unpack(">xI4", buf, off)
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
[247] = function(buf, off, t)
|
||||||
|
local ident
|
||||||
|
ident, t.comment, t.designsize, t.checksum, t.hppp, t.vppp, off = string.unpack(">xBs1I4I4I4I4", buf, off)
|
||||||
|
if ident ~= 89 then
|
||||||
|
error[[Not a PK file]]
|
||||||
|
end
|
||||||
|
return off
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
local function parse_commands(buf, off, t)
|
||||||
|
local cmd = buf:byte(off)
|
||||||
|
assert(cmd == 247)
|
||||||
|
repeat
|
||||||
|
if cmd < 240 then
|
||||||
|
local form = cmd & 0x07
|
||||||
|
local chr, newoff, length
|
||||||
|
if form < 4 then
|
||||||
|
length, chr, newoff = string.unpack(">xBB", buf, off)
|
||||||
|
length = length + ((form & 3)<<8)
|
||||||
|
elseif form < 7 then
|
||||||
|
length, chr, newoff = string.unpack(">xHB", buf, off)
|
||||||
|
length = length + ((form & 3)<<16)
|
||||||
|
else
|
||||||
|
length, chr, newoff = string.unpack(">xI4I4", buf, off)
|
||||||
|
end
|
||||||
|
newoff = newoff + length
|
||||||
|
t[chr] = decode_char(buf:sub(off, newoff))
|
||||||
|
off = newoff
|
||||||
|
else
|
||||||
|
local handler = commands[cmd]
|
||||||
|
if not handler then
|
||||||
|
print([[Unknown command ]] .. cmd)
|
||||||
|
return off-1
|
||||||
|
end
|
||||||
|
off = handler(buf, off, t)
|
||||||
|
end
|
||||||
|
cmd = buf:byte(off)
|
||||||
|
until cmd == 245
|
||||||
|
return off
|
||||||
|
end
|
||||||
|
return function(filename)
|
||||||
|
local f = assert(io.open(filename, 'rb'))
|
||||||
|
local pk = f:read'a'
|
||||||
|
f:close()
|
||||||
|
local res = {}
|
||||||
|
local off = parse_commands(pk, 1, res)
|
||||||
|
-- assert(off == #pk+1) -- TODO: Check that only fillup bytes follow
|
||||||
|
return res
|
||||||
|
end
|
|
@ -0,0 +1,167 @@
|
||||||
|
font.read_tfm = require'luametalatex-font-tfm'
|
||||||
|
local read_vf = require'luametalatex-font-vf'
|
||||||
|
font.read_vf = read_vf
|
||||||
|
local fontmap = require'luametalatex-pdf-font-map'.fontmap
|
||||||
|
local callback_find = callback.find
|
||||||
|
|
||||||
|
local old_font_define = font.define
|
||||||
|
local old_addcharacters = font.addcharacters
|
||||||
|
|
||||||
|
require'luametalatex-pdf-font-map'.mapfile(kpse.find_file('pdftex.map', 'map', true))
|
||||||
|
|
||||||
|
local all_fonts = {}
|
||||||
|
font.fonts = all_fonts
|
||||||
|
function font.getfont(id)
|
||||||
|
return all_fonts[id]
|
||||||
|
end
|
||||||
|
|
||||||
|
local fontextensions = {
|
||||||
|
ttf = {"truetype", "truetype fonts",},
|
||||||
|
otf = {"opentype", "opentype fonts",},
|
||||||
|
pfb = {"type1", "type1 fonts",},
|
||||||
|
}
|
||||||
|
fontextensions.cff = fontextensions.otf
|
||||||
|
local fontformats = {
|
||||||
|
fontextensions.pfb, fontextensions.otf, fontextensions.ttf,
|
||||||
|
}
|
||||||
|
|
||||||
|
local special_parser do
|
||||||
|
local l = lpeg or require'lpeg'
|
||||||
|
local space = l.S' '^0
|
||||||
|
local name = (1-l.P' ')^1
|
||||||
|
local reencode = name * space * 'ReEncodeFont'
|
||||||
|
local digit = l.R'09'
|
||||||
|
local number = digit^1 * ('.' * digit^0) + '.' * digit^1/tonumber
|
||||||
|
local milli_stmt = number * space * ('SlantFont' * l.Cc'slant' + 'ExtendFont' * l.Cc'extend') / function(n, k)
|
||||||
|
return k, (n*1000 + .5)//1
|
||||||
|
end
|
||||||
|
special_parser = l.Cf(l.Carg(1) * (space * (reencode + milli_stmt))^0 * space * -1, rawset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function font.define(f)
|
||||||
|
if (f.type or "unknown") == "unknown" then
|
||||||
|
local vf = read_vf(f.name, f.size)
|
||||||
|
if vf then
|
||||||
|
f.type = 'virtual'
|
||||||
|
f.fonts = vf.fonts
|
||||||
|
local realchars = f.characters
|
||||||
|
for cp, char in next, vf.characters do
|
||||||
|
assert(realchars[cp]).commands = char.commands
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f.type = 'real'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local format = f.format or "unknown"
|
||||||
|
local encodingbytes = f.encodingbytes or (f.format:sub(5) == "type" and 2 or 1)
|
||||||
|
f.encodingbytes = encodingbytes
|
||||||
|
if encodingbytes == 1 and f.type ~= 'virtual' and f.format ~= 'type3node' then
|
||||||
|
-- map file lookup
|
||||||
|
local entry = fontmap[f.name]
|
||||||
|
if entry then
|
||||||
|
local filename = entry[3]
|
||||||
|
local format = filename and filename:sub(-4, -4) == '.' and fontextensions[filename:sub(-3, -1)]
|
||||||
|
if format then
|
||||||
|
f.format = format[1]
|
||||||
|
f.filename = kpse.find_file(filename, format[2])
|
||||||
|
local encoding = entry[4]
|
||||||
|
if encoding then
|
||||||
|
f.encoding = kpse.find_file(encoding, 'enc files')
|
||||||
|
end
|
||||||
|
if entry[5] then
|
||||||
|
assert(special_parser:match(entry[5], 1, f))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local done = false
|
||||||
|
for _, format in ipairs(fontformats) do
|
||||||
|
local filename = kpse.find_file(filename, format[2])
|
||||||
|
if filename then
|
||||||
|
f.filename = filename
|
||||||
|
f.format = format[1]
|
||||||
|
local encoding = entry[4]
|
||||||
|
if encoding then
|
||||||
|
f.encoding = kpse.find_file(encoding, 'enc files')
|
||||||
|
end
|
||||||
|
if entry[5] then
|
||||||
|
assert(special_parser:match(entry[5], 1, f))
|
||||||
|
end
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not done then
|
||||||
|
print('!', 'type3', require'inspect'(entry))
|
||||||
|
f.format = "type3"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f.format = "type3"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local id = old_font_define(f)
|
||||||
|
all_fonts[id] = f
|
||||||
|
if f.fonts then
|
||||||
|
for i, f in next, f.fonts do
|
||||||
|
if not f.id then
|
||||||
|
f.id = assert(callback_find'define_font'(f.name, f.size or -1000))
|
||||||
|
elseif f.id == 0 then
|
||||||
|
f.id = id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
function font.addcharacters(fid, newdir)
|
||||||
|
old_addcharacters(fid, newdir) -- The easy part, the remaining stuff gets crazy
|
||||||
|
local fontdir = assert(all_fonts[fid], 'addcharacters expects an existing font')
|
||||||
|
local fonts_map
|
||||||
|
if newdir.fonts then -- FIXME: Handle default fonts table
|
||||||
|
if fontdir.fonts then
|
||||||
|
fonts_map = {}
|
||||||
|
for i,f in next, newdir.fonts do
|
||||||
|
if not f.id then
|
||||||
|
f.id = assert(callback_find'define_font'(f.name, f.size or -1000))
|
||||||
|
elseif f.id == 0 then
|
||||||
|
f.id = fid
|
||||||
|
end
|
||||||
|
for j,ff in next, fontdir.fonts do
|
||||||
|
if ff.id == f.id then
|
||||||
|
fonts_map[i] = j
|
||||||
|
goto FONT_MAPPING_SET -- A typical for ... do ... else ... end implemented using goto.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- NOT FOUND, so add it
|
||||||
|
local new_f = #fontdir.fonts + 1
|
||||||
|
fontdir.fonts[new_f] = f
|
||||||
|
fonts_map[i] = f
|
||||||
|
::FONT_MAPPING_SET::
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fontdir.fonts = newdir.fonts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for cp, glyph in next, newdir do
|
||||||
|
local existing = fontdir[cp]
|
||||||
|
if existing ~= glyph then
|
||||||
|
if existing then
|
||||||
|
-- texio.write_nl'Overwriting existing character. Here be dragons'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if glyph.commands then
|
||||||
|
local font_seen
|
||||||
|
for _, cmd in ipairs(glyph.commands) do
|
||||||
|
if cmd[1] == 'font' then
|
||||||
|
font_seen = true
|
||||||
|
cmd[2] = fonts_map[cmd[2]]
|
||||||
|
elseif cmd[1] == 'slot' then
|
||||||
|
font_seen = true
|
||||||
|
cmd[2] = fonts_map[cmd[2]]
|
||||||
|
elseif not font_seen and cmd[1] == 'char' then
|
||||||
|
font_seen = true
|
||||||
|
cmd[1], cmd[2], cmd[3] = 'slot', fonts_map[1], cmd[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -44,7 +44,7 @@ end
|
||||||
local boolean = (lpeg.P'true' + 'false')/{["true"] = true, ["false"] = false}
|
local boolean = (lpeg.P'true' + 'false')/{["true"] = true, ["false"] = false}
|
||||||
local anytype = {hexstring + literalstring + number + lname + boolean + lpeg.V(2) + name, lpeg.Ct('[' * (white^-1 * lpeg.V(1))^0 * white^-1 * ']' + '{' * (white^-1 * lpeg.V(1))^0 * white^-1 * '}' * white^-1 * lpeg.P"executeonly"^-1)}
|
local anytype = {hexstring + literalstring + number + lname + boolean + lpeg.V(2) + name, lpeg.Ct('[' * (white^-1 * lpeg.V(1))^0 * white^-1 * ']' + '{' * (white^-1 * lpeg.V(1))^0 * white^-1 * '}' * white^-1 * lpeg.P"executeonly"^-1)}
|
||||||
local dict = lpeg.Cf(lpeg.Carg(1) * lpeg.Cg(white^-1*lname*white^-1*(anytype)*white^-1*lpeg.P"readonly"^-1*white^-1*lpeg.P"noaccess"^-1*white^-1*(lpeg.P"def"+"ND"+"|-"))^0, rawset)
|
local dict = lpeg.Cf(lpeg.Carg(1) * lpeg.Cg(white^-1*lname*white^-1*(anytype)*white^-1*lpeg.P"readonly"^-1*white^-1*lpeg.P"noaccess"^-1*white^-1*(lpeg.P"def"+"ND"+"|-"))^0, rawset)
|
||||||
local encoding = (white+anytype-("for"*white))^0*"for"*white/0
|
local encoding = (white+anytype-("dup"*white))^0/0
|
||||||
* lpeg.Cf(lpeg.Ct''
|
* lpeg.Cf(lpeg.Ct''
|
||||||
* lpeg.Cg("dup"*white*number*white^-1*lname*white^-1*"put"*white)^0
|
* lpeg.Cg("dup"*white*number*white^-1*lname*white^-1*"put"*white)^0
|
||||||
, rawset)
|
, rawset)
|
||||||
|
@ -63,7 +63,7 @@ local function parse_fontinfo(offset, str)
|
||||||
offset = (white^-1*"end"*white^-1*lpeg.P"readonly"^-1*white^-1*"def"):match(str, offset)
|
offset = (white^-1*"end"*white^-1*lpeg.P"readonly"^-1*white^-1*"def"):match(str, offset)
|
||||||
return found, offset
|
return found, offset
|
||||||
end
|
end
|
||||||
local binary_bytes = lpeg.Cmt(number*white^-1*(lpeg.P'-| ' + 'RD '), function(s, p, l)return p+l, s:sub(p, p+l-1) end)*white*(lpeg.P"|-"+"|"+"ND"+"NP")
|
local binary_bytes = lpeg.Cmt(number*white^-1*(lpeg.P'-| ' + 'RD '), function(s, p, l)return p+l, s:sub(p, p+l-1) end)*white^-1*(lpeg.P"|-"+"|"+"ND"+"NP")
|
||||||
local charstr = white^-1*lname*(white^-1*(anytype-lname))^0/0*white^-1
|
local charstr = white^-1*lname*(white^-1*(anytype-lname))^0/0*white^-1
|
||||||
* lpeg.Cf(lpeg.Ct''
|
* lpeg.Cf(lpeg.Ct''
|
||||||
* lpeg.Cg(lname*white^-1*binary_bytes*white)^0
|
* lpeg.Cg(lname*white^-1*binary_bytes*white)^0
|
||||||
|
|
|
@ -15,6 +15,27 @@ local function read_scaled(buf, i, count, factor)
|
||||||
end
|
end
|
||||||
return result, i + count * 4
|
return result, i + count * 4
|
||||||
end
|
end
|
||||||
|
local function parse_ligkern(buf, offset, r_boundary, kerns)
|
||||||
|
local kerning, ligatures, done = {}, {}, {}
|
||||||
|
repeat
|
||||||
|
local skip, next, op, rem
|
||||||
|
skip, next, op, rem, offset = string.unpack("BBBB", buf, offset)
|
||||||
|
if skip > 128 then break end
|
||||||
|
if next == r_boundary then next = "right_boundary" end
|
||||||
|
if not done[next] then
|
||||||
|
done[next] = true
|
||||||
|
if op >= 128 then
|
||||||
|
kerning[next] = kerns[(op - 128 << 8) + rem + 1]
|
||||||
|
else
|
||||||
|
ligatures[next] = {
|
||||||
|
type = op,
|
||||||
|
char = rem,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until skip == 128
|
||||||
|
return next(kerning) and kerning or nil, next(ligatures) and ligatures or nil
|
||||||
|
end
|
||||||
local function parse_tfm(buf, i, size)
|
local function parse_tfm(buf, i, size)
|
||||||
local lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np
|
local lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np
|
||||||
lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np, i =
|
lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np, i =
|
||||||
|
@ -43,7 +64,7 @@ local function parse_tfm(buf, i, size)
|
||||||
italics, i = read_scaled(buf, i, ni, size)
|
italics, i = read_scaled(buf, i, ni, size)
|
||||||
for k,v in ipairs(italics) do if v == 0 then italics[k] = nil end end
|
for k,v in ipairs(italics) do if v == 0 then italics[k] = nil end end
|
||||||
ligatureoffset = i
|
ligatureoffset = i
|
||||||
if string.byte(buf, i, i) > 128 then
|
if nl ~= 0 and string.byte(buf, i, i) == 255 then
|
||||||
r_boundary = string.byte(buf, i+1, i+1)
|
r_boundary = string.byte(buf, i+1, i+1)
|
||||||
end
|
end
|
||||||
i = i + nl * 4
|
i = i + nl * 4
|
||||||
|
@ -56,7 +77,7 @@ local function parse_tfm(buf, i, size)
|
||||||
end
|
end
|
||||||
extensibles[j] = ext
|
extensibles[j] = ext
|
||||||
end
|
end
|
||||||
local slant = string.unpack(">i4", buf, i) >> 4
|
local slant = np ~= 0 and string.unpack(">i4", buf, i) >> 4 or nil
|
||||||
parameters = read_scaled(buf, i, np, size)
|
parameters = read_scaled(buf, i, np, size)
|
||||||
parameters[1] = slant
|
parameters[1] = slant
|
||||||
end
|
end
|
||||||
|
@ -76,30 +97,9 @@ local function parse_tfm(buf, i, size)
|
||||||
elseif tag == 1 then
|
elseif tag == 1 then
|
||||||
local offset = (charinfo & 0xFF) * 4 + ligatureoffset
|
local offset = (charinfo & 0xFF) * 4 + ligatureoffset
|
||||||
if string.byte(buf, offset, offset) > 128 then
|
if string.byte(buf, offset, offset) > 128 then
|
||||||
offset = string.unpack(">H", buf, offset + 2)
|
offset = string.unpack(">H", buf, offset + 2) * 4 + ligatureoffset
|
||||||
end
|
end
|
||||||
char.kerns, char.ligatures = {}, {}
|
char.kerns, char.ligatures = parse_ligkern(buf, offset, r_boundary, kerns)
|
||||||
local done = {}
|
|
||||||
repeat
|
|
||||||
local skip, next, op, rem
|
|
||||||
skip, next, op, rem, offset = string.unpack("BBBB", buf, offset)
|
|
||||||
if skip > 128 then break end
|
|
||||||
if next == r_boundary then next = "right_boundary" end
|
|
||||||
if not done[next] then
|
|
||||||
done[next] = true
|
|
||||||
if op >= 128 then
|
|
||||||
char.kerns[next] = kerns[(op - 128 << 8) + rem + 1]
|
|
||||||
else
|
|
||||||
char.ligatures[next] = {
|
|
||||||
type = op,
|
|
||||||
char = rem,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
offset = offset + 4*skip
|
|
||||||
until skip == 128
|
|
||||||
if not next(char.kerns) then char.kerns = nil end
|
|
||||||
if not next(char.ligatures) then char.ligatures = nil end
|
|
||||||
elseif tag == 2 then
|
elseif tag == 2 then
|
||||||
char.next = charinfo & 0xFF
|
char.next = charinfo & 0xFF
|
||||||
elseif tag == 3 then
|
elseif tag == 3 then
|
||||||
|
@ -108,6 +108,12 @@ local function parse_tfm(buf, i, size)
|
||||||
characters[cc] = char
|
characters[cc] = char
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if nl ~= 0 and string.byte(buf, ligatureoffset + (nl-1) * 4) == 255 then
|
||||||
|
local char = {}
|
||||||
|
characters.left_boundary = char
|
||||||
|
local offset = string.unpack(">H", buf, ligatureoffset + nl * 4 - 2) * 4 + ligatureoffset
|
||||||
|
char.kerns, char.ligatures = parse_ligkern(buf, offset, r_boundary, kerns)
|
||||||
|
end
|
||||||
return {
|
return {
|
||||||
checksum = checksum,
|
checksum = checksum,
|
||||||
direction = 0,
|
direction = 0,
|
||||||
|
@ -136,6 +142,7 @@ end
|
||||||
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
||||||
return function(name, size)
|
return function(name, size)
|
||||||
local filename = kpse.find_file(name, 'tfm', true)
|
local filename = kpse.find_file(name, 'tfm', true)
|
||||||
|
if not filename then return end
|
||||||
local f = io.open(filename)
|
local f = io.open(filename)
|
||||||
if not f then return end
|
if not f then return end
|
||||||
local buf = f:read'*a'
|
local buf = f:read'*a'
|
||||||
|
|
|
@ -9,10 +9,11 @@ local function read_fonts(buf, i, fonts, size)
|
||||||
if not cmd then return i end
|
if not cmd then return i end
|
||||||
local fid, check, scale, designsize, arealen, namelen, i =
|
local fid, check, scale, designsize, arealen, namelen, i =
|
||||||
string.unpack(cmd, buf, i + 1)
|
string.unpack(cmd, buf, i + 1)
|
||||||
|
fid = fid + 1 -- We prefer 1-based arrays
|
||||||
local fsize = size * scale >> 20
|
local fsize = size * scale >> 20
|
||||||
if fonts[fid] then error[[font number reused in VF file]] end
|
if fonts[fid] then error[[font number reused in VF file]] end
|
||||||
fonts[fid] = {
|
fonts[fid] = {
|
||||||
area = string.sub(buf, i, i+arealen-1),
|
area = arealen > 0 and string.sub(buf, i, i+arealen-1) or nil,
|
||||||
name = string.sub(buf, i+arealen, i+arealen+namelen-1),
|
name = string.sub(buf, i+arealen, i+arealen+namelen-1),
|
||||||
size = fsize,
|
size = fsize,
|
||||||
designsize = designsize >> 4,
|
designsize = designsize >> 4,
|
||||||
|
@ -134,9 +135,9 @@ local function read_chars(buf, i, characters, size)
|
||||||
if cmd >= 235 then
|
if cmd >= 235 then
|
||||||
cmd, i = string.unpack(Cmds[cmd-234], buf, i + 1)
|
cmd, i = string.unpack(Cmds[cmd-234], buf, i + 1)
|
||||||
else
|
else
|
||||||
i = i + 1
|
cmd, i = cmd - 171, i + 1
|
||||||
end
|
end
|
||||||
commands[#commands + 1] = { "font", cmd }
|
commands[#commands + 1] = { "font", cmd + 1 } -- 1-based fonts
|
||||||
elseif xxx[cmd] then
|
elseif xxx[cmd] then
|
||||||
cmd, i = string.unpack(xxx[cmd], buf, i + 1)
|
cmd, i = string.unpack(xxx[cmd], buf, i + 1)
|
||||||
commands[#commands + 1] = { "special", cmd }
|
commands[#commands + 1] = { "special", cmd }
|
||||||
|
@ -161,16 +162,17 @@ local function parse_vf(buf, i, size)
|
||||||
i = read_fonts(buf, i, fonts, size)
|
i = read_fonts(buf, i, fonts, size)
|
||||||
i = read_chars(buf, i, characters, size)
|
i = read_chars(buf, i, characters, size)
|
||||||
|
|
||||||
print(require'inspect'(font))
|
return font
|
||||||
end
|
end
|
||||||
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
||||||
return function(name, size, must_exist)
|
return function(name, size, must_exist)
|
||||||
local filename = kpse.find_file(name, 'vf', must_exist)
|
local filename = kpse.find_file(name, 'vf', must_exist)
|
||||||
|
if not filename then return end
|
||||||
local f = io.open(filename)
|
local f = io.open(filename)
|
||||||
if not f then return end
|
if not f then return end
|
||||||
local buf = f:read'*a'
|
local buf = f:read'*a'
|
||||||
f:close()
|
f:close()
|
||||||
local result = parse_tfm(buf, 1, size)
|
local result = parse_vf(buf, 1, size)
|
||||||
result.name = basename:match(name)
|
result.name = basename:match(name)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
do
|
do
|
||||||
local ourpath = arg[0]:match('^%-%-lua=(.*)luametalatex%-init%.lua$')
|
local ourpath = lua.startupfile:match('(.*[/\\])[^/\\]*%.lua$')
|
||||||
kpse = assert(package.loadlib(ourpath .. 'kpse.so', 'luaopen_kpse'))()
|
kpse = assert(package.loadlib(ourpath .. 'kpse.so', 'luaopen_kpse'))()
|
||||||
end
|
end
|
||||||
|
local interaction
|
||||||
do
|
do
|
||||||
local arg0, progname
|
local arg0, progname
|
||||||
for _, a in ipairs(arg) do
|
for _, a in ipairs(arg) do
|
||||||
|
@ -9,8 +10,20 @@ do
|
||||||
progname = a:sub(12)
|
progname = a:sub(12)
|
||||||
elseif a:sub(1,7) == "--arg0=" then
|
elseif a:sub(1,7) == "--arg0=" then
|
||||||
arg0 = a:sub(8)
|
arg0 = a:sub(8)
|
||||||
|
elseif a:match'^%-%-?interaction=' then
|
||||||
|
local interaction_name = a:sub(a:find'='+1)
|
||||||
|
interaction = ({
|
||||||
|
batchmode=0,
|
||||||
|
nonstopmode=1,
|
||||||
|
scrollmode=2,
|
||||||
|
errorstopmode=3,
|
||||||
|
})[interaction_name]
|
||||||
|
if not interaction then
|
||||||
|
texio.write('term', string.format('Unknown interaction mode %q ignored.\n', interaction_name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
os.arg0 = arg0
|
||||||
kpse.set_program_name(arg0, progname)
|
kpse.set_program_name(arg0, progname)
|
||||||
end
|
end
|
||||||
package.searchers[2] = function(modname)
|
package.searchers[2] = function(modname)
|
||||||
|
@ -24,100 +37,36 @@ package.searchers[2] = function(modname)
|
||||||
end
|
end
|
||||||
return mod, filename
|
return mod, filename
|
||||||
end
|
end
|
||||||
-- kpse.set_maketex("kpse_fmt_format", true)
|
kpse.set_maketex("fmt", true, "compile")
|
||||||
bit32 = require'luametalatex-bit32'
|
kpse.set_maketex("pk", true, "compile")
|
||||||
kpse.init_prog("LUATEX", 400, "nexthi", nil)
|
|
||||||
status.init_kpse = 1
|
|
||||||
require'luametalatex-init-config'
|
require'luametalatex-init-config'
|
||||||
status.safer_option = 0
|
local callback_register = callback.register
|
||||||
status.shell_escape = 0
|
local build_bytecode
|
||||||
local read_tfm = require'luametalatex-font-tfm'
|
|
||||||
local read_vf = require'luametalatex-font-vf'
|
|
||||||
font.read_tfm = read_tfm
|
|
||||||
font.read_vf = read_vf
|
|
||||||
require'module'
|
|
||||||
font.fonts = {}
|
|
||||||
function font.getfont(id)
|
|
||||||
return font.fonts[id]
|
|
||||||
end
|
|
||||||
pdf = {
|
|
||||||
getfontname = function(id) -- No font sharing
|
|
||||||
return id
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
local olddefinefont = font.define
|
|
||||||
function font.define(f)
|
|
||||||
local i = olddefinefont(f)
|
|
||||||
font.fonts[i] = f
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
callback.register('define_font', function(name, size)
|
|
||||||
local f = read_tfm(name, size)
|
|
||||||
local id = font.define(f)
|
|
||||||
if status.ini_version then
|
|
||||||
lua.prepared_code[#lua.prepared_code+1] = string.format("assert(%i == font.define(font.read_tfm(%q, %i)))", id, name, size)
|
|
||||||
end
|
|
||||||
return id
|
|
||||||
end)
|
|
||||||
callback.register('find_log_file', function(name) return name end)
|
|
||||||
-- callback.register('find_read_file', function(i, name) return kpse.find_file(name, 'tex', true) end)
|
|
||||||
callback.register('find_data_file', function(name, ...) return kpse.find_file(name, 'tex', true) end)
|
|
||||||
callback.register('read_data_file', function(name) error[[TODO]]return kpse.find_file(name, 'tex', true) end)
|
|
||||||
-- local file_meta = {\
|
|
||||||
callback.register('open_data_file', function(name)
|
|
||||||
local f = io.open(name)
|
|
||||||
return setmetatable({
|
|
||||||
reader = function() return f:read() end,
|
|
||||||
close = function()error[[1]] return f:close() end,
|
|
||||||
}, {
|
|
||||||
__gc = function()f:close()end,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
callback.register('find_format_file', function(name) return kpse.find_file(name, 'fmt', true) end)
|
|
||||||
callback.register('handle_error_hook', function()
|
|
||||||
repeat
|
|
||||||
texio.write_nl'? '
|
|
||||||
local line = io.read()
|
|
||||||
if not line then
|
|
||||||
error[[TODO: Handle EOL]]
|
|
||||||
end
|
|
||||||
if line == "" then return 3 end
|
|
||||||
local first = line:sub(1,1):upper()
|
|
||||||
if first == 'H' then
|
|
||||||
texio.write(tex.gethelptext())
|
|
||||||
elseif first == 'I' then
|
|
||||||
line = line:sub(2)
|
|
||||||
tex.runtoks(function()
|
|
||||||
tex.sprint(token.scan_token(), line)
|
|
||||||
end)
|
|
||||||
return 3
|
|
||||||
elseif first == 'Q' then texio.write'OK, entering \\batchmode...\n' return 0
|
|
||||||
elseif first == 'R' then texio.write'OK, entering \\nonstopmode...\n' return 1
|
|
||||||
elseif first == 'S' then texio.write'OK, entering \\scrollmode...\n' return 2
|
|
||||||
elseif first == 'X' then return -1
|
|
||||||
else
|
|
||||||
texio.write'Type <return> to proceed, S to scroll future error messages,\
|
|
||||||
\z R to run without stopping, Q to run quietly,\
|
|
||||||
\z I to insert something,\
|
|
||||||
\z H for help, X to quit.'
|
|
||||||
end
|
|
||||||
until false
|
|
||||||
-- print('handle')
|
|
||||||
return 3
|
|
||||||
end)
|
|
||||||
callback.register('pre_dump', function()
|
|
||||||
lua.prepared_code[1] = string.format("fixupluafunctions(%i)", fixupluafunctions())
|
|
||||||
lua.bytecode[1] = assert(load(table.concat(lua.prepared_code, ' ')))
|
|
||||||
end)
|
|
||||||
function texconfig.init()
|
|
||||||
lua.bytecode[2]()
|
|
||||||
if not status.ini_version then
|
|
||||||
lua.bytecode[2] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if status.ini_version then
|
if status.ini_version then
|
||||||
lua.prepared_code = {false}
|
local build_bytecode_mod = require'luametalatex-build-bytecode'
|
||||||
local code = package.searchers[2]('luametalatex-firstcode')
|
local preloaded_modules = {}
|
||||||
if type(code) == "string" then error(string.format("Initialization code not found %s", code)) end
|
local old_searcher = package.searchers[2]
|
||||||
lua.bytecode[2] = code
|
package.searchers[2] = function(name)
|
||||||
|
local mod, file = old_searcher(name)
|
||||||
|
if not file then return mod end -- Only works because we always return file when successful
|
||||||
|
preloaded_modules[#preloaded_modules+1] = {name, file}
|
||||||
|
return mod, file
|
||||||
|
end
|
||||||
|
function build_bytecode(str)
|
||||||
|
return load(build_bytecode_mod(preloaded_modules) .. "\nrequire'luametalatex-lateinit'(function()" .. str .. '\nend)', 'preloaded', 't')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
callback_register('find_format_file', function(name) return kpse.find_file(name, 'fmt', true) end)
|
||||||
|
function texconfig.init()
|
||||||
|
if interaction then
|
||||||
|
tex.setinteraction(interaction)
|
||||||
|
end
|
||||||
|
if build_bytecode then -- Effectivly if status.ini_version
|
||||||
|
require'luametalatex-lateinit'(build_bytecode)
|
||||||
|
else
|
||||||
|
local register = tex.count[262]+1
|
||||||
|
lua.bytecode[register]()
|
||||||
|
lua.bytecode[register] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
local initex = status.ini_version
|
||||||
|
|
||||||
|
if initex then
|
||||||
|
lua.prepared_code = {false}
|
||||||
|
end
|
||||||
|
|
||||||
|
bit32 = require'luametalatex-bit32' -- Why? And why so early?
|
||||||
|
status.init_kpse = 1 -- Why?
|
||||||
|
status.safer_option = 0 -- compat
|
||||||
|
status.shell_escape = 0 -- compat -- This is currently a lie.
|
||||||
|
-- require'module' -- ???
|
||||||
|
pdf = {
|
||||||
|
variable = {},
|
||||||
|
}
|
||||||
|
require'luametalatex-font-resolve' -- Replace font.define. Must be loaded before callbacks
|
||||||
|
require'luametalatex-callbacks'
|
||||||
|
|
||||||
|
local functions = lua.getfunctionstable()
|
||||||
|
-- I am not sure why this is necessary, but otherwise LuaMetaTeX resets
|
||||||
|
-- the functions table every time the getter is called
|
||||||
|
function lua.get_functions_table() return functions end
|
||||||
|
local set_lua = token.set_lua
|
||||||
|
-- There are two approaches to manage luafunctions ids without triggering
|
||||||
|
-- issues with ltluatex assigned numbers: Originally we assigned numbers
|
||||||
|
-- starting with 1, then switched to luatexbase ASAP and synchronised both
|
||||||
|
-- numbering schemes. But there is a useful quirk in ltluatex's luafunction
|
||||||
|
-- allocator: It only uses numbers upto 65535, so we can just use bigger
|
||||||
|
-- numbers. (This might have negative repercussins on performance because it
|
||||||
|
-- probably doesn't store the function in the array part of the Lua table.
|
||||||
|
-- Let's reconsider if this ever becomes a problem.
|
||||||
|
-- local new_luafunction = luatexbase.new_luafunction
|
||||||
|
local predefined_luafunctions = initex and 65536 -- 1<<16 -- We start with 1<<16 + 1 (1<<16=65536 is reserved for luametalatex-local)
|
||||||
|
local new_luafunction
|
||||||
|
function new_luafunction(name)
|
||||||
|
if predefined_luafunctions then
|
||||||
|
predefined_luafunctions = predefined_luafunctions + 1
|
||||||
|
return predefined_luafunctions
|
||||||
|
else
|
||||||
|
error[[Here only preexisting luafunctions should be set]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local undefined_cmd = token.command_id'undefined_cs'
|
||||||
|
local lua_call_cmd = token.command_id'lua_call'
|
||||||
|
local lua_value_cmd = token.command_id'lua_value'
|
||||||
|
local lua_expandable_call_cmd = token.command_id'lua_expandable_call'
|
||||||
|
function token.luacmd(name, func, ...)
|
||||||
|
local idx
|
||||||
|
local tok = token.create(name)
|
||||||
|
local cmd = tok.command
|
||||||
|
if cmd == lua_value_cmd then
|
||||||
|
idx = tok.index
|
||||||
|
elseif cmd == lua_call_cmd then
|
||||||
|
idx = tok.index
|
||||||
|
elseif cmd == lua_expandable_call_cmd then
|
||||||
|
idx = tok.index
|
||||||
|
elseif ... == 'force' then
|
||||||
|
idx = new_luafunction(name)
|
||||||
|
set_lua(name, idx, select(2, ...))
|
||||||
|
elseif cmd == undefined_cmd then
|
||||||
|
idx = new_luafunction(name)
|
||||||
|
set_lua(name, idx, ...)
|
||||||
|
else
|
||||||
|
error(tok.cmdname)
|
||||||
|
end
|
||||||
|
if functions[idx] then
|
||||||
|
error[[Already defined]]
|
||||||
|
end
|
||||||
|
functions[idx] = func
|
||||||
|
return idx
|
||||||
|
end
|
||||||
|
|
||||||
|
if initex then
|
||||||
|
local build_bytecode = nil -- To be filled
|
||||||
|
callback.register('pre_dump', function()
|
||||||
|
local prepared = lua.prepared_code
|
||||||
|
prepared[1] = string.format("fixupluafunctions(%i)", predefined_luafunctions)
|
||||||
|
for i=0,0 do -- maybeFIXME: In practise only one language is preloaded in LuaTeX anyway
|
||||||
|
-- for i=0,tex.count[19] do -- Sometimes catches reserved language ids which are not used yet
|
||||||
|
-- for i=0,lang.new():id()-1 do -- lang.new():id() is always 0 in luametatex?!?
|
||||||
|
local l = lang.new(i)
|
||||||
|
local str = string.format("do \z
|
||||||
|
local l = lang.new(%i)\z
|
||||||
|
l:hyphenationmin(%i)\z
|
||||||
|
l:prehyphenchar(%i)\z
|
||||||
|
l:posthyphenchar(%i)\z
|
||||||
|
l:preexhyphenchar(%i)\z
|
||||||
|
l:postexhyphenchar(%i)",
|
||||||
|
i,
|
||||||
|
l:hyphenationmin(),
|
||||||
|
l:prehyphenchar(),
|
||||||
|
l:posthyphenchar(),
|
||||||
|
l:preexhyphenchar(),
|
||||||
|
l:postexhyphenchar())
|
||||||
|
local patterns = l:patterns()
|
||||||
|
local exceptions = l:hyphenation()
|
||||||
|
if patterns and exceptions then
|
||||||
|
str = string.format("%sl:patterns(%q)l:hyphenation(%q)end", str, patterns, exceptions)
|
||||||
|
elseif patterns then
|
||||||
|
str = string.format("%sl:patterns(%q)end", str, patterns)
|
||||||
|
elseif exceptions then
|
||||||
|
str = string.format("%sl:hyphenation(%q)end", str, exceptions)
|
||||||
|
else
|
||||||
|
str = str .. 'end'
|
||||||
|
end
|
||||||
|
prepared[#prepared+1] = str
|
||||||
|
end
|
||||||
|
for i=2,#prepared do
|
||||||
|
if type(prepared[i]) ~= 'string' then
|
||||||
|
prepared[i] = assert(prepared[i]())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lua.bytecode[tex.count[262]+1] = build_bytecode(table.concat(prepared, '\n'))
|
||||||
|
end)
|
||||||
|
return function(f)
|
||||||
|
build_bytecode = f
|
||||||
|
return require'luametalatex-firstcode'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function fixupluafunctions(i)
|
||||||
|
predefined_luafunctions = i
|
||||||
|
fixupluafunctions = nil
|
||||||
|
end
|
||||||
|
return function(f)
|
||||||
|
f()
|
||||||
|
return require'luametalatex-firstcode'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,52 @@
|
||||||
|
-- Implement support for local variables.
|
||||||
|
|
||||||
|
local stack = {}
|
||||||
|
|
||||||
|
local restore_func = 65536 -- Reserved in firstcode
|
||||||
|
lua.get_functions_table()[restore_func] = function()
|
||||||
|
local level = assert(stack[tex.currentgrouplevel], 'Out of sync')
|
||||||
|
stack[tex.currentgrouplevel] = nil
|
||||||
|
for t,entries in next, level do
|
||||||
|
for k,v in next, entries do
|
||||||
|
t[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local restore_toks = {token.new(2, token.command_id'after_something') , token.new(restore_func, token.command_id'lua_call')} -- \atendofgroup FIXME: Detect mode automatically once token.primitive is fixed
|
||||||
|
local put_next = token.put_next
|
||||||
|
local runtoks = tex.runtoks
|
||||||
|
local function put_restore_toks()
|
||||||
|
put_next(restore_toks)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(t, k, v, global)
|
||||||
|
local l = tex.currentgrouplevel
|
||||||
|
if global then
|
||||||
|
for i=1,l do
|
||||||
|
local level = stack[i]
|
||||||
|
if level then
|
||||||
|
local saved = level[t]
|
||||||
|
if saved then
|
||||||
|
saved[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif l > 0 then
|
||||||
|
local level = stack[l]
|
||||||
|
if not level then
|
||||||
|
level = {}
|
||||||
|
runtoks(put_restore_toks)
|
||||||
|
stack[l] = level
|
||||||
|
end
|
||||||
|
|
||||||
|
local saved = level[t]
|
||||||
|
if not saved then
|
||||||
|
saved = {}
|
||||||
|
level[t] = saved
|
||||||
|
end
|
||||||
|
|
||||||
|
saved[k] = saved[k] or t[k]
|
||||||
|
end
|
||||||
|
|
||||||
|
t[k] = v
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
\directlua{unhook_expl()}
|
||||||
|
% See baseregisters for list of toks pdfvariables
|
||||||
|
\directlua{initialize_pdf_toks()}
|
||||||
|
\ifx\@tfor\undefined
|
||||||
|
\def\@tfor#1\do#2{}
|
||||||
|
\fi
|
||||||
|
\input ltexpl.ltx
|
|
@ -0,0 +1,34 @@
|
||||||
|
% To be loaded *after* microtype
|
||||||
|
%
|
||||||
|
% Patching \pickup@font to emulate LuaTeX's font expansion interface using \adjustspacing...
|
||||||
|
\RequirePackage{microtype}% Just to ensure it isn't loaded in the wrong position. Normally this shouldn't be necessary
|
||||||
|
\def\luametalatex@@setexpansion#-#1#2#3#-\relax{%
|
||||||
|
\adjustspacingstretch #1
|
||||||
|
\adjustspacingshrink #2
|
||||||
|
\adjustspacingstep #3
|
||||||
|
}%
|
||||||
|
\newluafunction\luametalatex@@expandglyphsinfont
|
||||||
|
\protected\luadef\pdffontexpand\luametalatex@@expandglyphsinfont
|
||||||
|
\def\luametalatex@@everyjobandnow#1{\toksapp\everyjob{#1}#1}%
|
||||||
|
%
|
||||||
|
\begingroup
|
||||||
|
\catcode`\!=\catcode`\%
|
||||||
|
\catcode`\%=12
|
||||||
|
\expanded{!
|
||||||
|
\endgroup
|
||||||
|
\def\noexpand\pickup@font{!
|
||||||
|
\unexpanded\expandafter{\pickup@font
|
||||||
|
\expandafter\expandafter\expandafter\luametalatex@@setexpansion\csname pickup@font@@hook@luametalatex@microtype@\the\fontid\font@name\endcsname{-1}{-1}{-1}\relax
|
||||||
|
}!
|
||||||
|
}!
|
||||||
|
\noexpand\luametalatex@@everyjobandnow{\noexpand\directlua{!
|
||||||
|
lua.get_functions_table()[\the\luametalatex@@expandglyphsinfont] = function()
|
||||||
|
token.put_next(token.create'fontid')
|
||||||
|
local font = token.scan_int()
|
||||||
|
local stretch = token.scan_int()
|
||||||
|
local shrink = token.scan_int()
|
||||||
|
local step = token.scan_int()
|
||||||
|
token.set_macro('pickup@font@@hook@luametalatex@microtype@' .. font, string.format("{}{%i}{%i}{%i}", stretch, shrink, step), "global")
|
||||||
|
end
|
||||||
|
}}
|
||||||
|
}%
|
|
@ -1,4 +1,4 @@
|
||||||
-- Provide enough compatibility function to make luaotfload happy
|
-- Provide enough compatibility functions to make luaotfload happy
|
||||||
|
|
||||||
local properties = node.direct.get_properties_table()
|
local properties = node.direct.get_properties_table()
|
||||||
local flush = node.direct.flush_list
|
local flush = node.direct.flush_list
|
||||||
|
@ -17,7 +17,7 @@ function node.direct.setcomponents(n, comp)
|
||||||
properties[n] = props
|
properties[n] = props
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local props_comp = props.components
|
local props_comp = rawget(props, 'components')
|
||||||
if props_comp then
|
if props_comp then
|
||||||
props_comp.components = comp -- Important even if nil to avoid double-free
|
props_comp.components = comp -- Important even if nil to avoid double-free
|
||||||
if not comp then props.components = nil end
|
if not comp then props.components = nil end
|
||||||
|
|
|
@ -29,6 +29,15 @@ local getexpansion = direct.getexpansion
|
||||||
local getchar = direct.getchar
|
local getchar = direct.getchar
|
||||||
local rangedimensions = direct.rangedimensions
|
local rangedimensions = direct.rangedimensions
|
||||||
local traverse_id = direct.traverse_id
|
local traverse_id = direct.traverse_id
|
||||||
|
local getdata = direct.getdata
|
||||||
|
|
||||||
|
local utils = require'luametalatex-pdf-utils'
|
||||||
|
local strip_floats = utils.strip_floats
|
||||||
|
local to_bp = utils.to_bp
|
||||||
|
|
||||||
|
local make_resources = require'luametalatex-pdf-resources'
|
||||||
|
local pdf_font_map = require'luametalatex-pdf-font-deduplicate'
|
||||||
|
local get_whatsit_handler = require'luametalatex-whatsits'.handler
|
||||||
|
|
||||||
local dir_id = node.id'dir'
|
local dir_id = node.id'dir'
|
||||||
|
|
||||||
|
@ -47,7 +56,6 @@ local function doublekeyed(t, id2name, name2id, index)
|
||||||
end
|
end
|
||||||
local nodehandler = (function()
|
local nodehandler = (function()
|
||||||
local function unknown_handler(_, n, x, y)
|
local function unknown_handler(_, n, x, y)
|
||||||
print(node.type(10))
|
|
||||||
write(format("Sorry, but the PDF backend does not support %q (id = %i) nodes right now. The supplied node will be dropped at coordinates (%i, %i).", node.type(getid(n)), getid(n), x//1, y//1))
|
write(format("Sorry, but the PDF backend does not support %q (id = %i) nodes right now. The supplied node will be dropped at coordinates (%i, %i).", node.type(getid(n)), getid(n), x//1, y//1))
|
||||||
end
|
end
|
||||||
return doublekeyed({}, node.type, node.id, function()
|
return doublekeyed({}, node.type, node.id, function()
|
||||||
|
@ -69,14 +77,12 @@ local whatsithandler = (function()
|
||||||
end)
|
end)
|
||||||
end)()
|
end)()
|
||||||
local glyph, text, page, cm_pending = 1, 2, 3, 4
|
local glyph, text, page, cm_pending = 1, 2, 3, 4
|
||||||
local gsub = string.gsub
|
|
||||||
local function projected_point(m, x, y, w)
|
local function projected_point(m, x, y, w)
|
||||||
w = w or 1
|
w = w or 1
|
||||||
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
||||||
end
|
end
|
||||||
local function sp2bp(sp)
|
local fontnames = setmetatable({}, {__index = function(t, k) local res = format("F%i", k) t[k] = res return res end})
|
||||||
return sp/65781.76
|
|
||||||
end
|
|
||||||
local topage
|
local topage
|
||||||
local function totext(p, fid)
|
local function totext(p, fid)
|
||||||
local last = p.mode
|
local last = p.mode
|
||||||
|
@ -90,15 +96,30 @@ local function totext(p, fid)
|
||||||
p.mode = text
|
p.mode = text
|
||||||
if last == text and p.font.fid == fid then return end
|
if last == text and p.font.fid == fid then return end
|
||||||
local f = font.getfont(fid) or font.fonts[fid]
|
local f = font.getfont(fid) or font.fonts[fid]
|
||||||
if last ~= text then p.strings[#p.strings+1] = "BT" p.pos.lx, p.pos.ly, p.pos.x, p.pos.y, p.font.exfactor = 0, 0, 0, 0, 0 end
|
if last ~= text then p.strings[#p.strings+1] = "BT" p.pos.lx, p.pos.ly, p.pos.x, p.pos.y, p.font.exfactor, p.font.extend, p.font.squeeze, p.font.slant = 0, 0, 0, 0, 0, 1, 1, 0 end
|
||||||
p:fontprovider(f, fid)
|
|
||||||
-- p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", f.parent, sp2bp(f.size)) -- TODO: Setting the mode, expansion, etc.
|
local pdf_fid = pdf_font_map[fid]
|
||||||
|
p.resources.Font[fontnames[pdf_fid]] = p.fontdirs[pdf_fid]
|
||||||
|
p.strings[#p.strings+1] = strip_floats(format("/F%i %f Tf 0 Tr", pdf_fid, to_bp(f.size))) -- TODO: Setting the mode, width, etc.
|
||||||
|
p.font.usedglyphs = p.usedglyphs[pdf_fid]
|
||||||
|
|
||||||
p.font.fid = fid
|
p.font.fid = fid
|
||||||
p.font.font = f
|
p.font.font = f
|
||||||
return false -- Return true if we need a new textmatrix
|
local need_tm = false
|
||||||
|
if p.font.extend ~= (f.extend or 1000)/1000 then
|
||||||
|
p.font.extend = (f.extend or 1000)/1000
|
||||||
|
need_tm = true
|
||||||
|
end
|
||||||
|
if p.font.squeeze ~= (f.squeeze or 1000)/1000 then
|
||||||
|
p.font.squeeze = (f.squeeze or 1000)/1000
|
||||||
|
need_tm = true
|
||||||
|
end
|
||||||
|
if p.font.slant ~= (f.slant or 0)/1000 then
|
||||||
|
p.font.slant = (f.slant or 0)/1000
|
||||||
|
need_tm = true
|
||||||
|
end
|
||||||
|
return need_tm
|
||||||
end
|
end
|
||||||
local inspect = require'inspect'
|
|
||||||
local function show(t) print(inspect(t)) end
|
|
||||||
function topage(p)
|
function topage(p)
|
||||||
local last = p.mode
|
local last = p.mode
|
||||||
if last == page then return end
|
if last == page then return end
|
||||||
|
@ -108,7 +129,7 @@ function topage(p)
|
||||||
elseif last == cm_pending then
|
elseif last == cm_pending then
|
||||||
local pending = p.pending_matrix
|
local pending = p.pending_matrix
|
||||||
if pending[1] ~= 1 or pending[2] ~= 0 or pending[3] ~= 0 or pending[4] ~= 1 or pending[5] ~= 0 or pending[6] ~= 0 then
|
if pending[1] ~= 1 or pending[2] ~= 0 or pending[3] ~= 0 or pending[4] ~= 1 or pending[5] ~= 0 or pending[6] ~= 0 then
|
||||||
p.strings[#p.strings+1] = format("%f %f %f %f %f %f cm", pending[1], pending[2], pending[3], pending[4], sp2bp(pending[5]), sp2bp(pending[6]))
|
p.strings[#p.strings+1] = strip_floats(format("%f %f %f %f %f %f cm", pending[1], pending[2], pending[3], pending[4], to_bp(pending[5]), to_bp(pending[6])))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error[[Unknown mode]]
|
error[[Unknown mode]]
|
||||||
|
@ -119,7 +140,7 @@ local function toglyph(p, fid, x, y, exfactor)
|
||||||
local last = p.mode
|
local last = p.mode
|
||||||
if last == glyph and p.font.fid == fid and p.pos.y == y and p.font.exfactor == exfactor then
|
if last == glyph and p.font.fid == fid and p.pos.y == y and p.font.exfactor == exfactor then
|
||||||
if x == p.pos.x then return end
|
if x == p.pos.x then return end
|
||||||
local xoffset = (x - p.pos.x)/p.font.font.size * 1000 / (1+exfactor/1000000)
|
local xoffset = (x - p.pos.x)/p.font.font.size * 1000 / p.font.extend / (1+exfactor/1000000)
|
||||||
if math.abs(xoffset) < 1000000 then -- 1000000 is arbitrary
|
if math.abs(xoffset) < 1000000 then -- 1000000 is arbitrary
|
||||||
p.pending[#p.pending+1] = format(")%i(", math.floor(-xoffset))
|
p.pending[#p.pending+1] = format(")%i(", math.floor(-xoffset))
|
||||||
p.pos.x = x
|
p.pos.x = x
|
||||||
|
@ -128,14 +149,31 @@ local function toglyph(p, fid, x, y, exfactor)
|
||||||
end
|
end
|
||||||
if totext(p, fid) or exfactor ~= p.font.exfactor then
|
if totext(p, fid) or exfactor ~= p.font.exfactor then
|
||||||
p.font.exfactor = exfactor
|
p.font.exfactor = exfactor
|
||||||
p.strings[#p.strings+1] = gsub(format("%f 0.0 %f %f %f %f Tm", 1+exfactor/1000000, 0, 1, sp2bp(x), sp2bp(y)), '%.?0+ ', ' ')
|
p.strings[#p.strings+1] = strip_floats(format("%f 0.0 %f %f %f %f Tm", p.font.extend * (1+exfactor/1000000), p.font.slant, p.font.squeeze, to_bp(x), to_bp(y)))
|
||||||
else
|
else
|
||||||
p.strings[#p.strings+1] = gsub(format("%f %f Td", sp2bp((x - p.pos.lx)/(1+exfactor/1000000)), sp2bp(y - p.pos.ly)), '%.?0+ ', ' ')
|
-- To invert the text transformation matrix (extend 0 0;slant squeeze 0;0 0 1)
|
||||||
|
-- we have to apply (extend^-1 0 0;-slant*extend^-1*squeeze^-1 squeeze^-1 0;0 0 1). (extend has to include expansion)
|
||||||
|
-- We optimize slightly by separating some steps
|
||||||
|
local dx, dy = to_bp((x - p.pos.lx)), to_bp(y - p.pos.ly) / p.font.squeeze
|
||||||
|
dx = (dx-p.font.slant*dy) / (p.font.extend * (1+exfactor/1000000))
|
||||||
|
p.strings[#p.strings+1] = strip_floats(format("%f %f Td", dx, dy))
|
||||||
end
|
end
|
||||||
p.pos.lx, p.pos.ly, p.pos.x, p.pos.y = x, y, x, y
|
p.pos.lx, p.pos.ly, p.pos.x, p.pos.y = x, y, x, y
|
||||||
p.mode = glyph
|
p.mode = glyph
|
||||||
p.pending[1] = "[("
|
p.pending[1] = "[("
|
||||||
end
|
end
|
||||||
|
-- Let's start with "handlers" for nodes which do not need any special handling:
|
||||||
|
local function ignore_node() end
|
||||||
|
-- The following are already handled by the list handler because they only correspond to blank space:
|
||||||
|
nodehandler.math = ignore_node
|
||||||
|
nodehandler.kern = ignore_node
|
||||||
|
-- The following are only for frontend use:
|
||||||
|
nodehandler.boundary = ignore_node
|
||||||
|
nodehandler.local_par = ignore_node
|
||||||
|
nodehandler.penalty = ignore_node
|
||||||
|
nodehandler.mark = ignore_node
|
||||||
|
|
||||||
|
-- Now we come to more interesting nodes:
|
||||||
function nodehandler.hlist(p, list, x0, y, outerlist, origin, level)
|
function nodehandler.hlist(p, list, x0, y, outerlist, origin, level)
|
||||||
if outerlist then
|
if outerlist then
|
||||||
if getid(outerlist) == 0 then
|
if getid(outerlist) == 0 then
|
||||||
|
@ -218,27 +256,43 @@ function nodehandler.vlist(p, list, x, y0, outerlist, origin, level)
|
||||||
y = y - (d or 0)
|
y = y - (d or 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
do
|
||||||
|
local rulesubtypes = {}
|
||||||
|
for i, n in next, node.subtypes'rule' do
|
||||||
|
rulesubtypes[n] = i
|
||||||
|
end
|
||||||
|
local box_rule = rulesubtypes.box
|
||||||
|
local image_rule = rulesubtypes.image
|
||||||
|
local user_rule = rulesubtypes.user
|
||||||
|
local empty_rule = rulesubtypes.empty
|
||||||
|
local outline_rule = rulesubtypes.outline
|
||||||
|
local ship_img = require'luametalatex-pdf-image'.ship
|
||||||
|
local ship_box = require'luametalatex-pdf-savedbox'.ship
|
||||||
|
-- print(require'inspect'(node.subtypes('glue')))
|
||||||
|
-- print(require'inspect'(node.fields('glue')))
|
||||||
|
-- print(require'inspect'(node.fields('rule')))
|
||||||
|
-- print(require'inspect'(node.fields('whatsit')))
|
||||||
function nodehandler.rule(p, n, x, y, outer)
|
function nodehandler.rule(p, n, x, y, outer)
|
||||||
if getwidth(n) == -1073741824 then setwidth(n, getwidth(outer)) end
|
if getwidth(n) == -1073741824 then setwidth(n, getwidth(outer)) end
|
||||||
if getheight(n) == -1073741824 then setheight(n, getheight(outer)) end
|
if getheight(n) == -1073741824 then setheight(n, getheight(outer)) end
|
||||||
if getdepth(n) == -1073741824 then setdepth(n, getdepth(outer)) end
|
if getdepth(n) == -1073741824 then setdepth(n, getdepth(outer)) end
|
||||||
local sub = getsubtype(n)
|
local sub = getsubtype(n)
|
||||||
if sub == 1 then
|
if getwidth(n) <= 0 or getdepth(n) + getheight(n) <= 0 then return end
|
||||||
error[[We can't handle boxes yet]]
|
if sub == box_rule then
|
||||||
elseif sub == 2 then
|
ship_box(getdata(n), p, n, x, y)
|
||||||
error[[We can't handle images yet]]
|
elseif sub == image_rule then
|
||||||
elseif sub == 3 then
|
ship_img(getdata(n), p, n, x, y)
|
||||||
elseif sub == 4 then
|
elseif sub == empty_rule then
|
||||||
|
elseif sub == user_rule then
|
||||||
error[[We can't handle user rules yet]]
|
error[[We can't handle user rules yet]]
|
||||||
elseif sub == 9 then
|
elseif sub == outline_rule then
|
||||||
error[[We can't handle outline rules yet]]
|
error[[We can't handle outline rules yet]]
|
||||||
else
|
else
|
||||||
if getwidth(n) <= 0 or getdepth(n) + getheight(n) <= 0 then return end
|
|
||||||
topage(p)
|
topage(p)
|
||||||
p.strings[#p.strings+1] = gsub(format("%f %f %f %f re f", sp2bp(x), sp2bp(y - getdepth(n)), sp2bp(getwidth(n)), sp2bp(getdepth(n) + getheight(n))), '%.?0+ ', ' ')
|
p.strings[#p.strings+1] = strip_floats(format("%f %f %f %f re f", to_bp(x), to_bp(y - getdepth(n)), to_bp(getwidth(n)), to_bp(getdepth(n) + getheight(n))))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function nodehandler.boundary() end
|
end
|
||||||
function nodehandler.disc(p, n, x, y, list, ...) -- FIXME: I am not sure why this can happen, let's assume we can use .replace
|
function nodehandler.disc(p, n, x, y, list, ...) -- FIXME: I am not sure why this can happen, let's assume we can use .replace
|
||||||
for n in traverse(getreplace(n)) do
|
for n in traverse(getreplace(n)) do
|
||||||
local next = getnext(n)
|
local next = getnext(n)
|
||||||
|
@ -247,8 +301,6 @@ function nodehandler.disc(p, n, x, y, list, ...) -- FIXME: I am not sure why thi
|
||||||
x = w + x
|
x = w + x
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function nodehandler.local_par() end
|
|
||||||
function nodehandler.math() end
|
|
||||||
function nodehandler.glue(p, n, x, y, outer, origin, level) -- Naturally this is an interesting one.
|
function nodehandler.glue(p, n, x, y, outer, origin, level) -- Naturally this is an interesting one.
|
||||||
local subtype = getsubtype(n)
|
local subtype = getsubtype(n)
|
||||||
if subtype < 100 then return end -- We only really care about leaders
|
if subtype < 100 then return end -- We only really care about leaders
|
||||||
|
@ -322,8 +374,6 @@ function nodehandler.glue(p, n, x, y, outer, origin, level) -- Naturally this is
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function nodehandler.kern() end
|
|
||||||
function nodehandler.penalty() end
|
|
||||||
|
|
||||||
local pdf_escape = require'luametalatex-pdf-escape'.escape_raw
|
local pdf_escape = require'luametalatex-pdf-escape'.escape_raw
|
||||||
local match = lpeg.match
|
local match = lpeg.match
|
||||||
|
@ -333,31 +383,34 @@ local function do_commands(p, c, f, fid, x, y, outer, ...)
|
||||||
for _, cmd in ipairs(c.commands) do
|
for _, cmd in ipairs(c.commands) do
|
||||||
if cmd[1] == "node" then
|
if cmd[1] == "node" then
|
||||||
local cmd = cmd[2]
|
local cmd = cmd[2]
|
||||||
|
assert(node.type(cmd))
|
||||||
|
cmd = todirect(cmd)
|
||||||
nodehandler[getid(cmd)](p, cmd, x, y, nil, ...)
|
nodehandler[getid(cmd)](p, cmd, x, y, nil, ...)
|
||||||
x = x + getwidth(cmd)
|
x = x + getwidth(cmd)
|
||||||
elseif cmd[1] == "font" then
|
elseif cmd[1] == "font" then
|
||||||
current_font = fonts[cmd[2]]
|
current_font = assert(fonts[cmd[2]], "invalid font requested")
|
||||||
elseif cmd[1] == "char" then
|
elseif cmd[1] == "char" then
|
||||||
local n = direct.new'glyph'
|
local n = direct.new'glyph'
|
||||||
setsubtype(n, 256)
|
setsubtype(n, 256)
|
||||||
setfont(n, current_font.id, cmd[2])
|
setfont(n, current_font.id, cmd[2])
|
||||||
nodehandler.glyph(p, n, x, y, outer, ...)
|
nodehandler.glyph(p, n, x, y, outer, ...)
|
||||||
direct.free(n)
|
|
||||||
x = x + getwidth(n)
|
x = x + getwidth(n)
|
||||||
|
direct.free(n)
|
||||||
elseif cmd[1] == "slot" then
|
elseif cmd[1] == "slot" then
|
||||||
|
current_font = assert(fonts[cmd[2]], "invalid font requested")
|
||||||
local n = direct.new'glyph'
|
local n = direct.new'glyph'
|
||||||
setsubtype(n, 256)
|
setsubtype(n, 256)
|
||||||
setfont(n, cmd[2], cmd[3])
|
setfont(n, current_font.id, cmd[3])
|
||||||
nodehandler.glyph(p, n, x, y, outer, ...)
|
nodehandler.glyph(p, n, x, y, outer, ...)
|
||||||
direct.free(n)
|
|
||||||
x = x + getwidth(n)
|
x = x + getwidth(n)
|
||||||
|
direct.free(n)
|
||||||
elseif cmd[1] == "rule" then
|
elseif cmd[1] == "rule" then
|
||||||
local n = direct.new'rule'
|
local n = direct.new'rule'
|
||||||
setheight(n, cmd[2])
|
setheight(n, cmd[2])
|
||||||
setwidth(n, cmd[3])
|
setwidth(n, cmd[3])
|
||||||
nodehandler.rule(p, n, x, y, outer, ...)
|
nodehandler.rule(p, n, x, y, outer, ...)
|
||||||
direct.free(n)
|
|
||||||
x = x + getwidth(n)
|
x = x + getwidth(n)
|
||||||
|
direct.free(n)
|
||||||
elseif cmd[1] == "left" then
|
elseif cmd[1] == "left" then
|
||||||
x = x + cmd[2]
|
x = x + cmd[2]
|
||||||
elseif cmd[1] == "down" then
|
elseif cmd[1] == "down" then
|
||||||
|
@ -383,10 +436,6 @@ local function do_commands(p, c, f, fid, x, y, outer, ...)
|
||||||
-- else
|
-- else
|
||||||
-- NOP, comment and invalid commands ignored
|
-- NOP, comment and invalid commands ignored
|
||||||
end
|
end
|
||||||
if #commands ~= 1 then error[[Unsupported command number]] end
|
|
||||||
if commands[1][1] ~= "node" then error[[Unsupported command name]] end
|
|
||||||
commands = commands[1][2]
|
|
||||||
nodehandler[getid(commands)](p, commands, x, y, nil, ...)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function nodehandler.glyph(p, n, x, y, ...)
|
function nodehandler.glyph(p, n, x, y, ...)
|
||||||
|
@ -417,21 +466,21 @@ function nodehandler.glyph(p, n, x, y, ...)
|
||||||
else
|
else
|
||||||
p.pending[#p.pending+1] = pdf_escape(string.pack('>H', index))
|
p.pending[#p.pending+1] = pdf_escape(string.pack('>H', index))
|
||||||
end
|
end
|
||||||
if not p.usedglyphs[index] then
|
if not p.font.usedglyphs[index] then
|
||||||
p.usedglyphs[index] = {index, math.floor(c.width * 1000 / f.size + .5), c.tounicode}
|
p.font.usedglyphs[index] = {index, math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
p.pending[#p.pending+1] = pdf_escape(string.char(getchar(n)))
|
p.pending[#p.pending+1] = pdf_escape(string.char(getchar(n)))
|
||||||
if not p.usedglyphs[getchar(n)] then
|
if not p.font.usedglyphs[getchar(n)] then
|
||||||
p.usedglyphs[getchar(n)] = {getchar(n), math.floor(c.width * 1000 / f.size + .5), c.tounicode}
|
p.font.usedglyphs[getchar(n)] = {getchar(n), math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
p.pos.x = p.pos.x + math.floor(getwidth(n)*(1+getexpansion(n)/1000000)+.5)
|
p.pos.x = p.pos.x + math.floor(getwidth(n)*(1+getexpansion(n)/1000000)+.5)
|
||||||
end
|
end
|
||||||
function nodehandler.whatsit(p, n, ...) -- Whatsit?
|
function nodehandler.whatsit(p, n, ...) -- Whatsit?
|
||||||
local prop = properties[n]-- or node.getproperty(n)
|
local handler, prop = get_whatsit_handler(n)
|
||||||
if prop and prop.handle then
|
if handler then
|
||||||
prop:handle(p, n, ...)
|
handler(prop, p, n, ...)
|
||||||
else
|
else
|
||||||
write("Invalid whatsit found (missing handler).")
|
write("Invalid whatsit found (missing handler).")
|
||||||
end
|
end
|
||||||
|
@ -455,8 +504,12 @@ function pdf.write_matrix(a, b, c, d, e, f, p)
|
||||||
pending[1], pending[2], pending[3], pending[4], pending[5], pending[6] = a, b, c, d, e, f
|
pending[1], pending[2], pending[3], pending[4], pending[5], pending[6] = a, b, c, d, e, f
|
||||||
end
|
end
|
||||||
local write_matrix = pdf.write_matrix
|
local write_matrix = pdf.write_matrix
|
||||||
|
local literal_type_names = { [0] =
|
||||||
|
'origin', 'page', 'direct', 'raw', 'text'
|
||||||
|
}
|
||||||
function pdf.write(mode, text, x, y, p)
|
function pdf.write(mode, text, x, y, p)
|
||||||
x, y, p = x or global_x, y or global_y, p or global_p
|
x, y, p = x or global_x, y or global_y, p or global_p
|
||||||
|
mode = literal_type_names[mode] or mode
|
||||||
if mode == "page" then
|
if mode == "page" then
|
||||||
topage(p)
|
topage(p)
|
||||||
p.strings[#p.strings + 1] = text
|
p.strings[#p.strings + 1] = text
|
||||||
|
@ -483,24 +536,9 @@ local ondemandmeta = {
|
||||||
return t[k]
|
return t[k]
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
local function writeresources(p)
|
local function nodewriter(file, n, fontdirs, usedglyphs, colorstacks, resources)
|
||||||
local resources = p.resources
|
|
||||||
local result = {"<<"}
|
|
||||||
for kind, t in pairs(resources) do if next(t) then
|
|
||||||
result[#result+1] = format("/%s<<", kind)
|
|
||||||
for name, value in pairs(t) do
|
|
||||||
result[#result+1] = format("/%s %i 0 R", name, value)
|
|
||||||
t[name] = nil
|
|
||||||
end
|
|
||||||
result[#result+1] = ">>"
|
|
||||||
end end
|
|
||||||
result[#result+1] = ">>"
|
|
||||||
return concat(result)
|
|
||||||
end
|
|
||||||
local fontnames = setmetatable({}, {__index = function(t, k) local res = format("F%i", k) t[k] = res return res end})
|
|
||||||
return function(file, n, fontdirs, usedglyphs, colorstacks)
|
|
||||||
n = todirect(n)
|
n = todirect(n)
|
||||||
setmetatable(usedglyphs, ondemandmeta)
|
resources = resources or make_resources()
|
||||||
local p = {
|
local p = {
|
||||||
is_page = not not colorstacks,
|
is_page = not not colorstacks,
|
||||||
file = file,
|
file = file,
|
||||||
|
@ -508,19 +546,15 @@ return function(file, n, fontdirs, usedglyphs, colorstacks)
|
||||||
strings = {},
|
strings = {},
|
||||||
pending = {},
|
pending = {},
|
||||||
pos = {},
|
pos = {},
|
||||||
fontprovider = function(p, f, fid)
|
|
||||||
if not f.parent then f.parent = pdf.getfontname(fid) end
|
|
||||||
p.resources.Font[fontnames[f.parent]] = fontdirs[f.parent]
|
|
||||||
p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", f.parent, sp2bp(f.size)) -- TODO: Setting the mode, expansion, etc.
|
|
||||||
p.usedglyphs = usedglyphs[f.parent]
|
|
||||||
end,
|
|
||||||
font = {},
|
font = {},
|
||||||
vfont = {},
|
vfont = {},
|
||||||
matrix = {1, 0, 0, 1, 0, 0},
|
matrix = {1, 0, 0, 1, 0, 0},
|
||||||
pending_matrix = {},
|
pending_matrix = {},
|
||||||
resources = setmetatable({}, ondemandmeta),
|
resources = resources,
|
||||||
annots = {},
|
annots = {},
|
||||||
linkcontext = file.linkcontext,
|
linkcontext = file.linkcontext,
|
||||||
|
fontdirs = fontdirs,
|
||||||
|
usedglyphs = usedglyphs,
|
||||||
}
|
}
|
||||||
if colorstacks then
|
if colorstacks then
|
||||||
for i=1, #colorstacks do
|
for i=1, #colorstacks do
|
||||||
|
@ -536,5 +570,7 @@ return function(file, n, fontdirs, usedglyphs, colorstacks)
|
||||||
nodehandler[getid(n)](p, n, 0, 0, n, nil, 0)
|
nodehandler[getid(n)](p, n, 0, 0, n, nil, 0)
|
||||||
-- nodehandler[getid(n)](p, n, 0, getdepth(n), n)
|
-- nodehandler[getid(n)](p, n, 0, getdepth(n), n)
|
||||||
topage(p)
|
topage(p)
|
||||||
return concat(p.strings, '\n'), writeresources(p), (p.annots[1] and string.format("/Annots[%s]", table.concat(p.annots, ' ')) or "")
|
return concat(p.strings, '\n'), resources, (p.annots[1] and string.format("/Annots[%s]", table.concat(p.annots, ' ')) or "")
|
||||||
end
|
end
|
||||||
|
require'luametalatex-pdf-savedbox':init_nodewriter(nodewriter)
|
||||||
|
return nodewriter
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
local mode = 6
|
local mode = 0
|
||||||
-- Control how much escaping is done... the mode is a bitset:
|
-- Control how much escaping is done... the mode is a bitset:
|
||||||
-- Bit 0: Disable auto-detection of pre-escaped input
|
-- Bit 0: Disable auto-detection of pre-escaped input
|
||||||
-- Bit 1: Convert UTF-8 input to UTF-16
|
-- Bit 1: Convert UTF-8 input to UTF-16
|
||||||
|
|
|
@ -457,7 +457,7 @@ function myfunc(buf, i0, fontid, usedcids, encoding, trust_widths)
|
||||||
if encoding == true then -- Use the built-in encoding
|
if encoding == true then -- Use the built-in encoding
|
||||||
CharStrings = parse_encoding(buf, i0, top.Encoding, CharStrings)
|
CharStrings = parse_encoding(buf, i0, top.Encoding, CharStrings)
|
||||||
elseif encoding then
|
elseif encoding then
|
||||||
encoding = require'parseEnc'(encoding)
|
encoding = require'luametalatex-font-enc'(encoding)
|
||||||
local encoded = {}
|
local encoded = {}
|
||||||
for i, n in pairs(encoding) do
|
for i, n in pairs(encoding) do
|
||||||
encoded[i] = CharStrings[n]
|
encoded[i] = CharStrings[n]
|
||||||
|
@ -580,7 +580,7 @@ end
|
||||||
-- local buf = file:read'a'
|
-- local buf = file:read'a'
|
||||||
-- file:close()
|
-- file:close()
|
||||||
-- io.open(arg[3], 'w'):write(myfunc(buf, 1, 1, nil, {{3}, {200}, {1000}, {1329}, {1330}, {1331}})):close()
|
-- io.open(arg[3], 'w'):write(myfunc(buf, 1, 1, nil, {{3}, {200}, {1000}, {1329}, {1330}, {1331}})):close()
|
||||||
return function(filename, encoding) return function(fontdir, usedcids)
|
return function(filename, fontid, encoding) return function(fontdir, usedcids)
|
||||||
local file = io.open(filename)
|
local file = io.open(filename)
|
||||||
local buf = file:read'a'
|
local buf = file:read'a'
|
||||||
local i = 1
|
local i = 1
|
||||||
|
@ -589,12 +589,12 @@ return function(filename, encoding) return function(fontdir, usedcids)
|
||||||
if magic == "ttcf" or magic == "OTTO" then
|
if magic == "ttcf" or magic == "OTTO" then
|
||||||
-- assert(not encoding) -- nil or false
|
-- assert(not encoding) -- nil or false
|
||||||
encoding = encoding or false
|
encoding = encoding or false
|
||||||
local magic, tables = sfnt.parse(buf, 1) -- TODO: Interpret widths etc, they might differ from the CFF ones.
|
local magic, tables = sfnt.parse(buf, fontid) -- TODO: Interpret widths etc, they might differ from the CFF ones.
|
||||||
assert(magic == "OTTO")
|
assert(magic == "OTTO")
|
||||||
-- Also CFF2 would be nice to have
|
-- Also CFF2 would be nice to have
|
||||||
i = tables['CFF '][1]
|
i = tables['CFF '][1]
|
||||||
end
|
end
|
||||||
local content, bbox = myfunc(buf, i, 1, usedcids, encoding)
|
local content, bbox = myfunc(buf, i, fontid, usedcids, encoding)
|
||||||
fontdir.bbox = bbox
|
fontdir.bbox = bbox
|
||||||
return content
|
return content
|
||||||
end end
|
end end
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
require'luametalatex-font-resolve' -- Ensure that font.fonts exists
|
||||||
|
|
||||||
|
local keymap = {}
|
||||||
|
|
||||||
|
-- There are multiple criteria for sharing backend fonts:
|
||||||
|
-- * Obviously encodingbytes have to be the same.
|
||||||
|
-- * The filename better is the same too.
|
||||||
|
-- * Similarly the index must be the same.
|
||||||
|
-- * Specifically in the PK font case *without* fixed DPI,
|
||||||
|
-- The size must be the same too.
|
||||||
|
-- * For fontmap based fonts, compare the name field instead,
|
||||||
|
-- of the normal filename, especially to capture encoding differences.
|
||||||
|
-- An alternative might be to only take the encoding into account.
|
||||||
|
-- This is also required for other fonts which might not be backed by
|
||||||
|
-- traditional files
|
||||||
|
local function build_sharekey(fontdir)
|
||||||
|
local encodingbytes = assert(fontdir.encodingbytes)
|
||||||
|
local key = string.format("%i:%s:", fontdir.encodingbytes, fontdir.format)
|
||||||
|
if encodingbytes == 1 then
|
||||||
|
if fontdir.format:sub(1,5) == "type3" then
|
||||||
|
return string.format("%s%i:%s", key, fontdir.size, fontdir.name)
|
||||||
|
end
|
||||||
|
key = string.format("%s%s:", key, fontdir.encoding or '')
|
||||||
|
end
|
||||||
|
key = string.format("%s%i:%s", key, fontdir.subfont or 1, fontdir.filename)
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
local fonts = font.fonts
|
||||||
|
local fontmap = setmetatable({}, {
|
||||||
|
__index = function(t, fid)
|
||||||
|
local key = build_sharekey(assert(fonts[fid]))
|
||||||
|
local mapped = keymap[key]
|
||||||
|
local share_parent
|
||||||
|
if mapped then
|
||||||
|
share_parent = t[mapped]
|
||||||
|
else
|
||||||
|
share_parent, keymap[key], t[fid] = fid, fid, fid
|
||||||
|
end
|
||||||
|
return share_parent
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
function pdf.getfontname(fid)
|
||||||
|
return fontmap[fid]
|
||||||
|
end
|
||||||
|
|
||||||
|
return fontmap
|
|
@ -42,7 +42,13 @@ local function mapfile(filename, operator)
|
||||||
for line in file:lines() do mapline(line, operator) end
|
for line in file:lines() do mapline(line, operator) end
|
||||||
file:close()
|
file:close()
|
||||||
end
|
end
|
||||||
|
local function reset()
|
||||||
|
for k in next, fontmap do
|
||||||
|
fontmap[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
return {
|
return {
|
||||||
|
reset = reset,
|
||||||
mapline = mapline,
|
mapline = mapline,
|
||||||
mapfile = mapfile,
|
mapfile = mapfile,
|
||||||
fontmap = fontmap
|
fontmap = fontmap
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
local pk_global_resolution, pk_resolution_is_fixed
|
||||||
|
local pdfvariable = pdf.variable
|
||||||
|
|
||||||
|
local read_pk = require'luametalatex-font-pk'
|
||||||
|
local strip_floats = require'luametalatex-pdf-utils'.strip_floats
|
||||||
|
return function(pdf, fontdir, usedcids)
|
||||||
|
if not pk_global_resolution then
|
||||||
|
pk_global_resolution = pdfvariable.pkresolution
|
||||||
|
if not pk_global_resolution or pk_global_resolution == 0 then
|
||||||
|
pk_global_resolution = kpse.var_value'pk_dpi' or 72
|
||||||
|
end
|
||||||
|
local mode = pdfvariable.pkmode
|
||||||
|
pk_resolution_is_fixed = pdfvariable.pkfixeddpi ~= 0
|
||||||
|
kpse.init_prog("LUATEX", pk_global_resolution, pkmode ~= '' and pkmode or nil, nil) -- ?
|
||||||
|
end
|
||||||
|
local pk = read_pk(kpse.find_file(fontdir.name, 'pk', pk_resolution_is_fixed and pk_global_resolution or (pk_global_resolution*fontdir.size/fontdir.designsize+.5)//1))
|
||||||
|
local designsize = pk.designsize/1044654.326 -- 1044654.326=2^20*72/72.27 -- designsize in bp
|
||||||
|
local hscale = 65536/pk.hppp / designsize -- 65291.158=2^16*72/72.27
|
||||||
|
local vscale = 65536/pk.vppp / designsize -- 65291.158=2^16*72/72.27
|
||||||
|
local bbox = {0, 0, 0, 0}
|
||||||
|
local matrix = {hscale, 0, 0, vscale, 0, 0}
|
||||||
|
local widths = {}
|
||||||
|
local first_cid = usedcids[1][1]-1
|
||||||
|
local charprocs = {}
|
||||||
|
local prev = 0
|
||||||
|
for i=1,#usedcids do
|
||||||
|
local used = usedcids[i]
|
||||||
|
local glyph = pk[used[1]]
|
||||||
|
for j=prev+1,used[1]-first_cid-1 do
|
||||||
|
widths[j] = 0
|
||||||
|
end
|
||||||
|
prev = used[1]-first_cid
|
||||||
|
widths[prev] = glyph.dx/2^16
|
||||||
|
local lower, left, upper, right = glyph.voff - glyph.h, -glyph.hoff, glyph.voff, -glyph.hoff + glyph.w
|
||||||
|
bbox[1], bbox[2], bbox[3], bbox[4] = math.min(bbox[1], left), math.min(bbox[2], lower), math.max(bbox[3], right), math.max(bbox[4], upper)
|
||||||
|
charprocs[i] = string.format("/G%i %i 0 R", used[1], pdf:stream(nil, "", string.format("%i %i %i %i %i %i d1 %i 0 0 %i %i %i cm BI /W %i/H %i/IM true/BPC 1/D[1 0] ID %s EI",
|
||||||
|
glyph.dx/2^16, glyph.dy, left, lower, right, upper, glyph.w, glyph.h, left, lower, glyph.w, glyph.h, glyph.data
|
||||||
|
)))
|
||||||
|
end
|
||||||
|
return bbox, matrix, pdf:indirect(nil, strip_floats('[' .. table.concat(widths, ' ') .. ']')), '<<' .. table.concat(charprocs) .. '>>'
|
||||||
|
end
|
|
@ -42,7 +42,7 @@ return function(filename, reencode)
|
||||||
reencode = kpse.find_file("8a.enc", "enc files")
|
reencode = kpse.find_file("8a.enc", "enc files")
|
||||||
end
|
end
|
||||||
if reencode then
|
if reencode then
|
||||||
parsed_t1.Encoding = require'parseEnc'(reencode)
|
parsed_t1.Encoding = require'luametalatex-font-enc'(reencode)
|
||||||
end
|
end
|
||||||
-- parsed_t1.Encoding[0] = ".notdef"
|
-- parsed_t1.Encoding[0] = ".notdef"
|
||||||
local glyphs = {}
|
local glyphs = {}
|
||||||
|
|
|
@ -83,7 +83,7 @@ return function(filename, fontid, reencode)
|
||||||
|
|
||||||
return function(fontdir, usedcids)
|
return function(fontdir, usedcids)
|
||||||
if reencode and string.unpack(">I4", buf, tables.post[1]) == 0x00020000 then
|
if reencode and string.unpack(">I4", buf, tables.post[1]) == 0x00020000 then
|
||||||
usedcids = readpostnames(buf, tables.post[1] + 32, usedcids, require'parseEnc'(reencode))
|
usedcids = readpostnames(buf, tables.post[1] + 32, usedcids, require'luametalatex-font-enc'(reencode))
|
||||||
else
|
else
|
||||||
usedcids = table.move(usedcids, 1, #usedcids, 1, {})
|
usedcids = table.move(usedcids, 1, #usedcids, 1, {})
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
local mapping = require'luametalatex-pdf-font-map'
|
local strip_floats = require'luametalatex-pdf-utils'.strip_floats
|
||||||
mapping.mapfile(kpse.find_file('pdftex.map', 'map', true))
|
|
||||||
local tounicode = {
|
local tounicode = {
|
||||||
[-3] = require'luametalatex-pdf-font-cmap3',
|
[-3] = require'luametalatex-pdf-font-cmap3',
|
||||||
require'luametalatex-pdf-font-cmap1',
|
require'luametalatex-pdf-font-cmap1',
|
||||||
|
@ -60,6 +60,31 @@ local function fontdescriptor(pdf, basefont, fontdir, stream, kind)
|
||||||
fontdir.StemV or 100, -- FIXME: How to determine StemV?
|
fontdir.StemV or 100, -- FIXME: How to determine StemV?
|
||||||
kind, stream)
|
kind, stream)
|
||||||
end
|
end
|
||||||
|
local function encodingtype3(pdf)
|
||||||
|
if not pdf.encodingtype3 then
|
||||||
|
pdf.encodingtype3 = string.format(" %i 0 R", pdf:indirect(nil, "\z
|
||||||
|
<</Differences[\z
|
||||||
|
0/G0/G1/G2/G3/G4/G5/G6/G7/G8/G9/G10/G11/G12/G13/G14/G15/G16\z
|
||||||
|
/G17/G18/G19/G20/G21/G22/G23/G24/G25/G26/G27/G28/G29/G30/G31\z
|
||||||
|
/G32/G33/G34/G35/G36/G37/G38/G39/G40/G41/G42/G43/G44/G45/G46\z
|
||||||
|
/G47/G48/G49/G50/G51/G52/G53/G54/G55/G56/G57/G58/G59/G60/G61\z
|
||||||
|
/G62/G63/G64/G65/G66/G67/G68/G69/G70/G71/G72/G73/G74/G75/G76\z
|
||||||
|
/G77/G78/G79/G80/G81/G82/G83/G84/G85/G86/G87/G88/G89/G90/G91\z
|
||||||
|
/G92/G93/G94/G95/G96/G97/G98/G99/G100/G101/G102/G103/G104/G105/G106\z
|
||||||
|
/G107/G108/G109/G110/G111/G112/G113/G114/G115/G116/G117/G118/G119/G120/G121\z
|
||||||
|
/G122/G123/G124/G125/G126/G127/G128/G129/G130/G131/G132/G133/G134/G135/G136\z
|
||||||
|
/G137/G138/G139/G140/G141/G142/G143/G144/G145/G146/G147/G148/G149/G150/G151\z
|
||||||
|
/G152/G153/G154/G155/G156/G157/G158/G159/G160/G161/G162/G163/G164/G165/G166\z
|
||||||
|
/G167/G168/G169/G170/G171/G172/G173/G174/G175/G176/G177/G178/G179/G180/G181\z
|
||||||
|
/G182/G183/G184/G185/G186/G187/G188/G189/G190/G191/G192/G193/G194/G195/G196\z
|
||||||
|
/G197/G198/G199/G200/G201/G202/G203/G204/G205/G206/G207/G208/G209/G210/G211\z
|
||||||
|
/G212/G213/G214/G215/G216/G217/G218/G219/G220/G221/G222/G223/G224/G225/G226\z
|
||||||
|
/G227/G228/G229/G230/G231/G232/G233/G234/G235/G236/G237/G238/G239/G240/G241\z
|
||||||
|
/G242/G243/G244/G245/G246/G247/G248/G249/G250/G251/G252/G253/G254/G255]>>"
|
||||||
|
))
|
||||||
|
end
|
||||||
|
return pdf.encodingtype3
|
||||||
|
end
|
||||||
local function cidmap1byte(pdf)
|
local function cidmap1byte(pdf)
|
||||||
if not pdf.cidmap1byte then
|
if not pdf.cidmap1byte then
|
||||||
pdf.cidmap1byte = string.format(" %i 0 R", pdf:stream(nil, [[/Type/CMap/CMapName/Identity-8-H/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>]],
|
pdf.cidmap1byte = string.format(" %i 0 R", pdf:stream(nil, [[/Type/CMap/CMapName/Identity-8-H/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>]],
|
||||||
|
@ -158,7 +183,7 @@ local function buildfont0cff(pdf, fontdir, usedcids)
|
||||||
if fontdir.format == "type1" then
|
if fontdir.format == "type1" then
|
||||||
cff = require'luametalatex-pdf-font-t1'(fontdir.filename, fontdir.encoding)(fontdir, usedcids)
|
cff = require'luametalatex-pdf-font-t1'(fontdir.filename, fontdir.encoding)(fontdir, usedcids)
|
||||||
elseif fontdir.format == "opentype" then
|
elseif fontdir.format == "opentype" then
|
||||||
cff = require'luametalatex-pdf-font-cff'(fontdir.filename, fontdir.encodingbytes == 1 and (fontdir.encoding or true))(fontdir, usedcids)
|
cff = require'luametalatex-pdf-font-cff'(fontdir.filename, 1, fontdir.encodingbytes == 1 and (fontdir.encoding or true))(fontdir, usedcids)
|
||||||
else
|
else
|
||||||
error[[Unsupported format]]
|
error[[Unsupported format]]
|
||||||
end
|
end
|
||||||
|
@ -224,50 +249,36 @@ local function buildfont0(pdf, fontdir, usedcids)
|
||||||
touni,
|
touni,
|
||||||
cidfont)
|
cidfont)
|
||||||
end
|
end
|
||||||
local fontextensions = {
|
local buildfontpk = require'luametalatex-pdf-font-pk'
|
||||||
ttf = {"truetype", "truetype fonts",},
|
local buildfontnode = require'luametalatex-pdf-font-node'.buildfont
|
||||||
otf = {"opentype", "opentype fonts",},
|
local function buildfont3(pdf, fontdir, usedcids)
|
||||||
pfb = {"type1", "type1 fonts",},
|
local buildfont = fontdir.format == 'type3' and buildfontpk
|
||||||
}
|
or fontdir.format == 'type3node' and buildfontnode
|
||||||
fontextensions.cff = fontextensions.otf
|
or error[[Unsupported Type3 based font format]]
|
||||||
local fontformats = {
|
usedcids = usedcids or allcids(fontdir)
|
||||||
fontextensions.pfb, fontextensions.otf, fontextensions.ttf,
|
table.sort(usedcids, function(a,b) return a[1]<b[1] end)
|
||||||
}
|
local enc = cidmap1byte(pdf)
|
||||||
|
local bbox, matrix, widths, charprocs = buildfont(pdf, fontdir, usedcids)
|
||||||
|
local touni = pdf:stream(nil, "", tounicode[1](fontdir, usedcids)) -- Done late to allow for defaults set from the font file
|
||||||
|
local matrices = strip_floats(string.format(
|
||||||
|
"/FontBBox[%f %f %f %f]/FontMatrix[%f %f %f %f %f %f]",
|
||||||
|
bbox[1], bbox[2], bbox[3], bbox[4],
|
||||||
|
matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6]))
|
||||||
|
return string.format(
|
||||||
|
"<</Type/Font/Subtype/Type3%s/CharProcs%s/Encoding%s/FirstChar %i/LastChar %i/Widths %i 0 R/ToUnicode %i 0 R>>",
|
||||||
|
-- "<</Type/Font/Subtype/Type3/CharProcs%s/Encoding%s/FirstChar %i/LastChar %i/Widths %i 0 R/ToUnicode %i 0 R/FontDescriptor %i 0 R>>",
|
||||||
|
matrices,
|
||||||
|
charprocs,
|
||||||
|
encodingtype3(pdf),
|
||||||
|
usedcids[1][1],
|
||||||
|
usedcids[#usedcids][1],
|
||||||
|
widths,
|
||||||
|
touni
|
||||||
|
) -- , descriptor) -- TODO
|
||||||
|
end
|
||||||
return function(pdf, fontdir, usedcids)
|
return function(pdf, fontdir, usedcids)
|
||||||
if fontdir.encodingbytes == 0 then fontdir.encodingbytes = nil end
|
if fontdir.format:sub(1,5) == "type3" then
|
||||||
if fontdir.format == "unknown" or not fontdir.format or fontdir.encodingbytes == 1 then -- TODO: How to check this?
|
return buildfont3(pdf, fontdir, usedcids)
|
||||||
fontdir.encodingbytes = fontdir.encodingbytes or 1
|
|
||||||
local mapentry = mapping.fontmap[fontdir.name]
|
|
||||||
if mapentry then
|
|
||||||
local format = mapentry[3] and mapentry[3]:sub(-4, -4) == '.' and fontextensions[mapentry[3]:sub(-3, -1)]
|
|
||||||
if format then
|
|
||||||
fontdir.format = format[1]
|
|
||||||
fontdir.filename = kpse.find_file(mapentry[3], format[2])
|
|
||||||
if mapentry[4] then
|
|
||||||
fontdir.encoding = kpse.find_file(mapentry[4], 'enc files')
|
|
||||||
end
|
|
||||||
goto format_set
|
|
||||||
else
|
|
||||||
for _, format in ipairs(fontformats) do
|
|
||||||
local font = kpse.find_file(mapentry[3],format[2])
|
|
||||||
if font then
|
|
||||||
fontdir.format = "type1"
|
|
||||||
fontdir.filename = font
|
|
||||||
if mapentry[4] then
|
|
||||||
fontdir.encoding = kpse.find_file(mapentry[4], 'enc files')
|
|
||||||
end
|
|
||||||
goto format_set
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
fontdir.format = "type3"
|
|
||||||
::format_set::
|
|
||||||
else
|
|
||||||
fontdir.encodingbytes = fontdir.encodingbytes or 2
|
|
||||||
end
|
|
||||||
if fontdir.format == "type3" then
|
|
||||||
error[[Currently unsupported]] -- TODO
|
|
||||||
else
|
else
|
||||||
return buildfont0(pdf, fontdir, usedcids)
|
return buildfont0(pdf, fontdir, usedcids)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
local box_fallback = {
|
||||||
|
BleedBox = "CropBox",
|
||||||
|
TrimBox = "CropBox",
|
||||||
|
ArtBox = "CropBox",
|
||||||
|
CropBox = "MediaBox",
|
||||||
|
}
|
||||||
|
|
||||||
|
local boxmap = {
|
||||||
|
media = "MediaBox",
|
||||||
|
crop = "CropBox",
|
||||||
|
bleed = "BleedBox",
|
||||||
|
trim = "TrimBox",
|
||||||
|
art = "ArtBox",
|
||||||
|
}
|
||||||
|
|
||||||
|
local utils = require'luametalatex-pdf-utils'
|
||||||
|
local strip_floats = utils.strip_floats
|
||||||
|
local to_sp = utils.to_sp
|
||||||
|
local to_bp = utils.to_bp
|
||||||
|
|
||||||
|
local function get_box(page, box)
|
||||||
|
box = boxmap[box]
|
||||||
|
while box do
|
||||||
|
local found = pdfe.getbox(page, box)
|
||||||
|
if found then
|
||||||
|
return {to_sp(found[1]), to_sp(found[2]), to_sp(found[3]), to_sp(found[4])}
|
||||||
|
end
|
||||||
|
box = box_fallback[box]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local pdf_functions = {}
|
||||||
|
|
||||||
|
local function open_pdfe(img)
|
||||||
|
local file = pdfe.open(img.filepath)
|
||||||
|
do
|
||||||
|
local userpassword = img.userpassword
|
||||||
|
local ownerpassword = img.ownerpassword
|
||||||
|
if userpassword or ownerpassword then
|
||||||
|
pdfe.unencrypt(file, userpassword, ownerpassword)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local status = pdfe.getstatus(file)
|
||||||
|
if status >= 0 then
|
||||||
|
return file
|
||||||
|
elseif status == -1 then
|
||||||
|
error[[PDF image is encrypted. Please provide the decryption key.]]
|
||||||
|
elseif status == -2 then
|
||||||
|
error[[PDF image could not be opened.]]
|
||||||
|
else
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function pdf_functions.scan(img)
|
||||||
|
local file = open_pdfe(img)
|
||||||
|
img.pages = pdfe.getnofpages(file)
|
||||||
|
img.page = img.page or 1
|
||||||
|
if img.page > img.pages then
|
||||||
|
error[[Not enough pages in PDF image]]
|
||||||
|
end
|
||||||
|
local page = pdfe.getpage(file, img.page)
|
||||||
|
local bbox = img.bbox or get_box(page, img.pagebox or 'crop') or {0, 0, 0, 0}
|
||||||
|
img.bbox = bbox
|
||||||
|
img.rotation = (360 - (page.Rotate or 0)) % 360
|
||||||
|
assert(img.rotation % 90 == 0, "Invalid /Rotate")
|
||||||
|
img.rotation = img.rotation / 90
|
||||||
|
if img.rotation < 0 then img.rotation = img.rotation + 4 end
|
||||||
|
img.xsize = bbox[3] - bbox[1]
|
||||||
|
img.ysize = bbox[4] - bbox[2]
|
||||||
|
img.xres, img.yres = nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local pdfe_deepcopy = require'luametalatex-pdfe-deepcopy'
|
||||||
|
function pdf_functions.write(pfile, img)
|
||||||
|
local file = open_pdfe(img)
|
||||||
|
local page = pdfe.getpage(file, img.page)
|
||||||
|
local bbox = img.bbox
|
||||||
|
local dict = strip_floats(string.format("/Subtype/Form/BBox[%f %f %f %f]/Resources ", to_bp(bbox[1]), to_bp(bbox[2]), to_bp(bbox[3]), to_bp(bbox[4])))
|
||||||
|
dict = dict .. pdfe_deepcopy(file, img.filepath, pfile, pdfe.getfromdictionary(page, 'Resources'))
|
||||||
|
local content, raw = page.Contents
|
||||||
|
-- Three cases: Contents is a stream, so copy the stream (Remember to copy filter if necessary)
|
||||||
|
-- Contents is an array of streams, so append all the streams as a new stream
|
||||||
|
-- Contents is missing. Then create an empty stream.
|
||||||
|
local type = pdfe.type(content)
|
||||||
|
if type == 'pdfe.stream' then
|
||||||
|
raw = true
|
||||||
|
for i=1,#content do
|
||||||
|
local key, type, value, detail = pdfe.getfromstream(content, i)
|
||||||
|
dict = dict .. pdfe_deepcopy(file, img.filepath, pfile, 5, key) .. ' ' .. pdfe_deepcopy(file, img.filepath, pfile, type, value, detail)
|
||||||
|
end
|
||||||
|
content = content(false)
|
||||||
|
elseif type == 'pdfe.array' then
|
||||||
|
local array = content
|
||||||
|
content = ''
|
||||||
|
for i=1,#array do
|
||||||
|
content = content .. array[i](true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
content = ''
|
||||||
|
end
|
||||||
|
local attr = img.attr
|
||||||
|
if attr then
|
||||||
|
dict = dict .. attr
|
||||||
|
end
|
||||||
|
pfile:stream(img.objnum, dict, content, nil, raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
return pdf_functions
|
|
@ -0,0 +1,351 @@
|
||||||
|
local strip_floats = require'luametalatex-pdf-utils'.strip_floats
|
||||||
|
|
||||||
|
local function ignore() end
|
||||||
|
local parse = setmetatable({
|
||||||
|
-- IHDR = below,
|
||||||
|
-- PLTE = below,
|
||||||
|
-- IDAT = below,
|
||||||
|
-- IEND = below,
|
||||||
|
-- I'm not yet sure what to do about the following four color management chunks:
|
||||||
|
-- These two will probably be ignored (if you care about this stuff, you probably
|
||||||
|
-- prefer an ICC profile anyway. Also especially cHRM requires some weird computations.)
|
||||||
|
-- cHRM = TODO, -- ignore?
|
||||||
|
-- gAMA = TODO, -- ignore?
|
||||||
|
-- iCCP is implemented, but profiles are not cached, so it might include the
|
||||||
|
-- same profile many times
|
||||||
|
-- iCCP = below,
|
||||||
|
-- I would expect sRGB to be the most common, but it is a bit complicated because
|
||||||
|
-- PDF seems to require us to ship an actual ICC profile to support sRGB. Maybe later.
|
||||||
|
-- sRGB = TODO,
|
||||||
|
sBIT = ignore,
|
||||||
|
bKGD = ignore, -- Background color. Ignored since we support transparency
|
||||||
|
hIST = ignore, -- Color histogram
|
||||||
|
-- tRNS = below,
|
||||||
|
-- pHYs = below, -- resolution information
|
||||||
|
sPLT = ignore, -- Suggested palette but we support full truetype
|
||||||
|
tIME = ignore, -- The following only store metadata
|
||||||
|
iTXt = ignore,
|
||||||
|
tEXt = ignore,
|
||||||
|
zTXt = ignore,
|
||||||
|
}, {
|
||||||
|
__index = function(_, n)
|
||||||
|
print("Table " .. n .. " unsupported") -- FIXME: Handle extensions by detecing if they are critical etc.
|
||||||
|
return ignore
|
||||||
|
end
|
||||||
|
})
|
||||||
|
function parse.IHDR(buf, i, after, ctxt)
|
||||||
|
if next(ctxt) then
|
||||||
|
error[[The header should come first]]
|
||||||
|
end
|
||||||
|
local compression, filter
|
||||||
|
ctxt.width, ctxt.height,
|
||||||
|
ctxt.bitdepth, ctxt.colortype,
|
||||||
|
compression, filter,
|
||||||
|
ctxt.interlace, i = string.unpack(">I4I4I1I1I1I1I1", buf, i)
|
||||||
|
if i ~= after then
|
||||||
|
return [[Invalid header size]]
|
||||||
|
end
|
||||||
|
if compression ~= 0 then
|
||||||
|
error [[Unsupported compression mode]]
|
||||||
|
end
|
||||||
|
if filter ~= 0 then
|
||||||
|
error [[Unsupported filter mode]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function parse.PLTE(buf, i, after, ctxt)
|
||||||
|
if ctxt.PLTE then
|
||||||
|
error[[Multiple palettes detected]]
|
||||||
|
end
|
||||||
|
if (after-i)%3 ~= 0 then
|
||||||
|
error[[Invalid palette lenght]]
|
||||||
|
end
|
||||||
|
ctxt.PLTE_len = (after-i) // 3
|
||||||
|
ctxt.PLTE = string.sub(buf, i, after-1)
|
||||||
|
end
|
||||||
|
function parse.tRNS(buf, i, after, ctxt)
|
||||||
|
if ctxt.colortype == 3 then
|
||||||
|
local count = assert(ctxt.PLTE_len)
|
||||||
|
local patt = lpeg.P(1) * lpeg.Cc'\xff'
|
||||||
|
for j=0,after-i-1 do
|
||||||
|
local off = i+j
|
||||||
|
patt = lpeg.P(string.char(j)) * lpeg.Cc(buf:sub(off, off)) + patt
|
||||||
|
end
|
||||||
|
ctxt.tRNS = lpeg.Cs(lpeg.Cg(patt)^0)
|
||||||
|
elseif ctxt.colortype == 0 then
|
||||||
|
local color
|
||||||
|
color, i = string.unpack(">I2", buf, i)
|
||||||
|
assert(i == after)
|
||||||
|
ctxt.tRNS = string.format('%i %i', color, color)
|
||||||
|
elseif ctxt.colortype == 2 then
|
||||||
|
local r, g, b
|
||||||
|
r, g, b, i = string.unpack(">I2I2I2", buf, i)
|
||||||
|
assert(i == after)
|
||||||
|
ctxt.tRNS = string.format('%i %i %i %i %i %i', r, r, g, g, b, b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local meterperinch = 0.0254
|
||||||
|
function parse.pHYs(buf, i, after, ctxt)
|
||||||
|
local xres, yres, unit
|
||||||
|
xres, yres, unit, i = string.unpack('>I4I4I1', buf, i)
|
||||||
|
if unit == 0 then
|
||||||
|
if xres > yres then
|
||||||
|
ctxt.xres, ctxt.yres = xres/yres, 0
|
||||||
|
elseif xres < yres then
|
||||||
|
ctxt.xres, ctxt.yres = 0, yres/xres
|
||||||
|
end
|
||||||
|
elseif unit == 1 then
|
||||||
|
ctxt.xres, ctxt.yres = xres * meterperinch, yres * meterperinch
|
||||||
|
else
|
||||||
|
error[[Invalid unit]]
|
||||||
|
end
|
||||||
|
assert(i == after)
|
||||||
|
end
|
||||||
|
function parse.sRGB(buf, i, after, ctxt)
|
||||||
|
assert(i+1 == after)
|
||||||
|
ctxt.sRGB = buf:char(i)
|
||||||
|
end
|
||||||
|
function parse.iCCP(buf, i, after, ctxt)
|
||||||
|
local j = buf:find('\0', i, true)
|
||||||
|
assert(j+1<after)
|
||||||
|
-- local name = buf:sub(i, j-1)
|
||||||
|
-- print('ICC Profile name: ' .. name)
|
||||||
|
assert(buf:byte(j+1) == 0) -- The only known compression mode
|
||||||
|
ctxt.iCCP = buf:sub(j+2, after-1)
|
||||||
|
-- ctxt.iCCP = xzip.decompress(buf:sub(j+2, after-1))
|
||||||
|
end
|
||||||
|
function parse.gAMA(buf, i, after, ctxt)
|
||||||
|
local gamma, i = string.unpack(">I4", buf, i)
|
||||||
|
assert(after == i)
|
||||||
|
ctxt.gAMA = 100000/gamma
|
||||||
|
end
|
||||||
|
function parse.cHRM(buf, i, after, ctxt)
|
||||||
|
local x_W, y_W, x_R, y_R, x_G, y_G, x_B, y_B, i = string.unpack(">I4I4I4I4I4I4I4I4", buf, i)
|
||||||
|
assert(after == i)
|
||||||
|
x_W, y_W, x_R, y_R, x_G, y_G, x_B, y_B = x_W/100000, y_W/100000, x_R/100000, y_R/100000,
|
||||||
|
x_G/100000, y_G/100000, x_B/100000, y_B/100000
|
||||||
|
local z = y_W*((x_G-x_B)*y_R-(x_R-x_B)*y_G+(x_R-x_G)*y_B)
|
||||||
|
z = 1/z
|
||||||
|
local Y_A = y_R*((x_G-x_B)*y_W-(x_W-x_B)*y_G+(x_W-x_G)*y_B) * z
|
||||||
|
local X_A, Z_A = Y_A*x_R/y_R, Y_A*((1-x_R)/y_R-1)
|
||||||
|
local Y_B = -y_G*((x_R-x_B)*y_W-(x_W-x_B)*y_R+(x_W-x_R)*y_B) * z
|
||||||
|
local X_B, Z_B = Y_B*x_G/y_G, Y_B*((1-x_G)/y_G-1)
|
||||||
|
local Y_C = y_B*((x_R-x_G)*y_W-(x_W-x_G)*y_R+(x_W-x_R)*y_G) * z
|
||||||
|
local X_C, Z_C = Y_C*x_B/y_B, Y_C*((1-x_B)/y_B-1)
|
||||||
|
|
||||||
|
local X_W, Y_W, Z_W = X_A+X_B+X_C, Y_A+Y_B+Y_C, Z_A+Z_B+Z_C
|
||||||
|
ctxt.cHRM = strip_floats(string.format("/WhitePoint[%f %f %f]/Matrix[%f %f %f %f %f %f %f %f %f]",
|
||||||
|
X_W, Y_W, Z_W,
|
||||||
|
X_A, Y_A, Z_A,
|
||||||
|
X_B, Y_B, Z_B,
|
||||||
|
X_C, Y_C, Z_C))
|
||||||
|
end
|
||||||
|
function parse.IDAT(buf, i, after, ctxt)
|
||||||
|
ctxt.IDAT = ctxt.IDAT or {}
|
||||||
|
table.insert(ctxt.IDAT, buf:sub(i, after-1))
|
||||||
|
end
|
||||||
|
function parse.IEND(buf, i, after)
|
||||||
|
if i ~= after then
|
||||||
|
error[[Unexpected data in end chunk]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(buf, i, len, limit)
|
||||||
|
i = i or 1
|
||||||
|
len = i+(len or #buf)
|
||||||
|
if buf:sub(i,i+7) ~= "\x89PNG\x0D\x0A\x1A\x0A" then
|
||||||
|
error[[You lied. This isn't a PNG file.]]
|
||||||
|
end
|
||||||
|
i = i+8
|
||||||
|
local chunks = {}
|
||||||
|
while i < len do
|
||||||
|
local length, tp, off = string.unpack(">I4c4", buf, i)
|
||||||
|
if tp == limit then break end
|
||||||
|
parse[tp](buf, off, off + length, chunks)
|
||||||
|
i = off + length + 4
|
||||||
|
end
|
||||||
|
return chunks, i
|
||||||
|
end
|
||||||
|
local function passes(buf, width, height, bitdepth, colortype)
|
||||||
|
local stride = (bitdepth == 16 and 2 or 1) * (1 + (colortype&3 == 2 and 2 or 0) + (colortype&4)/4)
|
||||||
|
local passes = {
|
||||||
|
{(width+7)//8, (height+7)//8},
|
||||||
|
{(width+3)//8, (height+7)//8},
|
||||||
|
{(width+3)//4, (height+3)//8},
|
||||||
|
{(width+1)//4, (height+3)//4},
|
||||||
|
{(width+1)//2, (height+1)//4},
|
||||||
|
{ width //2, (height+1)//2},
|
||||||
|
{ width , height //2},
|
||||||
|
}
|
||||||
|
local off = 1
|
||||||
|
local result
|
||||||
|
for i=1,#passes do
|
||||||
|
local xsize, ysize = passes[i][1], passes[i][2]
|
||||||
|
if xsize ~= 0 and ysize ~= 0 then
|
||||||
|
if bitdepth < 8 then
|
||||||
|
xsize = (xsize * bitdepth + 7) // 8
|
||||||
|
end
|
||||||
|
local after = off + (xsize+1) * stride * ysize
|
||||||
|
local pass = pngdecode.applyfilter(
|
||||||
|
buf:sub(off, after-1),
|
||||||
|
xsize,
|
||||||
|
ysize,
|
||||||
|
stride)
|
||||||
|
if bitdepth < 8 then
|
||||||
|
pass = pngdecode.expand(pass, passes[i][1], ysize, bitdepth, xsize)
|
||||||
|
end
|
||||||
|
result = pngdecode.interlace(width, height, stride, i, pass, result)
|
||||||
|
off = after
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(off == #buf+1)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local png_functions = {}
|
||||||
|
|
||||||
|
function png_functions.scan(img)
|
||||||
|
local file = io.open(img.filepath)
|
||||||
|
if not file then
|
||||||
|
error[[PDF image could not be opened.]]
|
||||||
|
end
|
||||||
|
local buf = file:read'a'
|
||||||
|
file:close()
|
||||||
|
local t = run(buf, 1, #buf, 'IDAT')
|
||||||
|
img.pages = 1
|
||||||
|
img.page = 1
|
||||||
|
img.rotation = 0
|
||||||
|
img.xsize, img.ysize = t.width, t.height
|
||||||
|
img.xres, img.yres = t.xres or 0, t.yres or 0
|
||||||
|
img.colordepth = t.bitdepth
|
||||||
|
end
|
||||||
|
|
||||||
|
local srgb_colorspace
|
||||||
|
local intents = {[0]=
|
||||||
|
'/Intent/Perceptual',
|
||||||
|
'/Intent/RelativeColorimetric',
|
||||||
|
'/Intent/Saturation',
|
||||||
|
'/Intent/AbsoluteColorimetric',
|
||||||
|
}
|
||||||
|
local function srgb_lookup(pfile, intent)
|
||||||
|
if not srgb_colorspace then
|
||||||
|
local f = io.open(kpse.find_file'sRGB.icc.zlib')
|
||||||
|
local profile = f:read'a'
|
||||||
|
f:close()
|
||||||
|
local objnum = pfile:stream(nil, '/Filter/FlateDecode/N ' .. tostring(colortype & 2 == 2 and '3' or '1'), t.iCCP, nil, true)
|
||||||
|
srgb_colorspace = string.format('[/ICCBased %i 0 R]', objnum)
|
||||||
|
end
|
||||||
|
return objnum, intents[intent] or ''
|
||||||
|
end
|
||||||
|
|
||||||
|
local pdf_escape = require'luametalatex-pdf-escape'.escape_bytes
|
||||||
|
|
||||||
|
local function rawimage(t, content)
|
||||||
|
content = xzip.decompress(content)
|
||||||
|
if t.interlace == 1 then
|
||||||
|
content = passes(content, t.width, t.height, t.bitdepth, t.colortype)
|
||||||
|
else
|
||||||
|
local xsize = t.width
|
||||||
|
if t.bitdepth < 8 then
|
||||||
|
xsize = (xsize * t.bitdepth + 7) // 8
|
||||||
|
end
|
||||||
|
local colortype = t.colortype
|
||||||
|
content = pngdecode.applyfilter(
|
||||||
|
content,
|
||||||
|
xsize,
|
||||||
|
t.height,
|
||||||
|
(t.bitdepth == 16 and 2 or 1) * (1 + (colortype&3 == 2 and 2 or 0) + (colortype&4)/4))
|
||||||
|
end
|
||||||
|
return content
|
||||||
|
end
|
||||||
|
|
||||||
|
function png_functions.write(pfile, img)
|
||||||
|
local file = io.open(img.filepath)
|
||||||
|
if not file then
|
||||||
|
error[[PDF image could not be opened.]]
|
||||||
|
end
|
||||||
|
local buf = file:read'a'
|
||||||
|
file:close()
|
||||||
|
local t = run(buf, 1, #buf, 'IEND')
|
||||||
|
local colorspace
|
||||||
|
local intent = ''
|
||||||
|
local colortype = t.colortype
|
||||||
|
if img.colorspace then
|
||||||
|
colorspace = string.format(' %i 0 R', img.colorspace)
|
||||||
|
elseif t.iCCP then
|
||||||
|
local icc_ref = pfile:stream(nil, '/Filter/FlateDecode/N ' .. tostring(colortype & 2 == 2 and '3' or '1'), t.iCCP, nil, true)
|
||||||
|
colorspace = string.format('[/ICCBased %i 0 R]', icc_ref)
|
||||||
|
elseif t.sRGB then
|
||||||
|
colorspace, intent = srgb_lookup(pfile, t.sRGB)
|
||||||
|
elseif colortype & 2 == 2 then -- RGB
|
||||||
|
if t.cHRM then
|
||||||
|
local gamma = t.gAMA or 2.2
|
||||||
|
gamma = gamma and strip_floats(string.format("/Gamma[%f %f %f]", gamma, gamma, gamma)) or ''
|
||||||
|
colorspace = string.format("[/CalRGB<<%s%s>>]", t.cHRM, gamma)
|
||||||
|
else
|
||||||
|
if t.gAMA then
|
||||||
|
texio.write_nl'Warning: (PNG) Gamma correction without chromaticity information is unsupported. Gamma value will be ignored.'
|
||||||
|
end
|
||||||
|
colorspace = '/DeviceRGB'
|
||||||
|
end
|
||||||
|
else -- Gray
|
||||||
|
if t.gAMA or t.cHRM then
|
||||||
|
texio.write_nl'Warning: (PNG) Gamma correction and chromaticity specifications are only supported for RGB images.'
|
||||||
|
end
|
||||||
|
colorspace = '/DeviceGray'
|
||||||
|
end
|
||||||
|
if colortype & 1 == 1 then -- Indexed
|
||||||
|
colorspace = string.format('[/Indexed%s %i%s]', colorspace, t.PLTE_len-1, pdf_escape(t.PLTE))
|
||||||
|
end
|
||||||
|
local colordepth = t.interlace == 1 and 8 or img.colordepth
|
||||||
|
local dict = string.format("/Subtype/Image/Width %i/Height %i/BitsPerComponent %i/ColorSpace%s", img.xsize, img.ysize, colordepth, colorspace)
|
||||||
|
|
||||||
|
local content = table.concat(t.IDAT)
|
||||||
|
local copy -- = true
|
||||||
|
if copy and (t.interlace == 1 or colortype & 4 == 4) then -- TODO: Add additional conditions
|
||||||
|
copy = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if copy then
|
||||||
|
-- In this case we never have to deal with an alpha component
|
||||||
|
dict = string.format(
|
||||||
|
'%s/Filter/FlateDecode/DecodeParms<</Colors %i/Columns %i/BitsPerComponent %i/Predictor 10>>',
|
||||||
|
dict, colortype == 2 and 3 or 1, img.xsize, colordepth)
|
||||||
|
else
|
||||||
|
content = rawimage(t, content)
|
||||||
|
if colortype & 4 == 4 then -- Alpha channel present
|
||||||
|
local mask
|
||||||
|
content, mask = pngdecode.splitmask(
|
||||||
|
content,
|
||||||
|
img.xsize,
|
||||||
|
img.ysize,
|
||||||
|
1 + (colortype&2),
|
||||||
|
colordepth//8) -- colordepth must be 8 or 16 if alpha is present
|
||||||
|
local mask_dict = string.format("/Subtype/Image/Width %i/Height %i/BitsPerComponent %i/ColorSpace/DeviceGray", img.xsize, img.ysize, colordepth)
|
||||||
|
local objnum = pfile:stream(nil, mask_dict, mask)
|
||||||
|
dict = string.format('%s/SMask %i 0 R', dict, objnum)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if t.tRNS then
|
||||||
|
if colortype == 3 then
|
||||||
|
local unpacked = copy and rawimage(t, content) or content
|
||||||
|
if colordepth ~= 8 then
|
||||||
|
unpacked = pngdecode.expand(unpacked, img.xsize, img.ysize, colordepth, (img.xsize*colordepth+7)//8)
|
||||||
|
end
|
||||||
|
unpacked = t.tRNS:match(unpacked)
|
||||||
|
local mask_dict = string.format("/Subtype/Image/Width %i/Height %i/BitsPerComponent 8/ColorSpace/DeviceGray", img.xsize, img.ysize)
|
||||||
|
local objnum = pfile:stream(nil, mask_dict, unpacked)
|
||||||
|
dict = string.format('%s/SMask %i 0 R', dict, objnum)
|
||||||
|
else
|
||||||
|
dict = string.format('%s/Mask[%s]', dict, t.tRNS)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local attr = img.attr
|
||||||
|
if attr then
|
||||||
|
dict = dict .. attr
|
||||||
|
end
|
||||||
|
pfile:stream(img.objnum, dict, content, nil, copy)
|
||||||
|
end
|
||||||
|
|
||||||
|
return png_functions
|
|
@ -0,0 +1,217 @@
|
||||||
|
local rawset = rawset
|
||||||
|
local setdata = node.direct.setdata
|
||||||
|
local nodenew = node.direct.new
|
||||||
|
local getwhd = node.direct.getwhd
|
||||||
|
local setwhd = node.direct.setwhd
|
||||||
|
local tonode = node.direct.tonode
|
||||||
|
local nodewrite = node.write
|
||||||
|
|
||||||
|
-- Mapping extensions to canonical type names if necessary
|
||||||
|
local imagetype_map = {
|
||||||
|
-- pdf1 = 'pdf',
|
||||||
|
}
|
||||||
|
local imagetypes = setmetatable({}, {__index = function(t, k)
|
||||||
|
local remapped = imagetype_map[k]
|
||||||
|
local module = remapped and t[remapped] or require('luametalatex-pdf-image-' .. k)
|
||||||
|
t[k] = module
|
||||||
|
return module
|
||||||
|
end})
|
||||||
|
|
||||||
|
local utils = require'luametalatex-pdf-utils'
|
||||||
|
local strip_floats = utils.strip_floats
|
||||||
|
local to_bp = utils.to_bp
|
||||||
|
|
||||||
|
local liberal_keys = {height = true, width = true, depth = true, transform = true}
|
||||||
|
local real_images = {}
|
||||||
|
local function relaxed_newindex(t, k, v)
|
||||||
|
if liberal_keys[k] then
|
||||||
|
return rawset(t, k, v)
|
||||||
|
else
|
||||||
|
real_images[t][k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function no_newindex(t, k, v)
|
||||||
|
if liberal_keys[k] then
|
||||||
|
return rawset(t, k, v)
|
||||||
|
else
|
||||||
|
error(string.format("You are not allowed to set %q in an already scanned image"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function get_underlying(t, k)
|
||||||
|
return assert(real_images[t])[k]
|
||||||
|
end
|
||||||
|
local meta = {__index = get_underlying, __newindex = relaxed_newindex}
|
||||||
|
local restricted_meta = {__index = get_underlying, __newindex = no_newindex}
|
||||||
|
-- transform isn't documented to be changable but it kind of fits
|
||||||
|
local function new(spec)
|
||||||
|
local img, real = {}, {}
|
||||||
|
real_images[img] = real
|
||||||
|
if spec then for k,v in next, spec do
|
||||||
|
(liberal_keys[k] and img or real)[k] = v
|
||||||
|
end end
|
||||||
|
img.depth = img.depth or 0
|
||||||
|
return setmetatable(img, meta)
|
||||||
|
end
|
||||||
|
local function scan(img)
|
||||||
|
local m = getmetatable(img)
|
||||||
|
local real
|
||||||
|
if m == restricted_meta then
|
||||||
|
real = real_images[img]
|
||||||
|
else
|
||||||
|
if m ~= meta then img = new(img) end
|
||||||
|
real = real_images[img]
|
||||||
|
if real.stream then error[[stream images are not yet supported]] end
|
||||||
|
assert(real.filename)
|
||||||
|
-- TODO: At some point we should just take the lowercased extension
|
||||||
|
local imagetype = real.filename:match'%.pdf$' and 'pdf'
|
||||||
|
or real.filename:match'%.png$' and 'png'
|
||||||
|
or error'Unsupported image format'
|
||||||
|
real.filepath = assert(kpse.find_file(real.filename), "Image not found")
|
||||||
|
real.imagetype = imagetype
|
||||||
|
imagetypes[imagetype].scan(real)
|
||||||
|
setmetatable(img, restricted_meta)
|
||||||
|
end
|
||||||
|
img.transform = img.transform or 0
|
||||||
|
-- (Re)Set dimensions
|
||||||
|
if img.depth and img.height and img.width then
|
||||||
|
return img
|
||||||
|
end
|
||||||
|
local flipped = (img.transform + real.rotation) % 2 == 1
|
||||||
|
if not (img.depth or img.height) then img.depth = 0 end
|
||||||
|
if not img.width and not (img.height and img.depth) then
|
||||||
|
local xsize, ysize = real.xsize, real.ysize
|
||||||
|
if not real.bbox then
|
||||||
|
local xres, yres = img.xres, img.yres
|
||||||
|
-- TODO: \pdfvariable Parameters
|
||||||
|
if xres == 0 then
|
||||||
|
xres = 72
|
||||||
|
yres = xres * ((not yres or yres == 0) and 1 or yres)
|
||||||
|
elseif yres == 0 then
|
||||||
|
yres = 72
|
||||||
|
xres = yres * ((not xres or xres == 0) and 1 or xres)
|
||||||
|
end
|
||||||
|
local xscale, yscale = 4736286.72/xres, 4736286.72/yres
|
||||||
|
xsize, ysize = xsize*xscale//1, ysize*yscale//1
|
||||||
|
end
|
||||||
|
local total_y
|
||||||
|
if flipped then
|
||||||
|
img.width = ysize
|
||||||
|
total_y = xsize
|
||||||
|
else
|
||||||
|
img.width = xsize
|
||||||
|
total_y = ysize
|
||||||
|
end
|
||||||
|
if img.height then
|
||||||
|
img.depth = total_y - img.height
|
||||||
|
else
|
||||||
|
img.height = total_y - img.depth
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ratio = flipped and xsize / ysize or ysize / xsize
|
||||||
|
if img.width then
|
||||||
|
if img.depth then
|
||||||
|
img.height = (ratio * img.width - img.depth) // 1
|
||||||
|
else
|
||||||
|
img.depth = (ratio * img.width - img.height) // 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
img.width = ((img.height + img.depth) / ratio) // 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return img
|
||||||
|
end
|
||||||
|
|
||||||
|
local img_by_objnum = {}
|
||||||
|
-- local function img_from_objnum(objnum, img)
|
||||||
|
-- img = img or {}
|
||||||
|
-- real_images[img] = assert(img_by_objnum[objnum])
|
||||||
|
-- return setmetatable(img, restricted_meta)
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- Noop if already reserved
|
||||||
|
function reserve(pfile, real)
|
||||||
|
local obj = real.objnum or pfile:getobj()
|
||||||
|
real.objnum = obj
|
||||||
|
img_by_objnum[obj] = real
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write_img(pfile, img)
|
||||||
|
local objnum = reserve(pfile, img)
|
||||||
|
if not img.written then
|
||||||
|
img.written = true
|
||||||
|
imagetypes[img.imagetype].write(pfile, img)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function do_img(data, p, n, x, y)
|
||||||
|
local img = assert(img_by_objnum[data >> 3], 'Invalid image ID')
|
||||||
|
write_img(p.file, img)
|
||||||
|
local mirror = data & 4 == 4
|
||||||
|
local rotate = (data + img.rotation) & 3
|
||||||
|
local width, height, depth = getwhd(n)
|
||||||
|
height = height + depth
|
||||||
|
local bbox = img.bbox
|
||||||
|
local xsize, ysize = img.xsize, img.ysize
|
||||||
|
local a, b, c, d, e, f = 1, 0, 0, 1
|
||||||
|
if bbox then
|
||||||
|
e, f = -bbox[1], -bbox[2]
|
||||||
|
else
|
||||||
|
e, f = 0, 0
|
||||||
|
xsize, ysize = 65781.76, 65781.76
|
||||||
|
end
|
||||||
|
if mirror then
|
||||||
|
a, e = -a, -e+xsize
|
||||||
|
end
|
||||||
|
for i=1,rotate do
|
||||||
|
a, b, c, d, e, f = -b, a, -d, c, -f+ysize, e
|
||||||
|
xsize, ysize = ysize, xsize
|
||||||
|
end
|
||||||
|
local xscale, yscale = width / xsize, height / ysize
|
||||||
|
a, c, e = a*xscale, c*xscale, e*xscale
|
||||||
|
b, d, f = b*yscale, d*yscale, f*yscale
|
||||||
|
e, f = to_bp(x + e), to_bp(y - depth + f)
|
||||||
|
p.resources.XObject['Im' .. tostring(img.objnum)] = img.objnum
|
||||||
|
pdf.write('page', strip_floats(string.format('q %f %f %f %f %f %f cm /Im%i Do Q', a, b, c, d, e, f, img.objnum)), nil, nil, p)
|
||||||
|
end
|
||||||
|
local ruleid = node.id'rule'
|
||||||
|
local ruletypes = node.subtypes'rule'
|
||||||
|
local imagerule
|
||||||
|
for n, name in next, ruletypes do
|
||||||
|
if name == 'image' then imagerule = n break end
|
||||||
|
end
|
||||||
|
assert(imagerule)
|
||||||
|
local function node(pfile, img)
|
||||||
|
img = scan(img)
|
||||||
|
local n = nodenew(ruleid, imagerule) -- image
|
||||||
|
setdata(n, (reserve(pfile, real_images[img]) << 3) | ((img.transform or 0) & 7))
|
||||||
|
setwhd(n, img.width or -0x40000000, img.height or -0x40000000, img.depth or -0x40000000)
|
||||||
|
return tonode(n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write(pfile, img)
|
||||||
|
img = scan(img)
|
||||||
|
nodewrite(node(pfile, img))
|
||||||
|
return img
|
||||||
|
end
|
||||||
|
|
||||||
|
local function immediatewrite(pfile, img)
|
||||||
|
img = scan(img)
|
||||||
|
write_img(pfile, real_images[img])
|
||||||
|
return img
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
new = new,
|
||||||
|
scan = scan,
|
||||||
|
write = write,
|
||||||
|
node = node,
|
||||||
|
from_num = function(i)
|
||||||
|
local img = {}
|
||||||
|
real_images[img] = assert(img_by_objnum[i])
|
||||||
|
return setmetatable(img, restricted_meta)
|
||||||
|
end,
|
||||||
|
get_num = function(pfile, img)
|
||||||
|
return reserve(pfile, real_images[img])
|
||||||
|
end,
|
||||||
|
ship = do_img,
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
local min = math.min
|
local min = math.min
|
||||||
local format = string.format
|
local format = string.format
|
||||||
local concat = table.concat
|
local concat = table.concat
|
||||||
|
local pdfvariable = pdf.variable
|
||||||
local function write(pdf, tree, total, max)
|
local function write(pdf, tree, total, max)
|
||||||
tree = tree or pdf.pages
|
tree = tree or pdf.pages
|
||||||
if #tree == 0 then
|
if #tree == 0 then
|
||||||
|
@ -22,25 +23,47 @@ local function write(pdf, tree, total, max)
|
||||||
local parentid = pdf:getobj()
|
local parentid = pdf:getobj()
|
||||||
newtree[-(i//6)] = parentid
|
newtree[-(i//6)] = parentid
|
||||||
parent = format("/Parent %i 0 R", parentid)
|
parent = format("/Parent %i 0 R", parentid)
|
||||||
|
elseif #tree <= 6 then
|
||||||
|
parent = pdfvariable.pagesattr
|
||||||
end
|
end
|
||||||
pdf:indirect(id, format('<</Type/Pages%s/Kids[%s 0 R]/Count %i>>', parent, concat(tree, ' 0 R ', 6*i+1, min(#tree, 6*i+6)), min(remaining, max)))
|
pdf:indirect(id, format('<</Type/Pages%s/Kids[%s 0 R]/Count %i>>', parent, concat(tree, ' 0 R ', 6*i+1, min(#tree, 6*i+6)), min(remaining, max)))
|
||||||
remaining = remaining - max
|
remaining = remaining - max
|
||||||
end
|
end
|
||||||
if #parent > 0 then
|
if newtree[0] then
|
||||||
return write(pdf, newtree, total, max*6)
|
return write(pdf, newtree, total, max*6)
|
||||||
end
|
end
|
||||||
return newtree[1]
|
return newtree[1]
|
||||||
end
|
end
|
||||||
local function newpage(pdf)
|
local function newpage(pdf)
|
||||||
local pageid = pdf:getobj()
|
local pages = pdf.pages
|
||||||
local pagenumber = #pdf.pages
|
local pagenumber = #pages+1
|
||||||
pdf.pages[pagenumber+1] = pageid
|
local pageid = pages.reserved and pages.reserved[pagenumber]
|
||||||
if 0 == pagenumber % 6 then
|
if pageid then
|
||||||
pdf.pages[-(pagenumber//6)] = pdf:getobj()
|
pages.reserved[pagenumber] = nil
|
||||||
|
else
|
||||||
|
pageid = pdf:getobj()
|
||||||
end
|
end
|
||||||
return pageid, pdf.pages[-(pagenumber//6)]
|
pages[pagenumber] = pageid
|
||||||
|
if 1 == pagenumber % 6 then
|
||||||
|
pages[-((pagenumber-1)//6)] = pdf:getobj()
|
||||||
|
end
|
||||||
|
return pageid, pages[-((pagenumber-1)//6)]
|
||||||
|
end
|
||||||
|
local function reservepage(pdf, num)
|
||||||
|
local pages = pdf.pages
|
||||||
|
if pages[num] then return pages[num] end
|
||||||
|
local reserved = pages.reserved
|
||||||
|
if reserved then
|
||||||
|
if reserved[num] then return reserved[num] end
|
||||||
|
else
|
||||||
|
reserved = {}
|
||||||
|
pages.reserved = reserved
|
||||||
|
end
|
||||||
|
reserved[num] = pdf:getobj()
|
||||||
|
return reserved[num]
|
||||||
end
|
end
|
||||||
return {
|
return {
|
||||||
write = write,
|
write = write,
|
||||||
newpage = newpage,
|
newpage = newpage,
|
||||||
|
reservepage = reservepage,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
local writer -- = require'luametalatex-nodewriter' -- This would introduce some cyclic dependency
|
||||||
|
local pdfvariable = pdf.variable
|
||||||
|
|
||||||
|
-- XForms currently have the form {width, height, depth, objnum, attributes, list, margin}
|
||||||
|
local xforms = {}
|
||||||
|
|
||||||
|
local utils = require'luametalatex-pdf-utils'
|
||||||
|
local strip_floats = utils.strip_floats
|
||||||
|
local to_bp = utils.to_bp
|
||||||
|
|
||||||
|
local function shipout(pfile, xform, fontdirs, usedglyphs)
|
||||||
|
local list, margin = xform.list, xform.margin
|
||||||
|
if not list then return xform.objnum end -- Already shipped out
|
||||||
|
local last_page = cur_page cur_page = nil
|
||||||
|
local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs)
|
||||||
|
cur_page = last_page
|
||||||
|
assert(annots == '')
|
||||||
|
if pdfvariable.xformattr ~= '' or pdfvariable.xformresources ~= '' then
|
||||||
|
texio.write_nl('term and log', 'WARNING (savedboxresource shipout): Ignoring unsupported PDF variables xformattr and xformresources. Specify resources and attributes for specific XForms instead.')
|
||||||
|
end
|
||||||
|
local bbox = strip_floats(string.format('/BBox[%f %f %f %f]', -to_bp(margin), -to_bp(list.depth+margin), to_bp(list.width+margin), to_bp(list.height+margin)))
|
||||||
|
local dict = string.format('/Subtype/Form%s/Resources%s%s', bbox, resources(xform.resources), xform.attributes or '')
|
||||||
|
node.flush_list(list)
|
||||||
|
xform.list = nil
|
||||||
|
local objnum = pfile:stream(xform.objnum, dict, out)
|
||||||
|
xform.objnum = objnum
|
||||||
|
return objnum
|
||||||
|
end
|
||||||
|
|
||||||
|
local function save(pfile, n, attr, resources, immediate, type, margin, fontdirs, usedglyphs)
|
||||||
|
local index = #xforms+1
|
||||||
|
local xform = {
|
||||||
|
list = assert(n, 'List required for saveboxresource'),
|
||||||
|
width = n.width,
|
||||||
|
height = n.height,
|
||||||
|
depth = n.depth,
|
||||||
|
attributes = attr,
|
||||||
|
resources = resources,
|
||||||
|
margin = margin,
|
||||||
|
-- type = type, -- TODO: Not yet used. Do we need this at all?
|
||||||
|
}
|
||||||
|
xforms[index] = xform
|
||||||
|
if immediate then
|
||||||
|
shipout(pfile, xform, fontdirs, usedglyphs)
|
||||||
|
end
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
local function adjust_sizes(width, height, depth, real_width, real_height, real_depth)
|
||||||
|
if not depth then
|
||||||
|
if height then
|
||||||
|
local scale = height/real_height
|
||||||
|
depth = (real_depth*scale + .5)//1
|
||||||
|
width = width or (real_width*scale + .5)//1
|
||||||
|
elseif width then
|
||||||
|
local scale = width/real_width
|
||||||
|
depth = (real_depth*scale + .5)//1
|
||||||
|
height = (real_height*scale + .5)//1
|
||||||
|
else
|
||||||
|
width, height, depth = real_width, real_height, real_depth
|
||||||
|
end
|
||||||
|
elseif height then
|
||||||
|
width = width or (real_width*(height+depth)/(real_height+real_depth) + .5)//1
|
||||||
|
else
|
||||||
|
width = width or real_width
|
||||||
|
local scale = width/real_width
|
||||||
|
height = ((real_depth+real_height)*scale + .5)//1 - depth
|
||||||
|
end
|
||||||
|
return width, height, depth
|
||||||
|
end
|
||||||
|
|
||||||
|
local ruleid = node.id'rule'
|
||||||
|
local ruletypes = node.subtypes'rule'
|
||||||
|
local boxrule
|
||||||
|
for n, name in next, ruletypes do
|
||||||
|
if name == 'box' then boxrule = n break end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function use(index, width, height, depth)
|
||||||
|
local xform = xforms[index]
|
||||||
|
if not xform then return nil, nil, nil, nil end
|
||||||
|
width, height, depth = adjust_sizes(width, height, depth, xform.width, xform.height, xform.depth)
|
||||||
|
local n = node.direct.new(ruleid, boxrule)
|
||||||
|
node.direct.setdata(n, index)
|
||||||
|
node.direct.setwhd(n, width, height, depth)
|
||||||
|
return node.direct.tonode(n), width, height, depth
|
||||||
|
end
|
||||||
|
|
||||||
|
local function do_box(data, p, n, x, y)
|
||||||
|
local xform = assert(xforms[data], 'Invalid XForm')
|
||||||
|
local objnum = shipout(p.file, xform, p.fontdirs, p.usedglyphs)
|
||||||
|
local width, height, depth = node.direct.getwhd(n)
|
||||||
|
local xscale, yscale = width / xform.width, (height+depth) / (xform.height+xform.depth)
|
||||||
|
p.resources.XObject['Fm' .. tostring(data)] = objnum
|
||||||
|
pdf.write('page', strip_floats(string.format('q %f 0 0 %f %f %f cm /Fm%i Do Q',
|
||||||
|
xscale, yscale,
|
||||||
|
to_bp(x), to_bp(y-depth+yscale*xform.depth),
|
||||||
|
data)), nil, nil, p)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
save = save,
|
||||||
|
use = use,
|
||||||
|
ship = do_box,
|
||||||
|
init_nodewriter = function(t, nodewriter) writer, t.init_nodewriter = nodewriter, nil end,
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
local l = lpeg or require'lpeg'
|
||||||
|
local trailing_zeros = l.P'0'^0 * -l.R'09'
|
||||||
|
local strip_floats_patt = l.Cs((1-l.R'09' +
|
||||||
|
(l.R'09')^1 * (l.P'.' * trailing_zeros / '' + l.P'.' * (l.R'09'-trailing_zeros)^1 * (trailing_zeros/''))^-1)^0)
|
||||||
|
local match = l.match
|
||||||
|
|
||||||
|
local function strip_floats(s)
|
||||||
|
return match(strip_floats_patt, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function to_bp(sp)
|
||||||
|
return sp/65781.76
|
||||||
|
end
|
||||||
|
|
||||||
|
local function to_sp(bp)
|
||||||
|
return (bp*65781.76+.5)//1
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
strip_floats = strip_floats,
|
||||||
|
to_bp = to_bp,
|
||||||
|
to_sp = to_sp,
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
local format = string.format
|
local format = string.format
|
||||||
local gsub = string.gsub
|
|
||||||
local byte = string.byte
|
local byte = string.byte
|
||||||
local pack = string.pack
|
local pack = string.pack
|
||||||
local error = error
|
local error = error
|
||||||
|
@ -7,6 +6,8 @@ local pairs = pairs
|
||||||
local setmetatable = setmetatable
|
local setmetatable = setmetatable
|
||||||
local assigned = {}
|
local assigned = {}
|
||||||
local delayed = {}
|
local delayed = {}
|
||||||
|
local compress = xzip.compress
|
||||||
|
local pdfvariable = pdf.variable
|
||||||
-- slightly tricky interface: No/nil return means that the objects content
|
-- slightly tricky interface: No/nil return means that the objects content
|
||||||
-- isn't known yet, while false indicates a delayed object.
|
-- isn't known yet, while false indicates a delayed object.
|
||||||
local function written(pdf, num)
|
local function written(pdf, num)
|
||||||
|
@ -26,34 +27,57 @@ local function stream(pdf, num, dict, content, isfile, raw)
|
||||||
content = f:read'a'
|
content = f:read'a'
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
pdf.file:write(format('%i 0 obj\n<<%s/Length %i>>stream\n', num, dict, #content))
|
local level = not raw and pdfvariable.compresslevel or 0
|
||||||
|
local filter = ''
|
||||||
|
if level > 0 then
|
||||||
|
local compressed = compress(content, level)
|
||||||
|
if #compressed < #content + 19 then -- Filter has some overhead
|
||||||
|
filter = "/Filter/FlateDecode"
|
||||||
|
content = compressed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pdf.file:write(format('%i 0 obj\n<<%s%s/Length %i>>stream\n', num, dict, filter, #content))
|
||||||
pdf.file:write(content)
|
pdf.file:write(content)
|
||||||
pdf.file:write'\nendstream\nendobj\n'
|
pdf.file:write'\nendstream\nendobj\n'
|
||||||
return num
|
return num
|
||||||
end
|
end
|
||||||
local function delayedstream(pdf, num, dict, content, isfile)
|
local function delayedstream(pdf, num, dict, content, isfile, raw)
|
||||||
if not num then num = pdf:getobj() end
|
if not num then num = pdf:getobj() end
|
||||||
if pdf[num] ~= assigned then
|
if pdf[num] ~= assigned then
|
||||||
error[[Invalid object]]
|
error[[Invalid object]]
|
||||||
end
|
end
|
||||||
pdf[num] = delayed
|
pdf[num] = delayed
|
||||||
pdf[-num] = {stream, dict, content, isfile}
|
pdf[-num] = {stream, dict, content, isfile, raw}
|
||||||
return num
|
return num
|
||||||
end
|
end
|
||||||
local function indirect(pdf, num, content, isfile)
|
local function indirect(pdf, num, content, isfile, objstream)
|
||||||
if not num then num = pdf:getobj() end
|
if not num then num = pdf:getobj() end
|
||||||
if pdf[num] ~= assigned then
|
if pdf[num] ~= assigned then
|
||||||
error[[Invalid object]]
|
error[[Invalid object]]
|
||||||
end
|
end
|
||||||
pdf[num] = {offset = pdf.file:seek()}
|
|
||||||
pdf.file:write(format('%i 0 obj\n', num))
|
|
||||||
if isfile then
|
if isfile then
|
||||||
local f = io.open(content)
|
local f = io.open(content)
|
||||||
content = f:read'a'
|
content = f:read'a'
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
pdf.file:write(content)
|
if objstream ~= false and pdfvariable.objcompresslevel ~= 0 then
|
||||||
pdf.file:write'\nendobj\n'
|
objstream = objstream or true
|
||||||
|
local objstr = pdf.objstream[objstream]
|
||||||
|
if not objstr then
|
||||||
|
objstr = {objnum = pdf:getobj(), off = 0, {}}
|
||||||
|
pdf.objstream[objstream] = objstr
|
||||||
|
end
|
||||||
|
local i = #objstr
|
||||||
|
pdf[num] = {objstr = objstr.objnum, i = i-1}
|
||||||
|
objstr[1][i] = string.format("%i %i ", num, objstr.off)
|
||||||
|
objstr[i+1] = content
|
||||||
|
objstr.off = objstr.off + #content
|
||||||
|
else
|
||||||
|
pdf[num] = {offset = pdf.file:seek()}
|
||||||
|
pdf.file:write(format('%i 0 obj\n', num))
|
||||||
|
pdf.file:write(content)
|
||||||
|
pdf.file:write'\nendobj\n'
|
||||||
|
end
|
||||||
return num
|
return num
|
||||||
end
|
end
|
||||||
local function delay(pdf, num, content, isfile)
|
local function delay(pdf, num, content, isfile)
|
||||||
|
@ -84,25 +108,34 @@ local function getid(pdf)
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
local function trailer(pdf)
|
local function trailer(pdf)
|
||||||
|
local linked = 0
|
||||||
|
for k,objstr in next, pdf.objstream do
|
||||||
|
objstr[1] = table.concat(objstr[1])
|
||||||
|
pdf:stream(objstr.objnum, string.format("/Type/ObjStm/N %i/First %i", #objstr-1, #objstr[1]), table.concat(objstr))
|
||||||
|
end
|
||||||
local nextid = getid(pdf)
|
local nextid = getid(pdf)
|
||||||
local myoff = pdf.file:seek()
|
local myoff = pdf.file:seek()
|
||||||
pdf[nextid] = {offset = myoff}
|
pdf[nextid] = {offset = myoff}
|
||||||
local linked = 0
|
|
||||||
local offsets = {}
|
local offsets = {}
|
||||||
for i=1,nextid do
|
for i=1,nextid do
|
||||||
local off = pdf[i].offset
|
local off = pdf[i].offset
|
||||||
if off then
|
if off then
|
||||||
offsets[i+1] = pack(">I1I3I1", 1, off, 0)
|
offsets[i+1] = pack(">I1I3I2", 1, off, 0)
|
||||||
else
|
else
|
||||||
offsets[linked+1] = pack(">I1I3I1", 0, i, 255)
|
local objstr = pdf[i].objstr
|
||||||
linked = i
|
if objstr then
|
||||||
|
offsets[i+1] = pack(">I1I3I2", 2, objstr, pdf[i].i)
|
||||||
|
else
|
||||||
|
offsets[linked+1] = pack(">I1I3I2", 0, i, 255)
|
||||||
|
linked = i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
offsets[linked+1] = '\0\0\0\0\255'
|
offsets[linked+1] = '\0\0\0\0\255\255'
|
||||||
pdf[nextid] = assigned
|
pdf[nextid] = assigned
|
||||||
-- TODO: Add an /ID according to 14.4
|
-- TODO: Add an /ID according to 14.4
|
||||||
local info = pdf.info and string.format("/Info %i 0 R", pdf.info) or ""
|
local info = pdf.info and string.format("/Info %i 0 R", pdf.info) or ""
|
||||||
stream(pdf, nextid, format([[/Type/XRef/Size %i/W[1 3 1]/Root %i 0 R%s]], nextid+1, pdf.root, info), table.concat(offsets))
|
stream(pdf, nextid, format([[/Type/XRef/Size %i/W[1 3 2]/Root %i 0 R%s]], nextid+1, pdf.root, info), table.concat(offsets))
|
||||||
pdf.file:write('startxref\n', myoff, '\n%%EOF')
|
pdf.file:write('startxref\n', myoff, '\n%%EOF')
|
||||||
end
|
end
|
||||||
local function close(pdf)
|
local function close(pdf)
|
||||||
|
@ -123,6 +156,7 @@ local pdfmeta = {
|
||||||
indirect = indirect,
|
indirect = indirect,
|
||||||
stream = stream,
|
stream = stream,
|
||||||
newpage = pagetree.newpage,
|
newpage = pagetree.newpage,
|
||||||
|
reservepage = pagetree.reservepage,
|
||||||
writepages = pagetree.write,
|
writepages = pagetree.write,
|
||||||
delayed = delay,
|
delayed = delay,
|
||||||
delayedstream = delayedstream,
|
delayedstream = delayedstream,
|
||||||
|
@ -133,7 +167,7 @@ pdfmeta.__index = pdfmeta
|
||||||
local function open(filename)
|
local function open(filename)
|
||||||
local file = io.open(filename, 'w')
|
local file = io.open(filename, 'w')
|
||||||
file:write"%PDF-X.X\n%🖋\n"
|
file:write"%PDF-X.X\n%🖋\n"
|
||||||
return setmetatable({file = file, version = '1.7', [0] = 0, pages = {}}, pdfmeta)
|
return setmetatable({file = file, version = '1.7', [0] = 0, pages = {}, objstream = {}}, pdfmeta)
|
||||||
end
|
end
|
||||||
return {
|
return {
|
||||||
open = open,
|
open = open,
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
local format = string.format
|
||||||
|
local strip_floats = require'luametalatex-pdf-utils'.strip_floats
|
||||||
|
local pdfe = pdfe
|
||||||
|
local l = lpeg
|
||||||
|
local regularchar = 1-l.S'\0\t\n\r\f ()<>[]{}/%#'
|
||||||
|
local escapednamechar = l.P(1)/function(s)
|
||||||
|
return format("#%02X")
|
||||||
|
end
|
||||||
|
local nameescape = l.Cs(l.Cc'/' * (regularchar + escapednamechar)^0)
|
||||||
|
local deepcopy_lookup deepcopy_lookup = {
|
||||||
|
function(_, pdf) -- 1: null
|
||||||
|
return 'null'
|
||||||
|
end,
|
||||||
|
function(_, pdf, b) -- 2: boolean
|
||||||
|
return b == 1 and 'true' or 'false'
|
||||||
|
end,
|
||||||
|
function(_, pdf, i) -- 3: integer
|
||||||
|
return format("%d", i)
|
||||||
|
end,
|
||||||
|
function(_, pdf, f) -- 4: number
|
||||||
|
return strip_floats(format("%f", f), "%.?0+[ %]]", "")
|
||||||
|
end,
|
||||||
|
function(_, pdf, name) -- 5: name
|
||||||
|
return nameescape:match(name)
|
||||||
|
end,
|
||||||
|
function(_, pdf, string, hex) -- 6: string
|
||||||
|
return hex and format("<%s>", string) or format("(%s)", string)
|
||||||
|
end,
|
||||||
|
function(references, pdf, array, size) -- 7: array
|
||||||
|
local a = {}
|
||||||
|
for i=1,size do
|
||||||
|
local type, value, detail = pdfe.getfromarray(array, i)
|
||||||
|
a[i] = deepcopy_lookup[type](references, pdf, value, detail)
|
||||||
|
end
|
||||||
|
return '[' .. table.concat(a, ' ') .. ']'
|
||||||
|
end,
|
||||||
|
function(references, pdf, dict, size) -- 8: dict
|
||||||
|
local a = {}
|
||||||
|
for i=1,size do
|
||||||
|
local key, type, value, detail = pdfe.getfromdictionary(dict, i)
|
||||||
|
a[2*i-1] = nameescape:match(key)
|
||||||
|
a[2*i] = deepcopy_lookup[type](references, pdf, value, detail)
|
||||||
|
end
|
||||||
|
return '<<' .. table.concat(a, ' ') .. '>>'
|
||||||
|
end,
|
||||||
|
nil, -- 9: stream (can only appear as a reference
|
||||||
|
function(references, pdf, ref, num)
|
||||||
|
local new = references[-num]
|
||||||
|
if not new then
|
||||||
|
new = pdf:getobj()
|
||||||
|
references[-num] = new
|
||||||
|
references[#references+1] = {ref, num}
|
||||||
|
end
|
||||||
|
return format("%i 0 R", new)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local references = setmetatable({}, {__index = function(t, n)
|
||||||
|
local v = {}
|
||||||
|
t[n] = v
|
||||||
|
return v
|
||||||
|
end})
|
||||||
|
|
||||||
|
return function(file, id, pdf, type, value, detail)
|
||||||
|
local references = references[id]
|
||||||
|
local res = deepcopy_lookup[type](references, pdf, value, detail)
|
||||||
|
local i, r = 1, references[1]
|
||||||
|
while r do
|
||||||
|
local type, value, detail, more = pdfe.getfromreference(r[1])
|
||||||
|
if type == 9 then
|
||||||
|
local a,j = {}, 0
|
||||||
|
for i=1,more do
|
||||||
|
local key, type, value, detail = pdfe.getfromdictionary(detail, i)
|
||||||
|
if key == 'Length' then
|
||||||
|
j=2
|
||||||
|
else
|
||||||
|
a[2*i-1-j] = nameescape:match(key)
|
||||||
|
a[2*i-j] = deepcopy_lookup[type](references, pdf, value, detail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pdf:stream(references[-r[2]], table.concat(a, ' '), value(false), false, true)
|
||||||
|
else
|
||||||
|
pdf:indirect(references[-r[2]], deepcopy_lookup[type](references, pdf, value, detail))
|
||||||
|
end
|
||||||
|
i = i+1
|
||||||
|
r = references[i]
|
||||||
|
end
|
||||||
|
for i=1,#references do references[i] = nil end
|
||||||
|
return res
|
||||||
|
end
|
|
@ -0,0 +1,96 @@
|
||||||
|
local whatsit_id = node.id'whatsit'
|
||||||
|
local whatsits = {
|
||||||
|
[0] = "open",
|
||||||
|
"write",
|
||||||
|
"close",
|
||||||
|
"special",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
"save_pos",
|
||||||
|
"late_lua",
|
||||||
|
"user_defined",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
"pdf_literal",
|
||||||
|
"pdf_refobj",
|
||||||
|
"pdf_annot",
|
||||||
|
"pdf_start_link",
|
||||||
|
"pdf_end_link",
|
||||||
|
"pdf_dest",
|
||||||
|
"pdf_action",
|
||||||
|
"pdf_thread",
|
||||||
|
"pdf_start_thread",
|
||||||
|
"pdf_end_thread",
|
||||||
|
"pdf_thread_data",
|
||||||
|
"pdf_link_data",
|
||||||
|
"pdf_colorstack",
|
||||||
|
"pdf_setmatrix",
|
||||||
|
"pdf_save",
|
||||||
|
"pdf_restore",
|
||||||
|
}
|
||||||
|
local whatsithandler = {}
|
||||||
|
-- for i = 0,#whatsits do -- #whatsits isn't guaranteed to work because of the nil entries
|
||||||
|
for i = 0,31 do
|
||||||
|
local v = whatsits[i]
|
||||||
|
if v then
|
||||||
|
whatsits[v] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function node.whatsits() return whatsits end
|
||||||
|
function node.subtype(s) return type(s) == "string" and whatsits[s] or nil end
|
||||||
|
|
||||||
|
local direct = node.direct
|
||||||
|
local getsubtype = direct.getsubtype
|
||||||
|
local properties = direct.get_properties_table()
|
||||||
|
local tonode, todirect = direct.tonode, direct.todirect
|
||||||
|
|
||||||
|
local function get_handler(n, subtype)
|
||||||
|
local props = properties[n]
|
||||||
|
return props and props.handle or whatsithandler[subtype or getsubtype(n)], props
|
||||||
|
end
|
||||||
|
local function new(name, handler)
|
||||||
|
assert(type(name) == 'string')
|
||||||
|
local subtype = whatsits[name]
|
||||||
|
if subtype then
|
||||||
|
if whatsithandler[subtype] then
|
||||||
|
texio.write_nl'WARNING: Overwriting default whatsit handler'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
subtype = #whatsits + 1
|
||||||
|
whatsits[subtype] = name
|
||||||
|
whatsits[name] = subtype
|
||||||
|
end
|
||||||
|
whatsithandler[subtype] = handler
|
||||||
|
return subtype
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Some fields might expect different values
|
||||||
|
local function setwhatsitfield(n, name, value)
|
||||||
|
local props = properties[n]
|
||||||
|
if not props then
|
||||||
|
props = {}
|
||||||
|
properties[n] = props
|
||||||
|
end
|
||||||
|
props[name] = value
|
||||||
|
end
|
||||||
|
direct.setwhatsitfield = setwhatsitfield
|
||||||
|
|
||||||
|
local function getwhatsitfield(n, name)
|
||||||
|
local props = properties[n]
|
||||||
|
return props and props[name]
|
||||||
|
end
|
||||||
|
direct.getwhatsitfield = getwhatsitfield
|
||||||
|
|
||||||
|
-- TODO: Some fields might be nodes and therefore have to be converted
|
||||||
|
function node.setwhatsitfield(n, ...) return setwhatsitfield(todirect(n), ...) end
|
||||||
|
function node.getwhatsitfield(n, ...) return getwhatsitfield(todirect(n), ...) end
|
||||||
|
|
||||||
|
return {
|
||||||
|
handler = get_handler,
|
||||||
|
new = new,
|
||||||
|
}
|
|
@ -113,9 +113,10 @@ error[[CRITICAL: Initialization script not found]]
|
||||||
-- error(msg)
|
-- error(msg)
|
||||||
os.setenv("engine", status.luatex_engine)
|
os.setenv("engine", status.luatex_engine)
|
||||||
local ret_value
|
local ret_value
|
||||||
|
local args = os.selfarg[1] and " \"" .. table.concat(os.selfarg, "\" \"") .. "\"" or ""
|
||||||
if is_initex then
|
if is_initex then
|
||||||
ret_value = os.execute(string.format("luametatex \"--lua=%s\" --arg0=\"%s\" \"%s\"", dir, os.selfarg[0], table.concat(os.selfarg, "\" \"")))
|
ret_value = os.execute(string.format("luametatex \"--lua=%s\" --arg0=\"%s\"%s", dir, os.selfarg[0], args))
|
||||||
else
|
else
|
||||||
ret_value = os.execute(string.format("luametatex \"--fmt=%s\" \"--lua=%s\" --arg0=\"%s\" \"%s\"", format, dir, os.selfarg[0], table.concat(os.selfarg, "\" \"")))
|
ret_value = os.execute(string.format("luametatex \"--fmt=%s\" \"--lua=%s\" --arg0=\"%s\"%s", format, dir, os.selfarg[0], args))
|
||||||
end
|
end
|
||||||
os.exit(x)
|
os.exit(x)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
luametalatex-init.lua
|
|
@ -0,0 +1,21 @@
|
||||||
|
% tex-ini-files 2016-04-15: luatex.ini
|
||||||
|
|
||||||
|
% Karl Berry, originally written 2008. Public domain.
|
||||||
|
% PDF output by default.
|
||||||
|
|
||||||
|
% Must be done first (as needs to 'tidy up')
|
||||||
|
% \input luatexconfig.tex
|
||||||
|
% Activate primitives
|
||||||
|
\input luatexiniconfig.tex
|
||||||
|
\begingroup
|
||||||
|
% load-unicode-data needs \eTeXversion
|
||||||
|
\catcode`\{=1
|
||||||
|
\catcode`\}=2
|
||||||
|
\global\chardef\eTeXversion=2
|
||||||
|
\global\def\eTeXrevision{.2}
|
||||||
|
\directlua{unhook_expl()}
|
||||||
|
\endgroup
|
||||||
|
\input load-unicode-data.tex
|
||||||
|
\input etex.src
|
||||||
|
\directlua{initialize_pdf_toks()}
|
||||||
|
\dump
|
|
@ -0,0 +1 @@
|
||||||
|
luametalatex.lua
|
Loading…
Reference in New Issue