typo-drp.lmt /size: 14 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['typo-drp'] = {
2    version   = 1.001,
3    comment   = "companion to typo-drp.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- This one is sensitive for order (e.g. when combined with first line
10-- processing.
11
12-- todo: use isglyph
13
14local tonumber, type, next = tonumber, type, next
15local ceil = math.ceil
16local settings_to_hash = utilities.parsers.settings_to_hash
17
18local trace_initials    = false  trackers.register("typesetters.initials", function(v) trace_initials = v end)
19local report_initials   = logs.reporter("nodes","initials")
20
21local initials          = typesetters.initials or { }
22typesetters.initials    = initials or { }
23
24local nodes             = nodes
25
26local tasks             = nodes.tasks
27local enableaction      = tasks.enableaction
28local disableaction     = tasks.disableaction
29
30local nuts              = nodes.nuts
31local tonut             = nodes.tonut
32
33local getnext           = nuts.getnext
34local getprev           = nuts.getprev
35local getchar           = nuts.getchar
36local getid             = nuts.getid
37local getattr           = nuts.getattr
38local getwhd            = nuts.getwhd
39
40local getprop           = nuts.getprop
41local setprop           = nuts.setprop
42
43local setlink           = nuts.setlink
44local setprev           = nuts.setprev
45local setnext           = nuts.setnext
46local setfont           = nuts.setfont
47local setscale          = nuts.setscale
48local setwhd            = nuts.setwhd
49local setkern           = nuts.setkern
50local setoffsets        = nuts.setoffsets
51local setglyphdata      = nuts.setglyphdata
52local getparstate       = nuts.getparstate
53
54local hpack_nodes       = nuts.hpack
55
56local nodecodes         = nodes.nodecodes
57
58local nodepool          = nuts.pool
59local new_kern          = nodepool.kern
60
61local insertbefore      = nuts.insertbefore
62local insertafter       = nuts.insertafter
63local remove_node       = nuts.remove
64
65local startofpar        = nuts.startofpar
66
67local nextnode          = nuts.traversers.node
68local nextglyph         = nuts.traversers.glyph
69
70local setcoloring       = nuts.colors.set
71
72local variables         = interfaces.variables
73local v_default         = variables.default
74local v_margin          = variables.margin
75local v_auto            = variables.auto
76local v_first           = variables.first
77local v_keep            = variables.keep
78local v_yes             = variables.yes
79local v_last            = variables.last
80
81local texget            = tex.get
82local texset            = tex.set
83local unsetvalue        = attributes.unsetvalue
84
85local glyph_code        = nodecodes.glyph
86local hlist_code        = nodecodes.hlist
87local glue_code         = nodecodes.glue
88local kern_code         = nodecodes.kern
89local par_code          = nodecodes.par
90
91local actions           = { }
92local busyactions       = { }
93initials.actions        = actions
94initials.busyactions    = actions
95
96local a_initial         = attributes.private("initial")
97
98local category          = characters.category
99
100local levels            = { }
101local getlevel          = tex.getnestlevel or function() return tex.getnest("prv") end
102
103local function set(par,specification)
104    enableaction("processors","typesetters.initials.handler")
105    if trace_initials then
106        report_initials("enabling initials")
107    end
108    levels[getlevel()] = true
109    setprop(par,a_initial,specification)
110end
111
112function initials.set(specification)
113    nuts.setparproperty(set,specification)
114end
115
116interfaces.implement {
117    name      = "setinitial",
118    actions   = initials.set,
119    arguments = {
120        {
121            { "location" },
122            { "enabled", "boolean" },
123            { "method" },
124            { "continue" },
125            { "distance" ,"dimen" },
126            { "hoffset" ,"dimen" },
127            { "voffset" ,"dimen" },
128            { "font", "integer" },
129            { "glyphscale", "integer" },
130            { "dynamic", "integer" },
131            { "ca", "integer" },
132            { "ma", "integer" },
133            { "ta", "integer" },
134            { "n", "integer" },
135            { "m", "integer" },
136        }
137    }
138}
139
140-- todo: prevent linebreak .. but normally a initial ends up at the top of
141-- a page so this has a low priority
142
143actions[v_default] = function(head,settings)
144    local skip = false
145    local busy = false
146    -- begin of par
147    local first  = getnext(head)
148    local indent = false
149    -- parbox .. needs to be set at 0
150    if first and getid(first) == hlist_code then
151        first  = getnext(first)
152        indent = true
153    end
154    -- we need to skip over kerns and glues (signals)
155    while first and getid(first) ~= glyph_code do
156        first = getnext(first)
157    end
158    if first and getid(first) == glyph_code then
159        local ma        = settings.ma or 0
160        local ca        = settings.ca
161        local ta        = settings.ta
162        local last      = first
163        local distance  = settings.distance or 0
164        local voffset   = settings.voffset or 0
165        local hoffset   = settings.hoffset or 0
166        local parindent = texget("parindent")
167        local baseline  = texget("baselineskip",false)
168        local lines     = tonumber(settings.n) or 0
169        local dynamic   = settings.dynamic
170        local font      = settings.font
171        local scale     = settings.glyphscale
172        local method    = settings_to_hash(settings.method)
173        local length    = tonumber(settings.m) or 1
174        --
175        -- 1 char | n chars | skip first quote | ignore punct | keep punct
176        --
177        if getattr(first,a_initial) then
178            for current in nextnode, getnext(first) do
179                if getattr(current,a_initial) then
180                    last = current
181                else
182                    break
183                end
184            end
185        elseif method[v_auto] then
186            local char = getchar(first)
187            local kind = category(char)
188            if kind == "po" or kind == "pi" then
189                if method[v_first] then
190                    -- remove quote etc before initial
191                    local next = getnext(first)
192                    if not next then
193                        -- don't start with a quote or so
194                        return head
195                    end
196                    last = nil
197                    for current in nextglyph, next do
198                        head, first = remove_node(head,first,true)
199                        first = current
200                        last = first
201                        break
202                    end
203                    if not last then
204                        -- no following glyph or so
205                        return head
206                    end
207                else
208                    -- keep quote etc with initial
209                    local next = getnext(first)
210                    if next and method[v_keep] then
211                        skip = first
212                    end
213                    if not next then
214                        -- don't start with a quote or so
215                        return head
216                    end
217                    for current in nextglyph, next do
218                        last = current
219                        break
220                    end
221                    if last == first then
222                        return head
223                    end
224                end
225            elseif kind == "pf" then
226                -- error: final quote
227            else
228                -- okay
229            end
230            -- maybe also: get all A. B. etc
231            local next = getnext(first)
232            if next then
233                for current, char in nextglyph, next do
234                    local kind = category(char)
235                    if kind == "po" then
236                        if method[v_last] then
237                            -- remove period etc after initial
238                            remove_node(head,current,true)
239                        else
240                            -- keep period etc with initial
241                            last = current
242                        end
243                    end
244                    break
245                end
246            end
247        else
248            for current in nextglyph, first do
249                last = current
250                if length <= 1 then
251                    break
252                else
253                    length = length - 1
254                end
255            end
256        end
257        local current = first
258        while true do
259            local id = getid(current)
260            if id == kern_code then
261                setkern(current,0)
262            elseif id == glyph_code and skip ~= current then
263                local next = getnext(current)
264                if font then
265                    setfont(current,font)
266                end
267                if scale then
268                    setscale(current,scale)
269                end
270                if dynamic > 0 then
271                    setglyphdata(current,dynamic)
272                end
273                setcoloring(current,ma,ca,ta)
274            end
275            if current == last then
276                break
277            else
278                current = getnext(current)
279            end
280        end
281        -- We pack so that successive handling cannot touch the dropped cap. Packaging
282        -- in a hlist is also needed because we cannot locally adapt e.g. parindent (not
283        -- yet stored in with par).
284        local prev = getprev(first)
285        local next = getnext(last)
286        --
287        setprev(first)
288        setnext(last)
289        local dropper = hpack_nodes(first)
290        local width, height, depth = getwhd(dropper)
291        setwhd(dropper,0,0,0)
292        --
293        setlink(prev,dropper,next)
294        --
295        if next then
296            local current = next
297            while current do
298                local id = getid(current)
299                if id == glue_code or id == kern_code then
300                    local next = getnext(current)
301                 -- remove_node(current,current,true) -- created an invalid next link and dangling remains
302                    remove_node(head,current,true)
303                    current = next
304                else
305                    break
306                end
307            end
308        end
309        --
310        local hoffset = width + hoffset + distance + (indent and parindent or 0)
311        for current in nextglyph, first do
312            if skip == current then
313                setoffsets(current,-hoffset,0)
314            else
315                setoffsets(current,-hoffset,-voffset) -- no longer - height here
316            end
317            if current == last then
318                break
319            end
320        end
321        --
322        first = dropper
323        --
324        if settings.location == v_margin then
325            -- okay
326        else
327            if lines == 0 then -- safeguard, not too precise
328                lines = ceil((height+voffset) / baseline)
329            end
330            -- We cannot set parshape yet ... when we can I'll add a slope
331            -- option (positive and negative, in emwidth).
332            local hangafter  = - lines
333            local hangindent = width + distance
334            if trace_initials then
335                report_initials("setting hangafter to %i and hangindent to %p",hangafter,hangindent)
336            end
337            if settings.continue == v_yes then
338                local state = {
339                    lines      = lines,
340                    hangindent = hangindent,
341                    parindent  = indent and parindent,
342                }
343                busy = function(head)
344                    local lines = state.lines
345                    local done  = getparstate(head,true).prevgraf or lines
346                    if done < lines then
347                        texset("hangafter",-(lines - done),true)
348                        texset("hangindent",state.hangindent,true)
349                        if state.parindent then
350                            insertafter(first,first,new_kern(-state.parindent))
351                        end
352                        state.lines =  lines - done
353                        return busy
354                    else
355                        return false
356                    end
357                end
358            end
359            texset("hangafter",hangafter,true)
360            texset("hangindent",hangindent,true)
361-- local hsize = texget("hsize",false)
362-- local step  = { hangindent, hsize - hangindent }
363-- local shape = { ["shift"] = true }
364-- for i=1,-hangafter do
365--     shape[#shape+1] = step
366-- end
367-- shape[#shape+1] = { 0, hsize }
368-- texset("parshape",shape)
369-- tex.snapshotpar(0x20000)
370        end
371        if indent then
372            insertafter(first,first,new_kern(-parindent))
373        end
374    end
375    return head, busy
376end
377
378-- we can count ... when all done, we can disable ...
379
380-- function initials.handler(head,groupcode)
381--     if getid(head) == par_code and startofpar(head) then
382--         local settings = getprop(head,a_initial)
383--         if settings then
384--             disableaction("processors","typesetters.initials.handler")
385--             local alternative = settings.alternative or v_default
386--             local action = actions[alternative] or actions[v_default]
387--             if action then
388--                 if trace_initials then
389--                     report_initials("processing initials, alternative %a",alternative)
390--                 end
391--                 return action(head,settings)
392--             end
393--         end
394--     end
395--     return head
396-- end
397
398-- doesn't work well nested but no sane user will do that
399
400local busy = false
401
402function initials.handler(head,groupcode)
403    if getid(head) == par_code and startofpar(head) then
404        if busy then
405            busy = busy(head)
406        end
407        if not busy then
408            local settings = getprop(head,a_initial)
409            if not settings or settings.continue ~= v_yes then
410                levels[getlevel()] = nil
411                if not next(levels) then
412                    disableaction("processors","typesetters.initials.handler")
413                    if trace_initials then
414                        report_initials("disabling initials")
415                    end
416                end
417            end
418            if settings then
419                local alternative = settings.alternative or v_default
420                local action = actions[alternative] or actions[v_default]
421                if action then
422                    if trace_initials then
423                        report_initials("processing initials, alternative %a",alternative)
424                    end
425                    head, busy = action(head,settings)
426                    return head
427                end
428            end
429        end
430    end
431    return head
432end
433
434