diff --git a/.commitlintrc.yml b/.commitlintrc.yml index dbf5190770..12a25a8fbe 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -46,13 +46,14 @@ rules: - nodes - outputters - packages + - pagebuilders - pdf - readme - release - settings - shapers - tooling - - typesetter + - typesetters - utilities help: | **Possible types**: diff --git a/build-aux/list-dist-files.sh.in b/build-aux/list-dist-files.sh.in index eaf29ee66b..c1705e220c 100755 --- a/build-aux/list-dist-files.sh.in +++ b/build-aux/list-dist-files.sh.in @@ -7,7 +7,7 @@ finder () { } printf '%s' "SILEDATA =" -finder core classes inputters languages outputters packages shapers typesetters -name '*.lua' -not -name '*_spec.lua' -not -name 'version.lua' +finder core classes inputters languages outputters packages shapers typesetters pagebuilders -name '*.lua' -not -name '*_spec.lua' -not -name 'version.lua' finder i18n -name '*.ftl' printf '\n%s' "LUALIBRARIES =" diff --git a/classes/base.lua b/classes/base.lua index b5edec930a..4cb1bd4113 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -58,7 +58,7 @@ function class:_init (options) self_.pageTemplate.firstContentFrame = self_.pageTemplate.frames[self_.firstContentFrame] end local frame = self_:initialFrame() - SILE.typesetter = SILE.defaultTypesetter(frame) + SILE.typesetter = SILE.typesetters.base(frame) SILE.typesetter:registerPageEndHook(function () SU.debug("frames", function () for _, v in pairs(SILE.frames) do SILE.outputter:debugFrame(v) end diff --git a/classes/tplain.lua b/classes/tplain.lua index 55f30de72d..c43a1f7a8e 100644 --- a/classes/tplain.lua +++ b/classes/tplain.lua @@ -20,7 +20,7 @@ function class:_t_common () self:loadPackage("hanmenkyoshi") self:registerPostinit(function (class_) class_:bidiDisableTypesetter(SILE.typesetter) - class_:bidiDisableTypesetter(SILE.defaultTypesetter) + class_:bidiDisableTypesetter(SILE.typesetters.base) end) self.defaultFrameset.content.tate = self.options.layout == "tate" self:declareHanmenFrame("content", self.defaultFrameset.content) diff --git a/core/pagebuilder.lua b/core/pagebuilder.lua index 37182313c0..36b68f159d 100644 --- a/core/pagebuilder.lua +++ b/core/pagebuilder.lua @@ -1,128 +1,3 @@ -return pl.class({ +SU.deprecated("core.pagebuilder", "pagebuilder.base", "0.14.6", "0.15.0") - _init = function (self) - self.awful_bad = 1073741823 - self.inf_bad = 10000 - self.eject_penalty = -self.inf_bad - self.deplorable = 100000 - end, - - collateVboxes = function(_, vboxlist) - local output = SILE.nodefactory.vbox() - output:append(vboxlist) - return output - end, - - -- Note: Almost 1/3 of the time in a typical SILE in taken iterating through - -- this function. As a result there are some micro-optimizations here that - -- make it a-typical of preferred coding styles. In particular note that - -- we absolutize heavily iterated lengths as early as possible and make - -- make direct calls to their integer amounts, assumed to be in points by - -- the point they are called **without actually checking**! - findBestBreak = function(self, options) - local vboxlist = SU.required(options, "vboxlist", "in findBestBreak") - local target = SU.required(options, "target", "in findBestBreak", "length") - local restart = options.restart or false - local force = options.force or false - local i = 0 - local totalHeight = SILE.length() - local bestBreak = nil - local started = false - if restart and restart.target == target then - totalHeight = restart.totalHeight - i = restart.i - started = restart.started - end - local leastC = self.inf_bad - SU.debug("pagebuilder", function () - return "Page builder for frame " .. SILE.typesetter.frame.id .. " called with " .. #vboxlist .. " nodes, " .. tostring(target) - end) - if SU.debugging("vboxes") then - for j, box in ipairs(vboxlist) do - SU.debug("vboxes", function () - return (j == i and " >" or " ") .. j .. ": " .. box - end) - end - end - while not started and i < #vboxlist do - i = i + 1 - if not vboxlist[i].is_vglue then - started = true - i = i - 1 - break - end - end - local pi - while i < #vboxlist do - i = i + 1 - local vbox = vboxlist[i] - SU.debug("pagebuilder", "Dealing with VBox", vbox) - if vbox.is_vbox then - totalHeight:___add(vbox.height) - totalHeight:___add(vbox.depth) - elseif vbox.is_vglue then - totalHeight:___add(vbox.height) - elseif vbox.is_insertion then - -- TODO: refactor as hook and without side effects! - target = SILE.insertions.processInsertion(vboxlist, i, totalHeight, target) - vbox = vboxlist[i] - end - local left = target - totalHeight - SU.debug("pagebuilder", "I have", left, "left") - -- if left < -20 then SU.error("\nCatastrophic page breaking failure!"); end - pi = 0 - if vbox.is_penalty then - pi = vbox.penalty - -- print("PI "..pi) - end - if vbox.is_penalty and vbox.penalty < self.inf_bad - or (vbox.is_vglue and i > 1 and not vboxlist[i-1].discardable) then - local badness - SU.debug("pagebuilder", "totalHeight", totalHeight, "with target", target) - if totalHeight.length.amount < target.length.amount then -- TeX #1039 - -- Account for infinite stretch? - badness = SU.rateBadness(self.inf_bad, left.length.amount, totalHeight.stretch.amount) - -- print("Height == "..totalHeight.length, "target=="..target, "stretch=="..totalHeight.stretch) - elseif left.length.amount < totalHeight.shrink.amount then badness = self.awful_bad - else badness = SU.rateBadness(self.inf_bad, -left.length.amount, totalHeight.shrink.amount) - end - - local c - if badness < self.awful_bad then - if pi <= self.eject_penalty then c = pi - elseif badness < self.inf_bad then c = badness + pi -- plus insert - else c = self.deplorable - end - else c = badness end - if c < leastC then - leastC = c - bestBreak = i - else - restart = { totalHeight = totalHeight, i = i, started = started, target = target} - end - -- print("Badness "..badness .." c = "..c) - SU.debug("pagebuilder", "Badness:", c) - if c == self.awful_bad or pi <= self.eject_penalty then - SU.debug("pagebuilder", "outputting") - local onepage = {} - if not bestBreak then bestBreak = i end - for j=1,bestBreak do - onepage[j] = table.remove(vboxlist,1) - end - while(#onepage > 1 and onepage[#onepage].discardable) do onepage[#onepage] = nil end - return onepage, pi - end - end - end - SU.debug("pagebuilder", "No page break here") - if force and bestBreak then - local onepage = {} - for j=1,bestBreak do - onepage[j] = table.remove(vboxlist,1) - end - return onepage, pi - end - return false, restart - end - -}) +return require("pagebuilders.base") diff --git a/core/sile.lua b/core/sile.lua index 3bc56657d4..1a945b5fe0 100644 --- a/core/sile.lua +++ b/core/sile.lua @@ -74,7 +74,8 @@ SILE.shapers = core_loader("shapers") SILE.outputters = core_loader("outputters") SILE.classes = core_loader("classes") SILE.packages = core_loader("packages") --- SILE.typesetters = core_loader("typesetters") +SILE.typesetters = core_loader("typesetters") +SILE.pagebuilders = core_loader("pagebuilders") -- Internal libraries that don't make assumptions on load, only use SILE.traceStack = require("core.tracestack")() @@ -133,6 +134,7 @@ SILE.init = function () SILE.shaper = SILE.shapers.harfbuzz() SILE.outputter = SILE.outputters.dummy() end + SILE.pagebuilder = SILE.pagebuilders.base() runEvals(SILE.input.evaluates, "evaluate") end @@ -165,6 +167,9 @@ SILE.use = function (module, options) elseif pack.type == "typesetter" then SILE.typesetters[name] = pack SILE.typesetter = pack(options) + elseif pack.type == "pagebuilder" then + SILE.pagebuilders[name] = pack + SILE.pagebuilder = pack(options) elseif pack.type == "package" then SILE.packages[name] = pack if class then @@ -328,6 +333,20 @@ function SILE.processFile (filename, format, options) return ret end +-- TODO: this probably needs deprecating, moved here just to get out of the way so +-- typesetters classing works as expected +SILE.typesetNaturally = function (frame, func) + local saveTypesetter = SILE.typesetter + if SILE.typesetter.frame then SILE.typesetter.frame:leave(SILE.typesetter) end + SILE.typesetter = SILE.typesetters.base(frame) + SILE.settings:temporarily(func) + SILE.typesetter:leaveHmode() + SILE.typesetter:chuck() + SILE.typesetter.frame:leave(SILE.typesetter) + SILE.typesetter = saveTypesetter + if SILE.typesetter.frame then SILE.typesetter.frame:enter(SILE.typesetter) end +end + -- Sort through possible places files could be function SILE.resolveFile (filename, pathprefix) local candidates = {} @@ -419,8 +438,6 @@ end -- Internal libraries that run core SILE functions on load SILE.settings = require("core.settings")() -SILE.pagebuilder = require("core.pagebuilder")() -require("typesetters.base") require("core.hyphenator-liang") require("core.languages") require("core.packagemanager") diff --git a/packages/balanced-frames/init.lua b/packages/balanced-frames/init.lua index d2c9f48c72..4ac6e8fa9e 100644 --- a/packages/balanced-frames/init.lua +++ b/packages/balanced-frames/init.lua @@ -78,7 +78,7 @@ function package:_init (class) if not unbalanced_buildPage then unbalanced_buildPage = SILE.typesetter.buildPage SILE.typesetter.buildPage = buildPage - SILE.defaultTypesetter.buildPage = buildPage + SILE.typesetters.base.buildPage = buildPage end end) end diff --git a/packages/bidi/init.lua b/packages/bidi/init.lua index 1894bc2001..8862b6c7bf 100644 --- a/packages/bidi/init.lua +++ b/packages/bidi/init.lua @@ -256,7 +256,7 @@ function package:_init () if SILE.typesetter then self:bidiEnableTypesetter(SILE.typesetter) end - self:bidiEnableTypesetter(SILE.defaultTypesetter) + self:bidiEnableTypesetter(SILE.typesetters.base) end function package:registerCommands () diff --git a/packages/break-firstfit/init.lua b/packages/break-firstfit/init.lua index cfad492d77..30e02ab22b 100644 --- a/packages/break-firstfit/init.lua +++ b/packages/break-firstfit/init.lua @@ -7,36 +7,9 @@ package._name = "break-firstfit" -- algorithm, especially when you're dealing with vertical -- typesetting. Oh, and it's really fast too. -local firstfit = function (typesetter, nl, breakWidth) - local breaks = {} - local length = SILE.length() - for i = 1,#nl do local n = nl[i] - if n.is_box then - SU.debug("break", n, n:lineContribution()) - length = length + n:lineContribution() - SU.debug("break", " Length now", length, "breakwidth", breakWidth) - end - if not n.is_box or n.isHangable then - SU.debug("break", n) - if n.is_glue then - length = length + n.width:absolute() - end - SU.debug("break", " Length now", length, "breakwidth", breakWidth) - -- Can we break? - if length:tonumber() >= breakWidth:tonumber() then - SU.debug("break", "Breaking!") - breaks[#breaks+1] = { position = i, width = breakWidth} - length = SILE.length() - end - end - end - breaks[#breaks+1] = { position = #nl, width = breakWidth} - return typesetter:breakpointsToLines(breaks) -end - function package:_init () base._init(self) - SILE.typesetter._breakIntoLines_firstfit = firstfit + SILE.typesetters.firstfit:cast(SILE.typesetter) end package.documentation = [[ diff --git a/packages/grid/init.lua b/packages/grid/init.lua index af00c0ee87..a7d8a9f5b0 100644 --- a/packages/grid/init.lua +++ b/packages/grid/init.lua @@ -3,54 +3,7 @@ local base = require("packages.base") local package = pl.class(base) package._name = "grid" --- TODO: consider registering as a setting instead of a frame property -local gridSpacing - -local function makeUp (totals) - local toadd = (gridSpacing - SILE.measurement(totals.gridCursor)) % gridSpacing - totals.gridCursor = totals.gridCursor + toadd - SU.debug("typesetter", "Makeup height =", toadd) - return SILE.nodefactory.vglue(toadd) -end - -local function leadingFor (typesetter, vbox, previous) - SU.debug("typesetter", " Considering leading between two lines (grid mode):") - SU.debug("typesetter", " 1)", previous) - SU.debug("typesetter", " 2)", vbox) - if not previous then return SILE.nodefactory.vglue() end - SU.debug("typesetter", " Depth of previous line was", previous.depth) - local totals = typesetter.frame.state.totals - local oldCursor = SILE.measurement(totals.gridCursor) - totals.gridCursor = oldCursor + vbox.height:absolute() + previous.depth - SU.debug("typesetter", " Cursor change =", totals.gridCursor - oldCursor) - return makeUp(typesetter.frame.state.totals) -end - -local function pushVglue (typesetter, spec) - -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) - node.height.stretch = SILE.measurement() - node.height.shrink = SILE.measurement() - local totals = typesetter.frame.state.totals - totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() - typesetter:pushVertical(node) - typesetter:pushVertical(makeUp(typesetter.frame.state.totals)) - return node -end - -local function pushExplicitVglue (typesetter, spec) - -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) - node.explicit = true - node.discardable = false - node.height.stretch = SILE.measurement() - node.height.shrink = SILE.measurement() - local totals = typesetter.frame.state.totals - totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() - typesetter:pushVertical(node) - typesetter:pushVertical(makeUp(typesetter.frame.state.totals)) - return node -end +local oldPagebuilderType, oldTypesetterType local function startGridInFrame (typesetter) if not SILE.typesetter.state.grid then return end -- Ensure the frame hook isn't effective when grid is off @@ -60,7 +13,7 @@ local function startGridInFrame (typesetter) typesetter.state.previousVbox = typesetter:pushVbox() return end - while queue[1] and queue[1].discardable do + while queue[1] and (queue[1].discardable or queue[1].gridleading) do table.remove(queue, 1) end if queue[1] then @@ -69,101 +22,37 @@ local function startGridInFrame (typesetter) end end -local function debugGrid () - local frame = SILE.typesetter.frame - local gridCursor = gridSpacing - while SILE.measurement(gridCursor) < SILE.measurement(frame:height()) do - SILE.outputter:drawRule(frame:left(), frame:top() + gridCursor, frame:width(), 0.1) - gridCursor = gridCursor + gridSpacing - end -end - -local gridPagebuilder = pl.class(require("core.pagebuilder")) - -function gridPagebuilder.findBestBreak (_, options) - local vboxlist = SU.required(options, "vboxlist", "in findBestBreak") - local target = SU.required(options, "target", "in findBestBreak") - local i = 0 - local totalHeight = SILE.length() - local bestBreak = 0 - SU.debug("pagebuilder", "Page builder for frame", SILE.typesetter.frame.id, "called with", #vboxlist, "nodes,", target) - if SU.debugging("vboxes") then - for j, box in ipairs(vboxlist) do - SU.debug("vboxes", (j == i and " >" or " ") .. j .. ": " .. box) - end - end - while i < #vboxlist do - i = i + 1 - if not vboxlist[i].is_vglue then - i = i - 1 - break - end - end - while i < #vboxlist do - i = i + 1 - local node = vboxlist[i] - SU.debug("pagebuilder", "Dealing with VBox", node) - if node.is_vbox then - totalHeight = totalHeight + node.height:absolute() + node.depth:absolute() - elseif node.is_vglue then - totalHeight = totalHeight + node.height:absolute() - elseif node.is_insertion then - -- TODO: refactor as hook and without side effects! - target = SILE.insertions.processInsertion(vboxlist, i, totalHeight, target) - node = vboxlist[i] - end - local left = target - totalHeight - local _left = left:tonumber() - SU.debug("pagebuilder", "I have", left, "left") - SU.debug("pagebuilder", "totalHeight", totalHeight, "with target", target) - local badness = 0 - if _left < 0 then badness = 1000000 end - if node.is_penalty then - if node.penalty < -3000 then badness = 100000 - else badness = -_left * _left - node.penalty - end - end - if badness > 0 then - local onepage = {} - for j = 1, bestBreak do - onepage[j] = table.remove(vboxlist, 1) - end - while #onepage > 1 and onepage[#onepage].discardable do - onepage[#onepage] = nil - end - return onepage, 1000 - end - bestBreak = i - end - return false, false -end - -local oldPageBuilder, oldLeadingFor, oldPushVglue, oldPushExplicitVglue - -function package:_init () +function package:_init (options) + self.options = options or {} + self.options.spacing = SILE.measurement(self.options.spacing or "1bs") base._init(self) - gridSpacing = SILE.measurement() end function package:registerCommands () - self:registerCommand("grid:debug", function (_, _) + self:registerCommand("grid:debug", function (options, _) + local spacing = SU.cast("measurement", options.spacing or self.options.spacing) + local debugGrid = function () + local frame = SILE.typesetter.frame + local gridCursor = spacing + while gridCursor < frame:height() do + SILE.outputter:drawRule(frame:left(), frame:top() + gridCursor, frame:width(), 0.1) + gridCursor = gridCursor + spacing + end + end debugGrid() SILE.typesetter:registerNewFrameHook(debugGrid) end) self:registerCommand("grid", function (options, _) + local spacing = SU.cast("measurement", options.spacing or self.options.spacing) SILE.typesetter.state.grid = true - SU.required(options, "spacing", "grid package") - gridSpacing = SILE.parseComplexFrameDimension(options.spacing) - oldPageBuilder = SILE.pagebuilder - SILE.pagebuilder = gridPagebuilder() - oldLeadingFor = SILE.typesetter.leadingFor - SILE.typesetter.leadingFor = leadingFor - oldPushVglue = SILE.typesetter.pushVglue - SILE.typesetter.pushVglue = pushVglue - oldPushExplicitVglue = SILE.typesetter.pushExplicitVglue - SILE.typesetter.pushExplicitVglue = pushExplicitVglue + self.options.spacing = SILE.parseComplexFrameDimension(spacing) + oldPagebuilderType = SILE.pagebuilder._name + oldTypesetterType = SILE.typesetter._name + SILE.pagebuilders.grid:cast(SILE.pagebuilder) + SILE.typesetters.grid:cast(SILE.typesetter) + SILE.typesetter.options = { spacing = self.options.spacing } if SILE.typesetter.frame then startGridInFrame(SILE.typesetter) end @@ -172,10 +61,8 @@ function package:registerCommands () self:registerCommand("no-grid", function (_, _) SILE.typesetter.state.grid = false - SILE.typesetter.leadingFor = oldLeadingFor - SILE.typesetter.pushVglue = oldPushVglue - SILE.typesetter.pushExplicitVglue = oldPushExplicitVglue - SILE.pagebuilder = oldPageBuilder + SILE.typesetters[oldTypesetterType]:cast(SILE.typesetter) + SILE.pagebuilders[oldPagebuilderType]:cast(SILE.pagebuilder) end, "Stops grid typesetting.") end diff --git a/packages/insertions/init.lua b/packages/insertions/init.lua index 37e1e5d031..14fe0273f5 100644 --- a/packages/insertions/init.lua +++ b/packages/insertions/init.lua @@ -84,7 +84,7 @@ SILE.nodefactory.insertionlist.frame = nil function SILE.nodefactory.insertionlist:_init (spec) SILE.nodefactory.vbox._init(self, spec) - self.typesetter = SILE.defaultTypesetter() + self.typesetter = SILE.typesetters.base() end function SILE.nodefactory.insertionlist:__tostring () diff --git a/packages/linespacing/init.lua b/packages/linespacing/init.lua index c5cf08e0e3..e3d7d35e55 100644 --- a/packages/linespacing/init.lua +++ b/packages/linespacing/init.lua @@ -47,7 +47,7 @@ local linespacingLeading = function (_, vbox, previous) end if method == "tex" then - return SILE.defaultTypesetter:leadingFor(vbox, previous) + return SILE.typesetters.base:leadingFor(vbox, previous) end if method == "fit-glyph" then @@ -148,7 +148,7 @@ function package:registerCommands () end) self:registerCommand("linespacing-off", function () - SILE.typesetter.leadingFor = SILE.defaultTypesetter.leadingFor + SILE.typesetter.leadingFor = SILE.typesetters.base.leadingFor end) end diff --git a/packages/parallel/init.lua b/packages/parallel/init.lua index 484091c2c5..78bdc3cb06 100644 --- a/packages/parallel/init.lua +++ b/packages/parallel/init.lua @@ -16,7 +16,7 @@ local allTypesetters = function (callback) SILE.typesetter = oldtypesetter end -local nulTypesetter = pl.class(SILE.defaultTypesetter) -- we ignore this +local nulTypesetter = pl.class(SILE.typesetters.base) -- we ignore this nulTypesetter.outputLinesToPage = function () end local parallelPagebreak = function () @@ -30,7 +30,7 @@ local parallelPagebreak = function () if #typesetter.state.outputQueue > 0 and calculations[frame].mark == 0 then -- More than one page worth of stuff here. -- Just ship out one page and hope for the best. - SILE.defaultTypesetter.buildPage(typesetter) + SILE.typesetters.base.buildPage(typesetter) else for l = 1, calculations[frame].mark do thispage[l] = table.remove(typesetter.state.outputQueue, 1) @@ -64,7 +64,7 @@ function package:_init (options) base._init(self, options) SILE.typesetter = nulTypesetter(SILE.getFrame("page")) for frame, typesetter in pairs(options.frames) do - typesetterPool[frame] = SILE.defaultTypesetter(SILE.getFrame(typesetter)) + typesetterPool[frame] = SILE.typesetters.base(SILE.getFrame(typesetter)) typesetterPool[frame].id = typesetter typesetterPool[frame].buildPage = function () -- No thank you, I will do that. diff --git a/packages/tate/init.lua b/packages/tate/init.lua index 70f24c6236..7071274f3e 100644 --- a/packages/tate/init.lua +++ b/packages/tate/init.lua @@ -6,30 +6,16 @@ package._name = "tate" SILE.tateFramePrototype = pl.class(SILE.framePrototype) SILE.tateFramePrototype.direction = "TTB-RTL" + SILE.tateFramePrototype.enterHooks = { function (_, typesetter) - typesetter.old_leadingFor = typesetter.leadingFor - typesetter.old_breakIntoLines = typesetter.breakIntoLines - typesetter.leadingFor = function(_, v) - v.height = SILE.length("1zw"):absolute() - local bls = SILE.settings:get("document.baselineskip") - local d = bls.height:absolute() - v.height - local len = SILE.length(d.length, bls.height.stretch, bls.height.shrink) - return SILE.nodefactory.vglue({height = len}) - end - -- Hackery alert, this should be implemented as an actual typesetter module - -- not a shoe-horned package module, but to keep the PR scope sane I'm - -- putting off that refactoring since the typesetter module itself needs - -- scope cleanup. - SILE.require("packages.break-firstfit", nil, true) - typesetter.breakIntoLines = typesetter._breakIntoLines_firstfit + SILE.typesetters.tate:cast(typesetter) end } SILE.tateFramePrototype.leaveHooks = { function (_, typesetter) - typesetter.leadingFor = typesetter.old_leadingFor - typesetter.breakIntoLines = typesetter.old_breakIntoLines + SILE.typesetters.base:cast(typesetter) end } @@ -78,7 +64,7 @@ function package:registerCommands () local prevDirection = oldT.frame.direction self:loadPackage("rotate") SILE.settings:temporarily(function() - local latinTypesetter = pl.class(SILE.defaultTypesetter) + local latinTypesetter = pl.class(SILE.typesetters.base) local dummyFrame = pl.class(SILE.framePrototype) dummyFrame.init = function (f) f.state = {} diff --git a/pagebuilders/base.lua b/pagebuilders/base.lua new file mode 100644 index 0000000000..a330fbb03b --- /dev/null +++ b/pagebuilders/base.lua @@ -0,0 +1,129 @@ +local pagebuilder = pl.class() +pagebuilder._name = "base" + +function pagebuilder:_init () + self.awful_bad = 1073741823 + self.inf_bad = 10000 + self.eject_penalty = -self.inf_bad + self.deplorable = 100000 +end + +function pagebuilder.collateVboxes (_, vboxlist) + local output = SILE.nodefactory.vbox() + output:append(vboxlist) + return output +end + +-- Note: Almost 1/3 of the time in a typical SILE in taken iterating through +-- this function. As a result there are some micro-optimizations here that +-- make it a-typical of preferred coding styles. In particular note that +-- we absolutize heavily iterated lengths as early as possible and make +-- make direct calls to their integer amounts, assumed to be in points by +-- the point they are called **without actually checking**! +function pagebuilder:findBestBreak (options) + local vboxlist = SU.required(options, "vboxlist", "in findBestBreak") + local target = SU.required(options, "target", "in findBestBreak", "length") + local restart = options.restart or false + local force = options.force or false + local i = 0 + local totalHeight = SILE.length() + local bestBreak = nil + local started = false + if restart and restart.target == target then + totalHeight = restart.totalHeight + i = restart.i + started = restart.started + end + local leastC = self.inf_bad + SU.debug("pagebuilder", function () + return "Page builder for frame " .. SILE.typesetter.frame.id .. " called with " .. #vboxlist .. " nodes, " .. tostring(target) + end) + if SU.debugging("vboxes") then + for j, box in ipairs(vboxlist) do + SU.debug("vboxes", function () + return (j == i and " >" or " ") .. j .. ": " .. box + end) + end + end + while not started and i < #vboxlist do + i = i + 1 + if not vboxlist[i].is_vglue then + started = true + i = i - 1 + break + end + end + local pi + while i < #vboxlist do + i = i + 1 + local vbox = vboxlist[i] + SU.debug("pagebuilder", "Dealing with VBox", vbox) + if vbox.is_vbox then + totalHeight:___add(vbox.height) + totalHeight:___add(vbox.depth) + elseif vbox.is_vglue then + totalHeight:___add(vbox.height) + elseif vbox.is_insertion then + -- TODO: refactor as hook and without side effects! + target = SILE.insertions.processInsertion(vboxlist, i, totalHeight, target) + vbox = vboxlist[i] + end + local left = target - totalHeight + SU.debug("pagebuilder", "I have", left, "left") + -- if left < -20 then SU.error("\nCatastrophic page breaking failure!"); end + pi = 0 + if vbox.is_penalty then + pi = vbox.penalty + -- print("PI "..pi) + end + if vbox.is_penalty and vbox.penalty < self.inf_bad + or (vbox.is_vglue and i > 1 and not vboxlist[i-1].discardable) then + local badness + SU.debug("pagebuilder", "totalHeight", totalHeight, "with target", target) + if totalHeight.length.amount < target.length.amount then -- TeX #1039 + -- Account for infinite stretch? + badness = SU.rateBadness(self.inf_bad, left.length.amount, totalHeight.stretch.amount) + -- print("Height == "..totalHeight.length, "target=="..target, "stretch=="..totalHeight.stretch) + elseif left.length.amount < totalHeight.shrink.amount then badness = self.awful_bad + else badness = SU.rateBadness(self.inf_bad, -left.length.amount, totalHeight.shrink.amount) + end + + local c + if badness < self.awful_bad then + if pi <= self.eject_penalty then c = pi + elseif badness < self.inf_bad then c = badness + pi -- plus insert + else c = self.deplorable + end + else c = badness end + if c < leastC then + leastC = c + bestBreak = i + else + restart = { totalHeight = totalHeight, i = i, started = started, target = target} + end + -- print("Badness "..badness .." c = "..c) + SU.debug("pagebuilder", "Badness:", c) + if c == self.awful_bad or pi <= self.eject_penalty then + SU.debug("pagebuilder", "outputting") + local onepage = {} + if not bestBreak then bestBreak = i end + for j=1,bestBreak do + onepage[j] = table.remove(vboxlist,1) + end + while(#onepage > 1 and onepage[#onepage].discardable) do onepage[#onepage] = nil end + return onepage, pi + end + end + end + SU.debug("pagebuilder", "No page break here") + if force and bestBreak then + local onepage = {} + for j=1,bestBreak do + onepage[j] = table.remove(vboxlist,1) + end + return onepage, pi + end + return false, restart +end + +return pagebuilder diff --git a/pagebuilders/grid.lua b/pagebuilders/grid.lua new file mode 100644 index 0000000000..1f038949db --- /dev/null +++ b/pagebuilders/grid.lua @@ -0,0 +1,68 @@ +local base = require("pagebuilders.base") + +local pagebuilder = pl.class(base) +pagebuilder._name = "grid" + +function pagebuilder:_init() + base._init(self) +end + +function pagebuilder.findBestBreak (_, options) + local vboxlist = SU.required(options, "vboxlist", "in findBestBreak") + local target = SU.required(options, "target", "in findBestBreak") + local i = 0 + local totalHeight = SILE.length() + local bestBreak = 0 + SU.debug("pagebuilder", "Page builder for frame", SILE.typesetter.frame.id, "called with", #vboxlist, "nodes,", target) + if SU.debugging("vboxes") then + for j, box in ipairs(vboxlist) do + SU.debug("vboxes", (j == i and " >" or " ") .. j .. ": " .. box) + end + end + while i < #vboxlist do + i = i + 1 + if not vboxlist[i].is_vglue then + i = i - 1 + break + end + end + while i < #vboxlist do + i = i + 1 + local node = vboxlist[i] + SU.debug("pagebuilder", "Dealing with VBox", node) + if node.is_vbox then + totalHeight = totalHeight + node.height:absolute() + node.depth:absolute() + elseif node.is_vglue then + totalHeight = totalHeight + node.height:absolute() + elseif node.is_insertion then + -- TODO: refactor as hook and without side effects! + target = SILE.insertions.processInsertion(vboxlist, i, totalHeight, target) + node = vboxlist[i] + end + local left = target - totalHeight + local _left = left:tonumber() + SU.debug("pagebuilder", "I have", left, "left") + SU.debug("pagebuilder", "totalHeight", totalHeight, "with target", target) + local badness = 0 + if _left < 0 then badness = 1000000 end + if node.is_penalty then + if node.penalty < -3000 then badness = 100000 + else badness = -_left * _left - node.penalty + end + end + if badness > 0 then + local onepage = {} + for j = 1, bestBreak do + onepage[j] = table.remove(vboxlist, 1) + end + while #onepage > 1 and onepage[#onepage].discardable do + onepage[#onepage] = nil + end + return onepage, 1000 + end + bestBreak = i + end + return false, false +end + +return pagebuilder diff --git a/spec/break_spec.lua b/spec/break_spec.lua index 67007c3695..b86d4ddb46 100644 --- a/spec/break_spec.lua +++ b/spec/break_spec.lua @@ -3,7 +3,7 @@ SILE = require("core.sile") describe("SILE.linebreak", function() SILE.documentState = { documentClass = { state = { } } } - SILE.typesetter = SILE.defaultTypesetter(SILE.newFrame({ id = "foo" })) + SILE.typesetter = SILE.typesetters.base(SILE.newFrame({ id = "foo" })) -- This is a list of boxes, with their dimensions, extracted from a specially hacked version of TeX. local hlist = {} diff --git a/spec/utilities_spec.lua b/spec/utilities_spec.lua index edabeb916a..248b3621dd 100644 --- a/spec/utilities_spec.lua +++ b/spec/utilities_spec.lua @@ -16,7 +16,7 @@ describe("SILE.utilities", function() describe("formatNumber", function () SILE.documentState = { documentClass = { state = { } } } - SILE.typesetter = SILE.defaultTypesetter(SILE.newFrame({ id = "dummy" })) + SILE.typesetter = SILE.typesetters.base(SILE.newFrame({ id = "dummy" })) describe ("Esperanto", function () SILE.call("language", { main = "eo" }) -- Really load AND activate the language @@ -160,7 +160,7 @@ describe("SILE.utilities", function() describe("collatedSort", function () SILE.documentState = { documentClass = { state = { } } } - SILE.typesetter = SILE.defaultTypesetter(SILE.newFrame({ id = "dummy" })) + SILE.typesetter = SILE.typesetters.base(SILE.newFrame({ id = "dummy" })) describe ("French", function () SILE.call("language", { main = "fr" }) -- Really load AND activate the language diff --git a/tests/bidi.expected b/tests/bidi.expected index 662449bfc9..6861fbed2d 100644 --- a/tests/bidi.expected +++ b/tests/bidi.expected @@ -214,7 +214,7 @@ Mx 11.2183 Mx 11.9751 Mx 12.8418 T 45 a=13.6475 39 a=6.2500 x=-0.3052 37 a=14.4287 54 a=12.9761 35 a=6.5918 x=-0.3662 43 a=11.2183 56 a=11.9751 29 a=12.8418 (בצלופחים) -Mx 85.5925 +Mx 203.5138 My 561.7893 Set font SBL Hebrew;25;400;;normal;;LTR T 189 w=12.5000 (1) diff --git a/tests/bug-1430.expected b/tests/bug-1430.expected new file mode 100644 index 0000000000..86e5e5198a --- /dev/null +++ b/tests/bug-1430.expected @@ -0,0 +1,93 @@ +Set paper size 297.6377985 419.5275636 +Begin page +Draw line 14.8819 70.9764 267.8740 0.1000 +Draw line 14.8819 120.9764 267.8740 0.1000 +Draw line 14.8819 170.9764 267.8740 0.1000 +Draw line 14.8819 220.9764 267.8740 0.1000 +Draw line 14.8819 270.9764 267.8740 0.1000 +Draw line 14.8819 320.9764 267.8740 0.1000 +Draw line 14.8819 370.9764 267.8740 0.1000 +Mx 14.8819 +My 70.9764 +Set font Gentium Plus;25;400;;normal;;LTR +T 54 82 80 72 w=56.1035 (Some) +Mx 75.7567 +T 68 86 70 72 81 71 72 85 86 w=101.5015 (ascenders) +Mx 182.0295 +T 82 85 w=22.4731 (or) +Mx 209.2740 +T 70 68 83 76 87 68 79 86 w=78.9307 (capitals) +Mx 14.8819 +My 120.9764 +T 75 72 85 72 w=46.7896 (here) +Mx 61.6714 +T 17 w=5.7251 (.) +Mx 67.3965 +T 17 w=5.7251 (.) +Mx 73.1216 +T 17 w=5.7251 (.) +Mx 14.8819 +My 370.9764 +T 50 89 72 85 739 82 90 w=93.5547 (Overflow) +Mx 113.6132 +Set font Gentium Plus;12.5;400;;normal;;LTR +T 90 76 87 75 w=23.4131 (with) +Mx 139.6146 +T 81 82 w=13.1836 (no) +Mx 155.3865 +T 68 86 70 72 81 71 72 85 86 w=50.7507 (ascenders) +Mx 208.7255 +T 82 85 w=11.2366 (or) +Mx 222.5504 +T 70 68 83 76 87 68 79 86 w=39.4653 (capitals) +Mx 264.6041 +T 89 76 68 w=15.2893 (via) +Mx 279.8934 +T 29 w=2.8625 (:) +New page +Draw line 14.8819 70.9764 267.8740 0.1000 +Draw line 14.8819 120.9764 267.8740 0.1000 +Draw line 14.8819 170.9764 267.8740 0.1000 +Draw line 14.8819 220.9764 267.8740 0.1000 +Draw line 14.8819 270.9764 267.8740 0.1000 +Draw line 14.8819 320.9764 267.8740 0.1000 +Draw line 14.8819 370.9764 267.8740 0.1000 +Mx 14.8819 +My 70.9764 +T 68 w=5.7373 (a) +Mx 23.9473 +T 82 81 72 85 w=23.9075 (oner) +Mx 51.1829 +T 82 w=6.2866 (o) +Mx 60.7977 +T 82 82 w=12.5732 (oo) +Mx 76.6991 +T 68 85 w=10.6873 (ar) +Mx 90.7145 +T 68 93 w=11.0413 (az) +New page +Draw line 14.8819 70.9764 267.8740 0.1000 +Draw line 14.8819 120.9764 267.8740 0.1000 +Draw line 14.8819 170.9764 267.8740 0.1000 +Draw line 14.8819 220.9764 267.8740 0.1000 +Draw line 14.8819 270.9764 267.8740 0.1000 +Draw line 14.8819 320.9764 267.8740 0.1000 +Draw line 14.8819 370.9764 267.8740 0.1000 +Mx 14.8819 +My 70.7933 +Set font Gentium Plus;25;400;;normal;;LTR +T 81 82 81 72 w=51.7090 (none) +Mx 73.2376 +T 82 89 w=24.9023 (ov) +Mx 104.7867 +T 72 80 w=31.6284 (em) +New page +Draw line 14.8819 70.9764 267.8740 0.1000 +Draw line 14.8819 120.9764 267.8740 0.1000 +Draw line 14.8819 170.9764 267.8740 0.1000 +Draw line 14.8819 220.9764 267.8740 0.1000 +Draw line 14.8819 270.9764 267.8740 0.1000 +Draw line 14.8819 320.9764 267.8740 0.1000 +Draw line 14.8819 370.9764 267.8740 0.1000 +End page +Finish diff --git a/tests/bug-1430.sil b/tests/bug-1430.sil new file mode 100644 index 0000000000..96788b9f4a --- /dev/null +++ b/tests/bug-1430.sil @@ -0,0 +1,18 @@ +\begin[papersize=a6]{document} +\neverindent +\nofolios +\use[module=packages.grid,spacing=50pt] +\font[size=25pt] +\grid +\grid:debug +Some ascenders or capitals here... + +\skip[height=200pt] + +Overflow \font[size=0.5em]{with no ascenders or capitals via: a oner o oo ar az} + +\vfill +\break + +none ov em +\end{document} diff --git a/tests/bug-244.expected b/tests/bug-244.expected index aa80f91258..7025b8cc47 100644 --- a/tests/bug-244.expected +++ b/tests/bug-244.expected @@ -1,6 +1,6 @@ Set paper size 595.275597 841.8897729 Begin page -Mx 539.9079 +Mx 541.9479 My 117.7637 Set font Noto Sans CJK JP;10;400;;normal;;LTR T 41 70 77 77 80 w=24.5800 (Hello) diff --git a/typesetters/base.lua b/typesetters/base.lua index 32b32a8424..b2751dfbbe 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -1,3 +1,7 @@ +local typesetter = pl.class() +typesetter.type = "typesetter" +typesetter._name = "base" + -- This is the default typesetter. You are, of course, welcome to create your own. local awful_bad = 1073741823 local inf_bad = 10000 @@ -5,69 +9,6 @@ local inf_bad = 10000 local supereject_penalty = 2 * -inf_bad -- local deplorable = 100000 -SILE.settings:declare({ - parameter = "typesetter.widowpenalty", - type = "integer", - default = 3000, - help = "Penalty to be applied to widow lines (at the start of a paragraph)" -}) - -SILE.settings:declare({ - parameter = "typesetter.parseppattern", - type = "string or integer", - default = "\r?\n[\r\n]+", - help = "Lua pattern used to separate paragraphs" -}) - -SILE.settings:declare({ - parameter = "typesetter.obeyspaces", - type = "boolean or nil", - default = nil, - help = "Whether to ignore paragraph initial spaces" -}) - -SILE.settings:declare({ - parameter = "typesetter.orphanpenalty", - type = "integer", - default = 3000, - help = "Penalty to be applied to orphan lines (at the end of a paragraph)" -}) - -SILE.settings:declare({ - parameter = "typesetter.parfillskip", - type = "glue", - default = SILE.nodefactory.glue("0pt plus 10000pt"), - help = "Glue added at the end of a paragraph" -}) - -SILE.settings:declare({ - parameter = "document.letterspaceglue", - type = "glue or nil", - default = nil, - help = "Glue added between tokens" -}) - -SILE.settings:declare({ - parameter = "typesetter.underfulltolerance", - type = "length or nil", - default = SILE.length("1em"), - help = "Amount a page can be underfull without warning" -}) - -SILE.settings:declare({ - parameter = "typesetter.overfulltolerance", - type = "length or nil", - default = SILE.length("5pt"), - help = "Amount a page can be overfull without warning" -}) - -SILE.settings:declare({ - parameter = "typesetter.breakwidth", - type = "measurement or nil", - default = nil, - help = "Width to break lines at" -}) - -- Local helper class to compare pairs of margins local _margins = pl.class({ lskip = SILE.nodefactory.glue(), @@ -83,14 +24,9 @@ local _margins = pl.class({ }) -SILE.defaultTypesetter = pl.class() -SILE.defaultTypesetter.hooks = {} - -SILE.defaultTypesetter.breadcrumbs = SU.breadcrumbs() - local warned = false -function SILE.defaultTypesetter:init (frame) +function typesetter:init (frame) SU.deprecated("std.object", "pl.class", "0.13.0", "0.14.0", warned and "" or [[ The typesetter instance inheritance system for instances has been refactored using a different object model. Your instance was created @@ -102,7 +38,74 @@ function SILE.defaultTypesetter:init (frame) self:_init(frame) end -function SILE.defaultTypesetter:_init (frame) +function typesetter:_init (frame) + + SILE.settings:declare({ + parameter = "typesetter.widowpenalty", + type = "integer", + default = 3000, + help = "Penalty to be applied to widow lines (at the start of a paragraph)" + }) + + SILE.settings:declare({ + parameter = "typesetter.parseppattern", + type = "string or integer", + default = "\r?\n[\r\n]+", + help = "Lua pattern used to separate paragraphs" + }) + + SILE.settings:declare({ + parameter = "typesetter.obeyspaces", + type = "boolean or nil", + default = nil, + help = "Whether to ignore paragraph initial spaces" + }) + + SILE.settings:declare({ + parameter = "typesetter.orphanpenalty", + type = "integer", + default = 3000, + help = "Penalty to be applied to orphan lines (at the end of a paragraph)" + }) + + SILE.settings:declare({ + parameter = "typesetter.parfillskip", + type = "glue", + default = SILE.nodefactory.glue("0pt plus 10000pt"), + help = "Glue added at the end of a paragraph" + }) + + SILE.settings:declare({ + parameter = "document.letterspaceglue", + type = "glue or nil", + default = nil, + help = "Glue added between tokens" + }) + + SILE.settings:declare({ + parameter = "typesetter.underfulltolerance", + type = "length or nil", + default = SILE.length("1em"), + help = "Amount a page can be underfull without warning" + }) + + SILE.settings:declare({ + parameter = "typesetter.overfulltolerance", + type = "length or nil", + default = SILE.length("5pt"), + help = "Amount a page can be overfull without warning" + }) + + SILE.settings:declare({ + parameter = "typesetter.breakwidth", + type = "measurement or nil", + default = nil, + help = "Width to break lines at" + }) + + self.hooks = {} + self.breadcrumbs = SU.breadcrumbs() + self.frame = nil self.stateQueue = {} self:initFrame(frame) @@ -112,7 +115,7 @@ function SILE.defaultTypesetter:_init (frame) return self end -function SILE.defaultTypesetter:initState () +function typesetter:initState () self.state = { nodes = {}, outputQueue = {}, @@ -120,43 +123,43 @@ function SILE.defaultTypesetter:initState () } end -function SILE.defaultTypesetter:initFrame (frame) +function typesetter:initFrame (frame) if frame then self.frame = frame self.frame:init(self) end end -function SILE.defaultTypesetter.getMargins () +function typesetter.getMargins () return _margins(SILE.settings:get("document.lskip"), SILE.settings:get("document.rskip")) end -function SILE.defaultTypesetter.setMargins (_, margins) +function typesetter.setMargins (_, margins) SILE.settings:set("document.lskip", margins.lskip) SILE.settings:set("document.rskip", margins.rskip) end -function SILE.defaultTypesetter:pushState () +function typesetter:pushState () self.stateQueue[#self.stateQueue+1] = self.state self:initState() end -function SILE.defaultTypesetter:popState (ncount) +function typesetter:popState (ncount) local offset = ncount and #self.stateQueue - ncount or nil self.state = table.remove(self.stateQueue, offset) if not self.state then SU.error("Typesetter state queue empty") end end -function SILE.defaultTypesetter:isQueueEmpty () +function typesetter:isQueueEmpty () if not self.state then return nil end return #self.state.nodes == 0 and #self.state.outputQueue == 0 end -function SILE.defaultTypesetter:vmode () +function typesetter:vmode () return #self.state.nodes == 0 end -function SILE.defaultTypesetter:debugState () +function typesetter:debugState () print("\n---\nI am in "..(self:vmode() and "vertical" or "horizontal").." mode") print("Writing into " .. tostring(self.frame)) print("Recent contributions: ") @@ -170,37 +173,37 @@ function SILE.defaultTypesetter:debugState () end -- Boxy stuff -function SILE.defaultTypesetter:pushHorizontal (node) +function typesetter:pushHorizontal (node) self:initline() self.state.nodes[#self.state.nodes+1] = node return node end -function SILE.defaultTypesetter:pushVertical (vbox) +function typesetter:pushVertical (vbox) self.state.outputQueue[#self.state.outputQueue+1] = vbox return vbox end -function SILE.defaultTypesetter:pushHbox (spec) +function typesetter:pushHbox (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local ntype = SU.type(spec) local node = (ntype == "hbox" or ntype == "zerohbox") and spec or SILE.nodefactory.hbox(spec) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushUnshaped (spec) +function typesetter:pushUnshaped (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local node = SU.type(spec) == "unshaped" and spec or SILE.nodefactory.unshaped(spec) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushGlue (spec) +function typesetter:pushGlue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local node = SU.type(spec) == "glue" and spec or SILE.nodefactory.glue(spec) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushExplicitGlue (spec) +function typesetter:pushExplicitGlue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local node = SU.type(spec) == "glue" and spec or SILE.nodefactory.glue(spec) node.explicit = true @@ -208,30 +211,30 @@ function SILE.defaultTypesetter:pushExplicitGlue (spec) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushPenalty (spec) +function typesetter:pushPenalty (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local node = SU.type(spec) == "penalty" and spec or SILE.nodefactory.penalty(spec) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushMigratingMaterial (material) +function typesetter:pushMigratingMaterial (material) local node = SILE.nodefactory.migrating({ material = material }) return self:pushHorizontal(node) end -function SILE.defaultTypesetter:pushVbox (spec) +function typesetter:pushVbox (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end local node = SU.type(spec) == "vbox" and spec or SILE.nodefactory.vbox(spec) return self:pushVertical(node) end -function SILE.defaultTypesetter:pushVglue (spec) +function typesetter:pushVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) return self:pushVertical(node) end -function SILE.defaultTypesetter:pushExplicitVglue (spec) +function typesetter:pushExplicitVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) node.explicit = true @@ -239,14 +242,14 @@ function SILE.defaultTypesetter:pushExplicitVglue (spec) return self:pushVertical(node) end -function SILE.defaultTypesetter:pushVpenalty (spec) +function typesetter:pushVpenalty (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end local node = SU.type(spec) == "penalty" and spec or SILE.nodefactory.penalty(spec) return self:pushVertical(node) end -- Actual typesetting functions -function SILE.defaultTypesetter:typeset (text) +function typesetter:typeset (text) text = tostring(text) if text:match("^%\r?\n$") then return end local pId = SILE.traceStack:pushText(text) @@ -260,20 +263,20 @@ function SILE.defaultTypesetter:typeset (text) SILE.traceStack:pop(pId) end -function SILE.defaultTypesetter:initline () +function typesetter:initline () if (#self.state.nodes == 0) then self.state.nodes[#self.state.nodes+1] = SILE.nodefactory.zerohbox() SILE.documentState.documentClass.newPar(self) end end -function SILE.defaultTypesetter:endline () +function typesetter:endline () self:leaveHmode() SILE.documentState.documentClass.endPar(self) end -- Takes string, writes onto self.state.nodes -function SILE.defaultTypesetter:setpar (text) +function typesetter:setpar (text) text = text:gsub("\r?\n", " "):gsub("\t", " ") if (#self.state.nodes == 0) then if not SILE.settings:get("typesetter.obeyspaces") then @@ -286,13 +289,13 @@ function SILE.defaultTypesetter:setpar (text) end end -function SILE.defaultTypesetter:breakIntoLines (nodelist, breakWidth) +function typesetter:breakIntoLines (nodelist, breakWidth) self:shapeAllNodes(nodelist) local breakpoints = SILE.linebreak:doBreak(nodelist, breakWidth) return self:breakpointsToLines(breakpoints) end -function SILE.defaultTypesetter.shapeAllNodes (_, nodelist) +function typesetter.shapeAllNodes (_, nodelist) local newNl = {} for i = 1, #nodelist do if nodelist[i].is_unshaped then @@ -309,7 +312,7 @@ end -- Empties self.state.nodes, breaks into lines, puts lines into vbox, adds vbox to -- Turns a node list into a list of vboxes -function SILE.defaultTypesetter:boxUpNodes () +function typesetter:boxUpNodes () local nodelist = self.state.nodes if #nodelist == 0 then return {} end for j = #nodelist, 1, -1 do @@ -366,20 +369,20 @@ function SILE.defaultTypesetter:boxUpNodes () return vboxes end -function SILE.defaultTypesetter.pageTarget (_) +function typesetter.pageTarget (_) SU.deprecated("SILE.typesetter:pageTarget", "SILE.typesetter:getTargetLength", "0.13.0", "0.14.0") end -function SILE.defaultTypesetter:getTargetLength () +function typesetter:getTargetLength () return self.frame:getTargetLength() end -function SILE.defaultTypesetter:registerHook (category, func) +function typesetter:registerHook (category, func) if not self.hooks[category] then self.hooks[category] = {} end table.insert(self.hooks[category], func) end -function SILE.defaultTypesetter:runHooks (category, data) +function typesetter:runHooks (category, data) if not self.hooks[category] then return data end for _, func in ipairs(self.hooks[category]) do data = func(self, data) @@ -387,19 +390,19 @@ function SILE.defaultTypesetter:runHooks (category, data) return data end -function SILE.defaultTypesetter:registerFrameBreakHook (func) +function typesetter:registerFrameBreakHook (func) self:registerHook("framebreak", func) end -function SILE.defaultTypesetter:registerNewFrameHook (func) +function typesetter:registerNewFrameHook (func) self:registerHook("newframe", func) end -function SILE.defaultTypesetter:registerPageEndHook (func) +function typesetter:registerPageEndHook (func) self:registerHook("pageend", func) end -function SILE.defaultTypesetter:buildPage () +function typesetter:buildPage () local pageNodeList local res if self:isQueueEmpty() then return false end @@ -422,7 +425,7 @@ function SILE.defaultTypesetter:buildPage () return true end -function SILE.defaultTypesetter.setVerticalGlue (_, pageNodeList, target) +function typesetter.setVerticalGlue (_, pageNodeList, target) local glues = {} local gTotal = SILE.length() local totalHeight = SILE.length() @@ -468,7 +471,7 @@ function SILE.defaultTypesetter.setVerticalGlue (_, pageNodeList, target) SU.debug("pagebuilder", "Glues for this page adjusted by", adjustment, "drawn from", gTotal) end -function SILE.defaultTypesetter:initNextFrame () +function typesetter:initNextFrame () local oldframe = self.frame self.frame:leave(self) if #self.state.outputQueue == 0 then @@ -509,7 +512,7 @@ function SILE.defaultTypesetter:initNextFrame () end -function SILE.defaultTypesetter:pushBack () +function typesetter:pushBack () SU.debug("typesetter", "Pushing back", #self.state.outputQueue, "nodes") local oldqueue = self.state.outputQueue self.state.outputQueue = {} @@ -573,7 +576,7 @@ function SILE.defaultTypesetter:pushBack () end end -function SILE.defaultTypesetter:outputLinesToPage (lines) +function typesetter:outputLinesToPage (lines) SU.debug("pagebuilder", "OUTPUTTING frame", self.frame.id) for _, line in ipairs(lines) do -- Annoyingly, explicit glue *should* disappear at the top of a page. @@ -587,7 +590,7 @@ function SILE.defaultTypesetter:outputLinesToPage (lines) end end -function SILE.defaultTypesetter:leaveHmode (independent) +function typesetter:leaveHmode (independent) SU.debug("typesetter", "Leaving hmode") local margins = self:getMargins() local vboxlist = self:boxUpNodes() @@ -603,11 +606,11 @@ function SILE.defaultTypesetter:leaveHmode (independent) end end -function SILE.defaultTypesetter:inhibitLeading () +function typesetter:inhibitLeading () self.state.previousVbox = nil end -function SILE.defaultTypesetter.leadingFor (_, vbox, previous) +function typesetter.leadingFor (_, vbox, previous) -- Insert leading SU.debug("typesetter", " Considering leading between two lines:") SU.debug("typesetter", " 1)", previous) @@ -628,7 +631,7 @@ function SILE.defaultTypesetter.leadingFor (_, vbox, previous) end end -function SILE.defaultTypesetter:addrlskip (slice, margins, hangLeft, hangRight) +function typesetter:addrlskip (slice, margins, hangLeft, hangRight) local LTR = self.frame:writingDirection() == "LTR" local rskip = margins[LTR and "rskip" or "lskip"] if not rskip then rskip = SILE.nodefactory.glue(0) end @@ -650,7 +653,7 @@ function SILE.defaultTypesetter:addrlskip (slice, margins, hangLeft, hangRight) table.insert(slice, 1, SILE.nodefactory.zerohbox()) end -function SILE.defaultTypesetter:breakpointsToLines (breakpoints) +function typesetter:breakpointsToLines (breakpoints) local linestart = 0 local lines = {} local nodes = self.state.nodes @@ -687,7 +690,7 @@ function SILE.defaultTypesetter:breakpointsToLines (breakpoints) return lines end -function SILE.defaultTypesetter.computeLineRatio (_, breakwidth, slice) +function typesetter.computeLineRatio (_, breakwidth, slice) -- This somewhat wrong, see #1362. -- This is a very partial workaround, at least made consistent with the -- nnode outputYourself routine expectation (which is somewhat wrong too) @@ -753,20 +756,10 @@ function SILE.defaultTypesetter.computeLineRatio (_, breakwidth, slice) return ratio, naturalTotals end -function SILE.defaultTypesetter:chuck () -- emergency shipout everything +function typesetter:chuck () -- emergency shipout everything self:leaveHmode(true) self:outputLinesToPage(self.state.outputQueue) self.state.outputQueue = {} end -SILE.typesetNaturally = function (frame, func) - local saveTypesetter = SILE.typesetter - if SILE.typesetter.frame then SILE.typesetter.frame:leave(SILE.typesetter) end - SILE.typesetter = SILE.defaultTypesetter(frame) - SILE.settings:temporarily(func) - SILE.typesetter:leaveHmode() - SILE.typesetter:chuck() - SILE.typesetter.frame:leave(SILE.typesetter) - SILE.typesetter = saveTypesetter - if SILE.typesetter.frame then SILE.typesetter.frame:enter(SILE.typesetter) end -end +return typesetter diff --git a/typesetters/firstfit.lua b/typesetters/firstfit.lua new file mode 100644 index 0000000000..0eae7af57a --- /dev/null +++ b/typesetters/firstfit.lua @@ -0,0 +1,33 @@ +local base = require("typesetters.base") + +local typesetter = pl.class(base) +typesetter._name = "firstfit" + +function typesetter:breakIntoLines (nl, breakWidth) + local breaks = {} + local length = SILE.length() + for i = 1,#nl do local n = nl[i] + if n.is_box then + SU.debug("break", n .. " " .. tostring(n:lineContribution())) + length = length + n:lineContribution() + SU.debug("break", " Length now " .. tostring(length) .. " breakwidth ".. tostring(breakWidth)) + end + if not n.is_box or n.isHangable then + SU.debug("break", n ) + if n.is_glue then + length = length + n.width:absolute() + end + SU.debug("break", " Length now " .. tostring(length) .. " breakwidth " .. tostring(breakWidth)) + -- Can we break? + if length:tonumber() >= breakWidth:tonumber() then + SU.debug("break", "Breaking!") + breaks[#breaks+1] = { position = i, width = breakWidth} + length = SILE.length() + end + end + end + breaks[#breaks+1] = { position = #nl, width = breakWidth} + return self:breakpointsToLines(breaks) +end + +return typesetter diff --git a/typesetters/grid.lua b/typesetters/grid.lua new file mode 100644 index 0000000000..6e6efbf44b --- /dev/null +++ b/typesetters/grid.lua @@ -0,0 +1,58 @@ +local base = require("typesetters.base") + +local typesetter = pl.class(base) +typesetter._name = "grid" + +local function makeUp (spacing, totals) + local toadd = (spacing - SILE.measurement(totals.gridCursor)) % spacing + totals.gridCursor = totals.gridCursor + toadd + SU.debug("typesetter", "Makeup height =", toadd) + return SILE.nodefactory.vglue({ discardable = false, gridleading = true, height = toadd }) +end + +function typesetter:_init(frame) + base._init(self, frame) + self.options = { spacing = SILE.measurement("1bs") } +end + +function typesetter:leadingFor (vbox, previous) + SU.debug("typesetter", " Considering leading between two lines (grid mode):") + SU.debug("typesetter", " 1)", previous) + SU.debug("typesetter", " 2)", vbox) + if not previous then return SILE.nodefactory.vglue() end + SU.debug("typesetter", " Depth of previous line was", previous.depth) + local totals = self.frame.state.totals + local oldCursor = SILE.measurement(totals.gridCursor) + totals.gridCursor = oldCursor + vbox.height:absolute() + previous.depth + SU.debug("typesetter", " Cursor change =", totals.gridCursor - oldCursor) + return makeUp(self.options.spacing, self.frame.state.totals) +end + +function typesetter:pushVglue (spec) + -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end + local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) + node.height.stretch = SILE.measurement() + node.height.shrink = SILE.measurement() + local totals = self.frame.state.totals + totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() + self:pushVertical(node) + self:pushVertical(makeUp(self.options.spacing, self.frame.state.totals)) + return node +end + +function typesetter:pushExplicitVglue (spec) + -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end + local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) + node.explicit = true + node.discardable = false + node.height.stretch = SILE.measurement() + node.height.shrink = SILE.measurement() + local totals = self.frame.state.totals + totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() + self:pushVertical(node) + self:pushVertical(makeUp(self.options.spacing, self.frame.state.totals)) + return node +end + + +return typesetter diff --git a/typesetters/tate.lua b/typesetters/tate.lua new file mode 100644 index 0000000000..a6725e3599 --- /dev/null +++ b/typesetters/tate.lua @@ -0,0 +1,14 @@ +local base = require("typesetters.firstfit") + +local typesetter = pl.class(base) +typesetter._name = "tate" + +function typesetter.leadingFor (_, v) + v.height = SILE.length("1zw"):absolute() + local bls = SILE.settings:get("document.baselineskip") + local d = bls.height:absolute() - v.height + local len = SILE.length(d.length, bls.height.stretch, bls.height.shrink) + return SILE.nodefactory.vglue({ height = len }) +end + +return typesetter