typo-drp.lua /size: 11 Kb    last modification: 2021-10-28 13:50
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.paragraphs 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 setattr           = nuts.setattr
44local setlink           = nuts.setlink
45local setprev           = nuts.setprev
46local setnext           = nuts.setnext
47local setfont           = nuts.setfont
48local setchar           = nuts.setchar
49local setwhd            = nuts.setwhd
50local setkern           = nuts.setkern
51local setoffsets        = nuts.setoffsets
52local setglyphdata      = nuts.setglyphdata
53local setattr           = nuts.setattr
54
55local hpack_nodes       = nuts.hpack
56
57local nodecodes         = nodes.nodecodes
58
59local nodepool          = nuts.pool
60local new_kern          = nodepool.kern
61
62local insertbefore      = nuts.insertbefore
63local insertafter       = nuts.insertafter
64local remove_node       = nuts.remove
65
66local startofpar        = nuts.startofpar
67
68local nextnode          = nuts.traversers.node
69local nextglyph         = nuts.traversers.glyph
70
71local variables         = interfaces.variables
72local v_default         = variables.default
73local v_margin          = variables.margin
74local v_auto            = variables.auto
75local v_first           = variables.first
76local v_keep            = variables.keep
77local v_last            = variables.last
78
79local texget            = tex.get
80local texset            = tex.set
81local unsetvalue        = attributes.unsetvalue
82
83local glyph_code        = nodecodes.glyph
84local hlist_code        = nodecodes.hlist
85local glue_code         = nodecodes.glue
86local kern_code         = nodecodes.kern
87local par_code          = nodecodes.par
88
89local actions           = { }
90initials.actions        = actions
91
92local a_initial         = attributes.private("initial")
93local a_color           = attributes.private('color')
94local a_transparency    = attributes.private('transparency')
95local a_colormodel      = attributes.private('colormodel')
96
97local category          = characters.category
98
99local function set(par,specification)
100    enableaction("processors","typesetters.initials.handler")
101    if trace_initials then
102        report_initials("enabling initials")
103    end
104    setprop(par,a_initial,specification)
105end
106
107function initials.set(specification)
108    nuts.setparproperty(set,specification)
109end
110
111interfaces.implement {
112    name      = "setinitial",
113    actions   = initials.set,
114    arguments = {
115        {
116            { "location" },
117            { "enabled", "boolean" },
118            { "method" },
119            { "distance" ,"dimen" },
120            { "hoffset" ,"dimen" },
121            { "voffset" ,"dimen" },
122            { "font", "integer" },
123            { "dynamic", "integer" },
124            { "ca", "integer" },
125            { "ma", "integer" },
126            { "ta", "integer" },
127            { "n", "integer" },
128            { "m", "integer" },
129        }
130    }
131}
132
133-- todo: prevent linebreak .. but normally a initial ends up at the top of
134-- a page so this has a low priority
135
136actions[v_default] = function(head,setting)
137    local skip = false
138    -- begin of par
139    local first  = getnext(head)
140    local indent = false
141    -- parbox .. needs to be set at 0
142    if first and getid(first) == hlist_code then
143        first  = getnext(first)
144        indent = true
145    end
146    -- we need to skip over kerns and glues (signals)
147    while first and getid(first) ~= glyph_code do
148        first = getnext(first)
149    end
150    if first and getid(first) == glyph_code then
151        local ma        = setting.ma or 0
152        local ca        = setting.ca
153        local ta        = setting.ta
154        local last      = first
155        local distance  = setting.distance or 0
156        local voffset   = setting.voffset or 0
157        local hoffset   = setting.hoffset or 0
158        local parindent = texget("parindent")
159        local baseline  = texget("baselineskip",false)
160        local lines     = tonumber(setting.n) or 0
161        local dynamic   = setting.dynamic
162        local font      = setting.font
163        local method    = settings_to_hash(setting.method)
164        local length    = tonumber(setting.m) or 1
165        --
166        -- 1 char | n chars | skip first quote | ignore punct | keep punct
167        --
168        if getattr(first,a_initial) then
169            for current in nextnode, getnext(first) do
170                if getattr(current,a_initial) then
171                    last = current
172                else
173                    break
174                end
175            end
176        elseif method[v_auto] then
177            local char = getchar(first)
178            local kind = category(char)
179            if kind == "po" or kind == "pi" then
180                if method[v_first] then
181                    -- remove quote etc before initial
182                    local next = getnext(first)
183                    if not next then
184                        -- don't start with a quote or so
185                        return head
186                    end
187                    last = nil
188                    for current in nextglyph, next do
189                        head, first = remove_node(head,first,true)
190                        first = current
191                        last = first
192                        break
193                    end
194                    if not last then
195                        -- no following glyph or so
196                        return head
197                    end
198                else
199                    -- keep quote etc with initial
200                    local next = getnext(first)
201                    if next and method[v_keep] then
202                        skip = first
203                    end
204                    if not next then
205                        -- don't start with a quote or so
206                        return head
207                    end
208                    for current in nextglyph, next do
209                        last = current
210                        break
211                    end
212                    if last == first then
213                        return head
214                    end
215                end
216            elseif kind == "pf" then
217                -- error: final quote
218            else
219                -- okay
220            end
221            -- maybe also: get all A. B. etc
222            local next = getnext(first)
223            if next then
224                for current, char in nextglyph, next do
225                    local kind = category(char)
226                    if kind == "po" then
227                        if method[v_last] then
228                            -- remove period etc after initial
229                            remove_node(head,current,true)
230                        else
231                            -- keep period etc with initial
232                            last = current
233                        end
234                    end
235                    break
236                end
237            end
238        else
239            for current in nextglyph, first do
240                last = current
241                if length <= 1 then
242                    break
243                else
244                    length = length - 1
245                end
246            end
247        end
248        local current = first
249        while true do
250            local id = getid(current)
251            if id == kern_code then
252                setkern(current,0)
253            elseif id == glyph_code and skip ~= current then
254                local next = getnext(current)
255                if font then
256                    setfont(current,font)
257                end
258                if dynamic > 0 then
259                    setglyphdata(current,dynamic)
260                end
261                -- can be a helper
262                if ca and ca > 0 then
263                    setattr(current,a_colormodel,ma == 0 and 1 or ma)
264                    setattr(current,a_color,ca)
265                end
266                if ta and ta > 0 then
267                    setattr(current,a_transparency,ta)
268                end
269                --
270            end
271            if current == last then
272                break
273            else
274                current = getnext(current)
275            end
276        end
277        -- We pack so that successive handling cannot touch the dropped cap. Packaging
278        -- in a hlist is also needed because we cannot locally adapt e.g. parindent (not
279        -- yet stored in with par).
280        local prev = getprev(first)
281        local next = getnext(last)
282        --
283        setprev(first)
284        setnext(last)
285        local dropper = hpack_nodes(first)
286        local width, height, depth = getwhd(dropper)
287        setwhd(dropper,0,0,0)
288        --
289        setlink(prev,dropper)
290        setlink(dropper,next)
291        --
292        if next then
293            local current = next
294            while current do
295                local id = getid(current)
296                if id == glue_code or id == kern_code then
297                    local next = getnext(current)
298                 -- remove_node(current,current,true) -- created an invalid next link and dangling remains
299                    remove_node(head,current,true)
300                    current = next
301                else
302                    break
303                end
304            end
305        end
306        --
307        local hoffset = width + hoffset + distance + (indent and parindent or 0)
308        for current in nextglyph, first do
309            if skip == current then
310                setoffsets(current,-hoffset,0)
311            else
312                setoffsets(current,-hoffset,-voffset) -- no longer - height here
313            end
314            if current == last then
315                break
316            end
317        end
318        --
319        first = dropper
320        --
321        if setting.location == v_margin then
322            -- okay
323        else
324            if lines == 0 then -- safeguard, not too precise
325                lines = ceil((height+voffset) / baseline)
326            end
327            -- We cannot set parshape yet ... when we can I'll add a slope
328            -- option (positive and negative, in emwidth).
329            local hangafter  = - lines
330            local hangindent = width + distance
331            if trace_initials then
332                report_initials("setting hangafter to %i and hangindent to %p",hangafter,hangindent)
333            end
334            texset("hangafter",hangafter)
335            texset("hangindent",hangindent)
336        end
337        if indent then
338            insertafter(first,first,new_kern(-parindent))
339        end
340    end
341    return head
342end
343
344-- we can count ... when all done, we can disable ...
345
346function initials.handler(head)
347    if getid(head) == par_code and startofpar(head) then
348        local settings = getprop(head,a_initial)
349        if settings then
350            disableaction("processors","typesetters.initials.handler")
351            local alternative = settings.alternative or v_default
352            local action = actions[alternative] or actions[v_default]
353            if action then
354                if trace_initials then
355                    report_initials("processing initials, alternative %a",alternative)
356                end
357                return action(head,settings)
358            end
359        end
360    end
361    return head
362end
363