From 0f5bc69981140553374ae2c5cc30f0fff913cf61 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 28 Jan 2023 01:32:30 +0100 Subject: [PATCH 1/8] feat(typesetters): Implement hbox building logic in the typesetter Construction of an hbox from content depends a lot on the typesetter internal logic and structure. The implementation here is based on the original one from the plain class, but doesn't push the hbox into the typesetter queue (so that it doesn't have to be "stolen" back if only built for measuring it) and additionally extracts and returns migrating nodes that would be lost otherwise inside the hbox. --- typesetters/base.lua | 95 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/typesetters/base.lua b/typesetters/base.lua index b2751dfbb..22651ad0b 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -591,6 +591,14 @@ function typesetter:outputLinesToPage (lines) end function typesetter:leaveHmode (independent) + if self.state.hmodeOnly then + -- HACK HBOX + -- This should likely be an error, but may break existing uses + -- (although these are probably already defective). + -- See also comment HACK HBOX in typesetter:makeHbox(). + SU.warn([[Building paragraphs in this context may have unpredictable results. +It will likely break in future versions]]) + end SU.debug("typesetter", "Leaving hmode") local margins = self:getMargins() local vboxlist = self:boxUpNodes() @@ -762,4 +770,91 @@ function typesetter:chuck () -- emergency shipout everything self.state.outputQueue = {} end +-- Logic for building an hbox from content. +-- It returns the hbox and an horizontal list of (migrating) elements +-- extracted outside of it. +-- None of these are pushed to the typesetter node queue. The caller +-- is responsible of doing it, if the hbox is built for anything +-- else than e.g. measuring it. Likewise, the call has to decide +-- what to do with the migrating content. +local _rtl_pre_post = function (box, atypesetter, line) + local advance = function () atypesetter.frame:advanceWritingDirection(box:scaledWidth(line)) end + if atypesetter.frame:writingDirection() == "RTL" then + advance() + return function () end + else + return advance + end +end +function typesetter:makeHbox (content) + local recentContribution = {} + local migratingNodes = {} + + -- HACK HBOX + -- This is from the original implementation. + -- It would be somewhat cleaner to use a temporary typesetter state + -- (pushState/popState) rather than using the current one, removing + -- the processed nodes from it afterwards. However, as long + -- as leaving horizontal mode is not strictly forbidden here, it would + -- lead to a possibly different result (the output queue being skipped). + -- See also HACK HBOX comment in typesetter:leaveHmode(). + local index = #(self.state.nodes)+1 + self.state.hmodeOnly = true + SILE.process(content) + self.state.hmodeOnly = false -- Wouldn't be needed in a temporary state + + local l = SILE.length() + local h, d = SILE.length(), SILE.length() + for i = index, #(self.state.nodes) do + local node = self.state.nodes[i] + if node.is_migrating then + migratingNodes[#migratingNodes+1] = node + elseif node.is_unshaped then + local shape = node:shape() + for _, attr in ipairs(shape) do + recentContribution[#recentContribution+1] = attr + h = attr.height > h and attr.height or h + d = attr.depth > d and attr.depth or d + l = l + attr:lineContribution():absolute() + end + else + recentContribution[#recentContribution+1] = node + l = l + node:lineContribution():absolute() + h = node.height > h and node.height or h + d = node.depth > d and node.depth or d + end + self.state.nodes[i] = nil -- wouldn't be needed in a temporary state + end + + local hbox = SILE.nodefactory.hbox({ + height = h, + width = l, + depth = d, + value = recentContribution, + outputYourself = function (box, atypesetter, line) + local _post = _rtl_pre_post(box, atypesetter, line) + local ox = atypesetter.frame.state.cursorX + local oy = atypesetter.frame.state.cursorY + SILE.outputter:setCursor(atypesetter.frame.state.cursorX, atypesetter.frame.state.cursorY) + 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 +end + +function typesetter:pushHlist (hlist) + for _, h in ipairs(hlist) do + self:pushHorizontal(h) + end +end + return typesetter From 5f677d16f3970aa188b87560b2bfbd5bc465ecef Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 28 Jan 2023 01:37:33 +0100 Subject: [PATCH 2/8] refactor(classes): Re-implement the hbox command in the plain class Use the hbox logic now provided by the typesetter. --- classes/plain.lua | 74 ++++++++++++----------------------------------- 1 file changed, 19 insertions(+), 55 deletions(-) diff --git a/classes/plain.lua b/classes/plain.lua index 8f5015912..d69860f0a 100644 --- a/classes/plain.lua +++ b/classes/plain.lua @@ -333,63 +333,27 @@ function class:registerCommands () end) end) - local _rtl_pre_post = function (box, typesetter, line) - local advance = function () typesetter.frame:advanceWritingDirection(box:scaledWidth(line)) end - if typesetter.frame:writingDirection() == "RTL" then - advance() - return function () end - else - return advance - end - end - self:registerCommand("hbox", function (_, content) - local index = #(SILE.typesetter.state.nodes)+1 - local recentContribution = {} - SILE.process(content) - local l = SILE.length() - local h, d = SILE.length(), SILE.length() - for i = index, #(SILE.typesetter.state.nodes) do - local node = SILE.typesetter.state.nodes[i] - if node.is_unshaped then - local shape = node:shape() - for _, attr in ipairs(shape) do - recentContribution[#recentContribution+1] = attr - h = attr.height > h and attr.height or h - d = attr.depth > d and attr.depth or d - l = l + attr:lineContribution():absolute() - end - else - recentContribution[#recentContribution+1] = node - l = l + node:lineContribution():absolute() - h = node.height > h and node.height or h - d = node.depth > d and node.depth or d - end - SILE.typesetter.state.nodes[i] = nil - end - local hbox = SILE.nodefactory.hbox({ - height = h, - width = l, - depth = d, - value = recentContribution, - outputYourself = function (self_, typesetter, line) - local _post = _rtl_pre_post(self_, typesetter, line) - local ox = typesetter.frame.state.cursorX - local oy = typesetter.frame.state.cursorY - SILE.outputter:setCursor(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY) - for _, node in ipairs(self_.value) do - node:outputYourself(typesetter, line) - end - typesetter.frame.state.cursorX = ox - typesetter.frame.state.cursorY = oy - _post() - SU.debug("hboxes", function () - SILE.outputter:debugHbox(self_, self_:scaledWidth(line)) - return "Drew debug outline around hbox" - end) - end - }) + local hbox, hlist = SILE.typesetter:makeHbox(content) + -- HACK + -- Direct insertion in the typesetter node queue comes from + -- the original implementation. + -- It would likely be clearer to use: SILE.typesetter:pushHbox(hbox) + -- but the latter adds a zerohbox sometimes (on initline), so it will + -- break some non-regression test and possibly have some effect at + -- places... For now, therefore, keep that unchanged, but it should + -- be investigated. table.insert(SILE.typesetter.state.nodes, hbox) + + if #hlist > 0 then + SU.warn("Hbox has migrating content (ignored for now, but likely to break in future versions)") + -- Ugly shim: + -- One day we ought to do SILE.typesetter:pushHlist(hlist) here, so as to push + -- back the migrating contents from within the hbox'ed content. + -- However, old Lua code assumed the hbox to be returned, and sometimes removed it + -- from the typesetter queue (for measuring, etc.), assuming it was the last + -- element in the queue... + end return hbox end, "Compiles all the enclosed horizontal-mode material into a single hbox") From da3ab6d73e267c448fac7f17e650a4feaeb1c577 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 28 Jan 2023 01:40:18 +0100 Subject: [PATCH 3/8] feat(packages): Support migrating content in re-wrapped hboxes And also remove the "hbox stealing" hack from packages. --- packages/chordmode/init.lua | 3 +-- packages/cropmarks/init.lua | 7 ++++--- packages/dropcaps/init.lua | 3 +-- packages/frametricks/init.lua | 3 +-- packages/gutenberg/init.lua | 7 +++++-- packages/leaders/init.lua | 7 ++++--- packages/lists/init.lua | 3 +-- packages/pdf/init.lua | 6 +++++- packages/pullquote/init.lua | 3 +-- packages/rebox/init.lua | 6 +++--- packages/rotate/init.lua | 4 ++-- packages/ruby/init.lua | 6 ++---- packages/rules/init.lua | 15 ++++++--------- packages/simpletable/init.lua | 7 +++++-- packages/tate/init.lua | 22 ++++++++++------------ 15 files changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/chordmode/init.lua b/packages/chordmode/init.lua index 541ceab83..daddd0dde 100644 --- a/packages/chordmode/init.lua +++ b/packages/chordmode/init.lua @@ -29,8 +29,7 @@ end function package:registerCommands () self:registerCommand("ch", function (options, content) - local chordBox = SILE.call("hbox", {}, { options.name }) - SILE.typesetter.state.nodes[#(SILE.typesetter.state.nodes)] = nil + local chordBox = SILE.typesetter:makeHbox({ options.name }) local origWidth = chordBox.width chordBox.width = SILE.length() chordBox.height = SILE.settings:get("chordmode.lineheight") diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index 3bd3c3a56..f206a310d 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -16,15 +16,16 @@ local function outputMarks () SILE.outputter:drawRule(page:right() + 10, page:bottom(), 10, 0.5) SILE.outputter:drawRule(page:right(), page:bottom() + 10, 0.5, 10) - SILE.call("hbox", {}, function () + local hbox, hlist = SILE.typesetter:makeHbox(function () SILE.settings:temporarily(function () SILE.call("noindent") SILE.call("font", { size="6pt" }) SILE.call("crop:header") end) end) - local hbox = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + if #hlist > 0 then + SU.error("Forbidden migrating content in crop header") + end SILE.typesetter.frame.state.cursorX = page:left() + 10 SILE.typesetter.frame.state.cursorY = page:top() - 13 diff --git a/packages/dropcaps/init.lua b/packages/dropcaps/init.lua index 81106d9f2..fbd1aa8b8 100644 --- a/packages/dropcaps/init.lua +++ b/packages/dropcaps/init.lua @@ -13,10 +13,9 @@ local shapeHbox = function (options, content) -- Clear irrelevant values before passing to font options.lines, options.join, options.raise, options.shift, options.color, options.scale = nil, nil, nil, nil, nil, nil SILE.call("noindent") - local hbox = SILE.call("hbox", {}, function () + local hbox = SILE.typesetter:makeHbox(function () SILE.call("font", options, content) end) - table.remove(SILE.typesetter.state.nodes) return hbox end diff --git a/packages/frametricks/init.lua b/packages/frametricks/init.lua index a2fa11e3b..1bc091108 100644 --- a/packages/frametricks/init.lua +++ b/packages/frametricks/init.lua @@ -183,8 +183,7 @@ function package:registerCommands () self:registerCommand("float", function (options, content) SILE.typesetter:leaveHmode() - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- steal it back + local hbox = SILE.typesetter:makeHbox(content) -- HACK What about migrating nodes here? local heightOfPageSoFar = SILE.pagebuilder:collateVboxes(SILE.typesetter.state.outputQueue).height local overshoot = SILE.length(heightOfPageSoFar + hbox.height - SILE.typesetter:getTargetLength()) if overshoot > SILE.length(0) then diff --git a/packages/gutenberg/init.lua b/packages/gutenberg/init.lua index a7afb66ef..b45f9d3e7 100644 --- a/packages/gutenberg/init.lua +++ b/packages/gutenberg/init.lua @@ -12,8 +12,11 @@ function package:registerCommands () self:registerCommand("alternative", function (_, content) local alts = {} for _, fragment in ipairs(content) do - SILE.call("hbox", {}, { fragment }) - table.insert(alts, table.remove(SILE.typesetter.state.nodes)) + local hbox, hlist = SILE.typesetter:makeHbox({ fragment }) + if #hlist > 0 then + SU.error("Forbidden migrating content in alternative") + end + table.insert(alts, hbox) end local alternative = SILE.nodefactory.alternative({ options = alts, diff --git a/packages/leaders/init.lua b/packages/leaders/init.lua index ac16fe23d..40ac9fba6 100644 --- a/packages/leaders/init.lua +++ b/packages/leaders/init.lua @@ -87,9 +87,10 @@ function package:registerCommands () self:registerCommand("leaders", function(options, content) local width = options.width and SU.cast("glue", options.width) or SILE.nodefactory.hfillglue() - SILE.call("hbox", {}, content) - local hbox = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + local hbox, hlist = SILE.typesetter:makeHbox(content) + if #hlist > 0 then + SU.error("Forbidden migrating content in leaders") + end local l = leader({ width = width, value = hbox }) SILE.typesetter:pushExplicitGlue(l) end) diff --git a/packages/lists/init.lua b/packages/lists/init.lua index dd41400fe..a7c862bf0 100644 --- a/packages/lists/init.lua +++ b/packages/lists/init.lua @@ -86,7 +86,7 @@ function package:doItem (options, content) SILE.call("par") end - local mark = SILE.call("hbox", {}, function () + local mark = SILE.typesetter:makeHbox(function () if enumStyle.display then if enumStyle.before then SILE.typesetter:typeset(enumStyle.before) end SILE.typesetter:typeset(self.class.packages.counters:formatCounter({ @@ -99,7 +99,6 @@ function package:doItem (options, content) SILE.typesetter:typeset(bullet) end end) - table.remove(SILE.typesetter.state.nodes) -- steal it back local stepback if enumStyle.display then diff --git a/packages/pdf/init.lua b/packages/pdf/init.lua index 6e4f90e33..f6622a3bc 100644 --- a/packages/pdf/init.lua +++ b/packages/pdf/init.lua @@ -113,7 +113,11 @@ function package:registerCommands () pdf.begin_annotation() end }) - local hbox = SILE.call("hbox", {}, content) -- hack + + 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), diff --git a/packages/pullquote/init.lua b/packages/pullquote/init.lua index b77210af2..078faf9a6 100644 --- a/packages/pullquote/init.lua +++ b/packages/pullquote/init.lua @@ -14,8 +14,7 @@ local typesetMark = function (open, setback, scale, color, mark) SILE.call("rebox", { width = setback, height = 0 }, { mark }) else SILE.typesetter:pushGlue(SILE.nodefactory.hfillglue()) - local hbox = SILE.call("hbox", {}, { mark }) - table.remove(SILE.typesetter.state.nodes) -- steal it back + local hbox = SILE.typesetter:makeHbox({ mark }) -- for measuring SILE.typesetter:pushGlue({ width = setback - hbox.width }) SILE.call("rebox", { width = hbox.width, height = 0 }, { mark }) SILE.typesetter:pushGlue({ width = -setback }) diff --git a/packages/rebox/init.lua b/packages/rebox/init.lua index d56147824..ac34335a5 100644 --- a/packages/rebox/init.lua +++ b/packages/rebox/init.lua @@ -6,8 +6,7 @@ package._name = "rebox" function package:registerCommands () self:registerCommand("rebox", function (options, content) - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- steal it back + local hbox, hlist = SILE.typesetter:makeHbox(content) if options.width then hbox.width = SILE.length(options.width) end if options.height then hbox.height = SILE.length(options.height) end if options.depth then hbox.depth = SILE.length(options.depth) end @@ -16,7 +15,8 @@ function package:registerCommands () typesetter.frame:advanceWritingDirection(node:scaledWidth(line)) end end - table.insert(SILE.typesetter.state.nodes, hbox) + SILE.typesetter:pushHbox(hbox) + SILE.typesetter:pushHlist(hlist) end, "Place the output within a hbox of specified width, height, depth and visibility") end diff --git a/packages/rotate/init.lua b/packages/rotate/init.lua index c7420cf00..5982f0c2c 100644 --- a/packages/rotate/init.lua +++ b/packages/rotate/init.lua @@ -67,8 +67,7 @@ function package:registerCommands () self:registerCommand("rotate", function(options, content) local angle = SU.required(options, "angle", "rotate command") local theta = -math.rad(angle) - local origbox = SILE.call("hbox", {}, content) - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + local origbox, hlist = SILE.typesetter:makeHbox(content) local h = origbox.height + origbox.depth local w = origbox.width.length local st = math.sin(theta) @@ -101,6 +100,7 @@ function package:registerCommands () depth = depth, outputYourself = outputRotatedHbox }) + SILE.typesetter:pushHlist(hlist) end) end diff --git a/packages/ruby/init.lua b/packages/ruby/init.lua index a43ed5c8e..ec8af8edf 100644 --- a/packages/ruby/init.lua +++ b/packages/ruby/init.lua @@ -65,14 +65,13 @@ function package:registerCommands () checkIfSpacerNeeded(reading) - SILE.call("hbox", {}, function () + local rubybox = SILE.call("hbox", {}, function () SILE.settings:temporarily(function () SILE.call("noindent") SILE.call("ruby:font") SILE.typesetter:typeset(reading) end) end) - local rubybox = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] rubybox.outputYourself = function (box, typesetter, line) local ox = typesetter.frame.state.cursorX local oy = typesetter.frame.state.cursorY @@ -87,8 +86,7 @@ function package:registerCommands () typesetter.frame.state.cursorY = oy end -- measure the content - SILE.call("hbox", {}, content) - local cbox = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] + local cbox = SILE.call("hbox", {}, content) SU.debug("ruby", "base box is", cbox) SU.debug("ruby", "reading is", rubybox) if cbox:lineContribution() > rubybox:lineContribution() then diff --git a/packages/rules/init.lua b/packages/rules/init.lua index f99bef3d4..c622f1a8a 100644 --- a/packages/rules/init.lua +++ b/packages/rules/init.lua @@ -149,9 +149,7 @@ function package:registerCommands () self:registerCommand("underline", function (_, content) local underlinePosition, underlineThickness = getUnderlineParameters() - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- steal it back... - + local hbox, hlist = SILE.typesetter:makeHbox(content) -- Re-wrap the hbox in another hbox responsible for boxing it at output -- time, when we will know the line contribution and can compute the scaled width -- of the box, taking into account possible stretching and shrinking. @@ -176,14 +174,13 @@ function package:registerCommands () SILE.outputter:drawRule(oldX, Y - underlinePosition, newX - oldX, underlineThickness) end }) + SILE.typesetter:pushHlist(hlist) end, "Underlines some content") self:registerCommand("strikethrough", function (_, content) local yStrikeoutPosition, yStrikeoutSize = getStrikethroughParameters() - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- steal it back... - + local hbox, hlist = SILE.typesetter:makeHbox(content) -- Re-wrap the hbox in another hbox responsible for boxing it at output -- time, when we will know the line contribution and can compute the scaled width -- of the box, taking into account possible stretching and shrinking. @@ -205,6 +202,7 @@ function package:registerCommands () SILE.outputter:drawRule(oldX, Y - yStrikeoutPosition - yStrikeoutSize / 2, newX - oldX, yStrikeoutSize) end }) + SILE.typesetter:pushHlist(hlist) end, "Strikes out some content") self:registerCommand("boxaround", function (_, content) @@ -212,9 +210,7 @@ function package:registerCommands () -- Plan replacement with a better suited package. SU.deprecated("\\boxaround (undocumented)", "\\framebox (package)", "0.12.0") - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- steal it back... - + local hbox, hlist = SILE.typesetter:makeHbox(content) -- Re-wrap the hbox in another hbox responsible for boxing it at output -- time, when we will know the line contribution and can compute the scaled width -- of the box, taking into account possible stretching and shrinking. @@ -245,6 +241,7 @@ function package:registerCommands () SILE.outputter:drawRule(oldX + w - thickness, Y - h, thickness, h + d) end }) + SILE.typesetter:pushHlist(hlist) end, "Draws a box around some content") end diff --git a/packages/simpletable/init.lua b/packages/simpletable/init.lua index 47ec84eef..b5d4ba577 100644 --- a/packages/simpletable/init.lua +++ b/packages/simpletable/init.lua @@ -38,11 +38,14 @@ function package:_init (options) self:registerCommand(tdTag, function(_, content) local tbl = SILE.scratch.simpletable.tables[#(SILE.scratch.simpletable.tables)] local row = tbl[#tbl] + local hbox, hlist = SILE.typesetter:makeHbox(content) row[#row+1] = { content = content, - hbox = SILE.call("hbox", {}, content) + hbox = hbox } - SILE.typesetter.state.nodes[#(SILE.typesetter.state.nodes)] = nil + if #hlist > 0 then + SU.warn("Ignored migrating content in simpletable row (unsupported)") + end end) self:registerCommand(tableTag, function(_, content) diff --git a/packages/tate/init.lua b/packages/tate/init.lua index 7071274f3..cae802fe5 100644 --- a/packages/tate/init.lua +++ b/packages/tate/init.lua @@ -89,18 +89,17 @@ function package:registerCommands () if SILE.typesetter.frame:writingDirection() ~= "TTB" or nodes[i].is_glue then SILE.typesetter:pushHorizontal(nodes[i]) elseif nodes[i]:lineContribution():tonumber() > 0 then - SILE.call("hbox", {}, function () + local hbox = SILE.call("hbox", {}, function () SILE.typesetter:pushHorizontal(nodes[i]) end) - local n = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] -- Turn off all complex flags. - for j = 1,#(n.value) do - for k = 1,#(n.value[j].nodes) do - n.value[j].nodes[k].value.complex = false + for j = 1, #(hbox.value) do + for k = 1, #(hbox.value[j].nodes) do + hbox.value[j].nodes[k].value.complex = false end end - n.oldOutputYourself = n.outputYourself - n.outputYourself = outputLatinInTate + hbox.oldOutputYourself = hbox.outputYourself + hbox.outputYourself = outputLatinInTate end end end, "Typeset rotated Western text in vertical Japanese") @@ -117,11 +116,10 @@ function package:registerCommands () SILE.settings:set("document.language", "und") SILE.settings:set("font.direction", "LTR") SILE.call("rotate",{angle =-90}, function () - SILE.call("hbox", {}, content) - local n = SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] - n.misfit = true - n.oldOutputYourself = n.outputYourself - n.outputYourself = outputTateChuYoko + local hbox = SILE.call("hbox", {}, content) + hbox.misfit = true + hbox.oldOutputYourself = hbox.outputYourself + hbox.outputYourself = outputTateChuYoko end) end) From fe4cad6ca479be0f3b75712da69eab116609c0c7 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 28 Jan 2023 04:12:28 +0100 Subject: [PATCH 4/8] refactor(classes): Avoid the hbox stealing hack in the base class --- classes/base.lua | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/classes/base.lua b/classes/base.lua index ae2579d20..e91b2feec 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -430,19 +430,16 @@ function class:registerCommands () self:registerCommand("discretionary", function (options, _) local discretionary = SILE.nodefactory.discretionary({}) if options.prebreak then - SILE.call("hbox", {}, function () SILE.typesetter:typeset(options.prebreak) end) - discretionary.prebreak = { SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] } - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + local hbox = SILE.typesetter:makeHbox({ options.prebreak }) + discretionary.prebreak = { hbox } end if options.postbreak then - SILE.call("hbox", {}, function () SILE.typesetter:typeset(options.postbreak) end) - discretionary.postbreak = { SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] } - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + local hbox = SILE.typesetter:makeHbox({ options.postbreak }) + discretionary.postbreak = { hbox } end if options.replacement then - SILE.call("hbox", {}, function () SILE.typesetter:typeset(options.replacement) end) - discretionary.replacement = { SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] } - SILE.typesetter.state.nodes[#SILE.typesetter.state.nodes] = nil + local hbox = SILE.typesetter:makeHbox({ options.replacement }) + discretionary.replacement = { hbox } end table.insert(SILE.typesetter.state.nodes, discretionary) end, "Inserts a discretionary node.") From 2f6976d4c983d4a7518c0960d29fab9eb6053d67 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Sat, 28 Jan 2023 04:14:49 +0100 Subject: [PATCH 5/8] docs(manual): Replace the hbox stealing hack in a code example --- documentation/c12-tricks.sil | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/documentation/c12-tricks.sil b/documentation/c12-tricks.sil index dc93eb741..4382d119a 100644 --- a/documentation/c12-tricks.sil +++ b/documentation/c12-tricks.sil @@ -228,17 +228,16 @@ function discovery:typesetProphecy (symbol) Next, we call another command to produce the symbol itself; this allows the book designer to change the symbols at the SILE level rather than having to -mess about with the Lua file. We use the \command{\\hbox} command to wrap the -output of the command into a hbox. \command{\\hbox} returns its output, but also -puts the box into the typesetter’s output node queue; we don’t want it to appear -in the main typeblock, so we remove the node again, leaving our private copy -in the \code{hbox} variable. +mess about with the Lua file. +We then wrap the output of the command into a hbox. Here, note that we do not +use the \command{\\hbox} command: it would put the box into the typesetter’s output +node queue, but we don’t want it to appear in the main typeblock. +So we just ask the typesetter to build the box and return it. \begin{verbatim} - local hbox = SILE.call("hbox",\{\}, function() + local hbox = SILE.typesetter:makeHbox(function() SILE.call("prophecy-"..symbol.."-mark") end) - table.remove(SILE.typesetter.state.nodes) \end{verbatim} What we \em{do} want in the output queue is our special hbox node which will From 91cb950c3b603f3154dc0aac1585f6cf2c1df127 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 29 Mar 2023 04:23:52 +0200 Subject: [PATCH 6/8] fix(typesetter): Account for discretionary dimensions in hbox building --- core/nodefactory.lua | 6 ++++++ typesetters/base.lua | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/core/nodefactory.lua b/core/nodefactory.lua index 478eda769..2043442e4 100644 --- a/core/nodefactory.lua +++ b/core/nodefactory.lua @@ -335,6 +335,12 @@ function nodefactory.discretionary:replacementHeight () return self.replaceh end +function nodefactory.discretionary:replacementDepth () + if self.replaced then return self.replaced end + self.replaced = _maxnode(self.replacement, "depth") + return self.replaced +end + nodefactory.alternative = pl.class(nodefactory.hbox) nodefactory.alternative.type = "alternative" diff --git a/typesetters/base.lua b/typesetters/base.lua index 22651ad0b..3687896ff 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -817,6 +817,23 @@ function typesetter:makeHbox (content) d = attr.depth > d and attr.depth or d l = l + attr:lineContribution():absolute() end + elseif node.is_discretionary then + -- HACK https://github.com/sile-typesetter/sile/issues/583 + -- Discretionary nodes have a null line contribution... + -- But if discretionary nodes occur inside an hbox, since the latter + -- is not line-broken, they will never be marked as 'used' and will + -- evaluate to the replacement content (if any)... + recentContribution[#recentContribution+1] = node + l = l + node:replacementWidth():absolute() + -- The replacement content may have ascenders and descenders... + local hdisc = node:replacementHeight():absolute() + local ddisc = node:replacementDepth():absolute() + h = hdisc > h and hdisc or h + d = ddisc > d and ddisc or d + -- By the way it's unclear how this is expected to work in TTB + -- writing direction. For other type of nodes, the line contribution + -- evaluates to the height rather than the width in TTB, but the + -- whole logic might then be dubious there too... else recentContribution[#recentContribution+1] = node l = l + node:lineContribution():absolute() From d6b490c544d45d4f365db7ebfa3dce1c5f2f6f79 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 29 Mar 2023 09:11:14 +0200 Subject: [PATCH 7/8] test(typesetter): Add test bug-583 for discretionary in hbox --- tests/bug-583.expected | 41 +++++++++++++++++++++++++++++++++++++++++ tests/bug-583.sil | 10 ++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/bug-583.expected create mode 100644 tests/bug-583.sil diff --git a/tests/bug-583.expected b/tests/bug-583.expected new file mode 100644 index 000000000..8b6c2432f --- /dev/null +++ b/tests/bug-583.expected @@ -0,0 +1,41 @@ +Set paper size 297.6377985 419.5275636 +Begin page +Mx 14.8819 +My 28.7889 +Set font Gentium Plus;10;400;;normal;;;LTR +T 11 w=3.1689 (() +Mx 18.0508 +T 60 68 86 68 w=18.9844 (Yasa) +Mx 37.0352 +T 183 w=2.2900 (’) +Mx 39.3252 +T 81 215 81 w=13.7451 (nın) +Mx 55.7305 +T 55 72 78 85 68 85 215 w=30.2246 (Tekrarı) +Mx 85.9551 +T 12 w=3.1689 ()) +Mx 91.7842 +T 76 81 w=8.2275 (in) +Mx 102.6719 +T 75 69 82 91 w=20.6543 (hbox) +Mx 14.8819 +My 40.7889 +T 11 w=3.1689 (() +Mx 18.0508 +T 60 68 86 68 w=18.9844 (Yasa) +Mx 37.0352 +T 183 w=2.2900 (’) +Mx 39.3252 +T 81 215 81 w=13.7451 (nın) +Mx 55.7289 +T 55 72 78 85 68 85 215 w=30.2246 (Tekrarı) +Mx 85.9535 +T 12 w=3.1689 ()) +Mx 91.7810 +T 81 82 87 w=13.9893 (not) +Mx 108.4288 +T 76 81 w=8.2275 (in) +Mx 119.3149 +T 75 69 82 91 w=20.6543 (hbox) +End page +Finish diff --git a/tests/bug-583.sil b/tests/bug-583.sil new file mode 100644 index 000000000..e843838d5 --- /dev/null +++ b/tests/bug-583.sil @@ -0,0 +1,10 @@ +\begin[papersize=a6]{document} +\neverindent +\nofolios +\define[command=ah]{\discretionary[prebreak=-, replacement=’]} + +(\hbox{Yasa\ah{}nın Tekrarı}) in hbox + +(Yasa\ah{}nın Tekrarı) not in hbox + +\end{document} From bbf07093eac16c31f89b51f6c4df2356450a90e1 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Wed, 29 Mar 2023 09:41:09 +0200 Subject: [PATCH 8/8] refactor(packages): Avoid hbox stealing hack in scalebox package --- documentation/c05-packages.sil | 2 +- packages/scalebox/init.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/c05-packages.sil b/documentation/c05-packages.sil index c0269cf26..ef5d8dffb 100644 --- a/documentation/c05-packages.sil +++ b/documentation/c05-packages.sil @@ -162,7 +162,7 @@ basic functionality to other packages and classes. Classes such as the \subsection{rebox} \package-documentation{rebox} -\section{scalebox} +\subsection{scalebox} \package-documentation{scalebox} \subsection{tableofcontents} diff --git a/packages/scalebox/init.lua b/packages/scalebox/init.lua index d3a5ac870..f4d8375c2 100644 --- a/packages/scalebox/init.lua +++ b/packages/scalebox/init.lua @@ -13,9 +13,8 @@ function package:registerCommands () SILE.outputter:_ensureInit() local pdf = require("justenoughlibtexpdf") - local hbox = SILE.call("hbox", {}, content) - table.remove(SILE.typesetter.state.nodes) -- Remove the box from queue - local xratio, yratio = SU.cast("number", options.xratio) or 1, SU.cast("number", options.yratio) or 1 + local hbox, hlist = SILE.typesetter:makeHbox(content) + local xratio, yratio = SU.cast("number", options.xratio or 1), SU.cast("number", options.yratio or 1) if xratio == 0 or yratio == 0 then SU.error("Scaling ratio cannot be null") end @@ -55,6 +54,7 @@ function package:registerCommands () typesetter.frame:advanceWritingDirection(outputWidth) end }) + SILE.typesetter:pushHlist(hlist) end, "Scale content by some horizontal and vertical ratios") end