font-imp-spacekerns.lua /size: 10 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['font-imp-spacekerns'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv and hand-ini.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
9if not context then return end
10
11-- This is an experiment. See font-ots.lua for original implementation.
12
13local type, next = type, next
14local insert, setmetatableindex = table.insert, table.setmetatableindex
15
16local fonts              = fonts
17local otf                = fonts.handlers.otf
18local fontdata           = fonts.hashes.identifiers
19local fontfeatures       = fonts.hashes.features
20local otffeatures        = fonts.constructors.features.otf
21local registerotffeature = otffeatures.register
22local handlers           = otf.handlers
23local setspacekerns      = nodes.injections.setspacekerns
24
25function handlers.trigger_space_kerns(head,dataset,sequence,initialrl,font,attr)
26    local features = fontfeatures[font]
27    local enabled  = features and features.spacekern
28    if enabled then
29        setspacekerns(font,sequence) -- called quite often, each glyphrun
30    end
31    return head, enabled
32end
33
34local function hasspacekerns(data)
35    local resources = data.resources
36    local sequences = resources.sequences
37    local validgpos = resources.features.gpos
38    if validgpos and sequences then
39        for i=1,#sequences do
40            local sequence = sequences[i]
41            local steps    = sequence.steps
42            if steps then -- and sequence.features[tag] then
43                local kind = sequence.type
44                if kind == "gpos_pair" then -- or kind == "gpos_single" then
45                    for i=1,#steps do
46                        local step     = steps[i]
47                        local coverage = step.coverage
48                        local rules    = step.rules
49                     -- if rules then
50                     --     -- not now: analyze (simple) rules
51                     -- elseif not coverage then
52                     --     -- nothing to do
53                     -- elseif kind == "gpos_single" then
54                     --     -- maybe a message that we ignore
55                     -- elseif kind == "gpos_pair" then
56                        if coverage and not rules then
57                            local format = step.format
58                            if format == "move" or format == "kern" then
59                                local kerns  = coverage[32]
60                                if kerns then
61                                    return true
62                                end
63                                for k, v in next, coverage do
64                                    if v[32] then
65                                        return true
66                                    end
67                                end
68                            elseif format == "pair" then
69                                local kerns  = coverage[32]
70                                if kerns then
71                                    for k, v in next, kerns do
72                                        local one = v[1]
73                                        if one and one ~= true then
74                                            return true
75                                        end
76                                    end
77                                end
78                                for k, v in next, coverage do
79                                    local kern = v[32]
80                                    if kern then
81                                        local one = kern[1]
82                                        if one and one ~= true then
83                                            return true
84                                        end
85                                    end
86                                end
87                            end
88                        end
89                    end
90                end
91            end
92        end
93    end
94    return false
95end
96
97otf.readers.registerextender {
98    name   = "spacekerns",
99    action = function(data)
100        data.properties.hasspacekerns = hasspacekerns(data)
101    end
102}
103
104local function newdata(t,k)
105    local v = {
106        left  = { },
107        right = { },
108        last  = 0,
109        feat  = nil,
110    }
111    t[k] = v
112    return v
113end
114
115local function spaceinitializer(tfmdata,value) -- attr
116    local resources  = tfmdata.resources
117    local spacekerns = resources and resources.spacekerns
118    if value and spacekerns == nil then
119        local rawdata    = tfmdata.shared and tfmdata.shared.rawdata
120        local properties = rawdata.properties
121        if properties and properties.hasspacekerns then
122            local sequences = resources.sequences
123            local validgpos = resources.features.gpos
124            if validgpos and sequences then
125                local data = setmetatableindex(newdata)
126                for i=1,#sequences do
127                    local sequence = sequences[i]
128                    local steps    = sequence.steps
129                    if steps then
130                        -- we don't support space kerns in other features
131                     -- local kern = sequence.features[tag]
132                     -- if kern then
133                        for tag, kern in next, sequence.features do
134
135                            local d     = data[tag]
136                            local left  = d.left
137                            local right = d.right
138                            local last  = d.last
139                            local feat  = d.feat
140
141                            local kind = sequence.type
142                            if kind == "gpos_pair" then -- or kind == "gpos_single" then
143                                if feat then
144                                    for script, languages in next, kern do
145                                        local f = feat[script]
146                                        if f then
147                                            for l in next, languages do
148                                                f[l] = true
149                                            end
150                                        else
151                                            feat[script] = languages
152                                        end
153                                    end
154                                else
155                                    feat = kern
156    d.feat = feat
157                                end
158                                for i=1,#steps do
159                                    local step     = steps[i]
160                                    local coverage = step.coverage
161                                    local rules    = step.rules
162                                 -- if rules then
163                                 --     -- not now: analyze (simple) rules
164                                 -- elseif not coverage then
165                                 --     -- nothing to do
166                                 -- elseif kind == "gpos_single" then
167                                 --     -- makes no sense in TeX
168                                 -- elseif kind == "gpos_pair" then
169                                    if coverage and not rules then
170                                        local format = step.format
171                                        if format == "move" or format == "kern" then
172                                            local kerns  = coverage[32]
173                                            if kerns then
174                                                for k, v in next, kerns do
175                                                    right[k] = v
176                                                end
177                                            end
178                                            for k, v in next, coverage do
179                                                local kern = v[32]
180                                                if kern then
181                                                    left[k] = kern
182                                                end
183                                            end
184                                        elseif format == "pair" then
185                                            local kerns  = coverage[32]
186                                            if kerns then
187                                                for k, v in next, kerns do
188                                                    local one = v[1]
189                                                    if one and one ~= true then
190                                                        right[k] = one[3]
191                                                    end
192                                                end
193                                            end
194                                            for k, v in next, coverage do
195                                                local kern = v[32]
196                                                if kern then
197                                                    local one = kern[1]
198                                                    if one and one ~= true then
199                                                        left[k] = one[3]
200                                                    end
201                                                end
202                                            end
203                                        end
204                                    end
205                                end
206                                last = i
207                            end
208d.last = last
209                        end
210                    end
211                end
212
213                for tag, d in next, data do
214                    local left  = d.left
215                    local right = d.right
216                    left  = next(left)  and left  or false
217                    right = next(right) and right or false
218                    if left or right then
219
220                        local last  = d.last
221                        local feat  = d.feat
222
223                        if last > 0 then
224                            local triggersequence = {
225                                -- no steps, see (!!)
226                                features = { [tag] = feat or { dflt = { dflt = true, } } },
227                                flags    = noflags,
228                                name     = "trigger_space_kerns",
229                                order    = { tag },
230                                type     = "trigger_space_kerns",
231                                left     = left,
232                                right    = right,
233                            }
234                            insert(sequences,last,triggersequence)
235                            d.last = d.last + 1
236                            spacekerns = true
237                        end
238                    end
239
240                end
241            end
242        end
243        if not spacekerns then
244            spacekerns = false
245        end
246        resources.spacekerns = spacekerns
247    end
248    return spacekerns
249end
250
251registerotffeature {
252    name         = "spacekern",
253    description  = "space kern injection",
254    default      = true,
255    initializers = {
256        node     = spaceinitializer,
257    },
258}
259