From aa538e215ddb4716e8360899e98fb6ef1512e477 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 20:11:48 +0200 Subject: [PATCH 01/10] feat(classes,outputters,packages): New approach to full bleed printing, cropmarks, background Decorrelate frame cursor position from actual outputter cursor. Add base class options (bleed, sheetsize) Move most of the direct low-level libtexpdf call to the outputter. --- classes/base.lua | 34 ++++++++++ outputters/base.lua | 6 ++ outputters/libtexpdf.lua | 126 +++++++++++++++++++++++++++++++++-- packages/background/init.lua | 76 +++++++++++++++------ packages/cropmarks/init.lua | 110 +++++++++++++----------------- packages/pdf/init.lua | 67 +++++++------------ packages/rotate/init.lua | 55 ++++++++------- packages/scalebox/init.lua | 17 ++--- 8 files changed, 319 insertions(+), 172 deletions(-) diff --git a/classes/base.lua b/classes/base.lua index 7cd902029..5e8249f56 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -80,9 +80,29 @@ end function class:setOptions (options) options = options or {} options.papersize = options.papersize or "a4" + options.bleed = options.bleed or "0" for option, value in pairs(options) do self.options[option] = value end + + if not SILE.documentState.sheetSize then + SILE.documentState.sheetSize = { + SILE.documentState.paperSize[1], + SILE.documentState.paperSize[2] + } + end + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + or SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] then + SU.error("Sheet size shall not be smaller than the paper size") + end + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size width augmented to take page bleed into account") + SILE.documentState.sheetSize[1] = SILE.documentState.paperSize[1] + SILE.documentState.bleed + end + if SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size height augmented to take page bleed into account") + SILE.documentState.sheetSize[2] = SILE.documentState.paperSize[2] + SILE.documentState.bleed + end end function class:declareOption (option, setter) @@ -116,6 +136,20 @@ function class:declareOptions () end return self.papersize end) + self:declareOption("sheetsize", function (_, size) + if size then + self.sheetsize = size + SILE.documentState.sheetSize = SILE.papersize(size) + end + return self.sheetsize + end) + self:declareOption("bleed", function (_, dimen) + if dimen then + self.bleed = dimen + SILE.documentState.bleed = SU.cast("measurement", dimen):tonumber() + end + return self.bleed + end) end function class.declareSettings (_) diff --git a/outputters/base.lua b/outputters/base.lua index a5ce0aacd..30fd6d75b 100644 --- a/outputters/base.lua +++ b/outputters/base.lua @@ -34,6 +34,12 @@ function outputter.debugFrame (_, _, _) end function outputter.debugHbox (_, _, _) end +function outputter.linkAnchor (_, _, _) end + +function outputter.enterLinkTarget (_, _, _) end + +function outputter.leaveLinkTarget (_, _, _, _, _, _, _) end + function outputter:getOutputFilename () local fname if SILE.outputFilename then diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index 80f2e37de..35a29dec9 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -22,13 +22,29 @@ local outputter = pl.class(base) outputter._name = "libtexpdf" outputter.extension = "pdf" +-- Sometimes setCoord is called before the outputter has ensure initialization! +local deltaX +local deltaY +local function trueXCoord (x) + if not deltaX then + deltaX = SILE.documentState.sheetSize[1] - SILE.documentState.paperSize[1] + end + return x + deltaX / 2 +end +local function trueYCoord (y) + if not deltaY then + deltaY = SILE.documentState.sheetSize[2] - SILE.documentState.paperSize[2] + end + return y + deltaY / 2 +end + -- The outputter init can't actually initialize output (as logical as it might -- have seemed) because that requires a page size which we don't know yet. -- function outputter:_init () end function outputter:_ensureInit () if not started then - local w, h = SILE.documentState.paperSize[1], SILE.documentState.paperSize[2] + local w, h = SILE.documentState.sheetSize[1], SILE.documentState.sheetSize[2] local fname = self:getOutputFilename() pdf.init(fname == "-" and "/dev/stdout" or fname, w, h, SILE.full_version) pdf.beginpage() @@ -90,7 +106,7 @@ function outputter:_drawString (str, width, x_offset, y_offset) local x, y = self:getCursor() pdf.colorpush_rgb(0,0,0) pdf.colorpop() - pdf.setstring(x+x_offset, y+y_offset, str, string.len(str), _font, width) + pdf.setstring(trueXCoord(x+x_offset), trueYCoord(y+y_offset), str, string.len(str), _font, width) end function outputter:drawHbox (value, width) @@ -157,7 +173,7 @@ function outputter:drawImage (src, x, y, width, height, pageno) width = SU.cast("number", width) height = SU.cast("number", height) self:_ensureInit() - pdf.drawimage(src, x, y, width, height, pageno or 1) + pdf.drawimage(src, trueXCoord(x), trueYCoord(y), width, height, pageno or 1) end function outputter:getImageSize (src, pageno) @@ -174,8 +190,8 @@ function outputter:drawSVG (figure, x, y, _, height, scalefactor) pdf.add_content("q") self:setCursor(x, y) x, y = self:getCursor() - local newy = y - SILE.documentState.paperSize[2] + height - pdf.add_content(table.concat({ scalefactor, 0, 0, -scalefactor, x, newy, "cm" }, " ")) + local newy = y - SILE.documentState.paperSize[2] / 2 + height - SILE.documentState.sheetSize[2] / 2 + pdf.add_content(table.concat({ scalefactor, 0, 0, -scalefactor, trueXCoord(x), newy, "cm" }, " ")) pdf.add_content(figure) pdf.add_content("Q") end @@ -187,7 +203,7 @@ function outputter:drawRule (x, y, width, height) height = SU.cast("number", height) self:_ensureInit() local paperY = SILE.documentState.paperSize[2] - pdf.setrule(x, paperY - y - height, width, height) + pdf.setrule(trueXCoord(x), trueYCoord(paperY - y - height), width, height) end function outputter:debugFrame (frame) @@ -228,4 +244,102 @@ function outputter:debugHbox (hbox, scaledWidth) self:popColor() end +-- The methods below are only implemented on outputters supporting these features. +-- In PDF, it relies on transformation matrices, but other backends may call +-- for a different strategy. +-- ! The API is unstable and subject to change. ! + +function outputter:scaleFn (xorigin, yorigin, xratio, yratio, callback) + xorigin = SU.cast("number", xorigin) + yorigin = SU.cast("number", yorigin) + local x0 = trueXCoord(xorigin) + local y0 = -trueYCoord(yorigin) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, x0, y0) + pdf.setmatrix(xratio, 0, 0, yratio, 0, 0) + pdf.setmatrix(1, 0, 0, 1, -x0, -y0) + callback() + pdf:grestore() +end + +function outputter:rotateFn (xorigin, yorigin, theta, callback) + xorigin = SU.cast("number", xorigin) + yorigin = SU.cast("number", yorigin) + local x0 = trueXCoord(xorigin) + local y0 = -trueYCoord(yorigin) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, x0, y0) + pdf.setmatrix(math.cos(theta), math.sin(theta), -math.sin(theta), math.cos(theta), 0, 0) + pdf.setmatrix(1, 0, 0, 1, -x0, -y0) + callback() + pdf:grestore() +end + +-- Other rotation unstable APIs + +function outputter:enterFrameRotate (xa, xb, y, theta) -- Unstable API see rotate package + xa = SU.cast("number", xa) + xb = SU.cast("number", xb) + y = SU.cast("number", y) + -- Keep center point the same? + local cx0 = trueXCoord(xa) + local cx1 = trueXCoord(xb) + local cy = -trueYCoord(y) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, cx1, cy) + pdf.setmatrix(math.cos(theta), math.sin(theta), -math.sin(theta), math.cos(theta), 0, 0) + pdf.setmatrix(1, 0, 0, 1, -cx0, -cy) +end + +function outputter.leaveFrameRotate (_) + pdf:grestore() +end + +-- Unstable link APIs + +function outputter:linkAnchor (x, y, name) + x = SU.cast("number", x) + y = SU.cast("number", y) + self:_ensureInit() + pdf.destination(name, trueXCoord(x), trueYCoord(y)) +end + +local function borderColor (color) + if color then + if color.r then return "/C [" .. color.r .. " " .. color.g .. " " .. color.b .. "]" end + if color.c then return "/C [" .. color.c .. " " .. color.m .. " " .. color.y .. " " .. color.k .. "]" end + if color.l then return "/C [" .. color.l .. "]" end + end + return "" +end +local function borderStyle (style, width) + if style == "underline" then return "/BS<>" end + if style == "dashed" then return "/BS<>" end + return "/Border[0 0 " .. width .. "]" +end + +function outputter:enterLinkTarget (_, _) -- destination, options as argument + -- HACK: + -- Looking at the code, pdf.begin_annotation does nothing, and Simon wrote a comment + -- about tracking boxes. Unsure what he implied with this obscure statement. + -- Sure thing is that some backends may need the destination here, e.g. an HTML backend + -- would generate a , as well as the options possibly for styling + -- on the link opening? + self:_ensureInit() + pdf.begin_annotation() +end +function outputter.leaveLinkTarget (_, x0, y0, x1, y1, dest, opts) + local bordercolor = borderColor(opts.bordercolor) + local borderwidth = SU.cast("integer", opts.borderwidth) + local borderstyle = borderStyle(opts.borderstyle, borderwidth) + local target = opts.external and "/Type/Action/S/URI/URI" or "/S/GoTo/D" + local d = "<>>>" + pdf.end_annotation(d, + trueXCoord(x0) , trueYCoord(y0 - opts.borderoffset), + trueXCoord(x1), trueYCoord(y1 + opts.borderoffset)) +end + return outputter diff --git a/packages/background/init.lua b/packages/background/init.lua index 02e2fbd6a..00740bae9 100644 --- a/packages/background/init.lua +++ b/packages/background/init.lua @@ -3,48 +3,80 @@ local base = require("packages.base") local package = pl.class(base) package._name = "background" -local outputBackground = function (color) - local page = SILE.getFrame("page") - local backgroundColor = SILE.color(color) - SILE.outputter:pushColor(backgroundColor) - SILE.outputter:drawRule(page:left(), page:top(), page:right(), page:bottom()) - SILE.outputter:popColor() +local outputBackground = function (background) + local pagea = SILE.getFrame("page") + local offset = SILE.documentState.bleed / 2 + if type(background.bg) == "string" then + -- FIXME + SILE.outputter:drawImage(background.bg, + pagea:left() - offset, pagea:top() - offset, + pagea:width() + 2 * offset, pagea:height() + 2 * offset) + elseif background.bg then + SILE.outputter:pushColor(background.bg) + SILE.outputter:drawRule( + pagea:left() - offset, pagea:top() - offset, + pagea:width() + 2 * offset, pagea:height() + 2 * offset) + SILE.outputter:popColor() + end + if not background.allpages then + background.bg = nil + end end +SILE.scratch.background = SILE.scratch.background or {} + function package:_init () base._init(self) - self:loadPackage("color") + self.class:registerHook("newpage", function (_) + outputBackground(SILE.scratch.background) + end ) end function package:registerCommands () self:registerCommand("background", function (options, _) - options.color = options.color or "white" - options.allpages = options.allpages or true - outputBackground(options.color) - if options.allpages and options.allpages ~= "false" then - local oldNewPage = SILE.documentState.documentClass.newPage - SILE.documentState.documentClass.newPage = function (self_) - local page = oldNewPage(self_) - outputBackground(options.color) - return page - end + if SU.boolean(options.disable, false) then + -- This option is certainly better than enforcing a white color. + SILE.scratch.background.bg = nil + return + end + + local allpages = SU.boolean(options.allpages, true) + SILE.scratch.background.allpages = allpages + local color = options.color and SILE.color(options.color) + local src = options.src + if src then + SILE.scratch.background.bg = src and SILE.resolveFile(src) or SU.error("Couldn't find file "..src) + elseif color then + SILE.scratch.background.bg = color + else + SU.error("background requires a color or an image src parameter") end - end, "Draws a solid background color on pages after initialization.") + outputBackground(SILE.scratch.background) + end, "Output a solid background color or an image on pages after initialization.") end package.documentation = [[ \begin{document} \use[module=packages.background] -The \autodoc:package{background} package allows you to set the color of the canvas background (by drawing a solid color block the full size of the page on page initialization). -The package provides a \autodoc:command{\background} command which requires at least one parameter, \autodoc:parameter{color=}, and sets the background of the current and all following pages to that color. -If you want to set only the current page background different from the default, use the parameter \autodoc:parameter{allpages=false}. -The color specification in the same as specified in the \autodoc:package{color} package. +As its name implies, the \autodoc:package{background} package allows you to set the color of the page canvas background or to use a background image extending to the full page width and height. + +The package provides a \autodoc:command{\background} command which requires one of the following parameters: +\begin{itemize} +\item{\autodoc:parameter{color=} sets the background of the current and all following pages to that color. The color specification has the same syntax as specified in the \autodoc:package{color} package.} +\item{\autodoc:parameter{src=} sets the backgound of the current and all following pages to the specified image. The latter will be scaled to the target dimension.} +\end{itemize} + +The background extends to the page trim area (“page bleed”) if the latter is defined. +This is to ensure that it indeed “bleeds” off the sides of the page, so as to avoid thin white lignes on an otherwise full color page when the paper sheet is cut to dimension but some pages are trimmed slightly more than others. +If setting only the current page background different from the default is desired, an extra parameter \autodoc:parameter{allpages=false} can be passed. \background[color=#e9d8ba,allpages=false] So, for example, \autodoc:command{\background[color=#e9d8ba,allpages=false]} will set a sepia tone background on the current page. +The \autodoc:parameter{disable=true} parameter allows disabling the background on the following pages. +It may be useful when \autodoc:parameter{allpages} is active from a previous invocation. \end{document} ]] diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index 59b6b8f71..8c7c65c91 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -7,51 +7,45 @@ local outcounter = 1 local function outputMarks () local page = SILE.getFrame("page") - SILE.outputter:drawRule(page:left() - 10, page:top(), -10, 0.5) - SILE.outputter:drawRule(page:left(), page:top() - 10, 0.5, -10) - SILE.outputter:drawRule(page:right() + 10, page:top(), 10, 0.5) - SILE.outputter:drawRule(page:right(), page:top() - 10, 0.5, -10) - SILE.outputter:drawRule(page:left() - 10, page:bottom(), -10, 0.5) - SILE.outputter:drawRule(page:left(), page:bottom() + 10, 0.5, 10) - SILE.outputter:drawRule(page:right() + 10, page:bottom(), 10, 0.5) - SILE.outputter:drawRule(page:right(), page:bottom() + 10, 0.5, 10) + -- Length of cromark bars + local cropsz = 20 + -- Ensure the crop marks stay outside the bleed area + local offset = math.max(10, SILE.documentState.bleed / 2) + + SILE.outputter:drawRule(page:left() - offset, page:top(), -cropsz, 0.5) + SILE.outputter:drawRule(page:left(), page:top() - offset, 0.5, -cropsz) + SILE.outputter:drawRule(page:right() + offset, page:top(), cropsz, 0.5) + SILE.outputter:drawRule(page:right(), page:top() - offset, 0.5, -cropsz) + SILE.outputter:drawRule(page:left() - offset, page:bottom(), -cropsz, 0.5) + SILE.outputter:drawRule(page:left() , page:bottom() + offset, 0.5, cropsz) + SILE.outputter:drawRule(page:right() + offset, page:bottom(), cropsz, 0.5) + SILE.outputter:drawRule(page:right(), page:bottom() + offset, 0.5, cropsz) local hbox, hlist = SILE.typesetter:makeHbox(function () SILE.settings:temporarily(function () SILE.call("noindent") SILE.call("font", { size="6pt" }) - SILE.call("crop:header") + if SILE.Commands["crop:header"] then + -- Deprecation shim: + -- If user redefined this command, still use it with a warning... + SU.deprecated("crop:header", "cropmarks:header", "0.14.0", "0.16.0") + SILE.call("crop:header") + else + SILE.call("cropmarks:header") + end end) end) if #hlist > 0 then - SU.error("Forbidden migrating content in crop header") + SU.error("Migrating content is forbidden in crop header") end - SILE.typesetter.frame.state.cursorX = page:left() + 10 - SILE.typesetter.frame.state.cursorY = page:top() - 13 + SILE.typesetter.frame.state.cursorX = page:left() + offset + SILE.typesetter.frame.state.cursorY = page:top() - offset - 4 outcounter = outcounter + 1 if hbox then - for i = 1, #(hbox.value) do hbox.value[i]:outputYourself(SILE.typesetter, { ratio = 1 }) end - end -end - -local function reconstrainFrameset (fs) - for n, f in pairs(fs) do - if n ~= "page" then - if f:isAbsoluteConstraint("right") then - f.constraints.right = "left(page) + (" .. f.constraints.right .. ")" - end - if f:isAbsoluteConstraint("left") then - f.constraints.left = "left(page) + (" .. f.constraints.left .. ")" - end - if f:isAbsoluteConstraint("top") then - f.constraints.top = "top(page) + (" .. f.constraints.top .. ")" - end - if f:isAbsoluteConstraint("bottom") then - f.constraints.bottom = "top(page) + (" .. f.constraints.bottom .. ")" - end - f:invalidate() + for i = 1, #(hbox.value) do + hbox.value[i]:outputYourself(SILE.typesetter, { ratio = 1 }) end end end @@ -63,52 +57,38 @@ end function package:registerCommands () - self:registerCommand("crop:header", function (_, _) - local info = SILE.input.filenames[1] .. " - " .. self.class:date("%x %X") .. " - " .. outcounter + self:registerCommand("cropmarks:header", function (_, _) + local info = SILE.masterFilename + .. " - " + .. self.class.packages.date:date({ format = "%x %X" }) + .. " - " .. outcounter SILE.typesetter:typeset(info) end) - self:registerCommand("crop:setup", function (options, _) - local papersize = SU.required(options, "papersize", "setting up crop marks") - local size = SILE.papersize(papersize) - local oldsize = SILE.documentState.paperSize - SILE.documentState.paperSize = size - local offsetx = ( SILE.documentState.paperSize[1] - oldsize[1] ) /2 - local offsety = ( SILE.documentState.paperSize[2] - oldsize[2] ) /2 - local page = SILE.getFrame("page") - page:constrain("right", page:right() + offsetx) - page:constrain("left", offsetx) - page:constrain("bottom", page:bottom() + offsety) - page:constrain("top", offsety) - if SILE.scratch.masters then - for _, v in pairs(SILE.scratch.masters) do - reconstrainFrameset(v.frames) - end - else - reconstrainFrameset(SILE.documentState.documentClass.pageTemplate.frames) - end - if SILE.typesetter.frame then SILE.typesetter.frame:init() end - local oldEndPage = SILE.documentState.documentClass.endPage - SILE.documentState.documentClass.endPage = function (self_) - oldEndPage(self_) + self:registerCommand("cropmarks:setup", function (_, _) + self.class:registerHook("endpage", function (_) outputMarks() - end + end ) end) + self:registerCommand("crop:setup", function (_, _) + SU.deprecated("crop:setup", "cropmarks:setup", "0.14.10", "0.17.0") + SILE.call("cropmarks:setup") + end) end package.documentation = [[ \begin{document} -When preparing a document for printing, you may be asked by the printer to add crop marks. -This means that you need to output the document on a slightly larger page size than your target paper and add printer’s crop marks to show where the paper should be trimmed down to the correct size. -(This is to ensure that pages where the content “bleeds” off the side of the page are correctly cut.) +When preparing a document for printing, you may be asked by the printer add crop marks. +This means that you need to output the document on a slightly larger page size than your target paper and add crop marks to show where the paper sheet should be trimmed down to the correct size. -This package provides the \autodoc:command{\crop:setup} command which should be run early in your document file. -It takes one argument, \autodoc:parameter{papersize}, which is the true target paper size. -It place cropmarks around the true page content. +Actual paper size, true page content area and bleed/trim area can all be set via class options. +This package provides the \autodoc:command{\cropmarks:setup} command which should be run early in your document file. +It places crop marks around the true page content. +The crop marks are guaranteed to stay outside the bleed/trim area, when defined. It also adds a header at the top of the page with the filename, date and output sheet number. -You can customize this header by redefining \autodoc:command{\crop:header}. +You can customize this header by redefining \autodoc:command{\cropmarks:header}. \end{document} ]] diff --git a/packages/pdf/init.lua b/packages/pdf/init.lua index f6622a3bc..c31d4e624 100644 --- a/packages/pdf/init.lua +++ b/packages/pdf/init.lua @@ -5,21 +5,6 @@ package._name = "pdf" local pdf -local function borderColor (color) - if color then - if color.r then return "/C [" .. color.r .. " " .. color.g .. " " .. color.b .. "]" end - if color.c then return "/C [" .. color.c .. " " .. color.m .. " " .. color.y .. " " .. color.k .. "]" end - if color.l then return "/C [" .. color.l .. "]" end - end - return "" -end - -local function borderStyle (style, width) - if style == "underline" then return "/BS<>" end - if style == "dashed" then return "/BS<>" end - return "/Border[0 0 " .. width .. "]" -end - local function validate_date (date) return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil end @@ -36,9 +21,6 @@ function package:registerCommands () self:registerCommand("pdf:destination", function (options, _) local name = SU.required(options, "name", "pdf:destination") - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end SILE.typesetter:pushHbox({ outputYourself = function (_, typesetter, line) local state = typesetter.frame.state @@ -46,7 +28,7 @@ function package:registerCommands () local x, y = state.cursorX, state.cursorY typesetter.frame:advancePageDirection(line.height) local _y = SILE.documentState.paperSize[2] - y - pdf.destination(name, x:tonumber(), _y:tonumber()) + SILE.outputter:linkAnchor(x, _y, name) end }) end) @@ -93,43 +75,44 @@ function package:registerCommands () self:registerCommand("pdf:link", function (options, content) local dest = SU.required(options, "dest", "pdf:link") - local target = options.external and "/Type/Action/S/URI/URI" or "/S/GoTo/D" + local external = SU.boolean(options.external, false) local borderwidth = options.borderwidth and SU.cast("measurement", options.borderwidth):tonumber() or 0 - local bordercolor = borderColor(SILE.color(options.bordercolor or "blue")) + local bordercolor = SILE.color(options.bordercolor or "blue") local borderoffset = SU.cast("measurement", options.borderoffset or "1pt"):tonumber() - local borderstyle = borderStyle(options.borderstyle, borderwidth) - local llx, lly - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end + local opts = { + external = external, + borderstyle = options.borderstyle, + bordercolor = bordercolor, + borderwidth = borderwidth, + borderoffset = borderoffset + } + + local x0, y0 SILE.typesetter:pushHbox({ value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), + height = 0, + width = 0, + depth = 0, outputYourself = function (_, typesetter, _) - llx = typesetter.frame.state.cursorX:tonumber() - lly = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY):tonumber() - pdf.begin_annotation() + x0 = typesetter.frame.state.cursorX:tonumber() + y0 = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY):tonumber() + SILE.outputter:enterLinkTarget(dest, opts) end }) - local hbox, hlist = SILE.typesetter:makeHbox(content) -- hack SILE.typesetter:pushHbox(hbox) - SILE.typesetter:pushHlist(hlist) - SILE.typesetter:pushHbox({ value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), + height = 0, + width = 0, + depth = 0, outputYourself = function (_, typesetter, _) - local d = "<>>>" - local x = typesetter.frame.state.cursorX:tonumber() - local y = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY + hbox.height):tonumber() - pdf.end_annotation(d, llx , lly - borderoffset, x, y + borderoffset) + local x1 = typesetter.frame.state.cursorX:tonumber() + local y1 = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY + hbox.height):tonumber() + SILE.outputter:leaveLinkTarget(x0, y0, x1, y1, dest, opts) -- Unstable API end }) + SILE.typesetter:pushHlist(hlist) end) self:registerCommand("pdf:metadata", function (options, _) diff --git a/packages/rotate/init.lua b/packages/rotate/init.lua index 5982f0c2c..6aad18cca 100644 --- a/packages/rotate/init.lua +++ b/packages/rotate/init.lua @@ -3,23 +3,25 @@ local base = require("packages.base") local package = pl.class(base) package._name = "rotate" -local pdf = require("justenoughlibtexpdf") - local enter = function (self, _) + -- Probably broken, see: + -- https://github.com/sile-typesetter/sile/issues/427 if not self.rotate then return end - local x = -math.rad(self.rotate) + if not SILE.outputter.enterFrameRotate then + return SU.warn("Frame '".. self.id "' will not be rotated: backend '" .. SILE.outputter._name .. "' does not support rotation") + end + local theta = -math.rad(self.rotate) -- Keep center point the same - pdf:gsave() - local cx = self:left():tonumber() - local cy = -self:bottom():tonumber() - pdf.setmatrix(1, 0, 0, 1, cx + math.sin(x) * self:height():tonumber(), cy) - pdf.setmatrix(math.cos(x), math.sin(x), -math.sin(x), math.cos(x), 0, 0) - pdf.setmatrix(1, 0, 0, 1, -cx, -cy) + local x0 = self:left():tonumber() + local x1 = x0 + math.sin(theta) * self:height():tonumber() + local y0 = self:bottom():tonumber() + SILE.outputter:enterFrameRotate(x0, x1, y0, theta) -- Unstable API end -local leave = function(self, _) +local leave = function(self, _) if not self.rotate then return end - pdf:grestore() + if not SILE.outputter.enterFrameRotate then return end -- no enter no leave. + SILE.outputter:leaveFrameRotate() end -- What is the width, depth and height of a rectangle width w and height h rotated by angle theta? @@ -35,20 +37,20 @@ end local outputRotatedHbox = function (self, typesetter, line) local origbox = self.value.orig - local x = self.value.theta + local theta = self.value.theta + -- Find origin of untransformed hbox - local save = typesetter.frame.state.cursorX - typesetter.frame.state.cursorX = typesetter.frame.state.cursorX - (origbox.width.length-self.width)/2 - - local horigin = (typesetter.frame.state.cursorX + origbox.width.length / 2):tonumber() - local vorigin = -(typesetter.frame.state.cursorY - (origbox.height - origbox.depth) / 2):tonumber() - pdf:gsave() - pdf.setmatrix(1, 0, 0, 1, horigin, vorigin) - pdf.setmatrix(math.cos(x), math.sin(x), -math.sin(x), math.cos(x), 0, 0) - pdf.setmatrix(1, 0, 0, 1, -horigin, -vorigin) - origbox:outputYourself(typesetter, line) - pdf:grestore() - typesetter.frame.state.cursorX = save + local X = typesetter.frame.state.cursorX + local Y = typesetter.frame.state.cursorY + typesetter.frame.state.cursorX = X - (origbox.width.length-self.width)/2 + local horigin = X + origbox.width.length / 2 + local vorigin = Y - (origbox.height - origbox.depth) / 2 + + SILE.outputter:rotateFn(horigin, vorigin, theta, function () + origbox:outputYourself(typesetter, line) + end) + typesetter.frame.state.cursorX = X + typesetter.frame.state.cursorY = Y typesetter.frame:advanceWritingDirection(self.width) end @@ -65,6 +67,10 @@ end function package:registerCommands () self:registerCommand("rotate", function(options, content) + if not SILE.outputter.rotateFn then + SU.warn("Output will not be rotated: backend '" .. SILE.outputter._name .. "' does not support rotation") + return SILE.process(content) + end local angle = SU.required(options, "angle", "rotate command") local theta = -math.rad(angle) local origbox, hlist = SILE.typesetter:makeHbox(content) @@ -92,7 +98,6 @@ function package:registerCommands () end depth = -depth if depth < SILE.length(0) then depth = SILE.length(0) end - SILE.outputter:_ensureInit() SILE.typesetter:pushHbox({ value = { orig = origbox, theta = theta}, height = height, diff --git a/packages/scalebox/init.lua b/packages/scalebox/init.lua index f4d8375c2..8ab331fbb 100644 --- a/packages/scalebox/init.lua +++ b/packages/scalebox/init.lua @@ -6,12 +6,10 @@ package._name = "scalebox" function package:registerCommands () self:registerCommand("scalebox", function(options, content) - if SILE.outputter._name ~= "libtexpdf" then - SU.warn("Output will not be scaled: \\scalebox only works with the libtexpdf backend") + if not SILE.outputter.scaleFn then + SU.warn("Output will not be scaled: backend '" .. SILE.outputter._name .. "' does not support scaling") return SILE.process(content) end - SILE.outputter:_ensureInit() - local pdf = require("justenoughlibtexpdf") local hbox, hlist = SILE.typesetter:makeHbox(content) local xratio, yratio = SU.cast("number", options.xratio or 1), SU.cast("number", options.yratio or 1) @@ -37,18 +35,13 @@ function package:registerCommands () local outputWidth = SU.rationWidth(node.width, node.width, line.ratio) local X = typesetter.frame.state.cursorX local Y = typesetter.frame.state.cursorY - local x0 = X:tonumber() - local y0 = -Y:tonumber() if xratio < 0 then typesetter.frame:advanceWritingDirection(-outputWidth) end - pdf:gsave() - pdf.setmatrix(1, 0, 0, 1, x0, y0) - pdf.setmatrix(xratio, 0, 0, yratio, 0, 0) - pdf.setmatrix(1, 0, 0, 1, -x0, -y0) - hbox.outputYourself(hbox, typesetter, line) - pdf:grestore() + SILE.outputter:scaleFn(X, Y, xratio, yratio, function () + hbox:outputYourself(typesetter, line) + end) typesetter.frame.state.cursorX = X typesetter.frame.state.cursorY = Y typesetter.frame:advanceWritingDirection(outputWidth) From c5113b72ddb44f2f6712ac45ff3e5b1668e27166 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Tue, 22 Aug 2023 02:43:08 +0200 Subject: [PATCH 02/10] fix(typesetters): Debug hbox could show incorrectly offset boxes --- typesetters/base.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/typesetters/base.lua b/typesetters/base.lua index 6c6584884..f086cbcd5 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -940,16 +940,20 @@ function typesetter:makeHbox (content) local ox = atypesetter.frame.state.cursorX local oy = atypesetter.frame.state.cursorY SILE.outputter:setCursor(atypesetter.frame.state.cursorX, atypesetter.frame.state.cursorY) + -- BEGIN SILEX FIX DEBUG + SU.debug("hboxes", function () + -- setCursor also invoked by the internal hboxes etc. + -- so we must show our debug box before outputting its content. + SILE.outputter:debugHbox(box, box:scaledWidth(line)) + return "Drew debug outline around hbox" + end) + -- END SILEX FIX DEBUG for _, node in ipairs(box.value) do node:outputYourself(atypesetter, line) end atypesetter.frame.state.cursorX = ox atypesetter.frame.state.cursorY = oy _post() - SU.debug("hboxes", function () - SILE.outputter:debugHbox(box, box:scaledWidth(line)) - return "Drew debug outline around hbox" - end) end }) return hbox, migratingNodes From 52df41f931141cfc32afaf50733a5e07be7e10fc Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 01:31:12 +0200 Subject: [PATCH 03/10] refactor(packages,outputters): Move pdf:bookmark and pdf:metadata logic to the outputter --- outputters/base.lua | 4 +++ outputters/libtexpdf.lua | 37 ++++++++++++++++++++++ packages/pdf/init.lua | 67 +++++++++------------------------------- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/outputters/base.lua b/outputters/base.lua index 30fd6d75b..2dfe4ae4a 100644 --- a/outputters/base.lua +++ b/outputters/base.lua @@ -40,6 +40,10 @@ function outputter.enterLinkTarget (_, _, _) end function outputter.leaveLinkTarget (_, _, _, _, _, _, _) end +function outputter.setMetadata (_, _, _) end + +function outputter.setBookmark (_, _, _) end + function outputter:getOutputFilename () local fname if SILE.outputFilename then diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index 35a29dec9..0c0c811ab 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -342,4 +342,41 @@ function outputter.leaveLinkTarget (_, x0, y0, x1, y1, dest, opts) trueXCoord(x1), trueYCoord(y1 + opts.borderoffset)) end +-- Bookmarks and metadata + +local function validate_date (date) + return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil +end + +function outputter:setMetadata (key, value) + if key == "Trapped" then + SU.warn("Skipping special metadata key \\Trapped") + return + end + + if key == "ModDate" or key == "CreationDate" then + if not validate_date(value) then + SU.warn("Invalid date: " .. value) + return + end + else + -- see comment in on bookmark + value = SU.utf8_to_utf16be(value) + end + self:_ensureInit() + pdf.metadata(key, value) +end + +function outputter:setBookmark (dest, title, level) + -- Added UTF8 to UTF16-BE conversion + -- For annotations and bookmarks, text strings must be encoded using + -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. + -- As PDFDocEncoding supports only limited character repertoire for + -- European languages, we use UTF-16BE for internationalization. + local ustr = SU.utf8_to_utf16be_hexencoded(title) + local d = "</A<>>>" + self:_ensureInit() + pdf.bookmark(d, level) +end + return outputter diff --git a/packages/pdf/init.lua b/packages/pdf/init.lua index c31d4e624..9a9e2f394 100644 --- a/packages/pdf/init.lua +++ b/packages/pdf/init.lua @@ -1,22 +1,13 @@ +-- +-- This package and its commands are perhaps ill-named: +-- Exception made of the pdf:literal command below, the concepts of links +-- (anchor, target), bookmarks, and metadata are not specific to PDF. +-- local base = require("packages.base") local package = pl.class(base) package._name = "pdf" -local pdf - -local function validate_date (date) - return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil -end - -function package:_init () - base._init(self) - pdf = require("justenoughlibtexpdf") - if SILE.outputter._name ~= "libtexpdf" then - SU.error("pdf package requires libtexpdf backend") - end -end - function package:registerCommands () self:registerCommand("pdf:destination", function (options, _) @@ -36,29 +27,26 @@ function package:registerCommands () self:registerCommand("pdf:bookmark", function (options, _) local dest = SU.required(options, "dest", "pdf:bookmark") local title = SU.required(options, "title", "pdf:bookmark") - local level = options.level or 1 - -- Added UTF8 to UTF16-BE conversion - -- For annotations and bookmarks, text strings must be encoded using - -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. - -- As PDFDocEncoding supports only limited character repertoire for - -- European languages, we use UTF-16BE for internationalization. - local ustr = SU.utf8_to_utf16be_hexencoded(title) - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end + local level = SU.cast("integer", options.level or 1) SILE.typesetter:pushHbox({ value = nil, height = SILE.measurement(0), width = SILE.measurement(0), depth = SILE.measurement(0), outputYourself = function () - local d = "</A<>>>" - pdf.bookmark(d, level) + SILE.outputter:setBookmark(dest, title, level) end }) end) self:registerCommand("pdf:literal", function (_, content) + -- NOTE: This method is used by the pdfstructure package and should + -- probably be moved elsewhere, so there's no attempt here to delegate + -- the low-level libtexpdf call to te outputter. + if SILE.outputter._name ~= "libtexpdf" then + SU.error("pdf package requires libtexpdf backend") + end + local pdf = require("justenoughlibtexpdf") if type(SILE.outputter._ensureInit) == "function" then SILE.outputter:_ensureInit() end @@ -122,32 +110,7 @@ function package:registerCommands () end local value = SU.required(options, "value", "pdf:metadata") - if key == "Trapped" then - SU.warn("Skipping special metadata key \\Trapped") - return - end - - if key == "ModDate" or key == "CreationDate" then - if not validate_date(value) then - SU.warn("Invalid date: " .. value) - return - end - else - -- see comment in pdf:bookmark - value = SU.utf8_to_utf16be(value) - end - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end - SILE.typesetter:pushHbox({ - value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), - outputYourself = function (_, _, _) - pdf.metadata(key, value) - end - }) + SILE.outputter:setMetadata(key, value) end) end From 697188bb0a8af0c9ba3938583c2f5cc570a8b380 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 13:16:34 +0200 Subject: [PATCH 04/10] refactor(packages,outputters): Code-cleanup in cropmarks, background and outputters --- outputters/libtexpdf.lua | 29 +++++++++++++++-------------- packages/background/init.lua | 19 ++++++++----------- packages/cropmarks/init.lua | 6 ++---- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index 0c0c811ab..e49b773f4 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -22,20 +22,21 @@ local outputter = pl.class(base) outputter._name = "libtexpdf" outputter.extension = "pdf" --- Sometimes setCoord is called before the outputter has ensure initialization! +-- N.B. Sometimes setCoord is called before the outputter has ensured initialization. +-- This ok for coordinates manipulation, at these points we know the page size. local deltaX local deltaY local function trueXCoord (x) if not deltaX then - deltaX = SILE.documentState.sheetSize[1] - SILE.documentState.paperSize[1] + deltaX = (SILE.documentState.sheetSize[1] - SILE.documentState.paperSize[1]) / 2 end - return x + deltaX / 2 + return x + deltaX end local function trueYCoord (y) if not deltaY then - deltaY = SILE.documentState.sheetSize[2] - SILE.documentState.paperSize[2] + deltaY = (SILE.documentState.sheetSize[2] - SILE.documentState.paperSize[2]) / 2 end - return y + deltaY / 2 + return y + deltaY end -- The outputter init can't actually initialize output (as logical as it might @@ -368,15 +369,15 @@ function outputter:setMetadata (key, value) end function outputter:setBookmark (dest, title, level) - -- Added UTF8 to UTF16-BE conversion - -- For annotations and bookmarks, text strings must be encoded using - -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. - -- As PDFDocEncoding supports only limited character repertoire for - -- European languages, we use UTF-16BE for internationalization. - local ustr = SU.utf8_to_utf16be_hexencoded(title) - local d = "</A<>>>" - self:_ensureInit() - pdf.bookmark(d, level) + -- Added UTF8 to UTF16-BE conversion + -- For annotations and bookmarks, text strings must be encoded using + -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. + -- As PDFDocEncoding supports only limited character repertoire for + -- European languages, we use UTF-16BE for internationalization. + local ustr = SU.utf8_to_utf16be_hexencoded(title) + local d = "</A<>>>" + self:_ensureInit() + pdf.bookmark(d, level) end return outputter diff --git a/packages/background/init.lua b/packages/background/init.lua index 00740bae9..10e7a7117 100644 --- a/packages/background/init.lua +++ b/packages/background/init.lua @@ -3,11 +3,12 @@ local base = require("packages.base") local package = pl.class(base) package._name = "background" -local outputBackground = function (background) +local background = {} + +local outputBackground = function () local pagea = SILE.getFrame("page") local offset = SILE.documentState.bleed / 2 if type(background.bg) == "string" then - -- FIXME SILE.outputter:drawImage(background.bg, pagea:left() - offset, pagea:top() - offset, pagea:width() + 2 * offset, pagea:height() + 2 * offset) @@ -23,13 +24,9 @@ local outputBackground = function (background) end end -SILE.scratch.background = SILE.scratch.background or {} - function package:_init () base._init(self) - self.class:registerHook("newpage", function (_) - outputBackground(SILE.scratch.background) - end ) + self.class:registerHook("newpage", outputBackground) end function package:registerCommands () @@ -37,18 +34,18 @@ function package:registerCommands () self:registerCommand("background", function (options, _) if SU.boolean(options.disable, false) then -- This option is certainly better than enforcing a white color. - SILE.scratch.background.bg = nil + background.bg = nil return end local allpages = SU.boolean(options.allpages, true) - SILE.scratch.background.allpages = allpages + background.allpages = allpages local color = options.color and SILE.color(options.color) local src = options.src if src then - SILE.scratch.background.bg = src and SILE.resolveFile(src) or SU.error("Couldn't find file "..src) + background.bg = src and SILE.resolveFile(src) or SU.error("Couldn't find file "..src) elseif color then - SILE.scratch.background.bg = color + background.bg = color else SU.error("background requires a color or an image src parameter") end diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index 8c7c65c91..87148ee9f 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -28,7 +28,7 @@ local function outputMarks () if SILE.Commands["crop:header"] then -- Deprecation shim: -- If user redefined this command, still use it with a warning... - SU.deprecated("crop:header", "cropmarks:header", "0.14.0", "0.16.0") + SU.deprecated("crop:header", "cropmarks:header", "0.15.0", "0.16.0") SILE.call("crop:header") else SILE.call("cropmarks:header") @@ -66,9 +66,7 @@ function package:registerCommands () end) self:registerCommand("cropmarks:setup", function (_, _) - self.class:registerHook("endpage", function (_) - outputMarks() - end ) + self.class:registerHook("endpage", outputMarks) end) self:registerCommand("crop:setup", function (_, _) From 3aecb459b9a34c4660f69cf6c9fabe1b5c610149 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 20:17:21 +0200 Subject: [PATCH 05/10] refactor(packages): Let url package always load the pdf package --- packages/url/init.lua | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/url/init.lua b/packages/url/init.lua index f30259445..b754595cc 100644 --- a/packages/url/init.lua +++ b/packages/url/init.lua @@ -3,8 +3,6 @@ local base = require("packages.base") local package = pl.class(base) package._name = "url" -local pdf - -- URL escape sequence, URL fragment: local preferBreakBefore = "%#" -- URL path elements, URL query arguments, acceptable extras: @@ -23,8 +21,7 @@ function package:_init () base._init(self) self:loadPackage("verbatim") self:loadPackage("inputfilter") - pdf = SILE.outputter._name == "libtexpdf" - if pdf then self:loadPackage("pdf") end + self:loadPackage("pdf") end function package.declareSettings (_) @@ -48,15 +45,6 @@ end function package:registerCommands () self:registerCommand("href", function (options, content) - if not pdf then - if options.src then - SILE.process(content) - else - SILE.call("url", { language = options.language }, content) - end - return -- DONE. - end - if options.src then SILE.call("pdf:link", { dest = options.src, external = true, borderwidth = options.borderwidth, From 89fb72c886941c194e81513f0de6b138098d5675 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 20:36:52 +0200 Subject: [PATCH 06/10] test(packages): Update bug-337 and bug-353 to load the color package --- tests/bug-337.sil | 1 + tests/bug-353.sil | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/bug-337.sil b/tests/bug-337.sil index 66a746aa1..6f71122e1 100644 --- a/tests/bug-337.sil +++ b/tests/bug-337.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.color] \script[src=inc.bug-337] \define[command=crop:header]{tests/bug-337.sil} \crop:setup[papersize=a6] diff --git a/tests/bug-353.sil b/tests/bug-353.sil index 636cb60e5..b564c40ca 100644 --- a/tests/bug-353.sil +++ b/tests/bug-353.sil @@ -1,5 +1,6 @@ \begin[papersize=a5]{document} \use[module=packages.background] +\use[module=packages.color] \nofolios \background[color=#e9d8ba] \color[color=#5a4129]{Sepia baby.} From 67bfcaeca57d8d584aa5c13a2eeefd5198aa20c2 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 23 Aug 2023 21:13:16 +0200 Subject: [PATCH 07/10] test(packages): Update cropmarks bug-337 and expectations --- tests/bug-337.expected | 81 +++++++++++++++++++++--------------------- tests/bug-337.sil | 6 ++-- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/tests/bug-337.expected b/tests/bug-337.expected index a21541759..ed8b90595 100644 --- a/tests/bug-337.expected +++ b/tests/bug-337.expected @@ -1,69 +1,68 @@ -Set paper size 297.6377985 419.5275636 +Set paper size 209.7637818 297.6377985 Begin page -Mx 43.9370 -My 67.1949 +My 6.2500 Set font Gentium Plus;10;400;;normal;;;LTR T 21 20 19 83 87 w=22.8174 (210pt) -Mx 69.4140 +Mx 25.4770 T 2258 w=4.1016 (×) -Mx 76.1752 +Mx 32.2382 T 21 28 27 83 87 w=22.8174 (298pt) -Draw line 33.9370 60.9449 -10.0000 0.5000 -Draw line 43.9370 50.9449 0.5000 -10.0000 -Draw line 263.7008 60.9449 10.0000 0.5000 -Draw line 253.7008 50.9449 0.5000 -10.0000 -Draw line 33.9370 358.5827 -10.0000 0.5000 -Draw line 43.9370 368.5827 0.5000 10.0000 -Draw line 263.7008 358.5827 10.0000 0.5000 -Draw line 253.7008 368.5827 0.5000 10.0000 -Mx 53.9370 -My 47.9449 +Draw line -10.0000 0.0000 -20.0000 0.5000 +Draw line 0.0000 -10.0000 0.5000 -20.0000 +Draw line 219.7638 0.0000 20.0000 0.5000 +Draw line 209.7638 -10.0000 0.5000 -20.0000 +Draw line -10.0000 297.6378 -20.0000 0.5000 +Draw line 0.0000 307.6378 0.5000 20.0000 +Draw line 219.7638 297.6378 20.0000 0.5000 +Draw line 209.7638 307.6378 0.5000 20.0000 +Mx 10.0000 +My -14.0000 Set font Gentium Plus;6;400;;normal;;;LTR T 87 72 86 87 86 w=11.5371 (tests) -Mx 65.4741 +Mx 21.5371 T 18 w=2.8154 (/) -Mx 68.2895 +Mx 24.3525 T 69 88 74 w=9.1523 (bug) -Mx 77.4419 +Mx 33.5049 T 16 w=2.0215 (-) -Mx 79.4634 +Mx 35.5264 T 22 22 26 w=8.4463 (337) -Mx 87.9097 +Mx 43.9727 T 17 w=1.3740 (.) -Mx 89.2837 +Mx 45.3467 T 86 76 79 w=5.5693 (sil) New page -Mx 43.9370 -My 67.1949 +Mx 0.0000 +My 6.2500 Set font Gentium Plus;10;400;;normal;;;LTR T 21 20 19 83 87 w=22.8174 (210pt) -Mx 69.4140 +Mx 25.4770 T 2258 w=4.1016 (×) -Mx 76.1752 +Mx 32.2382 T 21 28 27 83 87 w=22.8174 (298pt) -Draw line 33.9370 60.9449 -10.0000 0.5000 -Draw line 43.9370 50.9449 0.5000 -10.0000 -Draw line 263.7008 60.9449 10.0000 0.5000 -Draw line 253.7008 50.9449 0.5000 -10.0000 -Draw line 33.9370 358.5827 -10.0000 0.5000 -Draw line 43.9370 368.5827 0.5000 10.0000 -Draw line 263.7008 358.5827 10.0000 0.5000 -Draw line 253.7008 368.5827 0.5000 10.0000 -Mx 53.9370 -My 47.9449 +Draw line -10.0000 0.0000 -20.0000 0.5000 +Draw line 0.0000 -10.0000 0.5000 -20.0000 +Draw line 219.7638 0.0000 20.0000 0.5000 +Draw line 209.7638 -10.0000 0.5000 -20.0000 +Draw line -10.0000 297.6378 -20.0000 0.5000 +Draw line 0.0000 307.6378 0.5000 20.0000 +Draw line 219.7638 297.6378 20.0000 0.5000 +Draw line 209.7638 307.6378 0.5000 20.0000 +Mx 10.0000 +My -14.0000 Set font Gentium Plus;6;400;;normal;;;LTR T 87 72 86 87 86 w=11.5371 (tests) -Mx 65.4741 +Mx 21.5371 T 18 w=2.8154 (/) -Mx 68.2895 +Mx 24.3525 T 69 88 74 w=9.1523 (bug) -Mx 77.4419 +Mx 33.5049 T 16 w=2.0215 (-) -Mx 79.4634 +Mx 35.5264 T 22 22 26 w=8.4463 (337) -Mx 87.9097 +Mx 43.9727 T 17 w=1.3740 (.) -Mx 89.2837 +Mx 45.3467 T 86 76 79 w=5.5693 (sil) End page Finish diff --git a/tests/bug-337.sil b/tests/bug-337.sil index 6f71122e1..54ca7a454 100644 --- a/tests/bug-337.sil +++ b/tests/bug-337.sil @@ -1,8 +1,8 @@ -\begin[papersize=a7,class=book]{document} +\begin[papersize=a7,class=book, sheetsize=a6]{document} \use[module=packages.color] \script[src=inc.bug-337] -\define[command=crop:header]{tests/bug-337.sil} -\crop:setup[papersize=a6] +\define[command=cropmarks:header]{tests/bug-337.sil} +\cropmarks:setup \nofolios \set[parameter=document.parindent,value=0] From 2049745f85a05c3b6740faa0c8d9cd2525893854 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Thu, 24 Aug 2023 00:25:41 +0200 Subject: [PATCH 08/10] docs(manual): Document new full bleed printing and sheet size options --- documentation/c03-input.sil | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/documentation/c03-input.sil b/documentation/c03-input.sil index 051635cb4..2894ffdbb 100644 --- a/documentation/c03-input.sil +++ b/documentation/c03-input.sil @@ -61,6 +61,25 @@ Once some of the basic document properties have been set up using these fixed si For example, once the paper size is set, percentage of page width (\code{\%pw}) and height(\code{\%ph}) become valid units. In Chapter 4 we will meet more of these relative units, and in Chapter 7 we will meet some other ways of specifying lengths to make them stretchable or shrinkable. +When preparing a book for press printing, you may be asked by the professional printer to output the document on a larger sheet than your target paper, and to reserve a trim area around it. +This trick is often called “full bleed printing”. +Your document will be printed on an oversized sheet that will then be mechanically cut down to the target size. +You can specify the expected “trim” (or “bleed”) dimension, to be distributed evenly on all sides +of the document: + +\code{papersize=\em{}, bleed=\em{}}. + +For instance, a US trade book with an extra 0.125 inch bleed area can be specified by \code{papersize=6in x 9in, bleed=0.25in}. +The output paper size is then 6.25 per 9.25 inches, with the actual 6 per 9 inches inner content centered. + +Some packages, such as \autodoc:package{background} and \autodoc:package{cropmarks}, ensure their content extends over the trim area and thus indeed “bleeds” off the sides of the page, so that artifacts such as blank lines are avoided when the sheets are cut, would they be trimmed slightly differently for some assembling or technical reasons. + +Finally, there is also the case when the actual paper sheets available to you are larger than your target paper size, and yet you would want the output document to show properly centered: + +\code{papersize=\em{}, sheetsize=\em{}}. + +For instance, \code{papersize=6in x 9in, sheetsize=a4} produces an A4-dimensioned document, but with you content formatted as a 6 per 9 inches US trade book. You may, obviously, combine these options and also specify a bleed area. + \section{Ordinary text} On the whole, ordinary text isn’t particularly interesting—it’s just typeset. From 6f8a0224974eb92661b569da6c8bffa3b8a2e3a8 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Thu, 24 Aug 2023 01:00:50 +0200 Subject: [PATCH 09/10] chore(typesetters,outputters,packages): Clean in-code comments No code change --- outputters/base.lua | 6 +++--- outputters/libtexpdf.lua | 2 ++ packages/cropmarks/init.lua | 2 +- typesetters/base.lua | 4 +--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/outputters/base.lua b/outputters/base.lua index 2dfe4ae4a..4888a0605 100644 --- a/outputters/base.lua +++ b/outputters/base.lua @@ -34,11 +34,11 @@ function outputter.debugFrame (_, _, _) end function outputter.debugHbox (_, _, _) end -function outputter.linkAnchor (_, _, _) end +function outputter.linkAnchor (_, _, _) end -- Unstable API -function outputter.enterLinkTarget (_, _, _) end +function outputter.enterLinkTarget (_, _, _) end -- Unstable API -function outputter.leaveLinkTarget (_, _, _, _, _, _, _) end +function outputter.leaveLinkTarget (_, _, _, _, _, _, _) end -- Unstable API function outputter.setMetadata (_, _, _) end diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index e49b773f4..2c6387719 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -47,6 +47,8 @@ function outputter:_ensureInit () if not started then local w, h = SILE.documentState.sheetSize[1], SILE.documentState.sheetSize[2] local fname = self:getOutputFilename() + -- Ideally we could want to set the PDF CropBox, BleedBox, TrimBox... + -- Our wrapper only manages the MediaBox at this point. pdf.init(fname == "-" and "/dev/stdout" or fname, w, h, SILE.full_version) pdf.beginpage() started = true diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index 87148ee9f..5135faef9 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -7,7 +7,7 @@ local outcounter = 1 local function outputMarks () local page = SILE.getFrame("page") - -- Length of cromark bars + -- Length of crop mark bars local cropsz = 20 -- Ensure the crop marks stay outside the bleed area local offset = math.max(10, SILE.documentState.bleed / 2) diff --git a/typesetters/base.lua b/typesetters/base.lua index f086cbcd5..e30748b01 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -940,14 +940,12 @@ function typesetter:makeHbox (content) local ox = atypesetter.frame.state.cursorX local oy = atypesetter.frame.state.cursorY SILE.outputter:setCursor(atypesetter.frame.state.cursorX, atypesetter.frame.state.cursorY) - -- BEGIN SILEX FIX DEBUG SU.debug("hboxes", function () - -- setCursor also invoked by the internal hboxes etc. + -- setCursor is also invoked by the internal (wrapped) hboxes etc. -- so we must show our debug box before outputting its content. SILE.outputter:debugHbox(box, box:scaledWidth(line)) return "Drew debug outline around hbox" end) - -- END SILEX FIX DEBUG for _, node in ipairs(box.value) do node:outputYourself(atypesetter, line) end From ad2faa5a7f79d078decb26f592b2389ad1708a25 Mon Sep 17 00:00:00 2001 From: Caleb Maclennan Date: Wed, 13 Dec 2023 15:25:38 +0300 Subject: [PATCH 10/10] refactor(packages): Re-organize sheet size logic so errors happen in a more related function --- classes/base.lua | 35 +++++++++++++++-------------------- documentation/c03-input.sil | 20 +++++++++++--------- outputters/libtexpdf.lua | 12 ++++++++---- packages/cropmarks/init.lua | 2 +- tests/bug-337.sil | 2 +- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/classes/base.lua b/classes/base.lua index 090d5cdc8..aab8cb138 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -92,24 +92,6 @@ function class:setOptions (options) for option, value in pairs(options) do self.options[option] = value end - if not SILE.documentState.sheetSize then - SILE.documentState.sheetSize = { - SILE.documentState.paperSize[1], - SILE.documentState.paperSize[2] - } - end - if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] - or SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] then - SU.error("Sheet size shall not be smaller than the paper size") - end - if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + SILE.documentState.bleed then - SU.debug("frames", "Sheet size width augmented to take page bleed into account") - SILE.documentState.sheetSize[1] = SILE.documentState.paperSize[1] + SILE.documentState.bleed - end - if SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] + SILE.documentState.bleed then - SU.debug("frames", "Sheet size height augmented to take page bleed into account") - SILE.documentState.sheetSize[2] = SILE.documentState.paperSize[2] + SILE.documentState.bleed - end end function class:declareOption (option, setter) @@ -153,9 +135,22 @@ function class:declareOptions () if size then self.sheetsize = size SILE.documentState.sheetSize = SILE.papersize(size, self.options.landscape) + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + or SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] then + SU.error("Sheet size shall not be smaller than the paper size") + end + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size width augmented to take page bleed into account") + SILE.documentState.sheetSize[1] = SILE.documentState.paperSize[1] + SILE.documentState.bleed + end + if SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size height augmented to take page bleed into account") + SILE.documentState.sheetSize[2] = SILE.documentState.paperSize[2] + SILE.documentState.bleed + end + else + return self.sheetsize end - return self.sheetsize - end) + end) self:declareOption("bleed", function (_, dimen) if dimen then self.bleed = dimen diff --git a/documentation/c03-input.sil b/documentation/c03-input.sil index 16edf8e54..a8a42f390 100644 --- a/documentation/c03-input.sil +++ b/documentation/c03-input.sil @@ -8,7 +8,7 @@ It is even less true now that 3rd party plugins can add their own input formats. Hence this chanpter has been renamed. The original chapter title was "SILE’s Input Language", as if there was only one. -The truth is there \em{is} an input syntax we call "SIL", but even that is perhaps best thought of as a structured data systax rather than a unique language. +The truth is there \em{is} an input syntax we call "SIL", but even that is perhaps best thought of as a structured data syntax rather than a unique language. The input strings \code{\\em\{foo\}} in SIL input syntax is 100\% equivalent to \code{foo} in XML input syntax. The SIL input syntax is provided as an easier to type alternative than XML which can be a bit verbose and tedious to work with by hand. On the other hand if you're handling data written by some other program, XML might be a much better solution. @@ -61,6 +61,16 @@ Once some of the basic document properties have been set up using these fixed si For example, once the paper size is set, percentage of page width (\code{\%pw}) and height(\code{\%ph}) become valid units. In Chapter 4 we will meet more of these relative units, and in Chapter 7 we will meet some other ways of specifying lengths to make them stretchable or shrinkable. +\subsection{Setting orientation as landscape} + +The orientation of the page is defined as "portrait" by default, but if you want to set it as landscape there is an option for that: + +\begin[type=autodoc:codeblock]{raw} +\begin[landscape=true]{document} +\end{raw} + +\subsection{Full bleed printing} + When preparing a book for press printing, you may be asked by the professional printer to output the document on a larger sheet than your target paper, and to reserve a trim area around it. This trick is often called “full bleed printing”. Your document will be printed on an oversized sheet that will then be mechanically cut down to the target size. @@ -80,14 +90,6 @@ Finally, there is also the case when the actual paper sheets available to you ar For instance, \code{papersize=6in x 9in, sheetsize=a4} produces an A4-dimensioned document, but with you content formatted as a 6 per 9 inches US trade book. You may, obviously, combine these options and also specify a bleed area. -\subsection{Setting orientation as landscape} - -The orientation of the page is defined as "portrait" by default, but if you want to set it as landscape there is an option for that: - -\begin[type=autodoc:codeblock]{raw} -\begin[landscape=true]{document} -\end{raw} - \section{Ordinary text} On the whole, ordinary text isn’t particularly interesting—it’s just typeset. diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index 34d06f1d1..7750c135a 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -28,13 +28,15 @@ local deltaX local deltaY local function trueXCoord (x) if not deltaX then - deltaX = (SILE.documentState.sheetSize[1] - SILE.documentState.paperSize[1]) / 2 + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + deltaX = (sheetSize[1] - SILE.documentState.paperSize[1]) / 2 end return x + deltaX end local function trueYCoord (y) if not deltaY then - deltaY = (SILE.documentState.sheetSize[2] - SILE.documentState.paperSize[2]) / 2 + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + deltaY = (sheetSize[2] - SILE.documentState.paperSize[2]) / 2 end return y + deltaY end @@ -45,7 +47,8 @@ end function outputter:_ensureInit () if not started then - local w, h = SILE.documentState.sheetSize[1], SILE.documentState.sheetSize[2] + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + local w, h = sheetSize[1], sheetSize[2] local fname = self:getOutputFilename() -- Ideally we could want to set the PDF CropBox, BleedBox, TrimBox... -- Our wrapper only manages the MediaBox at this point. @@ -193,7 +196,8 @@ function outputter:drawSVG (figure, x, y, _, height, scalefactor) pdf.add_content("q") self:setCursor(x, y) x, y = self:getCursor() - local newy = y - SILE.documentState.paperSize[2] / 2 + height - SILE.documentState.sheetSize[2] / 2 + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + local newy = y - SILE.documentState.paperSize[2] / 2 + height - sheetSize[2] / 2 pdf.add_content(table.concat({ scalefactor, 0, 0, -scalefactor, trueXCoord(x), newy, "cm" }, " ")) pdf.add_content(figure) pdf.add_content("Q") diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index f889be605..2fd529f56 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -58,7 +58,7 @@ end function package:registerCommands () self:registerCommand("cropmarks:header", function (_, _) - local info = SILE.masterFilename + local info = SILE.input.filenames[1] .. " - " .. self.class.packages.date:date({ format = "%x %X" }) .. " - " .. outcounter diff --git a/tests/bug-337.sil b/tests/bug-337.sil index 54ca7a454..4cdfd922e 100644 --- a/tests/bug-337.sil +++ b/tests/bug-337.sil @@ -1,4 +1,4 @@ -\begin[papersize=a7,class=book, sheetsize=a6]{document} +\begin[papersize=a7,class=book,sheetsize=a6]{document} \use[module=packages.color] \script[src=inc.bug-337] \define[command=cropmarks:header]{tests/bug-337.sil}