if not modules then modules = { } end modules ['typo-cap'] = { version = 1.001, optimize = true, comment = "companion to typo-cap.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- see typo-cap.lua for a word traverser variant local next, type, tonumber = next, type, tonumber local format, insert = string.format, table.insert local div, getrandom, random = math.div, utilities.randomizer.get, math.random local trace_casing = false trackers.register("typesetters.casing", function(v) trace_casing = v end) local report_casing = logs.reporter("typesetting","casing") local nodes, node = nodes, node local nuts = nodes.nuts local getnext = nuts.getnext local getid = nuts.getid local getattr = nuts.getattr local getcharspec = nuts.getcharspec local getsubtype = nuts.getsubtype local getchar = nuts.getchar local isglyph = nuts.isglyph local getdisc = nuts.getdisc local setchar = nuts.setchar local setfont = nuts.setfont local setscales = nuts.setscales local copy_node = nuts.copy local endofmath = nuts.endofmath local insertafter = nuts.insertafter local findattribute = nuts.findattribute ----- unsetattributes = nuts.unsetattributes local nextglyph = nuts.traversers.glyph local nodecodes = nodes.nodecodes local kerncodes = nodes.kerncodes local glyph_code = nodecodes.glyph local kern_code = nodecodes.kern local disc_code = nodecodes.disc local math_code = nodecodes.math local fontkern_code = kerncodes.fontkern local enableaction = nodes.tasks.enableaction local newkern = nuts.pool.kern local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local fontchar = fonthashes.characters local currentfont = font.current local variables = interfaces.variables local v_reset = variables.reset local texsetattribute = tex.setattribute local texgetattribute = tex.getattribute local texgetscales = tex.getglyphscales local integer_value = tokens.values.integer typesetters = typesetters or { } local typesetters = typesetters typesetters.cases = typesetters.cases or { } local cases = typesetters.cases cases.actions = { } local actions = cases.actions local a_cases = attributes.private("case") local run = 0 -- a trick to make neighbouring ranges work local registervalue = attributes.registervalue local getvalue = attributes.getvalue local function set(tag,font,category,xscale,yscale) run = run + 1 local scales = { texgetscales() } local settings = { font = font, tag = tag, run = run, -- still needed ? category = category or 0, scales = scales, } if xscale and xscale ~= 1000 then scales[2] = scales[2] * xscale / 1000 end if yscale and yscale ~= 1000 then scales[3] = scales[3] * yscale / 1000 end texsetattribute(a_cases,registervalue(a_cases,settings)) end -- a previous implementation used char(0) as placeholder for the larger font, so we needed -- to remove it before it can do further harm ... that was too tricky as we use char 0 for -- other cases too -- -- we could do the whole glyph run here (till no more attributes match) but then we end up -- with more code .. maybe i will clean this up anyway as the lastfont hack is somewhat ugly -- ... on the other hand, we need to deal with cases like: -- -- \WORD {far too \Word{many \WORD{more \word{pushed} in between} useless} words} local uccodes = characters.uccodes local lccodes = characters.lccodes local categories = characters.categories -- true false true == mixed local function replacer(start,codes) local char, fnt = isglyph(start) local dc = codes[char] if dc then local ifc = fontchar[fnt] if type(dc) == "table" then for i=1,#dc do if not ifc[dc[i]] then return start, false end end for i=#dc,1,-1 do local chr = dc[i] if i == 1 then setchar(start,chr) else local g = copy_node(start) setchar(g,chr) insertafter(start,start,g) end end elseif ifc[dc] then setchar(start,dc) end end return start end local registered, n = { }, 0 local function register(name,f) if type(f) == "function" then n = n + 1 actions[n] = f registered[name] = n return n else local n = registered[f] registered[name] = n return n end end cases.register = register local function WORD(start,data,lastfont,n,count,where,first) lastfont[n] = false return replacer(first or start,uccodes) end local function word(start,data,lastfont,n,count,where,first) lastfont[n] = false return replacer(first or start,lccodes) end local function Words(start,data,lastfont,n,count,where,first) -- looks quite complex if where == "post" then return end if count == 1 and where ~= "post" then replacer(first or start,uccodes) return start, true else return start, true end end local function Word(start,data,lastfont,n,count,where,first) data.blocked = true return Words(start,data,lastfont,n,count,where,first) end local function camel(start,data,lastfont,n,count,where,first) word(start,data,lastfont,n,count,where,first) Words(start,data,lastfont,n,count,where,first) return start, true end local function mixed(start,data,lastfont,n,count,where,first,keep) if where == "post" then return end local used = first or start local char = getchar(used) local dc = uccodes[char] if not dc then -- quit elseif dc == char then local lfa = lastfont[n] if lfa then local s = data.scales setfont(used,lfa) if s then setscales(used,s[1],s[2],s[3]) end end elseif not keep then replacer(used,uccodes) end return start, true end local function Camel(start,data,lastfont,n,count,where,first) return mixed(start,data,lastfont,n,count,where,first,true) end local function Capital(start,data,lastfont,n,count,where,first,once) -- 3 local used = first or start if count == 1 and where ~= "post" then local lfa = lastfont[n] if lfa then local dc = uccodes[getchar(used)] if dc then local s = data.scales setfont(used,lfa) if s then setscales(used,s[1],s[2],s[3]) end end end end local s, c = replacer(first or start,uccodes) if once then lastfont[n] = false -- here end return start, c end local function capital(start,data,lastfont,n,where,count,first,count) -- 4 return Capital(start,data,lastfont,n,where,count,first,true) end local function none(start,data,lastfont,n,count,where,first) return start, true end local function randomized(start,data,lastfont,n,count,where,first) local used = first or start local char, font = getcharfont(used) local tfm = fontchar[font] lastfont[n] = false local kind = categories[char] if kind == "lu" then while true do local n = getrandom("capital lu",0x41,0x5A) if tfm[n] then -- this also intercepts tables setchar(used,n) return start end end elseif kind == "ll" then while true do local n = getrandom("capital ll",0x61,0x7A) if tfm[n] then -- this also intercepts tables setchar(used,n) return start end end end return start end register(variables.WORD, WORD) -- 1 register(variables.word, word) -- 2 register(variables.Word, Word) -- 3 register(variables.Words, Words) -- 4 register(variables.capital,capital) -- 5 register(variables.Capital,Capital) -- 6 register(variables.none, none) -- 7 (dummy) register(variables.random, randomized) -- 8 register(variables.mixed, mixed) -- 9 register(variables.camel, camel) -- 10 register(variables.Camel, Camel) -- 11 register(variables.cap, variables.capital) -- clone register(variables.Cap, variables.Capital) -- clone function cases.handler(head) local _, start = findattribute(head,a_cases) if start then local lastfont = { } local lastattr = nil local count = 0 while start do -- while because start can jump ahead local id = getid(start) if id == glyph_code then local attr = getattr(start,a_cases) if attr and attr > 0 then local data = getvalue(a_cases,attr) if data and not data.blocked then if attr ~= lastattr then lastattr = attr count = 1 else count = count + 1 end local tag = data.tag local font = data.font local run = data.run local action = actions[tag] -- map back to low number lastfont[tag] = font if action then local quit start, quit = action(start,data,lastfont,tag,count) if trace_casing then report_casing("case trigger %a, instance %a, fontid %a, result %a", tag,run,font,quit and "-" or "+") end elseif trace_casing then report_casing("unknown case trigger %a",tag) end end end elseif id == disc_code then local attr = getattr(start,a_cases) if attr and attr > 0 then local data = getvalue(a_cases,attr) if data and not data.blocked then if attr ~= lastattr then lastattr = attr count = 0 end local tag = data.tag local font = data.font local action = actions[tag] -- map back to low number lastfont[tag] = font if action then local pre, post, replace = getdisc(start) if replace then local cnt = count for g in nextglyph, replace do cnt = cnt + 1 getattr(g,a_cases) local h, quit = action(start,data,lastfont,tag,cnt,"replace",g) if quit then break end end end if pre then local cnt = count for g in nextglyph, pre do cnt = cnt + 1 getattr(g,a_cases) local h, quit = action(start,data,lastfont,tag,cnt,"pre",g) if quit then break end end end if post then local cnt = count for g in nextglyph, post do cnt = cnt + 1 getattr(g,a_cases) local h, quit = action(start,data,lastfont,tag,cnt,"post",g) if quit then break end end end end count = count + 1 end end else if id == math_code then start = endofmath(start) end count = 0 end if start then start = getnext(start) end end end return head end local enabled = false local function setcases(n,id,category,xscale,yscale) if n ~= v_reset then n = registered[n] or tonumber(n) if n then if not enabled then enableaction("processors","typesetters.cases.handler") if trace_casing then report_casing("enabling case handler") end enabled = true end set(n,id or currentfont(),category,xscale,yscale) return end end texsetattribute(a_cases) end cases.set = setcases -- interface interfaces.implement { name = "setcharactercasing", actions = function(name,category,xscale,yscale) setcases(name,false,category,xscale,yscale) end, arguments = { "argument", "integer", "integer", "integer" }, } interfaces.implement { name = "getcharactercasingcategory", public = true, usage = "value", actions = function() local v = getvalue(a_cases,texgetattribute(a_cases)) return integer_value, v and v.category or 0 end, } -- An example of a special plug, see type-imp-punk for usage. cases.register("randomvariant", function(start) local char, fnt = isglyph(start) local data = fontchar[fnt][char] if data then local variants = data.variants if variants then local n = #variants local i = getrandom("variant",1,n+1) if i > n then -- we keep the original else setchar(start,variants[i]) end return start, true end end return start, false end)