diff --git a/Data/frequency.txt b/Data/frequency.txt deleted file mode 100644 index d38b34c55..000000000 --- a/Data/frequency.txt +++ /dev/null @@ -1,1922 +0,0 @@ -# Robofab Data -# Simple pair frequency table. -# Based on a couple of dictionaries, french, english, german and italian texts. -# Should be better, more scientific and specific. But it's a start. -255362 er -249721 e, -249641 in -204518 en -182422 te -180257 re -178632 on -165973 ti -152769 at -139301 t, -137506 st -132984 an -131627 le -131472 es -131377 ra -129911 nt -128252 ng -123411 n, -117960 y, -116369 ar -113390 de -108530 al -108136 d, -107568 ri -106645 s, -105909 is -103450 or -101847 ne -99762 it -99372 li -97633 ed -93993 io -91288 se -91043 co -90384 ss -87513 ro -87509 nd -86813 ou -86315 un -84983 ic -84251 ch -84164 r, -83942 ve -79163 g, -76990 la -75700 ta -75329 he -73886 ge -72582 ea -70517 di -70198 me -68196 el -67007 tr -65864 si -65428 et -64632 as -64567 ce -64179 us -61344 ma -61234 ll -60692 th -59390 l, -58376 ac -58032 pe -57617 nc -57564 to -56213 ur -56132 lo -53466 il -52806 ca -51876 ie -51793 pr -51749 ns -51064 na -50356 ni -50160 ha -48777 ol -47605 ut -47148 ai -46416 ec -46231 bl -45286 rt -45045 mi -44923 sh -44816 om -42679 em -42469 ig -42352 be -42106 pa -41525 sc -41193 po -40813 ho -40717 ul -40526 hi -39642 ct -39545 ck -39345 ab -37646 ia -37505 ad -37068 ke -36998 iv -36061 h, -35879 ee -35045 ot -34796 im -34767 sp -34387 mo -34121 am -34070 mp -33988 ag -33885 os -33820 tu -33787 no -33739 ci -33605 ir -32421 oo -32401 fi -32096 p, -31920 id -31479 su -31111 ty -30897 pi -30880 ow -30705 so -30599 ap -30009 ru -29989 up -29927 ba -29283 pl -29197 k, -28930 m, -28659 tt -28594 op -28293 vi -28172 sa -28158 gr -28082 cr -27990 um -27678 rs -27555 ue -27503 fo -27446 of -27205 ly -27167 wa -27142 ry -27046 fe -26909 do -25938 rd -25754 lu -24822 ep -24575 bo -24544 cu -24383 wi -24113 fa -24093 ff -23687 ga -23615 ei -22684 da -22559 va -22557 oc -22425 qu -22367 br -22289 ex -21959 rr -21958 rm -21608 if -21487 we -21361 bi -21266 au -21243 gh -20723 fl -20634 ov -20381 od -20126 gi -19913 cl -19656 av -19585 ht -19426 eg -19136 c, -19112 ev -18855 ef -18813 rn -18602 fu -18548 ui -18477 pu -18345 ip -18278 ak -17757 ze -17729 og -17377 ay -17347 pp -16883 go -16837 bu -16745 uc -16696 dr -16329 rc -16264 nn -16115 sm -15922 lt -15845 mb -15826 gu -15757 mm -15722 ph -15521 ua -15363 oi -14962 vo -14954 du -14899 ub -14888 o, -14640 a, -14318 fr -14309 ld -14196 nf -13849 iz -13823 gl -13784 f, -13551 ki -13317 ug -13162 tl -13098 rg -13028 wo -12991 mu -12667 ib -12578 ud -12546 sl -12272 pt -12227 nk -12186 hr -11976 ob -11745 nu -11447 rk -11226 gn -10962 w, -10915 hu -10841 ts -10714 oa -10675 eb -10395 af -10285 eu -10200 dl -9867 rl -9839 sn -9803 aw -9801 ae -9740 rb -9726 ft -9529 cc -9299 sy -9278 ok -9076 wn -8774 ew -8658 hy -8645 tc -8567 eh -8537 ez -8465 rv -8144 uf -8011 gg -7971 .. -7893 zi -7835 rp -7812 oe -7548 ij -7517 dd -7505 cy -7433 ls -7339 sw -7304 dy -7256 nv -7255 e- -7235 ik -6969 bs -6818 bb -6568 lf -6562 ju -6317 aa -6255 eo -6239 wh -6127 ps -6052 gs -5969 ds -5852 je -5790 sk -5728 rf -5653 ey -5577 b, -5464 jo -5362 hl -5358 ye -5246 za -5197 xp -5182 ny -5169 ys -5019 kl -4975 xt -4968 kn -4961 dg -4952 ka -4740 x, -4499 lk -4465 nr -4445 nl -4436 xi -4361 tw -4313 d- -4222 dn -4052 t- -4040 ym -4034 hn -3977 eq -3915 az -3889 gy -3856 -s -3847 ja -3841 l- -3810 ln -3769 wr -3764 lm -3755 nh -3754 fy -3752 uo -3747 sq -3694 py -3691 oy -3664 yi -3529 by -3513 rh -3505 ek -3470 f- -3445 yp -3369 np -3362 ah -3359 nm -3337 xc -3334 nb -3264 ky -3223 my -3204 ko -3173 hm -3167 zo -3163 ks -3158 -c -3128 zu -3125 iu -3120 rw -3082 zz -3079 -d -3042 ix -2979 ya -2977 ms -2972 y- -2969 -t -2957 -f -2957 lv -2951 ox -2853 tz -2844 tn -2834 kt -2812 iq -2781 r- -2770 xe -2768 tf -2746 ax -2730 -a -2640 sf -2588 lp -2585 yn -2562 n- -2537 e` -2516 lg -2506 wl -2505 nz -2502 -b -2448 tm -2421 lc -2411 -o -2368 yl -2343 cs -2323 mn -2321 -p -2314 yc -2298 bt -2280 xa -2262 yo -2258 sg -2239 hs -2201 nw -2159 hw -2121 -m -2107 gt -2056 dv -2002 oh -1996 -w -1958 n. -1947 yr -1942 ku -1932 `r -1928 Ma -1924 dm -1861 -h -1859 gm -1837 o- -1828 sb -1756 lb -1731 -e -1702 i, -1676 nj -1662 h- -1625 ux -1621 pf -1586 nq -1577 rz -1554 Sc -1550 jn -1545 yt -1540 uv -1507 Ge -1483 vy -1464 St -1446 g- -1416 k- -1412 -l -1410 -g -1402 -r -1388 vu -1382 kr -1370 zl -1366 vr -1359 ws -1333 u, -1329 dw -1328 Be -1300 fs -1296 He -1291 ml -1259 -u -1244 -i -1239 df -1239 s- -1231 xu -1224 zy -1223 sr -1218 uz -1215 oz -1201 bj -1187 wd -1181 tb -1134 w- -1113 p- -1099 mf -1087 tg -1081 We -1076 Pa -1074 ej -1037 hf -1035 Ve -1028 xh -1024 kw -1009 sd -1000 Co -998 uk -972 aj -964 sv -952 dh -944 ji -941 Wa -934 mt -934 Ha -934 uw -922 jk -909 bd -891 Mi -886 ^t -885 wu -869 hb -855 dj -849 oq -848 Ka -846 e. -840 De -825 Ba -816 yd -811 db -782 En -759 Au -753 Ne -748 lw -748 Ko -742 t. -739 xy -738 Re -736 cq -732 z, -731 -n -730 bg -724 pn -723 xo -722 Pr -721 Sa -719 Sp -717 zt -716 kh -715 Fr -709 In -707 An -706 Br -703 Ch -702 tp -700 Da -699 zw -678 Ho -668 lh -665 wb -662 kk -653 pm -650 Se -640 yb -635 Va -635 aq -631 Gr -629 La -628 dc -625 hd -621 Zi -614 fg -614 Er -613 bm -612 Go -612 Ca -609 gd -602 lr -599 kb -599 Ar -596 m- -590 ih -584 d. -582 a^ -580 yg -574 lz -571 yw -570 md -566 s. -564 Tr -561 Me -559 dt -559 Ad -557 Do -544 Di -541 sj -539 e^ -538 nx -537 Ei -527 uy -527 oj -526 bn -525 Le -524 km -516 Mo -515 Na -514 ao -513 Hi -510 Bo -493 wk -492 Wi -490 a- -487 Lo -485 Ta -485 Fe -482 Ju -480 Ja -478 Fa -476 Al -461 r. -457 uh -457 Ik -456 Bu -454 tj -452 hg -447 So -444 To -440 Te -438 dp -438 Vo -435 rq -434 wf -430 Si -428 wy -422 Ab -418 jd -415 Ga -402 Un -390 bv -390 gb -390 Ra -389 Fl -387 rj -387 Po -382 c/ -381 wt -381 hk -380 Th -371 uu -366 gk -362 yf -360 gw -352 Li -350 Ze -350 Kr -349 g. -349 Ro -345 Fi -341 Pe -340 fd -339 Am -332 bf -332 hh -325 yh -323 -v -319 tv -317 mg -316 bw -316 sz -309 i^ -307 mr -302 Vi -299 td -299 pb -298 Jo -297 ^c -296 ii -294 kg -293 Bi -293 pg -292 wm -290 Su -288 Ki -285 At -283 yz -282 Fu -280 -k -280 Pl -279 i- -277 No -276 Wo -275 Ph -272 hp -272 vv -268 fn -268 k. -268 cn -266 Ti -266 a. -263 Ol -262 Zu -260 bh -260 Ku -257 fh -255 mh -253 tk -252 Fo -252 Ke -250 mv -249 Je -248 kp -245 iw -244 Du -240 gf -236 Lu -235 ^l -234 O, -233 Dr -232 kf -232 Tu -232 Ni -230 o^ -230 Kl -230 ^n -228 Ru -227 pw -224 fb -224 /a -218 l. -212 j, -210 Bl -209 Ap -209 gz -205 mw -204 -j -204 yu -204 Mu -201 Zo -197 C, -196 c- -194 fw -194 /o -193 -y -192 Or -190 u^ -184 pc -182 hz -180 P, -177 Qu -177 kd -177 j. -174 hc -174 Ue -173 Gl -173 Ea -172 As -171 Pi -168 mc -168 xq -166 xs -165 Ag -165 zb -161 Hu -160 Zw -159 bk -159 Pu -154 El -153 D, -152 Ex -150 wp -148 Nu -147 Em -143 Cr -142 Gu -141 pd -140 fp -140 Eu -136 n! -135 bz -132 K, -129 Sh -126 mk -126 u- -126 jl -125 Op -125 Um -124 pk -124 n? -124 Ri -123 Ac -122 Ci -122 A, -120 Yo -119 Cy -118 fm -118 Rh -116 f. -116 Pf -115 Wh -113 `n -113 `v -112 yx -112 n: -112 zv -111 Ob -111 gp -111 `t -110 m. -110 Wu -109 wz -108 kj -107 kz -107 Ak -107 z- -106 -q -106 fz -105 ^m -104 Os -103 bc -101 dk -101 uj -100 zs -99 x- -98 zp -98 L, -97 bp -96 vl -96 Is -95 kc -95 lq -94 fk -94 Gi -94 Kn -92 Cl -92 n; -91 Hy -90 b- -89 Za -88 uq -88 jf -87 Eh -87 u. -87 `l -86 Ce -85 Vr -85 OK -84 It -83 M, -80 Es -80 zk -79 zg -79 e? -78 hv -77 cm -76 Ee -75 t: -75 Of -75 zf -75 T, -73 zh -73 Ir -72 -S -72 S, -72 yk -72 Af -71 wg -71 jz -70 `m -70 F, -70 jg -69 AT -69 e! -69 Py -69 Ur -68 On -68 Ae -68 Im -67 y. -67 jv -66 kv -66 dz -66 Ya -66 vs -65 zm -65 cd -64 t? -64 p. -62 Oe -62 US -61 e: -60 -C -60 Ed -60 Ev -60 xl -60 Av -59 Om -57 jp -56 t! -56 ww -56 Sk -56 SP -55 rx -55 d! -55 Ai -54 Ec -54 wc -54 .] -54 yv -54 Ul -53 mz -53 VI -52 jt -51 -M -51 r? -51 G, -50 gj -50 Dy -49 Sy -49 My -49 js -48 Sw -48 SO -48 Cu -48 `s -48 NA -47 w. -47 OL -46 -D -46 d: -46 .! -46 `g -46 KO -45 i. -44 Tw -44 jc -44 By -44 h. -43 Ep -43 Oc -42 Aq -42 g! -42 e; -41 Ot -41 cz -40 Ji -40 Ty -40 zn -40 MP -40 mj -40 o. -39 -G -39 .? -39 gv -39 s: -39 cw -38 fc -38 d? -38 B, -37 -L -37 d; -37 xn -37 ^v -37 ^i -37 I, -36 -- -36 xb -36 xf -35 -T -35 Z, -35 fj -35 Ou -34 -B -34 Xe -34 dq -34 OD -34 II -33 t; -33 Sl -33 X- -33 MO -33 Et -33 s? -33 lj -33 R, -32 -A -32 -V -32 r! -32 PA -32 Id -31 k! -31 SD -31 s; -31 s! -31 RN -30 -R -30 CA -30 r: -30 `c -30 `d -30 !! -29 Gy -29 fv -29 pj -29 Oo -29 DM -29 IP -29 U, -28 2. -28 Q, -28 H, -28 TV -28 1. -28 ^p -28 /u -27 SA -27 Vu -27 AF -27 AS -27 l! -27 19 -27 v, -27 N, -26 -N -26 G- -26 g: -26 ^r -25 Ky -25 a` -24 -P -24 r; -24 iy -24 V- -24 OB -24 zr -24 Mc -24 PO -24 BI -23 LS -23 ES -23 pz -23 AB -23 AW -23 MC -23 FB -23 10 -23 ^b -22 Ov -22 b. -22 Ye -22 l: -22 j: -22 PR -21 Ic -21 00 -21 OG -21 AD -21 RA -21 IC -20 S- -20 ST -20 Od -20 [1 -20 3. -20 AC -20 AM -20 C- -20 DE -20 WO -20 Ps -20 PS -20 RF -19 ET -19 HF -19 [2 -19 AL -19 g; -19 `q -19 Yu -19 xw -19 DD -19 BO -19 Ui -18 m? -18 Sm -18 LC -18 BM -18 CO -18 VA -18 Aa -18 TL -18 3- -18 `- -18 DC -18 jj -18 cC -18 IQ -18 Ah -17 k? -17 gc -17 FO -17 ?? -17 DP -17 RT -17 a? -16 Eb -16 Ly -16 X, -16 pv -16 l? -16 xk -16 KP -16 Wy -16 TO -15 -E -15 -K -15 Sv -15 SS -15 wj -15 CR -15 VD -15 Oh -15 HC -15 AN -15 zd -15 RO -15 15 -15 TR -15 T- -15 h! -14 -F -14 -H -14 SR -14 yj -14 EC -14 Eg -14 k: -14 OU -14 TH -14 FD -14 l; -14 16 -14 WA -14 Ie -14 a: -13 -I -13 0, -13 CM -13 V, -13 W, -13 u? -13 AR -13 g? -13 Mm -13 12 -13 DN -13 DQ -13 j? -13 j; -13 PD -13 jm -13 vg -13 IN -13 TP -13 Ut -13 Uh -12 GP -12 GO -12 -, -12 Gn -12 SE -12 LM -12 EE -12 Bh -12 CD -12 i: -12 O- -12 Ef -12 OW -12 TS -12 MT -12 e) -12 DA -12 RD -12 c. -12 Nj -12 45 -11 -O -11 99 -11 20 -11 2, -11 EM -11 ED -11 XI -11 IT -11 IO -11 4. -11 a! -10 tq -10 m! -10 m: -10 SC -10 f: -10 IF -10 01 -10 xg -10 MF -10 CP -10 .: -10 `b -10 Y- -10 Dh -10 ^s -10 ^d -10 WC -10 vt -10 TA -10 hq -10 Zy -9 -J -9 EN -9 k; -9 Kh -9 Vs -9 Oi -9 OM -9 OS -9 MV -9 FC -9 OA -9 ?! -9 xm -9 KV -9 Wr -9 PP -9 PU -9 RC -9 IA -9 Il -9 Up -9 NG -9 hj -8 Sz -8 Sn -8 -z -8 f! -8 LE -8 VC -8 EA -8 ER -8 Ey -8 CY -8 p: -8 HE -8 ,. -8 AO -8 Az -8 mq -8 FM -8 ?. -8 A- -8 Dw -8 Ig -8 o? -8 Us -7 GE -7 f; -7 LA -7 y? -7 Ek -7 MS -7 CC -7 i! -7 O. -7 OP -7 OR -7 Aw -7 35 -7 FT -7 FF -7 UH -7 lx -7 u; -7 Pt -7 x) -7 lA -7 [3 -7 RE -7 (S -7 (1 -7 NB -7 N- -6 EH -6 m; -6 94 -6 EF -6 EP -6 Xa -6 VL -6 Ok -6 Oz -6 OO -6 50 -6 5. -6 B- -6 bq -6 ., -6 u: -6 -2 -6 u! -6 Ts -6 AA -6 SN -6 17 -6 DF -6 j- -6 RS -6 6c -6 Ih -6 BC -6 BW -6 (R -6 U- -6 AG -6 !. -6 NK -6 NC -6 ND -6 NT -6 a) -6 E. -5 -W -5 GC -5 GI -5 &D -5 j! -5 91 -5 LG -5 LT -5 25 -5 28 -5 QC -5 JD -5 w! -5 CH -5 p! -5 KG -5 51 -5 HQ -5 Ax -5 h: -5 zc -5 SI -5 Ml -5 MR -5 FV -5 UN -5 UK -5 qq -5 xd -5 11 -5 KW -5 1, -5 DJ -5 DU -5 D- -5 ^q -5 WR -5 SW -5 [4 -5 SU -5 PC -5 IS -5 IV -5 IM -5 ID -5 Ib -5 BE -5 TU -5 KC -5 TE -5 Uf -5 h? -5 Ny -5 NM -5 a; -4 GA -4 -1 -4 t) -4 -; -4 BX -4 90 -4 95 -4 OC -4 LL -4 r) -4 26 -4 27 -4 yq -4 Gh -4 XV -4 JP -4 J, -4 93 -4 CI -4 (L -4 VH -4 Vl -4 Ox -4 58 -4 OF -4 56 -4 Ds -4 OE -4 HO -4 H- -4 -: -4 zj -4 MA -4 30 -4 36 -4 MZ -4 FA -4 FE -4 YS -4 LF -4 Mn -4 s) -4 1) -4 DO -4 DI -4 w; -4 ^g -4 WE -4 PE -4 IU -4 vf -4 I. -4 IX -4 IB -4 Io -4 If -4 (T -4 (D -4 6. -4 TD -4 62 -4 UP -4 NN -4 NE -4 40 -4 E, -3 -Z -3 GS -3 GB -3 GH -3 92 -3 SH -3 LP -3 y! -3 y: -3 EL -3 M- -3 7. -3 70 -3 Qi -3 JZ -3 JA -3 w? -3 05 -3 0; -3 0) -3 CG -3 CV -3 p? -3 MI -3 ON -3 b! -3 HT -3 .M -3 HA -3 AP -3 n) -3 Ew -3 Mr -3 MD -3 38 -3 32 -3 31 -3 37 -3 `f -3 `p -3 f? -3 UD -3 RL -3 88 -3 81 -3 K& -3 .; -3 Kw -3 13 -3 DV -3 DS -3 WI -3 qa -3 Pn -3 PI -3 PM -3 TT -3 cp -3 RG -3 /M -3 vd -3 vp -3 v. -3 IE -3 BA -3 BB -3 BL -3 (B -3 (C -3 (F -3 TB -3 TC -3 ;S -3 o) -3 4) -3 E- -2 -U -2 GN -2 GM -2 -. -2 Z- -2 &R -2 &2 -2 VT -2 vb -2 9) -2 vk -2 9, -2 Sj -2 LO -2 LH -2 LR -2 fq -2 24 -2 21 -2 22 -2 23 -2 29 -2 (K -2 2- -2 2) -2 rD -2 EO -2 VM -2 EW -2 Eo -2 Ez -2 1b -2 XE -2 7, -2 $1 -2 Md -2 w: -2 04 -2 09 -2 0. -2 Bc -2 0c -2 ZU -2 DK -2 ME -2 CE -2 CU -2 (A -2 p; -2 Cz -2 V. -2 G. -2 i; -2 VE -2 VN -2 O! -2 5d -2 5g -2 5f -2 5c -2 Ow -2 55 -2 HI -2 Hz -2 v- -2 .- -2 .O -2 .D -2 .E -2 Tj -2 TI -2 AE -2 AK -2 AU -2 AV -2 Ay -2 gq -2 mR -2 Sg -2 SZ -2 SM -2 39 -2 FP -2 FR -2 FI -2 Fj -2 Ub -2 `z -2 F- -2 F. -2 Yk -2 Rt -2 RR -2 RM -2 ,0 -2 Ng -2 .p -2 86 -2 eG -2 xv -2 1a -2 1e -2 14 -2 18 -2 1- -2 :. -2 #2 -2 PV -2 PN -2 PL -2 P. -2 jb -2 kq -2 /D -2 vw -2 IZ -2 IG -2 Iv -2 (P -2 (J -2 RP -2 C. -2 UT -2 UR -2 h; -2 8) -2 !? -2 NI -2 NL -2 NS -2 NW -2 N. -2 48 -2 49 -2 47 -2 41 -1 tD -1 -Y -1 GU -1 tR -1 Gt -1 t1 -1 -4 -1 -6 -1 -0 -1 Gm -1 -8 -1 &c -1 ZI -1 r -1 ZZ -1 Zn -1 Zr -1 9a -1 ,l -1 m/ -1 Sr -1 Sq -1 96 -1 SL -1 SF -1 Lt -1 Ls -1 LD -1 LI -1 r/ -1 fA -1 y) -1 2; -1 2a -1 2b -1 EI -1 -3 -1 EK -1 EG -1 EX -1 EU -1 Eq -1 BN -1 M. -1 XX -1 XT -1 Xh -1 Xy -1 7d -1 7c -1 7b -1 7a -1 RB -1 vn -1 kB -1 Qy -1 74 -1 kW -1 78 -1 d/ -1 d) -1 JU -1 JK -1 Jk -1 KE -1 J- -1 J. -1 Mg -1 L& -1 02 -1 03 -1 06 -1 07 -1 0: -1 08 -1 cL -1 /2 -1 0b -1 0a -1 0f -1 ]) -1 ); -1 ), -1 CB -1 CS -1 CW -1 CT -1 Cm -1 Cd -1 ML -1 Cs -1 iC -1 iF -1 i? -1 VO -1 VV -1 33 -1 5o -1 5n -1 5i -1 5h -1 5k -1 5j -1 5e -1 Oa -1 57 -1 53 -1 5/ -1 5) -1 TG -1 b) -1 b? -1 b: -1 Hf -1 Hg -1 H. -1 [V -1 [B -1 .0 -1 HP -1 .I -1 .A -1 .B -1 .F -1 .G -1 .Y -1 .P -1 .S -1 .T -1 .V -1 .W -1 .h -1 u` -1 Tl -1 Tm -1 Tb -1 Tc -1 A. -1 AI -1 n -1 /Z -1 98 -1 Aj -1 .a -1 Sb -1 Pp -1 97 -1 Sd -1 g) -1 g( -1 TN -1 WU -1 SJ -1 3t -1 z. -1 Mb -1 Mw -1 Mv -1 3; -1 MB -1 MM -1 34 -1 MW -1 3/ -1 MY -1 FS -1 FU -1 FZ -1 FL -1 cD -1 Lw -1 Cb -1 UU -1 UV -1 YO -1 o` -1 Yb -1 ,4 -1 ,2 -1 sA -1 Ry -1 RI -1 RK -1 R- -1 R. -1 e[ -1 K. -1 xz -1 K2 -1 xr -1 1k -1 x. -1 x! -1 KS -1 DB -1 DT -1 DR -1 Dj -1 *N -1 MN -1 D. -1 ^f -1 WM -1 WV -1 -! -1 MH -1 q- -1 q, -1 [7 -1 [6 -1 qs -1 TW -1 W. -1 5, -1 qm -1 ,3 -1 Pb -1 .f -1 Pd -1 j -1 PX -1 .z -1 PB -1 PJ -1 PK -1 jw -1 jh -1 .v -1 cb -1 cg -1 6- -1 RY -1 60 -1 61 -1 cB -1 cG -1 P/ -1 P- -1 6a -1 6b -1 6d -1 c? -1 /3 -1 /O -1 /A -1 /T -1 /R -1 IY -1 IK -1 IL -1 4; -1 /8 -1 /5 -1 (p -1 BT -1 BV -1 BP -1 (l -1 BS -1 (b -1 (a -1 (f -1 (Y -1 (V -1 (W -1 (H -1 (I -1 (O -1 (M -1 (E -1 cf -1 B. -1 U2 -1 69 -1 o! -1 o; -1 T5 -1 Uz -1 Uk -1 Ud -1 Ug -1 ;. -1 UI -1 Nr -1 NO -1 NY -1 NZ -1 NP -1 NV -1 n/ -1 Wl -1 hD -1 D -1 6f -1 46 -1 42 -1 43 -1 4/ -1 4, -1 p -1 4t -1 4a \ No newline at end of file diff --git a/Lib/robofab/__init__.py b/Lib/robofab/__init__.py index 9dc25322e..0a9842167 100755 --- a/Lib/robofab/__init__.py +++ b/Lib/robofab/__init__.py @@ -75,5 +75,5 @@ class RoboFabError(Exception): pass class RoboFabWarning(Warning): pass -numberVersion = (1, 1, "develop", 3) -version = "1.1.3" +numberVersion = (1, 2, "develop", 0) +version = "1.2.0d" diff --git a/Lib/robofab/objects/family.py b/Lib/robofab/objects/family.py deleted file mode 100755 index 00d28cb9e..000000000 --- a/Lib/robofab/objects/family.py +++ /dev/null @@ -1,358 +0,0 @@ -"""This module has been deprecated.""" - -from warnings import warn -warn("family.py is deprecated.", DeprecationWarning) - -""" -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -W A R N I N G - -This is work in progress, a fast moving target. - -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -""" - -import os -from robofab import RoboFabError -from robofab.plistlib import readPlist, writePlist -from robofab.objects.objectsRF import RInfo, RFont, RLib, RGlyph, OpenFont -from robofab.ufoLib import UFOReader, writePlistAtomically, fontInfoAttrs -from robofab.objects.objectsBase import RBaseObject, BaseGroups -from robofab.glifLib import GlyphSet -import weakref - -""" -RoboFab family planning - -A font family is a group of UFO fonts that are related when - - they share a common source - - they are masters in various interpolations - - they are weights in a section of design space - -Some family infrastructure is needed because there is some -information that transcends font or glyph. It is also a tool -to track fonts which are part of a larger structure, make sure -all resources needed for a project are present, up to date etc. - -Structure: - MyFamilyProjectName.uff/ - # the file and folder structure of the extended UFO family. - - lib.plist - # a lib for the family - - fonts/ - # a folder with ufo's - masterA.ufo/ - masterB.ufo/ - # any number of fonts - ... - contents.plist - # a contents.plist to track resources - - shared/ - # a .ufo containing capable of containing all - # data that a .ufo can contain. this data is - # shared with all members of the family - metainfo.plist - fontinfo.plist - lib.plist - groups.plist - kerning.plist - glyphs/ - # any number of common or sharable glifs. - foundryLogo.glif - genericStuff.glif - - # location for inbetween masters, interpolation exceptions: - # glyphs that don't fit in any specific font - a_lightBold_028.glif - a_lightBold_102.glif - a_lightBold_103.glif - ... - contents.plist -""" - - -FONTS_DIRNAME = 'fonts' -FONTSCONTENTS_FILENAME = "contents.plist" -METAINFO_FILENAME = 'metainfo.plist' -LIB_FILENAME = 'lib.plist' -FAMILY_EXTENSION = ".uff" -FONT_EXTENSION = ".ufo" -SHARED_DIRNAME = "shared" - - -def makeUFFName(familyName): - return ''.join([familyName, FAMILY_EXTENSION]) - -def _scanContentsDirectory(path, forceRebuild=False): - contentsPath = os.path.join(path, FONTSCONTENTS_FILENAME) - if forceRebuild or not os.path.exists(contentsPath): - ext = FONT_EXTENSION - fileNames = os.listdir(path) - fileNames = [n for n in fileNames if n.endswith(ext)] - contents = {} - for n in fileNames: - contents[n[:-len(ext)]] = n - else: - contents = readPlist(contentsPath) - return contents - - -class FamilyReader(object): - - """A reader that reads all info from a .uff""" - - def __init__(self, path): - self._path = path - - def _checkForFile(self, path): - if not os.path.exists(path): - return False - else: - return True - - def readMetaInfo(self): - path = os.path.join(self._path, METAINFO_FILENAME) - if not self._checkForFile(path): - return - - def readLib(self): - path = os.path.join(self._path, LIB_FILENAME) - if not self._checkForFile(path): - return {} - return readPlist(path) - - def readFontsContents(self): - contentsPath = os.path.join(self._path, FONTS_DIRNAME) - contents = _scanContentsDirectory(contentsPath) - return contents - - def getSharedPath(self): - """Return the path of all shared values in the family, - rather then create a new instance for it.""" - return os.path.join(self._path, SHARED_DIRNAME) - - -class FamilyWriter(object): - - """a writer that builds all the necessary family stuff.""" - - - fileCreator = 'org.robofab.uffLib' - formatVersion = 1 - - def __init__(self, path): - self._path = path - - def _makeDirectory(self, subDirectory=None): - path = self._path - if subDirectory: - path = os.path.join(self._path, subDirectory) - if not os.path.exists(path): - os.makedirs(path) - if not os.path.exists(os.path.join(path, METAINFO_FILENAME)): - self._writeMetaInfo() - return path - - def _writeMetaInfo(self): - path = os.path.join(self._path, METAINFO_FILENAME) - metaInfo = { - 'creator': self.fileCreator, - 'formatVersion': self.formatVersion, - } - writePlistAtomically(metaInfo, path) - - def writeLib(self, libDict): - self._makeDirectory() - path = os.path.join(self._path, LIB_FILENAME) - if libDict: - writePlistAtomically(libDict, path) - elif os.path.exists(path): - os.remove(path) - - def writeFontsContents(self): - path = self.makeFontsPath() - contents = _scanContentsDirectory(path) - contentsPath = os.path.join(path, FONTSCONTENTS_FILENAME) - writePlistAtomically(contents, contentsPath) - - def makeFontsPath(self): - fontDir = self._makeDirectory(FONTS_DIRNAME) - return fontDir - - def makeSharedPath(self): - sharedDir = self._makeDirectory(SHARED_DIRNAME) - return sharedDir - - def getSharedGlyphSet(self): - path = self.makeSharedGlyphsPath() - return GlyphSet(path) - - -class RFamily(RBaseObject): - - """ - Sketch for Family, the font superstructure. - This should ultimately move to objectsRF - - The shared fontinfo and glyphset is just another font, named 'shared', - this avoids duplication of a lot of functionality in maintaining - the shared glyphset, reading, writing etc. - """ - - def __init__(self, path=None): - self._path = path - self.info = RInfo() # this should go away. it is part of shared.info. - self.lib = RLib() - self.shared = RFont() - self._fontsContents = {} # font name: path - self._fonts = {} # fontName: object - self.lib.setParent(self) - if self._path: - self._loadData() - - def __repr__(self): - if self.info.familyName: - name = self.info.familyName - else: - name = 'UnnamedFamily' - return "" %(name) - - def __len__(self): - return len(self._fontsContents.keys()) - - def __getitem__(self, fontKey): - if self._fontsContents.has_key(fontKey): - if not self._fonts.has_key(fontKey): - fontPath = os.path.join(self._path, FONTS_DIRNAME, self._fontsContents[fontKey]) - font = RFont(fontPath) - font.setParent(self) - self._fonts[fontKey] = font - # uh, is returning a proxy the right thing to do here? - return weakref.proxy(self._fonts[fontKey]) - raise IndexError - - def __setitem__(self, key, fontObject): - if not key: - key = 'None' - key = self._makeKey(key) - self._fontsContents[key] = None - self._fonts[key] = fontObject - fontObject._path = None - - def keys(self): - return self._fontsContents.keys() - - def has_key(self, key): - return self._fontsContents.has_key(key) - - __contains__ = has_key - - def _loadData(self): - fr = FamilyReader(self._path) - self._fontsContents = fr.readFontsContents() - self.shared = RFont(fr.getSharedPath()) - self.lib.update(fr.readLib()) - - def _hasChanged(self): - #mark the object as changed - self.setChanged(True) - - def _makeKey(self, key): - # add a numerical extension to the key if it already exists - if self._fontsContents.has_key(key): - if key[-2] == '.': - try: - key = key[:-1] + `int(key[-1]) + 1` - except ValueError: - key = key + '.1' - else: - key = key + 1 - self_makeKey(key) - return key - - def save(self, destDir=None, doProgress=False): - if not destDir: - saveAs = False - destDir = self._path - else: - saveAs = True - fw = FamilyWriter(destDir) - for fontName, fontPath in self._fontsContents.items(): - if saveAs and not self._fonts.has_key(fontName): - font = self[fontName] - if self._fonts.has_key(fontName): - if not fontPath or saveAs: - fontPath = os.path.join(path, fw.makeFontsPath(), ''.join([fontName, FONT_EXTENSION])) - self._fontsContents[fontName] = fontPath - self._fonts[fontName].save(fontPath, doProgress=False) - fw.writeFontsContents() - fw.writeLib(self.lib) - sharedPath = fw.makeSharedPath() - self.shared.save(sharedPath, doProgress=False) - self._path = destDir - - #def sharedGlyphNames(self): - # """a list of all shared glyphs""" - # keys = self.sharedGlyphs.keys() - # if self.sharedGlyphSet is not None: - # keys.extend(self.sharedGlyphSet.keys()) - # d = dict.fromkeys(keys) - # return d.keys() - # - #def getGlyph(self, glyphName, fontName=None): - # """retrieve a glyph from fontName, or from shared if no font is given.""" - # if fontName is None or fontName =="shared": - # # ask for a shared glyph - # return self.shared[glyphName] - # if self.has_key(fontName): - # return self[fontName].getGlyph(glyphName) - # return None - # - #def newGlyph(self, glyphName): - # """add a new shared glyph""" - # return self.shared.newGlyph(glyphName) - # - #def removeGlyph(self, glyphName): - # """remove a shared glyph""" - # self.shared.removeGlyph(glyphName) - - -if __name__ == "__main__": - - from robofab.world import OpenFont - from robofab.interface.all.dialogs import GetFolder - from PyBrowser import Browser - - #open and test - font = OpenFont() - family = RFamily() - family['aFont'] = font - family.lib['org.robofab.uffLibTest'] = 'TestOneTwo!' - family.shared.info.familyName = 'ThisIsAFamilyName' - family.shared.newGlyph('xxx') - path = GetFolder('where do you want to store this new .uff?') - path = os.path.join(path, makeUFFName('MyBigFamily')) - family.save(path) - #family = RFamily(path) - #Browser(family.getGlyph('ASharedGlyph_Yay')) - - ## save as test - #path = GetFolder('select a .uff directory') - #family = RFamily(path) - #family.newGlyph('xxx') - #family.name = 'YeOldFamily' - #newPath = os.path.join(os.path.split(path)[0], 'xxx'+os.path.split(path)[1]) - #family.save(newPath) - diff --git a/Lib/robofab/objects/featureLib.py b/Lib/robofab/objects/featureLib.py deleted file mode 100644 index e368dc691..000000000 --- a/Lib/robofab/objects/featureLib.py +++ /dev/null @@ -1,542 +0,0 @@ -from warnings import warn -warn("featureLib.py is deprecated.", DeprecationWarning) - -""" -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -W A R N I N G - -This is work in progress, a fast moving target. - -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -""" - - - -"""FeatureLib - -An attempt to get OpenType features written like Adobe's FDK -Feature descriptions to export into UFO and back. FontLab has -an interface for writing the features. FeatureLib offers some -tools to store the feature text, or to try interpretating it. - -There seem to be no clever ways to make an interpreter for -feature speak based on abstract descriptions of the language: - No Backus Naur Form description is available. - No python interpreter - No C source code available to the public - -So rather than go for the complete image, this implementation -is incomplete and probably difficult to extend. But it can -interpret the following features and write them back into the -right format and order: - - feature xxxx { - - } xxxx; - - # lines with comment - @classname = [name1 name2 etc]; - sub x x x by y y; - sub x from [a b c]; - pos xx yy 100; - -When interpret = False is passed as parameter it won't attempt to -interpret the feature text and just store it. Uninterpreted feature -text is exported as an url encoded string to ensure roundtripping -when the data is stored in a plist: - %09feature%20smcp%20%7B -This makes the feature text safe for storage in plist form without -breaking anything. - -Also, if interpretation fails for any reason, the feature text is stored -so data should be lost. - -To do: - - make it possible to read a .fea file and spit into seperate items - - test with more and different features from fontlab. - -""" - - -DEBUG = True - -DEFAULTNAME = "xxxx" - - - -__all__ = ["Feature", "FeatureSet", "many_to_many", "one_from_many", - "simple_pair", "extractFLFeatures", "putFeaturesLib", "getFeaturesLib"] - - -# substition types -# are there official opentype substitution titles for this? -many_to_many = 0 -one_from_many = 1 - -# kern types -simple_pair = 0 - -# lib key for features -featureLibKey = "org.robofab.features" - - -class Feature: - - """Feature contains one single feature, of any flavor. - Read from feature script - Write to feature script - Read from simple dict - Write to simple dict - Parse some of the lines - Accept edits and additions - """ - - def __init__(self, name=None, text=None, data=None, interpret=True): - if name is not None: - self.name = name - else: - self.name = DEFAULTNAME - self._sub = [] - self._pos = [] - self._comment = [] - self._feature = [] - self._classes = [] - self._tab = " "*4 - self._text = None - if text is not None: - self.readFeatureText(text, interpret) - elif data is not None: - self.fromDict(data) - - def __repr__(self): - return ""%(self.name) - - def addSub(self, itemsIn, itemsOut, subType=many_to_many): - """Add a substitution statement""" - self._sub.append((subType, itemsIn, itemsOut)) - - def addPos(self, itemOne, itemTwo, offset): - """Add a positioning statement""" - self._pos.append((itemOne, itemTwo, offset)) - - def hasSubs(self): - """Return True if this feature has substitutions defined.""" - return len(self._sub) > 0 - - def hasPos(self): - """Return True if this feature has positioning defined.""" - return len(self._pos) > 0 - - def readFeatureText(self, featureText, interpret=True): - """Read the feature text and try to make sense of it. - - Note: Should you want to preserve the actual featuretext - rather than the intrepreted data, set interpret = False - - In case the feature text isn't properly interpreted - (possible) or because the feature text is hand edited - and you just want it to round trip to UFO. - """ - if interpret: - if featureText is not None: - self.parse(featureText) - else: - self._text = featureText - - def parse(self, featureText): - """bluntly split the lines of feature code as they come from fontlab - This doesn't by any means parse all of the possible combinations - in a .fea file. It parses the pos and sub lines defines within a feature. - Something higher up should parse the seperate features from the .fea. - - It doesn't check for validity of the lines. - """ - lines = featureText.split("\n") - count = 0 - featureOpened = False - interpretOK = True - for l in lines: - # - # run through all lines - # - p = l.strip() - if len(p)==0:continue - if p[-1] == ";": - p = p[:-1] - p = p.split(" ") - count += 1 - - # - # plain substitutions - # example: - # sub @class496 by @class497; - # sub aring from [amacron aringacute adieresis aacute]; - # sub s s by s_s; - # - if p[0] == "sub": - if "by" in p: - # sub xx by xx; - self.addSub(p[1:p.index("by")], p[p.index("by")+1:], many_to_many) - elif "from" in p: - # sub x from [zzz]; - theList = " ".join(p[p.index("from")+1:])[1:-1].split(" ") - self.addSub(p[1:p.index("from")], theList, one_from_many) - - # - # plain kerning - # example: - # pos Yacute A -215; - # - elif p[0] == "pos": - items = p[1:-1] - value = int(p[-1]) - self._pos.append((simple_pair, items, value)) - - # - # comments - # - elif p[0] == "#": - # comment? - self._comment.append(" ".join(p[1:])) - - # - # features beginning or feature within feature - # - elif p[0] == "feature": - # comment? - if not featureOpened: - # ah, it's a fully wrapped description - if len(p[1]) == 4 and p[2] == "{": - self.name = p[1] - else: - print 'uh oh xxxxx', p - featureOpened = True - else: - # it's an unwrapped (from fontlab) description - self._feature.append(p[1:]) - - # - # feature ending - # - elif p[0] == "}" and p[1] == self.name: - featureOpened = False - - # - # special cases (humph) - # - - # - # special case: class definitions - # - elif "=" in p: - # check for class defenitions - # example: - # @MMK_L_A = [A Aacute]; - # @S = [S Sacute Scedille] - equalOperatorIndex = p.index("=") - classNames = p[:equalOperatorIndex] - # get the seperate names from the list: - classMembers = " ".join(p[equalOperatorIndex+1:])[1:-1].split(" ") - self._classes.append((classNames, classMembers)) - - # - # we can't make sense of it, store the feature text instead then.. - # - else: - print "Feature interpreter error:", p - interpretOK = False - if not interpretOK: - if DEBUG: - "Couldn't interpret all feature lines, storing the text as well." - self._text = featureText - - - def writeFeatureText(self, wrapped=True): - """return the feature as an OpenType feature string - wrapped = True: wrapped with featurename { feature items; } - wrapped = False: similar to that produced by FontLab - """ - - text = [] - if self._text: - # if literal feature text is stored: use that - # XXXX how to handle is there are new, manually added feature items? - # XXXX should the caller clear the text first? - from urllib import unquote - return unquote(self._text) - if wrapped: - text.append("feature %s {"%self.name) - if self._comment: - text.append(" # %s"%(" ".join(self._comment))) - if self._feature: - for f in self._feature: - text.append(" feature %s;"%(" ".join(f))) - if self._classes: - # - # first dump any in-feature class definitions - # - for classNames, classMembers in self._classes: - text.append(self._tab+"%s = [%s];"%(" ".join(classNames), " ".join(classMembers))) - if self._pos: - # - # run through the list twice to get the class kerns first - # - for posType, names, value in self._pos: - text.append(self._tab+"pos %s %d;"%(" ".join(names), value)) - if self._sub: - for (subType, stuffIn, stuffOut) in self._sub: - if subType == many_to_many: - text.append(self._tab+"sub %s by %s;"%((" ".join(stuffIn), " ".join(stuffOut)))) - elif subType == one_from_many: - text.append(self._tab+"sub %s from [%s];"%((" ".join(stuffIn), " ".join(stuffOut)))) - if wrapped: - text.append("} %s;"%self.name) - final = "\n".join(text)+"\n" - return final - - def asDict(self): - """Return the data of this feature as a plist ready dictionary""" - data = {} - data['name'] = self.name - if self._comment: - data['comment'] = self._comment - if self._sub: - data["sub"] = self._sub - if self._pos: - data["pos"] = self._pos - if self._feature: - data["feature"] = self._feature - if self._text: - from urllib import quote - data['text'] = quote(self._text) - return data - - def fromDict(self, aDict): - """Read the data from a dict.""" - self.name = aDict.get("name", DEFAULTNAME) - self._sub = aDict.get("sub", []) - self._pos = aDict.get("pos", []) - self._feature = aDict.get("feature", []) - self._comment = aDict.get("comment", []) - text = aDict.get('text', None) - if text is not None: - from urllib import unquote - self._text = unquote(text) - - -class FeatureSet(dict): - - """A dict to combine all features, and write them to various places""" - - def __init__(self, interpret=True): - self.interpret = interpret - - def readFL(self, aFont): - """Read the feature stuff from a RFont in FL context. - This can be structured better I think, but let's get - something working first. - """ - for name in aFont.getOTFeatures(): - if DEBUG: - print 'reading %s from %s'%(name, aFont.info.fullName) - self[name] = Feature(name, aFont.getOTFeature(name), interpret = self.interpret) - self.changed = True - - def writeFL(self, aFont, featureName=None): - """Write one or all features back""" - if featureName == None: - names = self.keys() - else: - names = [featureName] - for n in names: - text = self[n].writeFeatureText(wrapped=False) - if DEBUG: - print "writing feature %s"%n - print '- '*30 - print `text` - print `self[n]._text` - print '- '*30 - aFont.setOTFeature(n, text) - - def writeLib(self, aFont): - aFont.lib[featureLibKey] = self.asDict() - - def readLib(self, aFont): - """Read the feature stuff from the font lib. - Rather than add all this to yet another file in the UFO, - just store it in the lib. UFO users will be able to read - the data anyway. - """ - stuff = aFont.lib.get(featureLibKey, None) - if stuff is None: - if DEBUG: - print "No features found in this lib.." - return - self.clear() - self.update(stuff) - - def append(self, aFeature): - """Append a feature object to this set""" - self[aFeature.name] = aFeature - if DEBUG: - print "..added %s to FeatureSet"%aFeature.name - - def newFeature(self, name): - """Add a new feature and return it""" - self[name] = Feature(name) - return self[name] - - def update(self, aDict): - """Accept a dictionary with all features written out as dicts. - Ready for data read from plist - """ - for name, feature in aDict.items(): - self[name] = Feature(data=feature, interpret=self.interpret) - - def asDict(self): - """Return a dict with all features also written out as dicts. Not the same as self. - Data is ready for writing to plist - """ - data = {} - for name, feature in self.items(): - data[name] = feature.asDict() - return data - - -# convenience functions - -def extractFLFeatures(aFont, interpret=True): - """FontLab specific: copy features from the font to the font.lib""" - fs = FeatureSet(interpret = interpret) - fs.readFL(aFont) - fs.writeLib(aFont) - -def putFeaturesLib(aFont, featureSet): - """Put the features in the appropriate place in the font.lib""" - featureSet.writeLib(aFont) - -def getFeaturesLib(aFont, interpret=True): - """Get the featureset from a lib.""" - fs = FeatureSet(interpret = interpret) - fs.readLib(aFont) - return fs - - -if __name__ == "__main__": - - # examples - - print "-"*10, "sub many by many" - # a regular ligature feature - dligtext = """feature dlig { - # Latin - @MMK_L_A = [A Aacute]; - sub I J by IJ; - sub i j by ij; - sub s s by s_s; - } dlig; - """ - - feat1 = Feature(text=dligtext) - print feat1.asDict() - print - print feat1.writeFeatureText() - - - print "-"*10, "sub one from many" - # aalt one from many substitution - aalttext = """feature aalt { - sub aring from [amacron acircumflex adblgrave a agrave abreve acaron atilde aogonek aringacute adieresis aacute]; - sub utilde from [umacron uring uacute udieresisacute ucircumflex uhorn udblgrave udieresis uhungarumlaut udieresisgrave ugrave ubreve uogonek ucaron u]; - sub Hcircumflex from [H.sc Hcedilla.sc Hcircumflex.sc Hdotaccent H Hcedilla Hdieresis Hdieresis.sc Hdotaccent.sc]; - sub pdotaccent from [p pacute]; - } aalt; - """ - - feat2 = Feature(text=aalttext) - print feat2.asDict() - print - print feat2.writeFeatureText() - - - print "-"*10, "kerning" - # kern and positioning - kerntext = """ feature kern { - # Latin - pos Yacute A -215; - pos Yacute B -30; - pos Yacute C -100; - pos Yacute D -50; - pos Yacute E -35; - pos Yacute F -35; - pos Yacute G -80; - pos Yacute H -25; - # -- kerning classes - @MMK_L_A = [A Aacute]; - @MMK_R_C = [C Ccedilla]; - - } kern; - """ - - feat3 = Feature(text=kerntext) - print feat3.asDict() - print - print feat3.writeFeatureText() - - - - - print "-"*10, "something with groups in it" - # references to groups are treated like any other - grouptext = """ feature smcp { - # Latin - sub @class496 by @class497; - } smcp; - """ - - # Feature doesn't interpret the text in this example - # when interpret = False is given as parameter, - # the feature code is stored and reproduced exactly. - # But then you have to specify the name of the feature - # otherwise it will default and overwrite other features - # with the same default name. - - feat4 = Feature(name="smcp", text=grouptext, interpret = False) - - print feat4.asDict() - print - print feat4.writeFeatureText() - - - print "-"*10, "store the feature set in the lib" - # now create a feature set to dump all features in the lib - - set = FeatureSet() - set.append(feat1) - set.append(feat2) - set.append(feat3) - set.append(feat4) - - from robofab.world import NewFont - testFont = NewFont() - set.writeLib(testFont) - print testFont.lib[featureLibKey] - - print "-"*10, "read the feature set from the lib again" - - notherSet = FeatureSet() - notherSet.readLib(testFont) - for name, feat in notherSet.items(): - print name, feat - - - diff --git a/Lib/robofab/objects/objectsBase.py b/Lib/robofab/objects/objectsBase.py index 8ccc9bc82..bac6d3d83 100755 --- a/Lib/robofab/objects/objectsBase.py +++ b/Lib/robofab/objects/objectsBase.py @@ -18,12 +18,15 @@ do it with the objectsFL and objectsRF. from __future__ import generators from __future__ import division +from warnings import warn +import math +import copy +from robofab import ufoLib from robofab import RoboFabError from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect from fontTools.pens.basePen import AbstractPen -import math -import copy + #constants for dealing with segments, points and bPoints MOVE = 'move' @@ -146,39 +149,6 @@ class BasePostScriptHintValues(object): setattr(n, k, dup) return n - # math operations for psHint object - # Note: math operations can change integers to floats. - def __add__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, add) - return copied - - def __sub__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, sub) - return copied - - def __mul__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, mul) - return copiedInfo - - __rmul__ = __mul__ - - def __div__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, div) - return copiedInfo - - __rdiv__ = __div__ - - class BasePostScriptGlyphHintValues(BasePostScriptHintValues): """ Base class for glyph-level postscript hinting information. vStems, hStems @@ -212,6 +182,38 @@ class BasePostScriptGlyphHintValues(BasePostScriptHintValues): new.append((int(round(n[0])), int(round(n[1])))) setattr(self, name, new) + # math operations for psHint object + # Note: math operations can change integers to floats. + def __add__(self, other): + assert isinstance(other, BasePostScriptHintValues) + copied = self.copy() + self._processMathOne(copied, other, add) + return copied + + def __sub__(self, other): + assert isinstance(other, BasePostScriptHintValues) + copied = self.copy() + self._processMathOne(copied, other, sub) + return copied + + def __mul__(self, factor): + #if isinstance(factor, tuple): + # factor = factor[0] + copiedInfo = self.copy() + self._processMathTwo(copiedInfo, factor, mul) + return copiedInfo + + __rmul__ = __mul__ + + def __div__(self, factor): + #if isinstance(factor, tuple): + # factor = factor[0] + copiedInfo = self.copy() + self._processMathTwo(copiedInfo, factor, div) + return copiedInfo + + __rdiv__ = __div__ + def _processMathOne(self, copied, other, funct): for name, values in self._attributeNames.items(): a = None @@ -287,13 +289,141 @@ class BasePostScriptFontHintValues(BasePostScriptHintValues): def __init__(self, data=None): if data is not None: self.fromDict(data) - else: - for name in self._attributeNames.keys(): - setattr(self, name, self._attributeNames[name]['default']) def __repr__(self): return "" + # route attribute calls to info object + + def _bluesToPairs(self, values): + values.sort() + finalValues = [] + for value in values: + if not finalValues or len(finalValues[-1]) == 2: + finalValues.append([]) + finalValues[-1].append(value) + return finalValues + + def _bluesFromPairs(self, values): + finalValues = [] + for value1, value2 in values: + finalValues.append(value1) + finalValues.append(value2) + finalValues.sort() + return finalValues + + def _get_blueValues(self): + values = self.getParent().info.postscriptBlueValues + if values is None: + values = [] + values = self._bluesToPairs(values) + return values + + def _set_blueValues(self, values): + if values is None: + values = [] + values = self._bluesFromPairs(values) + self.getParent().info.postscriptBlueValues = values + + blueValues = property(_get_blueValues, _set_blueValues) + + def _get_otherBlues(self): + values = self.getParent().info.postscriptOtherBlues + if values is None: + values = [] + values = self._bluesToPairs(values) + return values + + def _set_otherBlues(self, values): + if values is None: + values = [] + values = self._bluesFromPairs(values) + self.getParent().info.postscriptOtherBlues = values + + otherBlues = property(_get_otherBlues, _set_otherBlues) + + def _get_familyBlues(self): + values = self.getParent().info.postscriptFamilyBlues + if values is None: + values = [] + values = self._bluesToPairs(values) + return values + + def _set_familyBlues(self, values): + if values is None: + values = [] + values = self._bluesFromPairs(values) + self.getParent().info.postscriptFamilyBlues = values + + familyBlues = property(_get_familyBlues, _set_familyBlues) + + def _get_familyOtherBlues(self): + values = self.getParent().info.postscriptFamilyOtherBlues + if values is None: + values = [] + values = self._bluesToPairs(values) + return values + + def _set_familyOtherBlues(self, values): + if values is None: + values = [] + values = self._bluesFromPairs(values) + self.getParent().info.postscriptFamilyOtherBlues = values + + familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues) + + def _get_vStems(self): + return self.getParent().info.postscriptStemSnapV + + def _set_vStems(self, value): + if value is None: + value = [] + self.getParent().info.postscriptStemSnapV = list(value) + + vStems = property(_get_vStems, _set_vStems) + + def _get_hStems(self): + return self.getParent().info.postscriptStemSnapH + + def _set_hStems(self, value): + if value is None: + value = [] + self.getParent().info.postscriptStemSnapH = list(value) + + hStems = property(_get_hStems, _set_hStems) + + def _get_blueScale(self): + return self.getParent().info.postscriptBlueScale + + def _set_blueScale(self, value): + self.getParent().info.postscriptBlueScale = value + + blueScale = property(_get_blueScale, _set_blueScale) + + def _get_blueShift(self): + return self.getParent().info.postscriptBlueShift + + def _set_blueShift(self, value): + self.getParent().info.postscriptBlueShift = value + + blueShift = property(_get_blueShift, _set_blueShift) + + def _get_blueFuzz(self): + return self.getParent().info.postscriptBlueFuzz + + def _set_blueFuzz(self, value): + self.getParent().info.postscriptBlueFuzz = value + + blueFuzz = property(_get_blueFuzz, _set_blueFuzz) + + def _get_forceBold(self): + return self.getParent().info.postscriptForceBold + + def _set_forceBold(self, value): + self.getParent().info.postscriptForceBold = value + + forceBold = property(_get_forceBold, _set_forceBold) + def round(self): """Round the values to reasonable values. - blueScale is not rounded, it is a float @@ -334,87 +464,6 @@ class BasePostScriptFontHintValues(BasePostScriptHintValues): for n in v: new.append([int(round(m)) for m in n]) setattr(self, name, new) - - - def _processMathOne(self, copied, other, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - if hasattr(copied, name): - a = getattr(copied, name) - if hasattr(other, name): - b = getattr(other, name) - if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: - # process single values - if a is not None and b is not None: - v = funct(a, b) - elif a is not None and b is None: - v = a - elif b is not None and a is None: - v = b - if v is not None: - setattr(copied, name, v) - elif name in ['hStems', 'vStems']: - if a is not None and b is not None: - if len(a) != len(b): - # can't do math with non matching zones - continue - l = len(a) - v = [funct(a[i], b[i]) for i in range(l)] - if v is not None: - setattr(copied, name, v) - else: - if a is not None and b is not None: - if len(a) != len(b): - # can't do math with non matching zones - continue - l = len(a) - for i in range(l): - if v is None: - v = [] - ai = a[i] - bi = b[i] - l2 = min(len(ai), len(bi)) - v2 = [funct(ai[j], bi[j]) for j in range(l2)] - v.append(v2) - if v is not None: - setattr(copied, name, v) - - def _processMathTwo(self, copied, factor, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - if hasattr(copied, name): - a = getattr(copied, name) - splitFactor = factor - isVertical = self._attributeNames[name]['isVertical'] - if isinstance(factor, tuple): - if isVertical: - splitFactor = factor[1] - else: - splitFactor = factor[0] - if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: - # process single values - if a is not None: - v = funct(a, splitFactor) - if v is not None: - setattr(copied, name, v) - elif name in ['hStems', 'vStems']: - if a is not None: - v = [funct(a[i], splitFactor) for i in range(len(a))] - if v is not None: - setattr(copied, name, v) - else: - if a is not None: - for i in range(len(a)): - if v is None: - v = [] - v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))] - v.append(v2) - if v is not None: - setattr(copied, name, v) @@ -591,7 +640,7 @@ class BaseFont(RBaseObject): def __repr__(self): try: - name = self.info.fullName + name = self.info.postscriptFullName except AttributeError: name = "unnamed_font" return "" %(name) @@ -643,6 +692,9 @@ class BaseFont(RBaseObject): def __getitem__(self, glyphName): return self.getGlyph(glyphName) + def __contains__(self, glyphName): + return self.has_key(glyphName) + def _hasChanged(self): #mark the object as changed self.setChanged(True) @@ -729,7 +781,7 @@ class BaseFont(RBaseObject): try: accent = self[accentName] except IndexError: - errors["glyph '%s' is missing in font %s"%(accentName, self.fullName)] = 1 + errors["glyph '%s' is missing in font %s"%(accentName, self.postscriptFullName)] = 1 continue localAnchors = {} foundAnchor = None @@ -752,7 +804,7 @@ class BaseFont(RBaseObject): try: baseX, baseY = anchors[foundAnchor[1:]] except KeyError: - errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.fullName)]=1 + errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.postscriptFullName)]=1 continue #calculate the accent componet offset values xShift = baseX - accentAnchorX @@ -831,12 +883,12 @@ class BaseFont(RBaseObject): fatalError = True if not errors.has_key('Missing Glyphs'): errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.fullName)) + errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName)) if glyphName not in maxGlyphNames: fatalError = True if not errors.has_key('Missing Glyphs'): errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.fullName)) + errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName)) # if no major problems, proceed. if not fatalError: # remove the glyph since FontLab has a problem with @@ -880,7 +932,6 @@ class BaseFont(RBaseObject): for sub in parts[1:]: item = getattr(item, sub) except (ImportError, AttributeError): - from warnings import warn warn("Can't find glyph name to file name converter function, " "falling back to default scheme (%s)" % funcName, RoboFabWarning) return None @@ -909,7 +960,7 @@ class BaseGlyph(RBaseObject): fontParent = self.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass try: @@ -1775,7 +1826,7 @@ class BaseContour(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass try: idx = `self.index` @@ -1788,7 +1839,6 @@ class BaseContour(RBaseObject): return len(self.segments) def __mul__(self, factor): - from warnings import warn warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.segments = [] @@ -1800,7 +1850,6 @@ class BaseContour(RBaseObject): __rmul__ = __mul__ def __add__(self, other): - from warnings import warn warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.segments = [] @@ -1810,7 +1859,6 @@ class BaseContour(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.segments = [] @@ -2185,7 +2233,7 @@ class BaseSegment(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass try: idx = `self.index` @@ -2194,7 +2242,6 @@ class BaseSegment(RBaseObject): return ""%(font, glyph, contourIndex, idx) def __mul__(self, factor): - from warnings import warn warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.points = [] @@ -2206,7 +2253,6 @@ class BaseSegment(RBaseObject): __rmul__ = __mul__ def __add__(self, other): - from warnings import warn warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.points = [] @@ -2215,7 +2261,6 @@ class BaseSegment(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) n = self.copy() n.points = [] @@ -2324,12 +2369,11 @@ class BasePoint(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%(font, glyph, contourIndex, segmentIndex) def __add__(self, other): - from warnings import warn warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) #Add one point to another n = self.copy() @@ -2337,7 +2381,6 @@ class BasePoint(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) #Subtract one point from another n = self.copy() @@ -2345,7 +2388,6 @@ class BasePoint(RBaseObject): return n def __mul__(self, factor): - from warnings import warn warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2) n = self.copy() @@ -2440,13 +2482,12 @@ class BaseBPoint(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%(font, glyph, contourIndex, segmentIndex, `self.index`) def __add__(self, other): - from warnings import warn warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) #Add one bPoint to another n = self.copy() @@ -2456,7 +2497,6 @@ class BaseBPoint(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) #Subtract one bPoint from another n = self.copy() @@ -2466,7 +2506,6 @@ class BaseBPoint(RBaseObject): return n def __mul__(self, factor): - from warnings import warn warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2) n = self.copy() @@ -2679,7 +2718,7 @@ class BaseComponent(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%(font, glyph, `self.index`) @@ -2708,7 +2747,6 @@ class BaseComponent(RBaseObject): return n def __add__(self, other): - from warnings import warn warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) #Add one Component to another n = self.copy() @@ -2717,7 +2755,6 @@ class BaseComponent(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) #Subtract one Component from another n = self.copy() @@ -2726,7 +2763,6 @@ class BaseComponent(RBaseObject): return n def __mul__(self, factor): - from warnings import warn warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2) n = self.copy() @@ -2798,12 +2834,11 @@ class BaseAnchor(RBaseObject): fontParent = glyphParent.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%(font, glyph, `self.index`) def __add__(self, other): - from warnings import warn warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) #Add one anchor to another n = self.copy() @@ -2811,7 +2846,6 @@ class BaseAnchor(RBaseObject): return n def __sub__(self, other): - from warnings import warn warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) #Substract one anchor from another n = self.copy() @@ -2819,7 +2853,6 @@ class BaseAnchor(RBaseObject): return n def __mul__(self, factor): - from warnings import warn warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2) n = self.copy() @@ -2898,364 +2931,116 @@ class BaseGuide(RBaseObject): self.changed = False # if the object needs to be saved self.selected = False - - class BaseInfo(RBaseObject): - - """Base class for all font.info objects.""" - + + _baseAttributes = ["_object", "changed", "selected", "getParent"] + _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2 + _infoAttributes = ufoLib.fontInfoAttributesVersion2 + # subclasses may define a list of environment + # specific attributes that can be retrieved or set. + _environmentAttributes = [] + # subclasses may define a list of attributes + # that should not follow the standard get/set + # order provided by __setattr__ and __getattr__. + # for these attributes, the environment specific + # set and get methods must handle this value + # without any pre-call validation. + # (yeah. this is because of some FontLab dumbness.) + _environmentOverrides = [] + + def __setattr__(self, attr, value): + # check to see if the attribute has been + # deprecated. if so, warn the caller and + # update the attribute and value. + if attr in self._deprecatedAttributes: + newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) + note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) + warn(note, DeprecationWarning) + attr = newAttr + value = newValue + # setting a known attribute + if attr in self._infoAttributes or attr in self._environmentAttributes: + # lightly test the validity of the value + if value is not None: + isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value) + if not isValidValue: + raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr)) + # use the environment specific info attr set + # method if it is defined. + if hasattr(self, "_environmentSetAttr"): + self._environmentSetAttr(attr, value) + # fallback to super + else: + super(BaseInfo, self).__setattr__(attr, value) + # unknown attribute, test to see if it is a python attr + elif attr in self.__dict__ or attr in self._baseAttributes: + super(BaseInfo, self).__setattr__(attr, value) + # raise an attribute error + else: + raise AttributeError("Unknown attribute %s." % attr) + + # subclasses with environment specific attr setting can + # implement this method. __setattr__ will call it if present. + # def _environmentSetAttr(self, attr, value): + # pass + + def __getattr__(self, attr): + if attr in self._environmentOverrides: + return self._environmentGetAttr(attr) + # check to see if the attribute has been + # deprecated. if so, warn the caller and + # flag the value as needing conversion. + needValueConversionTo1 = False + if attr in self._deprecatedAttributes: + oldAttr = attr + oldValue = attr + newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None) + note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) + warn(note, DeprecationWarning) + attr = newAttr + needValueConversionTo1 = True + # getting a known attribute + if attr in self._infoAttributes or attr in self._environmentAttributes: + # use the environment specific info attr get + # method if it is defined. + if hasattr(self, "_environmentGetAttr"): + value = self._environmentGetAttr(attr) + # fallback to super + else: + try: + value = super(BaseInfo, self).__getattribute__(attr) + except AttributeError: + return None + if needValueConversionTo1: + oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) + return value + # raise an attribute error + else: + raise AttributeError("Unknown attribute %s." % attr) + + # subclasses with environment specific attr retrieval can + # implement this method. __getattr__ will call it if present. + # it should return the requested value. + # def _environmentGetAttr(self, attr): + # pass + +class BaseFeatures(RBaseObject): + def __init__(self): RBaseObject.__init__(self) - - def __repr__(self): - font = self.fullName - return ""%font + self._text = "" + + def _get_text(self): + return self._text + + def _set_text(self, value): + assert isinstance(value, basestring) + self._text = value + + text = property(_get_text, _set_text, doc="raw feature text.") - def _get_familyName(self): - raise NotImplementedError - - def _set_familyName(self, value): - raise NotImplementedError - - familyName = property(_get_familyName, _set_familyName, doc="family name") - - def _get_styleName(self): - raise NotImplementedError - - def _set_styleName(self, value): - raise NotImplementedError - - styleName = property(_get_styleName, _set_styleName, doc="style name") - - def _get_fullName(self): - raise NotImplementedError - - def _set_fullName(self, value): - raise NotImplementedError - - fullName = property(_get_fullName, _set_fullName, doc="full name") - - def _get_fontName(self): - raise NotImplementedError - - def _set_fontName(self, value): - raise NotImplementedError - - fontName = property(_get_fontName, _set_fontName, doc="font name") - - def _get_menuName(self): - raise NotImplementedError - - def _set_menuName(self, value): - raise NotImplementedError - - menuName = property(_get_menuName, _set_menuName, doc="menu name") - - def _get_fondName(self): - raise NotImplementedError - - def _set_fondName(self, value): - raise NotImplementedError - - fondName = property(_get_fondName, _set_fondName, doc="fond name") - - def _get_otFamilyName(self): - raise NotImplementedError - - def _set_otFamilyName(self, value): - raise NotImplementedError - - otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="OpenType family name") - - def _get_otStyleName(self): - raise NotImplementedError - - def _set_otStyleName(self, value): - raise NotImplementedError - - otStyleName = property(_get_otStyleName, _set_otStyleName, doc="OpenType style name") - - def _get_otMacName(self): - raise NotImplementedError - - def _set_otMacName(self, value): - raise NotImplementedError - - otMacName = property(_get_otMacName, _set_otMacName, doc="Mac specific OpenType name") - - def _get_weightValue(self): - raise NotImplementedError - - def _set_weightValue(self, value): - raise NotImplementedError - - weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") - - def _get_weightName(self): - raise NotImplementedError - - def _set_weightName(self, value): - raise NotImplementedError - - weightName = property(_get_weightName, _set_weightName, doc="weight name") - - def _get_widthName(self): - raise NotImplementedError - - def _set_widthName(self, value): - raise NotImplementedError - - widthName = property(_get_widthName, _set_widthName, doc="width name") - - def _get_fontStyle(self): - raise NotImplementedError - - def _set_fontStyle(self, value): - raise NotImplementedError - - fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font style") - - def _get_msCharSet(self): - raise NotImplementedError - - def _set_msCharSet(self, value): - raise NotImplementedError - - msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms charset") - - def _get_note(self): - raise NotImplementedError - - def _set_note(self, value): - raise NotImplementedError - - note = property(_get_note, _set_note, doc="note") - - def _get_fondID(self): - raise NotImplementedError - - def _set_fondID(self, value): - raise NotImplementedError - - fondID = property(_get_fondID, _set_fondID, doc="fond id number") - - def _get_uniqueID(self): - raise NotImplementedError - - def _set_uniqueID(self, value): - raise NotImplementedError - - uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique id number") - - def _get_versionMajor(self): - raise NotImplementedError - - def _set_versionMajor(self, value): - raise NotImplementedError - - versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version major") - - def _get_versionMinor(self): - raise NotImplementedError - - def _set_versionMinor(self, value): - raise NotImplementedError - - versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version minor") - - def _get_year(self): - raise NotImplementedError - - def _set_year(self, value): - raise NotImplementedError - - year = property(_get_year, _set_year, doc="year") - - def _get_copyright(self): - raise NotImplementedError - - def _set_copyright(self, value): - raise NotImplementedError - - copyright = property(_get_copyright, _set_copyright, doc="copyright") - - def _get_notice(self): - raise NotImplementedError - - def _set_notice(self, value): - raise NotImplementedError - - notice = property(_get_notice, _set_notice, doc="notice") - - def _get_trademark(self): - raise NotImplementedError - - def _set_trademark(self, value): - raise NotImplementedError - - trademark = property(_get_trademark, _set_trademark, doc="trademark") - - def _get_license(self): - raise NotImplementedError - - def _set_license(self, value): - raise NotImplementedError - - license = property(_get_license, _set_license, doc="license") - - def _get_licenseURL(self): - raise NotImplementedError - - def _set_licenseURL(self, value): - raise NotImplementedError - - licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license url") - - def _get_createdBy(self): - raise NotImplementedError - - def _set_createdBy(self, value): - raise NotImplementedError - - createdBy = property(_get_createdBy, _set_createdBy, doc="source") - - def _get_designer(self): - raise NotImplementedError - - def _set_designer(self, value): - raise NotImplementedError - - designer = property(_get_designer, _set_designer, doc="designer") - - def _get_designerURL(self): - raise NotImplementedError - - def _set_designerURL(self, value): - raise NotImplementedError - - designerURL = property(_get_designerURL, _set_designerURL, doc="designer url") - - def _get_vendorURL(self): - raise NotImplementedError - - def _set_vendorURL(self, value): - raise NotImplementedError - - vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor url") - - def _get_ttVendor(self): - raise NotImplementedError - - def _set_ttVendor(self, value): - raise NotImplementedError - - ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") - - def _get_ttUniqueID(self): - raise NotImplementedError - - def _set_ttUniqueID(self, value): - raise NotImplementedError - - ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="TrueType unique id number") - - def _get_ttVersion(self): - raise NotImplementedError - - def _set_ttVersion(self, value): - raise NotImplementedError - - ttVersion = property(_get_ttVersion, _set_ttVersion, doc="TrueType version") - - def _get_unitsPerEm(self): - raise NotImplementedError - - def _set_unitsPerEm(self): - raise NotImplementedError - - unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") - - def _get_ascender(self): - raise NotImplementedError - - def _set_ascender(self, value): - raise NotImplementedError - - ascender = property(_get_ascender, _set_ascender, doc="ascender value") - - def _get_descender(self): - raise NotImplementedError - - def _set_descender(self, value): - raise NotImplementedError - - descender = property(_get_descender, _set_descender, doc="descender value") - - def _get_capHeight(self): - raise NotImplementedError - - def _set_capHeight(self, value): - raise NotImplementedError - - capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") - - def _get_xHeight(self): - raise NotImplementedError - - def _set_xHeight(self, value): - raise NotImplementedError - - xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") - - def _get_defaultWidth(self): - raise NotImplementedError - - def _set_defaultWidth(self, value): - raise NotImplementedError - - defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") - - def _get_italicAngle(self): - raise NotImplementedError - - def _set_italicAngle(self, value): - raise NotImplementedError - - italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") - - def _get_slantAngle(self): - raise NotImplementedError - - def _set_slantAngle(self, value): - raise NotImplementedError - - slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") - - def autoNaming(self, familyName=None, styleName=None): - """Automatically set the font naming info based on family and style names.""" - if familyName is None: - if not self.familyName: - raise RoboFabError, "Family name and style name must be complete" - else: - self.familyName = familyName - if styleName is None: - if not self.styleName: - raise RoboFabError, "Family name and style name must be complete" - else: - self.styleName = styleName - family = self.familyName - style = self.styleName - self.fullName = ' '.join((family, style)) - self.fontName = '-'.join((family, style)).replace(' ', '') - self.fondName = family - self.menuName = ' '.join((family, style)) - self.otFamilyName = family - self.otStyleName = style - self.otMacName = ' '.join((family, style)) - #self._hasChanged() - class BaseGroups(dict): """Base class for all RFont.groups objects""" @@ -3268,7 +3053,7 @@ class BaseGroups(dict): fontParent = self.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%font @@ -3312,7 +3097,7 @@ class BaseLib(dict): if parentObject is not None: #do we have a font? try: - parent = parentObject.info.fullName + parent = parentObject.info.postscriptFullName except AttributeError: #or do we have a glyph? try: @@ -3357,7 +3142,7 @@ class BaseKerning(RBaseObject): fontParent = self.getParent() if fontParent is not None: try: - font = fontParent.info.fullName + font = fontParent.info.postscriptFullName except AttributeError: pass return ""%font @@ -3705,4 +3490,3 @@ class BaseKerning(RBaseObject): raise ZeroDivisionError return self.__mul__(1.0/factor) - \ No newline at end of file diff --git a/Lib/robofab/objects/objectsFL.py b/Lib/robofab/objects/objectsFL.py index 9c49ed071..9e3b4585e 100755 --- a/Lib/robofab/objects/objectsFL.py +++ b/Lib/robofab/objects/objectsFL.py @@ -5,7 +5,7 @@ from FL import * from robofab.tools.toolsFL import GlyphIndexTable,\ AllFonts, NewGlyph from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\ + BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ roundPt, addPt, _box,\ MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\ @@ -16,6 +16,16 @@ from robofab import RoboFabError import os from robofab.plistlib import Data, Dict, readPlist, writePlist from StringIO import StringIO +from robofab import ufoLib +from warnings import warn +import datetime +from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab + + +try: + set +except NameError: + from sets import Set as set # local encoding if os.name in ["mac", "posix"]: @@ -23,6 +33,8 @@ if os.name in ["mac", "posix"]: else: LOCAL_ENCODING = "latin-1" +_IN_UFO_EXPORT = False + # a list of attributes that are to be copied when copying a glyph. # this is used by glyph.copy and font.insertGlyph GLYPH_COPY_ATTRS = [ @@ -105,9 +117,6 @@ _flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PF stem_snap_h stem_snap_v_num(integer) stem_snap_v - - - """ class PostScriptFontHintValues(BasePostScriptFontHintValues): @@ -121,141 +130,7 @@ class PostScriptFontHintValues(BasePostScriptFontHintValues): def copy(self): from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues return _PostScriptFontHintValues(data=self.asDict()) - - def _getBlueFuzz(self): - return self._object.blue_fuzz[self._masterIndex] - def _setBlueFuzz(self, value): - self._object.blue_fuzz[self._masterIndex] = value - def _getBlueScale(self): - return self._object.blue_scale[self._masterIndex] - def _setBlueScale(self, value): - self._object.blue_scale[self._masterIndex] = float(value) - - def _getBlueShift(self): - return self._object.blue_shift[self._masterIndex] - def _setBlueShift(self, value): - self._object.blue_shift[self._masterIndex] = value - - def _getForceBold(self): - return self._object.force_bold[self._masterIndex] == 1 - - def _setForceBold(self, value): - if value: - value = 1 - else: - value = 0 - self._object.force_bold[self._masterIndex] = value - - # Note: these attributes are wrapppers for lists, - # but regular list operatons won't have any effect. - # you really have to _get_ and _set_ a list. - - def _asPairs(self, l): - """Split a list of numbers into a list of pairs""" - if not len(l)%2 == 0: - l = l[:-1] - n = [[l[i], l[i+1]] for i in range(0, len(l), 2)] - n.sort() - return n - - def _flattenPairs(self, l): - """The reverse of _asPairs""" - n = [] - l.sort() - for i in l: - assert len(i) == 2, "Each entry must consist of two numbers" - n.append(i[0]) - n.append(i[1]) - return n - - def _checkForFontLabSanity(self, attribute, values): - """Function to handle problems with FontLab not allowing the max number of - alignment zones to be set to the max number. - Input: the name of the zones and the values to be set - Output: a warning when there are too many values to be set - and the max values which FontLab will allow. - """ - warn = False - if attribute in ['vStems', 'hStems']: - # the number of items to drop from the list if the list is too long, - # stems are single values, but the zones are pairs. - skip = 1 - total = min(self._attributeNames[attribute]['max'], len(values)) - if total == self._attributeNames[attribute]['max']: - total = self._attributeNames[attribute]['max'] - skip - warn = True - else: - skip = 2 - values = self._flattenPairs(values) - total = min(self._attributeNames[attribute]['max']*2, len(values)) - if total == self._attributeNames[attribute]['max']*2: - total = self._attributeNames[attribute]['max']*2 - skip - warn = True - if warn: - print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s."%(self._attributeNames[attribute]['max']-1, attribute, `values[total:]`) - return total, values[:total] - - - def _getBlueValues(self): - return self._asPairs(self._object.blue_values[self._masterIndex]) - def _setBlueValues(self, values): - total, values = self._checkForFontLabSanity('blueValues', values) - self._object.blue_values_num = total - for i in range(self._object.blue_values_num): - self._object.blue_values[self._masterIndex][i] = values[i] - - def _getOtherBlues(self): - return self._asPairs(self._object.other_blues[self._masterIndex]) - def _setOtherBlues(self, values): - total, values = self._checkForFontLabSanity('otherBlues', values) - self._object.other_blues_num = total - for i in range(self._object.other_blues_num): - self._object.other_blues[self._masterIndex][i] = values[i] - - def _getFamilyBlues(self): - return self._asPairs(self._object.family_blues[self._masterIndex]) - def _setFamilyBlues(self, values): - total, values = self._checkForFontLabSanity('familyBlues', values) - self._object.family_blues_num = total - for i in range(self._object.family_blues_num): - self._object.family_blues[self._masterIndex][i] = values[i] - - def _getFamilyOtherBlues(self): - return self._asPairs(self._object.family_other_blues[self._masterIndex]) - def _setFamilyOtherBlues(self, values): - total, values = self._checkForFontLabSanity('familyOtherBlues', values) - self._object.family_other_blues_num = total - for i in range(self._object.family_other_blues_num): - self._object.family_other_blues[self._masterIndex][i] = values[i] - - def _getVStems(self): - return list(self._object.stem_snap_v[self._masterIndex]) - def _setVStems(self, values): - total, values = self._checkForFontLabSanity('vStems', values) - self._object.stem_snap_v_num = total - for i in range(self._object.stem_snap_v_num): - self._object.stem_snap_v[self._masterIndex][i] = values[i] - - def _getHStems(self): - return list(self._object.stem_snap_h[self._masterIndex]) - def _setHStems(self, values): - total, values = self._checkForFontLabSanity('hStems', values) - self._object.stem_snap_h_num = total - for i in range(self._object.stem_snap_h_num): - self._object.stem_snap_h[self._masterIndex][i] = values[i] - - blueFuzz = property(_getBlueFuzz, _setBlueFuzz, doc="postscript hints: bluefuzz value") - blueScale = property(_getBlueScale, _setBlueScale, doc="postscript hints: bluescale value") - blueShift = property(_getBlueShift, _setBlueShift, doc="postscript hints: blueshift value") - forceBold = property(_getForceBold, _setForceBold, doc="postscript hints: force bold value") - blueValues = property(_getBlueValues, _setBlueValues, doc="postscript hints: blue values") - otherBlues = property(_getOtherBlues, _setOtherBlues, doc="postscript hints: other blue values") - familyBlues = property(_getFamilyBlues, _setFamilyBlues, doc="postscript hints: family blue values") - familyOtherBlues = property(_getFamilyOtherBlues, _setFamilyOtherBlues, doc="postscript hints: family other blue values") - vStems = property(_getVStems, _setVStems, doc="postscript hints: vertical stem values") - hStems = property(_getHStems, _setHStems, doc="postscript hints: horizontal stem values") - class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): """ Wrapper for glyph-level PostScript hinting information for FontLab. @@ -502,8 +377,10 @@ def NewFont(familyName=None, styleName=None): f = Font() fl.Add(f) rf = RFont(f) - rf.info.familyName = familyName - rf.info.styleName = styleName + if familyName is not None: + rf.info.familyName = familyName + if styleName is not None: + rf.info.styleName = styleName return rf def AllFonts(): @@ -559,6 +436,8 @@ class RFont(BaseFont): self._object = font self._lib = {} self._supportHints = True + self.psHints = PostScriptFontHintValues(self) + self.psHints.setParent(self) def keys(self): keys = {} @@ -600,16 +479,23 @@ class RFont(BaseFont): # return -1 - def _get_psHints(self): - return PostScriptFontHintValues(self) - - psHints = property(_get_psHints, doc="font level postscript hint data") +# def _get_psHints(self): +# h = PostScriptFontHintValues(self) +# h.setParent(self) +# return h +# +# psHints = property(_get_psHints, doc="font level postscript hint data") def _get_info(self): return RInfo(self._object) info = property(_get_info, doc="font info object") - + + def _get_features(self): + return RFeatures(self._object) + + features = property(_get_features, doc="features object") + def _get_kerning(self): kerning = {} f = self._object @@ -1025,92 +911,229 @@ class RFont(BaseFont): # the font must be the current font. so, make it so. fl.ifont = self.fontIndex fl.GenerateFont(flOutputType, finalPath) - - def _writeOpenTypeFeaturesToLib(self, fontLib): - flFont = self.naked() - if flFont.ot_classes: - fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings( - flFont.ot_classes) - if flFont.features: - features = {} - order = [] - for feature in flFont.features: - order.append(feature.tag) - features[feature.tag] = _normalizeLineEndings(feature.value) - fontLib["org.robofab.opentype.features"] = features - fontLib["org.robofab.opentype.featureorder"] = order - - def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, doHints=False): - """write a font to .ufo""" - from robofab.ufoLib import makeUFOPath, UFOWriter - from robofab.interface.all.dialogs import ProgressBar + + def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, + doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): + from robofab.interface.all.dialogs import ProgressBar, Message + # special glyph name to file name conversion if glyphNameToFileNameFunc is None: glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() if glyphNameToFileNameFunc is None: from robofab.tools.glyphNameSchemes import glyphNameToShortFileName glyphNameToFileNameFunc = glyphNameToShortFileName + # get a valid path if not path: if self.path is None: - # XXX this should really raise an exception instead - from robofab.interface.all.dialogs import Message Message("Please save this font first before exporting to UFO...") return else: - path = makeUFOPath(self.path) - nonGlyphCount = 4 + path = ufoLib.makeUFOPath(self.path) + # get the glyphs to export + if glyphs is None: + glyphs = self.keys() + # if the file exists, check the format version. + # if the format version being written is different + # from the format version of the existing UFO + # and only some files are set to be written + # raise an error. + if os.path.exists(path): + if os.path.exists(os.path.join(path, "metainfo.plist")): + reader = ufoLib.UFOReader(path) + existingFormatVersion = reader.formatVersion + if formatVersion != existingFormatVersion: + if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: + Message("When overwriting an existing UFO with a different format version all files must be written.") + return + # the lib must be written if format version is 1 + if not doLib and formatVersion == 1: + Message("The lib must be written when exporting format version 1.") + return + # set up the progress bar + nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) bar = None if doProgress: - bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self.glyphs)) + bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) + # try writing try: - u = UFOWriter(path) - u.writeInfo(self.info) - if bar: - bar.tick() - u.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - u.writeGroups(self.groups) - if bar: - bar.tick() - count = nonGlyphCount - glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) - glyphOrder = [] - for nakedGlyph in self.naked().glyphs: - glyph = RGlyph(nakedGlyph) - glyphOrder.append(glyph.name) - if doHints: - hintStuff = _glyphHintsToDict(glyph.naked()) - if hintStuff: - glyph.lib[postScriptHintDataLibKey] = hintStuff - glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) - # remove the hint dict from the lib - if doHints and glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - assert None not in glyphOrder, glyphOrder - glyphSet.writeContents() - # We make a shallow copy if lib, since we add some stuff for export - # that doesn't need to be retained in memory. + writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) + ## We make a shallow copy if lib, since we add some stuff for export + ## that doesn't need to be retained in memory. fontLib = dict(self.lib) - # Always export the postscript font hint values - psh = PostScriptFontHintValues(self) - d = psh.asDict() - fontLib[postScriptHintDataLibKey] = d - # Export the glyph order - fontLib["org.robofab.glyphOrder"] = glyphOrder - self._writeOpenTypeFeaturesToLib(fontLib) - u.writeLib(fontLib) - if bar: - bar.tick() + # write the font info + if doInfo: + global _IN_UFO_EXPORT + _IN_UFO_EXPORT = True + writer.writeInfo(self.info) + _IN_UFO_EXPORT = False + if bar: + bar.tick() + # write the kerning + if doKerning: + writer.writeKerning(self.kerning.asDict()) + if bar: + bar.tick() + # write the groups + if doGroups: + writer.writeGroups(self.groups) + if bar: + bar.tick() + # write the features + if doFeatures: + if formatVersion == 2: + writer.writeFeatures(self.features.text) + else: + self._writeOpenTypeFeaturesToLib(fontLib) + if bar: + bar.tick() + # write the lib + if doLib: + ## Always export the postscript font hint values to the lib in format version 1 + if formatVersion == 1: + d = self.psHints.asDict() + fontLib[postScriptHintDataLibKey] = d + ## Export the glyph order to the lib + glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] + fontLib["org.robofab.glyphOrder"] = glyphOrder + ## export the features + if doFeatures and formatVersion == 1: + self._writeOpenTypeFeaturesToLib(fontLib) + if bar: + bar.tick() + writer.writeLib(fontLib) + if bar: + bar.tick() + # write the glyphs + if glyphs: + glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) + count = nonGlyphCount + for nakedGlyph in self.naked().glyphs: + if nakedGlyph.name not in glyphs: + continue + glyph = RGlyph(nakedGlyph) + if doHints: + hintStuff = _glyphHintsToDict(glyph.naked()) + if hintStuff: + glyph.lib[postScriptHintDataLibKey] = hintStuff + glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) + # remove the hint dict from the lib + if doHints and glyph.lib.has_key(postScriptHintDataLibKey): + del glyph.lib[postScriptHintDataLibKey] + if bar and not count % 10: + bar.tick(count) + count = count + 1 + glyphSet.writeContents() + # only blindly stop if the user says to except KeyboardInterrupt: if bar: bar.close() bar = None + # kill the bar if bar: bar.close() - + + def _writeOpenTypeFeaturesToLib(self, fontLib): + # this should only be used for UFO format version 1 + flFont = self.naked() + fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(flFont.ot_classes).rstrip() + "\n" + if flFont.features: + features = {} + order = [] + for feature in flFont.features: + order.append(feature.tag) + features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" + fontLib["org.robofab.opentype.features"] = features + fontLib["org.robofab.opentype.featureorder"] = order + + def readUFO(self, path, doProgress=False, + doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): + """read a .ufo into the font""" + from robofab.pens.flPen import FLPointPen + from robofab.interface.all.dialogs import ProgressBar + # start up the reader + reader = ufoLib.UFOReader(path) + glyphSet = reader.getGlyphSet() + # get a list of glyphs that should be imported + if glyphs is None: + glyphs = glyphSet.keys() + # set up the progress bar + nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) + bar = None + if doProgress: + bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) + # start reading + try: + fontLib = reader.readLib() + # info + if doInfo: + reader.readInfo(self.info) + if bar: + bar.tick() + # glyphs + count = 1 + glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) + for glyphName in glyphOrder: + if glyphName not in glyphs: + continue + glyph = self.newGlyph(glyphName, clear=True) + pen = FLPointPen(glyph.naked()) + glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) + if doHints: + hintData = glyph.lib.get(postScriptHintDataLibKey) + if hintData: + _dictHintsToGlyph(glyph.naked(), hintData) + # now that the hints have been extracted from the glyph + # there is no reason to keep the location in the lib. + if glyph.lib.has_key(postScriptHintDataLibKey): + del glyph.lib[postScriptHintDataLibKey] + glyph.update() + if bar and not count % 10: + bar.tick(count) + count = count + 1 + # features + if doFeatures: + if reader.formatVersion == 1: + self._readOpenTypeFeaturesFromLib(fontLib) + else: + featureText = reader.readFeatures() + self.features.text = featureText + if bar: + bar.tick() + else: + # remove features stored in the lib + self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) + # kerning + if doKerning: + self.kerning.clear() + self.kerning.update(reader.readKerning()) + if bar: + bar.tick() + # groups + if doGroups: + self.groups.clear() + self.groups.update(reader.readGroups()) + if bar: + bar.tick() + # hints in format version 1 + if doHints and reader.formatVersion == 1: + self.psHints._loadFromLib(fontLib) + else: + # remove hint data stored in the lib + if fontLib.has_key(postScriptHintDataLibKey): + del fontLib[postScriptHintDataLibKey] + # lib + if doLib: + self.lib.clear() + self.lib.update(fontLib) + if bar: + bar.tick() + # only blindly stop if the user says to + except KeyboardInterrupt: + bar.close() + bar = None + # kill the bar + if bar: + bar.close() + def _getGlyphOrderFromLib(self, fontLib, glyphSet): glyphOrder = fontLib.get("org.robofab.glyphOrder") if glyphOrder is not None: @@ -1129,16 +1152,17 @@ class RFont(BaseFont): glyphNames.append(glyphName) else: glyphNames = glyphSet.keys() - # Sort according to unicode would be best, but is really - # expensive... glyphNames.sort() return glyphNames - def _readOpenTypeFeaturesFromLib(self, fontLib): + def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): + # setFeatures may be False. in this case, this method + # should only clear the data from the lib. classes = fontLib.get("org.robofab.opentype.classes") if classes is not None: del fontLib["org.robofab.opentype.classes"] - self.naked().ot_classes = classes + if setFeatures: + self.naked().ot_classes = classes features = fontLib.get("org.robofab.opentype.features") if features is not None: order = fontLib.get("org.robofab.opentype.featureorder") @@ -1155,62 +1179,11 @@ class RFont(BaseFont): oneFeature = features.get(tag) if oneFeature is not None: orderedFeatures.append((tag, oneFeature)) - self.naked().features.clean() - for tag, src in orderedFeatures: - self.naked().features.append(Feature(tag, src)) - - def readUFO(self, path, doProgress=False, doHints=True): - """read a .ufo into the font""" - from robofab.ufoLib import UFOReader - from robofab.pens.flPen import FLPointPen - from robofab.interface.all.dialogs import ProgressBar - nonGlyphCount = 4 - bar = None - u = UFOReader(path) - glyphSet = u.getGlyphSet() - fontLib = u.readLib() - glyphNames = self._getGlyphOrderFromLib(fontLib, glyphSet) - if doProgress: - bar = ProgressBar('Importing UFO', nonGlyphCount+len(glyphNames)) - try: - u.readInfo(self.info) - if bar: - bar.tick() - self._readOpenTypeFeaturesFromLib(fontLib) - self.lib.clear() - self.lib = fontLib - if bar: - bar.tick() - count = 2 - for glyphName in glyphNames: - glyph = self.newGlyph(glyphName, clear=True) - pen = FLPointPen(glyph.naked()) - glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) - if doHints: - hintData = glyph.lib.get(postScriptHintDataLibKey) - if hintData: - _dictHintsToGlyph(glyph.naked(), hintData) - # now that the hints have been extracted from the glyph - # there is no reason to keep the location in the lib. - if glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - glyph.update() - if bar and not count % 10: - bar.tick(count) - count = count + 1 - # import postscript font hint data - self.psHints._loadFromLib(fontLib) - self.kerning.clear() - self.kerning.update(u.readKerning()) - if bar: - bar.tick() - self.groups.clear() - self.groups = u.readGroups() - except KeyboardInterrupt: - bar.close() - bar = None - if bar: - bar.close() + if setFeatures: + self.naked().features.clean() + for tag, src in orderedFeatures: + self.naked().features.append(Feature(tag, src)) + class RGlyph(BaseGlyph): @@ -2291,7 +2264,7 @@ class RGuide(BaseGuide): if parentObject is not None: # do we have a font? try: - parent = parentObject.info.fullName + parent = parentObject.info.postscriptFullName except AttributeError: # or do we have a glyph? try: @@ -2608,398 +2581,481 @@ class RLib(BaseLib): self._stashLib() return i - + +def _infoMapDict(**kwargs): + default = dict( + nakedAttribute=None, + type=None, + requiresSetNum=False, + masterSpecific=False, + libLocation=None, + specialGetSet=False + ) + default.update(kwargs) + return default + +def _flipDict(d): + f = {} + for k, v in d.items(): + f[v] = k + return f + +_styleMapStyleName_fromFL = { + 64 : "regular", + 1 : "italic", + 32 : "bold", + 33 : "bold italic" +} +_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) + +_postscriptWindowsCharacterSet_fromFL = { + 0 : 1, + 1 : 2, + 2 : 3, + 77 : 4, + 128 : 5, + 129 : 6, + 130 : 7, + 134 : 8, + 136 : 9, + 161 : 10, + 162 : 11, + 163 : 12, + 177 : 13, + 178 : 14, + 186 : 15, + 200 : 16, + 204 : 17, + 222 : 18, + 238 : 19, + 255 : 20, +} +_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) + +_openTypeOS2Type_toFL = { + 1 : 0x0002, + 2 : 0x0004, + 3 : 0x0008, + 8 : 0x0100, + 9 : 0x0200, +} +_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) + +_openTypeOS2WidthClass_fromFL = { + "Ultra-condensed" : 1, + "Extra-condensed" : 2, + "Condensed" : 3, + "Semi-condensed" : 4, + "Medium (normal)" : 5, + "Semi-expanded" : 6, + "Expanded" : 7, + "Extra-expanded" : 8, + "Ultra-expanded" : 9, +} +_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) + +_postscriptHintAttributes = set(( + "postscriptBlueValues", + "postscriptOtherBlues", + "postscriptFamilyBlues", + "postscriptFamilyOtherBlues", + "postscriptStemSnapH", + "postscriptStemSnapV", +)) + + class RInfo(BaseInfo): - + """RoboFab wrapper for FL Font Info""" - + _title = "FLInfo" - + + _ufoToFLAttrMapping = { + "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"), + "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"), + "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"), + "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), + "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"), + "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"), + "year" : _infoMapDict(valueType=int, nakedAttribute="year"), + "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"), + "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"), + "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"), + "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), + "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), + "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), + "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), + "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), + "note" : _infoMapDict(valueType=str, nakedAttribute="note"), + "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values + "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), + "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. + "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), + "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), + "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), + "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"), + "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"), + "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"), + "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), + "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"), + "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"), + "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"), + "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), + "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"), + "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), + "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), + "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), + "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None), + "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), + "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), + "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"), + "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), + "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 + "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"), + "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), + "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), + "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), + "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"), + "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), + "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), + "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), + "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), + "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), + "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), + "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), + "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), + "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), + "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), + "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), + "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), + "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), + "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), + "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), + "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), + "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), + "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), + "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"), + "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"), + "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), + "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"), + "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), + "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"), + "postscriptIsFixedPitch" : _infoMapDict(valueType=bool, nakedAttribute="is_fixed_pitch"), + "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), + "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), + "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), + "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), + "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), + "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), + "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), + "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), + "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), + "postscriptForceBold" : _infoMapDict(valueType=bool, nakedAttribute="force_bold", masterSpecific=True), + "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), + "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None), + "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"), + "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"), + "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), + "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"), + "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"), + } + _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. + def __init__(self, font): - BaseInfo.__init__(self) + super(RInfo, self).__init__() self._object = font - - def _get_familyName(self): - return self._object.family_name - def _set_familyName(self, value): - self._object.family_name = value + def _environmentSetAttr(self, attr, value): + # special fontlab workarounds + if attr == "width": + warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) + attr = "openTypeOS2WidthClass" + if attr == "openTypeOS2WidthClass": + if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: + print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value + self._object.width = value + else: + self._object.width = _openTypeOS2WidthClass_toFL[value] + return + # get the attribute data + data = self._ufoToFLAttrMapping[attr] + flAttr = data["nakedAttribute"] + valueType = data["valueType"] + masterSpecific = data["masterSpecific"] + requiresSetNum = data["requiresSetNum"] + specialGetSet = data["specialGetSet"] + # warn about setting attributes not supported by FL + if flAttr is None: + print "The attribute %s is not supported by FontLab. This data will not be set." % attr + return + # make sure that the value is the proper type for FL + if valueType == "intList": + value = [int(i) for i in value] + elif valueType == str: + if value is None: + value = "" + value = value.encode(LOCAL_ENCODING) + elif valueType == int and not isinstance(value, int): + value = int(round(value)) + elif not isinstance(value, valueType): + value = valueType(value) + # handle postscript hint bug in FL + if attr in _postscriptHintAttributes: + value = self._handlePSHintBug(attr, value) + # handle special cases + if specialGetSet: + attr = "_set_%s" % attr + method = getattr(self, attr) + return method(value) + # set the value + obj = self._object + if len(flAttr.split(".")) > 1: + flAttrList = flAttr.split(".") + for i in flAttrList[:-1]: + obj = getattr(obj, i) + flAttr = flAttrList[-1] + ## set the foo_num attribute if necessary + if requiresSetNum: + numAttr = flAttr + "_num" + setattr(obj, numAttr, len(value)) + ## set master 0 if the data is master specific + if masterSpecific: + subObj = getattr(obj, flAttr) + if valueType == "intList": + for index, v in enumerate(value): + subObj[0][index] = v + else: + subObj[0] = value + ## otherwise use a regular set + else: + setattr(obj, flAttr, value) - familyName = property(_get_familyName, _set_familyName, doc="family_name") - - def _get_styleName(self): - return self._object.style_name + def _environmentGetAttr(self, attr): + # special fontlab workarounds + if attr == "width": + warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) + attr = "openTypeOS2WidthClass" + if attr == "openTypeOS2WidthClass": + value = self._object.width + if value not in _openTypeOS2WidthClass_fromFL: + print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value + return + else: + return _openTypeOS2WidthClass_fromFL[value] + # get the attribute data + data = self._ufoToFLAttrMapping[attr] + flAttr = data["nakedAttribute"] + valueType = data["valueType"] + masterSpecific = data["masterSpecific"] + specialGetSet = data["specialGetSet"] + # warn about setting attributes not supported by FL + if flAttr is None: + if not _IN_UFO_EXPORT: + print "The attribute %s is not supported by FontLab." % attr + return + # handle special cases + if specialGetSet: + attr = "_get_%s" % attr + method = getattr(self, attr) + return method() + # get the value + if len(flAttr.split(".")) > 1: + flAttrList = flAttr.split(".") + obj = self._object + for i in flAttrList: + obj = getattr(obj, i) + value = obj + else: + value = getattr(self._object, flAttr) + # grab the first master value if necessary + if masterSpecific: + value = value[0] + # convert if necessary + if valueType == "intList": + value = [int(i) for i in value] + elif valueType == str: + if value is None: + pass + else: + value = unicode(value, LOCAL_ENCODING) + elif not isinstance(value, valueType): + value = valueType(value) + return value - def _set_styleName(self, value): - self._object.style_name = value + # ------------------------------ + # individual attribute overrides + # ------------------------------ - styleName = property(_get_styleName, _set_styleName, doc="style_name") - - def _get_fullName(self): - return self._object.full_name + # styleMapStyleName - def _set_fullName(self, value): - self._object.full_name = value + def _get_styleMapStyleName(self): + return _styleMapStyleName_fromFL[self._object.font_style] - fullName = property(_get_fullName, _set_fullName, doc="full_name") - - def _get_fontName(self): - return self._object.font_name - - def _set_fontName(self, value): - self._object.font_name = value - - fontName = property(_get_fontName, _set_fontName, doc="font_name") - - def _get_menuName(self): - return self._object.menu_name - - def _set_menuName(self, value): - self._object.menu_name = value - - menuName = property(_get_menuName, _set_menuName, doc="menu_name") - - def _get_fondName(self): - return self._object.apple_name - - def _set_fondName(self, value): - self._object.apple_name = value - - fondName = property(_get_fondName, _set_fondName, doc="apple_name") - - def _get_otFamilyName(self): - return self._object.pref_family_name - - def _set_otFamilyName(self, value): - self._object.pref_family_name = value - - otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") - - def _get_otStyleName(self): - return self._object.pref_style_name - - def _set_otStyleName(self, value): - self._object.pref_style_name = value - - otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") - - def _get_otMacName(self): - return self._object.mac_compatible - - def _set_otMacName(self, value): - self._object.mac_compatible = value - - otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") - - def _get_weightValue(self): - return self._object.weight_code - - def _set_weightValue(self, value): - value = int(round(value)) # FL can't take float - 28/8/07 / evb - self._object.weight_code = value - - weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") - - def _get_weightName(self): - return self._object.weight - - def _set_weightName(self, value): - self._object.weight = value - - weightName = property(_get_weightName, _set_weightName, doc="weight name") - - def _get_widthName(self): - return self._object.width - - def _set_widthName(self, value): - self._object.width = value - - widthName = property(_get_widthName, _set_widthName, doc="width name") - - def _get_fontStyle(self): - return self._object.font_style - - def _set_fontStyle(self, value): + def _set_styleMapStyleName(self, value): + value = _styleMapStyleName_toFL[value] self._object.font_style = value - fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") - - def _get_msCharSet(self): - return self._object.ms_charset +# # openTypeHeadCreated +# +# # fontlab epoch: 1969-12-31 19:00:00 +# +# def _get_openTypeHeadCreated(self): +# value = self._object.ttinfo.head_creation +# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) +# delta = datetime.timedelta(seconds=value[0]) +# t = epoch - delta +# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) +# return string +# +# def _set_openTypeHeadCreated(self, value): +# date, time = value.split(" ") +# year, month, day = [int(i) for i in date.split("-")] +# hour, minute, second = [int(i) for i in time.split(":")] +# value = datetime.datetime(year, month, day, hour, minute, second) +# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) +# delta = epoch - value +# seconds = delta.seconds +# self._object.ttinfo.head_creation[0] = seconds - def _set_msCharSet(self, value): + # openTypeOS2WeightClass + + def _get_openTypeOS2WeightClass(self): + value = self._object.weight_code + if value == -1: + value = None + return value + + def _set_openTypeOS2WeightClass(self, value): + self._object.weight_code = value + + # openTypeOS2WinDescent + + def _get_openTypeOS2WinDescent(self): + return -self._object.ttinfo.os2_us_win_descent + + def _set_openTypeOS2WinDescent(self, value): + if value > 0: + raise ValueError("FontLab can only handle negative values for openTypeOS2WinDescent.") + self._object.ttinfo.os2_us_win_descent = abs(value) + + # openTypeOS2Type + + def _get_openTypeOS2Type(self): + value = self._object.ttinfo.os2_fs_type + intList = [] + for bit, bitNumber in _openTypeOS2Type_fromFL.items(): + if value & bit: + intList.append(bitNumber) + return intList + + def _set_openTypeOS2Type(self, values): + value = 0 + for bitNumber in values: + bit = _openTypeOS2Type_toFL[bitNumber] + value = value | bit + self._object.ttinfo.os2_fs_type = value + + # openTypeOS2Panose + + def _get_openTypeOS2Panose(self): + return [i for i in self._object.panose] + + def _set_openTypeOS2Panose(self, values): + for index, value in enumerate(values): + self._object.panose[index] = value + + # openTypeOS2FamilyClass + + def _get_openTypeOS2FamilyClass(self): + value = self._object.ttinfo.os2_s_family_class + for classID in range(15): + classValue = classID * 256 + if classValue > value: + classID -= 1 + classValue = classID * 256 + break + subclassID = value - classValue + return [classID, subclassID] + + def _set_openTypeOS2FamilyClass(self, values): + classID, subclassID = values + classID = classID * 256 + value = classID + subclassID + self._object.ttinfo.os2_s_family_class = value + + # postscriptWindowsCharacterSet + + def _get_postscriptWindowsCharacterSet(self): + value = self._object.ms_charset + value = _postscriptWindowsCharacterSet_fromFL[value] + return value + + def _set_postscriptWindowsCharacterSet(self, value): + value = _postscriptWindowsCharacterSet_toFL[value] self._object.ms_charset = value - msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") - - def _get_fondID(self): - return self._object.fond_id + # ----------------- + # FL bug workaround + # ----------------- - def _set_fondID(self, value): - self._object.fond_id = value + def _handlePSHintBug(self, attribute, values): + """Function to handle problems with FontLab not allowing the max number of + alignment zones to be set to the max number. + Input: the name of the zones and the values to be set + Output: a warning when there are too many values to be set + and the max values which FontLab will allow. + """ + originalValues = values + truncatedLength = None + if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): + if len(values) > 10: + values = values[:10] + truncatedLength = 10 + elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): + if len(values) > 12: + values = values[:12] + truncatedLength = 12 + elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): + if len(values) > 8: + values = values[:8] + truncatedLength = 8 + if truncatedLength is not None: + print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) + return values - fondID = property(_get_fondID, _set_fondID, doc="fond_id") - - def _get_uniqueID(self): - return self._object.unique_id - def _set_uniqueID(self, value): - self._object.unique_id = value +class RFeatures(BaseFeatures): - uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") - - def _get_versionMajor(self): - return self._object.version_major + _title = "FLFeatures" - def _set_versionMajor(self, value): - self._object.version_major = value + def __init__(self, font): + super(RFeatures, self).__init__() + self._object = font - versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") - - def _get_versionMinor(self): - return self._object.version_minor + def _get_text(self): + naked = self._object + features = [] + if naked.ot_classes: + features.append(_normalizeLineEndings(naked.ot_classes)) + for feature in naked.features: + features.append(_normalizeLineEndings(feature.value)) + return "".join(features) - def _set_versionMinor(self, value): - self._object.version_minor = value + def _set_text(self, value): + classes, features = splitFeaturesForFontLab(value) + naked = self._object + naked.ot_classes = classes + naked.features.clean() + for featureName, featureText in features: + f = Feature(featureName, featureText) + naked.features.append(f) - versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") - - def _get_year(self): - return self._object.year + text = property(_get_text, _set_text, doc="raw feature text.") - def _set_year(self, value): - self._object.year = value - - year = property(_get_year, _set_year, doc="year") - - def _get_note(self): - s = self._object.note - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_note(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.note = value - - note = property(_get_note, _set_note, doc="note") - - def _get_copyright(self): - s = self._object.copyright - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_copyright(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.copyright = value - - copyright = property(_get_copyright, _set_copyright, doc="copyright") - - def _get_notice(self): - s = self._object.notice - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_notice(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.notice = value - - notice = property(_get_notice, _set_notice, doc="notice") - - def _get_trademark(self): - s = self._object.trademark - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_trademark(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.trademark = value - - trademark = property(_get_trademark, _set_trademark, doc="trademark") - - def _get_license(self): - s = self._object.license - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_license(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.license = value - - license = property(_get_license, _set_license, doc="license") - - def _get_licenseURL(self): - return self._object.license_url - - def _set_licenseURL(self, value): - self._object.license_url = value - - licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") - - def _get_createdBy(self): - s = self._object.source - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_createdBy(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.source = value - - createdBy = property(_get_createdBy, _set_createdBy, doc="source") - - def _get_designer(self): - s = self._object.designer - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_designer(self, value): - if value is not None: - value = value.encode(LOCAL_ENCODING) - self._object.designer = value - - designer = property(_get_designer, _set_designer, doc="designer") - - def _get_designerURL(self): - return self._object.designer_url - - def _set_designerURL(self, value): - self._object.designer_url = value - - designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") - - def _get_vendorURL(self): - return self._object.vendor_url - - def _set_vendorURL(self, value): - self._object.vendor_url = value - - vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") - - def _get_ttVendor(self): - return self._object.vendor - - def _set_ttVendor(self, value): - self._object.vendor = value - - ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") - - def _get_ttUniqueID(self): - return self._object.tt_u_id - - def _set_ttUniqueID(self, value): - self._object.tt_u_id = value - - ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") - - def _get_ttVersion(self): - return self._object.tt_version - - def _set_ttVersion(self, value): - self._object.tt_version = value - - ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") - - def _get_unitsPerEm(self): - return self._object.upm - - def _set_unitsPerEm(self, value): - self._object.upm = int(round(value)) - - unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") - - def _get_ascender(self): - return self._object.ascender[0] - - def _set_ascender(self, value): - value = int(round(value)) - self._object.ascender[0] = value - - ascender = property(_get_ascender, _set_ascender, doc="ascender value") - - def _get_descender(self): - return self._object.descender[0] - - def _set_descender(self, value): - value = int(round(value)) - self._object.descender[0] = value - - descender = property(_get_descender, _set_descender, doc="descender value") - - def _get_capHeight(self): - return self._object.cap_height[0] - - def _set_capHeight(self, value): - value = int(round(value)) - self._object.cap_height[0] = value - - capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") - - def _get_xHeight(self): - return self._object.x_height[0] - - def _set_xHeight(self, value): - value = int(round(value)) - self._object.x_height[0] = value - - xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") - - def _get_defaultWidth(self): - return self._object.default_width[0] - - def _set_defaultWidth(self, value): - value = int(round(value)) - self._object.default_width[0] = value - - defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") - - def _get_italicAngle(self): - return self._object.italic_angle - - def _set_italicAngle(self, value): - try: - self._object.italic_angle = float(value) - except TypeError: - print "robofab.objects.objectsFL: can't set italic angle, possibly a FontLab API limitation" - - italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") - - def _get_slantAngle(self): - return self._object.slant_angle - - def _set_slantAngle(self, value): - try: - self._object.slant_angle = float(value) - except TypeError: - print "robofab.objects.objectsFL: can't set slant angle, possibly a FontLab API limitation" - - slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") - - #is this still needed? - def _get_full_name(self): - return self._object.full_name - - def _set_full_name(self, value): - self._object.full_name = value - - full_name = property(_get_full_name, _set_full_name, doc="FL: full_name") - - #is this still needed? - def _get_ms_charset(self): - return self._object.ms_charset - - def _set_ms_charset(self, value): - self._object.ms_charset = value - - ms_charset = property(_get_ms_charset, _set_ms_charset, doc="FL: ms_charset") diff --git a/Lib/robofab/objects/objectsRF.py b/Lib/robofab/objects/objectsRF.py index 446ae55fa..228b23613 100755 --- a/Lib/robofab/objects/objectsRF.py +++ b/Lib/robofab/objects/objectsRF.py @@ -1,7 +1,7 @@ """UFO for GlifLib""" from robofab import RoboFabError, RoboFabWarning -from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseLib,\ +from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ _interpolate, _interpolatePt, roundPt, addPt,\ @@ -45,8 +45,10 @@ def OpenFont(path=None, note=None): def NewFont(familyName=None, styleName=None): """Make a new font""" new = RFont() - new.info.familyName = familyName - new.info.styleName = styleName + if familyName is not None: + new.info.familyName = familyName + if styleName is not None: + new.info.styleName = styleName return new def AllFonts(): @@ -67,13 +69,16 @@ class PostScriptFontHintValues(BasePostScriptFontHintValues): """ def __init__(self, aFont=None, data=None): - # read the data from the font.lib, it won't be anywhere else + self.setParent(aFont) BasePostScriptFontHintValues.__init__(self) if aFont is not None: - self.setParent(aFont) + # in version 1, this data was stored in the lib + # if it is still there, guess that it is correct + # move it to font info and remove it from the lib. libData = aFont.lib.get(postScriptHintDataLibKey) if libData is not None: self.fromDict(libData) + del libData[postScriptHintDataLibKey] if data is not None: self.fromDict(data) @@ -123,15 +128,17 @@ class RFont(BaseFont): self.kerning.setParent(self) self.info = RInfo() self.info.setParent(self) + self.features = RFeatures() + self.features.setParent(self) self.groups = RGroups() self.groups.setParent(self) self.lib = RLib() self.lib.setParent(self) - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - if path: self._loadData(path) + else: + self.psHints = PostScriptFontHintValues(self) + self.psHints.setParent(self) def __setitem__(self, glyphName, glyph): """Set a glyph at key.""" @@ -152,19 +159,51 @@ class RFont(BaseFont): return len(self._glyphSet) def _loadData(self, path): - #Load the data into the font from robofab.ufoLib import UFOReader - u = UFOReader(path) - u.readInfo(self.info) - self.kerning.update(u.readKerning()) + reader = UFOReader(path) + fontLib = reader.readLib() + # info + reader.readInfo(self.info) + # kerning + self.kerning.update(reader.readKerning()) self.kerning.setChanged(False) - self.groups.update(u.readGroups()) - self.lib.update(u.readLib()) - # after reading the lib, read hinting data from the lib + # groups + self.groups.update(reader.readGroups()) + # features + if reader.formatVersion == 1: + # migrate features from the lib + features = [] + classes = fontLib.get("org.robofab.opentype.classes") + if classes is not None: + del fontLib["org.robofab.opentype.classes"] + features.append(classes) + splitFeatures = fontLib.get("org.robofab.opentype.features") + if splitFeatures is not None: + order = fontLib.get("org.robofab.opentype.featureorder") + if order is None: + order = splitFeatures.keys() + order.sort() + else: + del fontLib["org.robofab.opentype.featureorder"] + del fontLib["org.robofab.opentype.features"] + for tag in order: + oneFeature = splitFeatures.get(tag) + if oneFeature is not None: + features.append(oneFeature) + features = "\n".join(features) + else: + features = reader.readFeatures() + self.features.text = features + # hint data self.psHints = PostScriptFontHintValues(self) - self._glyphSet = u.getGlyphSet() + if postScriptHintDataLibKey in fontLib: + del fontLib[postScriptHintDataLibKey] + # lib + self.lib.update(fontLib) + # glyphs + self._glyphSet = reader.getGlyphSet() self._hasNotChanged(doGlyphs=False) - + def _loadGlyph(self, glyphName): """Load a single glyph from the glyphSet, on request.""" from robofab.pens.rfUFOPen import RFUFOPointPen @@ -329,7 +368,7 @@ class RFont(BaseFont): return reverseMap - def save(self, destDir=None, doProgress=False, saveNow=False): + def save(self, destDir=None, doProgress=False, formatVersion=2): """Save the Font in UFO format.""" # XXX note that when doing "save as" by specifying the destDir argument # _all_ glyphs get loaded into memory. This could be optimized by either @@ -337,6 +376,7 @@ class RFont(BaseFont): # well that would work) by simply clearing out self._objects after the # save. from robofab.ufoLib import UFOWriter + from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab # if no destination is given, or if # the given destination is the current # path, this is not a save as operation @@ -345,54 +385,78 @@ class RFont(BaseFont): destDir = self._path else: saveAs = True - u = UFOWriter(destDir) + # start a progress bar nonGlyphCount = 5 bar = None if doProgress: from robofab.interface.all.dialogs import ProgressBar - bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self._object.keys())) + bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) + # write + writer = UFOWriter(destDir, formatVersion=formatVersion) try: - #if self.info.changed: + # make a shallow copy of the lib. stuff may be added to it. + fontLib = dict(self.lib) + # info if bar: - bar.label('Saving info...') - u.writeInfo(self.info) + bar.label("Saving info...") + writer.writeInfo(self.info) if bar: bar.tick() + # kerning if self.kerning.changed or saveAs: if bar: - bar.label('Saving kerning...') - u.writeKerning(self.kerning.asDict()) - self.kerning.setChanged(False) + bar.label("Saving kerning...") + writer.writeKerning(self.kerning.asDict()) + if bar: + bar.tick() + # groups + if bar: + bar.label("Saving groups...") + writer.writeGroups(self.groups) if bar: bar.tick() - #if self.groups.changed: + # features if bar: - bar.label('Saving groups...') - u.writeGroups(self.groups) + bar.label("Saving features...") + features = self.features.text + if features is None: + features = "" + if formatVersion == 2: + writer.writeFeatures(features) + elif formatVersion == 1: + classes, features = splitFeaturesForFontLab(features) + if classes: + fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" + if features: + featureDict = {} + for featureName, featureText in features: + featureDict[featureName] = featureText.strip() + "\n" + fontLib["org.robofab.opentype.features"] = featureDict + fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] if bar: bar.tick() - - # save postscript hint data - self.lib[postScriptHintDataLibKey] = self.psHints.asDict() - - #if self.lib.changed: + # lib + if formatVersion == 1: + fontLib[postScriptHintDataLibKey] = self.psHints.asDict() if bar: - bar.label('Saving lib...') - u.writeLib(self.lib) + bar.label("Saving lib...") + writer.writeLib(fontLib) if bar: bar.tick() + # glyphs glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) + + glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) if len(self._scheduledForDeletion) != 0: if bar: - bar.label('Removing deleted glyphs......') + bar.label("Removing deleted glyphs...") for glyphName in self._scheduledForDeletion: if glyphSet.has_key(glyphName): glyphSet.deleteGlyph(glyphName) if bar: bar.tick() if bar: - bar.label('Saving glyphs...') + bar.label("Saving glyphs...") count = nonGlyphCount if saveAs: glyphNames = self.keys() @@ -407,15 +471,18 @@ class RFont(BaseFont): count = count + 1 glyphSet.writeContents() self._glyphSet = glyphSet + # only blindly stop if the user says to except KeyboardInterrupt: bar.close() bar = None + # kill the progress bar if bar: bar.close() + # reset internal stuff self._path = destDir self._scheduledForDeletion = [] self.setChanged(False) - + def newGlyph(self, glyphName, clear=True): """Make a new glyph with glyphName if the glyph exists and clear=True clear the glyph""" @@ -1126,370 +1193,9 @@ class RLib(BaseLib): class RInfo(BaseInfo): - _title = "RoboFabFonInfo" - - def __init__(self): - BaseInfo.__init__(self) - self.selected = False - - self._familyName = None - self._styleName = None - self._fullName = None - self._fontName = None - self._menuName = None - self._fondName = None - self._otFamilyName = None - self._otStyleName = None - self._otMacName = None - self._weightValue = None - self._weightName = None - self._widthName = None - self._fontStyle = None - self._msCharSet = None - self._note = None - self._fondID = None - self._uniqueID = None - self._versionMajor = None - self._versionMinor = None - self._year = None - self._copyright = None - self._notice = None - self._trademark = None - self._license = None - self._licenseURL = None - self._createdBy = None - self._designer = None - self._designerURL = None - self._vendorURL = None - self._ttVendor = None - self._ttUniqueID = None - self._ttVersion = None - self._unitsPerEm = None - self._ascender = None - self._descender = None - self._capHeight = None - self._xHeight = None - self._defaultWidth = None - self._italicAngle = None - self._slantAngle = None - - def _get_familyName(self): - return self._familyName - - def _set_familyName(self, value): - self._familyName = value - - familyName = property(_get_familyName, _set_familyName, doc="family_name") - - def _get_styleName(self): - return self._styleName - - def _set_styleName(self, value): - self._styleName = value - - styleName = property(_get_styleName, _set_styleName, doc="style_name") - - def _get_fullName(self): - return self._fullName - - def _set_fullName(self, value): - self._fullName = value - - fullName = property(_get_fullName, _set_fullName, doc="full_name") - - def _get_fontName(self): - return self._fontName - - def _set_fontName(self, value): - self._fontName = value - - fontName = property(_get_fontName, _set_fontName, doc="font_name") - - def _get_menuName(self): - return self._menuName - - def _set_menuName(self, value): - self._menuName = value - - menuName = property(_get_menuName, _set_menuName, doc="menu_name") - - def _get_fondName(self): - return self._fondName - - def _set_fondName(self, value): - self._fondName = value - - fondName = property(_get_fondName, _set_fondName, doc="apple_name") - - def _get_otFamilyName(self): - return self._otFamilyName - - def _set_otFamilyName(self, value): - self._otFamilyName = value - - otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") - - def _get_otStyleName(self): - return self._otStyleName - - def _set_otStyleName(self, value): - self._otStyleName = value - - otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") - - def _get_otMacName(self): - return self._otMacName - - def _set_otMacName(self, value): - self._otMacName = value - - otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") - - def _get_weightValue(self): - return self._weightValue - - def _set_weightValue(self, value): - self._weightValue = value - - weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") - - def _get_weightName(self): - return self._weightName - - def _set_weightName(self, value): - self._weightName = value - - weightName = property(_get_weightName, _set_weightName, doc="weight name") - - def _get_widthName(self): - return self._widthName - - def _set_widthName(self, value): - self._widthName = value - - widthName = property(_get_widthName, _set_widthName, doc="width name") + _title = "RoboFabFontInfo" - def _get_fontStyle(self): - return self._fontStyle - - def _set_fontStyle(self, value): - self._fontStyle = value - - fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") - - def _get_msCharSet(self): - return self._msCharSet - - def _set_msCharSet(self, value): - self._msCharSet = value - - msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") - - def _get_note(self): - return self._note - - def _set_note(self, value): - self._note = value - - note = property(_get_note, _set_note, doc="note") - - def _get_fondID(self): - return self._fondID - - def _set_fondID(self, value): - self._fondID = value - - fondID = property(_get_fondID, _set_fondID, doc="fond_id") - - def _get_uniqueID(self): - return self._uniqueID - - def _set_uniqueID(self, value): - self._uniqueID = value - - uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") - - def _get_versionMajor(self): - return self._versionMajor - - def _set_versionMajor(self, value): - self._versionMajor = value - - versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") - - def _get_versionMinor(self): - return self._versionMinor - - def _set_versionMinor(self, value): - self._versionMinor = value - - versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") - - def _get_year(self): - return self._year - - def _set_year(self, value): - self._year = value - - year = property(_get_year, _set_year, doc="year") - - def _get_copyright(self): - return self._copyright - - def _set_copyright(self, value): - self._copyright = value - - copyright = property(_get_copyright, _set_copyright, doc="copyright") - - def _get_notice(self): - return self._notice - - def _set_notice(self, value): - self._notice = value - - notice = property(_get_notice, _set_notice, doc="notice") - - def _get_trademark(self): - return self._trademark - - def _set_trademark(self, value): - self._trademark = value - - trademark = property(_get_trademark, _set_trademark, doc="trademark") - - def _get_license(self): - return self._license - - def _set_license(self, value): - self._license = value - - license = property(_get_license, _set_license, doc="license") - - def _get_licenseURL(self): - return self._licenseURL - - def _set_licenseURL(self, value): - self._licenseURL = value - - licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") - - def _get_designer(self): - return self._designer - - def _set_designer(self, value): - self._designer = value - - designer = property(_get_designer, _set_designer, doc="designer") - - def _get_createdBy(self): - return self._createdBy - - def _set_createdBy(self, value): - self._createdBy = value - - createdBy = property(_get_createdBy, _set_createdBy, doc="source") - - def _get_designerURL(self): - return self._designerURL - - def _set_designerURL(self, value): - self._designerURL = value - - designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") - - def _get_vendorURL(self): - return self._vendorURL - - def _set_vendorURL(self, value): - self._vendorURL = value - - vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") - - def _get_ttVendor(self): - return self._ttVendor - - def _set_ttVendor(self, value): - self._ttVendor = value - - ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") - - def _get_ttUniqueID(self): - return self._ttUniqueID - - def _set_ttUniqueID(self, value): - self._ttUniqueID = value - - ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") - - def _get_ttVersion(self): - return self._ttVersion - - def _set_ttVersion(self, value): - self._ttVersion = value - - ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") - - def _get_unitsPerEm(self): - return self._unitsPerEm - - def _set_unitsPerEm(self, value): - self._unitsPerEm = value - - unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") - - def _get_ascender(self): - return self._ascender - - def _set_ascender(self, value): - self._ascender = value - - ascender = property(_get_ascender, _set_ascender, doc="ascender value") - - def _get_descender(self): - return self._descender - - def _set_descender(self, value): - self._descender = value - - descender = property(_get_descender, _set_descender, doc="descender value") - - def _get_capHeight(self): - return self._capHeight - - def _set_capHeight(self, value): - self._capHeight = value - - capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") - - def _get_xHeight(self): - return self._xHeight - - def _set_xHeight(self, value): - self._xHeight = value - - xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") - - def _get_defaultWidth(self): - return self._defaultWidth - - def _set_defaultWidth(self, value): - self._defaultWidth = value - - defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") - - def _get_italicAngle(self): - return self._italicAngle - - def _set_italicAngle(self, value): - self._italicAngle = value - - italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") - - def _get_slantAngle(self): - return self._slantAngle - - def _set_slantAngle(self, value): - self._slantAngle = value - - slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") +class RFeatures(BaseFeatures): + + _title = "RoboFabFeatures" diff --git a/Lib/robofab/test/runAll.py b/Lib/robofab/test/runAll.py index f760edde5..28446a7b8 100644 --- a/Lib/robofab/test/runAll.py +++ b/Lib/robofab/test/runAll.py @@ -17,6 +17,7 @@ if __name__ == "__main__": mod = __import__(modName, {}, {}, ["*"]) except ImportError: print "*** skipped", fileName + continue suites.append(loader.loadTestsFromModule(mod)) diff --git a/Lib/robofab/test/testSupport.py b/Lib/robofab/test/testSupport.py index 508913037..f7f96c9ff 100755 --- a/Lib/robofab/test/testSupport.py +++ b/Lib/robofab/test/testSupport.py @@ -47,3 +47,232 @@ def runTests(testCases=None, verbosity=1): testSuite = unittest.TestSuite(suites) testRunner.run(testSuite) +# font info values used by several tests + +fontInfoVersion1 = { + "familyName" : "Some Font (Family Name)", + "styleName" : "Regular (Style Name)", + "fullName" : "Some Font-Regular (Postscript Full Name)", + "fontName" : "SomeFont-Regular (Postscript Font Name)", + "menuName" : "Some Font Regular (Style Map Family Name)", + "fontStyle" : 64, + "note" : "A note.", + "versionMajor" : 1, + "versionMinor" : 0, + "year" : 2008, + "copyright" : "Copyright Some Foundry.", + "notice" : "Some Font by Some Designer for Some Foundry.", + "trademark" : "Trademark Some Foundry", + "license" : "License info for Some Foundry.", + "licenseURL" : "http://somefoundry.com/license", + "createdBy" : "Some Foundry", + "designer" : "Some Designer", + "designerURL" : "http://somedesigner.com", + "vendorURL" : "http://somefoundry.com", + "unitsPerEm" : 1000, + "ascender" : 750, + "descender" : -250, + "capHeight" : 750, + "xHeight" : 500, + "defaultWidth" : 400, + "slantAngle" : -12.5, + "italicAngle" : -12.5, + "widthName" : "Medium (normal)", + "weightName" : "Medium", + "weightValue" : 500, + "fondName" : "SomeFont Regular (FOND Name)", + "otFamilyName" : "Some Font (Preferred Family Name)", + "otStyleName" : "Regular (Preferred Subfamily Name)", + "otMacName" : "Some Font Regular (Compatible Full Name)", + "msCharSet" : 0, + "fondID" : 15000, + "uniqueID" : 4000000, + "ttVendor" : "SOME", + "ttUniqueID" : "OpenType name Table Unique ID", + "ttVersion" : "OpenType name Table Version", +} + +fontInfoVersion2 = { + "familyName" : "Some Font (Family Name)", + "styleName" : "Regular (Style Name)", + "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", + "styleMapStyleName" : "regular", + "versionMajor" : 1, + "versionMinor" : 0, + "year" : 2008, + "copyright" : "Copyright Some Foundry.", + "trademark" : "Trademark Some Foundry", + "unitsPerEm" : 1000, + "descender" : -250, + "xHeight" : 500, + "capHeight" : 750, + "ascender" : 750, + "italicAngle" : -12.5, + "note" : "A note.", + "openTypeHeadCreated" : "2000/01/01 00:00:00", + "openTypeHeadLowestRecPPEM" : 10, + "openTypeHeadFlags" : [0, 1], + "openTypeHheaAscender" : 750, + "openTypeHheaDescender" : -250, + "openTypeHheaLineGap" : 200, + "openTypeHheaCaretSlopeRise" : 1, + "openTypeHheaCaretSlopeRun" : 0, + "openTypeHheaCaretOffset" : 0, + "openTypeNameDesigner" : "Some Designer", + "openTypeNameDesignerURL" : "http://somedesigner.com", + "openTypeNameManufacturer" : "Some Foundry", + "openTypeNameManufacturerURL" : "http://somefoundry.com", + "openTypeNameLicense" : "License info for Some Foundry.", + "openTypeNameLicenseURL" : "http://somefoundry.com/license", + "openTypeNameVersion" : "OpenType name Table Version", + "openTypeNameUniqueID" : "OpenType name Table Unique ID", + "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", + "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", + "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)", + "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", + "openTypeNameSampleText" : "Sample Text for Some Font.", + "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)", + "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)", + "openTypeOS2WidthClass" : 5, + "openTypeOS2WeightClass" : 500, + "openTypeOS2Selection" : [3], + "openTypeOS2VendorID" : "SOME", + "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "openTypeOS2FamilyClass" : [1, 1], + "openTypeOS2UnicodeRanges" : [0, 1], + "openTypeOS2CodePageRanges" : [0, 1], + "openTypeOS2TypoAscender" : 750, + "openTypeOS2TypoDescender" : -250, + "openTypeOS2TypoLineGap" : 200, + "openTypeOS2WinAscent" : 750, + "openTypeOS2WinDescent" : -250, + "openTypeOS2Type" : [], + "openTypeOS2SubscriptXSize" : 200, + "openTypeOS2SubscriptYSize" : 400, + "openTypeOS2SubscriptXOffset" : 0, + "openTypeOS2SubscriptYOffset" : -100, + "openTypeOS2SuperscriptXSize" : 200, + "openTypeOS2SuperscriptYSize" : 400, + "openTypeOS2SuperscriptXOffset" : 0, + "openTypeOS2SuperscriptYOffset" : 200, + "openTypeOS2StrikeoutSize" : 20, + "openTypeOS2StrikeoutPosition" : 300, + "openTypeVheaVertTypoAscender" : 750, + "openTypeVheaVertTypoDescender" : -250, + "openTypeVheaVertTypoLineGap" : 200, + "openTypeVheaCaretSlopeRise" : 0, + "openTypeVheaCaretSlopeRun" : 1, + "openTypeVheaCaretOffset" : 0, + "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", + "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", + "postscriptSlantAngle" : -12.5, + "postscriptUniqueID" : 4000000, + "postscriptUnderlineThickness" : 20, + "postscriptUnderlinePosition" : -200, + "postscriptIsFixedPitch" : False, + "postscriptBlueValues" : [500, 510], + "postscriptOtherBlues" : [-250, -260], + "postscriptFamilyBlues" : [500, 510], + "postscriptFamilyOtherBlues" : [-250, -260], + "postscriptStemSnapH" : [100, 120], + "postscriptStemSnapV" : [80, 90], + "postscriptBlueFuzz" : 1, + "postscriptBlueShift" : 7, + "postscriptBlueScale" : 0.039625, + "postscriptForceBold" : True, + "postscriptDefaultWidthX" : 400, + "postscriptNominalWidthX" : 400, + "postscriptWeightName" : "Medium", + "postscriptDefaultCharacter" : ".notdef", + "postscriptWindowsCharacterSet" : 1, + "macintoshFONDFamilyID" : 15000, + "macintoshFONDName" : "SomeFont Regular (FOND Name)", +} + +expectedFontInfo1To2Conversion = { + "familyName" : "Some Font (Family Name)", + "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", + "styleMapStyleName" : "regular", + "styleName" : "Regular (Style Name)", + "unitsPerEm" : 1000, + "ascender" : 750, + "capHeight" : 750, + "xHeight" : 500, + "descender" : -250, + "italicAngle" : -12.5, + "versionMajor" : 1, + "versionMinor" : 0, + "year" : 2008, + "copyright" : "Copyright Some Foundry.", + "trademark" : "Trademark Some Foundry", + "note" : "A note.", + "macintoshFONDFamilyID" : 15000, + "macintoshFONDName" : "SomeFont Regular (FOND Name)", + "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", + "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", + "openTypeNameDesigner" : "Some Designer", + "openTypeNameDesignerURL" : "http://somedesigner.com", + "openTypeNameLicense" : "License info for Some Foundry.", + "openTypeNameLicenseURL" : "http://somefoundry.com/license", + "openTypeNameManufacturer" : "Some Foundry", + "openTypeNameManufacturerURL" : "http://somefoundry.com", + "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", + "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)", + "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", + "openTypeNameUniqueID" : "OpenType name Table Unique ID", + "openTypeNameVersion" : "OpenType name Table Version", + "openTypeOS2VendorID" : "SOME", + "openTypeOS2WeightClass" : 500, + "openTypeOS2WidthClass" : 5, + "postscriptDefaultWidthX" : 400, + "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", + "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", + "postscriptSlantAngle" : -12.5, + "postscriptUniqueID" : 4000000, + "postscriptWeightName" : "Medium", + "postscriptWindowsCharacterSet" : 1 +} + +expectedFontInfo2To1Conversion = { + "familyName" : "Some Font (Family Name)", + "menuName" : "Some Font Regular (Style Map Family Name)", + "fontStyle" : 64, + "styleName" : "Regular (Style Name)", + "unitsPerEm" : 1000, + "ascender" : 750, + "capHeight" : 750, + "xHeight" : 500, + "descender" : -250, + "italicAngle" : -12.5, + "versionMajor" : 1, + "versionMinor" : 0, + "copyright" : "Copyright Some Foundry.", + "trademark" : "Trademark Some Foundry", + "note" : "A note.", + "fondID" : 15000, + "fondName" : "SomeFont Regular (FOND Name)", + "fullName" : "Some Font Regular (Compatible Full Name)", + "notice" : "Some Font by Some Designer for Some Foundry.", + "designer" : "Some Designer", + "designerURL" : "http://somedesigner.com", + "license" : "License info for Some Foundry.", + "licenseURL" : "http://somefoundry.com/license", + "createdBy" : "Some Foundry", + "vendorURL" : "http://somefoundry.com", + "otFamilyName" : "Some Font (Preferred Family Name)", + "otStyleName" : "Regular (Preferred Subfamily Name)", + "otMacName" : "Some Font Regular (Compatible Full Name)", + "ttUniqueID" : "OpenType name Table Unique ID", + "ttVersion" : "OpenType name Table Version", + "ttVendor" : "SOME", + "weightValue" : 500, + "widthName" : "Medium (normal)", + "defaultWidth" : 400, + "fontName" : "SomeFont-Regular (Postscript Font Name)", + "fullName" : "Some Font-Regular (Postscript Full Name)", + "slantAngle" : -12.5, + "uniqueID" : 4000000, + "weightName" : "Medium", + "msCharSet" : 0, + "year" : 2008 +} diff --git a/Lib/robofab/test/test_RInfoFL.py b/Lib/robofab/test/test_RInfoFL.py new file mode 100644 index 000000000..bfbd13477 --- /dev/null +++ b/Lib/robofab/test/test_RInfoFL.py @@ -0,0 +1,111 @@ +import unittest +from cStringIO import StringIO +import sys +from robofab import ufoLib +from robofab.objects.objectsFL import NewFont +from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2 + + +class RInfoRFTestCase(unittest.TestCase): + + def testRoundTripVersion2(self): + font = NewFont() + infoObject = font.info + for attr, value in fontInfoVersion2.items(): + if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: + continue + setattr(infoObject, attr, value) + newValue = getattr(infoObject, attr) + self.assertEqual((attr, newValue), (attr, value)) + font.close() + + def testVersion2UnsupportedSet(self): + saveStderr = sys.stderr + saveStdout = sys.stdout + tempStderr = StringIO() + sys.stderr = tempStderr + sys.stdout = tempStderr + font = NewFont() + infoObject = font.info + requiredWarnings = [] + try: + for attr, value in fontInfoVersion2.items(): + if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None: + continue + setattr(infoObject, attr, value) + s = "The attribute %s is not supported by FontLab." % attr + requiredWarnings.append((attr, s)) + finally: + sys.stderr = saveStderr + sys.stdout = saveStdout + tempStderr = tempStderr.getvalue() + for attr, line in requiredWarnings: + self.assertEquals((attr, line in tempStderr), (attr, True)) + font.close() + + def testVersion2UnsupportedGet(self): + saveStderr = sys.stderr + saveStdout = sys.stdout + tempStderr = StringIO() + sys.stderr = tempStderr + sys.stdout = tempStderr + font = NewFont() + infoObject = font.info + requiredWarnings = [] + try: + for attr, value in fontInfoVersion2.items(): + if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None: + continue + getattr(infoObject, attr, value) + s = "The attribute %s is not supported by FontLab." % attr + requiredWarnings.append((attr, s)) + finally: + sys.stderr = saveStderr + sys.stdout = saveStdout + tempStderr = tempStderr.getvalue() + for attr, line in requiredWarnings: + self.assertEquals((attr, line in tempStderr), (attr, True)) + font.close() + + def testRoundTripVersion1(self): + font = NewFont() + infoObject = font.info + for attr, value in fontInfoVersion1.items(): + if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: + setattr(infoObject, attr, value) + for attr, expectedValue in fontInfoVersion1.items(): + if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: + value = getattr(infoObject, attr) + self.assertEqual((attr, expectedValue), (attr, value)) + font.close() + + def testVersion1DeprecationRoundTrip(self): + saveStderr = sys.stderr + saveStdout = sys.stdout + tempStderr = StringIO() + sys.stderr = tempStderr + sys.stdout = tempStderr + font = NewFont() + infoObject = font.info + requiredWarnings = [] + try: + for attr, value in fontInfoVersion1.items(): + if attr in ufoLib.deprecatedFontInfoAttributesVersion2: + setattr(infoObject, attr, value) + v = getattr(infoObject, attr) + self.assertEquals((attr, value), (attr, v)) + s = "DeprecationWarning: The %s attribute has been deprecated." % attr + requiredWarnings.append((attr, s)) + finally: + sys.stderr = saveStderr + sys.stdout = saveStdout + tempStderr = tempStderr.getvalue() + for attr, line in requiredWarnings: + self.assertEquals((attr, line in tempStderr), (attr, True)) + font.close() + + +if __name__ == "__main__": + from robofab.test.testSupport import runTests + runTests() + diff --git a/Lib/robofab/test/test_RInfoRF.py b/Lib/robofab/test/test_RInfoRF.py new file mode 100644 index 000000000..3a8747033 --- /dev/null +++ b/Lib/robofab/test/test_RInfoRF.py @@ -0,0 +1,56 @@ +import unittest +from cStringIO import StringIO +import sys +from robofab import ufoLib +from robofab.objects.objectsRF import RInfo +from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2 + + +class RInfoRFTestCase(unittest.TestCase): + + def testRoundTripVersion2(self): + infoObject = RInfo() + for attr, value in fontInfoVersion2.items(): + setattr(infoObject, attr, value) + newValue = getattr(infoObject, attr) + self.assertEqual((attr, newValue), (attr, value)) + + def testRoundTripVersion1(self): + infoObject = RInfo() + for attr, value in fontInfoVersion1.items(): + if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: + setattr(infoObject, attr, value) + for attr, expectedValue in fontInfoVersion1.items(): + if attr not in ufoLib.deprecatedFontInfoAttributesVersion2: + value = getattr(infoObject, attr) + self.assertEqual((attr, expectedValue), (attr, value)) + + def testVersion1DeprecationRoundTrip(self): + """ + unittest doesn't catch warnings in self.assertRaises, + so some hackery is required to catch the warnings + that are raised when setting deprecated attributes. + """ + saveStderr = sys.stderr + tempStderr = StringIO() + sys.stderr = tempStderr + infoObject = RInfo() + requiredWarnings = [] + try: + for attr, value in fontInfoVersion1.items(): + if attr in ufoLib.deprecatedFontInfoAttributesVersion2: + setattr(infoObject, attr, value) + v = getattr(infoObject, attr) + self.assertEquals((attr, value), (attr, v)) + s = "DeprecationWarning: The %s attribute has been deprecated." % attr + requiredWarnings.append((attr, s)) + finally: + sys.stderr = saveStderr + tempStderr = tempStderr.getvalue() + for attr, line in requiredWarnings: + self.assertEquals((attr, line in tempStderr), (attr, True)) + + +if __name__ == "__main__": + from robofab.test.testSupport import runTests + runTests() diff --git a/Lib/robofab/test/test_fontLabUFOReadWrite.py b/Lib/robofab/test/test_fontLabUFOReadWrite.py new file mode 100644 index 000000000..91c072d26 --- /dev/null +++ b/Lib/robofab/test/test_fontLabUFOReadWrite.py @@ -0,0 +1,565 @@ +import os +import shutil +import unittest +import tempfile +from robofab.plistlib import readPlist +import robofab +from robofab.ufoLib import UFOReader, UFOWriter +from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion +from robofab.objects.objectsFL import NewFont, OpenFont + +vfbPath = os.path.dirname(robofab.__file__) +vfbPath = os.path.dirname(vfbPath) +vfbPath = os.path.dirname(vfbPath) +vfbPath = os.path.join(vfbPath, "TestData", "TestFont1.vfb") + +ufoPath1 = os.path.dirname(robofab.__file__) +ufoPath1 = os.path.dirname(ufoPath1) +ufoPath1 = os.path.dirname(ufoPath1) +ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo") +ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo") + + +expectedFormatVersion1Features = """@myClass = [A B]; + +feature liga { + sub A A by b; +} liga; +""" + +# robofab should remove these from the lib after a load. +removeFromFormatVersion1Lib = [ + "org.robofab.opentype.classes", + "org.robofab.opentype.features", + "org.robofab.opentype.featureorder", + "org.robofab.postScriptHintData" +] + + +class ReadUFOFormatVersion1TestCase(unittest.TestCase): + + def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): + self.font = NewFont() + self.ufoPath = ufoPath1 + self.font.readUFO(ufoPath1, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) + self.font.update() + + def tearDownFont(self): + self.font.close() + self.font = None + + def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): + reader = UFOReader(self.ufoPath) + results = {} + if doInfo: + infoMatches = True + info = self.font.info + for attr, expectedValue in expectedFontInfo1To2Conversion.items(): + writtenValue = getattr(info, attr) + if expectedValue != writtenValue: + infoMatches = False + break + results["info"]= infoMatches + if doKerning: + kerning = self.font.kerning.asDict() + expectedKerning = reader.readKerning() + results["kerning"] = expectedKerning == kerning + if doGroups: + groups = dict(self.font.groups) + expectedGroups = reader.readGroups() + results["groups"] = expectedGroups == groups + if doFeatures: + features = self.font.features.text + expectedFeatures = expectedFormatVersion1Features + # FontLab likes to add lines to the features, so skip blank lines. + features = [line for line in features.splitlines() if line] + expectedFeatures = [line for line in expectedFeatures.splitlines() if line] + results["features"] = expectedFeatures == features + if doLib: + lib = dict(self.font.lib) + expectedLib = reader.readLib() + for key in removeFromFormatVersion1Lib: + if key in expectedLib: + del expectedLib[key] + results["lib"] = expectedLib == lib + return results + + def testFull(self): + self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont(doInfo=True) + otherResults = self.compareToUFO(doInfo=False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + info = self.font.info + for attr, expectedValue in expectedFontInfo1To2Conversion.items(): + writtenValue = getattr(info, attr) + self.assertEqual((attr, expectedValue), (attr, writtenValue)) + self.tearDownFont() + + def testFeatures(self): + self.setUpFont(doFeatures=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testKerning(self): + self.setUpFont(doKerning=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testGroups(self): + self.setUpFont(doGroups=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testLib(self): + self.setUpFont(doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + +class ReadUFOFormatVersion2TestCase(unittest.TestCase): + + def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): + self.font = NewFont() + self.ufoPath = ufoPath2 + self.font.readUFO(ufoPath2, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) + self.font.update() + + def tearDownFont(self): + self.font.close() + self.font = None + + def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): + reader = UFOReader(self.ufoPath) + results = {} + if doInfo: + infoMatches = True + info = self.font.info + for attr, expectedValue in fontInfoVersion2.items(): + # cheat by skipping attrs that aren't supported + if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: + continue + writtenValue = getattr(info, attr) + if expectedValue != writtenValue: + infoMatches = False + break + results["info"]= infoMatches + if doKerning: + kerning = self.font.kerning.asDict() + expectedKerning = reader.readKerning() + results["kerning"] = expectedKerning == kerning + if doGroups: + groups = dict(self.font.groups) + expectedGroups = reader.readGroups() + results["groups"] = expectedGroups == groups + if doFeatures: + features = self.font.features.text + expectedFeatures = reader.readFeatures() + results["features"] = expectedFeatures == features + if doLib: + lib = dict(self.font.lib) + expectedLib = reader.readLib() + results["lib"] = expectedLib == lib + return results + + def testFull(self): + self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont(doInfo=True) + otherResults = self.compareToUFO(doInfo=False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + info = self.font.info + for attr, expectedValue in fontInfoVersion2.items(): + # cheat by skipping attrs that aren't supported + if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None: + continue + writtenValue = getattr(info, attr) + self.assertEqual((attr, expectedValue), (attr, writtenValue)) + self.tearDownFont() + + def testFeatures(self): + self.setUpFont(doFeatures=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testKerning(self): + self.setUpFont(doKerning=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testGroups(self): + self.setUpFont(doGroups=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testLib(self): + self.setUpFont(doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + +class WriteUFOFormatVersion1TestCase(unittest.TestCase): + + def setUpFont(self, doInfo=False, doKerning=False, doGroups=False): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + self.font = OpenFont(vfbPath) + self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, formatVersion=1) + self.font.close() + + def tearDownFont(self): + shutil.rmtree(self.dstDir) + + def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): + readerExpected = UFOReader(ufoPath1) + readerWritten = UFOReader(self.dstDir) + results = {} + if doInfo: + matches = True + expectedPath = os.path.join(ufoPath1, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + if not os.path.exists(writtenPath): + matches = False + else: + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + for attr, expectedValue in expected.items(): + if expectedValue != written[attr]: + matches = False + break + results["info"] = matches + if doKerning: + matches = True + expectedPath = os.path.join(ufoPath1, "kerning.plist") + writtenPath = os.path.join(self.dstDir, "kerning.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["kerning"] = matches + if doGroups: + matches = True + expectedPath = os.path.join(ufoPath1, "groups.plist") + writtenPath = os.path.join(self.dstDir, "groups.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["groups"] = matches + if doFeatures: + matches = True + featuresPath = os.path.join(self.dstDir, "features.fea") + libPath = os.path.join(self.dstDir, "lib.plist") + if os.path.exists(featuresPath): + matches = False + else: + fontLib = readPlist(libPath) + writtenText = [fontLib.get("org.robofab.opentype.classes", "")] + features = fontLib.get("org.robofab.opentype.features", {}) + featureOrder= fontLib.get("org.robofab.opentype.featureorder", []) + for featureName in featureOrder: + writtenText.append(features.get(featureName, "")) + writtenText = "\n".join(writtenText) + # FontLab likes to add lines to the features, so skip blank lines. + expectedText = [line for line in expectedFormatVersion1Features.splitlines() if line] + writtenText = [line for line in writtenText.splitlines() if line] + matches = "\n".join(expectedText) == "\n".join(writtenText) + results["features"] = matches + if doLib: + matches = True + expectedPath = os.path.join(ufoPath1, "lib.plist") + writtenPath = os.path.join(self.dstDir, "lib.plist") + if not os.path.exists(writtenPath): + matches = False + else: + # the test file doesn't have the glyph order + # so purge it from the written + writtenLib = readPlist(writtenPath) + del writtenLib["org.robofab.glyphOrder"] + matches = readPlist(expectedPath) == writtenLib + results["lib"] = matches + return results + + def testFull(self): + self.setUpFont(doInfo=True, doKerning=True, doGroups=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont(doInfo=True) + otherResults = self.compareToUFO(doInfo=False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + expectedPath = os.path.join(ufoPath1, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + for attr, expectedValue in expected.items(): + self.assertEqual((attr, expectedValue), (attr, written[attr])) + self.tearDownFont() + + def testFeatures(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], True) + self.tearDownFont() + + def testKerning(self): + self.setUpFont(doKerning=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], False) + self.tearDownFont() + + def testGroups(self): + self.setUpFont(doGroups=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], True) + self.tearDownFont() + + def testLib(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + + +class WriteUFOFormatVersion2TestCase(unittest.TestCase): + + def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + self.font = OpenFont(vfbPath) + self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures) + self.font.close() + + def tearDownFont(self): + shutil.rmtree(self.dstDir) + + def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True): + readerExpected = UFOReader(ufoPath2) + readerWritten = UFOReader(self.dstDir) + results = {} + if doInfo: + matches = True + expectedPath = os.path.join(ufoPath2, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + if not os.path.exists(writtenPath): + matches = False + else: + dummyFont = NewFont() + _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping) + dummyFont.close() + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + for attr, expectedValue in expected.items(): + # cheat by skipping attrs that aren't supported + if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None: + continue + if expectedValue != written[attr]: + matches = False + break + results["info"] = matches + if doKerning: + matches = True + expectedPath = os.path.join(ufoPath2, "kerning.plist") + writtenPath = os.path.join(self.dstDir, "kerning.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["kerning"] = matches + if doGroups: + matches = True + expectedPath = os.path.join(ufoPath2, "groups.plist") + writtenPath = os.path.join(self.dstDir, "groups.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["groups"] = matches + if doFeatures: + matches = True + expectedPath = os.path.join(ufoPath2, "features.fea") + writtenPath = os.path.join(self.dstDir, "features.fea") + if not os.path.exists(writtenPath): + matches = False + else: + f = open(expectedPath, "r") + expectedText = f.read() + f.close() + f = open(writtenPath, "r") + writtenText = f.read() + f.close() + # FontLab likes to add lines to the features, so skip blank lines. + expectedText = [line for line in expectedText.splitlines() if line] + writtenText = [line for line in writtenText.splitlines() if line] + matches = "\n".join(expectedText) == "\n".join(writtenText) + results["features"] = matches + if doLib: + matches = True + expectedPath = os.path.join(ufoPath2, "lib.plist") + writtenPath = os.path.join(self.dstDir, "lib.plist") + if not os.path.exists(writtenPath): + matches = False + else: + # the test file doesn't have the glyph order + # so purge it from the written + writtenLib = readPlist(writtenPath) + del writtenLib["org.robofab.glyphOrder"] + matches = readPlist(expectedPath) == writtenLib + results["lib"] = matches + return results + + def testFull(self): + self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont(doInfo=True) + otherResults = self.compareToUFO(doInfo=False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + expectedPath = os.path.join(ufoPath2, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + dummyFont = NewFont() + _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping) + dummyFont.close() + for attr, expectedValue in expected.items(): + # cheat by skipping attrs that aren't supported + if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None: + continue + self.assertEqual((attr, expectedValue), (attr, written[attr])) + self.tearDownFont() + + def testFeatures(self): + self.setUpFont(doFeatures=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testKerning(self): + self.setUpFont(doKerning=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testGroups(self): + self.setUpFont(doGroups=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], False) + self.tearDownFont() + + def testLib(self): + self.setUpFont(doLib=True) + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], False) + self.assertEqual(otherResults["kerning"], False) + self.assertEqual(otherResults["groups"], False) + self.assertEqual(otherResults["features"], False) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + +if __name__ == "__main__": + from robofab.test.testSupport import runTests + runTests() diff --git a/Lib/robofab/test/test_noneLabUFOReadWrite.py b/Lib/robofab/test/test_noneLabUFOReadWrite.py new file mode 100644 index 000000000..36dcc0582 --- /dev/null +++ b/Lib/robofab/test/test_noneLabUFOReadWrite.py @@ -0,0 +1,321 @@ +import os +import shutil +import unittest +import tempfile +from robofab.plistlib import readPlist +import robofab +from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion +from robofab.objects.objectsRF import NewFont, OpenFont +from robofab.ufoLib import UFOReader + +ufoPath1 = os.path.dirname(robofab.__file__) +ufoPath1 = os.path.dirname(ufoPath1) +ufoPath1 = os.path.dirname(ufoPath1) +ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo") +ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo") + +# robofab should remove these from the lib after a load. +removeFromFormatVersion1Lib = [ + "org.robofab.opentype.classes", + "org.robofab.opentype.features", + "org.robofab.opentype.featureorder", + "org.robofab.postScriptHintData" +] + + +class ReadUFOFormatVersion1TestCase(unittest.TestCase): + + def setUpFont(self): + self.font = OpenFont(ufoPath1) + self.font.update() + + def tearDownFont(self): + self.font.close() + self.font = None + + def compareToUFO(self, doInfo=True): + reader = UFOReader(ufoPath1) + results = {} + # info + infoMatches = True + info = self.font.info + for attr, expectedValue in expectedFontInfo1To2Conversion.items(): + writtenValue = getattr(info, attr) + if expectedValue != writtenValue: + infoMatches = False + break + results["info"]= infoMatches + # kerning + kerning = self.font.kerning.asDict() + expectedKerning = reader.readKerning() + results["kerning"] = expectedKerning == kerning + # groups + groups = dict(self.font.groups) + expectedGroups = reader.readGroups() + results["groups"] = expectedGroups == groups + # features + features = self.font.features.text + f = open(os.path.join(ufoPath2, "features.fea"), "r") + expectedFeatures = f.read() + f.close() + match = True + features = [line for line in features.splitlines() if line] + expectedFeatures = [line for line in expectedFeatures.splitlines() if line] + if expectedFeatures != features or reader.readFeatures() != "": + match = False + results["features"] = match + # lib + lib = dict(self.font.lib) + expectedLib = reader.readLib() + for key in removeFromFormatVersion1Lib: + if key in expectedLib: + del expectedLib[key] + results["lib"] = expectedLib == lib + return results + + def testFull(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont() + info = self.font.info + for attr, expectedValue in expectedFontInfo1To2Conversion.items(): + writtenValue = getattr(info, attr) + self.assertEqual((attr, expectedValue), (attr, writtenValue)) + self.tearDownFont() + + +class ReadUFOFormatVersion2TestCase(unittest.TestCase): + + def setUpFont(self): + self.font = OpenFont(ufoPath2) + self.font.update() + + def tearDownFont(self): + self.font.close() + self.font = None + + def compareToUFO(self, doInfo=True): + reader = UFOReader(ufoPath2) + results = {} + # info + infoMatches = True + info = self.font.info + for attr, expectedValue in fontInfoVersion2.items(): + writtenValue = getattr(info, attr) + if expectedValue != writtenValue: + infoMatches = False + break + results["info"]= infoMatches + # kerning + kerning = self.font.kerning.asDict() + expectedKerning = reader.readKerning() + results["kerning"] = expectedKerning == kerning + # groups + groups = dict(self.font.groups) + expectedGroups = reader.readGroups() + results["groups"] = expectedGroups == groups + # features + features = self.font.features.text + expectedFeatures = reader.readFeatures() + results["features"] = expectedFeatures == features + # lib + lib = dict(self.font.lib) + expectedLib = reader.readLib() + results["lib"] = expectedLib == lib + return results + + def testFull(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + def testInfo(self): + self.setUpFont() + info = self.font.info + for attr, expectedValue in fontInfoVersion2.items(): + writtenValue = getattr(info, attr) + self.assertEqual((attr, expectedValue), (attr, writtenValue)) + self.tearDownFont() + + +class WriteUFOFormatVersion1TestCase(unittest.TestCase): + + def setUpFont(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + self.font = OpenFont(ufoPath2) + self.font.save(self.dstDir, formatVersion=1) + + def tearDownFont(self): + shutil.rmtree(self.dstDir) + + def compareToUFO(self): + readerExpected = UFOReader(ufoPath1) + readerWritten = UFOReader(self.dstDir) + results = {} + # info + matches = True + expectedPath = os.path.join(ufoPath1, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + if not os.path.exists(writtenPath): + matches = False + else: + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + for attr, expectedValue in expected.items(): + if expectedValue != written.get(attr): + matches = False + break + results["info"] = matches + # kerning + matches = True + expectedPath = os.path.join(ufoPath1, "kerning.plist") + writtenPath = os.path.join(self.dstDir, "kerning.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["kerning"] = matches + # groups + matches = True + expectedPath = os.path.join(ufoPath1, "groups.plist") + writtenPath = os.path.join(self.dstDir, "groups.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["groups"] = matches + # features + matches = True + expectedPath = os.path.join(ufoPath1, "features.fea") + writtenPath = os.path.join(self.dstDir, "features.fea") + if os.path.exists(writtenPath): + matches = False + results["features"] = matches + # lib + matches = True + expectedPath = os.path.join(ufoPath1, "lib.plist") + writtenPath = os.path.join(self.dstDir, "lib.plist") + if not os.path.exists(writtenPath): + matches = False + else: + writtenLib = readPlist(writtenPath) + matches = readPlist(expectedPath) == writtenLib + results["lib"] = matches + return results + + def testFull(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + +class WriteUFOFormatVersion2TestCase(unittest.TestCase): + + def setUpFont(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + self.font = OpenFont(ufoPath2) + self.font.save(self.dstDir) + + def tearDownFont(self): + shutil.rmtree(self.dstDir) + + def compareToUFO(self): + readerExpected = UFOReader(ufoPath2) + readerWritten = UFOReader(self.dstDir) + results = {} + # info + matches = True + expectedPath = os.path.join(ufoPath2, "fontinfo.plist") + writtenPath = os.path.join(self.dstDir, "fontinfo.plist") + if not os.path.exists(writtenPath): + matches = False + else: + expected = readPlist(expectedPath) + written = readPlist(writtenPath) + for attr, expectedValue in expected.items(): + if expectedValue != written[attr]: + matches = False + break + results["info"] = matches + # kerning + matches = True + expectedPath = os.path.join(ufoPath2, "kerning.plist") + writtenPath = os.path.join(self.dstDir, "kerning.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["kerning"] = matches + # groups + matches = True + expectedPath = os.path.join(ufoPath2, "groups.plist") + writtenPath = os.path.join(self.dstDir, "groups.plist") + if not os.path.exists(writtenPath): + matches = False + else: + matches = readPlist(expectedPath) == readPlist(writtenPath) + results["groups"] = matches + # features + matches = True + expectedPath = os.path.join(ufoPath2, "features.fea") + writtenPath = os.path.join(self.dstDir, "features.fea") + if not os.path.exists(writtenPath): + matches = False + else: + f = open(expectedPath, "r") + expectedText = f.read() + f.close() + f = open(writtenPath, "r") + writtenText = f.read() + f.close() + # FontLab likes to add lines to the features, so skip blank lines. + expectedText = [line for line in expectedText.splitlines() if line] + writtenText = [line for line in writtenText.splitlines() if line] + matches = "\n".join(expectedText) == "\n".join(writtenText) + results["features"] = matches + # lib + matches = True + expectedPath = os.path.join(ufoPath2, "lib.plist") + writtenPath = os.path.join(self.dstDir, "lib.plist") + if not os.path.exists(writtenPath): + matches = False + else: + writtenLib = readPlist(writtenPath) + matches = readPlist(expectedPath) == writtenLib + results["lib"] = matches + return results + + def testFull(self): + self.setUpFont() + otherResults = self.compareToUFO() + self.assertEqual(otherResults["info"], True) + self.assertEqual(otherResults["kerning"], True) + self.assertEqual(otherResults["groups"], True) + self.assertEqual(otherResults["features"], True) + self.assertEqual(otherResults["lib"], True) + self.tearDownFont() + + +if __name__ == "__main__": + from robofab.test.testSupport import runTests + runTests() diff --git a/Lib/robofab/test/test_objectsFL.py b/Lib/robofab/test/test_objectsFL.py index eddef3fa2..948897097 100755 --- a/Lib/robofab/test/test_objectsFL.py +++ b/Lib/robofab/test/test_objectsFL.py @@ -12,7 +12,7 @@ from robofab.world import NewFont from robofab.test.testSupport import getDemoFontPath, getDemoFontGlyphSetPath from robofab.tools.glifImport import importAllGlifFiles from robofab.pens.digestPen import DigestPointPen -from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter +from robofab.pens.adapterPens import SegmentToPointPen def getDigests(font): diff --git a/Lib/robofab/test/test_psHints.py b/Lib/robofab/test/test_psHints.py index 16315ee83..96a3f106b 100644 --- a/Lib/robofab/test/test_psHints.py +++ b/Lib/robofab/test/test_psHints.py @@ -102,34 +102,6 @@ def test(): >>> f["new"].psHints.asDict() == g.psHints.asDict() True - - # multiplication - >>> v = f.psHints * 2 - >>> v.asDict() == {'vStems': [1000, 20], 'blueFuzz': 2, 'blueShift': 2, 'forceBold': 2, 'blueScale': 1.0, 'hStems': [200, 180]} - True - - # division - >>> v = f.psHints / 2 - >>> v.asDict() == {'vStems': [250.0, 5.0], 'blueFuzz': 0.5, 'blueShift': 0.5, 'forceBold': 0.5, 'blueScale': 0.25, 'hStems': [50.0, 45.0]} - True - - # multiplication with x, y, factor - # note the h stems are multiplied by .5, the v stems (and blue values) are multiplied by 10 - >>> v = f.psHints * (.5, 10) - >>> v.asDict() == {'vStems': [5000, 100], 'blueFuzz': 10, 'blueShift': 10, 'forceBold': 0.5, 'blueScale': 5.0, 'hStems': [50.0, 45.0]} - True - - # multiplication with x, y, factor - # note the h stems are divided by .5, the v stems (and blue values) are divided by 10 - >>> v = f.psHints / (.5, 10) - >>> v.asDict() == {'vStems': [50.0, 1.0], 'blueFuzz': 0.10000000000000001, 'blueShift': 0.10000000000000001, 'forceBold': 2.0, 'blueScale': 0.050000000000000003, 'hStems': [200.0, 180.0]} - True - - >>> v = f.psHints * .333 - >>> v.round() - >>> v.asDict() == {'vStems': [167, 3], 'blueScale': 0.16650000000000001, 'hStems': [33, 30]} - True - """ if __name__ == "__main__": diff --git a/Lib/robofab/test/test_ufoLib.py b/Lib/robofab/test/test_ufoLib.py new file mode 100644 index 000000000..e1da807bb --- /dev/null +++ b/Lib/robofab/test/test_ufoLib.py @@ -0,0 +1,1659 @@ +import os +import shutil +import unittest +import tempfile +from plistlib import writePlist, readPlist +from robofab.ufoLib import UFOReader, UFOWriter, UFOLibError, \ + convertUFOFormatVersion1ToFormatVersion2, convertUFOFormatVersion2ToFormatVersion1 +from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion + + +# the format version 1 lib.plist contains some data +# that these tests shouldn't be concerned about. +removeFromFormatVersion1Lib = [ + "org.robofab.opentype.classes", + "org.robofab.opentype.features", + "org.robofab.opentype.featureorder", + "org.robofab.postScriptHintData" +] + + +class TestInfoObject(object): pass + + +class ReadFontInfoVersion1TestCase(unittest.TestCase): + + def setUp(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + metaInfo = { + "creator": "test", + "formatVersion": 1 + } + path = os.path.join(self.dstDir, "metainfo.plist") + writePlist(metaInfo, path) + + def tearDown(self): + shutil.rmtree(self.dstDir) + + def _writeInfoToPlist(self, info): + path = os.path.join(self.dstDir, "fontinfo.plist") + writePlist(info, path) + + def testRead(self): + originalData = dict(fontInfoVersion1) + self._writeInfoToPlist(originalData) + infoObject = TestInfoObject() + reader = UFOReader(self.dstDir) + reader.readInfo(infoObject) + for attr in dir(infoObject): + if attr not in fontInfoVersion2: + continue + originalValue = fontInfoVersion2[attr] + readValue = getattr(infoObject, attr) + self.assertEqual(originalValue, readValue) + + def testFontStyleConversion(self): + fontStyle1To2 = { + 64 : "regular", + 1 : "italic", + 32 : "bold", + 33 : "bold italic" + } + for old, new in fontStyle1To2.items(): + info = dict(fontInfoVersion1) + info["fontStyle"] = old + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + infoObject = TestInfoObject() + reader.readInfo(infoObject) + self.assertEqual(new, infoObject.styleMapStyleName) + + def testWidthNameConversion(self): + widthName1To2 = { + "Ultra-condensed" : 1, + "Extra-condensed" : 2, + "Condensed" : 3, + "Semi-condensed" : 4, + "Medium (normal)" : 5, + "Semi-expanded" : 6, + "Expanded" : 7, + "Extra-expanded" : 8, + "Ultra-expanded" : 9 + } + for old, new in widthName1To2.items(): + info = dict(fontInfoVersion1) + info["widthName"] = old + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + infoObject = TestInfoObject() + reader.readInfo(infoObject) + self.assertEqual(new, infoObject.openTypeOS2WidthClass) + + +class ReadFontInfoVersion2TestCase(unittest.TestCase): + + def setUp(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + metaInfo = { + "creator": "test", + "formatVersion": 2 + } + path = os.path.join(self.dstDir, "metainfo.plist") + writePlist(metaInfo, path) + + def tearDown(self): + shutil.rmtree(self.dstDir) + + def _writeInfoToPlist(self, info): + path = os.path.join(self.dstDir, "fontinfo.plist") + writePlist(info, path) + + def testRead(self): + originalData = dict(fontInfoVersion2) + self._writeInfoToPlist(originalData) + infoObject = TestInfoObject() + reader = UFOReader(self.dstDir) + reader.readInfo(infoObject) + readData = {} + for attr in fontInfoVersion2.keys(): + readData[attr] = getattr(infoObject, attr) + self.assertEqual(originalData, readData) + + def testGenericRead(self): + # familyName + info = dict(fontInfoVersion2) + info["familyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # styleName + info = dict(fontInfoVersion2) + info["styleName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # styleMapFamilyName + info = dict(fontInfoVersion2) + info["styleMapFamilyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # styleMapStyleName + ## not a string + info = dict(fontInfoVersion2) + info["styleMapStyleName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info = dict(fontInfoVersion2) + info["styleMapStyleName"] = "REGULAR" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # versionMajor + info = dict(fontInfoVersion2) + info["versionMajor"] = "1" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # versionMinor + info = dict(fontInfoVersion2) + info["versionMinor"] = "0" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # copyright + info = dict(fontInfoVersion2) + info["copyright"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # trademark + info = dict(fontInfoVersion2) + info["trademark"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # unitsPerEm + info = dict(fontInfoVersion2) + info["unitsPerEm"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # descender + info = dict(fontInfoVersion2) + info["descender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # xHeight + info = dict(fontInfoVersion2) + info["xHeight"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # capHeight + info = dict(fontInfoVersion2) + info["capHeight"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # ascender + info = dict(fontInfoVersion2) + info["ascender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # italicAngle + info = dict(fontInfoVersion2) + info["italicAngle"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testHeadRead(self): + # openTypeHeadCreated + ## not a string + info = dict(fontInfoVersion2) + info["openTypeHeadCreated"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## invalid format + info = dict(fontInfoVersion2) + info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHeadLowestRecPPEM + info = dict(fontInfoVersion2) + info["openTypeHeadLowestRecPPEM"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHeadFlags + info = dict(fontInfoVersion2) + info["openTypeHeadFlags"] = [-1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testHheaRead(self): + # openTypeHheaAscender + info = dict(fontInfoVersion2) + info["openTypeHheaAscender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHheaDescender + info = dict(fontInfoVersion2) + info["openTypeHheaDescender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHheaLineGap + info = dict(fontInfoVersion2) + info["openTypeHheaLineGap"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHheaCaretSlopeRise + info = dict(fontInfoVersion2) + info["openTypeHheaCaretSlopeRise"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHheaCaretSlopeRun + info = dict(fontInfoVersion2) + info["openTypeHheaCaretSlopeRun"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeHheaCaretOffset + info = dict(fontInfoVersion2) + info["openTypeHheaCaretOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testNameRead(self): + # openTypeNameDesigner + info = dict(fontInfoVersion2) + info["openTypeNameDesigner"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameDesignerURL + info = dict(fontInfoVersion2) + info["openTypeNameDesignerURL"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameManufacturer + info = dict(fontInfoVersion2) + info["openTypeNameManufacturer"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameManufacturerURL + info = dict(fontInfoVersion2) + info["openTypeNameManufacturerURL"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameLicense + info = dict(fontInfoVersion2) + info["openTypeNameLicense"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameLicenseURL + info = dict(fontInfoVersion2) + info["openTypeNameLicenseURL"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameVersion + info = dict(fontInfoVersion2) + info["openTypeNameVersion"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameUniqueID + info = dict(fontInfoVersion2) + info["openTypeNameUniqueID"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameDescription + info = dict(fontInfoVersion2) + info["openTypeNameDescription"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNamePreferredFamilyName + info = dict(fontInfoVersion2) + info["openTypeNamePreferredFamilyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNamePreferredSubfamilyName + info = dict(fontInfoVersion2) + info["openTypeNamePreferredSubfamilyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameCompatibleFullName + info = dict(fontInfoVersion2) + info["openTypeNameCompatibleFullName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameSampleText + info = dict(fontInfoVersion2) + info["openTypeNameSampleText"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameWWSFamilyName + info = dict(fontInfoVersion2) + info["openTypeNameWWSFamilyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeNameWWSSubfamilyName + info = dict(fontInfoVersion2) + info["openTypeNameWWSSubfamilyName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testOS2Read(self): + # openTypeOS2WidthClass + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2WidthClass"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out or range + info = dict(fontInfoVersion2) + info["openTypeOS2WidthClass"] = 15 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2WeightClass + info = dict(fontInfoVersion2) + ## not an int + info["openTypeOS2WeightClass"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info["openTypeOS2WeightClass"] = -50 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2Selection + info = dict(fontInfoVersion2) + info["openTypeOS2Selection"] = [-1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2VendorID + info = dict(fontInfoVersion2) + info["openTypeOS2VendorID"] = 1234 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2Panose + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## too few values + info = dict(fontInfoVersion2) + info["openTypeOS2Panose"] = [0, 1, 2, 3] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2FamilyClass + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2FamilyClass"] = [1, str(1)] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## too few values + info = dict(fontInfoVersion2) + info["openTypeOS2FamilyClass"] = [1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["openTypeOS2FamilyClass"] = [1, 1, 1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info = dict(fontInfoVersion2) + info["openTypeOS2FamilyClass"] = [1, 201] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2UnicodeRanges + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2UnicodeRanges"] = ["0"] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info = dict(fontInfoVersion2) + info["openTypeOS2UnicodeRanges"] = [-1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2CodePageRanges + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2CodePageRanges"] = ["0"] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info = dict(fontInfoVersion2) + info["openTypeOS2CodePageRanges"] = [-1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2TypoAscender + info = dict(fontInfoVersion2) + info["openTypeOS2TypoAscender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2TypoDescender + info = dict(fontInfoVersion2) + info["openTypeOS2TypoDescender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2TypoLineGap + info = dict(fontInfoVersion2) + info["openTypeOS2TypoLineGap"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2WinAscent + info = dict(fontInfoVersion2) + info["openTypeOS2WinAscent"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2WinDescent + info = dict(fontInfoVersion2) + info["openTypeOS2WinDescent"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2Type + ## not an int + info = dict(fontInfoVersion2) + info["openTypeOS2Type"] = ["1"] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + ## out of range + info = dict(fontInfoVersion2) + info["openTypeOS2Type"] = [-1] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SubscriptXSize + info = dict(fontInfoVersion2) + info["openTypeOS2SubscriptXSize"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SubscriptYSize + info = dict(fontInfoVersion2) + info["openTypeOS2SubscriptYSize"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SubscriptXOffset + info = dict(fontInfoVersion2) + info["openTypeOS2SubscriptXOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SubscriptYOffset + info = dict(fontInfoVersion2) + info["openTypeOS2SubscriptYOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SuperscriptXSize + info = dict(fontInfoVersion2) + info["openTypeOS2SuperscriptXSize"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SuperscriptYSize + info = dict(fontInfoVersion2) + info["openTypeOS2SuperscriptYSize"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SuperscriptXOffset + info = dict(fontInfoVersion2) + info["openTypeOS2SuperscriptXOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2SuperscriptYOffset + info = dict(fontInfoVersion2) + info["openTypeOS2SuperscriptYOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2StrikeoutSize + info = dict(fontInfoVersion2) + info["openTypeOS2StrikeoutSize"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeOS2StrikeoutPosition + info = dict(fontInfoVersion2) + info["openTypeOS2StrikeoutPosition"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testVheaRead(self): + # openTypeVheaVertTypoAscender + info = dict(fontInfoVersion2) + info["openTypeVheaVertTypoAscender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeVheaVertTypoDescender + info = dict(fontInfoVersion2) + info["openTypeVheaVertTypoDescender"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeVheaVertTypoLineGap + info = dict(fontInfoVersion2) + info["openTypeVheaVertTypoLineGap"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeVheaCaretSlopeRise + info = dict(fontInfoVersion2) + info["openTypeVheaCaretSlopeRise"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeVheaCaretSlopeRun + info = dict(fontInfoVersion2) + info["openTypeVheaCaretSlopeRun"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # openTypeVheaCaretOffset + info = dict(fontInfoVersion2) + info["openTypeVheaCaretOffset"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testFONDRead(self): + # macintoshFONDFamilyID + info = dict(fontInfoVersion2) + info["macintoshFONDFamilyID"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # macintoshFONDName + info = dict(fontInfoVersion2) + info["macintoshFONDName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + + def testPostscriptRead(self): + # postscriptFontName + info = dict(fontInfoVersion2) + info["postscriptFontName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # postscriptFullName + info = dict(fontInfoVersion2) + info["postscriptFullName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # postscriptSlantAngle + info = dict(fontInfoVersion2) + info["postscriptSlantAngle"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject()) + # postscriptUniqueID + info = dict(fontInfoVersion2) + info["postscriptUniqueID"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptUnderlineThickness + info = dict(fontInfoVersion2) + info["postscriptUnderlineThickness"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptUnderlinePosition + info = dict(fontInfoVersion2) + info["postscriptUnderlinePosition"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptIsFixedPitch + info = dict(fontInfoVersion2) + info["postscriptIsFixedPitch"] = 2 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptBlueValues + ## not a list + info = dict(fontInfoVersion2) + info["postscriptBlueValues"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## uneven value count + info = dict(fontInfoVersion2) + info["postscriptBlueValues"] = [500] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptBlueValues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptOtherBlues + ## not a list + info = dict(fontInfoVersion2) + info["postscriptOtherBlues"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## uneven value count + info = dict(fontInfoVersion2) + info["postscriptOtherBlues"] = [500] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptFamilyBlues + ## not a list + info = dict(fontInfoVersion2) + info["postscriptFamilyBlues"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## uneven value count + info = dict(fontInfoVersion2) + info["postscriptFamilyBlues"] = [500] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptFamilyBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptFamilyOtherBlues + ## not a list + info = dict(fontInfoVersion2) + info["postscriptFamilyOtherBlues"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## uneven value count + info = dict(fontInfoVersion2) + info["postscriptFamilyOtherBlues"] = [500] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptFamilyOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptStemSnapH + ## not list + info = dict(fontInfoVersion2) + info["postscriptStemSnapH"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptStemSnapH"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptStemSnapV + ## not list + info = dict(fontInfoVersion2) + info["postscriptStemSnapV"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + ## too many values + info = dict(fontInfoVersion2) + info["postscriptStemSnapV"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptBlueFuzz + info = dict(fontInfoVersion2) + info["postscriptBlueFuzz"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptBlueShift + info = dict(fontInfoVersion2) + info["postscriptBlueShift"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptBlueScale + info = dict(fontInfoVersion2) + info["postscriptBlueScale"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptForceBold + info = dict(fontInfoVersion2) + info["postscriptForceBold"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptDefaultWidthX + info = dict(fontInfoVersion2) + info["postscriptDefaultWidthX"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptNominalWidthX + info = dict(fontInfoVersion2) + info["postscriptNominalWidthX"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptWeightName + info = dict(fontInfoVersion2) + info["postscriptWeightName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptDefaultCharacter + info = dict(fontInfoVersion2) + info["postscriptDefaultCharacter"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # postscriptWindowsCharacterSet + info = dict(fontInfoVersion2) + info["postscriptWindowsCharacterSet"] = -1 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # macintoshFONDFamilyID + info = dict(fontInfoVersion2) + info["macintoshFONDFamilyID"] = "abc" + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + # macintoshFONDName + info = dict(fontInfoVersion2) + info["macintoshFONDName"] = 123 + self._writeInfoToPlist(info) + reader = UFOReader(self.dstDir) + self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject()) + + +class WriteFontInfoVersion1TestCase(unittest.TestCase): + + def setUp(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + + def tearDown(self): + shutil.rmtree(self.dstDir) + + def makeInfoObject(self): + infoObject = TestInfoObject() + for attr, value in fontInfoVersion2.items(): + setattr(infoObject, attr, value) + return infoObject + + def readPlist(self): + path = os.path.join(self.dstDir, "fontinfo.plist") + return readPlist(path) + + def testWrite(self): + infoObject = self.makeInfoObject() + writer = UFOWriter(self.dstDir, formatVersion=1) + writer.writeInfo(infoObject) + writtenData = self.readPlist() + for attr, originalValue in fontInfoVersion1.items(): + newValue = writtenData[attr] + self.assertEqual(newValue, originalValue) + + def testFontStyleConversion(self): + fontStyle1To2 = { + 64 : "regular", + 1 : "italic", + 32 : "bold", + 33 : "bold italic" + } + for old, new in fontStyle1To2.items(): + infoObject = self.makeInfoObject() + infoObject.styleMapStyleName = new + writer = UFOWriter(self.dstDir, formatVersion=1) + writer.writeInfo(infoObject) + writtenData = self.readPlist() + self.assertEqual(writtenData["fontStyle"], old) + + def testWidthNameConversion(self): + widthName1To2 = { + "Ultra-condensed" : 1, + "Extra-condensed" : 2, + "Condensed" : 3, + "Semi-condensed" : 4, + "Medium (normal)" : 5, + "Semi-expanded" : 6, + "Expanded" : 7, + "Extra-expanded" : 8, + "Ultra-expanded" : 9 + } + for old, new in widthName1To2.items(): + infoObject = self.makeInfoObject() + infoObject.openTypeOS2WidthClass = new + writer = UFOWriter(self.dstDir, formatVersion=1) + writer.writeInfo(infoObject) + writtenData = self.readPlist() + self.assertEqual(writtenData["widthName"], old) + + +class WriteFontInfoVersion2TestCase(unittest.TestCase): + + def setUp(self): + self.dstDir = tempfile.mktemp() + os.mkdir(self.dstDir) + + def tearDown(self): + shutil.rmtree(self.dstDir) + + def makeInfoObject(self): + infoObject = TestInfoObject() + for attr, value in fontInfoVersion2.items(): + setattr(infoObject, attr, value) + return infoObject + + def readPlist(self): + path = os.path.join(self.dstDir, "fontinfo.plist") + return readPlist(path) + + def testWrite(self): + infoObject = self.makeInfoObject() + writer = UFOWriter(self.dstDir) + writer.writeInfo(infoObject) + writtenData = self.readPlist() + for attr, originalValue in fontInfoVersion2.items(): + newValue = writtenData[attr] + self.assertEqual(newValue, originalValue) + + def testGenericWrite(self): + # familyName + infoObject = self.makeInfoObject() + infoObject.familyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # styleName + infoObject = self.makeInfoObject() + infoObject.styleName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # styleMapFamilyName + infoObject = self.makeInfoObject() + infoObject.styleMapFamilyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # styleMapStyleName + ## not a string + infoObject = self.makeInfoObject() + infoObject.styleMapStyleName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject = self.makeInfoObject() + infoObject.styleMapStyleName = "REGULAR" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # versionMajor + infoObject = self.makeInfoObject() + infoObject.versionMajor = "1" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # versionMinor + infoObject = self.makeInfoObject() + infoObject.versionMinor = "0" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # copyright + infoObject = self.makeInfoObject() + infoObject.copyright = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # trademark + infoObject = self.makeInfoObject() + infoObject.trademark = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # unitsPerEm + infoObject = self.makeInfoObject() + infoObject.unitsPerEm = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # descender + infoObject = self.makeInfoObject() + infoObject.descender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # xHeight + infoObject = self.makeInfoObject() + infoObject.xHeight = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # capHeight + infoObject = self.makeInfoObject() + infoObject.capHeight = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # ascender + infoObject = self.makeInfoObject() + infoObject.ascender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # italicAngle + infoObject = self.makeInfoObject() + infoObject.italicAngle = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testHeadWrite(self): + # openTypeHeadCreated + ## not a string + infoObject = self.makeInfoObject() + infoObject.openTypeHeadCreated = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## invalid format + infoObject = self.makeInfoObject() + infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHeadLowestRecPPEM + infoObject = self.makeInfoObject() + infoObject.openTypeHeadLowestRecPPEM = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHeadFlags + infoObject = self.makeInfoObject() + infoObject.openTypeHeadFlags = [-1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testHheaWrite(self): + # openTypeHheaAscender + infoObject = self.makeInfoObject() + infoObject.openTypeHheaAscender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHheaDescender + infoObject = self.makeInfoObject() + infoObject.openTypeHheaDescender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHheaLineGap + infoObject = self.makeInfoObject() + infoObject.openTypeHheaLineGap = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHheaCaretSlopeRise + infoObject = self.makeInfoObject() + infoObject.openTypeHheaCaretSlopeRise = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHheaCaretSlopeRun + infoObject = self.makeInfoObject() + infoObject.openTypeHheaCaretSlopeRun = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeHheaCaretOffset + infoObject = self.makeInfoObject() + infoObject.openTypeHheaCaretOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testNameWrite(self): + # openTypeNameDesigner + infoObject = self.makeInfoObject() + infoObject.openTypeNameDesigner = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameDesignerURL + infoObject = self.makeInfoObject() + infoObject.openTypeNameDesignerURL = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameManufacturer + infoObject = self.makeInfoObject() + infoObject.openTypeNameManufacturer = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameManufacturerURL + infoObject = self.makeInfoObject() + infoObject.openTypeNameManufacturerURL = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameLicense + infoObject = self.makeInfoObject() + infoObject.openTypeNameLicense = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameLicenseURL + infoObject = self.makeInfoObject() + infoObject.openTypeNameLicenseURL = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameVersion + infoObject = self.makeInfoObject() + infoObject.openTypeNameVersion = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameUniqueID + infoObject = self.makeInfoObject() + infoObject.openTypeNameUniqueID = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameDescription + infoObject = self.makeInfoObject() + infoObject.openTypeNameDescription = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNamePreferredFamilyName + infoObject = self.makeInfoObject() + infoObject.openTypeNamePreferredFamilyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNamePreferredSubfamilyName + infoObject = self.makeInfoObject() + infoObject.openTypeNamePreferredSubfamilyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameCompatibleFullName + infoObject = self.makeInfoObject() + infoObject.openTypeNameCompatibleFullName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameSampleText + infoObject = self.makeInfoObject() + infoObject.openTypeNameSampleText = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameWWSFamilyName + infoObject = self.makeInfoObject() + infoObject.openTypeNameWWSFamilyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeNameWWSSubfamilyName + infoObject = self.makeInfoObject() + infoObject.openTypeNameWWSSubfamilyName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testOS2Write(self): + # openTypeOS2WidthClass + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2WidthClass = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out or range + infoObject = self.makeInfoObject() + infoObject.openTypeOS2WidthClass = 15 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2WeightClass + infoObject = self.makeInfoObject() + ## not an int + infoObject.openTypeOS2WeightClass = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject.openTypeOS2WeightClass = -50 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2Selection + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Selection = [-1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2VendorID + infoObject = self.makeInfoObject() + infoObject.openTypeOS2VendorID = 1234 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2Panose + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too few values + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Panose = [0, 1, 2, 3] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2FamilyClass + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2FamilyClass = [0, str(1)] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too few values + infoObject = self.makeInfoObject() + infoObject.openTypeOS2FamilyClass = [1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.openTypeOS2FamilyClass = [1, 1, 1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject = self.makeInfoObject() + infoObject.openTypeOS2FamilyClass = [1, 20] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2UnicodeRanges + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2UnicodeRanges = ["0"] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject = self.makeInfoObject() + infoObject.openTypeOS2UnicodeRanges = [-1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2CodePageRanges + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2CodePageRanges = ["0"] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject = self.makeInfoObject() + infoObject.openTypeOS2CodePageRanges = [-1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2TypoAscender + infoObject = self.makeInfoObject() + infoObject.openTypeOS2TypoAscender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2TypoDescender + infoObject = self.makeInfoObject() + infoObject.openTypeOS2TypoDescender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2TypoLineGap + infoObject = self.makeInfoObject() + infoObject.openTypeOS2TypoLineGap = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2WinAscent + infoObject = self.makeInfoObject() + infoObject.openTypeOS2WinAscent = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2WinDescent + infoObject = self.makeInfoObject() + infoObject.openTypeOS2WinDescent = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2Type + ## not an int + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Type = ["1"] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## out of range + infoObject = self.makeInfoObject() + infoObject.openTypeOS2Type = [-1] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SubscriptXSize + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SubscriptXSize = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SubscriptYSize + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SubscriptYSize = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SubscriptXOffset + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SubscriptXOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SubscriptYOffset + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SubscriptYOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SuperscriptXSize + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SuperscriptXSize = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SuperscriptYSize + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SuperscriptYSize = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SuperscriptXOffset + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SuperscriptXOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2SuperscriptYOffset + infoObject = self.makeInfoObject() + infoObject.openTypeOS2SuperscriptYOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2StrikeoutSize + infoObject = self.makeInfoObject() + infoObject.openTypeOS2StrikeoutSize = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeOS2StrikeoutPosition + infoObject = self.makeInfoObject() + infoObject.openTypeOS2StrikeoutPosition = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testVheaWrite(self): + # openTypeVheaVertTypoAscender + infoObject = self.makeInfoObject() + infoObject.openTypeVheaVertTypoAscender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeVheaVertTypoDescender + infoObject = self.makeInfoObject() + infoObject.openTypeVheaVertTypoDescender = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeVheaVertTypoLineGap + infoObject = self.makeInfoObject() + infoObject.openTypeVheaVertTypoLineGap = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeVheaCaretSlopeRise + infoObject = self.makeInfoObject() + infoObject.openTypeVheaCaretSlopeRise = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeVheaCaretSlopeRun + infoObject = self.makeInfoObject() + infoObject.openTypeVheaCaretSlopeRun = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # openTypeVheaCaretOffset + infoObject = self.makeInfoObject() + infoObject.openTypeVheaCaretOffset = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testFONDWrite(self): + # macintoshFONDFamilyID + infoObject = self.makeInfoObject() + infoObject.macintoshFONDFamilyID = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # macintoshFONDName + infoObject = self.makeInfoObject() + infoObject.macintoshFONDName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + def testPostscriptWrite(self): + # postscriptFontName + infoObject = self.makeInfoObject() + infoObject.postscriptFontName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptFullName + infoObject = self.makeInfoObject() + infoObject.postscriptFullName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptSlantAngle + infoObject = self.makeInfoObject() + infoObject.postscriptSlantAngle = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptUniqueID + infoObject = self.makeInfoObject() + infoObject.postscriptUniqueID = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptUnderlineThickness + infoObject = self.makeInfoObject() + infoObject.postscriptUnderlineThickness = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptUnderlinePosition + infoObject = self.makeInfoObject() + infoObject.postscriptUnderlinePosition = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptIsFixedPitch + infoObject = self.makeInfoObject() + infoObject.postscriptIsFixedPitch = 2 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptBlueValues + ## not a list + infoObject = self.makeInfoObject() + infoObject.postscriptBlueValues = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## uneven value count + infoObject = self.makeInfoObject() + infoObject.postscriptBlueValues = [500] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptBlueValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptOtherBlues + ## not a list + infoObject = self.makeInfoObject() + infoObject.postscriptOtherBlues = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## uneven value count + infoObject = self.makeInfoObject() + infoObject.postscriptOtherBlues = [500] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptFamilyBlues + ## not a list + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyBlues = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## uneven value count + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyBlues = [500] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptFamilyOtherBlues + ## not a list + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyOtherBlues = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## uneven value count + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyOtherBlues = [500] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptFamilyOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptStemSnapH + ## not list + infoObject = self.makeInfoObject() + infoObject.postscriptStemSnapH = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptStemSnapH = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptStemSnapV + ## not list + infoObject = self.makeInfoObject() + infoObject.postscriptStemSnapV = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + ## too many values + infoObject = self.makeInfoObject() + infoObject.postscriptStemSnapV = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptBlueFuzz + infoObject = self.makeInfoObject() + infoObject.postscriptBlueFuzz = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptBlueShift + infoObject = self.makeInfoObject() + infoObject.postscriptBlueShift = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptBlueScale + infoObject = self.makeInfoObject() + infoObject.postscriptBlueScale = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptForceBold + infoObject = self.makeInfoObject() + infoObject.postscriptForceBold = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptDefaultWidthX + infoObject = self.makeInfoObject() + infoObject.postscriptDefaultWidthX = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptNominalWidthX + infoObject = self.makeInfoObject() + infoObject.postscriptNominalWidthX = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptWeightName + infoObject = self.makeInfoObject() + infoObject.postscriptWeightName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptDefaultCharacter + infoObject = self.makeInfoObject() + infoObject.postscriptDefaultCharacter = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # postscriptWindowsCharacterSet + infoObject = self.makeInfoObject() + infoObject.postscriptWindowsCharacterSet = -1 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # macintoshFONDFamilyID + infoObject = self.makeInfoObject() + infoObject.macintoshFONDFamilyID = "abc" + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + # macintoshFONDName + infoObject = self.makeInfoObject() + infoObject.macintoshFONDName = 123 + writer = UFOWriter(self.dstDir) + self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject) + + + + +class ConversionFunctionsTestCase(unittest.TestCase): + + def tearDown(self): + path = self.getFontPath("TestFont1 (UFO1) converted.ufo") + if os.path.exists(path): + shutil.rmtree(path) + path = self.getFontPath("TestFont1 (UFO2) converted.ufo") + if os.path.exists(path): + shutil.rmtree(path) + + def getFontPath(self, fileName): + import robofab + path = os.path.dirname(robofab.__file__) + path = os.path.dirname(path) + path = os.path.dirname(path) + path = os.path.join(path, "TestData", fileName) + return path + + def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures): + # result + metainfoPath1 = os.path.join(path1, "metainfo.plist") + fontinfoPath1 = os.path.join(path1, "fontinfo.plist") + kerningPath1 = os.path.join(path1, "kerning.plist") + groupsPath1 = os.path.join(path1, "groups.plist") + libPath1 = os.path.join(path1, "lib.plist") + featuresPath1 = os.path.join(path1, "features.plist") + glyphsPath1 = os.path.join(path1, "glyphs") + glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist") + glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif") + glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif") + # expected result + metainfoPath2 = os.path.join(path2, "metainfo.plist") + fontinfoPath2 = os.path.join(path2, "fontinfo.plist") + kerningPath2 = os.path.join(path2, "kerning.plist") + groupsPath2 = os.path.join(path2, "groups.plist") + libPath2 = os.path.join(path2, "lib.plist") + featuresPath2 = os.path.join(path2, "features.plist") + glyphsPath2 = os.path.join(path2, "glyphs") + glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist") + glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif") + glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif") + # look for existence + self.assertEqual(os.path.exists(metainfoPath1), True) + self.assertEqual(os.path.exists(fontinfoPath1), True) + self.assertEqual(os.path.exists(kerningPath1), True) + self.assertEqual(os.path.exists(groupsPath1), True) + self.assertEqual(os.path.exists(libPath1), True) + self.assertEqual(os.path.exists(glyphsPath1), True) + self.assertEqual(os.path.exists(glyphsPath1_contents), True) + self.assertEqual(os.path.exists(glyphsPath1_A), True) + self.assertEqual(os.path.exists(glyphsPath1_B), True) + if testFeatures: + self.assertEqual(os.path.exists(featuresPath1), True) + # look for aggrement + data1 = readPlist(metainfoPath1) + data2 = readPlist(metainfoPath2) + self.assertEqual(data1, data2) + data1 = readPlist(fontinfoPath1) + self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items())) + data1 = readPlist(kerningPath1) + data2 = readPlist(kerningPath2) + self.assertEqual(data1, data2) + data1 = readPlist(groupsPath1) + data2 = readPlist(groupsPath2) + self.assertEqual(data1, data2) + data1 = readPlist(libPath1) + data2 = readPlist(libPath2) + if "UFO1" in libPath1: + for key in removeFromFormatVersion1Lib: + if key in data1: + del data1[key] + if "UFO1" in libPath2: + for key in removeFromFormatVersion1Lib: + if key in data2: + del data2[key] + self.assertEqual(data1, data2) + data1 = readPlist(glyphsPath1_contents) + data2 = readPlist(glyphsPath2_contents) + self.assertEqual(data1, data2) + data1 = readPlist(glyphsPath1_A) + data2 = readPlist(glyphsPath2_A) + self.assertEqual(data1, data2) + data1 = readPlist(glyphsPath1_B) + data2 = readPlist(glyphsPath2_B) + self.assertEqual(data1, data2) + + def test1To2(self): + path1 = self.getFontPath("TestFont1 (UFO1).ufo") + path2 = self.getFontPath("TestFont1 (UFO1) converted.ufo") + path3 = self.getFontPath("TestFont1 (UFO2).ufo") + convertUFOFormatVersion1ToFormatVersion2(path1, path2) + self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False) + + def test2To1(self): + path1 = self.getFontPath("TestFont1 (UFO2).ufo") + path2 = self.getFontPath("TestFont1 (UFO2) converted.ufo") + path3 = self.getFontPath("TestFont1 (UFO1).ufo") + convertUFOFormatVersion2ToFormatVersion1(path1, path2) + self.compareFileStructures(path2, path3, expectedFontInfo2To1Conversion, False) + + +if __name__ == "__main__": + from robofab.test.testSupport import runTests + runTests() diff --git a/Lib/robofab/tools/fontlabFeatureSplitter.py b/Lib/robofab/tools/fontlabFeatureSplitter.py new file mode 100644 index 000000000..3e0173dfc --- /dev/null +++ b/Lib/robofab/tools/fontlabFeatureSplitter.py @@ -0,0 +1,85 @@ +import re + +featureRE = re.compile( + "^" # start of line + "\s*" # + "feature" # feature + "\s+" # + "(\w{4})" # four alphanumeric characters + "\s*" # + "\{" # { + , re.MULTILINE # run in multiline to preserve line seps +) + +def splitFeaturesForFontLab(text): + """ + >>> result = splitFeaturesForFontLab(testText) + >>> result == expectedTestResult + True + """ + classes = "" + features = [] + while text: + m = featureRE.search(text) + if m is None: + classes = text + text = "" + else: + start, end = m.span() + # if start is not zero, this is the first match + # and all previous lines are part of the "classes" + if start > 0: + assert not classes + classes = text[:start] + # extract the current feature + featureName = m.group(1) + featureText = text[start:end] + text = text[end:] + # grab all text before the next feature definition + # and add it to the current definition + if text: + m = featureRE.search(text) + if m is not None: + start, end = m.span() + featureText += text[:start] + text = text[start:] + else: + featureText += text + text = "" + # store the feature + features.append((featureName, featureText)) + return classes, features + +testText = """ +@class1 = [a b c d]; + +feature liga { + sub f i by fi; +} liga; + +@class2 = [x y z]; + +feature salt { + sub a by a.alt; +} salt; feature ss01 {sub x by x.alt} ss01; + +feature ss02 {sub y by y.alt} ss02; + +# feature calt { +# sub a b' by b.alt; +# } calt; +""" + +expectedTestResult = ( + "\n@class1 = [a b c d];\n", + [ + ("liga", "\nfeature liga {\n sub f i by fi;\n} liga;\n\n@class2 = [x y z];\n"), + ("salt", "\nfeature salt {\n sub a by a.alt;\n} salt; feature ss01 {sub x by x.alt} ss01;\n"), + ("ss02", "\nfeature ss02 {sub y by y.alt} ss02;\n\n# feature calt {\n# sub a b' by b.alt;\n# } calt;\n") + ] +) + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/Lib/robofab/tools/nameTable.py b/Lib/robofab/tools/nameTable.py deleted file mode 100644 index aeae39521..000000000 --- a/Lib/robofab/tools/nameTable.py +++ /dev/null @@ -1,709 +0,0 @@ -from warnings import warn -warn("FontLab contains a bug that renders nameTable.py inoperable", Warning) - -""" -XXX: FontLab 4.6 contains a bug that renders this module inoperable. - -A simple wrapper around the not so simple OpenType -name table API in FontLab. - -For more information about the name table see: -http://www.microsoft.com/typography/otspec/name.htm - -The PID, EID, LID and NID arguments in the various -methods can be integer values or string values (as -long as the string value matches the key in the lookup -dicts shown below). All values must be strings and all -platform line ending conversion is handled automatically -EXCEPT in the setSpecificRecord method. If you need to do -line ending conversion, the convertLineEndings method -is publicly available. -""" - -from robofab import RoboFabError - - ## - ## internal pid constants - ## - -UNI = 'unicode' -UNI_INT = 0 -MAC = 'macintosh' -MAC_INT = 1 -MS = 'microsoft' -MS_INT = 3 - - ## - ## lookup dicts - ## - -def _flipDict(aDict): - bDict = {} - for k, v in aDict.items(): - bDict[v] = k - return bDict - -pidName2Int = { - UNI : UNI_INT, - MAC : MAC_INT, - MS : MS_INT, - } - -nidName2Int = { - 'copyright' : 0, - 'familyName' : 1, - 'subfamilyName' : 2, - 'uniqueID' : 3, - 'fullName' : 4, - 'versionString' : 5, - 'postscriptName' : 6, - 'trademark' : 7, - 'manufacturer' : 8, - 'designer' : 9, - 'description' : 10, - 'vendorURL' : 11, - 'designerURL' : 12, - 'license' : 13, - 'licenseURL' : 14, - # ID 15 is reserved - 'preferredFamily' : 16, - 'preferredSubfamily' : 17, - 'compatibleFull' : 18, - 'sampleText' : 19, - 'postscriptCID' : 20 - } - -nidInt2Name = _flipDict(nidName2Int) - -uniEIDName2Int = { - "unicode_1.0" : 0, - "unicode_1.1" : 1, - "iso_10646:1993" : 2, - "unicode_2.0_bmp" : 3, - "unicode_2.0_full" : 4, - } - -uniEIDInt2Name = _flipDict(uniEIDName2Int) - -uniLIDName2Int = {} - -uniLIDInt2Name = _flipDict(uniLIDName2Int) - -msEIDName2Int = { - - 'symbol' : 0, - 'unicode_bmp_only' : 1, - 'shift_jis' : 2, - 'prc' : 3, - 'big5' : 4, - 'wansung' : 5, - 'johab' : 6, - # 7 is reserved - # 8 is reserved - # 9 is reserved - 'unicode_full_repertoire' : 7, - } - -msEIDInt2Name = _flipDict(msEIDName2Int) - -msLIDName2Int = { - # need to find a parsable file - } - -msLIDInt2Name = _flipDict(msLIDName2Int) - -macEIDName2Int = { - "roman" : 0, - "japanese" : 1, - "chinese" : 2, - "korean" : 3, - "arabic" : 4, - "hebrew" : 5, - "greek" : 6, - "russian" : 7, - "rsymbol" : 8, - "devanagari" : 9, - "gurmukhi" : 10, - "gujarati" : 11, - "oriya" : 12, - "bengali" : 13, - "tamil" : 14, - "telugu" : 15, - "kannada" : 16, - "malayalam" : 17, - "sinhalese" : 18, - "burmese" : 19, - "khmer" : 20, - "thai" : 21, - "laotian" : 22, - "georgian" : 23, - "armenian" : 24, - "chinese" : 25, - "tibetan" : 26, - "mongolian" : 27, - "geez" : 28, - "slavic" : 29, - "vietnamese" : 30, - "sindhi" : 31, - "uninterpreted" : 32, - } - -macEIDInt2Name = _flipDict(macEIDName2Int) - -macLIDName2Int = { - "english" : 0, - "french" : 1, - "german" : 2, - "italian" : 3, - "dutch" : 4, - "swedish" : 5, - "spanish" : 6, - "danish" : 7, - "portuguese" : 8, - "norwegian" : 9, - "hebrew" : 10, - "japanese" : 11, - "arabic" : 12, - "finnish" : 13, - "inuktitut" : 14, - "icelandic" : 15, - "maltese" : 16, - "turkish" : 17, - "croatian" : 18, - "chinese" : 19, - "urdu" : 20, - "hindi" : 21, - "thai" : 22, - "korean" : 23, - "lithuanian" : 24, - "polish" : 25, - "hungarian" : 26, - "estonian" : 27, - "latvian" : 28, - "sami" : 29, - "faroese" : 30, - "farsi_persian" : 31, - "russian" : 32, - "chinese" : 33, - "flemish" : 34, - "irish gaelic" : 35, - "albanian" : 36, - "romanian" : 37, - "czech" : 38, - "slovak" : 39, - "slovenian" : 40, - "yiddish" : 41, - "serbian" : 42, - "macedonian" : 43, - "bulgarian" : 44, - "ukrainian" : 45, - "byelorussian" : 46, - "uzbek" : 47, - "kazakh" : 48, - "azerbaijani_cyrillic" : 49, - "azerbaijani_arabic" : 50, - "armenian" : 51, - "georgian" : 52, - "moldavian" : 53, - "kirghiz" : 54, - "tajiki" : 55, - "turkmen" : 56, - "mongolian_mongolian" : 57, - "mongolian_cyrillic" : 58, - "pashto" : 59, - "kurdish" : 60, - "kashmiri" : 61, - "sindhi" : 62, - "tibetan" : 63, - "nepali" : 64, - "sanskrit" : 65, - "marathi" : 66, - "bengali" : 67, - "assamese" : 68, - "gujarati" : 69, - "punjabi" : 70, - "oriya" : 71, - "malayalam" : 72, - "kannada" : 73, - "tamil" : 74, - "telugu" : 75, - "sinhalese" : 76, - "burmese" : 77, - "khmer" : 78, - "lao" : 79, - "vietnamese" : 80, - "indonesian" : 81, - "tagalong" : 82, - "malay_roman" : 83, - "malay_arabic" : 84, - "amharic" : 85, - "tigrinya" : 86, - "galla" : 87, - "somali" : 88, - "swahili" : 89, - "kinyarwanda_ruanda" : 90, - "rundi" : 91, - "nyanja_chewa" : 92, - "malagasy" : 93, - "esperanto" : 94, - "welsh" : 128, - "basque" : 129, - "catalan" : 130, - "latin" : 131, - "quenchua" : 132, - "guarani" : 133, - "aymara" : 134, - "tatar" : 135, - "uighur" : 136, - "dzongkha" : 137, - "javanese_roman" : 138, - "sundanese_roman" : 139, - "galician" : 140, - "afrikaans" : 141, - "breton" : 142, - "scottish_gaelic" : 144, - "manx_gaelic" : 145, - "irish_gaelic" : 146, - "tongan" : 147, - "greek_polytonic" : 148, - "greenlandic" : 149, - "azerbaijani_roman" : 150, - } - -macLIDInt2Name = _flipDict(macLIDName2Int) - - ## - ## value converters - ## - -def _convertNID2Int(nid): - if isinstance(nid, int): - return nid - return nidName2Int[nid] - -def _convertPID2Int(pid): - if isinstance(pid, int): - return pid - return pidName2Int[pid] - -def _convertEID2Int(pid, eid): - if isinstance(eid, int): - return eid - pid = _convertPID2Int(pid) - if pid == UNI_INT: - return uniEIDName2Int[eid] - elif pid == MAC_INT: - return macEIDName2Int[eid] - elif pid == MS_INT: - return msEIDName2Int[eid] - -def _convertLID2Int(pid, lid): - if isinstance(lid, int): - return lid - pid = _convertPID2Int(pid) - if pid == UNI_INT: - return uniLIDName2Int[lid] - elif pid == MAC_INT: - return macLIDName2Int[lid] - elif pid == MS_INT: - return msLIDName2Int[lid] - -def _compareValues(v1, v2): - if isinstance(v1, str): - v1 = v1.replace('\r\n', '\n') - if isinstance(v2, str): - v2 = v2.replace('\r\n', '\n') - return v1 == v2 - -def convertLineEndings(text, convertToMS=False): - """convert the line endings in a given text string""" - if isinstance(text, str): - if convertToMS: - text = text.replace('\r\n', '\n') - text = text.replace('\n', '\r\n') - else: - text = text.replace('\r\n', '\n') - return text - - ## - ## main object - ## - -class NameTable(object): - - """ - An object that allows direct manipulation of the name table of a given font. - - For example: - - from robofab.world import CurrentFont - from robofab.tools.nameTable import NameTable - f = CurrentFont() - nt = NameTable(f) - # bluntly set all copyright records to a string - nt.copyright = "Copyright 2004 RoboFab" - # get a record - print nt.copyright - # set a specific record to a string - nt.setSpecificRecord(pid=1, eid=0, lid=0, nid=0, value="You Mac-Roman-English folks should know that this is Copyright 2004 RoboFab.") - # get a record again to show what happens - # when the records for a NID are not the same - print nt.copyright - # look at the code to see what else is possible - f.update() - """ - - def __init__(self, font): - self._object = font - self._pid_eid_lid = {} - self._records = {} - self._indexRef = {} - self._populate() - - def _populate(self): - # keys are tuples (pid, eid, lid, nid) - self._records = {} - # keys are tuples (pid, eid, lid, nid), values are indices - self._indexRef = {} - count = 0 - for record in self._object.naked().fontnames: - pid = record.pid - eid = record.eid - lid = record.lid - nid = record.nid - value = record.name - self._records[(pid, eid, lid, nid)] = value - self._indexRef[(pid, eid, lid, nid)] = count - count = count + 1 - - def addRecord(self, pid, eid, lid, nidDict=None): - """add a record. the optional nidDict is - a dictionary of NIDs and values. If no - nidDict is given, the method will make - an empty entry for ALL public NIDs.""" - if nidDict is None: - nidDict = dict.fromkeys(nidInt2Name.keys()) - for nid in nidDict.keys(): - nidDict[nid] = '' - pid = _convertPID2Int(pid) - eid = _convertEID2Int(pid, eid) - lid = _convertLID2Int(pid, lid) - self.removeLID(pid, eid, lid) - for nid, value in nidDict.items(): - nid = _convertNID2Int(nid) - self._setRecord(pid, eid, lid, nid, value) - - def removePID(self, pid): - """remove a PID entry""" - pid = _convertPID2Int(pid) - for _pid, _eid, _lid, _nid in self._records.keys(): - if pid == _pid: - self._removeRecord(_pid, _eid, _lid, _nid) - - def removeEID(self, pid, eid): - """remove an EID from a PID entry""" - pid = _convertPID2Int(pid) - eid = _convertEID2Int(pid, eid) - for _pid, _eid, _lid, _nid in self._records.keys(): - if pid == _pid and eid == _eid: - self._removeRecord(_pid, _eid, _lid, _nid) - - def removeLID(self, pid, eid, lid): - """remove a LID from a PID entry""" - pid = _convertPID2Int(pid) - eid = _convertEID2Int(pid, eid) - lid = _convertLID2Int(pid, lid) - for _pid, _eid, _lid, _nid in self._records.keys(): - if pid == _pid and eid == _eid and lid == _lid: - self._removeRecord(_pid, _eid, _lid, _nid) - - def removeNID(self, nid): - """remove a NID from ALL PID, EID and LID entries""" - nid = _convertNID2Int(nid) - for _pid, _eid, _lid, _nid in self._records.keys(): - if nid == _nid: - self._removeRecord(_pid, _eid, _lid, _nid) - - def setSpecificRecord(self, pid, eid, lid, nid, value): - """set a specific record based on the PID, EID, LID and NID - this method does not do platform lineending conversion""" - pid = _convertPID2Int(pid) - eid = _convertEID2Int(pid, eid) - lid = _convertLID2Int(pid, lid) - nid = _convertNID2Int(nid) - # id 18 is mac only, so it should - # not be set for other PIDs - if pid != MAC_INT and nid == 18: - raise RoboFabError, "NID 18 is Macintosh only" - self._setRecord(pid, eid, lid, nid, value) - - # - # interface to FL name records - # - - def _removeRecord(self, pid, eid, lid, nid): - # remove the record from the font - # note: this won't raise an error if the record doesn't exist - if self._indexRef.has_key((pid, eid, lid, nid)): - index = self._indexRef[(pid, eid, lid, nid)] - del self._object.naked().fontnames[index] - self._populate() - - def _setRecord(self, pid, eid, lid, nid, value): - # set a record in the font - if pid != MAC_INT and nid == 18: - # id 18 is mac only, so it should - # not be set for other PIDs - return - if pid == UNI_INT or pid == MAC_INT: - value = convertLineEndings(value, convertToMS=False) - if pid == MS_INT: - value = convertLineEndings(value, convertToMS=True) - self._removeRecord(pid, eid, lid, nid) - from FL import NameRecord - nr = NameRecord(nid, pid, eid, lid, value) - self._object.naked().fontnames.append(nr) - self._populate() - - def _setAllRecords(self, nid, value): - # set nid for all pid, eid and lid records - done = [] - for _pid, _eid, _lid, _nid in self._records.keys(): - if (_pid, _eid, _lid) not in done: - self._setRecord(_pid, _eid, _lid, nid, value) - done.append((_pid, _eid, _lid)) - - def _getAllRecords(self, nid): - # this retrieves all nid records and compares - # them. if the values are all the same, it returns - # the value. otherwise it returns a list of all values - # as tuples (pid, eid, lid, value). - found = [] - for (_pid, _eid, _lid, _nid), value in self._records.items(): - if nid == _nid: - found.append((_pid, _eid, _lid, value)) - isSame = True - compare = {} - for pid, eid, lid, value in found: - if compare == {}: - compare = value - continue - vC = _compareValues(compare, value) - if not vC: - isSame = False - if isSame: - found = found[0][-1] - found = convertLineEndings(found, convertToMS=False) - return found - - # - # attrs - # - - def _get_copyright(self): - nid = 0 - return self._getAllRecords(nid) - - def _set_copyright(self, value): - nid = 0 - self._setAllRecords(nid, value) - - copyright = property(_get_copyright, _set_copyright, doc="NID 0") - - def _get_familyName(self): - nid = 1 - return self._getAllRecords(nid) - - def _set_familyName(self, value): - nid = 1 - self._setAllRecords(nid, value) - - familyName = property(_get_familyName, _set_familyName, doc="NID 1") - - def _get_subfamilyName(self): - nid = 2 - return self._getAllRecords(nid) - - def _set_subfamilyName(self, value): - nid = 2 - self._setAllRecords(nid, value) - - subfamilyName = property(_get_subfamilyName, _set_subfamilyName, doc="NID 2") - - def _get_uniqueID(self): - nid = 3 - return self._getAllRecords(nid) - - def _set_uniqueID(self, value): - nid = 3 - self._setAllRecords(nid, value) - - uniqueID = property(_get_uniqueID, _set_uniqueID, doc="NID 3") - - def _get_fullName(self): - nid = 4 - return self._getAllRecords(nid) - - def _set_fullName(self, value): - nid = 4 - self._setAllRecords(nid, value) - - fullName = property(_get_fullName, _set_fullName, doc="NID 4") - - def _get_versionString(self): - nid = 5 - return self._getAllRecords(nid) - - def _set_versionString(self, value): - nid = 5 - self._setAllRecords(nid, value) - - versionString = property(_get_versionString, _set_versionString, doc="NID 5") - - def _get_postscriptName(self): - nid = 6 - return self._getAllRecords(nid) - - def _set_postscriptName(self, value): - nid = 6 - self._setAllRecords(nid, value) - - postscriptName = property(_get_postscriptName, _set_postscriptName, doc="NID 6") - - def _get_trademark(self): - nid = 7 - return self._getAllRecords(nid) - - def _set_trademark(self, value): - nid = 7 - self._setAllRecords(nid, value) - - trademark = property(_get_trademark, _set_trademark, doc="NID 7") - - def _get_manufacturer(self): - nid = 8 - return self._getAllRecords(nid) - - def _set_manufacturer(self, value): - nid = 8 - self._setAllRecords(nid, value) - - manufacturer = property(_get_manufacturer, _set_manufacturer, doc="NID 8") - - def _get_designer(self): - nid = 9 - return self._getAllRecords(nid) - - def _set_designer(self, value): - nid = 9 - self._setAllRecords(nid, value) - - designer = property(_get_designer, _set_designer, doc="NID 9") - - def _get_description(self): - nid = 10 - return self._getAllRecords(nid) - - def _set_description(self, value): - nid = 10 - self._setAllRecords(nid, value) - - description = property(_get_description, _set_description, doc="NID 10") - - def _get_vendorURL(self): - nid = 11 - return self._getAllRecords(nid) - - def _set_vendorURL(self, value): - nid = 11 - self._setAllRecords(nid, value) - - vendorURL = property(_get_vendorURL, _set_vendorURL, doc="NID 11") - - def _get_designerURL(self): - nid = 12 - return self._getAllRecords(nid) - - def _set_designerURL(self, value): - nid = 12 - self._setAllRecords(nid, value) - - designerURL = property(_get_designerURL, _set_designerURL, doc="NID 12") - - def _get_license(self): - nid = 13 - return self._getAllRecords(nid) - - def _set_license(self, value): - nid = 13 - self._setAllRecords(nid, value) - - license = property(_get_license, _set_license, doc="NID 13") - - def _get_licenseURL(self): - nid = 14 - return self._getAllRecords(nid) - - def _set_licenseURL(self, value): - nid = 14 - self._setAllRecords(nid, value) - - licenseURL = property(_get_licenseURL, _set_licenseURL, doc="NID 14") - - def _get_preferredFamily(self): - nid = 16 - return self._getAllRecords(nid) - - def _set_preferredFamily(self, value): - nid = 16 - self._setAllRecords(nid, value) - - preferredFamily = property(_get_preferredFamily, _set_preferredFamily, doc="NID 16") - - def _get_preferredSubfamily(self): - nid = 17 - return self._getAllRecords(nid) - - def _set_preferredSubfamily(self, value): - nid = 17 - self._setAllRecords(nid, value) - - preferredSubfamily = property(_get_preferredSubfamily, _set_preferredSubfamily, doc="NID 17") - - def _get_compatibleFull(self): - nid = 18 - return self._getAllRecords(nid) - - def _set_compatibleFull(self, value): - nid = 18 - self._setAllRecords(nid, value) - - compatibleFull = property(_get_compatibleFull, _set_compatibleFull, doc="NID 18") - - def _get_sampleText(self): - nid = 19 - return self._getAllRecords(nid) - - def _set_sampleText(self, value): - nid = 19 - self._setAllRecords(nid, value) - - sampleText = property(_get_sampleText, _set_sampleText, doc="NID 19") - - def _get_postscriptCID(self): - nid = 20 - return self._getAllRecords(nid) - - def _set_postscriptCID(self, value): - nid = 20 - self._setAllRecords(nid, value) - - postscriptCID = property(_get_postscriptCID, _set_postscriptCID, doc="NID 20") - - -if __name__ == "__main__": - from robofab.world import CurrentFont - f = CurrentFont() - nt = NameTable(f) - print nt.copyright - f.update() diff --git a/Lib/robofab/tools/toolsAll.py b/Lib/robofab/tools/toolsAll.py index 98292102f..fd2acf593 100755 --- a/Lib/robofab/tools/toolsAll.py +++ b/Lib/robofab/tools/toolsAll.py @@ -12,34 +12,6 @@ else: have_broken_macsupport = 0 -import robofab -from robofab.plistlib import readPlist, writePlist - -def readFoundrySettings(dstPath): - """read the foundry settings xml file and return a keyed dict.""" - fileName = os.path.basename(dstPath) - if not os.path.exists(dstPath): - import shutil - # hm -- a fresh install, make a new default settings file - print "RoboFab: creating a new foundry settings file at", dstPath - srcDir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__))), 'Data') - srcPath = os.path.join(srcDir, 'template_' + fileName) - shutil.copy(srcPath, dstPath) - return readPlist(dstPath) - -def getFoundrySetting(key, path): - """get a specific setting from the foundry settings xml file.""" - d = readFoundrySettings(path) - return d.get(key) - -writeFoundrySettings = writePlist - -def setFoundrySetting(key, value, dstPath): - """write a specific entry in the foundry settings xml file.""" - d = readFoundrySettings(dstPath) - d[key] = value - writeFoundrySettings(d, dstPath) - def readGlyphConstructions(): """read GlyphConstruction and turn it into a dict""" from robofab.tools.glyphConstruction import _glyphConstruction @@ -58,9 +30,6 @@ def readGlyphConstructions(): glyphConstructions[name] = build return glyphConstructions - - - # # # glyph.unicode: ttFont["cmap"].getcmap(3, 1) diff --git a/Lib/robofab/ufoLib.py b/Lib/robofab/ufoLib.py index ee005c2bc..8a0683785 100755 --- a/Lib/robofab/ufoLib.py +++ b/Lib/robofab/ufoLib.py @@ -1,164 +1,288 @@ """" A library for importing .ufo files and their descendants. -This library works with robofab objects. Using the magic of the -U.F.O., common attributes are exported to and read from .plist files. +Refer to http://unifiedfontobject.com for the UFO specification. -It contains two very simple classes for reading and writing the -various components of the .ufo. Currently, the .ufo supports the -files detailed below. But, these files are not absolutely required. -If the a file is not included in the .ufo, it is implied that the data -of that file is empty. +The UFOReader and UFOWriter classes support versions 1 and 2 +of the specification. Up and down conversion functions are also +supplied in this library. These conversion functions are only +necessary if conversion without loading the UFO data into +a set of objects is desired. These functions are: + convertUFOFormatVersion1ToFormatVersion2 + convertUFOFormatVersion2ToFormatVersion1 -FontName.ufo/ - metainfo.plist # meta info about the .ufo bundle, most impartantly the - # format version number. - glyphs/ - contents.plist # a plist mapping all glyph names to file names - a.glif # a glif file - ...etc... - fontinfo.plist # font names, versions, copyright, dimentions, etc. - kerning.plist # kerning - lib.plist # user definable data - groups.plist # glyph group definitions +Two sets that list the font info attribute names for the two +fontinfo.plist formats are available for external use. These are: + fontInfoAttributesVersion1 + fontInfoAttributesVersion2 + +A set listing the fontinfo.plist attributes that were deprecated +in version 2 is available for external use: + deprecatedFontInfoAttributesVersion2 + +A function, validateFontInfoVersion2ValueForAttribute, that does +some basic validation on values for a fontinfo.plist value is +available for external use. + +Two value conversion functions are availble for converting +fontinfo.plist values between the possible format versions. + convertFontInfoValueForAttributeFromVersion1ToVersion2 + convertFontInfoValueForAttributeFromVersion2ToVersion1 """ import os +import shutil from cStringIO import StringIO +import calendar from robofab.plistlib import readPlist, writePlist from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE +try: + set +except NameError: + from sets import Set as set -def writePlistAtomically(obj, path): - """Write a plist for 'obj' to 'path'. Do this sort of atomically, - making it harder to cause corrupt files, for example when writePlist - encounters an error halfway during write. Also: don't write out the - file if it would be identical to what's already there, meaning the - modification date won't get stomped when writing the same data. - """ - f = StringIO() - writePlist(obj, f) - data = f.getvalue() - if os.path.exists(path): - f = open(path, READ_MODE) - oldData = f.read() - f.close() - if data == oldData: - return - f = open(path, WRITE_MODE) - f.write(data) - f.close() - - -GLYPHS_DIRNAME = 'glyphs' -METAINFO_FILENAME = 'metainfo.plist' -FONTINFO_FILENAME = 'fontinfo.plist' -LIB_FILENAME = 'lib.plist' -GROUPS_FILENAME = 'groups.plist' -KERNING_FILENAME = 'kerning.plist' - - -fontInfoAttrs = [ - # XXX we need to document how these map to OTF 'name' table fields - 'familyName', - 'styleName', - 'fullName', - 'fontName', - 'menuName', - 'fontStyle', - 'note', - 'versionMajor', - 'versionMinor', - 'year', - 'copyright', - 'notice', - 'trademark', - 'license', - 'licenseURL', - 'createdBy', - 'designer', - 'designerURL', - 'vendorURL', - 'unitsPerEm', - 'ascender', - 'descender', - 'capHeight', - 'xHeight', - 'defaultWidth', - 'slantAngle', - 'italicAngle', - 'widthName', - 'weightName', - 'weightValue', - - # dubious format-specific fields - 'fondName', - 'otFamilyName', - 'otStyleName', - 'otMacName', - 'msCharSet', - 'fondID', - 'uniqueID', - 'ttVendor', - 'ttUniqueID', - 'ttVersion', +__all__ = [ + "makeUFOPath" + "UFOLibError", + "UFOReader", + "UFOWriter", + "convertUFOFormatVersion1ToFormatVersion2", + "convertUFOFormatVersion2ToFormatVersion1", + "fontInfoAttributesVersion1", + "fontInfoAttributesVersion2", + "deprecatedFontInfoAttributesVersion2", + "validateFontInfoVersion2ValueForAttribute", + "convertFontInfoValueForAttributeFromVersion1ToVersion2", + "convertFontInfoValueForAttributeFromVersion2ToVersion1" ] -def makeUFOPath(fontPath): - """return a .ufo pathname based on a .vfb pathname""" - dir, name = os.path.split(fontPath) - name = '.'.join([name.split('.')[0], 'ufo']) - return os.path.join(dir, name) +class UFOLibError(Exception): pass + + +# ---------- +# File Names +# ---------- + +GLYPHS_DIRNAME = "glyphs" +METAINFO_FILENAME = "metainfo.plist" +FONTINFO_FILENAME = "fontinfo.plist" +LIB_FILENAME = "lib.plist" +GROUPS_FILENAME = "groups.plist" +KERNING_FILENAME = "kerning.plist" +FEATURES_FILENAME = "features.fea" + +supportedUFOFormatVersions = [1, 2] + + +# --------------------------- +# Format Conversion Functions +# --------------------------- + + +def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None): + """ + Function for converting a version format 1 UFO + to version format 2. inPath should be a path + to a UFO. outPath is the path where the new UFO + should be written. If outPath is not given, the + inPath will be used and, therefore, the UFO will + be converted in place. Otherwise, if outPath is + specified, nothing must exist at that path. + """ + if outPath is None: + outPath = inPath + if inPath != outPath and os.path.exists(outPath): + raise UFOLibError("A file already exists at %s." % outPath) + # use a reader for loading most of the data + reader = UFOReader(inPath) + if reader.formatVersion == 2: + raise UFOLibError("The UFO at %s is already format version 2." % inPath) + groups = reader.readGroups() + kerning = reader.readKerning() + libData = reader.readLib() + # read the info data manually and convert + infoPath = os.path.join(inPath, FONTINFO_FILENAME) + if not os.path.exists(infoPath): + infoData = {} + else: + infoData = readPlist(infoPath) + infoData = _convertFontInfoDataVersion1ToVersion2(infoData) + # if the paths are the same, only need to change the + # fontinfo and meta info files. + infoPath = os.path.join(outPath, FONTINFO_FILENAME) + if inPath == outPath: + metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) + metaInfo = dict( + creator="org.robofab.ufoLib", + formatVersion=2 + ) + writePlistAtomically(metaInfo, metaInfoPath) + writePlistAtomically(infoData, infoPath) + # otherwise write everything. + else: + writer = UFOWriter(outPath) + writer.writeGroups(groups) + writer.writeKerning(kerning) + writer.writeLib(libData) + # write the info manually + writePlistAtomically(infoData, infoPath) + # copy the glyph tree + inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) + outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) + if os.path.exists(inGlyphs): + shutil.copytree(inGlyphs, outGlyphs) + +def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None): + """ + Function for converting a version format 2 UFO + to version format 1. inPath should be a path + to a UFO. outPath is the path where the new UFO + should be written. If outPath is not given, the + inPath will be used and, therefore, the UFO will + be converted in place. Otherwise, if outPath is + specified, nothing must exist at that path. + """ + if outPath is None: + outPath = inPath + if inPath != outPath and os.path.exists(outPath): + raise UFOLibError("A file already exists at %s." % outPath) + # use a reader for loading most of the data + reader = UFOReader(inPath) + if reader.formatVersion == 1: + raise UFOLibError("The UFO at %s is already format version 1." % inPath) + groups = reader.readGroups() + kerning = reader.readKerning() + libData = reader.readLib() + # read the info data manually and convert + infoPath = os.path.join(inPath, FONTINFO_FILENAME) + if not os.path.exists(infoPath): + infoData = {} + else: + infoData = readPlist(infoPath) + infoData = _convertFontInfoDataVersion2ToVersion1(infoData) + # if the paths are the same, only need to change the + # fontinfo, metainfo and feature files. + infoPath = os.path.join(outPath, FONTINFO_FILENAME) + if inPath == outPath: + metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) + metaInfo = dict( + creator="org.robofab.ufoLib", + formatVersion=1 + ) + writePlistAtomically(metaInfo, metaInfoPath) + writePlistAtomically(infoData, infoPath) + featuresPath = os.path.join(inPath, FEATURES_FILENAME) + if os.path.exists(featuresPath): + os.remove(featuresPath) + # otherwise write everything. + else: + writer = UFOWriter(outPath, formatVersion=1) + writer.writeGroups(groups) + writer.writeKerning(kerning) + writer.writeLib(libData) + # write the info manually + writePlistAtomically(infoData, infoPath) + # copy the glyph tree + inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) + outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) + if os.path.exists(inGlyphs): + shutil.copytree(inGlyphs, outGlyphs) + + +# ---------- +# UFO Reader +# ---------- class UFOReader(object): - - """read the various components of the .ufo""" - + + """Read the various components of the .ufo.""" + def __init__(self, path): self._path = path - + self.readMetaInfo() + + def _get_formatVersion(self): + return self._formatVersion + + formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.") + def _checkForFile(self, path): if not os.path.exists(path): - #print "missing file: %s" % path return False else: return True - + def readMetaInfo(self): - """read metainfo.plist. mostly used - for internal operations""" + """ + Read metainfo.plist. Only used for internal operations. + """ path = os.path.join(self._path, METAINFO_FILENAME) if not self._checkForFile(path): - return - + raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path) + # should there be a blind try/except with a UFOLibError + # raised in except here (and elsewhere)? It would be nice to + # provide external callers with a single exception to catch. + data = readPlist(path) + formatVersion = data["formatVersion"] + if formatVersion not in supportedUFOFormatVersions: + raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path)) + self._formatVersion = formatVersion + def readGroups(self): - """read groups.plist. returns a dict that should - be applied to a font.groups object.""" + """ + Read groups.plist. Returns a dict. + """ path = os.path.join(self._path, GROUPS_FILENAME) if not self._checkForFile(path): return {} return readPlist(path) - + def readInfo(self, info): - """read info.plist. it requires a font.info object - as an argument. this will write the attributes - defined in the file into the info object.""" + """ + Read fontinfo.plist. It requires an object that allows + setting attributes with names that follow the fontinfo.plist + version 2 specification. This will write the attributes + defined in the file into the object. + """ + # load the file and return if there is no file path = os.path.join(self._path, FONTINFO_FILENAME) if not self._checkForFile(path): - return {} + return infoDict = readPlist(path) - for key, value in infoDict.items(): + infoDataToSet = {} + # version 1 + if self._formatVersion == 1: + for attr in fontInfoAttributesVersion1: + value = infoDict.get(attr) + if value is not None: + infoDataToSet[attr] = value + infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet) + # version 2 + elif self._formatVersion == 2: + for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items(): + value = infoDict.get(attr) + if value is None: + continue + infoDataToSet[attr] = value + # unsupported version + else: + raise NotImplementedError + # validate data + infoDataToSet = _validateInfoVersion2Data(infoDataToSet) + # populate the object + for attr, value in infoDataToSet.items(): try: - setattr(info, key, value) + setattr(info, attr, value) except AttributeError: - # object doesn't support setting this attribute - pass - + raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr) + def readKerning(self): - """read kerning.plist. returns a dict that should - be applied to a font.kerning object.""" + """ + Read kerning.plist. Returns a dict. + """ path = os.path.join(self._path, KERNING_FILENAME) if not self._checkForFile(path): return {} @@ -169,23 +293,39 @@ class UFOReader(object): value = kerningNested[left][right] kerning[left, right] = value return kerning - + def readLib(self): - """read lib.plist. returns a dict that should - be applied to a font.lib object.""" + """ + Read lib.plist. Returns a dict. + """ path = os.path.join(self._path, LIB_FILENAME) if not self._checkForFile(path): return {} return readPlist(path) - + + def readFeatures(self): + """ + Read features.fea. Returns a string. + """ + path = os.path.join(self._path, FEATURES_FILENAME) + if not self._checkForFile(path): + return "" + f = open(path, READ_MODE) + text = f.read() + f.close() + return text + def getGlyphSet(self): - """return the GlyphSet associated with the - glyphs directory in the .ufo""" + """ + Return the GlyphSet associated with the + glyphs directory in the .ufo. + """ glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) return GlyphSet(glyphsPath) def getCharacterMapping(self): - """Return a dictionary that maps unicode values (ints) to + """ + Return a dictionary that maps unicode values (ints) to lists of glyph names. """ glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) @@ -201,37 +341,61 @@ class UFOReader(object): return cmap +# ---------- +# UFO Writer +# ---------- + + class UFOWriter(object): - - """write the various components of the .ufo""" - - fileCreator = 'org.robofab.ufoLib' - formatVersion = 1 # the format version is an int, the next version will be 2. - - def __init__(self, path): + + """Write the various components of the .ufo.""" + + def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"): + if formatVersion not in supportedUFOFormatVersions: + raise UFOLibError("Unsupported UFO format (%d)." % formatVersion) self._path = path - + self._formatVersion = formatVersion + self._fileCreator = fileCreator + self._writeMetaInfo() + # handle down conversion + if formatVersion == 1: + ## remove existing features.fea + featuresPath = os.path.join(path, FEATURES_FILENAME) + if os.path.exists(featuresPath): + os.remove(featuresPath) + + def _get_formatVersion(self): + return self._formatVersion + + formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.") + + def _get_fileCreator(self): + return self._fileCreator + + fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.") + def _makeDirectory(self, subDirectory=None): path = self._path if subDirectory: path = os.path.join(self._path, subDirectory) if not os.path.exists(path): os.makedirs(path) - if not os.path.exists(os.path.join(path, METAINFO_FILENAME)): - self._writeMetaInfo() return path - + def _writeMetaInfo(self): + self._makeDirectory() path = os.path.join(self._path, METAINFO_FILENAME) - metaInfo = { - 'creator': self.fileCreator, - 'formatVersion': self.formatVersion, - } + metaInfo = dict( + creator=self._fileCreator, + formatVersion=self._formatVersion + ) writePlistAtomically(metaInfo, path) - + def writeGroups(self, groups): - """write groups.plist. this method requires a - dict of glyph groups as an argument.""" + """ + Write groups.plist. This method requires a + dict of glyph groups as an argument. + """ self._makeDirectory() path = os.path.join(self._path, GROUPS_FILENAME) groupsNew = {} @@ -241,23 +405,40 @@ class UFOWriter(object): writePlistAtomically(groupsNew, path) elif os.path.exists(path): os.remove(path) - + def writeInfo(self, info): - """write info.plist. this method requires a - font.info object. attributes will be taken from - the given object and written into the file""" + """ + Write info.plist. This method requires an object + that supports getting attributes that follow the + fontinfo.plist version 2 secification. Attributes + will be taken from the given object and written + into the file. + """ self._makeDirectory() path = os.path.join(self._path, FONTINFO_FILENAME) - infoDict = {} - for name in fontInfoAttrs: - value = getattr(info, name, None) - if value is not None: - infoDict[name] = value - writePlistAtomically(infoDict, path) - + # gather version 2 data + infoData = {} + for attr in _fontInfoAttributesVersion2ValueData.keys(): + try: + value = getattr(info, attr) + except AttributeError: + raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) + if value is None: + continue + infoData[attr] = value + # validate data + infoData = _validateInfoVersion2Data(infoData) + # down convert data to version 1 if necessary + if self._formatVersion == 1: + infoData = _convertFontInfoDataVersion2ToVersion1(infoData) + # write file + writePlistAtomically(infoData, path) + def writeKerning(self, kerning): - """write kerning.plist. this method requires a - dict of kerning pairs as an argument""" + """ + Write kerning.plist. This method requires a + dict of kerning pairs as an argument. + """ self._makeDirectory() path = os.path.join(self._path, KERNING_FILENAME) kerningDict = {} @@ -270,10 +451,12 @@ class UFOWriter(object): writePlistAtomically(kerningDict, path) elif os.path.exists(path): os.remove(path) - + def writeLib(self, libDict): - """write lib.plist. this method requires a - lib dict as an argument""" + """ + Write lib.plist. This method requires a + lib dict as an argument. + """ self._makeDirectory() path = os.path.join(self._path, LIB_FILENAME) if libDict: @@ -281,13 +464,619 @@ class UFOWriter(object): elif os.path.exists(path): os.remove(path) + def writeFeatures(self, features): + """ + Write features.fea. This method requires a + features string as an argument. + """ + if self._formatVersion == 1: + raise UFOLibError("features.fea is not allowed in UFO Format Version 1.") + self._makeDirectory() + path = os.path.join(self._path, FEATURES_FILENAME) + writeFileAtomically(features, path) + def makeGlyphPath(self): - """make the glyphs directory in the .ufo - returns the path of the directory created""" + """ + Make the glyphs directory in the .ufo. + Returns the path of the directory created. + """ glyphDir = self._makeDirectory(GLYPHS_DIRNAME) return glyphDir def getGlyphSet(self, glyphNameToFileNameFunc=None): - """return the GlyphSet associated with the - glyphs directory in the .ufo""" + """ + Return the GlyphSet associated with the + glyphs directory in the .ufo. + """ return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc) + +# ---------------- +# Helper Functions +# ---------------- + +def makeUFOPath(path): + """ + Return a .ufo pathname. + + >>> makeUFOPath("/directory/something.ext") + '/directory/something.ufo' + >>> makeUFOPath("/directory/something.another.thing.ext") + '/directory/something.another.thing.ufo' + """ + dir, name = os.path.split(path) + name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) + return os.path.join(dir, name) + +def writePlistAtomically(obj, path): + """ + Write a plist for "obj" to "path". Do this sort of atomically, + making it harder to cause corrupt files, for example when writePlist + encounters an error halfway during write. This also checks to see + if text matches the text that is already in the file at path. + If so, the file is not rewritten so that the modification date + is preserved. + """ + f = StringIO() + writePlist(obj, f) + data = f.getvalue() + writeFileAtomically(data, path) + +def writeFileAtomically(text, path): + """Write text into a file at path. Do this sort of atomically + making it harder to cause corrupt files. This also checks to see + if text matches the text that is already in the file at path. + If so, the file is not rewritten so that the modification date + is preserved.""" + if os.path.exists(path): + f = open(path, READ_MODE) + oldText = f.read() + f.close() + if text == oldText: + return + # if the text is empty, remove the existing file + if not text: + os.remove(path) + if text: + f = open(path, WRITE_MODE) + f.write(text) + f.close() + +# ---------------------- +# fontinfo.plist Support +# ---------------------- + +# Version 1 + +fontInfoAttributesVersion1 = set([ + "familyName", + "styleName", + "fullName", + "fontName", + "menuName", + "fontStyle", + "note", + "versionMajor", + "versionMinor", + "year", + "copyright", + "notice", + "trademark", + "license", + "licenseURL", + "createdBy", + "designer", + "designerURL", + "vendorURL", + "unitsPerEm", + "ascender", + "descender", + "capHeight", + "xHeight", + "defaultWidth", + "slantAngle", + "italicAngle", + "widthName", + "weightName", + "weightValue", + "fondName", + "otFamilyName", + "otStyleName", + "otMacName", + "msCharSet", + "fondID", + "uniqueID", + "ttVendor", + "ttUniqueID", + "ttVersion", +]) + +# Version 2 + +# Validators + +def validateFontInfoVersion2ValueForAttribute(attr, value): + """ + This performs very basic validation of the value for attribute + following the UFO fontinfo.plist specification. The results + of this should not be interpretted as *correct* for the font + that they are part of. This merely indicates that the value + is of the proper type and, where the specification defines + a set range of possible values for an attribute, that the + value is in the accepted range. + """ + dataValidationDict = _fontInfoAttributesVersion2ValueData[attr] + valueType = dataValidationDict.get("type") + validator = dataValidationDict.get("valueValidator") + valueOptions = dataValidationDict.get("valueOptions") + # have specific options for the validator + if valueOptions is not None: + isValidValue = validator(value, valueOptions) + # no specific options + else: + if validator == _fontInfoTypeValidator: + isValidValue = validator(value, valueType) + else: + isValidValue = validator(value) + return isValidValue + +def _validateInfoVersion2Data(infoData): + validInfoData = {} + for attr, value in infoData.items(): + isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value) + if not isValidValue: + raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) + else: + validInfoData[attr] = value + return infoData + +def _fontInfoTypeValidator(value, typ): + return isinstance(value, typ) + +def _fontInfoVersion2IntListValidator(values, validValues): + if not isinstance(values, (list, tuple)): + return False + valuesSet = set(values) + validValuesSet = set(validValues) + if len(valuesSet - validValuesSet) > 0: + return False + for value in values: + if not isinstance(value, int): + return False + return True + +def _fontInfoVersion2StyleMapStyleNameValidator(value): + options = ["regular", "italic", "bold", "bold italic"] + return value in options + +def _fontInfoVersion2OpenTypeHeadCreatedValidator(value): + # format: 0000/00/00 00:00:00 + if not isinstance(value, (str, unicode)): + return False + # basic formatting + if not len(value) == 19: + return False + if value.count(" ") != 1: + return False + date, time = value.split(" ") + if date.count("/") != 2: + return False + if time.count(":") != 2: + return False + # date + year, month, day = date.split("/") + if len(year) != 4: + return False + if len(month) != 2: + return False + if len(day) != 2: + return False + try: + year = int(year) + month = int(month) + day = int(day) + except ValueError: + return False + if month < 1 or month > 12: + return False + monthMaxDay = calendar.monthrange(year, month) + if month > monthMaxDay: + return False + # time + hour, minute, second = time.split(":") + if len(hour) != 2: + return False + if len(minute) != 2: + return False + if len(second) != 2: + return False + try: + hour = int(hour) + minute = int(minute) + second = int(second) + except ValueError: + return False + if hour < 0 or hour > 23: + return False + if minute < 0 or minute > 59: + return False + if second < 0 or second > 59: + return True + # fallback + return True + +def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value): + if not isinstance(value, int): + return False + if value < 0: + return False + return True + +def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value): + if not isinstance(value, int): + return False + if value < 1: + return False + if value > 9: + return False + return True + +def _fontInfoVersion2OpenTypeOS2PanoseValidator(values): + if not isinstance(values, (list, tuple)): + return False + if len(values) != 10: + return False + for value in values: + if not isinstance(value, int): + return False + # XXX further validation? + return True + +def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values): + if not isinstance(values, (list, tuple)): + return False + if len(values) != 2: + return False + for value in values: + if not isinstance(value, int): + return False + classID, subclassID = values + if classID < 0 or classID > 14: + return False + if subclassID < 0 or subclassID > 15: + return False + return True + +def _fontInfoVersion2PostscriptBluesValidator(values): + if not isinstance(values, (list, tuple)): + return False + if len(values) > 14: + return False + if len(values) % 2: + return False + for value in values: + if not isinstance(value, (int, float)): + return False + return True + +def _fontInfoVersion2PostscriptOtherBluesValidator(values): + if not isinstance(values, (list, tuple)): + return False + if len(values) > 10: + return False + if len(values) % 2: + return False + for value in values: + if not isinstance(value, (int, float)): + return False + return True + +def _fontInfoVersion2PostscriptStemsValidator(values): + if not isinstance(values, (list, tuple)): + return False + if len(values) > 12: + return False + for value in values: + if not isinstance(value, (int, float)): + return False + return True + +def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value): + validValues = range(1, 21) + if value not in validValues: + return False + return True + +# Attribute Definitions +# This defines the attributes, types and, in some +# cases the possible values, that can exist is +# fontinfo.plist. + +_fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14) +_fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4] +_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128) +_fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64) +_fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9] + +_fontInfoAttributesVersion2ValueData = { + "familyName" : dict(type=(str, unicode)), + "styleName" : dict(type=(str, unicode)), + "styleMapFamilyName" : dict(type=(str, unicode)), + "styleMapStyleName" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator), + "versionMajor" : dict(type=int), + "versionMinor" : dict(type=int), + "year" : dict(type=int), + "copyright" : dict(type=(str, unicode)), + "trademark" : dict(type=(str, unicode)), + "unitsPerEm" : dict(type=(int, float)), + "descender" : dict(type=(int, float)), + "xHeight" : dict(type=(int, float)), + "capHeight" : dict(type=(int, float)), + "ascender" : dict(type=(int, float)), + "italicAngle" : dict(type=(float, int)), + "note" : dict(type=(str, unicode)), + "openTypeHeadCreated" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator), + "openTypeHeadLowestRecPPEM" : dict(type=(int, float)), + "openTypeHeadFlags" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions), + "openTypeHheaAscender" : dict(type=(int, float)), + "openTypeHheaDescender" : dict(type=(int, float)), + "openTypeHheaLineGap" : dict(type=(int, float)), + "openTypeHheaCaretSlopeRise" : dict(type=int), + "openTypeHheaCaretSlopeRun" : dict(type=int), + "openTypeHheaCaretOffset" : dict(type=(int, float)), + "openTypeNameDesigner" : dict(type=(str, unicode)), + "openTypeNameDesignerURL" : dict(type=(str, unicode)), + "openTypeNameManufacturer" : dict(type=(str, unicode)), + "openTypeNameManufacturerURL" : dict(type=(str, unicode)), + "openTypeNameLicense" : dict(type=(str, unicode)), + "openTypeNameLicenseURL" : dict(type=(str, unicode)), + "openTypeNameVersion" : dict(type=(str, unicode)), + "openTypeNameUniqueID" : dict(type=(str, unicode)), + "openTypeNameDescription" : dict(type=(str, unicode)), + "openTypeNamePreferredFamilyName" : dict(type=(str, unicode)), + "openTypeNamePreferredSubfamilyName" : dict(type=(str, unicode)), + "openTypeNameCompatibleFullName" : dict(type=(str, unicode)), + "openTypeNameSampleText" : dict(type=(str, unicode)), + "openTypeNameWWSFamilyName" : dict(type=(str, unicode)), + "openTypeNameWWSSubfamilyName" : dict(type=(str, unicode)), + "openTypeOS2WidthClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator), + "openTypeOS2WeightClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator), + "openTypeOS2Selection" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions), + "openTypeOS2VendorID" : dict(type=(str, unicode)), + "openTypeOS2Panose" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator), + "openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator), + "openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions), + "openTypeOS2CodePageRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions), + "openTypeOS2TypoAscender" : dict(type=(int, float)), + "openTypeOS2TypoDescender" : dict(type=(int, float)), + "openTypeOS2TypoLineGap" : dict(type=(int, float)), + "openTypeOS2WinAscent" : dict(type=(int, float)), + "openTypeOS2WinDescent" : dict(type=(int, float)), + "openTypeOS2Type" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions), + "openTypeOS2SubscriptXSize" : dict(type=(int, float)), + "openTypeOS2SubscriptYSize" : dict(type=(int, float)), + "openTypeOS2SubscriptXOffset" : dict(type=(int, float)), + "openTypeOS2SubscriptYOffset" : dict(type=(int, float)), + "openTypeOS2SuperscriptXSize" : dict(type=(int, float)), + "openTypeOS2SuperscriptYSize" : dict(type=(int, float)), + "openTypeOS2SuperscriptXOffset" : dict(type=(int, float)), + "openTypeOS2SuperscriptYOffset" : dict(type=(int, float)), + "openTypeOS2StrikeoutSize" : dict(type=(int, float)), + "openTypeOS2StrikeoutPosition" : dict(type=(int, float)), + "openTypeVheaVertTypoAscender" : dict(type=(int, float)), + "openTypeVheaVertTypoDescender" : dict(type=(int, float)), + "openTypeVheaVertTypoLineGap" : dict(type=(int, float)), + "openTypeVheaCaretSlopeRise" : dict(type=int), + "openTypeVheaCaretSlopeRun" : dict(type=int), + "openTypeVheaCaretOffset" : dict(type=(int, float)), + "postscriptFontName" : dict(type=(str, unicode)), + "postscriptFullName" : dict(type=(str, unicode)), + "postscriptSlantAngle" : dict(type=(float, int)), + "postscriptUniqueID" : dict(type=int), + "postscriptUnderlineThickness" : dict(type=(int, float)), + "postscriptUnderlinePosition" : dict(type=(int, float)), + "postscriptIsFixedPitch" : dict(type=bool), + "postscriptBlueValues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), + "postscriptOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), + "postscriptFamilyBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), + "postscriptFamilyOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), + "postscriptStemSnapH" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), + "postscriptStemSnapV" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), + "postscriptBlueFuzz" : dict(type=(int, float)), + "postscriptBlueShift" : dict(type=(int, float)), + "postscriptBlueScale" : dict(type=(float, int)), + "postscriptForceBold" : dict(type=bool), + "postscriptDefaultWidthX" : dict(type=(int, float)), + "postscriptNominalWidthX" : dict(type=(int, float)), + "postscriptWeightName" : dict(type=(str, unicode)), + "postscriptDefaultCharacter" : dict(type=(str, unicode)), + "postscriptWindowsCharacterSet" : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator), + "macintoshFONDFamilyID" : dict(type=int), + "macintoshFONDName" : dict(type=(str, unicode)), +} +fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys()) + +# insert the type validator for all attrs that +# have no defined validator. +for attr, dataDict in _fontInfoAttributesVersion2ValueData.items(): + if "valueValidator" not in dataDict: + dataDict["valueValidator"] = _fontInfoTypeValidator + +# Version Conversion Support +# These are used from converting from version 1 +# to version 2 or vice-versa. + +def _flipDict(d): + flipped = {} + for key, value in d.items(): + flipped[value] = key + return flipped + +_fontInfoAttributesVersion1To2 = { + "menuName" : "styleMapFamilyName", + "designer" : "openTypeNameDesigner", + "designerURL" : "openTypeNameDesignerURL", + "createdBy" : "openTypeNameManufacturer", + "vendorURL" : "openTypeNameManufacturerURL", + "license" : "openTypeNameLicense", + "licenseURL" : "openTypeNameLicenseURL", + "ttVersion" : "openTypeNameVersion", + "ttUniqueID" : "openTypeNameUniqueID", + "notice" : "openTypeNameDescription", + "otFamilyName" : "openTypeNamePreferredFamilyName", + "otStyleName" : "openTypeNamePreferredSubfamilyName", + "otMacName" : "openTypeNameCompatibleFullName", + "weightName" : "postscriptWeightName", + "weightValue" : "openTypeOS2WeightClass", + "ttVendor" : "openTypeOS2VendorID", + "uniqueID" : "postscriptUniqueID", + "fontName" : "postscriptFontName", + "fondID" : "macintoshFONDFamilyID", + "fondName" : "macintoshFONDName", + "defaultWidth" : "postscriptDefaultWidthX", + "slantAngle" : "postscriptSlantAngle", + "fullName" : "postscriptFullName", + # require special value conversion + "fontStyle" : "styleMapStyleName", + "widthName" : "openTypeOS2WidthClass", + "msCharSet" : "postscriptWindowsCharacterSet" +} +_fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2) +deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys()) + +_fontStyle1To2 = { + 64 : "regular", + 1 : "italic", + 32 : "bold", + 33 : "bold italic" +} +_fontStyle2To1 = _flipDict(_fontStyle1To2) +# Some UFO 1 files have 0 +_fontStyle1To2[0] = "regular" + +_widthName1To2 = { + "Ultra-condensed" : 1, + "Extra-condensed" : 2, + "Condensed" : 3, + "Semi-condensed" : 4, + "Medium (normal)" : 5, + "Semi-expanded" : 6, + "Expanded" : 7, + "Extra-expanded" : 8, + "Ultra-expanded" : 9 +} +_widthName2To1 = _flipDict(_widthName1To2) +# FontLab's default width value is "Normal". +# Many format version 1 UFOs will have this. +_widthName1To2["Normal"] = 5 +# FontLab has an "All" width value. In UFO 1 +# move this up to "Normal". +_widthName1To2["All"] = 5 +# "medium" appears in a lot of UFO 1 files. +_widthName1To2["medium"] = 5 + +_msCharSet1To2 = { + 0 : 1, + 1 : 2, + 2 : 3, + 77 : 4, + 128 : 5, + 129 : 6, + 130 : 7, + 134 : 8, + 136 : 9, + 161 : 10, + 162 : 11, + 163 : 12, + 177 : 13, + 178 : 14, + 186 : 15, + 200 : 16, + 204 : 17, + 222 : 18, + 238 : 19, + 255 : 20 +} +_msCharSet2To1 = _flipDict(_msCharSet1To2) + +def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value): + """ + Convert value from version 1 to version 2 format. + Returns the new attribute name and the converted value. + If the value is None, None will be returned for the new value. + """ + # convert floats to ints if possible + if isinstance(value, float): + if int(value) == value: + value = int(value) + if value is not None: + if attr == "fontStyle": + v = _fontStyle1To2.get(value) + if v is None: + raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) + value = v + elif attr == "widthName": + v = _widthName1To2.get(value) + if v is None: + raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) + value = v + elif attr == "msCharSet": + v = _msCharSet1To2.get(value) + if v is None: + raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) + value = v + attr = _fontInfoAttributesVersion1To2.get(attr, attr) + return attr, value + +def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value): + """ + Convert value from version 2 to version 1 format. + Returns the new attribute name and the converted value. + If the value is None, None will be returned for the new value. + """ + if value is not None: + if attr == "styleMapStyleName": + value = _fontStyle2To1.get(value) + elif attr == "openTypeOS2WidthClass": + value = _widthName2To1.get(value) + elif attr == "postscriptWindowsCharacterSet": + value = _msCharSet2To1.get(value) + attr = _fontInfoAttributesVersion2To1.get(attr, attr) + return attr, value + +def _convertFontInfoDataVersion1ToVersion2(data): + converted = {} + for attr, value in data.items(): + # FontLab gives -1 for the weightValue + # for fonts wil no defined value. Many + # format version 1 UFOs will have this. + if attr == "weightValue" and value == -1: + continue + newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) + # skip if the attribute is not part of version 2 + if newAttr not in fontInfoAttributesVersion2: + continue + # catch values that can't be converted + if value is None: + raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) + # store + converted[newAttr] = newValue + return converted + +def _convertFontInfoDataVersion2ToVersion1(data): + converted = {} + for attr, value in data.items(): + newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) + # only take attributes that are registered for version 1 + if newAttr not in fontInfoAttributesVersion1: + continue + # catch values that can't be converted + if value is None: + raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) + # store + converted[newAttr] = newValue + return converted + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py b/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py index cb5cf63e1..6392e4674 100644 --- a/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py +++ b/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py @@ -21,7 +21,7 @@ for c in f: compatibles[d].append(c.name) print -print 'In %s, these glyphs could interpolate:'%(f.info.fullName) +print 'In %s, these glyphs could interpolate:'%(f.info.postscriptFullName) for d, names in compatibles.items(): if len(names) > 1: print ", ".join(names) \ No newline at end of file diff --git a/Scripts/RoboFabIntro/intro_FontObject.py b/Scripts/RoboFabIntro/intro_FontObject.py index 5db7d0baa..764b831a4 100644 --- a/Scripts/RoboFabIntro/intro_FontObject.py +++ b/Scripts/RoboFabIntro/intro_FontObject.py @@ -30,14 +30,14 @@ if f == None: Message("You should open a font first, there's nothing to look at now!") else: # and another dialog. - Message("The current font is %s"%(f.info.fullName)) + Message("The current font is %s"%(f.info.postscriptFullName)) # let's have a look at some of the attributes a RoboFab Font object has print "the number of glyphs:", len(f) # some of the attributes map straight to the FontLab Font class # We just straightened the camelCase here and there - print "full name of this font:", f.info.fullName + print "full name of this font:", f.info.postscriptFullName print "list of glyph names:", f.keys() print 'ascender:', f.info.ascender print 'descender:', f.info.descender diff --git a/Scripts/RoboFabIntro/intro_FoundrySettings.py b/Scripts/RoboFabIntro/intro_FoundrySettings.py index 469f22f84..500656cc9 100644 --- a/Scripts/RoboFabIntro/intro_FoundrySettings.py +++ b/Scripts/RoboFabIntro/intro_FoundrySettings.py @@ -50,13 +50,13 @@ font.info.year = time.gmtime(time.time())[0] # Apply those settings that we just loaded font.info.copyright = mySettings['copyright'] font.info.trademark = mySettings['trademark'] -font.info.license = mySettings['license'] -font.info.licenseURL = mySettings['licenseurl'] -font.info.notice = mySettings['notice'] -font.info.ttVendor = mySettings['ttvendor'] -font.info.vendorURL = mySettings['vendorurl'] -font.info.designer = mySettings['designer'] -font.info.designerURL = mySettings['designerurl'] +font.info.openTypeNameLicense = mySettings['license'] +font.info.openTypeNameLicenseURL = mySettings['licenseurl'] +font.info.openTypeNameDescription = mySettings['notice'] +font.info.openTypeOS2VendorID = mySettings['ttvendor'] +font.info.openTypeNameManufacturerURL = mySettings['vendorurl'] +font.info.openTypeNameDesigner = mySettings['designer'] +font.info.openTypeNameDesignerURL = mySettings['designerurl'] # and call the update method font.update() diff --git a/Scripts/RoboFabIntro/intro_Kerning.py b/Scripts/RoboFabIntro/intro_Kerning.py index 328fbb5d8..3960b9ff2 100644 --- a/Scripts/RoboFabIntro/intro_Kerning.py +++ b/Scripts/RoboFabIntro/intro_Kerning.py @@ -29,7 +29,7 @@ kerning = f.kerning # you call and attribute or make a change. # kerning gives you access to some bits of global data -print "%s has %s kerning pairs"%(f.info.fullName, len(kerning)) +print "%s has %s kerning pairs"%(f.info.postscriptFullName, len(kerning)) print "the average kerning value is %s"%kerning.getAverage() min, max = kerning.getExtremes() print "the largest kerning value is %s"%max diff --git a/Scripts/RoboFabUtils/RobustBatchGenerate.py b/Scripts/RoboFabUtils/RobustBatchGenerate.py index ac8c1ae66..7b5b3816a 100644 --- a/Scripts/RoboFabUtils/RobustBatchGenerate.py +++ b/Scripts/RoboFabUtils/RobustBatchGenerate.py @@ -25,7 +25,7 @@ def makeDestination(root): return macPath def generateOne(f, dstDir): - print "generating %s"%f.info.fullName + print "generating %s"%f.info.postscriptFullName f.generate('mactype1', dstDir) diff --git a/Scripts/RoboFabUtils/TestFontEquality.py b/Scripts/RoboFabUtils/TestFontEquality.py index 6aeb30493..04ef36557 100644 --- a/Scripts/RoboFabUtils/TestFontEquality.py +++ b/Scripts/RoboFabUtils/TestFontEquality.py @@ -8,13 +8,13 @@ af = AllFonts() results = [] line = [] for n in af: - line.append(`n.info.fullName`) + line.append(`n.info.postscriptFullName`) results.append(line) for i in range(len(af)): one = af[i] line = [] - line.append(af[i].info.fullName) + line.append(af[i].info.postscriptFullName) for j in range(len(af)): other = af[j] line.append(`one==other`) diff --git a/TestData/TestFont1 (UFO1).ufo/fontinfo.plist b/TestData/TestFont1 (UFO1).ufo/fontinfo.plist new file mode 100644 index 000000000..bab3aa2f3 --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/fontinfo.plist @@ -0,0 +1,87 @@ + + + + + ascender + 750 + capHeight + 750 + copyright + Copyright Some Foundry. + createdBy + Some Foundry + defaultWidth + 400 + descender + -250 + designer + Some Designer + designerURL + http://somedesigner.com + familyName + Some Font (Family Name) + fondID + 15000 + fondName + SomeFont Regular (FOND Name) + fontName + SomeFont-Regular (Postscript Font Name) + fontStyle + 64 + fullName + Some Font-Regular (Postscript Full Name) + italicAngle + -12.5 + license + License info for Some Foundry. + licenseURL + http://somefoundry.com/license + menuName + Some Font Regular (Style Map Family Name) + msCharSet + 0 + note + A note. + notice + Some Font by Some Designer for Some Foundry. + otFamilyName + Some Font (Preferred Family Name) + otMacName + Some Font Regular (Compatible Full Name) + otStyleName + Regular (Preferred Subfamily Name) + slantAngle + -12.5 + styleName + Regular (Style Name) + trademark + Trademark Some Foundry + ttUniqueID + OpenType name Table Unique ID + ttVendor + SOME + ttVersion + OpenType name Table Version + uniqueID + 4000000 + unitsPerEm + 1000 + vendorURL + http://somefoundry.com + versionMajor + 1 + versionMinor + 0 + weightName + Medium + weightValue + 500 + widthName + Medium (normal) + xHeight + 500 + year + 2008 + + + diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif b/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif new file mode 100644 index 000000000..36afacccf --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif b/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif new file mode 100644 index 000000000..ddcf3b22e --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist b/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist new file mode 100644 index 000000000..08f7bba83 --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist @@ -0,0 +1,10 @@ + + + + + A + A_.glif + B + B_.glif + + diff --git a/TestData/TestFont1 (UFO1).ufo/groups.plist b/TestData/TestFont1 (UFO1).ufo/groups.plist new file mode 100644 index 000000000..40d17d9fa --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/groups.plist @@ -0,0 +1,15 @@ + + + + + group1 + + A + + group2 + + A + B + + + diff --git a/TestData/TestFont1 (UFO1).ufo/kerning.plist b/TestData/TestFont1 (UFO1).ufo/kerning.plist new file mode 100644 index 000000000..c07bf22af --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/kerning.plist @@ -0,0 +1,16 @@ + + + + + A + + B + 100 + + B + + A + -200 + + + diff --git a/TestData/TestFont1 (UFO1).ufo/lib.plist b/TestData/TestFont1 (UFO1).ufo/lib.plist new file mode 100644 index 000000000..df50b2993 --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/lib.plist @@ -0,0 +1,72 @@ + + + + + org.robofab.opentype.classes + @myClass = [A B]; + + org.robofab.opentype.featureorder + + liga + + org.robofab.opentype.features + + liga + feature liga { + sub A A by b; +} liga; + + + org.robofab.postScriptHintData + + blueFuzz + 1 + blueScale + 0.039625 + blueShift + 7 + blueValues + + + 500 + 510 + + + familyBlues + + + 500 + 510 + + + familyOtherBlues + + + -260 + -250 + + + forceBold + + hStems + + 100 + 120 + + otherBlues + + + -260 + -250 + + + vStems + + 80 + 90 + + + org.robofab.testFontLibData + Foo Bar + + diff --git a/TestData/TestFont1 (UFO1).ufo/metainfo.plist b/TestData/TestFont1 (UFO1).ufo/metainfo.plist new file mode 100644 index 000000000..c044a5ff4 --- /dev/null +++ b/TestData/TestFont1 (UFO1).ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + org.robofab.ufoLib + formatVersion + 1 + + diff --git a/TestData/TestFont1 (UFO2).ufo/features.fea b/TestData/TestFont1 (UFO2).ufo/features.fea new file mode 100644 index 000000000..40188d9a7 --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/features.fea @@ -0,0 +1,5 @@ +@myClass = [A B]; + +feature liga { + sub A A by b; +} liga; diff --git a/TestData/TestFont1 (UFO2).ufo/fontinfo.plist b/TestData/TestFont1 (UFO2).ufo/fontinfo.plist new file mode 100644 index 000000000..021d46d62 --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/fontinfo.plist @@ -0,0 +1,239 @@ + + + + + ascender + 750 + capHeight + 750 + copyright + Copyright Some Foundry. + descender + -250 + familyName + Some Font (Family Name) + italicAngle + -12.5 + macintoshFONDFamilyID + 15000 + macintoshFONDName + SomeFont Regular (FOND Name) + note + A note. + openTypeHeadCreated + 2000/01/01 00:00:00 + openTypeHeadFlags + + 0 + 1 + + openTypeHeadLowestRecPPEM + 10 + openTypeHheaAscender + 750 + openTypeHheaCaretOffset + 0 + openTypeHheaCaretSlopeRise + 1 + openTypeHheaCaretSlopeRun + 0 + openTypeHheaDescender + -250 + openTypeHheaLineGap + 200 + openTypeNameCompatibleFullName + Some Font Regular (Compatible Full Name) + openTypeNameDescription + Some Font by Some Designer for Some Foundry. + openTypeNameDesigner + Some Designer + openTypeNameDesignerURL + http://somedesigner.com + openTypeNameLicense + License info for Some Foundry. + openTypeNameLicenseURL + http://somefoundry.com/license + openTypeNameManufacturer + Some Foundry + openTypeNameManufacturerURL + http://somefoundry.com + openTypeNamePreferredFamilyName + Some Font (Preferred Family Name) + openTypeNamePreferredSubfamilyName + Regular (Preferred Subfamily Name) + openTypeNameSampleText + Sample Text for Some Font. + openTypeNameUniqueID + OpenType name Table Unique ID + openTypeNameVersion + OpenType name Table Version + openTypeNameWWSFamilyName + Some Font (WWS Family Name) + openTypeNameWWSSubfamilyName + Regular (WWS Subfamily Name) + openTypeOS2CodePageRanges + + 0 + 1 + + openTypeOS2Panose + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + openTypeOS2FamilyClass + + 1 + 1 + + openTypeOS2Selection + + 3 + + openTypeOS2StrikeoutPosition + 300 + openTypeOS2StrikeoutSize + 20 + openTypeOS2SubscriptXOffset + 0 + openTypeOS2SubscriptXSize + 200 + openTypeOS2SubscriptYOffset + -100 + openTypeOS2SubscriptYSize + 400 + openTypeOS2SuperscriptXOffset + 0 + openTypeOS2SuperscriptXSize + 200 + openTypeOS2SuperscriptYOffset + 200 + openTypeOS2SuperscriptYSize + 400 + openTypeOS2Type + + + openTypeOS2TypoAscender + 750 + openTypeOS2TypoDescender + -250 + openTypeOS2TypoLineGap + 200 + openTypeOS2UnicodeRanges + + 0 + 1 + + openTypeOS2VendorID + SOME + openTypeOS2WeightClass + 500 + openTypeOS2WidthClass + 5 + openTypeOS2WinAscent + 750 + openTypeOS2WinDescent + -250 + openTypeVheaCaretOffset + 0 + openTypeVheaCaretSlopeRise + 0 + openTypeVheaCaretSlopeRun + 1 + openTypeVheaVertTypoAscender + 750 + openTypeVheaVertTypoDescender + -250 + openTypeVheaVertTypoLineGap + 200 + postscriptBlueFuzz + 1 + postscriptBlueScale + 0.039625 + postscriptBlueShift + 7 + postscriptBlueValues + + 500 + 510 + + postscriptDefaultCharacter + .notdef + postscriptDefaultWidthX + 400 + postscriptFamilyBlues + + 500 + 510 + + postscriptFamilyOtherBlues + + -250 + -260 + + postscriptFontName + SomeFont-Regular (Postscript Font Name) + postscriptForceBold + + postscriptFullName + Some Font-Regular (Postscript Full Name) + postscriptIsFixedPitch + + postscriptNominalWidthX + 400 + postscriptOtherBlues + + -250 + -260 + + postscriptSlantAngle + -12.5 + postscriptStemSnapH + + 100 + 120 + + postscriptStemSnapV + + 80 + 90 + + postscriptUnderlinePosition + -200 + postscriptUnderlineThickness + 20 + postscriptUniqueID + 4000000 + postscriptWeightName + Medium + postscriptWindowsCharacterSet + 1 + styleMapFamilyName + Some Font Regular (Style Map Family Name) + styleMapStyleName + regular + styleName + Regular (Style Name) + trademark + Trademark Some Foundry + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 500 + year + 2008 + + + diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif b/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif new file mode 100644 index 000000000..36afacccf --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif b/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif new file mode 100644 index 000000000..ddcf3b22e --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist b/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist new file mode 100644 index 000000000..08f7bba83 --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist @@ -0,0 +1,10 @@ + + + + + A + A_.glif + B + B_.glif + + diff --git a/TestData/TestFont1 (UFO2).ufo/groups.plist b/TestData/TestFont1 (UFO2).ufo/groups.plist new file mode 100644 index 000000000..40d17d9fa --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/groups.plist @@ -0,0 +1,15 @@ + + + + + group1 + + A + + group2 + + A + B + + + diff --git a/TestData/TestFont1 (UFO2).ufo/kerning.plist b/TestData/TestFont1 (UFO2).ufo/kerning.plist new file mode 100644 index 000000000..c07bf22af --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/kerning.plist @@ -0,0 +1,16 @@ + + + + + A + + B + 100 + + B + + A + -200 + + + diff --git a/TestData/TestFont1 (UFO2).ufo/lib.plist b/TestData/TestFont1 (UFO2).ufo/lib.plist new file mode 100644 index 000000000..0931165b7 --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/lib.plist @@ -0,0 +1,8 @@ + + + + + org.robofab.testFontLibData + Foo Bar + + diff --git a/TestData/TestFont1 (UFO2).ufo/metainfo.plist b/TestData/TestFont1 (UFO2).ufo/metainfo.plist new file mode 100644 index 000000000..f5ec4e54c --- /dev/null +++ b/TestData/TestFont1 (UFO2).ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + org.robofab.ufoLib + formatVersion + 2 + + diff --git a/TestData/TestFont1.vfb b/TestData/TestFont1.vfb new file mode 100644 index 000000000..65b19081b Binary files /dev/null and b/TestData/TestFont1.vfb differ