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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
|
-- Module luavlna
-- code originally created by Patrick Gundlach
-- http://tex.stackexchange.com/q/27780/2891
-- The code was adapted for plain TeX and added some more features
-- 1. It is possible to turn this functionality only for some letters
-- 2. Code now works even for single letters after brackets etc.
--
local M = {}
local utf_match = unicode.utf8.match
local utf_char = unicode.utf8.char
local utf_len = unicode.utf8.len
local glue_id = node.id "glue"
local glyph_id = node.id "glyph"
local hlist_id = node.id "hlist"
local vlist_id = node.id "vlist"
local math_id = node.id "math"
local period_char = string.byte(".")
local alpha = string.char(37).."a" -- alpha class, entering
-- percent char directly caused error
local alphas = {}
local match_char = function(x) return utf_match(x,alpha) end
local match_table = function(x, chars)
local chars=chars or {}
return chars[x]
end
local singlechars = {} -- {a=true,i=true,z=true, v=true, u=true, o = true}
local initials = {}
local main_language = nil
-- when main_language is set, we will not use lang info in the nodes, but
-- main language instead
local get_language = function(lang)
return main_language or lang
end
local set_main_language = function(lang)
main_language = lang
end
local debug = false
local tex4ht = false
-- Enable processing only for certain letters
-- must be table in the {char = true, char2=true} form
local set_singlechars= function(lang,c)
--print("Set single chars lua")
if type(lang) == "table" then
for _,l in pairs(lang) do
singlechars[l] = c
end
else
local lang = tonumber(lang)
-- for k,_ in pairs(c) do print(k) end
singlechars[lang] = c
end
end
local set_initials = function(lang,c)
if type(lang) == "table" then
for _,l in pairs(lang) do
initials[l] = c
end
else
local lang = tonumber(lang)
initials[lang]=c
end
end
local debug_tex4ht = function(head,p)
--[[ local w = node.new("glyph")
w.lang = tex.lang
w.font = font.current()
w.char = 64
]]
--node.remove(head,node.prev(p))
local w = node.new("whatsit", "special")
w.data = "t4ht=<span style='background-color:red;width:2pt;'> </span>"
return w, head
end
local debug_node = function(head,p)
local w
if tex4ht then
w, head = debug_tex4ht(head,p)
else
w = node.new("whatsit","pdf_literal")
w.data = "q 1 0 1 RG 1 0 1 rg 0 0 m 0 5 l 2 5 l 2 0 l b Q"
end
node.insert_after(head,head,w)
node.insert_after(head,w,p)
-- return w
end
local set_debug= function(x)
debug = x
end
local set_tex4ht = function()
tex4ht = true
end
local insert_penalty = function(head)
local p = node.new("penalty")
p.penalty = 10000
local debug = debug or false
if debug then
local w = debug_node(head,p)
else
node.insert_after(head,head,p)
end
return head
end
local replace_with_thin_space = function(head)
local gluenode = node.new(node.id("glue"))
local gluespec = node.new(node.id("glue_spec"))
gluenode.width = tex.sp("0.2em")
-- gluenode.spec = gluespec
gluenode.next = head.next
gluenode.prev = head.prev
gluenode.next.prev = gluenode
gluenode.prev.next = gluenode
return gluenode
end
local is_alpha = function(c)
local status = alphas[c]
if not status then
status = utf_match(c, alpha)
alphas[c] = status
end
return status
end
-- find whether letter is uppercase
local up_table = {}
local is_uppercase= function(c)
if not is_alpha(c) then return false end
local status = up_table[c]
if status ~= nil then
return status
end
status = unicode.utf8.upper(c) == c
up_table[c] = status
return status
end
local is_number = function(word)
return tonumber(string.sub(word, -1)) ~= nil
end
local init_buffer = ""
local is_initial = function(c, lang)
return is_uppercase(c)
end
local cut_off_end_chars = function(word, dot)
local last = string.sub(word, -1)
while word ~= "" and (not dot or last ~= ".") and not is_alpha(last) do
word = string.sub(word, 1, -2) -- remove last char
last = string.sub(word, -1)
end
return word
end
local part_until_non_alpha = function(word)
for i = 1, #word do
local c = word:sub(i,i)
if not is_alpha(c) then
word = string.sub(word, 1, i-1)
break
end
end
return word
end
function Set (list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
local presi = (require "luavlna-presi")
local si = Set(require "luavlna-si")
local is_unit = function(word)
if M.no_unit==true then return false end
word = part_until_non_alpha(word)
if si[word] then
return true
end
for _, prefix in pairs(presi) do
s, e = string.find(word, prefix)
if s == 1 then
local unit = string.sub(word, e+1)
if si[unit] then
return true
end
end
end
return false
end
local predegrees = Set (require "luavlna-predegrees")
local sufdegrees = Set (require "luavlna-sufdegrees")
local function prevent_single_letter (head)
local singlechars = singlechars -- or {}
-- match_char matches all single letters, but this method is abbandoned
-- in favor of using table with enabled letters. With this method, multiple
-- languages are supported
local test_fn = match_table -- singlechars and match_table or match_char
local space = true
local init = false
local anchor = head
local wasnumber = false
local word = ""
local no_predegrees = M.no_predegrees
local no_sufdegrees = M.no_sufdegrees
local in_math = false
while head do
local id = head.id
local nextn = head.next
-- local skip = node.has_attribute(head, luatexbase.attributes.preventsinglestatus)
local skip = node.has_attribute(head, M.preventsingleid)
if id == math_id then
if head.subtype == 0 then
word = ""
in_math = true
else
in_math = false
if is_number(word) then wasnumber = true end
word = ""
end
end
if skip ~= 1 and not in_math then
if id == glue_id then
if wasnumber then
if word ~= "" then
wasnumber = false
word = cut_off_end_chars(word, false)
if is_unit(word) then
anchor = replace_with_thin_space(anchor)
insert_penalty(anchor.prev)
end
end
elseif is_number(word) then
wasnumber = true
else
word = cut_off_end_chars(word, true)
if no_predegrees ~= true and predegrees[word] then
insert_penalty(head.prev)
elseif no_sufdegrees ~= true and sufdegrees[word] then
insert_penalty(anchor.prev)
end
end
space=true
anchor = head
word = ""
init = is_initial " " -- reset initials
elseif space==true and id == glyph_id and is_alpha(utf_char(head.char)) then -- a letter
local lang = get_language(head.lang)
local char = utf_char(head.char)
word = char
init = is_initial(char,lang)
local s = singlechars[lang] or {} -- load singlechars for node's lang
--[[
for k, n in pairs(singlechars) do
for c,_ in pairs(n) do
--print(type(k), c)
end
end
--]]
if test_fn(char, s) and nextn ~= nil and nextn.id == glue_id then -- only if we are at a one letter word
head = insert_penalty(head)
end
space = false
-- handle initials
-- uppercase letter followed by period (code 46)
elseif init and head.id == glyph_id and head.char == period_char and nextn.id == glue_id and utf_len(word) == 1 then
head = insert_penalty(head)
elseif head.id == glyph_id then
local char = utf_char(head.char)
word = word .. char
init = is_initial(char, head.lang)
-- hlist support
elseif head.id == hlist_id then
prevent_single_letter(head.head)
-- vlist support
elseif head.id == vlist_id then
prevent_single_letter(head.head)
end
elseif id == glyph_id and in_math then
word = word .. utf_char(head.char)
end
head = head.next
end
return true
end
M.preventsingle = prevent_single_letter
M.singlechars = set_singlechars
M.initials = set_initials
M.set_tex4ht = set_tex4ht
M.debug = set_debug
M.set_main_language = set_main_language
return M
|