summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/font-shp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/font-shp.lua')
-rw-r--r--tex/context/base/mkiv/font-shp.lua410
1 files changed, 410 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/font-shp.lua b/tex/context/base/mkiv/font-shp.lua
new file mode 100644
index 000000000..6e21848a4
--- /dev/null
+++ b/tex/context/base/mkiv/font-shp.lua
@@ -0,0 +1,410 @@
+if not modules then modules = { } end modules ['font-shp'] = {
+ 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"
+}
+
+local tonumber = tonumber
+local concat = table.concat
+local formatters = string.formatters
+
+local otf = fonts.handlers.otf
+local afm = fonts.handlers.afm
+
+local hashes = fonts.hashes
+local identifiers = hashes.identifiers
+
+local version = 0.007
+local shapescache = containers.define("fonts", "shapes", version, true)
+local streamscache = containers.define("fonts", "streams", version, true)
+
+-- shapes (can be come a separate file at some point)
+
+local function packoutlines(data,makesequence)
+ local subfonts = data.subfonts
+ if subfonts then
+ for i=1,#subfonts do
+ packoutlines(subfonts[i],makesequence)
+ end
+ return
+ end
+ local common = data.segments
+ if common then
+ return
+ end
+ local glyphs = data.glyphs
+ if not glyphs then
+ return
+ end
+ if makesequence then
+ for index=1,#glyphs do
+ local glyph = glyphs[index]
+ local segments = glyph.segments
+ if segments then
+ local sequence = { }
+ local nofsequence = 0
+ for i=1,#segments do
+ local segment = segments[i]
+ local nofsegment = #segment
+ nofsequence = nofsequence + 1
+ sequence[nofsequence] = segment[nofsegment]
+ for i=1,nofsegment-1 do
+ nofsequence = nofsequence + 1
+ sequence[nofsequence] = segment[i]
+ end
+ end
+ glyph.sequence = sequence
+ glyph.segments = nil
+ end
+ end
+ else
+ local hash = { }
+ local common = { }
+ local reverse = { }
+ local last = 0
+ for index=1,#glyphs do
+ local segments = glyphs[index].segments
+ if segments then
+ for i=1,#segments do
+ local h = concat(segments[i]," ")
+ hash[h] = (hash[h] or 0) + 1
+ end
+ end
+ end
+ for index=1,#glyphs do
+ local segments = glyphs[index].segments
+ if segments then
+ for i=1,#segments do
+ local segment = segments[i]
+ local h = concat(segment," ")
+ if hash[h] > 1 then -- minimal one shared in order to hash
+ local idx = reverse[h]
+ if not idx then
+ last = last + 1
+ reverse[h] = last
+ common[last] = segment
+ idx = last
+ end
+ segments[i] = idx
+ end
+ end
+ end
+ end
+ if last > 0 then
+ data.segments = common
+ end
+ end
+end
+
+local function unpackoutlines(data)
+ local subfonts = data.subfonts
+ if subfonts then
+ for i=1,#subfonts do
+ unpackoutlines(subfonts[i])
+ end
+ return
+ end
+ local common = data.segments
+ if not common then
+ return
+ end
+ local glyphs = data.glyphs
+ if not glyphs then
+ return
+ end
+ for index=1,#glyphs do
+ local segments = glyphs[index].segments
+ if segments then
+ for i=1,#segments do
+ local c = common[segments[i]]
+ if c then
+ segments[i] = c
+ end
+ end
+ end
+ end
+ data.segments = nil
+end
+
+-- todo: loaders per format
+
+local readers = otf.readers
+local cleanname = readers.helpers.cleanname
+
+local function makehash(filename,sub,instance)
+ local name = cleanname(file.basename(filename))
+ if instance then
+ return formatters["%s-%s-%s"](name,sub or 0,cleanname(instance))
+ else
+ return formatters["%s-%s"] (name,sub or 0)
+ end
+end
+
+local function loadoutlines(cache,filename,sub,instance)
+ local base = file.basename(filename)
+ local name = file.removesuffix(base)
+ local kind = file.suffix(filename)
+ local attr = lfs.attributes(filename)
+ local size = attr and attr.size or 0
+ local time = attr and attr.modification or 0
+ local sub = tonumber(sub)
+
+ -- fonts.formats
+
+ if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then
+ local hash = makehash(filename,sub,instance)
+ data = containers.read(cache,hash)
+ if not data or data.time ~= time or data.size ~= size then
+ data = readers.loadshapes(filename,sub,instance)
+ if data then
+ data.size = size
+ data.format = data.format or (kind == "otf" and "opentype") or "truetype"
+ data.time = time
+ packoutlines(data)
+ containers.write(cache,hash,data)
+ data = containers.read(cache,hash) -- frees old mem
+ end
+ end
+ unpackoutlines(data)
+ elseif size > 0 and (kind == "pfb") then
+ local hash = containers.cleanname(base) -- including suffix
+ data = containers.read(cache,hash)
+ if not data or data.time ~= time or data.size ~= size then
+ data = afm.readers.loadshapes(filename)
+ if data then
+ data.size = size
+ data.format = "type1"
+ data.time = time
+ packoutlines(data)
+ containers.write(cache,hash,data)
+ data = containers.read(cache,hash) -- frees old mem
+ end
+ end
+ unpackoutlines(data)
+ else
+ data = {
+ filename = filename,
+ size = 0,
+ time = time,
+ format = "unknown",
+ units = 1000,
+ glyphs = { }
+ }
+ end
+ return data
+end
+
+local function loadstreams(cache,filename,sub,instance)
+ local base = file.basename(filename)
+ local name = file.removesuffix(base)
+ local kind = file.suffix(filename)
+ local attr = lfs.attributes(filename)
+ local size = attr and attr.size or 0
+ local time = attr and attr.modification or 0
+ local sub = tonumber(sub)
+
+ -- fonts.formats
+
+ if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then
+ local hash = makehash(filename,sub,instance)
+ data = containers.read(cache,hash)
+ if not data or data.time ~= time or data.size ~= size then
+ data = readers.loadshapes(filename,sub,instance,true)
+ if data then
+ local glyphs = data.glyphs
+ local streams = { }
+ if glyphs then
+ for i=0,#glyphs do
+ streams[i] = glyphs[i].stream or ""
+ end
+ end
+ data.streams = streams
+ data.glyphs = nil
+ data.size = size
+ data.format = data.format or (kind == "otf" and "opentype") or "truetype"
+ data.time = time
+ containers.write(cache,hash,data)
+ data = containers.read(cache,hash) -- frees old mem
+ end
+ end
+ else
+ data = {
+ filename = filename,
+ size = 0,
+ time = time,
+ format = "unknown",
+ glyphs = { }
+ }
+ end
+ return data
+end
+
+local loadedshapes = { }
+local loadedstreams = { }
+
+local function loadoutlinedata(fontdata,streams)
+ local properties = fontdata.properties
+ local filename = properties.filename
+ local subindex = fontdata.subindex
+ local instance = properties.instance
+ local hash = makehash(filename,subindex,instance)
+ local loaded = loadedshapes[hash]
+ if not loaded then
+ loaded = loadoutlines(shapescache,filename,subindex,instance)
+ loadedshapes[hash] = loaded
+ end
+ return loaded
+end
+
+hashes.shapes = table.setmetatableindex(function(t,k)
+ local f = identifiers[k]
+ if f then
+ return loadoutlinedata(f)
+ end
+end)
+
+local function loadstreamdata(fontdata,streams)
+ local properties = fontdata.properties
+ local filename = properties.filename
+ local subindex = fontdata.subindex
+ local instance = properties.instance
+ local hash = makehash(filename,subindex,instance)
+ local loaded = loadedstreams[hash]
+ if not loaded then
+ loaded = loadstreams(streamscache,filename,subindex,instance)
+ loadedstreams[hash] = loaded
+ end
+ return loaded
+end
+
+hashes.streams = table.setmetatableindex(function(t,k)
+ local f = identifiers[k]
+ if f then
+ return loadstreamdata(f,true)
+ end
+end)
+
+otf.loadoutlinedata = loadoutlinedata -- not public
+otf.loadstreamdata = loadstreamdata -- not public
+otf.loadshapes = loadshapes
+
+-- experimental code, for me only ... unsupported
+
+local f_c = string.formatters["%F %F %F %F %F %F c"]
+local f_l = string.formatters["%F %F l"]
+local f_m = string.formatters["%F %F m"]
+
+local function segmentstopdf(segments,factor,bt,et)
+ local t = { }
+ local m = 0
+ local n = #segments
+ local d = false
+ for i=1,n do
+ local s = segments[i]
+ local w = s[#s]
+ if w == "c" then
+ m = m + 1
+ t[m] = f_c(s[1]*factor,s[2]*factor,s[3]*factor,s[4]*factor,s[5]*factor,s[6]*factor)
+ elseif w == "l" then
+ m = m + 1
+ t[m] = f_l(s[1]*factor,s[2]*factor)
+ elseif w == "m" then
+ m = m + 1
+ t[m] = f_m(s[1]*factor,s[2]*factor)
+ elseif w == "q" then
+ local p = segments[i-1]
+ local n = #p
+ local l_x, l_y = factor*p[n-2], factor*p[n-1]
+ local m_x, m_y = factor*s[1], factor*s[2]
+ local r_x, r_y = factor*s[3], factor*s[4]
+ m = m + 1
+ t[m] = f_c (
+ l_x + 2/3 * (m_x-l_x), l_y + 2/3 * (m_y-l_y),
+ r_x + 2/3 * (m_x-r_x), r_y + 2/3 * (m_y-r_y),
+ r_x, r_y
+ )
+ end
+ end
+ m = m + 1
+ t[m] = "h f" -- B*
+ if bt and et then
+ t[0] = bt
+ t[m+1] = et
+ return concat(t,"\n",0,m+1)
+ else
+ return concat(t,"\n")
+ end
+end
+
+local function addvariableshapes(tfmdata,key,value)
+ if value then
+ local shapes = otf.loadoutlinedata(tfmdata)
+ if not shapes then
+ return
+ end
+ local glyphs = shapes.glyphs
+ if not glyphs then
+ return
+ end
+ local characters = tfmdata.characters
+ local parameters = tfmdata.parameters
+ local hfactor = parameters.hfactor * (7200/7227)
+ local factor = hfactor / 65536
+ local getactualtext = otf.getactualtext
+ for unicode, char in next, characters do
+ if not char.commands then
+ local shape = glyphs[char.index]
+ if shape then
+ local segments = shape.segments
+ if segments then
+ -- we need inline in order to support color
+ local bt, et = getactualtext(char.tounicode or char.unicode or unicode)
+ char.commands = {
+ { "special", "pdf:" .. segmentstopdf(segments,factor,bt,et) }
+ }
+ end
+ end
+ end
+ end
+ end
+end
+
+otf.features.register {
+ name = "variableshapes", -- enforced for now
+ description = "variable shapes",
+ manipulators = {
+ base = addvariableshapes,
+ node = addvariableshapes,
+ }
+}
+
+-- In the end it is easier to just provide the new charstring (cff) and points (ttdf). First
+-- of all we already have the right information so there is no need to patch the already complex
+-- backend code (we only need to make sure the cff is valid). Also, I prototyped support for
+-- these fonts using (converted to) normal postscript shapes, a functionality that was already
+-- present for a while for metafun. This solution even permits us to come up with usage of such
+-- fonts in unexpected ways. It also opens the road to shapes generated with metafun includes
+-- as real cff (or ttf) shapes instead of virtual in-line shapes.
+--
+-- This is probably a prelude to writing a complete backend font inclusion plugin in lua. After
+-- all I already have most info. For this we just need to pass a list of used glyphs (or analyze
+-- them ourselves).
+
+local streams = fonts.hashes.streams
+
+if callbacks.supported.glyph_stream_provider then
+
+ callback.register("glyph_stream_provider",function(id,index,mode)
+ if id > 0 then
+ local streams = streams[id].streams
+ -- print(id,index,streams[index])
+ if streams then
+ return streams[index] or ""
+ end
+ end
+ return ""
+ end)
+
+end