Chapters
Reading settings
+ Typography +
+ ++ Layout +
+ ++ A spread needs the window to be wide enough for two columns. +
+Chapters
+{title}
- {#if needsScroll} -{title}
+ +{authors(book)}
+Read
Download
Grid view
-List view
-{LABELS[view]}
+>4)<16)C[H++]=N;else{var Q=0,R=0;for(16==N?(R=3+k(a,E,3),E+=2,Q=C[H-1]):17==N?(R=3+k(a,E,7),E+=3):18==N&&(R=11+k(a,E,127),E+=7);R--;)C[H++]=Q}}var V=C.subarray(0,j),W=C.subarray(j);D=p(V),F=p(W),A=b(V,D,1),U=b(W,F,1)}else T(1);if(E>M){s&&T(0);break}}w&&h(z+131072);for(var X=(1<>4;if((E+=15&Q)>M){s&&T(0);break}if(Q||T(2),$<256)f[z++]=$;else{if(256==$){Z=E,A=null;break}var _=$-254;if($>264){var rr=n[H=$-257];_=k(a,E,(1< >4;ar||T(3),E+=15&ar;W=u[er];if(er>3){rr=i[er];W+=m(a,E)&(1< M){s&&T(0);break}w&&h(z+131072);var nr=z+_;if(z a.length)&&(n=a.length),new r(a.subarray(e,n))}(f,0,z):f.subarray(0,z)},z=new r(0);function A(r,a){return E(r.subarray((e=r,n=a&&a.dictionary,(8!=(15&e[0])||e[0]>>4>7||(e[0]<<8|e[1])%31)&&T(6,"invalid zlib data"),(e[1]>>5&1)==+!n&&T(6,"invalid zlib data: "+(32&e[1]?"need":"unexpected")+" dictionary"),2+(e[1]>>3&4)),-4),{i:2},a&&a.out,a&&a.dictionary);var e,n}var U="undefined"!=typeof TextDecoder&&new TextDecoder;try{U.decode(z,{stream:!0})}catch(r){}export{A as unzlibSync}; diff --git a/frontend/src/lib/vendor/foliate-js/vendor/zip.js b/frontend/src/lib/vendor/foliate-js/vendor/zip.js new file mode 100644 index 0000000..a65fd86 --- /dev/null +++ b/frontend/src/lib/vendor/foliate-js/vendor/zip.js @@ -0,0 +1 @@ +const e=-2,t=-3,n=-5,i=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],r=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],a=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],s=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],o=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],l=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],c=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],u=15;function d(){let e,i,r,a,d,f;function h(e,i,s,o,l,c,h,w,_,b,p){let m,g,y,x,k,v,S,z,A,U,D,E,T,F,O;U=0,k=s;do{r[e[i+U]]++,U++,k--}while(0!==k);if(r[0]==s)return h[0]=-1,w[0]=0,0;for(z=w[0],v=1;v<=u&&0===r[v];v++);for(S=v,z k&&(z=k),w[0]=z,F=1< E+z;){if(x++,E+=z,O=y-E,O=O>z?z:O,(g=1<<(v=S-E))>m+1&&(g-=m+1,T=S,v 1440)return t;d[x]=D=b[0],b[0]+=O,0!==x?(f[x]=k,a[0]=v,a[1]=z,v=k>>>E-z,a[2]=D-d[x-1]-v,_.set(a,3*(d[x-1]+v))):h[0]=D}for(a[1]=S-E,U>=s?a[0]=192:p[U] >>E;v >>=1)k^=v;for(k^=v,A=(1< 257?(g==t?m.msg="oversubscribed distance tree":g==n?(m.msg="incomplete distance tree",g=t):-4!=g&&(m.msg="empty distance tree with lengths",g=t),g):0)}}d.inflate_trees_fixed=function(e,t,n,i){return e[0]=9,t[0]=5,n[0]=r,i[0]=a,0};function f(){const n=this;let r,a,s,o,l=0,c=0,u=0,d=0,f=0,h=0,w=0,_=0,b=0,p=0;function m(e,n,r,a,s,o,l,c){let u,d,f,h,w,_,b,p,m,g,y,x,k,v,S,z;b=c.next_in_index,p=c.avail_in,w=l.bitb,_=l.bitk,m=l.write,g=m >=d[z+1],_-=d[z+1],16&h){for(h&=15,k=d[z+2]+(w&i[h]),w>>=h,_-=h;_<15;)p--,w|=(255&c.read_byte(b++))<<_,_+=8;for(u=w&x,d=s,f=o,z=3*(f+u),h=d[z];;){if(w>>=d[z+1],_-=d[z+1],16&h){for(h&=15;_ >=h,_-=h,g-=k,m>=v)S=m-v,m-S>0&&2>m-S?(l.win[m++]=l.win[S++],l.win[m++]=l.win[S++],k-=2):(l.win.set(l.win.subarray(S,S+2),m),m+=2,S+=2,k-=2);else{S=m-v;do{S+=l.end}while(S<0);if(h=l.end-S,k>h){if(k-=h,m-S>0&&h>m-S)do{l.win[m++]=l.win[S++]}while(0!=--h);else l.win.set(l.win.subarray(S,S+h),m),m+=h,S+=h,h=0;S=0}}if(m-S>0&&k>m-S)do{l.win[m++]=l.win[S++]}while(0!=--k);else l.win.set(l.win.subarray(S,S+k),m),m+=k,S+=k,k=0;break}if(64&h)return c.msg="invalid distance code",k=c.avail_in-p,k=_>>3 >3:k,p+=k,b-=k,_-=k<<3,l.bitb=w,l.bitk=_,c.avail_in=p,c.total_in+=b-c.next_in_index,c.next_in_index=b,l.write=m,t;u+=d[z+2],u+=w&i[h],z=3*(f+u),h=d[z]}break}if(64&h)return 32&h?(k=c.avail_in-p,k=_>>3 >3:k,p+=k,b-=k,_-=k<<3,l.bitb=w,l.bitk=_,c.avail_in=p,c.total_in+=b-c.next_in_index,c.next_in_index=b,l.write=m,1):(c.msg="invalid literal/length code",k=c.avail_in-p,k=_>>3 >3:k,p+=k,b-=k,_-=k<<3,l.bitb=w,l.bitk=_,c.avail_in=p,c.total_in+=b-c.next_in_index,c.next_in_index=b,l.write=m,t);if(u+=d[z+2],u+=w&i[h],z=3*(f+u),0===(h=d[z])){w>>=d[z+1],_-=d[z+1],l.win[m++]=d[z+2],g--;break}}else w>>=d[z+1],_-=d[z+1],l.win[m++]=d[z+2],g--}while(g>=258&&p>=10);return k=c.avail_in-p,k=_>>3 >3:k,p+=k,b-=k,_-=k<<3,l.bitb=w,l.bitk=_,c.avail_in=p,c.total_in+=b-c.next_in_index,c.next_in_index=b,l.write=m,0}n.init=function(e,t,n,i,l,c){r=0,w=e,_=t,s=n,b=i,o=l,p=c,a=null},n.proc=function(n,g,y){let x,k,v,S,z,A,U,D=0,E=0,T=0;for(T=g.next_in_index,S=g.avail_in,D=n.bitb,E=n.bitk,z=n.write,A=z =258&&S>=10&&(n.bitb=D,n.bitk=E,g.avail_in=S,g.total_in+=T-g.next_in_index,g.next_in_index=T,n.write=z,y=m(w,_,s,b,o,p,n,g),T=g.next_in_index,S=g.avail_in,D=n.bitb,E=n.bitk,z=n.write,A=z >>=a[k+1],E-=a[k+1],v=a[k],0===v){d=a[k+2],r=6;break}if(16&v){f=15&v,l=a[k+2],r=2;break}if(!(64&v)){u=v,c=k/3+a[k+2];break}if(32&v){r=7;break}return r=9,g.msg="invalid literal/length code",y=t,n.bitb=D,n.bitk=E,g.avail_in=S,g.total_in+=T-g.next_in_index,g.next_in_index=T,n.write=z,n.inflate_flush(g,y);case 2:for(x=f;E >=x,E-=x,u=_,a=o,c=p,r=3;case 3:for(x=u;E >=a[k+1],E-=a[k+1],v=a[k],16&v){f=15&v,h=a[k+2],r=4;break}if(!(64&v)){u=v,c=k/3+a[k+2];break}return r=9,g.msg="invalid distance code",y=t,n.bitb=D,n.bitk=E,g.avail_in=S,g.total_in+=T-g.next_in_index,g.next_in_index=T,n.write=z,n.inflate_flush(g,y);case 4:for(x=f;E >=x,E-=x,r=5;case 5:for(U=z-h;U<0;)U+=n.end;for(;0!==l;){if(0===A&&(z==n.end&&0!==n.read&&(z=0,A=z 7&&(E-=8,S++,T--),n.write=z,y=n.inflate_flush(g,y),z=n.write,A=z e.avail_out&&(i=e.avail_out),0!==i&&t==n&&(t=0),e.avail_out-=i,e.total_out+=i,e.next_out.set(s.win.subarray(a,a+i),r),r+=i,a+=i,a==s.end&&(a=0,s.write==s.end&&(s.write=0),i=s.write-a,i>e.avail_out&&(i=e.avail_out),0!==i&&t==n&&(t=0),e.avail_out-=i,e.total_out+=i,e.next_out.set(s.win.subarray(a,a+i),r),r+=i,a+=i),e.next_out_index=r,s.read=a,t},s.proc=function(n,r){let a,f,x,k,v,S,z,A;for(k=n.next_in_index,v=n.avail_in,f=s.bitb,x=s.bitk,S=s.write,z=S >>1){case 0:f>>>=3,x-=3,a=7&x,f>>>=a,x-=a,l=1;break;case 1:U=[],D=[],E=[[]],T=[[]],d.inflate_trees_fixed(U,D,E,T),p.init(U[0],D[0],E[0],0,T[0],0),f>>>=3,x-=3,l=6;break;case 2:f>>>=3,x-=3,l=3;break;case 3:return f>>>=3,x-=3,l=9,n.msg="invalid block type",r=t,s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r)}break;case 1:for(;x<32;){if(0===v)return s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);r=0,v--,f|=(255&n.read_byte(k++))< >>16&65535)!=(65535&f))return l=9,n.msg="invalid stored block lengths",r=t,s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);c=65535&f,f=x=0,l=0!==c?2:0!==m?7:0;break;case 2:if(0===v)return s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);if(0===z&&(S==s.end&&0!==s.read&&(S=0,z=S v&&(a=v),a>z&&(a=z),s.win.set(n.read_buf(k,a),S),k+=a,v-=a,S+=a,z-=a,0!=(c-=a))break;l=0!==m?7:0;break;case 3:for(;x<14;){if(0===v)return s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);r=0,v--,f|=(255&n.read_byte(k++))< 29||(a>>5&31)>29)return l=9,n.msg="too many length or distance symbols",r=t,s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);if(a=258+(31&a)+(a>>5&31),!o||o.length>>=14,x-=14,w=0,l=4;case 4:for(;w<4+(u>>>10);){for(;x<3;){if(0===v)return s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);r=0,v--,f|=(255&n.read_byte(k++))< >>=3,x-=3}for(;w<19;)o[h[w++]]=0;if(_[0]=7,a=y.inflate_trees_bits(o,_,b,g,n),0!=a)return(r=a)==t&&(o=null,l=9),s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);w=0,l=5;case 5:for(;a=u,!(w>=258+(31&a)+(a>>5&31));){let e,c;for(a=_[0];x>>=a,x-=a,o[w++]=c;else{for(A=18==c?7:c-14,e=18==c?11:3;x>>=a,x-=a,e+=f&i[A],f>>>=A,x-=A,A=w,a=u,A+e>258+(31&a)+(a>>5&31)||16==c&&A<1)return o=null,l=9,n.msg="invalid bit length repeat",r=t,s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);c=16==c?o[A-1]:0;do{o[A++]=c}while(0!=--e);w=A}}if(b[0]=-1,F=[],O=[],C=[],W=[],F[0]=9,O[0]=6,a=u,a=y.inflate_trees_dynamic(257+(31&a),1+(a>>5&31),o,F,O,C,W,g,n),0!=a)return a==t&&(o=null,l=9),r=a,s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,s.inflate_flush(n,r);p.init(F[0],O[0],g,C[0],g,W[0]),l=6;case 6:if(s.bitb=f,s.bitk=x,n.avail_in=v,n.total_in+=k-n.next_in_index,n.next_in_index=k,s.write=S,1!=(r=p.proc(s,n,r)))return s.inflate_flush(n,r);if(r=0,p.free(n),k=n.next_in_index,v=n.avail_in,f=s.bitb,x=s.bitk,S=s.write,z=S 15?(i.inflateEnd(t),e):(i.wbits=n,t.istate.blocks=new w(t,1< >4)>o.wbits){o.mode=_,i.msg="invalid win size",o.marker=5;break}o.mode=1;case 1:if(0===i.avail_in)return a;if(a=r,i.avail_in--,i.total_in++,s=255&i.read_byte(i.next_in_index++),((o.method<<8)+s)%31!=0){o.mode=_,i.msg="incorrect header check",o.marker=5;break}if(!(32&s)){o.mode=7;break}o.mode=2;case 2:if(0===i.avail_in)return a;a=r,i.avail_in--,i.total_in++,o.need=(255&i.read_byte(i.next_in_index++))<<24&4278190080,o.mode=3;case 3:if(0===i.avail_in)return a;a=r,i.avail_in--,i.total_in++,o.need+=(255&i.read_byte(i.next_in_index++))<<16&16711680,o.mode=4;case 4:if(0===i.avail_in)return a;a=r,i.avail_in--,i.total_in++,o.need+=(255&i.read_byte(i.next_in_index++))<<8&65280,o.mode=5;case 5:return 0===i.avail_in?a:(a=r,i.avail_in--,i.total_in++,o.need+=255&i.read_byte(i.next_in_index++),o.mode=6,2);case 6:return o.mode=_,i.msg="need dictionary",o.marker=0,e;case 7:if(a=o.blocks.proc(i,a),a==t){o.mode=_,o.marker=0;break}if(0==a&&(a=r),1!=a)return a;a=r,o.blocks.reset(i,o.was),o.mode=12;case 12:return i.avail_in=0,1;case _:return t;default:return e}},i.inflateSetDictionary=function(t,n,i){let r=0,a=i;if(!t||!t.istate||6!=t.istate.mode)return e;const s=t.istate;return a>=1< >>1^3988292384:t>>>=1;C[e]=t}class W{constructor(e){this.crc=e||-1}append(e){let t=0|this.crc;for(let n=0,i=0|e.length;n>>8^C[255&(t^e[n])];this.crc=t}get(){return~this.crc}}class j extends TransformStream{constructor(){let e;const t=new W;super({transform(e,n){t.append(e),n.enqueue(e)},flush(){const n=new Uint8Array(4);new DataView(n.buffer).setUint32(0,t.get()),e.value=n}}),e=this}}const M={concat(e,t){if(0===e.length||0===t.length)return e.concat(t);const n=e[e.length-1],i=M.getPartial(n);return 32===i?e.concat(t):M._shiftRight(t,i,0|n,e.slice(0,e.length-1))},bitLength(e){const t=e.length;if(0===t)return 0;const n=e[t-1];return 32*(t-1)+M.getPartial(n)},clamp(e,t){if(32*e.length 0&&t&&(e[n-1]=M.partial(t,e[n-1]&2147483648>>t-1,1)),e},partial:(e,t,n)=>32===e?t:(n?0|t:t<<32-e)+1099511627776*e,getPartial:e=>Math.round(e/1099511627776)||32,_shiftRight(e,t,n,i){for(void 0===i&&(i=[]);t>=32;t-=32)i.push(n),n=0;if(0===t)return i.concat(e);for(let r=0;r >>t),n=e[r]<<32-t;const r=e.length?e[e.length-1]:0,a=M.getPartial(r);return i.push(M.partial(t+a&31,t+a>32?n:i.pop(),1)),i}},L={bytes:{fromBits(e){const t=M.bitLength(e)/8,n=new Uint8Array(t);let i;for(let r=0;r >>24,i<<=8;return n},toBits(e){const t=[];let n,i=0;for(n=0;n 9007199254740991)throw new Error("Cannot hash more than 2^53 - 1 bits");const a=new Uint32Array(n);let s=0;for(let e=t.blockSize+i-(t.blockSize+i&t.blockSize-1);e<=r;e+=t.blockSize)t._block(a.subarray(16*s,16*(s+1))),s+=1;return n.splice(0,16*s),t}finalize(){const e=this;let t=e._buffer;const n=e._h;t=M.concat(t,[M.partial(1,1)]);for(let e=t.length+2;15&e;e++)t.push(0);for(t.push(Math.floor(e._length/4294967296)),t.push(0|e._length);t.length;)e._block(t.splice(0,16));return e.reset(),n}_f(e,t,n,i){return e<=19?t&n|~t&i:e<=39?t^n^i:e<=59?t&n|t&i|n&i:e<=79?t^n^i:void 0}_S(e,t){return t< >>32-e}_block(e){const t=this,n=t._h,i=Array(80);for(let t=0;t<16;t++)i[t]=e[t];let r=n[0],a=n[1],s=n[2],o=n[3],l=n[4];for(let e=0;e<=79;e++){e>=16&&(i[e]=t._S(1,i[e-3]^i[e-8]^i[e-14]^i[e-16]));const n=t._S(5,r)+t._f(e,a,s,o)+l+i[e]+t._key[Math.floor(e/20)]|0;l=o,o=s,s=t._S(30,a),a=r,r=n}n[0]=n[0]+r|0,n[1]=n[1]+a|0,n[2]=n[2]+s|0,n[3]=n[3]+o|0,n[4]=n[4]+l|0}}},R={aes:class{constructor(e){const t=this;t._tables=[[[],[],[],[],[]],[[],[],[],[],[]]],t._tables[0][0][0]||t._precompute();const n=t._tables[0][4],i=t._tables[1],r=e.length;let a,s,o,l=1;if(4!==r&&6!==r&&8!==r)throw new Error("invalid aes key size");for(t._key=[s=e.slice(0),o=[]],a=r;a<4*r+28;a++){let e=s[a-1];(a%r==0||8===r&&a%r==4)&&(e=n[e>>>24]<<24^n[e>>16&255]<<16^n[e>>8&255]<<8^n[255&e],a%r==0&&(e=e<<8^e>>>24^l<<24,l=l<<1^283*(l>>7))),s[a]=s[a-r]^e}for(let e=0;a;e++,a--){const t=s[3&e?a:a-4];o[e]=a<=4||e<4?t:i[0][n[t>>>24]]^i[1][n[t>>16&255]]^i[2][n[t>>8&255]]^i[3][n[255&t]]}}encrypt(e){return this._crypt(e,0)}decrypt(e){return this._crypt(e,1)}_precompute(){const e=this._tables[0],t=this._tables[1],n=e[4],i=t[4],r=[],a=[];let s,o,l,c;for(let e=0;e<256;e++)a[(r[e]=e<<1^283*(e>>7))^e]=e;for(let u=s=0;!n[u];u^=o||1,s=a[s]||1){let a=s^s<<1^s<<2^s<<3^s<<4;a=a>>8^255&a^99,n[u]=a,i[a]=u,c=r[l=r[o=r[u]]];let d=16843009*c^65537*l^257*o^16843008*u,f=257*r[a]^16843008*a;for(let n=0;n<4;n++)e[n][u]=f=f<<24^f>>>8,t[n][a]=d=d<<24^d>>>8}for(let n=0;n<5;n++)e[n]=e[n].slice(0),t[n]=t[n].slice(0)}_crypt(e,t){if(4!==e.length)throw new Error("invalid aes block size");const n=this._key[t],i=n.length/4-2,r=[0,0,0,0],a=this._tables[t],s=a[0],o=a[1],l=a[2],c=a[3],u=a[4];let d,f,h,w=e[0]^n[0],_=e[t?3:1]^n[1],b=e[2]^n[2],p=e[t?1:3]^n[3],m=4;for(let e=0;e>>24]^o[_>>16&255]^l[b>>8&255]^c[255&p]^n[m],f=s[_>>>24]^o[b>>16&255]^l[p>>8&255]^c[255&w]^n[m+1],h=s[b>>>24]^o[p>>16&255]^l[w>>8&255]^c[255&_]^n[m+2],p=s[p>>>24]^o[w>>16&255]^l[_>>8&255]^c[255&b]^n[m+3],m+=4,w=d,_=f,b=h;for(let e=0;e<4;e++)r[t?3&-e:e]=u[w>>>24]<<24^u[_>>16&255]<<16^u[b>>8&255]<<8^u[255&p]^n[m++],d=w,w=_,_=b,b=p,p=d;return r}}},B={getRandomValues(e){const t=new Uint32Array(e.buffer),n=e=>{let t=987654321;const n=4294967295;return function(){t=36969*(65535&t)+(t>>16)&n;return(((t<<16)+(e=18e3*(65535&e)+(e>>16)&n)&n)/4294967296+.5)*(Math.random()>.5?1:-1)}};for(let i,r=0;r >24))e+=1<<24;else{let t=e>>16&255,n=e>>8&255,i=255&e;255===t?(t=0,255===n?(n=0,255===i?i=0:++i):++n):++t,e=0,e+=t<<16,e+=n<<8,e+=i}return e}incCounter(e){0===(e[0]=this.incWord(e[0]))&&(e[1]=this.incWord(e[1]))}calculate(e,t,n){let i;if(!(i=t.length))return[];const r=M.bitLength(t);for(let r=0;rnew N.hmacSha1(L.bytes.toBits(e)),pbkdf2(e,t,n,i){if(n=n||1e4,i<0||n<0)throw new Error("invalid params to pbkdf2");const r=1+(i>>5)<<2;let a,s,o,l,c;const u=new ArrayBuffer(r),d=new DataView(u);let f=0;const h=M;for(t=L.bytes.toBits(t),c=1;f<(r||1);c++){for(a=s=e.encrypt(h.concat(t,[c])),o=1;o r&&(e=(new n).update(e).finalize());for(let t=0;t this.resolveReady=e)),password:be(e,t),signed:n,strength:i-1,pending:new Uint8Array})},async transform(e,t){const n=this,{password:i,strength:a,resolveReady:s,ready:o}=n;i?(await async function(e,t,n,i){const r=await _e(e,t,n,me(i,0,$[t])),a=me(i,$[t]);if(r[0]!=a[0]||r[1]!=a[1])throw new Error(q)}(n,a,i,me(e,0,$[a]+2)),e=me(e,$[a]+2),r?t.error(new Error(K)):s()):await o;const l=new Uint8Array(e.length-te-(e.length-te)%G);t.enqueue(we(n,e,l,0,te,!0))},async flush(e){const{signed:t,ctr:n,hmac:i,pending:r,ready:a}=this;if(i&&n){await a;const s=me(r,0,r.length-te),o=me(r,r.length-te);let l=new Uint8Array;if(s.length){const e=ye(se,s);i.update(e);const t=n.update(e);l=ge(se,t)}if(t){const e=me(ge(se,i.digest()),0,te);for(let t=0;t this.resolveReady=e)),password:be(e,t),strength:n-1,pending:new Uint8Array})},async transform(e,t){const n=this,{password:i,strength:r,resolveReady:a,ready:s}=n;let o=new Uint8Array;i?(o=await async function(e,t,n){const i=Z(new Uint8Array($[t])),r=await _e(e,t,n,i);return pe(i,r)}(n,r,i),a()):await s;const l=new Uint8Array(o.length+e.length-e.length%G);l.set(o,0),t.enqueue(we(n,e,l,o.length,0))},async flush(e){const{ctr:t,hmac:n,pending:r,ready:a}=this;if(n&&t){await a;let s=new Uint8Array;if(r.length){const e=t.update(ye(se,r));n.update(e),s=ge(se,e)}i.signature=ge(se,n.digest()).slice(0,te),e.enqueue(pe(s,i.signature))}}}),i=this}}function we(e,t,n,i,r,a){const{ctr:s,hmac:o,pending:l}=e,c=t.length-r;let u;for(l.length&&(t=pe(l,t),n=function(e,t){if(t&&t>e.length){const n=e;(e=new Uint8Array(t)).set(n,0)}return e}(n,c-c%G)),u=0;u<=c-G;u+=G){const e=ye(se,me(t,u,u+G));a&&o.update(e);const r=s.update(e);a||o.update(r),n.set(ge(se,r),u+i)}return e.pending=me(t,u),n}async function _e(e,t,n,i){e.password=null;const r=await async function(e,t,n,i,r){if(!ue)return N.importKey(t);try{return await re.importKey(e,t,n,i,r)}catch(e){return ue=!1,N.importKey(t)}}("raw",n,Q,!1,Y),a=await async function(e,t,n){if(!de)return N.pbkdf2(t,e.salt,X.iterations,n);try{return await re.deriveBits(e,t,n)}catch(i){return de=!1,N.pbkdf2(t,e.salt,X.iterations,n)}}(Object.assign({salt:i},X),r,8*(2*ee[t]+2)),s=new Uint8Array(a),o=ye(se,me(s,0,ee[t])),l=ye(se,me(s,ee[t],2*ee[t])),c=me(s,2*ee[t]);return Object.assign(e,{keys:{key:o,authentication:l,passwordVerification:c},ctr:new le(new oe(o),Array.from(ne)),hmac:new ce(l)}),c}function be(e,t){return t===S?function(e){if(typeof TextEncoder==z){e=unescape(encodeURIComponent(e));const t=new Uint8Array(e.length);for(let n=0;n >>24]),r=~e.crcKey2.get(),e.keys=[n,i,r]}function De(e){const t=2|e.keys[2];return Ee(Math.imul(t,1^t)>>>8)}function Ee(e){return 255&e}function Te(e){return 4294967295&e}const Fe="deflate-raw";class Oe extends TransformStream{constructor(e,{chunkSize:t,CompressionStream:n,CompressionStreamNative:i}){super({});const{compressed:r,encrypted:a,useCompressionStream:s,zipCrypto:o,signed:l,level:c}=e,u=this;let d,f,h=We(super.readable);a&&!o||!l||(d=new j,h=Le(h,d)),r&&(h=Me(h,s,{level:c,chunkSize:t},i,n)),a&&(o?h=Le(h,new ve(e)):(f=new he(e),h=Le(h,f))),je(u,h,(()=>{let e;a&&!o&&(e=f.signature),a&&!o||!l||(e=new DataView(d.value.buffer).getUint32(0)),u.signature=e}))}}class Ce extends TransformStream{constructor(e,{chunkSize:t,DecompressionStream:n,DecompressionStreamNative:i}){super({});const{zipCrypto:r,encrypted:a,signed:s,signature:o,compressed:l,useCompressionStream:c}=e;let u,d,f=We(super.readable);a&&(r?f=Le(f,new ke(e)):(d=new fe(e),f=Le(f,d))),l&&(f=Me(f,c,{chunkSize:t},i,n)),a&&!r||!s||(u=new j,f=Le(f,u)),je(this,f,(()=>{if((!a||r)&&s){const e=new DataView(u.value.buffer);if(o!=e.getUint32(0,!1))throw new Error(H)}}))}}function We(e){return Le(e,new TransformStream({transform(e,t){e&&e.length&&t.enqueue(e)}}))}function je(e,t,n){t=Le(t,new TransformStream({flush:n})),Object.defineProperty(e,"readable",{get:()=>t})}function Me(e,t,n,i,r){try{e=Le(e,new(t&&i?i:r)(Fe,n))}catch(i){if(!t)return e;try{e=Le(e,new r(Fe,n))}catch(t){return e}}return e}function Le(e,t){return e.pipeThrough(t)}const Pe="message",Re="start",Be="pull",Ie="data",Ne="close",Ve="inflate";class qe extends TransformStream{constructor(e,t){super({});const n=this,{codecType:i}=e;let r;i.startsWith("deflate")?r=Oe:i.startsWith(Ve)&&(r=Ce);let a=0,s=0;const o=new r(e,t),l=super.readable,c=new TransformStream({transform(e,t){e&&e.length&&(s+=e.length,t.enqueue(e))},flush(){Object.assign(n,{inputSize:s})}}),u=new TransformStream({transform(e,t){e&&e.length&&(a+=e.length,t.enqueue(e))},flush(){const{signature:e}=o;Object.assign(n,{signature:e,outputSize:a,inputSize:s})}});Object.defineProperty(n,"readable",{get:()=>l.pipeThrough(c).pipeThrough(o).pipeThrough(u)})}}class He extends TransformStream{constructor(e){let t;super({transform:function n(i,r){if(t){const e=new Uint8Array(t.length+i.length);e.set(t),e.set(i,t.length),i=e,t=null}i.length>e?(r.enqueue(i.slice(0,e)),n(i.slice(e),r)):t=i},flush(e){t&&t.length&&e.enqueue(t)}})}}let Ke=typeof Worker!=z;class Ze{constructor(e,{readable:t,writable:n},{options:i,config:r,streamOptions:a,useWebWorkers:s,transferStreams:o,scripts:l},c){const{signal:u}=a;return Object.assign(e,{busy:!0,readable:t.pipeThrough(new He(r.chunkSize)).pipeThrough(new Ge(t,a),{signal:u}),writable:n,options:Object.assign({},i),scripts:l,transferStreams:o,terminate:()=>new Promise((t=>{const{worker:n,busy:i}=e;n?(i?e.resolveTerminated=t:(n.terminate(),t()),e.interface=null):t()})),onTaskFinished(){const{resolveTerminated:t}=e;t&&(e.resolveTerminated=null,e.terminated=!0,e.worker.terminate(),t()),e.busy=!1,c(e)}}),(s&&Ke?Xe:Qe)(e,r)}}class Ge extends TransformStream{constructor(e,{onstart:t,onprogress:n,size:i,onend:r}){let a=0;super({async start(){t&&await Je(t,i)},async transform(e,t){a+=e.length,n&&await Je(n,a,i),t.enqueue(e)},async flush(){e.size=a,r&&await Je(r,a)}})}}async function Je(e,...t){try{await e(...t)}catch(e){}}function Qe(e,t){return{run:()=>async function({options:e,readable:t,writable:n,onTaskFinished:i},r){try{const i=new qe(e,r);await t.pipeThrough(i).pipeTo(n,{preventClose:!0,preventAbort:!0});const{signature:a,inputSize:s,outputSize:o}=i;return{signature:a,inputSize:s,outputSize:o}}finally{i()}}(e,t)}}function Xe(e,t){const{baseURL:n,chunkSize:i}=t;if(!e.interface){let r;try{r=function(e,t,n){const i={type:"module"};let r,a;typeof e==A&&(e=e());try{r=new URL(e,t)}catch(t){r=e}if(Ye)try{a=new Worker(r)}catch(e){Ye=!1,a=new Worker(r,i)}else a=new Worker(r,i);return a.addEventListener(Pe,(e=>async function({data:e},t){const{type:n,value:i,messageId:r,result:a,error:s}=e,{reader:o,writer:l,resolveResult:c,rejectResult:u,onTaskFinished:d}=t;try{if(s){const{message:e,stack:t,code:n,name:i}=s,r=new Error(e);Object.assign(r,{stack:t,code:n,name:i}),f(r)}else{if(n==Be){const{value:e,done:n}=await o.read();et({type:Ie,value:e,done:n,messageId:r},t)}n==Ie&&(await l.ready,await l.write(new Uint8Array(i)),et({type:"ack",messageId:r},t)),n==Ne&&f(null,a)}}catch(s){et({type:Ne,messageId:r},t),f(s)}function f(e,t){e?u(e):c(t),l&&l.releaseLock(),d()}}(e,n))),a}(e.scripts[0],n,e)}catch(n){return Ke=!1,Qe(e,t)}Object.assign(e,{worker:r,interface:{run:()=>async function(e,t){let n,i;const r=new Promise(((e,t)=>{n=e,i=t}));Object.assign(e,{reader:null,writer:null,resolveResult:n,rejectResult:i,result:r});const{readable:a,options:s,scripts:o}=e,{writable:l,closed:c}=function(e){let t;const n=new Promise((e=>t=e)),i=new WritableStream({async write(t){const n=e.getWriter();await n.ready,await n.write(t),n.releaseLock()},close(){t()},abort:t=>e.getWriter().abort(t)});return{writable:i,closed:n}}(e.writable),u=et({type:Re,scripts:o.slice(1),options:s,config:t,readable:a,writable:l},e);u||Object.assign(e,{reader:a.getReader(),writer:l.getWriter()});const d=await r;u||await l.getWriter().close();return await c,d}(e,{chunkSize:i})}})}return e.interface}let Ye=!0,$e=!0;function et(e,{worker:t,writer:n,onTaskFinished:i,transferStreams:r}){try{let{value:n,readable:i,writable:a}=e;const s=[];if(n&&(n.byteLength !e.busy));if(n)return at(n),new Ze(n,e,t,w);if(tt.length nt.push({resolve:n,stream:e,workerOptions:t})))}()).run();function w(e){if(nt.length){const[{resolve:t,stream:n,workerOptions:i}]=nt.splice(0,1);t(new Ze(e,n,i,w))}else e.worker?(at(e),function(e,t){const{config:n}=t,{terminateWorkerTimeout:i}=n;Number.isFinite(i)&&i>=0&&(e.terminated?e.terminated=!1:e.terminateTimeout=setTimeout((async()=>{tt=tt.filter((t=>t!=e));try{await e.terminate()}catch(e){}}),i))}(e,t)):tt=tt.filter((t=>t!=e))}}function at(e){const{terminateTimeout:t}=e;t&&(clearTimeout(t),e.terminateTimeout=null)}const st=65536,ot="writable";class lt{constructor(){this.size=0}init(){this.initialized=!0}}class ct extends lt{get readable(){const e=this,{chunkSize:t=st}=e,n=new ReadableStream({start(){this.chunkOffset=0},async pull(i){const{offset:r=0,size:a,diskNumberStart:s}=n,{chunkOffset:o}=this;i.enqueue(await pt(e,r+o,Math.min(t,a-o),s)),o+t>a?i.close():this.chunkOffset+=t}});return n}}class ut extends ct{constructor(e){super(),Object.assign(this,{blob:e,size:e.size})}async readUint8Array(e,t){const n=this,i=e+t,r=e||i t&&(a=a.slice(e,i)),new Uint8Array(a)}}class dt extends lt{constructor(e){super();const t=new TransformStream,n=[];e&&n.push(["Content-Type",e]),Object.defineProperty(this,ot,{get:()=>t.writable}),this.blob=new Response(t.readable,{headers:n}).blob()}getData(){return this.blob}}class ft extends dt{constructor(e){super(e),Object.assign(this,{encoding:e,utf8:!e||"utf-8"==e.toLowerCase()})}async getData(){const{encoding:e,utf8:t}=this,n=await super.getData();if(n.text&&t)return n.text();{const t=new FileReader;return new Promise(((i,r)=>{Object.assign(t,{onload:({target:e})=>i(e.result),onerror:()=>r(t.error)}),t.readAsText(n,e)}))}}}class ht extends ct{constructor(e){super(),this.readers=e}async init(){const e=this,{readers:t}=e;e.lastDiskNumber=0,e.lastDiskOffset=0,await Promise.all(t.map((async(n,i)=>{await n.init(),i!=t.length-1&&(e.lastDiskOffset+=n.size),e.size+=n.size}))),super.init()}async readUint8Array(e,t,n=0){const i=this,{readers:r}=this;let a,s=n;-1==s&&(s=r.length-1);let o=e;for(;o>=r[s].size;)o-=r[s].size,s++;const l=r[s],c=l.size;if(o+t<=c)a=await pt(l,o,t);else{const r=c-o;a=new Uint8Array(t),a.set(await pt(l,o,r)),a.set(await i.readUint8Array(e+r,t-r,n),r)}return i.lastDiskNumber=Math.max(s,i.lastDiskNumber),a}}class wt extends lt{constructor(e,t=4294967295){super();const n=this;let i,r,a;Object.assign(n,{diskNumber:0,diskOffset:0,size:0,maxSize:t,availableSize:t});const s=new WritableStream({async write(t){const{availableSize:s}=n;if(a)t.length>=s?(await o(t.slice(0,s)),await l(),n.diskOffset+=i.size,n.diskNumber++,a=null,await this.write(t.slice(s))):await o(t);else{const{value:s,done:o}=await e.next();if(o&&!s)throw new Error("Writer iterator completed too soon");i=s,i.size=0,i.maxSize&&(n.maxSize=i.maxSize),n.availableSize=n.maxSize,await _t(i),r=s.writable,a=r.getWriter(),await this.write(t)}},async close(){await a.ready,await l()}});async function o(e){const t=e.length;t&&(await a.ready,await a.write(e),i.size+=t,n.size+=t,n.availableSize-=t)}async function l(){r.size=i.size,await a.close()}Object.defineProperty(n,ot,{get:()=>s})}}async function _t(e,t){if(!e.init||e.initialized)return Promise.resolve();await e.init(t)}function bt(e){return Array.isArray(e)&&(e=new ht(e)),e instanceof ReadableStream&&(e={readable:e}),e}function pt(e,t,n,i){return e.readUint8Array(t,n,i)}const mt="\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split(""),gt=256==mt.length;function yt(e,t){return t&&"cp437"==t.trim().toLowerCase()?function(e){if(gt){let t="";for(let n=0;n this[t]=e[t]))}}const Lt="File format is not recognized",Pt="Zip64 extra field not found",Rt="Compression method not supported",Bt="Split zip file",It="utf-8",Nt="cp437",Vt=[[zt,g],[At,g],[Ut,g],[Dt,y]],qt={[y]:{getValue:tn,bytes:4},[g]:{getValue:nn,bytes:8}};class Ht{constructor(e,t={}){Object.assign(this,{reader:bt(e),options:t,config:T})}async*getEntriesGenerator(e={}){const t=this;let{reader:n}=t;const{config:i}=t;if(await _t(n),n.size!==S&&n.readUint8Array||(n=new ut(await new Response(n.readable).blob()),await _t(n)),n.size =0;e--)if(s[e]==a[0]&&s[e+1]==a[1]&&s[e+2]==a[2]&&s[e+3]==a[3])return{offset:r+e,buffer:s.slice(e,e+i).buffer}}}(n,101010256,n.size,v,1048560);if(!r){throw 134695760==tn(rn(await pt(n,0,4)))?new Error(Bt):new Error("End of central directory not found")}const a=rn(r);let s=tn(a,12),o=tn(a,16);const l=r.offset,c=en(a,20),u=l+v+c;let d=en(a,4);const f=n.lastDiskNumber||0;let h=en(a,6),w=en(a,8),_=0,b=0;if(o==g||s==g||w==y||h==y){const e=rn(await pt(n,r.offset-20,20));if(117853008==tn(e,0)){o=nn(e,8);let t=await pt(n,o,56,-1),i=rn(t);const a=r.offset-20-56;if(tn(i,0)!=k&&o!=a){const e=o;o=a,_=o-e,t=await pt(n,o,56,-1),i=rn(t)}if(tn(i,0)!=k)throw new Error("End of Zip64 central directory locator not found");d==y&&(d=tn(i,16)),h==y&&(h=tn(i,20)),w==y&&(w=nn(i,32)),s==g&&(s=nn(i,40)),o-=s}}if(o>=n.size&&(_=n.size-o-s-v,o=n.size-s-v),f!=d)throw new Error(Bt);if(o<0)throw new Error(Lt);let p=0,m=await pt(n,o,s,h),z=rn(m);if(s){const e=r.offset-s;if(tn(z,p)!=x&&o!=e){const t=o;o=e,_+=o-t,m=await pt(n,o,s,h),z=rn(m)}}const A=r.offset-o-(n.lastDiskOffset||0);if(s!=A&&A>=0&&(s=A,m=await pt(n,o,s,h),z=rn(m)),o<0||o>=n.size)throw new Error(Lt);const U=Qt(t,e,"filenameEncoding"),D=Qt(t,e,"commentEncoding");for(let r=0;r a.getData(e,j,t),p=g;const{onprogress:M}=e;if(M)try{await M(r+1,w,new Mt(a))}catch(e){}yield j}const E=Qt(t,e,"extractPrependedData"),T=Qt(t,e,"extractAppendedData");return E&&(t.prependedData=b>0?await pt(n,0,b):new Uint8Array),t.comment=c?await pt(n,l+v,c):new Uint8Array,T&&(t.appendedData=u >>8&255:d>>>24&255),signature:d,compressed:0!=l&&!g,encrypted:i.encrypted&&!g,useWebWorkers:Qt(i,n,"useWebWorkers"),useCompressionStream:Qt(i,n,"useCompressionStream"),transferStreams:Qt(i,n,"transferStreams"),checkPasswordOnly:D},config:c,streamOptions:{signal:U,size:v,onstart:T,onprogress:F,onend:O}};let W=0;try{({outputSize:W}=await rt({readable:z,writable:E},C))}catch(e){if(!D||e.message!=K)throw e}finally{const e=Qt(i,n,"preventClose");E.size+=W,e||E.locked||await E.getWriter().close()}return D?S:e.getData?e.getData():E}}function Zt(e,t,n){const i=e.rawBitFlag=en(t,n+2),r=!(1&~i),a=tn(t,n+6);Object.assign(e,{encrypted:r,version:en(t,n),bitFlag:{level:(6&i)>>1,dataDescriptor:!(8&~i),languageEncodingFlag:!(2048&~i)},rawLastModDate:a,lastModDate:Xt(a),filenameLength:en(t,n+22),extraFieldLength:en(t,n+24)})}async function Gt(e,t,n,i,r){const{rawExtraField:a}=t,s=t.extraField=new Map,o=rn(new Uint8Array(a));let l=0;try{for(;l t[e]==n));for(let r=0,a=0;r =5&&(a.push(Et),s.push(Tt));let o=1;a.forEach(((n,r)=>{if(e.data.length>=o+4){const a=tn(i,o);t[n]=e[n]=new Date(1e3*a);const l=s[r];e[l]=a}o+=4}))}(_,t,r),t.extraFieldExtendedTimestamp=_);const b=s.get(6534);b&&(t.extraFieldUSDZ=b)}async function Jt(e,t,n,i,r){const a=rn(e.data),s=new W;s.append(r[n]);const o=rn(new Uint8Array(4));o.setUint32(0,s.get(),!0);const l=tn(a,1);Object.assign(e,{version:$t(a,0),[t]:yt(e.data.subarray(5)),valid:!r.bitFlag.languageEncodingFlag&&l==tn(o,0)}),e.valid&&(i[t]=e[t],i[t+"UTF8"]=!0)}function Qt(e,t,n){return t[n]===S?e.options[n]:t[n]}function Xt(e){const t=(4294901760&e)>>16,n=65535&e;try{return new Date(1980+((65024&t)>>9),((480&t)>>5)-1,31&t,(63488&n)>>11,(2016&n)>>5,2*(31&n),0)}catch(e){}}function Yt(e){return new Date(Number(e/BigInt(1e4)-BigInt(116444736e5)))}function $t(e,t){return e.getUint8(t)}function en(e,t){return e.getUint16(t,!0)}function tn(e,t){return e.getUint32(t,!0)}function nn(e,t){return Number(e.getBigUint64(t,!0))}function rn(e){return new DataView(e.buffer)}F({Inflate:function(e){const t=new m,i=e&&e.chunkSize?Math.floor(2*e.chunkSize):131072,r=new Uint8Array(i);let a=!1;t.inflateInit(),t.next_out=r,this.append=function(e,s){const o=[];let l,c,u=0,d=0,f=0;if(0!==e.length){t.next_in_index=0,t.next_in=e,t.avail_in=e.length;do{if(t.next_out_index=0,t.avail_out=i,0!==t.avail_in||a||(t.next_in_index=0,a=!0),l=t.inflate(0),a&&l===n){if(0!==t.avail_in)throw new Error("inflating: bad input")}else if(0!==l&&1!==l)throw new Error("inflating: "+t.msg);if((a||1===l)&&t.avail_in===e.length)throw new Error("inflating: bad input");t.next_out_index&&(t.next_out_index===i?o.push(new Uint8Array(r)):o.push(r.subarray(0,t.next_out_index))),f+=t.next_out_index,s&&t.next_in_index>0&&t.next_in_index!=u&&(s(t.next_in_index),u=t.next_in_index)}while(t.avail_in>0||0===t.avail_out);return o.length>1?(c=new Uint8Array(f),o.forEach((function(e){c.set(e,d),d+=e.length}))):c=o[0]?new Uint8Array(o[0]):new Uint8Array,c}},this.flush=function(){t.inflateEnd()}}});export{ut as BlobReader,dt as BlobWriter,ft as TextWriter,Ht as ZipReader,F as configure}; diff --git a/frontend/src/lib/vendor/foliate-js/view.js b/frontend/src/lib/vendor/foliate-js/view.js new file mode 100644 index 0000000..2443fa3 --- /dev/null +++ b/frontend/src/lib/vendor/foliate-js/view.js @@ -0,0 +1,704 @@ +import * as CFI from './epubcfi.js' +import { TOCProgress, SectionProgress, PageProgress } from './progress.js' +import { Overlayer } from './overlayer.js' +import { textWalker } from './text-walker.js' + +const SEARCH_PREFIX = 'foliate-search:' + +const NOTE_PREFIX = 'foliate-note:' + +const isZip = async file => { + const arr = new Uint8Array(await file.slice(0, 4).arrayBuffer()) + return arr[0] === 0x50 && arr[1] === 0x4b && arr[2] === 0x03 && arr[3] === 0x04 +} + +const isPDF = async file => { + const arr = new Uint8Array(await file.slice(0, 5).arrayBuffer()) + return arr[0] === 0x25 + && arr[1] === 0x50 && arr[2] === 0x44 && arr[3] === 0x46 + && arr[4] === 0x2d +} + +const isCBZ = ({ name, type }) => + type === 'application/vnd.comicbook+zip' || name.endsWith('.cbz') + +const isFB2 = ({ name, type }) => + type === 'application/x-fictionbook+xml' || name.endsWith('.fb2') + +const isFBZ = ({ name, type }) => + type === 'application/x-zip-compressed-fb2' + || name.endsWith('.fb2.zip') || name.endsWith('.fbz') + +const makeZipLoader = async file => { + const { configure, ZipReader, BlobReader, TextWriter, BlobWriter } = + await import('./vendor/zip.js') + configure({ useWebWorkers: false }) + const reader = new ZipReader(new BlobReader(file)) + const entries = await reader.getEntries() + const map = new Map(entries.map(entry => [entry.filename, entry])) + const load = f => (name, ...args) => + map.has(name) ? f(map.get(name), ...args) : null + const loadText = load(entry => entry.getData(new TextWriter())) + const loadBlob = load((entry, type) => entry.getData(new BlobWriter(type))) + const getSize = name => map.get(name)?.uncompressedSize ?? 0 + return { entries, loadText, loadBlob, getSize } +} + +const getFileEntries = async entry => entry.isFile ? entry + : (await Promise.all(Array.from( + await new Promise((resolve, reject) => entry.createReader() + .readEntries(entries => resolve(entries), error => reject(error))), + getFileEntries))).flat() + +const makeDirectoryLoader = async entry => { + const entries = await getFileEntries(entry) + const files = await Promise.all( + entries.map(entry => new Promise((resolve, reject) => + entry.file(file => resolve([file, entry.fullPath]), + error => reject(error))))) + const map = new Map(files.map(([file, path]) => + [path.replace(entry.fullPath + '/', ''), file])) + const decoder = new TextDecoder() + const decode = x => x ? decoder.decode(x) : null + const getBuffer = name => map.get(name)?.arrayBuffer() ?? null + const loadText = async name => decode(await getBuffer(name)) + const loadBlob = name => map.get(name) + const getSize = name => map.get(name)?.size ?? 0 + return { loadText, loadBlob, getSize } +} + +export class ResponseError extends Error {} +export class NotFoundError extends Error {} +export class UnsupportedTypeError extends Error {} + +const fetchFile = async url => { + const res = await fetch(url) + if (!res.ok) throw new ResponseError( + `${res.status} ${res.statusText}`, { cause: res }) + return new File([await res.blob()], new URL(res.url).pathname) +} + +export const makeBook = async file => { + if (typeof file === 'string') file = await fetchFile(file) + let book + if (file.isDirectory) { + const loader = await makeDirectoryLoader(file) + const { EPUB } = await import('./epub.js') + book = await new EPUB(loader).init() + } + else if (!file.size) throw new NotFoundError('File not found') + else if (await isZip(file)) { + const loader = await makeZipLoader(file) + if (isCBZ(file)) { + const { makeComicBook } = await import('./comic-book.js') + book = makeComicBook(loader, file) + } + else if (isFBZ(file)) { + const { makeFB2 } = await import('./fb2.js') + const { entries } = loader + const entry = entries.find(entry => entry.filename.endsWith('.fb2')) + const blob = await loader.loadBlob((entry ?? entries[0]).filename) + book = await makeFB2(blob) + } + else { + const { EPUB } = await import('./epub.js') + book = await new EPUB(loader).init() + } + } + else if (await isPDF(file)) { + const { makePDF } = await import('./pdf.js') + book = await makePDF(file) + } + else { + const { isMOBI, MOBI } = await import('./mobi.js') + if (await isMOBI(file)) { + const fflate = await import('./vendor/fflate.js') + book = await new MOBI({ unzlib: fflate.unzlibSync }).open(file) + } + else if (isFB2(file)) { + const { makeFB2 } = await import('./fb2.js') + book = await makeFB2(file) + } + } + if (!book) throw new UnsupportedTypeError('File type not supported') + return book +} + +class CursorAutohider { + #timeout + #el + #check + #state + constructor(el, check, state = {}) { + this.#el = el + this.#check = check + this.#state = state + if (this.#state.hidden) this.hide() + this.#el.addEventListener('mousemove', ({ screenX, screenY }) => { + // check if it actually moved + if (screenX === this.#state.x && screenY === this.#state.y) return + this.#state.x = screenX, this.#state.y = screenY + this.show() + if (this.#timeout) clearTimeout(this.#timeout) + if (check()) this.#timeout = setTimeout(this.hide.bind(this), 1000) + }, false) + } + cloneFor(el) { + return new CursorAutohider(el, this.#check, this.#state) + } + #hasSelection() { + const selection = this.#el.ownerDocument?.getSelection() + return selection ? !selection.isCollapsed : false + } + hide() { + // The pointer is what the reader aims a selection with, so leave it + // alone while one stands: a paused drag, or a double-click word + // select (which fires no mousemove at all), would otherwise blank it. + if (this.#hasSelection()) return + this.#el.style.cursor = 'none' + this.#state.hidden = true + } + show() { + this.#el.style.removeProperty('cursor') + this.#state.hidden = false + } +} + +class History extends EventTarget { + #arr = [] + #index = -1 + pushState(x) { + const last = this.#arr[this.#index] + if (last === x || last?.fraction && last.fraction === x.fraction) return + this.#arr[++this.#index] = x + this.#arr.length = this.#index + 1 + this.dispatchEvent(new Event('index-change')) + } + replaceState(x) { + const index = this.#index + this.#arr[index] = x + } + back() { + const index = this.#index + if (index <= 0) return + const detail = { state: this.#arr[index - 1] } + this.#index = index - 1 + this.dispatchEvent(new CustomEvent('popstate', { detail })) + this.dispatchEvent(new Event('index-change')) + } + forward() { + const index = this.#index + if (index >= this.#arr.length - 1) return + const detail = { state: this.#arr[index + 1] } + this.#index = index + 1 + this.dispatchEvent(new CustomEvent('popstate', { detail })) + this.dispatchEvent(new Event('index-change')) + } + get canGoBack() { + return this.#index > 0 + } + get canGoForward() { + return this.#index < this.#arr.length - 1 + } + clear() { + this.#arr = [] + this.#index = -1 + } +} + +const languageInfo = lang => { + if (!lang) return {} + try { + const canonical = Intl.getCanonicalLocales(lang)[0] + const locale = new Intl.Locale(canonical) + const isCJK = ['zh', 'ja', 'kr'].includes(locale.language) + const direction = (locale.getTextInfo?.() ?? locale.textInfo)?.direction + return { canonical, locale, isCJK, direction } + } catch (e) { + console.warn(e) + return {} + } +} + +export class View extends HTMLElement { + #root = this.attachShadow({ mode: 'open' }) + #sectionProgress + #tocProgress + #pageProgress + #cfiProgress + #searchResults = new Map() + #cursorAutohider = new CursorAutohider(this, () => + this.hasAttribute('autohide-cursor')) + isFixedLayout = false + lastLocation + history = new History() + constructor() { + super() + this.history.addEventListener('popstate', ({ detail }) => { + const resolved = this.resolveNavigation(detail.state) + this.renderer.goTo(resolved) + }) + } + async open(book) { + if (typeof book === 'string' + || typeof book.arrayBuffer === 'function' + || book.isDirectory) book = await makeBook(book) + this.book = book + this.language = languageInfo(book.metadata?.language) + + if (book.splitTOCHref && book.getTOCFragment) { + const ids = book.sections.map(s => s.id) + this.#sectionProgress = new SectionProgress(book.sections, 1500, 1600) + const splitHref = book.splitTOCHref.bind(book) + const getFragment = book.getTOCFragment.bind(book) + this.#tocProgress = new TOCProgress() + await this.#tocProgress.init({ + toc: book.toc ?? [], ids, splitHref, getFragment }) + this.#pageProgress = new TOCProgress() + await this.#pageProgress.init({ + toc: book.pageList ?? [], ids, splitHref, getFragment }) + } + this.#cfiProgress = new PageProgress(book, this.resolveNavigation.bind(this)) + + this.isFixedLayout = this.book.rendition?.layout === 'pre-paginated' + if (this.isFixedLayout) { + await import('./fixed-layout.js') + this.renderer = document.createElement('foliate-fxl') + } else { + await import('./paginator.js') + this.renderer = document.createElement('foliate-paginator') + } + this.renderer.setAttribute('exportparts', 'head,foot,filter,container') + this.renderer.addEventListener('load', e => this.#onLoad(e.detail)) + this.renderer.addEventListener('relocate', e => this.#onRelocate(e.detail)) + this.renderer.addEventListener('create-overlayer', e => + e.detail.attach(this.#createOverlayer(e.detail))) + this.renderer.open(book) + this.#root.append(this.renderer) + + if (book.sections.some(section => section.mediaOverlay)) { + const activeClass = book.media.activeClass + const playbackActiveClass = book.media.playbackActiveClass + this.mediaOverlay = book.getMediaOverlay() + let lastActive + this.mediaOverlay.addEventListener('highlight', e => { + const resolved = this.resolveNavigation(e.detail.text) + this.renderer.goTo(resolved) + .then(() => { + const { doc } = this.renderer.getContents() + .find(x => x.index = resolved.index) + const el = resolved.anchor(doc) + el.classList.add(activeClass) + if (playbackActiveClass) el.ownerDocument + .documentElement.classList.add(playbackActiveClass) + lastActive = new WeakRef(el) + }) + }) + this.mediaOverlay.addEventListener('unhighlight', () => { + const el = lastActive?.deref() + if (el) { + el.classList.remove(activeClass) + if (playbackActiveClass) el.ownerDocument + .documentElement.classList.remove(playbackActiveClass) + } + }) + } + } + close() { + this.renderer?.destroy() + this.renderer?.remove() + this.#sectionProgress = null + this.#tocProgress = null + this.#pageProgress = null + this.#cfiProgress = null + this.#searchResults = new Map() + this.lastLocation = null + this.history.clear() + this.tts = null + this.mediaOverlay = null + } + goToTextStart() { + return this.goTo(this.book.landmarks + ?.find(m => m.type.includes('bodymatter') || m.type.includes('text')) + ?.href ?? this.book.sections.findIndex(s => s.linear !== 'no')) + } + async init({ lastLocation, showTextStart }) { + const resolved = lastLocation ? this.resolveNavigation(lastLocation) : null + if (resolved) { + await this.renderer.goTo(resolved) + this.history.pushState(lastLocation) + } + else if (showTextStart) await this.goToTextStart() + else { + this.history.pushState(0) + await this.next() + } + } + #emit(name, detail, cancelable) { + return this.dispatchEvent(new CustomEvent(name, { detail, cancelable })) + } + #onRelocate({ reason, range, index, fraction, size }) { + const progress = this.#sectionProgress?.getProgress(index, fraction, size) ?? {} + const tocItem = this.#tocProgress?.getProgress(index, range) + const pageItem = this.#pageProgress?.getProgress(index, range) + const cfi = this.getCFI(index, range) + this.lastLocation = { ...progress, tocItem, pageItem, cfi, range } + if (reason === 'snap' || reason === 'page' || reason === 'scroll') + this.history.replaceState(cfi) + this.#emit('relocate', this.lastLocation) + } + #onLoad({ doc, index }) { + // set language and dir if not already set + doc.documentElement.lang ||= this.language.canonical ?? '' + if (!this.language.isCJK) + doc.documentElement.dir ||= this.language.direction ?? '' + + this.#handleLinks(doc, index) + this.#cursorAutohider.cloneFor(doc.documentElement) + + this.#emit('load', { doc, index }) + } + #handleLinks(doc, index) { + const { book } = this + const section = book.sections[index] + doc.addEventListener('click', e => { + const a = e.target.closest('a[href]') + if (!a) return + e.preventDefault() + const href_ = a.getAttribute('href') + const href = section?.resolveHref?.(href_) ?? href_ + if (book?.isExternal?.(href)) + Promise.resolve(this.#emit('external-link', { a, href }, true)) + .then(x => x ? globalThis.open(href, '_blank') : null) + .catch(e => console.error(e)) + else { + let internalHref = href + if (!book.resolveHref(href)) { + const hashIndex = href_.indexOf('#') + if (hashIndex >= 0) { + const hash = href_.slice(hashIndex) + internalHref = section?.resolveHref?.(hash) ?? href + } + } + Promise.resolve(this.#emit('link', { a, href: internalHref }, true)) + .then(x => x ? this.goTo(internalHref) : null) + .catch(e => console.error(e)) + } + }) + } + async addAnnotation(annotation, remove) { + const { value } = annotation + if (value.startsWith(SEARCH_PREFIX)) { + const cfi = value.replace(SEARCH_PREFIX, '') + const { index, anchor } = await this.resolveNavigation(cfi) + const obj = this.#getOverlayer(index) + if (obj) { + const { overlayer, doc } = obj + if (remove) { + overlayer.remove(value) + return + } + const range = doc ? anchor(doc) : anchor + if (range) overlayer.add(value, range, Overlayer.outline) + } + return + } else if (value.startsWith(NOTE_PREFIX)) { + const cfi = value.replace(NOTE_PREFIX, '') + const { index, anchor } = await this.resolveNavigation(cfi) + const obj = this.#getOverlayer(index) + if (obj) { + const { overlayer, doc } = obj + if (remove) { + overlayer.remove(value) + return + } + const range = doc ? anchor(doc) : anchor + if (range) { + const draw = (func, opts) => overlayer.add(value, range, func, opts) + this.#emit('draw-annotation', { draw, annotation, doc, range }) + } + } + return + } + const { index, anchor } = await this.resolveNavigation(value) + const obj = this.#getOverlayer(index) + if (obj) { + const { overlayer, doc } = obj + overlayer.remove(value) + if (!remove) { + const range = doc ? anchor(doc) : anchor + if (range) { + const draw = (func, opts) => overlayer.add(value, range, func, opts) + this.#emit('draw-annotation', { draw, annotation, doc, range }) + } + } + } + const label = this.#tocProgress?.getProgress(index)?.label ?? '' + return { index, label } + } + deleteAnnotation(annotation) { + return this.addAnnotation(annotation, true) + } + #getOverlayer(index) { + return this.renderer.getContents() + .find(x => x.index === index && x.overlayer) + } + #createOverlayer({ doc, index }) { + const overlayer = new Overlayer(doc) + doc.addEventListener('click', e => { + const [value, range, rect] = overlayer.hitTest(e) + if (value && !value.startsWith(SEARCH_PREFIX)) { + this.#emit('show-annotation', { value, index, range, rect }) + } + }, false) + + let lastHitTestTime = 0 + const THROTTLE_MS = 200 + const isAndroid = /Android/i.test(navigator.userAgent) + + doc.addEventListener('mousemove', (e) => { + if (isAndroid) return + const now = performance.now() + if (now - lastHitTestTime < THROTTLE_MS) return + lastHitTestTime = now + const [value] = overlayer.hitTest(e) + if (value && !value.startsWith(SEARCH_PREFIX)) { + doc.body.style.cursor = 'pointer' + } else { + doc.body.style.cursor = '' + } + }) + + const list = this.#searchResults.get(index) + if (list) for (const item of list) this.addAnnotation(item) + + this.#emit('create-overlay', { index }) + return overlayer + } + async showAnnotation(annotation) { + const { value } = annotation + const resolved = await this.goTo(value) + if (resolved) { + const { index, anchor } = resolved + const { doc } = this.#getOverlayer(index) + const range = anchor(doc) + this.#emit('show-annotation', { value, index, range }) + } + } + getCFI(index, range) { + const baseCFI = this.book.sections[index].cfi ?? CFI.fake.fromIndex(index) + if (!range) return baseCFI + return CFI.joinIndir(baseCFI, CFI.fromRange(range)) + } + resolveCFI(cfi) { + if (this.book.resolveCFI) + return this.book.resolveCFI(cfi) + else { + const parts = CFI.parse(cfi) + const index = CFI.fake.toIndex((parts.parent ?? parts).shift()) + const anchor = doc => CFI.toRange(doc, parts) + return { index, anchor } + } + } + resolveNavigation(target) { + try { + if (typeof target === 'number') return { index: target } + if (typeof target.fraction === 'number') { + const [index, anchor] = this.#sectionProgress.getSection(target.fraction) + return { index, anchor } + } + if (CFI.isCFI.test(target)) return this.resolveCFI(target) + return this.book.resolveHref(target) + } catch (e) { + console.error(e) + console.error(`Could not resolve target ${target}`) + } + } + async goTo(target) { + const resolved = this.resolveNavigation(target) + try { + await this.renderer.goTo(resolved) + this.history.pushState(target) + return resolved + } catch(e) { + console.error(e) + console.error(`Could not go to ${target}`) + } + } + async goToFraction(frac) { + const [index, anchor] = this.#sectionProgress.getSection(frac) + await this.renderer.goTo({ index, anchor }) + this.history.pushState({ fraction: frac }) + } + async select(target) { + try { + const obj = await this.resolveNavigation(target) + await this.renderer.goTo({ ...obj, select: true }) + this.history.pushState(target) + } catch(e) { + console.error(e) + console.error(`Could not go to ${target}`) + } + } + deselect() { + for (const { doc } of this.renderer.getContents()) + doc.defaultView.getSelection().removeAllRanges() + } + getSectionFractions() { + return (this.#sectionProgress?.sectionFractions ?? []) + .map(x => x + Number.EPSILON) + } + getProgressOf(index, range) { + const tocItem = this.#tocProgress?.getProgress(index, range) + const pageItem = this.#pageProgress?.getProgress(index, range) + return { tocItem, pageItem } + } + async getCFIProgress(cfi) { + const progress = await this.#cfiProgress?.getProgress(cfi) + if (!progress || progress.index === -1) return null + return this.#sectionProgress?.getProgress(progress.index, progress.fraction) + } + async getTOCItemOf(target) { + try { + const { index, anchor } = await this.resolveNavigation(target) + const doc = await this.book.sections[index].createDocument() + const frag = anchor(doc) + const isRange = frag instanceof Range + const range = isRange ? frag : doc.createRange() + if (!isRange) range.selectNodeContents(frag) + return this.#tocProgress?.getProgress(index, range) + } catch(e) { + console.error(e) + console.error(`Could not get ${target}`) + } + } + async prev(distance) { + await this.renderer.prev(distance) + } + async next(distance) { + await this.renderer.next(distance) + } + async pan(dx, dy) { + await this.renderer.pan(dx, dy) + } + isOverflowX() { + return this.renderer.isOverflowX + } + isOverflowY() { + return this.renderer.isOverflowY + } + goLeft() { + return this.book.dir === 'rtl' ? this.next() : this.prev() + } + goRight() { + return this.book.dir === 'rtl' ? this.prev() : this.next() + } + // A matcher result carries a primary `range` (nav/excerpt anchor) and, for + // nearby-words, per-word `subRanges` to highlight each matched word. + #toSearchMatch(index, { range, excerpt, subRanges }) { + const cfi = this.getCFI(index, range) + if (subRanges?.length) + return { cfi, cfis: subRanges.map(r => this.getCFI(index, r)), excerpt } + return { cfi, excerpt } + } + async * #searchSection(matcher, query, index) { + const doc = await this.book.sections[index].createDocument() + for (const match of matcher(doc, query)) + yield this.#toSearchMatch(index, match) + } + async * #searchBook(matcher, query) { + const { sections } = this.book + for (const [index, { createDocument }] of sections.entries()) { + if (!createDocument) continue + const doc = await createDocument() + const subitems = Array.from(matcher(doc, query), match => this.#toSearchMatch(index, match)) + const progress = (index + 1) / sections.length + yield { progress } + if (subitems.length) yield { index, subitems } + } + } + async * search(opts) { + this.clearSearch() + const { searchMatcher } = await import('./search.js') + const { sections } = this.book + const { query, index, results } = opts + const matcher = searchMatcher(textWalker, + { defaultLocale: this.language, ...opts }) + + const iter = results?.length + ? (async function* () { + for (const result of results) { + if (result.subitems) { + const progress = (result.index + 1) / sections.length + yield { progress } + yield { index: result.index, subitems: result.subitems } + } else { + yield { cfi: result.cfi, cfis: result.cfis, excerpt: result.excerpt } + } + } + })() + : index != null + ? this.#searchSection(matcher, query, index) + : this.#searchBook(matcher, query) + + const list = [] + const seen = new Set() + this.#searchResults.set(index, list) + // Add one annotation per unique CFI (a nearby-words match carries several + // via `cfis`); dedupe so overlapping CFIs don't collide in #searchResults. + const addHighlights = (cfis, sink, sinkSeen) => { + for (const cfi of cfis) { + if (sinkSeen.has(cfi)) continue + sinkSeen.add(cfi) + const item = { value: SEARCH_PREFIX + cfi } + sink.push(item) + this.addAnnotation(item) + } + } + + for await (const result of iter) { + if (result.subitems){ + const sectionList = [] + const sectionSeen = new Set() + for (const item of result.subitems) + addHighlights(item.cfis ?? [item.cfi], sectionList, sectionSeen) + this.#searchResults.set(result.index, sectionList) + yield { + index: result.index, + label: this.#tocProgress?.getProgress(result.index)?.label ?? '', + subitems: result.subitems, + } + } + else { + if (result.cfi) addHighlights(result.cfis ?? [result.cfi], list, seen) + yield result + } + } + yield 'done' + } + clearSearch() { + for (const list of this.#searchResults.values()) + for (const item of list) this.deleteAnnotation(item) + this.#searchResults.clear() + } + async initTTS(granularity = 'word', nodeFilter, highlighter) { + const contents = this.renderer.getContents() + const primaryIndex = this.renderer.primaryIndex + const primary = contents.find(x => x.index === primaryIndex) ?? contents[0] + const doc = primary?.doc + if (!doc) return + if (this.tts && this.tts.doc === doc) return + const { TTS } = await import('./tts.js') + this.tts = new TTS(doc, textWalker, nodeFilter, highlighter || (range => + this.renderer.scrollToAnchor(range, true)), granularity) + } + startMediaOverlay() { + const contents = this.renderer.getContents() + const primaryIndex = this.renderer.primaryIndex + const primary = contents.find(x => x.index === primaryIndex) ?? contents[0] + const { index } = primary ?? {} + return this.mediaOverlay.start(index) + } +} + +customElements.define('foliate-view', View) diff --git a/frontend/src/routes/(root)/(library)/book/[bookId]/+page.svelte b/frontend/src/routes/(root)/(library)/book/[bookId]/+page.svelte index 3502e99..2c2e1ed 100644 --- a/frontend/src/routes/(root)/(library)/book/[bookId]/+page.svelte +++ b/frontend/src/routes/(root)/(library)/book/[bookId]/+page.svelte @@ -1,28 +1,24 @@ - -- -