summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/font-ogr.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/font-ogr.lmt')
-rw-r--r--tex/context/base/mkiv/font-ogr.lmt672
1 files changed, 672 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/font-ogr.lmt b/tex/context/base/mkiv/font-ogr.lmt
new file mode 100644
index 000000000..94cf91494
--- /dev/null
+++ b/tex/context/base/mkiv/font-ogr.lmt
@@ -0,0 +1,672 @@
+if not modules then modules = { } end modules ['font-ogr'] = {
+ 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"
+}
+
+-- Here we deal with graphic variants and for now also color support ends up here
+-- but that might change. It's lmtx only code.
+
+if not context then
+ return
+elseif CONTEXTLMTXMODE == 0 then
+ return
+end
+
+local tostring, tonumber, next, type = tostring, tonumber, next, type
+local round, max, mod, div = math.round, math.max, math.mod, math.div
+local find = string.find
+local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash
+local utfbyte = utf.byte
+local formatters = string.formatters
+local settings_to_hash_strict, settings_to_array = utilities.parsers.settings_to_hash_strict, utilities.parsers.settings_to_array
+
+local otf = fonts.handlers.otf
+local otfregister = otf.features.register
+otf.svgenabled = true
+otf.pngenabled = true
+
+-- Just to remind me ... rewritten around the time this was posted on YT which
+-- was also around the 2019 ctx meeting:
+--
+-- Gavin Harrison - "Threatening War" by The Pineapple Thief
+-- https://www.youtube.com/watch?v=ENF9wth4kwM
+
+-- todo: svg color plugin
+-- todo: get rid of double cm in svg (tricky as also elsewhere)
+-- todo: png offsets (depth)
+-- todo: maybe collapse indices so that we have less files (harder to debug)
+-- todo: manage (read: assign) font id's in lua so we know in advance
+
+-- what here and what in backend ...
+
+do
+
+ -- This is a prelude to something better but I'm still experimenting.
+
+ local dropins = { }
+ fonts.dropins = dropins
+ local droppedin = 0
+ local identifiers = fonts.hashes.identifiers
+
+ function dropins.nextid()
+ droppedin = droppedin - 1
+ return droppedin
+ end
+
+ -- todo: pass specification table instead
+
+ function dropins.provide(method,t_tfmdata,indexdata,...)
+ local droppedin = dropins.nextid()
+ local t_characters = t_tfmdata.characters
+ local t_descriptions = t_tfmdata.descriptions
+ local t_properties = t_tfmdata.properties
+ local d_tfmdata = setmetatableindex({ },t_tfmdata)
+ local d_properties = setmetatableindex({ },t_properties)
+ d_tfmdata.properties = d_properties
+ local d_characters = { } -- setmetatableindex({ },t_characters) -- hm, index vs unicode
+ local d_descriptions = { } -- setmetatableindex({ },t_descriptions) -- hm, index vs unicode
+ d_tfmdata.characters = d_characters
+ d_tfmdata.descriptions = d_descriptions
+ d_properties.instance = - droppedin -- will become an extra element in the hash
+ t_properties.virtualized = true
+ identifiers[droppedin] = d_tfmdata
+ local fonts = t_tfmdata.fonts or { }
+ t_tfmdata.fonts = fonts
+ d_properties.format = "type3"
+ d_properties.method = method
+ d_properties.indexdata = { indexdata, ... } -- can take quite some memory
+ local slot = #fonts + 1
+ fonts[slot] = { id = droppedin }
+ return slot, droppedin, d_tfmdata, d_properties
+ end
+
+ -- todo: delay this, in which case we can be leaner and meaner
+
+ function dropins.clone(method,tfmdata,shapes,...) -- by index
+ if method and shapes then
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
+ local idx = 255
+ local slot = 0
+ for k, v in next, characters do
+ local index = v.index
+ if index then
+ local description = descriptions[k]
+ if description then
+ local shape = shapes[index]
+ if shape then
+ if idx >= 255 then
+ idx = 1
+ colrshapes = setmetatableindex({ },shapes)
+ slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
+ dropchars = tfmdrop.characters
+ dropdescs = tfmdrop.descriptions
+ else
+ idx = idx + 1
+ end
+ colrshapes[idx] = shape -- so not: description
+ -- todo: prepend
+ v.commands = { { "slot", slot, idx } }
+ -- hack to prevent that type 3 also gets 'use' flags .. todo
+ local c = { commands = false, index = idx, dropin = tfmdrop }
+ local d = { } -- { index = idx, dropin = tfmdrop }
+ setmetatableindex(c,v)
+ setmetatableindex(d,description)
+ dropchars[idx] = c
+ dropdescs[idx] = d -- not needed
+ end
+ end
+ end
+ end
+ else
+ -- error
+ end
+ end
+
+ function dropins.swap(method,tfmdata,shapes,...) -- by unicode
+ if method and shapes then
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
+ local idx = 255
+ local slot = 0
+ -- we can have a variant where shaped are by unicode and not by index
+ for k, v in next, characters do
+ local description = descriptions[k]
+ if description then
+ local shape = shapes[k]
+ if shape then
+ if idx >= 255 then
+ idx = 1
+ colrshapes = setmetatableindex({ },shapes)
+ slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
+ dropchars = tfmdrop.characters
+ dropdescs = tfmdrop.descriptions
+ else
+ idx = idx + 1
+ end
+ colrshapes[idx] = shape -- so not: description
+ -- todo: prepend
+ v.commands = { { "slot", slot, idx } }
+ -- hack to prevent that type 3 also gets 'use' flags .. todo
+ local c = { commands = false, index = idx, dropin = tfmdrop }
+ local d = { } -- index = idx, dropin = tfmdrop }
+ setmetatableindex(c,v)
+ setmetatableindex(d,description)
+ dropchars[idx] = c
+ dropdescs[idx] = d -- not needed
+ end
+ end
+ end
+ else
+ -- error
+ end
+ end
+
+end
+
+do -- this will move to its own module
+
+ local dropins = fonts.dropins
+
+ local shapes = setmetatableindex(function(t,k)
+ local v = {
+ glyphs = { },
+ parameters = {
+ units = 1000
+ },
+ }
+ t[k] = v
+ return v
+ end)
+
+ function dropins.registerglyphs(parameters)
+ local category = parameters.name
+ local target = shapes[category].parameters
+ for k, v in next, parameters do
+ if k ~= "glyphs" then
+ target[k] = v
+ end
+ end
+ end
+
+ function dropins.registerglyph(parameters)
+ local category = parameters.category
+ local unicode = parameters.unicode
+ local private = parameters.private
+ local unichar = parameters.unichar
+ if private then
+ unicode = fonts.helpers.newprivateslot(private)
+ elseif type(unichar) == "string" then
+ unicode = utfbyte(unichar)
+ else
+ local unitype = type(unicode)
+ if unitype == "string" then
+ local uninumber = tonumber(unicode)
+ if uninumber then
+ unicode = round(uninumber)
+ else
+ unicode = utfbyte(unicode)
+ end
+ elseif unitype == "number" then
+ unicode = round(unicode)
+ end
+ end
+ if unicode then
+ parameters.unicode = unicode
+ -- print(category,unicode)
+ shapes[category].glyphs[unicode] = parameters
+ else
+ -- error
+ end
+ end
+
+ -- local function hascolorspec(t)
+ -- if (t.color or "") ~= "" then
+ -- return true
+ -- elseif (t.fillcolor or "") ~= "" then
+ -- return true
+ -- elseif (t.drawcolor or "") ~= "" then
+ -- return true
+ -- elseif (t.linecolor or "") ~= "" then
+ -- return true
+ -- else
+ -- return false
+ -- end
+ -- end
+
+ local function hascolorspec(t)
+ for k, v in next, t do
+ if find(k,"color") then
+ return true
+ end
+ end
+ return false
+ end
+
+ local function initializemps(tfmdata,kind,value)
+ if value then
+ local specification = settings_to_hash_strict(value)
+ if not specification or not next(specification) then
+ specification = { category = value }
+ end
+ -- todo: multiple categories but then maybe also different
+ -- clones because of the units .. for now we assume the same
+ -- units
+ local category = specification.category
+ if category and category ~= "" then
+ local categories = settings_to_array(category)
+ local usedshapes = nil
+ local index = 0
+ local spread = tonumber(specification.spread or 0)
+ local hascolor = hascolorspec(specification)
+ specification.spread = spread -- now a number
+ for i=1,#categories do
+ local category = categories[i]
+ local mpsshapes = shapes[category]
+ if mpsshapes then
+ local properties = tfmdata.properties
+ local parameters = tfmdata.parameters
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local mpsparameters = mpsshapes.parameters
+ local units = mpsparameters.units or 1000
+ local defaultwidth = mpsparameters.width or 0
+ local defaultheight = mpsparameters.height or 0
+ local defaultdepth = mpsparameters.depth or 0
+ local usecolor = mpsparameters.usecolor
+ local spread = spread * units
+ local defaultcode = mpsparameters.code or ""
+ local scale = parameters.size / units
+ if hascolor then
+ -- the graphic has color
+ usecolor = false
+ else
+ -- do whatever is specified
+ end
+ usedshapes = usedshapes or {
+ instance = "simplefun",
+ units = units,
+ usecolor = usecolor,
+ specification = specification,
+ shapes = mpsshapes,
+ }
+ -- todo: deal with extensibles and more properties
+ for unicode, shape in sortedhash(mpsshapes.glyphs) do
+ -- local oldc = characters[unicode]
+ -- if oldc then
+ index = index + 1 -- todo: somehow we end up with 2 as first entry after 0
+ local wd = shape.width or defaultwidth
+ local ht = shape.height or defaultheight
+ local dp = shape.depth or defaultdepth
+ local newc = {
+ index = index, -- into usedshapes
+ width = scale * (wd + spread),
+ height = scale * ht,
+ depth = scale * dp,
+ unicode = unicode,
+ }
+ --
+ characters [unicode] = newc
+ descriptions[unicode] = newc
+ --
+ usedshapes[unicode] = shape.code or defaultcode
+ -- end
+ end
+ end
+ end
+ if usedshapes then
+ -- todo: different font when units and usecolor changes, maybe move into loop
+ -- above
+ dropins.swap("mps",tfmdata,usedshapes)
+ end
+ end
+ end
+ end
+
+ -- This kicks in quite late, after features have been checked. So if needed
+ -- substitutions need to be defined with force.
+
+ otfregister {
+ name = "metapost",
+ description = "metapost glyphs",
+ manipulators = {
+ base = initializemps,
+ node = initializemps,
+ }
+ }
+
+end
+
+-- This sits here for historcal reasons so for now we keep it here.
+
+local startactualtext = nil
+local stopactualtext = nil
+
+function otf.getactualtext(s)
+ if not startactualtext then
+ startactualtext = backends.codeinjections.startunicodetoactualtextdirect
+ stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect
+ end
+ return startactualtext(s), stopactualtext()
+end
+
+-- This is also somewhat specific.
+
+local sharedpalettes do
+
+ sharedpalettes = { }
+
+ local colors = attributes.list[attributes.private('color')] or { }
+ local transparencies = attributes.list[attributes.private('transparency')] or { }
+
+ function otf.registerpalette(name,values)
+ sharedpalettes[name] = values
+ local color = lpdf.color
+ local transparency = lpdf.transparency
+ local register = colors.register
+ for i=1,#values do
+ local v = values[i]
+ if v == "textcolor" then
+ values[i] = false
+ else
+ local c = nil
+ local t = nil
+ if type(v) == "table" then
+ c = register(name,"rgb",
+ max(round((v.r or 0)*255),255)/255,
+ max(round((v.g or 0)*255),255)/255,
+ max(round((v.b or 0)*255),255)/255
+ )
+ else
+ c = colors[v]
+ t = transparencies[v]
+ end
+ if c and t then
+ values[i] = color(1,c) .. " " .. transparency(t)
+ elseif c then
+ values[i] = color(1,c)
+ elseif t then
+ values[i] = color(1,t)
+ end
+ end
+ end
+ end
+
+end
+
+local initializeoverlay do
+
+ -- we should use the proper interface instead but for now:
+
+ local colors = attributes.colors
+ local rgbtocmyk = colors.rgbtocmyk
+
+ local f_cmyk = formatters["%.3N %.3f %.3N %.3N k"]
+ local f_rgb = formatters["%.3N %.3f %.3N rg"]
+ local f_gray = formatters["%.3N g"]
+
+ local function convert(t,k)
+ local v = { }
+ local m = colors.model
+ for i=1,#k do
+ local p = k[i]
+ local r, g, b = p[1]/255, p[2]/255, p[3]/255
+ if r == g and g == b then
+ p = f_gray(r)
+ elseif m == "cmyk" then
+ p = f_cmyk(rgbtocmyk(r,g,b))
+ else
+ p = f_rgb(r,g,b)
+ end
+ v[i] = p
+ end
+ t[k] = v
+ return v
+ end
+
+ initializeoverlay = function(tfmdata,kind,value) -- we really need the id ... todo
+ if value then
+ local resources = tfmdata.resources
+ local palettes = resources.colorpalettes
+ if palettes then
+ --
+ local converted = resources.converted
+ if not converted then
+ converted = setmetatableindex(convert)
+ resources.converted = converted
+ end
+ local colorvalues = sharedpalettes[value]
+ local default = false -- so the text color (bad for icon overloads)
+ if colorvalues then
+ default = colorvalues[#colorvalues]
+ else
+ colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { }
+ end
+ local classes = #colorvalues
+ if classes == 0 then
+ return
+ end
+ --
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
+ local idx = 255
+ local slot = 0
+ --
+ -- maybe delay in which case we have less fonts as we can be sparse
+ --
+ for k, v in next, characters do
+ local index = v.index
+ if index then
+ local description = descriptions[k]
+ if description then
+ local colorlist = description.colors
+ if colorlist then
+ if idx >= 255 then
+ idx = 1
+ colrshapes = { }
+ slot, droppedin, tfmdrop = fonts.dropins.provide("color",tfmdata,colrshapes,colorvalues)
+ dropchars = tfmdrop.characters
+ dropdescs = tfmdrop.descriptions
+ else
+ idx = idx + 1
+ end
+ --
+ colrshapes[idx] = description
+ -- todo: use extender
+ local u = { "use", 0 }
+ for i=1,#colorlist do
+ u[i+2] = colorlist[i].slot
+ end
+ v.commands = { u, { "slot", slot, idx } }
+ -- hack to prevent that type 3 also gets 'use' flags .. todo
+ local c = { commands = false, index = idx, dropin = tfmdata }
+ local d = { } -- index = idx, dropin = tfmdrop
+ setmetatableindex(c,v)
+ setmetatableindex(d,description)
+ dropchars[idx] = c
+ dropdescs[idx] = d -- not needed
+ end
+ end
+ end
+ end
+ return true
+ end
+ end
+ end
+
+ fonts.handlers.otf.features.register {
+ name = "colr",
+ description = "color glyphs",
+ manipulators = {
+ base = initializeoverlay,
+ node = initializeoverlay,
+ }
+ }
+
+end
+
+local initializesvg do
+
+ local report_svg = logs.reporter("fonts","svg")
+
+ local cached = true -- maybe always false (after i've optimized the lot)
+
+ directives.register("fonts.svg.cached", function(v) cached = v end)
+
+ initializesvg = function(tfmdata,kind,value) -- hm, always value
+ if value then
+ local properties = tfmdata.properties
+ local svg = properties.svg
+ local hash = svg and svg.hash
+ local timestamp = svg and svg.timestamp
+ if not hash then
+ return
+ end
+ local shapes = nil
+ local method = nil
+ local enforce = attributes.colors.model == "cmyk"
+ if cached and not enforce then
+ -- we need a different hash than for mkiv, so we append:
+ local pdfhash = hash .. "-svg"
+ local pdffile = containers.read(otf.pdfcache,pdfhash)
+ local pdfshapes = pdffile and pdffile.pdfshapes
+ local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf"))
+ if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then
+ local svgfile = containers.read(otf.svgcache,hash)
+ local svgshapes = svgfile and svgfile.svgshapes
+ pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg,tfmdata.parameters.units) or { }
+ -- look at ocl: we should store scale and x and y
+ containers.write(otf.pdfcache, pdfhash, {
+ pdfshapes = pdfshapes,
+ timestamp = timestamp,
+ })
+ end
+ shapes = pdfshapes
+ method = "pdf"
+ else
+ local mpsfile = containers.read(otf.mpscache,hash)
+ local mpsshapes = mpsfile and mpsfile.mpsshapes
+ if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then
+ local svgfile = containers.read(otf.svgcache,hash)
+ local svgshapes = svgfile and svgfile.svgshapes
+ -- still suboptimal
+ mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg,tfmdata.parameters.units) or { }
+ if enforce then
+ -- cheap conversion, no black component generation
+ mpsshapes.preamble = "interim svgforcecmyk := 1;"
+ end
+ containers.write(otf.mpscache, hash, {
+ mpsshapes = mpsshapes,
+ timestamp = timestamp,
+ })
+ end
+ shapes = mpsshapes
+ method = "mps"
+ end
+ if shapes then
+ shapes.fixdepth = value == "fixdepth"
+ fonts.dropins.clone(method,tfmdata,shapes)
+ end
+ return true
+ end
+ end
+
+ otfregister {
+ name = "svg",
+ description = "svg glyphs",
+ manipulators = {
+ base = initializesvg,
+ node = initializesvg,
+ }
+ }
+
+end
+
+local initializepng do
+
+ -- If this is really critical we can also use a pdf file as cache but I don't expect
+ -- png fonts to remain used.
+
+ local colors = attributes.colors
+
+ local report_png = logs.reporter("fonts","png conversion")
+
+ initializepng = function(tfmdata,kind,value) -- hm, always value
+ if value then
+ local properties = tfmdata.properties
+ local png = properties.png
+ local hash = png and png.hash
+ local timestamp = png and png.timestamp
+ if not hash then
+ return
+ end
+ local pngfile = containers.read(otf.pngcache,hash)
+ local pngshapes = pngfile and pngfile.pngshapes
+ if pngshapes then
+ if colors.model == "cmyk" then
+ pngshapes.enforcecmyk = true
+ end
+ fonts.dropins.clone("png",tfmdata,pngshapes)
+ end
+ return true
+ end
+ end
+
+ otfregister {
+ name = "sbix",
+ description = "sbix glyphs",
+ manipulators = {
+ base = initializepng,
+ node = initializepng,
+ }
+ }
+
+ otfregister {
+ name = "cblc",
+ description = "cblc glyphs",
+ manipulators = {
+ base = initializepng,
+ node = initializepng,
+ }
+ }
+
+end
+
+do
+
+ -- I need to check jpeg and such but will do that when I run into
+ -- it.
+
+ local function initializecolor(tfmdata,kind,value)
+ if value == "auto" then
+ return
+ initializeoverlay(tfmdata,kind,value) or
+ initializesvg(tfmdata,kind,value) or
+ initializepng(tfmdata,kind,value)
+ elseif value == "overlay" then
+ return initializeoverlay(tfmdata,kind,value)
+ elseif value == "svg" then
+ return initializesvg(tfmdata,kind,value)
+ elseif value == "png" or value == "bitmap" then
+ return initializepng(tfmdata,kind,value)
+ else
+ -- forget about it
+ end
+ end
+
+ otfregister {
+ name = "color",
+ description = "color glyphs",
+ manipulators = {
+ base = initializecolor,
+ node = initializecolor,
+ }
+ }
+
+end