summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/spacekern/spacekern.lua
blob: 1fd2d5624bca100595d6ac8e510f1e36111a20ec (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
-- Copyright (c) 2022-2023 Thomas Kelkel kelkel@emaileon.de

-- This file may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either
-- version 1.3c of this license or (at your option) any later
-- version. The latest version of this license is in

--    http://www.latex-project.org/lppl.txt

-- and version 1.3c or later is part of all distributions of
-- LaTeX version 2009/09/24 or later.

-- Version: 0.3

local ID = node.id
local GLYPH = ID ( "glyph" )
local DISC = ID ( "disc" )
local GLUE = ID ( "glue" )
local PENALTY = ID ( "penalty" )
local HLIST = ID ( "hlist" )
local INS = ID ( "ins" )

local SWAPPED = table.swapped
local SUBTYPES = node.subtypes
local SPACESKIP = SWAPPED ( SUBTYPES ("glue") )["spaceskip"]
local LEADERS = SWAPPED ( SUBTYPES ("glue") )["leaders"]
local LBPENALTY = SWAPPED ( SUBTYPES ("penalty") )["linebreakpenalty"]

local NEW = node.new
local REM = node.remove
local PREV = node.prev
local NEXT = node.next
local TAIL = node.tail
local INS_B = node.insert_before
local HAS_GLYPH = node.has_glyph
local T = node.traverse
local T_GLYPH = node.traverse_glyph

local pairs = pairs
local type = type

local U = unicode.utf8
local SUB = U.sub
local GSUB = U.gsub
local FIND = U.find

local FLOOR = math.floor

local GET_FONT = font.getfont

local ATC = luatexbase.add_to_callback

-----

local no_iw_kern = false

function spacekern_no_iw_kern ()
    no_iw_kern = true
end

local function round ( num, dec )
    return FLOOR ( num * 10^dec + 0.5 ) / 10^dec
end

local function find_first_last ( n, node_type, last )
    local d = NEXT
    if last then
        d = PREV
        n = TAIL ( n )
    end
    while true do
        if n and n.id == node_type then
            return n
        end
        if d ( n ) then
            n = d ( n )
        else
            return false
        end
    end
end

local function check_node ( n, space_check, disc_check, last )
    if n.replace then
        return find_first_last ( n.replace, GLYPH, last ), space_check, disc_check
    end
    return n, space_check, disc_check
end

local function find_glyph ( n, d, hlist_check, macro )
    if n then
        local space_check = false
        local disc_check = false
        if hlist_check and ( n.id == GLYPH or n.replace and HAS_GLYPH ( n.replace ) ) then
            return check_node ( n, space_check, disc_check, d == PREV )
        elseif not hlist_check then
            if d ( n ) then
                n = d ( n )
            else
                return false
            end
        end
        while not ( n.id == GLYPH or n.replace and HAS_GLYPH ( n.replace ) ) do
            if n.id == HLIST and n.head then
                local point = n.head
                if d == PREV then
                    point = TAIL ( point )
                end
                return find_glyph ( point, d, true, macro )
            end
            if not d ( n ) or n.id == INS or macro and n.id == GLUE and n.width == 0 then
                return false
            else
                if n.id == GLUE and n.subtype == SPACESKIP then
                    space_check = n
                elseif n.subtype == LEADERS or n.id == GLUE and n.stretch > 0 then
                    return false
                end
            end
            if n.id == DISC then
                disc_check = n
            end
            n = d ( n )
        end
        return check_node ( n, space_check, disc_check, d == PREV )
    end
    return false
end

local function make_kern ( head, font, first_glyph, second_glyph, insert_point )
    local tfmdata = GET_FONT ( font )
    if tfmdata and tfmdata.resources then
        local resources = tfmdata.resources
        if resources.sequences then
            local seq = resources.sequences
            for _, t in pairs ( seq ) do
                if t.steps and ( table.swapped ( t.order )["kern"] or tfmdata.specification.features.raw[t.name] ) then
                    local steps = t.steps
                    for _, k in pairs ( steps ) do
                        if k.coverage and k.coverage[first_glyph] then
                            local glyph_table = k.coverage[first_glyph]
                            if type ( glyph_table ) == "table" then
                                for key, value in pairs ( glyph_table ) do
                                    if key == second_glyph and type ( value ) == "number" then
                                        if not ( ( first_glyph == 32 or second_glyph == 32 ) and table.swapped ( t.order )["kern"] ) then
                                            insert_point.width = insert_point.width + value / tfmdata.units_per_em * tfmdata.size
                                        end
                                        if first_glyph == 32 or second_glyph == 32 then
                                            insert_point.stretch = insert_point.stretch + value / tfmdata.units_per_em * tfmdata.size * 0.5
                                            insert_point.shrink = insert_point.shrink + value / tfmdata.units_per_em * tfmdata.size * 0.33333
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return head, disc_node
end

local function check_glyph ( n, d, has_prev_next_glyph, prev_next_glyph )
    if find_glyph ( n, d ) then
        prev_next_glyph = find_glyph ( n, d )
        has_prev_next_glyph = true
    end
    return has_prev_next_glyph, prev_next_glyph
end

local function make_kerns ( head, n )
    local has_prev_glyph = false
    local has_next_glyph = false
    local prev_glyph = n
    local next_glyph = n
    has_prev_glyph, prev_glyph = check_glyph ( n, PREV, has_prev_glyph, prev_glyph )
    has_next_glyph, next_glyph = check_glyph ( n, NEXT, has_next_glyph, next_glyph )
    if has_prev_glyph and prev_glyph.char and prev_glyph.font then
        head = make_kern ( head, prev_glyph.font, prev_glyph.char, 32, n )
    end
    if has_next_glyph and next_glyph.char and next_glyph.font then
        head = make_kern ( head, next_glyph.font, 32, next_glyph.char, n )
    end
    if not no_iw_kern and has_prev_glyph and has_next_glyph and prev_glyph.char and next_glyph.char and prev_glyph.font and next_glyph.font then
        local tfmdata = GET_FONT ( prev_glyph.font )
        if tfmdata and tfmdata.name then
            local font_id = GSUB ( SUB ( tfmdata.name, 1, FIND ( tfmdata.name, ":" ) - 1 ), "\"", "" )
            local second_tfmdata = GET_FONT ( next_glyph.font )
            if second_tfmdata and second_tfmdata.name then
                local second_font_id = GSUB ( SUB ( second_tfmdata.name, 1, FIND ( second_tfmdata.name, ":" ) - 1 ), "\"", "" )
                if font_id == second_font_id then
                    head = make_kern ( head, prev_glyph.font, prev_glyph.char, next_glyph.char, n )
                end
            end
        end
    end
    return head
end

local function make_short_spaces ( head )
    for n in T_GLYPH ( head ) do
        if n.char and n.char == 59 and ( not find_glyph ( n, PREV ) or find_glyph ( n, PREV ).char ~= 59 ) then
            if find_glyph ( n, NEXT, false, true ) then
                local next_glyph = n
                next_glyph = find_glyph ( n, NEXT, false, true )
                if next_glyph.char and next_glyph.char == 59 then
                    REM ( head, next_glyph )
                    local SIZE = 0
                    if n.font then
                        local tfmdata = GET_FONT ( n.font )
                        if tfmdata.size then
                            SIZE = tfmdata.size
                        end
                    end
                    INS_B ( head, n, NEW ( GLUE ) )
                    local glue_node = PREV ( n )
                    glue_node.subtype = SPACESKIP
                    glue_node.width = round ( SIZE * .16667, 0 )
                    glue_node.stretch = glue_node.width * 0.5
                    glue_node.shrink = glue_node.width * 0.33333
                    local has_next_glyph = false
                    if find_glyph ( n, NEXT, false, true ) then
                        next_glyph = find_glyph ( n, NEXT, false, true )
                        has_next_glyph = true
                    end
                    if has_next_glyph and next_glyph.char and next_glyph.char == 59 then
                        INS_B ( head, glue_node, NEW ( PENALTY ) )
                        PREV ( glue_node ).subtype = LBPENALTY
                        PREV ( glue_node ).penalty = 10000
                        REM ( head, next_glyph )
                    end
                    REM ( head, n )
                end
            end
        end
    end
end

local function make_glues_and_kerns ( head )
    for n in T ( head ) do
        if n.id == GLUE and n.subtype == SPACESKIP then
            head = make_kerns ( head, n )
        end
    end
    return head
end

ATC ( "ligaturing", make_short_spaces, "make short spaces", 1 )
ATC ( "pre_linebreak_filter", make_glues_and_kerns, "kern between words and against space preline", 1 )
ATC ( "hpack_filter", make_glues_and_kerns, "kern between words and against space hpack" )