1if not modules then modules = { } end modules ['typo-cap'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to typo-cap.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files"
8}
9
10
11
12local next, type, tonumber = next, type, tonumber
13local format, insert = string.format, table.insert
14local div, getrandom, random = math.div, utilities.randomizer.get, math.random
15
16local trace_casing = false trackers.register("typesetters.casing", function(v) trace_casing = v end)
17
18local report_casing = logs.reporter("typesetting","casing")
19
20local nodes, node = nodes, node
21
22local nuts = nodes.nuts
23
24local getnext = nuts.getnext
25local getid = nuts.getid
26local getattr = nuts.getattr
27local getcharspec = nuts.getcharspec
28local getsubtype = nuts.getsubtype
29local getchar = nuts.getchar
30local isglyph = nuts.isglyph
31local getdisc = nuts.getdisc
32
33local setchar = nuts.setchar
34local setfont = nuts.setfont
35local setscales = nuts.setscales
36
37local copy_node = nuts.copy
38local endofmath = nuts.endofmath
39local insertafter = nuts.insertafter
40local findattribute = nuts.findattribute
41
42
43local nextglyph = nuts.traversers.glyph
44
45local nodecodes = nodes.nodecodes
46local kerncodes = nodes.kerncodes
47
48local glyph_code = nodecodes.glyph
49local kern_code = nodecodes.kern
50local disc_code = nodecodes.disc
51local math_code = nodecodes.math
52
53local fontkern_code = kerncodes.fontkern
54
55local enableaction = nodes.tasks.enableaction
56
57local newkern = nuts.pool.kern
58
59local fonthashes = fonts.hashes
60local fontdata = fonthashes.identifiers
61local fontchar = fonthashes.characters
62
63local currentfont = font.current
64
65local variables = interfaces.variables
66local v_reset = variables.reset
67
68local texsetattribute = tex.setattribute
69local texgetattribute = tex.getattribute
70local texgetscales = tex.getglyphscales
71
72local integer_value = tokens.values.integer
73
74typesetters = typesetters or { }
75local typesetters = typesetters
76
77typesetters.cases = typesetters.cases or { }
78local cases = typesetters.cases
79
80cases.actions = { }
81local actions = cases.actions
82local a_cases = attributes.private("case")
83
84local run = 0
85
86local registervalue = attributes.registervalue
87local getvalue = attributes.getvalue
88
89local function set(tag,font,category,xscale,yscale)
90 run = run + 1
91 local scales = { texgetscales() }
92 local settings = {
93 font = font,
94 tag = tag,
95 run = run,
96 category = category or 0,
97 scales = scales,
98 }
99 if xscale and xscale ~= 1000 then
100 scales[2] = scales[2] * xscale / 1000
101 end
102 if yscale and yscale ~= 1000 then
103 scales[3] = scales[3] * yscale / 1000
104 end
105 texsetattribute(a_cases,registervalue(a_cases,settings))
106end
107
108
109
110
111
112
113
114
115
116
117
118local uccodes = characters.uccodes
119local lccodes = characters.lccodes
120local categories = characters.categories
121
122
123
124local function replacer(start,codes)
125 local char, fnt = isglyph(start)
126 local dc = codes[char]
127 if dc then
128 local ifc = fontchar[fnt]
129 if type(dc) == "table" then
130 for i=1,#dc do
131 if not ifc[dc[i]] then
132 return start, false
133 end
134 end
135 for i=#dc,1,-1 do
136 local chr = dc[i]
137 if i == 1 then
138 setchar(start,chr)
139 else
140 local g = copy_node(start)
141 setchar(g,chr)
142 insertafter(start,start,g)
143 end
144 end
145 elseif ifc[dc] then
146 setchar(start,dc)
147 end
148 end
149 return start
150end
151
152local registered, n = { }, 0
153
154local function register(name,f)
155 if type(f) == "function" then
156 n = n + 1
157 actions[n] = f
158 registered[name] = n
159 return n
160 else
161 local n = registered[f]
162 registered[name] = n
163 return n
164 end
165end
166
167cases.register = register
168
169local function WORD(start,data,lastfont,n,count,where,first)
170 lastfont[n] = false
171 return replacer(first or start,uccodes)
172end
173
174local function word(start,data,lastfont,n,count,where,first)
175 lastfont[n] = false
176 return replacer(first or start,lccodes)
177end
178
179local function Words(start,data,lastfont,n,count,where,first)
180 if where == "post" then
181 return
182 end
183 if count == 1 and where ~= "post" then
184 replacer(first or start,uccodes)
185 return start, true
186 else
187 return start, true
188 end
189end
190
191local function Word(start,data,lastfont,n,count,where,first)
192 data.blocked = true
193 return Words(start,data,lastfont,n,count,where,first)
194end
195
196local function camel(start,data,lastfont,n,count,where,first)
197 word(start,data,lastfont,n,count,where,first)
198 Words(start,data,lastfont,n,count,where,first)
199 return start, true
200end
201
202local function mixed(start,data,lastfont,n,count,where,first,keep)
203 if where == "post" then
204 return
205 end
206 local used = first or start
207 local char = getchar(used)
208 local dc = uccodes[char]
209 if not dc then
210
211 elseif dc == char then
212 local lfa = lastfont[n]
213 if lfa then
214 local s = data.scales
215 setfont(used,lfa)
216 if s then
217 setscales(used,s[1],s[2],s[3])
218 end
219 end
220 elseif not keep then
221 replacer(used,uccodes)
222 end
223 return start, true
224end
225
226local function Camel(start,data,lastfont,n,count,where,first)
227 return mixed(start,data,lastfont,n,count,where,first,true)
228end
229
230local function Capital(start,data,lastfont,n,count,where,first,once)
231 local used = first or start
232 if count == 1 and where ~= "post" then
233 local lfa = lastfont[n]
234 if lfa then
235 local dc = uccodes[getchar(used)]
236 if dc then
237 local s = data.scales
238 setfont(used,lfa)
239 if s then
240 setscales(used,s[1],s[2],s[3])
241 end
242 end
243 end
244 end
245 local s, c = replacer(first or start,uccodes)
246 if once then
247 lastfont[n] = false
248 end
249 return start, c
250end
251
252local function capital(start,data,lastfont,n,where,count,first,count)
253 return Capital(start,data,lastfont,n,where,count,first,true)
254end
255
256local function none(start,data,lastfont,n,count,where,first)
257 return start, true
258end
259
260local function randomized(start,data,lastfont,n,count,where,first)
261 local used = first or start
262 local char,
263 font = getcharfont(used)
264 local tfm = fontchar[font]
265 lastfont[n] = false
266 local kind = categories[char]
267 if kind == "lu" then
268 while true do
269 local n = getrandom("capital lu",0x41,0x5A)
270 if tfm[n] then
271 setchar(used,n)
272 return start
273 end
274 end
275 elseif kind == "ll" then
276 while true do
277 local n = getrandom("capital ll",0x61,0x7A)
278 if tfm[n] then
279 setchar(used,n)
280 return start
281 end
282 end
283 end
284 return start
285end
286
287register(variables.WORD, WORD)
288register(variables.word, word)
289register(variables.Word, Word)
290register(variables.Words, Words)
291register(variables.capital,capital)
292register(variables.Capital,Capital)
293register(variables.none, none)
294register(variables.random, randomized)
295register(variables.mixed, mixed)
296register(variables.camel, camel)
297register(variables.Camel, Camel)
298
299register(variables.cap, variables.capital)
300register(variables.Cap, variables.Capital)
301
302function cases.handler(head)
303 local _, start = findattribute(head,a_cases)
304 if start then
305 local lastfont = { }
306 local lastattr = nil
307 local count = 0
308 while start do
309 local id = getid(start)
310 if id == glyph_code then
311 local attr = getattr(start,a_cases)
312 if attr and attr > 0 then
313 local data = getvalue(a_cases,attr)
314 if data and not data.blocked then
315 if attr ~= lastattr then
316 lastattr = attr
317 count = 1
318 else
319 count = count + 1
320 end
321 local tag = data.tag
322 local font = data.font
323 local run = data.run
324 local action = actions[tag]
325 lastfont[tag] = font
326 if action then
327 local quit
328 start, quit = action(start,data,lastfont,tag,count)
329 if trace_casing then
330 report_casing("case trigger %a, instance %a, fontid %a, result %a",
331 tag,run,font,quit and "-" or "+")
332 end
333 elseif trace_casing then
334 report_casing("unknown case trigger %a",tag)
335 end
336 end
337 end
338 elseif id == disc_code then
339 local attr = getattr(start,a_cases)
340 if attr and attr > 0 then
341 local data = getvalue(a_cases,attr)
342 if data and not data.blocked then
343 if attr ~= lastattr then
344 lastattr = attr
345 count = 0
346 end
347 local tag = data.tag
348 local font = data.font
349 local action = actions[tag]
350 lastfont[tag] = font
351 if action then
352 local pre, post, replace = getdisc(start)
353 if replace then
354 local cnt = count
355 for g in nextglyph, replace do
356 cnt = cnt + 1
357 getattr(g,a_cases)
358 local h, quit = action(start,data,lastfont,tag,cnt,"replace",g)
359 if quit then
360 break
361 end
362 end
363 end
364 if pre then
365 local cnt = count
366 for g in nextglyph, pre do
367 cnt = cnt + 1
368 getattr(g,a_cases)
369 local h, quit = action(start,data,lastfont,tag,cnt,"pre",g)
370 if quit then
371 break
372 end
373 end
374 end
375 if post then
376 local cnt = count
377 for g in nextglyph, post do
378 cnt = cnt + 1
379 getattr(g,a_cases)
380 local h, quit = action(start,data,lastfont,tag,cnt,"post",g)
381 if quit then
382 break
383 end
384 end
385 end
386 end
387 count = count + 1
388 end
389 end
390 else
391 if id == math_code then
392 start = endofmath(start)
393 end
394 count = 0
395 end
396 if start then
397 start = getnext(start)
398 end
399 end
400 end
401 return head
402end
403
404local enabled = false
405
406local function setcases(n,id,category,xscale,yscale)
407 if n ~= v_reset then
408 n = registered[n] or tonumber(n)
409 if n then
410 if not enabled then
411 enableaction("processors","typesetters.cases.handler")
412 if trace_casing then
413 report_casing("enabling case handler")
414 end
415 enabled = true
416 end
417 set(n,id or currentfont(),category,xscale,yscale)
418 return
419 end
420 end
421 texsetattribute(a_cases)
422end
423
424cases.set = setcases
425
426
427
428interfaces.implement {
429 name = "setcharactercasing",
430 actions = function(name,category,xscale,yscale)
431 setcases(name,false,category,xscale,yscale)
432 end,
433 arguments = { "argument", "integer", "integer", "integer" },
434}
435
436interfaces.implement {
437 name = "getcharactercasingcategory",
438 public = true,
439 usage = "value",
440 actions = function()
441 local v = getvalue(a_cases,texgetattribute(a_cases))
442 return integer_value, v and v.category or 0
443 end,
444}
445
446
447
448cases.register("randomvariant", function(start)
449 local char, fnt = isglyph(start)
450 local data = fontchar[fnt][char]
451 if data then
452 local variants = data.variants
453 if variants then
454 local n = #variants
455 local i = getrandom("variant",1,n+1)
456 if i > n then
457
458 else
459 setchar(start,variants[i])
460 end
461 return start, true
462 end
463 end
464 return start, false
465end)
466 |