From e987d73d2b9e660dcf7de8a4739b9b0ff4f3e794 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Tue, 3 Oct 2017 15:39:28 +0200
Subject: [PATCH] BinaryCIF support

---
 examples/1cbs_full.bcif           | Bin 0 -> 66851 bytes
 src/reader/cif/binary/decoder.ts  | 215 ++++++++++++++++++++++++++++++
 src/reader/cif/binary/encoding.ts | 152 +++++++++++++++++++++
 src/reader/cif/binary/field.ts    |  54 +++++++-
 src/reader/cif/binary/parser.ts   |  50 ++++++-
 src/reader/cif/index.ts           |   6 +-
 src/script.ts                     |  55 +++++---
 7 files changed, 510 insertions(+), 22 deletions(-)
 create mode 100644 examples/1cbs_full.bcif
 create mode 100644 src/reader/cif/binary/decoder.ts
 create mode 100644 src/reader/cif/binary/encoding.ts

diff --git a/examples/1cbs_full.bcif b/examples/1cbs_full.bcif
new file mode 100644
index 0000000000000000000000000000000000000000..38628c3dafaae093d7f5f6689f4bc471ef826b27
GIT binary patch
literal 66851
zcmeG_30zgh*R$OFHWmR9agEGO&4q`GOXk8NsDy+>E+ry71Z5K*hNk9LnyIO&SuR=b
zXzrSsshOFXnVPw0?)!o)kN57qbMH6zZ9EV^lfNkS!=IOX-z;a&oHKLg%$Yee6ISR_
z<I>`F8CwI>(lX+cQe*YH-nxw8x(pv}i`FgtSJ1D_q_os!zAaj{@Xd*j)yD=Tr^OA;
zoH>4ZqAr$ZT&fKW=$#Ws^9@eRNYZ7V1s)TYrpBh|zKw~qgN(^E!;Hz$Wo9MoSHz_y
zXQiZO&b1`W8J4BX7!ji%k*-@R7chRM8K<Pw!86}qIy5OYesyns2L07FBO`Xi8aYL<
zj57_HJ;5BmEJ&BEk6liSADom5R%B+xMNwGT61gfeD>Y1)I#{1*kB~olCyms>iRSEU
zyQfm*br}({akR9-s|JnG>jKlVQuQz=Df5lg%%s7oy7(V$nb3H1rd0tnl^LU@GU7~5
zrWwZ|irvzLtmNdiX$c9LI=v0kPySy_pCBG9EahhIMSw9=lYYRZDY2PDcU!0}6Hup5
zO3}q+>Er$r^xov>t8Lp{>)YJ7m5;Vn2fx-GTDPwR<gZ>P`Bx@o#^_Sg^&|chr2i^4
ztrAcgl}YJUiLsQ_85wCARiyskWSfIbDVx-*W7CsjtRuyLg4oN<gI^_R+H9Fkdre$g
zhVH*b@K!DST4*aF!B=Lay<<@ZEW_unGPD|t{*oS>5u1`}(`{y1lAAT5r`sji@@Nyx
zBej}Stz-3RDZP{Qy3Agw$s<yz(wN*mJ|Qh5h3XSleJnZcornxwT#~FYn)S)d3g@Vz
zY{cy%O95Pz3YCYY8!-Sv#!(qsHas>t>nUrUkHeomHmy@tW;LOM*166%B_*(Xw?T19
z2??>unYs$6JX<D8jGgi<fGlH`Un&44h#*8};yWn#Wy7#=ivj=u*gGI==~2sLbgBA`
z5w?Cd(-QhsQvB2HWfe7dkY`gB&bo)pVeG7XaPDB|0<_S3h0KK{y`2r8KO;8aiV=28
zw0^BBoC^<;xppqd(h3#}E=%9B&f?6I&ZpbrD~QzJq^Hr*M3<48mfGC<`3@aXGAaT2
zl}=XiC3B(#`E#fRAtptqPfUBtVyA+buqq`sHFHENwf<BBvSW-N((2cz#}CSmiPvSu
zWhABRsgm)Z=mDp?1O|tNy%E+m(kC)Fs(W~^?twmC1G@+L1auD%>K-2A6A{@fD!6;N
zPgLKCV4v>YOC+i+eWiuV!}`ivSx3xDj*WRomozw0{{&bj*)4gGH1U<IzdU4kbWpYy
zS9mvpWot(E!2tqGA3*>=j!uOULYAR=o4GHgW~B_$WyGW<#H6Gp>*BJKb(vKR`bT!)
z3TM!Yc%42rDY=Rt|I995QT(7fn5DyfZSF8BS^C%~H1br5vCO^Y6=S8il8_NQIEBtg
zs$|zQm0fnOtT6W#-IG{fs_G^GQ!lYk-05<tyedo0C)w%xWSjZKYNn(suGFRyQ)NW?
z-xq@I8rp+K6e|aw#k{eC%vBruG^cCwKJnD%m=PN%kBb$q7(YZgcObjFO*6Kxd)vE!
zbs%38L)YrF;`G##pPFFn2xoz(g*|j_B{jjk8R4mS2UH$o)<fvwFj-k18XVRqCZZB>
zMq%Sj31iArx80VD&JVHHIna5nh0a>b%df)ejUO>Z#6YRE5Zq!(bl3#4!8|@k1O>z-
z>XMVP{~ay;(H!_nTN$YCXtXV{^=88$-Qbwy*g?8vDtc1V{~c6+w0v38wRd=hH};31
z;UM;LnZm_^=CPS0QvNG6|FUZ(ptyB$0)Z0Ze0^~%&(sb3cM$y1qGhSJwO^YGr}?8r
zI<oG-c|fFZF^hDW_Oe({O3kD)CSGUm6AtUG<=9a97!$B^4Xh98*$xA1WwN$fnz{o;
z%N~TSvdCsT+Mr8~|JO<WM|X<lVZm?o4vwq@-NJFnsN|SiO!+GL|0Fg#JB+vwk^jXM
z|1ZgZKfhLOD?vklg#4FjUnT#k&%ont>i_492j!FhW}PTDOP^S!_&+Xd9-<yNh@r9-
z|6-C?$^EAy_t(j~r@4*)m&AT$SNqq?68~Q&S^g7>WijEa<jd2MFZP!HUy?7CU9H?)
z%2FJc9Gm$hwxm3Un`Q-B6~3$t?j~hVp`Ojwp9-S$zt?(W+bU-<trf49B%9rdpMYtt
zqC3H(2ihg=wH3e7{n1uUy1QET>)z{N)wHkLTK6k%vaQ-?ChBzhn2gxe!MftbTX)Oi
z?Mh4J)85CRshEY^%4Ou&I66)}^)=gak@(T=e|76tep;<>`*v;pE8e>ARJN_yK9zNn
z9X@0?Df+LtKd2q)?y#KoOqLKIhYfb;HP<rh?dZI=EhMaO*RZZpp^*`x!F0QOSl`~%
zv>e?vtimmB>N0UF_F_{5>W*OkXW@ve><U63Odun($#GG=c;V}rObH%`H;-|gMJ1L(
z&v>b^Tm*l#OxoI}wQuY8{=RLs?R?w&Y3W<rUhCJc!n?vh5Kq*5fc~T1;UVIwGAu%t
ziz^SYGFH+>NXt-Mya-8mamwf9Jb2U%WAh)@lqu${Y*fj9wi^0al&ELDSot^%w2v{)
zWmeE}jM)+rCNEafp(P|NoQ^JZyy+SlQsKR-Y|riD0Y+sTU@TqA86b#7h+%1$LkF%@
zjV={sa4MnWnk{^m?OM;m;`BJ(z&@NmdUW|l+p4vHn>PObZCkf%-@d(8+e+)#M%&82
z!aKsFhZoD}^2p)k*{*Sxi!D{dOY!isl5WGJTi`RE#0glBu{WuTZ^b9z{X?|?l~4<c
z7gVceo~4f(ERX%?3C=u=SyQDNJQda8*{*e$OO!oYGx!(JJBt@%|2?wlKR;tBW=fS*
zdOA|+nXZsmkW~72&R6WKR~m~=WlM~C9o_19NSDxcDT9*jr)SNvBzo$LDi!1vTDex%
zOW5e2;L7@AEZ$ZSoxhRyeWhj41CbJv=n>U1wq{k~Q~8HXXYC7F2-LJ#vr3&kz1HIT
zOc8ztC!16d6Drg821`>fwhPZJ(<sVVcmu5q>l@ROlLn{J6WEgCo*-kMeeYP6l9p<z
zRwd{U>N5ModI!?S$tTX6Q$31*R;xWDTkS3DkM_~V>f!!vnWIh2U|p(h#{@l(UOo+C
zwM}F^bvL~V>W!6n=vkMRPEXpRr*F_p5aa`LOY^!Mu6fVClT~7Rud&WLVv>_GALv;B
zN@6eT+AN@1U?q@dY$<70TK;S;A%D#6US@(#_?!i9RSNH8dMG_w<+5)jEuru}gM(r#
zNVIQEinp9O>?1cQ^C4`NpetBU7B8jfK7(W5D~RgLv@vbkRQ%SsM>p@%GRGo8Hrf`G
zV(g9LKQXQ@%WgKJ*~m)JzRM>28hYq|MpAZv?c4N!X@ZwI>3H5UWT>dlO8@0{cHB7U
z0L`yF&}?e3rAatIYxTcFTQyu4r%%f$Up31{%&(k8dnmMW5v^<%P~4s%(aMEb+0|_I
zM2SYBJz3n!MYNJEu(h;ufwB>;v?jJJCQg@ZKke@`OAo0!?N8n<_jn%Sj~<|`W|dN!
zz)Fj=NKEWwNy%37zdyQlag>raNP`{=Qs{+|o@;*&DHBrMV?jExB>h#*;NB%5t)^ZW
z$-0=><n+YHtDt!l7#m#zSdInQAZi?ZEX2x!WkqaoY)Z=i4s2OJ`Amg>gcCNW+RHE$
zDM#j6POi^NO`>-N#l&VOJqZ`O$JqQ*LCWF3*!EdkP_BxxX_B^Cs`YltDq;EbgyrVU
z^w>CE%;1c)tn?V!J{c3*{I$~k?q6xT$%*js(^~#l0;OJKky53Gs-5N~O8N5Am<r-U
zmBey9%eh>uHEU#jj$Ws$*VC(NpFr2BV#)u^k_~ioM@&@gAbNL3jO9L!D!x47W9ZA(
zRkl1R#bT3_t9bKFc>_x;UTds3$;HT*f7x`ePc0MGRThJ9)#Bv;dONwp&J0-Q*#F;I
zCRi)CqjVj;9xPthIIKw}(6(I5r1+ZDOg%jlG%0mx>|os!5K7N}Ex0mY25zCYbw@}Q
zwJZ2OaLe6dveR5zsxBs(Dywwo!xK7=_}RDfQ&S?syY&tUf3qjOi?4TZc(-7BeP7S$
zu#o8R=-0v`dv@;{-L-4i-aR9_MhAEA`Fe0jP*k_R-Frs$j*5zojFNBi>((<mBsiGn
zdp#;TC?qmM6WFtNSai=fgDJG0uSEw2zuuEV3<(a2pt;_RiVO~m3J(ei4(lEk^;%Td
zprGjJz^KURzR{7vl|Z~rDO0@du<UnzunEM(#r`+8zEm{>U9lH40%bRYwYF`~vGkb2
zr+q9>1z9~S*MS(;-5w9B)%LySBT{T?^|RV%Uang>nKIXr8HXbomUIoM_}x$sLBp<5
zTaK)<pI%ksDunOMt$$E*Z0b<+smyVSu}P^(@i7U>u}{Kw^B6;6xj68UH7^Gad|4ST
z{;<zzEH|NAZ^*V@*O*4{rb^QN8*XX-2h{Fc35NT!sr{^G>g8(J59!hl?Yh$6|Nd<J
zSjt81N4M)!U0#!KhW1HK)BDid*Qjqust>i)`B=QHW0QSi^=T=YKJmJA>flDr-acum
zKKewR4_!;m7~zwZsf(`!{i2Ia4Yo=l`+#h{cyV1!T%s<;a{l|EwA6Upm~1t5(Vx%Y
zMyTK5?DDv-M?P|BKGm6?;zsX<qQ|+>Gk56eZc&jD^yHoHVZrpw9eNOt<#e~#f}`ju
zJoGr8uDv7Z*>6#OBLn;PjtuS^+AFleIqFq*9qi+}m+f30>+x^amxarbz#T$RjXqxV
zai))oK6U6*lRlpGsYM?-jr=XYJ?P_PeY?`PoL5dO=hf1u#HQViR;^i=p-ZDJCN&}H
zDXR;Q*DmabFx4)TrjCa&eHC8uQ`UcQ+^BUBs|((HF9Sje(nFU@|Mha*zOly4khBac
zePT<mAk4NLzO^h!m#mLnZjo8=pWjelPEtpeO=?T?jG~7}x#dDw?LLHFHz9N`fKX+G
zu+9|-Yu<p+^EQOFu0iO2&+;w5J?=v2RA_m-=2`N}Y301y-%A~JXlbCa8EJGMD0Lfq
z`uc1+sn)G*syU4OUxin^lJ(CUXb5OVWdo8Z`HLU$f-TGv103Vti?KyIWC(I>%d6zk
zl+MLtwC2X*;B8j1J1fe`vfq&YkxV=FVR&HIz`%gOkiei`eW<O>FEB_ONdE?F1B3kn
z`}GP5m%myC_M<;q1%}dy@P2_I+Q1OM@Zj)JZFs0(uVBC4LE*uEy@LFDg=l+)_6`Zu
z2KoiI3Tz$NCeS~yZD6~=_JLa8K&>`V>ldhP6{u|;sBIId^$*mx4b-*^)V2@w^X=u=
ztHQ^vc4bnd*b>tY&=X`Whcwb5%Y0a)<t3l1Wqz#3Y}wP8M>b>;i9aDdz;X+s4{dbj
zxAmJV@yn7zKBi8BWyMhML^;31!_j*J9U*I=sYFhu#4lS)8yuFxl8;<<gji5k4s#i<
zR$TKQNa10R0Z4ox)>dPh3Kr7G*;?M6hoyuy;O#QwD5hpi$OEx9400cckxQ=$jr4E+
z19A9)5F3SRQXFrV6$@*@8xMrnF~cvj>{8T~DP0|2djLW`coo)x_Eu8NQ@Wjq;`FG;
zmcIC1Y8#SWKKcDS<P$txW{-5#dQQV}R6YfTQV|u}wHK98fgzy*exU)aLPPvQLt2Ff
z`Gp3x3hfg`#ZZuzN-r9qrBAUG3#H;r7G$Bp^mbvtP%6+uLqfayg?4S_DA+=&gbSsy
z^na^Ri;xTLM}?d$=RyNQL$tIotz4#-KJ?p<maL@((eNNGg%oO)g`uG|G?a#h(v+co
zR5og<c%%r?yjpGVpwIv<m6Q|_85yg@q%plhLuIk)N5!Q@EVh#K(^|9+ZKVxu<rml{
zw6!+0HI<(2WO*9eHn4pt6`WM2Qpu_fl|TGw$yB(~vSs<|M?>0#YFqcx_M#E~G>O0b
zrqONbcU$`1mVURR-|gskJNn(8ez&LJ?P<}zv}|8L`m_p_vsbuaJ~*Mwc2F#tCjdkn
zJ$=k#SeCj}95akX{K_&~md>_+<{yZ${*!;npYmT@CbJkf!=NE@R%?I_f*fGWZ+&BA
z?ovO&!?c@fil7EU<~|)RTwy+hN+mtjnuo=|>2!rDfG)H{OXfq#^NQn`n(!74uK{!I
zVfE;5s$N2sJp?K#1S&!e^sk2gMNmy4T3}sqNM*(YJ_j$-boD7j4OBv&eqSw4!@NWZ
zcLmm^Se_^jRa$a3hsVt2_&})m{ejpvH10I5Ux|8AOz7kbouP3p;lbiKb2*;y4#o3y
zaj5bYxR0iMj^ZZQ<VJA}(;5Cq!`R{wD^6{og2LbD5UzynX<Aeq!UVy~G;duhVj*%Y
ztt8#)<PiSgkSi2!ry(-syHes7Qu@M%AD5Utt{xtnOm~^n*@`aXUm-#d-Y&D}I7-l!
zy}O5p1oe6&pu)THqm^^U>gjPCP#)8{Bc00Azf^0Ye`yQ1f78xx(ZK=jXEc=ZmHwr@
ztYivHPIIUw9W^P?6xGdtDR=3&CEQXPH8j{`-OS(Ayih8QBNmqQ)Z{?ZSa2-H%YqR#
zP0%>nsOVpFTp52oEHenZQHv_`mMvPVW6aZu%p|=o(`M3ISmkC!cOX4UW2xOLg+SSE
zM&Bl6#K!4k^z>Sk%!HV7duP7isn5O-Qs<WN&U_f?C*VN$Fwp;#3!J6#Z7o5NC+Eaj
zCdyC7qw`^)ACu5{#BkH`Npl{}!zbhGStg+$6J@gpS1FIFGO6)oyg(lYx&pC&%od&8
z=<nprxG5!gSSFz#6K+XD+oien*r1$ZS#A^MXF(0?R$PlyP8oi16>K$PzAw)Tg`!N2
z!g2FJPS>bjuZ7YDh^W^pgHE9^YifHnK%)Ob=sbFaqs{KYlL7x%t8@9t`jC}V3+^ha
zL&(_gcEo(Oo(_ewZ{47@(uVVym8s>}*D!ulHYl_0IBKIy!vlNuuJBgFmWfC4e3F%Q
zHmliA*+BkbQ{rT`)A~)nM7vRjDtL<GI620W&i1ybfhE#ob9y>!vqPjI4rN*5X)FzO
z{I=Sx%|<YDY;kEcB~>$lz4S6lb-?n0w59VpmRjoD0Z%hNR+`vTIn>)Rp5j}A1|{MU
zjVnnFJ7pfIvrG{O9PH&v<~mpuZaXYnSn0PJ8+#cgAr@n1%SCBxhxj0!i!m^B%K<l<
zzhs>qIpjbmij5=ZXdQ}6cH|nJtT^O)Xt|F09dncncPOJo?c|!;T9f?s@LVOaF40oW
z^mU+wV=W#+ZHJnfYybcJc7*n*w5539Sep`DEx`lFvP;v$F&Cod^Rio!Bi&0-#*x+~
zDPiHKqf{tWw-PW*#mTfT(bh`BrZ$oiJ<I`%5_KpIwG{Op+V@JzDTlHhDe2f=9K#*@
zmP5|cEzcpx1C)A@tBx)B0cmH+PbpTij0b4=&^F;%6Izl3yoY_4z_1iI9OBIMm+`Xn
z9fvxXz(uZ|1JxY99l7ZM*P6a0RU9Cclq?SLEajBUM{7@0$i@_)b5u*dw%qGuT?&25
zR<g&^y^0kRrbC$ufumw_YNosp+AGkq5^7#cD91{uZ0#?#7%Hz>lw&=V8tyAxO?stF
zsuZh9ugD9c4_y(pTFx!TbJ>DkY(A%9=7#YT(}6Z|1xM|?6cAlMgDk!Jk)bAYMg!?i
z33?S|i5O}QHeU{jsTo;84R5lkoS{ZGnzqRCOW{G+7^#7e8oI5J6gB8CY7{PAjxF5w
zmdlsT>b7@@Qs|xyTeyswxnw!aio5-{H^t_5aau=gO2D&VEfhmy<Zs(!rhqNIOe1rQ
zTw7YE4d3G0+f&w{SDalePGO~?H@rY0)TNkNYavtFOl?p2w-p03J~lYEv=&a-e_0{e
z>grgx(rF&V(MBnAxwL$HS=L&~xY}~r-uARs4%))$+gwk3sa6=Y40B!0upHWfnS%A<
zIfqis6t;2HUL$L}GKV|1H*5J8df2eA)yP&8*2;SuG{#b5G42-LIF?aHYjdQ7`I~az
zR)W30#n?MwX^VB_iyc>M+qR{#;a-AbwtiwuYwullI@#Kgt){jXLEEhjlI?A$f|WNG
zYS_wm#L<@0%<bY9DYtY-9?IBJGr!jX2;v+Eoi)}$Ct3ewTwxyL?1tYSIp(08q|7%`
zGm{3>ljQB!$(6l#mvz`LRe6p8qDL-$&zQ+wyrt>U7h_VwlsSH2t4%{bWczyx+F}K>
zstwO(ukIsR2pw2htTsTd%nFuJ8~)R~b$^1Kyy|9t=t>%3=|kleP|`g*bamUjb(gN;
z0S!;%6!bUUfg`8aC@oues|hrW?mwbaIiRF_oiGho80fc#zG*rooy^FOhCcE(U?qjY
z&~F8Tz?G(B=`JZ1&8Kj&hRf;bqonXu^zKy^EmwZ96Ah#J6?qg^HM(9Xmm#N-%gLv3
zXnq%(PC;?k(0nc?8Yjb{u+8|=`k3*g;h5s<PT%G_&~FvRMIq;<X_Rzl6Q*wsttXXR
zfGwc$6edgSWiDUFhnDLi!=te6G?C%Zy2&^xXj-<2{+7ci92HG#rW3_SF56rmN;ic}
zPaFOe2NzmL1x+vW!A2{^eF~HA*fQfK=ap$F^M}$+rlo?yk$EN8o1rk|a$Mx{X<24i
zG@V?IlT2foHZ;GQmPy5MN%?D2`l&VY$q3-baKL)B0Cw!Xb^%=Wy}gch?8@c@=W4fL
zKS2L=mhtZy9qLhW9MJl;s_<FPLoDz1g<%Ib#Q%o_Rt==g>ujIuibC=5gc_ASJXxk*
z=q0mAGK)9boz0?K77z5XiEjDZG2SdbXqZjJ*nZ1t<ZrXcrf<3#!v1Xwv%#_Dq57pc
zjr_JfG7dI4ws>25`E7e-_%b{hwu}qiY+?Rw%PYf?<7|(d#`bN4=ZKFSC&RSCcdVZc
zrkqC3Bd4>$v*9bhZTQIPZS=Fn%W<}HWjMBQTX}N4EsYHi`7Nill`rR!;mTn){A^{~
z^4s#-emlm?`DB{f;^g1*Bg3`9l;1KfZNJrH={RIrEqj8BpPRf?8b}pCSpghLABzIG
z!R!b^S3zTvQZt{_krq{YyiQ+y3ft4r<5vX6X6RF6vP&8MtRgU{TT-?zJ|ZnCRlhue
zZmUVl_zhpTdGz>{;qLQWKyO?0qjvo`PP2-I;6o-MR{N8hw!cpp4kFS_8p72U<J^X#
zKhbN<7GV{dO5RtlDd1r)o`?(u?+NZg9P^5?57ST>P(2%OU^}4`MTJ~Xmr<+^ekBZ1
zDN$G035S8DVkR|G%`m+KPNE)Cobje02}4j_J{gW>m8J>ohk}#GUDR4+rB$lG;6An^
zzAjb2Q%`(>S;<^2_)^%WoK|>A^9gP!-a)m1$Pr}?le2J&MDsrgFDkM~KQbTBLc^sn
zT;22;ze*g2J4+k*4ML>Yi618HR`@b^a4qQ?^L6e!{JY|8zBT-ij6sWx&0Lp=D@AW%
zo;piB$`>kD6%oZT{%?3nJkJ3#*}10}&WvFOaEgMKyf=TJcUND+Kd=dO6X&OQzvG>x
z34ETyRanm}l^D;EmXdnpBh5!hWZD(2<apx51(N5Sd{8>fV{S42*W<W1$b7zlZO4sM
z4wY__79dm{&UBIn;Ts01cohs_-jlX51DV(H2XMSNgOQ5nGX$+<+oOd=Z{fF@?@WyH
zHMRxw7y5-8!5%YYD~!0Qv>)e?-ng0YGc&00ocJ943-&EiO8%TdwFtYy1MDa8cS8av
ziGb{ezcD_>tu>ahC!m0KldEhj%4H{^*|*<@9mQ(cU8s$gitEMc!glfp>IrKZ7O7Vv
zJ(~hEO{1|}(L9$<>>lY73IPL66QzS>D{M^0k&nqx{GJF+3YSa_aU--!{0@J}??Bsx
z1o)!z3#l{f27c!|<{c8gg?}St@P~((l~OgB#myA5os5QGxhB8|Cvvm6n(Q!XiIl*-
zArMVt=?6HG^j8GrU*JS(6TZORl=gAI;<I9qG=*y>tWZa=Mbb;`HtCytD*lG}E?!Ss
z@iTxkbG;}6y&?X}34EHW2Gdn~SNKQ~QP`A+;@4=MaToa<OeQsi&eBf&ThVz}2ENYT
z#%m0PoJujB`H|7Vxk3iKDW&qR>LUg(bsQh7^h0-W2fUu&joXUPDWilY%7(OseULXD
zH$b9LQ~Z^<Dq@SyxXwW%aWWn*e1T@;Ua%%JIo}(PMZL)};bqcY@rm*T+JM)h1wt<z
zPTs*^z$4@;@e&5Zm71II3O^C_S8WopBKGbjeggNE&_^|ZjWlKmXSj~w4fMT9&y5w-
z{06>A`hhQ~`GXX|wI<7*MzM*eG{ao30hokqnxfGFQYgJAb>nsk(=?y6*-!<ZhcDin
z#&wjI<BQxC6hel8NMWA1gzGHqQ3bNw&|dLmei7GB`W818e!@=Da8@CXVUd)OyHpG%
z*M&xC1KA=rA*WCwJCk^Gp(3l9K>Di=-hGoBL|X9q%0luDoW(@(EeVFB;98O|)`XMs
zFVdW%!yuQtK)QjB9t-hl&`4~rNXAFS4eWHXhTSJlDkub}*q89mg0tiT`H=6WG-$F|
zUWymqP`po~a1Ik~8jeE2I=C2ZlVU}{e8J2&P3AYj)2tHJFT4!8n%;I_$c97A91||E
zJBz%<UqM&#nlw<V#oZ<ENSl>o#2Bu&<VB`(Ix-g&iZ7!#$s&*|#YrzwQ+hP1FF@kK
zOvm%!F4G8MmN*?H!L#gz`>n;@u%*<<bc-y6I<ghyI3sk4%oRteYoSGjO~eH34t_UW
zA!nqYxy0%jIEo~a8JcytNJNIC<auTmPRk$8_vGK?U*TV@^AWyAw)1I<UFavWR;d(r
zG81qd9Eq2K6?db=W>7;4nYY9c{JL}(e@52AhIki<kUnKvkRr67sSXeE%TYEw3T8>k
za3k}+RDfTEzHl_$NOl!Al}7VZd1qA<X`wVyYAwACoCIfSE-n!7Fbg2Y@Lsm)L3Ro2
zQFXD9Ss_j$^+^cY#e6~f8cqpYq~~dS@)dsJ){)uZq}z3AHosEjG}{>!yAZhIpRTUs
z8^JZ8y6IbfFaH<sgj%}Sg;)94d4Kf*W{woi&d8rER3|!qm8KKggRU44i6=-KG@Hrc
znusheV7J0~1vzlJG@9fx4ZvJB29L^5;|h5K4scg$Hej0riJzwW3hWW*f{}_u%9Rbq
z6Jih66L%_@DxM@SONC67+KrtoA>j*Ub<&s#mRf@i>_j{#|1=bpo!N=fdETUKF0$e<
zbXXVx)A1-F1qX;rpbr?w{)uN6_GQ)^?l`%#$ECW05Bocu23Mnx$!YF{@&myW#IgB!
zNI^6F65++e(o)sm%zQ9SScrZAKS>|4HKh|^4_(&lCGKKejYTL19bpZSH|RM}k^oP+
z&1Z+g8O&Z`3rQ7{SuJk@_oNqLIyoYZ<vIy{oK~~Xp$25Ea^9_K{7ou1L^6#J<GU-4
z2~)*Yyn*kbXvS<X^<mx<8VYqqUtAAf6I}RVs+NXE;>W_fL_`NUSCt#tD|8?u(Ng>-
z+J`lYmDd%dDQO{21oP3$Tu-JQ$t3I1a{PV)OX83cM3HNJBSFuEsUpxlwmDK5X7c0s
zS?o@i9QGrq6xX9Y>;%&^d<>pveet2f_u&rX3$DYctZzd+P*cS%@n`7E?!rG7b>!<R
z-p0Q%5{MUSB2EaxgK5i+LoU)OX%y5jYw!Zj6Sm`zGN0nJa5Tv$jY(&ImtuxttD-yK
zkRZ=c^a;!b-O*y^jPN!;8g6I(uvGXe7cI@?W{?>u#rRg8UhE+}Ts%yECL`d7jK49+
z+4H~^{w)$t0<bfq#Z$y!e2yGM<H=CelB|K>iS?P@d5zphf+S)z-jF8mU;0XReHT(&
zAst>Nwkzyk<IQvX0-}~T6Q6%Mw@1+5Lx1$UJge1bHJ3=4rrfI|n(R}9yYXM&b7|t)
zr?3go+*){drz+cJSIsr*dg^g4-^88pbU}UmQ_~ms3vk<`#{!1B{as!A<DWOL)VS+3
z_}(tzIjy(q6r8ZTe)VNuw~y~v%xhFwq&dq4-DVT;>T3b`d9?OwMxL{{=J+a?72GQ>
z!Pgt311Xz#%zR#3GyI^cp!M%N;NT|Vt!iEC)?)Sf0l$BA>bL9$qNDxZsx^*yQd(oH
z!iv@!&x0@7`S_>8!;m8rl#S>#%0-+5gIFW3DJaAwIGSkz2|tUkFYc2D2|J{=;xzX$
zOf#u~zoEDan@Bm*V$>haAztVuez!2rsel=UcL*>qmA^sFgXfH&h+XlbqL0}V+**zY
z-*J<{2-kKXj{lMWTm|?9@G4x(*Jn;qDdi6%S(R{)9U;^={KDT6&I%c53hB+ZFpN|6
zCkAAMV+(@_!x!*7g}z`1f1=Ju=%{d6yr7OnujdyCS!h4>GaM26k&BYI2;FYL!|dDe
z?ZRH<ZOKb2beqPmL_5%C@*aB|MByFqO?(bCzn6dnf^cmSz<Ov1xB}t}=a6``2H+w!
z@d5`>3>RB%B+{|d;7-#46eR3nH=_>h-_m3pBlU+q_?9%fpszGlnu<J31mtlkM1_ws
zU8I*;ANWq;Dd`q>#Jw8p#SCS#Oml>bu({~UjK>nbi~aaTSQJ0xPcgq?6}gYzmAZ&y
zKr_XBW`NX}t#S803Bm$-i~LHui!)qOaBJ8L%*G?7Em9il#Mgke_$ZL1_>ql<0qE^p
zE_{kKl4|T<ilaHB6eG42UqZ88mP^lJjd%&nkouspAPY%AkH03HBzM(X^evdc%(?pq
zd4XRgxiQm>FY$kXOmdzK;MXI6jTekTZ;3@{6q=6u2`_<om3u+5kj;CO{_J1EH@pJI
z3B#ZpBcUv~22W?=xn|5SNQ2c({rl;-C0HPA21B`-(s}2h;4A4KT8E|>BBU`Oo~#lg
zq<o3dYzE%aFmwZ71W_!9TL>HO?WbH=AkN~BiYKHQoYu_=7Lv)Zui|ZVo_!A;X75~D
zLB0orBn{_Ee7RbpN?Klh1@I@`QLy556eo5-aa_GZ4?<fdd7aToMa=u)nz1u?L^uR{
zxnE|MNe9I?iXLDe^J>v@iD#Vfm-!!ZR|q2+_<T_y-_#0bY7#ecMm+|{Gvm3fs!Xmg
z(-JSaJ&+i2HmX^8i+=~6F<o(=i!Td4<c7Kk55~{48&S~BZbZunNL%=sP#^d@L8hMQ
zEO@H7;arnROk_ozng1fbShU3D408c3XJU&C{9EX66ofti3($9_@sgU~qj&)jV&o^#
z+16rGME*qX>}KQdsu5ySFdx?iL#0X5>!zQj_R=V_TCBrIu*cXj5|TRL!)Oq!#rlJp
zxb?03Vk>F9IG5bxdMIueL|4m}#*mtH+;|UtC!S#z-AN|rg#+S#Fo}=g8?)aS8nOfV
zI?g`}vHXwVgkqV{0xuBi@HK?D_~(Hi{K(XeU%|Z35UCy8A8f{5sYQRIR1?h;zr=Hx
zFHNJR^YB+D4gYMc4x52p{4$lF7{y$r#`$jWTmDxrS+TM(OL3ShbngMLNN3<Gd=!ji
zO`t#Oad)%i2I`@|j7Pcd{HK87Yq{^07IRwS?!1y+hS!UC*gn@FzXrrH%T4Z#o^K*P
z2M*#dkz2tT2@AivOv209PWW;`l5~x%#s$i*>@B#BIass-9*|xZ)`Err3){IIz6*Gf
zf4kO;U<$ukbaD#D=|azf?|4a?hh8vt662X3(s1rg;WPDztQSsZ4QTvzj_WUW!(WQ=
zV6Jf~Y0tkV>``17Cg3RU4YZSAD!8I|@HqY@)%yF9!YRIy^a`4a&k3L7Cge}<0AH8y
zC~jqa89ly1rn0|@OHDth7Mr4&{r6h(Kl72oUkbn<5jUe1rZ&oC!5@FayqmuTeuB55
z7jP%$*Mj5dU7<Vwo#GSx8PcFgc*a;mbjCM|UUu0HkKjMVCB_Wcf*%4K^SRZp;5MM0
zbf5J{k&2$;Zv`=+7CR0eE7%EqxdHA5=7zLV+{O-7Of_@_Ke35;QNas9N8W^8c#~@k
zdlSqQ@^Gf~JRO-gVLvz@CbQq-vxdodCxFZ-*4-#DwV1nNH^GApmZsrUbrf?34uzfJ
zb#Xtl&U8as3qKXo#I2eu_yxA9^aI&`@0#!;okd4VuXD{r1^!%^E4F8P;*G*Q;|{Wz
z^A<y;{hT*l4BEl@VuAU(V5O8SEf9RAQM85SO8unSc%4)zHivha9?W;ba>HG*tpva=
zejfjdiW94m7r+5!Z{`Okj^CsVB14PZG)YB-`SFe?ogrKS!;K%3ci<>xXYdC<T2g4L
zGj-U}I0SbEM^M1|)4U)~0HgVjh@MvpX-u&CXVIH$Nj}pYV0Y0u*Rs0<xy!_ewv&5k
zkoc!Dk^ButNPn8nh#jdOOef251JxUO!<*<;+>gJi&Sa0!UNF}bgX^P_V!H6QxLEjB
z{6lI{Eem-{SHK;13;snsuh@R4sdO9%gF%Mt{4#vP;N-OeUm&lF;p!c@7D`2D@1=@K
z%ntNn{v6Pfcde#idJ-iG*3`xeSWzg<A4>kfwV787+=Q-BRMo+I`359X<7O=4H*uef
z+myAL<-iDQ=H_w-q_cc8@P|O)G?S}a5YWN>_@F3)Tf$J`5UpV&azZeG(d3}hC^$|`
zfraens9U~P9E%o8CyG8J@t}>%hv;Q-m*lE~$O|`S>*1;5U+ha)u($+ofnJ3>$y)J}
z%e(kfGFtF*`X1F3lNF3GPH~gYAJ*jR#b)ev+&=#bn!qi0I?XhP!-S@266giSOT+mO
zc^$J}^9RfpKLpnmFQP513JyhQZ+-mo7Bop1#p|f9{56>?Tp(kF1@w7`WD6__d|?T{
zFn=<<nLnd&G}>E~U3VSsCBA(X!f`bgf!TNGiJ{P&o)9oXj8Y9b|IL|Ub#~q9hZ>`%
z{WVKhv(2TRXt<MK&3lSw-jj~I-rlW|USM4sO9vENM3vL~m)8|tb0Z+|PAjI6%yOA?
zCu?uaos+7TPzz)F!E<gcT`qlgHdr}CHSo`2A>X6Gua_5vS0Ao9$kcLMA{{w@gx#ec
z;G%zJ*Zq;z))_zlS#27Z_p|$_HJcYTc`l-e`|h>YuhgD>*I!I1$b9}*(H-{GFTwn^
zS5j}kUr>hDIPytpjcc7MT5FsmB!Wipg7Ga7%l4Bz!CB5rnyfh`eJc(`o_T+9ZGflH
z8uSo5<9$U_6nl$;@!KxTz(?YXFptjs8y3zNFL8SE8g%1#3E6NH%11YdM!E3bdf^}q
zU#~d90NhOamZ5WJZjMUFbzuhGsmp&WR6`x9wyCAEubOa%UnuMlS4dNZT4WH_M=xu>
z7Q6Ci$VW~!Q7{?;+uzgizf-+x8W_M&g+IaL!k=6d<x$fx@;v_swJ+((5^)qioP3Lh
z!JkoYp*8=bVrHR|j0I;zClt)z<`*#sn3>>DZiy72N`w9Jx7<d>2JS6<;a(B<4cS4~
zfCO&7a0yN|?W(30KSFwP6pR!8h6nKh(@&aHXcnm}d8w!4NmSSDe0MYXfO}WWQ-rWi
zQZ`d~cRS|;3Mn0DaP>)JR9))pvJ$)xdx!&>R5)E2jrPEe{Ldg!b%~kCXvm#gllXm7
z5LrqsXm$8|Vxi`uBr(x<wfr{Fk@>jj0L-Ja@OF|KtU#f7LO}=6hxn<xpk3@;)TRLO
zoT&xhv-W7VDIUVKGEEm>0$KPxS%$n|2$N8B7IYH*`Jwy>DMR?#H5>fM_+uvDAi44%
zfoOhU^(dx2`!&`X-1%SBrwWEU|48@neZovPCP*3ZO>_j;E$j(jBd^ju2*cTT@WA}L
zbnfrZjATw3n&Pea0344;8D~R1^kBn~f5Dg15~o8<Iz9q)bbLm1L>M96aPN+{u}5GH
z<9_J_;a%rX**0jP_#<e;grNKEZ1lWg7+O&jRc$%7gVqqgr`Dv?aJnehr3Si4b@FUO
zV=ht^Z#Y)%WA+s~qaBVmi6=mcI1U7}li^X;AMY(Z0eeXybS(XoO=n*QyTo+#E#Ad0
zL_MV~1r5L$swcA4N}D5`=1(cd!k?v2U@yK08AK+ci+C7X&W$d*DbyfY;+w!1RYQLg
zcjgpvLL;fw`7=R)XBeaKI+=vN;ijWlGLZO5ae}wdhOEJ7aFWrD{ekF&2w^L{z~}@m
z?oT@7Mg?_*#X^6w6Ai|!DGRqJ-%w5Hd*v5oW#LEsIWhr0FMLe&%z0r6*(y|*J_Q4)
zCX&ss6#7w{)M9F>-ili|C8B980pW%h(Qf`e{1W^rdgH&;+3Xbfo;U%!NV^pGxIf`F
zYJHnq*b8nqdbqD<o!~;T7VTlv&_$-LaT$D`+RwJrcH##mV+UrM^LW+={75b{7r-w3
ztMMb$2Z+qiXk7jm(n6HZb!LZhU!XjvbS9R>3We(4%uU9dnFPacZy`K*i}_nfC0k%N
zp9H)l6{sx%X$3c1^l@@y=Q5q8_H3Oi`?!NrA8axOQceD#*Z?e4b(OT-P60X}qhoFy
z)z{;Mo8S;XkGmi~j|+t_xYwy|84J1i4SFQcLNZSCjyMW>!W-h0!hT#Y@g~|RbO*D^
z2uUGDO6SS9;tRlC=p_8j^n%;OnS4`l3@rt_(f5V3c{ivRhLcEe1NSs$x-Np(q`n|p
z`db>n{*HwFG2B$a6TLuAawC*YJ*u-?L6)!+<q6|?#-%0P&o;-?3)kacnf`2lXr!7T
zFP&x-bd$>m{0i8UZHw0xo#PZv#~CkpS9lL@Vs<hOxES;$Y|Tu<b2vTR$jMfd7Vs*a
zOMff6h)tz|;y9OqFp_kFLzJ~~3Vux(%l=AoP3si5O}+7Z_p*tKSqI)W1d!G|7FsfA
zJf5Q~BcallTpBs-jG6i(i(X(dsg~YI3SvLIj>RnQ3*w|oX5;W=qRLywMM_C<Cm+Tw
z6T-M_!sj0M#22~Kg1ggJvWc!#%+CFQ>mUW;YkVB1P`C>}i2JFf&qw{SI9dqfhl&w6
zO&Y=XhA%O@j59<h(MvcXOr*7GP7?TN<zRk+@EgfRLy&>I%nTB1fJ+6#h(EO*wm01)
z^|*e@7V7S>8Q%$9QpB?w^dTI7i{ZcE>wu293A0Li7d*$N-J8WP2A|=L#=BB=I(O|X
zZiPRJV+E)f3A;<#=rSav2Rg}4Lq8gN^LxP8&NJAxXqs3g9cJ4iiEV&u6f8l1unDME
zQFYYZIMVe~b_UE5>d_umOC`V;SUpmhcHqxMjWV1JU}~dg*aJ<1x%Y1|Jy9)kRmvpM
zQnJ%_)PUKC=JRgCeDu2TtAN1)p_9u6+yh0BI^udhh3rAu#%?t>B7<}e{zi9WonhUK
zubRG=j?!6TwfyT+An#u79cBl3o7u;G10zUVWt`z9&=LyLZ>Bu1J$RpA=oSDo_#I$A
z7f;us7Ngmw?O+KXEYxNO;>DsLw_SKnGevBSPZFK+6K=AYgHDl-u#w4Kk;mO9-*Bgd
zOl6F)1x@2az%kB^_Vu~q1=t4q8s?FAc^@)`9m_}*+ZTn)e7f)*45YK%-9}%!^0rB6
zN-m+%bX*t)KV_;F%n><MWb{;bgEPT>u$AkI{J~gwhR)Gvv#+8z3+llBrpE43?CanF
zd6nJ7{$Y9voC8Z)s=F4}K-JhLY!XzFIWU1zH4PkqpRrAF&7wYh2$hAK$WnfhVxUV?
zHUlM+Z=`s3c2T}&9nIAcwdYpS8PXVOnUF-+2fT&#HF#zb>PvdkS?^3`UpxgF#pO~5
z<=gNStEX1%D9Ifa+%bv4%yedt!HbWUvc*m4l(NP8F~W1gSk#kw2eoC^VK3%2+>0qd
zY0OV(5i{z%ukjjAby2w;VP9Yxy7|?-b2y-AO;GfaFlB>FpVWY?@5R(JVSOj<jcCyO
z#plnt-ub{k>GLF)2u0ZaA71U;qWQXC3UWX_!LLw%N8Nc}_0;MIv5~EL`S*KQ7?aoQ
z{Lst4pFGuAFbv0UDCT$irQo^y`KSMWb?c>W9@lD*{VibL-g+H1?<>ZNooZa)kgIR5
zyyTIqd6zS-IHE2<Zyp#?`(FO(tFtwm6(@a;o}2VScMVi+44M4HjaN4DDJKUnfXW%Z
zJ)iG?<<G`HUafKQM9eDnU#8W0CdpB2T$L6Vmz5ry8uyQ_jqx_Mu&fnodn7%LR=&@1
zRb*Cbm@aj&KGBv7%OAayM(Q55BbM%kFz<+U#C9#+50RNhkDQ6o4a-Uz{-jjhtU-3{
z9^nLDu4VO60Qm7eia0P(?FXi^c~`>o+vU1(3+nA>ry91_>~!#EQGj$nT);&$J%o1Z
zWA`F7VcZ(r5o8EMq|?reoV~7`!vo-Lt^iI!jp%UwfkNZCm|d&5dME;n#9>l2rvctD
zoODq%6<eEDqUF*=({z`SDsSN}ac^~_bI1BKoOU?-eK|&ID5;Q}nxlJ6FRHm~8PxQ4
z9ed%cAVKs|dEAYrf@>}GG7WHB&+Jh8y9G$Ijm^&0mJ)CxJScqNbos82Y7q%Fg{gbg
z5L_BcJ5ak5&SI4Cl}I6|&f&1!=~$)gM^C&P&#DmPG}eezV~t~#M-AiI&xNgcqqBF7
z+-eu;((<|QcPOJJqmij$@D|+wzu41x?Q$+msx6&H{)+ADY(CjI1NLM48CEI}nCfe;
zn`Ssgs-}y1Vh^{ewF=xudMI2IzZxYBLPNNYY&&&g(LS<3YQZ#S$L1$OU($~i6=Ts>
zoWt(9bCi!~{e|JisJv{=;%WivM#xQyU`IJMfLGahH#?xULMk|?ic;k8-bHnhv#27=
zt9#cDsMn=v$2VVz$0<D`u`@MjoHMO-4pV#K`uuFvO*7KP&oi9(u>s81BlA>O`IAhd
zQ-d1O)i0_8TmzPc8yUe%+@t8EYFgl}IGH<z3@7JI!<Eir2jzayjZ)~4I)|?<odsi2
zL*_JDQ!r2(BW)yMQeAS09=+8Kq?^1{P3gAVn~K|9y2q$HIc&V4HBP7-RA)eqW2!~S
zZT>{lW(uic;W1nn%|>0|R?|*!3=bm5g>4EqsS#O(E=tqI&1^HFi?GsbV7)GGJCutx
zTUSj5qoA8KPc%rArM=t^=cS7M;H0!bz25s=O@lhfdGVGE*gz2}PBbktI;;9P?<qR2
zIIBr467WTD^zs@H56a<y`GZh6X(-L&_Jb%v&&_t<uD-0~*sFhZ11m`~Ip*T4M5bA}
z3%IvLXE7bOpd1ZQPd6=6w%{5T2B92&uXI%t1rwc|P0Q7@jT@C;;nSDfQNzh>rGncD
zPBN}VBCJjO!(9HLQ*GBZHFIlyr5dshi5$oQ+odQ)_U)6d#}%H`7~|^J@TKVe$A!iq
zQD`a_IQbgKvwcVdAz67{YyiDfGxBQFCDyl0Yn=u%BTaWn3x>j%_Bai3ovj?CRH#SN
z3BZ8g2ccQgN~L$+Qo6+hVK*t6)TbryXFWk9<7G6698}Cxh1DD4JzI%XZ!Ha{M%Tp#
zA5|YB-~xq)kJmKh01#aES39iiK-Qee6Su*Aq#vJ$8yV-}+R{dP2;^3}8M-lVM9W=s
z>-VeH&XxAQbsq?BOasLSc?sBqB#2E#1LMaY7joG>Fp-*ovpofu&*5pM-(Lpie9>NM
zySJ~;Z1>|%2C3ijP;n576dM~3!n>5-dthxTm(Qt{pqfY*&hL5!G)S#BggNdscfnd1
zAZ?eth4V#QO<pczjf*wiz%kJuZq{sfZOry#mnzO3jKmv>f!m>sXKzzeY%<bw&iHeM
zpgN$6xaNb6XsdV{HGqH*civlg8lA)I)rSfjvkJ}=^l+>1*|^$tk9bz4+*Xvn35kvQ
z9&jF>s@cqY=jHIJpn%FS1C=r=^to_QXykRfo}v0ux8154^RDs@DS!Kc^~_drk-5y>
zDnYfEQ7PM<T_ELx*5XJpoOn}pd8P8CG(*T{Yr9_c3|DsaWWMXjC&J-MpMrR>S4u`>
zIB%!=d<SJC(vaC_>_@4mU=AZc&to3voixDzr;%b~(2+l+Ua6dTZ#ME5w!tU`^4RAb
z&+bv2+`m?FT)0@+scvhRKF;m9(|>xyJTVH+1r3~<s`6BB%$`%j(E&V=o2{JZa?{O?
zS$?(~Xhav4nkgD72Qq{14kBHg!ckP-MAZVRTeTiSE-%zQ-7>XSJJ(Qg_Q#7sZ8bHu
z<_>@vw6%ID4eIskj@0~nzQ)OVZr<HAIhyUuHYyoKBXp7*PJ3|&Q~>9>c)84gzBhu1
zs6eLBMpK;M5~C$Am&5MqE}J#c(%fH0imk=j1OPXQ$vYrM)K_>Xc=T}UA`DsPou|rg
zC*>H9nK(l~zK{C?+S4~1rc?YIkudK*b;f({P=;}1H+q?T=*Fi4g+XFqydg|lFX_1m
zc4xJl*JnxR#2}Ok!ldm?eJU%4lXTop@<4iNU|}{ti}4pOQ#{uS5p=pAr8o#bFIqt5
z?hs}MoQ)Qt^|`5<iDDyTUd{F1=^oLAn>7WyRnDHk69_7mbAoC-?|o?{ZBZTRNa&~W
zAa2|Yr$NR7c(=~}`g^MnH%?!>SGr7`@hHW3jmr5vTzT4qOa<quzu!DlfV2lTrLuUr
zYYQ&YWAE81q#L_eN*C!!$R1_uU-JVOnE}ES)Pe~$ahi!#rcP!i(^`e2Ly{k;&j&Cx
zFV_s7SB#+}W;gaQ*+&37S-r-io5A@?Lv;|^LDn-DX?t8rI-o|%uS7qvlU?NOLl;My
z3GvlN;sEB{y?*SF!n(?pUQxCEozAJ2FUv91=MNiiI!y)r*wov5xz^%IYKC1SCc-Jk
zqx@7b54YovYPu8+CmbDT-@*s*Vi3T^vp!Td3;3#Mfx_qB9!R$o6}7&(o%YqQ=oXV^
ziWbzUQ6QW~3%Eolq{?RZgWKvEv~F`fBmNK-!>Mfd7xpq?bXu=rTjQ<N_?oO-sib3v
zM*~GN*?+N%({5@&I;lSRTsKdy+5lnu{vDSSVG!DF@)EmHnc-n<Af%fnGn*-g58$Ov
zgOFHsoGzMrg5l0gQR7+z?zHnBb*G&=2ZUa^?%SgkxB7ZbFAw7n+g=}V-H?0swuf_z
z+@-B@{@VZ7emYI^Zm~@fy<d7SSpC+Xrj4?%ZRQs_PrNem<#Xq6HwfP`vB1M8_jZTS
z>uU>y)Fl1ftu@y=o$J0{I=di<ob|hTX>Wt|FD3td`EWpRz&gDYz+9B}xgUS^+@7^X
zYwzst&{VAJGsqag?7Oie|LUQs_YXA~|HftKx_|oCX^=PT*1p5Jnko4Qa2`HXeb1eY
zJMF56UmhOV<N92U*S#Fiyqk{dqvbg9gtUy5*kpQ5LTttpxe>8yTW!T}t3}qyfMs{|
zn%FFTqU8{&r+wGg<9T;D?7XdVs(0`jwEG=ZIn@`tGTU6XsbhldbT7L)+Z}<coa!yd
z7uejZt#6w%x0z06hkd&nH>INN)Ngkbw^G;9ncSA!>}D;el^v=tmv~n>)msjEsB)^e
zj5$>v+5e{dld3$jsmolIM|O(NsxVeghipfivRyHH^obai>3Y~=U%K5E4Sls)Lx1-E
zzjk{5hMJb?|7$ts`X|)X?h7jWlgfUevj3>;A8PYowfTzL;%z>xHvd!GZ#kXKPgVZ5
z`L4>}vM;G499tgQ7gdgv-?m4_!3M_`Z%Z$~ZI2C4o1bcx|F4C{Pu%~vGd=1d9>Nkw
z?N}@dvY&2BTD&efCN(Ri)S}cgKYg~m;-%tmGAUce6`!akU)W>4@Yr_b&Tjiz(sUwj
z5jESWd$i@oJUu-CSeG$eml2bxkJa0+39#Nqy)Gp!Sr?a;tfM!>(<?ILpDv#&=#KCu
zBXpS+ekR1D`D8uCsMHNhIZ0_T`lJ+HOiE^HVwWk4tx)J-0)V60?(6i}j7;5=0cF0+
Y^@)I5YqlfOyAaAz!|L`L+S@byKQ|;e=l}o!

literal 0
HcmV?d00001

diff --git a/src/reader/cif/binary/decoder.ts b/src/reader/cif/binary/decoder.ts
new file mode 100644
index 000000000..f6c587f37
--- /dev/null
+++ b/src/reader/cif/binary/decoder.ts
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * From CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Encoding, EncodedData } from './encoding'
+
+/**
+ * Fixed point, delta, RLE, integer packing adopted from https://github.com/rcsb/mmtf-javascript/
+ * by Alexander Rose <alexander.rose@weirdbyte.de>, MIT License, Copyright (c) 2016
+ */
+
+export default function decode(data: EncodedData): any[] {
+    let current: any = data.data;
+    for (let i = data.encoding.length - 1; i >= 0; i--) {
+        current = decodeStep(current, data.encoding[i]);
+    }
+    return current as any[];
+}
+
+function decodeStep(data: any, encoding: Encoding): any {
+    switch (encoding.kind) {
+        case 'ByteArray': {
+            switch (encoding.type) {
+                case Encoding.IntDataType.Uint8: return data;
+                case Encoding.IntDataType.Int8: return int8(data);
+                case Encoding.IntDataType.Int16: return int16(data);
+                case Encoding.IntDataType.Uint16: return uint16(data);
+                case Encoding.IntDataType.Int32: return int32(data);
+                case Encoding.IntDataType.Uint32: return uint32(data);
+                case Encoding.FloatDataType.Float32: return float32(data);
+                case Encoding.FloatDataType.Float64: return float64(data);
+                default: throw new Error('Unsupported ByteArray type.')
+            }
+        }
+        case 'FixedPoint': return fixedPoint(data, encoding);
+        case 'IntervalQuantization': return intervalQuantization(data, encoding);
+        case 'RunLength': return runLength(data, encoding);
+        case 'Delta': return delta(data, encoding);
+        case 'IntegerPacking': return integerPacking(data, encoding);
+        case 'StringArray': return stringArray(data, encoding);
+    }
+}
+
+function getIntArray(type: Encoding.IntDataType, size: number) {
+    switch (type) {
+        case Encoding.IntDataType.Int8: return new Int8Array(size);
+        case Encoding.IntDataType.Int16: return new Int16Array(size);
+        case Encoding.IntDataType.Int32: return new Int32Array(size);
+        case Encoding.IntDataType.Uint8: return new Uint8Array(size);
+        case Encoding.IntDataType.Uint16: return new Uint16Array(size);
+        case Encoding.IntDataType.Uint32: return new Uint32Array(size);
+        default: throw new Error('Unsupported integer data type.');
+    }
+}
+
+function getFloatArray(type: Encoding.FloatDataType, size: number) {
+    switch (type) {
+        case Encoding.FloatDataType.Float32: return new Float32Array(size);
+        case Encoding.FloatDataType.Float64: return new Float64Array(size);
+        default: throw new Error('Unsupported floating data type.');
+    }
+}
+
+/* http://stackoverflow.com/questions/7869752/javascript-typed-arrays-and-endianness */
+const isLittleEndian = (function () {
+    const arrayBuffer = new ArrayBuffer(2);
+    const uint8Array = new Uint8Array(arrayBuffer);
+    const uint16array = new Uint16Array(arrayBuffer);
+    uint8Array[0] = 0xAA;
+    uint8Array[1] = 0xBB;
+    if (uint16array[0] === 0xBBAA) return true;
+    return false;
+})();
+
+function int8(data: Uint8Array) { return new Int8Array(data.buffer, data.byteOffset); }
+
+function flipByteOrder(data: Uint8Array, bytes: number) {
+    let buffer = new ArrayBuffer(data.length);
+    let ret = new Uint8Array(buffer);
+    for (let i = 0, n = data.length; i < n; i += bytes) {
+        for (let j = 0; j < bytes; j++) {
+            ret[i + bytes - j - 1] = data[i + j];
+        }
+    }
+    return buffer;
+}
+
+function view<T>(data: Uint8Array, byteSize: number, c: new (buffer: ArrayBuffer) => T) {
+    if (isLittleEndian) return new c(data.buffer);
+    return new c(flipByteOrder(data, byteSize));
+}
+
+function int16(data: Uint8Array) { return view(data, 2, Int16Array); }
+function uint16(data: Uint8Array) { return view(data, 2, Uint16Array); }
+function int32(data: Uint8Array) { return view(data, 4, Int32Array); }
+function uint32(data: Uint8Array) { return view(data, 4, Uint32Array); }
+function float32(data: Uint8Array) { return view(data, 4, Float32Array); }
+function float64(data: Uint8Array) { return view(data, 8, Float64Array); }
+
+function fixedPoint(data: Int32Array, encoding: Encoding.FixedPoint) {
+    let n = data.length;
+    let output = getFloatArray(encoding.srcType, n);
+    let f = 1 / encoding.factor;
+    for (let i = 0; i < n; i++) {
+        output[i] = f * data[i];
+    }
+    return output;
+}
+
+function intervalQuantization(data: Int32Array, encoding: Encoding.IntervalQuantization) {
+    let n = data.length;
+    let output = getFloatArray(encoding.srcType, n);
+    let delta = (encoding.max - encoding.min) / (encoding.numSteps - 1)
+    let min = encoding.min;
+    for (let i = 0; i < n; i++) {
+        output[i] = min + delta * data[i];
+    }
+    return output;
+}
+
+function runLength(data: Int32Array, encoding: Encoding.RunLength) {
+    let output = getIntArray(encoding.srcType, encoding.srcSize);
+    let dataOffset = 0;
+    for (let i = 0, il = data.length; i < il; i += 2) {
+        let value = data[i];  // value to be repeated
+        let length = data[i + 1];  // number of repeats
+        for (let j = 0; j < length; ++j) {
+            output[dataOffset++] = value;
+        }
+    }
+    return output;
+}
+
+function delta(data: (Int8Array | Int16Array | Int32Array), encoding: Encoding.Delta) {
+    let n = data.length;
+    let output = getIntArray(encoding.srcType, n);
+    if (!n) return output;
+    output[0] = data[0] + (encoding.origin | 0);
+    for (let i = 1; i < n; ++i) {
+        output[i] = data[i] + output[i - 1];
+    }
+    return output;
+}
+
+function integerPackingSigned(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) {
+    let upperLimit = encoding.byteCount === 1 ? 0x7F : 0x7FFF;
+    let lowerLimit = -upperLimit - 1;
+    let n = data.length;
+    let output = new Int32Array(encoding.srcSize);
+    let i = 0;
+    let j = 0;
+    while (i < n) {
+        let value = 0, t = data[i];
+        while (t === upperLimit || t === lowerLimit) {
+            value += t;
+            i++;
+            t = data[i];
+        }
+        value += t;
+        output[j] = value;
+        i++;
+        j++;
+    }
+    return output;
+}
+
+function integerPackingUnsigned(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) {
+    let upperLimit = encoding.byteCount === 1 ? 0xFF : 0xFFFF;
+    let n = data.length;
+    let output = new Int32Array(encoding.srcSize);
+    let i = 0;
+    let j = 0;
+    while (i < n) {
+        let value = 0, t = data[i];
+        while (t === upperLimit) {
+            value += t;
+            i++;
+            t = data[i];
+        }
+        value += t;
+        output[j] = value;
+        i++;
+        j++;
+    }
+    return output;
+}
+
+function integerPacking(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) {
+    return encoding.isUnsigned ? integerPackingUnsigned(data, encoding) : integerPackingSigned(data, encoding);
+}
+
+function stringArray(data: Uint8Array, encoding: Encoding.StringArray) {
+    let str = encoding.stringData;
+    let offsets = decode({ encoding: encoding.offsetEncoding, data: encoding.offsets });
+    let indices = decode({ encoding: encoding.dataEncoding, data });
+    let cache: any = Object.create(null);
+    let result = new Array(indices.length);
+    let offset = 0;
+    for (let i of indices) {
+        if (i < 0) {
+            result[offset++] = null;
+            continue;
+        }
+        let v = cache[i];
+        if (v === void 0) {
+            v = str.substring(offsets[i], offsets[i + 1]);
+            cache[i] = v;
+        }
+        result[offset++] = v;
+    }
+    return result;
+}
\ No newline at end of file
diff --git a/src/reader/cif/binary/encoding.ts b/src/reader/cif/binary/encoding.ts
new file mode 100644
index 000000000..c9369e964
--- /dev/null
+++ b/src/reader/cif/binary/encoding.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * From CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export const VERSION = '0.3.0';
+
+export type Encoding =
+    | Encoding.ByteArray
+    | Encoding.FixedPoint
+    | Encoding.RunLength
+    | Encoding.Delta
+    | Encoding.IntervalQuantization
+    | Encoding.IntegerPacking
+    | Encoding.StringArray;
+
+export interface EncodedFile {
+    version: string,
+    encoder: string,
+    dataBlocks: EncodedDataBlock[]
+}
+
+export interface EncodedDataBlock {
+    header: string,
+    categories: EncodedCategory[],
+}
+
+export interface EncodedCategory {
+    name: string,
+    rowCount: number,
+    columns: EncodedColumn[],
+}
+
+export interface EncodedColumn {
+    name: string,
+    data: EncodedData,
+
+    /**
+     * The mask represents the presence or absent of particular "CIF value".
+     * If the mask is not set, every value is present.
+     *
+     * 0 = Value is present
+     * 1 = . = value not specified
+     * 2 = ? = value unknown
+     */
+    mask?: EncodedData
+}
+
+export interface EncodedData {
+    encoding: Encoding[],
+    data: Uint8Array
+}
+
+export namespace Encoding {
+
+    export const enum IntDataType {
+        Int8 = 1,
+        Int16 = 2,
+        Int32 = 3,
+        Uint8 = 4,
+        Uint16 = 5,
+        Uint32 = 6,
+    }
+
+    export const enum FloatDataType {
+        Float32 = 32,
+        Float64 = 33
+    }
+
+    export type DataType = IntDataType | FloatDataType
+
+    export type IntArray = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array
+    export type FloatArray = Float32Array | Float64Array
+
+    export function getDataType(data: IntArray | FloatArray): DataType {
+        let srcType: DataType;
+        if (data instanceof Int8Array) srcType = Encoding.IntDataType.Int8;
+        else if (data instanceof Int16Array) srcType = Encoding.IntDataType.Int16;
+        else if (data instanceof Int32Array) srcType = Encoding.IntDataType.Int32;
+        else if (data instanceof Uint8Array) srcType = Encoding.IntDataType.Uint8;
+        else if (data instanceof Uint16Array) srcType = Encoding.IntDataType.Uint16;
+        else if (data instanceof Uint32Array) srcType = Encoding.IntDataType.Uint32;
+        else if (data instanceof Float32Array) srcType = Encoding.FloatDataType.Float32;
+        else if (data instanceof Float64Array) srcType = Encoding.FloatDataType.Float64;
+        else throw new Error('Unsupported integer data type.');
+        return srcType;
+    }
+
+    export function isSignedIntegerDataType(data: IntArray) {
+        return data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array;
+    }
+
+    // type[] -> Uint8[]
+    export interface ByteArray {
+        kind: 'ByteArray',
+        type: DataType
+    }
+
+    // (Float32 | Float64)[] -> Int32[]
+    export interface FixedPoint {
+        kind: 'FixedPoint',
+        factor: number,
+        srcType: FloatDataType
+    }
+
+    // (Float32|Float64)[] -> Int32
+    export interface IntervalQuantization {
+        kind: 'IntervalQuantization',
+        min: number,
+        max: number,
+        numSteps: number,
+        srcType: FloatDataType
+    }
+
+    // (Uint8 | Int8 | Int16 | Int32)[] -> Int32[]
+    export interface RunLength {
+        kind: 'RunLength',
+        srcType: IntDataType,
+        srcSize: number
+    }
+
+    // T=(Int8Array | Int16Array | Int32Array)[] -> T[]
+    export interface Delta {
+        kind: 'Delta',
+        origin: number,
+        srcType: IntDataType
+    }
+
+    // Int32[] -> (Int8 | Int16 | Uint8 | Uint16)[]
+    export interface IntegerPacking {
+        kind: 'IntegerPacking',
+        byteCount: number,
+        isUnsigned: boolean,
+        srcSize: number
+    }
+
+    // string[] -> Uint8[]
+    // stores 0 and indices of ends of strings:
+    // stringData = '123456'
+    // offsets = [0,2,5,6]
+    // encodes ['12','345','6']
+    export interface StringArray {
+        kind: 'StringArray',
+        dataEncoding: Encoding[],
+        stringData: string,
+        offsetEncoding: Encoding[],
+        offsets: Uint8Array
+    }
+
+}
\ No newline at end of file
diff --git a/src/reader/cif/binary/field.ts b/src/reader/cif/binary/field.ts
index 0ffdd02fc..974dd522b 100644
--- a/src/reader/cif/binary/field.ts
+++ b/src/reader/cif/binary/field.ts
@@ -1 +1,53 @@
-// TODO
\ No newline at end of file
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as Column from '../../common/column'
+import * as Data from '../data-model'
+import { EncodedColumn } from './encoding'
+import decode from './decoder'
+import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser'
+
+export default function Field(column: EncodedColumn): Data.Field {
+    const mask = column.mask ? decode(column.mask) as number[] : void 0;
+    const data = decode(column.data);
+    const isNumeric = (data as any).buffer && (data as any).byteLength && (data as any).BYTES_PER_ELEMENT;
+
+    const str: Data.Field['str'] = isNumeric
+        ? mask
+            ? row => mask[row] === Data.ValuePresence.Present ? '' + data[row] : ''
+            : row => '' + data[row]
+        : mask
+            ? row => mask[row] === Data.ValuePresence.Present ? data[row] : ''
+            : row => data[row];
+
+    const int: Data.Field['int'] = isNumeric
+        ? row => data[row]
+        : row => { const v = data[row]; return fastParseInt(v, 0, v.length); };
+
+    const float: Data.Field['float'] = isNumeric
+        ? row => data[row]
+        : row => { const v = data[row]; return fastParseFloat(v, 0, v.length); };
+
+    const presence: Data.Field['presence'] = mask
+        ? row => mask[row]
+        : row => Data.ValuePresence.Present;
+
+    const rowCount = data.length;
+
+    return {
+        isDefined: true,
+        rowCount,
+        str,
+        int,
+        float,
+        presence,
+        areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB],
+        stringEquals(row, v) { return str(row) === v; },
+        toStringArray(params) { return Column.createAndFillArray(rowCount, str, params); },
+        toIntArray(params) { return Column.createAndFillArray(rowCount, int, params); },
+        toFloatArray(params)  { return Column.createAndFillArray(rowCount, float, params); }
+    };
+}
\ No newline at end of file
diff --git a/src/reader/cif/binary/parser.ts b/src/reader/cif/binary/parser.ts
index 0ffdd02fc..1cdbd04b1 100644
--- a/src/reader/cif/binary/parser.ts
+++ b/src/reader/cif/binary/parser.ts
@@ -1 +1,49 @@
-// TODO
\ No newline at end of file
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as Data from '../data-model'
+import * as Encoding from './encoding'
+import Field from './field'
+import Result from '../../result'
+import decodeMsgPack from '../../../utils/msgpack/decode'
+
+function checkVersions(min: number[], current: number[]) {
+    for (let i = 0; i < 2; i++) {
+        if (min[i] > current[i]) return false;
+    }
+    return true;
+}
+
+function Category(data: Encoding.EncodedCategory): Data.Category {
+    const map = Object.create(null);
+    for (const col of data.columns) map[col.name] = col;
+    return {
+        rowCount: data.rowCount,
+        getField(name) {
+            const col = map[name];
+            return col ? Field(col) : Data.DefaultUndefinedField(data.rowCount);
+        }
+    }
+}
+
+export default function parse(data: Uint8Array): Result<Data.File> {
+    const minVersion = [0, 3];
+
+    try {
+        const unpacked = decodeMsgPack(data) as Encoding.EncodedFile;
+        if (!checkVersions(minVersion, unpacked.version.match(/(\d)\.(\d)\.\d/)!.slice(1).map(v => +v))) {
+            return Result.error<Data.File>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`);
+        }
+        const file = Data.File(unpacked.dataBlocks.map(block => {
+            const cats = Object.create(null);
+            for (const cat of block.categories) cats[cat.name] = Category(cat);
+            return Data.Block(cats, block.header);
+        }));
+        return Result.success(file);
+    } catch (e) {
+        return Result.error<Data.File>('' + e);
+    }
+}
\ No newline at end of file
diff --git a/src/reader/cif/index.ts b/src/reader/cif/index.ts
index c3882b9e8..e7dcc0f88 100644
--- a/src/reader/cif/index.ts
+++ b/src/reader/cif/index.ts
@@ -5,14 +5,18 @@
  */
 
 import parseText from './text/parser'
+import parseBinary from './binary/parser'
 import { Block } from './data-model'
 import { apply as applySchema } from './schema'
 import mmCIF from './schema/mmcif'
 
 export default {
     parseText,
+    parseBinary,
     applySchema,
     schema: {
         mmCIF: (block: Block) => applySchema(mmCIF, block)
     }
-}
\ No newline at end of file
+}
+
+export * from './data-model'
\ No newline at end of file
diff --git a/src/script.ts b/src/script.ts
index 0ac94deb9..ba82c95d2 100644
--- a/src/script.ts
+++ b/src/script.ts
@@ -72,32 +72,49 @@ export function _gro() {
     });
 }
 
+function runCIF(input: string | Uint8Array) {
+    console.time('parseCIF');
+    const parsed = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input);
+    console.timeEnd('parseCIF');
+    if (parsed.isError) {
+        console.log(parsed);
+        return;
+    }
+
+    const data = parsed.result.blocks[0];
+    const atom_site = data.categories._atom_site;
+    console.log(atom_site.getField('Cartn_x')!.float(0));
+    //console.log(atom_site.getField('label_atom_id')!.toStringArray());
+
+    const mmcif = CIF.schema.mmCIF(data);
+    console.log(mmcif.atom_site.Cartn_x.value(0));
+    console.log(mmcif.entity.type.toArray());
+    console.log(mmcif.pdbx_struct_oper_list.matrix.value(0));
+}
+
 export function _cif() {
-    const path = `./examples/1cbs_updated.cif`;
-    //const path = 'c:/test/quick/3j3q.cif';
+    let path = `./examples/1cbs_updated.cif`;
+    //path = 'c:/test/quick/3j3q.cif';
     fs.readFile(path, 'utf8', function (err, input) {
         if (err) {
             return console.log(err);
         }
+        console.log('------------------');
+        console.log('Text CIF:');
+        runCIF(input);
+    });
 
-        console.time('parseCIF');
-        const parsed = CIF.parseText(input);
-        console.timeEnd('parseCIF');
-        if (parsed.isError) {
-            console.log(parsed);
-            return;
+    path = `./examples/1cbs_full.bcif`;
+    //const path = 'c:/test/quick/3j3q.cif';
+    fs.readFile(path, function (err, input) {
+        if (err) {
+            return console.log(err);
         }
-
-        const data = parsed.result.blocks[0];
-
-        const atom_site = data.categories._atom_site;
-        console.log(atom_site.getField('Cartn_x')!.float(0));
-        //console.log(atom_site.getField('label_atom_id')!.toStringArray());
-
-        const mmcif = CIF.schema.mmCIF(data);
-        console.log(mmcif.atom_site.Cartn_x.value(0));
-        console.log(mmcif.entity.type.toArray());
-        console.log(mmcif.pdbx_struct_oper_list.matrix.value(0));
+        console.log('------------------');
+        console.log('BinaryCIF:');
+        const data = new Uint8Array(input.byteLength);
+        for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
+        runCIF(input);
     });
 }
 
-- 
GitLab