]> git.lyx.org Git - features.git/blob - development/tools/listFontWithLang.pl
Consider nesting level when autonesting
[features.git] / development / tools / listFontWithLang.pl
1 #! /usr/bin/env perl
2 # -*- mode: perl; -*-
3
4 # file listFontWithLang.pl
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING
7 # or at http://www.lyx.org/about/licence.php
8 #
9 # author Kornel Benko
10 # Full author contact details are available in the file CREDITS
11 # or at https://www.lyx.org/Credits
12 #
13 # Usage: listFontWithLang.pl <options>
14 #   Displays installed system font names selected by <options>
15 #   Option-strings with more that 1 char need be prefixed by '--'
16 #
17 # Option to get list of options: -h
18 #
19 # Some equivalencies for instance with option -n
20 #       -n arial
21 #       -N=arial
22 #       --nAme=Arial
23 #       --name arial
24 # Options and option-parameter are case insensitive
25
26 BEGIN {
27     use File::Spec;
28     my $p = File::Spec->rel2abs( __FILE__ );
29     $p =~ s/[\/\\]?[^\/\\]+$//;
30     unshift(@INC, $p);
31 }
32
33 use strict;
34 use warnings;
35 use Encode;
36 use GetOptions;
37 use constant {
38   SERIF => 1,
39   SANS => 2,
40   SCRIPT => 4,
41   FRAKTUR => 8,
42   DOUBLESTROKE => 16,
43   FANCY => 32,
44   INITIALS => 64,
45   SYMBOL => 128,
46 };
47
48 sub convertlang($);
49 sub extractlist($$$);   # my ($l, $islang, $txt, $rres) = @_;
50 sub getIndexes($$);
51 sub getVal($$$$);       # my ($l, $txtval, $txtlang, $combine) = @_;
52 sub getproperties($$$$);
53 sub ismathfont($$);
54 sub correctstyle($);
55 sub decimalUnicode($);
56 sub contains($$);
57 sub sprintIntervalls($);
58 sub buildFontName($$);
59 sub splitatlU($);          # split text at change from lower case to upper case
60 sub splitStyle($);
61
62 # Following fields for a parameter can be defined:
63 # fieldname:         Name of entry in %options
64 # type:              [:=][sif], ':' = optional, '=' = required, 's' = string, 'i' = integer, 'f' = float
65 # alias:             reference to a list of aliases e.g. ["alias1", "alias2", ... ]
66 # listsep:           Separator for multiple data
67 # comment:           Parameter description
68 my @optionsDef = (
69   # help + verbose already handled in 'GetOptions'
70   ["n",
71    {fieldname => "FontName", listsep => ',',
72     type => "=s", alias => ["name"],
73     comment => "Select font-names matching these (comma separated) regexes"},],
74   ["nn",
75    {fieldname => "NFontName",
76     type => "=s", listsep => ',',
77     comment => "Select font-names NOT matching these (comma separated) regexes"},],
78   ["p",
79    {fieldname => "Property",
80     type => "=s", listsep => ',',
81     comment => "Select fonts with properties matching these (comma separated) regexes"},],
82   ["np",
83    {fieldname => "NProperty",
84     type => "=s", listsep => ',',
85     comment => "Select fonts with properties NOT matching these (comma separated) regexes"},],
86   ["s",
87    {fieldname => "Scripts",
88     type => "=s", listsep => ',',
89     comment => "Select fonts with scripts matching these (comma separated) regexes"},],
90   ["ns",
91    {fieldname => "NScripts",
92     type => "=s", listsep => ',',
93     comment => "Select fonts with scripts NOT matching these (comma separated) regexes"},],
94   ["math",
95    {fieldname => "Math",
96     comment => "Select fonts probably containing math glyphs"},],
97   ["c",
98    {fieldname => "Contains",  alias => ["contains"],
99     type => "=s", listsep => ',',
100     comment => "Select fonts containing all these (possibly comma separated) glyphs",
101     comment2 => "____example: -c=\"0-9,u+32-u+x7f\"",}],
102   ["nc",
103    {fieldname => "NContains",
104     type => "=s", listsep => ',',
105     comment => "Select fonts NOT containing any of these (possibly comma separated) glyphs",
106     comment2 => "____example: --nc=\"0-9,u+32-u+x7f\"",}],
107   ["l",
108    {fieldname => "Lang",
109     type => "=s", alias=>["lang"],
110     comment => "Comma separated list of desired languages"},],
111   ["pc",
112    {fieldname => "PrintCharset", alias => ["printcharset"],
113     comment => "Print intervals of supported unicode character values"},],
114   ["pl",
115    {fieldname => "PrintLangs", alias => ["printlangs"],
116     comment => "Print supported languages"},],
117   ["pp",
118    {fieldname => "PrintProperties", alias => ["printproperties"],
119     comment => "Print properties from weight, slant and width"},],
120   ["ps",
121    {fieldname => "PrintScripts", alias => ["printscripts"],
122     comment => "Print supported scripts"},],
123   ["pf",
124    {fieldname => "PrintFiles", alias => ["printfiles"],
125     comment => "Print font file names"},],
126   ["pw",
127    {fieldname => "PrintWarnings",
128     comment => "Print warnings about discarded/overwritten fonts, conflicting styles"},],
129 );
130 my %options = %{&handleOptions(\@optionsDef)};
131
132 $options{Lang} = "" if (! defined($options{Lang}));
133
134 #############################################################
135 my %mapShortcuts = (
136   "Cond" => "Condensed",
137   "Expd" => "Expanded",
138   "Lt"   => "Light",
139   "Med"  => "Medium",
140   "med"  => "Medium",
141   "bol"  => "Bold",
142 );
143
144 my @langs = split(',', $options{Lang});
145 for my $lg (@langs) {
146   $lg = &convertlang($lg);
147 }
148
149 for my $charFld ("Contains", "NContains") {
150   if (defined($options{$charFld})) {
151     my %glyphs = ();         # To ignore duplicates
152     for my $a1 (@{$options{$charFld}}) {
153       for my $e (decimalUnicode($a1)) {
154         $glyphs{$e} = 1;
155       }
156     }
157     # create intervalls
158     my @glyphs = sort {$a <=> $b;} keys %glyphs;
159
160     # $options{$charFld} no longer needed, so use it for unicode-point intervalls
161     $options{$charFld} = [];
162     my ($first, $last) = (undef, undef);
163     for my $i (@glyphs) {
164       if (! defined($last)) {
165         $first = $i;
166         $last = $i;
167         next;
168       }
169       if ($i == $last+1) {
170         $last = $i;
171         next;
172       }
173       push(@{$options{$charFld}}, [$first, $last]);
174       $first = $i;
175       $last = $i;
176     }
177     if (defined($last)) {
178       push(@{$options{$charFld}}, [$first, $last]);
179     }
180     if (exists($options{verbose})) {
181       if ($charFld eq "Contains") {
182         print "Checking for unicode-points: " . &sprintIntervalls($options{$charFld}) . "\n";
183       }
184       else {
185         print "Ignore if matching unicode-points: " . &sprintIntervalls($options{$charFld}) . "\n";
186       }
187     }
188   }
189 }
190
191 my $cmd = "fc-list";
192 if (defined($langs[0])) {
193   $cmd .= " :lang=" . join(',', @langs);
194 }
195
196 my $format = "foundry=\"%{foundry}\"" .
197     " postscriptname=\"%{postscriptname}\"" .
198     " fn=\"%{fullname}\" fnl=\"%{fullnamelang}\"" .
199     " family=\"%{family}\" flang=\"%{familylang}\" " .
200     " style=\"%{style}\" stylelang=\"%{stylelang}\"";
201
202 if (exists($options{PrintScripts}) || defined($options{Scripts}) || defined($options{NScripts}) || exists($options{Math})) {
203   $format .= " script=\"%{capability}\"";
204 }
205 if (exists($options{PrintLangs}) || defined($langs[0])) {
206   $format .= " lang=\"%{lang}\"";
207 }
208 if (exists($options{PrintProperties}) || defined($options{Property}) || defined($options{NProperty})) {
209   $format .= " weight=%{weight} slant=%{slant} width=%{width} spacing=%{spacing}";
210 }
211 if (defined($options{Contains}) || defined($options{NContains}) || exists($options{PrintCharset})) {
212   $format .= " charset=\"%{charset}\"";
213 }
214 $format .= " file=\"%{file}\" abcd\\n";
215 $cmd .= " -f '$format'";
216 #print "$cmd\n";
217
218
219 my %ftypes = (
220   # Dummy internal map
221   # using '()' to prevent the initializer to take
222   #    the key as a string. (Constants in perl _are_ functions without argument)
223   SERIF() => "Serif",
224   SANS() => "Sans",
225   SCRIPT() => "Script",
226   FRAKTUR() => "Fraktur",
227   DOUBLESTROKE() => "DoubleStroke",
228   FANCY() => "Fancy",
229   INITIALS() => "Initials",
230   SYMBOL() => "Symbol",
231   "default" => 1,
232 );
233
234 my %weights = (
235   0 => "Thin",
236   40 => "Extralight",
237   50 => "Light",
238   55 => "Semilight",
239   75 => "Book",
240   80 => "Regular",
241   100 => "Medium",
242   180 => "Semibold",
243   200 => "Bold",
244   205 => "Extrabold",
245   210 => "Black",
246   215 => "ExtraBlack",
247 );
248
249 my %slants = (
250   0 => "Roman",
251   100 => "Italic",
252   110 => "Oblique",
253 );
254
255 my %widths = (
256   50 => "Ultracondensed",
257   63 => "Extracondensed",
258   75 => "Condensed",
259   87 => "Semicondensed",
260   100 => "Normal",
261   113 => "Semiexpanded",
262   125 => "Expanded",
263   150 => "Extraexpanded",
264   200 => "Ultraexpanded",
265 );
266
267 my %spacings = (
268   0 => "Proportional",
269   90 => "Dual",
270   100 => "Mono",
271   110 => "Charcell",
272   "default" => "Proportional",
273 );
274
275 # Build reverse mappings, (not needed yet)
276 for my $txt (qw(ftypes weights slants widths spacings)) {
277   my $map;
278   eval "\$map = \\%$txt";
279   for my $key (keys %{$map}) {
280     next if ($key !~ /^\d+$/);
281     my $value = lc($map->{$key});
282     $map->{"r"}->{$value} = $key;
283   }
284 }
285
286 # key:= fontname
287 #     subkey foundry
288 #            subfoundry
289 my %collectedfonts = ();
290 my %fontpriority = (
291   otf => 0,                # type 2, opentype CFF (Compact Font Format)
292   ttc => 1.1,              # type 1 (True Type font Collection)
293   ttf => 1.2,              # type 1 (True Type Font)
294   woff=> 1.3,              # type 1 (Web Open Font Format)
295   t1  => 1.4,              # type 1 (postscript)
296   pfb => 1.5,              # type 1 (Printer Font Binary)
297   pfa => 1.6,              # type 1 (Printer Font Ascii)
298   pfm => 2,                # requires associated .pfb file
299   pcf => 5,                # Bitmap (Packaged Collaboration Files)?
300 );
301 my $nexttype = 6;
302
303 my %serifFonts = (
304   # some extra serif fonts
305   "value" => SERIF,
306   "g" => qr/^gfs ?(didot)/i,
307 );
308 # list of regexes for known sans serif fonts
309 my %sansFonts = (
310   "value" => SANS,          # Sans serif
311   "a" => qr/^a(030|bydos|haroni|e?rial|ndika|ngostura|nonymous|rab|roania|rimo|sap|e almothnna|egean|egyptus|l (arabiya|battar|hor|manzomah|yarmook)|lmonte|natolian|ndale|nglepoise|njali|xaxa)/i,
312   "b" => qr/^b(abel ?stone ?modern|aekmuk|alker|altar|andal|angwool|arbatrick|aveuse|bold|dxsfm|ebas|erenika|eteckna|euron|iometric|iting|lue|m ?hanna)/i,
313   "c" => qr/^c(abin|aliban|antarell|arbon|arlito|handas|harles|hilanka|hinese ?rocks|hivo|mu bright|omfortaa|omi[cx]|oolvetica|ortoba|ousine|uprum|wtex(hei|yen)|yklop|ypro)/i,
314   "d" => qr/^(d2coding|dimnah|dosis|dyuthi)/i,
315   "e" => qr/^(electron|engebrechtre)/i,
316   "f" => qr/^(fandolhei|fetamont|fira|font awesome 5|forgotten)/i,
317   "g" => qr/^g(ardiner|aruda|fs ?neo|illius|ood ?times|ranada|raph|uanine|unplay)/i,
318   "h" => qr/^(hack|hani|haramain|harano|harmattan|hor\b)/i,
319   "i" => qr/^(ibm ?(plex ?mono|3270)|ikarius|inconsolata|induni.?h|iwona)/i,
320   "j" => qr/^j(ara|ura|s ?math.?bbold)/i,
321   "k" => qr/^(kalimati|kanji|karla|karma|kayrawan|kenyan|keraleeyam|khalid|khmer [or]|kiloji|klaudia|ko[mn]atu|kurier|kustom)/i,
322   "l" => qr/^l(aksaman|arabie|ato|eague|exend|exigulim|ibel|iberation|ibre franklin|ibris|inux biolinum|obster|ogix|ohit|oma)/i,
323   "m" => qr/^m(\+ |anchu|anjari|arcellus|ashq|eera|etal|igmix|igu|ikachan|intspirit|iriam ?clm|isaki|ona|onlam|ono(fonto|id|isome|noki)|ontserrat|otoyal|ukti|usica)/i,
324   "n" => qr/^(nachlieli|nada|nafees|nagham|nanum(barunpen|square)|nice)/i,
325   "o" => qr/^(ocr|okolaks|opendyslexic|ostorah|ouhud|over|oxygen)/i,
326   "p" => qr/^(padauk|pagul|paktype|pakenham|palladio|petra|phetsarath|play\b|poiret|port\b|primer\b|prociono|pt\b|purisa)/i,
327   "q" => qr/^(qt(ancient|helvet|avanti|doghaus|eratype|eurotype|floraline|frank|fritz|future|greece|howard|letter|optimum)|quercus)/i,
328   "r" => qr/^(rachana|radio\b|raleway|ricty|roboto|rosario)/i,
329   "s" => qr/^(salem|samanata|sawasdee|shado|sharja|simple|sophia|soul|source|switzera)/i,
330   "t" => qr/^(tarablus|teen|texgyre(adventor|heros)|tiresias|trebuchet|tscu|tuffy)/i,
331   "u" => qr/^u(buntu|kij (bom|chechek|cjk|diwani|ekran|elipbe|inchike|jelliy|kufi|mejnuntal|qara|qolyazma|teng|title|tor|tuz ?(neqish|tom))|mpush|n ?(dinaru|jamo|graphic|taza|vada|yetgul)|uni(kurd|space|versalis)|roob|rw ?classico)/i,
332   "v" => qr/^(veranda|vn ?urwclassico)/i,
333   "w" => qr/^(waree)/i,
334   "y" => qr/^(yanone)/i,
335   "z" => qr/^(zekton|zero)/i,
336 );
337 my %scriptFonts = (
338   "value" => SCRIPT,          # Script
339   "a" => qr/^a(becedario|ir ?cut|ugie|uriocus ?kalligraph)/i,
340   "b" => qr/^b(reip|rush ?script)/i,
341   "c" => qr/^(chancery)/i,
342   "d" => qr/^(dancing)/i,
343   "e" => qr/^(elegante)/i,
344   "f" => qr/^femkeklaver/i,
345   "j" => qr/^jsmath.?(rsfs)/i,
346   "k" => qr/^(kaushan|karumbi|kristi)/i,
347   "m" => qr/^(math ?jax.?script|miama)/i,
348   "n" => qr/^(nanum (brush|pen) script)/i,
349   "p" => qr/^pecita/i,
350   "q" => qr/^qt( ?black ?forest|arabian|boulevard|brush ?stroke|chancery|coronation|florencia|handwriting|linostroke|merry|pandora|slogan)/i,
351   "r" => qr/^((romande.*|ruf)script|rsfs)/i,
352   "t" => qr/^typo ?script/i,
353   "u" => qr/^u(n ?pilgi|rw ?chancery|kij ?(jelliy|moy|qolyazma ?(tez|yantu)))/i,
354 );
355
356 my %fraktFonts = (
357   "value" => FRAKTUR,          # Fraktur
358   "b" => qr/^boondox ?fraktur/i,
359   "e" => qr/^eufm/i,
360   "j" => qr/^(jsmath.?euf)/i,
361   "m" => qr/^(missaali)/i,
362   "o" => qr/^(oldania)/i,
363   "q" => qr/^qt(blackforest|cloisteredmonk|dublinirish|fraktur|heidelbergtype|(lino|london)scroll)/i,
364   "u" => qr/^ukij ?(kufi ?tar|mejnun ?reg)/i,
365 );
366
367 my %fancyFonts = (
368   "value" => FANCY,          # Fancy
369   "a" => qr/^a(bandoned|bberancy|driator|irmole|lmonte (snow|woodgrain)|nalecta|ni|nklepants|nn ?stone|oyagi|rt ?nouveau ?caps|stron|xaxa)/i,
370   "b" => qr/^b(aileys|alcony|altar|andal|arbatrick|aveuse|eat ?my|etsy|iometric|iting|lankenburg|oondox ?callig|org|oron|raeside|ramalea|udmo|urnstown|utterbelly)/i,
371   "c" => qr/^c(retino|msy|abin ?sketch|arbon|arolingan|harles|hicken|hilanka|hr\d)/i,
372   "d" => qr/^dseg/i,
373   "e" => qr/^electorate/i,
374   "f" => qr/^frederika/i,
375   "g" => qr/^(gfs.?theo)/i,
376   "j" => qr/^jsmath.cmsy/i,
377   "k" => qr/^keter|kicking|kredit|kouzan/i,
378   "l" => qr/^lcmsy/i,
379   "q" => qr/^qtcaslan ?open/i,
380   "u" => qr/^u(kij ?(saet|tiken)|nion ?city)/i,
381   "v" => qr/^vectroid/i,
382 );
383
384 my %initialFonts = (
385   "value" => INITIALS,          # Initials
386   "c" => qr/^carrick/i,
387   "e" => qr/^(eb.?garamond.?init)/i,
388   "t" => qr/^typographer/i,
389   "y" => qr/^(yinit)/i,
390 );
391
392 my %symbolFonts = (
393   "value" => SYMBOL,          # Symbol
394   "a" => qr/^a(cademicons|lblant|lianna|mar|nka|rb?\d|rchaic|rrow|rs|rt[mt]|ssy(rb\d+)?\b|miri ?quran|mit\b)/i,
395   "b" => qr/^b(aby ?jeepers|bding|euron|guq|lex|lsy|oondox ?upr|ullets|urma)/i,
396   "c" => qr/^c(aladings|cicons|hess|msy|mex|apacitor)/i,
397   "d" => qr/^(dingbats|drmsym|d05)/i,
398   "e" => qr/^e(lusiveicons|mmentaler|moji|sint|uterpe)/i,
399   "f" => qr/^(fandol.?brail|fdsymbol|fourierorns|font(awesome|ello|.?mfizz))/i,
400   "g" => qr/^(gan.?clm|gfs.?(baskerville|gazis|olga|porson|solomos|(bodoni|didot).?classic|complutum))/i,
401   "h" => qr/^h(ots|ershey)/i,
402   "j" => qr/^jsmath.?(msam|cmsy|masm|msbm|wasy|cmex|stmary)/i,
403   "l" => qr/^l(cmsy|msam)/i,
404   "m" => qr/^(marvosym|material|msam|msbm)/i,
405   "n" => qr/^(noto.*(emoji|brahmi))/i,
406   "o" => qr/^(octicons)/i,
407   "p" => qr/^patch/i,
408   "q" => qr/^(qtding ?bits)/i,
409   "s" => qr/^s(kak|tmary|s?msam|tix ?math)/i,
410   "t" => qr/^(typicons|twemoji)/i,
411   "u" => qr/^ukij ?(imaret|orxun|tughra)/i,
412   "w" => qr/^w(ebdings|asy|elfare ?brat)/i,
413 );
414
415 if (open(FI,  "$cmd |")) {
416  NXTLINE: while (my $l = <FI>) {
417     chomp($l);
418     while ($l !~ /abcd$/) {
419       $l .= <FI>;
420       chomp($l);
421     }
422     my $file = "";
423     my $fonttype;
424     if ($l =~ /file=\"([^\"]+)\"/) {
425       $file = $1;
426       #next if ($file !~ /\.(otf|ttf|pfa|pfb|pcf|ttc)$/i);
427       if ($file !~ /\.([a-z0-9]{2,5})$/i) {
428         print "Unhandled extension for file $file\n";
429         next;
430       }
431       $fonttype = lc($1);
432       if (! defined($fontpriority{$fonttype})) {
433         print "Added extension $fonttype for file $file\n";
434         $fontpriority{$fonttype} = $nexttype;
435         $nexttype++;
436       }
437     }
438     my %usedlangs = ();
439     if ($l =~ / lang=\"([^\"]+)\"/) {
440       my @ll = split(/\|/, $1);
441       for my $lx (@ll) {
442         $usedlangs{&convertlang($lx)} = 1;
443       }
444     }
445
446     for my $lang (@langs) {
447       next NXTLINE if (! defined($usedlangs{$lang}));
448     }
449     my ($fullname, $fuidx) = &getVal($l, "fn", "fnl", -1);
450     my ($style, $fsidx) = &getVal($l, "style", "stylelang", $fuidx);
451     $style =~ s/^\\040//;
452     my ($family, $faidx)  = &getVal($l, "family", "flang", $fsidx);
453
454     my $postscriptname = "";
455     if ($l =~ /postscriptname=\"([^\"]+)\"/) {
456       $postscriptname = $1;
457     }
458     my $fontname;
459     ($fontname, $style) = &buildFontName($family, $style);
460
461     if (defined($options{NFontName})) {
462       for my $fn (@{$options{NFontName}}) {
463         next NXTLINE if ($fontname =~ /$fn/i);
464       }
465     }
466     if (defined($options{FontName})) {
467       for my $fn (@{$options{FontName}}) {
468         next NXTLINE if ($fontname !~ /$fn/i);
469       }
470     }
471     my @charlist = ();
472     if (defined($options{Contains}) || defined($options{NContains}) || exists($options{PrintCharset})) {
473       if ($l =~ / charset=\"([^\"]+)\"/) {
474         my @list = split(/\s+/, $1);
475         for my $e (@list) {
476           my ($l, $h) = split('-', $e);
477           $h = $l if (! defined($h));
478           push(@charlist, [hex($l), hex($h)]);
479         }
480       }
481       if (defined($options{Contains})) {
482         for my $g (@{$options{Contains}}) {
483           next NXTLINE if (! contains($g, \@charlist));
484         }
485       }
486       if (defined($options{NContains})) {
487         for my $g (@{$options{NContains}}) {
488           # Ignore if ANY char exist in @charlist
489           for (my $i = $g->[0]; $i <= $g->[1]; $i++) {
490             next NXTLINE if (contains([$i,$i], \@charlist));
491           }
492         }
493       }
494     }
495     my $props = "";
496     my @errors = ();
497     if (exists($options{PrintProperties}) || defined($options{Property}) || defined($options{NProperty})) {
498       my $properties = getproperties($l, $fontname, $style, \@errors);
499       if (defined($options{Property})) {
500         for my $pn (@{$options{Property}}) {
501           next NXTLINE if ($properties !~ /$pn/i);
502         }
503       }
504       if (defined($options{NProperty})) {
505         for my $pn (@{$options{NProperty}}) {
506           next NXTLINE if ($properties =~ /$pn/i);
507         }
508       }
509       if (exists($options{PrintProperties})) {
510         $props .= " ($properties)";
511       }
512     }
513
514     if (exists($options{PrintLangs})) {
515       $props .= '(' . join(',', sort keys %usedlangs) . ')';
516     }
517     if (exists($options{PrintCharset})) {
518       $props .= '(' . &sprintIntervalls(\@charlist) . ')';
519     }
520     if (exists($options{PrintScripts}) || defined($options{Scripts}) || defined($options{NScripts}) || exists($options{Math})) {
521       my @scripts = ();
522       my $scripts = "";
523       if ($l =~ / script=\"([^\"]+)\"/) {
524         @scripts = split(/\s+/, $1);
525         for my $ent (@scripts) {
526           $ent =~ s/^\s*otlayout://;
527           $ent = lc($ent);
528         }
529         $scripts = join(',', @scripts);
530       }
531       if (exists($options{Math})) {
532         next NXTLINE if (! &ismathfont($fontname,\@scripts));
533       }
534       if (exists($options{PrintScripts})) {
535         $props .= "($scripts)";
536       }
537       if (!defined($scripts[0])) {
538         # No script defined in font, so check only $options{Scripts}
539         next NXTLINE if (defined($options{Scripts}));
540       }
541       else {
542         if (defined($options{Scripts})) {
543           for my $s (@{$options{Scripts}}) {
544             next NXTLINE if ($scripts !~ /$s/i);
545           }
546         }
547         if (defined($options{NScripts})) {
548           for my $s (@{$options{NScripts}}) {
549             next NXTLINE if ($scripts =~ /$s/i);
550           }
551         }
552       }
553     }
554     my $foundry = "";
555     if ($l =~ /foundry=\"([^\"]+)\"/) {
556       $foundry = $1;
557       $foundry =~ s/^\s+//;
558       $foundry =~ s/\s+$//;
559     }
560     if (defined($collectedfonts{$fontname}->{$foundry}->{errors})) {
561       # Apparently not the first one, so add some info
562       my $oldfonttype = $collectedfonts{$fontname}->{$foundry}->{fonttype};
563       if (defined($errors[0])) {
564         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, @errors);
565       }
566       if ($fontpriority{$oldfonttype} > $fontpriority{$fonttype}) {
567         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, "Warning: overwriting old info of file: " . $collectedfonts{$fontname}->{$foundry}->{file});
568       }
569       else {
570         push(@{$collectedfonts{$fontname}->{$foundry}->{errors}}, "Warning: discarding new info from file: $file");
571         next;
572       }
573     }
574     else {
575       $collectedfonts{$fontname}->{$foundry}->{errors} = \@errors;
576     }
577     $collectedfonts{$fontname}->{$foundry}->{props} = $props;
578     $collectedfonts{$fontname}->{$foundry}->{file} = $file;
579     $collectedfonts{$fontname}->{$foundry}->{fonttype} = $fonttype;
580   }
581   close(FI);
582 }
583
584 for my $fontname (sort keys %collectedfonts) {
585   my @foundries = sort keys %{$collectedfonts{$fontname}};
586   my $printfoundries = 0;
587   if (defined($foundries[1])) {
588     $printfoundries = 1;
589   }
590   for my $foundry (@foundries) {
591     if (exists($options{PrintWarnings})) {
592       for my $err (@{$collectedfonts{$fontname}->{$foundry}->{errors}}) {
593         print "$err\n";
594       }
595     }
596     my $fn = "Font : $fontname";
597     if ($printfoundries && ($foundry ne "")) {
598       $fn .= " \[$foundry\]";
599     }
600     print $fn;
601     print $collectedfonts{$fontname}->{$foundry}->{props};
602     if (exists($options{PrintFiles})) {
603       print ": " . $collectedfonts{$fontname}->{$foundry}->{file} . "\n";
604     }
605     else {
606       print "\n";
607     }
608   }
609 }
610
611 exit(0);
612 #################################################################################
613 sub convertlang($)
614 {
615   my ($ilang) = @_;
616   if ($ilang =~ /^\s*([a-z]+)([\-_]([a-z]+))?\s*$/i) {
617     my ($x, $y) = ($1, $3);
618     if (defined($y)) {
619       $ilang = lc($x) . '-' . lc($y);
620     }
621     else {
622       $ilang = lc($x);
623     }
624   }
625   return($ilang);
626 }
627
628 sub extractlist($$$)
629 {
630   my ($l, $islang, $txt, $rres) = @_;
631   my @res = ();
632   if ($l =~ /$txt=\"([^\"]+)\"/) {
633     @res = split(',', $1);
634     if ($islang) {
635       for my $lg (@res) {
636         $lg = &convertlang($lg);
637       }
638     }
639   }
640   @{$rres} = @res;
641 }
642
643 sub getIndexes($$)
644 {
645   my ($lang, $rlangs) = @_;
646   my @res = ();
647
648   for (my $i = 0; defined($rlangs->[$i]); $i++) {
649     if ($rlangs->[$i] eq $lang) {
650       push(@res, $i);
651     }
652   }
653   return(\@res);
654 }
655
656 sub getVal($$$$)
657 {
658   my ($l, $txtval, $txtlang, $startentry) = @_;
659   my @values = ();
660   my @langs = ();
661   &extractlist($l, 0, $txtval, \@values);
662   return("", -1) if (! defined($values[0]));
663   &extractlist($l, 1, $txtlang, \@langs);
664   my $i = &getIndexes("en", \@langs);
665   my $usedentry = -1;
666   my $actualentry = -1;
667   my $res = "";
668   for my $k (@{$i}) {
669     if (defined($values[$k])) {
670       $actualentry++;
671       if ($startentry < 0) {
672         if (length($values[$k]) > length($res)) {
673           $res = $values[$k];
674           $usedentry = $actualentry;
675         }
676       }
677       elsif ($actualentry == $startentry) {
678         $res = $values[$k];
679         $usedentry = $actualentry;
680         last;
681       }
682       else {
683         # select the longest entry if possible
684         if (length($values[$k]) > length($res)) {
685           $res = $values[$k];
686           $usedentry = $actualentry;
687         }
688       }
689     }
690   }
691   return($values[0], -1) if ($res eq "");
692   return($res, $usedentry);
693 }
694
695 sub getsinglevalue($$$)
696 {
697   my ($l, $txt, $rMap, $rget) = @_;
698   my $val;
699   if ($l =~ / $txt=(\d+)/) {
700     $val = $1;
701     # Search for nearest value to $val
702     if (defined($rMap->{$val})) {
703       return($rMap->{$val});
704     }
705     my $maxv = -1;
706     my $minv = 1000;
707     for my $key (keys %{$rMap}) {
708       next if ($key !~ /^\d+$/);
709       my $diff = abs($key - $val);
710       if ($diff < $minv) {
711         $maxv = $key;
712         $minv = $diff;
713       }
714       elsif ($diff == $minv) {
715         if ($key < $maxv) {
716           $maxv = $key;
717         }
718       }
719     }
720     if (! defined($rMap->{$maxv})) {
721       print "ERROR2: txt=$txt, val=$val\n";
722       exit(-2);
723     }
724     if ($val > $maxv) {
725       return($rMap->{$maxv} . "+$minv");
726     }
727     else {
728       return($rMap->{$maxv} . "-$minv");
729     }
730   }
731   else {
732     return(undef);
733   }
734 }
735
736 sub addTxt($$)
737 {
738   my ($txt, $val) = @_;
739   return("$txt($val)");
740 }
741
742 sub getftype($$)
743 {
744   my ($fontname, $style) = @_;
745   my $resftype = 0;
746   if ($fontname =~ /(sans)[-_ ]?(serif)?/i) {
747     $resftype |= SANS;
748   }
749   elsif ($fontname =~ /gothic|dotum|gulim/i) {
750     if ($fontname =~ /bisrat gothic/i) {
751       $resftype |= SERIF;
752     }
753     elsif ($fontname !~ /hershey/i) {
754       $resftype |= SANS;
755     }
756   }
757   elsif ($fontname =~ /^(js ?math.?)?bbold|msbm|^(ds(rom|serif|ss))|DoubleStruck/i) {
758     $resftype |= DOUBLESTROKE;  # Double stroke (math font)
759   }
760   if ($fontname =~ /serif|times|mincho|batang/i) {
761     $resftype |= SERIF; # Serif
762   }
763   if ($fontname =~ /initial(s|en)/i) {
764     $resftype |= INITIALS;
765     if ($fontname =~ /^linux ?libertine/i) {
766       $resftype |= SERIF;
767     }
768   }
769   if ($fontname =~ /participants/i) {
770     $resftype |= SANS|FANCY;
771   }
772   if ($fontname =~ /symbol|cherokee/i) {
773     if ($fontname !~ /^(symbola|asap)/i) {
774       $resftype |= SYMBOL;
775       if ($fontname =~ /^(ams ?math|computer modern bright msb)/i) {
776         $resftype |= DOUBLESTROKE | SERIF;
777       }
778     }
779   }
780   if ($fontname =~ /callig/i) {
781     $resftype |= FANCY;
782   }
783   # Now check for fonts without a hint in font name
784   if ($fontname =~ /^([a-z])/i) {
785     my $key = lc($1);
786     # check the mutual exclusive first
787     for my $rFonts (\%fraktFonts, \%scriptFonts, \%sansFonts, \%serifFonts) {
788       if (defined($rFonts->{$key})) {
789         if ($fontname =~ $rFonts->{$key}) {
790           $resftype |= $rFonts->{"value"};
791           last;
792         }
793       }
794     }
795     for my $rFonts (\%fancyFonts, \%initialFonts, \%symbolFonts) {
796       if (defined($rFonts->{$key})) {
797         if ($fontname =~ $rFonts->{$key}) {
798           $resftype |= $rFonts->{"value"};
799         }
800       }
801     }
802   }
803   if ("$fontname" =~ /^bpg/i) {
804     if ("$fontname" =~ /bpg (courier gpl|elite|serif)/i) {
805       $resftype |= SERIF;    # Serif
806     }
807     else {
808       $resftype |= SANS; # Sans Serif
809     }
810   }
811   elsif ("$fontname" =~ /^dustismo/i) {
812     if ("$fontname" =~ /^dustismo roman/i) {
813       $resftype |= SERIF;    # Serif
814     }
815     else {
816       $resftype |= SANS; # Sans Serif
817     }
818   }
819   elsif ("$fontname" =~ /^go\b/i) {
820     if ("$fontname" =~ /^go mono/i) {
821       $resftype |= SERIF;    # Serif
822     }
823     else {
824       $resftype |= SANS; # Sans Serif
825     }
826   }
827   # Create the string
828   my @ft = ();
829   if ($resftype == 0) {
830     $resftype = $ftypes{default};
831   }
832   else {
833     # fonts SANS, SERIF, SCRIPT and FRAKTUR are mutualy exclusive
834     if ($resftype & FRAKTUR) {
835       $resftype &= ~(SANS|SERIF|SCRIPT);
836     }
837     elsif ($resftype & SCRIPT) {
838       $resftype &= ~(SANS|SERIF);
839     }
840     elsif ($resftype & SANS) {
841       $resftype &= ~SERIF;
842     }
843   }
844   for (my $i = 1; $i < 513; $i *= 2) {
845     if ($resftype & $i) {
846       push(@ft, $ftypes{$i});
847     }
848   }
849   return(join(',', @ft));
850 }
851
852 sub getweight($$)
853 {
854   my ($fontname, $style) = @_;
855   my $result = undef;
856   for my $info ($style, $fontname) {
857     for my $key (keys %weights) {
858       next if ($key !~ /^\d+$/);
859       my $val = $weights{$key};
860       if ($info =~ /\b$val\b/i) {
861         return($val);
862       }
863     }
864   }
865   return($result);
866 }
867
868 sub getwidth($$)
869 {
870   my ($fontname, $style) = @_;
871   my $result = undef;
872   for my $key (keys %widths) {
873     next if ($key !~ /^\d+$/);
874     for my $info ($style, $fontname) {
875       if ($info =~ /\b$widths{$key}\b/i) {
876         return($widths{$key});
877       }
878       if ($info =~ /\bRegular\b/) {
879         if (!defined($result)) {
880           $result = $widths{100};
881         }
882       }
883     }
884   }
885   return($result);
886 }
887
888 sub getslant($$)
889 {
890   my ($fontname, $style) = @_;
891   for my $key (keys %slants) {
892     next if ($key !~ /^\d+$/);
893     if ($style =~ /\b$slants{$key}\b/i) {
894       return($slants{$key});
895     }
896   }
897   return(undef);
898 }
899
900 sub getspacing($$)
901 {
902   my ($fontname, $style) = @_;
903   for my $key (keys %spacings) {
904     next if ($key !~ /^\d+$/);
905     if ($style =~ /\b$spacings{$key}\b/i) {
906       return($spacings{$key});
907     }
908   }
909   if ("$fontname $style" =~ /(\bmono\b|luximono|typewriter|cursor|fixed)\b/i) {
910     return($spacings{100}); # Mono
911   }
912   else {
913     return(undef);
914   }
915 }
916
917 sub ismathfont($$)
918 {
919   my ($fontname, $rCapability) = @_;
920
921   return 1 if ($fontname =~ /math/i);
922   for my $cap (@{$rCapability}) {
923     return 1 if ($cap eq "math");
924   }
925   return 0;
926 }
927
928 sub getproperties($$$$)
929 {
930   my ($l, $fontname, $style, $rerrors) = @_;
931   my $newstyle = &correctstyle($style);
932   my $newfam = &correctstyle($fontname);
933   my @properties = ();
934
935   for my $txt (qw(ftype weight width slant spacing)) {
936     my ($map, $rget);
937     eval("\$map = " . '\%' . $txt . 's');
938     eval('$rget = \&' . "get$txt");
939     my $val2 = getsinglevalue($l, $txt, $map);
940     my $val1 = $rget->($newfam, $newstyle);
941     my $val;
942     if (defined($val2) && defined($val1) && ($val2 ne $val1)) {
943       if (($txt =~/^(weight|slant)$/) && ($newstyle =~ /$val1/i)){
944         # style overrides weight and slant
945         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val1 from style");
946         $val = $val1;
947       }
948       elsif ($newfam =~ /$val1/) {
949         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val1 from fontname");
950         $val = $val1;
951       }
952       else {
953         push(@{$rerrors}, "Fontname($fontname),Style($style): Values for $txt ($val1 != $val2) differ, pick $val2 from $txt-property");
954         $val = $val2;
955       }
956     }
957     elsif (! defined($val2)) {
958       $val = $val1;
959     }
960     else {
961       $val = $val2;
962     }
963     if (defined($val)) {
964       push(@properties, &addTxt($txt,$val));
965     }
966     else {
967       if (defined($map->{"default"})) {
968         push(@properties, &addTxt($txt,$map->{"default"}));
969       }
970       else {
971         push(@{$rerrors}, "Undefined value for $txt");
972       }
973     }
974   }
975   return(join(' ', @properties));
976 }
977
978 sub correctstyle($)
979 {
980   my ($style) = @_;
981   $style =~ s/^\s*\d+\s*//;
982   $style =~ s/\s*\d+$//;
983   $style =~ s/italic/ Italic/i;
984   $style =~ s/oblique/ Oblique/i;
985   $style =~ s/[\-_]/ /g;
986   $style =~ s/\breg\b/Regular/i;
987   $style =~ s/\bregita(lic)?\b/Regular Italic/i;
988   $style =~ s/\bregobl(ique)?\b/Regular Oblique/i;
989   $style =~ s/medium/Medium /i;
990   $style =~ s/\bmedita(lic)?\b/Medium Italic/i;
991   $style =~ s/\bmedobl(ique)?\b/Medium Oblique/i;
992   $style =~ s/\bmed\b/Medium /i;
993   $style =~ s/\bdemi\b/SemiBold/i;
994   $style =~ s/\bex(pd|t)\b/Expanded/i;
995   $style =~ s/semi ?cond(ensed)?/SemiCondensed/i;
996   $style =~ s/[sd]emi ?(bold|bd|bol)/SemiBold/i;
997   $style =~ s/semi ?(expanded|extended|expd)/SemiExpanded/i;
998   $style =~ s/[sd]emi ?light/SemiLight/i;
999   $style =~ s/ultra ?(expanded|extended|expd)/UltraExpanded/i;
1000   $style =~ s/light/Light /i;
1001   $style =~ s/\blt\b/Light /i;
1002   $style =~ s/(ultra|extra) ?(light|lt)/ExtraLight /i;
1003   $style =~ s/\bheavy\b/Extrabold/i;
1004   $style =~ s/\bhairline\b/Extralight/i;
1005   $style =~ s/\bcond\b/Condensed/i;
1006   $style =~ s/(roman)?slanted/ Italic/i;
1007   $style =~ s/\bslant\b/Italic/i;
1008   $style =~ s/\b(SC|Small(caps(alt)?)?)\b/SmallCaps/i;
1009   $style =~ s/w3 mono/Dual/i;
1010   $style =~ s/Regul[ea]r/Regular/i;
1011   $style =~ s/Megablack/ExtraBlack/i;
1012   $style =~ s/  +/ /g;
1013   $style =~ s/ +$//;
1014   return($style);
1015 }
1016
1017 # return list of unicode values of the input string
1018 #Allow input of intervals (e.g. 'a-z')
1019 sub decimalUnicode($)
1020 {
1021   my ($a) = @_;
1022   my @res = ();
1023   # Convert to unicode chars first
1024   while ($a =~ /^(.*)u\+(0?x[\da-f]+|\d+)(.*)$/i) {
1025     my ($prev, $d, $post) = ($1, $2, $3);
1026     if ($d =~ /^0?x(.+)$/) {
1027       $d = hex($1);
1028     }
1029     my $chr = encode('utf-8', chr($d));
1030     $a = $prev . $chr . $post;
1031   }
1032   # $a is now a string of unicode chars
1033   my $u = decode('utf-8', $a);
1034   my @a = split(//, $u);
1035   my $interval = 0;
1036   my $start = undef;
1037   for my $x (@a) {
1038     if ($x eq '-') {    # Interval
1039       $interval = 1;
1040       next;
1041     }
1042     if ($interval && defined($start)) {
1043       if (ord($x) < $start) {
1044         for (my $i = $start - 1; $i >= ord($x); $i--) {
1045           push(@res, $i);
1046         }
1047       }
1048       else {
1049         for (my $i = $start + 1; $i <= ord($x); $i++) {
1050           push(@res, $i);
1051         }
1052       }
1053       $start = undef;
1054     }
1055     else {
1056       $start = ord($x);
1057       push(@res, $start);
1058     }
1059     $interval = 0;
1060   }
1061   return(@res);
1062 }
1063
1064
1065 # check if the glyph-values in interval @{$ri} are contained
1066 # in one of the (sorted) intervals
1067 sub contains($$)
1068 {
1069   # ok if
1070   # ...re0..........re1...
1071   # ......start..end......
1072   my ($ri, $rList) = @_;
1073   my $start = $ri->[0];
1074   my $end = $ri->[1];
1075
1076   for my $re (@{$rList}) {
1077     next if ($re->[1] < $start);
1078     # now we found a possible matching interval
1079     return 1 if (($start >= $re->[0]) && ($end <= $re->[1]));
1080     return 0;
1081   }
1082   return 0;
1083 }
1084
1085 sub sprintIntervalls($)
1086 {
1087   my ($rList) = @_;
1088   my @out = ();
1089   for my $rE (@{$rList}) {
1090     if ($rE->[0] != $rE->[1]) {
1091       push(@out, $rE->[0] . '-' . $rE->[1]);
1092     }
1093     else {
1094       push(@out, $rE->[0]);
1095     }
1096   }
1097   return join(',', @out);
1098 }
1099
1100 sub buildFontName($$)
1101 {
1102   my ($family, $style) = @_;
1103
1104   my $result = "";
1105   $style =~ s/\\040//;
1106   $family =~ s/\\040/\-/;
1107   $family =~ s/\bcond\b/Condensed/i;
1108   $family =~ s/\bblk\b/Black/i;
1109   $family =~ s/\bsembd\b/SemiBold/i;
1110   $family =~ s/\bsemcond\b/SemiCondensed/i;
1111   $family =~ s/\bextcond\b/ExtraCondensed/i;
1112   $family =~ s/\bextbd\b/ExtraBold/i;
1113   $family =~ s/\bextlt\b/ExtraLight/i;
1114   $family =~ s/\bmed\b/Medium/i;
1115   if ($family =~ /^([A-Z]*[a-z]+)([A-Z]\w+)\b(.*)$/) {
1116     $family = $1 . splitatlU($2) . $3;
1117   }
1118   $family =~ s/^Ant Polt\b/Antykwa Poltawskiego/;
1119   $family =~ s/\b(Semi|Extra) (Bold|Condensed|Expanded|Light)\b/$1$2/;
1120   my @style = &splitStyle($style);
1121   for my $st (@style) {
1122     $st = ucfirst($st);
1123     if ($family !~ s/$st/$st/i) {
1124       $family .= " $st";
1125     }
1126     else {
1127       # check if $st in $family starts with ' '
1128       $family =~ s/(\w)$st/$1 $st/i;
1129     }
1130   }
1131   $result = $family;
1132   return($result, join(' ', @style));
1133 }
1134
1135 # split text at change from lower case to upper case
1136 sub splitatlU($)
1137 {
1138   my ($txt) = @_;
1139   if ($txt =~ /^([A-Z]+[a-z]*)(.*)$/) {
1140     if (defined($mapShortcuts{$1})) {
1141       return(" " . $mapShortcuts{$1} . splitatlU($2));
1142     }
1143     else {
1144       return(" $1" . splitatlU($2));
1145     }
1146   }
1147   return($txt);
1148 }
1149
1150 sub splitStyle($)
1151 {
1152   my @in = split(/[- ]/, $_[0]);
1153   my @result = ();
1154   my $prefix = "";
1155   for my $en (@in) {
1156     while ($en =~ s/^([A-Z][a-z]+)//) {
1157       my $found = $1;
1158       if ($found =~ /^(Semi|Extra)$/) {
1159         $prefix = $found;
1160         next;
1161       }
1162       elsif (defined($mapShortcuts{$found})) {
1163         $found = $mapShortcuts{$found};
1164       }
1165       push(@result, "$prefix$found");
1166       $prefix = "";
1167     }
1168     if ($en ne "") {
1169       push(@result, "$prefix$en");
1170       $prefix = "";
1171     }
1172   }
1173   return(@result);
1174 }