summaryrefslogtreecommitdiff
path: root/tex/context/base/node-ltp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/node-ltp.lua')
-rw-r--r--tex/context/base/node-ltp.lua3207
1 files changed, 3207 insertions, 0 deletions
diff --git a/tex/context/base/node-ltp.lua b/tex/context/base/node-ltp.lua
new file mode 100644
index 000000000..97e61cf18
--- /dev/null
+++ b/tex/context/base/node-ltp.lua
@@ -0,0 +1,3207 @@
+if not modules then modules = { } end modules ['node-par'] = {
+ version = 1.001,
+ comment = "companion to node-par.mkiv",
+ author = "Hans Hagen",
+ copyright = "ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "a translation of the built in parbuilder, initial convertsin by Taco Hoekwater",
+}
+
+-- todo: remove nest_stack from linebreak.w
+-- todo: use ex field as signal (index in ?)
+-- todo: attr driven unknown/on/off
+-- todo: permit global steps i.e. using an attribute that sets min/max/step and overloads the font parameters
+-- todo: split the three passes into three functions
+-- todo: simplify the direction stack, no copy needed
+-- todo: add more mkiv like tracing
+-- todo: add a couple of plugin hooks
+-- todo: maybe split expansion code paths
+-- todo: fix line numbers (cur_list.pg_field needed)
+-- todo: make kerns stretch an option and disable it by default (definitely not shrink)
+-- todo: check and improve protrusion
+-- todo: arabic etc (we could use pretty large scales there) .. marks and cursive
+
+--[[
+
+ This code is derived from traditional TeX and has bits of pdfTeX, Aleph (Omega), and of course LuaTeX. So,
+ the basic algorithm for sure is not our work. On the other hand, the directional model in LuaTeX is cleaned
+ up as is other code. And of course there are hooks for callbacks.
+
+ The first version of the code below was a conversion of the C code that in turn was a conversion from the
+ original Pascal code. Around September 2008 we experimented with cq. discussed possible approaches to improved
+ typesetting of Arabic and as our policy is that extensions happen in Lua this means that we need a parbuilder
+ in Lua. Taco's first conversion still looked quite C-ish and in the process of cleaning up we uncovered some odd
+ bits and pieces in the original code as well. I did some first cleanup to get rid of C-artefacts, and Taco and I
+ spent the usual amount of Skyping to sort out problems. At that point we diverted to other LuaTeX issues.
+
+ A while later I decided to pick up this thread and decided to look into better ways to deal with font expansion
+ (aka hz). I got it running using a simpler method. One reason why the built-in mechanims is slow is that there is
+ lots of redudancy in calculations. Expanded widths are recalculated each time and because the hpakc routine does
+ it again that gives some overhead. In the process extra fonts are created with different dimensions so that the
+ backend can deal with it. The alternative method doesn't create fonts but passes an expansion factor to the
+ pdf generator. The small patch needed for the backend code worked more or less okay but was never intergated into
+ LuaTeX due to lack of time.
+
+ This all happened in 2010 while listening to Peter Gabriels "Scratch My Back" and Camels "Rayaz" so it was a
+ rather relaxed job.
+
+ In 2012 I picked up this thread. Because both languages are similar but also quite different it took some time
+ to get compatible output. Because the C code uses macros, careful checking was needed. Of course Lua's table model
+ and local variables brought some work as well. And still the code looks a bit C-ish. We could not divert too much
+ from the original model simply because it's well documented but future versions (or variants) might as well look
+ different.
+
+ Eventually I'll split this code into passes so that we can better see what happens, but first we need to reach
+ a decent level of stability. The current expansion results are not the same as the built-in but that was never
+ the objective. It all has to do with slightly different calculations.
+
+ The original C-code related to protrusion and expansion is not that efficient as many (redundant) function
+ calls take place in the linebreaker and packer. As most work related to fonts is done in the backend, we
+ can simply stick to width calculations here. Also, it is no problem at all that we use floating point
+ calculations (as Lua has only floats). The final result will look ok as the hpack will nicely compensate
+ for rounding errors as it will normally distribute the content well enough. And let's admit: most texies
+ won't see it anyway. As long as we're cross platform compatible it's fine.
+
+ We use the table checked_expansion to keep track of font related parameters (per paragraph). The table is
+ also the signal that we have adjustments > 1. In retrospect one might wonder if adjusting kerns is such a
+ good idea because other spacing is also not treated. If we would stick to the regular hpack routine
+ we do have to follow the same logic, but I decided to use a Lua hpacker so that constraint went away. And
+ anyway, instead of doing a lookup in the kern table (that we don't have in node mode) the set kern value
+ is used. Disabling kern scaling will become an option in Luatex some day. You can blame me for all errors
+ that crept in and I know that there are some.
+
+ To be honest, I slowly start to grasp the magic here as normally I start from scratch when implementing
+ something (as it's the only way I can understand things). This time I had a recently acquired stack of
+ Porcupine Tree disks to get me through.
+
+ Picking up this effort was inspired by discussions between Luigi Scarso and me about efficiency of Lua
+ code and we needed some stress tests to compare regular LuaTeX and LuajitTeX. One of the tests was
+ processing tufte.tex as that one has lots of hyphenations and is a tough one to get right.
+
+ tufte: boxed 1000 times, no flushing in backend:
+
+ \testfeatureonce{1000}{\setbox0\hbox{\tufte}}
+ \testfeatureonce{1000}{\setbox0\vbox{\tufte}}
+ \startparbuilder[basic]\testfeatureonce{1000}{\setbox0\vbox{\tufte}}\stopparbuilder
+
+ method normal hz comment
+
+ luatex tex hbox 9.64 9.64 baseline font feature processing, hyphenation etc: 9.74
+ tex vbox 9.84 10.16 0.20 linebreak / 0.52 with hz -> 0.32 hz overhead (150pct more)
+ lua vbox 17.28 18.43 7.64 linebreak / 8.79 with hz -> 1.33 hz overhead ( 20pct more)
+
+ luajittex tex hbox 6.33 6.33 baseline font feature processing, hyphenation etc: 6.33
+ tex vbox 6.53 6.81 0.20 linebreak / 0.48 with hz -> 0.28 hz overhead (expected 0.32)
+ lua vbox 11.06 11.81 4.53 linebreak / 5.28 with hz -> 0.75 hz overhead
+
+ Interesting is that the runtime for the built-in parbuilder indeed increases much when expansion
+ is enabled, but in the Lua variant the extra overhead is way less significant. This means that when we
+ retrofit the same approach into the core, the overhead of expansion can be sort of nilled.
+
+]]--
+
+local utfchar = utf.char
+local write, write_nl = texio.write, texio.write_nl
+local sub, format = string.sub, string.format
+local round = math.round
+local insert, remove = table.insert, table.remove
+
+local fonts, nodes, node = fonts, nodes, node
+
+local trace_basic = false trackers.register("builders.paragraphs.basic", function(v) trace_basic = v end)
+local trace_lastlinefit = false trackers.register("builders.paragraphs.lastlinefit", function(v) trace_lastlinefit = v end)
+local trace_adjusting = false trackers.register("builders.paragraphs.adjusting", function(v) trace_adjusting = v end)
+local trace_protruding = false trackers.register("builders.paragraphs.protruding", function(v) trace_protruding = v end)
+local trace_expansion = false trackers.register("builders.paragraphs.expansion", function(v) trace_expansion = v end)
+local trace_quality = false trackers.register("builders.paragraphs.quality", function(v) trace_quality = v end)
+
+local report_parbuilders = logs.reporter("nodes","parbuilders")
+local report_hpackers = logs.reporter("nodes","hpackers")
+
+local calculate_badness = tex.badness
+local texnest = tex.nest
+local texlists = tex.lists
+
+-- (t == 0 and 0) or (s <= 0 and 10000) or calculate_badness(t,s)
+
+-- local function calculate_badness(t,s)
+-- if t == 0 then
+-- return 0
+-- elseif s <= 0 then
+-- return 10000 -- infinite_badness
+-- else
+-- local r
+-- if t <= 7230584 then
+-- r = (t * 297) / s
+-- elseif s >= 1663497 then
+-- r = t / (s / 297)
+-- else
+-- r = t
+-- end
+-- if r > 1290 then
+-- return 10000 -- infinite_badness
+-- else
+-- return (r * r * r + 0x20000) / 0x40000
+-- end
+-- end
+-- end
+
+local parbuilders = builders.paragraphs
+local constructors = parbuilders.constructors
+
+local setmetatableindex = table.setmetatableindex
+
+local fonthashes = fonts.hashes
+local fontdata = fonthashes.identifiers
+local chardata = fonthashes.characters
+local quaddata = fonthashes.quads
+local parameters = fonthashes.parameters
+
+local slide_nodes = node.slide
+local new_node = node.new
+local copy_node = node.copy
+local copy_node_list = node.copy_list
+local flush_node = node.free
+local flush_node_list = node.flush_list
+local hpack_nodes = node.hpack
+local xpack_nodes = node.hpack
+local replace_node = nodes.replace
+local insert_node_after = node.insert_after
+local insert_node_before = node.insert_before
+local traverse_by_id = node.traverse_id
+
+local setnodecolor = nodes.tracers.colors.set
+
+local nodepool = nodes.pool
+
+local nodecodes = nodes.nodecodes
+local whatcodes = nodes.whatcodes
+local kerncodes = nodes.kerncodes
+local glyphcodes = nodes.glyphcodes
+local gluecodes = nodes.gluecodes
+local margincodes = nodes.margincodes
+local disccodes = nodes.disccodes
+local mathcodes = nodes.mathcodes
+local fillcodes = nodes.fillcodes
+
+local temp_code = nodecodes.temp
+local glyph_code = nodecodes.glyph
+local ins_code = nodecodes.ins
+local mark_code = nodecodes.mark
+local adjust_code = nodecodes.adjust
+local penalty_code = nodecodes.penalty
+local whatsit_code = nodecodes.whatsit
+local disc_code = nodecodes.disc
+local math_code = nodecodes.math
+local kern_code = nodecodes.kern
+local glue_code = nodecodes.glue
+local hlist_code = nodecodes.hlist
+local vlist_code = nodecodes.vlist
+local unset_code = nodecodes.unset
+local marginkern_code = nodecodes.marginkern
+
+local leaders_code = gluecodes.leaders
+
+local localpar_code = whatcodes.localpar
+local dir_code = whatcodes.dir
+local pdfrefximage_code = whatcodes.pdfrefximage
+local pdfrefxform_code = whatcodes.pdfrefxform
+
+local kerning_code = kerncodes.kerning -- font kern
+local userkern_code = kerncodes.userkern
+
+local ligature_code = glyphcodes.ligature
+
+local stretch_orders = nodes.fillcodes
+
+local leftmargin_code = margincodes.left
+local rightmargin_code = margincodes.right
+
+local automatic_disc_code = disccodes.automatic
+local regular_disc_code = disccodes.regular
+local first_disc_code = disccodes.first
+local second_disc_code = disccodes.second
+
+local endmath_code = mathcodes.endmath
+
+local nosubtype_code = 0
+
+local unhyphenated_code = nodecodes.unhyphenated or 1
+local hyphenated_code = nodecodes.hyphenated or 2
+local delta_code = nodecodes.delta or 3
+local passive_code = nodecodes.passive or 4
+
+local maxdimen = number.maxdimen
+
+local max_halfword = 0x7FFFFFFF
+local infinite_penalty = 10000
+local eject_penalty = -10000
+local infinite_badness = 10000
+local awful_badness = 0x3FFFFFFF
+
+local fit_very_loose_class = 0 -- fitness for lines stretching more than their stretchability
+local fit_loose_class = 1 -- fitness for lines stretching 0.5 to 1.0 of their stretchability
+local fit_decent_class = 2 -- fitness for all other lines
+local fit_tight_class = 3 -- fitness for lines shrinking 0.5 to 1.0 of their shrinkability
+
+local new_penalty = nodepool.penalty
+local new_dir = nodepool.textdir
+local new_leftmarginkern = nodepool.leftmarginkern
+local new_rightmarginkern = nodepool.rightmarginkern
+local new_leftskip = nodepool.leftskip
+local new_rightskip = nodepool.rightskip
+local new_lineskip = nodepool.lineskip
+local new_baselineskip = nodepool.baselineskip
+local new_temp = nodepool.temp
+local new_rule = nodepool.rule
+
+local is_rotated = nodes.is_rotated
+local is_parallel = nodes.textdir_is_parallel
+local is_opposite = nodes.textdir_is_opposite
+local textdir_is_equal = nodes.textdir_is_equal
+local pardir_is_equal = nodes.pardir_is_equal
+local glyphdir_is_equal = nodes.glyphdir_is_equal
+
+local dir_pops = nodes.dir_is_pop
+local dir_negations = nodes.dir_negation
+local is_skipable = node.protrusion_skippable
+
+-- helpers --
+
+-- It makes more sense to move the somewhat messy dir state tracking
+-- out of the main functions. First we create a stack allocator.
+
+local function new_dir_stack(dir) -- also use elsewhere
+ return { n = 0, dir }
+end
+
+-- The next function checks a dir node and returns the new dir state. By
+-- using s static table we are quite efficient. This function is used
+-- in the parbuilder.
+
+local function checked_line_dir(stack,current)
+ if not dir_pops[current] then
+ local n = stack.n + 1
+ stack.n = n
+ stack[n] = current
+ return current.dir
+ elseif n > 0 then
+ local n = stack.n
+ local dirnode = stack[n]
+ dirstack.n = n - 1
+ return dirnode.dir
+ else
+ report_parbuilders("warning: missing pop node (%a)",1) -- in line ...
+ end
+end
+
+-- The next function checks a dir nodes in a list and appends the negations
+-- that are currently needed (some day LuaTeX will be more tolerant). We use
+-- the negations for the next line.
+
+local function inject_dirs_at_end_of_line(stack,current,start,stop)
+ local e = start
+ local n = stack.n
+ local h = nil
+ while start and start ~= stop do
+ if start.id == whatsit_code and start.subtype == dir_code then
+ if not dir_pops[start.dir] then
+ n = n + 1
+ stack[n] = start
+ elseif n > 0 then
+ n = n - 1
+ else
+ report_parbuilders("warning: missing pop node (%a)",2) -- in line ...
+ end
+ end
+ start = start.next
+ end
+ for i=n,1,-1 do
+ h, current = insert_node_after(current,current,new_dir(dir_negations[stack[i].dir]))
+ end
+ stack.n = n
+ return current
+end
+
+local function inject_dirs_at_begin_of_line(stack,current)
+ local h = nil
+ for i=stack.n,1,-1 do
+ h, current = insert_node_after(current,current,new_dir(stack[i]))
+ end
+ stack.n = 0
+ return current
+end
+
+-- diagnostics --
+
+local dummy = function() end
+
+local diagnostics = {
+ start = dummy,
+ stop = dummy,
+ current_pass = dummy,
+ break_node = dummy,
+ feasible_break = dummy,
+}
+
+-- statistics --
+
+local nofpars, noflines, nofprotrudedlines, nofadjustedlines = 0, 0, 0, 0
+
+local function register_statistics(par)
+ local statistics = par.statistics
+ nofpars = nofpars + 1
+ noflines = noflines + statistics.noflines
+ nofprotrudedlines = nofprotrudedlines + statistics.nofprotrudedlines
+ nofadjustedlines = nofadjustedlines + statistics.nofadjustedlines
+end
+
+-- resolvers --
+
+local whatsiters = {
+ get_width = { },
+ get_dimensions = { },
+}
+
+local get_whatsit_width = whatsiters.get_width
+local get_whatsit_dimensions = whatsiters.get_dimensions
+
+local function get_width (n) return n.width end
+local function get_dimensions(n) return n.width, n.height, n.depth end
+
+get_whatsit_width[pdfrefximage_code] = get_width
+get_whatsit_width[pdfrefxform_code ] = get_width
+
+get_whatsit_dimensions[pdfrefximage_code] = get_dimensions
+get_whatsit_dimensions[pdfrefxform_code ] = get_dimensions
+
+-- expansion etc --
+
+local function calculate_fraction(x,n,d,max_answer)
+ local the_answer = x * n/d + 1/2 -- round ?
+ if the_answer > max_answer then
+ return max_answer
+ elseif the_answer < -max_answer then
+ return -max_answer
+ else
+ return the_answer
+ end
+end
+
+local function check_shrinkage(par,n)
+ -- called often, so maybe move inline
+ if n.shrink_order ~= 0 and n.shrink ~= 0 then
+ if par.no_shrink_error_yet then
+ par.no_shrink_error_yet = false
+ report_parbuilders("infinite glue shrinkage found in a paragraph and removed")
+ end
+ n = copy_node(n)
+ n.shrink_order = 0
+ end
+ return n
+end
+
+-- It doesn't really speed up much but the additional memory usage is
+-- rather small so it doesn't hurt too much.
+
+local expansions = { }
+local nothing = { stretch = 0, shrink = 0 }
+
+setmetatableindex(expansions,function(t,font)
+ local expansion = parameters[font].expansion -- can be an extra hash
+ if expansion and expansion.auto then
+ local factors = { }
+ local c = chardata[font]
+ setmetatableindex(factors,function(t,char)
+ local fc = c[char]
+ local ef = fc.expansion_factor
+ if ef and ef > 0 then
+ local stretch = expansion.stretch
+ local shrink = expansion.shrink
+ if stretch ~= 0 or shrink ~= 0 then
+ local factor = ef / 1000
+ local ef_quad = factor * quaddata[font] / 1000
+ local v = {
+ glyphstretch = stretch * ef_quad,
+ glyphshrink = shrink * ef_quad,
+ factor = factor,
+ stretch = stretch,
+ shrink = shrink ,
+ }
+ t[char] = v
+ return v
+ end
+ end
+ t[char] = nothing
+ return nothing
+ end)
+ t[font] = factors
+ return factors
+ else
+ t[font] = false
+ return false
+ end
+end)
+
+-- local function char_stretch_shrink(p)
+-- local data = expansions[p.font][p.char]
+-- if data then
+-- return data.glyphstretch, data.glyphshrink
+-- else
+-- return 0, 0
+-- end
+-- end
+--
+-- local cal_margin_kern_var = char_stretch_shrink
+
+-- local function kern_stretch_shrink(p,d)
+-- local l = p.prev
+-- if l and l.id == glyph_code then -- how about disc nodes?
+-- local r = p.next
+-- if r and r.id == glyph_code then
+-- local lf, rf = l.font, r.font
+-- if lf == rf then
+-- local data = expansions[lf][l.char]
+-- if data then
+-- local stretch = data.stretch
+-- local shrink = data.shrink
+-- if stretch ~= 0 then
+-- -- stretch = data.factor * (d * stretch - d)
+-- stretch = data.factor * d * (stretch - 1)
+-- end
+-- if shrink ~= 0 then
+-- -- shrink = data.factor * (d * shrink - d)
+-- shrink = data.factor * d * (shrink - 1)
+-- end
+-- return stretch, shrink
+-- end
+-- end
+-- end
+-- end
+-- return 0, 0
+-- end
+
+local function kern_stretch_shrink(p,d)
+ local left = p.prev
+ if left and left.id == glyph_code then -- how about disc nodes?
+ local data = expansions[left.font][left.char]
+ if data then
+ local stretch = data.stretch
+ local shrink = data.shrink
+ if stretch ~= 0 then
+ -- stretch = data.factor * (d * stretch - d)
+ stretch = data.factor * d * (stretch - 1)
+ end
+ if shrink ~= 0 then
+ -- shrink = data.factor * (d * shrink - d)
+ shrink = data.factor * d * (shrink - 1)
+ end
+ return stretch, shrink
+ end
+ end
+ return 0, 0
+end
+
+local function kern_stretch_shrink(p,d)
+ return 0, 0
+end
+
+-- state:
+
+local function check_expand_pars(checked_expansion,f)
+ local expansion = parameters[f].expansion
+ if not expansion then
+ checked_expansion[f] = false
+ return false
+ end
+ local step = expansion.step or 0
+ local stretch = expansion.stretch or 0
+ local shrink = expansion.shrink or 0
+ if step == 0 or (stretch == 0 and schrink == 0) then
+ checked_expansion[f] = false
+ return false
+ end
+ local par = checked_expansion.par
+ if par.cur_font_step < 0 then
+ par.cur_font_step = step
+ elseif par.cur_font_step ~= step then
+ report_parbuilders("using fonts with different step of expansion in one paragraph is not allowed")
+ checked_expansion[f] = false
+ return false
+ end
+ if stretch == 0 then
+ -- okay
+ elseif par.max_stretch_ratio < 0 then
+ par.max_stretch_ratio = stretch -- expansion_factor
+ elseif par.max_stretch_ratio ~= stretch then
+ report_parbuilders("using fonts with different stretch limit of expansion in one paragraph is not allowed")
+ checked_expansion[f] = false
+ return false
+ end
+ if shrink == 0 then
+ -- okay
+ elseif par.max_shrink_ratio < 0 then
+ par.max_shrink_ratio = shrink -- - expansion_factor
+ elseif par.max_shrink_ratio ~= shrink then
+ report_parbuilders("using fonts with different shrink limit of expansion in one paragraph is not allowed")
+ checked_expansion[f] = false
+ return false
+ end
+ if trace_adjusting then
+ report_parbuilders("expanding font %a using step %a, shrink %a and stretch %a",f,step,stretch,shrink)
+ end
+ local e = expansions[f]
+ checked_expansion[f] = e
+ return e
+end
+
+local function check_expand_lines(checked_expansion,f)
+ local expansion = parameters[f].expansion
+ if not expansion then
+ checked_expansion[f] = false
+ return false
+ end
+ local step = expansion.step or 0
+ local stretch = expansion.stretch or 0
+ local shrink = expansion.shrink or 0
+ if step == 0 or (stretch == 0 and schrink == 0) then
+ checked_expansion[f] = false
+ return false
+ end
+ if trace_adjusting then
+ report_parbuilders("expanding font %a using step %a, shrink %a and stretch %a",f,step,stretch,shrink)
+ end
+ local e = expansions[f]
+ checked_expansion[f] = e
+ return e
+end
+
+-- protrusion
+
+local function find(head) -- do we really want to recurse into an hlist?
+ while head do
+ local id = head.id
+ if id == glyph_code then
+ return head
+ elseif id == hlist_code then
+ local found = find(head.list)
+ if found then
+ return found
+ else
+ head = head.next
+ end
+ elseif is_skipable(head) then
+ head = head.next
+ else
+ return head
+ end
+ end
+ return nil
+end
+
+local function find_protchar_left(l) -- weird function
+ local ln = l.next
+ if ln and ln.id == hlist_code and not ln.list and ln.width == 0 and ln.height == 0 and ln.depth == 0 then
+ l = l.next
+ else -- if d then -- was always true
+ local id = l.id
+ while ln and not (id == glyph_code or id < math_code) do -- is there always a glyph?
+ l = ln
+ ln = l.next
+ id = ln.id
+ end
+ end
+ -- if l.id == glyph_code then
+ -- return l
+ -- end
+ return find(l) or l
+end
+
+local function find(head,tail)
+ local tail = tail or slide_nodes(head)
+ while tail do
+ local id = tail.id
+ if id == glyph_code then
+ return tail
+ elseif id == hlist_code then
+ local found = find(tail.list)
+ if found then
+ return found
+ else
+ tail = tail.prev
+ end
+ elseif is_skipable(tail) then
+ tail = tail.prev
+ else
+ return tail
+ end
+ end
+ return nil
+end
+
+local function find_protchar_right(l,r)
+ return r and find(l,r) or r
+end
+
+local function left_pw(p)
+ local font = p.font
+ local prot = chardata[font][p.char].left_protruding
+ if not prot or prot == 0 then
+ return 0
+ end
+ return prot * quaddata[font] / 1000, p
+end
+
+local function right_pw(p)
+ local font = p.font
+ local prot = chardata[font][p.char].right_protruding
+ if not prot or prot == 0 then
+ return 0
+ end
+ return prot * quaddata[font] / 1000, p
+end
+
+-- par parameters
+
+local function reset_meta(par)
+ local active = {
+ id = hyphenated_code,
+ line_number = max_halfword,
+ }
+ active.next = par.active -- head of metalist
+ par.active = active
+ par.passive = nil
+end
+
+local function add_to_width(line_break_dir,checked_expansion,s) -- split into two loops (normal and expansion)
+ local size = 0
+ local adjust_stretch = 0
+ local adjust_shrink = 0
+ while s do
+ local id = s.id
+ if id == glyph_code then
+ if is_rotated[line_break_dir] then -- can be shared
+ size = size + s.height + s.depth
+ else
+ size = size + s.width
+ end
+ if checked_expansion then
+ local data = checked_expansion[s.font]
+ if data then
+ data = data[s.char]
+ if data then
+ adjust_stretch = adjust_stretch + data.glyphstretch
+ adjust_shrink = adjust_shrink + data.glyphshrink
+ end
+ end
+ end
+ elseif id == hlist_code or id == vlist_code then
+ if is_parallel[s.dir][line_break_dir] then
+ size = size + s.width
+ else
+ size = size + s.depth + s.height
+ end
+ elseif id == kern_code then
+ if checked_expansion and s.subtype == kerning_code then
+ local d = s.kern
+ if d ~= 0 then
+ local stretch, shrink = kern_stretch_shrink(s,d)
+ adjust_stretch = adjust_stretch + stretch
+ adjust_shrink = adjust_shrink + shrink
+ end
+ end
+ size = size + s.kern
+ elseif id == rule_code then
+ size = size + s.width
+ else
+ report_parbuilders("unsupported node at location %a",6)
+ end
+ s = s.next
+ end
+ return size, adjust_stretch, adjust_shrink
+end
+
+local function compute_break_width(par,break_type,p) -- split in two
+ local break_width = par.break_width
+ if break_type > unhyphenated_code then
+ local disc_width = par.disc_width
+ local checked_expansion = par.checked_expansion
+ local line_break_dir = par.line_break_dir
+ local break_size = break_width.size + disc_width.size
+ local break_adjust_stretch = break_width.adjust_stretch + disc_width.adjust_stretch
+ local break_adjust_shrink = break_width.adjust_shrink + disc_width.adjust_shrink
+ local replace = p.replace
+ if replace then
+ local size, adjust_stretch, adjust_shrink = add_to_width(line_break_dir,checked_expansion,replace)
+ break_size = break_size - size
+ break_adjust_stretch = break_adjust_stretch - adjust_stretch
+ break_adjust_shrink = break_adjust_shrink - adjust_shrink
+ end
+ local post = p.post
+ if post then
+ local size, adjust_stretch, adjust_shrink = add_to_width(line_break_dir,checked_expansion,post)
+ break_size = break_size + size
+ break_adjust_stretch = break_adjust_stretch + adjust_stretch
+ break_adjust_shrink = break_adjust_shrink + adjust_shrink
+ end
+ break_width.size = break_size
+ break_width.adjust_stretch = break_adjust_stretch
+ break_width.adjust_shrink = break_adjust_shrink
+ if not post then
+ p = p.next
+ else
+ return
+ end
+ end
+ while p do -- skip spacing etc
+ local id = p.id
+ if id == glyph_code then
+ return -- happens often
+ elseif id == glue_code then
+ local spec = p.spec
+ local order = stretch_orders[spec.stretch_order]
+ break_width.size = break_width.size - spec.width
+ break_width[order] = break_width[order] - spec.stretch
+ break_width.shrink = break_width.shrink - spec.shrink
+ elseif id == penalty_code then
+ -- do nothing
+ elseif id == kern_code then
+ if p.subtype == userkern_code then
+ break_width.size = break_width.size - p.kern
+ else
+ return
+ end
+ elseif id == math_code then
+ break_width.size = break_width.size - p.surround
+ else
+ return
+ end
+ p = p.next
+ end
+end
+
+local function append_to_vlist(par, b)
+ local prev_depth = par.prev_depth
+ if prev_depth > par.ignored_dimen then
+ if b.id == hlist_code then
+ local d = par.baseline_skip.width - prev_depth - b.height -- deficiency of space between baselines
+ local s = d < par.line_skip_limit and new_lineskip(tex.lineskip) or new_baselineskip(d)
+ -- local s = d < par.line_skip_limit
+ -- if s then
+ -- s = new_lineskip()
+ -- s.spec = tex.lineskip
+ -- else
+ -- s = new_baselineskip(d)
+ -- end
+ local head_field = par.head_field
+ if head_field then
+ local n = slide_nodes(head_field)
+ n.next, s.prev = s, n
+ else
+ par.head_field = s
+ end
+ end
+ end
+ local head_field = par.head_field
+ if head_field then
+ local n = slide_nodes(head_field)
+ n.next, b.prev = b, n
+ else
+ par.head_field = b
+ end
+ if b.id == hlist_code then
+ local pd = b.depth
+ par.prev_depth = pd
+ texnest[texnest.ptr].prevdepth = pd
+ end
+end
+
+local function append_list(par, b)
+ local head_field = par.head_field
+ if head_field then
+ local n = slide_nodes(head_field)
+ n.next, b.prev = b, n
+ else
+ par.head_field = b
+ end
+end
+
+-- We can actually make par local to this module as we never break inside a break call and that way the
+-- array is reused. At some point the information will be part of the paragraph spec as passed.
+
+local function initialize_line_break(head,display)
+
+ local hang_indent = tex.hangindent or 0
+ local hsize = tex.hsize or 0
+ local hang_after = tex.hangafter or 0
+ local par_shape_ptr = tex.parshape
+ local left_skip = tex.leftskip -- nodes
+ local right_skip = tex.rightskip -- nodes
+ local pretolerance = tex.pretolerance
+ local tolerance = tex.tolerance
+ local adjust_spacing = tex.pdfadjustspacing
+ local protrude_chars = tex.pdfprotrudechars
+ local last_line_fit = tex.lastlinefit
+
+ local newhead = new_temp()
+ newhead.next = head
+
+ local adjust_spacing_status = adjust_spacing > 1 and -1 or 0
+
+ -- metatables
+
+ local par = {
+ head = newhead,
+ head_field = nil,
+ display = display,
+ font_in_short_display = 0,
+ no_shrink_error_yet = true, -- have we complained about infinite shrinkage?
+ second_pass = false, -- is this our second attempt to break this paragraph?
+ final_pass = false, -- is this our final attempt to break this paragraph?
+ threshold = 0, -- maximum badness on feasible lines
+
+ passive = nil, -- most recent node on passive list
+ printed_node = head, -- most recent node that has been printed
+ pass_number = 0, -- the number of passive nodes allocated on this pass
+ auto_breaking = 0, -- make auto_breaking accessible out of line_break
+
+ active_width = { size = 0, stretch = 0, fi = 0, fil = 0, fill = 0, filll = 0, shrink = 0, adjust_stretch = 0, adjust_shrink = 0 },
+ break_width = { size = 0, stretch = 0, fi = 0, fil = 0, fill = 0, filll = 0, shrink = 0, adjust_stretch = 0, adjust_shrink = 0 },
+ disc_width = { size = 0, adjust_stretch = 0, adjust_shrink = 0 },
+ fill_width = { stretch = 0, fi = 0, fil = 0, fill = 0, filll = 0, shrink = 0 },
+ background = { size = 0, stretch = 0, fi = 0, fil = 0, fill = 0, filll = 0, shrink = 0 },
+
+ hang_indent = hang_indent,
+ hsize = hsize,
+ hang_after = hang_after,
+ par_shape_ptr = par_shape_ptr,
+ left_skip = left_skip,
+ right_skip = right_skip,
+ pretolerance = pretolerance,
+ tolerance = tolerance,
+
+ protrude_chars = protrude_chars,
+ adjust_spacing = adjust_spacing,
+ max_stretch_ratio = adjust_spacing_status,
+ max_shrink_ratio = adjust_spacing_status,
+ cur_font_step = adjust_spacing_status,
+ checked_expansion = false,
+ tracing_paragraphs = tex.tracingparagraphs > 0,
+
+ emergency_stretch = tex.emergencystretch or 0,
+ looseness = tex.looseness or 0,
+ line_penalty = tex.linepenalty or 0,
+ hyphen_penalty = tex.hyphenpenalty or 0,
+ broken_penalty = tex.brokenpenalty or 0,
+ inter_line_penalty = tex.interlinepenalty or 0,
+ club_penalty = tex.clubpenalty or 0,
+ widow_penalty = tex.widowpenalty or 0,
+ display_widow_penalty = tex.displaywidowpenalty or 0,
+ ex_hyphen_penalty = tex.exhyphenpenalty or 0,
+
+ adj_demerits = tex.adjdemerits or 0,
+ double_hyphen_demerits = tex.doublehyphendemerits or 0,
+ final_hyphen_demerits = tex.finalhyphendemerits or 0,
+
+ first_line = 0, -- tex.nest.modeline, -- 0, -- cur_list.pg_field
+
+ each_line_height = tex.pdfeachlineheight or 0, -- this will go away
+ each_line_depth = tex.pdfeachlinedepth or 0, -- this will go away
+ first_line_height = tex.pdffirstlineheight or 0, -- this will go away
+ last_line_depth = tex.pdflastlinedepth or 0, -- this will go away
+ ignored_dimen = tex.pdfignoreddimen or 0, -- this will go away
+
+ baseline_skip = tex.baselineskip or 0,
+ line_skip_limit = tex.lineskiplimit or 0,
+
+ prev_depth = texnest[texnest.ptr].prevdepth,
+
+ final_par_glue = slide_nodes(head), -- todo: we know tail already, slow
+
+ par_break_dir = tex.pardir,
+ line_break_dir = tex.pardir,
+
+ internal_pen_inter = 0, -- running localinterlinepenalty
+ internal_pen_broken = 0, -- running localbrokenpenalty
+ internal_left_box = nil, -- running localleftbox
+ internal_left_box_width = 0, -- running localleftbox width
+ init_internal_left_box = nil, -- running localleftbox
+ init_internal_left_box_width = 0, -- running localleftbox width
+ internal_right_box = nil, -- running localrightbox
+ internal_right_box_width = 0, -- running localrightbox width
+
+ best_place = { }, -- how to achieve minimal_demerits
+ best_pl_line = { }, -- corresponding line number
+ easy_line = 0, -- line numbers easy_line are equivalent in break nodes
+ last_special_line = 0, -- line numbers last_special_line all have the same width
+ first_width = 0, -- the width of all lines last_special_line, if no parshape has been specified
+ second_width = 0, -- the width of all lines last_special_line
+ first_indent = 0, -- left margin to go with first_width
+ second_indent = 0, -- left margin to go with second_width
+
+ best_bet = nil, -- use this passive node and its predecessors
+ fewest_demerits = 0, -- the demerits associated with best_bet
+ best_line = 0, -- line number following the last line of the new paragraph
+ line_diff = 0, -- the difference between the current line number and the optimum best_line
+
+ -- not yet used
+
+ best_pl_short = { }, -- shortfall corresponding to minimal_demerits
+ best_pl_glue = { }, -- corresponding glue stretch or shrink
+ do_last_line_fit = false,
+ last_line_fit = last_line_fit,
+
+ minimum_demerits = awful_badness,
+
+ minimal_demerits = {
+
+ [fit_very_loose_class] = awful_badness,
+ [fit_loose_class] = awful_badness,
+ [fit_decent_class] = awful_badness,
+ [fit_tight_class] = awful_badness,
+
+ },
+
+ prev_char_p = nil,
+
+ font_steps = { }, -- mine
+
+ statistics = {
+
+ noflines = 0,
+ nofprotrudedlines = 0,
+ nofadjustedlines = 0,
+
+ }
+
+ }
+
+ if adjust_spacing > 1 then
+ local checked_expansion = { par = par }
+ setmetatableindex(checked_expansion,check_expand_pars)
+ par.checked_expansion = checked_expansion
+ end
+
+ -- we need par for the error message
+
+ local background = par.background
+
+ local l = check_shrinkage(par,left_skip)
+ local r = check_shrinkage(par,right_skip)
+ local l_order = stretch_orders[l.stretch_order]
+ local r_order = stretch_orders[r.stretch_order]
+
+ background.size = l.width + r.width
+ background.shrink = l.shrink + r.shrink
+ background[l_order] = l.stretch
+ background[r_order] = r.stretch + background[r_order]
+
+ -- this will move up so that we can assign the whole par table
+
+ if not par_shape_ptr then
+ if hang_indent == 0 then
+ par.second_width = hsize
+ par.second_indent = 0
+ else
+ local abs_hang_after = hang_after >0 and hang_after or -hang_after
+ local abs_hang_indent = hang_indent>0 and hang_indent or -hang_indent
+ par.last_special_line = abs_hang_after
+ if hang_after < 0 then
+ par.first_width = hsize - abs_hang_indent
+ if hang_indent >= 0 then
+ par.first_indent = hang_indent
+ else
+ par.first_indent = 0
+ end
+ par.second_width = hsize
+ par.second_indent = 0
+ else
+ par.first_width = hsize
+ par.first_indent = 0
+ par.second_width = hsize - abs_hang_indent
+ if hang_indent >= 0 then
+ par.second_indent = hang_indent
+ else
+ par.second_indent = 0
+ end
+ end
+ end
+ else
+ local last_special_line = #par_shape_ptr
+ par.last_special_line = last_special_line
+ local ptr = par_shape_ptr[last_special_line]
+ par.second_width = ptr[2]
+ par.second_indent = ptr[1]
+ end
+
+ if par.looseness == 0 then
+ par.easy_line = par.last_special_line
+ else
+ par.easy_line = max_halfword
+ end
+
+ if pretolerance >= 0 then
+ par.threshold = pretolerance
+ par.second_pass = false
+ par.final_pass = false
+ else
+ par.threshold = tolerance
+ par.second_pass = true
+ par.final_pass = par.emergency_stretch <= 0
+ if trace_basic then
+ if par.final_pass then
+ report_parbuilders("enabling second and final pass")
+ else
+ report_parbuilders("enabling second pass")
+ end
+ end
+ end
+
+ if last_line_fit > 0 then
+ local spec = par.final_par_glue.spec
+ local stretch = spec.stretch
+ local stretch_order = spec.stretch_order
+ if stretch > 0 and stretch_order > 0 and background.fi == 0 and background.fil == 0 and background.fill == 0 and background.filll == 0 then
+ par.do_last_line_fit = true
+ local si = stretch_orders[stretch_order]
+ if trace_lastlinefit or trace_basic then
+ report_parbuilders("enabling last line fit, stretch order %a set to %a, linefit is %a",si,stretch,last_line_fit)
+ end
+ par.fill_width[si] = stretch
+ end
+ end
+
+ return par
+end
+
+local function post_line_break(par)
+
+ local prevgraf = texnest[texnest.ptr].prevgraf
+ local cur_line = prevgraf + 1 -- the current line number being justified
+ local cur_p = nil
+
+ local adjust_spacing = par.adjust_spacing
+ local protrude_chars = par.protrude_chars
+ local statistics = par.statistics
+
+ local p, s, k, w -- check when local
+
+ local q = par.best_bet.break_node
+ repeat -- goto first breakpoint
+ local r = q
+ q = q.prev_break
+ r.prev_break = cur_p
+ cur_p = r
+ until not q
+
+ local stack = new_dir_stack()
+
+ repeat
+
+ inject_dirs_at_begin_of_line(stack,par.head)
+
+ local q = nil
+ local r = cur_p.cur_break
+
+ local disc_break = false
+ local post_disc_break = false
+ local glue_break = false
+
+ if not r then
+ r = slide_nodes(par.head)
+ if r == par.final_par_glue then
+ q = r -- q refers to the last node of the line (and paragraph)
+ r = r.prev -- r refers to the node after which the dir nodes should be closed
+ end
+ else
+ local id = r.id
+ if id == glue_code then
+ -- r is normal skip
+ r = replace_node(r,new_rightskip(par.right_skip))
+ glue_break = true
+ q = r -- q refers to the last node of the line
+ r = r.prev -- r refers to the node after which the dir nodes should be closed
+ elseif id == disc_code then
+ -- todo: use insert_before/after
+ local prev_r = r.prev
+ local next_r = r.next
+ local subtype = r.subtype
+ local pre = r.pre
+ local post = r.post
+ local replace = r.replace
+ if subtype == second_disc_code then
+ if not (prev_r.id == disc_code and prev_r.subtype == first_disc_code) then
+ report_parbuilders('unsupported disc at location %a',3)
+ end
+ if pre then
+ flush_node_list(pre)
+ r.pre = nil
+ pre = nil -- signal
+ end
+ if replace then
+ local n = slide_nodes(replace)
+ prev_r.next = replace
+ replace.prev = prev_r
+ n.next = r
+ r.prev = n
+ r.replace = nil
+ replace = nil -- signal
+ end
+ local pre = prev_r.pre
+ local post = prev_r.post
+ local replace = prev_r.replace
+ if pre then
+ flush_node_list(pre)
+ prev_r.pre = nil
+ end
+ if replace then
+ flush_node_list(replace)
+ prev_r.replace = nil
+ end
+ if post then
+ flush_node_list(post)
+ prev_r.post = nil
+ end
+ elseif subtype == first_disc_code then
+ if not (v.id == disc_code and v.subtype == second_disc_code) then
+ report_parbuilders('unsupported disc at location %a',4)
+ end
+ next_r.subtype = regular_disc_code
+ next_r.replace = post
+ r.post = nil
+ end
+ if replace then
+ r.replace = nil -- free
+ flush_node_list(replace)
+ end
+ if pre then
+ local n = slide_nodes(pre)
+ prev_r.next = pre
+ pre.prev = prev_r
+ n.next = r
+ r.prev = n
+ r.pre = nil
+ end
+ if post then
+ local n = slide_nodes(post)
+ r.next = post
+ post.prev = r
+ n.next = next_r
+ next_r.prev = n
+ r.post = nil
+ post_disc_break = true
+ end
+ disc_break = true
+ elseif id == kern_code then
+ r.kern = 0
+ elseif r.id == math_code then
+ r.surround = 0
+ end
+ end
+ r = inject_dirs_at_end_of_line(stack,r,par.head.next,cur_p.cur_break)
+ local crb = cur_p.passive_right_box
+ if crb then
+ local s = copy_node(crb)
+ local e = r.next
+ r.next = s
+ s.prev = r
+ s.next = e
+ if e then
+ e.prev = s
+ end
+ r = s
+ end
+ if not q then
+ q = r
+ end
+ if q and q ~= par.head and protrude_chars > 0 then
+ local id = q.id
+ local c = (disc_break and (id == glyph_code or id ~= disc_code) and q) or q.prev
+ local p = find_protchar_right(par.head.next,c)
+ if p and p.id == glyph_code then
+ local w, last_rightmost_char = right_pw(p)
+ if last_rightmost_char and w ~= 0 then
+ -- so we inherit attributes, q is new pseudo head
+ q, c = insert_node_after(q,c,new_rightmarginkern(copy_node(last_rightmost_char),-w))
+ end
+ end
+ end
+ if not glue_break then
+ local h
+ h, q = insert_node_after(q,q,new_rightskip(par.right_skip)) -- q moves on as pseudo head
+ end
+ r = q.next
+ q.next = nil
+ local phead = par.head
+ q = phead.next
+ phead.next = r
+ if r then
+ r.prev = phead
+ end
+ local clb = cur_p.passive_left_box
+ if clb then -- here we miss some prev links
+ local s = copy_node(cb)
+ s = q.next
+ r.next = q
+ q = r
+ if s and cur_line == (par.first_line + 1) and s.id == hlist_code and not s.list then
+ q = q.next
+ r.next = s.next
+ s.next = r
+ end
+ end
+ if protrude_chars > 0 then
+ local p = find_protchar_left(q)
+ if p and p.id == glyph_code then
+ local w, last_leftmost_char = left_pw(p)
+ if last_leftmost_char and w ~= 0 then
+ -- so we inherit attributes, q is pseudo head and moves back
+ q = insert_node_before(q,q,new_leftmarginkern(copy_node(last_leftmost_char),-w))
+ end
+ end
+ end
+ local ls = par.left_skip
+ if ls and (ls.width ~= 0 or ls.stretch ~= 0 or ls.shrink ~= 0) then
+ q = insert_node_before(q,q,new_leftskip(ls))
+ end
+ local curwidth, cur_indent
+ if cur_line > par.last_special_line then
+ cur_indent = par.second_indent
+ cur_width = par.second_width
+ else
+ local psp = par.par_shape_ptr
+ if psp then
+ cur_indent = psp[cur_line][1]
+ cur_width = psp[cur_line][2]
+ else
+ cur_indent = par.first_indent
+ cur_width = par.first_width
+ end
+ end
+ statistics.noflines = statistics.noflines + 1
+ if adjust_spacing > 0 then
+ statistics.nofadjustedlines = statistics.nofadjustedlines + 1
+ just_box = xpack_nodes(q,cur_width,"cal_expand_ratio",par.par_break_dir) -- ,cur_p.analysis)
+ else
+ just_box = xpack_nodes(q,cur_width,"exactly",par.par_break_dir) -- ,cur_p.analysis)
+ end
+ if protrude_chars > 0 then
+ statistics.nofprotrudedlines = statistics.nofprotrudedlines + 1
+ end
+ -- wrong:
+ local adjust_head = texlists.adjust_head
+ local pre_adjust_head = texlists.pre_adjust_head
+ --
+ just_box.shift = cur_indent
+ if par.each_line_height ~= par.ignored_dimen then
+ just_box.height = par.each_line_height
+ end
+ if par.each_line_depth ~= par.ignored_dimen then
+ just_box.depth = par.each_line_depth
+ end
+ if par.first_line_height ~= par.ignored_dimen and (cur_line == par.first_line + 1) then
+ just_box.height = par.first_line_height
+ end
+ if par.last_line_depth ~= par.ignored_dimen and cur_line + 1 == par.best_line then
+ just_box.depth = par.last_line_depth
+ end
+ if texlists.pre_adjust_head ~= pre_adjust_head then
+ append_list(par, texlists.pre_adjust_head)
+ texlists.pre_adjust_head = pre_adjust_head
+ end
+ append_to_vlist(par, just_box)
+ if texlists.adjust_head ~= adjust_head then
+ append_list(par, texlists.adjust_head)
+ texlists.adjust_head = adjust_head
+ end
+ local pen
+ if cur_line + 1 ~= par.best_line then
+ if cur_p.passive_pen_inter then
+ pen = cur_p.passive_pen_inter
+ else
+ pen = par.inter_line_penalty
+ end
+ if cur_line == prevgraf + 1 then
+ pen = pen + par.club_penalty
+ end
+ if cur_line + 2 == par.best_line then
+ if par.display then
+ pen = pen + par.display_widow_penalty
+ else
+ pen = pen + par.widow_penalty
+ end
+ end
+ if disc_break then
+ if cur_p.passive_pen_broken ~= 0 then
+ pen = pen + cur_p.passive_pen_broken
+ else
+ pen = pen + par.broken_penalty
+ end
+ end
+ if pen ~= 0 then
+ append_to_vlist(par,new_penalty(pen))
+ end
+ end
+ cur_line = cur_line + 1
+ cur_p = cur_p.prev_break
+ if cur_p and not post_disc_break then
+ local phead = par.head
+ local r = phead
+ while true do
+ q = r.next
+ if q == cur_p.cur_break or q.id == glyph_code then
+ break
+ end
+ local id = q.id
+ if not (id == whatsit_code and q.subtype == localpar_code) then
+ if id < math_code or (id == kern_code and q.subtype ~= userkern_code) then
+ break
+ end
+ end
+ r = q
+ end
+ if r ~= phead then
+ r.next = nil
+ flush_node_list(phead.next)
+ phead.next = q
+ if q then
+ q.prev = phead
+ end
+ end
+ end
+ until not cur_p
+ if cur_line ~= par.best_line then -- or not par.head.next then
+ report_parbuilders("line breaking")
+ end
+ if par.head then -- added
+-- flush_node(par.head) -- the localpar_code whatsit
+ par.head = nil
+ end
+ cur_line = cur_line - 1
+ if trace_basic then
+ report_parbuilders("paragraph broken into %a lines",cur_line)
+ end
+ texnest[texnest.ptr].prevgraf = cur_line
+end
+
+local function wrap_up(par)
+ if par.tracing_paragraphs then
+ diagnostics.stop()
+ end
+ if par.do_last_line_fit then
+ local best_bet = par.best_bet
+ local active_short = best_bet.active_short
+ local active_glue = best_bet.active_glue
+ if active_short == 0 then
+ if trace_lastlinefit then
+ report_parbuilders("disabling last line fit, no active_short")
+ end
+ par.do_last_line_fit = false
+ else
+ local glue = par.final_par_glue
+ local spec = copy_node(glue.spec)
+ spec.width = spec.width + active_short - active_glue
+ spec.stretch = 0
+ -- flush_node(glue.spec) -- brrr, when we do this we can get an "invalid id stretch message", maybe dec refcount
+ glue.spec = spec
+ if trace_lastlinefit then
+ report_parbuilders("applying last line fit, short %a, glue %p",active_short,active_glue)
+ end
+ end
+ end
+ -- we have a bunch of glue and and temp nodes not freed
+ local head = par.head
+ if head.id == temp_code then
+ par.head = head.next
+ flush_node(head)
+ end
+ post_line_break(par)
+ reset_meta(par)
+ register_statistics(par)
+ return par.head_field
+end
+
+-- we could do active nodes differently ... table instead of linked list or a list
+-- with prev nodes
+
+local function deactivate_node(par,prev_prev_r,prev_r,r,cur_active_width,checked_expansion) -- no need for adjust if disabled
+ local active = par.active
+ local active_width = par.active_width
+ prev_r.next = r.next
+ -- removes r
+ -- r = nil
+ if prev_r == active then
+ r = active.next
+ if r.id == delta_code then
+ local aw = active_width.size + r.size active_width.size = aw cur_active_width.size = aw
+ local aw = active_width.stretch + r.stretch active_width.stretch = aw cur_active_width.stretch = aw
+ local aw = active_width.fi + r.fi active_width.fi = aw cur_active_width.fi = aw
+ local aw = active_width.fil + r.fil active_width.fil = aw cur_active_width.fil = aw
+ local aw = active_width.fill + r.fill active_width.fill = aw cur_active_width.fill = aw
+ local aw = active_width.filll + r.filll active_width.filll = aw cur_active_width.filll = aw
+ local aw = active_width.shrink + r.shrink active_width.shrink = aw cur_active_width.shrink = aw
+ if checked_expansion then
+ local aw = active_width.adjust_stretch + r.adjust_stretch active_width.adjust_stretch = aw cur_active_width.adjust_stretch = aw
+ local aw = active_width.adjust_shrink + r.adjust_shrink active_width.adjust_shrink = aw cur_active_width.adjust_shrink = aw
+ end
+ active.next = r.next
+ -- removes r
+ -- r = nil
+ end
+ elseif prev_r.id == delta_code then
+ r = prev_r.next
+ if r == active then
+ cur_active_width.size = cur_active_width.size - prev_r.size
+ cur_active_width.stretch = cur_active_width.stretch - prev_r.stretch
+ cur_active_width.fi = cur_active_width.fi - prev_r.fi
+ cur_active_width.fil = cur_active_width.fil - prev_r.fil
+ cur_active_width.fill = cur_active_width.fill - prev_r.fill
+ cur_active_width.filll = cur_active_width.filll - prev_r.filll
+ cur_active_width.shrink = cur_active_width.shrink - prev_r.shrink
+ if checked_expansion then
+ cur_active_width.adjust_stretch = cur_active_width.adjust_stretch - prev_r.adjust_stretch
+ cur_active_width.adjust_shrink = cur_active_width.adjust_shrink - prev_r.adjust_shrink
+ end
+ prev_prev_r.next = active
+ -- removes prev_r
+ -- prev_r = nil
+ prev_r = prev_prev_r
+ elseif r.id == delta_code then
+ local rn = r.size cur_active_width.size = cur_active_width.size + rn prev_r.size = prev_r.size + rn
+ local rn = r.stretch cur_active_width.stretch = cur_active_width.stretch + rn prev_r.stretch = prev_r.stretch + rn
+ local rn = r.fi cur_active_width.fi = cur_active_width.fi + rn prev_r.fi = prev_r.fi + rn
+ local rn = r.fil cur_active_width.fil = cur_active_width.fil + rn prev_r.fil = prev_r.fil + rn
+ local rn = r.fill cur_active_width.fill = cur_active_width.fill + rn prev_r.fill = prev_r.fill + rn
+ local rn = r.filll cur_active_width.filll = cur_active_width.filll + rn prev_r.filll = prev_r.fill + rn
+ local rn = r.shrink cur_active_width.shrink = cur_active_width.shrink + rn prev_r.shrink = prev_r.shrink + rn
+ if checked_expansion then
+ local rn = r.adjust_stretch cur_active_width.adjust_stretch = cur_active_width.adjust_stretch + rn prev_r.adjust_stretch = prev_r.adjust_stretch + rn
+ local rn = r.adjust_shrink cur_active_width.adjust_shrink = cur_active_width.adjust_shrink + rn prev_r.adjust_shrink = prev_r.adjust_shrink + rn
+ end
+ prev_r.next = r.next
+ -- removes r
+ -- r = nil
+ end
+ end
+ return prev_r, r
+end
+
+local function lastlinecrap(shortfall,active_short,active_glue,cur_active_width,fill_width,last_line_fit)
+ if active_short == 0 or active_glue <= 0 then
+ return false, 0, fit_decent_class, 0, 0
+ end
+ if cur_active_width.fi ~= fill_width.fi or cur_active_width.fil ~= fill_width.fil or cur_active_width.fill ~= fill_width.fill or cur_active_width.filll ~= fill_width.filll then
+ return false, 0, fit_decent_class, 0, 0
+ end
+ local adjustment = active_short > 0 and cur_active_width.stretch or cur_active_width.shrink
+ if adjustment <= 0 then
+ return false, 0, fit_decent_class, adjustment, 0
+ end
+ adjustment = calculate_fraction(adjustment,active_short,active_glue,maxdimen)
+ if last_line_fit < 1000 then
+ adjustment = calculate_fraction(adjustment,last_line_fit,1000,maxdimen) -- uses previous adjustment
+ end
+ local fit_class = fit_decent_class
+ if adjustment > 0 then
+ local stretch = cur_active_width.stretch
+ if adjustment > shortfall then
+ adjustment = shortfall
+ end
+ if adjustment > 7230584 and stretch < 1663497 then
+ return true, fit_very_loose_class, shortfall, adjustment, infinite_badness
+ end
+ -- if adjustment == 0 then -- badness = 0
+ -- return true, shortfall, fit_decent_class, 0, 0
+ -- elseif stretch <= 0 then -- badness = 10000
+ -- return true, shortfall, fit_very_loose_class, adjustment, 10000
+ -- end
+ -- local badness = (adjustment == 0 and 0) or (stretch <= 0 and 10000) or calculate_badness(adjustment,stretch)
+ local badness = calculate_badness(adjustment,stretch)
+ if badness > 99 then
+ return true, shortfall, fit_very_loose_class, adjustment, badness
+ elseif badness > 12 then
+ return true, shortfall, fit_loose_class, adjustment, badness
+ else
+ return true, shortfall, fit_decent_class, adjustment, badness
+ end
+ elseif adjustment < 0 then
+ local shrink = cur_active_width.shrink
+ if -adjustment > shrink then
+ adjustment = -shrink
+ end
+ local badness = calculate_badness(-adjustment,shrink)
+ if badness > 12 then
+ return true, shortfall, fit_tight_class, adjustment, badness
+ else
+ return true, shortfall, fit_decent_class, adjustment, badness
+ end
+ else
+ return false, 0, fit_decent_class, 0, 0
+ end
+end
+
+local function try_break(pi, break_type, par, first_p, cur_p, checked_expansion)
+
+ if pi >= infinite_penalty then
+ return -- this breakpoint is inhibited by infinite penalty
+ elseif pi <= -infinite_penalty then
+ pi = eject_penalty -- this breakpoint will be forced
+ end
+
+ local prev_prev_r = nil -- a step behind prev_r, if type(prev_r)=delta_code
+ local prev_r = par.active -- stays a step behind r
+ local r = nil -- runs through the active list
+ local no_break_yet = true -- have we found a feasible break at cur_p?
+ local node_r_stays_active = false -- should node r remain in the active list?
+ local line_width = 0 -- the current line will be justified to this width
+ local line_number = 0 -- line number of current active node
+ local old_line_number = 0 -- maximum line number in current equivalence class of lines
+
+ local protrude_chars = par.protrude_chars
+ local checked_expansion = par.checked_expansion
+ local break_width = par.break_width
+ local active_width = par.active_width
+ local background = par.background
+ local minimal_demerits = par.minimal_demerits
+ local best_place = par.best_place
+ local best_pl_line = par.best_pl_line
+ local best_pl_short = par.best_pl_short
+ local best_pl_glue = par.best_pl_glue
+ local do_last_line_fit = par.do_last_line_fit
+ local final_pass = par.final_pass
+ local tracing_paragraphs = par.tracing_paragraphs
+ -- local par_active = par.active
+
+ local cur_active_width = checked_expansion and { -- distance from current active node
+ size = active_width.size,
+ stretch = active_width.stretch,
+ fi = active_width.fi,
+ fil = active_width.fil,
+ fill = active_width.fill,
+ filll = active_width.filll,
+ shrink = active_width.shrink,
+ adjust_stretch = active_width.adjust_stretch,
+ adjust_shrink = active_width.adjust_shrink,
+ } or {
+ size = active_width.size,
+ stretch = active_width.stretch,
+ fi = active_width.fi,
+ fil = active_width.fil,
+ fill = active_width.fill,
+ filll = active_width.filll,
+ shrink = active_width.shrink,
+ }
+
+ while true do
+ r = prev_r.next
+ if r.id == delta_code then
+ cur_active_width.size = cur_active_width.size + r.size
+ cur_active_width.stretch = cur_active_width.stretch + r.stretch
+ cur_active_width.fi = cur_active_width.fi + r.fi
+ cur_active_width.fil = cur_active_width.fil + r.fil
+ cur_active_width.fill = cur_active_width.fill + r.fill
+ cur_active_width.filll = cur_active_width.filll + r.filll
+ cur_active_width.shrink = cur_active_width.shrink + r.shrink
+ if checked_expansion then
+ cur_active_width.adjust_stretch = cur_active_width.adjust_stretch + r.adjust_stretch
+ cur_active_width.adjust_shrink = cur_active_width.adjust_shrink + r.adjust_shrink
+ end
+ prev_prev_r = prev_r
+ prev_r = r
+ else
+ line_number = r.line_number
+ if line_number > old_line_number then
+ local minimum_demerits = par.minimum_demerits
+ if minimum_demerits < awful_badness and (old_line_number ~= par.easy_line or r == par.active) then
+ if no_break_yet then
+ no_break_yet = false
+ break_width.size = background.size
+ break_width.stretch = background.stretch
+ break_width.fi = background.fi
+ break_width.fil = background.fil
+ break_width.fill = background.fill
+ break_width.filll = background.filll
+ break_width.shrink = background.shrink
+ if checked_expansion then
+ break_width.adjust_stretch = 0
+ break_width.adjust_shrink = 0
+ end
+ if cur_p then
+ compute_break_width(par,break_type,cur_p)
+ end
+ end
+ if prev_r.id == delta_code then
+ prev_r.size = prev_r.size - cur_active_width.size + break_width.size
+ prev_r.stretch = prev_r.stretch - cur_active_width.stretc + break_width.stretch
+ prev_r.fi = prev_r.fi - cur_active_width.fi + break_width.fi
+ prev_r.fil = prev_r.fil - cur_active_width.fil + break_width.fil
+ prev_r.fill = prev_r.fill - cur_active_width.fill + break_width.fill
+ prev_r.filll = prev_r.filll - cur_active_width.filll + break_width.filll
+ prev_r.shrink = prev_r.shrink - cur_active_width.shrink + break_width.shrink
+ if checked_expansion then
+ prev_r.adjust_stretch = prev_r.adjust_stretch - cur_active_width.adjust_stretch + break_width.adjust_stretch
+ prev_r.adjust_shrink = prev_r.adjust_shrink - cur_active_width.adjust_shrink + break_width.adjust_shrink
+ end
+ elseif prev_r == par.active then
+ active_width.size = break_width.size
+ active_width.stretch = break_width.stretch
+ active_width.fi = break_width.fi
+ active_width.fil = break_width.fil
+ active_width.fill = break_width.fill
+ active_width.filll = break_width.filll
+ active_width.shrink = break_width.shrink
+ if checked_expansion then
+ active_width.adjust_stretch = break_width.adjust_stretch
+ active_width.adjust_shrink = break_width.adjust_shrink
+ end
+ else
+ local q = checked_expansion and {
+ id = delta_code,
+ subtype = nosubtype_code,
+ next = r,
+ size = break_width.size - cur_active_width.size,
+ stretch = break_width.stretch - cur_active_width.stretch,
+ fi = break_width.fi - cur_active_width.fi,
+ fil = break_width.fil - cur_active_width.fil,
+ fill = break_width.fill - cur_active_width.fill,
+ filll = break_width.filll - cur_active_width.filll,
+ shrink = break_width.shrink - cur_active_width.shrink,
+ adjust_stretch = break_width.adjust_stretch - cur_active_width.adjust_stretch,
+ adjust_shrink = break_width.adjust_shrink - cur_active_width.adjust_shrink,
+ } or {
+ id = delta_code,
+ subtype = nosubtype_code,
+ next = r,
+ size = break_width.size - cur_active_width.size,
+ stretch = break_width.stretch - cur_active_width.stretch,
+ fi = break_width.fi - cur_active_width.fi,
+ fil = break_width.fil - cur_active_width.fil,
+ fill = break_width.fill - cur_active_width.fill,
+ filll = break_width.filll - cur_active_width.filll,
+ shrink = break_width.shrink - cur_active_width.shrink,
+ }
+ prev_r.next = q
+ prev_prev_r = prev_r
+ prev_r = q
+ end
+ local adj_demerits = par.adj_demerits
+ local abs_adj_demerits = adj_demerits > 0 and adj_demerits or -adj_demerits
+ if abs_adj_demerits >= awful_badness - minimum_demerits then
+ minimum_demerits = awful_badness - 1
+ else
+ minimum_demerits = minimum_demerits + abs_adj_demerits
+ end
+ for fit_class = fit_very_loose_class, fit_tight_class do
+ if minimal_demerits[fit_class] <= minimum_demerits then
+ -- insert a new active node from best_place[fit_class] to cur_p
+ par.pass_number = par.pass_number + 1
+ local prev_break = best_place[fit_class]
+ local passive = {
+ id = passive_code,
+ subtype = nosubtype_code,
+ next = par.passive,
+ cur_break = cur_p,
+ serial = par.pass_number,
+ prev_break = prev_break,
+ passive_pen_inter = par.internal_pen_inter,
+ passive_pen_broken = par.internal_pen_broken,
+ passive_last_left_box = par.internal_left_box,
+ passive_last_left_box_width = par.internal_left_box_width,
+ passive_left_box = prev_break and prev_break.passive_last_left_box or par.init_internal_left_box,
+ passive_left_box_width = prev_break and prev_break.passive_last_left_box_width or par.init_internal_left_box_width,
+ passive_right_box = par.internal_right_box,
+ passive_right_box_width = par.internal_right_box_width,
+-- analysis = table.fastcopy(cur_active_width),
+ }
+ par.passive = passive
+ local q = {
+ id = break_type,
+ subtype = fit_class,
+ break_node = passive,
+ line_number = best_pl_line[fit_class] + 1,
+ total_demerits = minimal_demerits[fit_class], -- or 0,
+ next = r,
+ }
+ if do_last_line_fit then
+ local active_short = best_pl_short[fit_class]
+ local active_glue = best_pl_glue[fit_class]
+ q.active_short = active_short
+ q.active_glue = active_glue
+ if trace_lastlinefit then
+ report_parbuilders("setting short to %i and glue to %p using class %a",active_short,active_glue,fit_class)
+ end
+ end
+ -- q.next = r -- already done
+ prev_r.next = q
+ prev_r = q
+ if tracing_paragraphs then
+ diagnostics.break_node(par,q,fit_class,break_type,cur_p)
+ end
+ end
+ minimal_demerits[fit_class] = awful_badness
+ end
+ par.minimum_demerits = awful_badness
+ if r ~= par.active then
+ local q = checked_expansion and {
+ id = delta_code,
+ subtype = nosubtype_code,
+ next = r,
+ size = cur_active_width.size - break_width.size,
+ stretch = cur_active_width.stretch - break_width.stretch,
+ fi = cur_active_width.fi - break_width.fi,
+ fil = cur_active_width.fil - break_width.fil,
+ fill = cur_active_width.fill - break_width.fill,
+ filll = cur_active_width.filll - break_width.filll,
+ shrink = cur_active_width.shrink - break_width.shrink,
+ adjust_stretch = cur_active_width.adjust_stretch - break_width.adjust_stretch,
+ adjust_shrink = cur_active_width.adjust_shrink - break_width.adjust_shrink,
+ } or {
+ id = delta_code,
+ subtype = nosubtype_code,
+ next = r,
+ size = cur_active_width.size - break_width.size,
+ stretch = cur_active_width.stretch - break_width.stretch,
+ fi = cur_active_width.fi - break_width.fi,
+ fil = cur_active_width.fil - break_width.fil,
+ fill = cur_active_width.fill - break_width.fill,
+ filll = cur_active_width.filll - break_width.filll,
+ shrink = cur_active_width.shrink - break_width.shrink,
+ }
+ -- q.next = r -- already done
+ prev_r.next = q
+ prev_prev_r = prev_r
+ prev_r = q
+ end
+ end
+ if r == par.active then
+ return
+ end
+ if line_number > par.easy_line then
+ old_line_number = max_halfword - 1
+ line_width = par.second_width
+ else
+ old_line_number = line_number
+ if line_number > par.last_special_line then
+ line_width = par.second_width
+ elseif par.par_shape_ptr then
+ line_width = par.par_shape_ptr[line_number][2]
+ else
+ line_width = par.first_width
+ end
+ end
+ end
+ local artificial_demerits = false -- has d been forced to zero
+ local shortfall = line_width - cur_active_width.size - par.internal_right_box_width -- used in badness calculations
+ if not r.break_node then
+ shortfall = shortfall - par.init_internal_left_box_width
+ else
+ shortfall = shortfall - (r.break_node.passive_last_left_box_width or 0)
+ end
+ local pw, lp, rp -- used later on
+ if protrude_chars > 1 then
+ -- this is quite time consuming
+ local b = r.break_node
+ local l = b and b.cur_break or first_p
+ local o = cur_p and cur_p.prev
+ if cur_p and cur_p.id == disc_code and cur_p.pre then
+ o = slide_nodes(cur_p.pre)
+ else
+ o = find_protchar_right(l,o)
+ end
+ if o and o.id == glyph_code then
+ pw, rp = right_pw(o)
+ shortfall = shortfall + pw
+ end
+ local id = l.id
+ if id == glyph_code then
+ -- ok ?
+ elseif id == disc_code and l.post then
+ l = l.post -- TODO: first char could be a disc
+ else
+ l = find_protchar_left(l)
+ end
+ if l and l.id == glyph_code then
+ pw, lp = left_pw(l)
+ shortfall = shortfall + pw
+ end
+ end
+ if checked_expansion and shortfall ~= 0 then
+ local margin_kern_stretch = 0
+ local margin_kern_shrink = 0
+ if protrude_chars > 1 then
+ if lp then
+-- margin_kern_stretch, margin_kern_shrink = cal_margin_kern_var(lp)
+local data = expansions[lp.font][lp.char]
+if data then
+ margin_kern_stretch, margin_kern_shrink = data.glyphstretch, data.glyphshrink
+end
+ end
+ if rp then
+-- local mka, mkb = cal_margin_kern_var(rp)
+-- margin_kern_stretch = margin_kern_stretch + mka
+-- margin_kern_shrink = margin_kern_shrink + mkb
+local data = expansions[lp.font][lp.char]
+if data then
+ margin_kern_stretch = margin_kern_stretch + data.glyphstretch
+ margin_kern_shrink = margin_kern_shrink + data.glyphshrink
+end
+ end
+ end
+ local total = cur_active_width.adjust_stretch + margin_kern_stretch
+ if shortfall > 0 and total > 0 then
+ if total > shortfall then
+ shortfall = total / (par.max_stretch_ratio / par.cur_font_step) / 2
+ else
+ shortfall = shortfall - total
+ end
+ else
+ total = cur_active_width.adjust_shrink + margin_kern_shrink
+ if shortfall < 0 and total > 0 then
+ if total > - shortfall then
+ shortfall = - total / (par.max_shrink_ratio / par.cur_font_step) / 2
+ else
+ shortfall = shortfall + total
+ end
+ end
+ end
+ par.font_steps[line_number] = par.cur_font_step -- mine
+ else
+ par.font_steps[line_number] = 0 -- mine
+ end
+ local b = 0
+ local g = 0
+ local fit_class = fit_decent_class
+ local found = false
+ if shortfall > 0 then
+ if cur_active_width.fi ~= 0 or cur_active_width.fil ~= 0 or cur_active_width.fill ~= 0 or cur_active_width.filll ~= 0 then
+ if not do_last_line_fit then
+ -- okay
+ elseif not cur_p then
+ found, shortfall, fit_class, g, b = lastlinecrap(shortfall,r.active_short,r.active_glue,cur_active_width,par.fill_width,par.last_line_fit)
+ else
+ shortfall = 0
+ end
+ else
+ local stretch = cur_active_width.stretch
+ if shortfall > 7230584 and stretch < 1663497 then
+ b = infinite_badness
+ fit_class = fit_very_loose_class
+ else
+ b = calculate_badness(shortfall,stretch)
+ if b > 99 then
+ fit_class = fit_very_loose_class
+ elseif b > 12 then
+ fit_class = fit_loose_class
+ else
+ fit_class = fit_decent_class
+ end
+ end
+ end
+ else
+ local shrink = cur_active_width.shrink
+ if -shortfall > shrink then
+ b = infinite_badness + 1
+ else
+ b = calculate_badness(-shortfall,shrink)
+ end
+ if b > 12 then
+ fit_class = fit_tight_class
+ else
+ fit_class = fit_decent_class
+ end
+ end
+ if do_last_line_fit and not found then
+ if not cur_p then
+ -- g = 0
+ shortfall = 0
+ elseif shortfall > 0 then
+ g = cur_active_width.stretch
+ elseif shortfall < 0 then
+ g = cur_active_width.shrink
+ else
+ g = 0
+ end
+ end
+ -- ::FOUND::
+ local continue_only = false -- brrr
+ if b > infinite_badness or pi == eject_penalty then
+ if final_pass and par.minimum_demerits == awful_badness and r.next == par.active and prev_r == par.active then
+ artificial_demerits = true -- set demerits zero, this break is forced
+ node_r_stays_active = false
+ elseif b > par.threshold then
+ prev_r, r = deactivate_node(par,prev_prev_r,prev_r,r,cur_active_width,checked_expansion)
+ continue_only = true
+ else
+ node_r_stays_active = false
+ end
+ else
+ prev_r = r
+ if b > par.threshold then
+ continue_only = true
+ else
+ node_r_stays_active = true
+ end
+ end
+ if not continue_only then
+ local d = 0
+ if not artificial_demerits then
+ d = par.line_penalty + b
+ if (d >= 0 and d or -d) >= 10000 then -- abs(d)
+ d = 100000000
+ else
+ d = d * d
+ end
+ if pi == 0 then
+ -- nothing
+ elseif pi > 0 then
+ d = d + pi * pi
+ elseif pi > eject_penalty then
+ d = d - pi * pi
+ end
+ if break_type == hyphenated_code and r.id == hyphenated_code then
+ if cur_p then
+ d = d + par.double_hyphen_demerits
+ else
+ d = d + par.final_hyphen_demerits
+ end
+ end
+ local delta = fit_class - r.subtype
+ if (delta >= 0 and delta or -delta) > 1 then -- abs(delta)
+ d = d + par.adj_demerits
+ end
+ end
+ if tracing_paragraphs then
+ diagnostics.feasible_break(par,cur_p,r,b,pi,d,artificial_demerits)
+ end
+ d = d + r.total_demerits -- this is the minimum total demerits from the beginning to cur_p via r
+ if d <= minimal_demerits[fit_class] then
+ minimal_demerits[fit_class] = d
+ best_place [fit_class] = r.break_node
+ best_pl_line [fit_class] = line_number
+ if do_last_line_fit then
+ best_pl_short[fit_class] = shortfall
+ best_pl_glue [fit_class] = g
+ if trace_lastlinefit then
+ report_parbuilders("storing last line fit short %a and glue %p in class %a",shortfall,g,fit_class)
+ end
+ end
+ if d < par.minimum_demerits then
+ par.minimum_demerits = d
+ end
+ end
+ if not node_r_stays_active then
+ prev_r, r = deactivate_node(par,prev_prev_r,prev_r,r,cur_active_width,checked_expansion)
+ end
+ end
+ end
+ end
+end
+
+local function kern_break(par, cur_p, first_p, checked_expansion) -- move inline if needed
+ local v = cur_p.next
+ if par.auto_breaking and v.id == glue_code then
+ try_break(0, unhyphenated_code, par, first_p, cur_p, checked_expansion)
+ end
+ local active_width = par.active_width
+ if cur_p.id ~= math_code then
+ active_width.size = active_width.size + cur_p.kern
+ else
+ active_width.size = active_width.size + cur_p.surround
+ end
+end
+
+-- we can call the normal one for simple box building in the otr so we need
+-- frequent enabling/disabling
+
+local temp_head = new_temp()
+
+function constructors.methods.basic(head,d)
+ if trace_basic then
+ report_parbuilders("starting at %a",head)
+ end
+
+ local par = initialize_line_break(head,d)
+
+ local checked_expansion = par.checked_expansion
+ local active_width = par.active_width
+ local disc_width = par.disc_width
+ local background = par.background
+ local tracing_paragraphs = par.tracing_paragraphs
+
+ local dirstack = new_dir_stack()
+
+ if tracing_paragraphs then
+ diagnostics.start()
+ if par.pretolerance >= 0 then
+ diagnostics.current_pass(par,"firstpass")
+ end
+ end
+
+ while true do
+ reset_meta(par)
+ if par.threshold > infinite_badness then
+ par.threshold = infinite_badness
+ end
+ par.active.next = {
+ id = unhyphenated_code,
+ subtype = fit_decent_class,
+ next = par.active,
+ break_node = nil,
+ line_number = par.first_line + 1,
+ total_demerits = 0,
+ active_short = 0,
+ active_glue = 0,
+ }
+ active_width.size = background.size
+ active_width.stretch = background.stretch
+ active_width.fi = background.fi
+ active_width.fil = background.fil
+ active_width.fill = background.fill
+ active_width.filll = background.filll
+ active_width.shrink = background.shrink
+
+ if checked_expansion then
+ active_width.adjust_stretch = 0
+ active_width.adjust_shrink = 0
+ end
+
+ par.passive = nil -- = 0
+ par.printed_node = temp_head -- only when tracing, shared
+ par.printed_node.next = head
+ par.pass_number = 0
+ par.auto_breaking = true
+
+ local cur_p = head
+ local first_p = cur_p
+
+ par.font_in_short_display = 0
+
+ if cur_p and cur_p.id == whatsit_code and cur_p.subtype == localpar_code then
+ par.init_internal_left_box = cur_p.box_left
+ par.init_internal_left_box_width = cur_p.box_left_width
+ par.internal_pen_inter = cur_p.pen_inter
+ par.internal_pen_broken = cur_p.pen_broken
+ par.internal_left_box = par.init_internal_left_box
+ par.internal_left_box_width = par.init_internal_left_box_width
+ par.internal_right_box = cur_p.box_right
+ par.internal_right_box_width = cur_p.box_right_width
+ end
+
+ -- all passes are combined in this loop so maybe we should split this into
+ -- three function calls; we then also need to do the wrap_up elsewhere
+
+ -- split into normal and expansion loop
+
+ -- use an active local
+
+ local fontexp, lastfont -- we can pass fontexp to calculate width if needed
+
+ while cur_p and par.active.next ~= par.active do
+ while cur_p and cur_p.id == glyph_code do
+ if is_rotated[par.line_break_dir] then
+ active_width.size = active_width.size + cur_p.height + cur_p.depth
+ else
+ active_width.size = active_width.size + cur_p.width
+ end
+ if checked_expansion then
+ local data= checked_expansion[cur_p.font]
+ if data then
+ local currentfont = cur_p.font
+ if currentfont ~= lastfont then
+ fontexps = checked_expansion[currentfont] -- a bit redundant for the par line packer
+ lastfont = currentfont
+ end
+ if fontexps then
+ local expansion = fontexps[cur_p.char]
+ if expansion then
+ active_width.adjust_stretch = active_width.adjust_stretch + expansion.glyphstretch
+ active_width.adjust_shrink = active_width.adjust_shrink + expansion.glyphshrink
+ end
+ end
+ end
+ end
+ cur_p = cur_p.next
+ end
+ if not cur_p then -- TODO
+ report_parbuilders("problems with linebreak_tail")
+ os.exit()
+ end
+ local id = cur_p.id
+ if id == hlist_code or id == vlist_code then
+ if is_parallel[cur_p.dir][par.line_break_dir] then
+ active_width.size = active_width.size + cur_p.width
+ else
+ active_width.size = active_width.size + cur_p.depth + cur_p.height
+ end
+ elseif id == glue_code then
+ if par.auto_breaking then
+ local prev_p = cur_p.prev
+ if prev_p and prev_p ~= temp_head then
+ local id = prev_p.id
+ if id == glyph_code or
+ (id < math_code and (id ~= whatsit_code or prev_p.subtype ~= dir_code)) or -- was: precedes_break(prev_p)
+ (id == kern_code and prev_p.subtype ~= userkern_code) then
+ try_break(0, unhyphenated_code, par, first_p, cur_p, checked_expansion)
+ end
+ end
+ end
+ local spec = check_shrinkage(par,cur_p.spec)
+ local order = stretch_orders[spec.stretch_order]
+ cur_p.spec = spec
+ active_width.size = active_width.size + spec.width
+ active_width[order] = active_width[order] + spec.stretch
+ active_width.shrink = active_width.shrink + spec.shrink
+ elseif id == disc_code then
+ local subtype = cur_p.subtype
+ if subtype ~= second_disc_code then -- are there still second_disc_code in luatex
+ local line_break_dir = par.line_break_dir
+ if par.second_pass then -- todo: make second pass local
+ local actual_pen = subtype == automatic_disc_code and par.ex_hyphen_penalty or par.hyphen_penalty
+ local pre = cur_p.pre
+ if not pre then -- trivial pre-break
+ disc_width.size = 0
+ if checked_expansion then
+ disc_width.adjust_stretch = 0
+ disc_width.adjust_shrink = 0
+ end
+ try_break(actual_pen, hyphenated_code, par, first_p, cur_p, checked_expansion)
+ else
+ local size, adjust_stretch, adjust_shrink = add_to_width(line_break_dir,checked_expansion,pre)
+ disc_width.size = size
+ active_width.size = active_width.size + size
+ if checked_expansion then
+ disc_width.adjust_stretch = adjust_stretch
+ disc_width.adjust_shrink = adjust_shrink
+ active_width.adjust_stretch = active_width.adjust_stretch + adjust_stretch
+ active_width.adjust_shrink = active_width.adjust_shrink + adjust_shrink
+ else
+ -- disc_width.adjust_stretch = 0
+ -- disc_width.adjust_shrink = 0
+ end
+ try_break(actual_pen, hyphenated_code, par, first_p, cur_p, checked_expansion)
+ if subtype == first_disc_code then
+ local cur_p_next = cur_p.next
+ if cur_p_next.id ~= disc_code or cur_p_next.subtype ~= second_disc_code then
+ report_parbuilders("unsupported disc at location %a",1)
+ else
+ local pre = cur_p_next.pre
+ if pre then
+ local size, adjust_stretch, adjust_shrink = add_to_width(line_break_dir,checked_expansion,pre)
+ disc_width.size = disc_width.size + size
+ if checked_expansion then
+ disc_width.adjust_stretch = disc_width.adjust_stretch + adjust_stretch
+ disc_width.adjust_shrink = disc_width.adjust_shrink + adjust_shrink
+ end
+ try_break(actual_pen, hyphenated_code, par, first_p, cur_p_next, checked_expansion)
+ --
+ -- I will look into this some day ... comment in linebreak.w says that this fails,
+ -- maybe this is what Taco means with his comment in the luatex manual.
+ --
+ -- do_one_seven_eight(sub_disc_width_from_active_width);
+ -- do_one_seven_eight(reset_disc_width);
+ -- s = vlink_no_break(vlink(cur_p));
+ -- add_to_widths(s, line_break_dir, pdf_adjust_spacing,disc_width);
+ -- ext_try_break(...,first_p,vlink(cur_p));
+ --
+ else
+ report_parbuilders("unsupported disc at location %a",2)
+ end
+ end
+ end
+ -- beware, we cannot restore to a saved value as the try_break adapts active_width
+ active_width.size = active_width.size - disc_width.size
+ if checked_expansion then
+ active_width.adjust_stretch = active_width.adjust_stretch - disc_width.adjust_stretch
+ active_width.adjust_shrink = active_width.adjust_shrink - disc_width.adjust_shrink
+ end
+ end
+ end
+ local replace = cur_p.replace
+ if replace then
+ local size, adjust_stretch, adjust_shrink = add_to_width(line_break_dir,checked_expansion,replace)
+ active_width.size = active_width.size + size
+ if checked_expansion then
+ active_width.adjust_stretch = active_width.adjust_stretch + adjust_stretch
+ active_width.adjust_shrink = active_width.adjust_shrink + adjust_shrink
+ end
+ end
+ end
+ elseif id == kern_code then
+ if cur_p.subtype == userkern_code then
+ kern_break(par,cur_p,first_p, checked_expansion)
+ else
+ active_width.size = active_width.size + cur_p.kern
+ if checked_expansion and cur_p.subtype == kerning_code then
+ local d = cur_p.kern
+ if d ~= 0 then
+ local stretch, shrink = kern_stretch_shrink(cur_p,d)
+ active_width.adjust_stretch = active_width.adjust_stretch + stretch
+ active_width.adjust_shrink = active_width.adjust_shrink + shrink
+ end
+ end
+ end
+ elseif id == math_code then
+ par.auto_breaking = cur_p.subtype == endmath_code
+ kern_break(par,cur_p, first_p, checked_expansion)
+ elseif id == rule_code then
+ active_width.size = active_width.size + cur_p.width
+ elseif id == penalty_code then
+ try_break(cur_p.penalty, unhyphenated_code, par, first_p, cur_p, checked_expansion)
+ elseif id == whatsit_code then
+ local subtype = cur_p.subtype
+ if subtype == localpar_code then
+ par.internal_pen_inter = cur_p.pen_inter
+ par.internal_pen_broken = cur_p.pen_broken
+ par.internal_left_box = cur_p.box_left
+ par.internal_left_box_width = cur_p.box_left_width
+ par.internal_right_box = cur_p.box_right
+ par.internal_right_box_width = cur_p.box_right_width
+ elseif subtype == dir_code then
+ par.line_break_dir = checked_line_dir(dirstack) or par.line_break_dir
+ else
+ local get_width = get_whatsit_width[subtype]
+ if get_width then
+ active_width.size = active_width.size + get_width(cur_p)
+ end
+ end
+ elseif id == mark_code or id == ins_code or id == adjust_code then
+ -- skip
+ else
+ report_parbuilders("node of type %a found in paragraph",type(id))
+ end
+ cur_p = cur_p.next
+ end
+ if not cur_p then
+ try_break(eject_penalty, hyphenated_code, par, first_p, cur_p, checked_expansion)
+ local p_active = par.active
+ local n_active = p_active.next
+ if n_active ~= p_active then
+ local r = n_active
+ par.fewest_demerits = awful_badness
+ repeat -- use local d
+ if r.id ~= delta_code and r.total_demerits < par.fewest_demerits then
+ par.fewest_demerits = r.total_demerits
+ par.best_bet = r
+ end
+ r = r.next
+ until r == p_active
+ par.best_line = par.best_bet.line_number
+ local asked_looseness = par.looseness
+ if asked_looseness == 0 then
+ return wrap_up(par)
+ end
+ local r = n_active
+ local actual_looseness = 0
+ -- minimize assignments to par but happens seldom
+ repeat
+ if r.id ~= delta_code then
+ local line_diff = r.line_number - par.best_line
+ par.line_diff = line_diff
+ if (line_diff < actual_looseness and asked_looseness <= line_diff) or
+ (line_diff > actual_looseness and asked_looseness >= line_diff) then
+ par.best_bet = r
+ actual_looseness = line_diff
+ par.fewest_demerits = r.total_demerits
+ elseif line_diff == actual_looseness and r.total_demerits < par.fewest_demerits then
+ par.best_bet = r
+ par.fewest_demerits = r.total_demerits
+ end
+ end
+ r = r.next
+ until r == p_active -- weird, loop list?
+ par.best_line = par.best_bet.line_number
+ if actual_looseness == asked_looseness or par.final_pass then
+ return wrap_up(par)
+ end
+ end
+ end
+ reset_meta(par) -- clean up the memory by removing the break nodes
+ if not par.second_pass then
+ if tracing_paragraphs then
+ diagnostics.current_pass(par,"secondpass")
+ end
+ par.threshold = par.tolerance
+ par.second_pass = true
+ par.final_pass = par.emergency_stretch <= 0
+ else
+ if tracing_paragraphs then
+ diagnostics.current_pass(par,"emergencypass")
+ end
+ par.background.stretch = par.background.stretch + par.emergency_stretch
+ par.final_pass = true
+ end
+ end
+ return wrap_up(par)
+end
+
+-- standard tex logging .. will be adapted .. missing font names and to many []
+
+local function write_esc(cs)
+ local esc = tex.escapechar
+ if esc then
+ write("log",utfchar(esc),cs)
+ else
+ write("log",cs)
+ end
+end
+
+function diagnostics.start()
+end
+
+function diagnostics.stop()
+ write_nl("log",'')
+end
+
+function diagnostics.current_pass(par,what)
+ write_nl("log",format("@%s",what))
+end
+
+local function short_display(a,font_in_short_display)
+ while a do
+ local id = a.id
+ if id == glyph_code then
+ local font = a.font
+ if font ~= font_in_short_display then
+ write("log",tex.fontidentifier(font) .. ' ')
+ font_in_short_display = font
+ end
+ if a.subtype == ligature_code then
+ font_in_short_display = short_display(a.components,font_in_short_display)
+ else
+ write("log",utfchar(a.char))
+ end
+-- elseif id == rule_code then
+-- write("log","|")
+-- elseif id == glue_code then
+-- if a.spec.writable then
+-- write("log"," ")
+-- end
+-- elseif id == math_code then
+-- write("log","$")
+ elseif id == disc_code then
+ font_in_short_display = short_display(a.pre,font_in_short_display)
+ font_in_short_display = short_display(a.post,font_in_short_display)
+ else -- no explicit checking
+ write("log",format("[%s]",nodecodes[id]))
+ end
+ a = a.next
+ end
+ return font_in_short_display
+end
+
+diagnostics.short_display = short_display
+
+function diagnostics.break_node(par, q, fit_class, break_type, cur_p) -- %d ?
+ local passive = par.passive
+ local typ_ind = break_type == hyphenated_code and '-' or ""
+ if par.do_last_line_fit then
+ local s = number.toscaled(q.active_short)
+ local g = number.toscaled(q.active_glue)
+ if cur_p then
+ write_nl("log",format("@@%d: line %d.%d%s t=%s s=%s g=%s",
+ passive.serial or 0,q.line_number-1,fit_class,typ_ind,q.total_demerits,s,g))
+ else
+ write_nl("log",format("@@%d: line %d.%d%s t=%s s=%s a=%s",
+ passive.serial or 0,q.line_number-1,fit_class,typ_ind,q.total_demerits,s,g))
+ end
+ else
+ write_nl("log",format("@@%d: line %d.%d%s t=%s",
+ passive.serial or 0,q.line_number-1,fit_class,typ_ind,q.total_demerits))
+ end
+ if not passive.prev_break then
+ write("log"," -> @0")
+ else
+ write("log",format(" -> @%d", passive.prev_break.serial or 0))
+ end
+end
+
+function diagnostics.feasible_break(par, cur_p, r, b, pi, d, artificial_demerits)
+ local printed_node = par.printed_node
+ if printed_node ~= cur_p then
+ write_nl("log","")
+ if not cur_p then
+ par.font_in_short_display = short_display(printed_node.next,par.font_in_short_display)
+ else
+ local save_link = cur_p.next
+ cur_p.next = nil
+ write_nl("log","")
+ par.font_in_short_display = short_display(printed_node.next,par.font_in_short_display)
+ cur_p.next = save_link
+ end
+ par.printed_node = cur_p
+ end
+ write_nl("log","@")
+ if not cur_p then
+ write_esc("par")
+ else
+ local id = cur_p.id
+ if id == glue_code then
+ -- print nothing
+ elseif id == penalty_code then
+ write_esc("penalty")
+ elseif id == disc_code then
+ write_esc("discretionary")
+ elseif id == kern_code then
+ write_esc("kern")
+ elseif id == math_code then
+ write_esc("math")
+ else
+ write_esc("unknown")
+ end
+ end
+ local via, badness, demerits = 0, '*', '*'
+ if r.break_node then
+ via = r.break_node.serial or 0
+ end
+ if b <= infinite_badness then
+ badness = tonumber(d) -- format("%d", b)
+ end
+ if not artificial_demerits then
+ demerits = tonumber(d) -- format("%d", d)
+ end
+ write("log",format(" via @%d b=%s p=%s d=%s", via, badness, pi, demerits))
+end
+
+-- reporting --
+
+statistics.register("alternative parbuilders", function()
+ if nofpars > 0 then
+ return format("%s paragraphs, %s lines (%s protruded, %s adjusted)", nofpars, noflines, nofprotrudedlines, nofadjustedlines)
+ end
+end)
+
+-- actually scaling kerns is not such a good idea and it will become
+-- configureable
+
+-- This is no way a replacement for the built in (fast) packer
+-- it's just an alternative for special (testing) purposes.
+--
+-- We could use two hpacks: one to be used in the par builder
+-- and one to be used for other purposes. The one in the par
+-- builder is much more simple as it does not need the expansion
+-- code but only need to register the effective expansion factor
+-- with the glyph.
+
+local function glyph_width_height_depth(curdir,pdir,p)
+ if is_rotated[curdir] then
+ if is_parallel[curdir][pdir] then
+ local half = (p.height + p.depth) / 2
+ return p.width, half, half
+ else
+ local half = p.width / 2
+ return p.height + p.depth, half, half
+ end
+ elseif is_rotated[pdir] then
+ if is_parallel[curdir][pdir] then
+ local half = (p.height + p.depth) / 2
+ return p.width, half, half
+ else
+ return p.height + p.depth, p.width, 0 -- weird
+ end
+ else
+ if glyphdir_is_equal[curdir][pdir] then
+ return p.width, p.height, p.depth
+ elseif is_opposite[curdir][pdir] then
+ return p.width, p.depth, p.height
+ else -- can this happen?
+ return p.height + p.depth, p.width, 0 -- weird
+ end
+ end
+end
+
+local function pack_width_height_depth(curdir,pdir,p)
+ if is_rotated[curdir] then
+ if is_parallel[curdir][pdir] then
+ local half = (p.height + p.depth) / 2
+ return p.width, half, half
+ else -- can this happen?
+ local half = p.width / 2
+ return p.height + p.depth, half, half
+ end
+ else
+ if pardir_is_equal[curdir][pdir] then
+ return p.width, p.height, p.depth
+ elseif is_opposite[curdir][pdir] then
+ return p.width, p.depth, p.height
+ else -- weird dimensions, can this happen?
+ -- return p.width, p.depth, p.height
+ return p.height + p.depth, p.width, 0
+ end
+ end
+end
+
+-- local function xpack(head,width,method,direction,analysis)
+--
+-- -- inspect(analysis)
+--
+-- local expansion = method == "cal_expand_ratio"
+-- local natural = analysis.size
+-- local font_stretch = analysis.adjust_stretch
+-- local font_shrink = analysis.adjust_shrink
+-- local font_expand_ratio = 0
+-- local delta = width - natural
+--
+-- local hlist = new_node("hlist")
+--
+-- hlist.list = head
+-- hlist.dir = direction or tex.textdir
+-- hlist.width = width
+-- hlist.height = height
+-- hlist.depth = depth
+--
+-- if delta == 0 then
+--
+-- hlist.glue_sign = 0
+-- hlist.glue_order = 0
+-- hlist.glue_set = 0
+--
+-- else
+--
+-- local order = analysis.filll ~= 0 and fillcodes.filll or
+-- analysis.fill ~= 0 and fillcodes.fill or
+-- analysis.fil ~= 0 and fillcodes.fil or
+-- analysis.fi ~= 0 and fillcodes.fi or 0
+--
+-- if delta > 0 then
+--
+-- if expansion and order == 0 and font_stretch > 0 then
+-- font_expand_ratio = (delta/font_stretch) * 1000
+-- else
+-- local stretch = analysis.stretch
+-- if stretch ~= 0 then
+-- hlist.glue_sign = 1 -- stretch
+-- hlist.glue_order = order
+-- hlist.glue_set = delta/stretch
+-- else
+-- hlist.glue_sign = 0 -- nothing
+-- hlist.glue_order = order
+-- hlist.glue_set = 0
+-- end
+-- end
+-- print("stretch",hlist.glue_sign,hlist.glue_order,hlist.glue_set)
+--
+-- else
+--
+-- if expansion and order == 0 and font_shrink > 0 then
+-- font_expand_ratio = (delta/font_shrink) * 1000
+-- else
+-- local shrink = analysis.shrink
+-- if shrink ~= 0 then
+-- hlist.glue_sign = 2 -- shrink
+-- hlist.glue_order = order
+-- hlist.glue_set = - delta/shrink
+-- else
+-- hlist.glue_sign = 0 -- nothing
+-- hlist.glue_order = order
+-- hlist.glue_set = 0
+-- end
+-- end
+-- print("shrink",hlist.glue_sign,hlist.glue_order,hlist.glue_set)
+--
+-- end
+--
+-- end
+--
+-- if not expansion or font_expand_ratio == 0 then
+-- -- nothing
+-- elseif font_expand_ratio > 0 then
+-- if font_expand_ratio > 1000 then
+-- font_expand_ratio = 1000
+-- end
+-- local current = head
+-- while current do
+-- local id = current.id
+-- if id == glyph_code then
+-- local stretch, shrink = char_stretch_shrink(current) -- get only one
+-- if stretch then
+-- if trace_expansion then
+-- setnodecolor(g,"hz:positive")
+-- end
+-- current.expansion_factor = font_expand_ratio * stretch
+-- end
+-- elseif id == kern_code then
+-- local kern = current.kern
+-- if kern ~= 0 and current.subtype == kerning_code then
+-- current.kern = font_expand_ratio * current.kern
+-- end
+-- end
+-- current = current.next
+-- end
+-- elseif font_expand_ratio < 0 then
+-- if font_expand_ratio < -1000 then
+-- font_expand_ratio = -1000
+-- end
+-- local current = head
+-- while current do
+-- local id = current.id
+-- if id == glyph_code then
+-- local stretch, shrink = char_stretch_shrink(current) -- get only one
+-- if shrink then
+-- if trace_expansion then
+-- setnodecolor(g,"hz:negative")
+-- end
+-- current.expansion_factor = font_expand_ratio * shrink
+-- end
+-- elseif id == kern_code then
+-- local kern = current.kern
+-- if kern ~= 0 and current.subtype == kerning_code then
+-- current.kern = font_expand_ratio * current.kern
+-- end
+-- end
+-- current = current.next
+-- end
+-- end
+-- return hlist, 0
+-- end
+
+-- local expansion_stack = { } -- no dealloc
+
+local function hpack(head,width,method,direction) -- fast version when head = nil
+
+ -- we can pass the adjust_width and adjust_height so that we don't need to recalculate them but
+ -- with the glue mess it's less trivial as we lack detail
+
+ local hlist = new_node("hlist")
+
+ if head == nil then
+ return hlist, 0
+ end
+
+ local cal_expand_ratio = method == "cal_expand_ratio" -- "subst_ex_font" -- is gone
+
+ direction = direction or tex.textdir
+
+ local pack_begin_line = 0
+ local line = 0
+
+ local height = 0
+ local depth = 0
+ local natural = 0
+ local font_stretch = 0
+ local font_shrink = 0
+ local font_expand_ratio = 0
+ local last_badness = 0
+ local disc_stack = { }
+ local disc_level = 0
+ local expansion_stack = cal_expand_ratio and { } -- todo: optionally pass this
+ local expansion_index = 0
+ local total_stretch = { [0] = 0, 0, 0, 0, 0 }
+ local total_shrink = { [0] = 0, 0, 0, 0, 0 }
+
+ local hpack_dir = direction
+
+ local adjust_head = texlists.adjust_head
+ local pre_adjust_head = texlists.pre_adjust_head
+ local adjust_tail = adjust_head and slide_nodes(adjust_head)
+ local pre_adjust_tail = pre_adjust_head and slide_nodes(pre_adjust_head)
+
+ hlist.list = head
+ hlist.dir = hpack_dir
+
+ new_dir_stack(hpack_dir)
+
+ local checked_expansion = false
+
+ if cal_expand_ratio then
+ checked_expansion = { }
+ setmetatableindex(checked_expansion,check_expand_lines)
+ end
+
+ -- this one also needs to check the font, so in the end indeed we might end up with two variants
+
+ local fontexps, lastfont
+
+ local current = head
+
+ while current do
+ local id = current.id
+ if id == glyph_code then
+ if cal_expand_ratio then
+ local currentfont = current.font
+ if currentfont ~= lastfont then
+ fontexps = checked_expansion[currentfont] -- a bit redundant for the par line packer
+ lastfont = currentfont
+ end
+ if fontexps then
+ local expansion = fontexps[current.char]
+ if expansion then
+ font_stretch = font_stretch + expansion.glyphstretch
+ font_shrink = font_shrink + expansion.glyphshrink
+ expansion_index = expansion_index + 1
+ expansion_stack[expansion_index] = current
+ end
+ end
+ end
+ -- use inline if no expansion
+ local wd, ht, dp = glyph_width_height_depth(hpack_dir,"TLT",current) -- was TRT ?
+ natural = natural + wd
+ if ht > height then
+ height = ht
+ end
+ if dp > depth then
+ depth = dp
+ end
+ current = current.next
+ elseif id == kern_code then
+ local kern = current.kern
+ if kern == 0 then
+ -- no kern
+ elseif current.subtype == kerning_code then -- check p.kern
+ if cal_expand_ratio then
+ local stretch, shrink = kern_stretch_shrink(current,kern)
+ font_stretch = font_stretch + stretch
+ font_shrink = font_shrink + shrink
+ expansion_index = expansion_index + 1
+ expansion_stack[expansion_index] = current
+ end
+ natural = natural + kern
+ else
+ natural = natural + kern
+ end
+ current = current.next
+ elseif id == disc_code then
+ if current.subtype ~= second_disc_code then
+ -- todo : local stretch, shrink = char_stretch_shrink(s)
+ local replace = current.replace
+ if replace then
+ disc_level = disc_level + 1
+ disc_stack[disc_level] = current.next
+ current = replace
+ else
+ current = current.next
+ end
+ else
+ -- -- pre post replace
+ --
+ -- local stretch, shrink = char_stretch_shrink(current.pre)
+ -- font_stretch = font_stretch + stretch
+ -- font_shrink = font_shrink + shrink
+ -- expansion_index = expansion_index + 1
+ -- expansion_stack[expansion_index] = current.pre
+ --
+ current = current.next
+ end
+ elseif id == glue_code then
+ local spec = current.spec
+ natural = natural + spec.width
+ local op = spec.stretch_order
+ local om = spec.shrink_order
+ total_stretch[op] = total_stretch[op] + spec.stretch
+ total_shrink [om] = total_shrink [om] + spec.shrink
+ if current.subtype >= leaders_code then
+ local leader = current.leader
+ local ht = leader.height
+ local dp = leader.depth
+ if ht > height then
+ height = ht
+ end
+ if dp > depth then
+ depth = dp
+ end
+ end
+ current = current.next
+ elseif id == hlist_code or id == vlist_code then
+ local sh = current.shift
+ local wd, ht, dp = pack_width_height_depth(hpack_dir,current.dir or hpack_dir,current) -- added: or pack_dir
+ local hs, ds = ht - sh, dp + sh
+ natural = natural + wd
+ if hs > height then
+ height = hs
+ end
+ if ds > depth then
+ depth = ds
+ end
+ current = current.next
+ elseif id == rule_code then
+ local wd = current.width
+ local ht = current.height
+ local dp = current.depth
+ natural = natural + wd
+ if ht > height then
+ height = ht
+ end
+ if dp > depth then
+ depth = dp
+ end
+ current = current.next
+ elseif id == math_code then
+ natural = natural + current.surround
+ current = current.next
+ elseif id == unset_code then
+ local wd = current.width
+ local ht = current.height
+ local dp = current.depth
+ local sh = current.shift
+ local hs = ht - sh
+ local ds = dp + sh
+ natural = natural + wd
+ if hs > height then
+ height = hs
+ end
+ if ds > depth then
+ depth = ds
+ end
+ current = current.next
+ elseif id == ins_code or id == mark_code then
+ local prev = current.prev
+ local next = current.next
+ if adjust_tail then -- todo
+ if next then
+ next.prev = prev
+ end
+ if prev then
+ prev.next = next
+ end
+ current.prev = adjust_tail
+ current.next = nil
+ adjust_tail.next = current
+ adjust_tail = current
+ else
+ adjust_head = current
+ adjust_tail = current
+ current.prev = nil
+ current.next = nil
+ end
+ current = next
+ elseif id == adjust_code then
+ local list = current.list
+ if adjust_tail then
+ adjust_tail.next = list
+ adjust_tail = slide_nodes(list)
+ else
+ adjust_head = list
+ adjust_tail = slide_nodes(list)
+ end
+ current = current.next
+ elseif id == whatsit_code then
+ local subtype = current.subtype
+ if subtype == dir_code then
+ hpack_dir = checked_line_dir(stack,current) or hpack_dir
+ else
+ local get_dimensions = get_whatsit_dimensions[subtype]
+ if get_dimensions then
+ local wd, ht, dp = get_dimensions(current)
+ natural = natural + wd
+ if ht > height then
+ height = ht
+ end
+ if dp > depth then
+ depth = dp
+ end
+ end
+ end
+ current = current.next
+ elseif id == marginkern_code then
+ if cal_expand_ratio then
+ local glyph = current.glyph
+ local char_pw = current.subtype == leftmargin_code and left_pw or right_pw
+ font_stretch = font_stretch - current.width - char_pw(glyph)
+ font_shrink = font_shrink - current.width - char_pw(glyph)
+ expansion_index = expansion_index + 1
+ expansion_stack[expansion_index] = glyph
+ end
+ natural = natural + current.width
+ current = current.next
+ else
+ current = current.next
+ end
+ if not current and disc_level > 0 then
+ current = disc_stack[disc_level]
+ disc_level = disc_level - 1
+ end
+ end
+ if adjust_tail then
+ adjust_tail.next = nil -- todo
+ end
+ if pre_adjust_tail then
+ pre_adjust_tail.next = nil -- todo
+ end
+ if mode == "additional" then
+ width = width + natural
+ end
+ hlist.width = width
+ hlist.height = height
+ hlist.depth = depth
+ local delta = width - natural
+ if delta == 0 then
+ hlist.glue_sign = 0
+ hlist.glue_order = 0
+ hlist.glue_set = 0
+ elseif delta > 0 then
+ local order = (total_stretch[4] ~= 0 and 4 or total_stretch[3] ~= 0 and 3) or
+ (total_stretch[2] ~= 0 and 2 or total_stretch[1] ~= 0 and 1) or 0
+ if cal_expand_ratio and order == 0 and font_stretch > 0 then
+ font_expand_ratio = (delta/font_stretch) * 1000 -- round(delta/font_stretch * 1000)
+ else
+ local tso = total_stretch[order]
+ if tso ~= 0 then
+ hlist.glue_sign = 1
+ hlist.glue_order = order
+ hlist.glue_set = delta/tso
+ else
+ hlist.glue_sign = 0
+ hlist.glue_order = order
+ hlist.glue_set = 0
+ end
+-- print("stretch",hlist.glue_sign,hlist.glue_order,hlist.glue_set)
+ if order == 0 and hlist.list then
+ last_badness = calculate_badness(delta,total_stretch[0])
+ if last_badness > tex.hbadness then
+ if last_badness > 100 then
+ diagnostics.underfull_hbox(hlist,pack_begin_line,line,last_badness)
+ else
+ diagnostics.loose_hbox(hlist,pack_begin_line,line,last_badness)
+ end
+ end
+ end
+ end
+ else
+ local order = total_shrink[4] ~= 0 and 4 or total_shrink[3] ~= 0 and 3
+ or total_shrink[2] ~= 0 and 2 or total_shrink[1] ~= 0 and 1 or 0
+ if cal_expand_ratio and order == 0 and font_shrink > 0 then
+ font_expand_ratio = (delta/font_shrink) * 1000 -- round(delta/font_shrink * 1000)
+ else -- why was this else commented
+ local tso = total_shrink[order]
+ if tso ~= 0 then
+ hlist.glue_sign = 2
+ hlist.glue_order = order
+ hlist.glue_set = -delta/tso
+ else
+ hlist.glue_sign = 0
+ hlist.glue_order = order
+ hlist.glue_set = 0
+ end
+-- print("shrink",hlist.glue_sign,hlist.glue_order,hlist.glue_set)
+ if total_shrink[order] < -delta and order == 0 and hlist.list then
+ last_badness = 1000000
+ hlist.glue_set = 1
+ local fuzz = - delta - total_shrink[0]
+ local hfuzz = tex.hfuzz
+ if fuzz > hfuzz or tex.hbadness < 100 then
+ local overfullrule = tex.overfullrule
+ if fuzz > hfuzz and overfullrule > 0 then
+ -- weird, is always called and no rules shows up
+ slide_nodes(list).next = new_rule(overfullrule,nil,nil,hlist.dir)
+ end
+ diagnostics.overfull_hbox(hlist,pack_begin_line,line,-delta) -- - added
+ end
+ elseif order == 0 and hlist.list and last_badness > tex.hbadness then
+ diagnostics.bad_hbox(hlist,pack_begin_line,line,last_badness)
+ end
+ end
+ end
+ if cal_expand_ratio and font_expand_ratio ~= 0 then
+ -- if font_expand_ratio > 1000 then
+ -- font_expand_ratio = 1000
+ -- elseif font_expand_ratio < -1000 then
+ -- font_expand_ratio = -1000
+ -- end
+
+ local fontexps, lastfont
+
+ if font_expand_ratio > 0 then
+ for i=1,expansion_index do
+ local g = expansion_stack[i]
+ if g.id == glyph_code then
+ local currentfont = g.font
+ if currentfont ~= lastfont then
+ fontexps = expansions[currentfont]
+ lastfont = currentfont
+ end
+ local data = fontexps[g.char]
+ if trace_expansion then
+ setnodecolor(g,"hz:positive")
+ end
+ g.expansion_factor = font_expand_ratio * data.glyphstretch
+ else
+ local stretch, shrink = kern_stretch_shrink(g,g.kern)
+ g.expansion_factor = font_expand_ratio * stretch
+ end
+ end
+ else
+ for i=1,expansion_index do
+ local g = expansion_stack[i]
+ if g.id == glyph_code then
+ local currentfont = g.font
+ if currentfont ~= lastfont then
+ fontexps = expansions[currentfont]
+ lastfont = currentfont
+ end
+ local data = fontexps[g.char]
+ if trace_expansion then
+ setnodecolor(g,"hz:negative")
+ end
+ g.expansion_factor = font_expand_ratio * data.glyphshrink
+ else
+ local stretch, shrink = kern_stretch_shrink(g,g.kern)
+ g.expansion_factor = font_expand_ratio * shrink
+ end
+ end
+ end
+
+ end
+ return hlist, last_badness
+end
+
+nodes.hpack = hpack
+hpack_nodes = hpack -- comment this for old fashioned expansion
+xpack_nodes = hpack -- comment this for old fashioned expansion
+
+local function common_message(hlist,pack_begin_line,line,str)
+ write_nl("")
+ if status.output_active then -- unset
+ write(str," has occurred while \\output is active")
+ end
+ if pack_begin_line > 0 then
+ write(str," in paragraph at lines ",pack_begin_line,"--",line)
+ elseif pack_begin_line < 0 then
+ write(str," in alignment at lines ",-pack_begin_line,"--",line)
+ else
+ write(str," detected at line ",line)
+ end
+ write_nl("")
+ diagnostics.short_display(hlist.list,false)
+ write_nl("")
+ -- diagnostics.start()
+ -- show_box(hlist.list)
+ -- diagnostics.stop()
+end
+
+function diagnostics.overfull_hbox(hlist,pack_begin_line,line,d)
+ common_message(hlist,pack_begin_line,line,format("Overfull \\hbox (%spt too wide)",number.toscaled(d)))
+end
+
+function diagnostics.bad_hbox(hlist,pack_begin_line,line,b)
+ common_message(hlist,pack_begin_line,line,format("Tight \\hbox (badness %i)",b))
+end
+
+function diagnostics.underfull_hbox(hlist,pack_begin_line,line,b)
+ common_message(hlist,pack_begin_line,line,format("Underfull \\hbox (badness %i)",b))
+end
+
+function diagnostics.loose_hbox(hlist,pack_begin_line,line,b)
+ common_message(hlist,pack_begin_line,line,format("Loose \\hbox (badness %i)",b))
+end
+
+-- for the moment here:
+
+local utfchar = utf.char
+local concat = table.concat
+
+local nodecodes = nodes.nodecodes
+local hlist_code = nodecodes.hlist
+local vlist_code = nodecodes.vlist
+local glyph_code = nodecodes.glyph
+local kern_code = nodecodes.kern
+local setnodecolor = nodes.tracers.colors.set
+local parameters = fonts.hashes.parameters
+local basepoints = number.basepoints
+
+-- definecolor[hz:positive] [r=0.6]
+-- definecolor[hz:negative] [g=0.6]
+-- definecolor[hz:zero] [b=0.6]
+
+-- scale = multiplier + ef/multiplier
+
+local trace_verbose = false trackers.register("builders.paragraphs.expansion.verbose", function(v) trace_verbose = v end)
+
+local report_verbose = logs.reporter("fonts","expansion")
+
+local function colorize(n)
+ local size, font, ef, width, scale, list, flush, length
+ if trace_verbose then
+ width = 0
+ length = 0
+ list = { }
+ flush = function()
+ if length > 0 then
+ report_verbose("%0.3f : %10s %10s %s",scale,basepoints(width),basepoints(width*scale),concat(list,"",1,length))
+ width = 0
+ length = 0
+ end
+ end
+ else
+ length = 0
+ end
+ -- tricky: the built-in method creates dummy fonts and the last line normally has the
+ -- original font and that one then has ex.auto set
+ while n do
+ local id = n.id
+ if id == glyph_code then
+ local ne = n.expansion_factor
+ if ne == 0 then
+ if length > 0 then flush() end
+ else
+ local f = n.font
+ if f ~= font then
+ if length > 0 then
+ flush()
+ end
+ local pf = parameters[f]
+ local ex = pf.expansion
+ if ex and ex.auto then
+ size = pf.size
+ font = f -- save lookups
+ else
+ size = false
+ end
+ end
+ if size then
+ if ne ~= ef then
+ if length > 0 then
+ flush()
+ end
+ ef = ne
+ end
+ -- scale = 1.0 + ef / 1000 / 1000 / 1000
+ scale = 1.0 + ef / 1000000000
+ if scale > 1 then
+ setnodecolor(n,"hz:positive")
+ elseif scale < 1 then
+ setnodecolor(n,"hz:negative")
+ else
+ setnodecolor(n,"hz:zero")
+ end
+ if report_verbose then
+ length = length + 1
+ list[length] = utfchar(n.char)
+ width = width + n.width -- no kerning yet
+ end
+ end
+ end
+ elseif id == hlist_code or id == vlist_code then
+ if length > 0 then
+ flush()
+ end
+ colorize(n.list,flush)
+ else -- nothing to show on kerns
+ if length > 0 then
+ flush()
+ end
+ end
+ n = n.next
+ end
+ if length > 0 then
+ flush()
+ end
+end
+
+builders.paragraphs.expansion = builders.paragraphs.expansion or { }
+
+function builders.paragraphs.expansion.trace(head)
+ colorize(head,true)
+ return head
+end
+
+local tasks = nodes.tasks
+
+tasks.prependaction("shipouts","normalizers","builders.paragraphs.expansion.trace")
+tasks.disableaction("shipouts","builders.paragraphs.expansion.trace")
+
+trackers.register("builders.paragraphs.expansion.verbose", function(v)
+ if v then
+ tasks.enableaction("shipouts","builders.paragraphs.expansion.trace")
+ else
+ tasks.disableaction("shipouts","builders.paragraphs.expansion.trace")
+ end
+end)