if not modules then modules = { } end modules ['node-rul'] = {
    version   = 1.001,
    optimize  = true,
    comment   = "companion to node-rul.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- this will go to an auxiliary module
-- beware: rules now have a dir field
--
-- todo: make robust for layers ... order matters

-- todo: collect successive bit and pieces and combine them
--
-- path s ; s := shaped(p) ; % p[] has rectangles
-- fill s withcolor .5white ;
-- draw boundingbox s withcolor yellow;

local tonumber           = tonumber

local context            = context
local attributes         = attributes
local nodes              = nodes
local properties         = nodes.properties.data

local enableaction       = nodes.tasks.enableaction

local nuts               = nodes.nuts
local tonode             = nuts.tonode
local tonut              = nuts.tonut

local setnext            = nuts.setnext
local setprev            = nuts.setprev
local setlink            = nuts.setlink
local getnext            = nuts.getnext
local getprev            = nuts.getprev
local getid              = nuts.getid
local getdirection       = nuts.getdirection
local getattr            = nuts.getattr
local setattr            = nuts.setattr
local setattrs           = nuts.setattrs
local getfont            = nuts.getfont
local getsubtype         = nuts.getsubtype
local setsubtype         = nuts.setsubtype
local getlist            = nuts.getlist
local setwhd             = nuts.setwhd
local setattrlist        = nuts.setattrlist
local setshift           = nuts.setshift
local getwidth           = nuts.getwidth
local setwidth           = nuts.setwidth
local setweight          = nuts.setweight
local setoffsets         = nuts.setoffsets
local setfield           = nuts.setfield
local getruledata        = nuts.getruledata
local yscaled            = nuts.yscaled

local isglyph            = nuts.isglyph
local firstglyphnode     = nuts.firstglyphnode

local flushlist          = nuts.flushlist
local effectiveglue      = nuts.effectiveglue
local insertnodeafter    = nuts.insertafter
local insertnodebefore   = nuts.insertbefore
local find_tail          = nuts.tail
local setglue            = nuts.setglue
local getrangedimensions = nuts.rangedimensions
local hpack_nodes        = nuts.hpack
local copylist           = nuts.copylist

local nextlist           = nuts.traversers.list
local nextglue           = nuts.traversers.glue
local nextglyph          = nuts.traversers.glyph

local nodecodes          = nodes.nodecodes
local rulecodes          = nodes.rulecodes
local gluecodes          = nodes.gluecodes
local listcodes          = nodes.listcodes

local glyph_code         <const> = nodecodes.glyph
local par_code           <const> = nodecodes.par
local dir_code           <const> = nodecodes.dir
local glue_code          <const> = nodecodes.glue
local hlist_code         <const> = nodecodes.hlist

local indentlist_code    <const> = listcodes.indent
local linelist_code      <const> = listcodes.line
local container_code     <const> = listcodes.container

local leftskip_code         <const> = gluecodes.leftskip
local rightskip_code        <const> = gluecodes.rightskip
local parfillleftskip_code  <const> = gluecodes.parfillleftskip
local parfillrightskip_code <const> = gluecodes.parfillrightskip
local indentskip_code       <const> = gluecodes.indentskip

local nodepool           = nuts.pool

local new_rule           = nodepool.rule
local new_userrule       = nodepool.userrule
local new_kern           = nodepool.kern
local new_leader         = nodepool.leader

local n_tostring         = nodes.idstostring
local n_tosequence       = nodes.tosequence

local variables          = interfaces.variables
local implement          = interfaces.implement

local privateattribute   = attributes.private

local a_ruled            <const> = privateattribute('ruled')
local a_runningtext      <const> = privateattribute('runningtext')
local a_color            <const> = privateattribute('color')
local a_transparency     <const> = privateattribute('transparency')
local a_colormodel       <const> = privateattribute('colormodel')
local a_linefiller       <const> = privateattribute("linefiller")
local a_viewerlayer      <const> = privateattribute("viewerlayer")

local registervalue      = attributes.registervalue
local getvalue           = attributes.getvalue
local texsetattribute    = tex.setattribute

local v_both             <const> = variables.both
local v_left             <const> = variables.left
local v_right            <const> = variables.right
local v_local            <const> = variables["local"]
local v_yes              <const> = variables.yes
local v_foreground       <const> = variables.foreground

local fonthashes         = fonts.hashes
local fontdata           = fonthashes.identifiers
local fontresources      = fonthashes.resources

local glyphdimenfactor   = fonts.helpers.glyphdimenfactor
local splitdimen         = number.splitdimen
local setmetatableindex  = table.setmetatableindex

local runningrule        <const> = tex.magicconstants.runningrule

local striprange         = nuts.striprange
local processwords       = nuts.processwords

local setcoloring        = nuts.colors.set

do

    local rules              = nodes.rules or { }
    nodes.rules              = rules
    -- rules.data               = rules.data  or { }

    local nutrules           = nuts.rules or { }
    nuts.rules               = nutrules -- not that many

    -- we implement user rules here as it takes less code this way

    local function usernutrule(t,noattributes)
        local r = new_userrule(t.width or 0,t.height or 0,t.depth or 0)
        if noattributes == false or noattributes == nil then
            -- avoid fuzzy ones
        else
            setattrlist(r,true)
        end
        properties[r] = t
        return r
    end

    nutrules.userrule = usernutrule

    local function userrule(t,noattributes)
        return tonode(usernutrule(t,noattributes))
    end

    rules.userrule       = userrule
    local ruleactions    = { }

    rules   .ruleactions = ruleactions
    nutrules.ruleactions = ruleactions -- convenient

    local function mathaction(n,h,v,what)
        local font    = getruledata(n)
        local actions = fontresources[font].mathruleactions
        if actions then
            local action = actions[what]
            if action then
                action(n,h,v,font)
            end
        end
    end

    local function mathradical(n,h,v)
        mathaction(n,h,v,"radicalaction")
    end

    local function mathrule(n,h,v)
        mathaction(n,h,v,"hruleaction")
    end

    local function useraction(n,h,v)
        local p = properties[n]
        if p then
            local i = p.type or "draw"
            local a = ruleactions[i]
            if a then
                a(p,h,v,i,n)
            end
        end
    end

    local subtypeactions = {
        [rulecodes.user]     = useraction,
        [rulecodes.over]     = mathrule,
        [rulecodes.under]    = mathrule,
        [rulecodes.fraction] = mathrule,
        [rulecodes.radical]  = mathradical,
    }

    function rules.process(n,h,v)
        local n = tonut(n) -- already a nut
        local s = getsubtype(n)
        local a = subtypeactions[s]
        if a then
            a(n,h,v)
        end
    end

    local trace_ruled   = false  trackers.register("nodes.rules", function(v) trace_ruled = v end)
    local report_ruled  = logs.reporter("nodes","rules")
    local enabled       = false

    local texgetattribute = tex.getattribute

    local unsetvalue <const> = attributes.unsetvalue

    -- The setter is now the performance bottleneck but it is no longer
    -- limited to a certain number of cases before we cycle resources.

    function rules.set(settings)
        if not enabled then
            enableaction("shipouts","nodes.rules.handler")
            enabled = true
        end
        local text = settings.text
        if text then
            settings.text = tonut(text)
         -- nodepool.register(text) -- todo: have a cleanup hook
        end
        -- todo: only when explicitly enabled
        local attr = texgetattribute(a_ruled)
        if attr ~= unsetvalue then
            settings.nestingvalue = attr
            settings.nestingdata  = getvalue(a_ruled,attr) -- so still accessible when we wipe
        end
        texsetattribute(a_ruled,registervalue(a_ruled,settings))
    end

    attributes.setcleaner(a_ruled,function(t)
        local text = t.text
        if text then
            flushlist(text)
        end
    end)

    -- we could check the passed level on the real one ... (no need to pass level)

    local function flush_ruled(head,f,l,d,level,parent,strip) -- not that fast but acceptable for this purpose
        local max   = d.max or 1
        local level = d.stack or d.level or 1
        if level > max then
            -- todo: trace message
            return head
        end
        -- id == glyph_code
        -- id == rule_code
        -- id == hlist_code and getattr(n,a_runningtext)
        -- id == disc_code
        -- id == boundary_code
        local font = nil
        local glph = nil
        local char, id = isglyph(f)
        if char then
            font = id
            glph = f
        elseif id == hlist_code then
            font = getattr(f,a_runningtext)
        else
            -- rare case:
            if id == disc_code then
                font = usesfont(f)
            end
            -- hope for the best
            if not font then
                local g = firstglyphnode(f,l)
                if g then
                    font = getfont(g)
                end
            end
        end
        if not font then
            -- maybe single rule -- saveguard ... we need to deal with rules and so (math)
            return head
        end
        local r, m
        if strip then
            if trace_ruled then
                local before = n_tosequence(f,l,true)
                f, l = striprange(f,l)
                local after = n_tosequence(f,l,true)
                report_ruled("range stripper, before %a, after %a",before,after)
            else
                f, l = striprange(f,l)
            end
        end
        if not f then
            return head
        end
        local wd, ht, dp    = getrangedimensions(parent,f,getnext(l))
        local method        = d.method or 0
        local empty         = d.empty == v_yes
        local offset        = d.offset or 0
        local dy            = d.dy or 0
        local order         = d.order
        local mp            = d.mp
        local rulethickness = d.rulethickness
        local unit          = d.unit or "ex"
        local ma            = d.ma
        local ca            = d.ca
        local ta            = d.ta
        local colorspace    = ma > 0 and ma or getattr(f,a_colormodel) or 1
        local color         = ca > 0 and ca or getattr(f,a_color)
        local transparency  = ta > 0 and ta or getattr(f,a_transparency)
        local foreground    = order == v_foreground
        local layer         = getattr(f,a_viewerlayer)
        local e             = glyphdimenfactor(unit,glph,font)
        local rt            = tonumber(rulethickness)
        if rt then
            rulethickness = e * rulethickness / 2
        else
            local n, u = splitdimen(rulethickness)
            if n and u then -- we need to intercept ex and em and % and ...
                rulethickness = n * glyphdimenfactor(u,glyph,font) / 2
            else
                rulethickness = 1/5
            end
        end

        --
        if level > max then
            level = max
        end
        if method == 0 then -- center
            offset = 2*offset
            m = (offset+(level-1)*dy)*e/2 + rulethickness/2
        else
            m = 0
        end

        local function inject(r,wd,ht,dp)
            if layer then
                setattr(r,a_viewerlayer,layer)
            end
            if empty then
                head = insertnodebefore(head,f,r)
                setlink(r,getnext(l))
                setprev(f)
                setnext(l)
                flushlist(f)
            else
                local k = new_kern(-wd)
                if foreground then
                    insertnodeafter(head,l,k)
                    insertnodeafter(head,k,r)
                    l = r
                else
                    head = insertnodebefore(head,f,r)
                    insertnodeafter(head,r,k)
                end
            end
            if trace_ruled then
                report_ruled("level %a, width %p, height %p, depth %p, nodes %a, text %a",
                    level,wd,ht,dp,n_tostring(f,l),n_tosequence(f,l,true))
            end
        end

        if mp and mp ~= "" then
            local r = usernutrule {
                width  = wd,
                height = ht,
                depth  = dp,
                type   = "mp",
                factor = e,
                offset = offset - (level-1)*dy, -- br ... different direction
                line   = rulethickness,
                data   = mp,
                ma     = colorspace,
                ca     = color,
                ta     = transparency,
            }
            inject(r,wd,ht,dp)
        else
            local tx = d.text
            if tx then
                local l = copylist(tx)
                if d["repeat"] == v_yes then
                    l = new_leader(wd,l)
                    setattrlist(l,tx)
                end
                l = hpack_nodes(l,wd,"exactly")
                inject(l,wd,ht,dp)
            else
                local rule
                if method == 2 or method == 3 then
                    local height = d.height
                    local depth  = d.depth
                    if height > ht then ht = height end
                    if depth  > dp then dp = depth  end
                    local of = offset
                    ht = ht + of
                    dp = dp + of
                    wd = wd + 2*of
                    if method == 2 then
                        rule = nodepool.outlinerule(wd,ht,dp,rulethickness)
                    else
                        rule = nodepool.rule(wd,ht,dp)
                    end
                    setoffsets(rule,-of,0)
                else
                    local hd = (offset+(level-1)*dy)*e - m
                    ht =  hd + rulethickness
                    dp = -hd + rulethickness
                    rule = new_rule(wd,ht,dp)
                end
                inject(setcoloring(rule,colorspace,color,transparency),wd,ht,dp)
            end
        end
        return head
    end

    rules.handler = function(head)
        local data = attributes.values[a_ruled]
 --or-- local data = getvalues(a_ruled)
        if data then
            head = processwords(a_ruled,data,flush_ruled,head)
        end
        return head
    end

    implement {
        name      = "setrule",
        actions   = rules.set,
        arguments = {
            {
                { "continue" },
                { "unit" },
                { "order" },
                { "level", "integer" },
                { "stack", "integer" },
                { "method", "integer" },
                { "offset", "number" },
                { "rulethickness" },
                { "dy", "number" },
                { "max", "number" },
                { "ma", "integer" },
                { "ca", "integer" },
                { "ta", "integer" },
                { "mp" },
                { "empty" },
                { "text", "box" },
                { "repeat" },
                { "height", "dimension" },
                { "depth", "dimension" },
            }
        }
    }

end

do

    local trace_shifted  = false  trackers.register("nodes.shifting", function(v) trace_shifted = v end)
    local report_shifted = logs.reporter("nodes","shifting")
    local a_shifted      <const> = privateattribute('shifted')
    local enabled        = false

    local shifts         = nodes.shifts or { }
    nodes.shifts         = shifts

    function shifts.set(settings)
        if not enabled then
            -- we could disable when no more found
            enableaction("shipouts","nodes.shifts.handler")
            enabled = true
        end
        texsetattribute(a_shifted,registervalue(a_shifted,settings))
    end

    local function flush_shifted(head,first,last,data,level,parent,strip) -- not that fast but acceptable for this purpose
        if true then
            first, last = striprange(first,last)
        end
        local prev = getprev(first)
        local next = getnext(last)
        setprev(first)
        setnext(last)
        local width, height, depth = getrangedimensions(parent,first,next)
        local list = hpack_nodes(first,width,"exactly") -- we can use a simple pack
        if first == head then
            head = list
        end
        if prev then
            setlink(prev,list)
        end
        if next then
            setlink(list,next)
        end
        local raise = data.dy * glyphdimenfactor(data.unit,first)
        setshift(list,raise) -- is taken into dimensions
        setwhd(list,width,height,depth)
        if trace_shifted then
            report_shifted("width %p, nodes %a, text %a",width,n_tostring(first,last),n_tosequence(first,last,true))
        end
        return head
    end

    shifts.handler = function(head)
        local data = attributes.values[a_shifted]
        if data then
            head = processwords(a_shifted,data,flush_shifted,head)
        end
        return head
    end

    implement {
        name      = "setshift",
        actions   = shifts.set,
        arguments = {
            {
                { "continue" },
                { "unit" },
                { "method", "integer" },
                { "dy", "number" },
            }
        }
    }

end

-- linefillers

do

    local linefillers = nodes.linefillers or { }
    nodes.linefillers = linefillers
    local enabled     = false

    local usernutrule = nuts.rules.userrule

    function linefillers.set(settings)
        if not enabled then
            enableaction("finalizers","nodes.linefillers.handler")
            enabled = true
        end
        texsetattribute(a_linefiller,registervalue(a_linefiller,settings))
    end

    local function linefiller(current,data,width,location)
        local height = data.height
        local depth  = data.depth
        local mp     = data.mp
        local ma     = data.ma
        local ca     = data.ca
        local ta     = data.ta
        if mp and mp ~= "" then
            return usernutrule {
                width     = width,
                height    = height,
                depth     = depth,
                type      = "mp",
                line      = data.rulethickness,
                data      = mp,
                ma        = ma,
                ca        = ca,
                ta        = ta,
                option    = location,
                direction = getdirection(current),
            }
        else
            return setcoloring(new_rule(width,height,depth),ma,ca,ta)
        end
    end

    function linefillers.filler(current,data,width,height,depth)
        if width and width > 0 then
            local height = height or data.height or 0
            local depth  = depth  or data.depth  or 0
            if (height + depth) ~= 0 then
                local mp = data.mp
                local ma = data.ma
                local ca = data.ca
                local ta = data.ta
                if mp and mp ~= "" then
                    return usernutrule {
                        width     = width,
                        height    = height,
                        depth     = depth,
                        type      = "mp",
                        line      = data.rulethickness,
                        data      = mp,
                        ma        = ma,
                        ca        = ca,
                        ta        = ta,
                        option    = location,
                        direction = getdirection(current),
                    }
                else
                    return setcoloring(new_rule(width,height,depth),ma,ca,ta)
                end
            end
        end
    end

    local function getskips(list) -- this could be a helper .. is already one
        local ls = nil
        local rs = nil
        local is = nil
        local pl = nil
        local pr = nil
        local ok = false
        for n, subtype in nextglue, list do
            if subtype == rightskip_code then
                rs = n
            elseif subtype == parfillrightskip_code then
                pr = n
            elseif subtype == leftskip_code then
                ls = n
            elseif subtype == indentskip_code then
                is = n
            elseif subtype == parfillleftskip_code then
                pl = n
            end
        end
        return is, ls, pl, pr, rs
    end

    linefillers.handler = function(head)
        local data = attributes.values[a_linefiller]
        if data then
            -- we have a normalized line ..
            for current, id, subtype, list in nextlist, head do
                if subtype == linelist_code and list then
                    local a = getattr(current,a_linefiller)
                    if a then
                        local data = data[a]
                        if data then
                            local location   = data.location
                            local scope      = data.scope
                            local distance   = data.distance
                            local threshold  = data.threshold
                            local leftlocal  = false
                            local rightlocal = false
                            --
                            if scope == v_right then
                                leftlocal = true
                            elseif scope == v_left then
                                rightlocal = true
                            elseif scope == v_local then
                                leftlocal  = true
                                rightlocal = true
                            end
                            -- todo: initleft initright fillleft
                            local is, ls, pl, pr, rs = getskips(list)
                            if ls and rs then
                                if location == v_left or location == v_both then
                                    local indentation = is and getwidth(is) or 0
                                    local leftfixed   = ls and getwidth(ls) or 0
                                    local lefttotal   = ls and effectiveglue(ls,current) or 0
                                    local width = lefttotal - (leftlocal and leftfixed or 0) + indentation - distance
                                    if width > threshold then
                                        if is then
                                            setwidth(is,0)
                                        end
                                        setglue(ls,leftlocal and getwidth(ls) or nil)
                                        if distance > 0 then
                                            insertnodeafter(list,ls,new_kern(distance))
                                        end
                                        insertnodeafter(list,ls,linefiller(current,data,width,"left"))
                                    end
                                end
                                --
                                if location == v_right or location == v_both then
                                    local rightfixed = rs and getwidth(rs) or 0
                                    local righttotal = rs and effectiveglue(rs,current) or 0
                                    local parfixed   = pr and getwidth(pr) or 0
                                    local partotal   = pr and effectiveglue(pr,current) or 0
                                    local width = righttotal - (rightlocal and rightfixed or 0) + partotal - distance
                                    if width > threshold then
                                        if pr then
                                            setglue(pr)
                                        end
                                        setglue(rs,rightlocal and getwidth(rs) or nil)
                                        if distance > 0 then
                                            insertnodebefore(list,rs,new_kern(distance))
                                        end
                                        insertnodebefore(list,rs,linefiller(current,data,width,"right"))
                                    end
                                end
                            else
                                -- error, not a properly normalized line
                            end
                        end
                    end
                end
            end
        end
        return head
    end

    implement {
        name      = "setlinefiller",
        actions   = linefillers.set,
        arguments = {
            {
                { "method", "integer" },
                { "location", "string" },
                { "scope", "string" },
                { "mp", "string" },
                { "ma", "integer" },
                { "ca", "integer" },
                { "ta", "integer" },
                { "depth", "dimension" },
                { "height", "dimension" },
                { "distance", "dimension" },
                { "threshold", "dimension" },
                { "rulethickness", "dimension" },
            }
        }
    }

end

-- We add a bonus feature here (experiment):

interfaces.implement {
    name      = "autorule",
    protected = true,
    public    = true,
    arguments = {
        {
            { "width", "dimension" },
            { "height", "dimension" },
            { "depth", "dimension" },
            { "xoffset", "dimension" },
            { "yoffset", "dimension" },
            { "left", "dimension" },
            { "right", "dimension" },
            { "on", "dimension" },
            { "off", "dimension" },
        },
    },
    actions   = function(t)
        local n = new_rule(
            t.width  or runningrule,
            t.height or runningrule,
            t.depth  or runningrule
        )
        setattrlist(n,true)
        setoffsets(n,t.xoffset,t.yoffset,t.left,t.right,t.on,t.off)
        context(tonode(n))
    end
}

-- joke

do

    local setlistcolor    = nodes.tracers.colors.setlist

    local trace_shadowed  = false  trackers.register("nodes.shadowing", function(v) trace_shifted = v end)
    local report_shadowed = logs.reporter("nodes","shadowing")
    local a_shadowed      <const> = privateattribute("shadowed")
    local enabled         = false

    local shadows         = nodes.shadows or { }
    nodes.shadows         = shadows

    function shadows.set(settings)
        if not enabled then
            -- we could disable when no more found
            enableaction("shipouts","nodes.shadows.handler")
            enabled = true
        end
        texsetattribute(a_shadowed,registervalue(a_shadowed,settings))
    end

    local function flush_shadowed(head,first,last,data,level,parent,strip) -- not that fast but acceptable for this purpose
        if true then
            first, last = striprange(first,last)
        end
        local next  = getnext(last)
        local copy  = copylist(first,next)
        local width = getrangedimensions(parent,first,next)
        local list  = hpack_nodes(copy,width,"exactly") -- we can use a simple pack

        setlistcolor(copy,data.ca,data.ma,data.ta)

        head = insertnodebefore(head,first,list)
        local xoffset = data.dx * glyphdimenfactor(data.unit,first)
        local yoffset = data.dy * glyphdimenfactor(data.unit,first)
        local weight  = data.weight or 0
        setoffsets(list,xoffset,yoffset)
        setwhd(list,0,0,0)
        if weight > 0 then
            for g in nextglyph, copy do
                setweight(g,weight,true)
            end
        end
     -- if trace_shadowed then
     --     report_shadowed("width %p, nodes %a, text %a",width,n_tostring(first,last),n_tosequence(first,last,true))
     -- end
        return head
    end

    shadows.handler = function(head)
        local data = attributes.values[a_shadowed]
        if data then
            head = processwords(a_shadowed,data,flush_shadowed,head)
        end
        return head
    end

    implement {
        name      = "setshadow",
        actions   = shadows.set,
        arguments = {
            {
                { "continue" },
                { "unit" },
                { "weight", "integer" },
                { "method", "integer" },
                { "dx", "number" },
                { "dy", "number" },
                { "ma", "integer" },
                { "ca", "integer" },
                { "ta", "integer" },
            }
        }
    }

end

do

    local insert, concat, sort = table.insert, table.concat, table.sort

    local ctx_latelua = context.latelua

    local bpfactor    = number.dimenfactors.bp

    local s_close     <const> = "h"
    local s_stroke    <const> = "S"

    local f_linewidth = string.formatters["%N w"]
    local f_moveto    = string.formatters["%N %N m"]
    local f_lineto    = string.formatters["%N %N l"]

    local segments    = { }

    -- positions can be used instead of r, c but this tracers better

    -- todo: codeinjections.wrapuplinesegment

    local function wrapup(t,c,x,y)
        local n = t.n
        local a = t.a
        if a == 3 then -- reset
            segments[n] = nil
        else
            local s = segments[n]
            if not s then
                s = { d = t.d }
                segments[n] = s
            end
            if a == 0 then -- register
                insert(s,{ t.r, t.c, x, y })
            elseif a == 1 or a == 2 then -- end close+end
                insert(s,{ t.r, t.c, x, y })
                local l = #s
                if l > 1 then
                    -- needs checking
                    sort(s,function(a,b)
                       if b[1] < a[1] then
                            return false
                       else
                            return a[2] > b[2]
                       end
                    end)
                    sort(s,function(a,b)
                       if b[3] < a[3] then
                            return false
                       else
                            return a[4] > b[4]
                       end
                    end)
                    --
                    local r  = { }
                    local si = s[1]
                    local x  = si[3] * bpfactor
                    local y  = si[4] * bpfactor
                    local w  = s.d * bpfactor
                    r[#r+1] = f_linewidth(w)
                    r[#r+1] = f_moveto(x,y)
                    for i=2,l do
                        local s = s[i]
                        local x = s[3] * bpfactor
                        local y = s[4] * bpfactor
                        r[#r+1] = f_lineto(x,y)
                    end
                    if a == 2 then
                        r[#r+1] = s_close
                    end
                    r[#r+1] = s_stroke
                    r = concat(r, " ")
                    lpdf.print("origin",r)
                    segments[n] = nil
                end
            end
        end
    end

    implement {
        name      = "linesegment",
        protected = true,
        arguments = { "string", "integer", "integer", "integer", "dimension" },
        actions   = function(n,r,c,a,d)
            ctx_latelua {
                action = wrapup, -- todo: codeinjections.wrapuplinesegment
                r = r, -- row
                c = c, -- column
                a = a, -- action
                n = n, -- namespace
                d = d, -- rulethickness
            }
        end
    }

end
