From aaa9626254d8b9186a45ecdb49e2b21862a0a48c Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 17:44:52 +0300 Subject: [PATCH 01/19] New repo structure --- Readme.md | 130 --------------------------------------- bdf/siji.bdf => doko.bdf | 6 +- doko.otb | Bin 0 -> 20264 bytes pcf/siji.pcf => doko.pcf | Bin 51744 -> 51744 bytes 4 files changed, 3 insertions(+), 133 deletions(-) delete mode 100644 Readme.md rename bdf/siji.bdf => doko.bdf (99%) create mode 100644 doko.otb rename pcf/siji.pcf => doko.pcf (99%) diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 91f3c22..0000000 --- a/Readme.md +++ /dev/null @@ -1,130 +0,0 @@ -# Siji - -Siji is an iconic bitmap font based on the Stlarch font with additional glyphs. - -It inherits additional glyphs from Sm4tik xbm icon pack, Lokaltog Symbols font, xbm-icon font, Uushi font, FontAwesome, and Tewi font with personal additions. - -Siji is built on top of Stlarch, so your existing stlarch unicode values will **just work**. - -Siji is a **Work In Progress**, so more glyphs will be added over the time. - -![Siji](preview.png "Preview of Siji") - -## What's the difference between Stlarch and Siji ? - -- Siji comes with **215 new glyphs**, selectively ported from other fonts and xbm icons with personal additions. -- Increased letter spacing. -- Properly centered glyphs. - -## Installation - -### Arch Linux - -[PKGBUILD](https://aur.archlinux.org/packages/siji-git) by [brandon99](https://github.com/brandon099) - -### CRUX - -[CRUX port](http://dl.z3bra.org/crux/v3.2/siji-font/) by [z3bra](http://z3bra.org/) - -### Others - -Clone this repo and `cd` into the siji directory: - -```sh -git clone https://github.com/stark/siji && cd siji -``` - -Then run the installer script with `./install.sh`. - -By default Siji will be installed in your `$HOME/.fonts` directory, it will be created if the directory is non-existent. - -If you wish to install Siji in another directory then run the `install.sh` script with the `-d` flag and specify the font directory as an argument. - -**Example:** - -```sh -./install.sh -d ~/.fonts -``` - -## How to get the glyph codes ? - -Install `xfd`: - -### Arch Linux -``` -sudo pacman -S xorg-xfd -``` - -### Debian -``` -sudo apt-get install x11-utils -``` - -### Fedora -``` -sudo dnf install xorg-x11-apps -``` - -After installing `xfd` run the `view.sh` script: - -```sh -./view.sh -``` - -## Using Siji with other programs: - -As long as the program allows to set a fallback font, Siji will work. - -Examples of using Siji with some programs: - -### Dwm - -```C -static const char font[] = "-wuncon-siji-medium-r-normal--10-100-75-75-c-80-iso10646-1" "," /* For Iconic Glyphs */ - "-*-tamsyn-medium-r-normal-*-12-*-*-*-*-*-*-1"; /* For Normal Text */ -``` - -### Dmenu - -```sh -dmenu -fn '-*-tamsyn-medium-r-normal-*-12-*-*-*-*-*-*-1','-wuncon-siji-medium-r-normal--10-100-75-75-c-80-iso10646-1' -``` - -### Lemonbar - -```sh -lemonbar -p -f '-*-tamsyn-medium-r-normal-*-12-*-*-*-*-*-*-1' -f '-wuncon-siji-medium-r-normal--10-100-75-75-c-80-iso10646-1' -``` - -## TODO - -- [ ] More Glyphs! -- [ ] Adding glyphs of different sizes. -- [ ] Improving glyph alignment. -- [ ] Creating small and large version of siji. - -## Credits - -A Big Thanks to the following Authors for their spectacular work which made Siji possible: - -**Sm4tik** for sm4tik xbm icon pack - -**Stlarch** for stlarch font - -**Sunaku** for sm4tik font - -**Lokaltog** for symbols font - -**w0ng** for xbm icon font - -**Dave Gandy** for FontAwesome - -**Lucy** for Tewi font - -**Phallus** for Lemon and Uushi font - -## License - -Siji is licensed under GPLv2. - -See `LICENSE` file for copyright details. diff --git a/bdf/siji.bdf b/doko.bdf similarity index 99% rename from bdf/siji.bdf rename to doko.bdf index 3e0f92c..8369829 100644 --- a/bdf/siji.bdf +++ b/doko.bdf @@ -1,11 +1,11 @@ STARTFONT 2.1 -FONT -Wuncon-Siji-Medium-R-Normal--10-100-75-75-C-80-ISO10646-1 +FONT -Wuncon-Doko-Medium-R-Normal--10-100-75-75-C-80-ISO10646-1 SIZE 10 75 75 FONTBOUNDINGBOX 12 12 0 -2 STARTPROPERTIES 23 FONTNAME_REGISTRY "" FOUNDRY "Wuncon" -FAMILY_NAME "Siji" +FAMILY_NAME "Doko" WEIGHT_NAME "Medium" SLANT "R" SETWIDTH_NAME "Normal" @@ -27,7 +27,7 @@ DEFAULT_CHAR 0 FONT_DESCENT 2 FONT_ASCENT 8 COMMENT """"""""""""""""""""""Procon - An Iconic Font based on Stlarch font, Sm4tik's xbm pack, FontAwesome and Lokaltog Symbol Font"""""""""""""""""""""" -COMMENT ""Siji font - an iconic bitmap font based on Stlarch by Stark"" +COMMENT ""Doko font - an iconic bitmap font based on Siji"" ENDPROPERTIES CHARS 631 STARTCHAR U+E000 diff --git a/doko.otb b/doko.otb new file mode 100644 index 0000000000000000000000000000000000000000..963cf863b926cc3527c0addef41cb03742ae67cf GIT binary patch literal 20264 zcmeHviGLJT+I8Jqy(MXqus2~zgFze>?Ax8CTO-*6C=g_EL5C0^fk+kz35!4$a1<0p zbQp~rI*N{n%jhV~NF*2)eFtTnaY3UHL=8Ghf(Qv_sqftTR9Bey_x=UnZ#p@t=Pq^6 zQ_o$_?dq;*Ow+Vztya^u`I*_d-cR>z9HMC%ZcWp7=VWHjuD))_?V2`bpQeq<%gLOY zl_2K+Q`55dYFgy7+}zpIwui=!M)?zHWklYrEBrSkuDDs#*b=1YFDxxsrp0`Ra>v`q z%UoQtrU?0XxzN6*Eh#KmreVhI-)J;G-VM%G_>H#INH1?jR*}6;0 z7Z%7A&UfJtFD+QTjPHZz8k>x?x2&MF@XDMB2er>WT2{WI@~V})H)&c#i>7%RHF>ST znjw{+=f|X;)uL?3(6ldK`)gv~(Fow$0nMdxnTr?B(F4f8#r6ngsFqAq9c(g(-)3dW zGnuNDZsyr+kFZ}fJ8ISQL&)1oNA_VIq*09iq*9Ausx$FXOUKG7-1ja+XpC< z*AjktH6}f~5aFW#9{(GG|3=`y5%~W+0?+`d1KJ34T}(wvu8<=%o94yQh99KAUMbhn z7R@lcRE{DBy$CO+Os?DERUf8$g$u*!|DTW1+KW~&?3&xO8KM`CEQCtC!pzsTi?!QL z+onZnO{Q(vVztwz?a(5bNA`wI>RTj0HiNznFl`Q|(Wb3yQ?+E%wrNAO=S|y=emrd2 z4s8&6YLYesbysSBO`9}h>Pj54wQ_AKpk<&wHg;@_LC0 zslfQ8@qV8l_5SZhm78XDO}iehRA?*UuXNB03Lhw>WAev~3WXIbip$HqKA)K2^ZAey zZuUPeRQeFUSVY?#&Bk9exu;>kf4tP5g=w5|rZXF}GY4}r7mHw#EQ+~VKNihmSS;($ z2C#u_5F5;fu%XPuhOsy{oQ+^3*(eszylgZZ!!BY8Y%IH&jboRv->^&BW$bb`o=sp^ zFdq}l&jKuwC9!0d!h$Tsu4GrSRCYC+$kJFk%V3!-i)FJMmdhrw$!rR{hF#01vT1BO z%VRUxb!;Y^#b&cP?0R+syOG_*=CYgFE$p{!9?NI*Spi$X7P3XGkQK4TYzZr7x3Zm#@4cRY(1-HHLRA^u?=h^yPf@x-NEi;o7iS{ z7rUEnVfV0m*;aNR+s5u^53t{}?d%WiLG}=kUScn^KeJcZtL!iAHTF8&!}hYjvcIu6*gn?4-emh(BRjy} zVomIA_6~cO9c1sZ_u2ojzq3Q^1NI?1%>KbXV*h0SVjr_l*r)6>c7%P-zF^JlOZF8z z%8s!XcAS09POxv-x9mIC%1*NH*$?dB>__$!JH^^qJ3GyOW@p$h>?}LSI#?$=&wgcH ztef?)US@FF{haIE#_im}o!rGEcqEVFZr+ba^B5k>`||;OARojB^C5gF_wZpnjt}P} z_((pA$8#?q&BySIcmf~GFXrR;CHy!1Qhph~oR8-d_!Zp81^4p+Pvl8FnWyj|5AiGc zRXmkn%_s6Sp3XCPCePy8JcsAvoUt9cEt<#l`m-^g$0zvFlCJNYKQncv0l=3DqZ{9eA5-^aJ{`}qU> z_k2751AmY|guVO2{1N^re~drQckn0pll+hTDgHFy$)DlR@;~wC_%8lD-_2j(FY=f8 z%lyy$75*y!3xAEj&iC-W{IC3P{0+X3H}E(4e%{Cr@V9sqf1AI<-{lASd;ER=Km70f z5dVOG$Pe>>@Q?UE`M>zb{1g5u|BN5upYtzxGyjr*#gFo1yoDd$+~!?YcvE z>MlJ(kJO`dx86^W)?@Toy}v#{AE*z~2kS%hp}I#OrpM{S^%43=eUu)rd-c)!82utW zK_9DMtdG+#(SM^~s$ZsGu8-Fz=vU}IUFd#2peO1{da|CP2lbGCrGAy3s$Z>7)YJ5I zJwwmbv-E5|N6*zK>67&-`ZfBs`c!?IK3&h#XXw}IGxb^eY<-S?y?%p!qkfYjnA(eWAWcFVu_l#rhJxSieZ|nC zjyad*Ul>}z5wwBh%Z2V0pbe~Ux4@m#1|mG0PzKgFOm2X zi7%1(5{WO7_!5aPk@ymcFOm2Xi7%1(5{WO7_!5aPk@ymcFNye)h%brwl87&f_>zb( ziTIL;FNye)h%brwl87&f_>zb(iTIL;FPZp~i7%P>l8G;w_>ze)nfQ{4FPZp~i7%P> zl8G;w_>ze)nfQ{4FNOG0h%bfsQiv~w_)>^3h4@m4FNOG0h%bfsQiv~w_)>^3h4@m4 zFGzer;tLXAkobbc7bLzQ@db%5NPI!!3ld+D_=3b2B)%Z=1&J?2d?Dfs5nqV-Lc|v$ zz7X++h%ZEZA>s=WUx@fZ#1|sI5b=eGFN63ph%bZqGKepO_%etugZMItFN63ph%bZq zGKepO_%etugZMItFO&E(i7%7*GKnvf_%ewvllU@;FO&E(i7%7*GKnvf_%ewvllU@; zFN^rHh%bxyvWPE>__ByEi}__ByEi}X7_;QFZhxl@cFNgSY zh%blua)>X7_;QFZhxl@cFPHdoi7%J7O9|6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{t41Q zLHZ|1{{-ouApH}he}eQ+kp2nMKSBB@NdE-spCJ7cq<@0+Pmulz(mz4^CrJMU>7O9| z6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{t41QLHZ|1{{-ouApH}he}eQ+kp2nM zKSBB@NdE-spCJ7cq<@0+Pmulz(mz4^CrJMU>7O9|6QqBF^iPof3DQ47`X@;L1nHk3 z{S%~rg7i<2{t41QLHZ|1{{-ouApH}he}eQ+kp2nMKSBB@NdE-spCJ7cq<@0+Pmulz z(mz4^CrJMU>7O9|6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{)td>#KI*76-g;6 zNy4)HmKCt9M9WIDtiE$oth}IQg~C>{&$29zWQ!x&;z+hQk}Zy8izC_MNVYhVEshk6 zBgNuKu{csJjueX{#o|b@I8rQ*6pJIp;s{zCL5m}3aRe=npv4iiID!^O(BcSM96^gC zWO0Nnj*!I>vN%E(N66yn>&uYE5wbW!VUD0L%n|g3IfA}0N6;7M2>QYtL0^(>Vfms$ zvm1j@hvL$L#f2;ErMT5r5Y84peQ|MRfg|oE0thlng z!ckJNtg?Jr6y@fZFDopws}}Xjg2myp{JzMd;*!$*k^TkS^F3l$S5HYFG~X z!$Ao8!+{R^Q*4VW%9rVAJbVJy?S=T{u%dvRR4qFy3KuPc+e-=-E-kDmD_ph0xu(3T zvTA{KxdE%mK#HxXxNMQTvaq7GxU8Tge?hV3ZlV>{L@TO^R#X$Ms3uxYCt6M?S`3Nd zo(m?1doGx0^<0w0Va?WHlEsl^aU@wBNft+v#gSxjBv~9u7Kb%kgUQx)ShF=~&DNkb zTZ7hY4O+7`XwBAOabP3 zhu`9`CUeM|%pt$U;kP*a7DvG12v{5eiz8rh1T2n#bsg614h1ZZfW;B8I1+tPB?WS2 z$SA(0MAkt(yGq%vRhnf^XA&}Sz0 znMu(L3rY&h78O*`l1QtgT4>$oLaGu2R~8py9EIy*$*o+{cj|=;b0{g~#sZE7nOY1F z3v1$slKtUJTU59(yzYjQ;Z@;+{Nl2eg%#oX97;}(Sz5SeL3u&NBD5w0<-D#6qp+~T zkt4_Q5=Uks*4{(qpEolW^Q{dGl3EVozn*`x$nOD zE(fk=?7>^EzvNMMVGEC(TYJcb`^fI~z3zuzng6C~_dAke+Pid6HO^5@Hsj-1d@M^5 zZfdB;tD(v1)Sd2wRcBg1TVAVb)ye$o>N-`h;ryA_!%t8_b5m+VLuwP9o}Rz1sA%G^ z-09>_wpL_TtjCY4R!d%m8wej{?xqpScc;3y#43R-){fJ zP#3hKt~GSb<-i54X_>gDb-ywLodtJzYQp9-N5W?D>y}w6P1$m&*X{AmCl=Uo$(G%3 zcuJq$yl1}QaKZK74I4LZ=rzr!UbW0u->}R#U^>HfQ8{eO+NS-KtZv$G$oi4;5?ooH zn&VqWYs&K}R~S7f+=^&KT6cHa2zBwn$y+yV+B!LCv)LT(ksmqmp@7@=+p0mas+8FW zfAYy-d{k(+JKUa*h96a~t`8rMVcv{$Z0`0Voa1nJJ8ON7s?^Y`wN%aHQKY`Vm;UZV zn#xbjtgEYQZ7p)yrDulGU7*H_E711h!;uML``G?mo6`;_P-BZ%4FOqWL!=hAk3Gfd zg6;^ly}A0hYP)}6m0_$3^e3WfIm-IxkxUsb8wT!{w%+#?{n3twwzbN^J4dbWc(g#B zx9QL+!#H(llS-tejUGLEVe7jp5$ivF_UvhYtV52q&0R*v0Zo}>45Q<1O;z!vRvW2! zsf0A(08Jg2yVo#!-7ad$S7R78K8mh+&L~wf>cVr(lGUnY^@SxaH?FRCOSm;8Y6P|B z+5(>HFpmIFwJ+8w`%|f^y;|-%$<$t#2 z+kZWDmhAL~6AO>lX?7QPxO+El+}LYcuUgg{8+#RVJPoT5-b;~T3rrqXruR7s5 z*kW37mNoZai&{uf`fi$O#eEvK=6;%{W?)Umfdd&eruohXmifU~miZM-6(!fytuMW_ zb*f8E5$}SYu8u?0ub2Rj2h*J{=%Sne82@U~3nDme_aY1!tQh%t)jDnPcT3xkwQ6d~ z(LY~ZBSwF#2Q8qc9Q~&bZHh40jqZi6bj5C9*47!GYPB+~t=ZSpqO!x%McM05_p}gq z^Zt0rE#m`iSW~NL#vYt~*Es6L+dKu312qr~IVNa$7;;R|dYGD7Uk{;@OKGrc-hRXQ z#2hNE&CRXVsfzFC35B1vtL$p&-EHQe_aqdxwpJ&26lbrep!+a&-+;pD*B8+6?(FRB z>eA!m=^8L+Gw05&cIkF(A9~Zwp{*{M?9E&8V-9~rVdqZEt~0{ZVy7X0RQ=9Po4Q#4 zcsh66j_nU_H|xWGENpMydfsYt^X9%bH*Y=<3UVwVQ#qza@4>182{)M1*wm1Tm#LW> z&JRe)Go`VvS{9g^xl8YkS89lb7Pcp?agJ(9#q$T62f?mZ=&O^*4VtA2)kS8G_ zGqZkYlkzn+^Zwg6-IAzpP0RQ zNYx5WO%d1wF8|>>o9?i=FaC$a4iR@#o5@4YB*VXVkv@XKU5${`Qxje_f`QMnlREEelYe z@OEB`qf}SwrR5zRSo2l)70IsPCy7qTQ*?J zW?D~K7Pf9?9yV*rf+S?rnASU%1!*wz`sCp5=bpQJs(A%d@0Pam+Pk13E(y{g<0YQzqQ6dQ^!|cvUp87R%+>Dv@hvIDzHZTs-A+ELUVzf))Ah^H`Fp+BRB~ z`x01`&BTsvr*~{K6PsySrV=FNx~w`{UIBIaXjk6eTDudQ2-on=`DA&Uy6}}rxv}5| zPdCzOxZU-_jlL?Zt%!0E_Lbo^_paTZzWm)DK&4N*RZmq=ch)s z+bJt5YFC}M^4+3^Ev9uGg>pyi`SzLa0_w@$RDn!6jRWE*)9O4_B&c-dxp3afbEoTT zRWR44cC=vwD;I5$lvAGXU9lz2G(U8sSjvy0xkH(Tdtx{@v}K~)0Lu#9zIQ!MZaOXX zXgC*}<#mGO)wtSdOKWuMaBuzVN0jf~KBFmyE(4|;rSgifeRBKAL|!--=3%o!x39^n z04g)rP^XcHS9i4SBP~if69}088BK1wEXn1g-J0TRqLaX7=1Q(qs-U>c3i7fjS8+9| zSz#PYtL{0CH_3>Bac|q3T`iWKwr9C%w>`dhVvlJ%y8H6)?Y_#Y<80f%zAJq0slNPs zfBG_Qxq#bK?RNRD^{_8+NNLVtpLW=%u1IFN&nV9# zhwPs)d4Gi(eav+i%Hcd?^eTUCkV!5c)6GeWKo$5yRh|J#mUw?mI~Z;VduGJ4N!@v2lE zx4B}mR8z_(4%ZmRQkNZ2Wi~9`u2^q+o!f4UbmI+(Y_nY@q-CsmH&!)Ylb*L}bfZQK z`r1x;cf`#ck+^?H*B>8Key+WfuHQXq+&ABh8w4GeQtdtd?t5<>P+j4F=EKn6wx|=D z5YB1`tJ5Y(#+dBGgdet3WP;uqDQ?#{{t6>9{OsZz?!qlR3 zBt3ofj&!xs4Cnmz^SrOw=4#qgE~vk{sAyk(t}5C3#lG(rUw!jwYKeks?rtki<5giM zif{w3wTKc%t4y@gKy5p=FWxZTROdIguU?lLhdUy2t1LH3@+N{x_}_p3`|sa>->(wO z-n)H6#^{03DiPiPqDj?L?`u&B9x-0>AD#m+o&Ld5iM~X4K;TVuU+=zR%}L zEh%b>aOx56^8-?GXX-$&)2*TZJ6Y}AD>~x4jP{5q79nq}df)Y&@GNjMqlIp@o_6>S zdYWQuIwRd$gnRzt-Wv3FxBhvZ-HG~V|1?`g=X=9V9hr?J%OiR)SZ6C4vdc|=5n_W z{F>+epw~W;(v2$c?pN0SaOlD29=L4|q4tL!?X5{0<%ahz_ql-!R@N*&SAFp15+)m! zXV_bAvzve0DXVMFYd4#}xatT*JZ;C3^6oUFhgWN^NXeTf2eEvS8}TQ4mrXNjcC>b? z2&$5<6B{UjJ+c4R6B{C(JklMz{7hR{N87q(QBe%xYD|sU&~kEO%*K|zaeA$$I%lHT ze{$^1=VouHAui*a_};@mb=9O9QEn~L9m6^&X7kVY>E|0Bx#`J=Vl=nR)mtVwoR2?o zi;m59?Vz81jpp0l)cPhlpp?@!`;D5zXL^jDUgdPHht=W^d85ykMm>@>dPZlzbNc#P zqcKwUNU*?C{j7~@0eZ@zdhU#j^{By%jM9(kL4=3I~W!&S=NO|ME zF{WFSe^hFJV^p^$|4^A0)2SJnSq5e4*3!Z?d*K~k`@eC^Fs(cP>f*!u4J;tFZhZ0F zZFE_B3C(|7GM!H+BH|@%js}fm8TktI^?KyMM9jk6)%zXWY=h7qPxTZU< z2R3$Fsg$y>tW>||J$)%--d^AGjyAKVaco`tnvP@L=uVeALKIFL*{`1~!X*=N!OpJE zU|d9mx>aJQP9uJhCyH6Qo_cte~w_X>$Icc3Tx;A&js@>rOdwM_j zl2zXGSFfu6V1Cu_)rMo*?40Yd@Gytlydf(;;m{Ut>FG^-x7z8^qnzli@YOjxF|^Qm z@oz;O2#&M0JQ}-USzf31pP7%vzM?YC6XTMLK6w=blfS+|7-|Ni|OlJk3YB(i?pkHcoe~SL4Gs z)=s{1xJyU!zRB1r;J86c>y8e(B4A<>h~L_%*EKR1p8RRnU7Wic8=JA%+VSCs=g5&} z^*IQ>kUm`9QdX0)CVi1VZui9lS&f`HU|rSKb3X4_TZfs>s4?2(b~~PztDG#CX%Ey% zrsaB)5z}StjjF}(rYPfOQoELuuRQ!>Oz(e4pE{Yb1M=0_SgYYJ4fg0=#<@l)5m|4! z3VQCu|Y2u03-5g_V^6Ub@xT^$^>&SNe|@Rt3f}N>Hbxr?wmdVwE=3E=7_i zXnxJv{GJ<(Ie3>U_cyx$fUZ{;bTN`gUbu^OjK&R7=qqrt>1>UQ{x^3p~aa&tjYybHRELrRRyk#yMj++}A zwz*`{fUkDGfqx;Op4ew`cuH}&dPq)w@{A|;wRH6vbPlZP%9;nO7|%Fr+FD?pvTlcU zx3V6D^{BG`1nVVby#{NKvfhDpNLkIWzQi+-^i*W4vKArrvo;WauLX>-h0nCX#s&dH zRJ|Fn5>#Fitcl9XgLR#34c(Q3)YjW9>~~>Di5Ej zvp1~LgX-MFxp~uPYvXUIDqC1yHeNnpIG&y`96xjX^zsUPt84st;lr&(iVannrcxB2KQ zU4)iDDKk4aCp&-2^xQlx2VcS{T;yF*i!+o8;L! zxfye&&d!%ks37KN=gi8=K@?=L^E1fK%9}P#=8gHkUNb7n(YxO9-i$Ku6j;Rzz4Gx^ z?}CCAU`ENTN_;hL;Sz5VaxeGJDov~`UOH}tclCl&?=pN%?s8Q@ZG4U@buEf6(QG^$`QG9Fh7|ndH zRnE{LCJ=yUl<{))x@8`V?8VgWij=nb;%l(a4X3>{*YRL29)3iHbWP`LaGZgBnO1G# z7-Od8jFc~jnI1+-D;;m9`{5V|j05EV6v+Ul1DKK85&*MM+W>3@9tEBOUIh*S9|Ces zI0>A^CBP3B1B?KE10(}kfSmik1#Si8tgZp>2C(k1r+}A%2H<_*Q{Zc$4d_PDFylE^ z5$*-X10f(6K${#s@*-e4unxErcmUV|JP+&v-U2=VjsPbBw9C8jNVgL}jC8cCUk1Qe z9lq+<0JDJt0RHN$fNEe9upM{;cmY7$dJ}LM_#F5aI1RvO`P{e-|C7dc5pX$xwrz;3 z4RN)>XB*;bTLGY58{%Ml2-pd{0_+Fy-&SmB)Ak*3h8A=?`pJ%dvX2FPz*PX^V4nrd z2k>8K?5ly_0rvrq0e=Es1sZ|B1Bk8tIPfFTfuVu^azq1Y*Ksj`2hts>0NQmRz7F{6 zC;|{)$65ewJGKJ#z*7L?;MfZw4i1b32YhyX2mB25Xqppoa1I7W1D66AU(Ph(THpo% z+Tkn%(6$q8JGTLk0M7t_2KE65fqw!=fqw%SXV`;lt{4D*yATf-{C1&T*CYV(aODF_ z0rZ_~BLJUWXxH^DfOxnNV;AD#!kBP<1EAkr@HZk7K)*$d0VV)f0*FP#OaN_0pv{Pt zKpn6Jco29RfUgm60%$YhGvEZ!26SPbW1K}|{6@wDh(RRALSzG^9Pk>@0K5-l@Zp7C;92f@#fHdG*0R84(2$Tb8*L^#1F95&Y ze*|^|uLBr!?*9Qk2GF+qBmm$0!S{X`%l(D{@VlQ6NCnV${SfDV^MGPt1+X623~UFU z1fB=rcR$3XAN=lzw)-6iP63E@G-47x7(o9;Ujd-q=qbSU0DO)v1u!O}>wvp~KLGGK z`bFSxz`MXd0r(sJZ=e&)sSSt)MgWLKj31Z?Tmzupm<2!?fLO%b2|NJo0N`uP9-s;M z5Ww7s`3Atai1}61V(kE89P0t#Z|vnj3XlO@3(NuX0mLG<5c;HeX2}lRfe*f73+V6iWPzBTicLRt?f5f^!`mz6B z;BDX^z?T5p?*EIXwps(+IKuYK_yOq9v_a^+h_&~)HtU+-U1E-xb{Kc z1Lrhtunr6a;NM{QH+Uj|`8F7_91PzE-vK-b{0V?hgAV~Xe=z!eaJQxni3V`)5X5Ro zHZTjo*c`G9Kpzi59}IZ{cp2Cad;a$ zp9cN{90WcBnt^Wt_%Iahco1XHVBjJ^0BOKfU@m|ddzJ%e&$AKO0&EA+Zyv+S89PJIC4HN*l-r?|V_%`54;LpGT;3METa2o%XhXMV8(SQIlfa`z)U^!3+ zYy+MI;KvB`-G~o?kAW|M;{e9wh_gVqrj5k79N7;T3XB1gfGl7-fNLCC3akS#9!BE& iM(zUk0EqiYv^nx9fc_lWf&X*IfGA)vFcP>J@clof%;Ne0 literal 0 HcmV?d00001 diff --git a/pcf/siji.pcf b/doko.pcf similarity index 99% rename from pcf/siji.pcf rename to doko.pcf index c863f4f8fede0379f8168e533d82520856a1fe03..2721dc15b6ecfcff0c9f8d7b4d9e94180b3bc62d 100644 GIT binary patch delta 29 gcmZ25g?Ygg<_)tLSzPk7^Ec096o#_4GG!kJ0HLl5-T(jq delta 29 gcmZ25g?Ygg<_)tLS%NdOGB?j-6o#_4GG!kJ0HPWS;s5{u From 89b7fb6a37b82ee56b01cf17b16466604937af55 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 17:45:38 +0300 Subject: [PATCH 02/19] Add README --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e8ccf5 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Doko + +Doko is a fixed and maintained [Siji](https://github.com/stark/siji). + +**Contributions are welcome** + +![doko](preview.png "preview of doko") + +## Differences from Siji: +- Build OTB font => support in modern apps +- New repository structure, new build system, releases => easier to package +- *More coming soon* + +## Installation + +[![Packaging status](https://repology.org/badge/vertical-allrepos/fonts:doko.svg)](https://repology.org/project/fonts:doko/versions) + +### Manual + +#### Requirements: +- bdftopcf +- fonttosfnt + +```sh +git clone https://github.com/begss/doko && cd doko && make install +``` + +## TODO + +- [ ] More Glyphs! +- [ ] Adding glyphs of different sizes. +- [ ] Improving glyph alignment. +- [ ] Creating small and large version of doko. + +## Credits + +**[stark](https://github.com/stark) for [Siji](https://github.com/stark/siji)** + +**Sm4tik** for sm4tik xbm icon pack + +**Stlarch** for stlarch font + +**Sunaku** for sm4tik font + +**Lokaltog** for symbols font + +**w0ng** for xbm icon font + +**Dave Gandy** for FontAwesome + +**Lucy** for Tewi font + +**Phallus** for Lemon and Uushi font From d444810a19e0b7fbd174f324f4b29cd5f6877911 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 17:49:28 +0300 Subject: [PATCH 03/19] Use makefile instead of scripts --- Makefile | 14 ++++ doko.otb | Bin 20264 -> 13392 bytes install.sh | 193 ----------------------------------------------------- view.sh | 46 ------------- 4 files changed, 14 insertions(+), 239 deletions(-) create mode 100644 Makefile delete mode 100755 install.sh delete mode 100755 view.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fdd7d0d --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +prefix = ~/.local +x11dir = $(prefix)/share/fonts/doko +otbdir = $(prefix)/share/fonts/doko + +install: pcf otb + mkdir -p $(x11dir) $(otbdir) + cp doko.pcf $(x11dir) + cp doko.otb $(otbdir) + +pcf: doko.bdf + bdftopcf doko.bdf -o doko.pcf + +otb: doko.bdf + fonttosfnt -g 2 -m 2 -o doko.otb -- doko.bdf diff --git a/doko.otb b/doko.otb index 963cf863b926cc3527c0addef41cb03742ae67cf..ff70ba01f091b2d74dce6b4974f1f320ae8f4092 100644 GIT binary patch literal 13392 zcmeHOd3;n=vOeeDUT!BB(m>EQEC~{Xaa=m!!!R~<2&m|wFbpW71Bqx*WZx0!g}5Xb zSyV>CsNjMN!sr`EWkzBeqoD7BQJ;=zlmvq|`co2!&<*MId0(A-Z)oE(%bWk6x6`-k z)TvWdw@#g^b58@~jImBE#8fu+(!!DBpLy)li$M8=v8;#(Ioq%=z?~ z-(EOyRqL!57(4r0@Z2gQw0cp7YvWIYZH2i}pp+W-KiM#MSq8 zy5Dkh@lAd9q;)pigKq)Bt)@YK-vqjIZ<)DZ$+nBiYk=l3=4zNRdulO_W9$NaZO3L7 zFPWn(Ww3Z4>Cc%}JoDzRkLQ&#HYk%Z-gD0E`3n;7u+O2|OZ`k?OnmRE!6ig2)-3<1 zo7L6TNhp}8t3wNaFyFFthBz-C~)7b zrly17_Z6;N_b{Z1CTimfQ#&j?=gn$581Q*Kx#QM7T$t@sq~}rq$on?Wi9v^~LWvR{r_=4}^T6`Qhl0`lSuSmPV@w z#f$Iq1aW_IKkm1_ME9Av4@v4CZ;=4V8cd*5kBcR!d$~v?SC*EpOeR-8{sP@!c!ln- z$a|^LQEK!&ZgrtA%>nmmBRs)_6@8dSjVmq`ZH>A^!;|&&Cldai($4%*Ys$*jjLNs! zY!1EGyAB8ZX8Ya3Ot`s2p5Tnn$SzRc#d!w7Ix zoa8kfSQ-sUbZPXFwnoDMSE~@FEjh5|5Hp@RP*q3k)o6rkwp1cQoiy>n z=rM5FVK7C$J}~*~1Af1uDq68jm~#n1(Yh)jUA4^DQkytnp#GLx-vKEf505Mr@U>gn z7?J2uX=OXLiAnV6Y1=i}{;u&DIdJKr0y)9%-@JV09-A@QqP{v?h>)azSn>J)Y&dS< z1yzkx4}_UrQyhA-w6rv7VlT8~uaqXGI$CYnhCQ^{+R>SW+HFpKaieEXgNbFeW0UqY z$aRa}-F_3x`kRGK`kP;_qTujdyM_l%^o@P(=)OP$C49gso59c(kh8t+o0iRIR{_0W3Y?3CPvGEV#3+L6Q-PGSM646-V0|4Bd6r*`%BNgH*gUB-QlW zGk#TnV-j!o6eIh}KoArYMtBH{2_fC#E3B+6^vRr<-#WQkh!4zAiPqIc13szm*abI# z*euBb7#F@>*9@C-OQO+0u1D%jdWsXZ#`JZ)`RZ$mjqr}g3=JG@!g(4k#}_ zi6N#%g;IkI!#xYT=3Z--l$xqSTxP9Vd9rKn7_+2=1Jq&GnyqRgTUHTdJ>VXHWP+3= z9rKkD(D|)Xg!l@T20RiMAIO>L-*f&IT|LHAce7t9i=`agKl8V;gC@G)8{<(pjX8Rw zeU9cXJab%sBTo{F$|_5HZecVxH`hpwK44LlVcDbUr^4Y=<|MeiQ^MgX$UIa<-M=|= z-kqHhioo;7KDFRdb9K50y`MdTa0H&aczO3PfKw292Go|-4lty828<~y8-s={K)Ka- z_SL1KS6p~-RE@#zD}11A4T8lmFSoF;a!ZYD_zLg4qwMZNpJBw<$NEh${q7$4?)vrb z4s=Ux)BtXlk8KmHP(H{~ZP2#KrwhfcPfoN7#e~Yr309$?iiCZNRA&SZ=3EwvXEwem zkxE?B_`9=i{(XC$*jib+b#Iw`@?j&(&saF0$yf*8^}MgXu&EB4e$G1%J1VMn<)rsz zpJE#@Zp-u!RMnyC=Y;Hbo1OYF{qUI4XDqY3hTQu5_IxmbOqDG8tKe5FAD|R_gJwQ> zW}AV)&b3YlRxAvCiqZklvb|fSoir;{eWP1Sq|YQCEivq0UVg@k7ABWZB8UwRe0Qv^ z_s%I#h2%2*{M%!v=Ey}YsE!!x8)XC=Q)9}o{-~%KvotprL-Lj#C6u+OgS1nFDkBBB zjHk4oMzD{4^yRwK9nmK0Fv4{s!HUz=Ok2=j(f(dFGzc zre&k=AzWIMb7NK3e;{|0p9ud#LkYwF$DZh#aoAWS!r5y-%@468PaW~2gnREZQmX= zq|O(os)dNhF;6}sCR9kHs5SRqq8dA86&u|is$40)`dDp7Et9xosE9T0f?kZ= z)ff{8yk_n@Z&stV81l_BO44ZqiTz{Bit+fQTG|T+QgCbuCEeT_N|W7#Vxp!!(TW%n ztz3{amI)a+?zyq%?1=QH@v>l|^j=`1^lo6H^zI-x5sPAN-$dlTVpd5>vz)X=yGy1v znAkyd(%#ea`ICv_P|}z8O=M8sH{V3!drR^{i%tMrbmC}bNcwDBZptyGH4LV(ETLCE2NAXlHnC3 z!zZMS8j^umT#`{E>ivPXqe851dbPEIfdA#W*oZx{V_2IIj)ZKjPFzAnXH)eGY|>UA zk*(F=eBz%g@IGYMXP=PoQnOFk6}&XHL>!9UsW&&<=`E?&J7}*=tF>NM>y;K0NpF$S zpLRy|1*59xDfcd1Jn!tlJo~+i=Mh9;PO1}y0VGn2S1M*d;4|ZJ` zkM|Cb(?#o{e({PGzl$Yxhnl9BcJ{T3WJ^VkY=sq@=0=t=TDH8}ps4rF*(LjI$U2%k zry#7`ZD~3-w0^Z80}CK4_2i73G+4gg?WQ~l0tAzqcsJkq>mxi8DEk~(@l9Qf8- zUw--4TLb63dB@7(eY$n-+@-)JB7yk4K=T{ik2O6M+k&XKd*)9ATT=c2)xj`s<1 z)ZyLZsc{G6X*zT1MbnZ&Oh-cfsOk?z#ql&6)LCsfmUI5gq2$LO_u!EWEU@krZ{9fM!MZk>ZO(D?hKG_te{UVuYx;?9H!ccJI}zCP{0vS)X@tGu zcDs3RHb|`#UcBD?!&AF(F5};{e|Ewz+LQp((nznLrim`YjO@nboU24|Q#39;D0^BP zR~ilMqXtfIT$$!n(scLSmZsKN)ABj#=^XBg_%c>D93GZY+OQ)_4KX=6!}6*R_Z#=j z_?1CJmpGK2to^1n=ojfaOVcxWeAr0kqn+x>sy|%!=!OiY6IE@vz~Ovk^9?FCXratw z-iZ0m-xd8I5s{1pqg!<(J;CTESVo-T zmO#2qFh9DIFalTgA)UOj{NxMMYO4hna!7ZfEfN8L_L@D7Ez7c$NeuSKguz%*=>Kxk?apnS@?h~cvp?T^ctiB?y%0sXc4ig6>UlX z?ts&yraR$#>q(q(1PZ1i`!(0D{QkCvhukaYjES@F5+8Q|NfOP0S%XVHcmV^==EpC| zW|{+WY@O7(>5IFTv4pVxRhDQCWho+*rc9dUp0Ip>IMCW|X1r$pMUO=4 zkpApA0`~0RUndWMRXX6q#-~m$v0%YX1GBcB(~Spd89?je*0z&l>YHL_dw5VZXKiyl zOF4`BsqH~Apf+WP;Tf%BM|uc#Kz9JkmL% zA<+~URYzt!UubKJ2jA)G{&Dp%!MB~}vxQRkd z!bFqV2a_f$vX2T2-E>lHFocQvHnE@3uceBfgdR~zMQ=izR8o;G(Na>8T}sp%QjuLl zXd|h}ZX~pYRAjdh+A5%8Q!Al@RMg`P-Ka**4u%I|!FYR9#7AiD&AZ$+vdMAjAw}hi zevxYDs_vR@b8?%mWNsg8cX44W+$x}ad>j?wu6VKB;*DF8$Mhb#V}4|s!u(ad{)Q`IKkg`4d2?3 z^+!~|_H_GfX!;JPF!OiZ(H5GG?AzX36}k;gV?9g>9-q>7t(btf4m#wZ-(%=PC#szI zEYDMMS>G;Zr>~^xxhZh2)CesH4Sn{eUxDV7f~7Vumm zXh~fXJL33tstu)w_|ql7&N2XRVs6$Ga0%0s#f}a8yKg@=* z0>A_t&WfPi&5GF!z#G_1_GiF3>_t}1n2Nf~y8-6#Ox^?VecqGf_c8S_&*PWiceNbu zgHC3IDTFlnWL;P;W?2MOj{N88J znbzyZ9HKMgpNKf_-FoXB71Re&M>Du+Gx2Y7_<27H3=;co)&;BWDLfN%1*`BA`6 z_%VKhF^8AOcpKnIo>bsjN0Fi`g8>&P7b|#f$6;l-QV5t(Mko^)HgC!`%3{DA%!8f5 zKzxnkPemJq-#2M?@SOOy^Gue@2I6Dz{r(5|lAxut?yL_xAKt!<{gzFEZ)xSI6wL%JHb`%=4bJ~`~rRn zAH^r|B7Pg@QR-CT|Bk+}^#U!Ooq=aOPd?)~HW|_vu~N30RkA19e?s~mNdJ@_X5Vtg zb?)J3^Yi#%KAex{*YaY>U&w>JoZrtM!Ce2DSM$AmKX2qsyj5{3ZY4|Ur{pO^l`EC; zO0jaAvRDZ#70QFkW6E}Ahw{4eu2QEQR*tJoO;;bl!J&QTn#rCp~Fe5FdwYl7j*uH{~=Tl(o5`HHl^bx+5zrgjjY=Yt((}KI1vkDc(MOh0IJe{A(4X=!XS13Xj@i4F85Aw(OHvS@J@Gbs6f8WyC`3Ycl zxsS71aoL$-DIGd9jc4rqF50K#UdC>Qk<+@dZU#>VexJvQ9vyqAmYj@{-<6Xw@jG&4 z+-8)4hV_Hp1Fb1T_8y|K&RCs2SwCdRUtxV-X6SKuLSKh9@4gqbPOJy}1y<3eY@A%l zi}7>nJ!}Kk&~x}mU0skTZq`N*-6YyZ{*|Qd@N>VCchmy^uk#;@4~e9ZXCY#{zLdgWDJrZ2mbzsNVVrY>P4 zVcRvRCDYk_wt|(xKTjbCyvrI`6H9O>?}Gf2%Lnm7ekJnDRDLU;%a`+2d?VJ>U-^IW zPx)6ohLzt%>81Pv>uH2C2KL^f%vDw>_nDM$JJ@|@v$UfZDxUPAhwMtn7LNW* zTRjIr%YYBhN2Cv71?&o}oa>PTX2`Yk0DBB8=M`jtI`$R(>EdVbb71RGel@=aKAny# zwj3E?4PV3Tu8pWzl(Q>&rExbPJ46XP^}@dn!M}t+wZxaPYZuO%%5cCU+8oo8B?uSB zH-b#F%am!3X#f=IG=f$DF4sl{d_|1z6Z7(NN@mYmkYir7F-phj6X@f--XtDLUL5jh zw2lfk0MG%b;KM#!M3yj22f-X9Icnh|i0@bSQ%9y`_>MEKN%AKZ_16(%z+{PQqoiA- ztad82 z`YiZkeHPu~BSZS|+psL%3wN4gEZr9R?G#fh(@-z^yyzonD>W$CqO1XFc&?6=w$ z{4v?7@;c(y*nbvUx-5A#-opP8*l|3K`7s+Tc~-y0Ytd8TkJ*t*PsOdVM6+xn*s*Q( zS#*Mb?`z5Vck%rra!4n|QffR|zd%ct1uegjOrlZzTk?0Y(OVFaF@)GT*zLt+H9?PbG1g7fwN!*+ZId}gqJY^n!QgM1OGoffHz=;PScF*^!u?Ax8CTO-*6C=g_EL5C0^fk+kz35!4$a1<0p zbQp~rI*N{n%jhV~NF*2)eFtTnaY3UHL=8Ghf(Qv_sqftTR9Bey_x=UnZ#p@t=Pq^6 zQ_o$_?dq;*Ow+Vztya^u`I*_d-cR>z9HMC%ZcWp7=VWHjuD))_?V2`bpQeq<%gLOY zl_2K+Q`55dYFgy7+}zpIwui=!M)?zHWklYrEBrSkuDDs#*b=1YFDxxsrp0`Ra>v`q z%UoQtrU?0XxzN6*Eh#KmreVhI-)J;G-VM%G_>H#INH1?jR*}6;0 z7Z%7A&UfJtFD+QTjPHZz8k>x?x2&MF@XDMB2er>WT2{WI@~V})H)&c#i>7%RHF>ST znjw{+=f|X;)uL?3(6ldK`)gv~(Fow$0nMdxnTr?B(F4f8#r6ngsFqAq9c(g(-)3dW zGnuNDZsyr+kFZ}fJ8ISQL&)1oNA_VIq*09iq*9Ausx$FXOUKG7-1ja+XpC< z*AjktH6}f~5aFW#9{(GG|3=`y5%~W+0?+`d1KJ34T}(wvu8<=%o94yQh99KAUMbhn z7R@lcRE{DBy$CO+Os?DERUf8$g$u*!|DTW1+KW~&?3&xO8KM`CEQCtC!pzsTi?!QL z+onZnO{Q(vVztwz?a(5bNA`wI>RTj0HiNznFl`Q|(Wb3yQ?+E%wrNAO=S|y=emrd2 z4s8&6YLYesbysSBO`9}h>Pj54wQ_AKpk<&wHg;@_LC0 zslfQ8@qV8l_5SZhm78XDO}iehRA?*UuXNB03Lhw>WAev~3WXIbip$HqKA)K2^ZAey zZuUPeRQeFUSVY?#&Bk9exu;>kf4tP5g=w5|rZXF}GY4}r7mHw#EQ+~VKNihmSS;($ z2C#u_5F5;fu%XPuhOsy{oQ+^3*(eszylgZZ!!BY8Y%IH&jboRv->^&BW$bb`o=sp^ zFdq}l&jKuwC9!0d!h$Tsu4GrSRCYC+$kJFk%V3!-i)FJMmdhrw$!rR{hF#01vT1BO z%VRUxb!;Y^#b&cP?0R+syOG_*=CYgFE$p{!9?NI*Spi$X7P3XGkQK4TYzZr7x3Zm#@4cRY(1-HHLRA^u?=h^yPf@x-NEi;o7iS{ z7rUEnVfV0m*;aNR+s5u^53t{}?d%WiLG}=kUScn^KeJcZtL!iAHTF8&!}hYjvcIu6*gn?4-emh(BRjy} zVomIA_6~cO9c1sZ_u2ojzq3Q^1NI?1%>KbXV*h0SVjr_l*r)6>c7%P-zF^JlOZF8z z%8s!XcAS09POxv-x9mIC%1*NH*$?dB>__$!JH^^qJ3GyOW@p$h>?}LSI#?$=&wgcH ztef?)US@FF{haIE#_im}o!rGEcqEVFZr+ba^B5k>`||;OARojB^C5gF_wZpnjt}P} z_((pA$8#?q&BySIcmf~GFXrR;CHy!1Qhph~oR8-d_!Zp81^4p+Pvl8FnWyj|5AiGc zRXmkn%_s6Sp3XCPCePy8JcsAvoUt9cEt<#l`m-^g$0zvFlCJNYKQncv0l=3DqZ{9eA5-^aJ{`}qU> z_k2751AmY|guVO2{1N^re~drQckn0pll+hTDgHFy$)DlR@;~wC_%8lD-_2j(FY=f8 z%lyy$75*y!3xAEj&iC-W{IC3P{0+X3H}E(4e%{Cr@V9sqf1AI<-{lASd;ER=Km70f z5dVOG$Pe>>@Q?UE`M>zb{1g5u|BN5upYtzxGyjr*#gFo1yoDd$+~!?YcvE z>MlJ(kJO`dx86^W)?@Toy}v#{AE*z~2kS%hp}I#OrpM{S^%43=eUu)rd-c)!82utW zK_9DMtdG+#(SM^~s$ZsGu8-Fz=vU}IUFd#2peO1{da|CP2lbGCrGAy3s$Z>7)YJ5I zJwwmbv-E5|N6*zK>67&-`ZfBs`c!?IK3&h#XXw}IGxb^eY<-S?y?%p!qkfYjnA(eWAWcFVu_l#rhJxSieZ|nC zjyad*Ul>}z5wwBh%Z2V0pbe~Ux4@m#1|mG0PzKgFOm2X zi7%1(5{WO7_!5aPk@ymcFOm2Xi7%1(5{WO7_!5aPk@ymcFNye)h%brwl87&f_>zb( ziTIL;FNye)h%brwl87&f_>zb(iTIL;FPZp~i7%P>l8G;w_>ze)nfQ{4FPZp~i7%P> zl8G;w_>ze)nfQ{4FNOG0h%bfsQiv~w_)>^3h4@m4FNOG0h%bfsQiv~w_)>^3h4@m4 zFGzer;tLXAkobbc7bLzQ@db%5NPI!!3ld+D_=3b2B)%Z=1&J?2d?Dfs5nqV-Lc|v$ zz7X++h%ZEZA>s=WUx@fZ#1|sI5b=eGFN63ph%bZqGKepO_%etugZMItFN63ph%bZq zGKepO_%etugZMItFO&E(i7%7*GKnvf_%ewvllU@;FO&E(i7%7*GKnvf_%ewvllU@; zFN^rHh%bxyvWPE>__ByEi}__ByEi}X7_;QFZhxl@cFNgSY zh%blua)>X7_;QFZhxl@cFPHdoi7%J7O9|6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{t41Q zLHZ|1{{-ouApH}he}eQ+kp2nMKSBB@NdE-spCJ7cq<@0+Pmulz(mz4^CrJMU>7O9| z6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{t41QLHZ|1{{-ouApH}he}eQ+kp2nM zKSBB@NdE-spCJ7cq<@0+Pmulz(mz4^CrJMU>7O9|6QqBF^iPof3DQ47`X@;L1nHk3 z{S%~rg7i<2{t41QLHZ|1{{-ouApH}he}eQ+kp2nMKSBB@NdE-spCJ7cq<@0+Pmulz z(mz4^CrJMU>7O9|6QqBF^iPof3DQ47`X@;L1nHk3{S%~rg7i<2{)td>#KI*76-g;6 zNy4)HmKCt9M9WIDtiE$oth}IQg~C>{&$29zWQ!x&;z+hQk}Zy8izC_MNVYhVEshk6 zBgNuKu{csJjueX{#o|b@I8rQ*6pJIp;s{zCL5m}3aRe=npv4iiID!^O(BcSM96^gC zWO0Nnj*!I>vN%E(N66yn>&uYE5wbW!VUD0L%n|g3IfA}0N6;7M2>QYtL0^(>Vfms$ zvm1j@hvL$L#f2;ErMT5r5Y84peQ|MRfg|oE0thlng z!ckJNtg?Jr6y@fZFDopws}}Xjg2myp{JzMd;*!$*k^TkS^F3l$S5HYFG~X z!$Ao8!+{R^Q*4VW%9rVAJbVJy?S=T{u%dvRR4qFy3KuPc+e-=-E-kDmD_ph0xu(3T zvTA{KxdE%mK#HxXxNMQTvaq7GxU8Tge?hV3ZlV>{L@TO^R#X$Ms3uxYCt6M?S`3Nd zo(m?1doGx0^<0w0Va?WHlEsl^aU@wBNft+v#gSxjBv~9u7Kb%kgUQx)ShF=~&DNkb zTZ7hY4O+7`XwBAOabP3 zhu`9`CUeM|%pt$U;kP*a7DvG12v{5eiz8rh1T2n#bsg614h1ZZfW;B8I1+tPB?WS2 z$SA(0MAkt(yGq%vRhnf^XA&}Sz0 znMu(L3rY&h78O*`l1QtgT4>$oLaGu2R~8py9EIy*$*o+{cj|=;b0{g~#sZE7nOY1F z3v1$slKtUJTU59(yzYjQ;Z@;+{Nl2eg%#oX97;}(Sz5SeL3u&NBD5w0<-D#6qp+~T zkt4_Q5=Uks*4{(qpEolW^Q{dGl3EVozn*`x$nOD zE(fk=?7>^EzvNMMVGEC(TYJcb`^fI~z3zuzng6C~_dAke+Pid6HO^5@Hsj-1d@M^5 zZfdB;tD(v1)Sd2wRcBg1TVAVb)ye$o>N-`h;ryA_!%t8_b5m+VLuwP9o}Rz1sA%G^ z-09>_wpL_TtjCY4R!d%m8wej{?xqpScc;3y#43R-){fJ zP#3hKt~GSb<-i54X_>gDb-ywLodtJzYQp9-N5W?D>y}w6P1$m&*X{AmCl=Uo$(G%3 zcuJq$yl1}QaKZK74I4LZ=rzr!UbW0u->}R#U^>HfQ8{eO+NS-KtZv$G$oi4;5?ooH zn&VqWYs&K}R~S7f+=^&KT6cHa2zBwn$y+yV+B!LCv)LT(ksmqmp@7@=+p0mas+8FW zfAYy-d{k(+JKUa*h96a~t`8rMVcv{$Z0`0Voa1nJJ8ON7s?^Y`wN%aHQKY`Vm;UZV zn#xbjtgEYQZ7p)yrDulGU7*H_E711h!;uML``G?mo6`;_P-BZ%4FOqWL!=hAk3Gfd zg6;^ly}A0hYP)}6m0_$3^e3WfIm-IxkxUsb8wT!{w%+#?{n3twwzbN^J4dbWc(g#B zx9QL+!#H(llS-tejUGLEVe7jp5$ivF_UvhYtV52q&0R*v0Zo}>45Q<1O;z!vRvW2! zsf0A(08Jg2yVo#!-7ad$S7R78K8mh+&L~wf>cVr(lGUnY^@SxaH?FRCOSm;8Y6P|B z+5(>HFpmIFwJ+8w`%|f^y;|-%$<$t#2 z+kZWDmhAL~6AO>lX?7QPxO+El+}LYcuUgg{8+#RVJPoT5-b;~T3rrqXruR7s5 z*kW37mNoZai&{uf`fi$O#eEvK=6;%{W?)Umfdd&eruohXmifU~miZM-6(!fytuMW_ zb*f8E5$}SYu8u?0ub2Rj2h*J{=%Sne82@U~3nDme_aY1!tQh%t)jDnPcT3xkwQ6d~ z(LY~ZBSwF#2Q8qc9Q~&bZHh40jqZi6bj5C9*47!GYPB+~t=ZSpqO!x%McM05_p}gq z^Zt0rE#m`iSW~NL#vYt~*Es6L+dKu312qr~IVNa$7;;R|dYGD7Uk{;@OKGrc-hRXQ z#2hNE&CRXVsfzFC35B1vtL$p&-EHQe_aqdxwpJ&26lbrep!+a&-+;pD*B8+6?(FRB z>eA!m=^8L+Gw05&cIkF(A9~Zwp{*{M?9E&8V-9~rVdqZEt~0{ZVy7X0RQ=9Po4Q#4 zcsh66j_nU_H|xWGENpMydfsYt^X9%bH*Y=<3UVwVQ#qza@4>182{)M1*wm1Tm#LW> z&JRe)Go`VvS{9g^xl8YkS89lb7Pcp?agJ(9#q$T62f?mZ=&O^*4VtA2)kS8G_ zGqZkYlkzn+^Zwg6-IAzpP0RQ zNYx5WO%d1wF8|>>o9?i=FaC$a4iR@#o5@4YB*VXVkv@XKU5${`Qxje_f`QMnlREEelYe z@OEB`qf}SwrR5zRSo2l)70IsPCy7qTQ*?J zW?D~K7Pf9?9yV*rf+S?rnASU%1!*wz`sCp5=bpQJs(A%d@0Pam+Pk13E(y{g<0YQzqQ6dQ^!|cvUp87R%+>Dv@hvIDzHZTs-A+ELUVzf))Ah^H`Fp+BRB~ z`x01`&BTsvr*~{K6PsySrV=FNx~w`{UIBIaXjk6eTDudQ2-on=`DA&Uy6}}rxv}5| zPdCzOxZU-_jlL?Zt%!0E_Lbo^_paTZzWm)DK&4N*RZmq=ch)s z+bJt5YFC}M^4+3^Ev9uGg>pyi`SzLa0_w@$RDn!6jRWE*)9O4_B&c-dxp3afbEoTT zRWR44cC=vwD;I5$lvAGXU9lz2G(U8sSjvy0xkH(Tdtx{@v}K~)0Lu#9zIQ!MZaOXX zXgC*}<#mGO)wtSdOKWuMaBuzVN0jf~KBFmyE(4|;rSgifeRBKAL|!--=3%o!x39^n z04g)rP^XcHS9i4SBP~if69}088BK1wEXn1g-J0TRqLaX7=1Q(qs-U>c3i7fjS8+9| zSz#PYtL{0CH_3>Bac|q3T`iWKwr9C%w>`dhVvlJ%y8H6)?Y_#Y<80f%zAJq0slNPs zfBG_Qxq#bK?RNRD^{_8+NNLVtpLW=%u1IFN&nV9# zhwPs)d4Gi(eav+i%Hcd?^eTUCkV!5c)6GeWKo$5yRh|J#mUw?mI~Z;VduGJ4N!@v2lE zx4B}mR8z_(4%ZmRQkNZ2Wi~9`u2^q+o!f4UbmI+(Y_nY@q-CsmH&!)Ylb*L}bfZQK z`r1x;cf`#ck+^?H*B>8Key+WfuHQXq+&ABh8w4GeQtdtd?t5<>P+j4F=EKn6wx|=D z5YB1`tJ5Y(#+dBGgdet3WP;uqDQ?#{{t6>9{OsZz?!qlR3 zBt3ofj&!xs4Cnmz^SrOw=4#qgE~vk{sAyk(t}5C3#lG(rUw!jwYKeks?rtki<5giM zif{w3wTKc%t4y@gKy5p=FWxZTROdIguU?lLhdUy2t1LH3@+N{x_}_p3`|sa>->(wO z-n)H6#^{03DiPiPqDj?L?`u&B9x-0>AD#m+o&Ld5iM~X4K;TVuU+=zR%}L zEh%b>aOx56^8-?GXX-$&)2*TZJ6Y}AD>~x4jP{5q79nq}df)Y&@GNjMqlIp@o_6>S zdYWQuIwRd$gnRzt-Wv3FxBhvZ-HG~V|1?`g=X=9V9hr?J%OiR)SZ6C4vdc|=5n_W z{F>+epw~W;(v2$c?pN0SaOlD29=L4|q4tL!?X5{0<%ahz_ql-!R@N*&SAFp15+)m! zXV_bAvzve0DXVMFYd4#}xatT*JZ;C3^6oUFhgWN^NXeTf2eEvS8}TQ4mrXNjcC>b? z2&$5<6B{UjJ+c4R6B{C(JklMz{7hR{N87q(QBe%xYD|sU&~kEO%*K|zaeA$$I%lHT ze{$^1=VouHAui*a_};@mb=9O9QEn~L9m6^&X7kVY>E|0Bx#`J=Vl=nR)mtVwoR2?o zi;m59?Vz81jpp0l)cPhlpp?@!`;D5zXL^jDUgdPHht=W^d85ykMm>@>dPZlzbNc#P zqcKwUNU*?C{j7~@0eZ@zdhU#j^{By%jM9(kL4=3I~W!&S=NO|ME zF{WFSe^hFJV^p^$|4^A0)2SJnSq5e4*3!Z?d*K~k`@eC^Fs(cP>f*!u4J;tFZhZ0F zZFE_B3C(|7GM!H+BH|@%js}fm8TktI^?KyMM9jk6)%zXWY=h7qPxTZU< z2R3$Fsg$y>tW>||J$)%--d^AGjyAKVaco`tnvP@L=uVeALKIFL*{`1~!X*=N!OpJE zU|d9mx>aJQP9uJhCyH6Qo_cte~w_X>$Icc3Tx;A&js@>rOdwM_j zl2zXGSFfu6V1Cu_)rMo*?40Yd@Gytlydf(;;m{Ut>FG^-x7z8^qnzli@YOjxF|^Qm z@oz;O2#&M0JQ}-USzf31pP7%vzM?YC6XTMLK6w=blfS+|7-|Ni|OlJk3YB(i?pkHcoe~SL4Gs z)=s{1xJyU!zRB1r;J86c>y8e(B4A<>h~L_%*EKR1p8RRnU7Wic8=JA%+VSCs=g5&} z^*IQ>kUm`9QdX0)CVi1VZui9lS&f`HU|rSKb3X4_TZfs>s4?2(b~~PztDG#CX%Ey% zrsaB)5z}StjjF}(rYPfOQoELuuRQ!>Oz(e4pE{Yb1M=0_SgYYJ4fg0=#<@l)5m|4! z3VQCu|Y2u03-5g_V^6Ub@xT^$^>&SNe|@Rt3f}N>Hbxr?wmdVwE=3E=7_i zXnxJv{GJ<(Ie3>U_cyx$fUZ{;bTN`gUbu^OjK&R7=qqrt>1>UQ{x^3p~aa&tjYybHRELrRRyk#yMj++}A zwz*`{fUkDGfqx;Op4ew`cuH}&dPq)w@{A|;wRH6vbPlZP%9;nO7|%Fr+FD?pvTlcU zx3V6D^{BG`1nVVby#{NKvfhDpNLkIWzQi+-^i*W4vKArrvo;WauLX>-h0nCX#s&dH zRJ|Fn5>#Fitcl9XgLR#34c(Q3)YjW9>~~>Di5Ej zvp1~LgX-MFxp~uPYvXUIDqC1yHeNnpIG&y`96xjX^zsUPt84st;lr&(iVannrcxB2KQ zU4)iDDKk4aCp&-2^xQlx2VcS{T;yF*i!+o8;L! zxfye&&d!%ks37KN=gi8=K@?=L^E1fK%9}P#=8gHkUNb7n(YxO9-i$Ku6j;Rzz4Gx^ z?}CCAU`ENTN_;hL;Sz5VaxeGJDov~`UOH}tclCl&?=pN%?s8Q@ZG4U@buEf6(QG^$`QG9Fh7|ndH zRnE{LCJ=yUl<{))x@8`V?8VgWij=nb;%l(a4X3>{*YRL29)3iHbWP`LaGZgBnO1G# z7-Od8jFc~jnI1+-D;;m9`{5V|j05EV6v+Ul1DKK85&*MM+W>3@9tEBOUIh*S9|Ces zI0>A^CBP3B1B?KE10(}kfSmik1#Si8tgZp>2C(k1r+}A%2H<_*Q{Zc$4d_PDFylE^ z5$*-X10f(6K${#s@*-e4unxErcmUV|JP+&v-U2=VjsPbBw9C8jNVgL}jC8cCUk1Qe z9lq+<0JDJt0RHN$fNEe9upM{;cmY7$dJ}LM_#F5aI1RvO`P{e-|C7dc5pX$xwrz;3 z4RN)>XB*;bTLGY58{%Ml2-pd{0_+Fy-&SmB)Ak*3h8A=?`pJ%dvX2FPz*PX^V4nrd z2k>8K?5ly_0rvrq0e=Es1sZ|B1Bk8tIPfFTfuVu^azq1Y*Ksj`2hts>0NQmRz7F{6 zC;|{)$65ewJGKJ#z*7L?;MfZw4i1b32YhyX2mB25Xqppoa1I7W1D66AU(Ph(THpo% z+Tkn%(6$q8JGTLk0M7t_2KE65fqw!=fqw%SXV`;lt{4D*yATf-{C1&T*CYV(aODF_ z0rZ_~BLJUWXxH^DfOxnNV;AD#!kBP<1EAkr@HZk7K)*$d0VV)f0*FP#OaN_0pv{Pt zKpn6Jco29RfUgm60%$YhGvEZ!26SPbW1K}|{6@wDh(RRALSzG^9Pk>@0K5-l@Zp7C;92f@#fHdG*0R84(2$Tb8*L^#1F95&Y ze*|^|uLBr!?*9Qk2GF+qBmm$0!S{X`%l(D{@VlQ6NCnV${SfDV^MGPt1+X623~UFU z1fB=rcR$3XAN=lzw)-6iP63E@G-47x7(o9;Ujd-q=qbSU0DO)v1u!O}>wvp~KLGGK z`bFSxz`MXd0r(sJZ=e&)sSSt)MgWLKj31Z?Tmzupm<2!?fLO%b2|NJo0N`uP9-s;M z5Ww7s`3Atai1}61V(kE89P0t#Z|vnj3XlO@3(NuX0mLG<5c;HeX2}lRfe*f73+V6iWPzBTicLRt?f5f^!`mz6B z;BDX^z?T5p?*EIXwps(+IKuYK_yOq9v_a^+h_&~)HtU+-U1E-xb{Kc z1Lrhtunr6a;NM{QH+Uj|`8F7_91PzE-vK-b{0V?hgAV~Xe=z!eaJQxni3V`)5X5Ro zHZTjo*c`G9Kpzi59}IZ{cp2Cad;a$ zp9cN{90WcBnt^Wt_%Iahco1XHVBjJ^0BOKfU@m|ddzJ%e&$AKO0&EA+Zyv+S89PJIC4HN*l-r?|V_%`54;LpGT;3METa2o%XhXMV8(SQIlfa`z)U^!3+ zYy+MI;KvB`-G~o?kAW|M;{e9wh_gVqrj5k79N7;T3XB1gfGl7-fNLCC3akS#9!BE& iM(zUk0EqiYv^nx9fc_lWf&X*IfGA)vFcP>J@clof%;Ne0 diff --git a/install.sh b/install.sh deleted file mode 100755 index 53d84b6..0000000 --- a/install.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/sh - -# About: Siji font installer -# Maintainer: stark -# License: GPLv2 See LICENSE file for copyright details - -# TODO: Add '-f' flag for specifying the font manually -# Smarter non-zero checking for font existence - -# Specify the font directory -XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} -font_dir="$XDG_DATA_HOME/fonts" - -# Specify the font -font="siji" -bdf_font="bdf/${font}.bdf" -pcf_font="pcf/${font}.pcf" - -# Bold Colors for formatting -g="\033[1;32m" # Green -r="\033[1;31m" # Red -y="\033[1;33m" # Yellow -w="\033[1;37m" # White -rs="\033[0m" # Reset - -_init_() -{ - check_font -} - -usage() -{ -cat << HELP -Usage: - - ./install.sh [option] directory - - If invoked without any option then '$(basename $pcf_font)' will be installed in the '${font_dir}' directory - -Options: - - -d Specify the directory for installing the font - Default is '${font_dir}' it will be created if non-existent - -h Show this help message - -Example usage: - - ./install -d ~/.fonts - -HELP -} - -success() -{ - printf "[${g}OK${rs}] $1 ${w}$2${rs}\n" -} - -warning() -{ - printf "[${y}XX${rs}] ${y}warning:${rs} $1 ${w}$2${rs}\n" -} - -failure() -{ - printf "[${r}XX${rs}] ${r}failed:${rs} $1 ${w}$2${rs}\n" -} - -die() -{ - "$@" - printf "${r}\nExiting${rs}\n" - exit 1 -} - -check_dir() -{ - # Check if the specified font directory exists - if [ -d $font_dir ]; then - success "Found directory:" "$font_dir" - else - warning "directory not found:" "$font_dir" - success "Creating directory:" "$font_dir" - - # Create the font directory if non-existent - mkdir -p $font_dir - fi -} - -check_font() -{ - # Check if any font is specified or not ( TODO: Better non-zero checking ) - test -z "$bdf_font" && test -z "$pcf_font" && die error 1 - - # Check if the program 'bdftopcf' is installed or not - if [ $(command -v bdftopcf) ]; then - check_dir - - # If 'bdftopcf' is installed then proceed to compile '$bdf_font' - make_pcf - else - check_dir - warning "Application 'bdftopcf':" "Not Found" - success "Installing precompiled" "$(basename $pcf_font)" - - # If 'bdftopcf' is not installed then copy the precompiled '$pcf_font' - copy_pcf - fi -} - -make_pcf() -{ - if [ -f "$bdf_font" ]; then - - # If it exists then proceed to compile the '$bdf_font' - success "Compiling" "$(basename $bdf_font)" - bdftopcf $bdf_font -o "${font_dir}/$(basename $pcf_font)" - - # Update the font cache - update_cache - else - die error 2 - fi -} - -copy_pcf() -{ - if [ -f "$pcf_font" ]; then - success "Copying" "'$pcf_font' -> '$font_dir'" - - # If $pcf_font exists then proceed to copying - cp $pcf_font $font_dir - - # Update the font cache - update_cache - else - die error 2 - fi -} - -update_cache() -{ - success "Updating font cache... Please Wait" - mkfontdir $font_dir - xset +fp $font_dir - xset fp rehash - fc-cache -f - success "Finished. Your font cache has been updated" - - # Perform post install stuff - post_install -} - -post_install() -{ - if [ -f "$HOME/.xinitrc" ]; then - file="${w}$HOME/.xinitrc${rs}" - else - file="custom startup script that gets executed during xlogin" - fi -printf " - Successfully installed ${w}$(basename $pcf_font) -> ${font_dir}${rs} - Add the following snippet in your ${file}: - - ${w}xset +fp ${font_dir}${rs} - ${w}xset fp rehash${rs} - - If it already exists then you can skip this step. -" -} - -error() -{ - case $1 in - 1) failure "No font specified" - ;; - 2) failure "Font not found" - ;; - *) failure "Unknown option" - ;; - esac -} - -while getopts "hd:" opt; do - case $opt in - d) font_dir=${OPTARG};; - h) usage; exit 0;; - *) usage; exit 1;; - esac -done - -_init_ -exit 0 -# vim: ft=sh ts=4 :et diff --git a/view.sh b/view.sh deleted file mode 100755 index 07f181d..0000000 --- a/view.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh - -check_xfd() -{ - if [ "$(command -v xfd)" ]; then - showfont - else - printf "Application 'xfd' is not installed !\n" - exit 1 - fi -} - -showfont() -{ - rows='19' - cols='34' - font='-wuncon-siji-medium-r-normal--10-100-75-75-c-80-iso10646-1' - - xfd_color solarized_dark - - printf "xfd*Background: $xfd_bg\n\ - xfd*Foreground: $xfd_fg\n" | xrdb -merge - - xfd -rows $rows -columns $cols -fn $font 2>&1 >/dev/null & -} - -xfd_color() -{ - case $1 in - solarized_dark) - xfd_bg="#002b36" - xfd_fg="#839496" - ;; - solarized_light) - xfd_bg="#fdf6e3" - xfd_fg="#657b83" - ;; - *) - printf "Invalid colorscheme\n" - exit 1 - ;; - esac -} - -check_xfd -exit 0 From c3277edd783c9751f1528db8b78997762959bc68 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 21:52:01 +0300 Subject: [PATCH 04/19] Use python scripts from Terminus for converting --- Makefile | 5 +- README.md | 3 +- bin/LICENSE | 339 ++++++++++++++++++++ bin/bdf.py | 309 ++++++++++++++++++ bin/bdfcheck.py | 380 +++++++++++++++++++++++ bin/bdfexp.py | 245 +++++++++++++++ bin/bdftofnt.py | 222 +++++++++++++ bin/bdftopsf.py | 241 +++++++++++++++ bin/fncli.py | 162 ++++++++++ bin/fnio.py | 176 +++++++++++ bin/fnutil.py | 98 ++++++ bin/otb1cli.py | 99 ++++++ bin/otb1exp.py | 808 ++++++++++++++++++++++++++++++++++++++++++++++++ bin/otb1get.py | 663 +++++++++++++++++++++++++++++++++++++++ bin/ucstoany.py | 188 +++++++++++ doko.bdf | 2 +- doko.otb | Bin 13392 -> 22916 bytes 17 files changed, 3936 insertions(+), 4 deletions(-) create mode 100644 bin/LICENSE create mode 100644 bin/bdf.py create mode 100644 bin/bdfcheck.py create mode 100644 bin/bdfexp.py create mode 100644 bin/bdftofnt.py create mode 100644 bin/bdftopsf.py create mode 100644 bin/fncli.py create mode 100644 bin/fnio.py create mode 100644 bin/fnutil.py create mode 100644 bin/otb1cli.py create mode 100644 bin/otb1exp.py create mode 100644 bin/otb1get.py create mode 100644 bin/ucstoany.py diff --git a/Makefile b/Makefile index fdd7d0d..b001536 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,7 @@ pcf: doko.bdf bdftopcf doko.bdf -o doko.pcf otb: doko.bdf - fonttosfnt -g 2 -m 2 -o doko.otb -- doko.bdf + python3 bin/otb1cli.py -o doko.otb doko.bdf + +clean: + rm doko.otb doko.pcf diff --git a/README.md b/README.md index 6e8ccf5..be89020 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Doko is a fixed and maintained [Siji](https://github.com/stark/siji). ![doko](preview.png "preview of doko") ## Differences from Siji: -- Build OTB font => support in modern apps +- Build **correct** OTB font => support in modern apps - New repository structure, new build system, releases => easier to package - *More coming soon* @@ -19,7 +19,6 @@ Doko is a fixed and maintained [Siji](https://github.com/stark/siji). #### Requirements: - bdftopcf -- fonttosfnt ```sh git clone https://github.com/begss/doko && cd doko && make install diff --git a/bin/LICENSE b/bin/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/bin/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/bin/bdf.py b/bin/bdf.py new file mode 100644 index 0000000..8ce8cb3 --- /dev/null +++ b/bin/bdf.py @@ -0,0 +1,309 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import re +import codecs +from collections import OrderedDict +from enum import IntEnum, unique + +import fnutil + +# -- Width -- +DPARSE_LIMIT = 512 +SPARSE_LIMIT = 32000 + +class Width: + def __init__(self, x, y): + self.x = x + self.y = y + + + @staticmethod + def parse(name, value, limit): + words = fnutil.split_words(name, value, 2) + return Width(fnutil.parse_dec(name + '.x', words[0], -limit, limit), + fnutil.parse_dec(name + '.y', words[1], -limit, limit)) + + + @staticmethod + def parse_s(name, value): + return Width.parse(name, value, SPARSE_LIMIT) + + + @staticmethod + def parse_d(name, value): + return Width.parse(name, value, DPARSE_LIMIT) + + + def __str__(self): + return '%d %d' % (self.x, self.y) + + +# -- BXX -- +class BBX: + def __init__(self, width, height, xoff, yoff): + self.width = width + self.height = height + self.xoff = xoff + self.yoff = yoff + + + @staticmethod + def parse(name, value): + words = fnutil.split_words(name, value, 4) + return BBX(fnutil.parse_dec('width', words[0], 1, DPARSE_LIMIT), + fnutil.parse_dec('height', words[1], 1, DPARSE_LIMIT), + fnutil.parse_dec('bbxoff', words[2], -DPARSE_LIMIT, DPARSE_LIMIT), + fnutil.parse_dec('bbyoff', words[3], -DPARSE_LIMIT, DPARSE_LIMIT)) + + + def row_size(self): + return (self.width + 7) >> 3 + + + def __str__(self): + return '%d %d %d %d' % (self.width, self.height, self.xoff, self.yoff) + + +# -- Props -- +def skip_comments(line): + return None if line[:7] == b'COMMENT' else line + + +class Props(OrderedDict): + def __iter__(self): + return self.items().__iter__() + + + def read(self, input, name, callback=None): + return self.parse(input.read_lines(skip_comments), name, callback) + + + def parse(self, line, name, callback=None): + if not line or not line.startswith(bytes(name, 'ascii')): + raise Exception(name + ' expected') + + value = line[len(name):].lstrip() + self[name] = value + return value if callback is None else callback(name, value) + + + def set(self, name, value): + self[name] = value if isinstance(value, (bytes, bytearray)) else bytes(str(value), 'ascii') + + +# -- Base -- +class Base: + def __init__(self): + self.props = Props() + self.bbx = None + + +# -- Char +HEX_BYTES = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70) + +class Char(Base): + def __init__(self): + Base.__init__(self) + self.code = -1 + self.swidth = None + self.dwidth = None + self.data = None + + + def bitmap(self): + bitmap = '' + row_size = self.bbx.row_size() + + for index in range(0, len(self.data), row_size): + bitmap += self.data[index : index + row_size].hex() + '\n' + + return bytes(bitmap, 'ascii').upper() + + + def _read(self, input): + # HEADER + self.props.read(input, 'STARTCHAR') + self.code = self.props.read(input, 'ENCODING', fnutil.parse_dec) + self.swidth = self.props.read(input, 'SWIDTH', Width.parse_s) + self.dwidth = self.props.read(input, 'DWIDTH', Width.parse_d) + self.bbx = self.props.read(input, 'BBX', BBX.parse) + line = input.read_lines(skip_comments) + + if line and line.startswith(b'ATTRIBUTES'): + self.props.parse(line, 'ATTRIBUTES') + line = input.read_lines(skip_comments) + + # BITMAP + if self.props.parse(line, 'BITMAP'): + raise Exception('BITMAP expected') + + row_len = self.bbx.row_size() * 2 + self.data = bytearray() + + for _ in range(0, self.bbx.height): + line = input.read_lines(skip_comments) + + if not line: + raise Exception('bitmap data expected') + + if len(line) == row_len: + self.data += codecs.decode(line, 'hex') + else: + raise Exception('invalid bitmap length') + + # FINAL + if input.read_lines(skip_comments) != b'ENDCHAR': + raise Exception('ENDCHAR expected') + + return self + + + @staticmethod + def read(input): + return Char()._read(input) # pylint: disable=protected-access + + + def write(self, output): + for [name, value] in self.props: + output.write_prop(name, value) + + output.write_line(self.bitmap() + b'ENDCHAR') + + +# -- Font -- +@unique +class XLFD(IntEnum): + FOUNDRY = 1 + FAMILY_NAME = 2 + WEIGHT_NAME = 3 + SLANT = 4 + SETWIDTH_NAME = 5 + ADD_STYLE_NAME = 6 + PIXEL_SIZE = 7 + POINT_SIZE = 8 + RESOLUTION_X = 9 + RESOLUTION_Y = 10 + SPACING = 11 + AVERAGE_WIDTH = 12 + CHARSET_REGISTRY = 13 + CHARSET_ENCODING = 14 + +CHARS_MAX = 65535 + +class Font(Base): + def __init__(self): + Base.__init__(self) + self.xlfd = [] + self.chars = [] + self.default_code = -1 + + + @property + def bold(self): + return b'bold' in self.xlfd[XLFD.WEIGHT_NAME].lower() + + + @property + def italic(self): + return self.xlfd[XLFD.SLANT] in [b'I', b'O'] + + + @property + def proportional(self): + return self.xlfd[XLFD.SPACING] == b'P' + + + def _read(self, input): + # HEADER + line = input.read_line() + + if self.props.parse(line, 'STARTFONT') != b'2.1': + raise Exception('STARTFONT 2.1 expected') + + self.xlfd = self.props.read(input, 'FONT', lambda name, value: value.split(b'-', 15)) + + if len(self.xlfd) != 15 or self.xlfd[0] != b'': + raise Exception('non-XLFD font names are not supported') + + self.props.read(input, 'SIZE') + self.bbx = self.props.read(input, 'FONTBOUNDINGBOX', BBX.parse) + line = input.read_lines(skip_comments) + + if line and line.startswith(b'STARTPROPERTIES'): + num_props = self.props.parse(line, 'STARTPROPERTIES', fnutil.parse_dec) + + for _ in range(0, num_props): + line = input.read_lines(skip_comments) + + if line is None: + raise Exception('property expected') + + match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) + + if not match: + raise Exception('invalid property format') + + name = str(match.group(1), 'ascii') + value = match.group(2) + + if self.props.get(name) is not None: + raise Exception('duplicate property') + + if name == 'DEFAULT_CHAR': + self.default_code = fnutil.parse_dec(name, value) + + self.props[name] = value + + if self.props.read(input, 'ENDPROPERTIES') != b'': + raise Exception('ENDPROPERTIES expected') + + line = input.read_lines(skip_comments) + + # GLYPHS + num_chars = fnutil.parse_dec('CHARS', self.props.parse(line, 'CHARS'), 1, CHARS_MAX) + + for _ in range(0, num_chars): + self.chars.append(Char.read(input)) + + #if next((char.code for char in self.chars if char.code == self.default_code), -1) != self.default_code: + #raise Exception('invalid DEFAULT_CHAR') + + # FINAL + if input.read_lines(skip_comments) != b'ENDFONT': + raise Exception('ENDFONT expected') + + if input.read_line() is not None: + raise Exception('garbage after ENDFONT') + + return self + + + @staticmethod + def read(input): + return Font()._read(input) # pylint: disable=protected-access + + + def write(self, output): + for [name, value] in self.props: + output.write_prop(name, value) + + for char in self.chars: + char.write(output) + + output.write_line(b'ENDFONT') diff --git a/bin/bdfcheck.py b/bin/bdfcheck.py new file mode 100644 index 0000000..c7790f0 --- /dev/null +++ b/bin/bdfcheck.py @@ -0,0 +1,380 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import re +import codecs +from collections import OrderedDict +from enum import IntEnum, unique + +import fnutil +import fncli +import fnio +import bdf + +# -- Params -- +class Params(fncli.Params): # pylint: disable=too-many-instance-attributes + def __init__(self): + fncli.Params.__init__(self) + self.ascii_chars = True + self.bbx_exceeds = True + self.dupl_codes = -1 + self.extra_bits = True + self.attributes = True + self.dupl_names = -1 + self.dupl_props = True + self.common_slant = True + self.common_weight = True + self.xlfd_fontnm = True + self.ywidth_zero = True + + +# -- Options -- +HELP = ('' + + 'usage: bdfcheck [options] [INPUT...]\n' + + 'Check BDF font(s) for various problems\n' + + '\n' + + ' -A disable non-ascii characters check\n' + + ' -B disable BBX exceeding FONTBOUNDINGBOX checks\n' + + ' -c/-C enable/disable duplicate character codes check\n' + + ' (default = enabled for registry ISO10646)\n' + + ' -E disable extra bits check\n' + + ' -I disable ATTRIBUTES check\n' + + ' -n/-N enable duplicate character names check\n' + + ' (default = enabled for registry ISO10646)\n' + + ' -P disable duplicate properties check\n' + + ' -S disable common slant check\n' + + ' -W disable common weight check\n' + + ' -X disable XLFD font name check\n' + + ' -Y disable zero WIDTH Y check\n' + + ' --help display this help and exit\n' + + ' --version display the program version and license, and exit\n' + + ' --excstk display the exception stack on error\n' + + '\n' + + 'File directives: COMMENT bdfcheck --enable|disable-\n' + + ' (also available as long command line options)\n' + + '\n' + + 'Check names: ascii-chars, bbx-exceeds, duplicate-codes, extra-bits,\n' + + ' attributes, duplicate-names, duplicate-properties, common-slant,\n' + + ' common-weight, xlfd-font, ywidth-zero\n' + + '\n' + + 'The input BDF(s) must be v2.1 with unicode encoding.\n') + +VERSION = 'bdfcheck 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +class Options(fncli.Options): + def __init__(self): + fncli.Options.__init__(self, [], HELP, VERSION) + + + def parse(self, name, directive, params): + value = name.startswith('--enable') or name[1].islower() + + if name in ['-A', '--enable-ascii-chars', '--disable-ascii-chars']: + params.ascii_chars = value + elif name in ['-B', '--enable-bbx-exceeds', '--disable-bbx-exceeds']: + params.bbx_exceeds = value + elif name in ['-c', '-C', '--enable-duplicate-codes', '--disable-duplicate-codes']: + params.dupl_codes = value + elif name in ['-E', '--enable-extra-bits', '--disable-extra-bits']: + params.extra_bits = value + elif name in ['-I', '--enable-attributes', '--disable-attributes']: + params.attributes = value + elif name in ['-n', '-N', '--enable-duplicate-names', '--disable-duplicate-names']: + params.dupl_names = value + elif name in ['-P', '--enable-duplicate-properties', '--disable-duplicate-properties']: + params.dupl_props = value + elif name in ['-S', '--enable-common-slant', '--disable-common-slant']: + params.common_slant = value + elif name in ['-W', '--enable-common-weight', '--disable-common-weight']: + params.common_weight = value + elif name in ['-X', '--enable-xlfd-font', '--disable-xlfd-font']: + params.xlfd_fontnm = value + elif name in ['-Y', '--enable-ywidth-zero', '--disable-ywidth-zero']: + params.ywidth_zero = value + else: + return directive is not True and self.fallback(name, params) + + return directive is not True or name.startswith('--') + + +# -- DupMap -- +class DupMap(OrderedDict): + def __init__(self, prefix, severity, descript, quote): + OrderedDict.__init__(self) + self.prefix = prefix + self.descript = descript + self.severity = severity + self.quote = quote + + + def check(self): + for value, lines in self.items(): + if len(lines) > 1: + text = 'duplicate %s %s at lines' % (self.descript, str(value)) + + for index, line in enumerate(lines): + text += ' ' if index == 0 else ' and ' if index == len(lines) - 1 else ', ' + text += str(line) + + fnutil.message(self.prefix, self.severity, text) + + + def push(self, value, line_no): + try: + self[value].append(line_no) + except KeyError: + self[value] = [line_no] + + +# -- InputFileStream -- +@unique +class MODE(IntEnum): + META = 0 + PROPS = 1 + BITMAP = 2 + +class InputFileStream(fnio.InputFileStream): + def __init__(self, file_name, parsed): + fnio.InputFileStream.__init__(self, file_name) + self.parsed = parsed + self.mode = MODE.META + self.proplocs = DupMap(self.location(), 'error', 'property', '') + self.namelocs = DupMap(self.location(), 'warning', 'character name', '"') + self.codelocs = DupMap(self.location(), 'warning', 'encoding', '') + self.handlers = [ + (b'STARTCHAR', lambda value: self.append_name(value)), + (b'ENCODING', lambda value: self.append_code(value)), + (b'SWIDTH', lambda value: self.check_width('SWIDTH', value, bdf.Width.parse_s)), + (b'DWIDTH', lambda value: self.check_width('DWIDTH', value, bdf.Width.parse_d)), + (b'BBX', lambda value: self.set_last_box(value)), + (b'BITMAP', lambda _: self.set_mode(MODE.BITMAP)), + (b'SIZE', InputFileStream.check_size), + (b'ATTRIBUTES', lambda value: self.check_attr(value)), + (b'STARTPROPERTIES', lambda _: self.set_mode(MODE.PROPS)), + (b'FONTBOUNDINGBOX', lambda value: self.set_font_box(value)), + ] + self.xlfd_name = False + self.last_box = None + self.font_box = None + self.options = Options() + + + def append(self, option, valocs, value): + if option: + valocs.push(str(value, 'ascii'), self.line_no) + + + def append_code(self, value): + fnutil.parse_dec('encoding', value) + self.append(self.parsed.dupl_codes, self.codelocs, value) + + + def append_name(self, value): + self.append(self.parsed.dupl_names, self.namelocs, b'"%s"' % value) + + + def check_width(self, name, value, parse): + if self.parsed.ywidth_zero and parse(name, value).y != 0: + fnutil.warning(self.location(), 'non-zero %s Y' % name) + + + def set_font_box(self, value): + self.font_box = bdf.BBX.parse('FONTBOUNDINGBOX', value) + + + def set_last_box(self, value): + bbx = bdf.BBX.parse('BBX', value) + + if self.parsed.bbx_exceeds: + exceeds = [] + + if bbx.xoff < self.font_box.xoff: + exceeds.append('xoff < FONTBOUNDINGBOX xoff') + + if bbx.yoff < self.font_box.yoff: + exceeds.append('yoff < FONTBOUNDINGBOX yoff') + + if bbx.width > self.font_box.width: + exceeds.append('width > FONTBOUNDINGBOX width') + + if bbx.height > self.font_box.height: + exceeds.append('height > FONTBOUNDINGBOX height') + + for exceed in exceeds: + fnutil.message(self.location(), '', exceed) + + self.last_box = bbx + + + def set_mode(self, new_mode): + self.mode = new_mode + + + def check(self): + self.process(bdf.Font.read) + self.proplocs.check() + self.namelocs.check() + self.codelocs.check() + + + @staticmethod + def check_size(value): + words = fnutil.split_words('SIZE', value, 3) + fnutil.parse_dec('point size', words[0], 1, None) + fnutil.parse_dec('x resolution', words[1], 1, None) + fnutil.parse_dec('y resolution', words[2], 1, None) + + + def check_attr(self, value): + if not re.fullmatch(br'[\dA-Fa-f]{4}', value): + raise Exception('ATTRIBUTES must be 4 hex-encoded characters') + + if self.parsed.attributes: + fnutil.warning(self.location(), 'ATTRIBUTES may cause problems with freetype') + + + def check_font(self, value): + xlfd = value[4:].lstrip().split(b'-', 15) + + if len(xlfd) == 15 and xlfd[0] == b'': + unicode = (xlfd[bdf.XLFD.CHARSET_REGISTRY].upper() == b'ISO10646') + + if self.parsed.dupl_codes == -1: + self.parsed.dupl_codes = unicode + + if self.parsed.dupl_names == -1: + self.parsed.dupl_names = unicode + + if self.parsed.common_weight: + weight = str(xlfd[bdf.XLFD.WEIGHT_NAME], 'ascii') + compare = weight.lower() + consider = 'Bold' if 'bold' in compare else 'Normal' + + if compare in ['medium', 'regular']: + compare = 'normal' + + if compare != consider.lower(): + fnutil.warning(self.location(), 'weight "%s" may be considered %s' % (weight, consider)) + + if self.parsed.common_slant: + slant = str(xlfd[bdf.XLFD.SLANT], 'ascii') + consider = 'Italic' if re.search('^[IO]', slant) else 'Regular' + + if not re.fullmatch('[IOR]', slant): + fnutil.warning(self.location(), 'slant "%s" may be considered %s' % (slant, consider)) + + else: + if self.parsed.xlfd_fontnm: + fnutil.warning(self.location(), 'non-XLFD font name') + + value = b'FONT --------------' + + return value + + + def check_prop(self, line): + match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) + + if not match: + raise Exception('invalid property format') + + name = match.group(1) + value = match.group(2) + + if value.startswith(b'"'): + if len(value) < 2 or not value.endswith(b'"'): + raise Exception('no closing double quote') + if re.search(b'[^"]"[^"]', value[1 : len(value) - 1]): + raise Exception('unescaped double quote') + else: + fnutil.parse_dec('value', value, None, None) + + self.append(self.parsed.dupl_props, self.proplocs, name) + return b'P%d 1' % self.line_no + + + def check_bitmap(self, line): + if len(line) != self.last_box.row_size() * 2: + raise Exception('invalid bitmap length') + + data = codecs.decode(line, 'hex') + + if self.parsed.extra_bits: + check_x = (self.last_box.width - 1) | 7 + last_byte = data[len(data) - 1] + bit_no = 7 - (self.last_box.width & 7) + + for x in range(self.last_box.width, check_x + 1): + if last_byte & (1 << bit_no): + fnutil.warning(self.location(), 'extra bit(s) starting with x=%d' % x) + break + bit_no -= 1 + + + def check_line(self, line): + if re.search(b'[^\t\f\v\x20-\xff]', line): + raise Exception('control character(s)') + + if self.parsed.ascii_chars and re.search(b'[\x7f-\xff]', line): + fnutil.warning(self.location(), 'non-ascii character(s)') + + if self.mode == MODE.META: + if not self.xlfd_name and line.startswith(b'FONT'): + line = self.check_font(line) + self.xlfd_name = True + else: + for handler in self.handlers: + if line.startswith(handler[0]): + handler[1](line[len(handler[0]):].lstrip()) + break + elif self.mode == MODE.PROPS: + if line.startswith(b'ENDPROPERTIES'): + self.mode = MODE.META + else: + line = self.check_prop(line) + else: # MODE.BITMAP + if line.startswith(b'ENDCHAR'): + self.mode = MODE.META + else: + self.check_bitmap(line) + + return line + + + def read_check(self, line, callback): + match = re.search(br'^COMMENT\s*bdfcheck\s+(-.*)$', line) + + if match and not self.options.parse(str(match[1], 'ascii'), True, self.parsed): + raise Exception('invalid bdfcheck directive') + + line = callback(line) + return self.check_line(line) if line is not None else None + + + def read_lines(self, callback): + return fnio.InputFileStream.read_lines(self, lambda line: self.read_check(line, callback)) + + +# -- Main -- +def main_program(nonopt, parsed): + for input_name in nonopt or [None]: + InputFileStream(input_name, parsed).check() + + +if __name__ == '__main__': + fncli.start('bdfcheck.py', Options(), Params(), main_program) diff --git a/bin/bdfexp.py b/bin/bdfexp.py new file mode 100644 index 0000000..8409935 --- /dev/null +++ b/bin/bdfexp.py @@ -0,0 +1,245 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from collections import OrderedDict + +import fnutil +import fncli +import fnio +import bdf + +# -- Font -- +class Font(bdf.Font): + def __init__(self): + bdf.Font.__init__(self) + self.min_width = 0 # used in proportional() + self.avg_width = 0 + + + def _expand(self, char): + if char.dwidth.x >= 0: + if char.bbx.xoff >= 0: + width = max(char.bbx.xoff + char.bbx.width, char.dwidth.x) + dst_xoff = char.bbx.xoff + exp_xoff = 0 + else: + width = max(char.bbx.width, char.dwidth.x - char.bbx.xoff) + dst_xoff = 0 + exp_xoff = char.bbx.xoff + else: + rev_xoff = char.bbx.xoff + char.bbx.width + + if rev_xoff <= 0: + width = -min(char.dwidth.x, char.bbx.xoff) + dst_xoff = width + char.bbx.xoff + exp_xoff = -width + else: + width = max(char.bbx.width, rev_xoff - char.dwidth.x) + dst_xoff = width - char.bbx.width + exp_xoff = rev_xoff - width + + height = self.bbx.height + + if width == char.bbx.width and height == char.bbx.height: + return + + src_row_size = char.bbx.row_size() + dst_row_size = (width + 7) >> 3 + dst_ymax = self.px_ascender - char.bbx.yoff + dst_ymin = dst_ymax - char.bbx.height + copy_row = (dst_xoff & 7) == 0 + dst_data = bytearray(dst_row_size * height) + + for dst_y in range(dst_ymin, dst_ymax): + src_byte_no = (dst_y - dst_ymin) * src_row_size + dst_byte_no = dst_y * dst_row_size + (dst_xoff >> 3) + + if copy_row: + dst_data[dst_byte_no : dst_byte_no + src_row_size] = \ + char.data[src_byte_no : src_byte_no + src_row_size] + else: + src_bit_no = 7 + dst_bit_no = 7 - (dst_xoff & 7) + + for _ in range(0, char.bbx.width): + if char.data[src_byte_no] & (1 << src_bit_no): + dst_data[dst_byte_no] |= (1 << dst_bit_no) + + if src_bit_no > 0: + src_bit_no -= 1 + else: + src_bit_no = 7 + src_byte_no += 1 + + if dst_bit_no > 0: + dst_bit_no -= 1 + else: + dst_bit_no = 7 + dst_byte_no += 1 + + char.bbx = bdf.BBX(width, height, exp_xoff, self.bbx.yoff) + char.props.set('BBX', char.bbx) + char.data = dst_data + + + def expand(self): + # PREXPAND / VERTICAL + ascent = self.props.get('FONT_ASCENT') + descent = self.props.get('FONT_DESCENT') + px_ascent = 0 if ascent is None else fnutil.parse_dec('FONT_ASCENT', ascent, 0, bdf.DPARSE_LIMIT) + px_descent = 0 if descent is None else fnutil.parse_dec('FONT_DESCENT', descent, 0, bdf.DPARSE_LIMIT) + + for char in self.chars: + px_ascent = max(px_ascent, char.bbx.height + char.bbx.yoff) + px_descent = max(px_descent, -char.bbx.yoff) + + self.bbx.height = px_ascent + px_descent + self.bbx.yoff = -px_descent + + # EXPAND / HORIZONTAL + total_width = 0 + self.min_width = self.chars[0].bbx.width + + for char in self.chars: + self._expand(char) + self.min_width = min(self.min_width, char.bbx.width) + self.bbx.width = max(self.bbx.width, char.bbx.width) + self.bbx.xoff = min(self.bbx.xoff, char.bbx.xoff) + total_width += char.bbx.width + + self.avg_width = round(total_width / len(self.chars)) + self.props.set('FONTBOUNDINGBOX', self.bbx) + + + def expand_x(self): + for char in self.chars: + if char.dwidth.x != char.bbx.width: + char.swidth.x = round(char.bbx.width * 1000 / self.bbx.height) + char.props.set('SWIDTH', char.swidth) + char.dwidth.x = char.bbx.width + char.props.set('DWIDTH', char.dwidth) + + char.bbx.xoff = 0 + char.props.set('BBX', char.bbx) + + self.bbx.xoff = 0 + self.props.set('FONTBOUNDINGBOX', self.bbx) + + + def expand_y(self): + props = OrderedDict(( + ('FONT_ASCENT', self.px_ascender), + ('FONT_DESCENT', -self.px_descender), + ('PIXEL_SIZE', self.bbx.height) + )) + + for [name, value] in props.items(): + if self.props.get(name) is not None: + self.props.set(name, value) + + self.xlfd[bdf.XLFD.PIXEL_SIZE] = bytes(str(self.bbx.height), 'ascii') + self.props.set('FONT', b'-'.join(self.xlfd)) + + + @property + def proportional(self): + return self.bbx.width > self.min_width or bdf.Font.proportional.fget(self) # pylint: disable=no-member + + @property + def px_ascender(self): + return self.bbx.height + self.bbx.yoff + + @property + def px_descender(self): + return self.bbx.yoff + + + def _read(self, input): + bdf.Font._read(self, input) + self.expand() + return self + + @staticmethod + def read(input): + return Font()._read(input) # pylint: disable=protected-access + + +# -- Params -- +class Params(fncli.Params): + def __init__(self): + fncli.Params.__init__(self) + self.expand_x = False + self.expand_y = False + self.output_name = None + + +# -- Options -- +HELP = ('' + + 'usage: bdfexp [-X] [-Y] [-o OUTPUT] [INPUT]\n' + + 'Expand BDF font bitmaps\n' + + '\n' + + ' -X zero xoffs, set character S/DWIDTH.X from the output\n' + + ' BBX.width if needed\n' + + ' -Y enlarge FONT_ASCENT, FONT_DESCENT and PIXEL_SIZE to\n' + + ' cover the font bounding box, if needed\n' + + ' -o OUTPUT output file (default = stdout)\n' + + ' --help display this help and exit\n' + + ' --version display the program version and license, and exit\n' + + ' --excstk display the exception stack on error\n' + + '\n' + + 'The input must be a BDF 2.1 font with unicode encoding.\n') + +VERSION = 'bdfexp 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +class Options(fncli.Options): + def __init__(self): + fncli.Options.__init__(self, ['-o'], HELP, VERSION) + + + def parse(self, name, value, params): + if name == '-X': + params.expand_x = True + elif name == '-Y': + params.expand_y = True + elif name == '-o': + params.output_name = value + else: + self.fallback(name, params) + + +# -- Main -- +def main_program(nonopt, parsed): + if len(nonopt) > 1: + raise Exception('invalid number of arguments, try --help') + + # READ INPUT + font = fnio.read_file(nonopt[0] if nonopt else None, Font.read) + + # EXTRA ACTIONS + if parsed.expand_x: + font.expand_x() + + if parsed.expand_y: + font.expand_y() + + # WRITE OUTPUT + fnio.write_file(parsed.output_name, lambda ofs: font.write(ofs)) + + +if __name__ == '__main__': + fncli.start('bdfexp.py', Options(), Params(), main_program) diff --git a/bin/bdftofnt.py b/bin/bdftofnt.py new file mode 100644 index 0000000..0bfeed8 --- /dev/null +++ b/bin/bdftofnt.py @@ -0,0 +1,222 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import re + +import fnutil +import fncli +import fnio +import bdf +import bdfexp + +# -- Params -- +class Params(fncli.Params): + def __init__(self): + fncli.Params.__init__(self) + self.char_set = -1 + self.min_char = -1 + self.fnt_family = 0 + self.output_name = None + + +# -- Options -- +HELP = ('' + + 'usage: bdftofnt [-c CHARSET] [-m MINCHAR] [-f FAMILY] [-o OUTPUT] [INPUT]\n' + + 'Convert a BDF font to Windows FNT\n' + + '\n' + + ' -c CHARSET fnt character set (default = 0, see wingdi.h ..._CHARSET)\n' + + ' -m MINCHAR fnt minimum character code (8-bit CP decimal, not unicode)\n' + + ' -f FAMILY fnt family: DontCare, Roman, Swiss, Modern or Decorative\n' + + ' -o OUTPUT output file (default = stdout, may not be a terminal)\n' + + ' --help display this help and exit\n' + + ' --version display the program version and license, and exit\n' + + ' --excstk display the exception stack on error\n' + + '\n' + + 'The input must be a BDF 2.1 font with unicode encoding.\n') + +VERSION = 'bdftofnt 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +FNT_FAMILIES = ['DontCare', 'Roman', 'Swiss', 'Modern', 'Decorative'] + +class Options(fncli.Options): + def __init__(self): + fncli.Options.__init__(self, ['-c', '-m', '-f', '-o'], HELP, VERSION) + + + def parse(self, name, value, params): + if name == '-c': + params.char_set = fnutil.parse_dec('CHARSET', value, 0, 255) + elif name == '-m': + params.min_char = fnutil.parse_dec('MINCHAR', value, 0, 255) + elif name == '-f': + if value in FNT_FAMILIES: + params.fnt_family = FNT_FAMILIES.index(value) + else: + raise Exception('invalid FAMILY') + elif name == '-o': + params.output_name = value + else: + self.fallback(name, params) + + +# -- Main -- +FNT_HEADER_SIZE = 118 +FNT_CHARSETS = [238, 204, 0, 161, 162, 177, 178, 186, 163] + +def main_program(nonopt, parsed): + if len(nonopt) > 1: + raise Exception('invalid number of arguments, try --help') + + char_set = parsed.char_set + min_char = parsed.min_char + + # READ INPUT + ifs = fnio.InputFileStream(nonopt[0] if nonopt else None) + font = ifs.process(bdfexp.Font.read) + + # COMPUTE + if char_set == -1: + encoding = font.xlfd[bdf.XLFD.CHARSET_ENCODING] + + if re.fullmatch(b'(cp)?125[0-8]', encoding.lower()): + char_set = FNT_CHARSETS[int(encoding[-1:])] + else: + char_set = 255 + + try: + num_chars = len(font.chars) + + if num_chars > 256: + raise Exception('too many characters, the maximum is 256') + + if min_char == -1: + if num_chars in [192, 256]: + min_char = 256 - num_chars + else: + min_char = font.chars[0].code + + max_char = min_char + num_chars - 1 + + if max_char >= 256: + raise Exception('the maximum character code is too big, (re)specify -m') + + # HEADER + vtell = FNT_HEADER_SIZE + (num_chars + 1) * 4 + bits_offset = vtell + ctable = [] + width_bytes = 0 + + # CTABLE/GLYPHS + for char in font.chars: + row_size = char.bbx.row_size() + ctable.append(char.bbx.width) + ctable.append(vtell) + vtell += row_size * font.bbx.height + width_bytes += row_size + + if vtell > 0xFFFF: + raise Exception('too much character data') + + # SENTINEL + sentinel = 2 - width_bytes % 2 + ctable.append(sentinel * 8) + ctable.append(vtell) + vtell += sentinel * font.bbx.height + width_bytes += sentinel + + if width_bytes > 0xFFFF: + raise Exception('the total character width is too big') + + except Exception as ex: + ex.message = ifs.location() + getattr(ex, 'message', str(ex)) + raise + + # WRITE + def write_fnt(output): + # HEADER + family = font.xlfd[bdf.XLFD.FAMILY_NAME] + copyright = font.props.get('COPYRIGHT') + copyright = fnutil.unquote(copyright)[:60] if copyright is not None else b'' + + output.write16(0x0200) # font version + output.write32(vtell + len(family) + 1) # total size + output.write_zstr(copyright, 60 - len(copyright)) + output.write16(0) # gdi, device type + output.write16(round(font.bbx.height * 72 / 96)) + output.write16(96) # vertical resolution + output.write16(96) # horizontal resolution + output.write16(font.px_ascender) # base line + output.write16(0) # internal leading + output.write16(0) # external leading + output.write8(int(font.italic)) + output.write8(0) # underline + output.write8(0) # strikeout + output.write16(700 if font.bold else 400) + output.write8(char_set) + output.write16(0 if font.proportional else font.bbx.width) + output.write16(font.bbx.height) + output.write8((parsed.fnt_family << 4) + int(font.proportional)) + output.write16(font.avg_width) + output.write16(font.bbx.width) + output.write8(min_char) + output.write8(max_char) + + default_index = max_char - min_char + break_index = 0 + + if font.default_code != -1: + default_index = next(index for index, char in enumerate(font.chars) if char.code == font.default_code) + + if min_char <= 0x20 <= max_char: + break_index = 0x20 - min_char + + output.write8(default_index) + output.write8(break_index) + output.write16(width_bytes) + output.write32(0) # device name + output.write32(vtell) + output.write32(0) # gdi bits pointer + output.write32(bits_offset) + output.write8(0) # reserved + + # CTABLE + for value in ctable: + output.write16(value) + + # GLYPHS + data = bytearray(font.bbx.height * font.bbx.row_size()) + + for char in font.chars: + row_size = char.bbx.row_size() + counter = 0 + # MS coordinates + for n in range(0, row_size): + for y in range(0, font.bbx.height): + data[counter] = char.data[row_size * y + n] + counter += 1 + output.write(data[:counter]) + output.write(bytes(sentinel * font.bbx.height)) + + # FAMILY + output.write_zstr(family, 1) + + fnio.write_file(parsed.output_name, write_fnt, encoding=None) + + +if __name__ == '__main__': + fncli.start('bdftofnt.py', Options(), Params(), main_program) diff --git a/bin/bdftopsf.py b/bin/bdftopsf.py new file mode 100644 index 0000000..1ad8e1b --- /dev/null +++ b/bin/bdftopsf.py @@ -0,0 +1,241 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import fnutil +import fncli +import fnio +import bdfexp + +# -- Params -- +class Params(fncli.Params): + def __init__(self): + fncli.Params.__init__(self) + self.version = -1 + self.exchange = -1 + self.output_name = None + + +# -- Options -- +HELP = ('' + + 'usage: bdftopsf [-1|-2|-r] [-g|-G] [-o OUTPUT] [INPUT.bdf] [TABLE...]\n' + + 'Convert a BDF font to PC Screen Font or raw font\n' + + '\n' + + ' -1, -2 write a PSF version 1 or 2 font (default = 1 if possible)\n' + + ' -r, --raw write a RAW font\n' + + ' -g, --vga exchange the characters at positions 0...31 with these at\n' + + ' 192...223 (default for VGA text mode compliant PSF fonts\n' + + ' with 224 to 512 characters starting with unicode 00A3)\n' + + ' -G do not exchange characters 0...31 and 192...223\n' + + ' -o OUTPUT output file (default = stdout, may not be a terminal)\n' + + ' --help display this help and exit\n' + + ' --version display the program version and license, and exit\n' + + ' --excstk display the exception stack on error\n' + + '\n' + + 'The input must be a monospaced unicode-encoded BDF 2.1 font.\n' + + '\n' + + 'The tables are text files with two or more hexadecimal unicodes per line:\n' + + 'a character code from the BDF, and extra code(s) for it. All extra codes\n' + + 'are stored sequentially in the PSF unicode table for their character.\n' + + ' is always specified as FFFE, although it is stored as FE in PSF2.\n') + +VERSION = 'bdftopsf 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +class Options(fncli.Options): + def __init__(self): + fncli.Options.__init__(self, ['-o'], HELP, VERSION) + + + def parse(self, name, value, params): + if name in ['-1', '-2']: + params.version = int(name[1]) + elif name in ['-r', '--raw']: + params.version = 0 + elif name in ['-g', '--vga']: + params.exchange = True + elif name == '-G': + params.exchange = False + elif name == '-o': + params.output_name = value + else: + self.fallback(name, params) + + +# -- Main -- +def main_program(nonopt, parsed): + version = parsed.version + exchange = parsed.exchange + bdfile = len(nonopt) > 0 and nonopt[0].lower().endswith('.bdf') + ver1_unicodes = True + + # READ INPUT + ifs = fnio.InputFileStream(nonopt[0] if bdfile else None) + font = ifs.process(bdfexp.Font.read) + + try: + for char in font.chars: + prefix = 'char %d: ' % char.code + + if char.bbx.width != font.bbx.width: + raise Exception(prefix + 'output width not equal to maximum output width') + + if char.code == 65534: + raise Exception(prefix + 'not a character, use 65535 for empty position') + + if char.code >= 65536: + if version == 1: + raise Exception(prefix + '-1 requires unicodes <= 65535') + ver1_unicodes = False + + # VERSION + ver1_num_chars = len(font.chars) == 256 or len(font.chars) == 512 + + if version == 1: + if not ver1_num_chars: + raise Exception('-1 requires a font with 256 or 512 characters') + + if font.bbx.width != 8: + raise Exception('-1 requires a font with width 8') + + # EXCHANGE + vga_num_chars = len(font.chars) >= 224 and len(font.chars) <= 512 + vga_text_size = font.bbx.width == 8 and font.bbx.height in [8, 14, 16] + + if exchange is True: + if not vga_num_chars: + raise Exception('-g/--vga requires a font with 224...512 characters') + + if not vga_text_size: + raise Exception('-g/--vga requires an 8x8, 8x14 or 8x16 font') + + except Exception as ex: + ex.message = ifs.location() + getattr(ex, 'message', str(ex)) + raise + + # READ TABLES + tables = dict() + + def load_extra(line): + nonlocal ver1_unicodes + words = line.split() + + if len(words) < 2: + raise Exception('invalid format') + + uni = fnutil.parse_hex('unicode', words[0]) + + if uni == 0xFFFE: + raise Exception('FFFE is not a character') + + if next((char for char in font.chars if char.code == uni), None): + if uni > fnutil.UNICODE_BMP_MAX: + ver1_unicodes = False + + if uni not in tables: + tables[uni] = [] + + table = tables[uni] + + for word in words[1:]: + dup = fnutil.parse_hex('extra code', word) + + if dup == 0xFFFF: + raise Exception('FFFF is not a character') + + if dup > fnutil.UNICODE_BMP_MAX: + ver1_unicodes = False + + if not dup in table or 0xFFFE in table: + tables[uni].append(dup) + + if version == 1 and not ver1_unicodes: + raise Exception('-1 requires unicodes <= %X' % fnutil.UNICODE_BMP_MAX) + + for table_name in nonopt[int(bdfile):]: + fnio.read_file(table_name, lambda ifs: ifs.read_lines(load_extra)) + + # VERSION + if version == -1: + version = 1 if ver1_num_chars and ver1_unicodes and font.bbx.width == 8 else 2 + + # EXCHANGE + if exchange == -1: + exchange = vga_text_size and version >= 1 and vga_num_chars and font.chars[0].code == 0x00A3 + + if exchange: + font.chars = font.chars[192:224] + font.chars[32:192] + font.chars[0:32] + font.chars[224:] + + # WRITE + def write_psf(output): + # HEADER + if version == 1: + output.write8(0x36) + output.write8(0x04) + output.write8((len(font.chars) >> 8) + 1) + output.write8(font.bbx.height) + elif version == 2: + output.write32(0x864AB572) + output.write32(0x00000000) + output.write32(0x00000020) + output.write32(0x00000001) + output.write32(len(font.chars)) + output.write32(len(font.chars[0].data)) + output.write32(font.bbx.height) + output.write32(font.bbx.width) + + # GLYPHS + for char in font.chars: + output.write(char.data) + + # UNICODES + if version > 0: + def write_unicode(code): + if version == 1: + output.write16(code) + elif code <= 0x7F: + output.write8(code) + elif code in [0xFFFE, 0xFFFF]: + output.write8(code & 0xFF) + else: + if code <= 0x7FF: + output.write8(0xC0 + (code >> 6)) + else: + if code <= 0xFFFF: + output.write8(0xE0 + (code >> 12)) + else: + output.write8(0xF0 + (code >> 18)) + output.write8(0x80 + ((code >> 12) & 0x3F)) + + output.write8(0x80 + ((code >> 6) & 0x3F)) + + output.write8(0x80 + (code & 0x3F)) + + for char in font.chars: + if char.code != 0xFFFF: + write_unicode(char.code) + + if char.code in tables: + for extra in tables[char.code]: + write_unicode(extra) + + write_unicode(0xFFFF) + + fnio.write_file(parsed.output_name, write_psf, encoding=None) + + +if __name__ == '__main__': + fncli.start('bdftopsf.py', Options(), Params(), main_program) diff --git a/bin/fncli.py b/bin/fncli.py new file mode 100644 index 0000000..bfe2637 --- /dev/null +++ b/bin/fncli.py @@ -0,0 +1,162 @@ +# +# Copyright (C) 2018-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import sys +import os +import re + +# -- Params -- +class Params: + def __init__(self): + self.excstk = False + + +# -- Options -- +class Options: + def __init__(self, need_args, help_text, version_text): + for name in need_args: + if not re.fullmatch('(-[^-]|--[^=]+)', name): + raise Exception('invalid option name "%s"' % name) + + self.need_args = need_args + self.help_text = help_text + self.version_text = version_text + + + def posixly_correct(self): # pylint: disable=no-self-use + return 'POSIXLY_CORRECT' in os.environ + + + def needs_arg(self, name): + return name in self.need_args + + + def fallback(self, name, params): + if name == '--excstk': + params.excstk = True + elif name == '--help' and self.help_text is not None: + sys.stdout.write(self.help_text) + sys.exit(0) + elif name == '--version' and self.version_text is not None: + sys.stdout.write(self.version_text) + sys.exit(0) + else: + suffix = ' (taking an argument?)' if self.needs_arg(name) else '' + suffix += ', try --help' if self.help_text is not None else '' + raise Exception('unknown option "%s"%s' % (name, suffix)) + + + def reader(self, args, skip_zero=True): + return Options.Reader(self, args, skip_zero) + + + class Reader: + def __init__(self, options, args, skip_zero): + self.options = options + self.args = args + self.skip_zero = skip_zero + + + def __iter__(self): + return Options.Reader.Iterator(self) + + + class Iterator: + def __init__(self, reader): + self.options = reader.options + self.args = reader.args + self.optind = int(reader.skip_zero) + self.chrind = 1 + self.endopt = False + + + def __next__(self): + if self.chrind == 0: + self.optind += 1 + self.chrind = 1 + + if self.optind == len(self.args): + raise StopIteration + + arg = self.args[self.optind] + + if self.endopt or arg == '-' or not arg.startswith('-'): + self.endopt = self.options.posixly_correct() + name = None + value = arg + elif arg == '--': + self.chrind = 0 + self.endopt = True + return next(self) + elif not arg.startswith('--'): + name = '-' + arg[self.chrind] + self.chrind += 1 + if self.chrind < len(arg): + if not self.options.needs_arg(name): + return (name, None) + value = arg[self.chrind:] + else: + value = None + elif '=' in arg and arg.index('=') >= 3: + name = arg.split('=', 1)[0] + if not self.options.needs_arg(name): + raise Exception('option "%s" does not take an argument' % name) + value = arg[len(name) + 1:] + else: + name = arg + value = None + + if value is None and int(self.options.needs_arg(name)) > 0: + self.optind += 1 + if self.optind == len(self.args): + raise Exception('option "%s" requires an argument' % name) + value = self.args[self.optind] + + self.chrind = 0 + return (name, value) + + +# -- Main -- +def start(program_name, options, params, main_program): + parsed = Params() if params is None else params + + try: + + if sys.hexversion < 0x3050000: + raise Exception('python 3.5.0 or later required') + + if params is None: + return main_program(options.reader(sys.argv), lambda name: options.fallback(name, parsed)) + + nonopt = [] + + for [name, value] in options.reader(sys.argv): + if name is None: + nonopt.append(value) + else: + options.parse(name, value, parsed) + + return main_program(nonopt, parsed) + + except Exception as ex: + if parsed.excstk: + raise # loses the message information, but preserves the start() caller stack info + + message = getattr(ex, 'message', str(ex)) + sys.stderr.write('%s: %s\n' % (sys.argv[0] if sys.argv[0] else program_name, message)) + sys.exit(1) diff --git a/bin/fnio.py b/bin/fnio.py new file mode 100644 index 0000000..e2b3191 --- /dev/null +++ b/bin/fnio.py @@ -0,0 +1,176 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import codecs +import struct +import sys +import os + +# -- InputFileStream -- +class InputFileStream: + def __init__(self, file_name, encoding='binary'): + if file_name is not None: + self.file = open(file_name, 'r') if encoding is None else open(file_name, 'rb') + self.st_name = file_name + else: + self.file = sys.stdin if encoding is None else sys.stdin.buffer + self.st_name = '' + + if encoding not in [None, 'binary']: + self.file = codecs.getreader(encoding)(self.file) + + self.line_no = 0 + self.eof = False + + + def close(self): + self.unseek() + self.file.close() + + + def fstat(self): + return None if (self.file == sys.stdin.buffer or self.file.isatty()) else os.fstat(self.file.fileno()) + + + def location(self): + return '%s:%s' % (self.st_name, 'EOF: ' if self.eof else '%d: ' % self.line_no if self.line_no > 0 else ' ') + + + def process(self, callback): + try: + result = callback(self) + self.close() + return result + except Exception as ex: + ex.message = self.location() + getattr(ex, 'message', str(ex)) + raise + + + def read_line(self): + return self.read_lines(lambda line: line) + + + def read_lines(self, callback): + try: + for line in self.file: + self.line_no += 1 + self.eof = False + line = callback(line.rstrip()) + if line is not None: + return line + except OSError: + self.unseek() + raise + + self.eof = True + return None + + + def unseek(self): + self.line_no = 0 + self.eof = False + + +# -- OutputFileStream -- +class OutputFileStream: + def __init__(self, file_name, encoding='binary'): + if file_name is not None: + self.file = open(file_name, 'wb') + self.st_name = file_name + else: + self.file = sys.stdout.buffer + self.st_name = '' + + if encoding is None and self.file.isatty(): + raise Exception(self.location() + 'binary output may not be send to a terminal') + + self.encoding = (None if encoding == 'binary' else encoding) + self.close_attempt = False + + + def abort(self): + errors = '' + + if self.file != sys.stdout.buffer: + if not self.close_attempt: + try: + self.close() + except Exception as ex: + errors += '\n%sclose: %s' % (self.location(), str(ex)) + + try: + os.remove(self.st_name) + except Exception as ex: + errors += '\n%sunlink: %s' % (self.location(), str(ex)) + + return errors + + + def close(self): + self.close_attempt = True + self.file.close() + + + def location(self): + return self.st_name + ': ' + + + def process(self, callback): + try: + callback(self) + self.close() + except Exception as ex: + ex.message = self.location() + getattr(ex, 'message', str(ex)) + self.abort() + raise + + + def write(self, data): + self.file.write(data) + + + def write8(self, value): + self.write(struct.pack('B', value)) + + + def write16(self, value): + self.write(struct.pack(' +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import sys + +# -- Various -- +UNICODE_MAX = 1114111 # 0x10FFFF +UNICODE_BMP_MAX = 65535 # 0xFFFF + +def parse_dec(name, s, min_value=0, max_value=UNICODE_MAX): + try: + value = int(s) + except ValueError: + raise Exception('invalid %s format' % name) + + if min_value is not None and value < min_value: + raise Exception('%s must be >= %d' % (name, min_value)) + + if max_value is not None and value > max_value: + raise Exception('%s must be <= %d' % (name, max_value)) + + return value + + +def parse_hex(name, s, min_value=0, max_value=UNICODE_MAX): + try: + value = int(s, 16) + except ValueError: + raise Exception('invalid %s format' % name) + + if min_value is not None and value < min_value: + raise Exception('%s must be >= %X' % (name, min_value)) + + if max_value is not None and value > max_value: + raise Exception('%s must be <= %X' % (name, max_value)) + + return value + + +def quote(bstr): + return b'"%s"' % bstr.replace(b'"', b'""') + + +def unquote(bstr, name=None): + if len(bstr) >= 2 and bstr.startswith(b'"') and bstr.endswith(b'"'): + bstr = bstr[1 : len(bstr) - 1].replace(b'""', b'"') + elif name is not None: + raise Exception(name + ' must be quoted') + + return bstr + + +def message(prefix, severity, text): + sys.stderr.write('%s%s%s\n' % (prefix, severity + ': ' if severity else '', text)) + + +def warning(prefix, text): + message(prefix, 'warning', text) + + +def split_words(name, value, count): + words = value.split(None, count) + + if len(words) != count: + raise Exception('%s must contain %d values' % (name, count)) + + return words + + +GPL2PLUS_LICENSE = ('' + + 'This program is free software; you can redistribute it and/or modify it\n' + + 'under the terms of the GNU General Public License as published by the Free\n' + + 'Software Foundation; either version 2 of the License, or (at your option)\n' + + 'any later version.\n' + + '\n' + + 'This program is distributed in the hope that it will be useful, but\n' + + 'WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n' + + 'or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n' + + 'for more details.\n' + + '\n' + + 'You should have received a copy of the GNU General Public License along\n' + + 'with this program; if not, write to the Free Software Foundation, Inc.,\n' + + '51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n') diff --git a/bin/otb1cli.py b/bin/otb1cli.py new file mode 100644 index 0000000..92ab07b --- /dev/null +++ b/bin/otb1cli.py @@ -0,0 +1,99 @@ +# +# Copyright (C) 2018-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from datetime import datetime, timezone + +import fnutil +import fncli +import fnio +import otb1exp + +# -- Params -- +class Params(otb1exp.Params): + def __init__(self): + otb1exp.Params.__init__(self) + self.output_name = None + self.real_time = True + + +# -- Options -- +HELP = ('' + + 'usage: otb1cli [options] [INPUT]\n' + + 'Convert a BDF font to OTB\n' + + '\n' + + ' -o OUTPUT output file (default = stdout, may not be a terminal)\n' + + ' -d DIR-HINT set font direction hint (default = 0)\n' + + ' -e EM-SIZE set em size (default = 1024)\n' + + ' -g LINE-GAP set line gap (default = 0)\n' + + ' -l LOW-PPEM set lowest recorded PPEM (default = font height)\n' + + ' -E ENCODING BDF string properties encoding (default = utf-8)\n' + + ' -W WLANG-ID set Windows name-s language ID (default = 0x0409)\n' + + ' -T use the current date and time for created/modified\n' + + ' (default = get them from INPUT if not stdin/terminal)\n' + + ' -X set xMaxExtent = 0 (default = max character width)\n' + + ' -L write a single loca entry (default = CHARS entries)\n' + + ' -P write PostScript glyph names (default = no names)\n' + + '\n' + + 'Notes:\n' + + ' The input must be a BDF 2.1 font with unicode encoding.\n' + + ' All bitmaps are expanded first. Bitmap widths are used.\n' + + ' Overlapping characters are not supported.\n') + +VERSION = 'otb1cli 0.24, Copyright (C) 2018-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +class Options(otb1exp.Options): + def __init__(self): + otb1exp.Options.__init__(self, ['-o'], HELP, VERSION) + + + def parse(self, name, value, params): + if name == '-o': + params.output_name = value + elif name == '-T': + params.real_time = False + else: + otb1exp.Options.parse(self, name, value, params) + + +# -- Main -- +def main_program(nonopt, parsed): + if len(nonopt) > 1: + raise Exception('invalid number of arguments, try --help') + + # READ INPUT + def read_otb(ifs): + if parsed.real_time: + try: + stat = ifs.fstat() + if stat: + parsed.created = datetime.fromtimestamp(stat.st_ctime, timezone.utc) + parsed.modified = datetime.fromtimestamp(stat.st_mtime, timezone.utc) + except Exception as ex: + fnutil.warning(ifs.location(), str(ex)) + + return otb1exp.Font.read(ifs, parsed) + + font = fnio.read_file(nonopt[0] if nonopt else None, read_otb) + + # WRITE OUTPUT + sfnt = otb1exp.SFNT(font) + fnio.write_file(parsed.output_name, lambda ofs: ofs.write(sfnt.data), encoding=None) + + +if __name__ == '__main__': + fncli.start('otb1cli.py', Options(), Params(), main_program) diff --git a/bin/otb1exp.py b/bin/otb1exp.py new file mode 100644 index 0000000..51e2745 --- /dev/null +++ b/bin/otb1exp.py @@ -0,0 +1,808 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import struct +import codecs +import math +from datetime import datetime, timezone +from itertools import groupby +from enum import IntEnum, unique +from collections import OrderedDict + +import fnutil +import fncli +import bdf +import bdfexp +import otb1get + +# -- Table -- +class Table: + def __init__(self, name): + self.data = bytearray(0) + self.table_name = name + + + def check_size(self, size): + if size != self.size: + raise Exception('internal error: %s size = %d instead of %d' % (self.table_name, self.size, size)) + + + def checksum(self): + cksum = 0 + data = self.data + self.padding + + for offset in range(0, self.size, 4): + cksum += struct.unpack('>L', data[offset : offset + 4])[0] + + return cksum & 0xFFFFFFFF + + + def pack(self, format, value, name): + try: + return struct.pack(format, value) + except struct.error as ex: + raise Exception('%s.%s: %s' % (self.table_name, name, str(ex))) + + + @property + def size(self): + return len(self.data) + + + @property + def padding(self): + return bytes(((self.size + 1) & 3) ^ 1) + + + def rewrite_uint32(self, value, offset): + self.data[offset : offset + 4] = struct.pack('>L', value) + + + def write(self, data): + self.data += data + + + def write_int8(self, value, name): + self.data += self.pack('b', value, name) + + + def write_uint8(self, value, name): + self.data += self.pack('B', value, name) + + + def write_int16(self, value, name): + self.data += self.pack('>h', value, name) + + + def write_uint16(self, value, name): + self.data += self.pack('>H', value, name) + + + def write_uint32(self, value, name): + self.data += self.pack('>L', value, name) + + + def write_uint64(self, value, name): + self.data += self.pack('>Q', value, name) + + + def write_fixed(self, value, name): + self.data += self.pack('>l', round(value * 65536), name) + + + def write_table(self, table): + self.data += table.data + + +# -- Params -- +EM_SIZE_MIN = 64 +EM_SIZE_MAX = 16384 +EM_SIZE_DEFAULT = 1024 + +class Params(fncli.Params): # pylint: disable=too-many-instance-attributes + def __init__(self): + fncli.Params.__init__(self) + self.created = datetime.now(timezone.utc) + self.modified = self.created + self.dir_hint = 0 + self.em_size = EM_SIZE_DEFAULT + self.line_gap = 0 + self.low_ppem = 0 + self.encoding = 'utf_8' + self.w_lang_id = 0x0409 + self.x_max_extent = True + self.single_loca = False + self.post_names = False + + +# -- Options -- +class Options(fncli.Options): + def __init__(self, need_args, help_text, version_text): + fncli.Options.__init__(self, need_args + ['-d', '-e', '-g', '-l', '-E', '-W'], help_text, version_text) + + + def parse(self, name, value, params): + if name == '-d': + params.dir_hint = fnutil.parse_dec('DIR-HINT', value, -2, 2) + elif name == '-e': + params.em_size = fnutil.parse_dec('EM-SIZE', value, EM_SIZE_MIN, EM_SIZE_MAX) + elif name == '-g': + params.line_gap = fnutil.parse_dec('LINE-GAP', value, 0, EM_SIZE_MAX << 1) + elif name == '-l': + params.low_ppem = fnutil.parse_dec('LOW-PPEM', value, 1, bdf.DPARSE_LIMIT) + elif name == '-E': + params.encoding = value + elif name == '-W': + params.w_lang_id = fnutil.parse_hex('WLANG-ID', value, 0, 0x7FFF) + elif name == '-X': + params.x_max_extent = False + elif name == '-L': + params.single_loca = True + elif name == '-P': + params.post_names = True + else: + self.fallback(name, params) + + +# -- Font -- +class Font(bdfexp.Font): + def __init__(self, params): + bdfexp.Font.__init__(self) + self.params = params + self.em_ascender = 0 + self.em_descender = 0 + self.em_max_width = 0 + self.mac_style = 0 + self.line_size = 0 + + + @property + def bmp_only(self): + return self.max_code <= fnutil.UNICODE_BMP_MAX + + @property + def created(self): + return Font.sfntime(self.params.created) + + def decode(self, data): + return codecs.decode(data, self.params.encoding) + + def em_scale(self, value, divisor=0): + return round(value * self.params.em_size / (divisor or self.bbx.height)) + + def em_scale_width(self, base): + return self.em_scale(base.bbx.width) + + @property + def italic_angle(self): + value = self.props.get('ITALIC_ANGLE') # must be integer + return fnutil.parse_dec('ITALIC_ANGLE', value, -45, 45) if value else -11.5 if self.italic else 0 + + @property + def max_code(self): + return self.chars[-1].code + + @property + def min_code(self): + return self.chars[0].code + + @property + def modified(self): + return Font.sfntime(self.params.modified) + + + def prepare(self): + self.chars.sort(key=lambda c: c.code) + self.chars = [next(elem[1]) for elem in groupby(self.chars, key=lambda c: c.code)] + self.props.set('CHARS', len(self.chars)) + self.em_ascender = self.em_scale(self.px_ascender) + self.em_descender = self.em_ascender - self.params.em_size + self.em_max_width = self.em_scale_width(self) + self.mac_style = int(self.bold) + (int(self.italic) << 1) + self.line_size = self.em_scale(round(self.bbx.height / 17) or 1) + + + def _read(self, input): + bdfexp.Font._read(self, input) + self.prepare() + return self + + @staticmethod + def read(input, params): # pylint: disable=arguments-differ + return Font(params)._read(input) # pylint: disable=protected-access + + + @staticmethod + def sfntime(stamp): + return math.floor((stamp - datetime(1904, 1, 1, tzinfo=timezone.utc)).total_seconds()) + + @property + def underline_position(self): + return round((self.em_descender + self.line_size) / 2) + + @property + def x_max_extent(self): + return self.em_max_width if self.params.x_max_extent else 0 + + +# -- BDAT -- +BDAT_HEADER_SIZE = 4 +BDAT_METRIC_SIZE = 5 + +class BDAT(Table): + def __init__(self, font): + Table.__init__(self, 'EBDT') + # header + self.write_fixed(2, 'version') + # format 1 data + for char in font.chars: + self.write_uint8(font.bbx.height, 'height') + self.write_uint8(char.bbx.width, 'width') + self.write_int8(0, 'bearingX') + self.write_int8(font.px_ascender, 'bearingY') + self.write_uint8(char.bbx.width, 'advance') + self.write(char.data) # imageData + + + @staticmethod + def get_char_size(char): + return BDAT_METRIC_SIZE + len(char.data) + + +# -- BLOC -- +BLOC_TABLE_SIZE_OFFSET = 12 +BLOC_PREFIX_SIZE = 0x38 # header 0x08 + 1 bitmapSizeTable * 0x30 +BLOC_INDEX_ARRAY_SIZE = 8 # 1 index record * 0x08 + +class BLOC(Table): + def __init__(self, font): + Table.__init__(self, 'EBLC') + # header + self.write_fixed(2, 'version') + self.write_uint32(1, 'numSizes') + # bitmapSizeTable + self.write_uint32(BLOC_PREFIX_SIZE, 'indexSubTableArrayOffset') + self.write_uint32(0, 'indexTableSize') # adjusted later + self.write_uint32(1, 'numberOfIndexSubTables') + self.write_uint32(0, 'colorRef') + # hori + self.write_int8(font.px_ascender, 'hori ascender') + self.write_int8(font.px_descender, 'hori descender') + self.write_uint8(font.bbx.width, 'hori widthMax') + self.write_int8(1, 'hori caretSlopeNumerator') + self.write_int8(0, 'hori caretSlopeDenominator') + self.write_int8(0, 'hori caretOffset') + self.write_int8(0, 'hori minOriginSB') + self.write_int8(0, 'hori minAdvanceSB') + self.write_int8(font.px_ascender, 'hori maxBeforeBL') + self.write_int8(font.px_descender, 'hori minAfterBL') + self.write_int16(0, 'hori padd') + # vert + self.write_int8(0, 'vert ascender') + self.write_int8(0, 'vert descender') + self.write_uint8(0, 'vert widthMax') + self.write_int8(0, 'vert caretSlopeNumerator') + self.write_int8(0, 'vert caretSlopeDenominator') + self.write_int8(0, 'vert caretOffset') + self.write_int8(0, 'vert minOriginSB') + self.write_int8(0, 'vert minAdvanceSB') + self.write_int8(0, 'vert maxBeforeBL') + self.write_int8(0, 'vert minAfterBL') + self.write_int16(0, 'vert padd') + # (bitmapSizeTable) + self.write_uint16(0, 'startGlyphIndex') + self.write_uint16(len(font.chars) - 1, 'endGlyphIndex') + self.write_uint8(font.bbx.height, 'ppemX') + self.write_uint8(font.bbx.height, 'ppemY') + self.write_uint8(1, 'bitDepth') + self.write_uint8(1, 'flags') # small metrics are horizontal + # indexSubTableArray + self.write_uint16(0, 'firstGlyphIndex') + self.write_uint16(len(font.chars) - 1, 'lastGlyphIndex') + self.write_uint32(BLOC_INDEX_ARRAY_SIZE, 'additionalOffsetToIndexSubtable') + # indexSubtableHeader + self.write_uint16(1 if font.proportional else 2, 'indexFormat') + self.write_uint16(1, 'imageFormat') # BDAT -> small metrics, byte-aligned + self.write_uint32(BDAT_HEADER_SIZE, 'imageDataOffset') + # indexSubtable data + if font.proportional: + offset = 0 + + for char in font.chars: + self.write_uint32(offset, 'offsetArray[]') + offset += BDAT.get_char_size(char) + + self.write_uint32(offset, 'offsetArray[]') + else: + self.write_uint32(BDAT.get_char_size(font.chars[0]), 'imageSize') + self.write_uint8(font.bbx.height, 'height') + self.write_uint8(font.bbx.width, 'width') + self.write_int8(0, 'horiBearingX') + self.write_int8(font.px_ascender, 'horiBearingY') + self.write_uint8(font.bbx.width, 'horiAdvance') + self.write_int8(-(font.bbx.width >> 1), 'vertBearingX') + self.write_int8(0, 'vertBearingY') + self.write_uint8(font.bbx.height, 'vertAdvance') + # adjust + self.rewrite_uint32(self.size - BLOC_PREFIX_SIZE, BLOC_TABLE_SIZE_OFFSET) + + +# -- OS/2 -- +OS_2_TABLE_SIZE = 96 + +class OS_2(Table): # pylint: disable=invalid-name + def __init__(self, font): + Table.__init__(self, 'OS/2') + # Version 4 + x_avg_char_width = font.em_scale(font.avg_width) # otb1get.x_avg_char_width(font) + ul_char_ranges = otb1get.ul_char_ranges(font) + ul_code_pages = otb1get.ul_code_pages(font) if font.bmp_only else [0, 0] + # mostly from FontForge + script_xsize = font.em_scale(30, 100) + script_ysize = font.em_scale(40, 100) + subscript_yoff = script_ysize >> 1 + xfactor = math.tan(font.italic_angle * math.pi / 180) + subscript_xoff = 0 # stub, no overlapping characters yet + superscript_yoff = font.em_ascender - script_ysize + superscript_xoff = -round(xfactor * superscript_yoff) + # write + self.write_uint16(4, 'version') + self.write_int16(x_avg_char_width, 'xAvgCharWidth') + self.write_uint16(700 if font.bold else 400, 'usWeightClass') + self.write_uint16(5, 'usWidthClass') # medium + self.write_int16(0, 'fsType') + self.write_int16(script_xsize, 'ySubscriptXSize') + self.write_int16(script_ysize, 'ySubscriptYSize') + self.write_int16(subscript_xoff, 'ySubscriptXOffset') + self.write_int16(subscript_yoff, 'ySubscriptYOffset') + self.write_int16(script_xsize, 'ySuperscriptXSize') + self.write_int16(script_ysize, 'ySuperscriptYSize') + self.write_int16(superscript_xoff, 'ySuperscriptXOffset') + self.write_int16(superscript_yoff, 'ySuperscriptYOffset') + self.write_int16(font.line_size, 'yStrikeoutSize') + self.write_int16(font.em_scale(25, 100), 'yStrikeoutPosition') + self.write_int16(0, 'sFamilyClass') # no classification + self.write_uint8(2, 'bFamilyType') # text and display + self.write_uint8(0, 'bSerifStyle') # any + self.write_uint8(8 if font.bold else 6, 'bWeight') + self.write_uint8(3 if font.proportional else 9, 'bProportion') + self.write_uint8(0, 'bContrast') + self.write_uint8(0, 'bStrokeVariation') + self.write_uint8(0, 'bArmStyle') + self.write_uint8(0, 'bLetterform') + self.write_uint8(0, 'bMidline') + self.write_uint8(0, 'bXHeight') + self.write_uint32(ul_char_ranges[0], 'ulCharRange1') + self.write_uint32(ul_char_ranges[1], 'ulCharRange2') + self.write_uint32(ul_char_ranges[2], 'ulCharRange3') + self.write_uint32(ul_char_ranges[3], 'ulCharRange4') + self.write_uint32(0x586F7334, 'achVendID') # 'Xos4' + self.write_uint16(OS_2.fs_selection(font), 'fsSelection') + self.write_uint16(min(font.min_code, fnutil.UNICODE_BMP_MAX), 'firstChar') + self.write_uint16(min(font.max_code, fnutil.UNICODE_BMP_MAX), 'lastChar') + self.write_int16(font.em_ascender, 'sTypoAscender') + self.write_int16(font.em_descender, 'sTypoDescender') + self.write_int16(font.params.line_gap, 'sTypoLineGap') + self.write_uint16(font.em_ascender, 'usWinAscent') + self.write_uint16(-font.em_descender, 'usWinDescent') + self.write_uint32(ul_code_pages[0], 'ulCodePageRange1') + self.write_uint32(ul_code_pages[1], 'ulCodePageRange2') + self.write_int16(font.em_scale(font.px_ascender * 0.6), 'sxHeight') # stub + self.write_int16(font.em_scale(font.px_ascender * 0.8), 'sCapHeight') # stub + self.write_uint16(OS_2.default_char(font), 'usDefaultChar') + self.write_uint16(OS_2.break_char(font), 'usBreakChar') + self.write_uint16(1, 'usMaxContext') + # check + self.check_size(OS_2_TABLE_SIZE) + + + @staticmethod + def break_char(font): + return 0x20 if next((char for char in font.chars if char.code == 0x20), None) else font.min_code + + + @staticmethod + def default_char(font): + if font.default_code != -1 and font.default_code <= fnutil.UNICODE_BMP_MAX: + return font.default_code + + return 0 if font.min_code == 0 else font.max_code + + + @staticmethod + def fs_selection(font): + fs_selection = int(font.bold) * 5 + int(font.italic) + return fs_selection if fs_selection != 0 else 0x40 if font.xlfd[bdf.XLFD.SLANT] == 'R' else 0 + + +# -- cmap -- +CMAP_4_PREFIX_SIZE = 12 +CMAP_4_FORMAT_SIZE = 16 +CMAP_4_SEGMENT_SIZE = 8 + +CMAP_12_PREFIX_SIZE = 20 +CMAP_12_FORMAT_SIZE = 16 +CMAP_12_GROUP_SIZE = 12 + +class CMapRange: + def __init__(self, glyph_index=0, start_code=0, final_code=-2): + self.glyph_index = glyph_index + self.start_code = start_code + self.final_code = final_code + + + @property + def id_delta(self): + return (self.glyph_index - self.start_code) & 0xFFFF + + +class CMAP(Table): + def __init__(self, font): + Table.__init__(self, 'cmap') + # make ranges + ranges = [] + range = CMapRange() + index = -1 + + for char in font.chars: + index += 1 + code = char.code + + if code == range.final_code + 1: + range.final_code += 1 + else: + range = CMapRange(index, code, code) + ranges.append(range) + # write + if font.bmp_only: + if font.max_code < 0xFFFF: + ranges.append(CMapRange(0, 0xFFFF, 0xFFFF)) + + self.write_format_4(ranges) + else: + self.write_format_12(ranges) + + + def write_format_4(self, ranges): + # index + self.write_uint16(0, 'version') + self.write_uint16(1, 'numberSubtables') + # encoding subtables index + self.write_uint16(3, 'platformID') # Microsoft + self.write_uint16(1, 'platformSpecificID') # Unicode BMP (UCS-2) + self.write_uint32(CMAP_4_PREFIX_SIZE, 'offset') # for Unicode BMP (UCS-2) + # cmap format 4 + seg_count = len(ranges) + subtable_size = CMAP_4_FORMAT_SIZE + seg_count * CMAP_4_SEGMENT_SIZE + search_range = 2 << math.floor(math.log2(seg_count)) + + self.write_uint16(4, 'format') + self.write_uint16(subtable_size, 'length') + self.write_uint16(0, 'language') # none/independent + self.write_uint16(seg_count * 2, 'segCountX2') + self.write_uint16(search_range, 'searchRange') + self.write_uint16(int(math.log2(search_range / 2)), 'entrySelector') + self.write_uint16((seg_count * 2) - search_range, 'rangeShift') + for range in ranges: + self.write_uint16(range.final_code, 'endCode') + self.write_uint16(0, 'reservedPad') + for range in ranges: + self.write_uint16(range.start_code, 'startCode') + for range in ranges: + self.write_uint16(range.id_delta, 'idDelta') + for _ in ranges: + self.write_uint16(0, 'idRangeOffset') + # check + self.check_size(CMAP_4_PREFIX_SIZE + subtable_size) + + + def write_format_12(self, ranges): + # index + self.write_uint16(0, 'version') + self.write_uint16(2, 'numberSubtables') + # encoding subtables + self.write_uint16(0, 'platformID') # Unicode + self.write_uint16(4, 'platformSpecificID') # Unicode 2.0+ full range + self.write_uint32(CMAP_12_PREFIX_SIZE, 'offset') # for Unicode 2.0+ full range + self.write_uint16(3, 'platformID') # Microsoft + self.write_uint16(10, 'platformSpecificID') # Unicode UCS-4 + self.write_uint32(CMAP_12_PREFIX_SIZE, 'offset') # for Unicode UCS-4 + # cmap format 12 + subtable_size = CMAP_12_FORMAT_SIZE + len(ranges) * CMAP_12_GROUP_SIZE + + self.write_fixed(12, 'format') + self.write_uint32(subtable_size, 'length') + self.write_uint32(0, 'language') # none/independent + self.write_uint32(len(ranges), 'nGroups') + for range in ranges: + self.write_uint32(range.start_code, 'startCharCode') + self.write_uint32(range.final_code, 'endCharCode') + self.write_uint32(range.glyph_index, 'startGlyphID') + # check + self.check_size(CMAP_12_PREFIX_SIZE + subtable_size) + + +# -- glyf -- +class GLYF(Table): + def __init__(self, _font): + Table.__init__(self, 'glyf') + + +# -- head -- +HEAD_TABLE_SIZE = 54 +HEAD_CHECKSUM_OFFSET = 8 + +class HEAD(Table): + def __init__(self, font): + Table.__init__(self, 'head') + self.write_fixed(1, 'version') + self.write_fixed(1, 'fontRevision') + self.write_uint32(0, 'checksumAdjustment') # adjusted later + self.write_uint32(0x5F0F3CF5, 'magicNumber') + self.write_uint16(HEAD.flags(font), 'flags') + self.write_uint16(font.params.em_size, 'unitsPerEm') + self.write_uint64(font.created, 'created') + self.write_uint64(font.modified, 'modified') + self.write_int16(0, 'xMin') + self.write_int16(font.em_descender, 'yMin') + self.write_int16(font.em_max_width, 'xMax') + self.write_int16(font.em_ascender, 'yMax') + self.write_uint16(font.mac_style, 'macStyle') + self.write_uint16(font.params.low_ppem or font.bbx.height, 'lowestRecPPEM') + self.write_int16(font.params.dir_hint, 'fontDirectionHint') + self.write_int16(0, 'indexToLocFormat') # short + self.write_int16(0, 'glyphDataFormat') # current + # check + self.check_size(HEAD_TABLE_SIZE) + + + @staticmethod + def flags(font): + return 0x20B if otb1get.contains_rtl(font) else 0x0B # y0 base, x0 lsb, scale int + + +# -- hhea -- +HHEA_TABLE_SIZE = 36 + +class HHEA(Table): + def __init__(self, font): + Table.__init__(self, 'hhea') + self.write_fixed(1, 'version') + self.write_int16(font.em_ascender, 'ascender') + self.write_int16(font.em_descender, 'descender') + self.write_int16(font.params.line_gap, 'lineGap') + self.write_uint16(font.em_max_width, 'advanceWidthMax') + self.write_int16(0, 'minLeftSideBearing') + self.write_int16(0, 'minRightSideBearing') + self.write_int16(font.x_max_extent, 'xMaxExtent') + self.write_int16(100 if font.italic else 1, 'caretSlopeRise') + self.write_int16(20 if font.italic else 0, 'caretSlopeRun') + self.write_int16(0, 'caretOffset') + self.write_int16(0, 'reserved') + self.write_int16(0, 'reserved') + self.write_int16(0, 'reserved') + self.write_int16(0, 'reserved') + self.write_int16(0, 'metricDataFormat') # current + self.write_uint16(len(font.chars), 'numOfLongHorMetrics') + # check + self.check_size(HHEA_TABLE_SIZE) + + +# -- hmtx -- +class HMTX(Table): + def __init__(self, font): + Table.__init__(self, 'hmtx') + for char in font.chars: + self.write_uint16(font.em_scale_width(char), 'advanceWidth') + self.write_int16(0, 'leftSideBearing') + + +# -- loca -- +class LOCA(Table): + def __init__(self, font): + Table.__init__(self, 'loca') + if not font.params.single_loca: + for _ in font.chars: + self.write_uint16(0, 'offset') + self.write_uint16(0, 'offset') + + +# -- maxp -- +MAXP_TABLE_SIZE = 32 + +class MAXP(Table): + def __init__(self, font): + Table.__init__(self, 'maxp') + self.write_fixed(1, 'version') + self.write_uint16(len(font.chars), 'numGlyphs') + self.write_uint16(0, 'maxPoints') + self.write_uint16(0, 'maxContours') + self.write_uint16(0, 'maxComponentPoints') + self.write_uint16(0, 'maxComponentContours') + self.write_uint16(2, 'maxZones') + self.write_uint16(0, 'maxTwilightPoints') + self.write_uint16(1, 'maxStorage') + self.write_uint16(1, 'maxFunctionDefs') + self.write_uint16(0, 'maxInstructionDefs') + self.write_uint16(64, 'maxStackElements') + self.write_uint16(0, 'maxSizeOfInstructions') + self.write_uint16(0, 'maxComponentElements') + self.write_uint16(0, 'maxComponentDepth') + # check + self.check_size(MAXP_TABLE_SIZE) + + +# -- name -- +@unique # pylint: disable=invalid-name +class NAME_ID(IntEnum): # pylint: disable=invalid-name + COPYRIGHT = 0 + FONT_FAMILY = 1 + FONT_SUBFAMILY = 2 + UNIQUE_SUBFAMILY = 3 + FULL_FONT_NAME = 4 + LICENSE = 14 + +NAME_HEADER_SIZE = 6 +NAME_RECORD_SIZE = 12 + +class NAME(Table): + def __init__(self, font): + Table.__init__(self, 'name') + # compute names + names = OrderedDict() + copyright = font.props.get('COPYRIGHT') + + if copyright is not None: + names[NAME_ID.COPYRIGHT] = fnutil.unquote(copyright) + + family = font.xlfd[bdf.XLFD.FAMILY_NAME] + style = [b'Regular', b'Bold', b'Italic', b'Bold Italic'][font.mac_style] + + names[NAME_ID.FONT_FAMILY] = family + names[NAME_ID.FONT_SUBFAMILY] = style + names[NAME_ID.UNIQUE_SUBFAMILY] = b'%s %s bitmap height %d' % (family, style, font.bbx.height) + names[NAME_ID.FULL_FONT_NAME] = b'%s %s' % (family, style) + + license = font.props.get('LICENSE') + notice = font.props.get('NOTICE') + + if license is None and notice is not None and b'license' in notice.lower(): + license = notice + + if license is not None: + names[NAME_ID.LICENSE] = fnutil.unquote(license) + + # header + count = len(names) * (1 + 1) # Unicode + Microsoft + string_offset = NAME_HEADER_SIZE + NAME_RECORD_SIZE * count + + self.write_uint16(0, 'format') + self.write_uint16(count, 'count') + self.write_uint16(string_offset, 'stringOffset') + # name records / create values + values = Table('name') + + for [name_id, bstr] in names.items(): + s = font.decode(bstr) + value = codecs.encode(s, 'utf_16_be') + bmp = font.bmp_only and len(value) == len(s) * 2 + # Unicode + self.write_uint16(0, 'platformID') # Unicode + self.write_uint16(3 if bmp else 4, 'platformSpecificID') + self.write_uint16(0, 'languageID') # none + self.write_uint16(name_id, 'nameID') + self.write_uint16(len(value), 'length') # in bytes + self.write_uint16(values.size, 'offset') + # Microsoft + self.write_uint16(3, 'platformID') # Microsoft + self.write_uint16(1 if bmp else 10, 'platformSpecificID') + self.write_uint16(font.params.w_lang_id, 'languageID') + self.write_uint16(name_id, 'nameID') + self.write_uint16(len(value), 'length') # in bytes + self.write_uint16(values.size, 'offset') + # value + values.write(value) + + # write values + self.write_table(values) + # check + self.check_size(string_offset + values.size) + + +# -- post -- +POST_TABLE_SIZE = 32 + +class POST(Table): + def __init__(self, font): + Table.__init__(self, 'post') + self.write_fixed(2 if font.params.post_names else 3, 'format') + self.write_fixed(font.italic_angle, 'italicAngle') + self.write_int16(font.underline_position, 'underlinePosition') + self.write_int16(font.line_size, 'underlineThickness') + self.write_uint32(0 if font.proportional else 1, 'isFixedPitch') + self.write_uint32(0, 'minMemType42') + self.write_uint32(0, 'maxMemType42') + self.write_uint32(0, 'minMemType1') + self.write_uint32(0, 'maxMemType1') + # names + if font.params.post_names: + self.write_uint16(len(font.chars), 'numberOfGlyphs') + post_names = otb1get.post_mac_names() + post_mac_count = len(post_names) + + for name in [char.props['STARTCHAR'] for char in font.chars]: + if name in post_names: + self.write_uint16(post_names.index(name), 'glyphNameIndex') + else: + self.write_uint16(len(post_names), 'glyphNameIndex') + post_names.append(name) + + for name in post_names[post_mac_count:]: + self.write_uint8(len(name), 'glyphNameLength') + self.write(name) + # check + else: + self.check_size(POST_TABLE_SIZE) + + +# -- SFNT -- +SFNT_HEADER_SIZE = 12 +SFNT_RECORD_SIZE = 16 +SFNT_SUBTABLES = (BDAT, BLOC, OS_2, CMAP, GLYF, HEAD, HHEA, HMTX, LOCA, MAXP, NAME, POST) + +class SFNT(Table): + def __init__(self, font): + Table.__init__(self, 'SFNT') + # create tables + tables = [] + for ctor in SFNT_SUBTABLES: + tables.append(ctor(font)) + # header + num_tables = len(tables) + entry_selector = math.floor(math.log2(num_tables)) + search_range = 16 << entry_selector + content_offset = SFNT_HEADER_SIZE + num_tables * SFNT_RECORD_SIZE + offset = content_offset + content = Table('SFNT') + head_checksum_offset = -1 + + self.write_fixed(1, 'sfntVersion') + self.write_uint16(num_tables, 'numTables') + self.write_uint16(search_range, 'searchRange') + self.write_uint16(entry_selector, 'entrySelector') + self.write_uint16(num_tables * 16 - search_range, 'rangeShift') + # table records / create content + for table in tables: + self.write(bytes(table.table_name, 'ascii')) + self.write_uint32(table.checksum(), 'checkSum') + self.write_uint32(offset, 'offset') + self.write_uint32(len(table.data), 'length') + # create content + if table.table_name == 'head': + head_checksum_offset = offset + HEAD_CHECKSUM_OFFSET + + padded_data = table.data + table.padding + content.write(padded_data) + offset += len(padded_data) + # write content + self.write_table(content) + # check + self.check_size(content_offset + len(content.data)) + # adjust + if head_checksum_offset != -1: + self.rewrite_uint32((0xB1B0AFBA - self.checksum()) & 0xFFFFFFFF, head_checksum_offset) diff --git a/bin/otb1get.py b/bin/otb1get.py new file mode 100644 index 0000000..65a4485 --- /dev/null +++ b/bin/otb1get.py @@ -0,0 +1,663 @@ +# +# Copyright (C) 2018-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# pylint: disable=bad-whitespace + +# -- x_avg_char_width -- +WEIGHT_FACTORS = ( + ( 'a', 64 ), + ( 'b', 14 ), + ( 'c', 27 ), + ( 'd', 35 ), + ( 'e', 100 ), + ( 'f', 20 ), + ( 'g', 14 ), + ( 'h', 42 ), + ( 'i', 63 ), + ( 'j', 3 ), + ( 'k', 6 ), + ( 'l', 35 ), + ( 'm', 20 ), + ( 'n', 56 ), + ( 'o', 56 ), + ( 'p', 17 ), + ( 'q', 4 ), + ( 'r', 49 ), + ( 's', 56 ), + ( 't', 71 ), + ( 'u', 31 ), + ( 'v', 10 ), + ( 'w', 18 ), + ( 'x', 3 ), + ( 'y', 18 ), + ( 'z', 2 ), + ( ' ', 166 ) +) + +def x_avg_char_width(font): + x_avg_total_width = 0 + + for factor in WEIGHT_FACTORS: + char = next((char for char in font.chars if char.code == ord(factor[0])), None) + + if char is None: + return 0 + + x_avg_total_width += font.scaleWidth(char) * factor[1] + + return round(x_avg_total_width / 1000) + + +# -- ul_char_ranges -- +CHAR_RANGES = ( + ( 0, 0x0000, 0x007F ), + ( 1, 0x0080, 0x00FF ), + ( 2, 0x0100, 0x017F ), + ( 3, 0x0180, 0x024F ), + ( 4, 0x0250, 0x02AF ), + ( 4, 0x1D00, 0x1D7F ), + ( 4, 0x1D80, 0x1DBF ), + ( 5, 0x02B0, 0x02FF ), + ( 5, 0xA700, 0xA71F ), + ( 6, 0x0300, 0x036F ), + ( 6, 0x1DC0, 0x1DFF ), + ( 7, 0x0370, 0x03FF ), + ( 8, 0x2C80, 0x2CFF ), + ( 9, 0x0400, 0x04FF ), + ( 9, 0x0500, 0x052F ), + ( 9, 0x2DE0, 0x2DFF ), + ( 9, 0xA640, 0xA69F ), + ( 10, 0x0530, 0x058F ), + ( 11, 0x0590, 0x05FF ), + ( 12, 0xA500, 0xA63F ), + ( 13, 0x0600, 0x06FF ), + ( 13, 0x0750, 0x077F ), + ( 14, 0x07C0, 0x07FF ), + ( 15, 0x0900, 0x097F ), + ( 16, 0x0980, 0x09FF ), + ( 17, 0x0A00, 0x0A7F ), + ( 18, 0x0A80, 0x0AFF ), + ( 19, 0x0B00, 0x0B7F ), + ( 20, 0x0B80, 0x0BFF ), + ( 21, 0x0C00, 0x0C7F ), + ( 22, 0x0C80, 0x0CFF ), + ( 23, 0x0D00, 0x0D7F ), + ( 24, 0x0E00, 0x0E7F ), + ( 25, 0x0E80, 0x0EFF ), + ( 26, 0x10A0, 0x10FF ), + ( 26, 0x2D00, 0x2D2F ), + ( 27, 0x1B00, 0x1B7F ), + ( 28, 0x1100, 0x11FF ), + ( 29, 0x1E00, 0x1EFF ), + ( 29, 0x2C60, 0x2C7F ), + ( 29, 0xA720, 0xA7FF ), + ( 30, 0x1F00, 0x1FFF ), + ( 31, 0x2000, 0x206F ), + ( 31, 0x2E00, 0x2E7F ), + ( 32, 0x2070, 0x209F ), + ( 33, 0x20A0, 0x20CF ), + ( 34, 0x20D0, 0x20FF ), + ( 35, 0x2100, 0x214F ), + ( 36, 0x2150, 0x218F ), + ( 37, 0x2190, 0x21FF ), + ( 37, 0x27F0, 0x27FF ), + ( 37, 0x2900, 0x297F ), + ( 37, 0x2B00, 0x2BFF ), + ( 38, 0x2200, 0x22FF ), + ( 38, 0x2A00, 0x2AFF ), + ( 38, 0x27C0, 0x27EF ), + ( 38, 0x2980, 0x29FF ), + ( 39, 0x2300, 0x23FF ), + ( 40, 0x2400, 0x243F ), + ( 41, 0x2440, 0x245F ), + ( 42, 0x2460, 0x24FF ), + ( 43, 0x2500, 0x257F ), + ( 44, 0x2580, 0x259F ), + ( 45, 0x25A0, 0x25FF ), + ( 46, 0x2600, 0x26FF ), + ( 47, 0x2700, 0x27BF ), + ( 48, 0x3000, 0x303F ), + ( 49, 0x3040, 0x309F ), + ( 50, 0x30A0, 0x30FF ), + ( 50, 0x31F0, 0x31FF ), + ( 51, 0x3100, 0x312F ), + ( 51, 0x31A0, 0x31BF ), + ( 52, 0x3130, 0x318F ), + ( 53, 0xA840, 0xA87F ), + ( 54, 0x3200, 0x32FF ), + ( 55, 0x3300, 0x33FF ), + ( 56, 0xAC00, 0xD7AF ), + ( 57, 0xD800, 0xDFFF ), + ( 58, 0x10900, 0x1091F ), + ( 59, 0x4E00, 0x9FFF ), + ( 59, 0x2E80, 0x2EFF ), + ( 59, 0x2F00, 0x2FDF ), + ( 59, 0x2FF0, 0x2FFF ), + ( 59, 0x3400, 0x4DBF ), + ( 59, 0x20000, 0x2A6DF ), + ( 59, 0x3190, 0x319F ), + ( 60, 0xE000, 0xF8FF ), + ( 61, 0x31C0, 0x31EF ), + ( 61, 0xF900, 0xFAFF ), + ( 61, 0x2F800, 0x2FA1F ), + ( 62, 0xFB00, 0xFB4F ), + ( 63, 0xFB50, 0xFDFF ), + ( 64, 0xFE20, 0xFE2F ), + ( 65, 0xFE10, 0xFE1F ), + ( 65, 0xFE30, 0xFE4F ), + ( 66, 0xFE50, 0xFE6F ), + ( 67, 0xFE70, 0xFEFF ), + ( 68, 0xFF00, 0xFFEF ), + ( 69, 0xFFF0, 0xFFFF ), + ( 70, 0x0F00, 0x0FFF ), + ( 71, 0x0700, 0x074F ), + ( 72, 0x0780, 0x07BF ), + ( 73, 0x0D80, 0x0DFF ), + ( 74, 0x1000, 0x109F ), + ( 75, 0x1200, 0x137F ), + ( 75, 0x1380, 0x139F ), + ( 75, 0x2D80, 0x2DDF ), + ( 76, 0x13A0, 0x13FF ), + ( 77, 0x1400, 0x167F ), + ( 78, 0x1680, 0x169F ), + ( 79, 0x16A0, 0x16FF ), + ( 80, 0x1780, 0x17FF ), + ( 80, 0x19E0, 0x19FF ), + ( 81, 0x1800, 0x18AF ), + ( 82, 0x2800, 0x28FF ), + ( 83, 0xA000, 0xA48F ), + ( 83, 0xA490, 0xA4CF ), + ( 84, 0x1700, 0x171F ), + ( 84, 0x1720, 0x173F ), + ( 84, 0x1740, 0x175F ), + ( 84, 0x1760, 0x177F ), + ( 85, 0x10300, 0x1032F ), + ( 86, 0x10330, 0x1034F ), + ( 87, 0x10400, 0x1044F ), + ( 88, 0x1D000, 0x1D0FF ), + ( 88, 0x1D100, 0x1D1FF ), + ( 88, 0x1D200, 0x1D24F ), + ( 89, 0x1D400, 0x1D7FF ), + ( 90, 0xF0000, 0xFFFFD ), + ( 90, 0x100000, 0x10FFFD ), + ( 91, 0xFE00, 0xFE0F ), + ( 91, 0xE0100, 0xE01EF ), + ( 92, 0xE0000, 0xE007F ), + ( 93, 0x1900, 0x194F ), + ( 94, 0x1950, 0x197F ), + ( 95, 0x1980, 0x19DF ), + ( 96, 0x1A00, 0x1A1F ), + ( 97, 0x2C00, 0x2C5F ), + ( 98, 0x2D30, 0x2D7F ), + ( 99, 0x4DC0, 0x4DFF ), + ( 100, 0xA800, 0xA82F ), + ( 101, 0x10000, 0x1007F ), + ( 101, 0x10080, 0x100FF ), + ( 101, 0x10100, 0x1013F ), + ( 102, 0x10140, 0x1018F ), + ( 103, 0x10380, 0x1039F ), + ( 104, 0x103A0, 0x103DF ), + ( 105, 0x10450, 0x1047F ), + ( 106, 0x10480, 0x104AF ), + ( 107, 0x10800, 0x1083F ), + ( 108, 0x10A00, 0x10A5F ), + ( 109, 0x1D300, 0x1D35F ), + ( 110, 0x12000, 0x123FF ), + ( 110, 0x12400, 0x1247F ), + ( 111, 0x1D360, 0x1D37F ), + ( 112, 0x1B80, 0x1BBF ), + ( 113, 0x1C00, 0x1C4F ), + ( 114, 0x1C50, 0x1C7F ), + ( 115, 0xA880, 0xA8DF ), + ( 116, 0xA900, 0xA92F ), + ( 117, 0xA930, 0xA95F ), + ( 118, 0xAA00, 0xAA5F ), + ( 119, 0x10190, 0x101CF ), + ( 120, 0x101D0, 0x101FF ), + ( 121, 0x102A0, 0x102DF ), + ( 121, 0x10280, 0x1029F ), + ( 121, 0x10920, 0x1093F ), + ( 122, 0x1F030, 0x1F09F ), + ( 122, 0x1F000, 0x1F02F ) +) + +def ul_char_ranges(font): + char_ranges = [0, 0, 0, 0] + + for char in font.chars: + range = next((range for range in CHAR_RANGES if range[1] <= char.code <= range[2]), None) + + if range is not None: + char_ranges[range[0] >> 5] |= 1 << (range[0] & 0x1F) + + if font.max_code >= 0x10000: + char_ranges[57 >> 5] |= 1 << (57 & 0x1F) + + return char_ranges + + +# -- ul_code_pages -- +def ul_code_pages(font): + space_index = next((index for index, char in enumerate(font.chars) if char.code == 0x20), len(font.chars)) + ascii = int(len(font.chars) >= space_index + 0x5F and font.chars[space_index + 0x5E].code == 0x7E) + findf = lambda unicode: int(next((char for char in font.chars if char.code == unicode), None) is not None) + graph = findf(0x2524) + radic = findf(0x221A) + code_pages = [0, 0] + + # conditions from FontForge + for char in font.chars: + unicode = char.code + + if unicode == 0x00DE: + code_pages[0] |= (ascii) << 0 # 1252 Latin1 + elif unicode == 0x255A: + code_pages[1] |= (ascii) << 30 # 850 WE/Latin1 + code_pages[1] |= (ascii) << 31 # 437 US + elif unicode == 0x013D: + code_pages[0] |= (ascii) << 1 # 1250 Latin 2: Eastern Europe + code_pages[1] |= (ascii & graph) << 26 # 852 Latin 2 + elif unicode == 0x0411: + code_pages[0] |= 1 << 2 # 1251 Cyrillic + code_pages[1] |= (findf(0x255C) & graph) << 17 # 866 MS-DOS Russian + code_pages[1] |= (findf(0x0405) & graph) << 25 # 855 IBM Cyrillic + elif unicode == 0x0386: + code_pages[0] |= 1 << 3 # 1253 Greek + code_pages[1] |= (findf(0x00BD) & graph) << 16 # 869 IBM Greek + code_pages[1] |= (graph & radic) << 28 # 737 Greek; former 437 G + elif unicode == 0x0130: + code_pages[0] |= (ascii) << 4 # 1254 Turkish + code_pages[1] |= (ascii & graph) << 24 # 857 IBM Turkish + elif unicode == 0x05D0: + code_pages[0] |= 1 << 5 # 1255 Hebrew + code_pages[1] |= (graph & radic) << 21 # 862 Hebrew + elif unicode == 0x0631: + code_pages[0] |= 1 << 6 # 1256 Arabic + code_pages[1] |= (radic) << 19 # 864 Arabic + code_pages[1] |= (graph) << 29 # 708 Arabic; ASMO 708 + elif unicode == 0x0157: + code_pages[0] |= (ascii) << 7 # 1257 Windows Baltic + code_pages[1] |= (ascii & graph) << 27 # 775 MS-DOS Baltic + elif unicode == 0x20AB: + code_pages[0] |= 1 << 8 # 1258 Vietnamese + elif unicode == 0x0E45: + code_pages[0] |= 1 << 16 # 874 Thai + elif unicode == 0x30A8: + code_pages[0] |= 1 << 17 # 932 JIS/Japan + elif unicode == 0x3105: + code_pages[0] |= 1 << 18 # 936 Chinese: Simplified chars + elif unicode == 0x3131: + code_pages[0] |= 1 << 19 # 949 Korean Wansung + elif unicode == 0x592E: + code_pages[0] |= 1 << 20 # 950 Chinese: Traditional chars + elif unicode == 0xACF4: + code_pages[0] |= 1 << 21 # 1361 Korean Johab + elif unicode == 0x2030: + code_pages[0] |= (findf(0x2211) & ascii) << 29 # Macintosh Character Set (Roman) + elif unicode == 0x2665: + code_pages[0] |= (ascii) << 30 # OEM Character Set + elif unicode == 0x00C5: + code_pages[1] |= (ascii & graph & radic) << 18 # 865 MS-DOS Nordic + elif unicode == 0x00E9: + code_pages[1] |= (ascii & graph & radic) << 20 # 863 MS-DOS Canadian French + elif unicode == 0x00F5: + code_pages[1] |= (ascii & graph & radic) << 23 # 860 MS-DOS Portuguese + elif unicode == 0x00FE: + code_pages[1] |= (ascii & graph) << 22 # 861 MS-DOS Icelandic + elif 0xF000 <= unicode <= 0xF0FF: + code_pages[0] |= 1 << 31 # Symbol Character Set + + return code_pages + + +# -- strong_rtl_flag -- +RTL_RANGES = ( + ( 0x05BE, 0x05BE ), + ( 0x05C0, 0x05C0 ), + ( 0x05C3, 0x05C3 ), + ( 0x05C6, 0x05C6 ), + ( 0x05D0, 0x05EA ), + ( 0x05EF, 0x05F4 ), + ( 0x0608, 0x0608 ), + ( 0x060B, 0x060B ), + ( 0x060D, 0x060D ), + ( 0x061B, 0x061C ), + ( 0x061E, 0x064A ), + ( 0x066D, 0x066F ), + ( 0x0671, 0x06D5 ), + ( 0x06E5, 0x06E6 ), + ( 0x06EE, 0x06EF ), + ( 0x06FA, 0x070D ), + ( 0x070F, 0x0710 ), + ( 0x0712, 0x072F ), + ( 0x074D, 0x07A5 ), + ( 0x07B1, 0x07B1 ), + ( 0x07C0, 0x07EA ), + ( 0x07F4, 0x07F5 ), + ( 0x07FA, 0x07FA ), + ( 0x07FE, 0x0815 ), + ( 0x081A, 0x081A ), + ( 0x0824, 0x0824 ), + ( 0x0828, 0x0828 ), + ( 0x0830, 0x083E ), + ( 0x0840, 0x0858 ), + ( 0x085E, 0x085E ), + ( 0x0860, 0x086A ), + ( 0x08A0, 0x08B4 ), + ( 0x08B6, 0x08BD ), + ( 0x200F, 0x200F ), + ( 0x202B, 0x202B ), + ( 0x202E, 0x202E ), + ( 0xFB1D, 0xFB1D ), + ( 0xFB1F, 0xFB28 ), + ( 0xFB2A, 0xFB36 ), + ( 0xFB38, 0xFB3C ), + ( 0xFB3E, 0xFB3E ), + ( 0xFB40, 0xFB41 ), + ( 0xFB43, 0xFB44 ), + ( 0xFB46, 0xFBC1 ), + ( 0xFBD3, 0xFD3D ), + ( 0xFD50, 0xFD8F ), + ( 0xFD92, 0xFDC7 ), + ( 0xFDF0, 0xFDFC ), + ( 0xFE70, 0xFE74 ), + ( 0xFE76, 0xFEFC ), + ( 0x10800, 0x10FFF ), + ( 0x1E800, 0x1EFFF ), + (-1, 0) +) + +def contains_rtl(font): + index = 0 + + for char in font.chars: + while char.code > RTL_RANGES[index][1]: + index += 1 + if RTL_RANGES[index][0] == -1: + break + + if char.code >= RTL_RANGES[index][0]: + return True + + return False + + +# -- post_mac_names -- +POST_MAC_NAMES = ( + b'.notdef', + b'.null', + b'nonmarkingreturn', + b'space', + b'exclam', + b'quotedbl', + b'numbersign', + b'dollar', + b'percent', + b'ampersand', + b'quotesingle', + b'parenleft', + b'parenright', + b'asterisk', + b'plus', + b'comma', + b'hyphen', + b'period', + b'slash', + b'zero', + b'one', + b'two', + b'three', + b'four', + b'five', + b'six', + b'seven', + b'eight', + b'nine', + b'colon', + b'semicolon', + b'less', + b'equal', + b'greater', + b'question', + b'at', + b'A', + b'B', + b'C', + b'D', + b'E', + b'F', + b'G', + b'H', + b'I', + b'J', + b'K', + b'L', + b'M', + b'N', + b'O', + b'P', + b'Q', + b'R', + b'S', + b'T', + b'U', + b'V', + b'W', + b'X', + b'Y', + b'Z', + b'bracketleft', + b'backslash', + b'bracketright', + b'asciicircum', + b'underscore', + b'grave', + b'a', + b'b', + b'c', + b'd', + b'e', + b'f', + b'g', + b'h', + b'i', + b'j', + b'k', + b'l', + b'm', + b'n', + b'o', + b'p', + b'q', + b'r', + b's', + b't', + b'u', + b'v', + b'w', + b'x', + b'y', + b'z', + b'braceleft', + b'bar', + b'braceright', + b'asciitilde', + b'Adieresis', + b'Aring', + b'Ccedilla', + b'Eacute', + b'Ntilde', + b'Odieresis', + b'Udieresis', + b'aacute', + b'agrave', + b'acircumflex', + b'adieresis', + b'atilde', + b'aring', + b'ccedilla', + b'eacute', + b'egrave', + b'ecircumflex', + b'edieresis', + b'iacute', + b'igrave', + b'icircumflex', + b'idieresis', + b'ntilde', + b'oacute', + b'ograve', + b'ocircumflex', + b'odieresis', + b'otilde', + b'uacute', + b'ugrave', + b'ucircumflex', + b'udieresis', + b'dagger', + b'degree', + b'cent', + b'sterling', + b'section', + b'bullet', + b'paragraph', + b'germandbls', + b'registered', + b'copyright', + b'trademark', + b'acute', + b'dieresis', + b'notequal', + b'AE', + b'Oslash', + b'infinity', + b'plusminus', + b'lessequal', + b'greaterequal', + b'yen', + b'mu', + b'partialdiff', + b'summation', + b'product', + b'pi', + b'integral', + b'ordfeminine', + b'ordmasculine', + b'Omega', + b'ae', + b'oslash', + b'questiondown', + b'exclamdown', + b'logicalnot', + b'radical', + b'florin', + b'approxequal', + b'Delta', + b'guillemotleft', + b'guillemotright', + b'ellipsis', + b'nonbreakingspace', + b'Agrave', + b'Atilde', + b'Otilde', + b'OE', + b'oe', + b'endash', + b'emdash', + b'quotedblleft', + b'quotedblright', + b'quoteleft', + b'quoteright', + b'divide', + b'lozenge', + b'ydieresis', + b'Ydieresis', + b'fraction', + b'currency', + b'guilsinglleft', + b'guilsinglright', + b'fi', + b'fl', + b'daggerdbl', + b'periodcentered', + b'quotesinglbase', + b'quotedblbase', + b'perthousand', + b'Acircumflex', + b'Ecircumflex', + b'Aacute', + b'Edieresis', + b'Egrave', + b'Iacute', + b'Icircumflex', + b'Idieresis', + b'Igrave', + b'Oacute', + b'Ocircumflex', + b'apple', + b'Ograve', + b'Uacute', + b'Ucircumflex', + b'Ugrave', + b'dotlessi', + b'circumflex', + b'tilde', + b'macron', + b'breve', + b'dotaccent', + b'ring', + b'cedilla', + b'hungarumlaut', + b'ogonek', + b'caron', + b'Lslash', + b'lslash', + b'Scaron', + b'scaron', + b'Zcaron', + b'zcaron', + b'brokenbar', + b'Eth', + b'eth', + b'Yacute', + b'yacute', + b'Thorn', + b'thorn', + b'minus', + b'multiply', + b'onesuperior', + b'twosuperior', + b'threesuperior', + b'onehalf', + b'onequarter', + b'threequarters', + b'franc', + b'Gbreve', + b'gbreve', + b'Idotaccent', + b'Scedilla', + b'scedilla', + b'Cacute', + b'cacute', + b'Ccaron', + b'ccaron', + b'dcroat' +) + +def post_mac_names(): + return list(POST_MAC_NAMES) diff --git a/bin/ucstoany.py b/bin/ucstoany.py new file mode 100644 index 0000000..6715c24 --- /dev/null +++ b/bin/ucstoany.py @@ -0,0 +1,188 @@ +# +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import re +import copy + +import fnutil +import fncli +import fnio +import bdf + +# -- Params -- +class Params(fncli.Params): + def __init__(self): + fncli.Params.__init__(self) + self.filter_ffff = False + self.family_name = None + self.output_name = None + + +# -- Options -- +HELP = ('' + + 'usage: ucstoany [-f] [-F FAMILY] [-o OUTPUT] INPUT REGISTRY ENCODING TABLE...\n' + + 'Generate a BDF font subset.\n' + + '\n' + + ' -f, --filter Discard characters with unicode FFFF; with registry ISO10646,\n' + + ' encode the first 32 characters with their indexes; with other\n' + + ' registries, encode all characters with indexes\n' + + ' -F FAMILY output font family name (default = input)\n' + + ' -o OUTPUT output file (default = stdout)\n' + + ' TABLE text file, one hexadecimal unicode per line\n' + + ' --help display this help and exit\n' + + ' --version display the program version and license, and exit\n' + + ' --excstk display the exception stack on error\n' + + '\n' + + 'The input must be a BDF 2.1 font with unicode encoding.\n' + + 'Unlike ucs2any, all TABLE-s form a single subset of the input font.\n') + +VERSION = 'ucstoany 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE + +class Options(fncli.Options): + def __init__(self): + fncli.Options.__init__(self, ['-F', '-o'], HELP, VERSION) + + + def parse(self, name, value, params): + if name in ['-f', '--filter']: + params.filter_ffff = True + elif name == '-F': + params.family_name = bytes(value, 'ascii') + if '-' in value: + raise Exception('FAMILY may not contain "-"') + elif name == '-o': + params.output_name = value + else: + self.fallback(name, params) + + +# -- Main -- +def main_program(nonopt, parsed): + # NON-OPTIONS + if len(nonopt) < 4: + raise Exception('invalid number of arguments, try --help') + + input_name = nonopt[0] + registry = nonopt[1] + encoding = nonopt[2] + new_codes = [] + + if not re.fullmatch(r'[A-Za-z][\w.:()]*', registry) or not re.fullmatch(r'[\w.:()]+', encoding): + raise Exception('invalid registry or encoding') + + # READ INPUT + old_font = fnio.read_file(input_name, bdf.Font.read) + + # READ TABLES + def load_code(line): + new_codes.append(fnutil.parse_hex('unicode', line)) + + for table_name in nonopt[3:]: + fnio.read_file(table_name, lambda ifs: ifs.read_lines(load_code)) + + if not new_codes: + raise Exception('no characters in the output font') + + # CREATE GLYPHS + new_font = bdf.Font() + charmap = {char.code:char for char in old_font.chars} + index = 0 + unstart = 0 + family = parsed.family_name if parsed.family_name is not None else old_font.xlfd[bdf.XLFD.FAMILY_NAME] + + if parsed.filter_ffff: + unstart = 32 if registry == 'ISO10646' else bdf.CHARS_MAX + + for code in new_codes: + if code == 0xFFFF and parsed.filter_ffff: + index += 1 + continue + + if code in charmap: + old_char = charmap[code] + uni_ffff = False + else: + uni_ffff = True + + if code != 0xFFFF: + raise Exception('%s does not contain U+%04X' % (input, code)) + + if old_font.default_code != -1: + old_char = charmap[old_font.default_code] + elif 0xFFFD in charmap: + old_char = charmap[0xFFFD] + else: + raise Exception('%s does not contain U+FFFF, and no replacement found' % input) + + new_char = copy.copy(old_char) + new_char.code = code if index >= unstart else index + index += 1 + new_char.props = copy.copy(old_char.props) + new_char.props.set('ENCODING', new_char.code) + new_font.chars.append(new_char) + + if uni_ffff: + new_char.props.set('STARTCHAR', b'uniFFFF') + elif old_char.code == old_font.default_code or (old_char.code == 0xFFFD and new_font.default_code == -1): + new_font.default_code = new_char.code + + # CREATE HEADER + registry = bytes(registry, 'ascii') + encoding = bytes(encoding, 'ascii') + + for [name, value] in old_font.props: + if name == 'FONT': + new_font.xlfd = old_font.xlfd[:] + new_font.xlfd[bdf.XLFD.FAMILY_NAME] = family + new_font.xlfd[bdf.XLFD.CHARSET_REGISTRY] = registry + new_font.xlfd[bdf.XLFD.CHARSET_ENCODING] = encoding + value = b'-'.join(new_font.xlfd) + elif name == 'STARTPROPERTIES': + num_props = fnutil.parse_dec(name, value, 1) + elif name == 'FAMILY_NAME': + value = fnutil.quote(family) + elif name == 'CHARSET_REGISTRY': + value = fnutil.quote(registry) + elif name == 'CHARSET_ENCODING': + value = fnutil.quote(encoding) + elif name == 'DEFAULT_CHAR': + if new_font.default_code != -1: + value = new_font.default_code + else: + num_props -= 1 + continue + elif name == 'ENDPROPERTIES': + if new_font.default_code != -1 and new_font.props.get('DEFAULT_CHAR') is None: + new_font.props.set('DEFAULT_CHAR', new_font.default_code) + num_props += 1 + + new_font.props.set('STARTPROPERTIES', num_props) + elif name == 'CHARS': + value = len(new_font.chars) + + new_font.props.set(name, value) + + # COPY FIELDS + new_font.bbx = old_font.bbx + + # WRITE OUTPUT + fnio.write_file(parsed.output_name, lambda ofs: new_font.write(ofs)) + + +if __name__ == '__main__': + fncli.start('ucstoany.py', Options(), Params(), main_program) diff --git a/doko.bdf b/doko.bdf index 8369829..ab9f8b5 100644 --- a/doko.bdf +++ b/doko.bdf @@ -26,7 +26,7 @@ QUAD_WIDTH 8 DEFAULT_CHAR 0 FONT_DESCENT 2 FONT_ASCENT 8 -COMMENT """"""""""""""""""""""Procon - An Iconic Font based on Stlarch font, Sm4tik's xbm pack, FontAwesome and Lokaltog Symbol Font"""""""""""""""""""""" +COMMENT """"""""""""""""""""""Doko - An Iconic Font based on Stlarch font, Sm4tik's xbm pack, FontAwesome and Lokaltog Symbol Font"""""""""""""""""""""" COMMENT ""Doko font - an iconic bitmap font based on Siji"" ENDPROPERTIES CHARS 631 diff --git a/doko.otb b/doko.otb index ff70ba01f091b2d74dce6b4974f1f320ae8f4092..5f283785d7571aa3a6ac8c7d426e34998c173e32 100644 GIT binary patch literal 22916 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 SyqHtlDf|CfBo3*GVEaFMct))N literal 13392 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 Date: Mon, 3 May 2021 22:00:01 +0300 Subject: [PATCH 05/19] Add scripts credit to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index be89020..037264e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ git clone https://github.com/begss/doko && cd doko && make install **[stark](https://github.com/stark) for [Siji](https://github.com/stark/siji)** +**Dimitar Zhekov** for bdf to otb convertation scripts + **Sm4tik** for sm4tik xbm icon pack **Stlarch** for stlarch font From 0e96586069f3447953c51170bbeff82ff93651eb Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 22:24:24 +0300 Subject: [PATCH 06/19] Rename to Neosiji and maintain compatibility with Siji --- Makefile | 18 +++++++++--------- README.md | 14 +++++++------- doko.bdf => siji.bdf | 8 ++++---- doko.otb => siji.otb | Bin 22916 -> 22916 bytes doko.pcf => siji.pcf | Bin 51744 -> 51744 bytes 5 files changed, 20 insertions(+), 20 deletions(-) rename doko.bdf => siji.bdf (99%) rename doko.otb => siji.otb (98%) rename doko.pcf => siji.pcf (99%) diff --git a/Makefile b/Makefile index b001536..37dc110 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,17 @@ prefix = ~/.local -x11dir = $(prefix)/share/fonts/doko -otbdir = $(prefix)/share/fonts/doko +x11dir = $(prefix)/share/fonts/siji +otbdir = $(prefix)/share/fonts/siji install: pcf otb mkdir -p $(x11dir) $(otbdir) - cp doko.pcf $(x11dir) - cp doko.otb $(otbdir) + cp siji.pcf $(x11dir) + cp siji.otb $(otbdir) -pcf: doko.bdf - bdftopcf doko.bdf -o doko.pcf +pcf: siji.bdf + bdftopcf siji.bdf -o siji.pcf -otb: doko.bdf - python3 bin/otb1cli.py -o doko.otb doko.bdf +otb: siji.bdf + python3 bin/otb1cli.py -o siji.otb siji.bdf clean: - rm doko.otb doko.pcf + rm siji.otb siji.pcf diff --git a/README.md b/README.md index 037264e..1ae1326 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Doko +# Neosiji -Doko is a fixed and maintained [Siji](https://github.com/stark/siji). +Neosiji is a fixed and maintained [Siji](https://github.com/stark/siji). **Contributions are welcome** -![doko](preview.png "preview of doko") +![Neosiji](preview.png "Preview of Siji") ## Differences from Siji: - Build **correct** OTB font => support in modern apps @@ -13,7 +13,7 @@ Doko is a fixed and maintained [Siji](https://github.com/stark/siji). ## Installation -[![Packaging status](https://repology.org/badge/vertical-allrepos/fonts:doko.svg)](https://repology.org/project/fonts:doko/versions) +It may be already used as upstream in your distro's Siji package. Otherwise, install it manually or package it. ### Manual @@ -21,7 +21,7 @@ Doko is a fixed and maintained [Siji](https://github.com/stark/siji). - bdftopcf ```sh -git clone https://github.com/begss/doko && cd doko && make install +git clone https://github.com/begss/neosiji && cd neosiji && make install ``` ## TODO @@ -29,11 +29,11 @@ git clone https://github.com/begss/doko && cd doko && make install - [ ] More Glyphs! - [ ] Adding glyphs of different sizes. - [ ] Improving glyph alignment. -- [ ] Creating small and large version of doko. +- [ ] Creating small and large version of Siji. ## Credits -**[stark](https://github.com/stark) for [Siji](https://github.com/stark/siji)** +[**stark**](https://github.com/stark) for [Siji](https://github.com/stark/siji) **Dimitar Zhekov** for bdf to otb convertation scripts diff --git a/doko.bdf b/siji.bdf similarity index 99% rename from doko.bdf rename to siji.bdf index ab9f8b5..fd65049 100644 --- a/doko.bdf +++ b/siji.bdf @@ -1,11 +1,11 @@ STARTFONT 2.1 -FONT -Wuncon-Doko-Medium-R-Normal--10-100-75-75-C-80-ISO10646-1 +FONT -Wuncon-Siji-Medium-R-Normal--10-100-75-75-C-80-ISO10646-1 SIZE 10 75 75 FONTBOUNDINGBOX 12 12 0 -2 STARTPROPERTIES 23 FONTNAME_REGISTRY "" FOUNDRY "Wuncon" -FAMILY_NAME "Doko" +FAMILY_NAME "Siji" WEIGHT_NAME "Medium" SLANT "R" SETWIDTH_NAME "Normal" @@ -26,8 +26,8 @@ QUAD_WIDTH 8 DEFAULT_CHAR 0 FONT_DESCENT 2 FONT_ASCENT 8 -COMMENT """"""""""""""""""""""Doko - An Iconic Font based on Stlarch font, Sm4tik's xbm pack, FontAwesome and Lokaltog Symbol Font"""""""""""""""""""""" -COMMENT ""Doko font - an iconic bitmap font based on Siji"" +COMMENT """"""""""""""""""""""Neosiji - An Iconic Font based on Siji"""""""""""""""""""""" +COMMENT ""Neosiji font - an iconic bitmap font based on Siji"" ENDPROPERTIES CHARS 631 STARTCHAR U+E000 diff --git a/doko.otb b/siji.otb similarity index 98% rename from doko.otb rename to siji.otb index 5f283785d7571aa3a6ac8c7d426e34998c173e32..6d9881e6237a9ab62566e208f0b4d64acf1ec113 100644 GIT binary patch delta 102 zcmZqK%-FJ-aY7Q4?eU2zzRafbc^kJhc*v~zzjbjuzs*+$ZWaa*xU==IIFx4H9N@W@ mUpAN_lOYR;gBVg7(iut_au^aBiWs1xlcge!* Date: Mon, 3 May 2021 22:37:31 +0300 Subject: [PATCH 07/19] Add .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b3cf9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +siji.pcf +siji.otb +bin/__pycache__ From 640434096dfa57d5632f75f1feaad66dbf872a25 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 22:38:11 +0300 Subject: [PATCH 08/19] Don't ship precompiled fonts in repo --- siji.otb | Bin 22916 -> 0 bytes siji.pcf | Bin 51744 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 siji.otb delete mode 100644 siji.pcf diff --git a/siji.otb b/siji.otb deleted file mode 100644 index 6d9881e6237a9ab62566e208f0b4d64acf1ec113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22916 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 Si#fHOviBcF;*gpMw*LbmmPf1r diff --git a/siji.pcf b/siji.pcf deleted file mode 100644 index c863f4f8fede0379f8168e533d82520856a1fe03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51744 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 From fc071c4fe767d0cf02f4e287491bfd6fa850c025 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 22:41:57 +0300 Subject: [PATCH 09/19] Add 'build' makefile target --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 37dc110..ee8cd80 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ prefix = ~/.local x11dir = $(prefix)/share/fonts/siji otbdir = $(prefix)/share/fonts/siji +build: pcf otb + install: pcf otb mkdir -p $(x11dir) $(otbdir) cp siji.pcf $(x11dir) From ee77a91fcd836250e6d2623e6cb1145a4f0bd714 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 3 May 2021 23:08:56 +0300 Subject: [PATCH 10/19] Add simple script to show all glyphs to README --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1ae1326..803c142 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,35 @@ Neosiji is a fixed and maintained [Siji](https://github.com/stark/siji). ## Differences from Siji: - Build **correct** OTB font => support in modern apps - New repository structure, new build system, releases => easier to package -- *More coming soon* +- *More hopefully coming soon* ## Installation -It may be already used as upstream in your distro's Siji package. Otherwise, install it manually or package it. - -### Manual - #### Requirements: +- python3 - bdftopcf ```sh git clone https://github.com/begss/neosiji && cd neosiji && make install ``` +## How to get all glyphs + +```sh +#!/bin/sh + +codes="0 1 2 3 4 5 6 7 8 9 a b c d e f" + +for code0 in $codes; do + for code1 in $codes; do + for code2 in $codes; do + /usr/bin/printf "%s - \ue$code0$code1$code2\n" \ + e$code0$code1$code2 + done + done +done +``` + ## TODO - [ ] More Glyphs! From 2e16eff54cfb5d5b9f7c374ffcf863ba4d5ce95b Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 26 Jul 2021 17:38:15 +0300 Subject: [PATCH 11/19] rename to 'siji-ng' --- README.md | 8 ++++---- siji.bdf | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 803c142..b23a579 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Neosiji +# Siji-ng -Neosiji is a fixed and maintained [Siji](https://github.com/stark/siji). +A fixed and maintained [Siji](https://github.com/stark/siji) iconic bitmap font. **Contributions are welcome** -![Neosiji](preview.png "Preview of Siji") +![Siji-ng](preview.png "Preview of Siji") ## Differences from Siji: - Build **correct** OTB font => support in modern apps @@ -18,7 +18,7 @@ Neosiji is a fixed and maintained [Siji](https://github.com/stark/siji). - bdftopcf ```sh -git clone https://github.com/begss/neosiji && cd neosiji && make install +git clone https://github.com/begss/siji-ng && cd siji-ng && make install ``` ## How to get all glyphs diff --git a/siji.bdf b/siji.bdf index fd65049..f9347a1 100644 --- a/siji.bdf +++ b/siji.bdf @@ -26,8 +26,8 @@ QUAD_WIDTH 8 DEFAULT_CHAR 0 FONT_DESCENT 2 FONT_ASCENT 8 -COMMENT """"""""""""""""""""""Neosiji - An Iconic Font based on Siji"""""""""""""""""""""" -COMMENT ""Neosiji font - an iconic bitmap font based on Siji"" +COMMENT """"""""""""""""""""""Siji-ng - An Iconic Font based on Siji"""""""""""""""""""""" +COMMENT ""Siji-ng font - an iconic bitmap font based on Siji"" ENDPROPERTIES CHARS 631 STARTCHAR U+E000 From 8e1ab54b2980742caf0f6d3acf875a860c2eb6cc Mon Sep 17 00:00:00 2001 From: Warren Kozak Date: Thu, 18 Mar 2021 13:25:34 -0500 Subject: [PATCH 12/19] siji.bdf: add DWM layout icons and Gentoo icon --- siji.bdf | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/siji.bdf b/siji.bdf index f9347a1..8c7449e 100644 --- a/siji.bdf +++ b/siji.bdf @@ -29,7 +29,7 @@ FONT_ASCENT 8 COMMENT """"""""""""""""""""""Siji-ng - An Iconic Font based on Siji"""""""""""""""""""""" COMMENT ""Siji-ng font - an iconic bitmap font based on Siji"" ENDPROPERTIES -CHARS 631 +CHARS 634 STARTCHAR U+E000 ENCODING 57344 SWIDTH 1152 0 @@ -12019,4 +12019,61 @@ BITMAP 0000 0000 ENDCHAR +STARTCHAR dwm_centeredmaster +ENCODING 57975 +SWIDTH 1152 0 +DWIDTH 12 0 +BBX 12 12 0 -2 +BITMAP +0000 +0000 +3FC0 +2940 +39C0 +2940 +2940 +39C0 +2940 +3FC0 +0000 +0000 +ENDCHAR +STARTCHAR dwm_centeredfloatingmaster +ENCODING 57976 +SWIDTH 1152 0 +DWIDTH 12 0 +BBX 12 12 0 -2 +BITMAP +0000 +0000 +3FC0 +2940 +2F40 +2940 +2940 +2F40 +2940 +3FC0 +0000 +0000 +ENDCHAR +STARTCHAR gentoo +ENCODING 57977 +SWIDTH 1152 0 +DWIDTH 12 0 +BBX 12 12 0 -2 +BITMAP +0000 +0000 +0700 +0F80 +0EC0 +07E0 +03E0 +07C0 +0F80 +0F00 +0000 +0000 +ENDCHAR ENDFONT From dea3bc77779dd9b9833eec3bcdb1a34308a59c40 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Mon, 26 Jul 2021 17:57:43 +0300 Subject: [PATCH 13/19] README.md: mention new icons, add credits for them and minor reformat --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b23a579..a937a96 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ A fixed and maintained [Siji](https://github.com/stark/siji) iconic bitmap font. ![Siji-ng](preview.png "Preview of Siji") ## Differences from Siji: -- Build **correct** OTB font => support in modern apps -- New repository structure, new build system, releases => easier to package -- *More hopefully coming soon* +- Build **correct** OTB font => support in modern apps. +- New icons: DWM centeredmaster/centeredfloatingmaster, Gentoo. +- New repository structure, new build system, releases => easier to package. +- *More hopefully coming soon.* ## Installation @@ -47,22 +48,25 @@ done ## Credits -[**stark**](https://github.com/stark) for [Siji](https://github.com/stark/siji) +[**stark**](https://github.com/stark) for [Siji](https://github.com/stark/siji). -**Dimitar Zhekov** for bdf to otb convertation scripts +[**wrkzk**](https://github.com/wrkzk) for `dwm_centeredmaster`, +`dwm_centeredfloatingmaster` and `gentoo` icons. -**Sm4tik** for sm4tik xbm icon pack +**Dimitar Zhekov** for bdf to otb convertation scripts. -**Stlarch** for stlarch font +**Sm4tik** for sm4tik xbm icon pack. -**Sunaku** for sm4tik font +**Stlarch** for stlarch font. -**Lokaltog** for symbols font +**Sunaku** for sm4tik font. -**w0ng** for xbm icon font +**Lokaltog** for symbols font. -**Dave Gandy** for FontAwesome +**w0ng** for xbm icon font. -**Lucy** for Tewi font +**Dave Gandy** for FontAwesome. -**Phallus** for Lemon and Uushi font +**Lucy** for Tewi font. + +**Phallus** for Lemon and Uushi font. From f4ac79bd094bbadd8e550d30bd3709e15a627eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Janou=C5=A1ek?= Date: Sat, 5 Sep 2020 01:20:00 +0200 Subject: [PATCH 14/19] siji.bdf: add docker icon --- siji.bdf | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/siji.bdf b/siji.bdf index 8c7449e..a425ca3 100644 --- a/siji.bdf +++ b/siji.bdf @@ -29,7 +29,7 @@ FONT_ASCENT 8 COMMENT """"""""""""""""""""""Siji-ng - An Iconic Font based on Siji"""""""""""""""""""""" COMMENT ""Siji-ng font - an iconic bitmap font based on Siji"" ENDPROPERTIES -CHARS 634 +CHARS 635 STARTCHAR U+E000 ENCODING 57344 SWIDTH 1152 0 @@ -12076,4 +12076,23 @@ BITMAP 0000 0000 ENDCHAR +STARTCHAR docker +ENCODING 57978 +SWIDTH 1152 0 +DWIDTH 12 0 +BBX 12 12 0 -2 +BITMAP +0000 +0200 +0F00 +3F00 +7FA0 +0030 +FFE0 +EFE0 +FFC0 +7F80 +3E00 +0000 +ENDCHAR ENDFONT From 68e82ff8e4e3f677f4bcc8007276d4d7c9cf65ca Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Tue, 27 Jul 2021 01:21:58 +0300 Subject: [PATCH 15/19] README.md: update icons and credits --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a937a96..0daa6aa 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ A fixed and maintained [Siji](https://github.com/stark/siji) iconic bitmap font. ## Differences from Siji: - Build **correct** OTB font => support in modern apps. -- New icons: DWM centeredmaster/centeredfloatingmaster, Gentoo. +- New icons: `dwm_centeredmaster`, `dwm_centeredfloatingmaster`, `gentoo`, + `docker`. - New repository structure, new build system, releases => easier to package. - *More hopefully coming soon.* @@ -53,6 +54,8 @@ done [**wrkzk**](https://github.com/wrkzk) for `dwm_centeredmaster`, `dwm_centeredfloatingmaster` and `gentoo` icons. +[**the-papi**](https://github.com/the-papi) for `docker` icon. + **Dimitar Zhekov** for bdf to otb convertation scripts. **Sm4tik** for sm4tik xbm icon pack. From b625c664c3615d36092c052821ee3c22fb27cfaa Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Sun, 12 Dec 2021 14:00:30 +0300 Subject: [PATCH 16/19] Makefile: use common varable names --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index ee8cd80..26db0f5 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ -prefix = ~/.local -x11dir = $(prefix)/share/fonts/siji -otbdir = $(prefix)/share/fonts/siji +DESTDIR = ~ +PREFIX = /.local +X11DIR = $(DESTDIR)$(PREFIX)/share/fonts/misc +OTBDIR = $(DESTDIR)$(PREFIX)/share/fonts/misc build: pcf otb -install: pcf otb - mkdir -p $(x11dir) $(otbdir) - cp siji.pcf $(x11dir) - cp siji.otb $(otbdir) +install: build + install -Dm644 siji.pcf $(X11DIR)/siji.pcf + install -Dm644 siji.otb $(OTBDIR)/siji.otb pcf: siji.bdf bdftopcf siji.bdf -o siji.pcf From 58a3d4d9b80b341a4f3fe6c9c0b4cce0bd72a1a2 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Sun, 12 Dec 2021 14:04:13 +0300 Subject: [PATCH 17/19] README.md: fix "show all glyphs" script --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0daa6aa..a9ac7eb 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,15 @@ git clone https://github.com/begss/siji-ng && cd siji-ng && make install ## How to get all glyphs -```sh -#!/bin/sh +```bash +#!/bin/bash codes="0 1 2 3 4 5 6 7 8 9 a b c d e f" for code0 in $codes; do for code1 in $codes; do for code2 in $codes; do - /usr/bin/printf "%s - \ue$code0$code1$code2\n" \ - e$code0$code1$code2 + printf "e$code0$code1$code2 - \ue$code0$code1$code2\n" done done done From b3fdf6bbab17eaa13c62b8c4a4404fca5c337181 Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Sun, 12 Dec 2021 14:11:11 +0300 Subject: [PATCH 18/19] README.md: add repology packaging status --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a9ac7eb..b808ae6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ A fixed and maintained [Siji](https://github.com/stark/siji) iconic bitmap font. ## Installation +[![Packaging status]( +https://repology.org/badge/vertical-allrepos/siji-ng.svg)]( +https://repology.org/project/siji-ng/versions) + #### Requirements: - python3 - bdftopcf From 59ddd620276d07f704b8471bac577d34636e28ca Mon Sep 17 00:00:00 2001 From: Maxim Karasev Date: Tue, 30 Aug 2022 16:32:09 +0300 Subject: [PATCH 19/19] Makefile: allow customization of installed font directory name --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 26db0f5..423fec7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ DESTDIR = ~ PREFIX = /.local -X11DIR = $(DESTDIR)$(PREFIX)/share/fonts/misc -OTBDIR = $(DESTDIR)$(PREFIX)/share/fonts/misc +NAME = siji +X11DIR = $(DESTDIR)$(PREFIX)/share/fonts/$(NAME) +OTBDIR = $(DESTDIR)$(PREFIX)/share/fonts/$(NAME) build: pcf otb