diff options
author | Karl Berry <karl@freefriends.org> | 2021-09-28 21:03:49 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-09-28 21:03:49 +0000 |
commit | d4bac16bb0999b06c9903b611457f81a44a0e12d (patch) | |
tree | 4c282b8748154aae0a050e8a1ed2f4dd38b19e66 /Master/texmf-dist/tex/latex | |
parent | 8701e9350761f855769dd3417ddd3850f0b06b23 (diff) |
luapstricks in lualatex (28sep21)
git-svn-id: svn://tug.org/texlive/trunk@60642 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex')
-rw-r--r-- | Master/texmf-dist/tex/latex/luapstricks/luapstricks.lua | 3946 |
1 files changed, 0 insertions, 3946 deletions
diff --git a/Master/texmf-dist/tex/latex/luapstricks/luapstricks.lua b/Master/texmf-dist/tex/latex/luapstricks/luapstricks.lua deleted file mode 100644 index eebca868012..00000000000 --- a/Master/texmf-dist/tex/latex/luapstricks/luapstricks.lua +++ /dev/null @@ -1,3946 +0,0 @@ ----- luapstricks.lua --- Copyright 2021 Marcel Krüger <tex@2krueger.de> --- --- This work may be distributed and/or modified under the --- conditions of the LaTeX Project Public License, either version 1.3 --- of this license or (at your option) any later version. --- The latest version of this license is in --- http://www.latex-project.org/lppl.txt --- and version 1.3 or later is part of all distributions of LaTeX --- version 2005/12/01 or later. --- --- This work has the LPPL maintenance status `maintained'. --- --- The Current Maintainer of this work is M. Krüger --- --- This work consists of the file luapstricks.lua. - -if luatexbase then - luatexbase.provides_module { - name = 'luapstricks', - version = 'v0.1', - date = '2021-09-16', - description = 'PSTricks backend for LuaLaTeX', - } -end - -local pdfprint = vf.pdf -- Set later to have the right mode -local function gobble() end - -local pi = math.pi -local two_pi = 2*pi -local pi2_inv = 2/pi -local pi3_inv = 3/pi - -local sin_table = {0, 1, 0, -1} - -local l = lpeg - -local whitespace = (l.S'\0\t\n\r\f ' + '%' * (1-l.P'\n')^0 * (l.P'\n' + -1))^1 - -local regular = 1 - l.S'\0\t\n\r\f %()<>[]{}/' - -local exitmarker = {} -local lookup - --- local integer = l.S'+-'^-1 * l.R'09'^1 / tonumber -local real = l.S'+-'^-1 * (l.R'09'^1 * ('.' * l.R'09'^0)^-1 + '.' * l.R'09'^1) * (l.S'Ee' * l.S'+-'^-1 * l.R'09'^1)^-1 / tonumber -local radix_scanner = setmetatable({}, {__index = function(t, b) - local digit - if b < 10 then - digit = l.R('0' .. string.char(string.byte'0' + b - 1)) - else - digit = l.R'09' - if b > 10 then - digit = digit + l.R('A' .. string.char(string.byte'A' + b - 11)) - digit = digit + l.R('a' .. string.char(string.byte'a' + b - 11)) - end - end - digit = l.C(digit^1) * l.Cp() - t[b] = digit - return digit -end}) -local radix = l.Cmt(l.R'09' * l.R'09'^-1 / tonumber * '#', function(subj, pos, radix) - if radix < 2 or radix > 36 then return end - local digits, pos = radix_scanner[radix]:match(subj, pos) - if not digits then return end - digits = tonumber(digits, radix) - return pos, digits -end) -local number = radix + real -- + integer -- Every integer is also a real - -local str_view do - local meta = { - __index = function(s, k) - if k == 'value' then - return string.sub(s.base.value, s.offset, s.last) - end - end, - __newindex = function(s, k, v) - if k == 'value' then - s.base.value = string.sub(s.base.value, 1, s.offset-1) .. v .. string.sub(s.base.value, s.last+1) - return - end - -- We could do rawset here, but there is no reason for setting keys anyway - assert(false) - end, - } - function str_view(base, offset, length) - if getmetatable(base) == meta then - offset = offset + base.offset - 1 - base = base.base - end - return setmetatable({ - kind = 'string', - base = base, - offset = offset, - last = offset + length - 1, - }, meta) - end -end - -local string_patt do - local literal = '(' * l.Cs(l.P{( - l.Cg('\\' * ( - 'n' * l.Cc'\n' - + 'r' * l.Cc'\r' - + 't' * l.Cc'\t' - + 'b' * l.Cc'\b' - + 'f' * l.Cc'\f' - + '\\' * l.Cc'\\' - + '(' * l.Cc'(' - + ')' * l.Cc')' - + l.R'07' * l.R'07'^-2 / function(s) return string.char(tonumber(s, 8) % 0x100) end - + ('\r' * l.P'\n'^-1 + '\n')^-1 * l.Cc'' - )) - + l.Cg('\r' * l.P'\n'^-1 * l.Cc'\n') - + (1-l.S'()') - + '(' * l.V(1) * ')' - )^0}) * ')' - local hexchar = l.R('09', 'af', 'AF') - local hexbyte = hexchar * hexchar^-1 / function(s) - local b = tonumber(s, 16) - return #s == 1 and 16*b or b - end - local hex = '<' * (hexbyte^0 / string.char) * '>' - string_patt = literal + hex -- TODO: Base85 is not implemented -end - -local name = l.C(regular^1 + l.S'[]' + '<<' + '>>') -local literal_name = '/' * l.C(regular^0) -local imm_name = '//' * l.C(regular^0) - --- All objects are literal by default, except names represented as direct strings and operators -local any_object = l.P{whitespace^-1 * ( - number * -regular - + l.Ct(l.Cg(string_patt, 'value') * l.Cg(l.Cc'string', 'kind')) - + imm_name / function(name) return lookup(name) end - + l.Ct(l.Cg(literal_name, 'value') * l.Cg(l.Cc'name', 'kind')) - + name - + l.Ct(l.Cg(l.Ct(l.Cg('{' * l.Ct(l.V(1)^0) * whitespace^-1 * '}', 'value') * l.Cg(l.Cc'array', 'kind')), 'value') * l.Cg(l.Cc'executable', 'kind')) -)} -local object_list = l.Ct(any_object^0) * whitespace^-1 * (-1 + l.Cp()) - -local function parse_ps(s) - local tokens, fail_offset = object_list:match(s) - if fail_offset then - error(string.format('Failed to parse PS tokens at `%s\'', s:sub(fail_offset))) - end - return tokens -end - -local serialize_pdf do - function serialize_pdf(obj) - local t = type(obj) - if t == 'number' then - return string.format(math.type(obj) == 'float' and '%.5f' or '%i', obj) - elseif t == 'boolean' then - return obj and 'true' or 'false' - elseif t == 'string' then - return '/' .. obj - elseif t == 'table' then - t = obj.kind - if t == 'name' then - return '/' .. obj.value - elseif t == 'string' then - return '(' .. obj.value .. ')' -- TODO: Escaping - elseif t == 'dict' then - local helper = {} - for k, v in next, obj.value do - helper[#helper+1] = serialize_pdf(k) - helper[#helper+1] = serialize_pdf(v) - end - return '<<' .. table.concat(helper, ' ') .. '>>' - elseif t == 'array' then - local helper = {} - for i, v in ipairs(obj.value) do - helper[i] = serialize_pdf(v) - end - return '[' .. table.concat(helper, ' ') .. ']' - else - error'Unable to serialize object' - end - end - error'Unable to serialize object' - end -end - -local srand, rrand, rand do - local state - function srand(s) - state = s//1 - if state < 1 then - state = -(state % 0x7ffffffe) + 1 - elseif state > 0x7ffffffe then - state = 0x7ffffffe - end - end - function rrand() - return state - end - function rand() - state = (16807 * state) % 0x7fffffff - -- if state <= 0 then - -- state = state + 0x7fffffff - -- end - return state - end - srand(math.random(1, 0x7ffffffe)) -end - -local maybe_decompress do - local compressed_pattern = '%!PS\n\z - currentfile<</Predictor 1' * l.R'05' * '/Columns ' * (l.R'09'^1/tonumber) * '>>/FlateDecode filter cvx exec\n' - * l.C(l.P(1)^1) - - local stacklimit = 999000 - - function maybe_decompress(data) - local columns, compressed = compressed_pattern:match(data) - if not columns then return data end - - data = zlib.decompress(compressed) - local bytes do - local size = #data - if size < stacklimit then - bytes = {data:byte(1, -1)} - else - bytes = {} - local off = 1 - for i = 1, size, stacklimit do - table.move({data:byte(i, i+stacklimit-1)}, 1, stacklimit, i, bytes) - end - end - end - local new_data = {} - local start_row = 1 - local out_row = 1 - while true do - local control = bytes[start_row] - if not control then break end - if control == 0 or (control == 2 and start_row == 1) then - table.move(bytes, start_row + 1, start_row + columns, out_row, new_data) - elseif control == 1 then - local last = bytes[start_row + 1] - new_data[out_row] = last - for i = 2, columns do - last = (bytes[start_row + i] + last) & 0xFF - new_data[out_row + i - 1] = last - end - elseif control == 2 then - for i = 1, columns do - new_data[out_row + i - 1] = (bytes[start_row + i] + new_data[out_row - columns - 1 + i]) & 0xFF - end - else - error'Unimplemented' - end - start_row = start_row + columns + 1 - out_row = out_row + columns - end - local result = '' - local size = #new_data - for i = 1, size, stacklimit do - result = result .. string.char(table.unpack(new_data, i, i + stacklimit > size and size or i + stacklimit - 1)) - end - return result - end -end - -local font_aliases = { - -- First add some help to find the TeX Gyre names under the corresponding URW font names - ['NimbusRoman-Regular'] = 'kpse:texgyretermes-regular.otf', - ['NimbusRoman-Italic'] = 'kpse:texgyretermes-italic.otf', - ['NimbusRoman-Bold'] = 'kpse:texgyretermes-bold.otf', - ['NimbusRoman-BoldItalic'] = 'kpse:texgyretermes-bolditalic.otf', - - ['NimbusSans-Regular'] = 'kpse:texgyreheros-regular.otf', - ['NimbusSans-Italic'] = 'kpse:texgyreheros-italic.otf', - ['NimbusSans-Bold'] = 'kpse:texgyreheros-bold.otf', - ['NimbusSans-BoldItalic'] = 'kpse:texgyreheros-bolditalic.otf', - - ['NimbusSansNarrow-Regular'] = 'kpse:texgyreheroscn-regular.otf', - ['NimbusSansNarrow-Oblique'] = 'kpse:texgyreheroscn-italic.otf', - ['NimbusSansNarrow-Bold'] = 'kpse:texgyreheroscn-bold.otf', - ['NimbusSansNarrow-BoldOblique'] = 'kpse:texgyreheroscn-bolditalic.otf', - - ['NimbusMonoPS-Regular'] = 'kpse:texgyrecursor-regular.otf', - ['NimbusMonoPS-Italic'] = 'kpse:texgyrecursor-italic.otf', - ['NimbusMonoPS-Bold'] = 'kpse:texgyrecursor-bold.otf', - ['NimbusMonoPS-BoldItalic'] = 'kpse:texgyrecursor-bolditalic.otf', - - ['URWBookman-Light'] = 'kpse:texgyrebonum-regular.otf', - ['URWBookman-LightItalic'] = 'kpse:texgyrebonum-italic.otf', - ['URWBookman-Demi'] = 'kpse:texgyrebonum-bold.otf', - ['URWBookman-DemiItalic'] = 'kpse:texgyrebonum-bolditalic.otf', - - ['URWGothic-Book'] = 'kpse:texgyreadventor-regular.otf', - ['URWGothic-BookOblique'] = 'kpse:texgyreadventor-italic.otf', - ['URWGothic-Demi'] = 'kpse:texgyreadventor-bold.otf', - ['URWGothic-DemiOblique'] = 'kpse:texgyreadventor-bolditalic.otf', - - -- These fonts have weird names in their URW variant, so we use the standard font names directly instead. - ['NewCenturySchlbk-Roman'] = 'kpse:texgyreschola-regular.otf', - ['NewCenturySchlbk-Italic'] = 'kpse:texgyreschola-italic.otf', - ['NewCenturySchlbk-Bold'] = 'kpse:texgyreschola-bold.otf', - ['NewCenturySchlbk-BoldItalic'] = 'kpse:texgyreschola-bolditalic.otf', - - ['Palatino-Roman'] = 'kpse:texgyrepagella-regular.otf', - ['Palatino-Italic'] = 'kpse:texgyrepagella-italic.otf', - ['Palatino-Bold'] = 'kpse:texgyrepagella-bold.otf', - ['Palatino-BoldItalic'] = 'kpse:texgyrepagella-bolditalic.otf', - - ['ZapfChancery-MediumItalic'] = 'kpse:texgyrechorus-mediumitalic.otf', - - -- The two symbol fonts don't have OpenType equivalents in TeX Live - -- so we use TFM based fonts instead - ['StandardSymbolsPS'] = 'usyr', - ['Dingbats'] = 'uzdr', -} --- Then map the standard 35 font names to the URW names as done by GhostScript --- (Except for New Century Schoolbook which got mapped directly before. -for psname, remapped in next, { - ['Times-Roman'] = 'NimbusRoman-Regular', - ['Times-Italic'] = 'NimbusRoman-Italic', - ['Times-Bold'] = 'NimbusRoman-Bold', - ['Times-BoldItalic'] = 'NimbusRoman-BoldItalic', - - ['Helvetica'] = 'NimbusSans-Regular', - ['Helvetica-Oblique'] = 'NimbusSans-Italic', - ['Helvetica-Bold'] = 'NimbusSans-Bold', - ['Helvetica-BoldOblique'] = 'NimbusSans-BoldItalic', - - ['Helvetica-Narrow'] = 'NimbusSansNarrow-Regular', - ['Helvetica-Narrow-Oblique'] = 'NimbusSansNarrow-Oblique', - ['Helvetica-Narrow-Bold'] = 'NimbusSansNarrow-Bold', - ['Helvetica-Narrow-BoldOblique'] = 'NimbusSansNarrow-BoldOblique', - - ['Courier'] = 'NimbusMonoPS-Regular', - ['Courier-Oblique'] = 'NimbusMonoPS-Italic', - ['Courier-Bold'] = 'NimbusMonoPS-Bold', - ['Courier-BoldOblique'] = 'NimbusMonoPS-BoldItalic', - - ['Bookman-Light'] = 'URWBookman-Light', - ['Bookman-LightItalic'] = 'URWBookman-LightItalic', - ['Bookman-Demi'] = 'URWBookman-Demi', - ['Bookman-DemiItalic'] = 'URWBookman-DemiItalic', - - ['AvantGarde-Book'] = 'URWGothic-Book', - ['AvantGarde-BookOblique'] = 'URWGothic-BookOblique', - ['AvantGarde-Demi'] = 'URWGothic-Demi', - ['AvantGarde-DemiOblique'] = 'URWGothic-DemiOblique', - - ['Symbol'] = 'StandardSymbolsPS', - ['StandardSymL'] = 'StandardSymbolsPS', - - ['ZapfDingbats'] = 'Dingbats', - - -- Some additional names needed for PSTricks - ['NimbusRomNo9L-Regu'] = 'NimbusRoman-Regular', - ['NimbusRomNo9L-ReguItal'] = 'NimbusRoman-Italic', - ['NimbusRomNo9L-Medi'] = 'NimbusRoman-Bold', - ['NimbusRomNo9L-MediItal'] = 'NimbusRoman-BoldItalic', - ['NimbusRomNo9L-Bold'] = 'NimbusRoman-Bold', - - ['NimbusSanL-Regu'] = 'NimbusSans-Regular', - ['NimbusSanL-ReguItal'] = 'NimbusSans-Italic', - ['NimbusSanL-Bold'] = 'NimbusSans-Bold', - ['NimbusSanL-BoldItal'] = 'NimbusSans-BoldItalic', - - ['NimbusSanL-BoldCond'] = 'NimbusSansNarrow-Bold', - ['NimbusSanL-BoldCondItal'] = 'NimbusSansNarrow-BoldOblique', - ['NimbusSanL-ReguCond'] = 'NimbusSansNarrow-Regular', - ['NimbusSanL-ReguCondItal'] = 'NimbusSansNarrow-Oblique', - - ['NimbusMonL-Regu'] = 'NimbusMonoPS-Regular', - ['NimbusMonL-ReguObli'] = 'NimbusMonoPS-Italic', - ['NimbusMonL-Bold'] = 'NimbusMonoPS-Bold', - ['NimbusMonL-BoldObli'] = 'NimbusMonoPS-BoldItalic', - - ['URWBookmanL-DemiBoldItal'] = 'URWBookman-DemiItalic', - ['URWBookmanL-DemiBold'] = 'URWBookman-Demi', - ['URWBookmanL-LighItal'] = 'URWBookman-LightItalic', - ['URWBookmanL-Ligh'] = 'URWBookman-Light', - - ['URWGothicL-BookObli'] = 'URWGothic-BookOblique', - ['URWGothicL-Book'] = 'URWGothic-Book', - ['URWGothicL-DemiObli'] = 'URWGothic-DemiOblique', - ['URWGothicL-Demi'] = 'URWGothic-Demi', - - ['CenturySchL-Roma'] = 'NewCenturySchlbk-Roman', - ['CenturySchL-Ital'] = 'NewCenturySchlbk-Italic', - ['CenturySchL-Bold'] = 'NewCenturySchlbk-Bold', - ['CenturySchL-BoldItal'] = 'NewCenturySchlbk-BoldItalic', - - ['URWPalladioL-Roma'] = 'Palatino-Roman', - ['URWPalladioL-Ital'] = 'Palatino-Italic', - ['URWPalladioL-Bold'] = 'Palatino-Bold', - ['URWPalladioL-BoldItal'] = 'Palatino-BoldItalic', - - ['URWChanceryL-MediItal'] = 'ZapfChancery-MediumItalic', -} do - font_aliases[psname] = font_aliases[remapped] or remapped -end - -local operand_stack = {} - -local pushs do - local function helper(height, args, arg, ...) - if args == 0 then return end - height = height + 1 - operand_stack[height] = arg - return helper(height, args - 1, ...) - end - function pushs(...) - return helper(#operand_stack, select('#', ...), ...) - end -end -local function push(value) - operand_stack[#operand_stack+1] = value -end - -local function ps_error(kind, ...) - pushs(...) - return error{pserror = kind, trace = debug.traceback()} -end - -local function pop(...) - local height = #operand_stack - if height == 0 then - return ps_error('stackunderflow', ...) - end - local v = operand_stack[height] - operand_stack[height] = nil - return v, v -end -local function pop_num(...) - local raw = pop(...) - local n = raw - local tn = type(n) - if tn == 'table' and n.kind == 'executable' then - n = n.value - tn = type(n) - end - if tn ~= 'number' then - ps_error('typecheck', raw, ...) - end - return n, raw -end -local pop_int = pop_num -local function pop_proc(...) - local v = pop() - if type(v) ~= 'table' or v.kind ~= 'executable' or type(v.value) ~= 'table' or v.value.kind ~= 'array' then - ps_error('typecheck', v, ...) - end - return v.value.value, v -end -local pop_bool = pop -local function pop_dict() - local orig = pop() - local dict = orig - if type(dict) ~= 'table' then - ps_error('typecheck', orig) - end - if dict.kind == 'executable' then - dict = dict.value - if type(dict) ~= 'table' then - ps_error('typecheck', orig) - end - end - if dict.kind ~= 'dict' then - ps_error('typecheck', orig) - end - return dict.value, orig, dict -end -local function pop_array() - local orig = pop() - local arr = orig - if type(arr) == 'table' and arr.kind == 'executable' then - arr = arr.value - end - if type(arr) ~= 'table' or arr.kind ~= 'array' then - ps_error('typecheck', orig) - end - return arr -end -local pop_string = pop -local function pop_key() - local key = pop() - if type(key) == 'table' then - local kind = key.kind - if kind == 'executable' then - key = key.value - if type(key) ~= 'table' then return key end - kind = key.kind - end - if kind == 'string' or kind == 'name' or kind == 'operator' then - key = key.value - end - end - return key -end - -local execute_ps, execute_tok - -local dictionary_stack -local graphics_stack = {{ - matrix = {10, 0, 0, 10, 0, 0}, -- Chosen for consistency with GhostScript's pdfwrite. Must be the same as defaultmatrix - linewidth = nil, - current_path = nil, - current_point = nil, - color = {}, - fillconstantalpha = 1, - strokeconstantalpha = 1, - alphaisshape = nil, - blendmode = nil, - linejoin = nil, - linecap = nil, - strokeadjust = nil, - font = nil, - dash = nil, - saved_delayed = nil, -- nil if the `gsave` of this graphic state is not delayed - flatness = 1, - miterlimit = nil, -}} - -local lua_node_lookup = setmetatable({}, {__mode = 'k'}) -local char_width_storage -- Non nil only at the beginning of a Type 3 glyph. Used to export the width. -local ExtGStateCount = 0 -local pdfdict_gput = token.create'pdfdict_gput:nnn' -if pdfdict_gput.cmdname == 'undefined_cs' then - pdfdict_gput = nil -end -local lbrace = token.create(string.byte'{') -local rbrace = token.create(string.byte'}') -local ExtGState = setmetatable({}, {__index = pdfdict_gput and function(t, k) - ExtGStateCount = ExtGStateCount + 1 - local name = 'PSExtG' .. ExtGStateCount - tex.runtoks(function() - tex.write(pdfdict_gput, lbrace, 'g__pdf_Core/Page/Resources/ExtGState', rbrace, lbrace, name, rbrace, lbrace, k, rbrace) - end) - ltx.__pdf.Page.Resources.ExtGState = true - ltx.pdf.Page_Resources_gpush(tex.count.g_shipout_readonly_int) - name = '/' .. name .. ' gs' - t[k] = name - return name -end or function() - texio.write_nl"Extended graphic state modifications dropped since `pdfmanagement-testphase' is not loaded." - return '' -end}) - -local write_shading do - local ShadingCount = 0 - if pdfdict_gput then - function write_shading(attr, data) - local obj = pdf.obj{ - type = 'stream', - immediate = false, - attr = attr, - string = data, - } - pdf.refobj(obj) - ShadingCount = ShadingCount + 1 - local name = 'PSShad' .. ShadingCount - local k = obj .. ' 0 R' - tex.runtoks(function() - tex.write(pdfdict_gput, lbrace, 'g__pdf_Core/Page/Resources/Shading', rbrace, lbrace, name, rbrace, lbrace, k, rbrace) - end) - ltx.__pdf.Page.Resources.Shading = true - ltx.pdf.Page_Resources_gpush(tex.count.g_shipout_readonly_int) - name = '/' .. name - return name - end - else - function write_shading() - texio.write_nl"Extended graphic state modifications dropped since `pdfmanagement-testphase' is not loaded." - return '' - end - end -end - -local function matrix_transform(x, y, xx, xy, yx, yy, dx, dy) - return x * xx + y * yx + dx, x * xy + y * yy + dy -end -local function matrix_invert(xx, xy, yx, yy, dx, dy) - local determinante = xx*yy - xy*yx - xx, xy, yx, yy = yy/determinante, -xy/determinante, -yx/determinante, xx/determinante - dx, dy = - dx * xx - dy * yx, - dx * xy - dy * yy - return xx, xy, yx, yy, dx, dy -end -local delayed = { - text = {}, - matrix = {1, 0, 0, 1, 0, 0}, -} -local function update_matrix(xx, xy, yx, yy, dx, dy) - local matrix = graphics_stack[#graphics_stack].matrix - matrix[1], matrix[2], - matrix[3], matrix[4], - matrix[5], matrix[6] - = xx * matrix[1] + xy * matrix[3], xx * matrix[2] + xy * matrix[4], - yx * matrix[1] + yy * matrix[3], yx * matrix[2] + yy * matrix[4], - dx * matrix[1] + dy * matrix[3] + matrix[5], dx * matrix[2] + dy * matrix[4] + matrix[6] - - local delayed_matrix = delayed.matrix - delayed_matrix[1], delayed_matrix[2], - delayed_matrix[3], delayed_matrix[4], - delayed_matrix[5], delayed_matrix[6] - = xx * delayed_matrix[1] + xy * delayed_matrix[3], xx * delayed_matrix[2] + xy * delayed_matrix[4], - yx * delayed_matrix[1] + yy * delayed_matrix[3], yx * delayed_matrix[2] + yy * delayed_matrix[4], - dx * delayed_matrix[1] + dy * delayed_matrix[3] + delayed_matrix[5], dx * delayed_matrix[2] + dy * delayed_matrix[4] + delayed_matrix[6] - - local current_path = graphics_stack[#graphics_stack].current_path - if not current_path then return end - - local determinante = xx*yy - xy*yx - xx, xy, yx, yy, dx, dy = matrix_invert(xx, xy, yx, yy, dx, dy) - local i=1 - while current_path[i] do - local entry = current_path[i] - if type(entry) == 'number' then - local after = current_path[i+1] - assert(type(after) == 'number') - current_path[i], current_path[i+1] = xx * entry + yx * after + dx, xy * entry + yy * after + dy - i = i+2 - else - i = i+1 - end - end - local current_point = graphics_stack[#graphics_stack].current_point - local x, y = current_point[1], current_point[2] - current_point[1], current_point[2] = xx * x + yx * y + dx, xy * x + yy * y + dy -end - -local function delayed_print(str) - local delayed_text = delayed.text - delayed_text[#delayed_text + 1] = str -end - -local function reset_delayed(delayed) - local delayed_matrix = delayed.matrix - local delayed_text = delayed.text - for i=1, #delayed_text do - delayed_text[i] = nil - end - delayed_matrix[1], delayed_matrix[2], - delayed_matrix[3], delayed_matrix[4], - delayed_matrix[5], delayed_matrix[6] = 1, 0, 0, 1, 0, 0 -end - -local function flush_delayed_table(delayed, state, force_start) - local delayed_matrix = delayed.matrix - local delayed_text = delayed.text - - local cm_string = string.format('%.5f %.5f %.5f %.5f %.5f %.5f cm', delayed_matrix[1], delayed_matrix[2], - delayed_matrix[3], delayed_matrix[4], - delayed_matrix[5], delayed_matrix[6]) - if cm_string == "1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm" then - cm_string = nil - end - - -- Before flushing, make sure that the current graphics state has started. - graphics_stack_height = graphics_stack_height or #graphics_stack - local saved_delayed = state.saved_delayed - if saved_delayed and(cm_string or delayed_text[1] or force_start) then - state.saved_delayed = nil - pdfprint'q' - end - for i=1, #delayed_text do - pdfprint(delayed_text[i]) - end - if cm_string then - pdfprint((cm_string:gsub('%.?0+ ', ' '))) - end - return reset_delayed(delayed) -end - -local function flush_delayed(force_start) - local pre_first_delayed_group - for i = #graphics_stack, 1, -1 do - if not graphics_stack[i].saved_delayed then - pre_first_delayed_group = i - break - end - end - for i = pre_first_delayed_group, #graphics_stack-1 do - flush_delayed_table(graphics_stack[i+1].saved_delayed, graphics_stack[i]) -- No need for force_start here - end - return flush_delayed_table(delayed, graphics_stack[#graphics_stack], force_start) -end - -function drawarc(xc, yc, r, a1, a2) - a1, a2 = math.rad(a1), math.rad(a2) - local dx, dy = r*math.cos(a1), r*math.sin(a1) - local x, y = xc + dx, yc + dy - local segments = math.ceil(math.abs(a2-a1)*pi2_inv) - local da = (a2-a1)/segments - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - local i - if current_path then - i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2] = x, y, 'l' - i = i + 3 - else - current_path = {x, y, 'm'} - i = 4 - state.current_path = current_path - state.current_point = {} - end - local factor = 4*math.tan(da/4)/3 - dx, dy = factor*dy, -factor*dx - for _=1, segments do - current_path[i], current_path[i+1] = x - dx, y - dy - a1 = a1 + da - dx, dy = r*math.cos(a1), r*math.sin(a1) - x, y = xc + dx, yc + dy - dx, dy = factor*dy, -factor*dx - current_path[i+2], current_path[i+3] = x + dx, y + dy - current_path[i+4], current_path[i+5] = x, y - current_path[i+6] = 'c' - i = i + 7 - end - state.current_point[1], state.current_point[2] = x, y -end - -local function try_lookup(name) - for i = #dictionary_stack, 1, -1 do - local dict = dictionary_stack[i] - local value = dict.value[name] - if value ~= nil then - return value - end - end -end -function lookup(name) - local result = try_lookup(name) - if result == nil then - return error(string.format('Unknown name %q', name)) - end - return result -end -local function bind(proc) - for i=1, #proc do - local entry = proc[i] - local tentry = type(entry) - if tentry == 'table' and entry.kind == 'executable' and type(entry.value) == 'table' and entry.value.kind == 'array' then - bind(entry.value.value) - elseif tentry == 'string' then - local res = try_lookup(entry) - if type(res) == 'function' then - proc[i] = res - end - end - end -end - -local subdivide, flatten do - function subdivide(t, x0, y0, x1, y1, x2, y2, x3, y3) - local mt = 1-t - local x01, y01 = mt * x0 + t * x1, mt * y0 + t * y1 - local x12, y12 = mt * x1 + t * x2, mt * y1 + t * y2 - local x23, y23 = mt * x2 + t * x3, mt * y2 + t * y3 - local x012, y012 = mt * x01 + t * x12, mt * y01 + t * y12 - local x123, y123 = mt * x12 + t * x23, mt * y12 + t * y23 - local x0123, y0123 = mt * x012 + t * x123, mt * y012 + t * y123 - return x01, y01, x012, y012, x0123, y0123, x123, y123, x23, y23, x3, y3 - end - local function flatness(x0, y0, x1, y1, x2, y2, x3, y3) - local dx, dy = x3-x0, y3-y0 - local dist = math.sqrt(dx*dx + dy*dy) - local d1 = math.abs(dx * (x0-x1) - dy * (y0-y1)) / dist - local d2 = math.abs(dx * (x0-x2) - dy * (y0-y2)) / dist - return d1 > d2 and d1 or d2 - end - function flatten(out, target, x0, y0, x1, y1, x2, y2, x3, y3) - local current = flatness(x0, y0, x1, y1, x2, y2, x3, y3) - if current <= target then - local i = #out - -- out[i+1], out[i+2], - -- out[i+3], out[i+4], - -- out[i+5], out[i+6], out[i+7] - -- = x1, y1, x2, y2, x3, y3, 'c' - out[i+1], out[i+2], out[i+3] - = x3, y3, 'l' - return - end - local a, b, c, d, e, f, g, h, i, j, k, l = subdivide(.5, x0, y0, x1, y1, x2, y2, x3, y3) - flatten(out, target, x0, y0, a, b, c, d, e, f) - return flatten(out, target, e, f, g, h, i, j, k, l) - end -end - -local function ps_to_string(a) - local ta = type(a) - if ta == 'table' and a.kind == 'executable' then - a = a.value - ta = type(a) - end - if ta == 'string' then - elseif ta == 'boolean' then - a = a and 'true' or 'false' - elseif ta == 'number' then - a = string.format(math.type(a) == 'float' and '%.6g' or '%i', a) - -- a = tostring(a) - elseif ta == 'function' then - texio.write_nl'Warning: cvs on operators is unsupported. Replaced by dummy.' - a = '--nostringval--' - elseif ta == 'table' then - local kind = a.kind - if kind == 'string' or kind == 'name' then - a = a.value - elseif kind == 'operator' then - texio.write_nl'Warning: cvs on operators is unsupported. Replaced by dummy.' - a = '--nostringval--' - else - a = '--nostringval--' - end - elseif ta == 'userdata' and a.read then - a = 'file' - else - assert(false) - end - return a -end - -local mark = {kind = 'mark'} -local null = {kind = 'null'} -local globaldict = {kind = 'dict', value = {}} -local userdict = {kind = 'dict', value = { - SDict = {kind = 'dict', value = { - normalscale = {kind = 'executable', value = {kind = 'array', value = {}}}, - }}, - ['@beginspecial'] = {kind = 'executable', value = {kind = 'array', value = {}}}, - ['@setspecial'] = {kind = 'executable', value = {kind = 'array', value = {}}}, - ['@endspecial'] = {kind = 'executable', value = {kind = 'array', value = {}}}, -}} -local FontDirectory = {kind = 'dict', value = {}} -local ResourceCategories = {kind = 'dict', value = {}} - -local function num_to_base(num, base, ...) - if num == 0 then return string.char(...) end - local remaining = num // base - local digit = num - base * remaining - if digit < 10 then - digit = digit + 0x30 - else - digit = digit + 0x37 - end - return num_to_base(remaining, base, digit, ...) -end - -local systemdict -local function generic_show(str, ax, ay) - local state = graphics_stack[#graphics_stack] - local current_point = state.current_point - if not current_point then return nil, 'nocurrentpoint' end - local rawpsfont = state.font - if not rawpsfont then return nil, 'invalidfont' end - local str = str.value - local psfont = rawpsfont.value - local fid = psfont.FID - local matrix = psfont.FontMatrix.value - local fonttype = psfont.FontType - if fonttype ~= 0x1CA and fonttype ~= 3 then - texio.write_nl'Font support is not implemented' - return true - end - local x0, y0 = current_point[1], current_point[2] - update_matrix( - matrix[1], matrix[2], - matrix[3], matrix[4], - matrix[5] + x0, matrix[6] + y0) - local w = 0 - if fonttype == 0x1CA then - local characters = assert(font.getfont(fid)).characters - flush_delayed() - if pdfprint ~= gobble then - vf.push() - vf.fontid(fid) - end - for b in string.bytes(str) do - if pdfprint ~= gobble then - vf.char(b) - if ax then - vf.right(ax) - vf.down(-ay) - end - end - local char = characters[b] - w = w + (char and char.width or 0) - end - if pdfprint ~= gobble then - vf.pop() - end - w = w/65781.76 - elseif fonttype == 3 then - for b in string.bytes(str) do - systemdict.value.gsave() - local state = graphics_stack[#graphics_stack] - state.current_point, state.current_path = nil - push(rawpsfont) - push(b) - local this_w - char_width_storage = function(width) - this_w = width - end - execute_tok(psfont.BuildChar) -- FIXME(maybe): Switch to BuildGlyph? - systemdict.value.grestore() - w = w + assert(this_w, 'Type 3 character failed to set width') - update_matrix(1, 0, 0, 1, this_w, 0) - if ax then - update_matrix(1, 0, 0, 1, ax, ay) - end - end - update_matrix(1, 0, 0, 1, -w, 0) - if ax then - local count = #str - update_matrix(1, 0, 0, 1, count * -ax, count * -ay) - end - else - assert(false) - end - if ax then - local count = #str - push(w + count * ax) - push(count * ay) - else - push(w) - push(0) - end - systemdict.value.rmoveto() - update_matrix(matrix_invert( - matrix[1], matrix[2], - matrix[3], matrix[4], - matrix[5] + x0, matrix[6] + y0)) - return true -end - -systemdict = {kind = 'dict', value = { - dup = function() - local v = pop() - push(v) - push(v) - end, - exch = function() - local b = pop() - local a = pop(b) - push(b) - push(a) - end, - pop = function() - pop() - end, - clear = function() - for i = 1, #operand_stack do - operand_stack[i] = nil - end - end, - copy = function() - local arg, orig = pop() - local exec - if type(arg) == 'table' and arg.kind == 'executable' then - exec = true - arg = arg.value - end - if type(arg) == 'number' then - local height = #operand_stack - if arg > height then - error'copy argument larger then stack' - end - table.move(operand_stack, height-arg+1, height, height+1) - elseif type(arg) == 'table' then - -- See remarks in getinterval about missing functionality - local kind = arg.kind - if kind == 'array' then - local src = pop_array().value - if #src ~= #arg.value then - error'copy with different sized arrays is not implemented yet' - end - table.move(src, 1, #src, 1, arg.value) - elseif kind == 'string' then - local src = pop_string().value - if #src == #arg.value then - elseif #src < #arg.value then - arg = str_view(arg, 1, #src) - else - ps_error'rangecheck' - end - arg.value = src - elseif kind == 'dict' then - local src = pop_dict() - if next(arg.value) then - error'Target dictionary must be empty' - end - for k, v in next, src do - arg.value[k] = v - end - else - ps_error'typecheck' - end - push(exec and {kind = 'executable', value = arg} or arg) - else - ps_error('typecheck', orig) - end - end, - roll = function() - local j, arg2 = pop_int() - local n, arg1 = pop_int(arg2) - if n < 0 then - ps_error('rangecheck', arg1, arg2) - end - if n == 0 or j == 0 then return end - local height = #operand_stack - if j < 0 then - j = (-j) % n - local temp = table.move(operand_stack, height-n+1, height-n+j, 1, {}) - table.move(operand_stack, height-n+j+1, height, height-n+1) - table.move(temp, 1, j, height-j+1, operand_stack) - else - j = j % n - local temp = table.move(operand_stack, height-j+1, height, 1, {}) - table.move(operand_stack, height-n+1, height-j, height-n+j+1) - table.move(temp, 1, j, height-n+1, operand_stack) - end - end, - index = function() - local i, arg1 = pop_int() - local height = #operand_stack - if i < 0 or height <= i then - ps_error('rangecheck', arg1) - end - push(operand_stack[height - i]) - end, - null = function() - push(null) - end, - mark = function() - push(mark) - end, - ['['] = function() - push(mark) - end, - [']'] = function() - systemdict.value.counttomark() - systemdict.value.array() - systemdict.value.astore() - systemdict.value.exch() - systemdict.value.pop() - end, - ['<<'] = function() - push(mark) - end, - ['>>'] = function() - local mark_pos - for i = #operand_stack, 1, -1 do - if operand_stack[i] == mark then - mark_pos = i - break - end - end - if not mark_pos then error'Unmatched mark' end - local dict = lua.newtable(0, (#operand_stack-mark_pos) // 2) - for i = mark_pos + 1, #operand_stack - 1, 2 do - push(operand_stack[i]) - local key = pop_key() - dict[key] = operand_stack[i+1] - end - for i = mark_pos, #operand_stack do - operand_stack[i] = nil - end - push{kind = 'dict', value = dict} - end, - count = function() - push(#operand_stack) - end, - counttomark = function() - local height = #operand_stack - for i=height, 1, -1 do - local entry = operand_stack[i] - if type(entry) == 'table' and entry.kind == 'mark' then - return push(height-i) - end - end - error'Unmatched mark' - end, - cleartomark = function() - local entry - repeat - entry = pop() - until (not entry) or type(entry) == 'table' and entry.kind == 'mark' - if not entry then error'Unmatched mark' end - end, - - ['if'] = function() - local proc, arg2 = pop_proc() - local cond = pop_bool(arg2) - if cond then - execute_ps(proc) - end - end, - ifelse = function() - local proc_else, arg3 = pop_proc() - local proc_then, arg2 = pop_proc(arg3) - local cond = pop_bool(arg2, arg3) - if cond then - execute_ps(proc_then) - else - execute_ps(proc_else) - end - end, - ['for'] = function() - local proc, arg4 = pop_proc() - local limit, arg3 = pop_num(arg4) - local step, arg2 = pop_num(arg3, arg4) - local initial = pop_num(arg2, arg3, arg4) - local success, err = pcall(function() - for i=initial, limit, step do - push(i) - execute_ps(proc) - end - end) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - forall = function() - local proc, arg2 = pop_proc() - local obj, arg1 = pop() - if type(obj) ~= 'table' then - ps_error('typecheck', arg1, arg2) - end - if obj.kind == 'executable' then - obj = obj.value - if type(obj) ~= 'table' then - ps_error('typecheck', arg1, arg2) - end - end - local success, err = pcall( - obj.kind == 'array' and function() - for i=1, #obj.value do - push(obj.value[i]) - execute_ps(proc) - end - end - or obj.kind == 'string' and function() - for b in string.bytes(obj.value) do - push(b) - execute_ps(proc) - end - end - or obj.kind == 'dict' and function() - for k, v in next, obj.value do - pushs(k, v) - execute_ps(proc) - end - end - or ps_error('typecheck', arg1, arg2)) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - ['repeat'] = function() - local proc, arg2 = pop_proc() - local count = pop_int(arg2) - local success, err = pcall(function() - for i=1, count do - execute_ps(proc) - end - end) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - loop = function() - local proc = pop_proc() - local success, err = pcall(function() - while true do - execute_ps(proc) - end - end) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - - pathforall = function() - local close = pop_proc() - local curve = pop_proc() - local line = pop_proc() - local move = pop_proc() - local state = graphics_stack[#graphics_stack] - local path = state.current_path - if not path then return end - path = table.move(path, 1, #path, 1, {}) -- We don't want to be affected by modifications - local success, err = pcall( function() - local i = 1 - while true do - local entry = path[i] - if type(entry) == 'string' then - execute_ps(entry == 'm' and move or entry == 'l' and line or entry == 'c' and curve or entry == 'h' and close or error'Unexpected path operator') - elseif entry then - push(entry) - else - break - end - i = i + 1 - end - end) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - - ['.texboxforall'] = function() - local proc, arg2 = pop_proc() - local boxop = pop() - local box = lua_node_lookup[boxop] - if not box then - -- push(boxop) - -- -- push(proc) - ps_error('typecheck', boxop, arg2) - end - if node.direct.getid(box.box) ~= node.id'hlist' then - -- push(boxop) - -- push(proc) - error'.texboxforall is currently only supported for hboxes' - end - - local head = node.direct.getlist(box.box) - head = node.direct.flatten_discretionaries(head) - node.direct.setlist(box.box, head) - local success, err = pcall(function() - local x, y = 0, 0 - local n = head - while n do - local after = node.direct.getnext(n) - local width = node.direct.rangedimensions(box.box, n, after)/65781.76 - push(mark) - local id = node.type(node.direct.getid(n)) - local subbox = {box = n, parent = box} -- parent is needed for lifetime reasons - local function op() - flush_delayed() - vf.push() - local n = subbox.box -- Same as the outer box, but this preserves the lifetime of subbox - local parent = subbox.parent.box - local after = node.direct.getnext(n) - local head = node.direct.getlist(parent) - node.direct.setnext(n, nil) - node.direct.setlist(parent, subbox.box) - vf.node(parent) - node.direct.setnext(n, after) - node.direct.setlist(parent, head) - vf.pop() - end - lua_node_lookup[subbox] = op - push(op) - push(x) - push(y) - push(width) - push(0) - push(id) - execute_ps(proc) - if width ~= 0 then - x = x + width - end - n = after - end - end) - if not success and err ~= exitmarker then - error(err, 0) - end - end, - pathbbox = function() - local current_path = assert(graphics_stack[#graphics_stack].current_path, 'nocurrentpoint') - local i=1 - local llx, lly, urx, ury - while current_path[i] do - local entry = current_path[i] - if type(entry) == 'number' then - local after = current_path[i+1] - assert(type(after) == 'number') - llx = llx and llx < entry and llx or entry - lly = lly and lly < after and lly or after - urx = urx and urx > entry and urx or entry - ury = ury and ury > after and ury or after - i = i+2 - else - i = i+1 - end - end - push(llx) - push(lly) - push(urx) - push(ury) - end, - - ['not'] = function() - local val, orig = pop() - local tval = type(val) - if tval == 'table' and val.kind == 'executable' then - val = val.value - local tval = type(val) - end - if tval == 'boolean' then - push(not val) - elseif tval == 'number' then - push(~val) - else - ps_error('typecheck', orig) - end - end, - ['and'] = function() - local val, orig = pop() - local tval = type(val) - if tval == 'table' and val.kind == 'executable' then - val = val.value - local tval = type(val) - end - if tval == 'boolean' then - push(pop_bool() and val) - elseif tval == 'number' then - push(val & pop_int()) - else - ps_error('typecheck', orig) - end - end, - ['or'] = function() - local val, orig = pop() - local tval = type(val) - if tval == 'table' and val.kind == 'executable' then - val = val.value - local tval = type(val) - end - if tval == 'boolean' then - push(pop_bool() or val) - elseif tval == 'number' then - push(val | pop_int()) - else - ps_error('typecheck', orig) - end - end, - ['xor'] = function() - local val, orig = pop() - local tval = type(val) - if tval == 'table' and val.kind == 'executable' then - val = val.value - local tval = type(val) - end - if tval == 'boolean' then - push(val ~= pop_bool()) - elseif tval == 'number' then - push(val ~ pop_int()) - else - ps_error('typecheck', orig) - end - end, - bitshift = function() - local shift, arg2 = pop_num() - local val = pop_num(arg2) - push(val << shift) - end, - - eq = function() - local b = pop() - local a = pop(b) - if type(a) == 'table' and (a.kind == 'executable' or a.kind == 'name' or a.kind == 'operator') then - a = a.value - end - if type(a) == 'table' and a.kind == 'string' then - a = a.value - end - if type(b) == 'table' and (b.kind == 'executable' or b.kind == 'name' or b.kind == 'operator') then - b = b.value - end - if type(b) == 'table' and b.kind == 'string' then - b = b.value - end - push(a==b) - end, - ne = function() - local b = pop() - local a = pop(b) - if type(a) == 'table' and (a.kind == 'executable' or a.kind == 'name' or a.kind == 'operator') then - a = a.value - end - if type(a) == 'table' and a.kind == 'string' then - a = a.value - end - if type(b) == 'table' and (b.kind == 'executable' or b.kind == 'name' or b.kind == 'operator') then - b = b.value - end - if type(b) == 'table' and b.kind == 'string' then - b = b.value - end - push(a~=b) - end, - gt = function() - local b, arg2 = pop() - local a, arg1 = pop(arg2) - local ta, tb = type(a), type(b) - if ta == 'table' and a.kind == 'executable' then - a = a.value ta = type(a) - end - if tb == 'table' and b.kind == 'executable' then - b = b.value tb = type(b) - end - if ta == 'number' then - if tb ~= 'number' then - ps_error('typecheck', arg1, arg2) - end - elseif ta == 'table' and ta.kind == 'string' then - if tb ~= 'table' or tb.kind ~= 'string' then - ps_error('typecheck', arg1, arg2) - end - a, b = a.value, b.value - else - ps_error('typecheck', arg1, arg2) - end - push(a>b) - end, - ge = function() - local b, arg2 = pop() - local a, arg1 = pop(arg2) - local ta, tb = type(a), type(b) - if ta == 'table' and a.kind == 'executable' then - a = a.value ta = type(a) - end - if tb == 'table' and b.kind == 'executable' then - b = b.value tb = type(b) - end - if ta == 'number' then - if tb ~= 'number' then - ps_error('typecheck', arg1, arg2) - end - elseif ta == 'table' and ta.kind == 'string' then - if tb ~= 'table' or tb.kind ~= 'string' then - ps_error('typecheck', arg1, arg2) - end - a, b = a.value, b.value - else - ps_error('typecheck', arg1, arg2) - end - push(a>=b) - end, - le = function() - local b, arg2 = pop() - local a, arg1 = pop(arg2) - local ta, tb = type(a), type(b) - if ta == 'table' and a.kind == 'executable' then - a = a.value ta = type(a) - end - if tb == 'table' and b.kind == 'executable' then - b = b.value tb = type(b) - end - if ta == 'number' then - if tb ~= 'number' then - ps_error('typecheck', arg1, arg2) - end - elseif ta == 'table' and ta.kind == 'string' then - if tb ~= 'table' or tb.kind ~= 'string' then - ps_error('typecheck', arg1, arg2) - end - a, b = a.value, b.value - else - ps_error('typecheck', arg1, arg2) - end - push(a<=b) - end, - lt = function() - local b, arg2 = pop() - local a, arg1 = pop(arg2) - local ta, tb = type(a), type(b) - if ta == 'table' and a.kind == 'executable' then - a = a.value ta = type(a) - end - if tb == 'table' and b.kind == 'executable' then - b = b.value tb = type(b) - end - if ta == 'number' then - if tb ~= 'number' then - ps_error('typecheck', arg1, arg2) - end - elseif ta == 'table' and a.kind == 'string' then - if tb ~= 'table' or b.kind ~= 'string' then - ps_error('typecheck', arg1, arg2) - end - a, b = a.value, b.value - else - ps_error('typecheck', arg1, arg2) - end - push(a<b) - end, - - add = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a+b) - end, - sub = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a-b) - end, - mul = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a*b) - end, - div = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a/b) - end, - idiv = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a//b) - end, - mod = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a%b) - end, - exp = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - push(a^b) - end, - sqrt = function() - push(math.sqrt(pop_num())) - end, - sin = function() - local x = pop_num() - local i, f = math.modf(x/90) - if f == 0 then - push(sin_table[i % 4 + 1]) - else - push(math.sin(math.rad(x))) - end - end, - cos = function() - local x = pop_num() - local i, f = math.modf(x/90) - if f == 0 then - push(sin_table[(i+1) % 4 + 1]) - else - push(math.cos(math.rad(x))) - end - end, - atan = function() - local b, arg2 = pop_num() - local a = pop_num(arg2) - local res = math.deg(math.atan(a, b)) - if res < 0 then res = res + 360 end - push(res) - end, - arccos = function() - push(math.deg(math.acos(pop_num()))) - end, - arcsin = function() - push(math.deg(math.asin(pop_num()))) - end, - abs = function() - push(math.abs(pop_num())) - end, - neg = function() - push(-pop_num()) - end, - round = function() - return push(math.floor(pop_num()+.5)) - end, - ceiling = function() - return push(math.ceil(pop_num())) - end, - floor = function() - return push(math.floor(pop_num())) - end, - ln = function() - push(math.log((pop_num()))) - end, - log = function() - push(math.log(pop_num(), 10)) - end, - truncate = function() - push((math.modf(pop_num()))) - end, - cvn = function() - local a, raw = pop() - if type(a) == 'table' and a.kind == 'executable' then - local val = a.value - if type(val) ~= 'table' or val.kind ~= 'string' then - ps_error('typecheck', raw) - end - push(val.value) - end - if type(a) ~= 'table' or a.kind ~= 'string' then - ps_error('typecheck', raw) - end - return push{kind = 'name', value = a.value} - end, - cvi = function() - local a, raw = pop() - if type(a) == 'table' and a.kind == 'executable' then - a = a.value - end - if type(a) == 'table' and a.kind == 'string' then - a = (number * -1):match(a.value) - if not a then - ps_error('syntaxerror', raw) - end - end - if type(a) ~= 'number' then ps_error('typecheck', raw) end - push(a//1) - end, - cvr = function() - local a, raw = pop() - if type(a) == 'table' and a.kind == 'executable' then - a = a.value - end - if type(a) == 'table' and a.kind == 'string' then - a = (number * -1):match(a.value) - if not a then - ps_error('syntaxerror', raw) - end - end - if type(a) ~= 'number' then ps_error('typecheck', raw) end - push(a*1.) - end, - cvs = function() - local old_str, arg2 = pop_string() - local a, arg1 = pop() - a = ps_to_string(a) - if #old_str.value < #a then ps_error('rangecheck', arg1, arg2) end - old_str.value = a .. string.sub(old_str.value, #a+1, -1) - return push{kind = 'string', value = a} - end, - cvrs = function() - local old_str, arg3 = pop_string() - local radix, arg2 = pop_num() - local num, arg1 = pop_num() - if radix == 10 then - num = string.format(math.type(num) == 'float' and '%.6g' or '%i', num) - else - num = num//1 - if num < 0 and num >= -0x80000000 then - num = num + 0x100000000 - end - if num < 0 then - ps_error('rangecheck', arg1, arg2, arg3) - end - num = num == 0 and '0' or num_to_base(num, radix) - end - if #old_str.value < #num then ps_error('rangecheck', arg1, arg2, arg3) end - old_str.value = num .. string.sub(old_str.value, #num+1, -1) - return push{kind = 'string', value = num} - end, - - string = function() - push{kind = 'string', value = string.rep('\0', (pop_int()))} - end, - search = function() - local seek = pop_string() - local str = pop_string() - local start, stop = string.find(str.value, seek.value, 1, true) - if start then - push(str_view(str, stop + 1, #str.value - stop)) - push(str_view(str, start, stop - start + 1)) - push(str_view(str, 1, start - 1)) - push(true) - else - push(str) - push(false) - end - end, - - array = function() - local size = pop_int() - local arr = lua.newtable(size, 0) - for i=1, size do arr[i] = null end - push{kind = 'array', value = arr} - end, - astore = function() - local arr = pop_array() - local size = #arr.value - for i=size, 1, -1 do - arr.value[i] = pop() - end - push(arr) - end, - aload = function() - local arr = pop_array() - table.move(arr.value, 1, #arr.value, #operand_stack + 1, operand_stack) - push(arr) - end, - getinterval = function() - local count, arg3 = pop_int() - local index, arg2 = pop_int() - local arr, arg1 = pop() - if type(arr) ~= 'table' then ps_error('typecheck', arg1, arg2, arg3) end - if arr.kind == 'executable' then - arr = arr.value - if type(arr) ~= 'table' then ps_error('typecheck', arg1, arg2, arg3) end - end - if arr.kind == 'string' then - push(str_view(arr, index + 1, count)) - elseif arr.kind == 'array' then - -- TODO: At least for the array case, we could use metamethods to make get element sharing behavior - push{kind = 'array', value = table.move(arr.value, index + 1, index + count, 1, {})} - else - ps_error('typecheck', arg1, arg2, arg3) - end - end, - putinterval = function() - local from, arg2 = pop() - local index, arg1 = pop_int() - if type(from) ~= 'table' then ps_error('typecheck', arg1, arg2) end - if from.kind == 'executable' then - from = from.value - if type(from) ~= 'table' then ps_error('typecheck', arg1, arg2) end - end - if from.kind == 'string' then - local to = pop_string() - from = from.value - to.value = string.sub(to.value, 1, index) .. from .. string.sub(to.value, index + 1 + #from) - elseif from.kind == 'array' then - local to = pop_array() - table.move(from.value, 1, #from.value, index + 1, to.value) - else - ps_error('typecheck', arg1, arg2) - end - end, - - dict = function() - local size = pop_int() - push{kind = 'dict', value = lua.newtable(0, size)} - end, - begin = function() - local _ - _, _, dictionary_stack[#dictionary_stack + 1] = pop_dict() - end, - ['end'] = function() - if #dictionary_stack <= 3 then - ps_error'dictstackunderflow' - end - dictionary_stack[#dictionary_stack] = nil - end, - currentdict = function() - push(dictionary_stack[#dictionary_stack]) - end, - bind = function() - local d = pop() - push(d) - if type(d) ~= 'table' then ps_error'typecheck' end - if d.kind == 'executable' then - d = d.value - if type(d) ~= 'table' then ps_error'typecheck' end - end - if d.kind ~= 'array' then ps_error'typecheck' end - bind(d.value) - end, - def = function() - local value = pop() - local key = pop_key() - dictionary_stack[#dictionary_stack].value[key] = value - end, - store = function() - local value = pop() - local key = pop_key() - for i=#dictionary_stack, 1, -1 do - if dictionary_stack[i].value[key] ~= nil then - dictionary_stack[i].value[key] = value - return - end - end - dictionary_stack[#dictionary_stack].value[key] = value - end, - known = function() - local key = pop_key() - local dict = pop() - push(dict.value[key] ~= nil) - end, - where = function() - local key = pop_key() - for i = #dictionary_stack, 1, -1 do - local dict = dictionary_stack[i] - local value = dict.value[key] - if value ~= nil then - push(dict) - return push(true) - end - end - return push(false) - end, - load = function() - push(lookup(pop_key())) - end, - get = function() - local key = pop() - local obj = pop() - if type(obj) ~= 'table' then ps_error'typecheck' end - if obj.kind == 'executable' then - obj = obj.value - if type(obj) ~= 'table' then ps_error'typecheck' end - end - local val = obj.value - if obj.kind == 'string' then - push(key) key = pop_int() - if key < 0 or key >= #val then ps_error'rangecheck' end - push(string.byte(val, key+1)) - elseif obj.kind == 'array' then - push(key) key = pop_int() - if key < 0 or key >= #val then ps_error'rangecheck' end - push(val[key+1]) - elseif obj.kind == 'dict' then - push(key) key = pop_key() - push(val[key]) - else - ps_error'typecheck' - end - end, - put = function() - local value = pop() - local key = pop() - local obj = pop() - if type(obj) ~= 'table' then ps_error'typecheck' end - if obj.kind == 'executable' then - obj = obj.value - if type(obj) ~= 'table' then ps_error'typecheck' end - end - local val = obj.value - if obj.kind == 'string' then - push(key) key = pop_int() - if key < 0 or key >= #val then ps_error'rangecheck' end - push(value) value = pop_int() - obj.value = string.sub(val, 1, key) .. string.char(value) .. string.sub(val, key+2, #val) - elseif obj.kind == 'array' then - push(key) key = pop_int() - if key < 0 or key >= #val then ps_error'rangecheck' end - val[key+1] = value - elseif obj.kind == 'dict' then - push(key) key = pop_key() - val[key] = value - else - ps_error'typecheck' - end - end, - undef = function() - local key = pop_key() - local dict = pop_dict() - dict[key] = nil - end, - length = function() - local obj = pop() - if type(obj) == 'string' then - return push(#obj) - elseif type(obj) ~= 'table' then - ps_error'typecheck' - end - if obj.kind == 'executable' then - obj = obj.value - if type(obj) ~= 'table' then ps_error'typecheck' end - end - local val = obj.value - if obj.kind == 'string' then - push(#val) - elseif obj.kind == 'name' then - push(#val) - elseif obj.kind == 'array' then - push(#val) - elseif obj.kind == 'dict' then - local length = 0 - for _ in next, val do - length = length + 1 - end - push(length) - else - ps_error'typecheck' - end - end, - - matrix = function() - push{kind = 'array', value = {1, 0, 0, 1, 0, 0}} - end, - defaultmatrix = function() - local m = pop_array() - local mm = m.value - assert(#mm == 6) - mm[1], mm[2], mm[3], mm[4], mm[5], mm[6] = 10, 0, 0, 10, 0, 0 - push(m) - end, - currentmatrix = function() - local m = pop_array() - assert(#m.value == 6) - table.move(graphics_stack[#graphics_stack].matrix, 1, 6, 1, m.value) - push(m) - end, - currentlinewidth = function() - push(assert(graphics_stack[#graphics_stack].linewidth, 'linewidth has to be set before it is queried')) - end, - currentmiterlimit = function() - push(assert(graphics_stack[#graphics_stack].miterlimit, 'miterlimit has to be set before it is queried')) - end, - currentflat = function() - push(graphics_stack[#graphics_stack].flatness) - end, - setlinewidth = function() - local lw = pop_num() - graphics_stack[#graphics_stack].linewidth = lw - delayed_print(string.format('%.3f w', lw)) - end, - setlinejoin = function() - local linejoin = pop_int() - graphics_stack[#graphics_stack].linejoin = linejoin - delayed_print(string.format('%i j', linejoin)) - end, - setlinecap = function() - local linecap = pop_int() - graphics_stack[#graphics_stack].linecap = linecap - delayed_print(string.format('%i J', linecap)) - end, - setmiterlimit = function() - local ml = pop_int() - graphics_stack[#graphics_stack].miterlimit = ml - delayed_print(string.format('%.3f M', ml)) - end, - setstrokeadjust = function() - local sa = pop_bool() - graphics_stack[#graphics_stack].strokeadjust = sa - delayed_print(ExtGState[sa and '<</SA true>>' or '<</SA false>>']) - end, - setdash = function() - local offset = pop_num() - local patt = pop_array().value - graphics_stack[#graphics_stack].dash = {offset = offset, pattern = patt} - local mypatt = {} - for i=1, #patt do - mypatt[i] = string.format('%.3f', patt[i]) - end - delayed_print(string.format('[%s] %.3f d', table.concat(mypatt, ' '), offset)) - end, - setflat = function() - local flatness = pop_num() - graphics_stack[#graphics_stack].flatness = flatness - delayed_print(string.format('%.3f i', flatness)) - end, - currentpoint = function() - local current_point = assert(graphics_stack[#graphics_stack].current_point, 'nocurrentpoint') - push(current_point[1]) - push(current_point[2]) - end, - - moveto = function() - local y = pop_num() - local x = pop_num() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if current_path then - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2] = x, y, 'm' - local current_point = state.current_point - current_point[1], current_point[2] = x, y - else - state.current_path = {x, y, 'm'} - state.current_point = {x, y} - end - end, - rmoveto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local y = pop_num() - local x = pop_num() - local current_point = state.current_point - x, y = current_point[1] + x, current_point[2] + y - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2] = x, y, 'm' - current_point[1], current_point[2] = x, y - end, - lineto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local y = pop_num() - local x = pop_num() - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2] = x, y, 'l' - local current_point = state.current_point - current_point[1], current_point[2] = x, y - end, - rlineto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local y = pop_num() - local x = pop_num() - local current_point = state.current_point - x, y = x + current_point[1], y + current_point[2] - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2] = x, y, 'l' - current_point[1], current_point[2] = x, y - end, - curveto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local y3 = pop_num() - local x3 = pop_num() - local y2 = pop_num() - local x2 = pop_num() - local y1 = pop_num() - local x1 = pop_num() - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2], current_path[i+3], current_path[i+4], current_path[i+5], current_path[i+6] = x1, y1, x2, y2, x3, y3, 'c' - local current_point = state.current_point - current_point[1], current_point[2] = x3, y3 - end, - rcurveto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local current_point = state.current_point - local x0, y0 = current_point[1], current_point[2] - local y3 = pop_num() + y0 - local x3 = pop_num() + x0 - local y2 = pop_num() + y0 - local x2 = pop_num() + x0 - local y1 = pop_num() + y0 - local x1 = pop_num() + x0 - local i = #current_path + 1 - current_path[i], current_path[i+1], current_path[i+2], current_path[i+3], current_path[i+4], current_path[i+5], current_path[i+6] = x1, y1, x2, y2, x3, y3, 'c' - local current_point = state.current_point - current_point[1], current_point[2] = x3, y3 - end, - closepath = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - local current_point = state.current_point - if not current_path then return end - local x, y - for i=#current_path, 1, -1 do - if current_path[i] == 'm' then - x, y = assert(tonumber(current_path[i-2])), assert(tonumber(current_path[i-1])) - end - end - current_point[1], current_point[2] = assert(x), y - current_path[#current_path + 1] = 'h' - end, - - arc = function() - local a2 = pop_num() - local a1 = pop_num() - local r = pop_num() - local yc = pop_num() - local xc = pop_num() - while a2 < a1 do - a2 = a2 + 360 - end - drawarc(xc, yc, r, a1, a2) - end, - arcn = function() - local a2 = pop_num() - local a1 = pop_num() - local r = pop_num() - local yc = pop_num() - local xc = pop_num() - while a1 < a2 do - a1 = a1 + 360 - end - drawarc(xc, yc, r, a1, a2) - end, - arcto = function() - local state = graphics_stack[#graphics_stack] - local current_path = assert(state.current_path, 'nocurrentpoint') - local current_point = state.current_point - local x0, y0 = current_point[1], current_point[2] - - local r = pop_num() - local y2 = pop_num() - local x2 = pop_num() - local y1 = pop_num() - local x1 = pop_num() - - local dx1, dy1 = x1 - x0, y1 - y0 - local dx2, dy2 = x2 - x1, y2 - y1 - - local a1 = math.atan(dy1, dx1) - local a2 = math.atan(dy2, dx2) - - if a1 - pi > a2 then - a1 = a1 - two_pi - elseif a2 - pi > a1 then - a2 = a2 - two_pi - end - - if a1 > a2 then - a1 = a1 + math.pi/2 - a2 = a2 + math.pi/2 - else - a1 = a1 - math.pi/2 - a2 = a2 - math.pi/2 - end - - local ox1, oy1 = r * math.cos(a1), r * math.sin(a1) - local ox2, oy2 = r * math.cos(a2), r * math.sin(a2) - -- Now we need to calculate the intersection of the lines offset by o1/o2 - -- to determine the center. We inlin eth ematix inverse for performance and better handling of edge cases. - -- local t1, t2 = matrix_transform(0, 0, matrix_invert(dx1, dy1, dx2, dy2, ox2-ox1, oy2-oy1)) - local det = dx1*dy2 - dy1*dx2 - if math.abs(det) < 0.0000001 then - -- Just draw a line - push(x1) - push(y1) - systemdict.value.lineto() - push(x1) - push(y1) - push(x1) - push(y1) - return - end - local t1 = (ox1 - ox2) * dy2/det + (oy2 - oy1) * dx2/det - local cx, cy = x1 - ox1 + t1 * dx1, y1 - oy1 + t1 * dy1 - -- local ccx, ccy = x1 - ox2 - t2 * dx2, y1 - oy2 + t2 * dy2 - drawarc(cx, cy, r, a1*180/pi, a2*180/pi) - - push(cx + ox1) - push(cy + oy1) - push(cx + ox2) - push(cy + oy2) - end, - arct = function() - systemdict.value.arcto() - pop() - pop() - pop() - pop() - end, - - eoclip = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if not current_path then return end - flush_delayed(true) - for i = 1, #current_path do - if type(current_path[i]) == 'number' then - pdfprint(string.format('%.5f', current_path[i])) - else - pdfprint(current_path[i]) - end - end - pdfprint'W* n' - end, - clip = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if not current_path then return end - flush_delayed(true) - for i = 1, #current_path do - if type(current_path[i]) == 'number' then - pdfprint(string.format('%.5f', current_path[i])) - else - pdfprint(current_path[i]) - end - end - pdfprint'W n' - end, - eofill = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if not current_path then return end - current_path[#current_path+1] = 'f*' - for i = 1, #current_path do - if type(current_path[i]) == 'number' then - current_path[i] = string.format('%.5f', current_path[i]) - end - end - flush_delayed() - pdfprint((table.concat(current_path, ' '):gsub('%.?0+ ', ' '))) - state.current_path, state.current_point = nil - end, - fill = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if not current_path then return end - current_path[#current_path+1] = 'f' - for i = 1, #current_path do - if type(current_path[i]) == 'number' then - current_path[i] = string.format('%.5f', current_path[i]) - end - end - flush_delayed() - pdfprint((table.concat(current_path, ' '):gsub('%.?0+ ', ' '))) - state.current_path, state.current_point = nil - end, - stroke = function() - local state = graphics_stack[#graphics_stack] - local current_path = state.current_path - if not current_path then return end - current_path[#current_path+1] = 'S' - for i = 1, #current_path do - if type(current_path[i]) == 'number' then - current_path[i] = string.format('%.5f', current_path[i]) - end - end - flush_delayed() - pdfprint((table.concat(current_path, ' '):gsub('%.?0+ ', ' '))) - state.current_path, state.current_point = nil - end, - flattenpath = function() - local state = graphics_stack[#graphics_stack] - local old_path = state.current_path - if not old_path then return end - local new_path = {} - local last_x, last_y = nil, 0 - local saved_x, saved_y - local subpath_x, subpath_y - local last_op = 1 - local matrix = state.matrix - local tolerance = state.flatness / math.sqrt(matrix[1]*matrix[4]-matrix[2]*matrix[3]) - for i=1, #old_path do - local entry = old_path[i] - if type(entry) == 'string' then - if entry == 'c' then - assert(i - last_op == 6) - flatten(new_path, tolerance, saved_x, saved_y, table.unpack(old_path, last_op, i-1)) - table.move(old_path, last_op + 4, last_op + 5, #new_path + 1, new_path) - new_path[#new_path+1] = 'l' - else - if entry == 'm' then - subpath_x, subpath_y = last_x, last_y - elseif entry == 'h' then - last_x, last_y = subpath_x, subpath_y - end - table.move(old_path, last_op, i, #new_path + 1, new_path) - end - saved_x, saved_y = last_x, last_y - last_op = i + 1 - else - if last_y then - last_x, last_y = entry - else - last_y = entry - end - end - end - assert(last_op == #old_path + 1) - state.current_path = new_path - end, - - rectclip = function() - flush_delayed() - local top = pop() - if type(top) == 'table' and top.kind == 'executable' then - top = top.value - end - if type(top) == 'number' then - local h = top - local w = pop_num() - local y = pop_num() - local x = pop_num() - pdfprint((string.format('%.5f %.5f %.5f %.5f re W n', x, y, w, h):gsub('%.?0+ ', ' '))) - else - error'Unsupported rectclip variant' - end - end, - rectstroke = function() - flush_delayed() - local top = pop() - if type(top) == 'table' and top.kind == 'executable' then - top = top.value - end - if type(top) == 'number' then - local h = top - local w = pop_num() - local y = pop_num() - local x = pop_num() - pdfprint((string.format('%.5f %.5f %.5f %.5f re S', x, y, w, h):gsub('%.?0+ ', ' '))) - else - error'Unsupported rectstroke variant' - end - end, - rectfill = function() - flush_delayed() - local top = pop() - if type(top) == 'table' and top.kind == 'executable' then - top = top.value - end - if type(top) == 'number' then - local h = top - local w = pop_num() - local y = pop_num() - local x = pop_num() - pdfprint((string.format('%.5f %.5f %.5f %.5f re f', x, y, w, h):gsub('%.?0+ ', ' '))) - else - error'Unsupported rectfill variant' - end - end, - - shfill = function() - local shading_dict, arg1 = pop_dict() - flush_delayed() - local data_src - local pdf_dict = '' - for k, v in next, shading_dict do - if k == 'DataSource' then - data_src = v - else - pdf_dict = pdf_dict .. serialize_pdf(k) .. ' ' .. serialize_pdf(v) - end - end - if shading_dict.ShadingType == 4 then - assert(data_src) - if type(data_src) ~= 'table' then - push(arg1) - ps_error'typecheck' - end - if data_src.kind == 'string' then - data_src = data_src.value - elseif data_src.kind == 'array' then - data_src = data_src.value - local color_model = shading_dict.ColorSpace.value[1] - if type(color_model) == 'table' and color_model.kind == 'name' then - color_model = color_model.value - end - if color_model == 'DeviceRGB' then - color_model = 3 - elseif color_model == 'DeviceCMYK' then - color_model = 4 - elseif color_model == 'DeviceGray' then - color_model = 1 - else - error'Unsupported color model in Shading dictionary' - end - local components = color_model + 3 - pdf_dict = pdf_dict .. '/BitsPerCoordinate 24/BitsPerComponent 8/BitsPerFlag 8/Decode[-8192 8191 -8192 8191' .. string.rep(' 0 1', color_model) .. ']' - local data = '' - for i = 1, #data_src-components+1, components do - data = data .. string.pack('>BI3I3', data_src[i], (data_src[i+1]*1024+.5)//1 + 8388608, (data_src[i+2]*1024+.5)//1 + 8388608) - for j = i + 3, i + 2 + color_model do - data = data .. string.pack('B', (data_src[j]*255+.5)//1) - end - end - data_src = data - else - error'Unsupported DataSource variant' - end - local obj = write_shading(pdf_dict, data_src) - pdfprint(string.format('%s sh', write_shading(pdf_dict, data_src))) - end - end, - - scale = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - local mv = m.value - if #mv ~= 6 then error'Unexpected size of matrix' end - local y = pop_num() - local x = pop_num() - mv[1], mv[2], mv[3], mv[4], mv[5], mv[6] = x, 0, 0, y, 0, 0 - push(m) - else - push(m) - local y = pop_num() - local x = pop_num() - update_matrix(x, 0, 0, y, 0, 0) - end - end, - translate = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - local mv = m.value - if #mv ~= 6 then error'Unexpected size of matrix' end - local y = pop_num() - local x = pop_num() - mv[1], mv[2], mv[3], mv[4], mv[5], mv[6] = 1, 0, 0, 1, x, y - push(m) - else - push(m) - local y = pop_num() - local x = pop_num() - update_matrix(1, 0, 0, 1, x, y) - end - end, - rotate = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - local mv = m.value - if #mv ~= 6 then error'Unexpected size of matrix' end - local angle = math.rad(pop_num()) - local s, c = math.sin(angle), math.cos(angle) - mv[1], mv[2], mv[3], mv[4], mv[5], mv[6] = c, s, -s, c, 0, 0 - push(m) - else - push(m) - local angle = math.rad(pop_num()) - local s, c = math.sin(angle), math.cos(angle) - update_matrix(c, s, -s, c, 0, 0) - end - end, - transform = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - m = m.value - if #m ~= 6 then error'Unexpected size of matrix' end - else - push(m) - m = graphics_stack[#graphics_stack].matrix - end - local y = pop_num() - local x = pop_num() - x, y = matrix_transform(x, y, m[1], m[2], m[3], m[4], m[5], m[6]) - push(x) - push(y) - end, - itransform = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - m = m.value - if #m ~= 6 then error'Unexpected size of matrix' end - else - push(m) - m = graphics_stack[#graphics_stack].matrix - end - local y = pop_num() - local x = pop_num() - x, y = matrix_transform(x, y, matrix_invert(m[1], m[2], m[3], m[4], m[5], m[6])) - push(x) - push(y) - end, - dtransform = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - m = m.value - if #m ~= 6 then error'Unexpected size of matrix' end - else - push(m) - m = graphics_stack[#graphics_stack].matrix - end - local y = pop_num() - local x = pop_num() - x, y = matrix_transform(x, y, m[1], m[2], m[3], m[4], 0, 0) - push(x) - push(y) - end, - idtransform = function() - local m = pop() - if type(m) == 'table' and m.kind == 'array' then - m = m.value - if #m ~= 6 then error'Unexpected size of matrix' end - else - push(m) - m = graphics_stack[#graphics_stack].matrix - end - local y = pop_num() - local x = pop_num() - x, y = matrix_transform(x, y, matrix_invert(m[1], m[2], m[3], m[4], 0, 0)) - push(x) - push(y) - end, - concatmatrix = function() - local m3a = pop_array() - local m3 = m3a.value - if #m3 ~= 6 then error'Unexpected size of matrix' end - local m2 = pop_array().value - if #m2 ~= 6 then error'Unexpected size of matrix' end - local m1 = pop_array().value - if #m1 ~= 6 then error'Unexpected size of matrix' end - m3[1], m3[2], - m3[3], m3[4], - m3[5], m3[6] - = m1[1] * m2[1] + m1[2] * m2[3], m1[1] * m2[2] + m1[2] * m2[4], - m1[3] * m2[1] + m1[4] * m2[3], m1[3] * m2[2] + m1[4] * m2[4], - m1[5] * m2[1] + m1[6] * m2[3] + m2[5], m1[5] * m2[2] + m1[6] * m2[4] + m2[6] - push(m3a) - end, - invertmatrix = function() - local target = pop_array() - local T = target.value - assert(#T == 6) - local M = pop_array().value - assert(#M == 6) - T[1], T[2], T[3], T[4], T[5], T[6] - = matrix_invert(M[1], M[2], M[3], M[4], M[5], M[6]) - push(target) - end, - concat = function() - local m = pop_array().value - if #m ~= 6 then error'Unexpected size of matrix' end - update_matrix(m[1], m[2], m[3], m[4], m[5], m[6]) - end, - -- setmatrix is not supported in PDF, so we invert the old matrix first - setmatrix = function() - local m = pop() - if type(m) ~= 'table' or m.kind ~= 'array' then - ps_error'typecheck' - end - local m = m.value - if #m ~= 6 then ps_error'rangecheck' end - local old = graphics_stack[#graphics_stack].matrix - local pt = graphics_stack[#graphics_stack].current_point - local a, b, c, d, e, f = matrix_invert(old[1], old[2], old[3], old[4], old[5], old[6]) - update_matrix(a, b, c, d, e, f) - update_matrix(m[1], m[2], m[3], m[4], m[5], m[6]) - end, - setpdfcolor = function() - local pdf = pop_string().value - local color = graphics_stack[#graphics_stack].color - delayed_print(pdf) - color.space = {kind = 'array', value = {{kind = 'name', value = 'PDF'}}} - for i=2, #color do color[i] = nil end - color[1] = pdf - end, - setgray = function() - local g = pop_num() - local color = graphics_stack[#graphics_stack].color - color.space = {kind = 'array', value = {{kind = 'name', value = 'DeviceGray'}}} - for i=2, #color do color[i] = nil end - color[1] = g - delayed_print(string.format('%.3f g %.3f G', g, g)) - end, - setrgbcolor = function() - local b = pop_num() - local g = pop_num() - local r = pop_num() - local color = graphics_stack[#graphics_stack].color - color.space = {kind = 'array', value = {{kind = 'name', value = 'DeviceRGB'}}} - for i=4, #color do color[i] = nil end - color[1], color[2], color[3] = r, g, b - delayed_print(string.format('%.3f %.3f %.3f rg %.3f %.3f %.3f RG', r, g, b, r, g, b)) - end, - -- Conversion based on Wikipedia article about HSB colorspace - sethsbcolor = function() - local b = pop_num() - local s = pop_num() - local h = pop_num() - if b < 0 then b = 0 elseif b > 1 then b = 1 end - if s < 0 then s = 0 elseif s > 1 then s = 1 end - if h < 0 then h = 0 elseif h > 1 then h = 1 end - local hi, hf = math.modf(6 * h) - local p, q, t = b * (1 - s), b * (1 - s*hf), b * (1 - s * (1-hf)) - if hi == 0 or hi == 6 then - push(b) push(t) push(p) - elseif hi == 1 then - push(q) push(b) push(p) - elseif hi == 2 then - push(p) push(b) push(t) - elseif hi == 3 then - push(p) push(q) push(b) - elseif hi == 4 then - push(t) push(p) push(b) - elseif hi == 5 then - push(b) push(p) push(q) - end - return systemdict.value.setrgbcolor() - end, - setcmykcolor = function() - local k = pop_num() - local y = pop_num() - local m = pop_num() - local c = pop_num() - local color = graphics_stack[#graphics_stack].color - color.space = {kind = 'array', value = {{kind = 'name', value = 'DeviceCMYK'}}} - for i=5, #color do color[i] = nil end - color[1], color[2], color[3], color[4] = c, m, y, k - delayed_print(string.format('%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K', c, m, y, k, c, m, y, k)) - end, - ['.setopacityalpha'] = function() - error'Unsupported, use .setfillconstantalpha instead' - end, - ['.setfillconstantalpha'] = function() - local alpha = pop_num() - graphics_stack[#graphics_stack].fillconstantalpha = alpha - delayed_print(ExtGState['<</ca ' .. alpha .. '>>']) - end, - ['.setstrokeconstantalpha'] = function() - local alpha = pop_num() - graphics_stack[#graphics_stack].strokeconstantalpha = alpha - delayed_print(ExtGState['<</CA ' .. alpha .. '>>']) - end, - ['.currentalphaisshape'] = function() - local ais = graphics_stack[#graphics_stack].alphaisshape - if ais == nil then error'alphaisshape has to be set before it is queried' end - push(ais) - end, - ['.setalphaisshape'] = function() - local ais = pop_bool() - graphics_stack[#graphics_stack].alphaisshape = ais - delayed_print(ExtGState['<</AIS ' .. (ais and 'true' or 'false') .. '>>']) - end, - ['.currentblendmode'] = function() - local blendmode = graphics_stack[#graphics_stack].blendmode - if blendmode == nil then error'blendmode has to be set before it is queried' end - push{kind = 'name', value = blendmode} - end, - ['.setblendmode'] = function() - local blendmode = pop() - if type(blendmode) == 'string' then - elseif type(blendmode) == 'table' and blendmode.kind == 'name' then - blendmode = blendmode.value - else - push(blendmode) - ps_error'typecheck' - end - graphics_stack[#graphics_stack].blendmode = blendmode - delayed_print(ExtGState['<</BM /' .. blendmode .. '>>']) - end, - newpath = function() - local state = graphics_stack[#graphics_stack] - state.current_point = nil - state.current_path = nil - end, - - currentcolorspace = function() - local color = graphics_stack[#graphics_stack].color - if not color then error'Color has to be set before it is queried' end - push(color.space) - end, - currentcolor = function() - local color = graphics_stack[#graphics_stack].color - if not color then error'Color has to be set before it is queried' end - for i = 1, #color do - push(color[i]) - end - end, - currentcmykcolor = function() - local c, m, y, k - local color = graphics_stack[#graphics_stack].color - if not color then error'Color has to be set before it is queried' end - local space = color.space.value[1] - if type(space) == 'table' and space.kind == 'name' then space = space.value end - if space == 'DeviceRGB' then - c, m, y = 1 - color[1], 1 - color[2], 1 - color[3] - -- k = math.min(c, m, y) - -- TODO: Undercolor removal/black generation - -- local undercolor = undercolorremoval(k) - -- local undercolor = 0 - -- k = blackgeneration(k) - k = 0 - -- c, m, y = c - undercolor, y - undercolor, k - undercolor - elseif space == 'DeviceGray' then - c, m, y, k = 0, 0, 0, 1 - color[1] - elseif space == 'DeviceCMYK' then - c, m, y, k = color[1], color[2], color[3], color[4] - elseif space == 'PDF' then - c, m, y, k = 0, 0, 0, 1 - print('???', 'tocmyk', color[1]) - else - r, g, b, k = 0, 0, 0, 1 - end - push(r) - push(g) - push(b) - end, - currentgraycolor = function() - local g - local color = graphics_stack[#graphics_stack].color - if not color then error'Color has to be set before it is queried' end - local space = color.space.value[1] - if type(space) == 'table' and space.kind == 'name' then space = space.value end - if space == 'DeviceRGB' then - g = 0.3 * color[1] + 0.59 * color[2], 0.11 * color[3] - elseif space == 'DeviceGray' then - g = color[1] - elseif space == 'DeviceCMYK' then - g = math.min(1, math.max(0, 0.3 * color[1] + 0.59 * color[2] + 0.11 * color[3] + color[4])) - elseif space == 'PDF' then - g = 1 - print('???', 'togray', color[1]) - else - g = 1 - end - push(g) - end, - currentrgbcolor = function() - local r, g, b - local color = graphics_stack[#graphics_stack].color - if not color then error'Color has to be set before it is queried' end - local space = color.space.value[1] - if type(space) == 'table' and space.kind == 'name' then space = space.value end - if space == 'DeviceRGB' then - r, g, b = color[1], color[2], color[3] - elseif space == 'DeviceGray' then - r = color[1] - g, b = r, r - elseif space == 'DeviceCMYK' then - local c, m, y, k = color[1], color[2], color[3], color[4] - c, m, y = c+k, m+k, y+k - r, g, b = c >= 1 and 0 or 1-c, m >= 1 and 0 or 1-m, y >= 1 and 0 or 1-y - elseif space == 'PDF' then - r, g, b = 0, 0, 0 - print('???', 'torgb', color[1]) - else - r, g, b = 0, 0, 0 - end - push(r) - push(g) - push(b) - end, - currenthsbcolor = function() - systemdict.value.currentrgbcolor() - local b = pop_num() - local g = pop_num() - local r = pop_num() - local M, m = math.max(r, g, b), math.min(r, g, b) - local H - if M == m then - H = 0 - elseif M == r then - H = (g-b)/(M-m) / 6 - if H < 0 then H = H + 1 end - elseif M == g then - H = (b-r)/(M-m) / 6 + 1/3 - elseif assert(M == b) then - H = (r-g)/(M-m) / 6 + 2/3 - end - local S = M == 0 and 0 or (M-m)/M - local B = M - push(H) - push(S) - push(B) - end, - currentfont = function() - local f = graphics_stack[#graphics_stack].font - if f then - push(f) - else - push{kind = 'dict', value = { - FID = font.current(), - FontMatrix = {kind = 'array', value = {1, 0, 0, 1, 0, 0}}, - FontName = {kind = 'name', value = tex.fontname(font.current())}, - FontType = 0x1CA, - }} - end - end, - - gsave = function() - graphics_stack[#graphics_stack+1] = table.copy(graphics_stack[#graphics_stack]) - graphics_stack[#graphics_stack].saved_delayed = delayed - delayed = { - text = {}, - matrix = {1, 0, 0, 1, 0, 0}, - } - end, - grestore = function() - local saved_delayed = graphics_stack[#graphics_stack].saved_delayed - if saved_delayed then - delayed = saved_delayed - else - pdfprint'Q' - reset_delayed(delayed) - end - graphics_stack[#graphics_stack] = nil - end, - - setglobal = pop_bool, - - flush = function() - io.stdout:flush() - end, - print = function() - local msg = pop_string() - io.stdout:write(msg.value) - end, - stack = function() - for i=#operand_stack, 1, -1 do - texio.write_nl('term and log', ps_to_string(operand_stack[i])) - end - end, - ['='] = function() - texio.write_nl('term and log', ps_to_string(pop())) - end, - ['=='] = function() -- FIXME: Should give a better representation - texio.write_nl('term and log', ps_to_string(pop())) - end, - - stringwidth = function() - local state = graphics_stack[#graphics_stack] - local rawpsfont = assert(state.font, 'invalidfont') - local str = pop_string().value - local psfont = rawpsfont.value - local fid = psfont.FID - local matrix = psfont.FontMatrix.value - local fonttype = psfont.FontType - if fonttype ~= 0x1CA and fonttype ~= 3 then - texio.write_nl'Font support is not implemented' - return - end - local w = 0 - if fonttype == 0x1CA then - local characters = assert(font.getfont(fid)).characters - for b in string.bytes(str) do - local char = characters[b] - w = w + (char and char.width or 0) - end - w = w/65781.76 - elseif fonttype == 3 then - local saved_delayed = delayed - delayed = { - text = {}, - matrix = {1, 0, 0, 1, 0, 0}, - } - local saved_saved_delayed = state.saved_delayed - state.saved_delayed = nil - local saved_pdfprint = pdfprint - pdfprint = gobble - for b in string.bytes(str) do - systemdict.value.gsave() - local state = graphics_stack[#graphics_stack] - state.current_point, state.current_path = nil - push(rawpsfont) - push(b) - local this_w - char_width_storage = function(width) - this_w = width - end - execute_tok(psfont.BuildChar) -- FIXME(maybe): Switch to BuildGlyph? - systemdict.value.grestore() - w = w + assert(this_w, 'Type 3 character failed to set width') - update_matrix(1, 0, 0, 1, this_w, 0) - end - update_matrix(1, 0, 0, 1, -w, 0) - pdfprint = saved_pdfprint - state.saved_delayed = saved_saved_delayed - delayed = saved_delayed - end - local x, y = matrix_transform(w, 0, - matrix[1], matrix[2], - matrix[3], matrix[4], - 0, 0) - push(x) - push(y) - end, - ashow = function() - local str, arg3 = pop_string() - local ay, arg2 = pop_num(arg3) - local ax, arg1 = pop_num(arg2, arg3) - local res, err = generic_show(str, ax, ay) - if not res then - ps_error(err, arg1, arg2, arg3) - end - end, - show = function() - local str, orig = pop_string() - local res, err = generic_show(str) - if not res then - ps_error(err, orig) - end - end, - definefont = function() - local fontdict, raw_fontdict = pop_dict() - local fontkey = pop_key() - fontdict.FontMatrix = fontdict.FontMatrix or {kind = 'array', value = {1, 0, 0, 1, 0, 0}} - if assert(fontdict.FontType) == 0x1CA then - local fontname = fontdict.FontName - if type(fontname) == 'table' and fontname.kind == 'name' then - fontname = fontname.value - elseif type(fontname) ~= 'string' then - pushs(fontkey, raw_fontdict) - ps_error'typecheck' - end - local fid = fonts.definers.read(fontname, 65782) - if not fid then ps_error'invalidfont' end - if not tonumber(fid) then - local data = fid - fid = font.define(data) - fonts.definers.register(data, fid) - end - fontdict.FID = fid - else - texio.write_nl'definefont is not implemnted. Pushing dummy font.' - end - FontDirectory[fontkey] = raw_fontdict - push(raw_fontdict) - end, - makefont = function() - local m = pop_array().value - if #m ~= 6 then error'Unexpected size of matrix' end - local fontdict = pop_dict() - local new_fontdict = {} - for k,v in next, fontdict do - new_fontdict[k] = v - end - local old_m = assert(fontdict.FontMatrix, 'invalidfont').value - new_fontdict.FontMatrix = {kind = 'array', value = { - old_m[1] * m[1] + old_m[2] * m[3], old_m[1] * m[2] + old_m[2] * m[4], - old_m[3] * m[1] + old_m[4] * m[3], old_m[3] * m[2] + old_m[4] * m[4], - old_m[5] * m[1] + old_m[6] * m[3] + m[5], old_m[5] * m[2] + old_m[6] * m[4] + m[6], - }} - push{kind = 'dict', value = new_fontdict} - end, - scalefont = function() - local factor = pop_num() - local fontdict = pop_dict() - local new_fontdict = {} - for k,v in next, fontdict do - new_fontdict[k] = v - end - local old_m = assert(fontdict.FontMatrix, 'invalidfont').value - new_fontdict.FontMatrix = {kind = 'array', value = { - factor * old_m[1], factor * old_m[2], - factor * old_m[3], factor * old_m[4], - factor * old_m[5], factor * old_m[6], - }} - push{kind = 'dict', value = new_fontdict} - end, - setfont = function() - local _, _, fontdict = pop_dict() - local state = graphics_stack[#graphics_stack] - state.font = fontdict - end, - ['.findfontid'] = function() - local fid = pop_int() - - if font.frozen(fid) == nil then - push(fid) - ps_error'invalidfont' - end - local fontsize_inv = 65782/pdf.getfontsize(fid) - local fontname = tex.fontname(fid) - return push{kind = 'dict', value = { - FID = fid, - FontMatrix = {kind = 'array', value = {fontsize_inv, 0, 0, fontsize_inv, 0, 0}}, - FontName = {kind = 'name', value = fontname}, - FontType = 0x1CA, - }} - end, - findfont = function() - local fontname = pop_key() - local fontdict = FontDirectory[fontname] - if fontdict then push(fontdict) return end - - fontname = font_aliases[fontname] or fontname - local fid = fonts.definers.read(fontname, 65782) - if not fid then ps_error'invalidfont' end - if not tonumber(fid) then - local data = fid - fid = font.define(data) - fonts.definers.register(data, fid) - end - return push{kind = 'dict', value = { - FID = fid, - FontMatrix = {kind = 'array', value = {1, 0, 0, 1, 0, 0}}, - FontName = {kind = 'name', value = fontname}, - FontType = 0x1CA, - }} - end, - selectfont = function() - systemdict.value.exch() - systemdict.value.findfont() - systemdict.value.exch() - if type(operand_stack[#operand_stack]) == 'number' then - systemdict.value.scalefont() - else - systemdict.value.makefont() - end - systemdict.value.setfont() - end, - - setcharwidth = function() - -- Pop and ignore the advance height -- FIXME(maybe) - pop_num() - assert(char_width_storage, 'undefined')(pop_num()) - char_width_storage = nil - end, - setcachedevice = function() - -- First pop and ignore the bounding box - pop_num() - pop_num() - pop_num() - pop_num() - -- Fallback to setcharwidth - systemdict.value.setcharwidth() - end, - setcachedevice2 = function() - -- First pop additional entries for setccachedevice2 -- TODO: Implement other writing modes - pop_num() - pop_num() - pop_num() - pop_num() - -- Fallback to setcachedevice - systemdict.value.setcachedevice() - end, - - findresource = function() - local category = pop_key() - local catdict = ResourceCategories.value[category] - if not catdict then - push(category) - print('undefined resource category', category) - ps_error'undefined' - end - local dict_height = #dictionary_stack + 1 - dictionary_stack[dict_height] = catdict - execute_tok'FindResource' - if #dictionary_stack ~= dict_height or dictionary_stack[dict_height] ~= catdict then - error'Messed up dictionary stack in custom resource' - end - dictionary_stack[dict_height] = nil - end, - resourcestatus = function() - local category = pop_key() - local catdict = ResourceCategories.value[category] - if not catdict then - push(category) - print('undefined resource category', category) - ps_error'undefined' - end - local dict_height = #dictionary_stack + 1 - dictionary_stack[dict_height] = catdict - execute_tok'ResourceStatus' - if #dictionary_stack ~= dict_height or dictionary_stack[dict_height] ~= catdict then - error'Messed up dictionary stack in custom resource' - end - dictionary_stack[dict_height] = nil - end, - resourceforall = function() - local category = pop_key() - local catdict = ResourceCategories.value[category] - if not catdict then - push(category) - print('undefined resource category', category) - ps_error'undefined' - end - local dict_height = #dictionary_stack + 1 - dictionary_stack[dict_height] = catdict - execute_tok'ResourceForAll' - if #dictionary_stack ~= dict_height or dictionary_stack[dict_height] ~= catdict then - error'Messed up dictionary stack in custom resource' - end - dictionary_stack[dict_height] = nil - end, - defineresource = function() - local category = pop_key() - local catdict = ResourceCategories.value[category] - if not catdict then - push(category) - print('undefined resource category', category) - ps_error'undefined' - end - local dict_height = #dictionary_stack + 1 - dictionary_stack[dict_height] = catdict - execute_tok'DefineResource' - if #dictionary_stack ~= dict_height or dictionary_stack[dict_height] ~= catdict then - error'Messed up dictionary stack in custom resource' - end - dictionary_stack[dict_height] = nil - end, - undefineresource = function() - local category = pop_key() - local catdict = ResourceCategories.value[category] - if not catdict then - push(category) - print('undefined resource category', category) - ps_error'undefined' - end - local dict_height = #dictionary_stack + 1 - dictionary_stack[dict_height] = catdict - execute_tok'UndefineResource' - if #dictionary_stack ~= dict_height or dictionary_stack[dict_height] ~= catdict then - error'Messed up dictionary stack in custom resource' - end - dictionary_stack[dict_height] = nil - end, - - realtime = function() - push(os.gettimeofday() * 1000 // 1) - end, - - rrand = function() - push(rrand()) - end, - srand = function() - srand(pop_int()) - end, - rand = function() - push(rand()) - end, - - type = function() - local val = pop() - local tval = type(val) - if tval == 'table' and val.kind == 'executable' then - val = val.value - tval = type(val) - end - local tname - if tval == 'string' then - tname = 'nametype' - elseif tval == 'number' then - tname = math.type(val) == 'integer' and 'integertype' or 'realtype' - elseif tval == 'boolean' then - tname = 'booleantype' - elseif tval == 'function' then - tname = 'operatortype' - elseif tval == 'table' then - local kind = val.kind - if kind == 'name' then - tname = 'nametype' - elseif kind == 'operator' then - tname = 'operatortype' - elseif kind == 'array' then - tname = 'arraytype' - elseif kind == 'dict' then - tname = 'dicttype' - elseif kind == 'dict' then - tname = 'dicttype' - elseif kind == 'null' then - tname = 'nulltype' - elseif kind == 'mark' then - tname = 'nulltype' - elseif kind == 'string' then - tname = 'stringtype' - else - assert(false, 'Unexpected type') - end - else - assert(false, 'Unexpected type') - end - push(tname) - -- filetype - -- fonttype - -- gstatetype (LanguageLevel 2) - -- packedarraytype (LanguageLevel 2) - -- savetype - end, - xcheck = function() - local a = pop() - local ta = type(a) - push(ta == 'function' or ta == 'name' or (ta == 'table' and a.kind == 'executable')) - end, - cvlit = function() - local a = pop() - local ta = type(a) - if (ta == 'table' and a.kind == 'executable') or ta == 'string' or ta == 'function' then - return push(a.value) - end - if ta == 'string' then - return push{kind = 'name', value = a} - end - if ta == 'function' then - return push{kind = 'operator', value = a} - end - return push(a) - end, - cvx = function() - local a = pop() - local ta = type(a) - if (ta == 'table' and a.kind == 'executable') or ta == 'string' or ta == 'function' then - return push(a) - elseif ta == 'table' and (a.kind == 'operator' or a.kind == 'name') then - return push(a.value) - else - return push{kind = 'executable', value = a} - end - end, - exec = function() - return execute_tok((pop())) - end, - stopped = function() - local proc = pop() - local success, err = pcall(execute_tok, proc) - if success then - push(false) - elseif err == 'stop' or true then -- Since we don implement error handlers, all errors act like their error handler included "stop" - push(true) - end - end, - stop = function() - error'stop' - end, - exit = function() - error(exitmarker) - end, - quit = function() - os.exit() - end, - run = function() - local filename = pop_string().value - local resolved = kpse.find_file(filename, 'PostScript header') - if not resolved then - error(string.format('Unable to find file %q.', filename)) - end - local f = assert(io.open(resolved, 'rb')) - local data = maybe_decompress(f:read'a') - f:close() - return execute_tok{kind = 'executable', value = {kind = 'string', value = data}} - end, - - -- We don't implement local/global separation, so we ignore setglobal and always report currentglobal as true - setglobal = function() - pop() - end, - currentglobal = function() - push(true) - end, - - closefile = function() - local f = pop() - f:close() - end, - file = function() - local access = pop_string() - local orig_filename = pop_string() - local filename = orig_filename.value - if access.value:sub(1, 1) == 'a' then - filename = kpse.find_file(filename) - if not filename then - push(orig_filename) - push(access) - ps_error'undefinedfilename' - end - end - if access.value == '' then - push(orig_filename) - push(access) - ps_error'invalidfileaccess' - end - local f = io.open(filename, access.value) - if not f then - push(orig_filename) - push(access) - ps_error'invalidfileaccess' - end - push(f) - end, - write = function() - local data = pop_num() - local f = pop() - data = data % 256 - f:write(string.char(data)) - end, - writestring = function() - local data = pop_string().value - local f = pop() - f:write(data) - end, - readstring = function() - local target = pop_string() - local f = pop() - local data = f:read(#target.value) - if #target.value == #data then - target.value = data - push(target) - push(true) - systemdict.value.stack() - else - target = str_view(target, 1, #data) - target.value = data - push(target) - push(false) - systemdict.value.stack() - end - end, - readline = function() - local target = pop_string() - local f = pop() - local data = f:read'L' -- TODO: \r should be accepted as EOL marker too - if data then - if #data > #target.value then - push(f) - push(target) - ps_error'rangecheck' - end - target = str_view(target, 1, #data) - target.value = data - push(target) - push(true) - else - push{kind = 'string', value = ''} - push(false) - end - end, - - token = function() - local arg = pop() - if type(arg) ~= 'table' or arg.kind ~= 'string' then - push(arg) - if type(arg) == 'userdata' and arg.read then - error'token applied to file arguments is no yet implemented' - else - ps_error'typecheck' - end - end - local str = arg.value - local tok, after = l.match(any_object * l.Cp(), str) - if after == nil then - if l.match(whitespace^-1 * -1, str) then - push(false) - else - push(arg) - ps_error'syntaxerror' - end - else - push(str_view(arg, after, #str - after + 1)) - push(tok) - push(true) - end - end, - - revision = 1000, - ['true'] = true, - ['false'] = false, - systemdict = systemdict, - globaldict = globaldict, - FontDirectory = FontDirectory, - - ISOLatin1Encoding = {kind = 'array', value = { - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = 'space'}, - {kind = 'name', value = 'exclam'}, - {kind = 'name', value = 'quotedbl'}, - {kind = 'name', value = 'numbersign'}, - {kind = 'name', value = 'dollar'}, - {kind = 'name', value = 'percent'}, - {kind = 'name', value = 'ampersand'}, - {kind = 'name', value = 'quoteright'}, - {kind = 'name', value = 'parenleft'}, - {kind = 'name', value = 'parenright'}, - {kind = 'name', value = 'asterisk'}, - {kind = 'name', value = 'plus'}, - {kind = 'name', value = 'comma'}, - {kind = 'name', value = 'minus'}, - {kind = 'name', value = 'period'}, - {kind = 'name', value = 'slash'}, - {kind = 'name', value = 'zero'}, - {kind = 'name', value = 'one'}, - {kind = 'name', value = 'two'}, - {kind = 'name', value = 'three'}, - {kind = 'name', value = 'four'}, - {kind = 'name', value = 'five'}, - {kind = 'name', value = 'six'}, - {kind = 'name', value = 'seven'}, - {kind = 'name', value = 'eight'}, - {kind = 'name', value = 'nine'}, - {kind = 'name', value = 'colon'}, - {kind = 'name', value = 'semicolon'}, - {kind = 'name', value = 'less'}, - {kind = 'name', value = 'equal'}, - {kind = 'name', value = 'greater'}, - {kind = 'name', value = 'question'}, - {kind = 'name', value = 'at'}, - {kind = 'name', value = 'A'}, - {kind = 'name', value = 'B'}, - {kind = 'name', value = 'C'}, - {kind = 'name', value = 'D'}, - {kind = 'name', value = 'E'}, - {kind = 'name', value = 'F'}, - {kind = 'name', value = 'G'}, - {kind = 'name', value = 'H'}, - {kind = 'name', value = 'I'}, - {kind = 'name', value = 'J'}, - {kind = 'name', value = 'K'}, - {kind = 'name', value = 'L'}, - {kind = 'name', value = 'M'}, - {kind = 'name', value = 'N'}, - {kind = 'name', value = 'O'}, - {kind = 'name', value = 'P'}, - {kind = 'name', value = 'Q'}, - {kind = 'name', value = 'R'}, - {kind = 'name', value = 'S'}, - {kind = 'name', value = 'T'}, - {kind = 'name', value = 'U'}, - {kind = 'name', value = 'V'}, - {kind = 'name', value = 'W'}, - {kind = 'name', value = 'X'}, - {kind = 'name', value = 'Y'}, - {kind = 'name', value = 'Z'}, - {kind = 'name', value = 'bracketleft'}, - {kind = 'name', value = 'backslash'}, - {kind = 'name', value = 'bracketright'}, - {kind = 'name', value = 'asciicircum'}, - {kind = 'name', value = 'underscore'}, - {kind = 'name', value = 'quoteleft'}, - {kind = 'name', value = 'a'}, - {kind = 'name', value = 'b'}, - {kind = 'name', value = 'c'}, - {kind = 'name', value = 'd'}, - {kind = 'name', value = 'e'}, - {kind = 'name', value = 'f'}, - {kind = 'name', value = 'g'}, - {kind = 'name', value = 'h'}, - {kind = 'name', value = 'i'}, - {kind = 'name', value = 'j'}, - {kind = 'name', value = 'k'}, - {kind = 'name', value = 'l'}, - {kind = 'name', value = 'm'}, - {kind = 'name', value = 'n'}, - {kind = 'name', value = 'o'}, - {kind = 'name', value = 'p'}, - {kind = 'name', value = 'q'}, - {kind = 'name', value = 'r'}, - {kind = 'name', value = 's'}, - {kind = 'name', value = 't'}, - {kind = 'name', value = 'u'}, - {kind = 'name', value = 'v'}, - {kind = 'name', value = 'w'}, - {kind = 'name', value = 'x'}, - {kind = 'name', value = 'y'}, - {kind = 'name', value = 'z'}, - {kind = 'name', value = 'braceleft'}, - {kind = 'name', value = 'bar'}, - {kind = 'name', value = 'braceright'}, - {kind = 'name', value = 'asciitilde'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = 'dotlessi'}, - {kind = 'name', value = 'grave'}, - {kind = 'name', value = 'acute'}, - {kind = 'name', value = 'circumflex'}, - {kind = 'name', value = 'tilde'}, - {kind = 'name', value = 'macron'}, - {kind = 'name', value = 'breve'}, - {kind = 'name', value = 'dotaccent'}, - {kind = 'name', value = 'dieresis'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = 'ring'}, - {kind = 'name', value = 'cedilla'}, - {kind = 'name', value = '.notdef'}, - {kind = 'name', value = 'hungarumlaut'}, - {kind = 'name', value = 'ogonek'}, - {kind = 'name', value = 'caron'}, - {kind = 'name', value = 'space'}, - {kind = 'name', value = 'exclamdown'}, - {kind = 'name', value = 'cent'}, - {kind = 'name', value = 'sterling'}, - {kind = 'name', value = 'currency'}, - {kind = 'name', value = 'yen'}, - {kind = 'name', value = 'brokenbar'}, - {kind = 'name', value = 'section'}, - {kind = 'name', value = 'dieresis'}, - {kind = 'name', value = 'copyright'}, - {kind = 'name', value = 'ordfeminine'}, - {kind = 'name', value = 'guillemotleft'}, - {kind = 'name', value = 'logicalnot'}, - {kind = 'name', value = 'hyphen'}, - {kind = 'name', value = 'registered'}, - {kind = 'name', value = 'macron'}, - {kind = 'name', value = 'degree'}, - {kind = 'name', value = 'plusminus'}, - {kind = 'name', value = 'twosuperior'}, - {kind = 'name', value = 'threesuperior'}, - {kind = 'name', value = 'acute'}, - {kind = 'name', value = 'mu'}, - {kind = 'name', value = 'paragraph'}, - {kind = 'name', value = 'periodcentered'}, - {kind = 'name', value = 'cedilla'}, - {kind = 'name', value = 'onesuperior'}, - {kind = 'name', value = 'ordmasculine'}, - {kind = 'name', value = 'guillemotright'}, - {kind = 'name', value = 'onequarter'}, - {kind = 'name', value = 'onehalf'}, - {kind = 'name', value = 'threequarters'}, - {kind = 'name', value = 'questiondown'}, - {kind = 'name', value = 'Agrave'}, - {kind = 'name', value = 'Aacute'}, - {kind = 'name', value = 'Acircumflex'}, - {kind = 'name', value = 'Atilde'}, - {kind = 'name', value = 'Adieresis'}, - {kind = 'name', value = 'Aring'}, - {kind = 'name', value = 'AE'}, - {kind = 'name', value = 'Ccedilla'}, - {kind = 'name', value = 'Egrave'}, - {kind = 'name', value = 'Eacute'}, - {kind = 'name', value = 'Ecircumflex'}, - {kind = 'name', value = 'Edieresis'}, - {kind = 'name', value = 'Igrave'}, - {kind = 'name', value = 'Iacute'}, - {kind = 'name', value = 'Icircumflex'}, - {kind = 'name', value = 'Idieresis'}, - {kind = 'name', value = 'Eth'}, - {kind = 'name', value = 'Ntilde'}, - {kind = 'name', value = 'Ograve'}, - {kind = 'name', value = 'Oacute'}, - {kind = 'name', value = 'Ocircumflex'}, - {kind = 'name', value = 'Otilde'}, - {kind = 'name', value = 'Odieresis'}, - {kind = 'name', value = 'multiply'}, - {kind = 'name', value = 'Oslash'}, - {kind = 'name', value = 'Ugrave'}, - {kind = 'name', value = 'Uacute'}, - {kind = 'name', value = 'Ucircumflex'}, - {kind = 'name', value = 'Udieresis'}, - {kind = 'name', value = 'Yacute'}, - {kind = 'name', value = 'Thorn'}, - {kind = 'name', value = 'germandbls'}, - {kind = 'name', value = 'agrave'}, - {kind = 'name', value = 'aacute'}, - {kind = 'name', value = 'acircumflex'}, - {kind = 'name', value = 'atilde'}, - {kind = 'name', value = 'adieresis'}, - {kind = 'name', value = 'aring'}, - {kind = 'name', value = 'ae'}, - {kind = 'name', value = 'ccedilla'}, - {kind = 'name', value = 'egrave'}, - {kind = 'name', value = 'eacute'}, - {kind = 'name', value = 'ecircumflex'}, - {kind = 'name', value = 'edieresis'}, - {kind = 'name', value = 'igrave'}, - {kind = 'name', value = 'iacute'}, - {kind = 'name', value = 'icircumflex'}, - {kind = 'name', value = 'idieresis'}, - {kind = 'name', value = 'eth'}, - {kind = 'name', value = 'ntilde'}, - {kind = 'name', value = 'ograve'}, - {kind = 'name', value = 'oacute'}, - {kind = 'name', value = 'ocircumflex'}, - {kind = 'name', value = 'otilde'}, - {kind = 'name', value = 'odieresis'}, - {kind = 'name', value = 'divide'}, - {kind = 'name', value = 'oslash'}, - {kind = 'name', value = 'ugrave'}, - {kind = 'name', value = 'uacute'}, - {kind = 'name', value = 'ucircumflex'}, - {kind = 'name', value = 'udieresis'}, - {kind = 'name', value = 'yacute'}, - {kind = 'name', value = 'thorn'}, - {kind = 'name', value = 'ydieresis'}, - }} -}} -systemdict.value.systemdict = systemdict -dictionary_stack = {systemdict, globaldict, userdict} --- local execution_stack = {} -- Currently not implemented - --- Quite some stuff is missing here since these aren't implemented yet. Anyway mostly useful for testing. -ResourceCategories.value.Font = {kind = 'dict', value = { - Category = {kind = 'name', value = 'Font'}, - InstanceType = 'dicttype', - DefineResource = systemdict.value.definefont, - FindResource = systemdict.value.findfont, -}} - -ResourceCategories.value.Generic = {kind = 'dict', value = { - Category = {kind = 'name', value = 'Generic'}, - DefineResource = function() - local instance = pop() - local key = pop_key() - execute_tok'.Instances' - local instances = pop_dict() - instances[key] = instance - push(instance) - end, - UndefineResource = function() - local key = pop_key() - execute_tok'.Instances' - local instances = pop_dict() - instances[key] = nil - end, - FindResource = function() - local key = pop_key() - execute_tok'.Instances' - local instances = pop_dict() - local instance = instances[key] - if instance then - push(instance) - return - end - push(key) - ps_error'undefinedresource' - end, - -- ResourceStatus = function() - -- local key = pop_key() - -- execute_tok'.Instances' - -- local instances = pop_dict() - -- local instance = instances[key] - -- if instance then - -- push(instance) - -- return - -- end - -- push(key) - -- ps_error'undefinedresource' - -- end, - -- ResourceForAll = function() - -- local key = pop_key() - -- execute_tok'.Instances' - -- local instances = pop_dict() - -- local instance = instances[key] - -- if instance then - -- push(instance) - -- return - -- end - -- push(key) - -- ps_error'undefinedresource' - -- end, - ['.Instances'] = {kind = 'dict', value = {}}, -}} - -local register_texbox do - local meta = {__gc = function(t) node.direct.free(t.box) end} - local dict = {} - ResourceCategories.value['.TeXBox'] = {kind = 'dict', value = { - Category = {kind = 'name', value = '.TeXBox'}, - DefineResource = function() - push{kind = 'name', value = '.TeXBox'} - ps_error'undefined' - end, - UndefineResource = function() - local key = pop_key() - dict[key] = nil - end, - FindResource = function() - local key = pop_key() - local instance = dict[key] - if instance then - push(instance) - return - end - push(key) - ps_error'undefinedresource' - end, - }} - local id = 0 - function register_texbox(box) - id = id + 1 - box = setmetatable({box = node.direct.todirect(box)}, meta) - local op = function() flush_delayed() vf.push() vf.node(box.box) vf.pop() end - lua_node_lookup[op] = box - dict[id] = op - return id - end -end - -ResourceCategories.value.Category = {kind = 'dict', value = { - Category = {kind = 'name', value = 'Generic'}, - InstanceType = 'dicttype', - DefineResource = function() - local instance = pop() - local key = pop_key() - ResourceCategories.value[key] = instance - push(instance) - end, - UndefineResource = function() - local key = pop_key() - ResourceCategories.value[key] = nil - end, - FindResource = function() - local key = pop_key() - local instance = ResourceCategories.value[key] - if instance then - push(instance) - return - end - push(key) - ps_error'undefinedresource' - end, - -- ResourceStatus = function() - -- local key = pop_key() - -- execute_tok'.Instances' - -- local instances = pop_dict() - -- local instance = instances[key] - -- if instance then - -- push(instance) - -- return - -- end - -- push(key) - -- ps_error'undefinedresource' - -- end, - -- ResourceForAll = function() - -- local key = pop_key() - -- execute_tok'.Instances' - -- local instances = pop_dict() - -- local instance = instances[key] - -- if instance then - -- push(instance) - -- return - -- end - -- push(key) - -- ps_error'undefinedresource' - -- end, -}} - -function execute_tok(tok, suppress_proc) - local ttok = type(tok) - if ttok == 'string' then - return execute_tok(lookup(tok)) - elseif ttok == 'function' then - return tok() - elseif ttok == 'table' and tok.kind == 'executable' then - local vtok = tok.value - ttok = type(vtok) - if suppress_proc and ttok == 'table' and tok.value.kind == 'array' then - return push(tok) - end - if ttok == 'table' then - local kind = vtok.kind - if kind == 'array' then - return execute_ps(vtok.value) - elseif kind == 'string' then - return execute_ps(assert(parse_ps(vtok.value), 'syntaxerror')) - else - error'Unimplemented' - end - elseif ttok == 'number' then - return push(tok) - else - error'Unimplemented' - end - else - return push(tok) - end -end - -function execute_ps(tokens) - for i=1, #tokens do - execute_tok(tokens[i], true) - end -end -local any_object_or_end = any_object * l.Cp() + whitespace^-1 * -1 * l.Cc(nil) + l.Cp() * l.Cc(false) -function execute_string(str, context) - local pos = 1 - while true do - local tok - tok, pos = any_object_or_end:match(str, pos) - if pos then - local success, err = pcall(execute_tok, tok, true) - if not success then - if context and type(err) == 'table' and err.pserror and not err.context then - err.tok = tok - err.context = context - end - error(err) - end - elseif pos == false then - ps_error'syntaxerror' - else - break - end - end -end - -local func = luatexbase.new_luafunction'luaPSTheader' -token.set_lua('luaPSTheader', func, 'protected') -lua.get_functions_table()[func] = function() - local stack_depth = #operand_stack - local filename = token.scan_argument() - local f = io.open(kpse.find_file(filename, 'PostScript header'), 'r') - local src = f:read'a' - f:close() - execute_string(src, filename) - if #operand_stack ~= stack_depth then - error'Unexpected values on operand stack' - end -end - -local func = luatexbase.new_luafunction'showPS' -token.set_lua('showPS', func, 'protected') -lua.get_functions_table()[func] = function() - local command = token.scan_argument(true) - local tokens = parse_ps(command) - execute_ps(tokens) - for i = 1, #operand_stack do - local op = operand_stack[i] - operand_stack[i] = nil - if type(op) == 'table' then - print(op.kind, op.value) - else - print(op) - end - end -end - -local ps_tokens, ps_direct, ps_context, ps_pos_x, ps_pos_y -local fid = font.define{ - name = 'dummy virtual font for PS rendering', - -- type = 'virtual', - characters = { - [0x1F3A8] = { - commands = { - {'lua', function(fid) - local n = node.new('glyph', 256) - n.font = fid - n.char = 1 - assert(not ps_pos_x) - ps_pos_x, ps_pos_y = pdf.getpos() - n.xoffset = -ps_pos_x - n.yoffset = -ps_pos_y - n = node.hpack(n) - vf.node(node.direct.todirect(n)) - node.free(n) - end} - } - }, - [1] = { - commands = { - {'lua', function() - local tokens, direct = assert(ps_tokens), ps_direct - ps_tokens = nil - local x, y = pdf.getpos() - local height = #operand_stack - operand_stack[height + 1], operand_stack[height + 2] = ps_pos_x/65781.76, ps_pos_y/65781.76 - ps_pos_x, ps_pos_y = nil - if direct then - systemdict.value.moveto() - else - systemdict.value.gsave() - systemdict.value.translate() - end - local success, err = pcall(execute_string, tokens, ps_context) - if not success then - if type(err) == 'table' and err.pserror then - tex.error(string.format('luapstricks: %q error occured while executing PS code from %q', err.pserror, err.context), { - string.format('The error occured while executing the PS command %q.\n%s', err.tok, err.trace) - }) - else - error(err, 0) - end - end - flush_delayed() - if not direct then - systemdict.value.grestore() - local new_height = #operand_stack - assert(new_height >= height) - for k = height + 1, new_height do - operand_stack[k] = nil - end - end - end} - } - }, - }, -} - -local modes = tex.getmodevalues() -local func = luatexbase.new_luafunction'luaPST' -token.set_lua('luaPST', func, 'protected') -lua.get_functions_table()[func] = function() - local context = string.format('%s:%i', status.filename, status.linenumber) - local direct = token.scan_keyword'direct' - local tokens = token.scan_argument(true) - local n = node.new('whatsit', 'late_lua') - function n.data() - assert(not ps_tokens) - ps_tokens = tokens - ps_direct = direct - ps_context = context - end - local nn = node.new('glyph') - nn.subtype = 256 - nn.font, nn.char = fid, 0x1F3A8 - n.next = nn - local modename = modes[math.abs(tex.nest.top.mode)] - if 'horizontal' ~= modename then - n = node.hpack(n) -- Glyphs can only appear in hmode - if 'math' == modename then - local d = node.new'disc' - d.penalty = 10000 - d.replace = n - n = d - end - end - if tex.nest.ptr == 0 then - -- Main vertical list. Here we might appear before the page starts properly - -- and should not freeze page specifications. Since we don't have any outer dimensions, - -- we can ensure this by sneaking our node into the current page list whithout going though - -- build_page. - tex.triggerbuildpage() -- First ensure that everything else is contributed properly. - tex.lists.page_head = node.insert_after(tex.lists.page_head, nil, n) - else - node.write(n) - end -end - -do - func = luatexbase.new_luafunction'luaPSTcolor' - token.set_lua('luaPSTcolor', func) - local ps_rgb = 'rgb ' * l.C(l.P(1)^0) * l.Cc' setrgbcolor' * l.Cc'rgb ' - local ps_cmyk = 'cmyk ' * l.C(l.P(1)^0) * l.Cc' setcmykcolor' * l.Cc'cmyk ' - local ps_gray = 'gray ' * l.C(l.P(1)^0) * l.Cc' setgray' * l.Cc'gray ' - local pscolor = ps_rgb + ps_gray + ps_gray - local pdf_rgb = l.Cmt(l.C(number * whitespace * number * whitespace * number / 0) * whitespace * 'rg' - * whitespace * l.C(number * whitespace * number * whitespace * number / 0) * whitespace * 'RG' * -1, function(s, p, a, b) - if a == b then - return true, a, ' setrgbcolor', 'rgb ' - else - return false - end - end) - local pdf_cmyk = l.Cmt(l.C(number * whitespace * number * whitespace * number * whitespace * number / 0) * whitespace * 'k' - * whitespace * l.C(number * whitespace * number * whitespace * number * whitespace * number / 0) * whitespace * 'K' * -1, function(s, p, a, b) - if a == b then - return true, a, ' setcmykcolor', 'cmyk ' - else - return false - end - end) - local pdf_gray = l.Cmt(l.C(number / 0) * whitespace * 'g' - * whitespace * l.C(number / 0) * whitespace * 'G' * -1, function(s, p, a, b) - if a == b then - return true, a, ' setgray', 'gray ' - else - return false - end - end) - local pdf_other = l.Cs(l.Cc'(' * l.P(1)^0 * l.Cc')') * l.C' setpdfcolor' * l.C'gray ' - local pdfcolor = pdf_rgb + pdf_cmyk + pdf_gray + pdf_other - local anycolor = pscolor + pdfcolor - lua.get_functions_table()[func] = function() - local dvips_format = token.scan_keyword'dvips' - local result, suffix, prefix = anycolor:match(token.scan_argument()) - tex.sprint(-2, dvips_format and prefix .. result or result .. suffix) - end -end - -func = luatexbase.new_luafunction'luaPSTbox' -token.set_lua('luaPSTbox', func) -lua.get_functions_table()[func] = function() - local box = register_texbox(token.scan_list()) - tex.sprint(-2, tostring(box)) -end |