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