-
Notifications
You must be signed in to change notification settings - Fork 6
/
selnolig.lua
217 lines (196 loc) · 6.37 KB
/
selnolig.lua
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
-- Lua code for the selnolig package.
-- To be loaded with an instruction such as
-- \directlua{ require("selnolig.lua") }
-- from a (Lua)LaTeX .sty file.
--
-- Author: Mico Loretan (loretan dot mico at gmail dot com)
-- (with crucial contributions from Taco Hoekwater,
-- Patrick Gundlach, and Steffen Hildebrandt; and
-- further contributions by Urs Liska)
--
-- The entire selnolig package is placed under the terms
-- of the LaTeX Project Public License, version 1.3 or
-- later. (http://www.latex-project.org/lppl.txt).
-- It has the status "maintained".
local err, warn, info, log = luatexbase.provides_module({
name = "selnolig",
version = "0.333",
date = "2019/08/01",
description = "Selective suppression of typographic ligatures",
author = "Mico Loretan",
copyright = "Mico Loretan",
license = "LPPL 1.3 or later"
})
selnolig = { }
local debug=false -- default: don't output detailed inf.
function selnolig.activate_debug ( status )
debug=status
end
-- Define variables corresponding to various text nodes;
-- cf. sections 8.1.2 and 8.1.4 of LuaTeX reference guide
local rule = node.id('rule')
local glue = node.id("glue") --
local kern = node.id('kern')
local glyph = node.id('glyph') --
local whatsit = node.id("whatsit") --
local userdefined
for n,v in pairs ( node.whatsits() ) do
if v == 'user_defined' then userdefined = n end
end
local identifier = 123456 -- any unique identifier
local noliga={}
local keepliga={} -- String -> Boolean
local function selnolig_debug_info(s)
if debug then
texio.write_nl(s)
end
end
local blocknode = node.new(whatsit, userdefined)
blocknode.type = 100
blocknode.user_id = identifier
local suppression_on = true -- if false, selnolig_process_ligatures won't do anything
local prefix_length = function ( word , byte )
return unicode.utf8.len ( string.sub ( word , 0 , byte ) )
end
-- Problem: string.find and unicode.utf8.find return
-- the byte-position at which the pattern is found
-- instead of the character-position. Fix this by
-- providing a dedicated string search function.
local unicode_find = function ( s , pattern , position )
-- Start by correcting the incoming position
if position ~= nil then
-- selnolig_debug_info("SNL Position: "..position)
sub = string.sub(s, 1, position)
position=position+string.len(sub) - unicode.utf8.len(sub)
-- selnolig_debug_info("SNL Corrected position: "..position)
end
-- Now execute find and fix it accordingly
byte_pos = unicode.utf8.find(s, pattern, position)
if byte_pos ~= nil then
-- "convert" byte_pos to "unicode_pos"
return unicode.utf8.len( string.sub(s, 1, byte_pos) )
else
return nil
end
end
local function selnolig_process_ligatures(nodes,tail)
if not suppression_on then
return -- suppression disabled
end
local s={}
local current_node=nodes
local build_liga_table = function(strlen,t)
local p={}
for i = 1, strlen do
p[i]=0
end
for k,v in pairs(t) do
-- selnolig_debug_info("SNL Match: "..v[3])
local c= unicode_find(noliga[v[3]],"|")
local correction=1
while c~=nil do
--selnolig_debug_info("SNL Position "..(v[1]+c))
p[v[1]+c-correction] = 1
c = unicode_find(noliga[v[3]],"|",c+1)
correction = correction+1
end
end
--selnolig_debug_info("SNL Liga table: "..table.concat(p, ""))
return p
end
local apply_ligatures=function(head,ligatures)
local i=1
local hh=head
local last=node.tail(head)
for curr in node.traverse_id(glyph,head) do
if ligatures[i]==1 then
selnolig_debug_info("SNL Inserting nolig whatsit before glyph: " ..unicode.utf8.char(curr.char))
node.insert_before(hh,curr, node.copy(blocknode))
hh=curr
end
last=curr
if i==#ligatures then
-- selnolig_debug_info("SNL Leave node list on position: "..i)
break
end
i=i+1
end
if(last~=nil) then
selnolig_debug_info("SNL Last char: "..unicode.utf8.char(last.char))
end
end
for t in node.traverse(nodes) do
if t.id==glyph then
s[#s+1]=unicode.utf8.char(t.char)
end
if ( t.id==glue or t.next==nil or t.id==kern or t.id==rule ) then
local f=string.gsub(table.concat(s,""),"[\\?!,\\.]+","")
local throwliga={}
for k,v in pairs (noliga) do
local count=1
local match = string.find(f,k)
while match do
count = match
keep = false
debug_k1 = ""
for k1,v1 in pairs (keepliga) do
if v1 and string.find(f,k1) and string.find(k1,k) then
debug_k1=k1
keep=true
break
end
end
if not keep then
selnolig_debug_info("SNL pattern match: "..f .." - "..k)
local n = match + string.len(k) - 1
table.insert(throwliga,{prefix_length(f,match),n,k})
else
selnolig_debug_info("SNL pattern match nolig and keeplig: "..f .." - "..k.." - "..debug_k1)
end
match= string.find(f,k,count+1)
end
end
if #throwliga==0 then
-- selnolig_debug_info("SNL No ligature suppression for: "..f)
else
selnolig_debug_info("SNL Do ligature suppression for: "..f)
local ligabreaks = build_liga_table(f:len(),throwliga)
apply_ligatures(current_node,ligabreaks)
end
s = {}
current_node = t
end
end
end -- end of function selnolig_process_ligatures(nodes,tail)
function selnolig.suppress_liga(s,t)
noliga[s] = t
end
function selnolig.always_keep_liga(s)
for _, v in ipairs(s:explode(',')) do
local item = v:gsub('\n', ''):gsub(
'^%s+',''):gsub(
'%s+$', ''):gsub(
'%s-%%x+$', '')
if item ~= '' then
keepliga[item] = true
end
end
end
function selnolig.enable_suppression(val)
suppression_on = val
if val then
selnolig_debug_info("SNL Turning ligature suppression back on")
else
selnolig_debug_info("SNL Turning ligature suppression off")
end
end
function selnolig.enableselnolig()
luatexbase.add_to_callback( "ligaturing",
selnolig_process_ligatures,
"Suppress ligatures selectively", 1 )
end
function selnolig.disableselnolig()
luatexbase.remove_from_callback( "ligaturing",
"Suppress ligatures selectively" )
end
return selnolig