You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
14 KiB
466 lines
14 KiB
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
|
|
|
|
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 end_of_math = nuts.end_of_math
|
|
|
|
local nodecodes = nodes.nodecodes
|
|
----- glyph_code = nodecodes.glyph
|
|
local disc_code = nodecodes.disc
|
|
local math_code = nodecodes.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.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
|
|
|
|
if not setstate or not getstate then
|
|
-- generic (might move to the nod lib)
|
|
setstate = function(n,v)
|
|
setprop(n,"state",v)
|
|
end
|
|
getstate = function(n,v)
|
|
local s = getprop(n,"state")
|
|
if v then
|
|
return s == v
|
|
else
|
|
return s
|
|
end
|
|
end
|
|
nuts.setstate = setstate
|
|
nuts.getstate = getstate
|
|
end
|
|
|
|
-- 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,
|
|
}
|
|
|
|
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
|
|
|
|
-- done can go away as can tonut
|
|
|
|
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
|
|
current = tonut(current)
|
|
while current do
|
|
local char, id = ischar(current,font)
|
|
if char and not getstate(current) then
|
|
done = true
|
|
local d = descriptions[char]
|
|
if d then
|
|
if d.class == "mark" then
|
|
done = true
|
|
setstate(current,s_mark)
|
|
elseif useunicodemarks and categories[char] == "mn" then
|
|
done = true
|
|
setstate(current,s_mark)
|
|
elseif n == 0 then
|
|
first, last, n = current, current, 1
|
|
setstate(current,s_init)
|
|
else
|
|
last, n = current, n+1
|
|
setstate(current,s_medi)
|
|
end
|
|
else -- finish
|
|
if first and first == last then
|
|
setstate(last,s_isol)
|
|
elseif last then
|
|
setstate(last,s_fina)
|
|
end
|
|
first, last, n = nil, nil, 0
|
|
end
|
|
elseif char == false then
|
|
-- other font
|
|
if first and first == last then
|
|
setstate(last,s_isol)
|
|
elseif last then
|
|
setstate(last,s_fina)
|
|
end
|
|
first, last, n = nil, nil, 0
|
|
if id == math_code then
|
|
current = 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 and first == last then
|
|
setstate(last,s_isol)
|
|
elseif last then
|
|
setstate(last,s_fina)
|
|
end
|
|
first, last, n = nil, nil, 0
|
|
if id == math_code then
|
|
current = end_of_math(current)
|
|
end
|
|
end
|
|
current = getnext(current)
|
|
end
|
|
if first and first == last then
|
|
setstate(last,s_isol)
|
|
elseif last then
|
|
setstate(last,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
|
|
|
|
local mappers = allocate {
|
|
l = s_init, -- left
|
|
d = s_medi, -- double
|
|
c = s_medi, -- joiner
|
|
r = s_fina, -- right
|
|
u = s_isol, -- nonjoiner
|
|
}
|
|
|
|
-- we can also use this trick for devanagari
|
|
|
|
local classifiers = characters.classifiers
|
|
|
|
if not classifiers then
|
|
|
|
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 = table.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
|
|
|
|
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)
|
|
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, last = nil, 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, last = nil, 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, last = nil, 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, last = nil, 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)
|
|
end
|
|
end
|
|
current = getnext(current)
|
|
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
|
|
|
|
directives.register("otf.analyze.useunicodemarks",function(v)
|
|
analyzers.useunicodemarks = v
|
|
end)
|
|
|