diff options
Diffstat (limited to 'tex/context/base/font-afm.lua')
-rw-r--r-- | tex/context/base/font-afm.lua | 1942 |
1 files changed, 971 insertions, 971 deletions
diff --git a/tex/context/base/font-afm.lua b/tex/context/base/font-afm.lua index cb0c2438f..58408457e 100644 --- a/tex/context/base/font-afm.lua +++ b/tex/context/base/font-afm.lua @@ -1,971 +1,971 @@ -if not modules then modules = { } end modules ['font-afm'] = { - 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" -} - ---[[ldx-- -<p>Some code may look a bit obscure but this has to do with the -fact that we also use this code for testing and much code evolved -in the transition from <l n='tfm'/> to <l n='afm'/> to <l -n='otf'/>.</p> - -<p>The following code still has traces of intermediate font support -where we handles font encodings. Eventually font encoding goes -away.</p> ---ldx]]-- - -local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers - -local next, type, tonumber = next, type, tonumber -local format, match, gmatch, lower, gsub, strip = string.format, string.match, string.gmatch, string.lower, string.gsub, string.strip -local abs = math.abs -local P, S, C, R, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.C, lpeg.R, lpeg.match, lpeg.patterns -local derivetable = table.derive - -local trace_features = false trackers.register("afm.features", function(v) trace_features = v end) -local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end) -local trace_loading = false trackers.register("afm.loading", function(v) trace_loading = v end) -local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) - -local report_afm = logs.reporter("fonts","afm loading") - -local findbinfile = resolvers.findbinfile - -local definers = fonts.definers -local readers = fonts.readers -local constructors = fonts.constructors - -local afm = constructors.newhandler("afm") -local pfb = constructors.newhandler("pfb") - -local afmfeatures = constructors.newfeatures("afm") -local registerafmfeature = afmfeatures.register - -afm.version = 1.410 -- incrementing this number one up will force a re-cache -afm.cache = containers.define("fonts", "afm", afm.version, true) -afm.autoprefixed = true -- this will become false some day (catches texnansi-blabla.*) - -afm.helpdata = { } -- set later on so no local for this -afm.syncspace = true -- when true, nicer stretch values -afm.addligatures = true -- best leave this set to true -afm.addtexligatures = true -- best leave this set to true -afm.addkerns = true -- best leave this set to true - -local function setmode(tfmdata,value) - if value then - tfmdata.properties.mode = lower(value) - end -end - -registerafmfeature { - name = "mode", - description = "mode", - initializers = { - base = setmode, - node = setmode, - } -} - ---[[ldx-- -<p>We start with the basic reader which we give a name similar to the -built in <l n='tfm'/> and <l n='otf'/> reader.</p> ---ldx]]-- - ---~ Comment FONTIDENTIFIER LMMATHSYMBOLS10 ---~ Comment CODINGSCHEME TEX MATH SYMBOLS ---~ Comment DESIGNSIZE 10.0 pt ---~ Comment CHECKSUM O 4261307036 ---~ Comment SPACE 0 plus 0 minus 0 ---~ Comment QUAD 1000 ---~ Comment EXTRASPACE 0 ---~ Comment NUM 676.508 393.732 443.731 ---~ Comment DENOM 685.951 344.841 ---~ Comment SUP 412.892 362.892 288.889 ---~ Comment SUB 150 247.217 ---~ Comment SUPDROP 386.108 ---~ Comment SUBDROP 50 ---~ Comment DELIM 2390 1010 ---~ Comment AXISHEIGHT 250 - -local comment = P("Comment") -local spacing = patterns.spacer -- S(" \t")^1 -local lineend = patterns.newline -- S("\n\r") -local words = C((1 - lineend)^1) -local number = C((R("09") + S("."))^1) / tonumber * spacing^0 -local data = lpeg.Carg(1) - -local pattern = ( -- needs testing ... not used anyway as we no longer need math afm's - comment * spacing * - ( - data * ( - ("CODINGSCHEME" * spacing * words ) / function(fd,a) end + - ("DESIGNSIZE" * spacing * number * words ) / function(fd,a) fd[ 1] = a end + - ("CHECKSUM" * spacing * number * words ) / function(fd,a) fd[ 2] = a end + - ("SPACE" * spacing * number * "plus" * number * "minus" * number) / function(fd,a,b,c) fd[ 3], fd[ 4], fd[ 5] = a, b, c end + - ("QUAD" * spacing * number ) / function(fd,a) fd[ 6] = a end + - ("EXTRASPACE" * spacing * number ) / function(fd,a) fd[ 7] = a end + - ("NUM" * spacing * number * number * number ) / function(fd,a,b,c) fd[ 8], fd[ 9], fd[10] = a, b, c end + - ("DENOM" * spacing * number * number ) / function(fd,a,b ) fd[11], fd[12] = a, b end + - ("SUP" * spacing * number * number * number ) / function(fd,a,b,c) fd[13], fd[14], fd[15] = a, b, c end + - ("SUB" * spacing * number * number ) / function(fd,a,b) fd[16], fd[17] = a, b end + - ("SUPDROP" * spacing * number ) / function(fd,a) fd[18] = a end + - ("SUBDROP" * spacing * number ) / function(fd,a) fd[19] = a end + - ("DELIM" * spacing * number * number ) / function(fd,a,b) fd[20], fd[21] = a, b end + - ("AXISHEIGHT" * spacing * number ) / function(fd,a) fd[22] = a end - ) - + (1-lineend)^0 - ) - + (1-comment)^1 -)^0 - -local function scan_comment(str) - local fd = { } - lpegmatch(pattern,str,1,fd) - return fd -end - --- On a rainy day I will rewrite this in lpeg ... or we can use the (slower) fontloader --- as in now supports afm/pfb loading but it's not too bad to have different methods --- for testing approaches. - -local keys = { } - -function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces - data.metadata.fullname = strip (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end -function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end -function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end -function keys.Descender (data,line) data.metadata.descender = tonumber (line) end -function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end -function keys.Comment (data,line) - -- Comment DesignSize 12 (pts) - -- Comment TFM designsize: 12 (in points) - line = lower(line) - local designsize = match(line,"designsize[^%d]*(%d+)") - if designsize then data.metadata.designsize = tonumber(designsize) end -end - -local function get_charmetrics(data,charmetrics,vector) - local characters = data.characters - local chr, ind = { }, 0 - for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do - if k == 'C' then - v = tonumber(v) - if v < 0 then - ind = ind + 1 -- ? - else - ind = v - end - chr = { - index = ind - } - elseif k == 'WX' then - chr.width = tonumber(v) - elseif k == 'N' then - characters[v] = chr - elseif k == 'B' then - local llx, lly, urx, ury = match(v,"^ *(.-) +(.-) +(.-) +(.-)$") - chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) } - elseif k == 'L' then - local plus, becomes = match(v,"^(.-) +(.-)$") - local ligatures = chr.ligatures - if ligatures then - ligatures[plus] = becomes - else - chr.ligatures = { [plus] = becomes } - end - end - end -end - -local function get_kernpairs(data,kernpairs) - local characters = data.characters - for one, two, value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do - local chr = characters[one] - if chr then - local kerns = chr.kerns - if kerns then - kerns[two] = tonumber(value) - else - chr.kerns = { [two] = tonumber(value) } - end - end - end -end - -local function get_variables(data,fontmetrics) - for key, rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do - local keyhandler = keys[key] - if keyhandler then - keyhandler(data,rest) - end - end -end - -local function get_indexes(data,pfbname) - data.resources.filename = resolvers.unresolve(pfbname) -- no shortcut - local pfbblob = fontloader.open(pfbname) - if pfbblob then - local characters = data.characters - local pfbdata = fontloader.to_table(pfbblob) - if pfbdata then - local glyphs = pfbdata.glyphs - if glyphs then - if trace_loading then - report_afm("getting index data from %a",pfbname) - end - for index, glyph in next, glyphs do - local name = glyph.name - if name then - local char = characters[name] - if char then - if trace_indexing then - report_afm("glyph %a has index %a",name,index) - end - char.index = index - end - end - end - elseif trace_loading then - report_afm("no glyph data in pfb file %a",pfbname) - end - elseif trace_loading then - report_afm("no data in pfb file %a",pfbname) - end - fontloader.close(pfbblob) - elseif trace_loading then - report_afm("invalid pfb file %a",pfbname) - end -end - -local function readafm(filename) - local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging - if ok and afmblob then - local data = { - resources = { - filename = resolvers.unresolve(filename), - version = afm.version, - creator = "context mkiv", - }, - properties = { - hasitalics = false, - }, - goodies = { - }, - metadata = { - filename = file.removesuffix(file.basename(filename)) - }, - characters = { - -- a temporary store - }, - descriptions = { - -- the final store - }, - } - afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics) - if trace_loading then - report_afm("loading char metrics") - end - get_charmetrics(data,charmetrics,vector) - return "" - end) - afmblob = gsub(afmblob,"StartKernPairs(.-)EndKernPairs", function(kernpairs) - if trace_loading then - report_afm("loading kern pairs") - end - get_kernpairs(data,kernpairs) - return "" - end) - afmblob = gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics) - if trace_loading then - report_afm("loading variables") - end - data.afmversion = version - get_variables(data,fontmetrics) - data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now - return "" - end) - return data - else - if trace_loading then - report_afm("no valid afm file %a",filename) - end - return nil - end -end - ---[[ldx-- -<p>We cache files. Caching is taken care of in the loader. We cheat a bit -by adding ligatures and kern information to the afm derived data. That -way we can set them faster when defining a font.</p> ---ldx]]-- - -local addkerns, addligatures, addtexligatures, unify, normalize -- we will implement these later - -function afm.load(filename) - -- hm, for some reasons not resolved yet - filename = resolvers.findfile(filename,'afm') or "" - if filename ~= "" then - local name = file.removesuffix(file.basename(filename)) - local data = containers.read(afm.cache,name) - local attr = lfs.attributes(filename) - local size, time = attr.size or 0, attr.modification or 0 - -- - local pfbfile = file.replacesuffix(name,"pfb") - local pfbname = resolvers.findfile(pfbfile,"pfb") or "" - if pfbname == "" then - pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or "" - end - local pfbsize, pfbtime = 0, 0 - if pfbname ~= "" then - local attr = lfs.attributes(pfbname) - pfbsize = attr.size or 0 - pfbtime = attr.modification or 0 - end - if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then - report_afm("reading %a",filename) - data = readafm(filename) - if data then - if pfbname ~= "" then - get_indexes(data,pfbname) - elseif trace_loading then - report_afm("no pfb file for %a",filename) - end - report_afm("unifying %a",filename) - unify(data,filename) - if afm.addligatures then - report_afm("add ligatures") - addligatures(data) - end - if afm.addtexligatures then - report_afm("add tex ligatures") - addtexligatures(data) - end - if afm.addkerns then - report_afm("add extra kerns") - addkerns(data) - end - normalize(data) - report_afm("add tounicode data") - fonts.mappings.addtounicode(data,filename) - data.size = size - data.time = time - data.pfbsize = pfbsize - data.pfbtime = pfbtime - report_afm("saving %a in cache",name) - data = containers.write(afm.cache, name, data) - data = containers.read(afm.cache,name) - end - end - return data - else - return nil - end -end - -local uparser = fonts.mappings.makenameparser() - -unify = function(data, filename) - local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context - local unicodes, names = { }, { } - local private = constructors.privateoffset - local descriptions = data.descriptions - for name, blob in next, data.characters do - local code = unicodevector[name] -- or characters.name_to_unicode[name] - if not code then - code = lpegmatch(uparser,name) - if not code then - code = private - private = private + 1 - report_afm("assigning private slot %U for unknown glyph name %a",code,name) - end - end - local index = blob.index - unicodes[name] = code - names[name] = index - blob.name = name - descriptions[code] = { - boundingbox = blob.boundingbox, - width = blob.width, - kerns = blob.kerns, - index = index, - name = name, - } - end - for unicode, description in next, descriptions do - local kerns = description.kerns - if kerns then - local krn = { } - for name, kern in next, kerns do - local unicode = unicodes[name] - if unicode then - krn[unicode] = kern - else - print(unicode,name) - end - end - description.kerns = krn - end - end - data.characters = nil - local resources = data.resources - local filename = resources.filename or file.removesuffix(file.basename(filename)) - resources.filename = resolvers.unresolve(filename) -- no shortcut - resources.unicodes = unicodes -- name to unicode - resources.marks = { } -- todo - resources.names = names -- name to index - resources.private = private -end - -normalize = function(data) -end - ---[[ldx-- -<p>These helpers extend the basic table with extra ligatures, texligatures -and extra kerns. This saves quite some lookups later.</p> ---ldx]]-- - -local addthem = function(rawdata,ligatures) - if ligatures then - local descriptions = rawdata.descriptions - local resources = rawdata.resources - local unicodes = resources.unicodes - local names = resources.names - for ligname, ligdata in next, ligatures do - local one = descriptions[unicodes[ligname]] - if one then - for _, pair in next, ligdata do - local two, three = unicodes[pair[1]], unicodes[pair[2]] - if two and three then - local ol = one.ligatures - if ol then - if not ol[two] then - ol[two] = three - end - else - one.ligatures = { [two] = three } - end - end - end - end - end - end -end - -addligatures = function(rawdata) addthem(rawdata,afm.helpdata.ligatures ) end -addtexligatures = function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end - ---[[ldx-- -<p>We keep the extra kerns in separate kerning tables so that we can use -them selectively.</p> ---ldx]]-- - --- This is rather old code (from the beginning when we had only tfm). If --- we unify the afm data (now we have names all over the place) then --- we can use shcodes but there will be many more looping then. But we --- could get rid of the tables in char-cmp then. Als, in the generic version --- we don't use the character database. (Ok, we can have a context specific --- variant). - -addkerns = function(rawdata) -- using shcodes is not robust here - local descriptions = rawdata.descriptions - local resources = rawdata.resources - local unicodes = resources.unicodes - local function do_it_left(what) - if what then - for unicode, description in next, descriptions do - local kerns = description.kerns - if kerns then - local extrakerns - for complex, simple in next, what do - complex = unicodes[complex] - simple = unicodes[simple] - if complex and simple then - local ks = kerns[simple] - if ks and not kerns[complex] then - if extrakerns then - extrakerns[complex] = ks - else - extrakerns = { [complex] = ks } - end - end - end - end - if extrakerns then - description.extrakerns = extrakerns - end - end - end - end - end - local function do_it_copy(what) - if what then - for complex, simple in next, what do - complex = unicodes[complex] - simple = unicodes[simple] - if complex and simple then - local complexdescription = descriptions[complex] - if complexdescription then -- optional - local simpledescription = descriptions[complex] - if simpledescription then - local extrakerns - local kerns = simpledescription.kerns - if kerns then - for unicode, kern in next, kerns do - if extrakerns then - extrakerns[unicode] = kern - else - extrakerns = { [unicode] = kern } - end - end - end - local extrakerns = simpledescription.extrakerns - if extrakerns then - for unicode, kern in next, extrakerns do - if extrakerns then - extrakerns[unicode] = kern - else - extrakerns = { [unicode] = kern } - end - end - end - if extrakerns then - complexdescription.extrakerns = extrakerns - end - end - end - end - end - end - end - -- add complex with values of simplified when present - do_it_left(afm.helpdata.leftkerned) - do_it_left(afm.helpdata.bothkerned) - -- copy kerns from simple char to complex char unless set - do_it_copy(afm.helpdata.bothkerned) - do_it_copy(afm.helpdata.rightkerned) -end - ---[[ldx-- -<p>The copying routine looks messy (and is indeed a bit messy).</p> ---ldx]]-- - -local function adddimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name - if data then - for unicode, description in next, data.descriptions do - local bb = description.boundingbox - if bb then - local ht, dp = bb[4], -bb[2] - if ht == 0 or ht < 0 then - -- no need to set it and no negative heights, nil == 0 - else - description.height = ht - end - if dp == 0 or dp < 0 then - -- no negative depths and no negative depths, nil == 0 - else - description.depth = dp - end - end - end - end -end - -local function copytotfm(data) - if data and data.descriptions then - local metadata = data.metadata - local resources = data.resources - local properties = derivetable(data.properties) - local descriptions = derivetable(data.descriptions) - local goodies = derivetable(data.goodies) - local characters = { } - local parameters = { } - local unicodes = resources.unicodes - -- - for unicode, description in next, data.descriptions do -- use parent table - characters[unicode] = { } - end - -- - local filename = constructors.checkedfilename(resources) - local fontname = metadata.fontname or metadata.fullname - local fullname = metadata.fullname or metadata.fontname - local endash = unicodes['space'] - local emdash = unicodes['emdash'] - local spacer = "space" - local spaceunits = 500 - -- - local monospaced = metadata.isfixedpitch - local charwidth = metadata.charwidth - local italicangle = metadata.italicangle - local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight - properties.monospaced = monospaced - parameters.italicangle = italicangle - parameters.charwidth = charwidth - parameters.charxheight = charxheight - -- same as otf - if properties.monospaced then - if descriptions[endash] then - spaceunits, spacer = descriptions[endash].width, "space" - end - if not spaceunits and descriptions[emdash] then - spaceunits, spacer = descriptions[emdash].width, "emdash" - end - if not spaceunits and charwidth then - spaceunits, spacer = charwidth, "charwidth" - end - else - if descriptions[endash] then - spaceunits, spacer = descriptions[endash].width, "space" - end - if not spaceunits and charwidth then - spaceunits, spacer = charwidth, "charwidth" - end - end - spaceunits = tonumber(spaceunits) - if spaceunits < 200 then - -- todo: warning - end - -- - parameters.slant = 0 - parameters.space = spaceunits - parameters.space_stretch = 500 - parameters.space_shrink = 333 - parameters.x_height = 400 - parameters.quad = 1000 - -- - if italicangle then - parameters.italicangle = italicangle - parameters.italicfactor = math.cos(math.rad(90+italicangle)) - parameters.slant = - math.round(math.tan(italicangle*math.pi/180)) - end - if monospaced then - parameters.space_stretch = 0 - parameters.space_shrink = 0 - elseif afm.syncspace then - parameters.space_stretch = spaceunits/2 - parameters.space_shrink = spaceunits/3 - end - parameters.extra_space = parameters.space_shrink - if charxheight then - parameters.x_height = charxheight - else - -- same as otf - local x = unicodes['x'] - if x then - local x = descriptions[x] - if x then - parameters.x_height = x.height - end - end - -- - end - local fd = data.fontdimens - if fd and fd[8] and fd[9] and fd[10] then -- math - for k,v in next, fd do - parameters[k] = v - end - end - -- - parameters.designsize = (metadata.designsize or 10)*65536 - parameters.ascender = abs(metadata.ascender or 0) - parameters.descender = abs(metadata.descender or 0) - parameters.units = 1000 - -- - properties.spacer = spacer - properties.encodingbytes = 2 - properties.format = fonts.formats[filename] or "type1" - properties.filename = filename - properties.fontname = fontname - properties.fullname = fullname - properties.psname = fullname - properties.name = filename or fullname or fontname - -- - if next(characters) then - return { - characters = characters, - descriptions = descriptions, - parameters = parameters, - resources = resources, - properties = properties, - goodies = goodies, - } - end - end - return nil -end - ---[[ldx-- -<p>Originally we had features kind of hard coded for <l n='afm'/> -files but since I expect to support more font formats, I decided -to treat this fontformat like any other and handle features in a -more configurable way.</p> ---ldx]]-- - -function afm.setfeatures(tfmdata,features) - local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) - if okay then - return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) - else - return { } -- will become false - end -end - -local function checkfeatures(specification) -end - -local function afmtotfm(specification) - local afmname = specification.filename or specification.name - if specification.forced == "afm" or specification.format == "afm" then -- move this one up - if trace_loading then - report_afm("forcing afm format for %a",afmname) - end - else - local tfmname = findbinfile(afmname,"ofm") or "" - if tfmname ~= "" then - if trace_loading then - report_afm("fallback from afm to tfm for %a",afmname) - end - return -- just that - end - end - if afmname ~= "" then - -- weird, isn't this already done then? - local features = constructors.checkedfeatures("afm",specification.features.normal) - specification.features.normal = features - constructors.hashinstance(specification,true) -- also weird here - -- - specification = definers.resolve(specification) -- new, was forgotten - local cache_id = specification.hash - local tfmdata = containers.read(constructors.cache, cache_id) -- cache with features applied - if not tfmdata then - local rawdata = afm.load(afmname) - if rawdata and next(rawdata) then - adddimensions(rawdata) - tfmdata = copytotfm(rawdata) - if tfmdata and next(tfmdata) then - local shared = tfmdata.shared - if not shared then - shared = { } - tfmdata.shared = shared - end - shared.rawdata = rawdata - shared.features = features - shared.processes = afm.setfeatures(tfmdata,features) - end - elseif trace_loading then - report_afm("no (valid) afm file found with name %a",afmname) - end - tfmdata = containers.write(constructors.cache,cache_id,tfmdata) - end - return tfmdata - end -end - ---[[ldx-- -<p>As soon as we could intercept the <l n='tfm'/> reader, I implemented an -<l n='afm'/> reader. Since traditional <l n='pdftex'/> could use <l n='opentype'/> -fonts with <l n='afm'/> companions, the following method also could handle -those cases, but now that we can handle <l n='opentype'/> directly we no longer -need this features.</p> ---ldx]]-- - -local function read_from_afm(specification) - local tfmdata = afmtotfm(specification) - if tfmdata then - tfmdata.properties.name = specification.name - tfmdata = constructors.scale(tfmdata, specification) - local allfeatures = tfmdata.shared.features or specification.features.normal - constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) - fonts.loggers.register(tfmdata,'afm',specification) - end - return tfmdata -end - ---[[ldx-- -<p>Here comes the implementation of a few features. We only implement -those that make sense for this format.</p> ---ldx]]-- - -local function prepareligatures(tfmdata,ligatures,value) - if value then - local descriptions = tfmdata.descriptions - for unicode, character in next, tfmdata.characters do - local description = descriptions[unicode] - local dligatures = description.ligatures - if dligatures then - local cligatures = character.ligatures - if not cligatures then - cligatures = { } - character.ligatures = cligatures - end - for unicode, ligature in next, dligatures do - cligatures[unicode] = { - char = ligature, - type = 0 - } - end - end - end - end -end - -local function preparekerns(tfmdata,kerns,value) - if value then - local rawdata = tfmdata.shared.rawdata - local resources = rawdata.resources - local unicodes = resources.unicodes - local descriptions = tfmdata.descriptions - for u, chr in next, tfmdata.characters do - local d = descriptions[u] - local newkerns = d[kerns] - if newkerns then - local kerns = chr.kerns - if not kerns then - kerns = { } - chr.kerns = kerns - end - for k,v in next, newkerns do - local uk = unicodes[k] - if uk then - kerns[uk] = v - end - end - end - end - end -end - -local list = { - -- [0x0022] = 0x201D, - [0x0027] = 0x2019, - -- [0x0060] = 0x2018, -} - -local function texreplacements(tfmdata,value) - local descriptions = tfmdata.descriptions - local characters = tfmdata.characters - for k, v in next, list do - characters [k] = characters [v] -- we forget about kerns - descriptions[k] = descriptions[v] -- we forget about kerns - end -end - -local function ligatures (tfmdata,value) prepareligatures(tfmdata,'ligatures', value) end -local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end -local function kerns (tfmdata,value) preparekerns (tfmdata,'kerns', value) end -local function extrakerns (tfmdata,value) preparekerns (tfmdata,'extrakerns', value) end - -registerafmfeature { - name = "liga", - description = "traditional ligatures", - initializers = { - base = ligatures, - node = ligatures, - } -} - -registerafmfeature { - name = "kern", - description = "intercharacter kerning", - initializers = { - base = kerns, - node = kerns, - } -} - -registerafmfeature { - name = "extrakerns", - description = "additional intercharacter kerning", - initializers = { - base = extrakerns, - node = extrakerns, - } -} - -registerafmfeature { - name = 'tlig', - description = 'tex ligatures', - initializers = { - base = texligatures, - node = texligatures, - } -} - -registerafmfeature { - name = 'trep', - description = 'tex replacements', - initializers = { - base = texreplacements, - node = texreplacements, - } -} - --- readers - -local check_tfm = readers.check_tfm - -fonts.formats.afm = "type1" -fonts.formats.pfb = "type1" - -local function check_afm(specification,fullname) - local foundname = findbinfile(fullname, 'afm') or "" -- just to be sure - if foundname == "" then - foundname = fonts.names.getfilename(fullname,"afm") or "" - end - if foundname == "" and afm.autoprefixed then - local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.* - if encoding and shortname and fonts.encodings.known[encoding] then - shortname = findbinfile(shortname,'afm') or "" -- just to be sure - if shortname ~= "" then - foundname = shortname - if trace_defining then - report_afm("stripping encoding prefix from filename %a",afmname) - end - end - end - end - if foundname ~= "" then - specification.filename = foundname - specification.format = "afm" - return read_from_afm(specification) - end -end - -function readers.afm(specification,method) - local fullname, tfmdata = specification.filename or "", nil - if fullname == "" then - local forced = specification.forced or "" - if forced ~= "" then - tfmdata = check_afm(specification,specification.name .. "." .. forced) - end - if not tfmdata then - method = method or definers.method or "afm or tfm" - if method == "tfm" then - tfmdata = check_tfm(specification,specification.name) - elseif method == "afm" then - tfmdata = check_afm(specification,specification.name) - elseif method == "tfm or afm" then - tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name) - else -- method == "afm or tfm" or method == "" then - tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name) - end - end - else - tfmdata = check_afm(specification,fullname) - end - return tfmdata -end - -function readers.pfb(specification,method) -- only called when forced - local original = specification.specification - if trace_defining then - report_afm("using afm reader for %a",original) - end - specification.specification = gsub(original,"%.pfb",".afm") - specification.forced = "afm" - return readers.afm(specification,method) -end +if not modules then modules = { } end modules ['font-afm'] = {
+ 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"
+}
+
+--[[ldx--
+<p>Some code may look a bit obscure but this has to do with the
+fact that we also use this code for testing and much code evolved
+in the transition from <l n='tfm'/> to <l n='afm'/> to <l
+n='otf'/>.</p>
+
+<p>The following code still has traces of intermediate font support
+where we handles font encodings. Eventually font encoding goes
+away.</p>
+--ldx]]--
+
+local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers
+
+local next, type, tonumber = next, type, tonumber
+local format, match, gmatch, lower, gsub, strip = string.format, string.match, string.gmatch, string.lower, string.gsub, string.strip
+local abs = math.abs
+local P, S, C, R, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.C, lpeg.R, lpeg.match, lpeg.patterns
+local derivetable = table.derive
+
+local trace_features = false trackers.register("afm.features", function(v) trace_features = v end)
+local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end)
+local trace_loading = false trackers.register("afm.loading", function(v) trace_loading = v end)
+local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
+
+local report_afm = logs.reporter("fonts","afm loading")
+
+local findbinfile = resolvers.findbinfile
+
+local definers = fonts.definers
+local readers = fonts.readers
+local constructors = fonts.constructors
+
+local afm = constructors.newhandler("afm")
+local pfb = constructors.newhandler("pfb")
+
+local afmfeatures = constructors.newfeatures("afm")
+local registerafmfeature = afmfeatures.register
+
+afm.version = 1.410 -- incrementing this number one up will force a re-cache
+afm.cache = containers.define("fonts", "afm", afm.version, true)
+afm.autoprefixed = true -- this will become false some day (catches texnansi-blabla.*)
+
+afm.helpdata = { } -- set later on so no local for this
+afm.syncspace = true -- when true, nicer stretch values
+afm.addligatures = true -- best leave this set to true
+afm.addtexligatures = true -- best leave this set to true
+afm.addkerns = true -- best leave this set to true
+
+local function setmode(tfmdata,value)
+ if value then
+ tfmdata.properties.mode = lower(value)
+ end
+end
+
+registerafmfeature {
+ name = "mode",
+ description = "mode",
+ initializers = {
+ base = setmode,
+ node = setmode,
+ }
+}
+
+--[[ldx--
+<p>We start with the basic reader which we give a name similar to the
+built in <l n='tfm'/> and <l n='otf'/> reader.</p>
+--ldx]]--
+
+--~ Comment FONTIDENTIFIER LMMATHSYMBOLS10
+--~ Comment CODINGSCHEME TEX MATH SYMBOLS
+--~ Comment DESIGNSIZE 10.0 pt
+--~ Comment CHECKSUM O 4261307036
+--~ Comment SPACE 0 plus 0 minus 0
+--~ Comment QUAD 1000
+--~ Comment EXTRASPACE 0
+--~ Comment NUM 676.508 393.732 443.731
+--~ Comment DENOM 685.951 344.841
+--~ Comment SUP 412.892 362.892 288.889
+--~ Comment SUB 150 247.217
+--~ Comment SUPDROP 386.108
+--~ Comment SUBDROP 50
+--~ Comment DELIM 2390 1010
+--~ Comment AXISHEIGHT 250
+
+local comment = P("Comment")
+local spacing = patterns.spacer -- S(" \t")^1
+local lineend = patterns.newline -- S("\n\r")
+local words = C((1 - lineend)^1)
+local number = C((R("09") + S("."))^1) / tonumber * spacing^0
+local data = lpeg.Carg(1)
+
+local pattern = ( -- needs testing ... not used anyway as we no longer need math afm's
+ comment * spacing *
+ (
+ data * (
+ ("CODINGSCHEME" * spacing * words ) / function(fd,a) end +
+ ("DESIGNSIZE" * spacing * number * words ) / function(fd,a) fd[ 1] = a end +
+ ("CHECKSUM" * spacing * number * words ) / function(fd,a) fd[ 2] = a end +
+ ("SPACE" * spacing * number * "plus" * number * "minus" * number) / function(fd,a,b,c) fd[ 3], fd[ 4], fd[ 5] = a, b, c end +
+ ("QUAD" * spacing * number ) / function(fd,a) fd[ 6] = a end +
+ ("EXTRASPACE" * spacing * number ) / function(fd,a) fd[ 7] = a end +
+ ("NUM" * spacing * number * number * number ) / function(fd,a,b,c) fd[ 8], fd[ 9], fd[10] = a, b, c end +
+ ("DENOM" * spacing * number * number ) / function(fd,a,b ) fd[11], fd[12] = a, b end +
+ ("SUP" * spacing * number * number * number ) / function(fd,a,b,c) fd[13], fd[14], fd[15] = a, b, c end +
+ ("SUB" * spacing * number * number ) / function(fd,a,b) fd[16], fd[17] = a, b end +
+ ("SUPDROP" * spacing * number ) / function(fd,a) fd[18] = a end +
+ ("SUBDROP" * spacing * number ) / function(fd,a) fd[19] = a end +
+ ("DELIM" * spacing * number * number ) / function(fd,a,b) fd[20], fd[21] = a, b end +
+ ("AXISHEIGHT" * spacing * number ) / function(fd,a) fd[22] = a end
+ )
+ + (1-lineend)^0
+ )
+ + (1-comment)^1
+)^0
+
+local function scan_comment(str)
+ local fd = { }
+ lpegmatch(pattern,str,1,fd)
+ return fd
+end
+
+-- On a rainy day I will rewrite this in lpeg ... or we can use the (slower) fontloader
+-- as in now supports afm/pfb loading but it's not too bad to have different methods
+-- for testing approaches.
+
+local keys = { }
+
+function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces
+ data.metadata.fullname = strip (line) end
+function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end
+function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end
+function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end
+function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end
+function keys.Descender (data,line) data.metadata.descender = tonumber (line) end
+function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end
+function keys.Comment (data,line)
+ -- Comment DesignSize 12 (pts)
+ -- Comment TFM designsize: 12 (in points)
+ line = lower(line)
+ local designsize = match(line,"designsize[^%d]*(%d+)")
+ if designsize then data.metadata.designsize = tonumber(designsize) end
+end
+
+local function get_charmetrics(data,charmetrics,vector)
+ local characters = data.characters
+ local chr, ind = { }, 0
+ for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do
+ if k == 'C' then
+ v = tonumber(v)
+ if v < 0 then
+ ind = ind + 1 -- ?
+ else
+ ind = v
+ end
+ chr = {
+ index = ind
+ }
+ elseif k == 'WX' then
+ chr.width = tonumber(v)
+ elseif k == 'N' then
+ characters[v] = chr
+ elseif k == 'B' then
+ local llx, lly, urx, ury = match(v,"^ *(.-) +(.-) +(.-) +(.-)$")
+ chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) }
+ elseif k == 'L' then
+ local plus, becomes = match(v,"^(.-) +(.-)$")
+ local ligatures = chr.ligatures
+ if ligatures then
+ ligatures[plus] = becomes
+ else
+ chr.ligatures = { [plus] = becomes }
+ end
+ end
+ end
+end
+
+local function get_kernpairs(data,kernpairs)
+ local characters = data.characters
+ for one, two, value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do
+ local chr = characters[one]
+ if chr then
+ local kerns = chr.kerns
+ if kerns then
+ kerns[two] = tonumber(value)
+ else
+ chr.kerns = { [two] = tonumber(value) }
+ end
+ end
+ end
+end
+
+local function get_variables(data,fontmetrics)
+ for key, rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do
+ local keyhandler = keys[key]
+ if keyhandler then
+ keyhandler(data,rest)
+ end
+ end
+end
+
+local function get_indexes(data,pfbname)
+ data.resources.filename = resolvers.unresolve(pfbname) -- no shortcut
+ local pfbblob = fontloader.open(pfbname)
+ if pfbblob then
+ local characters = data.characters
+ local pfbdata = fontloader.to_table(pfbblob)
+ if pfbdata then
+ local glyphs = pfbdata.glyphs
+ if glyphs then
+ if trace_loading then
+ report_afm("getting index data from %a",pfbname)
+ end
+ for index, glyph in next, glyphs do
+ local name = glyph.name
+ if name then
+ local char = characters[name]
+ if char then
+ if trace_indexing then
+ report_afm("glyph %a has index %a",name,index)
+ end
+ char.index = index
+ end
+ end
+ end
+ elseif trace_loading then
+ report_afm("no glyph data in pfb file %a",pfbname)
+ end
+ elseif trace_loading then
+ report_afm("no data in pfb file %a",pfbname)
+ end
+ fontloader.close(pfbblob)
+ elseif trace_loading then
+ report_afm("invalid pfb file %a",pfbname)
+ end
+end
+
+local function readafm(filename)
+ local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging
+ if ok and afmblob then
+ local data = {
+ resources = {
+ filename = resolvers.unresolve(filename),
+ version = afm.version,
+ creator = "context mkiv",
+ },
+ properties = {
+ hasitalics = false,
+ },
+ goodies = {
+ },
+ metadata = {
+ filename = file.removesuffix(file.basename(filename))
+ },
+ characters = {
+ -- a temporary store
+ },
+ descriptions = {
+ -- the final store
+ },
+ }
+ afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics)
+ if trace_loading then
+ report_afm("loading char metrics")
+ end
+ get_charmetrics(data,charmetrics,vector)
+ return ""
+ end)
+ afmblob = gsub(afmblob,"StartKernPairs(.-)EndKernPairs", function(kernpairs)
+ if trace_loading then
+ report_afm("loading kern pairs")
+ end
+ get_kernpairs(data,kernpairs)
+ return ""
+ end)
+ afmblob = gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics)
+ if trace_loading then
+ report_afm("loading variables")
+ end
+ data.afmversion = version
+ get_variables(data,fontmetrics)
+ data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now
+ return ""
+ end)
+ return data
+ else
+ if trace_loading then
+ report_afm("no valid afm file %a",filename)
+ end
+ return nil
+ end
+end
+
+--[[ldx--
+<p>We cache files. Caching is taken care of in the loader. We cheat a bit
+by adding ligatures and kern information to the afm derived data. That
+way we can set them faster when defining a font.</p>
+--ldx]]--
+
+local addkerns, addligatures, addtexligatures, unify, normalize -- we will implement these later
+
+function afm.load(filename)
+ -- hm, for some reasons not resolved yet
+ filename = resolvers.findfile(filename,'afm') or ""
+ if filename ~= "" then
+ local name = file.removesuffix(file.basename(filename))
+ local data = containers.read(afm.cache,name)
+ local attr = lfs.attributes(filename)
+ local size, time = attr.size or 0, attr.modification or 0
+ --
+ local pfbfile = file.replacesuffix(name,"pfb")
+ local pfbname = resolvers.findfile(pfbfile,"pfb") or ""
+ if pfbname == "" then
+ pfbname = resolvers.findfile(file.basename(pfbfile),"pfb") or ""
+ end
+ local pfbsize, pfbtime = 0, 0
+ if pfbname ~= "" then
+ local attr = lfs.attributes(pfbname)
+ pfbsize = attr.size or 0
+ pfbtime = attr.modification or 0
+ end
+ if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then
+ report_afm("reading %a",filename)
+ data = readafm(filename)
+ if data then
+ if pfbname ~= "" then
+ get_indexes(data,pfbname)
+ elseif trace_loading then
+ report_afm("no pfb file for %a",filename)
+ end
+ report_afm("unifying %a",filename)
+ unify(data,filename)
+ if afm.addligatures then
+ report_afm("add ligatures")
+ addligatures(data)
+ end
+ if afm.addtexligatures then
+ report_afm("add tex ligatures")
+ addtexligatures(data)
+ end
+ if afm.addkerns then
+ report_afm("add extra kerns")
+ addkerns(data)
+ end
+ normalize(data)
+ report_afm("add tounicode data")
+ fonts.mappings.addtounicode(data,filename)
+ data.size = size
+ data.time = time
+ data.pfbsize = pfbsize
+ data.pfbtime = pfbtime
+ report_afm("saving %a in cache",name)
+ data = containers.write(afm.cache, name, data)
+ data = containers.read(afm.cache,name)
+ end
+ end
+ return data
+ else
+ return nil
+ end
+end
+
+local uparser = fonts.mappings.makenameparser()
+
+unify = function(data, filename)
+ local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context
+ local unicodes, names = { }, { }
+ local private = constructors.privateoffset
+ local descriptions = data.descriptions
+ for name, blob in next, data.characters do
+ local code = unicodevector[name] -- or characters.name_to_unicode[name]
+ if not code then
+ code = lpegmatch(uparser,name)
+ if not code then
+ code = private
+ private = private + 1
+ report_afm("assigning private slot %U for unknown glyph name %a",code,name)
+ end
+ end
+ local index = blob.index
+ unicodes[name] = code
+ names[name] = index
+ blob.name = name
+ descriptions[code] = {
+ boundingbox = blob.boundingbox,
+ width = blob.width,
+ kerns = blob.kerns,
+ index = index,
+ name = name,
+ }
+ end
+ for unicode, description in next, descriptions do
+ local kerns = description.kerns
+ if kerns then
+ local krn = { }
+ for name, kern in next, kerns do
+ local unicode = unicodes[name]
+ if unicode then
+ krn[unicode] = kern
+ else
+ print(unicode,name)
+ end
+ end
+ description.kerns = krn
+ end
+ end
+ data.characters = nil
+ local resources = data.resources
+ local filename = resources.filename or file.removesuffix(file.basename(filename))
+ resources.filename = resolvers.unresolve(filename) -- no shortcut
+ resources.unicodes = unicodes -- name to unicode
+ resources.marks = { } -- todo
+ resources.names = names -- name to index
+ resources.private = private
+end
+
+normalize = function(data)
+end
+
+--[[ldx--
+<p>These helpers extend the basic table with extra ligatures, texligatures
+and extra kerns. This saves quite some lookups later.</p>
+--ldx]]--
+
+local addthem = function(rawdata,ligatures)
+ if ligatures then
+ local descriptions = rawdata.descriptions
+ local resources = rawdata.resources
+ local unicodes = resources.unicodes
+ local names = resources.names
+ for ligname, ligdata in next, ligatures do
+ local one = descriptions[unicodes[ligname]]
+ if one then
+ for _, pair in next, ligdata do
+ local two, three = unicodes[pair[1]], unicodes[pair[2]]
+ if two and three then
+ local ol = one.ligatures
+ if ol then
+ if not ol[two] then
+ ol[two] = three
+ end
+ else
+ one.ligatures = { [two] = three }
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+addligatures = function(rawdata) addthem(rawdata,afm.helpdata.ligatures ) end
+addtexligatures = function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end
+
+--[[ldx--
+<p>We keep the extra kerns in separate kerning tables so that we can use
+them selectively.</p>
+--ldx]]--
+
+-- This is rather old code (from the beginning when we had only tfm). If
+-- we unify the afm data (now we have names all over the place) then
+-- we can use shcodes but there will be many more looping then. But we
+-- could get rid of the tables in char-cmp then. Als, in the generic version
+-- we don't use the character database. (Ok, we can have a context specific
+-- variant).
+
+addkerns = function(rawdata) -- using shcodes is not robust here
+ local descriptions = rawdata.descriptions
+ local resources = rawdata.resources
+ local unicodes = resources.unicodes
+ local function do_it_left(what)
+ if what then
+ for unicode, description in next, descriptions do
+ local kerns = description.kerns
+ if kerns then
+ local extrakerns
+ for complex, simple in next, what do
+ complex = unicodes[complex]
+ simple = unicodes[simple]
+ if complex and simple then
+ local ks = kerns[simple]
+ if ks and not kerns[complex] then
+ if extrakerns then
+ extrakerns[complex] = ks
+ else
+ extrakerns = { [complex] = ks }
+ end
+ end
+ end
+ end
+ if extrakerns then
+ description.extrakerns = extrakerns
+ end
+ end
+ end
+ end
+ end
+ local function do_it_copy(what)
+ if what then
+ for complex, simple in next, what do
+ complex = unicodes[complex]
+ simple = unicodes[simple]
+ if complex and simple then
+ local complexdescription = descriptions[complex]
+ if complexdescription then -- optional
+ local simpledescription = descriptions[complex]
+ if simpledescription then
+ local extrakerns
+ local kerns = simpledescription.kerns
+ if kerns then
+ for unicode, kern in next, kerns do
+ if extrakerns then
+ extrakerns[unicode] = kern
+ else
+ extrakerns = { [unicode] = kern }
+ end
+ end
+ end
+ local extrakerns = simpledescription.extrakerns
+ if extrakerns then
+ for unicode, kern in next, extrakerns do
+ if extrakerns then
+ extrakerns[unicode] = kern
+ else
+ extrakerns = { [unicode] = kern }
+ end
+ end
+ end
+ if extrakerns then
+ complexdescription.extrakerns = extrakerns
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ -- add complex with values of simplified when present
+ do_it_left(afm.helpdata.leftkerned)
+ do_it_left(afm.helpdata.bothkerned)
+ -- copy kerns from simple char to complex char unless set
+ do_it_copy(afm.helpdata.bothkerned)
+ do_it_copy(afm.helpdata.rightkerned)
+end
+
+--[[ldx--
+<p>The copying routine looks messy (and is indeed a bit messy).</p>
+--ldx]]--
+
+local function adddimensions(data) -- we need to normalize afm to otf i.e. indexed table instead of name
+ if data then
+ for unicode, description in next, data.descriptions do
+ local bb = description.boundingbox
+ if bb then
+ local ht, dp = bb[4], -bb[2]
+ if ht == 0 or ht < 0 then
+ -- no need to set it and no negative heights, nil == 0
+ else
+ description.height = ht
+ end
+ if dp == 0 or dp < 0 then
+ -- no negative depths and no negative depths, nil == 0
+ else
+ description.depth = dp
+ end
+ end
+ end
+ end
+end
+
+local function copytotfm(data)
+ if data and data.descriptions then
+ local metadata = data.metadata
+ local resources = data.resources
+ local properties = derivetable(data.properties)
+ local descriptions = derivetable(data.descriptions)
+ local goodies = derivetable(data.goodies)
+ local characters = { }
+ local parameters = { }
+ local unicodes = resources.unicodes
+ --
+ for unicode, description in next, data.descriptions do -- use parent table
+ characters[unicode] = { }
+ end
+ --
+ local filename = constructors.checkedfilename(resources)
+ local fontname = metadata.fontname or metadata.fullname
+ local fullname = metadata.fullname or metadata.fontname
+ local endash = unicodes['space']
+ local emdash = unicodes['emdash']
+ local spacer = "space"
+ local spaceunits = 500
+ --
+ local monospaced = metadata.isfixedpitch
+ local charwidth = metadata.charwidth
+ local italicangle = metadata.italicangle
+ local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight
+ properties.monospaced = monospaced
+ parameters.italicangle = italicangle
+ parameters.charwidth = charwidth
+ parameters.charxheight = charxheight
+ -- same as otf
+ if properties.monospaced then
+ if descriptions[endash] then
+ spaceunits, spacer = descriptions[endash].width, "space"
+ end
+ if not spaceunits and descriptions[emdash] then
+ spaceunits, spacer = descriptions[emdash].width, "emdash"
+ end
+ if not spaceunits and charwidth then
+ spaceunits, spacer = charwidth, "charwidth"
+ end
+ else
+ if descriptions[endash] then
+ spaceunits, spacer = descriptions[endash].width, "space"
+ end
+ if not spaceunits and charwidth then
+ spaceunits, spacer = charwidth, "charwidth"
+ end
+ end
+ spaceunits = tonumber(spaceunits)
+ if spaceunits < 200 then
+ -- todo: warning
+ end
+ --
+ parameters.slant = 0
+ parameters.space = spaceunits
+ parameters.space_stretch = 500
+ parameters.space_shrink = 333
+ parameters.x_height = 400
+ parameters.quad = 1000
+ --
+ if italicangle then
+ parameters.italicangle = italicangle
+ parameters.italicfactor = math.cos(math.rad(90+italicangle))
+ parameters.slant = - math.round(math.tan(italicangle*math.pi/180))
+ end
+ if monospaced then
+ parameters.space_stretch = 0
+ parameters.space_shrink = 0
+ elseif afm.syncspace then
+ parameters.space_stretch = spaceunits/2
+ parameters.space_shrink = spaceunits/3
+ end
+ parameters.extra_space = parameters.space_shrink
+ if charxheight then
+ parameters.x_height = charxheight
+ else
+ -- same as otf
+ local x = unicodes['x']
+ if x then
+ local x = descriptions[x]
+ if x then
+ parameters.x_height = x.height
+ end
+ end
+ --
+ end
+ local fd = data.fontdimens
+ if fd and fd[8] and fd[9] and fd[10] then -- math
+ for k,v in next, fd do
+ parameters[k] = v
+ end
+ end
+ --
+ parameters.designsize = (metadata.designsize or 10)*65536
+ parameters.ascender = abs(metadata.ascender or 0)
+ parameters.descender = abs(metadata.descender or 0)
+ parameters.units = 1000
+ --
+ properties.spacer = spacer
+ properties.encodingbytes = 2
+ properties.format = fonts.formats[filename] or "type1"
+ properties.filename = filename
+ properties.fontname = fontname
+ properties.fullname = fullname
+ properties.psname = fullname
+ properties.name = filename or fullname or fontname
+ --
+ if next(characters) then
+ return {
+ characters = characters,
+ descriptions = descriptions,
+ parameters = parameters,
+ resources = resources,
+ properties = properties,
+ goodies = goodies,
+ }
+ end
+ end
+ return nil
+end
+
+--[[ldx--
+<p>Originally we had features kind of hard coded for <l n='afm'/>
+files but since I expect to support more font formats, I decided
+to treat this fontformat like any other and handle features in a
+more configurable way.</p>
+--ldx]]--
+
+function afm.setfeatures(tfmdata,features)
+ local okay = constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
+ if okay then
+ return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
+ else
+ return { } -- will become false
+ end
+end
+
+local function checkfeatures(specification)
+end
+
+local function afmtotfm(specification)
+ local afmname = specification.filename or specification.name
+ if specification.forced == "afm" or specification.format == "afm" then -- move this one up
+ if trace_loading then
+ report_afm("forcing afm format for %a",afmname)
+ end
+ else
+ local tfmname = findbinfile(afmname,"ofm") or ""
+ if tfmname ~= "" then
+ if trace_loading then
+ report_afm("fallback from afm to tfm for %a",afmname)
+ end
+ return -- just that
+ end
+ end
+ if afmname ~= "" then
+ -- weird, isn't this already done then?
+ local features = constructors.checkedfeatures("afm",specification.features.normal)
+ specification.features.normal = features
+ constructors.hashinstance(specification,true) -- also weird here
+ --
+ specification = definers.resolve(specification) -- new, was forgotten
+ local cache_id = specification.hash
+ local tfmdata = containers.read(constructors.cache, cache_id) -- cache with features applied
+ if not tfmdata then
+ local rawdata = afm.load(afmname)
+ if rawdata and next(rawdata) then
+ adddimensions(rawdata)
+ tfmdata = copytotfm(rawdata)
+ if tfmdata and next(tfmdata) then
+ local shared = tfmdata.shared
+ if not shared then
+ shared = { }
+ tfmdata.shared = shared
+ end
+ shared.rawdata = rawdata
+ shared.features = features
+ shared.processes = afm.setfeatures(tfmdata,features)
+ end
+ elseif trace_loading then
+ report_afm("no (valid) afm file found with name %a",afmname)
+ end
+ tfmdata = containers.write(constructors.cache,cache_id,tfmdata)
+ end
+ return tfmdata
+ end
+end
+
+--[[ldx--
+<p>As soon as we could intercept the <l n='tfm'/> reader, I implemented an
+<l n='afm'/> reader. Since traditional <l n='pdftex'/> could use <l n='opentype'/>
+fonts with <l n='afm'/> companions, the following method also could handle
+those cases, but now that we can handle <l n='opentype'/> directly we no longer
+need this features.</p>
+--ldx]]--
+
+local function read_from_afm(specification)
+ local tfmdata = afmtotfm(specification)
+ if tfmdata then
+ tfmdata.properties.name = specification.name
+ tfmdata = constructors.scale(tfmdata, specification)
+ local allfeatures = tfmdata.shared.features or specification.features.normal
+ constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
+ fonts.loggers.register(tfmdata,'afm',specification)
+ end
+ return tfmdata
+end
+
+--[[ldx--
+<p>Here comes the implementation of a few features. We only implement
+those that make sense for this format.</p>
+--ldx]]--
+
+local function prepareligatures(tfmdata,ligatures,value)
+ if value then
+ local descriptions = tfmdata.descriptions
+ for unicode, character in next, tfmdata.characters do
+ local description = descriptions[unicode]
+ local dligatures = description.ligatures
+ if dligatures then
+ local cligatures = character.ligatures
+ if not cligatures then
+ cligatures = { }
+ character.ligatures = cligatures
+ end
+ for unicode, ligature in next, dligatures do
+ cligatures[unicode] = {
+ char = ligature,
+ type = 0
+ }
+ end
+ end
+ end
+ end
+end
+
+local function preparekerns(tfmdata,kerns,value)
+ if value then
+ local rawdata = tfmdata.shared.rawdata
+ local resources = rawdata.resources
+ local unicodes = resources.unicodes
+ local descriptions = tfmdata.descriptions
+ for u, chr in next, tfmdata.characters do
+ local d = descriptions[u]
+ local newkerns = d[kerns]
+ if newkerns then
+ local kerns = chr.kerns
+ if not kerns then
+ kerns = { }
+ chr.kerns = kerns
+ end
+ for k,v in next, newkerns do
+ local uk = unicodes[k]
+ if uk then
+ kerns[uk] = v
+ end
+ end
+ end
+ end
+ end
+end
+
+local list = {
+ -- [0x0022] = 0x201D,
+ [0x0027] = 0x2019,
+ -- [0x0060] = 0x2018,
+}
+
+local function texreplacements(tfmdata,value)
+ local descriptions = tfmdata.descriptions
+ local characters = tfmdata.characters
+ for k, v in next, list do
+ characters [k] = characters [v] -- we forget about kerns
+ descriptions[k] = descriptions[v] -- we forget about kerns
+ end
+end
+
+local function ligatures (tfmdata,value) prepareligatures(tfmdata,'ligatures', value) end
+local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end
+local function kerns (tfmdata,value) preparekerns (tfmdata,'kerns', value) end
+local function extrakerns (tfmdata,value) preparekerns (tfmdata,'extrakerns', value) end
+
+registerafmfeature {
+ name = "liga",
+ description = "traditional ligatures",
+ initializers = {
+ base = ligatures,
+ node = ligatures,
+ }
+}
+
+registerafmfeature {
+ name = "kern",
+ description = "intercharacter kerning",
+ initializers = {
+ base = kerns,
+ node = kerns,
+ }
+}
+
+registerafmfeature {
+ name = "extrakerns",
+ description = "additional intercharacter kerning",
+ initializers = {
+ base = extrakerns,
+ node = extrakerns,
+ }
+}
+
+registerafmfeature {
+ name = 'tlig',
+ description = 'tex ligatures',
+ initializers = {
+ base = texligatures,
+ node = texligatures,
+ }
+}
+
+registerafmfeature {
+ name = 'trep',
+ description = 'tex replacements',
+ initializers = {
+ base = texreplacements,
+ node = texreplacements,
+ }
+}
+
+-- readers
+
+local check_tfm = readers.check_tfm
+
+fonts.formats.afm = "type1"
+fonts.formats.pfb = "type1"
+
+local function check_afm(specification,fullname)
+ local foundname = findbinfile(fullname, 'afm') or "" -- just to be sure
+ if foundname == "" then
+ foundname = fonts.names.getfilename(fullname,"afm") or ""
+ end
+ if foundname == "" and afm.autoprefixed then
+ local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.*
+ if encoding and shortname and fonts.encodings.known[encoding] then
+ shortname = findbinfile(shortname,'afm') or "" -- just to be sure
+ if shortname ~= "" then
+ foundname = shortname
+ if trace_defining then
+ report_afm("stripping encoding prefix from filename %a",afmname)
+ end
+ end
+ end
+ end
+ if foundname ~= "" then
+ specification.filename = foundname
+ specification.format = "afm"
+ return read_from_afm(specification)
+ end
+end
+
+function readers.afm(specification,method)
+ local fullname, tfmdata = specification.filename or "", nil
+ if fullname == "" then
+ local forced = specification.forced or ""
+ if forced ~= "" then
+ tfmdata = check_afm(specification,specification.name .. "." .. forced)
+ end
+ if not tfmdata then
+ method = method or definers.method or "afm or tfm"
+ if method == "tfm" then
+ tfmdata = check_tfm(specification,specification.name)
+ elseif method == "afm" then
+ tfmdata = check_afm(specification,specification.name)
+ elseif method == "tfm or afm" then
+ tfmdata = check_tfm(specification,specification.name) or check_afm(specification,specification.name)
+ else -- method == "afm or tfm" or method == "" then
+ tfmdata = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
+ end
+ end
+ else
+ tfmdata = check_afm(specification,fullname)
+ end
+ return tfmdata
+end
+
+function readers.pfb(specification,method) -- only called when forced
+ local original = specification.specification
+ if trace_defining then
+ report_afm("using afm reader for %a",original)
+ end
+ specification.specification = gsub(original,"%.pfb",".afm")
+ specification.forced = "afm"
+ return readers.afm(specification,method)
+end
|