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.") 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") 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/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/documentation/c12-tricks.sil b/documentation/c12-tricks.sil index c347c47fd..8d5af0b58 100644 --- a/documentation/c12-tricks.sil +++ b/documentation/c12-tricks.sil @@ -199,15 +199,16 @@ function discovery:typesetProphecy (symbol) \end{verbatim} 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. + this allows the book designer to change the symbols at the SILE level rather than having to 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 put the marking into the margin. 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/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 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) 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} diff --git a/typesetters/base.lua b/typesetters/base.lua index b2751dfbb..3687896ff 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,108 @@ 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 + 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() + 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