1if not modules then modules = { } end modules ['font-syn'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19local next, tonumber, type, tostring = next, tonumber, type, tostring
20local sub, gsub, match, find, lower, upper = string.sub, string.gsub, string.match, string.find, string.lower, string.upper
21local concat, sort, fastcopy, tohash = table.concat, table.sort, table.fastcopy, table.tohash
22local serialize, sortedhash = table.serialize, table.sortedhash
23local lpegmatch = lpeg.match
24local unpack = unpack or table.unpack
25local formatters, topattern = string.formatters, string.topattern
26local round = math.round
27local P, R, S, C, Cc, Ct, Cs = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cs
28local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
29local isfile, modificationtime = lfs.isfile, lfs.modification
30
31local allocate = utilities.storage.allocate
32local sparse = utilities.storage.sparse
33local setmetatableindex = table.setmetatableindex
34
35local removesuffix = file.removesuffix
36local splitbase = file.splitbase
37local splitname = file.splitname
38local basename = file.basename
39local nameonly = file.nameonly
40local pathpart = file.pathpart
41local suffixonly = file.suffix
42local filejoin = file.join
43local is_qualified_path = file.is_qualified_path
44local exists = io.exists
45
46local findfile = resolvers.findfile
47local cleanpath = resolvers.cleanpath
48local resolveprefix = resolvers.resolve
49
50local settings_to_hash = utilities.parsers.settings_to_hash_tolerant
51
52local trace_names = false trackers.register("fonts.names", function(v) trace_names = v end)
53local trace_warnings = false trackers.register("fonts.warnings", function(v) trace_warnings = v end)
54local trace_specifications = false trackers.register("fonts.specifications", function(v) trace_specifications = v end)
55local trace_rejections = false trackers.register("fonts.rejections", function(v) trace_rejections = v end)
56
57local report_names = logs.reporter("fonts","names")
58
59
60
61
62fonts = fonts or { }
63
64local names = fonts.names or allocate { }
65fonts.names = names
66
67local filters = names.filters or { }
68names.filters = filters
69
70local treatments = fonts.treatments or { }
71fonts.treatments = treatments
72
73names.data = names.data or allocate { }
74
75names.version = 1.131
76names.basename = "names"
77names.saved = false
78names.loaded = false
79names.be_clever = true
80names.enabled = true
81names.cache = containers.define("fonts","data",names.version,true)
82
83local usesystemfonts = true
84local autoreload = true
85
86directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) end)
87directives.register("fonts.usesystemfonts", function(v) usesystemfonts = toboolean(v) end)
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
117local weights = Cs (
118 P("demibold")
119 + P("semibold")
120 + P("mediumbold")
121 + P("ultrabold")
122 + P("extrabold")
123 + P("ultralight")
124 + P("extralight")
125 + P("bold")
126 + P("demi")
127 + P("semi")
128 + P("light")
129 + P("medium")
130 + P("heavy")
131 + P("ultra")
132 + P("black")
133
134 + P("bol")
135 + P("regular") / "normal"
136)
137
138
139
140
141
142
143
144
145
146
147
148
149
150local normalized_weights = sparse {
151 regular = "normal",
152}
153
154local styles = Cs (
155 P("reverseoblique") / "reverseitalic"
156 + P("regular") / "normal"
157 + P("italic")
158 + P("oblique") / "italic"
159 + P("slanted")
160 + P("slant") / "slanted"
161 + P("roman") / "normal"
162 + P("ital") / "italic"
163 + P("ita") / "italic"
164
165)
166
167local normalized_styles = sparse {
168 reverseoblique = "reverseitalic",
169 regular = "normal",
170 oblique = "italic",
171}
172
173local widths = Cs(
174 P("condensed")
175 + P("thin")
176 + P("expanded")
177 + P("cond") / "condensed"
178
179 + P("normal")
180 + P("book") / "normal"
181)
182
183local normalized_widths = sparse()
184
185local variants = Cs(
186 P("smallcaps")
187 + P("oldstyle")
188 + P("caps") / "smallcaps"
189)
190
191local normalized_variants = sparse()
192
193names.knownweights = {
194 "black",
195 "bold",
196 "demi",
197 "demibold",
198 "extrabold",
199 "heavy",
200 "light",
201 "medium",
202 "mediumbold",
203 "normal",
204 "regular",
205 "semi",
206 "semibold",
207 "ultra",
208 "ultrabold",
209 "ultralight",
210}
211
212names.knownstyles = {
213 "italic",
214 "normal",
215 "oblique",
216 "regular",
217 "reverseitalic",
218 "reverseoblique",
219 "roman",
220 "slanted",
221}
222
223names.knownwidths = {
224 "book",
225 "condensed",
226 "expanded",
227 "normal",
228 "thin",
229}
230
231names.knownvariants = {
232 "normal",
233 "oldstyle",
234 "smallcaps",
235}
236
237local remappedweights = {
238 [""] = "normal",
239 ["bol"] = "bold",
240}
241
242local remappedstyles = {
243 [""] = "normal",
244}
245
246local remappedwidths = {
247 [""] = "normal",
248}
249
250local remappedvariants = {
251 [""] = "normal",
252}
253
254names.remappedweights = remappedweights setmetatableindex(remappedweights ,"self")
255names.remappedstyles = remappedstyles setmetatableindex(remappedstyles ,"self")
256names.remappedwidths = remappedwidths setmetatableindex(remappedwidths ,"self")
257names.remappedvariants = remappedvariants setmetatableindex(remappedvariants,"self")
258
259local any = P(1)
260
261local analyzed_table
262
263local analyzer = Cs (
264 (
265 weights / function(s) analyzed_table[1] = s return "" end
266 + styles / function(s) analyzed_table[2] = s return "" end
267 + widths / function(s) analyzed_table[3] = s return "" end
268 + variants / function(s) analyzed_table[4] = s return "" end
269 + any
270 )^0
271)
272
273local splitter = lpeg.splitat("-")
274
275function names.splitspec(askedname)
276 local name, weight, style, width, variant = lpegmatch(splitter,askedname)
277 weight = weight and lpegmatch(weights, weight) or weight
278 style = style and lpegmatch(styles, style) or style
279 width = width and lpegmatch(widths, width) or width
280 variant = variant and lpegmatch(variants,variant) or variant
281 if trace_names then
282 report_names("requested name %a split in name %a, weight %a, style %a, width %a and variant %a",
283 askedname,name,weight,style,width,variant)
284 end
285 if not weight or not weight or not width or not variant then
286 weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal"
287 if trace_names then
288 report_names("request %a normalized to '%s-%s-%s-%s-%s'",
289 askedname,name,weight,style,width,variant)
290 end
291 end
292 return name or askedname, weight, style, width, variant
293end
294
295local function analyzespec(somename)
296 if somename then
297 analyzed_table = { }
298 local name = lpegmatch(analyzer,somename)
299 return name, analyzed_table[1], analyzed_table[2], analyzed_table[3], analyzed_table[4]
300 end
301end
302
303
304
305
306filters.afm = fonts.handlers.afm.readers.getinfo
307filters.otf = fonts.handlers.otf.readers.getinfo
308filters.ttf = filters.otf
309filters.ttc = filters.otf
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412filters.list = {
413 "otf", "ttf", "ttc", "afm",
414}
415
416
417
418names.fontconfigfile = "fonts.conf"
419names.osfontdirvariable = "OSFONTDIR"
420names.extrafontsvariable = "EXTRAFONTS"
421names.runtimefontsvariable = "RUNTIMEFONTS"
422
423filters.paths = { }
424filters.names = { }
425
426function names.getpaths(trace)
427 local hash, result, r = { }, { }, 0
428 local function collect(t,where)
429 for i=1,#t do
430 local v = cleanpath(t[i])
431 v = gsub(v,"/+$","")
432 local key = lower(v)
433 report_names("variable %a specifies path %a",where,v)
434 if not hash[key] then
435 r = r + 1
436 result[r] = v
437 hash[key] = true
438 end
439 end
440 end
441 local path = names.osfontdirvariable or ""
442 if path ~= "" then
443 collect(resolvers.expandedpathlist(path),path)
444 end
445 local path = names.extrafontsvariable or ""
446 if path ~= "" then
447 collect(resolvers.expandedpathlist(path),path)
448 end
449 if xml then
450 local confname = resolvers.expansion("FONTCONFIG_FILE") or ""
451 if confname == "" then
452 confname = names.fontconfigfile or ""
453 end
454 if confname ~= "" then
455
456 local name = findfile(confname,"fontconfig files") or ""
457 if name == "" then
458
459 name = filejoin("/etc",confname)
460 if not isfile(name) then
461 name = ""
462 end
463 end
464 if name ~= "" and isfile(name) then
465 if trace_names then
466 report_names("%s fontconfig file %a","loading",name)
467 end
468 local xmldata = xml.load(name)
469
470 xml.include(xmldata,"include","",true,function(incname)
471 if not is_qualified_path(incname) then
472 local path = pathpart(name)
473 if path ~= "" then
474 incname = filejoin(path,incname)
475 end
476 end
477 if isfile(incname) then
478 if trace_names then
479 report_names("%s fontconfig file %a","merging included",incname)
480 end
481 return io.loaddata(incname)
482 elseif trace_names then
483 report_names("%s fontconfig file: %a","ignoring included",incname)
484 end
485 end)
486
487 local fontdirs = xml.collect_texts(xmldata,"dir",true)
488 if trace_names then
489 report_names("%s dirs found in fontconfig",#fontdirs)
490 end
491 collect(fontdirs,"fontconfig file")
492 end
493 end
494 end
495 sort(result)
496 function names.getpaths()
497 return result
498 end
499 return result
500end
501
502local function cleanname(name)
503 return (gsub(lower(name),"[^%a%d]",""))
504end
505
506local function cleanfilename(fullname,defaultsuffix)
507 if fullname then
508 local path, name, suffix = splitname(fullname)
509 if name then
510 name = gsub(lower(name),"[^%a%d]","")
511 if suffix and suffix ~= "" then
512 return name .. ".".. suffix
513 elseif defaultsuffix and defaultsuffix ~= "" then
514 return name .. ".".. defaultsuffix
515 else
516 return name
517 end
518 end
519 end
520 return "badfontname"
521end
522
523local sorter = function(a,b)
524 return a > b
525end
526
527
528
529names.cleanname = cleanname
530names.cleanfilename = cleanfilename
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546local function check_name(data,result,filename,modification,suffix,subfont)
547
548 local specifications = data.specifications
549
550 local fullname = result.fullname
551 local fontname = result.fontname
552 local family = result.family
553 local subfamily = result.subfamily
554 local familyname = result.familyname
555 local subfamilyname = result.subfamilyname
556
557
558 local weight = result.weight
559 local width = result.width
560 local italicangle = tonumber(result.italicangle)
561 local subfont = subfont
562 local rawname = fullname or fontname or familyname
563 local filebase = removesuffix(basename(filename))
564 local cleanfilename = cleanname(filebase)
565
566 fullname = fullname and cleanname(fullname)
567 fontname = fontname and cleanname(fontname)
568 family = family and cleanname(family)
569 subfamily = subfamily and cleanname(subfamily)
570 familyname = familyname and cleanname(familyname)
571 subfamilyname = subfamilyname and cleanname(subfamilyname)
572
573
574 weight = weight and cleanname(weight)
575 width = width and cleanname(width)
576 italicangle = italicangle == 0 and nil
577
578 local a_name, a_weight, a_style, a_width, a_variant = analyzespec(fullname or fontname or familyname)
579
580 local width = width or a_width
581 local variant = a_variant
582 local style = subfamilyname or subfamily
583 if style then
584 style = gsub(style,"[^%a]","")
585 elseif italicangle then
586 style = "italic"
587 end
588 if not variant or variant == "" then
589 variant = "normal"
590 end
591 if not weight or weight == "" then
592 weight = a_weight
593 end
594 if not style or style == "" then
595 style = a_style
596 end
597 if not familyname then
598 familyname = a_name
599 end
600 fontname = fontname or fullname or familyname or filebase
601 fullname = fullname or fontname
602 familyname = familyname or fontname
603
604 local units = result.units or 1000
605 local designsize = result.designsize or 0
606 local minsize = result.minsize or 0
607 local maxsize = result.maxsize or 0
608 local angle = result.italicangle or 0
609 local pfmwidth = result.pfmwidth or 0
610 local pfmweight = result.pfmweight or 0
611
612 local instancenames = result.instancenames
613
614 specifications[#specifications+1] = {
615 filename = filename,
616 cleanfilename = cleanfilename,
617
618 format = lower(suffix),
619 subfont = subfont,
620 rawname = rawname,
621 fullname = fullname,
622 fontname = fontname,
623 family = family,
624 subfamily = subfamily,
625 familyname = familyname,
626 subfamilyname = subfamilyname,
627
628
629 weight = weight,
630 style = style,
631 width = width,
632 variant = variant,
633 units = units ~= 1000 and units or nil,
634 pfmwidth = pfmwidth ~= 0 and pfmwidth or nil,
635 pfmweight = pfmweight ~= 0 and pfmweight or nil,
636 angle = angle ~= 0 and angle or nil,
637 minsize = minsize ~= 0 and minsize or nil,
638 maxsize = maxsize ~= 0 and maxsize or nil,
639 designsize = designsize ~= 0 and designsize or nil,
640 modification = modification ~= 0 and modification or nil,
641 instancenames = instancenames or nil,
642 }
643end
644
645local function cleanupkeywords()
646 local data = names.data
647 local specifications = names.data.specifications
648 if specifications then
649 local weights = { }
650 local styles = { }
651 local widths = { }
652 local variants = { }
653 for i=1,#specifications do
654 local s = specifications[i]
655
656 local _, b_weight, b_style, b_width, b_variant = analyzespec(s.weight)
657 local _, c_weight, c_style, c_width, c_variant = analyzespec(s.style)
658 local _, d_weight, d_style, d_width, d_variant = analyzespec(s.width)
659 local _, e_weight, e_style, e_width, e_variant = analyzespec(s.variant)
660 local _, f_weight, f_style, f_width, f_variant = analyzespec(s.fullname or "")
661 local weight = b_weight or c_weight or d_weight or e_weight or f_weight or "normal"
662 local style = b_style or c_style or d_style or e_style or f_style or "normal"
663 local width = b_width or c_width or d_width or e_width or f_width or "normal"
664 local variant = b_variant or c_variant or d_variant or e_variant or f_variant or "normal"
665 weight = remappedweights [weight or ""]
666 style = remappedstyles [style or ""]
667 width = remappedwidths [width or ""]
668 variant = remappedvariants[variant or ""]
669 weights [weight ] = (weights [weight ] or 0) + 1
670 styles [style ] = (styles [style ] or 0) + 1
671 widths [width ] = (widths [width ] or 0) + 1
672 variants[variant] = (variants[variant] or 0) + 1
673 if weight ~= s.weight then
674 s.fontweight = s.weight
675 end
676 s.weight, s.style, s.width, s.variant = weight, style, width, variant
677 end
678 local statistics = data.statistics
679 statistics.used_weights = weights
680 statistics.used_styles = styles
681 statistics.used_widths = widths
682 statistics.used_variants = variants
683 end
684end
685
686local function collectstatistics(runtime)
687 local data = names.data
688 local specifications = data.specifications
689 local statistics = data.statistics
690 if specifications then
691 local f_w = formatters["%i"]
692 local f_a = formatters["%0.2f"]
693
694 local weights = { }
695 local styles = { }
696 local widths = { }
697 local variants = { }
698
699 local angles = { }
700
701 local pfmweights = { } setmetatableindex(pfmweights,"table")
702 local pfmwidths = { } setmetatableindex(pfmwidths, "table")
703
704 for i=1,#specifications do
705 local s = specifications[i]
706
707 local weight = s.weight
708 local style = s.style
709 local width = s.width
710 local variant = s.variant
711 if weight then weights [weight ] = (weights [weight ] or 0) + 1 end
712 if style then styles [style ] = (styles [style ] or 0) + 1 end
713 if width then widths [width ] = (widths [width ] or 0) + 1 end
714 if variant then variants[variant] = (variants[variant] or 0) + 1 end
715
716 local angle = f_a(tonumber(s.angle) or 0)
717 angles[angle] = (angles[angles] or 0) + 1
718
719 local pfmweight = f_w(s.pfmweight or 0)
720 local pfmwidth = f_w(s.pfmwidth or 0)
721 local tweights = pfmweights[pfmweight]
722 local twidths = pfmwidths [pfmwidth]
723 tweights[pfmweight] = (tweights[pfmweight] or 0) + 1
724 twidths[pfmwidth] = (twidths [pfmwidth] or 0) + 1
725 end
726
727 statistics.weights = weights
728 statistics.styles = styles
729 statistics.widths = widths
730 statistics.variants = variants
731 statistics.angles = angles
732 statistics.pfmweights = pfmweights
733 statistics.pfmwidths = pfmwidths
734 statistics.fonts = #specifications
735
736 setmetatableindex(pfmweights,nil)
737 setmetatableindex(pfmwidths, nil)
738
739 report_names("")
740 report_names("statistics: ")
741 report_names("")
742 report_names("weights")
743 report_names("")
744 report_names(formatters[" %T"](weights))
745 report_names("")
746 report_names("styles")
747 report_names("")
748 report_names(formatters[" %T"](styles))
749 report_names("")
750 report_names("widths")
751 report_names("")
752 report_names(formatters[" %T"](widths))
753 report_names("")
754 report_names("variants")
755 report_names("")
756 report_names(formatters[" %T"](variants))
757 report_names("")
758 report_names("angles")
759 report_names("")
760 report_names(formatters[" %T"](angles))
761 report_names("")
762 report_names("pfmweights")
763 report_names("")
764 for k, v in sortedhash(pfmweights) do
765 report_names(formatters[" %-10s: %T"](k,v))
766 end
767 report_names("")
768 report_names("pfmwidths")
769 report_names("")
770 for k, v in sortedhash(pfmwidths) do
771 report_names(formatters[" %-10s: %T"](k,v))
772 end
773 report_names("")
774 report_names("registered fonts : %i", statistics.fonts)
775 report_names("read files : %i", statistics.readfiles)
776 report_names("skipped files : %i", statistics.skippedfiles)
777 report_names("duplicate files : %i", statistics.duplicatefiles)
778 if runtime then
779 report_names("total scan time : %0.3f seconds",runtime)
780 end
781 end
782end
783
784local function collecthashes()
785 local data = names.data
786 local mappings = data.mappings
787 local fallbacks = data.fallbacks
788 local specifications = data.specifications
789 local nofmappings = 0
790 local noffallbacks = 0
791 if specifications then
792
793 local conflicts = setmetatableindex("table")
794 for index=1,#specifications do
795 local specification = specifications[index]
796 local format = specification.format
797 local fullname = specification.fullname
798 local fontname = specification.fontname
799
800
801
802 local familyname = specification.familyname or specification.family
803 local subfamilyname = specification.subfamilyname
804 local subfamily = specification.subfamily
805 local weight = specification.weight
806 local mapping = mappings[format]
807 local fallback = fallbacks[format]
808 local instancenames = specification.instancenames
809 if fullname and not mapping[fullname] then
810 mapping[fullname] = index
811 nofmappings = nofmappings + 1
812 end
813 if fontname and not mapping[fontname] then
814 mapping[fontname] = index
815 nofmappings = nofmappings + 1
816 end
817 if instancenames then
818 for i=1,#instancenames do
819 local instance = fullname .. instancenames[i]
820 mapping[instance] = index
821 nofmappings = nofmappings + 1
822 end
823 end
824
825
826
827
828
829
830
831
832 if familyname then
833 if weight and weight ~= sub(familyname,#familyname-#weight+1,#familyname) then
834 local madename = familyname .. weight
835 if not mapping[madename] and not fallback[madename] then
836 fallback[madename] = index
837 noffallbacks = noffallbacks + 1
838 end
839 end
840 if subfamily and subfamily ~= sub(familyname,#familyname-#subfamily+1,#familyname) then
841 local extraname = familyname .. subfamily
842 if not mapping[extraname] and not fallback[extraname] then
843 fallback[extraname] = index
844 noffallbacks = noffallbacks + 1
845 end
846 end
847 if subfamilyname and subfamilyname ~= sub(familyname,#familyname-#subfamilyname+1,#familyname) then
848 local extraname = familyname .. subfamilyname
849 if not mapping[extraname] and not fallback[extraname] then
850 fallback[extraname] = index
851 noffallbacks = noffallbacks + 1
852 end
853 end
854
855 if not mapping[familyname] and not fallback[familyname] then
856 fallback[familyname] = index
857 noffallbacks = noffallbacks + 1
858 end
859 local conflict = conflicts[format]
860 conflict[familyname] = (conflict[familyname] or 0) + 1
861 end
862 end
863 for format, conflict in next, conflicts do
864 local fallback = fallbacks[format]
865 for familyname, n in next, conflict do
866 if n > 1 then
867 fallback[familyname] = nil
868 noffallbacks = noffallbacks - n
869 end
870 end
871 end
872 end
873 return nofmappings, noffallbacks
874end
875
876local function collectfamilies()
877 local data = names.data
878 local specifications = data.specifications
879 local families = data.families
880 for index=1,#specifications do
881 local familyname = specifications[index].familyname
882 local family = families[familyname]
883 if not family then
884 families[familyname] = { index }
885 else
886 family[#family+1] = index
887 end
888 end
889end
890
891local function checkduplicate(where)
892 local data = names.data
893 local mapping = data[where]
894 local specifications = data.specifications
895 local loaded = { }
896 if specifications and mapping then
897
898 local order = filters.list
899 for i=1,#order do
900 local m = mapping[order[i]]
901 for k, v in sortedhash(m) do
902 local s = specifications[v]
903 local hash = formatters["%s-%s-%s-%s-%s"](s.familyname,s.weight or "*",s.style or "*",s.width or "*",s.variant or "*")
904 local h = loaded[hash]
905 if h then
906 local ok = true
907 local fn = s.filename
908 for i=1,#h do
909 if h[i] == fn then
910 ok = false
911 break
912 end
913 end
914 if ok then
915 h[#h+1] = fn
916 end
917 else
918 loaded[hash] = { s.filename }
919 end
920 end
921 end
922 end
923 local n = 0
924 for k, v in sortedhash(loaded) do
925 local nv = #v
926 if nv > 1 then
927 if trace_warnings then
928 report_names("lookup %a clashes with %a",k,v)
929 end
930 n = n + nv
931 end
932 end
933 report_names("%a double lookups in %a",n,where)
934end
935
936local function checkduplicates()
937 checkduplicate("mappings")
938 checkduplicate("fallbacks")
939end
940
941local function sorthashes()
942 local data = names.data
943 local list = filters.list
944 local mappings = data.mappings
945 local fallbacks = data.fallbacks
946 local sorted_mappings = { }
947 local sorted_fallbacks = { }
948 data.sorted_mappings = sorted_mappings
949 data.sorted_fallbacks = sorted_fallbacks
950 for i=1,#list do
951 local l = list[i]
952 sorted_mappings [l] = table.keys(mappings[l])
953 sorted_fallbacks[l] = table.keys(fallbacks[l])
954 sort(sorted_mappings [l],sorter)
955 sort(sorted_fallbacks[l],sorter)
956 end
957 local sorted_families = table.keys(data.families)
958 data.sorted_families = sorted_families
959 sort(sorted_families,sorter)
960end
961
962local function unpackreferences()
963 local data = names.data
964 local specifications = data.specifications
965 if specifications then
966 for k, v in sortedhash(data.families) do
967 for i=1,#v do
968 v[i] = specifications[v[i]]
969 end
970 end
971 local mappings = data.mappings
972 if mappings then
973 for _, m in sortedhash(mappings) do
974 for k, v in sortedhash(m) do
975 m[k] = specifications[v]
976 end
977 end
978 end
979 local fallbacks = data.fallbacks
980 if fallbacks then
981 for _, f in sortedhash(fallbacks) do
982 for k, v in sortedhash(f) do
983 f[k] = specifications[v]
984 end
985 end
986 end
987 end
988end
989
990local function analyzefiles(olddata)
991
992 if not trace_warnings then
993 report_names("warnings are disabled (tracker 'fonts.warnings')")
994 end
995
996 local data = names.data
997 local done = { }
998 local totalnofread = 0
999 local totalnofskipped = 0
1000 local totalnofduplicates = 0
1001 local nofread = 0
1002 local nofskipped = 0
1003 local nofduplicates = 0
1004 local skip_paths = filters.paths
1005 local skip_names = filters.names
1006 local specifications = data.specifications
1007 local oldindices = olddata and olddata.indices or { }
1008 local oldspecifications = olddata and olddata.specifications or { }
1009 local oldrejected = olddata and olddata.rejected or { }
1010 local treatmentdata = treatments.data or { }
1011
1012
1013 local function walk_tree(pathlist,suffix,identify)
1014 if pathlist then
1015 for i=1,#pathlist do
1016 local path = pathlist[i]
1017 path = cleanpath(path .. "/")
1018 path = gsub(path,"/+","/")
1019 local pattern = path .. "**." .. suffix
1020 report_names("globbing path %a",pattern)
1021 local t = dir.glob(pattern)
1022 sort(t,sorter)
1023 for j=1,#t do
1024 local completename = t[j]
1025 identify(completename,basename(completename),suffix,completename)
1026 end
1027
1028 end
1029 end
1030 end
1031
1032 local function identify(completename,name,suffix,storedname)
1033 local pathpart, basepart = splitbase(completename)
1034 nofread = nofread + 1
1035 local treatment = treatmentdata[completename] or treatmentdata[basepart]
1036 if treatment and treatment.ignored then
1037 if trace_names or trace_rejections then
1038 report_names("%s font %a is ignored, reason %a",suffix,completename,treatment.comment or "unknown")
1039 end
1040 nofskipped = nofskipped + 1
1041 elseif done[name] then
1042 if lower(completename) ~= lower(done[name]) then
1043
1044 if trace_names or trace_rejections then
1045 report_names("%s font %a already done as %a",suffix,completename,done[name])
1046 end
1047 nofduplicates = nofduplicates + 1
1048 nofskipped = nofskipped + 1
1049 end
1050 elseif not exists(completename) then
1051
1052 if trace_names or trace_rejections then
1053 report_names("%s font %a does not really exist",suffix,completename)
1054 end
1055 nofskipped = nofskipped + 1
1056 elseif not is_qualified_path(completename) and findfile(completename,suffix) == "" then
1057
1058 if trace_names or trace_rejections then
1059 report_names("%s font %a cannot be found by backend",suffix,completename)
1060 end
1061 nofskipped = nofskipped + 1
1062 else
1063 if #skip_paths > 0 then
1064 for i=1,#skip_paths do
1065 if find(pathpart,skip_paths[i]) then
1066 if trace_names or trace_rejections then
1067 report_names("rejecting path of %s font %a",suffix,completename)
1068 end
1069 nofskipped = nofskipped + 1
1070 return
1071 end
1072 end
1073 end
1074 if #skip_names > 0 then
1075 for i=1,#skip_paths do
1076 if find(basepart,skip_names[i]) then
1077 done[name] = true
1078 if trace_names or trace_rejections then
1079 report_names("rejecting name of %s font %a",suffix,completename)
1080 end
1081 nofskipped = nofskipped + 1
1082 return
1083 end
1084 end
1085 end
1086 if trace_names then
1087 report_names("identifying %s font %a",suffix,completename)
1088 end
1089
1090 local result = nil
1091 local modification = modificationtime(completename)
1092 if olddata and modification and modification > 0 then
1093 local oldindex = oldindices[storedname]
1094 if oldindex then
1095 local oldspecification = oldspecifications[oldindex]
1096 if oldspecification and oldspecification.filename == storedname then
1097 local oldmodification = oldspecification.modification
1098 if oldmodification == modification then
1099 result = oldspecification
1100 specifications[#specifications + 1] = result
1101 else
1102
1103 end
1104 else
1105
1106 end
1107 elseif oldrejected[storedname] == modification then
1108 result = false
1109 end
1110 end
1111 if result == nil then
1112 local lsuffix = lower(suffix)
1113 local result, message = filters[lsuffix](completename)
1114 if result then
1115 if #result > 0 then
1116 for r=1,#result do
1117 check_name(data,result[r],storedname,modification,suffix,r)
1118 end
1119 else
1120 check_name(data,result,storedname,modification,suffix)
1121 end
1122 if trace_warnings and message and message ~= "" then
1123 report_names("warning when identifying %s font %a, %s",suffix,completename,message)
1124 end
1125 elseif trace_warnings then
1126 nofskipped = nofskipped + 1
1127 report_names("error when identifying %s font %a, %s",suffix,completename,message or "unknown")
1128 end
1129 end
1130 done[name] = completename
1131 end
1132 logs.flush()
1133 end
1134
1135 local function traverse(what, method)
1136 local list = filters.list
1137 for n=1,#list do
1138 local suffix = list[n]
1139 local t = os.gettimeofday()
1140 nofread, nofskipped, nofduplicates = 0, 0, 0
1141 suffix = lower(suffix)
1142 report_names("identifying %s font files with suffix %a",what,suffix)
1143 method(suffix)
1144 suffix = upper(suffix)
1145 report_names("identifying %s font files with suffix %a",what,suffix)
1146 method(suffix)
1147 totalnofread, totalnofskipped, totalnofduplicates = totalnofread + nofread, totalnofskipped + nofskipped, totalnofduplicates + nofduplicates
1148 local elapsed = os.gettimeofday() - t
1149 report_names("%s %s files identified, %s skipped, %s duplicates, %s hash entries added, runtime %0.3f seconds",nofread,what,nofskipped,nofduplicates,nofread-nofskipped,elapsed)
1150 end
1151 logs.flush()
1152 end
1153
1154
1155
1156 local function withtree(suffix)
1157 resolvers.dowithfilesintree(".*%." .. suffix .. "$", function(method,root,path,name)
1158 if method == "file" or method == "tree" then
1159 local completename = root .."/" .. path .. "/" .. name
1160 completename = resolveprefix(completename)
1161 identify(completename,name,suffix,name)
1162 return true
1163 end
1164 end, function(blobtype,blobpath,pattern)
1165 blobpath = resolveprefix(blobpath)
1166 report_names("scanning path %a for %s files",blobpath,suffix)
1167 end, function(blobtype,blobpath,pattern,total,checked,done)
1168 blobpath = resolveprefix(blobpath)
1169 report_names("%s %s files checked, %s okay",checked,suffix,done)
1170 end)
1171 end
1172
1173 local function withlsr(suffix)
1174
1175
1176 local pathlist = resolvers.splitpath(resolvers.showpath("ls-R") or "")
1177 walk_tree(pathlist,suffix,identify)
1178 end
1179
1180 local function withsystem(suffix)
1181 walk_tree(names.getpaths(trace),suffix,identify)
1182 end
1183
1184 traverse("tree",withtree)
1185
1186 if not usesystemfonts then
1187 report_names("ignoring system fonts")
1188 elseif texconfig.kpse_init then
1189 traverse("lsr", withlsr)
1190 else
1191 traverse("system", withsystem)
1192 end
1193
1194 data.statistics.readfiles = totalnofread
1195 data.statistics.skippedfiles = totalnofskipped
1196 data.statistics.duplicatefiles = totalnofduplicates
1197
1198
1199
1200
1201
1202end
1203
1204local function addfilenames()
1205 local data = names.data
1206 local specifications = data.specifications
1207 local indices = { }
1208 local files = { }
1209 for i=1,#specifications do
1210 local fullname = specifications[i].filename
1211 files[cleanfilename(fullname)] = fullname
1212 indices[fullname] = i
1213 end
1214 data.files = files
1215 data.indices = indices
1216end
1217
1218local function rejectclashes()
1219 local specifications = names.data.specifications
1220 local used = { }
1221 local okay = { }
1222 local rejected = { }
1223 local o = 0
1224 for i=1,#specifications do
1225 local s = specifications[i]
1226 local f = s.fontname
1227 if f then
1228 local fnd = used[f]
1229 local fnm = s.filename
1230 if fnd then
1231 if trace_warnings then
1232 report_names("fontname %a clashes, %a rejected in favor of %a",f,fnm,fnd)
1233 end
1234 rejected[f] = s.modification
1235 else
1236 used[f] = fnm
1237 o = o + 1
1238 okay[o] = s
1239 end
1240 else
1241 o = o + 1
1242 okay[o] = s
1243 end
1244 end
1245 local d = #specifications - #okay
1246 if d > 0 then
1247 report_names("%s files rejected due to clashes",d)
1248 end
1249 names.data.specifications = okay
1250 names.data.rejected = rejected
1251end
1252
1253local function resetdata()
1254 local mappings = { }
1255 local fallbacks = { }
1256 for _, k in next, filters.list do
1257 mappings [k] = { }
1258 fallbacks[k] = { }
1259 end
1260 names.data = {
1261 version = names.version,
1262 mappings = mappings,
1263 fallbacks = fallbacks,
1264 specifications = { },
1265 families = { },
1266 statistics = { },
1267 names = { },
1268 indices = { },
1269 rejected = { },
1270 datastate = resolvers.datastate(),
1271 }
1272end
1273
1274function names.identify(force)
1275 local starttime = os.gettimeofday()
1276 resetdata()
1277 analyzefiles(not force and names.readdata(names.basename))
1278 rejectclashes()
1279 collectfamilies()
1280 cleanupkeywords()
1281 collecthashes()
1282 checkduplicates()
1283 addfilenames()
1284
1285 collectstatistics(os.gettimeofday()-starttime)
1286end
1287
1288function names.is_permitted(name)
1289 return containers.is_usable(names.cache, name)
1290end
1291function names.writedata(name,data)
1292 containers.write(names.cache,name,data)
1293end
1294function names.readdata(name)
1295 return containers.read(names.cache,name)
1296end
1297
1298function names.load(reload,force)
1299 if not names.loaded then
1300 if reload then
1301 if names.is_permitted(names.basename) then
1302 names.identify(force)
1303 names.writedata(names.basename,names.data)
1304 else
1305 report_names("unable to access database cache")
1306 end
1307 names.saved = true
1308 end
1309 local data = names.readdata(names.basename)
1310 names.data = data
1311 if not names.saved then
1312 if not data or not next(data) or not data.specifications or not next(data.specifications) then
1313 names.load(true)
1314 end
1315 names.saved = true
1316 end
1317 if not data then
1318 report_names("accessing the data table failed")
1319 else
1320 unpackreferences()
1321 sorthashes()
1322 end
1323 names.loaded = true
1324 end
1325end
1326
1327local function list_them(mapping,sorted,pattern,t,all)
1328 if mapping[pattern] then
1329 t[pattern] = mapping[pattern]
1330 else
1331 for k=1,#sorted do
1332 local v = sorted[k]
1333 if not t[v] and find(v,pattern) then
1334 t[v] = mapping[v]
1335 if not all then
1336 return
1337 end
1338 end
1339 end
1340 end
1341end
1342
1343function names.list(pattern,reload,all)
1344 names.load()
1345 if names.loaded then
1346 local t = { }
1347 local data = names.data
1348 if data then
1349 local list = filters.list
1350 local mappings = data.mappings
1351 local sorted_mappings = data.sorted_mappings
1352 local fallbacks = data.fallbacks
1353 local sorted_fallbacks = data.sorted_fallbacks
1354 for i=1,#list do
1355 local format = list[i]
1356 list_them(mappings[format],sorted_mappings[format],pattern,t,all)
1357 if next(t) and not all then
1358 return t
1359 end
1360 list_them(fallbacks[format],sorted_fallbacks[format],pattern,t,all)
1361 if next(t) and not all then
1362 return t
1363 end
1364 end
1365 end
1366 return t
1367 end
1368end
1369
1370local reloaded = false
1371
1372local function is_reloaded()
1373 if not reloaded then
1374 local data = names.data
1375 if autoreload then
1376 local c_status = serialize(resolvers.datastate())
1377 local f_status = serialize(data.datastate)
1378 if c_status == f_status then
1379 if trace_names then
1380 report_names("font database has matching configuration and file hashes")
1381 end
1382 return
1383 else
1384 report_names("font database has mismatching configuration and file hashes")
1385 end
1386 else
1387 report_names("font database is regenerated (controlled by directive 'fonts.autoreload')")
1388 end
1389 names.loaded = false
1390 reloaded = true
1391 logs.flush()
1392 names.load(true)
1393 end
1394end
1395
1396
1397
1398
1399local function fuzzy(mapping,sorted,name,sub)
1400 local condensed = gsub(name,"[^%a%d]","")
1401 local pattern = condensed .. "$"
1402 local matches = false
1403 for k=1,#sorted do
1404 local v = sorted[k]
1405 if v == condensed then
1406 return mapping[v], v
1407 elseif find(v,pattern) then
1408 return mapping[v], v
1409 elseif find(v,condensed) then
1410 if matches then
1411 matches[#matches+1] = v
1412 else
1413 matches = { v }
1414 end
1415 end
1416 end
1417 if matches then
1418 if #matches > 1 then
1419 sort(matches,function(a,b) return #a < #b end)
1420 end
1421 matches = matches[1]
1422 return mapping[matches], matches
1423 end
1424end
1425
1426
1427
1428local function checkinstance(found,askedname)
1429 local instancenames = found.instancenames
1430 if instancenames then
1431 local fullname = found.fullname
1432 for i=1,#instancenames do
1433 local instancename = instancenames[i]
1434 if fullname .. instancename == askedname then
1435 local f = fastcopy(found)
1436 f.instances = nil
1437 f.instance = instancename
1438 return f
1439 end
1440 end
1441 end
1442 return found
1443end
1444
1445local function foundname(name,sub)
1446 local data = names.data
1447 local mappings = data.mappings
1448 local sorted_mappings = data.sorted_mappings
1449 local fallbacks = data.fallbacks
1450 local sorted_fallbacks = data.sorted_fallbacks
1451 local list = filters.list
1452
1453
1454
1455 for i=1,#list do
1456 local l = list[i]
1457 local found = mappings[l][name]
1458 if found then
1459 if trace_names then
1460 report_names("resolved via direct name match: %a",name)
1461 end
1462 return checkinstance(found,name)
1463 end
1464 end
1465 for i=1,#list do
1466 local l = list[i]
1467 local found, fname = fuzzy(mappings[l],sorted_mappings[l],name,sub)
1468 if found then
1469 if trace_names then
1470 report_names("resolved via fuzzy name match: %a onto %a",name,fname)
1471 end
1472 return checkinstance(found,name)
1473 end
1474 end
1475 for i=1,#list do
1476 local l = list[i]
1477 local found = fallbacks[l][name]
1478 if found then
1479 if trace_names then
1480 report_names("resolved via direct fallback match: %a",name)
1481 end
1482 return checkinstance(found,name)
1483 end
1484 end
1485 for i=1,#list do
1486 local l = list[i]
1487 local found, fname = fuzzy(sorted_mappings[l],sorted_fallbacks[l],name,sub)
1488 if found then
1489 if trace_names then
1490 report_names("resolved via fuzzy fallback match: %a onto %a",name,fname)
1491 end
1492 return checkinstance(found,name)
1493 end
1494 end
1495 if trace_names then
1496 report_names("font with name %a cannot be found",name)
1497 end
1498end
1499
1500function names.resolvedspecification(askedname,sub)
1501 if askedname and askedname ~= "" and names.enabled then
1502 askedname = cleanname(askedname)
1503 names.load()
1504 local found = foundname(askedname,sub)
1505 if not found and is_reloaded() then
1506 found = foundname(askedname,sub)
1507 end
1508 return found
1509 end
1510end
1511
1512function names.resolve(askedname,sub)
1513 local found = names.resolvedspecification(askedname,sub)
1514 if found then
1515 return found.filename, found.subfont and found.rawname, found.subfont, found.instance
1516 end
1517end
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530local runtimefiles = { }
1531local runtimedone = false
1532
1533local function addruntimepath(path)
1534 names.load()
1535 local paths = type(path) == "table" and path or { path }
1536 local suffixes = tohash(filters.list)
1537 for i=1,#paths do
1538 local path = resolveprefix(paths[i])
1539 if path ~= "" then
1540 local list = dir.glob(path.."/*")
1541 for i=1,#list do
1542 local fullname = list[i]
1543 local suffix = lower(suffixonly(fullname))
1544 if suffixes[suffix] then
1545 local c = cleanfilename(fullname)
1546 runtimefiles[c] = fullname
1547 if trace_names then
1548 report_names("adding runtime filename %a for %a",c,fullname)
1549 end
1550 end
1551 end
1552 end
1553 end
1554end
1555
1556local function addruntimefiles(variable)
1557 local paths = variable and resolvers.expandedpathlistfromvariable(variable)
1558 if paths and #paths > 0 then
1559 addruntimepath(paths)
1560 end
1561end
1562
1563names.addruntimepath = addruntimepath
1564names.addruntimefiles = addruntimefiles
1565
1566function names.getfilename(askedname,suffix)
1567 if not runtimedone then
1568 addruntimefiles(names.runtimefontsvariable)
1569 runtimedone = true
1570 end
1571 local cleanname = cleanfilename(askedname,suffix)
1572 local found = runtimefiles[cleanname]
1573 if found then
1574 return found
1575 end
1576 names.load()
1577 local files = names.data.files
1578 local found = files and files[cleanname] or ""
1579 if found == "" and is_reloaded() then
1580 files = names.data.files
1581 found = files and files[cleanname] or ""
1582 end
1583 if found and found ~= "" then
1584 return resolvers.findbinfile(found,suffix) or ""
1585 end
1586end
1587
1588
1589
1590local function s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family)
1591 if family then
1592 for i=1,#family do
1593 local f = family[i]
1594 if f and weight == f.weight and style == f.style and width == f.width and variant == f.variant then
1595 found[#found+1], done[f] = f, true
1596 if not all then return end
1597 end
1598 end
1599 end
1600end
1601local function m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname)
1602 for i=1,#sorted do
1603 local k = sorted[i]
1604 local family = families[k]
1605 for i=1,#family do
1606 local f = family[i]
1607 if not done[f] and weight == f.weight and style == f.style and width == f.width and variant == f.variant and find(f.fontname,strictname) then
1608 found[#found+1], done[f] = f, true
1609 if not all then return end
1610 end
1611 end
1612 end
1613end
1614
1615local function s_collect_weight_style_width(found,done,all,weight,style,width,family)
1616 if family then
1617 for i=1,#family do
1618 local f = family[i]
1619 if f and weight == f.weight and style == f.style and width == f.width then
1620 found[#found+1], done[f] = f, true
1621 if not all then return end
1622 end
1623 end
1624 end
1625end
1626local function m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname)
1627 for i=1,#sorted do
1628 local k = sorted[i]
1629 local family = families[k]
1630 for i=1,#family do
1631 local f = family[i]
1632 if not done[f] and weight == f.weight and style == f.style and width == f.width and find(f.fontname,strictname) then
1633 found[#found+1], done[f] = f, true
1634 if not all then return end
1635 end
1636 end
1637 end
1638end
1639
1640local function s_collect_weight_style(found,done,all,weight,style,family)
1641 if family then
1642 for i=1,#family do local f = family[i]
1643 if f and weight == f.weight and style == f.style then
1644 found[#found+1], done[f] = f, true
1645 if not all then return end
1646 end
1647 end
1648 end
1649end
1650local function m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname)
1651 for i=1,#sorted do
1652 local k = sorted[i]
1653 local family = families[k]
1654 for i=1,#family do
1655 local f = family[i]
1656 if not done[f] and weight == f.weight and style == f.style and find(f.fontname,strictname) then
1657 found[#found+1], done[f] = f, true
1658 if not all then return end
1659 end
1660 end
1661 end
1662end
1663
1664local function s_collect_style_width(found,done,all,style,width,family)
1665 if family then
1666 for i=1,#family do local f = family[i]
1667 if f and style == f.style and width == f.width then
1668 found[#found+1], done[f] = f, true
1669 if not all then return end
1670 end
1671 end
1672 end
1673end
1674local function m_collect_style_width(found,done,all,style,width,families,sorted,strictname)
1675 for i=1,#sorted do
1676 local k = sorted[i]
1677 local family = families[k]
1678 for i=1,#family do
1679 local f = family[i]
1680 if not done[f] and style == f.style and width == f.width and find(f.fontname,strictname) then
1681 found[#found+1], done[f] = f, true
1682 if not all then return end
1683 end
1684 end
1685 end
1686end
1687
1688local function s_collect_weight(found,done,all,weight,family)
1689 if family then
1690 for i=1,#family do local f = family[i]
1691 if f and weight == f.weight then
1692 found[#found+1], done[f] = f, true
1693 if not all then return end
1694 end
1695 end
1696 end
1697end
1698local function m_collect_weight(found,done,all,weight,families,sorted,strictname)
1699 for i=1,#sorted do
1700 local k = sorted[i]
1701 local family = families[k]
1702 for i=1,#family do
1703 local f = family[i]
1704 if not done[f] and weight == f.weight and find(f.fontname,strictname) then
1705 found[#found+1], done[f] = f, true
1706 if not all then return end
1707 end
1708 end
1709 end
1710end
1711
1712local function s_collect_style(found,done,all,style,family)
1713 if family then
1714 for i=1,#family do local f = family[i]
1715 if f and style == f.style then
1716 found[#found+1], done[f] = f, true
1717 if not all then return end
1718 end
1719 end
1720 end
1721end
1722local function m_collect_style(found,done,all,style,families,sorted,strictname)
1723 for i=1,#sorted do
1724 local k = sorted[i]
1725 local family = families[k]
1726 for i=1,#family do
1727 local f = family[i]
1728 if not done[f] and style == f.style and find(f.fontname,strictname) then
1729 found[#found+1], done[f] = f, true
1730 if not all then return end
1731 end
1732 end
1733 end
1734end
1735
1736local function s_collect_width(found,done,all,width,family)
1737 if family then
1738 for i=1,#family do local f = family[i]
1739 if f and width == f.width then
1740 found[#found+1], done[f] = f, true
1741 if not all then return end
1742 end
1743 end
1744 end
1745end
1746local function m_collect_width(found,done,all,width,families,sorted,strictname)
1747 for i=1,#sorted do
1748 local k = sorted[i]
1749 local family = families[k]
1750 for i=1,#family do
1751 local f = family[i]
1752 if not done[f] and width == f.width and find(f.fontname,strictname) then
1753 found[#found+1], done[f] = f, true
1754 if not all then return end
1755 end
1756 end
1757 end
1758end
1759
1760local function s_collect(found,done,all,family)
1761 if family then
1762 for i=1,#family do local f = family[i]
1763 if f then
1764 found[#found+1], done[f] = f, true
1765 if not all then return end
1766 end
1767 end
1768 end
1769end
1770local function m_collect(found,done,all,families,sorted,strictname)
1771 for i=1,#sorted do
1772 local k = sorted[i]
1773 local family = families[k]
1774 for i=1,#family do
1775 local f = family[i]
1776 if not done[f] and find(f.fontname,strictname) then
1777 found[#found+1], done[f] = f, true
1778 if not all then return end
1779 end
1780 end
1781 end
1782end
1783
1784local function collect(stage,found,done,name,weight,style,width,variant,all)
1785 local data = names.data
1786 local families = data.families
1787 local sorted = data.sorted_families
1788 local strictname = "^".. name
1789 local family = families[name]
1790 if trace_names then
1791 report_names("resolving name %a, weight %a, style %a, width %a, variant %a",name,weight,style,width,variant)
1792 end
1793 if weight and weight ~= "" then
1794 if style and style ~= "" then
1795 if width and width ~= "" then
1796 if variant and variant ~= "" then
1797 if trace_names then
1798 report_names("resolving stage %s, name %a, weight %a, style %a, width %a, variant %a",stage,name,weight,style,width,variant)
1799 end
1800 s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family)
1801 m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname)
1802 else
1803 if trace_names then
1804 report_names("resolving stage %s, name %a, weight %a, style %a, width %a",stage,name,weight,style,width)
1805 end
1806 s_collect_weight_style_width(found,done,all,weight,style,width,family)
1807 m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname)
1808 end
1809 else
1810 if trace_names then
1811 report_names("resolving stage %s, name %a, weight %a, style %a",stage,name,weight,style)
1812 end
1813 s_collect_weight_style(found,done,all,weight,style,family)
1814 m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname)
1815 end
1816 else
1817 if trace_names then
1818 report_names("resolving stage %s, name %a, weight %a",stage,name,weight)
1819 end
1820 s_collect_weight(found,done,all,weight,family)
1821 m_collect_weight(found,done,all,weight,families,sorted,strictname)
1822 end
1823 elseif style and style ~= "" then
1824 if width and width ~= "" then
1825 if trace_names then
1826 report_names("resolving stage %s, name %a, style %a, width %a",stage,name,style,width)
1827 end
1828 s_collect_style_width(found,done,all,style,width,family)
1829 m_collect_style_width(found,done,all,style,width,families,sorted,strictname)
1830 else
1831 if trace_names then
1832 report_names("resolving stage %s, name %a, style %a",stage,name,style)
1833 end
1834 s_collect_style(found,done,all,style,family)
1835 m_collect_style(found,done,all,style,families,sorted,strictname)
1836 end
1837 elseif width and width ~= "" then
1838 if trace_names then
1839 report_names("resolving stage %s, name %a, width %a",stage,name,width)
1840 end
1841 s_collect_width(found,done,all,width,family)
1842 m_collect_width(found,done,all,width,families,sorted,strictname)
1843 else
1844 if trace_names then
1845 report_names("resolving stage %s, name %a",stage,name)
1846 end
1847 s_collect(found,done,all,family)
1848 m_collect(found,done,all,families,sorted,strictname)
1849 end
1850end
1851
1852local function heuristic(name,weight,style,width,variant,all)
1853 local found, done = { }, { }
1854
1855 weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal"
1856 name = cleanname(name)
1857 collect(1,found,done,name,weight,style,width,variant,all)
1858
1859 if #found == 0 and variant ~= "normal" then
1860 variant = "normal"
1861 collect(4,found,done,name,weight,style,width,variant,all)
1862 end
1863 if #found == 0 and width ~= "normal" then
1864 width = "normal"
1865 collect(2,found,done,name,weight,style,width,variant,all)
1866 end
1867 if #found == 0 and weight ~= "normal" then
1868 weight = "normal"
1869 collect(3,found,done,name,weight,style,width,variant,all)
1870 end
1871 if #found == 0 and style ~= "normal" then
1872 style = "normal"
1873 collect(4,found,done,name,weight,style,width,variant,all)
1874 end
1875
1876 local nf = #found
1877 if trace_names then
1878 if nf then
1879 local t = { }
1880 for i=1,nf do
1881 t[i] = formatters["%a"](found[i].fontname)
1882 end
1883 report_names("name %a resolved to %s instances: % t",name,nf,t)
1884 else
1885 report_names("name %a unresolved",name)
1886 end
1887 end
1888 if all then
1889 return nf > 0 and found
1890 else
1891 return found[1]
1892 end
1893end
1894
1895function names.specification(askedname,weight,style,width,variant,reload,all)
1896 if askedname and askedname ~= "" and names.enabled then
1897 askedname = cleanname(askedname)
1898 names.load(reload)
1899 local found = heuristic(askedname,weight,style,width,variant,all)
1900 if not found and is_reloaded() then
1901 found = heuristic(askedname,weight,style,width,variant,all)
1902 if not filename then
1903 found = foundname(askedname)
1904 end
1905 end
1906 return found
1907 end
1908end
1909
1910function names.collect(askedname,weight,style,width,variant,reload,all)
1911 if askedname and askedname ~= "" and names.enabled then
1912 askedname = cleanname(askedname)
1913 names.load(reload)
1914 local list = heuristic(askedname,weight,style,width,variant,true)
1915 if not list or #list == 0 and is_reloaded() then
1916 list = heuristic(askedname,weight,style,width,variant,true)
1917 end
1918 return list
1919 end
1920end
1921
1922function names.collectspec(askedname,reload,all)
1923 local name, weight, style, width, variant = names.splitspec(askedname)
1924 return names.collect(name,weight,style,width,variant,reload,all)
1925end
1926
1927function names.resolvespec(askedname,sub)
1928 local found = names.specification(names.splitspec(askedname))
1929 if found then
1930 return found.filename, found.subfont and found.rawname
1931 end
1932end
1933
1934function names.collectfiles(askedname,reload)
1935 if askedname and askedname ~= "" and names.enabled then
1936 askedname = cleanname(askedname)
1937 names.load(reload)
1938 local list = { }
1939 local specifications = names.data.specifications
1940 for i=1,#specifications do
1941 local s = specifications[i]
1942 if find(cleanname(basename(s.filename)),askedname) then
1943 list[#list+1] = s
1944 end
1945 end
1946 return list
1947 end
1948end
1949
1950
1951
1952
1953
1954
1955
1956function names.exists(name)
1957 local found = false
1958 local list = filters.list
1959 for k=1,#list do
1960 local v = list[k]
1961 found = (findfile(name,v) or "") ~= ""
1962 if found then
1963 return found
1964 end
1965 end
1966 return (findfile(name,"tfm") or "") ~= "" or (names.resolve(name) or "") ~= ""
1967end
1968
1969local lastlookups, lastpattern = { }, ""
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018local function look_them_up(lookups,specification)
2019 for key, value in sortedhash(specification) do
2020 local t = { }
2021 local n = 0
2022 if find(value,"*",1,true) then
2023 value = topattern(value)
2024 for i=1,#lookups do
2025 local s = lookups[i]
2026 if find(s[key],value) then
2027 n = n + 1
2028 t[n] = lookups[i]
2029 end
2030 end
2031 else
2032 for i=1,#lookups do
2033 local s = lookups[i]
2034 if s[key] == value then
2035 n = n + 1
2036 t[n] = lookups[i]
2037 end
2038 end
2039 end
2040 if trace_names then
2041 report_names("%s matches for key %a with value %a",#t,key,value)
2042 end
2043 lookups = t
2044 end
2045 return lookups
2046end
2047
2048local function first_look(name,reload)
2049 names.load(reload)
2050 local data = names.data
2051 local specifications = data.specifications
2052 local families = data.families
2053 if name then
2054 return families[name]
2055 else
2056 return specifications
2057 end
2058end
2059
2060function names.lookup(pattern,name,reload)
2061 names.load(reload)
2062 local data = names.data
2063 local specifications = data.specifications
2064 local families = data.families
2065 local lookups = specifications
2066 if name then
2067 name = cleanname(name)
2068 end
2069 if type(pattern) == "table" then
2070 local familyname = pattern.familyname
2071 if familyname then
2072 familyname = cleanname(familyname)
2073 pattern.familyname = familyname
2074 end
2075 local lookups = first_look(name or familyname,reload)
2076 if lookups then
2077 if trace_names then
2078 report_names("starting with %s lookups for '%T'",#lookups,pattern)
2079 end
2080 lookups = look_them_up(lookups,pattern)
2081 end
2082 lastpattern = false
2083 lastlookups = lookups or { }
2084 elseif lastpattern ~= pattern then
2085 local lookups = first_look(name or (not find(pattern,"=",1,true) and pattern),reload)
2086 if lookups then
2087 if trace_names then
2088 report_names("starting with %s lookups for %a",#lookups,pattern)
2089 end
2090 local specification = settings_to_hash(pattern)
2091 local familyname = specification.familyname
2092 if familyname then
2093 familyname = cleanname(familyname)
2094 specification.familyname = familyname
2095 end
2096 lookups = look_them_up(lookups,specification)
2097 end
2098 lastpattern = pattern
2099 lastlookups = lookups or { }
2100 end
2101 return #lastlookups
2102end
2103
2104function names.getlookupkey(key,n)
2105 local l = lastlookups[n or 1]
2106 return (l and l[key]) or ""
2107end
2108
2109function names.noflookups()
2110 return #lastlookups
2111end
2112
2113function names.getlookups(pattern,name,reload)
2114 if pattern then
2115 names.lookup(pattern,name,reload)
2116 end
2117 return lastlookups
2118end
2119
2120
2121
2122local specifications = allocate()
2123names.specifications = specifications
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137function names.register(files)
2138 if files then
2139 local list, commonname = files.list, files.name
2140 if list then
2141 local n, m = 0, 0
2142 for filename, filespec in sortedhash(list) do
2143 local name = lower(filespec.name or commonname)
2144 if name and name ~= "" then
2145 local style = normalized_styles [lower(filespec.style or "normal")]
2146 local width = normalized_widths [lower(filespec.width or "normal")]
2147 local weight = normalized_weights [lower(filespec.weight or "normal")]
2148 local variant = normalized_variants[lower(filespec.variant or "normal")]
2149 local weights = specifications[name ] if not weights then weights = { } specifications[name ] = weights end
2150 local styles = weights [weight] if not styles then styles = { } weights [weight] = styles end
2151 local widths = styles [style ] if not widths then widths = { } styles [style ] = widths end
2152 local variants = widths [width ] if not variants then variants = { } widths [width ] = variants end
2153 variants[variant] = filename
2154 n = n + 1
2155 else
2156 m = m + 1
2157 end
2158 end
2159 if trace_specifications then
2160 report_names("%s filenames registered, %s filenames rejected",n,m)
2161 end
2162 end
2163 end
2164end
2165
2166function names.registered(name,weight,style,width,variant)
2167 local ok = specifications[name]
2168 ok = ok and (ok[(weight and weight ~= "" and weight ) or "normal"] or ok.normal)
2169 ok = ok and (ok[(style and style ~= "" and style ) or "normal"] or ok.normal)
2170 ok = ok and (ok[(width and width ~= "" and width ) or "normal"] or ok.normal)
2171 ok = ok and (ok[(variant and variant ~= "" and variant) or "normal"] or ok.normal)
2172
2173
2174
2175 if ok then
2176 return {
2177 filename = ok,
2178 subname = "",
2179
2180 }
2181 end
2182end
2183
2184function names.resolvespec(askedname,sub)
2185 local name, weight, style, width, variant = names.splitspec(askedname)
2186 if trace_specifications then
2187 report_names("resolving specification: %a to name=%s, weight=%s, style=%s, width=%s, variant=%s",askedname,name,weight,style,width,variant)
2188 end
2189 local found = names.registered(name,weight,style,width,variant)
2190 if found and found.filename then
2191 if trace_specifications then
2192 report_names("resolved by registered names: %a to %s",askedname,found.filename)
2193 end
2194 return found.filename, found.subname, found.rawname
2195 else
2196 found = names.specification(name,weight,style,width,variant)
2197 if found and found.filename then
2198 if trace_specifications then
2199 report_names("resolved by font database: %a to %s",askedname,found.filename)
2200 end
2201 return found.filename, found.subfont and found.rawname
2202 end
2203 end
2204 if trace_specifications then
2205 report_names("unresolved: %s",askedname)
2206 end
2207end
2208
2209function fonts.names.ignoredfile(filename)
2210 return false
2211end
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226 |