summaryrefslogtreecommitdiff
path: root/luaotfload-letterspace.lua
blob: 847f1752cd5821be91fd862f0615f70471fc4341 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
if not modules then modules = { } end modules ['letterspace'] = {
    version   = "2.4",
    comment   = "companion to luaotfload.lua",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL; adapted by Philipp Gesang",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local next               = next
local nodes, node, fonts = nodes, node, fonts

local find_node_tail     = node.tail or node.slide
local free_node          = node.free
local free_nodelist      = node.flush_list
local copy_node          = node.copy
local copy_nodelist      = node.copy_list
local insert_node_before = node.insert_before
local insert_node_after  = node.insert_after

local nodepool           = nodes.pool
local tasks              = nodes.tasks

local new_kern           = nodepool.kern
local new_glue           = nodepool.glue

local nodecodes          = nodes.nodecodes
local kerncodes          = nodes.kerncodes
local skipcodes          = nodes.skipcodes

local glyph_code         = nodecodes.glyph
local kern_code          = nodecodes.kern
local disc_code          = nodecodes.disc
local glue_code          = nodecodes.glue
local hlist_code         = nodecodes.hlist
local vlist_code         = nodecodes.vlist
local math_code          = nodecodes.math

local kerning_code       = kerncodes.kerning
local userkern_code      = kerncodes.userkern

local fonthashes         = fonts.hashes
local chardata           = fonthashes.characters
local quaddata           = fonthashes.quads

typesetters              = typesetters or { }
local typesetters        = typesetters

typesetters.kernfont     = typesetters.kernfont or { }
local kernfont           = typesetters.kernfont

kernfont.keepligature    = false
kernfont.keeptogether    = false

local kern_injector = function (fillup,kern)
  if fillup then
    local g = new_glue(kern)
    local s = g.spec
    s.stretch = kern
    s.stretch_order = 1
    return g
  else
    return new_kern(kern)
  end
end

--[[doc--

    Caveat lector.
    This is a preliminary, makeshift adaptation of the Context
    character kerning mechanism that emulates XeTeX-style fontwise
    letterspacing. Note that in its present state it is far inferior to
    the original, which is attribute-based and ignores font-boundaries.
    Nevertheless, due to popular demand the following callback has been
    added. It should not be relied upon to be present in future
    versions.

--doc]]--

local kernfactors = { } --- fontid -> factor

local kerncharacters
kerncharacters = function (head)
  local start, done   = head, false
  local lastfont      = nil
  local keepligature  = kernfont.keepligature --- function
  local keeptogether  = kernfont.keeptogether --- function
  local fillup        = false

  local identifiers   = fonthashes.identifiers
  local kernfactors   = kernfactors

  local firstkern     = true

  while start do
    local id = start.id
    if id == glyph_code then

      --- 1) look up kern factor (slow, but cached rudimentarily)
      local krn
      local fontid = start.font
      do
        krn = kernfactors[fontid]
        if not krn then
          local tfmdata = identifiers[fontid]
          if not tfmdata then -- unsafe
            tfmdata = font.fonts[fontid]
          end
          if tfmdata then
            fontproperties = tfmdata.properties
            if fontproperties then
              krn = fontproperties.kerncharacters
            end
          end
          kernfactors[fontid] = krn
        end
        if not krn or krn == 0 then
          firstkern = true
          goto nextnode
        elseif firstkern then
          firstkern = false
          if not start.components then
            goto nextnode
          end
        end
      end

      if krn == "max" then
        krn = .25
        fillup = true
      else
        fillup = false
      end

      lastfont = fontid

      --- 2) resolve ligatures
      local c = start.components
      if c then
        if keepligature and keepligature(start) then
          -- keep 'm
        else
          c = kerncharacters (c)
          local s = start
          local p, n = s.prev, s.next
          local tail = find_node_tail(c)
          if p then
            p.next = c
            c.prev = p
          else
            head = c
          end
          if n then
            n.prev = tail
          end
          tail.next = n
          start = c
          s.components = nil
          -- we now leak nodes !
          --  free_node(s)
          done = true
        end
      end -- kern ligature

      --- 3) apply the extra kerning
      local prev = start.prev
      if prev then
        local pid = prev.id

        if not pid then
          -- nothing

        elseif pid == kern_code then
          if prev.subtype == kerning_code   --- context does this by means of an
          or prev.subtype == userkern_code  --- attribute; we may need a test
          then
            if keeptogether and prev.prev.id == glyph_code and keeptogether(prev.prev,start) then
              -- keep
            else
              prev.subtype = userkern_code
              prev.kern = prev.kern + quaddata[lastfont]*krn -- here
              done = true
            end
          end

        elseif pid == glyph_code then
          if prev.font == lastfont then
            local prevchar, lastchar = prev.char, start.char
            if keeptogether and keeptogether(prev,start) then
              -- keep 'm
            elseif identifiers[lastfont] then
              local kerns = chardata[lastfont][prevchar].kerns
              local kern = kerns and kerns[lastchar] or 0
              krn = kern + quaddata[lastfont]*krn -- here
              insert_node_before(head,start,kern_injector(fillup,krn))
              done = true
            end
          else
            krn = quaddata[lastfont]*krn -- here
            insert_node_before(head,start,kern_injector(fillup,krn))
            done = true
          end

        elseif pid == disc_code then
          -- a bit too complicated, we can best not copy and just calculate
          -- but we could have multiple glyphs involved so ...
          local disc = prev -- disc
          local pre, post, replace = disc.pre, disc.post, disc.replace
          local prv, nxt = disc.prev, disc.next

          if pre and prv then -- must pair with start.prev
            -- this one happens in most cases
            local before = copy_node(prv)
            pre.prev = before
            before.next = pre
            before.prev = nil
            pre = kerncharacters (before)
            pre = pre.next
            pre.prev = nil
            disc.pre = pre
            free_node(before)
          end

          if post and nxt then  -- must pair with start
            local after = copy_node(nxt)
            local tail = find_node_tail(post)
            tail.next = after
            after.prev = tail
            after.next = nil
            post = kerncharacters (post)
            tail.next = nil
            disc.post = post
            free_node(after)
          end

          if replace and prv and nxt then -- must pair with start and start.prev
            local before = copy_node(prv)
            local after = copy_node(nxt)
            local tail = find_node_tail(replace)
            replace.prev = before
            before.next = replace
            before.prev = nil
            tail.next = after
            after.prev = tail
            after.next = nil
            replace = kerncharacters (before)
            replace = replace.next
            replace.prev = nil
            after.prev.next = nil
            disc.replace = replace
            free_node(after)
            free_node(before)
          elseif identifiers[lastfont] then
            if prv and prv.id == glyph_code and prv.font == lastfont then
              local prevchar, lastchar = prv.char, start.char
              local kerns = chardata[lastfont][prevchar].kerns
              local kern = kerns and kerns[lastchar] or 0
              krn = kern + quaddata[lastfont]*krn -- here
            else
              krn = quaddata[lastfont]*krn -- here
            end
            disc.replace = kern_injector(false,krn) -- only kerns permitted, no glue
          end

        end
      end
    end

    ::nextnode::
    if start then
      start = start.next
    end
  end
  return head, done
end

kernfont.handler = kerncharacters

--- vim:sw=2:ts=2:expandtab:tw=71