if not modules then modules = { } end modules ['font-otx'] = { version = 1.001, comment = "companion to font-otf.lua (analysing)", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- context only local type = type if not trackers then trackers = { register = function() end } end ----- trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) local fonts, nodes, node = fonts, nodes, node local allocate = utilities.storage.allocate local otf = fonts.handlers.otf local analyzers = fonts.analyzers local initializers = allocate() local methods = allocate() analyzers.initializers = initializers analyzers.methods = methods ---------.useunicodemarks = false local a_state = attributes.private('state') local nodecodes = nodes.nodecodes local glyph_code = nodecodes.glyph local math_code = nodecodes.math local traverse_id = node.traverse_id local traverse_node_list = node.traverse local end_of_math = node.end_of_math local fontdata = fonts.hashes.identifiers local categories = characters and characters.categories or { } -- sorry, only in context local chardata = characters and characters.data local otffeatures = fonts.constructors.newfeatures("otf") local registerotffeature = otffeatures.register --[[ldx--

Analyzers run per script and/or language and are needed in order to process features right.

--ldx]]-- -- never use these numbers directly local s_init = 1 local s_rphf = 7 local s_medi = 2 local s_half = 8 local s_fina = 3 local s_pref = 9 local s_isol = 4 local s_blwf = 10 local s_mark = 5 local s_pstf = 11 local s_rest = 6 local states = { init = s_init, medi = s_medi, fina = s_fina, isol = s_isol, mark = s_mark, rest = s_rest, rphf = s_rphf, half = s_half, pref = s_pref, blwf = s_blwf, pstf = s_pstf, } local features = { init = s_init, medi = s_medi, fina = s_fina, isol = s_isol, -- mark = s_mark, } analyzers.states = states analyzers.features = features analyzers.useunicodemarks = false -- todo: analyzers per script/lang, cross font, so we need an font id hash -> script -- e.g. latin -> hyphenate, arab -> 1/2/3 analyze -- its own namespace function analyzers.setstate(head,font) local useunicodemarks = analyzers.useunicodemarks local tfmdata = fontdata[font] local descriptions = tfmdata.descriptions local first, last, current, n, done = nil, nil, head, 0, false -- maybe make n boolean while current do local id = current.id if id == glyph_code and current.font == font then done = true local char = current.char local d = descriptions[char] if d then if d.class == "mark" then done = true current[a_state] = s_mark elseif useunicodemarks and categories[char] == "mn" then done = true current[a_state] = s_mark elseif n == 0 then first, last, n = current, current, 1 current[a_state] = s_init else last, n = current, n+1 current[a_state] = s_medi end else -- finish if first and first == last then last[a_state] = s_isol elseif last then last[a_state] = s_fina end first, last, n = nil, nil, 0 end elseif id == disc_code then -- always in the middle current[a_state] = s_midi last = current else -- finish if first and first == last then last[a_state] = s_isol elseif last then last[a_state] = s_fina end first, last, n = nil, nil, 0 if id == math_code then current = end_of_math(current) end end current = current.next end if first and first == last then last[a_state] = s_isol elseif last then last[a_state] = s_fina end return head, done end -- in the future we will use language/script attributes instead of the -- font related value, but then we also need dynamic features which is -- somewhat slower; and .. we need a chain of them local function analyzeinitializer(tfmdata,value) -- attr local script, language = otf.scriptandlanguage(tfmdata) -- attr local action = initializers[script] if not action then -- skip elseif type(action) == "function" then return action(tfmdata,value) else local action = action[language] if action then return action(tfmdata,value) end end end local function analyzeprocessor(head,font,attr) local tfmdata = fontdata[font] local script, language = otf.scriptandlanguage(tfmdata,attr) local action = methods[script] if not action then -- skip elseif type(action) == "function" then return action(head,font,attr) else action = action[language] if action then return action(head,font,attr) end end return head, false end registerotffeature { name = "analyze", description = "analysis of (for instance) character classes", default = true, initializers = { node = analyzeinitializer, }, processors = { position = 1, node = analyzeprocessor, } } -- latin methods.latn = analyzers.setstate local arab_warned = { } local function warning(current,what) local char = current.char if not arab_warned[char] then log.report("analyze","arab: character %C has no %a class",char,what) arab_warned[char] = true end end local mappers = { l = s_init, -- left d = s_medi, -- double c = s_medi, -- joiner r = s_fina, -- right u = s_isol, -- nonjoiner } local classifiers = { } -- we can also use this trick for devanagari local first_arabic, last_arabic = characters.blockrange("arabic") local first_syriac, last_syriac = characters.blockrange("syriac") local first_mandiac, last_mandiac = characters.blockrange("mandiac") local first_nko, last_nko = characters.blockrange("nko") table.setmetatableindex(classifiers,function(t,k) local c = chardata[k] local v = false if c then local arabic = c.arabic if arabic then v = mappers[arabic] if not v then log.report("analyze","error in mapping arabic %C",k) -- error v = false end elseif k >= first_arabic and k <= last_arabic or k >= first_syriac and k <= last_syriac or k >= first_mandiac and k <= last_mandiac or k >= first_nko and k <= last_nko then if categories[k] == "mn" then v = s_mark else v = s_rest end else end end t[k] = v return v end) function methods.arab(head,font,attr) local first, last = nil, nil local c_first, c_last = nil, nil local current, done = head, false while current do local id = current.id if id == glyph_code and current.font == font and current.subtype<256 and not current[a_state] then done = true local char = current.char local classifier = classifiers[char] if not classifier then if last then if c_last == s_medi or c_last == s_fina then last[a_state] = s_fina else warning(last,"fina") last[a_state] = s_error end first, last = nil, nil elseif first then if c_first == s_medi or c_first == s_fina then first[a_state] = s_isol else warning(first,"isol") first[a_state] = s_error end first = nil end elseif classifier == s_mark then current[a_state] = s_mark elseif classifier == s_isol then if last then if c_last == s_medi or c_last == s_fina then last[a_state] = s_fina else warning(last,"fina") last[a_state] = s_error end first, last = nil, nil elseif first then if c_first == s_medi or c_first == s_fina then first[a_state] = s_isol else warning(first,"isol") first[a_state] = s_error end first = nil end current[a_state] = s_isol elseif classifier == s_medi then if first then last = current c_last = classifier current[a_state] = s_medi else current[a_state] = s_init first = current c_first = classifier end elseif classifier == s_fina then if last then if last[a_state] ~= s_init then last[a_state] = s_medi end current[a_state] = s_fina first, last = nil, nil elseif first then -- if first[a_state] ~= s_init then -- -- needs checking -- first[a_state] = s_medi -- end current[a_state] = s_fina first = nil else current[a_state] = s_isol end else -- classifier == s_rest current[a_state] = s_rest if last then if c_last == s_medi or c_last == s_fina then last[a_state] = s_fina else warning(last,"fina") last[a_state] = s_error end first, last = nil, nil elseif first then if c_first == s_medi or c_first == s_fina then first[a_state] = s_isol else warning(first,"isol") first[a_state] = s_error end first = nil end end else if last then if c_last == s_medi or c_last == s_fina then last[a_state] = s_fina else warning(last,"fina") last[a_state] = s_error end first, last = nil, nil elseif first then if c_first == s_medi or c_first == s_fina then first[a_state] = s_isol else warning(first,"isol") first[a_state] = s_error end first = nil end if id == math_code then -- a bit duplicate as we test for glyphs twice current = end_of_math(current) end end current = current.next end if last then if c_last == s_medi or c_last == s_fina then last[a_state] = s_fina else warning(last,"fina") last[a_state] = s_error end elseif first then if c_first == s_medi or c_first == s_fina then first[a_state] = s_isol else warning(first,"isol") first[a_state] = s_error end end return head, done end methods.syrc = methods.arab methods.mand = methods.arab methods.nko = methods.arab directives.register("otf.analyze.useunicodemarks",function(v) analyzers.useunicodemarks = v end)