PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/texlive/texmf-dist/tex/luatex/luaotfload
Viewing File: /usr/share/texlive/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua
if not modules then modules = { } end modules ['font-nms'] = { version = 1.002, comment = "companion to luaotfload.lua", author = "Khaled Hosny and Elie Roux", copyright = "Luaotfload Development Team", license = "GNU GPL v2" } fonts = fonts or { } fonts.names = fonts.names or { } local names = fonts.names local names_dir = "luatex-cache/generic/names" names.version = 2.009 -- not the same as in context names.data = nil names.path = { basename = "otfl-names.lua", localdir = file.join(kpse.expand_var("$TEXMFVAR"), names_dir), systemdir = file.join(kpse.expand_var("$TEXMFSYSVAR"), names_dir), } local splitpath, expandpath = file.split_path, kpse.expand_path local glob, basename = dir.glob, file.basename local upper, lower, format = string.upper, string.lower, string.format local gsub, match, rpadd = string.gsub, string.match, string.rpadd local gmatch, sub, find = string.gmatch, string.sub, string.find local utfgsub = unicode.utf8.gsub local trace_short = false --tracing adapted to rebuilding of the database inside a document local trace_search = false --trackers.register("names.search", function(v) trace_search = v end) local trace_loading = false --trackers.register("names.loading", function(v) trace_loading = v end) local function sanitize(str) if str then return utfgsub(lower(str), "[^%a%d]", "") else return str -- nil end end local function fontnames_init() return { mappings = { }, status = { }, version = names.version, } end local function load_names() local localpath = file.join(names.path.localdir, names.path.basename) local systempath = file.join(names.path.systemdir, names.path.basename) local kpsefound = kpse.find_file(names.path.basename) local foundname local data if kpsefound and file.isreadable(kpsefound) then data = dofile(kpsefound) foundname = kpsefound elseif file.isreadable(localpath) then data = dofile(localpath) foundname = localpath elseif file.isreadable(systempath) then data = dofile(systempath) foundname = systempath end if data then logs.info("Font names database loaded: " .. foundname) else logs.info([[Font names database not found, generating new one. This can take several minutes; please be patient.]]) data = names.update(fontnames_init()) names.save(data) end return data end local synonyms = { regular = { "normal", "roman", "plain", "book", "medium" }, bold = { "boldregular", "demi", "demibold" }, italic = { "regularitalic", "normalitalic", "oblique", "slanted" }, bolditalic = { "boldoblique", "boldslanted", "demiitalic", "demioblique", "demislanted", "demibolditalic" }, } local loaded = false local reloaded = false function names.resolve(specification) local name = sanitize(specification.name) local style = sanitize(specification.style) or "regular" local size if specification.optsize then size = tonumber(specification.optsize) elseif specification.size then size = specification.size / 65536 end if not loaded then names.data = names.load() loaded = true end local data = names.data if type(data) == "table" and data.version == names.version then if data.mappings then local found = { } for _,face in next, data.mappings do local family = sanitize(face.names.family) local subfamily = sanitize(face.names.subfamily) local fullname = sanitize(face.names.fullname) local psname = sanitize(face.names.psname) local fontname = sanitize(face.fontname) local pfullname = sanitize(face.fullname) local optsize, dsnsize, maxsize, minsize if #face.size > 0 then optsize = face.size dsnsize = optsize[1] and optsize[1] / 10 -- can be nil maxsize = optsize[2] and optsize[2] / 10 or dsnsize minsize = optsize[3] and optsize[3] / 10 or dsnsize end if name == family then if subfamily == style then if optsize then if dsnsize == size or (size > minsize and size <= maxsize) then found[1] = face break else found[#found+1] = face end else found[1] = face break end elseif synonyms[style] and table.contains(synonyms[style], subfamily) then if optsize then if dsnsize == size or (size > minsize and size <= maxsize) then found[1] = face break else found[#found+1] = face end else found[1] = face break end elseif subfamily == "regular" or table.contains(synonyms.regular, subfamily) then found.fallback = face end else if name == fullname or name == pfullname or name == fontname or name == psname then if optsize then if dsnsize == size or (size > minsize and size <= maxsize) then found[1] = face break else found[#found+1] = face end else found[1] = face break end end end end if #found == 1 then if kpse.lookup(found[1].filename[1]) then logs.report("load font", "font family='%s', subfamily='%s' found: %s", name, style, found[1].filename[1]) return found[1].filename[1], found[1].filename[2] end elseif #found > 1 then -- we found matching font(s) but not in the requested optical -- sizes, so we loop through the matches to find the one with -- least difference from the requested size. local closest local least = math.huge -- initial value is infinity for i,face in next, found do local dsnsize = face.size[1]/10 local difference = math.abs(dsnsize-size) if difference < least then closest = face least = difference end end if kpse.lookup(closest.filename[1]) then logs.report("load font", "font family='%s', subfamily='%s' found: %s", name, style, closest.filename[1]) return closest.filename[1], closest.filename[2] end elseif found.fallback then return found.fallback.filename[1], found.fallback.filename[2] end -- no font found so far if not reloaded then -- try reloading the database names.data = names.update(names.data) names.save(names.data) reloaded = true return names.resolve(specification) else -- else, fallback to filename return specification.name, false end end else if not reloaded then names.data = names.update() names.save(names.data) reloaded = true return names.resolve(specification) else return specification.name, false end end end names.resolvespec = names.resolve function names.set_log_level(level) if level == 2 then trace_loading = true elseif level >= 3 then trace_loading = true trace_search = true end end local lastislog = 0 local function log(fmt, ...) lastislog = 1 texio.write_nl(format("luaotfload | %s", format(fmt,...))) io.flush() end logs = logs or { } logs.report = logs.report or log logs.info = logs.info or log local function font_fullinfo(filename, subfont, texmf) local t = { } local f = fontloader.open(filename, subfont) if not f then if trace_loading then logs.report("error: failed to open %s", filename) end return end local m = fontloader.to_table(f) fontloader.close(f) collectgarbage('collect') -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size if m.fontstyle_name then for _,v in next, m.fontstyle_name do if v.lang == 1033 then t.fontstyle_name = v.name end end end if m.names then for _,v in next, m.names do if v.lang == "English (US)" then t.names = { -- see -- http://developer.apple.com/textfonts/ -- TTRefMan/RM06/Chap6name.html fullname = v.names.compatfull or v.names.fullname, family = v.names.preffamilyname or v.names.family, subfamily= t.fontstyle_name or v.names.prefmodifiers or v.names.subfamily, psname = v.names.postscriptname } end end else -- no names table, propably a broken font if trace_loading then logs.report("broken font rejected: %s", basefile) end return end t.fontname = m.fontname t.fullname = m.fullname t.familyname = m.familyname t.filename = { texmf and basename(filename) or filename, subfont } t.weight = m.pfminfo.weight t.width = m.pfminfo.width t.slant = m.italicangle -- don't waste the space with zero values t.size = { m.design_size ~= 0 and m.design_size or nil, m.design_range_top ~= 0 and m.design_range_top or nil, m.design_range_bottom ~= 0 and m.design_range_bottom or nil, } return t end local function load_font(filename, fontnames, newfontnames, texmf) local newmappings = newfontnames.mappings local newstatus = newfontnames.status local mappings = fontnames.mappings local status = fontnames.status local basefile = texmf and basename(filename) or filename if filename then if table.contains(names.blacklist, filename) or table.contains(names.blacklist, basename(filename)) then if trace_search then logs.report("ignoring font '%s'", filename) end return end local timestamp, db_timestamp db_timestamp = status[basefile] and status[basefile].timestamp timestamp = lfs.attributes(filename, "modification") local index_status = newstatus[basefile] or (not texmf and newstatus[basename(filename)]) if index_status and index_status.timestamp == timestamp then -- already indexed this run return end newstatus[basefile] = newstatus[basefile] or { } newstatus[basefile].timestamp = timestamp newstatus[basefile].index = newstatus[basefile].index or { } if db_timestamp == timestamp and not newstatus[basefile].index[1] then for _,v in next, status[basefile].index do local index = #newstatus[basefile].index newmappings[#newmappings+1] = mappings[v] newstatus[basefile].index[index+1] = #newmappings end if trace_loading then logs.report("font already indexed: %s", basefile) end return end local info = fontloader.info(filename) if info then if type(info) == "table" and #info > 1 then for i in next, info do local fullinfo = font_fullinfo(filename, i-1, texmf) if not fullinfo then return end local index = newstatus[basefile].index[i] if newstatus[basefile].index[i] then index = newstatus[basefile].index[i] else index = #newmappings+1 end newmappings[index] = fullinfo newstatus[basefile].index[i] = index end else local fullinfo = font_fullinfo(filename, false, texmf) if not fullinfo then return end local index if newstatus[basefile].index[1] then index = newstatus[basefile].index[1] else index = #newmappings+1 end newmappings[index] = fullinfo newstatus[basefile].index[1] = index end else if trace_loading then logs.report("failed to load %s", basefile) end end end end local function path_normalize(path) --[[ path normalization: - a\b\c -> a/b/c - a/../b -> b - /cygdrive/a/b -> a:/b - reading symlinks under non-Win32 - using kpse.readable_file on Win32 ]] if os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then path = path:gsub('\\', '/') path = path:lower() path = path:gsub('^/cygdrive/(%a)/', '%1:/') end if os.type ~= "windows" and os.type ~= "msdos" then local dest = lfs.readlink(path) if dest then if kpse.readable_file(dest) then path = dest elseif kpse.readable_file(file.join(file.dirname(path), dest)) then path = file.join(file.dirname(path), dest) else -- broken symlink? end end end path = file.collapse_path(path) return path end fonts.path_normalize = path_normalize names.blacklist = { } local function read_blacklist() local files = { kpse.lookup("otfl-blacklist.cnf", {all=true, format="tex"}) } local blacklist = names.blacklist if files and type(files) == "table" then for _,v in next, files do for line in io.lines(v) do line = line:strip() -- to get rid of lines like " % foo" if line:find("^%%") or line:is_empty() then -- comment or empty line else line = line:split("%")[1] line = line:strip() if trace_search then logs.report("blacklisted file: %s", line) end blacklist[#blacklist+1] = line end end end end end local font_extensions = { "otf", "ttf", "ttc", "dfont" } local function scan_dir(dirname, fontnames, newfontnames, texmf) --[[ This function scans a directory and populates the list of fonts with all the fonts it finds. - dirname is the name of the directory to scan - names is the font database to fill - texmf is a boolean saying if we are scanning a texmf directory ]] local list, found = { }, { } local nbfound = 0 if trace_search then logs.report("scanning '%s'", dirname) end for _,i in next, font_extensions do for _,ext in next, { i, upper(i) } do found = glob(format("%s/**.%s$", dirname, ext)) -- note that glob fails silently on broken symlinks, which happens -- sometimes in TeX Live. if trace_search then logs.report("%s '%s' fonts found", #found, ext) end nbfound = nbfound + #found table.append(list, found) end end if trace_search then logs.report("%d fonts found in '%s'", nbfound, dirname) end for _,file in next, list do file = path_normalize(file) if trace_loading then logs.report("loading font: %s", file) end load_font(file, fontnames, newfontnames, texmf) end end local function scan_texmf_fonts(fontnames, newfontnames) --[[ This function scans all fonts in the texmf tree, through kpathsea variables OPENTYPEFONTS and TTFONTS of texmf.cnf ]] if expandpath("$OSFONTDIR"):is_empty() then logs.info("Scanning TEXMF fonts...") else logs.info("Scanning TEXMF and OS fonts...") end local fontdirs = expandpath("$OPENTYPEFONTS"):gsub("^\.", "") fontdirs = fontdirs .. expandpath("$TTFONTS"):gsub("^\.", "") if not fontdirs:is_empty() then for _,d in next, splitpath(fontdirs) do scan_dir(d, fontnames, newfontnames, true) end end end --[[ For the OS fonts, there are several options: - if OSFONTDIR is set (which is the case under windows by default but not on the other OSs), it scans it at the same time as the texmf tree, in the scan_texmf_fonts. - if not: - under Windows and Mac OSX, we take a look at some hardcoded directories - under Unix, we read /etc/fonts/fonts.conf and read the directories in it This means that if you have fonts in fancy directories, you need to set them in OSFONTDIR. ]] local function read_fonts_conf(path, results) --[[ This function parses /etc/fonts/fonts.conf and returns all the dir it finds. The code is minimal, please report any error it may generate. ]] local f = io.open(path) if not f then error("Cannot open the file "..path) end local incomments = false for line in f:lines() do while line and line ~= "" do -- spaghetti code... hmmm... if incomments then local tmp = find(line, '-->') if tmp then incomments = false line = sub(line, tmp+3) else line = nil end else local tmp = find(line, '<!--') local newline = line if tmp then -- for the analysis, we take everything that is before the -- comment sign newline = sub(line, 1, tmp-1) -- and we loop again with the comment incomments = true line = sub(line, tmp+4) else -- if there is no comment start, the block after that will -- end the analysis, we exit the while loop line = nil end for dir in gmatch(newline, '<dir>([^<]+)</dir>') do -- now we need to replace ~ by kpse.expand_path('~') if sub(dir, 1, 1) == '~' then dir = file.join(kpse.expand_path('~'), sub(dir, 2)) end -- we exclude paths with texmf in them, as they should be -- found anyway if not find(dir, 'texmf') then results[#results+1] = dir end end for include in gmatch(newline, '<include[^<]*>([^<]+)</include>') do -- include here can be four things: a directory or a file, -- in absolute or relative path. if sub(include, 1, 1) == '~' then include = file.join(kpse.expand_path('~'),sub(include, 2)) -- First if the path is relative, we make it absolute: elseif not lfs.isfile(include) and not lfs.isdir(include) then include = file.join(file.dirname(path), include) end if lfs.isfile(include) then -- maybe we should prevent loops here? -- we exclude path with texmf in them, as they should -- be found otherwise read_fonts_conf(include, results) elseif lfs.isdir(include) then for _,f in next, glob(file.join(include, "*.conf")) do read_fonts_conf(f, results) end end end end end end f:close() return results end -- for testing purpose names.read_fonts_conf = read_fonts_conf local function get_os_dirs() if os.name == 'macosx' then return { file.join(kpse.expand_path('~'), "Library/Fonts"), "/Library/Fonts", "/System/Library/Fonts", "/Network/Library/Fonts", } elseif os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then local windir = os.getenv("WINDIR") return { file.join(windir, 'Fonts') } else return read_fonts_conf("/etc/fonts/fonts.conf", {}) end end local function scan_os_fonts(fontnames, newfontnames) --[[ This function scans the OS fonts through - fontcache for Unix (reads the fonts.conf file and scans the directories) - a static set of directories for Windows and MacOSX ]] logs.info("Scanning OS fonts...") if trace_search then logs.info("Searching in static system directories...") end for _,d in next, get_os_dirs() do scan_dir(d, fontnames, newfontnames, false) end end local function update_names(fontnames, force) --[[ The main function, scans everything - fontnames is the final table to return - force is whether we rebuild it from scratch or not ]] logs.info("Updating the font names database:") if force then fontnames = fontnames_init() else if not fontnames then fontnames = names.load() end if fontnames.version ~= names.version then fontnames = fontnames_init() if trace_search then logs.report("No font names database or old one found; " .."generating new one") end end end local newfontnames = fontnames_init() read_blacklist() scan_texmf_fonts(fontnames, newfontnames) if expandpath("$OSFONTDIR"):is_empty() then scan_os_fonts(fontnames, newfontnames) end return newfontnames end local function save_names(fontnames) local savepath = names.path.localdir if not lfs.isdir(savepath) then dir.mkdirs(savepath) end savepath = file.join(savepath, names.path.basename) if file.iswritable(savepath) then table.tofile(savepath, fontnames, true) logs.info("Font names database saved: %s \n", savepath) return savepath else logs.info("Failed to save names database\n") return nil end end local function scan_external_dir(dir) local old_names, new_names if loaded then old_names = names.data else old_names = names.load() loaded = true end new_names = table.copy(old_names) scan_dir(dir, old_names, new_names) names.data = new_names end names.scan = scan_external_dir names.load = load_names names.update = update_names names.save = save_names