font-sel.lua /size: 33 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-sel'] = {
2    version   = 1.001,
3    comment   = "companion to font-sel.mkvi",
4    author    = "Wolfgang Schuster",
5    copyright = "Wolfgang Schuster",
6    license   = "GNU General Public License"
7}
8
9local next, type = next, type
10
11local context                    = context
12local cleanname                  = fonts.names.cleanname
13local gsub, splitup, find, lower = string.gsub, string.splitup, string.find, string.lower
14local concat, sortedkeys         = table.concat, table.sortedkeys
15local merge, remove              = table.merge, table.remove
16local splitbase, removesuffix    = file.splitbase, file.removesuffix
17local splitat, lpegmatch         = lpeg.splitat, lpeg.match
18
19local formatters                 = string.formatters
20local settings_to_array          = utilities.parsers.settings_to_array
21local settings_to_hash           = utilities.parsers.settings_to_hash
22local allocate                   = utilities.storage.allocate
23
24local v_default                  = interfaces.variables.default
25
26local implement                  = interfaces.implement
27
28local fonts                      = fonts
29
30local getlookups                 = fonts.names.getlookups
31local registerdesignsizes        = fonts.goodies.designsizes.register
32local bodyfontsizes              = storage.shared.bodyfontsizes
33
34fonts.select                     = fonts.select or { }
35local selectfont                 = fonts.select
36
37selectfont.data                  = selectfont.data         or allocate()
38selectfont.fallbacks             = selectfont.fallbacks    or allocate()
39selectfont.methods               = selectfont.methods      or allocate()
40selectfont.extras                = selectfont.extras       or allocate()
41selectfont.alternatives          = selectfont.alternatives or allocate()
42selectfont.presets               = selectfont.presets      or allocate()
43selectfont.defaults              = selectfont.defaults     or allocate()
44
45storage.register("fonts/select/presets", selectfont.presets, "fonts.select.presets")
46
47local data                       = selectfont.data
48local fallbacks                  = selectfont.fallbacks
49local methods                    = selectfont.methods
50local extras                     = selectfont.extras
51local alternatives               = selectfont.alternatives
52local presets                    = selectfont.presets
53local defaults                   = selectfont.defaults
54
55local ctx_definefontsynonym      = context.definefontsynonym
56local ctx_resetfontfallback      = context.resetfontfallback
57local ctx_startfontclass         = context.startfontclass
58local ctx_stopfontclass          = context.stopfontclass
59local ctx_loadfontgoodies        = context.loadfontgoodies
60local ctx_definefontfallback     = context.definefontfallback
61local ctx_definetypeface         = context.definetypeface
62local ctx_definebodyfont         = context.definebodyfont
63
64local trace_register     = false  trackers.register("selectfont.register",     function(v) trace_register     = v end)
65local trace_files        = false  trackers.register("selectfont.files",        function(v) trace_files        = v end)
66local trace_features     = false  trackers.register("selectfont.features",     function(v) trace_features     = v end)
67local trace_goodies      = false  trackers.register("selectfont.goodies",      function(v) trace_goodies      = v end)
68local trace_alternatives = false  trackers.register("selectfont.alternatives", function(v) trace_alternatives = v end)
69local trace_typescript   = false  trackers.register("selectfont.typescripts",  function(v) trace_typescript   = v end)
70
71local report_selectfont   = logs.reporter("selectfont")
72local report_files        = logs.reporter("selectfont","files")
73local report_features     = logs.reporter("selectfont","features")
74local report_goodies      = logs.reporter("selectfont","goodies")
75local report_typescript   = logs.reporter("selectfont","typescripts")
76
77defaults["rm"] = { features = { ["sc"] = "*,f:smallcaps" } }
78defaults["ss"] = { features = { ["sc"] = "*,f:smallcaps" } }
79
80defaults["asanamath"]          = { options = { extras = "asana-math",           features = "math\\mathsizesuffix",         goodies = "anana-math"           } }
81defaults["cambriamath"]        = { options = { extras = "cambria-math",         features = "math\\mathsizesuffix",         goodies = "cambria-math"         } }
82defaults["dejavumath"]         = { options = { extras = "dejavu",               features = "math\\mathsizesuffix"                                           } }
83defaults["neoeuler"]           = { options = { extras = "euler-math",           features = "math\\mathsizesuffix"                                           } }
84defaults["latinmodernmath"]    = { options = { extras = "lm,lm-math",           features = "math\\mathsizesuffix,lm-math", goodies = "lm"                   } }
85defaults["lucidabrightmathot"] = { options = { extras = "lucida-opentype-math", features = "math\\mathsizesuffix",         goodies = "lucida-opentype-math" } }
86defaults["minionmath"]         = { options = { extras = "minion-math",          features = "math\\mathsizesuffix",         goodies = "minion-math"          } }
87defaults["texgyredejavumath"]  = { options = { extras = "dejavu",               features = "math\\mathsizesuffix"                                           } }
88defaults["texgyrepagellamath"] = { options = { extras = "texgyre",              features = "math\\mathsizesuffix"                                           } }
89defaults["texgyrebonummath"]   = { options = { extras = "texgyre",              features = "math\\mathsizesuffix"                                           } }
90defaults["texgyrescholamath"]  = { options = { extras = "texgyre",              features = "math\\mathsizesuffix"                                           } }
91defaults["texgyretermesmath"]  = { options = { extras = "texgyre",              features = "math\\mathsizesuffix"                                           } }
92defaults["xitsmath"]           = { options = { extras = "xits-math",            features = "math\\mathsizesuffix",         goodies = "xits-math"            } }
93
94extras["features"] = function(data,alternative,features)
95    local d = data.options.features
96    local e = gsub(gsub(features,"*",d),"{(.*)}","%1")
97    local f = data.features
98    if trace_features then
99        report_features("alternative '%s': saving features '%s'",alternative,e)
100    end
101    if not f then
102        f = { }
103        data.features = f
104    end
105    f[alternative] = e
106end
107
108extras["goodies"] = function(data,alternative,goodies)
109    local e = gsub(goodies,"{(.*)}","%1")
110    local g = data.goodies
111    if trace_goodies then
112        report_goodies("alternative '%s': saving goodies '%s'",alternative,e)
113    end
114    if not g then
115        g = { }
116        data.goodies = g
117    end
118    g[alternative] = e
119end
120
121local function selectfont_savefile(data,alternative,bodyfontsize,size,file)
122    local f    = data.files
123    local p, n = splitbase(file["filename"])
124    local t    = file["format"]
125    local r    = file["rawname"]
126    if t == "ttc" then
127        n = formatters["%s(%s)"](n,r)
128    end
129    if not f then
130        f = { }
131        data.files = f
132    end
133    local a = f[alternative]
134    if not a then
135        a = { }
136        f[alternative] = a
137    end
138    a[bodyfontsize] = { size, n }
139    if trace_files then
140        report_files("alternative '%s': saving file '%s' for size '%s'",alternative,n,size)
141    end
142end
143
144methods["name"] = function(data,alternative,name)
145    local family   = data.metadata.family
146    local filename = cleanname(gsub(name,"*",family))
147    if trace_alternatives then
148        report_selectfont("alternative '%s': using method 'name' with argument '%s'",alternative,filename)
149    end
150    local fontname = getlookups{ fontname = filename }
151    local fullname = getlookups{ fullname = filename }
152    if #fontname > 0 then
153        selectfont_savefile(data,alternative,0,"default",fontname[1])
154    elseif #fullname > 0 then
155        selectfont_savefile(data,alternative,0,"default",fullname[1])
156    else
157        if trace_alternatives then
158            report_selectfont("alternative '%s': no font was found for the requested name '%s'",alternative,filename)
159        end
160    end
161end
162
163methods["file"] = function(data,alternative,file)
164    local family   = data.metadata.family
165    local filename = cleanname(gsub(removesuffix(file),"*",family))
166    if trace_alternatives then
167        report_selectfont("alternative '%s': using method 'file' with argument '%s'",alternative,filename)
168    end
169    local filename = getlookups{ cleanfilename = cleanname(filename) }
170    if #filename > 0 then
171        selectfont_savefile(data,alternative,0,"default",filename[1])
172    else
173        if trace_alternatives then
174            report_selectfont("alternative '%s': no font was found for the requested file '%s'",alternative,cleanname(gsub(removesuffix(file),"*",family)))
175        end
176    end
177end
178
179local m_weight = {
180    ["thin"]       = 100,
181    ["extralight"] = 200,
182    ["light"]      = 300,
183    ["regular"]    = 400,
184    ["medium"]     = 500,
185    ["semibold"]   = 600,
186    ["bold"]       = 700,
187    ["extrabold"]  = 800,
188    ["black"]      = 900
189}
190
191local m_width = {
192    ["ultracondensed"] = 1,
193    ["extracondensed"] = 2,
194    ["condensed"]      = 3,
195    ["semicondensed"]  = 4,
196    ["normal"]         = 5,
197    ["semiexpanded"]   = 6,
198    ["expanded"]       = 7,
199    ["extraexpanded"]  = 8,
200    ["ultraexpanded"]  = 9,
201}
202
203local m_name = {
204    ["thin"]             = { weight = "thin"                                       },
205    ["thinitalic"]       = { weight = "thin",                  style = "italic"    },
206    ["extralight"]       = { weight = "extralight"                                 },
207    ["extralightitalic"] = { weight = "extralight",            style = "italic"    },
208    ["light"]            = { weight = "light"                                      },
209    ["lightitalic"]      = { weight = "light",                 style = "italic"    },
210    ["regular"]          = { weight = { "regular", "medium" }                      },
211    ["italic"]           = { weight = { "regular", "medium" }, style = "italic"    },
212    ["medium"]           = { weight = "medium"                                     },
213    ["mediumitalic"]     = { weight = "medium",                style = "italic"    },
214    ["semibold"]         = { weight = "semibold"                                   },
215    ["semibolditalic"]   = { weight = "semibold",              style = "italic"    },
216    ["bold"]             = { weight = { "bold", "semibold" }                       },
217    ["bolditalic"]       = { weight = { "bold", "semibold" },  style = "italic"    },
218    ["extrabold"]        = { weight = "extrabold"                                  },
219    ["extrabolditalic"]  = { weight = "extrabold",             style = "italic"    },
220    ["black"]            = { weight = "black"                                      },
221    ["blackitalic"]      = { weight = "black",                 style = "italic"    },
222    ["smallcaps"]        = { weight = "regular",             variant = "smallcaps" },
223}
224
225local m_alternative = {
226    ["tf"] = "regular",
227    ["bf"] = "bold",
228    ["it"] = "italic",
229    ["sl"] = "italic",
230    ["bi"] = "bolditalic",
231    ["bs"] = "bolditalic",
232    ["sc"] = "smallcaps"
233}
234
235local function m_style_family(family)
236    local askedname  = cleanname(family)
237    local familyname = getlookups{ familyname = askedname }
238    local family     = getlookups{ family     = askedname }
239    local fontname   = getlookups{ fontname   = askedname }
240    if #familyname > 0 then
241        return familyname
242    elseif #family > 0 then
243        return family
244    elseif #fontname > 0 then
245        local fontfamily = fontname[1]["familyname"]
246        report_selectfont("the name '%s' is not a proper family name, use '%s' instead.",askedname,fontfamily)
247        return nil
248    else
249        return nil
250    end
251end
252
253local function m_style_subfamily(entries,style,family)
254    local t      = { }
255    local style  = cleanname(style)
256    local family = cleanname(family)
257    for index, entry in next, entries do
258        if entry["familyname"] == family and entry["subfamilyname"] == style then -- familyname + subfamilyname
259            t[#t+1] = entry
260        elseif entry["family"] == family and entry["subfamily"] == style then -- family + subfamily
261            t[#t+1] = entry
262        end
263    end
264    return #t ~= 0 and t or nil
265end
266
267local function m_style_weight(entries,style)
268    local t = { }
269    local weight    = m_name[style] and m_name[style]["weight"] or "regular"
270    if type(weight) == "table" then
271        for _, w in next, weight do
272            local found = false
273            local pfmweight = m_weight[w]
274            for index, entry in next, entries do
275                if entry["pfmweight"] == pfmweight then
276                    found = true
277                    t[#t+1] = entry
278                elseif entry["weight"] == w then
279                    found = true
280                    t[#t+1] = entry
281                end
282            end
283            if found then break end
284        end
285    else
286        local pfmweight = m_weight[weight]
287        for index, entry in next, entries do
288            if entry["pfmweight"] == pfmweight then
289                t[#t+1] = entry
290            elseif entry["weight"] == weight then
291                t[#t+1] = entry
292            end
293        end
294    end
295    return #t ~= 0 and t or nil
296end
297
298local function m_style_style(entries,style)
299    local t = { }
300    local style = m_name[style] and m_name[style]["style"] or "normal"
301    for index, entry in next, entries do
302        if style == "italic" and entry["angle"] and entry["angle"] ~= 0 then
303            t[#t+1] = entry
304        elseif style == "normal" and entry["angle"] and entry["angle"] ~= 0 then
305         -- Fix needed for fonts with wrong value for the style field
306        elseif entry["style"] == style then
307            t[#t+1] = entry
308        end
309    end
310    return #t ~= 0 and t or nil
311end
312
313local function m_style_variant(entries,style)
314    local t = { }
315    local variant = m_name[style] and m_name[style]["variant"] or "normal"
316    for index, entry in next, entries do
317        if entry["variant"] == variant then
318            t[#t+1] = entry
319        end
320    end
321    return #t ~= 0 and t or nil
322end
323
324local function m_style_width(entries,style)
325    local t = { }
326    local width    = m_name[style] and m_name[style]["width"] or "normal"
327    local pfmwidth = m_width[width]
328    for index, entry in next, entries do
329        if entry["pfmwidth"] == pfmwidth then
330            t[#t+1] = entry
331        end
332    end
333    return #t ~= 0 and t or nil
334end
335
336local function m_style_size(data,alternative,entries)
337    if #entries == 1 then
338        selectfont_savefile(data,alternative,0,"default",entries[1])
339    else
340        for index, entry in next, entries do
341            local minsize = entry["minsize"]
342            local maxsize = entry["maxsize"]
343            if minsize and maxsize then
344                for size, state in next, bodyfontsizes do
345                    local bodyfontsize, _ = number.splitdimen(size)
346                          bodyfontsize    = bodyfontsize * 10
347                    if minsize < bodyfontsize and bodyfontsize < maxsize then
348                        if bodyfontsize == 100 then
349                            selectfont_savefile(data,alternative,0,"default",entry)
350                        end
351                        selectfont_savefile(data,alternative,bodyfontsize,size,entry)
352                    end
353                end
354            else
355                if trace_alternatives then
356                    report_selectfont("alternative '%s': multiple files are available for the requested style '%s' from '%s'",alternative,style,family)
357                end
358            end
359        end
360    end
361end
362
363methods["style"] = function(data,alternative,style)
364    local fontfamily = data.metadata.family
365    local designsize = data.options.designsize
366    local fontstyle  = m_alternative[style] or style
367    local entries    = m_style_family(fontfamily)
368    if entries then
369        local subfamily = m_style_subfamily(entries,fontstyle,fontfamily)
370        if subfamily then
371            entries = subfamily
372        else
373            entries = m_style_weight(entries,fontstyle)
374            if entries then
375                entries = m_style_style(entries,fontstyle)
376                if entries then
377                    entries = m_style_variant(entries,fontstyle)
378                    if entries and #entries > 1 and designsize == "default" then
379                        entries = m_style_width(entries,fontstyle)
380                    end
381                end
382            end
383        end
384    end
385    if entries then
386        m_style_size(data,alternative,entries)
387    else
388        if trace_alternatives then
389            report_selectfont("alternative '%s': no font was found for the requested style '%s' from '%s'",alternative,style,family)
390        end
391    end
392end
393
394methods[v_default] = function(data,alternative)
395    local family = data.metadata.family
396    if trace_alternatives then
397        report_selectfont("alternative '%s': using method 'default'",alternative)
398    end
399    local result = getlookups{ familyname = cleanname(family) }
400    if #result == 1 and alternative == "tf" then
401        if trace_alternatives then
402            report_selectfont("alternative '%s': the family '%s' contains only one font",alternative,family)
403        end
404        selectfont_savefile(data,alternative,0,"default",result[1])
405     -- if trace_alternatives then
406     --     report_selectfont("alternative '%s': changing method 'default' to method 'style'",alternative)
407     -- end
408     -- methods["file"](data,alternative,result[1]["filename"])
409    else
410        if trace_alternatives then
411            report_selectfont("alternative '%s': changing method 'default' to method 'style'",alternative)
412        end
413        methods["style"](data,alternative,alternative)
414    end
415end
416
417local function selectfont_savealternative(data,alternative,userdata)
418    local a = data.alternatives
419    local e = userdata[alternative]
420    if not a then
421        a = { }
422        data.alternatives = a
423    end
424    a[alternative] = e
425end
426
427function selectfont.fontdata(index)
428    local data     = data[index]
429    local style    = data.metadata.style
430    local defaults = defaults[style]
431    if defaults then
432        for category, argument in next, defaults do
433            local extra  = extras[category]
434            if extra then
435                for alternative, entry in next, argument do
436                    extra(data,alternative,entry)
437                end
438            end
439        end
440    end
441end
442
443function selectfont.userdata(index)
444    local data     = data[index]
445    local preset   = data.options.preset
446    local presets  = presets[preset]
447    local userdata = settings_to_hash(data.userdata)
448    if presets then
449        merge(userdata,presets)
450    end
451    for alternative, _ in next, alternatives do
452        selectfont_savealternative(data,alternative,userdata)
453    end
454end
455
456function selectfont.registerfiles(index)
457    local data  = data[index]
458    local colon = splitat(":",true)
459    for alternative, _ in next, alternatives do
460        local arguments = data.alternatives[alternative]
461        if arguments and arguments ~= "" then
462            local entries = settings_to_array(arguments)
463            for index, entry in next, entries do
464                method, argument = lpegmatch(colon,entry)
465                if not argument then
466                    argument = method
467                    method   = "name"
468                end
469                if #entries == 1 and method == "features" then
470                    extras["features"](data,alternative,argument)
471                    methods[v_default](data,alternative)
472                else
473                    (extras[method] or methods[method] or methods[v_default])(data,alternative,argument)
474                end
475            end
476        else
477            methods[v_default](data,alternative)
478        end
479    end
480end
481
482function selectfont.registerfontalternative(alternative)
483    local a = alternatives[alternative]
484    if not a then
485        if trace_register then
486            report_selectfont("register alternative '%s'",alternative)
487        end
488        a = true
489        alternatives[alternative] = a
490    end
491end
492
493function selectfont.registerfallback(index)
494    local data      = data[index]
495    local fontclass = data.metadata.typeface
496    local fontstyle = data.metadata.style
497    local fallback  = fallbacks[fontclass]
498    if not fallback then
499        fallback = { }
500        fallbacks[fontclass] = fallback
501    end
502    local entries = fallback[fontstyle]
503    if not entries then
504        entries = { }
505        fallback[fontstyle] = entries
506    end
507    entries[#entries+1] = index
508end
509
510function selectfont.registerfontfamily(settings)
511    local index = #data + 1
512    data[index] = settings
513    selectfont.fontdata     (index)
514    selectfont.userdata     (index)
515    selectfont.registerfiles(index)
516    return index
517end
518
519local m_synonym = {
520    ["rm"] = {
521        ["tf"] = "Serif",
522        ["bf"] = "SerifBold",
523        ["it"] = "SerifItalic",
524        ["sl"] = "SerifSlanted",
525        ["bi"] = "SerifBoldItalic",
526        ["bs"] = "SerifBoldSlanted",
527        ["sc"] = "SerifCaps",
528    },
529    ["ss"] = {
530        ["tf"] = "Sans",
531        ["bf"] = "SansBold",
532        ["it"] = "SansItalic",
533        ["sl"] = "SansSlanted",
534        ["bi"] = "SansBoldItalic",
535        ["bs"] = "SansBoldSlanted",
536        ["sc"] = "SansCaps",
537    },
538    ["tt"] = {
539        ["tf"] = "Mono",
540        ["bf"] = "MonoBold",
541        ["it"] = "MonoItalic",
542        ["sl"] = "MonoSlanted",
543        ["bi"] = "MonoBoldItalic",
544        ["bs"] = "MonoBoldSlanted",
545        ["sc"] = "MonoCaps",
546    },
547    ["mm"] = {
548        ["tf"] = "MathRoman",
549        ["bf"] = "MathBold",
550    },
551    ["hw"] = {
552        ["tf"] = "Handwriting",
553    },
554    ["cg"] = {
555        ["tf"] = "Calligraphy",
556    },
557}
558
559function selectfont.features(data,style,alternative)
560    local family   = data.metadata.family
561    local features = data.features
562    local options  = data.options
563    local defaults = defaults[cleanname(family)]
564    if features and features[alternative] then
565        return features[alternative]
566    elseif defaults and defaults.options and defaults.options.features then
567        return defaults.options.features
568    else
569        return options.features
570    end
571end
572
573function selectfont.goodies(data,style,alternative)
574    local family   = data.metadata.family
575    local goodies  = data.goodies
576    local options  = data.options
577    local defaults = defaults[cleanname(family)]
578    if goodies and goodies[alternative] then
579        return goodies[alternative]
580    elseif defaults and defaults.options and defaults.options.goodies then
581        return defaults.options.goodies
582    else
583        return options.goodies
584    end
585end
586
587function selectfont.fontsynonym(data,class,style,alternative,index)
588    local fontfiles    = data.files[alternative] or data.files["tf"]
589    local fontsizes    = sortedkeys(fontfiles)
590    local fallback     = index ~= 0
591    local fontclass    = lower(class)
592  --local fontfeature  = data.features and data.features[alternative] or data.options.features
593  --local fontgoodie   = data.goodies  and data.goodies [alternative] or data.options.goodies
594    local fontfeature  = selectfont.features(data,style,alternative)
595    local fontgoodie   = selectfont.goodies (data,style,alternative)
596    local synonym      = m_synonym[style] and m_synonym[style][alternative]
597    local fontfile     = formatters    ["file-%s-%s-%s"](fontclass,style,alternative)
598    local fontsynonym  = formatters ["synonym-%s-%s-%s"](fontclass,style,alternative)
599    if fallback then
600        fontfile     = formatters    ["file-%s-%s-%s-%s"](fontclass,style,alternative,index)
601        fontsynonym  = formatters ["synonym-%s-%s-%s-%s"](fontclass,style,alternative,index)
602    end
603    local fontfallback = formatters["fallback-%s-%s-%s"](fontclass,style,alternative)
604    for _, fontsize in next, fontsizes do
605     -- if trace_typescript then
606     --     report_typescript("synonym: '%s', size: '%s', file: '%s'",fontfile,fontfiles[fontsize][1],fontfiles[fontsize][2])
607     -- end
608        registerdesignsizes(fontfile,fontfiles[fontsize][1],fontfiles[fontsize][2])
609    end
610    if fallback then
611     -- if trace_typescript then
612     --     report_typescript("synonym: '%s', file: '%s', features: '%s'",fontsynonym,fontfile,fontfeature)
613     -- end
614        ctx_definefontsynonym( { fontsynonym }, { fontfile }, { features = fontfeature } )
615    else
616     -- if trace_typescript then
617     --     report_typescript("synonym: '%s', file: '%s', features: '%s', goodies: '%s', fallbacks: '%s'",fontsynonym,fontfile,fontfeature,fontgoodie,fontfallback)
618     -- end
619        ctx_definefontsynonym( { fontsynonym }, { fontfile }, { features = fontfeature, goodies = fontgoodie, fallbacks = fontfallback } )
620        if synonym then
621         -- if trace_typescript then
622         --     report_typescript("synonym: '%s', file: '%s'",synonym,fontsynonym)
623         -- end
624            ctx_definefontsynonym( { synonym }, { fontsynonym } )
625        end
626    end
627end
628
629function selectfont.fontfallback(data,class,style,alternative,index)
630    local range        = data.options.range
631    local scale        = data.options.rscale ~= "" and data.options.rscale or 1
632    local check        = data.options.check  ~= "" and data.options.check  or ""
633    local force        = data.options.force  ~= "" and data.options.force  or ""
634    local fontfeature  = data.features and data.features[alternative] or data.options.features
635    local fontclass    = lower(class)
636    local fontsynonym  = formatters["synonym-%s-%s-%s-%s"](fontclass,style,alternative,index)
637    local fontfallback = formatters["fallback-%s-%s-%s"]  (fontclass,style,alternative)
638    if index == 1 then
639        ctx_resetfontfallback( { fontfallback } )
640    end
641 -- if trace_typescript then
642 --     report_typescript("fallback: '%s', synonym: '%s', range: '%s', scale: '%s', check: '%s', force: '%s'",fontfallback,fontsynonym,range,scale,check,force)
643 -- end
644    ctx_definefontfallback( { fontfallback }, { fontsynonym }, { range }, { rscale = scale, check = check, force = force } )
645end
646
647function selectfont.filefallback(data,class,style,alternative,index)
648    local range        = data.options.range
649    local offset       = data.options.offset
650    local scale        = data.options.rscale ~= "" and data.options.rscale or 1
651    local check        = data.options.check  ~= "" and data.options.check  or "yes"
652    local force        = data.options.force  ~= "" and data.options.force  or "yes"
653    local fontfile     = data.files[alternative] and data.files[alternative][0] or data.files["tf"][0]
654    local fontfeature  = data.features and data.features[alternative] or data.options.features
655    local fontclass    = lower(class)
656    local fontfallback = formatters["fallback-%s-%s-%s"](fontclass,style,alternative)
657    if index == 1 then
658        ctx_resetfontfallback( { fontfallback } )
659    end
660 -- if trace_typescript then
661 --     report_typescript("fallback: '%s', file: '%s', features: '%s', range: '%s', scale: '%s', check: '%s', force: '%s', offset: '%s'",fontfallback,fontfile[2],fontfeature,range,scale,check,force,offset)
662 -- end
663    ctx_definefontfallback( { fontfallback }, { formatters["file:%s*%s"](fontfile[2],fontfeature) }, { range }, { rscale = scale, check = check, force = force, offset = offset } )
664end
665
666function selectfont.mathfallback(index,entry,class,style)
667    local data = data[entry]
668    ctx_startfontclass( { class } )
669        for alternative, _ in next, alternatives do
670            if alternative == "tf" or alternative == "bf" then
671                selectfont.filefallback(data,class,style,alternative,index)
672            end
673        end
674    ctx_stopfontclass()
675end
676
677function selectfont.textfallback(index,entry,class,style)
678    local data = data[entry]
679    ctx_startfontclass( { class } )
680        for alternative, _ in next, alternatives do
681            selectfont.fontsynonym (data,class,style,alternative,index)
682            selectfont.fontfallback(data,class,style,alternative,index)
683        end
684    ctx_stopfontclass()
685end
686
687function selectfont.fallback(data)
688    local fontclass = data.metadata.typeface
689    local fontstyle = data.metadata.style
690    local fallbacks = fallbacks[fontclass] and fallbacks[fontclass][fontstyle]
691    if fallbacks then
692        for index, entry in next, fallbacks do
693         -- I need different fallback routines for math and text because
694         -- font synonyms can’t be used with math fonts and I have to apply
695         -- feature settings with the \definefontfallback command.
696            if fontstyle == "mm" then
697                selectfont.mathfallback(index,entry,fontclass,fontstyle)
698            else
699                selectfont.textfallback(index,entry,fontclass,fontstyle)
700            end
701        end
702    end
703end
704
705function selectfont.typescript(data)
706    local class    = data.metadata.typeface
707    local family   = data.metadata.family
708    local style    = data.metadata.style
709    local extras   = data.options.extras
710    local defaults = defaults[cleanname(family)]
711    if extras == "" then
712        extras = defaults and defaults.options and defaults.options.extras or ""
713    end
714    ctx_startfontclass( { class } )
715        if extras ~= "" then
716            extras = settings_to_array(extras)
717            for _, extra in next, extras do
718                ctx_loadfontgoodies( { extra } )
719            end
720        end
721        for alternative, _ in next, alternatives do
722            if style == "mm" then
723             -- Set math fonts only for upright and bold alternatives
724                if alternative == "tf" or alternative == "bf" then
725                    selectfont.fontsynonym (data,class,style,alternative,0)
726                end
727            else
728                selectfont.fontsynonym (data,class,style,alternative,0)
729            end
730        end
731    ctx_stopfontclass()
732end
733
734function selectfont.bodyfont(data)
735    local class       = data.metadata.typeface
736    local fontstyle   = data.metadata.style
737    local fontclass   = lower(class)
738    local fontsizes   = concat(sortedkeys(bodyfontsizes),",")
739    local fontsynonym = nil
740    local fontlist    = { }
741    for alternative, _ in next, alternatives do
742        fontsynonym           = formatters["synonym-%s-%s-%s"](fontclass,fontstyle,alternative)
743        fontlist[#fontlist+1] = formatters["%s=%s sa 1"]      (alternative,fontsynonym)
744     -- if trace_typescript then
745     --     report_typescript("alternative '%s': synonym '%s'",alternative,fontsynonym)
746     -- end
747    end
748    fontlist = concat(fontlist,",")
749    ctx_definebodyfont( { class }, { fontsizes }, { fontstyle }, { fontlist } )
750end
751
752local m_style = {
753    ["rm"] = "serif",
754    ["ss"] = "sans",
755    ["tt"] = "mono",
756    ["mm"] = "math",
757    ["hw"] = "handwriting",
758    ["cg"] = "calligraphy",
759}
760
761function selectfont.typeface(data)
762    local fontclass = data.metadata.typeface
763    local fontstyle = data.metadata.style
764    local style     = m_style[fontstyle]
765    local size      = data.options.designsize ~= "" and data.options.designsize or "default"
766    local scale     = data.options.rscale     ~= "" and data.options.rscale     or 1
767 -- if trace_typescript then
768 --     report_typescript("class: '%s', style: '%s', size: '%s', scale: '%s'",fontclass,fontstyle,size,scale)
769 -- end
770    ctx_definetypeface( { fontclass }, { fontstyle }, { style }, { "" }, { "default" }, { designsize = size, rscale = scale } )
771end
772
773function selectfont.default(data)
774    local family    = data.metadata.family
775    local fontclass = data.metadata.typeface
776    local fontstyle = data.metadata.style
777    local style     = m_style[fontstyle]
778    report_selectfont("the requested font '%s' has no files for the 'tf' alternative, Latin Modern is used instead.",family)
779    ctx_definetypeface( { fontclass }, { fontstyle }, { style }, { "modern" }, { "default" } )
780end
781
782function selectfont.definefontfamily(index)
783    local data      = data[index]
784    local fontstyle = data.metadata.style
785    local fontfiles = data.files and data.files["tf"]
786    if fontfiles then
787        selectfont.fallback  (data)
788        selectfont.typescript(data)
789        if fontstyle ~= "mm" then
790            selectfont.bodyfont(data)
791        end
792        selectfont.typeface(data)
793    else
794        selectfont.default(data)
795    end
796end
797
798function selectfont.definefallbackfamily(index)
799    local data      = data[index]
800    local family    = data.metadata.family
801    local fontclass = data.metadata.typeface
802    local fontstyle = data.metadata.style
803    local fontfiles = data.files
804    if fontfiles then
805        selectfont.registerfallback(index)
806    else
807        report_selectfont("the requested fallback font '%s' for typeface '%s' style '%s' was ignored because no files where found.",family,fontclass,fontstyle)
808    end
809end
810
811function selectfont.definefontfamilypreset(name,data)
812    local p = presets[name]
813    local d = settings_to_hash(data)
814    if not p then
815        p = d
816        presets[name] = p
817    end
818end
819
820implement {
821    name      = "registerfontfamily",
822    actions   = { selectfont.registerfontfamily, context },
823    arguments = {
824        {
825            {
826                "metadata", {
827                    { "typeface" },
828                    { "style" },
829                    { "family" }
830                }
831            },
832            {
833                "options", {
834                    { "designsize" },
835                    { "rscale" },
836                    { "goodies" },
837                    { "preset" },
838                    { "extras" },
839                    { "features" },
840                    { "range" },
841                    { "offset" },
842                    { "check" },
843                    { "force" }
844                }
845            },
846            {
847                "userdata"
848            }
849        }
850    }
851}
852
853implement {
854    name      = "registerfontalternative",
855    actions   = selectfont.registerfontalternative,
856    arguments = "string"
857}
858
859implement {
860    name      = "definefontfamily",
861    actions   = selectfont.definefontfamily,
862    arguments = "integer"
863}
864
865implement {
866    name      = "definefallbackfamily",
867    actions   = selectfont.definefallbackfamily,
868    arguments = "integer"
869}
870
871implement {
872    name      = "definefontfamilypreset",
873    actions   = selectfont.definefontfamilypreset,
874    arguments = "2 strings",
875}
876