diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/font-ota.lmt')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkxl/font-ota.lmt | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/font-ota.lmt b/Master/texmf-dist/tex/context/base/mkxl/font-ota.lmt new file mode 100644 index 00000000000..6475c57e64b --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkxl/font-ota.lmt @@ -0,0 +1,580 @@ +if not modules then modules = { } end modules ['font-ota'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- context only + +local type = type +local setmetatableindex = table.setmetatableindex + +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 + +local nuts = nodes.nuts +local tonut = nuts.tonut + +local getnext = nuts.getnext +local getprev = nuts.getprev +local getprev = nuts.getprev +local getprop = nuts.getprop +local setprop = nuts.setprop +local getsubtype = nuts.getsubtype +local getchar = nuts.getchar +local ischar = nuts.ischar +local isnextchar = nuts.isnextchar +----- isprevchar = nuts.isprevchar + +local end_of_math = nuts.end_of_math + +local nodecodes = nodes.nodecodes +local disc_code = nodecodes.disc +local math_code = nodecodes.math + +local fontdata = fonts.hashes.identifiers +local descriptiondata = fonts.hashes.descriptions +local categories = characters and characters.categories or { } -- sorry, only in context +local chardata = characters and characters.data + +local otffeatures = fonts.constructors.features.otf +local registerotffeature = otffeatures.register + +--[[ldx-- +<p>Analyzers run per script and/or language and are needed in order to +process features right.</p> +--ldx]]-- + +local setstate = nuts.setstate +local getstate = nuts.getstate + +local classifiers = characters.classifiers + +-- 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 = allocate { + init = s_init, + medi = s_medi, + med2 = s_medi, + fina = s_fina, + fin2 = s_fina, + fin3 = 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 = allocate { + init = s_init, + medi = s_medi, + med2 = s_medi, + fina = s_fina, + fin2 = s_fina, + fin3 = 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 mappers = allocate { + l = s_init, -- left + d = s_medi, -- double + c = s_medi, -- joiner + r = s_fina, -- right + u = s_isol, -- nonjoiner +} + +analyzers.states = states +analyzers.features = features +analyzers.useunicodemarks = false + +if not classifiers then + + -- why not just always use categories[k] = "mn" + + local f_arabic, l_arabic = characters.blockrange("arabic") + local f_syriac, l_syriac = characters.blockrange("syriac") + local f_mandiac, l_mandiac = characters.blockrange("mandiac") + local f_nko, l_nko = characters.blockrange("nko") + local f_ext_a, l_ext_a = characters.blockrange("arabicextendeda") + + classifiers = setmetatableindex(function(t,k) + if type(k) == "number" then + 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 >= f_arabic and k <= l_arabic) or + (k >= f_syriac and k <= l_syriac) or + (k >= f_mandiac and k <= l_mandiac) or + (k >= f_nko and k <= l_nko) or + (k >= f_ext_a and k <= l_ext_a) then + if categories[k] == "mn" then + v = s_mark + else + v = s_rest + end + end + end + t[k] = v + return v + end + end) + + characters.classifiers = classifiers + +end + +-- 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 + +local is_letter = characters.is_letter +local is_mark = characters.is_mark + +function analyzers.setstate(head,font) -- latin + local useunicodemarks = analyzers.useunicodemarks + local descriptions = descriptiondata[font] + local first = false + local last = false + local current = head + local done = false + -- only letters + while current do + -- local char, id = ischar(current,font) + local nxt, char, id = isnextchar(current,font) + if char then + if not getstate(current) then + -- local d = descriptions[char] + -- if d then + -- if d.class == "mark" or (useunicodemarks and categories[char] == "mn") then + if is_mark[char] then + setstate(current,s_mark) + elseif is_letter[char] then + if first then + setstate(current,s_medi) + else + setstate(current,s_init) + first = current + done = true + end + last = current + else + goto PICKUP + end + goto NEXT + -- end + end + ::PICKUP:: + if first then + setstate(last,first == last and s_isol or s_fina) + end + first = false + elseif char == false then + -- other font + if first then + setstate(last,first == last and s_isol or s_fina) + end + first = false + if id == math_code then + -- current = end_of_math(current) + nxt = getnext(end_of_math(current)) + end + elseif id == disc_code then + -- always in the middle .. it doesn't make much sense to assign a property + -- here ... we might at some point decide to flag the components when present + -- but even then it's kind of bogus + setstate(current,s_medi) + last = current + else -- finish + if first then + setstate(last,first == last and s_isol or s_fina) + end + first = false + if id == math_code then + current = end_of_math(current) + end + end + ::NEXT:: + -- current = getnext(current) + current = nxt + end + if first then + setstate(last,first == last and s_isol or 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 character classes", + default = true, + initializers = { + node = analyzeinitializer, + }, + processors = { + position = 1, + node = analyzeprocessor, + } +} + +-- latin + +methods.latn = analyzers.setstate +-------.dflt = analyzers.setstate % can be an option or just the default + +local arab_warned = { } + +local function warning(current,what) + local char = getchar(current) + if not arab_warned[char] then + log.report("analyze","arab: character %C has no %a class",char,what) + arab_warned[char] = true + end +end + +-- we can also use this trick for devanagari + +function methods.arab(head,font,attr) + local first, last, c_first, c_last + local current = head + local done = false + current = tonut(current) + while current do + -- local char, id = ischar(current,font) + local nxt, char, id = isnextchar(current,font) + if char and not getstate(current) then + done = true + local classifier = classifiers[char] + if not classifier then + if last then + if c_last == s_medi or c_last == s_fina then + setstate(last,s_fina) + else + warning(last,"fina") + setstate(last,s_error) + end + first = nil + last = nil + elseif first then + if c_first == s_medi or c_first == s_fina then + setstate(first,s_isol) + else + warning(first,"isol") + setstate(first,s_error) + end + first = nil + end + elseif classifier == s_mark then + setstate(current,s_mark) + elseif classifier == s_isol then + if last then + if c_last == s_medi or c_last == s_fina then + setstate(last,s_fina) + else + warning(last,"fina") + setstate(last,s_error) + end + first = nil + last = nil + elseif first then + if c_first == s_medi or c_first == s_fina then + setstate(first,s_isol) + else + warning(first,"isol") + setstate(first,s_error) + end + first = nil + end + setstate(current,s_isol) + elseif classifier == s_medi then + if first then + last = current + c_last = classifier + setstate(current,s_medi) + else + setstate(current,s_init) + first = current + c_first = classifier + end + elseif classifier == s_fina then + if last then + if getstate(last) ~= s_init then + setstate(last,s_medi) + end + setstate(current,s_fina) + first, last = nil, nil + elseif first then + -- if getstate(first) ~= s_init then + -- -- needs checking + -- setstate(first,s_medi) + -- end + setstate(current,s_fina) + first = nil + else + setstate(current,s_isol) + end + else -- classifier == s_rest + setstate(current,s_rest) + if last then + if c_last == s_medi or c_last == s_fina then + setstate(last,s_fina) + else + warning(last,"fina") + setstate(last,s_error) + end + first = nil + last = nil + elseif first then + if c_first == s_medi or c_first == s_fina then + setstate(first,s_isol) + else + warning(first,"isol") + setstate(first,s_error) + end + first = nil + end + end + else + if last then + if c_last == s_medi or c_last == s_fina then + setstate(last,s_fina) + else + warning(last,"fina") + setstate(last,s_error) + end + first = nil + last = nil + elseif first then + if c_first == s_medi or c_first == s_fina then + setstate(first,s_isol) + else + warning(first,"isol") + setstate(first,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) + nxt = getnext(end_of_math(current)) + end + end + -- current = getnext(current) + current = nxt + end + if last then + if c_last == s_medi or c_last == s_fina then + setstate(last,s_fina) + else + warning(last,"fina") + setstate(last,s_error) + end + elseif first then + if c_first == s_medi or c_first == s_fina then + setstate(first,s_isol) + else + warning(first,"isol") + setstate(first,s_error) + end + end + return head, done +end + +methods.syrc = methods.arab +methods.mand = methods.arab +methods.nko = methods.arab + +-- a quick first attemp .. more later + +do + + -- https://github.com/n8willis/opentype-shaping-documents/blob/master/opentype-shaping-mongolian.md#joining-properties + -- todo syrc + + local joining = setmetatableindex(function(t,k) + if type(k) == "number" then + local c = chardata[k] + local v = false + if c then + local mongolian = c.mongolian + -- + v = mongolian + end + t[k] = v + return v + end + end) + + function methods.mong(head,font,attr) + local first, last + local current = head + local done = false + local prevjoin = nil + local prestate = nil + current = tonut(current) + + local function wrapup() + if last then + if last ~= first then + local s = getstate(last) + if s == s_medi then + setstate(last,s_fina) + elseif s == s_init then + setstate(last,s_isol) + end + end + last = nil + first = nil + prevjoin = nil + prestate = nil + end + end + + while current do + -- local char, id = ischar(current,font) + local nxt, char, id = isnextchar(current,font) + if char and not getstate(current) then + local currjoin = joining[char] + done = true + if not last then + setstate(current,s_isol) + prevjoin = currjoin + first = current + last = current + prevstate = s_isol + elseif currjoin == "t" then -- transparent + -- keep state + last = current + elseif prevjoin == "d" or prevjoin == "jc" or prevjoin == "l" then + if currjoin == "d" or prevjoin == "jc" or prevjoin == "r" then + local s = getstate(last) + if s == s_isol then + setstate(last,s_init) + elseif s == s_fina then + setstate(last,s_medi) + end + setstate(current,s_fina) + prevstate = s_fina + elseif prevjoin == "nj" or prevjoin == "l" then + local s = getstate(last) + if s == s_medi then + setstate(last,s_fina) + elseif s == s_init then + setstate(last,s_isol) + end + setstate(current,s_isol) + prevstate = s_isol + end + prevjoin = currjoin + last = current + elseif prevjoin == "nj" or prevjoin == "r" then + if s == s_medi then + setstate(last,s_fina) + elseif s == s_init then + setstate(last,s_isol) + end + setstate(current,s_isol) + prevjoin = currjoin + prevstate = s_isol + last = current + elseif last then + wrapup() + end + else + if last then + wrapup() + end + if id == math_code then -- a bit duplicate as we test for glyphs twice + -- current = end_of_math(current) + nxt = getnext(end_of_math(current)) + end + end + -- current = getnext(current) + current = nxt + end + if last then + wrapup() + end + return head, done + end + +end + +directives.register("otf.analyze.useunicodemarks",function(v) + analyzers.useunicodemarks = v +end) |