summaryrefslogtreecommitdiff
path: root/tex/context/base/font-syn.lua
blob: 24456e7ebeb67b57366a2164c8f4cbb979fc7eec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
if not modules then modules = { } end modules ['font-syn'] = {
    version   = 1.001,
    comment   = "companion to font-ini.tex",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

--[[ldx--
<p>This module implements a name to filename resolver. Names are resolved
using a table that has keys filtered from the font related files.</p>
--ldx]]--

fonts = fonts or { }
input = input or { }
texmf = texmf or { }

fonts.names            = { }
fonts.names.filters    = { }
fonts.names.data       = { }
fonts.names.version    = 1.04
fonts.names.saved      = false
fonts.names.loaded     = false
fonts.names.be_clever  = true
fonts.names.enabled    = true
fonts.names.autoreload = toboolean(os.env['MTX.FONTS.AUTOLOAD'] or os.env['MTX_FONTS_AUTOLOAD'] or "no")
fonts.names.cache      = containers.define("fonts","data",fonts.names.version,true)

--[[ldx--
<p>It would make sense to implement the filters in the related modules,
but to keep the overview, we define them here.</p>
--ldx]]--

fonts.names.filters.otf = fontforge.info
fonts.names.filters.ttf = fontforge.info
fonts.names.filters.ttc = fontforge.info

function fonts.names.filters.afm(name)
    local f = io.open(name)
    if f then
        local hash = { }
        for line in f:lines() do
            local key, value = line:match("^(.+)%s+(.+)%s*$")
            if key and #key > 0 then
                hash[key:lower()] = value
            end
            if line:find("StartCharMetrics") then
                break
            end
        end
        f:close()
        return hash
    else
        return nil
    end
end

function fonts.names.filters.pfb(name)
    return fontforge.info(name)
end

--[[ldx--
<p>The scanner loops over the filters using the information stored in
the file databases. Watch how we check not only for the names, but also
for combination with the weight of a font.</p>
--ldx]]--

fonts.names.filters.list = {
    "otf", "ttf", "ttc", "afm" -- pfb is quite messy, too many messages, maybe broken
}

fonts.names.filters.fixes = {
    { "reg$", "regular", },
    { "ita$", "italic", },
    { "ital$", "italic", },
    { "cond$", "condensed", },
}

fonts.names.xml_configuration_file    = "fonts.conf" -- a bit weird format, bonus feature
fonts.names.environment_path_variable = "osfontdir"  -- the official way, in minimals etc

function fonts.names.getpaths(instance)
    local hash, result = { }, { }
    local function collect(t)
        for i=1, #t do
            local v = input.clean_path(t[i])
            v = v:gsub("/+$","")
            local key = v:lower()
            if not hash[key] then
                hash[key], result[#result+1] = true, v
            end
        end
    end
    local path = fonts.names.environment_path_variable
    if path and path ~= "" then
        collect(input.expanded_path_list(instance,path))
    end
    local name = fonts.names.xml_configuration_file
    if name and not name == "" then
        local name = input.find_file(instance,name,"other")
        if name ~= "" then
            collect(xml.collect_texts(xml.load(name),"dir",true))
        end
    end
    function fonts.names.getpaths()
        return result
    end
    return result
end

function fonts.names.identify()
    fonts.names.data = {
        mapping = { },
        version = fonts.names.version
    }
    local done, mapping, nofread, nofok = { }, fonts.names.data.mapping, 0, 0
    local function add(n,fontname,filename,suffix, sub)
        n = n:lower()
        if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end
        n = n:gsub("[^%a%d]","")
        if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end
    end
    local function check(result, filename, suffix, is_sub)
        local fontname = result.fullname
        if fontname then
            add(result.fullname, fontname, filename, suffix, is_sub)
        end
        if result.fontname then
            fontname = fontname or result.fontname
            add(result.fontname, fontname, filename, suffix, is_sub)
        end
        if result.familyname and result.weight then
            local madename = result.familyname .. " " .. result.weight
            fontname = fontname or madename
            add(madename, fontname, filename, suffix, is_sub)
        end
    end
    local function identify(completename,name,suffix)
        if not done[name] and io.exists(completename) then
            nofread = nofread + 1
            logs.info("fontnames", "identifying " .. suffix .. " font " .. completename)
            logs.push()
            local result = fonts.names.filters[suffix](completename)
            logs.pop()
            if result then
                if not result[1] then
                    check(result,name,suffix,false)
                else for _, r in ipairs(result) do
                    check(r,name,suffix,true)
                end end
            end
            done[name] = true
        end
    end
    local function traverse(what, method)
        for n, suffix in pairs(fonts.names.filters.list) do
            nofread, nofok  = 0, 0
            local t = os.gettimeofday() -- use elapser
            logs.report("fontnames", string.format("identifying %s font files with suffix %s",what,suffix))
            method(suffix)
            logs.report("fontnames", string.format("%s %s files identified, %s hash entries added, runtime %s seconds", nofread, what,nofok, os.gettimeofday()-t))
        end
    end
    traverse("tree", function(suffix)
        input.with_files(texmf.instance,".*%." .. suffix .. "$", function(method,root,path,name)
            if method == "file" then
                identify(root .."/" .. path .. "/" .. name,name,suffix)
            end
        end)
    end)
    traverse("system", function(suffix)
        local pathlist = fonts.names.getpaths(texmf.instance) -- input.expanded_path_list(texmf.instance,"osfontdir")
        if pathlist then
            for _, path in ipairs(pathlist) do
                path = input.clean_path(path .. "/")
                path = path:gsub("/+","/")
                local pattern = path .. "*." .. suffix
                logs.report("fontnames", "globbing path " .. pattern)
                local t = dir.glob(pattern)
                for _, name in pairs(t) do -- ipairs
                    local mode = lfs.attributes(name,'mode')
                    if mode == "file" then
                        identify(name,file.basename(name),suffix)
                    end
                end
            end
        end
    end)
    local t = { }
    for _, f in ipairs(fonts.names.filters.fixes) do
        local expression, replacement = f[1], f[2]
        for k,v in pairs(mapping) do
            local fix, pos = k:gsub(expression,replacement)
            if pos > 0 and not mapping[fix] then
                t[fix] = v
            end
        end
    end
    for k,v in pairs(t) do
        mapping[k] = v
    end
end

function fonts.names.load(reload)
    if not fonts.names.loaded then
        if reload then
            if containers.is_usable(fonts.names.cache, "names") then
                fonts.names.identify()
                containers.write(fonts.names.cache, "names", fonts.names.data)
            end
            fonts.names.saved = true
        else
            fonts.names.data = containers.read(fonts.names.cache, "names")
            if not fonts.names.saved then
                if table.is_empty(fonts.names.data) or table.is_empty(fonts.names.data.mapping) then
                    fonts.names.load(true)
                end
                fonts.names.saved = true
            end
        end
        fonts.names.loaded = true
    end
end

function fonts.names.list(pattern,reload)
    fonts.names.load(reload)
    if fonts.names.loaded then
        local t = { }
        for k,v in pairs(fonts.names.data.mapping) do
            if k:find(pattern) then
                t[k] = v
            end
        end
        return t
    else
        return nil
    end
end

--[[ldx--
<p>The resolver also checks if the cached names are loaded. Being clever
here is for testing purposes only (it deals with names prefixed by an
encoding name).</p>
--ldx]]--

do

    local function found(name)
        if fonts.names.data then
            local result, mapping = nil, fonts.names.data.mapping
            local mn = mapping[name]
            if mn then
                return mn[2], mn[3], mn[4]
            end
            if fonts.names.be_clever then -- this will become obsolete
                local encoding, tag = name:match("^(.-)[%-%:](.+)$")
                local mt = mapping[tag]
                if tag and fonts.enc.is_known(encoding) and mt then
                    return mt[1], encoding .. "-" .. mt[3], mt[4]
                end
            end
            -- name, type, file
            for k,v in pairs(mapping) do
                if k:find(name) then
                    return v[2], v[3], v[4]
                end
            end
            local condensed = name:gsub("[^%a%d]","")
            local mc = mapping[condensed]
            if mc then
                return mc[2], mc[3], mc[4]
            end
            for k,v in pairs(mapping) do
                if k:find(condensed) then
                    return v[2], v[3], v[4]
                end
            end
        end
        return nil, nil, nil
    end

    local reloaded = false

    function fonts.names.resolve(askedname, sub)
        if not askedname then
            return nil, nil
        elseif fonts.names.enabled then
            askedname = askedname:lower()
            fonts.names.load()
            local name, filename, is_sub = found(askedname)
            if not filename and not reloaded and fonts.names.autoreload then
                fonts.names.loaded = false
                reloaded = true
                io.flush()
                fonts.names.load(true)
                name, filename, is_sub = found(askedname)
            end
            if is_sub then
                return filename, name
            else
                return filename, sub
            end
        else
            return filename, sub
        end
    end

end

--[[ldx--
<p>A handy helper.</p>
--ldx]]--

function fonts.names.table(pattern,reload,all)
    local t = fonts.names.list(pattern,reload)
    if t then
        tex.sprint(tex.ctxcatcodes,"\\start\\nonknuthmode\\starttabulate[|T|T|T|T|T|]")
        tex.sprint(tex.ctxcatcodes,"\\NC hashname\\NC type\\NC fontname\\NC filename\\NC\\NR\\HL")
        for k,v in pairs(table.sortedkeys(t)) do
            if all or v == t[v][2]:lower() then
                local type, name, file = unpack(t[v])
                if type and name and file then
                    tex.sprint(tex.ctxcatcodes,string.format("\\NC %s\\NC %s\\NC %s\\NC %s\\NC\\NR",v,type, name, file))
                else
                    logs.report("font table", "skipping ".. v)
                end
            end
        end
        tex.sprint(tex.ctxcatcodes,"\\stoptabulate\\stop")
    end
end


--[[ldx--
<p>Fallbacks, not permanent but a transition thing.</p>
--ldx]]--

fonts.names.new_to_old = {
    ["lmroman10-capsregular"]                = "lmromancaps10-oblique",
    ["lmroman10-capsoblique"]                = "lmromancaps10-regular",
    ["lmroman10-demi"]                       = "lmromandemi10-oblique",
    ["lmroman10-demioblique"]                = "lmromandemi10-regular",
    ["lmroman8-oblique"]                     = "lmromanslant8-regular",
    ["lmroman9-oblique"]                     = "lmromanslant9-regular",
    ["lmroman10-oblique"]                    = "lmromanslant10-regular",
    ["lmroman12-oblique"]                    = "lmromanslant12-regular",
    ["lmroman17-oblique"]                    = "lmromanslant17-regular",
    ["lmroman10-boldoblique"]                = "lmromanslant10-bold",
    ["lmroman10-dunhill"]                    = "lmromandunh10-oblique",
    ["lmroman10-dunhilloblique"]             = "lmromandunh10-regular",
    ["lmroman10-unslanted"]                  = "lmromanunsl10-regular",
    ["lmsans10-demicondensed"]               = "lmsansdemicond10-regular",
    ["lmsans10-demicondensedoblique"]        = "lmsansdemicond10-oblique",
    ["lmsansquotation8-bold"]                = "lmsansquot8-bold",
    ["lmsansquotation8-boldoblique"]         = "lmsansquot8-boldoblique",
    ["lmsansquotation8-oblique"]             = "lmsansquot8-oblique",
    ["lmsansquotation8-regular"]             = "lmsansquot8-regular",
    ["lmtypewriter8-regular"]                = "lmmono8-regular",
    ["lmtypewriter9-regular"]                = "lmmono9-regular",
    ["lmtypewriter10-regular"]               = "lmmono10-regular",
    ["lmtypewriter12-regular"]               = "lmmono12-regular",
    ["lmtypewriter10-italic"]                = "lmmono10-italic",
    ["lmtypewriter10-oblique"]               = "lmmonoslant10-regular",
    ["lmtypewriter10-capsoblique"]           = "lmmonocaps10-oblique",
    ["lmtypewriter10-capsregular"]           = "lmmonocaps10-regular",
    ["lmtypewriter10-light"]                 = "lmmonolt10-regular",
    ["lmtypewriter10-lightoblique"]          = "lmmonolt10-oblique",
    ["lmtypewriter10-lightcondensed"]        = "lmmonoltcond10-regular",
    ["lmtypewriter10-lightcondensedoblique"] = "lmmonoltcond10-oblique",
    ["lmtypewriter10-dark"]                  = "lmmonolt10-bold",
    ["lmtypewriter10-darkoblique"]           = "lmmonolt10-boldoblique",
    ["lmtypewritervarwd10-regular"]          = "lmmonoproplt10-regular",
    ["lmtypewritervarwd10-oblique"]          = "lmmonoproplt10-oblique",
    ["lmtypewritervarwd10-light"]            = "lmmonoprop10-regular",
    ["lmtypewritervarwd10-lightoblique"]     = "lmmonoprop10-oblique",
    ["lmtypewritervarwd10-dark"]             = "lmmonoproplt10-bold",
    ["lmtypewritervarwd10-darkoblique"]      = "lmmonoproplt10-boldoblique",
}

fonts.names.old_to_new = table.swapped(fonts.names.new_to_old)