summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/core-spa.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/core-spa.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/core-spa.lua1072
1 files changed, 1072 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/core-spa.lua b/Master/texmf-dist/tex/context/base/core-spa.lua
new file mode 100644
index 00000000000..f5d6a11d1a4
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/core-spa.lua
@@ -0,0 +1,1072 @@
+if not modules then modules = { } end modules ['core-spa'] = {
+ version = 1.001,
+ comment = "companion to core-spa.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- todo: test without unset
+
+-- vertical space handler
+
+vspacing = vspacing or { }
+
+vspacing.categories = {
+ [0] = 'discard',
+ [1] = 'largest',
+ [2] = 'force' ,
+ [3] = 'penalty',
+ [4] = 'add' ,
+ [5] = 'disable',
+ [6] = 'nowhite',
+ [7] = 'goback',
+}
+
+function vspacing.tocategories(str)
+ local t = { }
+ for s in str:gmatch("[^, ]") do
+ local n = tonumber(s)
+ if n then
+ t[vspacing.categories[n]] = true
+ else
+ t[b] = true
+ end
+ end
+ return t
+end
+
+function vspacing.tocategory(str)
+ if type(str) == "string" then
+ return set.tonumber(vspacing.tocategories(str))
+ else
+ return set.tonumber({ [vspacing.categories[str]] = true })
+ end
+end
+
+function vspacing.tostring(t)
+ local str = nil
+ for k,v in pairs(vspacing.categories) do
+ if t[v] then
+ if str then str = str .. " +" .. v else str = "+" .. v end
+ else
+ if str then str = str .. " -" .. v else str = "-" .. v end
+ end
+ end
+ return str or ""
+end
+
+do
+
+ local map = { }
+ local skip = { }
+
+ vspacing.fixed = false
+
+ function vspacing.analyse(str)
+ local category, order, penalty, command, fixed = { }, 0, 0, { }, vspacing.fixed
+ local function analyse(str)
+ for sign,amount,_,keyword in str:gmatch("([+%-]*)([%.%d]*)([%*]*)([^,%* ]+)") do
+ if keyword then
+ if map[keyword] then
+ analyse(map[keyword])
+ elseif keyword == "fixed" then
+ fixed = true
+ elseif keyword == "flexible" then
+ fixed = false
+ else
+ local a, b = keyword:match("(.-):(.-)$")
+ if a and b then
+ if a == "category" then
+ -- is a set
+ local n = tonumber(b)
+ if n then
+ category[vspacing.categories[n]] = true
+ else
+ category[b] = true
+ end
+ elseif a == "order" then
+ -- last one counts
+ order = tonumber(b) or 0
+ elseif a == "penalty" then
+ -- last one counts
+ penalty = tonumber(b) or 0
+ elseif a == "skip" then
+ -- last one counts
+ command[#command+1] = { 1, tonumber[b] }
+ end
+ else
+ if amount == "" then amount = 1 end
+ if sign == "-" then amount = -amount end
+ if skip[keyword] then
+ command[#command+1] = { amount, skip[keyword][1], skip[keyword][2] }
+ end
+ end
+ end
+ end
+ end
+ end
+ analyse(str)
+ category = set.tonumber(category)
+ local p = print
+ p("\\startblankhandling")
+ if category > 0 then
+ p(("\\setblankcategory{%s}"):format(category))
+ end
+ if order > 0 then
+ p(("\\setblankorder{%s}"):format(order))
+ end
+ if penalty > 0 then
+ p(("\\setblankpenalty{%s}"):format(penalty))
+ end
+ for k,v in ipairs(command) do
+ p(("\\addblankskip{%s}{%s}{%s}"):format(v[1],v[2],v[3]))
+ end
+ if fixed then
+ p("\\fixedblankskip")
+ else
+ p("\\flexibleblankskip")
+ end
+ p("\\stopblankhandling")
+ end
+
+ function vspacing.setmap(from,to)
+ map[from] = to
+ end
+
+ function vspacing.setskip(key,value,grid)
+ if value ~= "" then
+ if grid == "" then grid = value end
+ skip[key] = { value, grid }
+ end
+ end
+
+end
+
+-- implementation
+
+nodes.snapvalues = { }
+
+function nodes.setsnapvalue(n,ht,dp)
+ nodes.snapvalues[n] = { ht, dp, ht+dp }
+end
+
+do
+
+ nodes.trace_collapse = false
+
+ local kern, glue, penalty, hlist = node.id('kern'), node.id('glue'), node.id('penalty'), node.id('hlist')
+
+ local has_attribute = node.has_attribute
+ local has_field = node.has_field
+
+ local trace_list = { }
+
+ local function reset_tracing()
+ trace_list = { }
+ end
+ local function trace_skip(str,sc,so,sp,data)
+ trace_list[#trace_list+1] = string.format("%s %10s %10s %10s %10s", str:padd(8), (data.spec and data.spec.width) or "?", sc or "-", so or "-", sp or "-")
+ end
+ local function trace_info(...)
+ trace_list[#trace_list+1] = table.concat({...}," ")
+ end
+ local function trace_done(str,data)
+ if data.id == penalty then
+ trace_list[#trace_list+1] = string.format("%s %10s penalty", str:padd(8), data.penalty)
+ else
+ trace_list[#trace_list+1] = string.format("%s %10s glue", str:padd(8), (data.spec and data.spec.width) or "?")
+ end
+ end
+ local function show_tracing()
+ texio.write_nl(table.concat(trace_list,"\n"))
+ end
+
+ -- we assume that these are defined
+
+ local skip_category = attributes.numbers['skip-category'] or 101
+ local skip_penalty = attributes.numbers['skip-penalty'] or 102
+ local skip_order = attributes.numbers['skip-order'] or 103
+ local snap_category = attributes.numbers['snap-category'] or 111
+ local display_math = attributes.numbers['display-math'] or 121
+
+ -- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display
+
+ local user_skip = 0
+ local line_skip = 1
+ local baseline_skip = 2
+ local par_skip = 3
+ local above_display_skip = 4
+ local below_display_skip = 5
+ local above_display_short_skip = 6
+ local below_display_short_skip = 7
+ local left_skip_code = 8
+ local right_skip_code = 9
+ local top_skip_code = 10
+ local split_top_skip_code = 11
+ local tab_skip_code = 12
+ local space_skip_code = 13
+ local xspace_skip_code = 14
+ local par_fill_skip_code = 15
+ local thin_mu_skip_code = 16
+ local med_mu_skip_code = 17
+ local thick_mu_skip_code = 18
+
+ local skips = {
+ [ 0] = "user_skip",
+ [ 1] = "line_skip",
+ [ 2] = "baseline_skip",
+ [ 3] = "par_skip",
+ [ 4] = "above_display_skip",
+ [ 5] = "below_display_skip",
+ [ 6] = "above_display_short_skip",
+ [ 7] = "below_display_short_skip",
+ [ 8] = "left_skip_code",
+ [ 9] = "right_skip_code",
+ [10] = "top_skip_code",
+ [11] = "split_top_skip_code",
+ [12] = "tab_skip_code",
+ [13] = "space_skip_code",
+ [14] = "xspace_skip_code",
+ [15] = "par_fill_skip_code",
+ [16] = "thin_mu_skip_code",
+ [17] = "med_mu_skip_code",
+ [18] = "thick_mu_skip_code",
+ }
+
+ function nodes.is_display_math(head)
+ local n = head.prev
+ while n do
+ local id = n.id
+ if id == penalty then
+ elseif id == glue then
+ if n.subtype == 6 then -- above_display_short_skip
+ return true
+ end
+ else
+ break
+ end
+ n = n.prev
+ end
+ n = head.next
+ while n do
+ local id = n.id
+ if id == penalty then
+ elseif id == glue then
+ if n.subtype == 7 then -- below_display_short_skip
+ return true
+ end
+ else
+ break
+ end
+ n = n.next
+ end
+ return false
+ end
+
+ local function collapser(head,where)
+ if head and head.next then
+node.slide(head) -- hm, why
+ input.starttiming(nodes)
+ local trace = nodes.trace_collapse
+ local current, tail = head, nil
+ local glue_order, glue_data = 0, nil
+ local penalty_order, penalty_data, natural_penalty = 0, nil, nil
+ if trace then reset_tracing() end
+ local parskip, ignore_parskip, ignore_following, ignore_whitespace = nil, false, false, false
+ if trace then trace_info("start analyzing") end
+ while current do
+ local id = current.id
+ if id == glue and current.subtype == 0 then -- todo, other subtypes, like math
+ local sc = has_attribute(current,skip_category)
+ local so = has_attribute(current,skip_order ) or 1
+ local sp = has_attribute(current,skip_penalty )
+ if not sc then
+ if glue_data then
+ if trace then trace_done("before",glue_data) end
+ head, current = nodes.before(head,current,glue_data)
+ end
+ if trace then trace_skip("natural",sc,so,sp,current) end
+ glue_order, glue_data = 0, nil
+ current = current.next
+ else
+ local sct = set.totable(sc)
+ if trace then trace_info("catset",vspacing.tostring(sct)) end
+ if sct.disable then
+ ignore_following = true
+ end
+ if sct.nowhite then
+ ignore_whitespace = true
+ end
+ if sct.discard then
+ if trace then trace_skip("ignore",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ else
+ if sp then
+ if not penalty_data then
+ penalty_data = sp
+ elseif penalty_order < so then
+ penalty_order, penalty_data = so, sp
+ elseif penalty_order == so and sp > penalty_data then
+ penalty_data = sp
+ end
+ end
+ if ignore_following then
+ if trace then trace_skip("disabled",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ elseif not glue_data then
+ if trace then trace_skip("assign",sc,so,sp,current) end
+ glue_order = so
+ head, current, glue_data = nodes.remove(head, current)
+ elseif glue_order < so then
+ if trace then trace_skip("force",sc,so,sp,current) end
+ glue_order = so
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ elseif glue_order == so then
+ if sct.largest then
+ if current.spec.width > glue_data.spec.width then
+ if trace then trace_skip("larger",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ else
+ if trace then trace_skip("smaller",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ elseif sct.goback then
+ if trace then trace_skip("force",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ elseif sct.force then
+ -- todo: inject kern
+ if trace then trace_skip("force",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current, glue_data = nodes.remove(head, current)
+ elseif sct.penalty then
+ if trace then trace_skip("penalty",sc,so,sp,current) end
+ node.free(glue_data)
+ head, current = nodes.remove(head, current, true)
+ elseif sct.add then
+ if trace then trace_skip("add",sc,so,sp,current) end
+ local old, new = glue_data.spec, current.spec
+ old.width = old.width + new.width
+ old.stretch = old.stretch + new.stretch
+ old.shrink = old.shrink + new.shrink
+ head, current = nodes.remove(head, current, true)
+ else
+ if trace then trace_skip("unknown",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ else
+ if trace then trace_skip("unknown",sc,so,sp,current) end
+ head, current = nodes.remove(head, current, true)
+ end
+ end
+ end
+ -- elseif id == penalty then
+ -- natural_penalty = current.penalty
+ -- head, current = nodes.remove(head, current, true)
+ elseif id == glue and current.subtype == 2 then
+ -- baselineskip
+ local sn = has_attribute(current,snap_category)
+ if sn then
+ -- local sv = nodes.snapvalues[sn]
+ -- if sv then
+ head, current = nodes.remove(head, current, true)
+ -- else
+ -- current = current.next
+ -- end
+ elseif ignore_whitespace then
+ head, current = nodes.remove(head, current, true)
+ else
+ current = current.next
+ end
+ elseif id == glue and current.subtype == 3 then
+ parskip = current
+ current = current.next
+ else
+ if trace then trace_info(node.type(current)) end
+ if glue_data then
+ head, current = nodes.before(head,current,glue_data)
+ if trace then trace_done("before",glue_data) end
+ glue_order, glue_data = 0, nil
+ end
+ if id == hlist and where == 'hmode_par' then
+ local list = current.list
+ if list then
+ local sn = has_attribute(list,snap_category)
+ if sn then
+ local sv = nodes.snapvalues[sn]
+ if sv then
+ local height, depth, lineheight = sv[1], sv[2], sv[3]
+ current.height = math.ceil((current.height-height)/lineheight)*lineheight + height
+ current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth
+ end
+ end
+ end
+ end
+ current = current.next
+ end
+ tail = current -- WRONG ! ! ! !
+ end
+ if trace then trace_info("stop analyzing") end
+ -- if natural_penalty and (not penalty_data or natural_penalty > penalty_data) then
+ -- penalty_data = natural_penalty
+ -- end
+ if trace then trace_info("start flushing") end
+ if parskip and glue_data then
+ local ps, gs = parskip.spec, glue_data.spec
+ if ps and gs and ps.width > gs.width then
+ gs.width = ps.width
+ head, current = nodes.remove(head, parskip, true)
+ end
+ end
+ if glue_data then
+ if trace then trace_done("first",glue_data) end
+ head, tail = nodes.after(head,tail,glue_data)
+ end
+ if penalty_data then
+ local p = nodes.penalty(penalty_data)
+ if trace then trace_done("second",p) end
+ head, head = nodes.before(head,head,p)
+ end
+ if trace then trace_info("stop flushing") end
+ if trace then show_tracing() end
+ input.stoptiming(nodes)
+ end
+ return head, true
+ end
+
+ local head, tail = nil, nil
+
+ function nodes.flush_vertical_spacing()
+ if head and head.next then
+ local t = collapser(head)
+ head = nil
+ -- tail = nil
+ return t
+ else
+ return head
+ end
+ end
+
+ function nodes.handle_page_spacing(t, where)
+ -- we need to add the latest t too, else we miss skips and such
+ if t then
+-- node.slide(t) -- redunant
+ if t.next then
+ local tt = node.slide(t)
+ local id = tt.id
+ if id == glue then -- or id == penalty then -- or maybe: if not hlist or vlist
+ if head then
+ t.prev = tail
+ tail.next = t
+ else
+ head = t
+ end
+ tail = tt
+ t = nil
+ else
+ if head then
+ t.prev = tail
+ tail.next = t
+ -- tail = tt
+ t = collapser(head,where)
+ head = nil
+ -- tail = nil
+ else
+ t = collapser(t,where)
+ end
+ end
+ elseif head then
+ t.prev = tail
+ tail.next = t
+ t = collapser(head,where)
+ head = nil
+ else
+ t = collapser(t,where)
+ end
+ end
+ return t
+ end
+
+ function nodes.handle_vbox_spacing(t)
+ if t and t.next then
+ node.slide(t)
+ return collapser(t,'whole')
+ else
+ return t
+ end
+ end
+
+end
+
+-- experimental callback definitions will be moved elsewhere
+
+-- not yet ... we need to get rid of lastskip stuff first
+--
+-- callback.register('vpack_filter', nodes.handle_vbox_spacing)
+-- callback.register('buildpage_filter', nodes.handle_page_spacing)
+
+-- horizontal stuff
+
+-- probably a has_glyphs is rather fast too
+
+do
+
+ local has_attribute = node.has_attribute
+ local unset_attribute = node.unset_attribute
+ local set_attribute = node.set_attribute
+
+--~ local function unset_attribute(n,attribute)
+--~ set_attribute(n,attribute,0)
+--~ end
+
+ local glyph = node.id("glyph")
+ local kern = node.id("kern")
+ local disc = node.id('disc')
+ local glue = node.id('glue')
+ local hlist = node.id('hlist')
+ local vlist = node.id('vlist')
+
+ spacings = spacings or { }
+ spacings.mapping = spacings.mapping or { }
+ spacings.enabled = false
+
+ input.storage.register(false,"spacings/mapping", spacings.mapping, "spacings.mapping")
+
+ function spacings.setspacing(id,char,left,right)
+ local mapping = spacings.mapping[id]
+ if not mapping then
+ mapping = { }
+ spacings.mapping[id] = mapping
+ end
+ local map = mapping[char]
+ if not map then
+ map = { }
+ mapping[char] = map
+ end
+ map.left, map.right = left, right
+ end
+
+ -- todo: no ligatures
+
+ function spacings.process(namespace,attribute,head)
+ local done, mapping, fontids = false, spacings.mapping, fonts.tfm.id
+ for start in node.traverse_id(glyph,head) do -- tricky since we inject
+ local attr = has_attribute(start,attribute)
+ if attr and attr > 0 then
+ local map = mapping[attr]
+ if map then
+ map = map[start.char]
+ unset_attribute(start,attribute)
+ if map then
+ local kern, prev = map.left, start.prev
+ if kern and kern ~= 0 and prev and prev.id == glyph then
+ node.insert_before(head,start,nodes.kern(tex.scale(fontids[start.font].parameters[6],kern)))
+ -- node.insert_before(head,start,nodes.kern(fontids[start.font].parameters[6]*kern))
+ done = true
+ end
+ local kern, next = map.right, start.next
+ if kern and kern ~= 0 and next and next.id == glyph then
+ node.insert_after(head,start,nodes.kern(tex.scale(fontids[start.font].parameters[6],kern)))
+ -- node.insert_after(head,start,nodes.kern(fontids[start.font].parameters[6]*kern))
+ done = true
+ end
+ end
+ end
+ end
+ end
+ return head, done
+ end
+
+ lists.plugins.spacing = {
+ namespace = spacings,
+ processor = spacings.process,
+ }
+
+ kerns = kerns or { }
+ kerns.mapping = kerns.mapping or { }
+ kerns.enabled = false
+
+ input.storage.register(false, "kerns/mapping", kerns.mapping, "kerns.mapping")
+
+ function kerns.setspacing(id,factor)
+ kerns.mapping[id] = factor
+ end
+
+-- local marks = fti[font].shared.otfdata.luatex.marks
+-- if not marks[tchar] then
+
+ function kerns.process(namespace,attribute,head) -- todo interchar kerns / disc nodes / can be made faster
+ local fti, scale = fonts.tfm.id, tex.scale
+ local start, done, mapping, fontids, lastfont = head, false, kerns.mapping, fonts.tfm.id, nil
+ while start do
+ -- faster to test for attr first
+ local attr = has_attribute(start,attribute)
+ if attr and attr > 0 then
+ unset_attribute(start,attribute)
+ local krn = mapping[attr]
+ if krn and krn ~= 0 then
+ local id = start.id
+ if id == glyph then
+ lastfont = start.font
+ local c = start.components
+ if c then
+ local s = start
+ local tail = node.slide(c)
+ if s.prev then
+ s.prev.next = c
+ c.prev = s.prev
+ else
+ head = c
+ end
+ if s.next then
+ s.next.prev = tail
+ end
+ tail.next = s.next
+ start = c
+ start.attr = s.attr
+ s.attr = nil
+ s.components = nil
+ node.free(s)
+ done = true
+ end
+ local prev = start.prev
+ if prev then
+ local pid = prev.id
+ if not pid then
+ -- nothing
+ elseif pid == kern and prev.subtype == 0 then
+ prev.subtype = 1
+ prev.kern = prev.kern + scale(fontids[lastfont].parameters[6],krn)
+ -- prev.kern = prev.kern + fontids[lastfont].parameters[6]*krn
+ done = true
+ elseif pid == glyph then
+ -- fontdata access can be done more efficient
+ if prev.font == lastfont then
+ local prevchar, lastchar = prev.char, start.char
+ local tfm = fti[lastfont].characters[prevchar]
+ local ickern = tfm.kerns
+ if ickern and ickern[lastchar] then
+ krn = scale(ickern[lastchar]+fontids[lastfont].parameters[6],krn)
+ -- krn = krn*(ickern[lastchar]+fontids[lastfont].parameters[6])
+ else
+ krn = scale(fontids[lastfont].parameters[6],krn)
+ -- krn = krn*fontids[lastfont].parameters[6]
+ end
+ else
+ krn = scale(fontids[lastfont].parameters[6],krn)
+ -- krn = krn*fontids[lastfont].parameters[6]
+ end
+ node.insert_before(head,start,nodes.kern(krn))
+ done = true
+ elseif pid == disc then
+ local disc = start.prev -- disc
+ local pre, post, replace = disc.pre, disc.post, disc.replace
+ if pre then -- must pair with start.prev
+ local before = node.copy(disc.prev)
+ pre.prev = before
+ before.next = pre
+ before.prev = nil
+ pre = kerns.process(namespace,attribute,before)
+ pre = pre.next
+ pre.prev = nil
+ disc.pre = pre
+ node.free(before)
+ end
+ if post then -- must pair with start
+ local after = node.copy(disc.next)
+ local tail = node.slide(post)
+ tail.next = after
+ after.prev = tail
+ after.next = nil
+ post = kerns.process(namespace,attribute,post)
+ tail.next = nil
+ disc.post = post
+ node.free(after)
+ end
+ if replace then -- must pair with start and start.prev
+ local before = node.copy(disc.prev)
+ local after = node.copy(disc.next)
+ local tail = node.slide(post)
+ replace.prev = before
+ before.next = replace
+ before.prev = nil
+ tail.next = after
+ after.prev = tail
+ after.next = nil
+ replace = kerns.process(namespace,attribute,before)
+ replace = replace.next
+ replace.prev = nil
+ tail.next = nil
+ disc.replace = replace
+ node.free(after)
+ node.free(before)
+ end
+ end
+ end
+ elseif id == glue and start.subtype == 0 then
+ local s = start.spec
+ local w = s.width
+ if w > 0 then
+ local width, stretch, shrink = w+2*scale(w,krn), s.stretch, s.shrink
+ start.spec = nodes.glue_spec(width,scale(stretch,width/w),scale(shrink,width/w))
+ -- local width, stretch, shrink = w+2*w*krn, s.stretch, s.shrink
+ -- start.spec = nodes.glue_spec(width,stretch*width/w,shrink*width/w))
+ done = true
+ end
+ elseif false and id == kern and start.subtype == 0 then -- handle with glyphs
+ local sk = start.kern
+ if sk > 0 then
+ -- start.kern = scale(sk,krn)
+ start.kern = sk*krn
+ done = true
+ end
+ elseif lastfont and (id == hlist or id == vlist) then -- todo: lookahead
+ if start.prev then
+ node.insert_before(head,start,nodes.kern(scale(fontids[lastfont].parameters[6],krn)))
+ -- node.insert_before(head,start,nodes.kern(fontids[lastfont].parameters[6]*krn))
+ done = true
+ end
+ if start.next then
+ node.insert_after(head,start,nodes.kern(scale(fontids[lastfont].parameters[6],krn)))
+ -- node.insert_after(head,start,nodes.kern(fontids[lastfont].parameters[6]*krn))
+ done = true
+ end
+ end
+ end
+ end
+ if start then
+ start = start.next
+ end
+ end
+ return head, done
+ end
+
+ lists.plugins.kern = {
+ namespace = kerns,
+ processor = kerns.process,
+ }
+
+ -- spacing == attributename !! does not belong here but we will
+ -- relocate node and attribute stuff once it's more complete !!
+
+ cases = cases or { }
+ cases.enabled = false
+ cases.actions = { }
+
+ -- hm needs to be run before glyphs: chars.plugins
+
+ local function helper(start, code, codes)
+ local data, char = characters.data, start.char
+ local dc = data[char]
+ if dc then
+ local fnt = start.font
+ local ifc = fonts.tfm.id[fnt].characters
+ local ucs = dc[codes]
+ if ucs then
+ local ok = true
+ for i=1,#ucs do
+ ok = ok and ifc[ucs[i]]
+ end
+ if ok then
+ local prev, original, copy = start, start, node.copy
+ for i=1,#ucs do
+ local chr = ucs[i]
+ prev = start
+ if i == 1 then
+ start.char = chr
+ else
+ local g = copy(original)
+ g.char = chr
+ local next = start.next
+ g.prev = start
+ if next then
+ g.next = next
+ start.next = g
+ next.prev = g
+ end
+ start = g
+ end
+ end
+ return prev, true
+ end
+ return start, false
+ end
+ local uc = dc[code]
+ if uc and ifc[uc] then
+ start.char = uc
+ return start, true
+ end
+ end
+ return start, false
+ end
+
+ local function upper(start)
+ return helper(start,'uccode','uccodes')
+ end
+ local function lower(start)
+ return helper(start,'lccode','lccodes')
+ end
+
+ cases.actions[1], cases.actions[2] = upper, lower
+
+ cases.actions[3] = function(start)
+ local prev = start.prev
+ if prev and prev.id == kern and prev.subtype == 0 then
+ prev = prev.prev
+ end
+ if not prev or prev.id ~= glyph then
+ return upper(start)
+ else
+ return start, false
+ end
+ end
+
+ cases.actions[4] = function(start)
+ local prev, next = start.prev, start.next
+ if prev and prev.id == kern and prev.subtype == 0 then
+ prev = prev.prev
+ end
+ if next and next.id == kern and next.subtype == 0 then
+ next = next.next
+ end
+ if (not prev or prev.id ~= glyph) and next and next.id == glyph then
+ return upper(start)
+ else
+ return start, false
+ end
+ end
+
+ cases.actions[5] = function(start)
+ local data = characters.data
+ local ch = start.char
+ local mr = math.random
+ local tfm = fonts.tfm.id[start.font].characters
+ if data[ch].lccode then
+ while true do
+ local d = data[mr(1,0xFFFF)]
+ if d then
+ local uc = d.uccode
+ if uc and tfm[uc] then
+ start.char = uc
+ return start, true
+ end
+ end
+ end
+ elseif data[ch].uccode then
+ while true do
+ local d = data[mr(1,0xFFFF)]
+ if d then
+ local lc = d.lccode
+ if lc and tfm[lc] then
+ start.char = lc
+ return start, true
+ end
+ end
+ end
+ else
+ return start, false
+ end
+ end
+
+ -- node.traverse_id_attr
+
+ function cases.process(namespace,attribute,head) -- not real fast but also not used on much data
+ local done, actions = false, cases.actions
+ for start in node.traverse_id(glyph,head) do
+ local attr = has_attribute(start,attribute)
+ if attr and attr > 0 then
+ unset_attribute(start,attribute)
+ local action = actions[attr]
+ if action then
+ local _, ok = action(start)
+ done = done and ok
+ end
+ end
+ end
+ return head, done
+ end
+
+ chars.plugins.case = {
+ namespace = cases,
+ processor = cases.process,
+ }
+
+ breakpoints = breakpoints or { }
+ breakpoints.mapping = breakpoints.mapping or { }
+ breakpoints.methods = breakpoints.methods or { }
+ breakpoints.enabled = false
+
+ input.storage.register(false,"breakpoints/mapping", breakpoints.mapping, "breakpoints.mapping")
+
+ function breakpoints.setreplacement(id,char,kind,before,after)
+ local mapping = breakpoints.mapping[id]
+ if not mapping then
+ mapping = { }
+ breakpoints.mapping[id] = mapping
+ end
+ mapping[char] = { kind or 1, before or 1, after or 1 }
+ end
+
+ breakpoints.methods[1] = function(head,start)
+ -- no discretionary needed
+ -- \def\prewordbreak {\penalty\plustenthousand\hskip\zeropoint\relax}
+ -- \def\postwordbreak {\penalty\zerocount\hskip\zeropoint\relax}
+ -- texio.write_nl(string.format("injecting replacement type %s for character %s",map[1],utf.char(start.char)))
+ if start.prev and start.next then
+ node.insert_before(head,start,nodes.penalty(10000))
+ node.insert_before(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.penalty(0))
+ end
+ return head, start
+ end
+ breakpoints.methods[2] = function(head,start) -- ( => (-
+ if start.prev and start.next then
+ local tmp = start
+ start = nodes.disc()
+ start.prev, start.next = tmp.prev, tmp.next
+ tmp.prev.next, tmp.next.prev = start, start
+ tmp.prev, tmp.next = nil, nil
+ start.replace = tmp
+ local tmp, hyphen = node.copy(tmp), node.copy(tmp)
+ hyphen.char = languages.prehyphenchar(tmp.lang)
+ tmp.next, hyphen.prev = hyphen, tmp
+ start.post = tmp
+ node.insert_before(head,start,nodes.penalty(10000))
+ node.insert_before(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.penalty(10000))
+ end
+ return head, start
+ end
+ breakpoints.methods[3] = function(head,start) -- ) => -)
+ if start.prev and start.next then
+ local tmp = start
+ start = nodes.disc()
+ start.prev, start.next = tmp.prev, tmp.next
+ tmp.prev.next, tmp.next.prev = start, start
+ tmp.prev, tmp.next = nil, nil
+ start.replace = tmp
+ local tmp, hyphen = node.copy(tmp), node.copy(tmp)
+ hyphen.char = languages.prehyphenchar(tmp.lang)
+ tmp.prev, hyphen.next = hyphen, tmp
+ start.pre = hyphen
+ node.insert_before(head,start,nodes.penalty(10000))
+ node.insert_before(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.glue(0))
+ node.insert_after(head,start,nodes.penalty(10000))
+ end
+ return head, start
+ end
+
+ function breakpoints.process(namespace,attribute,head)
+ local done, mapping, fontids = false, breakpoints.mapping, fonts.tfm.id
+ local start, n = head, 0
+ while start do
+ local id = start.id
+ if id == glyph then
+ local attr = has_attribute(start,attribute)
+ if attr and attr > 0 then
+ unset_attribute(start,attribute) -- maybe test for subtype > 256 (faster)
+ -- look ahead and back n chars
+ local map = mapping[attr]
+ if map then
+ local smap = map[start.char]
+ if smap then
+ if n >= smap[2] then
+ local m = smap[3]
+ local next = start.next
+ while next do -- gamble on same attribute
+ local id = next.id
+ if id == glyph then -- gamble on same attribute
+ if map[next.char] then
+ break
+ elseif m == 1 then
+ local method = breakpoints.methods[smap[1]]
+ if method then
+ head, start = method(head,start)
+ done = true
+ end
+ break
+ else
+ m = m - 1
+ next = next.next
+ end
+ elseif id == kern and next.subtype == 0 then
+ next = next.next
+ -- ignore intercharacter kerning, will go way
+ else
+ -- we can do clever and set n and jump ahead but ... not now
+ break
+ end
+ end
+ end
+ n = 0
+ else
+ n = n + 1
+ end
+ else
+ n = 0
+ end
+ end
+ elseif id == kern and start.subtype == 0 then
+ -- ignore intercharacter kerning, will go way
+ else
+ n = 0
+ end
+ start = start.next
+ end
+ return head, done
+ end
+
+ chars.plugins.breakpoint = {
+ namespace = breakpoints,
+ processor = breakpoints.process,
+ }
+
+end
+
+-- educational: snapper
+
+--~ function demo_snapper(head,where) -- snap_category 105 / nodes.snapvalue = { [1] = { 8*65536, 4*65536, 12*65536 } }
+--~ if head then
+--~ local current, tail, dummy = head, nil, nil
+--~ while current do
+--~ local id = current.id
+--~ if id == glue and current.subtype == 2 then
+--~ local sn = has_attribute(current,snap_category)
+--~ if sn then
+--~ local sv = nodes.snapvalues[sn]
+--~ if sv then
+--~ head, current, dummy = node.delete(head, current)
+--~ node.free(dummy)
+--~ else
+--~ current = current.next
+--~ end
+--~ else
+--~ current = current.next
+--~ end
+--~ else
+--~ if id == hlist and where == 'hmode_par' and current.list then
+--~ local sn = has_attribute(current.list,snap_category)
+--~ if sn then
+--~ local sv = nodes.snapvalues[sn]
+--~ if sv then
+--~ local height, depth, lineheight = sv[1], sv[2], sv[3]
+--~ current.height = math.ceil((current.height-height)/lineheight)*lineheight + height
+--~ current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth
+--~ end
+--~ end
+--~ end
+--~ current = current.next
+--~ end
+--~ tail = current
+--~ end
+--~ end
+--~ return head
+--~ end
+
+--~ callback.register('buildpage_filter', demo_snapper)