@prose-reader/koreader 29.0.0 → 30.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/cfi"),t=require("@prose-reader/shared");var n=new Set(`address.annotation.applet.article.aside.audio.author.autoboxing.blockquote.body.book-name.book-title.canvas.caption.center.city.col.colgroup.coverpage.custom-info.date.dd.details.dialog.dir.div.dl.docfragment.document-info.dt.email.empty-line.epigraph.fictionbook.fieldset.figcaption.figure.floatbox.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.header.hgroup.history.home-page.hr.html.id.iframe.isbn.keygen.keywords.lang.legend.li.listing.main.map.menu.multicol.nav.nickname.noframes.noscript.ol.optgroup.output.p.part.plaintext.poem.pre.program-used.publish-info.publisher.search.section.select.sequence.source.src-lang.src-ocr.src-title-info.src-url.stanza.subtitle.summary.table.tabularbox.tbody.td.text-author.textarea.tfoot.th.thead.title-info.tr.track.ul.v.version.video.xmp.year`.split(`.`)),r=new Set([`button`,`input`,`marquee`]),i=new Set([`area`,`base`,`basefont`,`bgsound`,`binary`,`clonenode`,`datalist`,`description`,`genre`,`head`,`link`,`meta`,`noembed`,`param`,`pseudoelem`,`script`,`style`,`stylesheet`,`template`,`title`]),a=new Set(`annotation.applet.area.audio.author.base.basefont.bgsound.clonenode.col.colgroup.coverpage.description.docfragment.document-info.embed.empty-line.epigraph.fictionbook.head.html.image.img.input.keygen.link.meta.object.param.part.poem.pseudoelem.publish-info.source.src-title-info.stanza.svg.table.tbody.tfoot.thead.title-info.tr.track`.split(`.`)),o=new Set([`listing`,`plaintext`,`pre`,`style`,`textarea`,`xmp`]),s=new Set([`desc`,`metadata`,`style`,`text`,`textpath`,`title`,`tspan`]),c=new Set(`annotation-xml.maction.maligngroup.malignmark.math.menclose.merror.mfenced.mfrac.mglyph.mi.mlabeledtr.mlongdiv.mmultiscripts.mn.mo.mover.mpadded.mphantom.mprescripts.mroot.mrow.ms.mscarries.mscarry.msgroup.msline.mspace.msqrt.msrow.mstack.mstyle.msub.msubsup.msup.mtable.mtd.mtext.mtr.munder.munderover.none.semantics`.split(`.`)),l=new Set([`mglyph`,`mi`,`mn`,`mo`,`mtext`,`mspace`,`ms`]),u=new Set([`script`,`style`]),d={table:new Set([`caption`,`col`,`colgroup`,`tbody`,`tfoot`,`thead`,`tr`]),tbody:new Set([`tr`]),tfoot:new Set([`tr`]),thead:new Set([`tr`]),tr:new Set([`td`,`th`])},f=e=>{let t=d[e.localName.toLowerCase()];if(!t)return!1;for(let n=e.firstElementChild;n;n=n.nextElementSibling)if(!t.has(n.localName.toLowerCase()))return!0;return!1},ee=new Set([`pre`,`textarea`]),te=1,p=e=>e.nodeType===te,m=(e,t)=>t?e.localName:e.localName.toLowerCase(),ne=new Set([`col`,`colgroup`,`tbody`,`td`,`tfoot`,`th`,`thead`,`tr`]),h=e=>i.has(e)?`none`:n.has(e)?`block`:r.has(e)?`inlineBlock`:e===`ruby`?`ruby`:`inline`,g=e=>{let t=e.localName.toLowerCase();return e.hasAttribute(`hidden`)&&!ne.has(t)?`none`:h(t)},_=e=>{let t=e.localName.toLowerCase(),n=!1,r=!1,i=t===`math`;for(let t=e.parentNode;t&&p(t);t=t.parentNode){let e=t.localName.toLowerCase();o.has(e)&&(n=!0),e===`svg`&&(r=!0),e===`math`&&(i=!0)}if(r){let e=s.has(t);return{display:`inline`,block:!1,allowsText:e,pre:n||e,stripsLeadingNewline:!1,trimsText:!1,restructured:!1,opaque:u.has(t),insideSvg:!0}}if(i)return{display:`inline`,block:!1,allowsText:l.has(t)||!c.has(t),pre:n,stripsLeadingNewline:!1,trimsText:!0,restructured:!0,opaque:!1,insideSvg:!1};let d=g(e);return{display:d,block:d===`block`||d===`inlineBlock`,allowsText:!a.has(t),pre:n||o.has(t),stripsLeadingNewline:ee.has(t),trimsText:!1,restructured:d===`ruby`,opaque:u.has(t),insideSvg:t===`svg`}},re=8192,v=9,y=10,b=13,x=32,S=e=>e===x||e===b||e===y||e===v,ie=e=>{for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n!==x&&n!==b&&n!==y&&n!==v)return!1}return!0},C=e=>e>65535?2:1,w=(e,t,n)=>{let r=0;if(!t.pre){let i=0,a=0,o=e.length;if(t.trim)for(;o>0&&S(e.charCodeAt(o-1));)o--;for(;r<e.length;){let s=e.codePointAt(r)??0,c=C(s),l=1;if(S(s)?(l=+(i===0),i++,t.trim&&(a===0||r>=o)&&(l=0)):i=0,a+=l,!n(r,c,l))return;r+=c}return}let i=0,a=0,o=t.stripsLeadingNewline;for(;r<e.length;){let t=e.codePointAt(r)??0,s=C(t),c=1,l=!1;if(t===b){let t=r+s<e.length?e.codePointAt(r+s):void 0;(r===0||i!==y)&&t!==y?(l=!0,i=y):c=0}else t===y?(l=!0,i=y):t===v?(c=8-(a&7),a+=c,i=v):(a++,i=t);if(l&&(a=1),c>0&&o&&(o=!1,l&&(c=0)),!n(r,s,c))return;r+=s}},T=(e,t,n)=>{let r=0;return w(e,n,(e,n,i)=>e+n>t?!1:(r+=i,!0)),r},E=(e,t,n)=>{if(t<0)return;let r=0,i;return w(e,n,(e,n,a)=>a===0?!0:r===t||r+a>t?(i=e,!1):(r+=a,!0)),i===void 0?r===t?e.length:void 0:i},D=e=>{if(e.length<8192)return[{start:0,end:e.length}];let t=[],n=0;for(;n<e.length;){let r=n,i=0;for(;r<e.length&&i<8192;)r+=C(e.codePointAt(r)??0),i++;if(i<8192){t.push({start:n,end:e.length});break}let a=r;for(let t=r-1;t>=n;t--){let n=e.charCodeAt(t);if(n===x){a=t+1;break}if(n===b||n===y){let i=t+1<r?e.charCodeAt(t+1):0;if(n===b&&i!==y||n===y&&i!==b){a=t+1;break}}}t.push({start:n,end:a}),n=a}return t},ae=3,oe=4,O=e=>e.nodeType===ae||e.nodeType===oe,k=(e,t)=>{let n=[],r=0;if(t.opaque)return n;for(let i=e.firstChild;i;i=i.nextSibling,r++){if(p(i)){let e=t.insideSvg?`inline`:g(i),a=e===`block`?`block`:e===`none`?`invisible`:`inline`;n.push({node:i,childIndex:r,role:a,whitespace:!1,first:n.length===0});continue}if(!O(i)||!t.allowsText)continue;let e=ie(i.data);e&&(t.block&&!t.pre&&n.length===0||t.trimsText)||n.push({node:i,childIndex:r,role:`text`,whitespace:e,first:n.length===0})}return n},A=e=>e.role===`inline`||e.role===`text`,j=e=>e!==void 0&&e.role===`text`&&e.whitespace,M=(e,t)=>{let n=()=>e.map(e=>({kind:`node`,child:e}));if(t.display===`block`||t.display===`inlineBlock`){let r=e.some(e=>e.role===`block`),i=e.some(A);if(!r||!i)return n();let a=[],o=0;for(;o<e.length;){let n=e[o];if(!n)break;if(!A(n)){a.push({kind:`node`,child:n}),o++;continue}let r=o;for(;r<e.length;){let t=e[r];if(!t||!A(t))break;r++}let i=e.slice(o,r);if(!t.pre){let e=0;for(;e<i.length&&j(i[e]);)e++;i=i.slice(e),i.length>0&&j(i[i.length-1])&&(i=i.slice(0,-1))}i.length>0&&a.push({kind:`box`,children:i}),o=r}return a}if(t.display===`inline`&&!t.pre){if(!e.some(e=>e.role===`block`))return n();let t=e.some(e=>e.role===`inline`),r=e.some(e=>e.role===`text`&&!e.whitespace);if(!t&&!r)return n();let i=new Set;return e.forEach((t,n)=>{if(t.role!==`block`)return;let r=e[n-1],a=e[n+1];r&&j(r)&&i.add(r),a&&j(a)&&i.add(a)}),e.filter(e=>!i.has(e)).map(e=>({kind:`node`,child:e}))}return n()},N=(e,t)=>M(k(e,t),t),P=(e,t,n)=>{if(p(e.node))return n({kind:`element`,node:e.node,childIndex:e.childIndex});let r=D(e.node.data);for(let i=0;i<r.length;i++){let a=r[i];if(a&&!n({kind:`text`,node:e.node,childIndex:e.childIndex,start:a.start,end:a.end,pieceIndex:i,pieceCount:r.length,mode:{pre:t.pre,stripsLeadingNewline:t.stripsLeadingNewline&&e.first&&i===0,trim:t.trimsText}}))return!1}return!0},F=(e,t,n=_(e))=>{for(let r of N(e,n)){let e=r.kind===`box`?r.children:[r.child];for(let r of e)if(!P(r,n,t))return}},I=(e,t=_(e))=>{let n=[];for(let r of N(e,t))if(r.kind===`box`){let e=r.children[0],i=r.children[r.children.length-1];e&&i&&P(e,t,e=>(n.push({child:e,lastChildIndex:i.childIndex}),!1))}else P(r.child,t,e=>(n.push({child:e,lastChildIndex:e.childIndex}),!0));return n},L=e=>e.body??void 0,R=e=>{let t=`/body/DocFragment[${e.spineItemIndex+1}]/body`;for(let n of e.steps)if(n.kind===`nodeIndex`)t+=`/${n.index}`;else{let e=n.kind===`text`?`text()`:n.name;t+=n.index===void 0?`/${e}`:`/${e}[${n.index}]`}return e.point===void 0?t:`${t}.${e.point}`},z=9,B=e=>e.nodeType===z,V=(e,t,n)=>{let r=m(t,n),i,a=0;if(F(e,e=>e.kind!==`element`||m(e.node,n)!==r||(a++,e.node===t&&(i=a),i===void 0||a===1)),i!==void 0)return{kind:`element`,name:r,index:a>1?i:void 0}},H=(e,t)=>{let n=[],r=e;for(;r!==t;){if(!r||!p(r))return;n.push(r),r=r.parentNode}n.reverse();let i=[],a=t,o=!1;for(let e of n){let t=V(a,e,o);if(!t)return;i.push(t),e.localName.toLowerCase()===`svg`&&(o=!0),a=e}return i},U=(e,t,n)=>{let r,i=0,a=0;if(F(e,e=>{if(e.kind!==`text`)return!0;if(a++,r===void 0&&e.node===t){let t=e.pieceIndex===e.pieceCount-1;(n<e.end||t)&&(r=e,i=a)}return r===void 0||a===1}),r)return{piece:r,index:i,count:a}},W=(e,t)=>{let n=_(e),r=0;for(let{child:i,lastChildIndex:a}of I(e,n)){if(i.childIndex>=t||t<=a)break;r++}return(n.restructured||f(e))&&r>0?void 0:r},G=(e,t)=>{if(!Number.isInteger(t)||t<0)return;let{node:n,offset:r}=e,i=B(n)?n:n.ownerDocument;if(!i)return;let a=L(i),o=(e,n)=>e===void 0?void 0:R({spineItemIndex:t,steps:e,point:n,containsBoxingElements:!1});if(!a)return B(n)||n===i.documentElement?o([],void 0):void 0;if(B(n))return;if(O(n)){let e=n.parentNode,t=r??0;if(!e||!p(e)||t<0||t>n.data.length)return;let i=U(e,n,t);if(!i)return;let{piece:s}=i,c=H(e,a);return c?(c.push({kind:`text`,index:i.count>1?i.index:void 0}),o(c,T(n.data.slice(s.start,s.end),t-s.start,s.mode))):void 0}if(!p(n))return;if(r===void 0)return o(H(n,a),void 0);if(r<0||r>n.childNodes.length)return;let s=W(n,r);return s===void 0?void 0:o(H(n,a),s)},K=[`autoBoxing`,`tabularBox`,`rubyBox`,`mathBox`,`floatBox`,`inlineBox`,`pseudoElem`],se=new Set(K.map(e=>e.toLowerCase())),q=e=>e>=48&&e<=57,ce=/^[A-Za-z_][A-Za-z0-9_:-]*$/,J=e=>e===void 0||e===`/`||e===`.`,Y=(e,t)=>{let n=t;for(;n<e.length&&q(e.charCodeAt(n));)n++;return n},le=e=>{let t=[],n=0;for(;n<e.length;){let r=e[n];if(r===`.`){let r=n+1,i=Y(e,r);return i===r||i!==e.length?void 0:{steps:t,point:Number(e.slice(r,i))}}if(r!==`/`)return;let i=n+1;if(q(e.charCodeAt(i))){let r=Y(e,i);if(!J(e[r]))return;let a=Number(e.slice(i,r));if(a<1)return;t.push({kind:`nodeIndex`,index:a}),n=r;continue}let a=i;for(;a<e.length&&!`[/.`.includes(e[a]??``);)a++;if(a===i)return;let o=e.slice(i,a),s,c=a;if(e[a]===`[`){let t=e.indexOf(`]`,a+1);if(t===-1)return;let n=e.slice(a+1,t);if(n.length===0||Y(n,0)!==n.length||(s=Number(n),s<1))return;c=t+1}if(!J(e[c]))return;if(o===`text()`)t.push({kind:`text`,index:s});else if(ce.test(o))t.push({kind:`element`,name:o,index:s});else return;n=c}return{steps:t,point:void 0}},X=(e,t)=>e?.kind===`element`&&e.name.toLowerCase()===t,Z=e=>{let t=le(e);if(!t)return;let[n,r,i,...a]=t.steps;if(!X(n,`body`)||(n.index??1)!==1||!X(r,`docfragment`))return;let o=(r.index??1)-1;if(i===void 0)return{spineItemIndex:o,steps:[],point:void 0,containsBoxingElements:!1};if(X(i,`body`)&&(i.index??1)===1)return{spineItemIndex:o,steps:a,point:t.point,containsBoxingElements:a.some(e=>e.kind===`element`&&se.has(e.name.toLowerCase()))}},ue=(e,t)=>{let n=t.index??1,r=t.kind===`element`?t.name.toLowerCase():void 0,i=0,a;return F(e,e=>t.kind===`nodeIndex`||(t.kind===`text`?e.kind===`text`:e.kind===`element`&&e.node.localName.toLowerCase()===r)?(i++,i!==n||(a=e,!1)):!0),a},de=(e,t,n)=>{let r=t[n]?.child;return r?r.kind===`text`&&r.pieceIndex>0?{node:r.node,offset:r.start}:{node:e,offset:r.childIndex}:{node:e,offset:e.childNodes.length}},Q=(e,t)=>{let n=typeof e==`string`?Z(e):e;if(!n||n.containsBoxingElements)return;let r=L(t);if(!r)return n.steps.length===0?{node:t.documentElement}:void 0;let i={kind:`parent`,node:r};for(let e of n.steps){if(i.kind===`text`)return;let t=ue(i.node,e);if(!t)return;i=t.kind===`element`?{kind:`parent`,node:t.node}:t}if(i.kind===`text`){if(n.point===void 0)return{node:i.node,offset:i.start};let e=E(i.node.data.slice(i.start,i.end),n.point,i.mode);return e===void 0?void 0:{node:i.node,offset:i.start+e}}let a=i.node;if(n.point===void 0)return{node:a};let o=I(a);if(!(n.point>o.length))return de(a,o,n.point)},$=e=>{let{node:t,offset:n}=e;if(!p(t)||n===void 0)return e;let r=t.childNodes[n];if(r)return O(r)?{node:r,offset:0}:{node:r};let i=t.lastChild;return i&&O(i)?{node:i,offset:i.data.length}:{node:t}},fe=(t,n)=>{try{let r=typeof t==`string`?Z(t):t;if(!r)return;let i=n(r.spineItemIndex);if(!i)return;let a=Q(r,i.document);if(!a)return;let{node:o,offset:s}=$(a);return(0,e.generate)({node:o,offset:O(o)?s:void 0,spineIndex:r.spineItemIndex,spineId:i.id})}catch{return}},pe=e=>Array.isArray(e)?e[0]:e,me=(t,n)=>{try{let r=(0,e.getEpubCfiSpineItemref)(t);if(!r)return;let{spineIndex:i}=r;if(!Number.isInteger(i)||i<0)return;let a=(0,e.parse)(t);if((0,e.isIndirectionOnly)(a))return R({spineItemIndex:i,steps:[],point:void 0,containsBoxingElements:!1});let o=n(i);if(!o)return;let s=(0,e.resolve)(a,o.document);return s.node?G(s.isRange?{node:s.node.startContainer,offset:s.node.startOffset}:{node:s.node,offset:O(s.node)?pe(s.offset)??0:void 0},i):void 0}catch{return}},he=1024,ge=[0,1024,4096,16384,65536,262144,1048576,4194304,16777216,67108864,268435456,1073741824],_e=async e=>{let n=await Promise.all(ge.filter(t=>t<e.size).map(async t=>new Uint8Array(await e.slice(t,t+he).arrayBuffer())));return(0,t.md5HexDigest)(n)},ve=e=>(0,t.md5HexDigest)([new TextEncoder().encode(e)]);exports.CRENGINE_TEXT_SPLIT_SIZE=re,exports.XPOINTER_BOXING_ELEMENT_NAMES=K,exports.cfiToXPointer=me,exports.generateXPointer=G,exports.getKoreaderFileDigest=_e,exports.getKoreaderFileNameDigest=ve,exports.parseXPointer=Z,exports.resolveXPointer=Q,exports.serializeXPointer=R,exports.toCfiPosition=$,exports.xPointerToCfi=fe;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/cfi"),t=require("@prose-reader/shared");var n=new Set(`address.annotation.applet.article.aside.audio.author.autoboxing.blockquote.body.book-name.book-title.canvas.caption.center.city.col.colgroup.coverpage.custom-info.date.dd.details.dialog.dir.div.dl.docfragment.document-info.dt.email.empty-line.epigraph.fictionbook.fieldset.figcaption.figure.floatbox.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.header.hgroup.history.home-page.hr.html.id.iframe.isbn.keygen.keywords.lang.legend.li.listing.main.map.menu.multicol.nav.nickname.noframes.noscript.ol.optgroup.output.p.part.plaintext.poem.pre.program-used.publish-info.publisher.search.section.select.sequence.source.src-lang.src-ocr.src-title-info.src-url.stanza.subtitle.summary.table.tabularbox.tbody.td.text-author.textarea.tfoot.th.thead.title-info.tr.track.ul.v.version.video.xmp.year`.split(`.`)),r=new Set([`button`,`input`,`marquee`]),i=new Set([`area`,`base`,`basefont`,`bgsound`,`binary`,`clonenode`,`datalist`,`description`,`genre`,`head`,`link`,`meta`,`noembed`,`param`,`pseudoelem`,`script`,`style`,`stylesheet`,`template`,`title`]),a=new Set(`annotation.applet.area.audio.author.base.basefont.bgsound.clonenode.col.colgroup.coverpage.description.docfragment.document-info.embed.empty-line.epigraph.fictionbook.head.html.image.img.input.keygen.link.meta.object.param.part.poem.pseudoelem.publish-info.source.src-title-info.stanza.svg.table.tbody.tfoot.thead.title-info.tr.track`.split(`.`)),o=new Set([`listing`,`plaintext`,`pre`,`style`,`textarea`,`xmp`]),s=new Set([`desc`,`metadata`,`style`,`text`,`textpath`,`title`,`tspan`]),c=new Set(`annotation-xml.maction.maligngroup.malignmark.math.menclose.merror.mfenced.mfrac.mglyph.mi.mlabeledtr.mlongdiv.mmultiscripts.mn.mo.mover.mpadded.mphantom.mprescripts.mroot.mrow.ms.mscarries.mscarry.msgroup.msline.mspace.msqrt.msrow.mstack.mstyle.msub.msubsup.msup.mtable.mtd.mtext.mtr.munder.munderover.none.semantics`.split(`.`)),l=new Set([`mglyph`,`mi`,`mn`,`mo`,`mtext`,`mspace`,`ms`]),u=new Set([`script`,`style`]),d={table:new Set([`caption`,`col`,`colgroup`,`tbody`,`tfoot`,`thead`,`tr`]),tbody:new Set([`tr`]),tfoot:new Set([`tr`]),thead:new Set([`tr`]),tr:new Set([`td`,`th`])},f=e=>{let t=d[e.localName.toLowerCase()];if(!t)return!1;for(let n=e.firstElementChild;n;n=n.nextElementSibling)if(!t.has(n.localName.toLowerCase()))return!0;return!1},ee=new Set([`pre`,`textarea`]),te=1,p=e=>e.nodeType===te,m=(e,t)=>t?e.localName:e.localName.toLowerCase(),ne=new Set([`col`,`colgroup`,`tbody`,`td`,`tfoot`,`th`,`thead`,`tr`]),h=e=>i.has(e)?`none`:n.has(e)?`block`:r.has(e)?`inlineBlock`:e===`ruby`?`ruby`:`inline`,g=e=>{let t=e.localName.toLowerCase();return e.hasAttribute(`hidden`)&&!ne.has(t)?`none`:h(t)},_=e=>{let t=e.localName.toLowerCase(),n=!1,r=!1,i=t===`math`;for(let t=e.parentNode;t&&p(t);t=t.parentNode){let e=t.localName.toLowerCase();o.has(e)&&(n=!0),e===`svg`&&(r=!0),e===`math`&&(i=!0)}if(r){let e=s.has(t);return{display:`inline`,block:!1,allowsText:e,pre:n||e,stripsLeadingNewline:!1,trimsText:!1,restructured:!1,opaque:u.has(t),insideSvg:!0}}if(i)return{display:`inline`,block:!1,allowsText:l.has(t)||!c.has(t),pre:n,stripsLeadingNewline:!1,trimsText:!0,restructured:!0,opaque:!1,insideSvg:!1};let d=g(e);return{display:d,block:d===`block`||d===`inlineBlock`,allowsText:!a.has(t),pre:n||o.has(t),stripsLeadingNewline:ee.has(t),trimsText:!1,restructured:d===`ruby`,opaque:u.has(t),insideSvg:t===`svg`}},re=8192,v=9,y=10,b=13,x=32,S=e=>e===x||e===b||e===y||e===v,ie=e=>{for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n!==x&&n!==b&&n!==y&&n!==v)return!1}return!0},C=e=>e>65535?2:1,w=(e,t,n)=>{let r=0;if(!t.pre){let i=0,a=0,o=e.length;if(t.trim)for(;o>0&&S(e.charCodeAt(o-1));)o--;for(;r<e.length;){let s=e.codePointAt(r)??0,c=C(s),l=1;if(S(s)?(l=+(i===0),i++,t.trim&&(a===0||r>=o)&&(l=0)):i=0,a+=l,!n(r,c,l))return;r+=c}return}let i=0,a=0,o=t.stripsLeadingNewline;for(;r<e.length;){let t=e.codePointAt(r)??0,s=C(t),c=1,l=!1;if(t===b){let t=r+s<e.length?e.codePointAt(r+s):void 0;(r===0||i!==y)&&t!==y?(l=!0,i=y):c=0}else t===y?(l=!0,i=y):t===v?(c=8-(a&7),a+=c,i=v):(a++,i=t);if(l&&(a=1),c>0&&o&&(o=!1,l&&(c=0)),!n(r,s,c))return;r+=s}},T=(e,t,n)=>{let r=0;return w(e,n,(e,n,i)=>e+n>t?!1:(r+=i,!0)),r},E=(e,t,n)=>{if(t<0)return;let r=0,i;return w(e,n,(e,n,a)=>a===0?!0:r===t||r+a>t?(i=e,!1):(r+=a,!0)),i===void 0?r===t?e.length:void 0:i},D=e=>{if(e.length<8192)return[{start:0,end:e.length}];let t=[],n=0;for(;n<e.length;){let r=n,i=0;for(;r<e.length&&i<8192;)r+=C(e.codePointAt(r)??0),i++;if(i<8192){t.push({start:n,end:e.length});break}let a=r;for(let t=r-1;t>=n;t--){let n=e.charCodeAt(t);if(n===x){a=t+1;break}if(n===b||n===y){let i=t+1<r?e.charCodeAt(t+1):0;if(n===b&&i!==y||n===y&&i!==b){a=t+1;break}}}t.push({start:n,end:a}),n=a}return t},ae=3,oe=4,O=e=>e.nodeType===ae||e.nodeType===oe,k=(e,t)=>{let n=[],r=0;if(t.opaque)return n;for(let i=e.firstChild;i;i=i.nextSibling,r++){if(p(i)){let e=t.insideSvg?`inline`:g(i),a=e===`block`?`block`:e===`none`?`invisible`:`inline`;n.push({node:i,childIndex:r,role:a,whitespace:!1,first:n.length===0});continue}if(!O(i)||!t.allowsText)continue;let e=ie(i.data);e&&(t.block&&!t.pre&&n.length===0||t.trimsText)||n.push({node:i,childIndex:r,role:`text`,whitespace:e,first:n.length===0})}return n},A=e=>e.role===`inline`||e.role===`text`,j=e=>e!==void 0&&e.role===`text`&&e.whitespace,M=(e,t)=>{let n=()=>e.map(e=>({kind:`node`,child:e}));if(t.display===`block`||t.display===`inlineBlock`){let r=e.some(e=>e.role===`block`),i=e.some(A);if(!r||!i)return n();let a=[],o=0;for(;o<e.length;){let n=e[o];if(!n)break;if(!A(n)){a.push({kind:`node`,child:n}),o++;continue}let r=o;for(;r<e.length;){let t=e[r];if(!t||!A(t))break;r++}let i=e.slice(o,r);if(!t.pre){let e=0;for(;e<i.length&&j(i[e]);)e++;i=i.slice(e),i.length>0&&j(i[i.length-1])&&(i=i.slice(0,-1))}i.length>0&&a.push({kind:`box`,children:i}),o=r}return a}if(t.display===`inline`&&!t.pre){if(!e.some(e=>e.role===`block`))return n();let t=e.some(e=>e.role===`inline`),r=e.some(e=>e.role===`text`&&!e.whitespace);if(!t&&!r)return n();let i=new Set;return e.forEach((t,n)=>{if(t.role!==`block`)return;let r=e[n-1],a=e[n+1];r&&j(r)&&i.add(r),a&&j(a)&&i.add(a)}),e.filter(e=>!i.has(e)).map(e=>({kind:`node`,child:e}))}return n()},N=(e,t)=>M(k(e,t),t),P=(e,t,n)=>{if(p(e.node))return n({kind:`element`,node:e.node,childIndex:e.childIndex});let r=D(e.node.data);for(let i=0;i<r.length;i++){let a=r[i];if(a&&!n({kind:`text`,node:e.node,childIndex:e.childIndex,start:a.start,end:a.end,pieceIndex:i,pieceCount:r.length,mode:{pre:t.pre,stripsLeadingNewline:t.stripsLeadingNewline&&e.first&&i===0,trim:t.trimsText}}))return!1}return!0},F=(e,t,n=_(e))=>{for(let r of N(e,n)){let e=r.kind===`box`?r.children:[r.child];for(let r of e)if(!P(r,n,t))return}},I=(e,t=_(e))=>{let n=[];for(let r of N(e,t))if(r.kind===`box`){let e=r.children[0],i=r.children[r.children.length-1];e&&i&&P(e,t,e=>(n.push({child:e,lastChildIndex:i.childIndex}),!1))}else P(r.child,t,e=>(n.push({child:e,lastChildIndex:e.childIndex}),!0));return n},L=e=>e.body??void 0,R=e=>{let t=`/body/DocFragment[${e.spineItemIndex+1}]/body`;for(let n of e.steps)if(n.kind===`nodeIndex`)t+=`/${n.index}`;else{let e=n.kind===`text`?`text()`:n.name;t+=n.index===void 0?`/${e}`:`/${e}[${n.index}]`}return e.point===void 0?t:`${t}.${e.point}`},z=9,B=e=>e.nodeType===z,V=(e,t,n)=>{let r=m(t,n),i,a=0;if(F(e,e=>e.kind!==`element`||m(e.node,n)!==r||(a++,e.node===t&&(i=a),i===void 0||a===1)),i!==void 0)return{kind:`element`,name:r,index:a>1?i:void 0}},H=(e,t)=>{let n=[],r=e;for(;r!==t;){if(!r||!p(r))return;n.push(r),r=r.parentNode}n.reverse();let i=[],a=t,o=!1;for(let e of n){let t=V(a,e,o);if(!t)return;i.push(t),e.localName.toLowerCase()===`svg`&&(o=!0),a=e}return i},U=(e,t,n)=>{let r,i=0,a=0;if(F(e,e=>{if(e.kind!==`text`)return!0;if(a++,r===void 0&&e.node===t){let t=e.pieceIndex===e.pieceCount-1;(n<e.end||t)&&(r=e,i=a)}return r===void 0||a===1}),r)return{piece:r,index:i,count:a}},W=(e,t)=>{let n=_(e),r=0;for(let{child:i,lastChildIndex:a}of I(e,n)){if(i.childIndex>=t||t<=a)break;r++}return(n.restructured||f(e))&&r>0?void 0:r},G=(e,t)=>{if(!Number.isInteger(t)||t<0)return;let{node:n,offset:r}=e,i=B(n)?n:n.ownerDocument;if(!i)return;let a=L(i),o=(e,n)=>e===void 0?void 0:R({spineItemIndex:t,steps:e,point:n,containsBoxingElements:!1});if(!a)return B(n)||n===i.documentElement?o([],void 0):void 0;if(B(n))return;if(O(n)){let e=n.parentNode,t=r??0;if(!e||!p(e)||t<0||t>n.data.length)return;let i=U(e,n,t);if(!i)return;let{piece:s}=i,c=H(e,a);return c?(c.push({kind:`text`,index:i.count>1?i.index:void 0}),o(c,T(n.data.slice(s.start,s.end),t-s.start,s.mode))):void 0}if(!p(n))return;if(r===void 0)return o(H(n,a),void 0);if(r<0||r>n.childNodes.length)return;let s=W(n,r);return s===void 0?void 0:o(H(n,a),s)},K=[`autoBoxing`,`tabularBox`,`rubyBox`,`mathBox`,`floatBox`,`inlineBox`,`pseudoElem`],se=new Set(K.map(e=>e.toLowerCase())),q=e=>e>=48&&e<=57,ce=/^[A-Za-z_][A-Za-z0-9_:-]*$/,J=e=>e===void 0||e===`/`||e===`.`,Y=(e,t)=>{let n=t;for(;n<e.length&&q(e.charCodeAt(n));)n++;return n},le=e=>{let t=[],n=0;for(;n<e.length;){let r=e[n];if(r===`.`){let r=n+1,i=Y(e,r);return i===r||i!==e.length?void 0:{steps:t,point:Number(e.slice(r,i))}}if(r!==`/`)return;let i=n+1;if(q(e.charCodeAt(i))){let r=Y(e,i);if(!J(e[r]))return;let a=Number(e.slice(i,r));if(a<1)return;t.push({kind:`nodeIndex`,index:a}),n=r;continue}let a=i;for(;a<e.length&&!`[/.`.includes(e[a]??``);)a++;if(a===i)return;let o=e.slice(i,a),s,c=a;if(e[a]===`[`){let t=e.indexOf(`]`,a+1);if(t===-1)return;let n=e.slice(a+1,t);if(n.length===0||Y(n,0)!==n.length||(s=Number(n),s<1))return;c=t+1}if(!J(e[c]))return;if(o===`text()`)t.push({kind:`text`,index:s});else if(ce.test(o))t.push({kind:`element`,name:o,index:s});else return;n=c}return{steps:t,point:void 0}},X=(e,t)=>e?.kind===`element`&&e.name.toLowerCase()===t,Z=e=>{let t=le(e);if(!t)return;let[n,r,i,...a]=t.steps;if(!X(n,`body`)||(n.index??1)!==1||!X(r,`docfragment`))return;let o=(r.index??1)-1;if(i===void 0)return{spineItemIndex:o,steps:[],point:void 0,containsBoxingElements:!1};if(X(i,`body`)&&(i.index??1)===1)return{spineItemIndex:o,steps:a,point:t.point,containsBoxingElements:a.some(e=>e.kind===`element`&&se.has(e.name.toLowerCase()))}},ue=(e,t)=>{let n=t.index??1,r=t.kind===`element`?t.name.toLowerCase():void 0,i=0,a;return F(e,e=>t.kind===`nodeIndex`||(t.kind===`text`?e.kind===`text`:e.kind===`element`&&e.node.localName.toLowerCase()===r)?(i++,i!==n||(a=e,!1)):!0),a},de=(e,t,n)=>{let r=t[n]?.child;return r?r.kind===`text`&&r.pieceIndex>0?{node:r.node,offset:r.start}:{node:e,offset:r.childIndex}:{node:e,offset:e.childNodes.length}},Q=(e,t)=>{let n=typeof e==`string`?Z(e):e;if(!n||n.containsBoxingElements)return;let r=L(t);if(!r)return n.steps.length===0?{node:t.documentElement}:void 0;let i={kind:`parent`,node:r};for(let e of n.steps){if(i.kind===`text`)return;let t=ue(i.node,e);if(!t)return;i=t.kind===`element`?{kind:`parent`,node:t.node}:t}if(i.kind===`text`){if(n.point===void 0)return{node:i.node,offset:i.start};let e=E(i.node.data.slice(i.start,i.end),n.point,i.mode);return e===void 0?void 0:{node:i.node,offset:i.start+e}}let a=i.node;if(n.point===void 0)return{node:a};let o=I(a);if(!(n.point>o.length))return de(a,o,n.point)},$=e=>{let{node:t,offset:n}=e;if(!p(t)||n===void 0)return e;let r=t.childNodes[n];if(r)return O(r)?{node:r,offset:0}:{node:r};let i=t.lastChild;return i&&O(i)?{node:i,offset:i.data.length}:{node:t}},fe=(t,n)=>{try{let r=typeof t==`string`?Z(t):t;if(!r)return;let i=n(r.spineItemIndex);if(!i)return;let a=Q(r,i.document);if(!a)return;let{node:o,offset:s}=$(a);return(0,e.generate)({node:o,offset:O(o)?s:void 0,spineIndex:r.spineItemIndex,spineId:i.id})}catch{return}},pe=e=>Array.isArray(e)?e[0]:e,me=(t,n)=>{try{let r=(0,e.getEpubCfiSpineItemref)(t);if(!r)return;let{spineIndex:i}=r;if(!Number.isInteger(i)||i<0)return;let a=(0,e.parse)(t);if((0,e.isIndirectionOnly)(a))return R({spineItemIndex:i,steps:[],point:void 0,containsBoxingElements:!1});let o=n(i);if(!o)return;let s=(0,e.resolve)(a,o.document);return s.node?G(s.isRange?{node:s.node.startContainer,offset:s.node.startOffset}:{node:s.node,offset:O(s.node)?pe(s.offset)??0:void 0},i):void 0}catch{return}},he=1024,ge=[0,1024,4096,16384,65536,262144,1048576,4194304,16777216,67108864,268435456,1073741824],_e=async e=>{let n=await Promise.all(ge.filter(t=>t<e.size).map(async t=>new Uint8Array(await e.slice(t,t+he).arrayBuffer())));return(0,t.md5HexDigest)(n)},ve=e=>(0,t.md5HexDigest)([new TextEncoder().encode(e)]),ye=new Set([`application/epub+zip`,`application/epub`]),be=new Set([`application/pdf`,`application/vnd.comicbook+zip`,`application/x-cbz`,`application/vnd.comicbook-rar`,`application/vnd.rar`,`application/x-cbr`,`application/vnd.comicbook+tar`,`application/x-cbt`]),xe=e=>{let t=e.split(`;`)[0]?.trim().toLowerCase()??``;if(ye.has(t))return`xpointer`;if(be.has(t))return`page`};exports.CRENGINE_TEXT_SPLIT_SIZE=re,exports.XPOINTER_BOXING_ELEMENT_NAMES=K,exports.cfiToXPointer=me,exports.generateXPointer=G,exports.getKoreaderFileDigest=_e,exports.getKoreaderFileNameDigest=ve,exports.getKoreaderProgressKind=xe,exports.parseXPointer=Z,exports.resolveXPointer=Q,exports.serializeXPointer=R,exports.toCfiPosition=$,exports.xPointerToCfi=fe;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["generate","getEpubCfiSpineItemref","parse","isIndirectionOnly","resolve","md5HexDigest","_prose_reader_cfi","_prose_reader_shared","BLOCK_ELEMENTS","INLINE_BLOCK_ELEMENTS","INVISIBLE_ELEMENTS","NO_TEXT_ELEMENTS","PRE_ELEMENTS","SVG_TEXT_ELEMENTS","MATHML_ELEMENTS","MATHML_TOKEN_ELEMENTS","RAW_TEXT_ELEMENTS","PROPER_TABLE_CHILDREN","hasImproperTableChildren","proper","element","child","LEADING_NEWLINE_STRIPPING_ELEMENTS","ELEMENT_NODE","isElement","node","crengineElementName","insideSvg","TABLE_PART_ELEMENTS","getDisplayByName","name","getElementDisplay","getParentContext","parent","pre","insideMath","ancestor","ancestorName","allowsText","display","CRENGINE_TEXT_SPLIT_SIZE","TAB","LF","CR","SPACE","isSpaceLike","codePoint","isEmptySpace","index","text","charCode","codePointLength","visitCodePoints","mode","precedingSpaces","emitted","lastKept","length","units","visit","lastChar","column","stripNewline","newline","next","toCrengineOffset","offset","rawIndex","rawLength","rawOffset","toRawOffset","crengineOffset","splitTextPieces","pieces","start","windowEnd","codePoints","end","TEXT_NODE","CDATA_SECTION_NODE","isTextNode","parseChildren","children","childIndex","context","role","whitespace","isRunItem","isWhitespaceText","renderChildren","asNodes","hasBlockItems","hasInline","rendered","run","first","hasInlineNodes","hasNonEmptyText","removed","previous","getRenderedChildren","visitParsedChild","pieceIndex","piece","visitCrengineChildren","entry","getCrengineBoxedChildren","last","getFragmentBody","document","serializeXPointer","output","parsed","step","DOCUMENT_NODE","isDocument","elementStep","count","stepsTo","chain","body","steps","current","findTextPiece","found","foundIndex","childIndexBefore","lastChildIndex","generateXPointer","spineItemIndex","position","build","point","XPOINTER_BOXING_ELEMENT_NAMES","boxingElementNames","isDigit","ELEMENT_NAME","isStepBoundary","char","readDigits","from","input","tokenize","prefix","digitsStart","digitsEnd","stepStart","nameEnd","stepEnd","closing","digits","isElementNamed","parseXPointer","tokenized","root","fragment","findChild","wantedIndex","wantedName","positionOfChild","resolveXPointer","xpointer","toCfiPosition","xPointerToCfi","spineItem","getSpineItem","firstOffset","cfiToXPointer","itemref","cfi","spineIndex","resolved","PARTIAL_MD5_SAMPLE_SIZE","PARTIAL_MD5_SAMPLE_OFFSETS","getKoreaderFileDigest","samples","file","getKoreaderFileNameDigest","fileName"],"sources":["../src/crengine/elements.ts","../src/crengine/text.ts","../src/crengine/children.ts","../src/serialize.ts","../src/generate.ts","../src/parse.ts","../src/resolve.ts","../src/cfi.ts","../src/digest.ts"],"sourcesContent":["/**\n * How crengine classifies elements while it loads a spine item, from its\n * built-in element table (crengine/include/fb2def.h) and the stylesheet\n * KOReader applies before any book CSS (data/html5.css, imported by\n * data/epub.css). These defaults decide what happens to text nodes in\n * `ldomElementWriter::onText` and `ldomNode::initNodeRendMethod`\n * (crengine/src/lvtinydom.cpp):\n *\n * - `display` — whether whitespace-only text children are dropped,\n * - `allow_text` — elements such as `<table>` or `<tr>` drop every text child,\n * - `white-space` — pre-like elements keep whitespace verbatim.\n *\n * Only these defaults are known here. crengine takes the values from the\n * computed style, so a book stylesheet that changes `display` or\n * `white-space` (class rules, inline styles) is not seen: this is the\n * package's documented approximation.\n */\n\n/**\n * Elements whose default `display` is neither `inline` nor `none`: block,\n * list-item, table and table parts. `isBlockNode` in crengine treats all of\n * them alike (`display > css_d_inline && display != css_d_none`). Unknown\n * elements are inline.\n */\nconst BLOCK_ELEMENTS = new Set([\n \"address\",\n \"annotation\",\n \"applet\",\n \"article\",\n \"aside\",\n \"audio\",\n \"author\",\n \"autoboxing\",\n \"blockquote\",\n \"body\",\n \"book-name\",\n \"book-title\",\n \"canvas\",\n \"caption\",\n \"center\",\n \"city\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"custom-info\",\n \"date\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"docfragment\",\n \"document-info\",\n \"dt\",\n \"email\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"floatbox\",\n \"footer\",\n \"form\",\n \"frame\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"header\",\n \"hgroup\",\n \"history\",\n \"home-page\",\n \"hr\",\n \"html\",\n \"id\",\n \"iframe\",\n \"isbn\",\n \"keygen\",\n \"keywords\",\n \"lang\",\n \"legend\",\n \"li\",\n \"listing\",\n \"main\",\n \"map\",\n \"menu\",\n \"multicol\",\n \"nav\",\n \"nickname\",\n \"noframes\",\n \"noscript\",\n \"ol\",\n \"optgroup\",\n \"output\",\n \"p\",\n \"part\",\n \"plaintext\",\n \"poem\",\n \"pre\",\n \"program-used\",\n \"publish-info\",\n \"publisher\",\n \"search\",\n \"section\",\n \"select\",\n \"sequence\",\n \"source\",\n \"src-lang\",\n \"src-ocr\",\n \"src-title-info\",\n \"src-url\",\n \"stanza\",\n \"subtitle\",\n \"summary\",\n \"table\",\n \"tabularbox\",\n \"tbody\",\n \"td\",\n \"text-author\",\n \"textarea\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n \"ul\",\n \"v\",\n \"version\",\n \"video\",\n \"xmp\",\n \"year\",\n])\n\n/** `display: inline-block` in html5.css: a block for its own children, an inline item for its parent. */\nconst INLINE_BLOCK_ELEMENTS = new Set([\"button\", \"input\", \"marquee\"])\n\n/** `display: none` (fb2def.h and html5.css): invisible, not counted as inline nor block by the parent. */\nconst INVISIBLE_ELEMENTS = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"binary\",\n \"clonenode\",\n \"datalist\",\n \"description\",\n \"genre\",\n \"head\",\n \"link\",\n \"meta\",\n \"noembed\",\n \"param\",\n \"pseudoelem\",\n \"script\",\n \"style\",\n \"stylesheet\",\n \"template\",\n \"title\",\n])\n\n/** Elements defined with `allow_text = false` in fb2def.h: their text children are never inserted. */\nconst NO_TEXT_ELEMENTS = new Set([\n \"annotation\",\n \"applet\",\n \"area\",\n \"audio\",\n \"author\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"clonenode\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"description\",\n \"docfragment\",\n \"document-info\",\n \"embed\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"head\",\n \"html\",\n \"image\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"object\",\n \"param\",\n \"part\",\n \"poem\",\n \"pseudoelem\",\n \"publish-info\",\n \"source\",\n \"src-title-info\",\n \"stanza\",\n \"svg\",\n \"table\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n])\n\n/** Elements whose default `white-space` is `pre` or `pre-wrap` (fb2def.h, html5.css). */\nconst PRE_ELEMENTS = new Set([\n \"listing\",\n \"plaintext\",\n \"pre\",\n \"style\",\n \"textarea\",\n \"xmp\",\n])\n\n/**\n * Inside `<svg>` crengine only keeps text under these elements, verbatim\n * (`ldomElementWriter` constructor, `_insideSVG`).\n */\nconst SVG_TEXT_ELEMENTS = new Set([\n \"desc\",\n \"metadata\",\n \"style\",\n \"text\",\n \"textpath\",\n \"title\",\n \"tspan\",\n])\n\n/**\n * MathML elements crengine knows (fb2def.h, `EL_MATHML_START`..`EL_MATHML_END`).\n * Inside `<math>`, `MathMLHelper::getMathMLAdjustedText` (crengine/src/mathml.cpp)\n * drops the text of every one of them except the token elements, whose text\n * it trims; text under any other element is trimmed too.\n */\nconst MATHML_ELEMENTS = new Set([\n \"annotation-xml\",\n \"maction\",\n \"maligngroup\",\n \"malignmark\",\n \"math\",\n \"menclose\",\n \"merror\",\n \"mfenced\",\n \"mfrac\",\n \"mglyph\",\n \"mi\",\n \"mlabeledtr\",\n \"mlongdiv\",\n \"mmultiscripts\",\n \"mn\",\n \"mo\",\n \"mover\",\n \"mpadded\",\n \"mphantom\",\n \"mprescripts\",\n \"mroot\",\n \"mrow\",\n \"ms\",\n \"mscarries\",\n \"mscarry\",\n \"msgroup\",\n \"msline\",\n \"mspace\",\n \"msqrt\",\n \"msrow\",\n \"mstack\",\n \"mstyle\",\n \"msub\",\n \"msubsup\",\n \"msup\",\n \"mtable\",\n \"mtd\",\n \"mtext\",\n \"mtr\",\n \"munder\",\n \"munderover\",\n \"none\",\n \"semantics\",\n])\n\n/** `EL_MATHML_TOKEN_START`..`EL_MATHML_TOKEN_END`: the MathML elements that hold text. */\nconst MATHML_TOKEN_ELEMENTS = new Set([\n \"mglyph\",\n \"mi\",\n \"mn\",\n \"mo\",\n \"mtext\",\n \"mspace\",\n \"ms\",\n])\n\n/**\n * crengine's parser reads the content of these as one raw text node up to the\n * closing tag (`LVXMLParser::ReadText`, `m_in_html_style_tag` /\n * `m_in_html_script_tag`), so the elements an XML parser finds inside them\n * do not exist for it. Nothing is modelled inside them.\n */\nconst RAW_TEXT_ELEMENTS = new Set([\"script\", \"style\"])\n\n/**\n * The children a table part may hold without crengine wrapping them in a\n * `tabularBox` (`initTableRendMethods`, \"Generate missing child wrappers\"):\n * any other element child makes the part's child count unknowable.\n */\nconst PROPER_TABLE_CHILDREN: Record<string, Set<string>> = {\n table: new Set([\n \"caption\",\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"tr\",\n ]),\n tbody: new Set([\"tr\"]),\n tfoot: new Set([\"tr\"]),\n thead: new Set([\"tr\"]),\n tr: new Set([\"td\", \"th\"]),\n}\n\n/** Whether crengine had to complete this table part with wrappers of its own. */\nexport const hasImproperTableChildren = (element: Element) => {\n const proper = PROPER_TABLE_CHILDREN[element.localName.toLowerCase()]\n\n if (!proper) return false\n\n for (\n let child = element.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n if (!proper.has(child.localName.toLowerCase())) return true\n }\n\n return false\n}\n\n/** `pre` and `textarea` lose a newline immediately following their start tag (`_stripLeadingNewlineChar`, DOM >= 20210904). */\nconst LEADING_NEWLINE_STRIPPING_ELEMENTS = new Set([\"pre\", \"textarea\"])\n\n/**\n * What an element is to crengine's rendering pass: `block` and `inlineBlock`\n * contain blocks or a final line box, `inline` flows in its parent's text,\n * `none` is invisible, `ruby` is inline with its own layout.\n */\nexport type ElementDisplay =\n | \"block\"\n | \"inlineBlock\"\n | \"inline\"\n | \"none\"\n | \"ruby\"\n\nexport type ParentContext = {\n display: ElementDisplay\n /** Whitespace-only first text child dropped at parse time (`_isBlock`). */\n block: boolean\n /** Text children inserted at all (`_allowText`). */\n allowsText: boolean\n /** Text kept verbatim, tabs expanded (`TXTFLG_PRE`). */\n pre: boolean\n stripsLeadingNewline: boolean\n /** Inside `<math>`: text trimmed of surrounding blanks, dropped when nothing is left. */\n trimsText: boolean\n /** Ruby and MathML: crengine rebuilds the children with its own wrappers, so child indexes are not knowable. */\n restructured: boolean\n /** `<style>` / `<script>`: raw text for crengine, no children modelled. */\n opaque: boolean\n /** Descendant of `<svg>`: crengine keeps element names case-sensitive there. */\n insideSvg: boolean\n}\n\nconst ELEMENT_NODE = 1\n\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === ELEMENT_NODE\n\n/** The name crengine's XML parser stored: lowercased, except inside `<svg>` (`TXTFLG_CASE_SENSITIVE_TAGS_ATTRS`). */\nexport const crengineElementName = (element: Element, insideSvg: boolean) =>\n insideSvg ? element.localName : element.localName.toLowerCase()\n\n/** Table parts keep their display under `[hidden]` (html5.css re-sets it, the table algorithm needs them). */\nconst TABLE_PART_ELEMENTS = new Set([\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"tr\",\n])\n\nconst getDisplayByName = (name: string): ElementDisplay => {\n if (INVISIBLE_ELEMENTS.has(name)) return \"none\"\n if (BLOCK_ELEMENTS.has(name)) return \"block\"\n if (INLINE_BLOCK_ELEMENTS.has(name)) return \"inlineBlock\"\n if (name === \"ruby\") return \"ruby\"\n\n return \"inline\"\n}\n\n/** The element's default display in KOReader, `[hidden]` included (html5.css). */\nexport const getElementDisplay = (element: Element): ElementDisplay => {\n const name = element.localName.toLowerCase()\n\n if (element.hasAttribute(\"hidden\") && !TABLE_PART_ELEMENTS.has(name))\n return \"none\"\n\n return getDisplayByName(name)\n}\n\nexport const getParentContext = (parent: Element): ParentContext => {\n const name = parent.localName.toLowerCase()\n let pre = false\n let insideSvg = false\n let insideMath = name === \"math\"\n\n for (\n let ancestor: Node | null = parent.parentNode;\n ancestor && isElement(ancestor);\n ancestor = ancestor.parentNode\n ) {\n const ancestorName = ancestor.localName.toLowerCase()\n\n if (PRE_ELEMENTS.has(ancestorName)) pre = true\n if (ancestorName === \"svg\") insideSvg = true\n if (ancestorName === \"math\") insideMath = true\n }\n\n if (insideSvg) {\n const allowsText = SVG_TEXT_ELEMENTS.has(name)\n\n return {\n display: \"inline\",\n block: false,\n allowsText,\n pre: pre || allowsText,\n stripsLeadingNewline: false,\n trimsText: false,\n restructured: false,\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: true,\n }\n }\n\n if (insideMath) {\n return {\n display: \"inline\",\n block: false,\n allowsText: MATHML_TOKEN_ELEMENTS.has(name) || !MATHML_ELEMENTS.has(name),\n pre,\n stripsLeadingNewline: false,\n trimsText: true,\n restructured: true,\n opaque: false,\n insideSvg: false,\n }\n }\n\n const display = getElementDisplay(parent)\n\n return {\n display,\n block: display === \"block\" || display === \"inlineBlock\",\n allowsText: !NO_TEXT_ELEMENTS.has(name),\n pre: pre || PRE_ELEMENTS.has(name),\n stripsLeadingNewline: LEADING_NEWLINE_STRIPPING_ELEMENTS.has(name),\n trimsText: false,\n restructured: display === \"ruby\",\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: name === \"svg\",\n }\n}\n","/**\n * What crengine makes of a text node's characters, and how its offsets map to\n * the raw DOM text. Every rule is from `PreProcessXmlString`, `ExpandTabs` and\n * `LVXMLParser::ReadText` in crengine/src/lvxml.cpp and\n * `ldomElementWriter::onText` in crengine/src/lvtinydom.cpp.\n *\n * crengine strings are UTF-32, so its offsets count code points; DOM offsets\n * count UTF-16 units. Both conversions live here.\n */\n\n/** `TEXT_SPLIT_SIZE` (crengine/src/lvxml.cpp): longer text becomes several text nodes. */\nexport const CRENGINE_TEXT_SPLIT_SIZE = 8192\n\nexport type TextMode = {\n /** `white-space: pre` and friends: whitespace kept, tabs expanded. */\n pre: boolean\n /** First text of a `<pre>` / `<textarea>`: a leading newline is dropped. */\n stripsLeadingNewline: boolean\n /** MathML token text: blanks at both ends removed after the whitespace pass. */\n trim?: boolean\n}\n\nexport type TextPiece = {\n /** UTF-16 range in the raw text node data. */\n start: number\n end: number\n}\n\nconst TAB = 9\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\nconst isSpaceLike = (codePoint: number) =>\n codePoint === SPACE ||\n codePoint === CR ||\n codePoint === LF ||\n codePoint === TAB\n\n/** `IsEmptySpace` (crengine/src/lvtinydom.cpp): only these four count as space; NBSP does not. */\nexport const isEmptySpace = (text: string): boolean => {\n for (let index = 0; index < text.length; index++) {\n const charCode = text.charCodeAt(index)\n\n if (\n charCode !== SPACE &&\n charCode !== CR &&\n charCode !== LF &&\n charCode !== TAB\n )\n return false\n }\n\n return true\n}\n\nconst codePointLength = (codePoint: number) => (codePoint > 0xffff ? 2 : 1)\n\n/**\n * Visits the raw text code point by code point with the number of characters\n * crengine keeps for it (`units`): 0 for a collapsed space or a dropped\n * carriage return, up to 8 for an expanded tab, 1 otherwise. Stops early when\n * the visitor returns `false`.\n */\nconst visitCodePoints = (\n text: string,\n mode: TextMode,\n visit: (rawIndex: number, rawLength: number, units: number) => boolean,\n) => {\n let index = 0\n\n if (!mode.pre) {\n // Outside pre: \\r, \\n and \\t become spaces and a run of spaces keeps its\n // first one. Trimming (MathML) then removes the space at either end.\n let precedingSpaces = 0\n let emitted = 0\n let lastKept = text.length\n\n if (mode.trim) {\n while (lastKept > 0 && isSpaceLike(text.charCodeAt(lastKept - 1)))\n lastKept--\n }\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n\n if (isSpaceLike(codePoint)) {\n units = precedingSpaces === 0 ? 1 : 0\n precedingSpaces++\n\n if (mode.trim && (emitted === 0 || index >= lastKept)) units = 0\n } else {\n precedingSpaces = 0\n }\n\n emitted += units\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n\n return\n }\n\n // Inside pre: \\r\\n and lone \\r become \\n, tabs expand to 8-column stops\n // (columns counted from the start of the text node, a newline itself\n // being column 1 of its line), and the first character is dropped when it\n // is the newline that follows a <pre> start tag.\n let lastChar = 0\n let column = 0\n let stripNewline = mode.stripsLeadingNewline\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n let newline = false\n\n if (codePoint === CR) {\n const next =\n index + length < text.length\n ? text.codePointAt(index + length)\n : undefined\n const kept = (index === 0 || lastChar !== LF) && next !== LF\n\n if (kept) {\n newline = true\n lastChar = LF\n } else {\n units = 0\n }\n } else if (codePoint === LF) {\n newline = true\n lastChar = LF\n } else if (codePoint === TAB) {\n units = 8 - (column & 7)\n column += units\n lastChar = TAB\n } else {\n column++\n lastChar = codePoint\n }\n\n if (newline) {\n // ExpandTabs resets the column, then counts the newline itself.\n column = 1\n }\n\n if (units > 0 && stripNewline) {\n stripNewline = false\n\n if (newline) units = 0\n }\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n}\n\n/** Length of crengine's text, in code points. */\nexport const getCrengineTextLength = (text: string, mode: TextMode) => {\n let length = 0\n\n visitCodePoints(text, mode, (_rawIndex, _rawLength, units) => {\n length += units\n\n return true\n })\n\n return length\n}\n\n/**\n * crengine offset (code points of the normalised text) of a raw UTF-16\n * offset: the characters kept for every code point that ends at or before it.\n */\nexport const toCrengineOffset = (\n text: string,\n rawOffset: number,\n mode: TextMode,\n) => {\n let offset = 0\n\n visitCodePoints(text, mode, (rawIndex, rawLength, units) => {\n if (rawIndex + rawLength > rawOffset) return false\n\n offset += units\n\n return true\n })\n\n return offset\n}\n\n/**\n * Raw UTF-16 offset of a crengine offset, or `undefined` when it is past the\n * end of crengine's text. Characters crengine dropped are skipped, so the\n * position lands right before the next character it kept; an offset inside\n * an expanded tab lands before the tab.\n */\nexport const toRawOffset = (\n text: string,\n crengineOffset: number,\n mode: TextMode,\n): number | undefined => {\n if (crengineOffset < 0) return undefined\n\n let offset = 0\n let rawOffset: number | undefined\n\n visitCodePoints(text, mode, (rawIndex, _rawLength, units) => {\n if (units === 0) return true\n\n if (offset === crengineOffset || offset + units > crengineOffset) {\n rawOffset = rawIndex\n\n return false\n }\n\n offset += units\n\n return true\n })\n\n if (rawOffset !== undefined) return rawOffset\n\n return offset === crengineOffset ? text.length : undefined\n}\n\n/**\n * The pieces crengine's parser cuts a long text node into\n * (`LVXMLParser::ReadText`): once 8192 raw code points are buffered, the\n * buffer is emitted up to and including its last space (or its last lone\n * newline), and the rest starts the next piece. The rule is applied to the\n * decoded DOM text; crengine applies it to the source, so a long text with\n * entity references may split a few characters away.\n */\nexport const splitTextPieces = (text: string): TextPiece[] => {\n if (text.length < CRENGINE_TEXT_SPLIT_SIZE)\n return [{ start: 0, end: text.length }]\n\n const pieces: TextPiece[] = []\n let start = 0\n\n while (start < text.length) {\n let windowEnd = start\n let codePoints = 0\n\n while (windowEnd < text.length && codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n windowEnd += codePointLength(text.codePointAt(windowEnd) ?? 0)\n codePoints++\n }\n\n if (codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n pieces.push({ start, end: text.length })\n\n break\n }\n\n let end = windowEnd\n\n for (let index = windowEnd - 1; index >= start; index--) {\n const charCode = text.charCodeAt(index)\n\n if (charCode === SPACE) {\n end = index + 1\n\n break\n }\n\n if (charCode === CR || charCode === LF) {\n const next = index + 1 < windowEnd ? text.charCodeAt(index + 1) : 0\n\n if (\n (charCode === CR && next !== LF) ||\n (charCode === LF && next !== CR)\n ) {\n end = index + 1\n\n break\n }\n }\n }\n\n pieces.push({ start, end })\n start = end\n }\n\n return pieces\n}\n","import {\n getElementDisplay,\n getParentContext,\n isElement,\n type ParentContext,\n} from \"./elements\"\nimport { isEmptySpace, splitTextPieces, type TextMode } from \"./text\"\n\nconst TEXT_NODE = 3\nconst CDATA_SECTION_NODE = 4\n\nexport const isTextNode = (node: Node): node is Text =>\n node.nodeType === TEXT_NODE || node.nodeType === CDATA_SECTION_NODE\n\n/**\n * A child as crengine holds it: browser comments and processing instructions\n * do not exist, dropped text nodes do not exist, and a long text node is\n * several text nodes.\n */\nexport type CrengineChild =\n | { kind: \"element\"; node: Element; childIndex: number }\n | {\n kind: \"text\"\n node: Text\n childIndex: number\n start: number\n end: number\n pieceIndex: number\n pieceCount: number\n mode: TextMode\n }\n\n/** How the parent's rendering pass sees a child (`detectChildTypes`, `isInlineNode`). */\ntype ChildRole = \"block\" | \"inline\" | \"invisible\" | \"text\"\n\ntype ParsedChild = {\n node: Element | Text\n childIndex: number\n role: ChildRole\n whitespace: boolean\n /** First child inserted by the parser (for `_stripLeadingNewlineChar`). */\n first: boolean\n}\n\n/** Children as the parser inserted them (`ldomElementWriter::onText`, crengine/src/lvtinydom.cpp). */\nconst parseChildren = (parent: Element, context: ParentContext) => {\n const children: ParsedChild[] = []\n let childIndex = 0\n\n if (context.opaque) return children\n\n for (\n let node = parent.firstChild;\n node;\n node = node.nextSibling, childIndex++\n ) {\n if (isElement(node)) {\n const display = context.insideSvg ? \"inline\" : getElementDisplay(node)\n const role: ChildRole =\n display === \"block\"\n ? \"block\"\n : display === \"none\"\n ? \"invisible\"\n : \"inline\"\n\n children.push({\n node,\n childIndex,\n role,\n whitespace: false,\n first: children.length === 0,\n })\n\n continue\n }\n\n if (!isTextNode(node) || !context.allowsText) continue\n\n const whitespace = isEmptySpace(node.data)\n\n // A whitespace-only text that would open a block is not inserted, and\n // MathML text with nothing left once trimmed is not either.\n if (\n whitespace &&\n ((context.block && !context.pre && children.length === 0) ||\n context.trimsText)\n )\n continue\n\n children.push({\n node,\n childIndex,\n role: \"text\",\n whitespace,\n first: children.length === 0,\n })\n }\n\n return children\n}\n\nconst isRunItem = (child: ParsedChild) =>\n child.role === \"inline\" || child.role === \"text\"\n\nconst isWhitespaceText = (child: ParsedChild | undefined) =>\n child !== undefined && child.role === \"text\" && child.whitespace\n\n/**\n * A child once the element is closed: a node, or an `autoBoxing` wrapper\n * crengine put around a run of inline content that sits among block children.\n */\ntype RenderedChild =\n | { kind: \"node\"; child: ParsedChild }\n | { kind: \"box\"; children: ParsedChild[] }\n\n/**\n * What `ldomNode::initNodeRendMethod` (crengine/src/lvtinydom.cpp) does to\n * the children once the element is closed. A block holding both block\n * children and inline content gets every run of inline nodes wrapped in an\n * `autoBoxing`, and `autoboxChildren` first drops the whitespace-only text\n * nodes opening the run and the one closing it (a run made only of them\n * disappears) unless the block is preformatted. An inline element holding\n * block children loses the whitespace-only text nodes next to those blocks,\n * when it also holds inline elements or real text.\n */\nconst renderChildren = (\n children: ParsedChild[],\n context: ParentContext,\n): RenderedChild[] => {\n const asNodes = (): RenderedChild[] =>\n children.map((child) => ({ kind: \"node\", child }))\n\n if (context.display === \"block\" || context.display === \"inlineBlock\") {\n const hasBlockItems = children.some((child) => child.role === \"block\")\n const hasInline = children.some(isRunItem)\n\n if (!hasBlockItems || !hasInline) return asNodes()\n\n const rendered: RenderedChild[] = []\n let index = 0\n\n while (index < children.length) {\n const child = children[index]\n\n if (!child) break\n\n if (!isRunItem(child)) {\n rendered.push({ kind: \"node\", child })\n index++\n\n continue\n }\n\n let end = index\n\n while (end < children.length) {\n const next = children[end]\n\n if (!next || !isRunItem(next)) break\n\n end++\n }\n\n let run = children.slice(index, end)\n\n if (!context.pre) {\n let first = 0\n\n while (first < run.length && isWhitespaceText(run[first])) first++\n\n run = run.slice(first)\n\n if (run.length > 0 && isWhitespaceText(run[run.length - 1]))\n run = run.slice(0, -1)\n }\n\n if (run.length > 0) rendered.push({ kind: \"box\", children: run })\n\n index = end\n }\n\n return rendered\n }\n\n if (context.display === \"inline\" && !context.pre) {\n const hasBlockNodes = children.some((child) => child.role === \"block\")\n\n if (!hasBlockNodes) return asNodes()\n\n const hasInlineNodes = children.some((child) => child.role === \"inline\")\n const hasNonEmptyText = children.some(\n (child) => child.role === \"text\" && !child.whitespace,\n )\n\n if (!hasInlineNodes && !hasNonEmptyText) return asNodes()\n\n const removed = new Set<ParsedChild>()\n\n children.forEach((child, index) => {\n if (child.role !== \"block\") return\n\n const previous = children[index - 1]\n const next = children[index + 1]\n\n if (previous && isWhitespaceText(previous)) removed.add(previous)\n if (next && isWhitespaceText(next)) removed.add(next)\n })\n\n return children\n .filter((child) => !removed.has(child))\n .map((child) => ({ kind: \"node\", child }))\n }\n\n return asNodes()\n}\n\nconst getRenderedChildren = (parent: Element, context: ParentContext) =>\n renderChildren(parseChildren(parent, context), context)\n\nconst visitParsedChild = (\n child: ParsedChild,\n context: ParentContext,\n visit: (child: CrengineChild) => boolean,\n): boolean => {\n if (isElement(child.node)) {\n return visit({\n kind: \"element\",\n node: child.node,\n childIndex: child.childIndex,\n })\n }\n\n const pieces = splitTextPieces(child.node.data)\n\n for (let pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {\n const piece = pieces[pieceIndex]\n\n if (!piece) continue\n\n const keepGoing = visit({\n kind: \"text\",\n node: child.node,\n childIndex: child.childIndex,\n start: piece.start,\n end: piece.end,\n pieceIndex,\n pieceCount: pieces.length,\n mode: {\n pre: context.pre,\n stripsLeadingNewline:\n context.stripsLeadingNewline && child.first && pieceIndex === 0,\n trim: context.trimsText,\n },\n })\n\n if (!keepGoing) return false\n }\n\n return true\n}\n\n/**\n * Walks the nodes crengine kept, in order, stopping when `visit` returns\n * `false`: what its parser inserted, minus what its rendering pass removed,\n * with text longer than `TEXT_SPLIT_SIZE` split into pieces. `autoBoxing`\n * wrappers are walked through, as `getNodeByIndex` does for path steps.\n */\nexport const visitCrengineChildren = (\n parent: Element,\n visit: (child: CrengineChild) => boolean,\n context: ParentContext = getParentContext(parent),\n) => {\n for (const entry of getRenderedChildren(parent, context)) {\n const children = entry.kind === \"box\" ? entry.children : [entry.child]\n\n for (const child of children) {\n if (!visitParsedChild(child, context, visit)) return\n }\n }\n}\n\n/** One child as an element point (`.N`) counts it, with the browser index of the last node it covers. */\nexport type CrengineBoxedChild = {\n child: CrengineChild\n lastChildIndex: number\n}\n\n/**\n * The children an element point (`.N`) counts: crengine checks the point\n * against the element's real child count, where an `autoBoxing` wrapper is\n * one child. Each entry holds the first node the wrapper holds, or the node\n * itself, and where the wrapper ends.\n */\nexport const getCrengineBoxedChildren = (\n parent: Element,\n context: ParentContext = getParentContext(parent),\n): CrengineBoxedChild[] => {\n const children: CrengineBoxedChild[] = []\n\n for (const entry of getRenderedChildren(parent, context)) {\n if (entry.kind === \"box\") {\n const first = entry.children[0]\n const last = entry.children[entry.children.length - 1]\n\n if (first && last) {\n visitParsedChild(first, context, (child) => {\n children.push({ child, lastChildIndex: last.childIndex })\n\n return false\n })\n }\n\n continue\n }\n\n visitParsedChild(entry.child, context, (child) => {\n children.push({ child, lastChildIndex: child.childIndex })\n\n return true\n })\n }\n\n return children\n}\n\nexport const getCrengineChildren = (parent: Element): CrengineChild[] => {\n const children: CrengineChild[] = []\n\n visitCrengineChildren(parent, (child) => {\n children.push(child)\n\n return true\n })\n\n return children\n}\n\n/**\n * The `<body>` crengine's fragment body corresponds to. An SVG spine item\n * has none: crengine wraps such a file in a body of its own, and how it\n * parses what follows (the XML declaration becomes an element there) is not\n * something a DOM can show, so the item is handled as a whole.\n */\nexport const getFragmentBody = (document: Document): Element | undefined =>\n document.body ?? undefined\n","import type { ParsedXPointer } from \"./types\"\n\n/**\n * Prints a pointer in the shape `toStringV2` (crengine/src/lvtinydom.cpp)\n * produces for DOM versions 20200223..20260811, the one every third-party\n * consumer was written against: an index is written only where the parsed\n * structure carries one, and the spine item prefix is always\n * `/body/DocFragment[N]/body`.\n *\n * `serializeXPointer(parseXPointer(s))` is `s` for every pointer crengine\n * writes in that shape for a book with several spine items; the single-item\n * prefix `/body/DocFragment/body` and the explicit `body[1]` prefixes come\n * back normalised. `generateXPointer` decides where an index is needed; this\n * function never looks at a DOM.\n */\nexport const serializeXPointer = (parsed: ParsedXPointer): string => {\n let output = `/body/DocFragment[${parsed.spineItemIndex + 1}]/body`\n\n for (const step of parsed.steps) {\n if (step.kind === \"nodeIndex\") {\n output += `/${step.index}`\n } else {\n const name = step.kind === \"text\" ? \"text()\" : step.name\n\n output +=\n step.index === undefined ? `/${name}` : `/${name}[${step.index}]`\n }\n }\n\n return parsed.point === undefined ? output : `${output}.${parsed.point}`\n}\n","import {\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n isTextNode,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport {\n crengineElementName,\n getParentContext,\n hasImproperTableChildren,\n isElement,\n} from \"./crengine/elements\"\nimport { toCrengineOffset } from \"./crengine/text\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, XPointerStep } from \"./types\"\n\nconst DOCUMENT_NODE = 9\n\nconst isDocument = (node: Node): node is Document =>\n node.nodeType === DOCUMENT_NODE\n\n/**\n * Position of `node` among the same-name element children of `parent`, as\n * `toStringV2` (crengine/src/lvtinydom.cpp) writes it: the index is emitted\n * only when the parent holds more than one element of that name.\n */\nconst elementStep = (\n parent: Element,\n node: Element,\n insideSvg: boolean,\n): XPointerStep | undefined => {\n const name = crengineElementName(node, insideSvg)\n let index: number | undefined\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (\n child.kind !== \"element\" ||\n crengineElementName(child.node, insideSvg) !== name\n )\n return true\n\n count++\n\n if (child.node === node) index = count\n\n // Once the node is found, one more same-name sibling settles the shape.\n return index === undefined || count === 1\n })\n\n if (index === undefined) return undefined\n\n return { kind: \"element\", name, index: count > 1 ? index : undefined }\n}\n\n/**\n * Steps from the fragment body down to `element` (excluded when it is the\n * body). `undefined` when the element does not hang under the body crengine\n * knows (detached, or in `<head>`).\n */\nconst stepsTo = (\n element: Element,\n body: Element,\n): XPointerStep[] | undefined => {\n const chain: Element[] = []\n let node: Node | null = element\n\n while (node !== body) {\n if (!node || !isElement(node)) return undefined\n\n chain.push(node)\n node = node.parentNode\n }\n\n chain.reverse()\n\n const steps: XPointerStep[] = []\n let parent: Element = body\n let insideSvg = false\n\n for (const current of chain) {\n const step = elementStep(parent, current, insideSvg)\n\n if (!step) return undefined\n\n steps.push(step)\n\n if (current.localName.toLowerCase() === \"svg\") insideSvg = true\n\n parent = current\n }\n\n return steps\n}\n\n/** The kept piece of `text` holding UTF-16 `offset`, its 1-based rank among the parent's text pieces and whether it has siblings. */\nconst findTextPiece = (parent: Element, text: Text, offset: number) => {\n let found: Extract<CrengineChild, { kind: \"text\" }> | undefined\n let foundIndex = 0\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (child.kind !== \"text\") return true\n\n count++\n\n if (found === undefined && child.node === text) {\n const last = child.pieceIndex === child.pieceCount - 1\n\n if (offset < child.end || last) {\n found = child\n foundIndex = count\n }\n }\n\n // Once the piece is found, one more text sibling settles the shape.\n return found === undefined || count === 1\n })\n\n if (!found) return undefined\n\n return { piece: found, index: foundIndex, count }\n}\n\n/**\n * crengine's child index for the boundary before browser child `offset`:\n * every child crengine's element holds (an `autoBoxing` wrapper counting\n * once, each piece of a split text node counting) that starts before it. A\n * boundary inside a wrapper becomes the wrapper's start. `undefined` where\n * crengine rebuilds the children with wrappers of its own (ruby, MathML)\n * and a non-zero index cannot be known.\n */\nconst childIndexBefore = (parent: Element, offset: number) => {\n const context = getParentContext(parent)\n let index = 0\n\n for (const { child, lastChildIndex } of getCrengineBoxedChildren(\n parent,\n context,\n )) {\n if (child.childIndex >= offset || offset <= lastChildIndex) break\n\n index++\n }\n\n const unknowable = context.restructured || hasImproperTableChildren(parent)\n\n return unknowable && index > 0 ? undefined : index\n}\n\n/**\n * The classic V2 pointer for a DOM position, as `toStringV2`\n * (crengine/src/lvtinydom.cpp) writes it for a book opened with a DOM\n * version between 20200223 and 20260811: `[N]` only where the name is shared\n * by more than one sibling, `text()` without `[N]` when the parent holds a\n * single text node. The result resolves in crengine to the same node.\n *\n * `undefined` for positions crengine has no node for: a whitespace-only text\n * node it dropped, text under an element that takes no text, an element\n * outside the `<body>`, an offset past the end of a text node, anything\n * inside an SVG spine item but the item itself.\n */\nexport const generateXPointer = (\n position: DomPosition,\n spineItemIndex: number,\n): string | undefined => {\n if (!Number.isInteger(spineItemIndex) || spineItemIndex < 0) return undefined\n\n const { node, offset } = position\n const document = isDocument(node) ? node : node.ownerDocument\n\n if (!document) return undefined\n\n const body = getFragmentBody(document)\n const build = (\n steps: XPointerStep[] | undefined,\n point: number | undefined,\n ) =>\n steps === undefined\n ? undefined\n : serializeXPointer({\n spineItemIndex,\n steps,\n point,\n containsBoxingElements: false,\n })\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return isDocument(node) || node === document.documentElement\n ? build([], undefined)\n : undefined\n }\n\n if (isDocument(node)) return undefined\n\n if (isTextNode(node)) {\n const parent = node.parentNode\n const rawOffset = offset ?? 0\n\n if (\n !parent ||\n !isElement(parent) ||\n rawOffset < 0 ||\n rawOffset > node.data.length\n )\n return undefined\n\n const text = findTextPiece(parent, node, rawOffset)\n\n if (!text) return undefined\n\n const { piece } = text\n const steps = stepsTo(parent, body)\n\n if (!steps) return undefined\n\n steps.push({ kind: \"text\", index: text.count > 1 ? text.index : undefined })\n\n return build(\n steps,\n toCrengineOffset(\n node.data.slice(piece.start, piece.end),\n rawOffset - piece.start,\n piece.mode,\n ),\n )\n }\n\n if (!isElement(node)) return undefined\n\n if (offset === undefined) return build(stepsTo(node, body), undefined)\n\n if (offset < 0 || offset > node.childNodes.length) return undefined\n\n const point = childIndexBefore(node, offset)\n\n return point === undefined ? undefined : build(stepsTo(node, body), point)\n}\n","import type { ParsedXPointer, XPointerStep } from \"./types\"\n\n/**\n * Wrapper elements crengine inserts into its own DOM for layout\n * (crengine/include/fb2def.h, `EL_BOXING_START`..`EL_BOXING_END`) plus the\n * `pseudoElem` it creates for `::before` / `::after`. None exists in the source\n * XHTML, so a pointer naming one cannot be resolved in a browser DOM.\n */\nexport const XPOINTER_BOXING_ELEMENT_NAMES: readonly string[] = [\n \"autoBoxing\",\n \"tabularBox\",\n \"rubyBox\",\n \"mathBox\",\n \"floatBox\",\n \"inlineBox\",\n \"pseudoElem\",\n]\n\nconst boxingElementNames = new Set<string>(\n XPOINTER_BOXING_ELEMENT_NAMES.map((name) => name.toLowerCase()),\n)\n\nconst isDigit = (charCode: number) => charCode >= 48 && charCode <= 57\n\n/** An element name as crengine's XML parser can produce one (no `.`, which ends a step). */\nconst ELEMENT_NAME = /^[A-Za-z_][A-Za-z0-9_:-]*$/\n\nconst isStepBoundary = (char: string | undefined) =>\n char === undefined || char === \"/\" || char === \".\"\n\nconst readDigits = (input: string, from: number) => {\n let end = from\n\n while (end < input.length && isDigit(input.charCodeAt(end))) end++\n\n return end\n}\n\n/**\n * Port of `ParseXPathStep` (crengine/src/lvtinydom.cpp): a pointer is a\n * sequence of `/name`, `/name[N]`, `/text()`, `/text()[N]` or `/N` steps, with\n * an optional `.N` point that must end the string (`createXPointerV2` rejects\n * anything after it).\n *\n * Stricter than crengine on malformed input: `[N]` must be plain decimal\n * digits, where crengine's `atoi` would also take signs, hex or surrounding\n * blanks and silently turn garbage into an unresolvable index 0, and a step\n * name must be a plausible element name, where crengine would look up any\n * text (`text()x`, `p q`) and fail to find an element.\n */\nconst tokenize = (\n input: string,\n): { steps: XPointerStep[]; point: number | undefined } | undefined => {\n const steps: XPointerStep[] = []\n let position = 0\n\n while (position < input.length) {\n const prefix = input[position]\n\n if (prefix === \".\") {\n const digitsStart = position + 1\n const digitsEnd = readDigits(input, digitsStart)\n\n if (digitsEnd === digitsStart || digitsEnd !== input.length)\n return undefined\n\n return { steps, point: Number(input.slice(digitsStart, digitsEnd)) }\n }\n\n if (prefix !== \"/\") return undefined\n\n const stepStart = position + 1\n\n if (isDigit(input.charCodeAt(stepStart))) {\n const digitsEnd = readDigits(input, stepStart)\n\n if (!isStepBoundary(input[digitsEnd])) return undefined\n\n const index = Number(input.slice(stepStart, digitsEnd))\n\n if (index < 1) return undefined\n\n steps.push({ kind: \"nodeIndex\", index })\n position = digitsEnd\n\n continue\n }\n\n let nameEnd = stepStart\n\n while (nameEnd < input.length && !\"[/.\".includes(input[nameEnd] ?? \"\"))\n nameEnd++\n\n if (nameEnd === stepStart) return undefined\n\n const name = input.slice(stepStart, nameEnd)\n let index: number | undefined\n let stepEnd = nameEnd\n\n if (input[nameEnd] === \"[\") {\n const closing = input.indexOf(\"]\", nameEnd + 1)\n\n if (closing === -1) return undefined\n\n const digits = input.slice(nameEnd + 1, closing)\n\n if (digits.length === 0 || readDigits(digits, 0) !== digits.length)\n return undefined\n\n index = Number(digits)\n\n if (index < 1) return undefined\n\n stepEnd = closing + 1\n }\n\n if (!isStepBoundary(input[stepEnd])) return undefined\n\n if (name === \"text()\") {\n steps.push({ kind: \"text\", index })\n } else if (ELEMENT_NAME.test(name)) {\n steps.push({ kind: \"element\", name, index })\n } else {\n return undefined\n }\n position = stepEnd\n }\n\n return { steps, point: undefined }\n}\n\nconst isElementNamed = (\n step: XPointerStep | undefined,\n name: string,\n): step is Extract<XPointerStep, { kind: \"element\" }> =>\n step?.kind === \"element\" && step.name.toLowerCase() === name\n\n/**\n * Accepts every shape crengine has emitted (`toStringV1`, `toStringV2` and\n * its explicit-index variant for DOM versions >= 20260812) plus the bare\n * `/body/DocFragment[N]` third parties write for the start of a spine item.\n *\n * Returns `undefined` for anything crengine's own parser would reject or that\n * does not start with the `/body/DocFragment[N]/body` prefix of an EPUB\n * pointer. crengine's `#id` form is rejected too: it names an element of the\n * whole book DOM by id, which a spine item document cannot answer.\n */\nexport const parseXPointer = (input: string): ParsedXPointer | undefined => {\n const tokenized = tokenize(input)\n\n if (!tokenized) return undefined\n\n const [root, fragment, body, ...steps] = tokenized.steps\n\n if (!isElementNamed(root, \"body\") || (root.index ?? 1) !== 1) return undefined\n\n if (!isElementNamed(fragment, \"docfragment\")) return undefined\n\n const spineItemIndex = (fragment.index ?? 1) - 1\n\n // `/body/DocFragment[N]` alone, with or without a point (Kavita emits\n // `/body/DocFragment[N].0`): the start of the spine item.\n if (body === undefined) {\n return {\n spineItemIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n }\n }\n\n if (!isElementNamed(body, \"body\") || (body.index ?? 1) !== 1) return undefined\n\n return {\n spineItemIndex,\n steps,\n point: tokenized.point,\n containsBoxingElements: steps.some(\n (step) =>\n step.kind === \"element\" &&\n boxingElementNames.has(step.name.toLowerCase()),\n ),\n }\n}\n","import {\n type CrengineBoxedChild,\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport { toRawOffset } from \"./crengine/text\"\nimport { parseXPointer } from \"./parse\"\nimport type { DomPosition, ParsedXPointer, XPointerStep } from \"./types\"\n\ntype Current =\n | { kind: \"parent\"; node: Element }\n | Extract<CrengineChild, { kind: \"text\" }>\n\n/**\n * One step of `createXPointerV2` (crengine/src/lvtinydom.cpp): the N-th child\n * matching the step (first when the index is omitted), counted among the\n * children crengine kept. Element names compare case-insensitively.\n */\nconst findChild = (\n parent: Element,\n step: XPointerStep,\n): CrengineChild | undefined => {\n const wantedIndex = step.index ?? 1\n const wantedName =\n step.kind === \"element\" ? step.name.toLowerCase() : undefined\n let count = 0\n let found: CrengineChild | undefined\n\n visitCrengineChildren(parent, (child) => {\n const matches =\n step.kind === \"nodeIndex\" ||\n (step.kind === \"text\"\n ? child.kind === \"text\"\n : child.kind === \"element\" &&\n child.node.localName.toLowerCase() === wantedName)\n\n if (!matches) return true\n\n count++\n\n if (count === wantedIndex) {\n found = child\n\n return false\n }\n\n return true\n })\n\n return found\n}\n\nconst positionOfChild = (\n parent: Element,\n children: CrengineBoxedChild[],\n index: number,\n): DomPosition => {\n const child = children[index]?.child\n\n if (!child) return { node: parent, offset: parent.childNodes.length }\n\n // The boundary before a later piece of a split text node lies inside it.\n if (child.kind === \"text\" && child.pieceIndex > 0) {\n return { node: child.node, offset: child.start }\n }\n\n return { node: parent, offset: child.childIndex }\n}\n\n/**\n * Resolves a pointer inside the spine item's document, following\n * `createXPointerV2` (crengine/src/lvtinydom.cpp). Returns `undefined` when\n * crengine would fail too: a step without a match, a point past the end of\n * the text or of the children (nothing is clamped), a V1 pointer naming a\n * boxing element.\n *\n * A text position is returned with a UTF-16 offset into the text node,\n * ready for `Range.setStart`; a text step without a point is its start. In\n * a spine item without a `<body>` (an SVG document) only the item itself\n * resolves, to its root element.\n */\nexport const resolveXPointer = (\n xpointer: string | ParsedXPointer,\n document: Document,\n): DomPosition | undefined => {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed || parsed.containsBoxingElements) return undefined\n\n const body = getFragmentBody(document)\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return parsed.steps.length === 0\n ? { node: document.documentElement }\n : undefined\n }\n\n let current: Current = { kind: \"parent\", node: body }\n\n for (const step of parsed.steps) {\n if (current.kind === \"text\") return undefined\n\n const child = findChild(current.node, step)\n\n if (!child) return undefined\n\n current =\n child.kind === \"element\" ? { kind: \"parent\", node: child.node } : child\n }\n\n if (current.kind === \"text\") {\n if (parsed.point === undefined) {\n return { node: current.node, offset: current.start }\n }\n\n const rawOffset = toRawOffset(\n current.node.data.slice(current.start, current.end),\n parsed.point,\n current.mode,\n )\n\n return rawOffset === undefined\n ? undefined\n : { node: current.node, offset: current.start + rawOffset }\n }\n\n const parent = current.node\n\n if (parsed.point === undefined) return { node: parent }\n\n const children = getCrengineBoxedChildren(parent)\n\n if (parsed.point > children.length) return undefined\n\n return positionOfChild(parent, children, parsed.point)\n}\n","import {\n generate,\n getEpubCfiSpineItemref,\n isIndirectionOnly,\n parse,\n resolve,\n} from \"@prose-reader/cfi\"\nimport { isTextNode } from \"./crengine/children\"\nimport { isElement } from \"./crengine/elements\"\nimport { generateXPointer } from \"./generate\"\nimport { parseXPointer } from \"./parse\"\nimport { resolveXPointer } from \"./resolve\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, ParsedXPointer } from \"./types\"\n\nexport type XPointerSpineItem = {\n /** The spine item's content document, parsed as XHTML (what prose renders in its frames). */\n document: Document\n /** The `<itemref>` idref, written as the CFI's id assertion (`/6/14[chap05]!`) when given. */\n id?: string\n}\n\n/**\n * Hands out the document of a spine item by its 0-based index. A pointer\n * names its own spine item (`DocFragment[N]`) and is resolved in that\n * document, never in whichever one happens to be on screen: Readest shipped\n * that bug (readest/readest#5980) and landed a chapter away.\n */\nexport type XPointerSpineItemLookup = (\n spineItemIndex: number,\n) => XPointerSpineItem | undefined\n\n/**\n * The position a CFI can point to for a DOM position. A CFI cannot point\n * between an element's children, so a child index point becomes the start of\n * that child, and the end of the children the end of the last text child or\n * the element itself. Any other position is returned as it is.\n */\nexport const toCfiPosition = (position: DomPosition): DomPosition => {\n const { node, offset } = position\n\n if (!isElement(node) || offset === undefined) return position\n\n const child = node.childNodes[offset]\n\n if (child) {\n return isTextNode(child) ? { node: child, offset: 0 } : { node: child }\n }\n\n const last = node.lastChild\n\n return last && isTextNode(last)\n ? { node: last, offset: last.data.length }\n : { node }\n}\n\n/**\n * The CFI of a KOReader position, built with `@prose-reader/cfi` in the\n * spine item document the pointer names. `undefined` when the pointer is\n * malformed, its spine item is not available or it does not resolve; the\n * spine index alone is still available through `parseXPointer`.\n */\nexport const xPointerToCfi = (\n xpointer: string | ParsedXPointer,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed) return undefined\n\n const spineItem = getSpineItem(parsed.spineItemIndex)\n\n if (!spineItem) return undefined\n\n const position = resolveXPointer(parsed, spineItem.document)\n\n if (!position) return undefined\n\n const { node, offset } = toCfiPosition(position)\n\n return generate({\n node,\n offset: isTextNode(node) ? offset : undefined,\n spineIndex: parsed.spineItemIndex,\n spineId: spineItem.id,\n })\n } catch {\n return undefined\n }\n}\n\nconst firstOffset = (offset: number | number[] | undefined) =>\n Array.isArray(offset) ? offset[0] : offset\n\n/**\n * The KOReader pointer of a prose CFI: the CFI's start when it is a range,\n * the start of the spine item for a root CFI (`epubcfi(/6/14[chap05]!)`).\n * `undefined` when the CFI is malformed, its spine item is not available or\n * it does not resolve in that document.\n */\nexport const cfiToXPointer = (\n cfi: string,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const itemref = getEpubCfiSpineItemref(cfi)\n\n if (!itemref) return undefined\n\n const { spineIndex } = itemref\n\n if (!Number.isInteger(spineIndex) || spineIndex < 0) return undefined\n\n const parsed = parse(cfi)\n\n if (isIndirectionOnly(parsed)) {\n return serializeXPointer({\n spineItemIndex: spineIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n })\n }\n\n const spineItem = getSpineItem(spineIndex)\n\n if (!spineItem) return undefined\n\n const resolved = resolve(parsed, spineItem.document)\n\n if (!resolved.node) return undefined\n\n const position: DomPosition = resolved.isRange\n ? {\n node: resolved.node.startContainer,\n offset: resolved.node.startOffset,\n }\n : {\n node: resolved.node,\n offset: isTextNode(resolved.node)\n ? (firstOffset(resolved.offset) ?? 0)\n : undefined,\n }\n\n return generateXPointer(position, spineIndex)\n } catch {\n return undefined\n }\n}\n","import { md5HexDigest } from \"@prose-reader/shared\"\n\n/** Bytes `util.partialMD5` reads at each of its offsets. */\nconst PARTIAL_MD5_SAMPLE_SIZE = 1024\n\n/**\n * Where `util.partialMD5` (KOReader, frontend/util.lua) reads: at\n * `bit.lshift(1024, 2 * i)` for `i` from -1 to 10. LuaJIT keeps the low five\n * bits of a shift count, so `i = -1` shifts 1024 by 30 bits, out of 32 bits,\n * and reads at 0.\n */\nconst PARTIAL_MD5_SAMPLE_OFFSETS = [\n 0, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864,\n 268435456, 1073741824,\n]\n\n/**\n * The digest KOReader files a book's reading position under with its default\n * \"Binary\" document matching method: `util.partialMD5` (frontend/util.lua),\n * the MD5 of 1 KiB samples of the file at the offsets above, as lowercase hex.\n * A sample the end of the file cuts short is hashed as it is, and the samples\n * stop at the first offset at or past the end: at most 12 KiB of `file` are\n * read.\n *\n * KOReader computes it from the bytes it opens the first time and keeps it in\n * the book's settings, so the digests match only for the same bytes.\n */\nexport const getKoreaderFileDigest = async (file: Blob): Promise<string> => {\n const samples = await Promise.all(\n PARTIAL_MD5_SAMPLE_OFFSETS.filter((offset) => offset < file.size).map(\n async (offset) =>\n new Uint8Array(\n await file\n .slice(offset, offset + PARTIAL_MD5_SAMPLE_SIZE)\n .arrayBuffer(),\n ),\n ),\n )\n\n return md5HexDigest(samples)\n}\n\n/**\n * The digest KOReader files a book's reading position under with its\n * \"Filename\" document matching method (`KOSync:getFileNameDigest`,\n * plugins/kosync.koplugin/main.lua): the MD5 of the file name, as lowercase\n * hex.\n *\n * `fileName` is the base name, extension included, as the device's file\n * system stores it. KOReader hashes its UTF-8 bytes as they are, without\n * Unicode normalization: an `é` stored as one code point and one stored as an\n * `e` and a combining accent give two digests.\n */\nexport const getKoreaderFileNameDigest = (fileName: string): string =>\n md5HexDigest([new TextEncoder().encode(fileName)])\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["generate","getEpubCfiSpineItemref","parse","isIndirectionOnly","resolve","md5HexDigest","_prose_reader_cfi","_prose_reader_shared","BLOCK_ELEMENTS","INLINE_BLOCK_ELEMENTS","INVISIBLE_ELEMENTS","NO_TEXT_ELEMENTS","PRE_ELEMENTS","SVG_TEXT_ELEMENTS","MATHML_ELEMENTS","MATHML_TOKEN_ELEMENTS","RAW_TEXT_ELEMENTS","PROPER_TABLE_CHILDREN","hasImproperTableChildren","proper","element","child","LEADING_NEWLINE_STRIPPING_ELEMENTS","ELEMENT_NODE","isElement","node","crengineElementName","insideSvg","TABLE_PART_ELEMENTS","getDisplayByName","name","getElementDisplay","getParentContext","parent","pre","insideMath","ancestor","ancestorName","allowsText","display","CRENGINE_TEXT_SPLIT_SIZE","TAB","LF","CR","SPACE","isSpaceLike","codePoint","isEmptySpace","index","text","charCode","codePointLength","visitCodePoints","mode","precedingSpaces","emitted","lastKept","length","units","visit","lastChar","column","stripNewline","newline","next","toCrengineOffset","offset","rawIndex","rawLength","rawOffset","toRawOffset","crengineOffset","splitTextPieces","pieces","start","windowEnd","codePoints","end","TEXT_NODE","CDATA_SECTION_NODE","isTextNode","parseChildren","children","childIndex","context","role","whitespace","isRunItem","isWhitespaceText","renderChildren","asNodes","hasBlockItems","hasInline","rendered","run","first","hasInlineNodes","hasNonEmptyText","removed","previous","getRenderedChildren","visitParsedChild","pieceIndex","piece","visitCrengineChildren","entry","getCrengineBoxedChildren","last","getFragmentBody","document","serializeXPointer","output","parsed","step","DOCUMENT_NODE","isDocument","elementStep","count","stepsTo","chain","body","steps","current","findTextPiece","found","foundIndex","childIndexBefore","lastChildIndex","generateXPointer","spineItemIndex","position","build","point","XPOINTER_BOXING_ELEMENT_NAMES","boxingElementNames","isDigit","ELEMENT_NAME","isStepBoundary","char","readDigits","from","input","tokenize","prefix","digitsStart","digitsEnd","stepStart","nameEnd","stepEnd","closing","digits","isElementNamed","parseXPointer","tokenized","root","fragment","findChild","wantedIndex","wantedName","positionOfChild","resolveXPointer","xpointer","toCfiPosition","xPointerToCfi","spineItem","getSpineItem","firstOffset","cfiToXPointer","itemref","cfi","spineIndex","resolved","PARTIAL_MD5_SAMPLE_SIZE","PARTIAL_MD5_SAMPLE_OFFSETS","getKoreaderFileDigest","samples","file","getKoreaderFileNameDigest","fileName","XPOINTER_BOOK_MEDIA_TYPES","PAGE_BOOK_MEDIA_TYPES","getKoreaderProgressKind","essence","mediaType"],"sources":["../src/crengine/elements.ts","../src/crengine/text.ts","../src/crengine/children.ts","../src/serialize.ts","../src/generate.ts","../src/parse.ts","../src/resolve.ts","../src/cfi.ts","../src/digest.ts","../src/progress.ts"],"sourcesContent":["/**\n * How crengine classifies elements while it loads a spine item, from its\n * built-in element table (crengine/include/fb2def.h) and the stylesheet\n * KOReader applies before any book CSS (data/html5.css, imported by\n * data/epub.css). These defaults decide what happens to text nodes in\n * `ldomElementWriter::onText` and `ldomNode::initNodeRendMethod`\n * (crengine/src/lvtinydom.cpp):\n *\n * - `display` — whether whitespace-only text children are dropped,\n * - `allow_text` — elements such as `<table>` or `<tr>` drop every text child,\n * - `white-space` — pre-like elements keep whitespace verbatim.\n *\n * Only these defaults are known here. crengine takes the values from the\n * computed style, so a book stylesheet that changes `display` or\n * `white-space` (class rules, inline styles) is not seen: this is the\n * package's documented approximation.\n */\n\n/**\n * Elements whose default `display` is neither `inline` nor `none`: block,\n * list-item, table and table parts. `isBlockNode` in crengine treats all of\n * them alike (`display > css_d_inline && display != css_d_none`). Unknown\n * elements are inline.\n */\nconst BLOCK_ELEMENTS = new Set([\n \"address\",\n \"annotation\",\n \"applet\",\n \"article\",\n \"aside\",\n \"audio\",\n \"author\",\n \"autoboxing\",\n \"blockquote\",\n \"body\",\n \"book-name\",\n \"book-title\",\n \"canvas\",\n \"caption\",\n \"center\",\n \"city\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"custom-info\",\n \"date\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"docfragment\",\n \"document-info\",\n \"dt\",\n \"email\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"floatbox\",\n \"footer\",\n \"form\",\n \"frame\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"header\",\n \"hgroup\",\n \"history\",\n \"home-page\",\n \"hr\",\n \"html\",\n \"id\",\n \"iframe\",\n \"isbn\",\n \"keygen\",\n \"keywords\",\n \"lang\",\n \"legend\",\n \"li\",\n \"listing\",\n \"main\",\n \"map\",\n \"menu\",\n \"multicol\",\n \"nav\",\n \"nickname\",\n \"noframes\",\n \"noscript\",\n \"ol\",\n \"optgroup\",\n \"output\",\n \"p\",\n \"part\",\n \"plaintext\",\n \"poem\",\n \"pre\",\n \"program-used\",\n \"publish-info\",\n \"publisher\",\n \"search\",\n \"section\",\n \"select\",\n \"sequence\",\n \"source\",\n \"src-lang\",\n \"src-ocr\",\n \"src-title-info\",\n \"src-url\",\n \"stanza\",\n \"subtitle\",\n \"summary\",\n \"table\",\n \"tabularbox\",\n \"tbody\",\n \"td\",\n \"text-author\",\n \"textarea\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n \"ul\",\n \"v\",\n \"version\",\n \"video\",\n \"xmp\",\n \"year\",\n])\n\n/** `display: inline-block` in html5.css: a block for its own children, an inline item for its parent. */\nconst INLINE_BLOCK_ELEMENTS = new Set([\"button\", \"input\", \"marquee\"])\n\n/** `display: none` (fb2def.h and html5.css): invisible, not counted as inline nor block by the parent. */\nconst INVISIBLE_ELEMENTS = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"binary\",\n \"clonenode\",\n \"datalist\",\n \"description\",\n \"genre\",\n \"head\",\n \"link\",\n \"meta\",\n \"noembed\",\n \"param\",\n \"pseudoelem\",\n \"script\",\n \"style\",\n \"stylesheet\",\n \"template\",\n \"title\",\n])\n\n/** Elements defined with `allow_text = false` in fb2def.h: their text children are never inserted. */\nconst NO_TEXT_ELEMENTS = new Set([\n \"annotation\",\n \"applet\",\n \"area\",\n \"audio\",\n \"author\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"clonenode\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"description\",\n \"docfragment\",\n \"document-info\",\n \"embed\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"head\",\n \"html\",\n \"image\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"object\",\n \"param\",\n \"part\",\n \"poem\",\n \"pseudoelem\",\n \"publish-info\",\n \"source\",\n \"src-title-info\",\n \"stanza\",\n \"svg\",\n \"table\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n])\n\n/** Elements whose default `white-space` is `pre` or `pre-wrap` (fb2def.h, html5.css). */\nconst PRE_ELEMENTS = new Set([\n \"listing\",\n \"plaintext\",\n \"pre\",\n \"style\",\n \"textarea\",\n \"xmp\",\n])\n\n/**\n * Inside `<svg>` crengine only keeps text under these elements, verbatim\n * (`ldomElementWriter` constructor, `_insideSVG`).\n */\nconst SVG_TEXT_ELEMENTS = new Set([\n \"desc\",\n \"metadata\",\n \"style\",\n \"text\",\n \"textpath\",\n \"title\",\n \"tspan\",\n])\n\n/**\n * MathML elements crengine knows (fb2def.h, `EL_MATHML_START`..`EL_MATHML_END`).\n * Inside `<math>`, `MathMLHelper::getMathMLAdjustedText` (crengine/src/mathml.cpp)\n * drops the text of every one of them except the token elements, whose text\n * it trims; text under any other element is trimmed too.\n */\nconst MATHML_ELEMENTS = new Set([\n \"annotation-xml\",\n \"maction\",\n \"maligngroup\",\n \"malignmark\",\n \"math\",\n \"menclose\",\n \"merror\",\n \"mfenced\",\n \"mfrac\",\n \"mglyph\",\n \"mi\",\n \"mlabeledtr\",\n \"mlongdiv\",\n \"mmultiscripts\",\n \"mn\",\n \"mo\",\n \"mover\",\n \"mpadded\",\n \"mphantom\",\n \"mprescripts\",\n \"mroot\",\n \"mrow\",\n \"ms\",\n \"mscarries\",\n \"mscarry\",\n \"msgroup\",\n \"msline\",\n \"mspace\",\n \"msqrt\",\n \"msrow\",\n \"mstack\",\n \"mstyle\",\n \"msub\",\n \"msubsup\",\n \"msup\",\n \"mtable\",\n \"mtd\",\n \"mtext\",\n \"mtr\",\n \"munder\",\n \"munderover\",\n \"none\",\n \"semantics\",\n])\n\n/** `EL_MATHML_TOKEN_START`..`EL_MATHML_TOKEN_END`: the MathML elements that hold text. */\nconst MATHML_TOKEN_ELEMENTS = new Set([\n \"mglyph\",\n \"mi\",\n \"mn\",\n \"mo\",\n \"mtext\",\n \"mspace\",\n \"ms\",\n])\n\n/**\n * crengine's parser reads the content of these as one raw text node up to the\n * closing tag (`LVXMLParser::ReadText`, `m_in_html_style_tag` /\n * `m_in_html_script_tag`), so the elements an XML parser finds inside them\n * do not exist for it. Nothing is modelled inside them.\n */\nconst RAW_TEXT_ELEMENTS = new Set([\"script\", \"style\"])\n\n/**\n * The children a table part may hold without crengine wrapping them in a\n * `tabularBox` (`initTableRendMethods`, \"Generate missing child wrappers\"):\n * any other element child makes the part's child count unknowable.\n */\nconst PROPER_TABLE_CHILDREN: Record<string, Set<string>> = {\n table: new Set([\n \"caption\",\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"tr\",\n ]),\n tbody: new Set([\"tr\"]),\n tfoot: new Set([\"tr\"]),\n thead: new Set([\"tr\"]),\n tr: new Set([\"td\", \"th\"]),\n}\n\n/** Whether crengine had to complete this table part with wrappers of its own. */\nexport const hasImproperTableChildren = (element: Element) => {\n const proper = PROPER_TABLE_CHILDREN[element.localName.toLowerCase()]\n\n if (!proper) return false\n\n for (\n let child = element.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n if (!proper.has(child.localName.toLowerCase())) return true\n }\n\n return false\n}\n\n/** `pre` and `textarea` lose a newline immediately following their start tag (`_stripLeadingNewlineChar`, DOM >= 20210904). */\nconst LEADING_NEWLINE_STRIPPING_ELEMENTS = new Set([\"pre\", \"textarea\"])\n\n/**\n * What an element is to crengine's rendering pass: `block` and `inlineBlock`\n * contain blocks or a final line box, `inline` flows in its parent's text,\n * `none` is invisible, `ruby` is inline with its own layout.\n */\nexport type ElementDisplay =\n | \"block\"\n | \"inlineBlock\"\n | \"inline\"\n | \"none\"\n | \"ruby\"\n\nexport type ParentContext = {\n display: ElementDisplay\n /** Whitespace-only first text child dropped at parse time (`_isBlock`). */\n block: boolean\n /** Text children inserted at all (`_allowText`). */\n allowsText: boolean\n /** Text kept verbatim, tabs expanded (`TXTFLG_PRE`). */\n pre: boolean\n stripsLeadingNewline: boolean\n /** Inside `<math>`: text trimmed of surrounding blanks, dropped when nothing is left. */\n trimsText: boolean\n /** Ruby and MathML: crengine rebuilds the children with its own wrappers, so child indexes are not knowable. */\n restructured: boolean\n /** `<style>` / `<script>`: raw text for crengine, no children modelled. */\n opaque: boolean\n /** Descendant of `<svg>`: crengine keeps element names case-sensitive there. */\n insideSvg: boolean\n}\n\nconst ELEMENT_NODE = 1\n\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === ELEMENT_NODE\n\n/** The name crengine's XML parser stored: lowercased, except inside `<svg>` (`TXTFLG_CASE_SENSITIVE_TAGS_ATTRS`). */\nexport const crengineElementName = (element: Element, insideSvg: boolean) =>\n insideSvg ? element.localName : element.localName.toLowerCase()\n\n/** Table parts keep their display under `[hidden]` (html5.css re-sets it, the table algorithm needs them). */\nconst TABLE_PART_ELEMENTS = new Set([\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"tr\",\n])\n\nconst getDisplayByName = (name: string): ElementDisplay => {\n if (INVISIBLE_ELEMENTS.has(name)) return \"none\"\n if (BLOCK_ELEMENTS.has(name)) return \"block\"\n if (INLINE_BLOCK_ELEMENTS.has(name)) return \"inlineBlock\"\n if (name === \"ruby\") return \"ruby\"\n\n return \"inline\"\n}\n\n/** The element's default display in KOReader, `[hidden]` included (html5.css). */\nexport const getElementDisplay = (element: Element): ElementDisplay => {\n const name = element.localName.toLowerCase()\n\n if (element.hasAttribute(\"hidden\") && !TABLE_PART_ELEMENTS.has(name))\n return \"none\"\n\n return getDisplayByName(name)\n}\n\nexport const getParentContext = (parent: Element): ParentContext => {\n const name = parent.localName.toLowerCase()\n let pre = false\n let insideSvg = false\n let insideMath = name === \"math\"\n\n for (\n let ancestor: Node | null = parent.parentNode;\n ancestor && isElement(ancestor);\n ancestor = ancestor.parentNode\n ) {\n const ancestorName = ancestor.localName.toLowerCase()\n\n if (PRE_ELEMENTS.has(ancestorName)) pre = true\n if (ancestorName === \"svg\") insideSvg = true\n if (ancestorName === \"math\") insideMath = true\n }\n\n if (insideSvg) {\n const allowsText = SVG_TEXT_ELEMENTS.has(name)\n\n return {\n display: \"inline\",\n block: false,\n allowsText,\n pre: pre || allowsText,\n stripsLeadingNewline: false,\n trimsText: false,\n restructured: false,\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: true,\n }\n }\n\n if (insideMath) {\n return {\n display: \"inline\",\n block: false,\n allowsText: MATHML_TOKEN_ELEMENTS.has(name) || !MATHML_ELEMENTS.has(name),\n pre,\n stripsLeadingNewline: false,\n trimsText: true,\n restructured: true,\n opaque: false,\n insideSvg: false,\n }\n }\n\n const display = getElementDisplay(parent)\n\n return {\n display,\n block: display === \"block\" || display === \"inlineBlock\",\n allowsText: !NO_TEXT_ELEMENTS.has(name),\n pre: pre || PRE_ELEMENTS.has(name),\n stripsLeadingNewline: LEADING_NEWLINE_STRIPPING_ELEMENTS.has(name),\n trimsText: false,\n restructured: display === \"ruby\",\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: name === \"svg\",\n }\n}\n","/**\n * What crengine makes of a text node's characters, and how its offsets map to\n * the raw DOM text. Every rule is from `PreProcessXmlString`, `ExpandTabs` and\n * `LVXMLParser::ReadText` in crengine/src/lvxml.cpp and\n * `ldomElementWriter::onText` in crengine/src/lvtinydom.cpp.\n *\n * crengine strings are UTF-32, so its offsets count code points; DOM offsets\n * count UTF-16 units. Both conversions live here.\n */\n\n/** `TEXT_SPLIT_SIZE` (crengine/src/lvxml.cpp): longer text becomes several text nodes. */\nexport const CRENGINE_TEXT_SPLIT_SIZE = 8192\n\nexport type TextMode = {\n /** `white-space: pre` and friends: whitespace kept, tabs expanded. */\n pre: boolean\n /** First text of a `<pre>` / `<textarea>`: a leading newline is dropped. */\n stripsLeadingNewline: boolean\n /** MathML token text: blanks at both ends removed after the whitespace pass. */\n trim?: boolean\n}\n\nexport type TextPiece = {\n /** UTF-16 range in the raw text node data. */\n start: number\n end: number\n}\n\nconst TAB = 9\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\nconst isSpaceLike = (codePoint: number) =>\n codePoint === SPACE ||\n codePoint === CR ||\n codePoint === LF ||\n codePoint === TAB\n\n/** `IsEmptySpace` (crengine/src/lvtinydom.cpp): only these four count as space; NBSP does not. */\nexport const isEmptySpace = (text: string): boolean => {\n for (let index = 0; index < text.length; index++) {\n const charCode = text.charCodeAt(index)\n\n if (\n charCode !== SPACE &&\n charCode !== CR &&\n charCode !== LF &&\n charCode !== TAB\n )\n return false\n }\n\n return true\n}\n\nconst codePointLength = (codePoint: number) => (codePoint > 0xffff ? 2 : 1)\n\n/**\n * Visits the raw text code point by code point with the number of characters\n * crengine keeps for it (`units`): 0 for a collapsed space or a dropped\n * carriage return, up to 8 for an expanded tab, 1 otherwise. Stops early when\n * the visitor returns `false`.\n */\nconst visitCodePoints = (\n text: string,\n mode: TextMode,\n visit: (rawIndex: number, rawLength: number, units: number) => boolean,\n) => {\n let index = 0\n\n if (!mode.pre) {\n // Outside pre: \\r, \\n and \\t become spaces and a run of spaces keeps its\n // first one. Trimming (MathML) then removes the space at either end.\n let precedingSpaces = 0\n let emitted = 0\n let lastKept = text.length\n\n if (mode.trim) {\n while (lastKept > 0 && isSpaceLike(text.charCodeAt(lastKept - 1)))\n lastKept--\n }\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n\n if (isSpaceLike(codePoint)) {\n units = precedingSpaces === 0 ? 1 : 0\n precedingSpaces++\n\n if (mode.trim && (emitted === 0 || index >= lastKept)) units = 0\n } else {\n precedingSpaces = 0\n }\n\n emitted += units\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n\n return\n }\n\n // Inside pre: \\r\\n and lone \\r become \\n, tabs expand to 8-column stops\n // (columns counted from the start of the text node, a newline itself\n // being column 1 of its line), and the first character is dropped when it\n // is the newline that follows a <pre> start tag.\n let lastChar = 0\n let column = 0\n let stripNewline = mode.stripsLeadingNewline\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n let newline = false\n\n if (codePoint === CR) {\n const next =\n index + length < text.length\n ? text.codePointAt(index + length)\n : undefined\n const kept = (index === 0 || lastChar !== LF) && next !== LF\n\n if (kept) {\n newline = true\n lastChar = LF\n } else {\n units = 0\n }\n } else if (codePoint === LF) {\n newline = true\n lastChar = LF\n } else if (codePoint === TAB) {\n units = 8 - (column & 7)\n column += units\n lastChar = TAB\n } else {\n column++\n lastChar = codePoint\n }\n\n if (newline) {\n // ExpandTabs resets the column, then counts the newline itself.\n column = 1\n }\n\n if (units > 0 && stripNewline) {\n stripNewline = false\n\n if (newline) units = 0\n }\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n}\n\n/** Length of crengine's text, in code points. */\nexport const getCrengineTextLength = (text: string, mode: TextMode) => {\n let length = 0\n\n visitCodePoints(text, mode, (_rawIndex, _rawLength, units) => {\n length += units\n\n return true\n })\n\n return length\n}\n\n/**\n * crengine offset (code points of the normalised text) of a raw UTF-16\n * offset: the characters kept for every code point that ends at or before it.\n */\nexport const toCrengineOffset = (\n text: string,\n rawOffset: number,\n mode: TextMode,\n) => {\n let offset = 0\n\n visitCodePoints(text, mode, (rawIndex, rawLength, units) => {\n if (rawIndex + rawLength > rawOffset) return false\n\n offset += units\n\n return true\n })\n\n return offset\n}\n\n/**\n * Raw UTF-16 offset of a crengine offset, or `undefined` when it is past the\n * end of crengine's text. Characters crengine dropped are skipped, so the\n * position lands right before the next character it kept; an offset inside\n * an expanded tab lands before the tab.\n */\nexport const toRawOffset = (\n text: string,\n crengineOffset: number,\n mode: TextMode,\n): number | undefined => {\n if (crengineOffset < 0) return undefined\n\n let offset = 0\n let rawOffset: number | undefined\n\n visitCodePoints(text, mode, (rawIndex, _rawLength, units) => {\n if (units === 0) return true\n\n if (offset === crengineOffset || offset + units > crengineOffset) {\n rawOffset = rawIndex\n\n return false\n }\n\n offset += units\n\n return true\n })\n\n if (rawOffset !== undefined) return rawOffset\n\n return offset === crengineOffset ? text.length : undefined\n}\n\n/**\n * The pieces crengine's parser cuts a long text node into\n * (`LVXMLParser::ReadText`): once 8192 raw code points are buffered, the\n * buffer is emitted up to and including its last space (or its last lone\n * newline), and the rest starts the next piece. The rule is applied to the\n * decoded DOM text; crengine applies it to the source, so a long text with\n * entity references may split a few characters away.\n */\nexport const splitTextPieces = (text: string): TextPiece[] => {\n if (text.length < CRENGINE_TEXT_SPLIT_SIZE)\n return [{ start: 0, end: text.length }]\n\n const pieces: TextPiece[] = []\n let start = 0\n\n while (start < text.length) {\n let windowEnd = start\n let codePoints = 0\n\n while (windowEnd < text.length && codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n windowEnd += codePointLength(text.codePointAt(windowEnd) ?? 0)\n codePoints++\n }\n\n if (codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n pieces.push({ start, end: text.length })\n\n break\n }\n\n let end = windowEnd\n\n for (let index = windowEnd - 1; index >= start; index--) {\n const charCode = text.charCodeAt(index)\n\n if (charCode === SPACE) {\n end = index + 1\n\n break\n }\n\n if (charCode === CR || charCode === LF) {\n const next = index + 1 < windowEnd ? text.charCodeAt(index + 1) : 0\n\n if (\n (charCode === CR && next !== LF) ||\n (charCode === LF && next !== CR)\n ) {\n end = index + 1\n\n break\n }\n }\n }\n\n pieces.push({ start, end })\n start = end\n }\n\n return pieces\n}\n","import {\n getElementDisplay,\n getParentContext,\n isElement,\n type ParentContext,\n} from \"./elements\"\nimport { isEmptySpace, splitTextPieces, type TextMode } from \"./text\"\n\nconst TEXT_NODE = 3\nconst CDATA_SECTION_NODE = 4\n\nexport const isTextNode = (node: Node): node is Text =>\n node.nodeType === TEXT_NODE || node.nodeType === CDATA_SECTION_NODE\n\n/**\n * A child as crengine holds it: browser comments and processing instructions\n * do not exist, dropped text nodes do not exist, and a long text node is\n * several text nodes.\n */\nexport type CrengineChild =\n | { kind: \"element\"; node: Element; childIndex: number }\n | {\n kind: \"text\"\n node: Text\n childIndex: number\n start: number\n end: number\n pieceIndex: number\n pieceCount: number\n mode: TextMode\n }\n\n/** How the parent's rendering pass sees a child (`detectChildTypes`, `isInlineNode`). */\ntype ChildRole = \"block\" | \"inline\" | \"invisible\" | \"text\"\n\ntype ParsedChild = {\n node: Element | Text\n childIndex: number\n role: ChildRole\n whitespace: boolean\n /** First child inserted by the parser (for `_stripLeadingNewlineChar`). */\n first: boolean\n}\n\n/** Children as the parser inserted them (`ldomElementWriter::onText`, crengine/src/lvtinydom.cpp). */\nconst parseChildren = (parent: Element, context: ParentContext) => {\n const children: ParsedChild[] = []\n let childIndex = 0\n\n if (context.opaque) return children\n\n for (\n let node = parent.firstChild;\n node;\n node = node.nextSibling, childIndex++\n ) {\n if (isElement(node)) {\n const display = context.insideSvg ? \"inline\" : getElementDisplay(node)\n const role: ChildRole =\n display === \"block\"\n ? \"block\"\n : display === \"none\"\n ? \"invisible\"\n : \"inline\"\n\n children.push({\n node,\n childIndex,\n role,\n whitespace: false,\n first: children.length === 0,\n })\n\n continue\n }\n\n if (!isTextNode(node) || !context.allowsText) continue\n\n const whitespace = isEmptySpace(node.data)\n\n // A whitespace-only text that would open a block is not inserted, and\n // MathML text with nothing left once trimmed is not either.\n if (\n whitespace &&\n ((context.block && !context.pre && children.length === 0) ||\n context.trimsText)\n )\n continue\n\n children.push({\n node,\n childIndex,\n role: \"text\",\n whitespace,\n first: children.length === 0,\n })\n }\n\n return children\n}\n\nconst isRunItem = (child: ParsedChild) =>\n child.role === \"inline\" || child.role === \"text\"\n\nconst isWhitespaceText = (child: ParsedChild | undefined) =>\n child !== undefined && child.role === \"text\" && child.whitespace\n\n/**\n * A child once the element is closed: a node, or an `autoBoxing` wrapper\n * crengine put around a run of inline content that sits among block children.\n */\ntype RenderedChild =\n | { kind: \"node\"; child: ParsedChild }\n | { kind: \"box\"; children: ParsedChild[] }\n\n/**\n * What `ldomNode::initNodeRendMethod` (crengine/src/lvtinydom.cpp) does to\n * the children once the element is closed. A block holding both block\n * children and inline content gets every run of inline nodes wrapped in an\n * `autoBoxing`, and `autoboxChildren` first drops the whitespace-only text\n * nodes opening the run and the one closing it (a run made only of them\n * disappears) unless the block is preformatted. An inline element holding\n * block children loses the whitespace-only text nodes next to those blocks,\n * when it also holds inline elements or real text.\n */\nconst renderChildren = (\n children: ParsedChild[],\n context: ParentContext,\n): RenderedChild[] => {\n const asNodes = (): RenderedChild[] =>\n children.map((child) => ({ kind: \"node\", child }))\n\n if (context.display === \"block\" || context.display === \"inlineBlock\") {\n const hasBlockItems = children.some((child) => child.role === \"block\")\n const hasInline = children.some(isRunItem)\n\n if (!hasBlockItems || !hasInline) return asNodes()\n\n const rendered: RenderedChild[] = []\n let index = 0\n\n while (index < children.length) {\n const child = children[index]\n\n if (!child) break\n\n if (!isRunItem(child)) {\n rendered.push({ kind: \"node\", child })\n index++\n\n continue\n }\n\n let end = index\n\n while (end < children.length) {\n const next = children[end]\n\n if (!next || !isRunItem(next)) break\n\n end++\n }\n\n let run = children.slice(index, end)\n\n if (!context.pre) {\n let first = 0\n\n while (first < run.length && isWhitespaceText(run[first])) first++\n\n run = run.slice(first)\n\n if (run.length > 0 && isWhitespaceText(run[run.length - 1]))\n run = run.slice(0, -1)\n }\n\n if (run.length > 0) rendered.push({ kind: \"box\", children: run })\n\n index = end\n }\n\n return rendered\n }\n\n if (context.display === \"inline\" && !context.pre) {\n const hasBlockNodes = children.some((child) => child.role === \"block\")\n\n if (!hasBlockNodes) return asNodes()\n\n const hasInlineNodes = children.some((child) => child.role === \"inline\")\n const hasNonEmptyText = children.some(\n (child) => child.role === \"text\" && !child.whitespace,\n )\n\n if (!hasInlineNodes && !hasNonEmptyText) return asNodes()\n\n const removed = new Set<ParsedChild>()\n\n children.forEach((child, index) => {\n if (child.role !== \"block\") return\n\n const previous = children[index - 1]\n const next = children[index + 1]\n\n if (previous && isWhitespaceText(previous)) removed.add(previous)\n if (next && isWhitespaceText(next)) removed.add(next)\n })\n\n return children\n .filter((child) => !removed.has(child))\n .map((child) => ({ kind: \"node\", child }))\n }\n\n return asNodes()\n}\n\nconst getRenderedChildren = (parent: Element, context: ParentContext) =>\n renderChildren(parseChildren(parent, context), context)\n\nconst visitParsedChild = (\n child: ParsedChild,\n context: ParentContext,\n visit: (child: CrengineChild) => boolean,\n): boolean => {\n if (isElement(child.node)) {\n return visit({\n kind: \"element\",\n node: child.node,\n childIndex: child.childIndex,\n })\n }\n\n const pieces = splitTextPieces(child.node.data)\n\n for (let pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {\n const piece = pieces[pieceIndex]\n\n if (!piece) continue\n\n const keepGoing = visit({\n kind: \"text\",\n node: child.node,\n childIndex: child.childIndex,\n start: piece.start,\n end: piece.end,\n pieceIndex,\n pieceCount: pieces.length,\n mode: {\n pre: context.pre,\n stripsLeadingNewline:\n context.stripsLeadingNewline && child.first && pieceIndex === 0,\n trim: context.trimsText,\n },\n })\n\n if (!keepGoing) return false\n }\n\n return true\n}\n\n/**\n * Walks the nodes crengine kept, in order, stopping when `visit` returns\n * `false`: what its parser inserted, minus what its rendering pass removed,\n * with text longer than `TEXT_SPLIT_SIZE` split into pieces. `autoBoxing`\n * wrappers are walked through, as `getNodeByIndex` does for path steps.\n */\nexport const visitCrengineChildren = (\n parent: Element,\n visit: (child: CrengineChild) => boolean,\n context: ParentContext = getParentContext(parent),\n) => {\n for (const entry of getRenderedChildren(parent, context)) {\n const children = entry.kind === \"box\" ? entry.children : [entry.child]\n\n for (const child of children) {\n if (!visitParsedChild(child, context, visit)) return\n }\n }\n}\n\n/** One child as an element point (`.N`) counts it, with the browser index of the last node it covers. */\nexport type CrengineBoxedChild = {\n child: CrengineChild\n lastChildIndex: number\n}\n\n/**\n * The children an element point (`.N`) counts: crengine checks the point\n * against the element's real child count, where an `autoBoxing` wrapper is\n * one child. Each entry holds the first node the wrapper holds, or the node\n * itself, and where the wrapper ends.\n */\nexport const getCrengineBoxedChildren = (\n parent: Element,\n context: ParentContext = getParentContext(parent),\n): CrengineBoxedChild[] => {\n const children: CrengineBoxedChild[] = []\n\n for (const entry of getRenderedChildren(parent, context)) {\n if (entry.kind === \"box\") {\n const first = entry.children[0]\n const last = entry.children[entry.children.length - 1]\n\n if (first && last) {\n visitParsedChild(first, context, (child) => {\n children.push({ child, lastChildIndex: last.childIndex })\n\n return false\n })\n }\n\n continue\n }\n\n visitParsedChild(entry.child, context, (child) => {\n children.push({ child, lastChildIndex: child.childIndex })\n\n return true\n })\n }\n\n return children\n}\n\nexport const getCrengineChildren = (parent: Element): CrengineChild[] => {\n const children: CrengineChild[] = []\n\n visitCrengineChildren(parent, (child) => {\n children.push(child)\n\n return true\n })\n\n return children\n}\n\n/**\n * The `<body>` crengine's fragment body corresponds to. An SVG spine item\n * has none: crengine wraps such a file in a body of its own, and how it\n * parses what follows (the XML declaration becomes an element there) is not\n * something a DOM can show, so the item is handled as a whole.\n */\nexport const getFragmentBody = (document: Document): Element | undefined =>\n document.body ?? undefined\n","import type { ParsedXPointer } from \"./types\"\n\n/**\n * Prints a pointer in the shape `toStringV2` (crengine/src/lvtinydom.cpp)\n * produces for DOM versions 20200223..20260811, the one every third-party\n * consumer was written against: an index is written only where the parsed\n * structure carries one, and the spine item prefix is always\n * `/body/DocFragment[N]/body`.\n *\n * `serializeXPointer(parseXPointer(s))` is `s` for every pointer crengine\n * writes in that shape for a book with several spine items; the single-item\n * prefix `/body/DocFragment/body` and the explicit `body[1]` prefixes come\n * back normalised. `generateXPointer` decides where an index is needed; this\n * function never looks at a DOM.\n */\nexport const serializeXPointer = (parsed: ParsedXPointer): string => {\n let output = `/body/DocFragment[${parsed.spineItemIndex + 1}]/body`\n\n for (const step of parsed.steps) {\n if (step.kind === \"nodeIndex\") {\n output += `/${step.index}`\n } else {\n const name = step.kind === \"text\" ? \"text()\" : step.name\n\n output +=\n step.index === undefined ? `/${name}` : `/${name}[${step.index}]`\n }\n }\n\n return parsed.point === undefined ? output : `${output}.${parsed.point}`\n}\n","import {\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n isTextNode,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport {\n crengineElementName,\n getParentContext,\n hasImproperTableChildren,\n isElement,\n} from \"./crengine/elements\"\nimport { toCrengineOffset } from \"./crengine/text\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, XPointerStep } from \"./types\"\n\nconst DOCUMENT_NODE = 9\n\nconst isDocument = (node: Node): node is Document =>\n node.nodeType === DOCUMENT_NODE\n\n/**\n * Position of `node` among the same-name element children of `parent`, as\n * `toStringV2` (crengine/src/lvtinydom.cpp) writes it: the index is emitted\n * only when the parent holds more than one element of that name.\n */\nconst elementStep = (\n parent: Element,\n node: Element,\n insideSvg: boolean,\n): XPointerStep | undefined => {\n const name = crengineElementName(node, insideSvg)\n let index: number | undefined\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (\n child.kind !== \"element\" ||\n crengineElementName(child.node, insideSvg) !== name\n )\n return true\n\n count++\n\n if (child.node === node) index = count\n\n // Once the node is found, one more same-name sibling settles the shape.\n return index === undefined || count === 1\n })\n\n if (index === undefined) return undefined\n\n return { kind: \"element\", name, index: count > 1 ? index : undefined }\n}\n\n/**\n * Steps from the fragment body down to `element` (excluded when it is the\n * body). `undefined` when the element does not hang under the body crengine\n * knows (detached, or in `<head>`).\n */\nconst stepsTo = (\n element: Element,\n body: Element,\n): XPointerStep[] | undefined => {\n const chain: Element[] = []\n let node: Node | null = element\n\n while (node !== body) {\n if (!node || !isElement(node)) return undefined\n\n chain.push(node)\n node = node.parentNode\n }\n\n chain.reverse()\n\n const steps: XPointerStep[] = []\n let parent: Element = body\n let insideSvg = false\n\n for (const current of chain) {\n const step = elementStep(parent, current, insideSvg)\n\n if (!step) return undefined\n\n steps.push(step)\n\n if (current.localName.toLowerCase() === \"svg\") insideSvg = true\n\n parent = current\n }\n\n return steps\n}\n\n/** The kept piece of `text` holding UTF-16 `offset`, its 1-based rank among the parent's text pieces and whether it has siblings. */\nconst findTextPiece = (parent: Element, text: Text, offset: number) => {\n let found: Extract<CrengineChild, { kind: \"text\" }> | undefined\n let foundIndex = 0\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (child.kind !== \"text\") return true\n\n count++\n\n if (found === undefined && child.node === text) {\n const last = child.pieceIndex === child.pieceCount - 1\n\n if (offset < child.end || last) {\n found = child\n foundIndex = count\n }\n }\n\n // Once the piece is found, one more text sibling settles the shape.\n return found === undefined || count === 1\n })\n\n if (!found) return undefined\n\n return { piece: found, index: foundIndex, count }\n}\n\n/**\n * crengine's child index for the boundary before browser child `offset`:\n * every child crengine's element holds (an `autoBoxing` wrapper counting\n * once, each piece of a split text node counting) that starts before it. A\n * boundary inside a wrapper becomes the wrapper's start. `undefined` where\n * crengine rebuilds the children with wrappers of its own (ruby, MathML)\n * and a non-zero index cannot be known.\n */\nconst childIndexBefore = (parent: Element, offset: number) => {\n const context = getParentContext(parent)\n let index = 0\n\n for (const { child, lastChildIndex } of getCrengineBoxedChildren(\n parent,\n context,\n )) {\n if (child.childIndex >= offset || offset <= lastChildIndex) break\n\n index++\n }\n\n const unknowable = context.restructured || hasImproperTableChildren(parent)\n\n return unknowable && index > 0 ? undefined : index\n}\n\n/**\n * The classic V2 pointer for a DOM position, as `toStringV2`\n * (crengine/src/lvtinydom.cpp) writes it for a book opened with a DOM\n * version between 20200223 and 20260811: `[N]` only where the name is shared\n * by more than one sibling, `text()` without `[N]` when the parent holds a\n * single text node. The result resolves in crengine to the same node.\n *\n * `undefined` for positions crengine has no node for: a whitespace-only text\n * node it dropped, text under an element that takes no text, an element\n * outside the `<body>`, an offset past the end of a text node, anything\n * inside an SVG spine item but the item itself.\n */\nexport const generateXPointer = (\n position: DomPosition,\n spineItemIndex: number,\n): string | undefined => {\n if (!Number.isInteger(spineItemIndex) || spineItemIndex < 0) return undefined\n\n const { node, offset } = position\n const document = isDocument(node) ? node : node.ownerDocument\n\n if (!document) return undefined\n\n const body = getFragmentBody(document)\n const build = (\n steps: XPointerStep[] | undefined,\n point: number | undefined,\n ) =>\n steps === undefined\n ? undefined\n : serializeXPointer({\n spineItemIndex,\n steps,\n point,\n containsBoxingElements: false,\n })\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return isDocument(node) || node === document.documentElement\n ? build([], undefined)\n : undefined\n }\n\n if (isDocument(node)) return undefined\n\n if (isTextNode(node)) {\n const parent = node.parentNode\n const rawOffset = offset ?? 0\n\n if (\n !parent ||\n !isElement(parent) ||\n rawOffset < 0 ||\n rawOffset > node.data.length\n )\n return undefined\n\n const text = findTextPiece(parent, node, rawOffset)\n\n if (!text) return undefined\n\n const { piece } = text\n const steps = stepsTo(parent, body)\n\n if (!steps) return undefined\n\n steps.push({ kind: \"text\", index: text.count > 1 ? text.index : undefined })\n\n return build(\n steps,\n toCrengineOffset(\n node.data.slice(piece.start, piece.end),\n rawOffset - piece.start,\n piece.mode,\n ),\n )\n }\n\n if (!isElement(node)) return undefined\n\n if (offset === undefined) return build(stepsTo(node, body), undefined)\n\n if (offset < 0 || offset > node.childNodes.length) return undefined\n\n const point = childIndexBefore(node, offset)\n\n return point === undefined ? undefined : build(stepsTo(node, body), point)\n}\n","import type { ParsedXPointer, XPointerStep } from \"./types\"\n\n/**\n * Wrapper elements crengine inserts into its own DOM for layout\n * (crengine/include/fb2def.h, `EL_BOXING_START`..`EL_BOXING_END`) plus the\n * `pseudoElem` it creates for `::before` / `::after`. None exists in the source\n * XHTML, so a pointer naming one cannot be resolved in a browser DOM.\n */\nexport const XPOINTER_BOXING_ELEMENT_NAMES: readonly string[] = [\n \"autoBoxing\",\n \"tabularBox\",\n \"rubyBox\",\n \"mathBox\",\n \"floatBox\",\n \"inlineBox\",\n \"pseudoElem\",\n]\n\nconst boxingElementNames = new Set<string>(\n XPOINTER_BOXING_ELEMENT_NAMES.map((name) => name.toLowerCase()),\n)\n\nconst isDigit = (charCode: number) => charCode >= 48 && charCode <= 57\n\n/** An element name as crengine's XML parser can produce one (no `.`, which ends a step). */\nconst ELEMENT_NAME = /^[A-Za-z_][A-Za-z0-9_:-]*$/\n\nconst isStepBoundary = (char: string | undefined) =>\n char === undefined || char === \"/\" || char === \".\"\n\nconst readDigits = (input: string, from: number) => {\n let end = from\n\n while (end < input.length && isDigit(input.charCodeAt(end))) end++\n\n return end\n}\n\n/**\n * Port of `ParseXPathStep` (crengine/src/lvtinydom.cpp): a pointer is a\n * sequence of `/name`, `/name[N]`, `/text()`, `/text()[N]` or `/N` steps, with\n * an optional `.N` point that must end the string (`createXPointerV2` rejects\n * anything after it).\n *\n * Stricter than crengine on malformed input: `[N]` must be plain decimal\n * digits, where crengine's `atoi` would also take signs, hex or surrounding\n * blanks and silently turn garbage into an unresolvable index 0, and a step\n * name must be a plausible element name, where crengine would look up any\n * text (`text()x`, `p q`) and fail to find an element.\n */\nconst tokenize = (\n input: string,\n): { steps: XPointerStep[]; point: number | undefined } | undefined => {\n const steps: XPointerStep[] = []\n let position = 0\n\n while (position < input.length) {\n const prefix = input[position]\n\n if (prefix === \".\") {\n const digitsStart = position + 1\n const digitsEnd = readDigits(input, digitsStart)\n\n if (digitsEnd === digitsStart || digitsEnd !== input.length)\n return undefined\n\n return { steps, point: Number(input.slice(digitsStart, digitsEnd)) }\n }\n\n if (prefix !== \"/\") return undefined\n\n const stepStart = position + 1\n\n if (isDigit(input.charCodeAt(stepStart))) {\n const digitsEnd = readDigits(input, stepStart)\n\n if (!isStepBoundary(input[digitsEnd])) return undefined\n\n const index = Number(input.slice(stepStart, digitsEnd))\n\n if (index < 1) return undefined\n\n steps.push({ kind: \"nodeIndex\", index })\n position = digitsEnd\n\n continue\n }\n\n let nameEnd = stepStart\n\n while (nameEnd < input.length && !\"[/.\".includes(input[nameEnd] ?? \"\"))\n nameEnd++\n\n if (nameEnd === stepStart) return undefined\n\n const name = input.slice(stepStart, nameEnd)\n let index: number | undefined\n let stepEnd = nameEnd\n\n if (input[nameEnd] === \"[\") {\n const closing = input.indexOf(\"]\", nameEnd + 1)\n\n if (closing === -1) return undefined\n\n const digits = input.slice(nameEnd + 1, closing)\n\n if (digits.length === 0 || readDigits(digits, 0) !== digits.length)\n return undefined\n\n index = Number(digits)\n\n if (index < 1) return undefined\n\n stepEnd = closing + 1\n }\n\n if (!isStepBoundary(input[stepEnd])) return undefined\n\n if (name === \"text()\") {\n steps.push({ kind: \"text\", index })\n } else if (ELEMENT_NAME.test(name)) {\n steps.push({ kind: \"element\", name, index })\n } else {\n return undefined\n }\n position = stepEnd\n }\n\n return { steps, point: undefined }\n}\n\nconst isElementNamed = (\n step: XPointerStep | undefined,\n name: string,\n): step is Extract<XPointerStep, { kind: \"element\" }> =>\n step?.kind === \"element\" && step.name.toLowerCase() === name\n\n/**\n * Accepts every shape crengine has emitted (`toStringV1`, `toStringV2` and\n * its explicit-index variant for DOM versions >= 20260812) plus the bare\n * `/body/DocFragment[N]` third parties write for the start of a spine item.\n *\n * Returns `undefined` for anything crengine's own parser would reject or that\n * does not start with the `/body/DocFragment[N]/body` prefix of an EPUB\n * pointer. crengine's `#id` form is rejected too: it names an element of the\n * whole book DOM by id, which a spine item document cannot answer.\n */\nexport const parseXPointer = (input: string): ParsedXPointer | undefined => {\n const tokenized = tokenize(input)\n\n if (!tokenized) return undefined\n\n const [root, fragment, body, ...steps] = tokenized.steps\n\n if (!isElementNamed(root, \"body\") || (root.index ?? 1) !== 1) return undefined\n\n if (!isElementNamed(fragment, \"docfragment\")) return undefined\n\n const spineItemIndex = (fragment.index ?? 1) - 1\n\n // `/body/DocFragment[N]` alone, with or without a point (Kavita emits\n // `/body/DocFragment[N].0`): the start of the spine item.\n if (body === undefined) {\n return {\n spineItemIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n }\n }\n\n if (!isElementNamed(body, \"body\") || (body.index ?? 1) !== 1) return undefined\n\n return {\n spineItemIndex,\n steps,\n point: tokenized.point,\n containsBoxingElements: steps.some(\n (step) =>\n step.kind === \"element\" &&\n boxingElementNames.has(step.name.toLowerCase()),\n ),\n }\n}\n","import {\n type CrengineBoxedChild,\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport { toRawOffset } from \"./crengine/text\"\nimport { parseXPointer } from \"./parse\"\nimport type { DomPosition, ParsedXPointer, XPointerStep } from \"./types\"\n\ntype Current =\n | { kind: \"parent\"; node: Element }\n | Extract<CrengineChild, { kind: \"text\" }>\n\n/**\n * One step of `createXPointerV2` (crengine/src/lvtinydom.cpp): the N-th child\n * matching the step (first when the index is omitted), counted among the\n * children crengine kept. Element names compare case-insensitively.\n */\nconst findChild = (\n parent: Element,\n step: XPointerStep,\n): CrengineChild | undefined => {\n const wantedIndex = step.index ?? 1\n const wantedName =\n step.kind === \"element\" ? step.name.toLowerCase() : undefined\n let count = 0\n let found: CrengineChild | undefined\n\n visitCrengineChildren(parent, (child) => {\n const matches =\n step.kind === \"nodeIndex\" ||\n (step.kind === \"text\"\n ? child.kind === \"text\"\n : child.kind === \"element\" &&\n child.node.localName.toLowerCase() === wantedName)\n\n if (!matches) return true\n\n count++\n\n if (count === wantedIndex) {\n found = child\n\n return false\n }\n\n return true\n })\n\n return found\n}\n\nconst positionOfChild = (\n parent: Element,\n children: CrengineBoxedChild[],\n index: number,\n): DomPosition => {\n const child = children[index]?.child\n\n if (!child) return { node: parent, offset: parent.childNodes.length }\n\n // The boundary before a later piece of a split text node lies inside it.\n if (child.kind === \"text\" && child.pieceIndex > 0) {\n return { node: child.node, offset: child.start }\n }\n\n return { node: parent, offset: child.childIndex }\n}\n\n/**\n * Resolves a pointer inside the spine item's document, following\n * `createXPointerV2` (crengine/src/lvtinydom.cpp). Returns `undefined` when\n * crengine would fail too: a step without a match, a point past the end of\n * the text or of the children (nothing is clamped), a V1 pointer naming a\n * boxing element.\n *\n * A text position is returned with a UTF-16 offset into the text node,\n * ready for `Range.setStart`; a text step without a point is its start. In\n * a spine item without a `<body>` (an SVG document) only the item itself\n * resolves, to its root element.\n */\nexport const resolveXPointer = (\n xpointer: string | ParsedXPointer,\n document: Document,\n): DomPosition | undefined => {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed || parsed.containsBoxingElements) return undefined\n\n const body = getFragmentBody(document)\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return parsed.steps.length === 0\n ? { node: document.documentElement }\n : undefined\n }\n\n let current: Current = { kind: \"parent\", node: body }\n\n for (const step of parsed.steps) {\n if (current.kind === \"text\") return undefined\n\n const child = findChild(current.node, step)\n\n if (!child) return undefined\n\n current =\n child.kind === \"element\" ? { kind: \"parent\", node: child.node } : child\n }\n\n if (current.kind === \"text\") {\n if (parsed.point === undefined) {\n return { node: current.node, offset: current.start }\n }\n\n const rawOffset = toRawOffset(\n current.node.data.slice(current.start, current.end),\n parsed.point,\n current.mode,\n )\n\n return rawOffset === undefined\n ? undefined\n : { node: current.node, offset: current.start + rawOffset }\n }\n\n const parent = current.node\n\n if (parsed.point === undefined) return { node: parent }\n\n const children = getCrengineBoxedChildren(parent)\n\n if (parsed.point > children.length) return undefined\n\n return positionOfChild(parent, children, parsed.point)\n}\n","import {\n generate,\n getEpubCfiSpineItemref,\n isIndirectionOnly,\n parse,\n resolve,\n} from \"@prose-reader/cfi\"\nimport { isTextNode } from \"./crengine/children\"\nimport { isElement } from \"./crengine/elements\"\nimport { generateXPointer } from \"./generate\"\nimport { parseXPointer } from \"./parse\"\nimport { resolveXPointer } from \"./resolve\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, ParsedXPointer } from \"./types\"\n\nexport type XPointerSpineItem = {\n /** The spine item's content document, parsed as XHTML (what prose renders in its frames). */\n document: Document\n /** The `<itemref>` idref, written as the CFI's id assertion (`/6/14[chap05]!`) when given. */\n id?: string\n}\n\n/**\n * Hands out the document of a spine item by its 0-based index. A pointer\n * names its own spine item (`DocFragment[N]`) and is resolved in that\n * document, never in whichever one happens to be on screen: Readest shipped\n * that bug (readest/readest#5980) and landed a chapter away.\n */\nexport type XPointerSpineItemLookup = (\n spineItemIndex: number,\n) => XPointerSpineItem | undefined\n\n/**\n * The position a CFI can point to for a DOM position. A CFI cannot point\n * between an element's children, so a child index point becomes the start of\n * that child, and the end of the children the end of the last text child or\n * the element itself. Any other position is returned as it is.\n */\nexport const toCfiPosition = (position: DomPosition): DomPosition => {\n const { node, offset } = position\n\n if (!isElement(node) || offset === undefined) return position\n\n const child = node.childNodes[offset]\n\n if (child) {\n return isTextNode(child) ? { node: child, offset: 0 } : { node: child }\n }\n\n const last = node.lastChild\n\n return last && isTextNode(last)\n ? { node: last, offset: last.data.length }\n : { node }\n}\n\n/**\n * The CFI of a KOReader position, built with `@prose-reader/cfi` in the\n * spine item document the pointer names. `undefined` when the pointer is\n * malformed, its spine item is not available or it does not resolve; the\n * spine index alone is still available through `parseXPointer`.\n */\nexport const xPointerToCfi = (\n xpointer: string | ParsedXPointer,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed) return undefined\n\n const spineItem = getSpineItem(parsed.spineItemIndex)\n\n if (!spineItem) return undefined\n\n const position = resolveXPointer(parsed, spineItem.document)\n\n if (!position) return undefined\n\n const { node, offset } = toCfiPosition(position)\n\n return generate({\n node,\n offset: isTextNode(node) ? offset : undefined,\n spineIndex: parsed.spineItemIndex,\n spineId: spineItem.id,\n })\n } catch {\n return undefined\n }\n}\n\nconst firstOffset = (offset: number | number[] | undefined) =>\n Array.isArray(offset) ? offset[0] : offset\n\n/**\n * The KOReader pointer of a prose CFI: the CFI's start when it is a range,\n * the start of the spine item for a root CFI (`epubcfi(/6/14[chap05]!)`).\n * `undefined` when the CFI is malformed, its spine item is not available or\n * it does not resolve in that document.\n */\nexport const cfiToXPointer = (\n cfi: string,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const itemref = getEpubCfiSpineItemref(cfi)\n\n if (!itemref) return undefined\n\n const { spineIndex } = itemref\n\n if (!Number.isInteger(spineIndex) || spineIndex < 0) return undefined\n\n const parsed = parse(cfi)\n\n if (isIndirectionOnly(parsed)) {\n return serializeXPointer({\n spineItemIndex: spineIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n })\n }\n\n const spineItem = getSpineItem(spineIndex)\n\n if (!spineItem) return undefined\n\n const resolved = resolve(parsed, spineItem.document)\n\n if (!resolved.node) return undefined\n\n const position: DomPosition = resolved.isRange\n ? {\n node: resolved.node.startContainer,\n offset: resolved.node.startOffset,\n }\n : {\n node: resolved.node,\n offset: isTextNode(resolved.node)\n ? (firstOffset(resolved.offset) ?? 0)\n : undefined,\n }\n\n return generateXPointer(position, spineIndex)\n } catch {\n return undefined\n }\n}\n","import { md5HexDigest } from \"@prose-reader/shared\"\n\n/** Bytes `util.partialMD5` reads at each of its offsets. */\nconst PARTIAL_MD5_SAMPLE_SIZE = 1024\n\n/**\n * Where `util.partialMD5` (KOReader, frontend/util.lua) reads: at\n * `bit.lshift(1024, 2 * i)` for `i` from -1 to 10. LuaJIT keeps the low five\n * bits of a shift count, so `i = -1` shifts 1024 by 30 bits, out of 32 bits,\n * and reads at 0.\n */\nconst PARTIAL_MD5_SAMPLE_OFFSETS = [\n 0, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864,\n 268435456, 1073741824,\n]\n\n/**\n * The digest KOReader files a book's reading position under with its default\n * \"Binary\" document matching method: `util.partialMD5` (frontend/util.lua),\n * the MD5 of 1 KiB samples of the file at the offsets above, as lowercase hex.\n * A sample the end of the file cuts short is hashed as it is, and the samples\n * stop at the first offset at or past the end: at most 12 KiB of `file` are\n * read.\n *\n * KOReader computes it from the bytes it opens the first time and keeps it in\n * the book's settings, so the digests match only for the same bytes.\n */\nexport const getKoreaderFileDigest = async (file: Blob): Promise<string> => {\n const samples = await Promise.all(\n PARTIAL_MD5_SAMPLE_OFFSETS.filter((offset) => offset < file.size).map(\n async (offset) =>\n new Uint8Array(\n await file\n .slice(offset, offset + PARTIAL_MD5_SAMPLE_SIZE)\n .arrayBuffer(),\n ),\n ),\n )\n\n return md5HexDigest(samples)\n}\n\n/**\n * The digest KOReader files a book's reading position under with its\n * \"Filename\" document matching method (`KOSync:getFileNameDigest`,\n * plugins/kosync.koplugin/main.lua): the MD5 of the file name, as lowercase\n * hex.\n *\n * `fileName` is the base name, extension included, as the device's file\n * system stores it. KOReader hashes its UTF-8 bytes as they are, without\n * Unicode normalization: an `é` stored as one code point and one stored as an\n * `e` and a combining accent give two digests.\n */\nexport const getKoreaderFileNameDigest = (fileName: string): string =>\n md5HexDigest([new TextEncoder().encode(fileName)])\n","/**\n * What a KOReader sync server's `progress` holds for a book: a crengine\n * xpointer, or a 1-based page number written as a string (\"12\").\n */\nexport type KoreaderProgressKind = \"xpointer\" | \"page\"\n\n/** Books KOReader opens with crengine, whose `progress` is an xpointer. */\nconst XPOINTER_BOOK_MEDIA_TYPES: ReadonlySet<string> = new Set([\n \"application/epub+zip\",\n \"application/epub\",\n])\n\n/**\n * Books KOReader opens with MuPDF, whose `progress` is a page number: a PDF\n * and a comic archive.\n */\nconst PAGE_BOOK_MEDIA_TYPES: ReadonlySet<string> = new Set([\n \"application/pdf\",\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n \"application/vnd.comicbook-rar\",\n \"application/vnd.rar\",\n \"application/x-cbr\",\n \"application/vnd.comicbook+tar\",\n \"application/x-cbt\",\n])\n\n/**\n * The form of a book's `progress`, from the media type of its file. KOReader's\n * sync plugin picks the form from the engine that opened the book (`has_pages`\n * in `plugins/kosync.koplugin/main.lua`), and KOReader picks the engine from\n * the file's extension (`frontend/document/documentregistry.lua`):\n *\n * - `\"xpointer\"` for an EPUB, opened with crengine.\n * - `\"page\"` for a PDF or a CBZ, CBR or CBT, opened with MuPDF.\n * - `undefined` for anything else, `application/octet-stream` included. A\n * plain text file is opened with crengine too, but crengine builds its own\n * document from the text, with paragraphs and headings it detects, and no\n * xpointer into it can be computed from the text alone. A plain ZIP is\n * opened with the engine its first file calls for, which its media type\n * does not tell: a caller that knows a ZIP opens as a comic gives it a\n * comic's media type.\n *\n * A KOReader user can choose MuPDF for a single EPUB, which then syncs page\n * numbers; nothing in the file says so.\n */\nexport const getKoreaderProgressKind = (\n mediaType: string,\n): KoreaderProgressKind | undefined => {\n const essence = mediaType.split(\";\")[0]?.trim().toLowerCase() ?? \"\"\n\n if (XPOINTER_BOOK_MEDIA_TYPES.has(essence)) return \"xpointer\"\n if (PAGE_BOOK_MEDIA_TYPES.has(essence)) return \"page\"\n\n return undefined\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { CRENGINE_TEXT_SPLIT_SIZE } from './crengine/text';
3
3
  export { getKoreaderFileDigest, getKoreaderFileNameDigest } from './digest';
4
4
  export { generateXPointer } from './generate';
5
5
  export { parseXPointer, XPOINTER_BOXING_ELEMENT_NAMES } from './parse';
6
+ export { getKoreaderProgressKind, type KoreaderProgressKind } from './progress';
6
7
  export { resolveXPointer } from './resolve';
7
8
  export { serializeXPointer } from './serialize';
8
9
  export type { DomPosition, ParsedXPointer, XPointerStep } from './types';
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
68
68
  if (!t) return !1;
69
69
  for (let n = e.firstElementChild; n; n = n.nextElementSibling) if (!t.has(n.localName.toLowerCase())) return !0;
70
70
  return !1;
71
- }, m = /* @__PURE__ */ new Set(["pre", "textarea"]), h = 1, g = (e) => e.nodeType === h, _ = (e, t) => t ? e.localName : e.localName.toLowerCase(), v = /* @__PURE__ */ new Set([
71
+ }, re = /* @__PURE__ */ new Set(["pre", "textarea"]), ie = 1, m = (e) => e.nodeType === ie, h = (e, t) => t ? e.localName : e.localName.toLowerCase(), ae = /* @__PURE__ */ new Set([
72
72
  "col",
73
73
  "colgroup",
74
74
  "tbody",
@@ -77,12 +77,12 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
77
77
  "th",
78
78
  "thead",
79
79
  "tr"
80
- ]), y = (e) => c.has(e) ? "none" : o.has(e) ? "block" : s.has(e) ? "inlineBlock" : e === "ruby" ? "ruby" : "inline", b = (e) => {
80
+ ]), g = (e) => c.has(e) ? "none" : o.has(e) ? "block" : s.has(e) ? "inlineBlock" : e === "ruby" ? "ruby" : "inline", _ = (e) => {
81
81
  let t = e.localName.toLowerCase();
82
- return e.hasAttribute("hidden") && !v.has(t) ? "none" : y(t);
83
- }, x = (e) => {
82
+ return e.hasAttribute("hidden") && !ae.has(t) ? "none" : g(t);
83
+ }, v = (e) => {
84
84
  let t = e.localName.toLowerCase(), n = !1, r = !1, i = t === "math";
85
- for (let t = e.parentNode; t && g(t); t = t.parentNode) {
85
+ for (let t = e.parentNode; t && m(t); t = t.parentNode) {
86
86
  let e = t.localName.toLowerCase();
87
87
  u.has(e) && (n = !0), e === "svg" && (r = !0), e === "math" && (i = !0);
88
88
  }
@@ -111,54 +111,54 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
111
111
  opaque: !1,
112
112
  insideSvg: !1
113
113
  };
114
- let a = b(e);
114
+ let a = _(e);
115
115
  return {
116
116
  display: a,
117
117
  block: a === "block" || a === "inlineBlock",
118
118
  allowsText: !l.has(t),
119
119
  pre: n || u.has(t),
120
- stripsLeadingNewline: m.has(t),
120
+ stripsLeadingNewline: re.has(t),
121
121
  trimsText: !1,
122
122
  restructured: a === "ruby",
123
123
  opaque: p.has(t),
124
124
  insideSvg: t === "svg"
125
125
  };
126
- }, re = 8192, S = 9, C = 10, w = 13, T = 32, E = (e) => e === T || e === w || e === C || e === S, D = (e) => {
126
+ }, y = 8192, b = 9, x = 10, S = 13, C = 32, w = (e) => e === C || e === S || e === x || e === b, oe = (e) => {
127
127
  for (let t = 0; t < e.length; t++) {
128
128
  let n = e.charCodeAt(t);
129
- if (n !== T && n !== w && n !== C && n !== S) return !1;
129
+ if (n !== C && n !== S && n !== x && n !== b) return !1;
130
130
  }
131
131
  return !0;
132
- }, O = (e) => e > 65535 ? 2 : 1, k = (e, t, n) => {
132
+ }, T = (e) => e > 65535 ? 2 : 1, E = (e, t, n) => {
133
133
  let r = 0;
134
134
  if (!t.pre) {
135
135
  let i = 0, a = 0, o = e.length;
136
- if (t.trim) for (; o > 0 && E(e.charCodeAt(o - 1));) o--;
136
+ if (t.trim) for (; o > 0 && w(e.charCodeAt(o - 1));) o--;
137
137
  for (; r < e.length;) {
138
- let s = e.codePointAt(r) ?? 0, c = O(s), l = 1;
139
- if (E(s) ? (l = +(i === 0), i++, t.trim && (a === 0 || r >= o) && (l = 0)) : i = 0, a += l, !n(r, c, l)) return;
138
+ let s = e.codePointAt(r) ?? 0, c = T(s), l = 1;
139
+ if (w(s) ? (l = +(i === 0), i++, t.trim && (a === 0 || r >= o) && (l = 0)) : i = 0, a += l, !n(r, c, l)) return;
140
140
  r += c;
141
141
  }
142
142
  return;
143
143
  }
144
144
  let i = 0, a = 0, o = t.stripsLeadingNewline;
145
145
  for (; r < e.length;) {
146
- let t = e.codePointAt(r) ?? 0, s = O(t), c = 1, l = !1;
147
- if (t === w) {
146
+ let t = e.codePointAt(r) ?? 0, s = T(t), c = 1, l = !1;
147
+ if (t === S) {
148
148
  let t = r + s < e.length ? e.codePointAt(r + s) : void 0;
149
- (r === 0 || i !== C) && t !== C ? (l = !0, i = C) : c = 0;
150
- } else t === C ? (l = !0, i = C) : t === S ? (c = 8 - (a & 7), a += c, i = S) : (a++, i = t);
149
+ (r === 0 || i !== x) && t !== x ? (l = !0, i = x) : c = 0;
150
+ } else t === x ? (l = !0, i = x) : t === b ? (c = 8 - (a & 7), a += c, i = b) : (a++, i = t);
151
151
  if (l && (a = 1), c > 0 && o && (o = !1, l && (c = 0)), !n(r, s, c)) return;
152
152
  r += s;
153
153
  }
154
- }, ie = (e, t, n) => {
154
+ }, D = (e, t, n) => {
155
155
  let r = 0;
156
- return k(e, n, (e, n, i) => e + n > t ? !1 : (r += i, !0)), r;
157
- }, ae = (e, t, n) => {
156
+ return E(e, n, (e, n, i) => e + n > t ? !1 : (r += i, !0)), r;
157
+ }, O = (e, t, n) => {
158
158
  if (t < 0) return;
159
159
  let r = 0, i;
160
- return k(e, n, (e, n, a) => a === 0 ? !0 : r === t || r + a > t ? (i = e, !1) : (r += a, !0)), i === void 0 ? r === t ? e.length : void 0 : i;
161
- }, oe = (e) => {
160
+ return E(e, n, (e, n, a) => a === 0 ? !0 : r === t || r + a > t ? (i = e, !1) : (r += a, !0)), i === void 0 ? r === t ? e.length : void 0 : i;
161
+ }, k = (e) => {
162
162
  if (e.length < 8192) return [{
163
163
  start: 0,
164
164
  end: e.length
@@ -166,7 +166,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
166
166
  let t = [], n = 0;
167
167
  for (; n < e.length;) {
168
168
  let r = n, i = 0;
169
- for (; r < e.length && i < 8192;) r += O(e.codePointAt(r) ?? 0), i++;
169
+ for (; r < e.length && i < 8192;) r += T(e.codePointAt(r) ?? 0), i++;
170
170
  if (i < 8192) {
171
171
  t.push({
172
172
  start: n,
@@ -177,13 +177,13 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
177
177
  let a = r;
178
178
  for (let t = r - 1; t >= n; t--) {
179
179
  let n = e.charCodeAt(t);
180
- if (n === T) {
180
+ if (n === C) {
181
181
  a = t + 1;
182
182
  break;
183
183
  }
184
- if (n === w || n === C) {
184
+ if (n === S || n === x) {
185
185
  let i = t + 1 < r ? e.charCodeAt(t + 1) : 0;
186
- if (n === w && i !== C || n === C && i !== w) {
186
+ if (n === S && i !== x || n === x && i !== S) {
187
187
  a = t + 1;
188
188
  break;
189
189
  }
@@ -199,8 +199,8 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
199
199
  let n = [], r = 0;
200
200
  if (t.opaque) return n;
201
201
  for (let i = e.firstChild; i; i = i.nextSibling, r++) {
202
- if (g(i)) {
203
- let e = t.insideSvg ? "inline" : b(i), a = e === "block" ? "block" : e === "none" ? "invisible" : "inline";
202
+ if (m(i)) {
203
+ let e = t.insideSvg ? "inline" : _(i), a = e === "block" ? "block" : e === "none" ? "invisible" : "inline";
204
204
  n.push({
205
205
  node: i,
206
206
  childIndex: r,
@@ -211,7 +211,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
211
211
  continue;
212
212
  }
213
213
  if (!A(i) || !t.allowsText) continue;
214
- let e = D(i.data);
214
+ let e = oe(i.data);
215
215
  e && (t.block && !t.pre && n.length === 0 || t.trimsText) || n.push({
216
216
  node: i,
217
217
  childIndex: r,
@@ -275,12 +275,12 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
275
275
  }
276
276
  return n();
277
277
  }, N = (e, t) => ue(le(e, t), t), P = (e, t, n) => {
278
- if (g(e.node)) return n({
278
+ if (m(e.node)) return n({
279
279
  kind: "element",
280
280
  node: e.node,
281
281
  childIndex: e.childIndex
282
282
  });
283
- let r = oe(e.node.data);
283
+ let r = k(e.node.data);
284
284
  for (let i = 0; i < r.length; i++) {
285
285
  let a = r[i];
286
286
  if (a && !n({
@@ -299,12 +299,12 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
299
299
  })) return !1;
300
300
  }
301
301
  return !0;
302
- }, F = (e, t, n = x(e)) => {
302
+ }, F = (e, t, n = v(e)) => {
303
303
  for (let r of N(e, n)) {
304
304
  let e = r.kind === "box" ? r.children : [r.child];
305
305
  for (let r of e) if (!P(r, n, t)) return;
306
306
  }
307
- }, I = (e, t = x(e)) => {
307
+ }, I = (e, t = v(e)) => {
308
308
  let n = [];
309
309
  for (let r of N(e, t)) if (r.kind === "box") {
310
310
  let e = r.children[0], i = r.children[r.children.length - 1];
@@ -326,8 +326,8 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
326
326
  }
327
327
  return e.point === void 0 ? t : `${t}.${e.point}`;
328
328
  }, z = 9, B = (e) => e.nodeType === z, V = (e, t, n) => {
329
- let r = _(t, n), i, a = 0;
330
- if (F(e, (e) => e.kind !== "element" || _(e.node, n) !== r || (a++, e.node === t && (i = a), i === void 0 || a === 1)), i !== void 0) return {
329
+ let r = h(t, n), i, a = 0;
330
+ if (F(e, (e) => e.kind !== "element" || h(e.node, n) !== r || (a++, e.node === t && (i = a), i === void 0 || a === 1)), i !== void 0) return {
331
331
  kind: "element",
332
332
  name: r,
333
333
  index: a > 1 ? i : void 0
@@ -335,7 +335,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
335
335
  }, H = (e, t) => {
336
336
  let n = [], r = e;
337
337
  for (; r !== t;) {
338
- if (!r || !g(r)) return;
338
+ if (!r || !m(r)) return;
339
339
  n.push(r), r = r.parentNode;
340
340
  }
341
341
  n.reverse();
@@ -361,7 +361,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
361
361
  count: a
362
362
  };
363
363
  }, de = (e, t) => {
364
- let n = x(e), r = 0;
364
+ let n = v(e), r = 0;
365
365
  for (let { child: i, lastChildIndex: a } of I(e, n)) {
366
366
  if (i.childIndex >= t || t <= a) break;
367
367
  r++;
@@ -381,16 +381,16 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
381
381
  if (B(n)) return;
382
382
  if (A(n)) {
383
383
  let e = n.parentNode, t = r ?? 0;
384
- if (!e || !g(e) || t < 0 || t > n.data.length) return;
384
+ if (!e || !m(e) || t < 0 || t > n.data.length) return;
385
385
  let i = U(e, n, t);
386
386
  if (!i) return;
387
387
  let { piece: s } = i, c = H(e, a);
388
388
  return c ? (c.push({
389
389
  kind: "text",
390
390
  index: i.count > 1 ? i.index : void 0
391
- }), o(c, ie(n.data.slice(s.start, s.end), t - s.start, s.mode))) : void 0;
391
+ }), o(c, D(n.data.slice(s.start, s.end), t - s.start, s.mode))) : void 0;
392
392
  }
393
- if (!g(n)) return;
393
+ if (!m(n)) return;
394
394
  if (r === void 0) return o(H(n, a), void 0);
395
395
  if (r < 0 || r > n.childNodes.length) return;
396
396
  let s = de(n, r);
@@ -480,7 +480,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
480
480
  }, he = (e, t) => {
481
481
  let n = t.index ?? 1, r = t.kind === "element" ? t.name.toLowerCase() : void 0, i = 0, a;
482
482
  return F(e, (e) => t.kind === "nodeIndex" || (t.kind === "text" ? e.kind === "text" : e.kind === "element" && e.node.localName.toLowerCase() === r) ? (i++, i !== n || (a = e, !1)) : !0), a;
483
- }, Z = (e, t, n) => {
483
+ }, ge = (e, t, n) => {
484
484
  let r = t[n]?.child;
485
485
  return r ? r.kind === "text" && r.pieceIndex > 0 ? {
486
486
  node: r.node,
@@ -492,7 +492,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
492
492
  node: e,
493
493
  offset: e.childNodes.length
494
494
  };
495
- }, Q = (e, t) => {
495
+ }, Z = (e, t) => {
496
496
  let n = typeof e == "string" ? X(e) : e;
497
497
  if (!n || n.containsBoxingElements) return;
498
498
  let r = L(t);
@@ -515,7 +515,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
515
515
  node: i.node,
516
516
  offset: i.start
517
517
  };
518
- let e = ae(i.node.data.slice(i.start, i.end), n.point, i.mode);
518
+ let e = O(i.node.data.slice(i.start, i.end), n.point, i.mode);
519
519
  return e === void 0 ? void 0 : {
520
520
  node: i.node,
521
521
  offset: i.start + e
@@ -524,10 +524,10 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
524
524
  let a = i.node;
525
525
  if (n.point === void 0) return { node: a };
526
526
  let o = I(a);
527
- if (!(n.point > o.length)) return Z(a, o, n.point);
528
- }, $ = (e) => {
527
+ if (!(n.point > o.length)) return ge(a, o, n.point);
528
+ }, Q = (e) => {
529
529
  let { node: t, offset: n } = e;
530
- if (!g(t) || n === void 0) return e;
530
+ if (!m(t) || n === void 0) return e;
531
531
  let r = t.childNodes[n];
532
532
  if (r) return A(r) ? {
533
533
  node: r,
@@ -538,15 +538,15 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
538
538
  node: i,
539
539
  offset: i.data.length
540
540
  } : { node: t };
541
- }, ge = (t, n) => {
541
+ }, _e = (t, n) => {
542
542
  try {
543
543
  let r = typeof t == "string" ? X(t) : t;
544
544
  if (!r) return;
545
545
  let i = n(r.spineItemIndex);
546
546
  if (!i) return;
547
- let a = Q(r, i.document);
547
+ let a = Z(r, i.document);
548
548
  if (!a) return;
549
- let { node: o, offset: s } = $(a);
549
+ let { node: o, offset: s } = Q(a);
550
550
  return e({
551
551
  node: o,
552
552
  offset: A(o) ? s : void 0,
@@ -556,7 +556,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
556
556
  } catch {
557
557
  return;
558
558
  }
559
- }, _e = (e) => Array.isArray(e) ? e[0] : e, ve = (e, a) => {
559
+ }, ve = (e) => Array.isArray(e) ? e[0] : e, $ = (e, a) => {
560
560
  try {
561
561
  let o = t(e);
562
562
  if (!o) return;
@@ -577,7 +577,7 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
577
577
  offset: u.node.startOffset
578
578
  } : {
579
579
  node: u.node,
580
- offset: A(u.node) ? _e(u.offset) ?? 0 : void 0
580
+ offset: A(u.node) ? ve(u.offset) ?? 0 : void 0
581
581
  }, s) : void 0;
582
582
  } catch {
583
583
  return;
@@ -598,8 +598,21 @@ var o = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
598
598
  ], xe = async (e) => {
599
599
  let t = await Promise.all(be.filter((t) => t < e.size).map(async (t) => new Uint8Array(await e.slice(t, t + ye).arrayBuffer())));
600
600
  return a(t);
601
- }, Se = (e) => a([new TextEncoder().encode(e)]);
601
+ }, Se = (e) => a([new TextEncoder().encode(e)]), Ce = /* @__PURE__ */ new Set(["application/epub+zip", "application/epub"]), we = /* @__PURE__ */ new Set([
602
+ "application/pdf",
603
+ "application/vnd.comicbook+zip",
604
+ "application/x-cbz",
605
+ "application/vnd.comicbook-rar",
606
+ "application/vnd.rar",
607
+ "application/x-cbr",
608
+ "application/vnd.comicbook+tar",
609
+ "application/x-cbt"
610
+ ]), Te = (e) => {
611
+ let t = e.split(";")[0]?.trim().toLowerCase() ?? "";
612
+ if (Ce.has(t)) return "xpointer";
613
+ if (we.has(t)) return "page";
614
+ };
602
615
  //#endregion
603
- export { re as CRENGINE_TEXT_SPLIT_SIZE, G as XPOINTER_BOXING_ELEMENT_NAMES, ve as cfiToXPointer, W as generateXPointer, xe as getKoreaderFileDigest, Se as getKoreaderFileNameDigest, X as parseXPointer, Q as resolveXPointer, R as serializeXPointer, $ as toCfiPosition, ge as xPointerToCfi };
616
+ export { y as CRENGINE_TEXT_SPLIT_SIZE, G as XPOINTER_BOXING_ELEMENT_NAMES, $ as cfiToXPointer, W as generateXPointer, xe as getKoreaderFileDigest, Se as getKoreaderFileNameDigest, Te as getKoreaderProgressKind, X as parseXPointer, Z as resolveXPointer, R as serializeXPointer, Q as toCfiPosition, _e as xPointerToCfi };
604
617
 
605
618
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["BLOCK_ELEMENTS","INLINE_BLOCK_ELEMENTS","INVISIBLE_ELEMENTS","NO_TEXT_ELEMENTS","PRE_ELEMENTS","SVG_TEXT_ELEMENTS","MATHML_ELEMENTS","MATHML_TOKEN_ELEMENTS","RAW_TEXT_ELEMENTS","PROPER_TABLE_CHILDREN","hasImproperTableChildren","proper","element","child","LEADING_NEWLINE_STRIPPING_ELEMENTS","ELEMENT_NODE","isElement","node","crengineElementName","insideSvg","TABLE_PART_ELEMENTS","getDisplayByName","name","getElementDisplay","getParentContext","parent","pre","insideMath","ancestor","ancestorName","allowsText","display","CRENGINE_TEXT_SPLIT_SIZE","TAB","LF","CR","SPACE","isSpaceLike","codePoint","isEmptySpace","index","text","charCode","codePointLength","visitCodePoints","mode","precedingSpaces","emitted","lastKept","length","units","visit","lastChar","column","stripNewline","newline","next","toCrengineOffset","offset","rawIndex","rawLength","rawOffset","toRawOffset","crengineOffset","splitTextPieces","pieces","start","windowEnd","codePoints","end","TEXT_NODE","CDATA_SECTION_NODE","isTextNode","parseChildren","children","childIndex","context","role","whitespace","isRunItem","isWhitespaceText","renderChildren","asNodes","hasBlockItems","hasInline","rendered","run","first","hasInlineNodes","hasNonEmptyText","removed","previous","getRenderedChildren","visitParsedChild","pieceIndex","piece","visitCrengineChildren","entry","getCrengineBoxedChildren","last","getFragmentBody","document","serializeXPointer","output","parsed","step","DOCUMENT_NODE","isDocument","elementStep","count","stepsTo","chain","body","steps","current","findTextPiece","found","foundIndex","childIndexBefore","lastChildIndex","generateXPointer","spineItemIndex","position","build","point","XPOINTER_BOXING_ELEMENT_NAMES","boxingElementNames","isDigit","ELEMENT_NAME","isStepBoundary","char","readDigits","from","input","tokenize","prefix","digitsStart","digitsEnd","stepStart","nameEnd","stepEnd","closing","digits","isElementNamed","parseXPointer","tokenized","root","fragment","findChild","wantedIndex","wantedName","positionOfChild","resolveXPointer","xpointer","toCfiPosition","xPointerToCfi","spineItem","getSpineItem","generate","firstOffset","cfiToXPointer","itemref","getEpubCfiSpineItemref","cfi","spineIndex","parse","isIndirectionOnly","resolved","resolve","PARTIAL_MD5_SAMPLE_SIZE","PARTIAL_MD5_SAMPLE_OFFSETS","getKoreaderFileDigest","samples","file","md5HexDigest","getKoreaderFileNameDigest","fileName"],"sources":["../src/crengine/elements.ts","../src/crengine/text.ts","../src/crengine/children.ts","../src/serialize.ts","../src/generate.ts","../src/parse.ts","../src/resolve.ts","../src/cfi.ts","../src/digest.ts"],"sourcesContent":["/**\n * How crengine classifies elements while it loads a spine item, from its\n * built-in element table (crengine/include/fb2def.h) and the stylesheet\n * KOReader applies before any book CSS (data/html5.css, imported by\n * data/epub.css). These defaults decide what happens to text nodes in\n * `ldomElementWriter::onText` and `ldomNode::initNodeRendMethod`\n * (crengine/src/lvtinydom.cpp):\n *\n * - `display` — whether whitespace-only text children are dropped,\n * - `allow_text` — elements such as `<table>` or `<tr>` drop every text child,\n * - `white-space` — pre-like elements keep whitespace verbatim.\n *\n * Only these defaults are known here. crengine takes the values from the\n * computed style, so a book stylesheet that changes `display` or\n * `white-space` (class rules, inline styles) is not seen: this is the\n * package's documented approximation.\n */\n\n/**\n * Elements whose default `display` is neither `inline` nor `none`: block,\n * list-item, table and table parts. `isBlockNode` in crengine treats all of\n * them alike (`display > css_d_inline && display != css_d_none`). Unknown\n * elements are inline.\n */\nconst BLOCK_ELEMENTS = new Set([\n \"address\",\n \"annotation\",\n \"applet\",\n \"article\",\n \"aside\",\n \"audio\",\n \"author\",\n \"autoboxing\",\n \"blockquote\",\n \"body\",\n \"book-name\",\n \"book-title\",\n \"canvas\",\n \"caption\",\n \"center\",\n \"city\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"custom-info\",\n \"date\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"docfragment\",\n \"document-info\",\n \"dt\",\n \"email\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"floatbox\",\n \"footer\",\n \"form\",\n \"frame\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"header\",\n \"hgroup\",\n \"history\",\n \"home-page\",\n \"hr\",\n \"html\",\n \"id\",\n \"iframe\",\n \"isbn\",\n \"keygen\",\n \"keywords\",\n \"lang\",\n \"legend\",\n \"li\",\n \"listing\",\n \"main\",\n \"map\",\n \"menu\",\n \"multicol\",\n \"nav\",\n \"nickname\",\n \"noframes\",\n \"noscript\",\n \"ol\",\n \"optgroup\",\n \"output\",\n \"p\",\n \"part\",\n \"plaintext\",\n \"poem\",\n \"pre\",\n \"program-used\",\n \"publish-info\",\n \"publisher\",\n \"search\",\n \"section\",\n \"select\",\n \"sequence\",\n \"source\",\n \"src-lang\",\n \"src-ocr\",\n \"src-title-info\",\n \"src-url\",\n \"stanza\",\n \"subtitle\",\n \"summary\",\n \"table\",\n \"tabularbox\",\n \"tbody\",\n \"td\",\n \"text-author\",\n \"textarea\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n \"ul\",\n \"v\",\n \"version\",\n \"video\",\n \"xmp\",\n \"year\",\n])\n\n/** `display: inline-block` in html5.css: a block for its own children, an inline item for its parent. */\nconst INLINE_BLOCK_ELEMENTS = new Set([\"button\", \"input\", \"marquee\"])\n\n/** `display: none` (fb2def.h and html5.css): invisible, not counted as inline nor block by the parent. */\nconst INVISIBLE_ELEMENTS = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"binary\",\n \"clonenode\",\n \"datalist\",\n \"description\",\n \"genre\",\n \"head\",\n \"link\",\n \"meta\",\n \"noembed\",\n \"param\",\n \"pseudoelem\",\n \"script\",\n \"style\",\n \"stylesheet\",\n \"template\",\n \"title\",\n])\n\n/** Elements defined with `allow_text = false` in fb2def.h: their text children are never inserted. */\nconst NO_TEXT_ELEMENTS = new Set([\n \"annotation\",\n \"applet\",\n \"area\",\n \"audio\",\n \"author\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"clonenode\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"description\",\n \"docfragment\",\n \"document-info\",\n \"embed\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"head\",\n \"html\",\n \"image\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"object\",\n \"param\",\n \"part\",\n \"poem\",\n \"pseudoelem\",\n \"publish-info\",\n \"source\",\n \"src-title-info\",\n \"stanza\",\n \"svg\",\n \"table\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n])\n\n/** Elements whose default `white-space` is `pre` or `pre-wrap` (fb2def.h, html5.css). */\nconst PRE_ELEMENTS = new Set([\n \"listing\",\n \"plaintext\",\n \"pre\",\n \"style\",\n \"textarea\",\n \"xmp\",\n])\n\n/**\n * Inside `<svg>` crengine only keeps text under these elements, verbatim\n * (`ldomElementWriter` constructor, `_insideSVG`).\n */\nconst SVG_TEXT_ELEMENTS = new Set([\n \"desc\",\n \"metadata\",\n \"style\",\n \"text\",\n \"textpath\",\n \"title\",\n \"tspan\",\n])\n\n/**\n * MathML elements crengine knows (fb2def.h, `EL_MATHML_START`..`EL_MATHML_END`).\n * Inside `<math>`, `MathMLHelper::getMathMLAdjustedText` (crengine/src/mathml.cpp)\n * drops the text of every one of them except the token elements, whose text\n * it trims; text under any other element is trimmed too.\n */\nconst MATHML_ELEMENTS = new Set([\n \"annotation-xml\",\n \"maction\",\n \"maligngroup\",\n \"malignmark\",\n \"math\",\n \"menclose\",\n \"merror\",\n \"mfenced\",\n \"mfrac\",\n \"mglyph\",\n \"mi\",\n \"mlabeledtr\",\n \"mlongdiv\",\n \"mmultiscripts\",\n \"mn\",\n \"mo\",\n \"mover\",\n \"mpadded\",\n \"mphantom\",\n \"mprescripts\",\n \"mroot\",\n \"mrow\",\n \"ms\",\n \"mscarries\",\n \"mscarry\",\n \"msgroup\",\n \"msline\",\n \"mspace\",\n \"msqrt\",\n \"msrow\",\n \"mstack\",\n \"mstyle\",\n \"msub\",\n \"msubsup\",\n \"msup\",\n \"mtable\",\n \"mtd\",\n \"mtext\",\n \"mtr\",\n \"munder\",\n \"munderover\",\n \"none\",\n \"semantics\",\n])\n\n/** `EL_MATHML_TOKEN_START`..`EL_MATHML_TOKEN_END`: the MathML elements that hold text. */\nconst MATHML_TOKEN_ELEMENTS = new Set([\n \"mglyph\",\n \"mi\",\n \"mn\",\n \"mo\",\n \"mtext\",\n \"mspace\",\n \"ms\",\n])\n\n/**\n * crengine's parser reads the content of these as one raw text node up to the\n * closing tag (`LVXMLParser::ReadText`, `m_in_html_style_tag` /\n * `m_in_html_script_tag`), so the elements an XML parser finds inside them\n * do not exist for it. Nothing is modelled inside them.\n */\nconst RAW_TEXT_ELEMENTS = new Set([\"script\", \"style\"])\n\n/**\n * The children a table part may hold without crengine wrapping them in a\n * `tabularBox` (`initTableRendMethods`, \"Generate missing child wrappers\"):\n * any other element child makes the part's child count unknowable.\n */\nconst PROPER_TABLE_CHILDREN: Record<string, Set<string>> = {\n table: new Set([\n \"caption\",\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"tr\",\n ]),\n tbody: new Set([\"tr\"]),\n tfoot: new Set([\"tr\"]),\n thead: new Set([\"tr\"]),\n tr: new Set([\"td\", \"th\"]),\n}\n\n/** Whether crengine had to complete this table part with wrappers of its own. */\nexport const hasImproperTableChildren = (element: Element) => {\n const proper = PROPER_TABLE_CHILDREN[element.localName.toLowerCase()]\n\n if (!proper) return false\n\n for (\n let child = element.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n if (!proper.has(child.localName.toLowerCase())) return true\n }\n\n return false\n}\n\n/** `pre` and `textarea` lose a newline immediately following their start tag (`_stripLeadingNewlineChar`, DOM >= 20210904). */\nconst LEADING_NEWLINE_STRIPPING_ELEMENTS = new Set([\"pre\", \"textarea\"])\n\n/**\n * What an element is to crengine's rendering pass: `block` and `inlineBlock`\n * contain blocks or a final line box, `inline` flows in its parent's text,\n * `none` is invisible, `ruby` is inline with its own layout.\n */\nexport type ElementDisplay =\n | \"block\"\n | \"inlineBlock\"\n | \"inline\"\n | \"none\"\n | \"ruby\"\n\nexport type ParentContext = {\n display: ElementDisplay\n /** Whitespace-only first text child dropped at parse time (`_isBlock`). */\n block: boolean\n /** Text children inserted at all (`_allowText`). */\n allowsText: boolean\n /** Text kept verbatim, tabs expanded (`TXTFLG_PRE`). */\n pre: boolean\n stripsLeadingNewline: boolean\n /** Inside `<math>`: text trimmed of surrounding blanks, dropped when nothing is left. */\n trimsText: boolean\n /** Ruby and MathML: crengine rebuilds the children with its own wrappers, so child indexes are not knowable. */\n restructured: boolean\n /** `<style>` / `<script>`: raw text for crengine, no children modelled. */\n opaque: boolean\n /** Descendant of `<svg>`: crengine keeps element names case-sensitive there. */\n insideSvg: boolean\n}\n\nconst ELEMENT_NODE = 1\n\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === ELEMENT_NODE\n\n/** The name crengine's XML parser stored: lowercased, except inside `<svg>` (`TXTFLG_CASE_SENSITIVE_TAGS_ATTRS`). */\nexport const crengineElementName = (element: Element, insideSvg: boolean) =>\n insideSvg ? element.localName : element.localName.toLowerCase()\n\n/** Table parts keep their display under `[hidden]` (html5.css re-sets it, the table algorithm needs them). */\nconst TABLE_PART_ELEMENTS = new Set([\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"tr\",\n])\n\nconst getDisplayByName = (name: string): ElementDisplay => {\n if (INVISIBLE_ELEMENTS.has(name)) return \"none\"\n if (BLOCK_ELEMENTS.has(name)) return \"block\"\n if (INLINE_BLOCK_ELEMENTS.has(name)) return \"inlineBlock\"\n if (name === \"ruby\") return \"ruby\"\n\n return \"inline\"\n}\n\n/** The element's default display in KOReader, `[hidden]` included (html5.css). */\nexport const getElementDisplay = (element: Element): ElementDisplay => {\n const name = element.localName.toLowerCase()\n\n if (element.hasAttribute(\"hidden\") && !TABLE_PART_ELEMENTS.has(name))\n return \"none\"\n\n return getDisplayByName(name)\n}\n\nexport const getParentContext = (parent: Element): ParentContext => {\n const name = parent.localName.toLowerCase()\n let pre = false\n let insideSvg = false\n let insideMath = name === \"math\"\n\n for (\n let ancestor: Node | null = parent.parentNode;\n ancestor && isElement(ancestor);\n ancestor = ancestor.parentNode\n ) {\n const ancestorName = ancestor.localName.toLowerCase()\n\n if (PRE_ELEMENTS.has(ancestorName)) pre = true\n if (ancestorName === \"svg\") insideSvg = true\n if (ancestorName === \"math\") insideMath = true\n }\n\n if (insideSvg) {\n const allowsText = SVG_TEXT_ELEMENTS.has(name)\n\n return {\n display: \"inline\",\n block: false,\n allowsText,\n pre: pre || allowsText,\n stripsLeadingNewline: false,\n trimsText: false,\n restructured: false,\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: true,\n }\n }\n\n if (insideMath) {\n return {\n display: \"inline\",\n block: false,\n allowsText: MATHML_TOKEN_ELEMENTS.has(name) || !MATHML_ELEMENTS.has(name),\n pre,\n stripsLeadingNewline: false,\n trimsText: true,\n restructured: true,\n opaque: false,\n insideSvg: false,\n }\n }\n\n const display = getElementDisplay(parent)\n\n return {\n display,\n block: display === \"block\" || display === \"inlineBlock\",\n allowsText: !NO_TEXT_ELEMENTS.has(name),\n pre: pre || PRE_ELEMENTS.has(name),\n stripsLeadingNewline: LEADING_NEWLINE_STRIPPING_ELEMENTS.has(name),\n trimsText: false,\n restructured: display === \"ruby\",\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: name === \"svg\",\n }\n}\n","/**\n * What crengine makes of a text node's characters, and how its offsets map to\n * the raw DOM text. Every rule is from `PreProcessXmlString`, `ExpandTabs` and\n * `LVXMLParser::ReadText` in crengine/src/lvxml.cpp and\n * `ldomElementWriter::onText` in crengine/src/lvtinydom.cpp.\n *\n * crengine strings are UTF-32, so its offsets count code points; DOM offsets\n * count UTF-16 units. Both conversions live here.\n */\n\n/** `TEXT_SPLIT_SIZE` (crengine/src/lvxml.cpp): longer text becomes several text nodes. */\nexport const CRENGINE_TEXT_SPLIT_SIZE = 8192\n\nexport type TextMode = {\n /** `white-space: pre` and friends: whitespace kept, tabs expanded. */\n pre: boolean\n /** First text of a `<pre>` / `<textarea>`: a leading newline is dropped. */\n stripsLeadingNewline: boolean\n /** MathML token text: blanks at both ends removed after the whitespace pass. */\n trim?: boolean\n}\n\nexport type TextPiece = {\n /** UTF-16 range in the raw text node data. */\n start: number\n end: number\n}\n\nconst TAB = 9\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\nconst isSpaceLike = (codePoint: number) =>\n codePoint === SPACE ||\n codePoint === CR ||\n codePoint === LF ||\n codePoint === TAB\n\n/** `IsEmptySpace` (crengine/src/lvtinydom.cpp): only these four count as space; NBSP does not. */\nexport const isEmptySpace = (text: string): boolean => {\n for (let index = 0; index < text.length; index++) {\n const charCode = text.charCodeAt(index)\n\n if (\n charCode !== SPACE &&\n charCode !== CR &&\n charCode !== LF &&\n charCode !== TAB\n )\n return false\n }\n\n return true\n}\n\nconst codePointLength = (codePoint: number) => (codePoint > 0xffff ? 2 : 1)\n\n/**\n * Visits the raw text code point by code point with the number of characters\n * crengine keeps for it (`units`): 0 for a collapsed space or a dropped\n * carriage return, up to 8 for an expanded tab, 1 otherwise. Stops early when\n * the visitor returns `false`.\n */\nconst visitCodePoints = (\n text: string,\n mode: TextMode,\n visit: (rawIndex: number, rawLength: number, units: number) => boolean,\n) => {\n let index = 0\n\n if (!mode.pre) {\n // Outside pre: \\r, \\n and \\t become spaces and a run of spaces keeps its\n // first one. Trimming (MathML) then removes the space at either end.\n let precedingSpaces = 0\n let emitted = 0\n let lastKept = text.length\n\n if (mode.trim) {\n while (lastKept > 0 && isSpaceLike(text.charCodeAt(lastKept - 1)))\n lastKept--\n }\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n\n if (isSpaceLike(codePoint)) {\n units = precedingSpaces === 0 ? 1 : 0\n precedingSpaces++\n\n if (mode.trim && (emitted === 0 || index >= lastKept)) units = 0\n } else {\n precedingSpaces = 0\n }\n\n emitted += units\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n\n return\n }\n\n // Inside pre: \\r\\n and lone \\r become \\n, tabs expand to 8-column stops\n // (columns counted from the start of the text node, a newline itself\n // being column 1 of its line), and the first character is dropped when it\n // is the newline that follows a <pre> start tag.\n let lastChar = 0\n let column = 0\n let stripNewline = mode.stripsLeadingNewline\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n let newline = false\n\n if (codePoint === CR) {\n const next =\n index + length < text.length\n ? text.codePointAt(index + length)\n : undefined\n const kept = (index === 0 || lastChar !== LF) && next !== LF\n\n if (kept) {\n newline = true\n lastChar = LF\n } else {\n units = 0\n }\n } else if (codePoint === LF) {\n newline = true\n lastChar = LF\n } else if (codePoint === TAB) {\n units = 8 - (column & 7)\n column += units\n lastChar = TAB\n } else {\n column++\n lastChar = codePoint\n }\n\n if (newline) {\n // ExpandTabs resets the column, then counts the newline itself.\n column = 1\n }\n\n if (units > 0 && stripNewline) {\n stripNewline = false\n\n if (newline) units = 0\n }\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n}\n\n/** Length of crengine's text, in code points. */\nexport const getCrengineTextLength = (text: string, mode: TextMode) => {\n let length = 0\n\n visitCodePoints(text, mode, (_rawIndex, _rawLength, units) => {\n length += units\n\n return true\n })\n\n return length\n}\n\n/**\n * crengine offset (code points of the normalised text) of a raw UTF-16\n * offset: the characters kept for every code point that ends at or before it.\n */\nexport const toCrengineOffset = (\n text: string,\n rawOffset: number,\n mode: TextMode,\n) => {\n let offset = 0\n\n visitCodePoints(text, mode, (rawIndex, rawLength, units) => {\n if (rawIndex + rawLength > rawOffset) return false\n\n offset += units\n\n return true\n })\n\n return offset\n}\n\n/**\n * Raw UTF-16 offset of a crengine offset, or `undefined` when it is past the\n * end of crengine's text. Characters crengine dropped are skipped, so the\n * position lands right before the next character it kept; an offset inside\n * an expanded tab lands before the tab.\n */\nexport const toRawOffset = (\n text: string,\n crengineOffset: number,\n mode: TextMode,\n): number | undefined => {\n if (crengineOffset < 0) return undefined\n\n let offset = 0\n let rawOffset: number | undefined\n\n visitCodePoints(text, mode, (rawIndex, _rawLength, units) => {\n if (units === 0) return true\n\n if (offset === crengineOffset || offset + units > crengineOffset) {\n rawOffset = rawIndex\n\n return false\n }\n\n offset += units\n\n return true\n })\n\n if (rawOffset !== undefined) return rawOffset\n\n return offset === crengineOffset ? text.length : undefined\n}\n\n/**\n * The pieces crengine's parser cuts a long text node into\n * (`LVXMLParser::ReadText`): once 8192 raw code points are buffered, the\n * buffer is emitted up to and including its last space (or its last lone\n * newline), and the rest starts the next piece. The rule is applied to the\n * decoded DOM text; crengine applies it to the source, so a long text with\n * entity references may split a few characters away.\n */\nexport const splitTextPieces = (text: string): TextPiece[] => {\n if (text.length < CRENGINE_TEXT_SPLIT_SIZE)\n return [{ start: 0, end: text.length }]\n\n const pieces: TextPiece[] = []\n let start = 0\n\n while (start < text.length) {\n let windowEnd = start\n let codePoints = 0\n\n while (windowEnd < text.length && codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n windowEnd += codePointLength(text.codePointAt(windowEnd) ?? 0)\n codePoints++\n }\n\n if (codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n pieces.push({ start, end: text.length })\n\n break\n }\n\n let end = windowEnd\n\n for (let index = windowEnd - 1; index >= start; index--) {\n const charCode = text.charCodeAt(index)\n\n if (charCode === SPACE) {\n end = index + 1\n\n break\n }\n\n if (charCode === CR || charCode === LF) {\n const next = index + 1 < windowEnd ? text.charCodeAt(index + 1) : 0\n\n if (\n (charCode === CR && next !== LF) ||\n (charCode === LF && next !== CR)\n ) {\n end = index + 1\n\n break\n }\n }\n }\n\n pieces.push({ start, end })\n start = end\n }\n\n return pieces\n}\n","import {\n getElementDisplay,\n getParentContext,\n isElement,\n type ParentContext,\n} from \"./elements\"\nimport { isEmptySpace, splitTextPieces, type TextMode } from \"./text\"\n\nconst TEXT_NODE = 3\nconst CDATA_SECTION_NODE = 4\n\nexport const isTextNode = (node: Node): node is Text =>\n node.nodeType === TEXT_NODE || node.nodeType === CDATA_SECTION_NODE\n\n/**\n * A child as crengine holds it: browser comments and processing instructions\n * do not exist, dropped text nodes do not exist, and a long text node is\n * several text nodes.\n */\nexport type CrengineChild =\n | { kind: \"element\"; node: Element; childIndex: number }\n | {\n kind: \"text\"\n node: Text\n childIndex: number\n start: number\n end: number\n pieceIndex: number\n pieceCount: number\n mode: TextMode\n }\n\n/** How the parent's rendering pass sees a child (`detectChildTypes`, `isInlineNode`). */\ntype ChildRole = \"block\" | \"inline\" | \"invisible\" | \"text\"\n\ntype ParsedChild = {\n node: Element | Text\n childIndex: number\n role: ChildRole\n whitespace: boolean\n /** First child inserted by the parser (for `_stripLeadingNewlineChar`). */\n first: boolean\n}\n\n/** Children as the parser inserted them (`ldomElementWriter::onText`, crengine/src/lvtinydom.cpp). */\nconst parseChildren = (parent: Element, context: ParentContext) => {\n const children: ParsedChild[] = []\n let childIndex = 0\n\n if (context.opaque) return children\n\n for (\n let node = parent.firstChild;\n node;\n node = node.nextSibling, childIndex++\n ) {\n if (isElement(node)) {\n const display = context.insideSvg ? \"inline\" : getElementDisplay(node)\n const role: ChildRole =\n display === \"block\"\n ? \"block\"\n : display === \"none\"\n ? \"invisible\"\n : \"inline\"\n\n children.push({\n node,\n childIndex,\n role,\n whitespace: false,\n first: children.length === 0,\n })\n\n continue\n }\n\n if (!isTextNode(node) || !context.allowsText) continue\n\n const whitespace = isEmptySpace(node.data)\n\n // A whitespace-only text that would open a block is not inserted, and\n // MathML text with nothing left once trimmed is not either.\n if (\n whitespace &&\n ((context.block && !context.pre && children.length === 0) ||\n context.trimsText)\n )\n continue\n\n children.push({\n node,\n childIndex,\n role: \"text\",\n whitespace,\n first: children.length === 0,\n })\n }\n\n return children\n}\n\nconst isRunItem = (child: ParsedChild) =>\n child.role === \"inline\" || child.role === \"text\"\n\nconst isWhitespaceText = (child: ParsedChild | undefined) =>\n child !== undefined && child.role === \"text\" && child.whitespace\n\n/**\n * A child once the element is closed: a node, or an `autoBoxing` wrapper\n * crengine put around a run of inline content that sits among block children.\n */\ntype RenderedChild =\n | { kind: \"node\"; child: ParsedChild }\n | { kind: \"box\"; children: ParsedChild[] }\n\n/**\n * What `ldomNode::initNodeRendMethod` (crengine/src/lvtinydom.cpp) does to\n * the children once the element is closed. A block holding both block\n * children and inline content gets every run of inline nodes wrapped in an\n * `autoBoxing`, and `autoboxChildren` first drops the whitespace-only text\n * nodes opening the run and the one closing it (a run made only of them\n * disappears) unless the block is preformatted. An inline element holding\n * block children loses the whitespace-only text nodes next to those blocks,\n * when it also holds inline elements or real text.\n */\nconst renderChildren = (\n children: ParsedChild[],\n context: ParentContext,\n): RenderedChild[] => {\n const asNodes = (): RenderedChild[] =>\n children.map((child) => ({ kind: \"node\", child }))\n\n if (context.display === \"block\" || context.display === \"inlineBlock\") {\n const hasBlockItems = children.some((child) => child.role === \"block\")\n const hasInline = children.some(isRunItem)\n\n if (!hasBlockItems || !hasInline) return asNodes()\n\n const rendered: RenderedChild[] = []\n let index = 0\n\n while (index < children.length) {\n const child = children[index]\n\n if (!child) break\n\n if (!isRunItem(child)) {\n rendered.push({ kind: \"node\", child })\n index++\n\n continue\n }\n\n let end = index\n\n while (end < children.length) {\n const next = children[end]\n\n if (!next || !isRunItem(next)) break\n\n end++\n }\n\n let run = children.slice(index, end)\n\n if (!context.pre) {\n let first = 0\n\n while (first < run.length && isWhitespaceText(run[first])) first++\n\n run = run.slice(first)\n\n if (run.length > 0 && isWhitespaceText(run[run.length - 1]))\n run = run.slice(0, -1)\n }\n\n if (run.length > 0) rendered.push({ kind: \"box\", children: run })\n\n index = end\n }\n\n return rendered\n }\n\n if (context.display === \"inline\" && !context.pre) {\n const hasBlockNodes = children.some((child) => child.role === \"block\")\n\n if (!hasBlockNodes) return asNodes()\n\n const hasInlineNodes = children.some((child) => child.role === \"inline\")\n const hasNonEmptyText = children.some(\n (child) => child.role === \"text\" && !child.whitespace,\n )\n\n if (!hasInlineNodes && !hasNonEmptyText) return asNodes()\n\n const removed = new Set<ParsedChild>()\n\n children.forEach((child, index) => {\n if (child.role !== \"block\") return\n\n const previous = children[index - 1]\n const next = children[index + 1]\n\n if (previous && isWhitespaceText(previous)) removed.add(previous)\n if (next && isWhitespaceText(next)) removed.add(next)\n })\n\n return children\n .filter((child) => !removed.has(child))\n .map((child) => ({ kind: \"node\", child }))\n }\n\n return asNodes()\n}\n\nconst getRenderedChildren = (parent: Element, context: ParentContext) =>\n renderChildren(parseChildren(parent, context), context)\n\nconst visitParsedChild = (\n child: ParsedChild,\n context: ParentContext,\n visit: (child: CrengineChild) => boolean,\n): boolean => {\n if (isElement(child.node)) {\n return visit({\n kind: \"element\",\n node: child.node,\n childIndex: child.childIndex,\n })\n }\n\n const pieces = splitTextPieces(child.node.data)\n\n for (let pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {\n const piece = pieces[pieceIndex]\n\n if (!piece) continue\n\n const keepGoing = visit({\n kind: \"text\",\n node: child.node,\n childIndex: child.childIndex,\n start: piece.start,\n end: piece.end,\n pieceIndex,\n pieceCount: pieces.length,\n mode: {\n pre: context.pre,\n stripsLeadingNewline:\n context.stripsLeadingNewline && child.first && pieceIndex === 0,\n trim: context.trimsText,\n },\n })\n\n if (!keepGoing) return false\n }\n\n return true\n}\n\n/**\n * Walks the nodes crengine kept, in order, stopping when `visit` returns\n * `false`: what its parser inserted, minus what its rendering pass removed,\n * with text longer than `TEXT_SPLIT_SIZE` split into pieces. `autoBoxing`\n * wrappers are walked through, as `getNodeByIndex` does for path steps.\n */\nexport const visitCrengineChildren = (\n parent: Element,\n visit: (child: CrengineChild) => boolean,\n context: ParentContext = getParentContext(parent),\n) => {\n for (const entry of getRenderedChildren(parent, context)) {\n const children = entry.kind === \"box\" ? entry.children : [entry.child]\n\n for (const child of children) {\n if (!visitParsedChild(child, context, visit)) return\n }\n }\n}\n\n/** One child as an element point (`.N`) counts it, with the browser index of the last node it covers. */\nexport type CrengineBoxedChild = {\n child: CrengineChild\n lastChildIndex: number\n}\n\n/**\n * The children an element point (`.N`) counts: crengine checks the point\n * against the element's real child count, where an `autoBoxing` wrapper is\n * one child. Each entry holds the first node the wrapper holds, or the node\n * itself, and where the wrapper ends.\n */\nexport const getCrengineBoxedChildren = (\n parent: Element,\n context: ParentContext = getParentContext(parent),\n): CrengineBoxedChild[] => {\n const children: CrengineBoxedChild[] = []\n\n for (const entry of getRenderedChildren(parent, context)) {\n if (entry.kind === \"box\") {\n const first = entry.children[0]\n const last = entry.children[entry.children.length - 1]\n\n if (first && last) {\n visitParsedChild(first, context, (child) => {\n children.push({ child, lastChildIndex: last.childIndex })\n\n return false\n })\n }\n\n continue\n }\n\n visitParsedChild(entry.child, context, (child) => {\n children.push({ child, lastChildIndex: child.childIndex })\n\n return true\n })\n }\n\n return children\n}\n\nexport const getCrengineChildren = (parent: Element): CrengineChild[] => {\n const children: CrengineChild[] = []\n\n visitCrengineChildren(parent, (child) => {\n children.push(child)\n\n return true\n })\n\n return children\n}\n\n/**\n * The `<body>` crengine's fragment body corresponds to. An SVG spine item\n * has none: crengine wraps such a file in a body of its own, and how it\n * parses what follows (the XML declaration becomes an element there) is not\n * something a DOM can show, so the item is handled as a whole.\n */\nexport const getFragmentBody = (document: Document): Element | undefined =>\n document.body ?? undefined\n","import type { ParsedXPointer } from \"./types\"\n\n/**\n * Prints a pointer in the shape `toStringV2` (crengine/src/lvtinydom.cpp)\n * produces for DOM versions 20200223..20260811, the one every third-party\n * consumer was written against: an index is written only where the parsed\n * structure carries one, and the spine item prefix is always\n * `/body/DocFragment[N]/body`.\n *\n * `serializeXPointer(parseXPointer(s))` is `s` for every pointer crengine\n * writes in that shape for a book with several spine items; the single-item\n * prefix `/body/DocFragment/body` and the explicit `body[1]` prefixes come\n * back normalised. `generateXPointer` decides where an index is needed; this\n * function never looks at a DOM.\n */\nexport const serializeXPointer = (parsed: ParsedXPointer): string => {\n let output = `/body/DocFragment[${parsed.spineItemIndex + 1}]/body`\n\n for (const step of parsed.steps) {\n if (step.kind === \"nodeIndex\") {\n output += `/${step.index}`\n } else {\n const name = step.kind === \"text\" ? \"text()\" : step.name\n\n output +=\n step.index === undefined ? `/${name}` : `/${name}[${step.index}]`\n }\n }\n\n return parsed.point === undefined ? output : `${output}.${parsed.point}`\n}\n","import {\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n isTextNode,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport {\n crengineElementName,\n getParentContext,\n hasImproperTableChildren,\n isElement,\n} from \"./crengine/elements\"\nimport { toCrengineOffset } from \"./crengine/text\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, XPointerStep } from \"./types\"\n\nconst DOCUMENT_NODE = 9\n\nconst isDocument = (node: Node): node is Document =>\n node.nodeType === DOCUMENT_NODE\n\n/**\n * Position of `node` among the same-name element children of `parent`, as\n * `toStringV2` (crengine/src/lvtinydom.cpp) writes it: the index is emitted\n * only when the parent holds more than one element of that name.\n */\nconst elementStep = (\n parent: Element,\n node: Element,\n insideSvg: boolean,\n): XPointerStep | undefined => {\n const name = crengineElementName(node, insideSvg)\n let index: number | undefined\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (\n child.kind !== \"element\" ||\n crengineElementName(child.node, insideSvg) !== name\n )\n return true\n\n count++\n\n if (child.node === node) index = count\n\n // Once the node is found, one more same-name sibling settles the shape.\n return index === undefined || count === 1\n })\n\n if (index === undefined) return undefined\n\n return { kind: \"element\", name, index: count > 1 ? index : undefined }\n}\n\n/**\n * Steps from the fragment body down to `element` (excluded when it is the\n * body). `undefined` when the element does not hang under the body crengine\n * knows (detached, or in `<head>`).\n */\nconst stepsTo = (\n element: Element,\n body: Element,\n): XPointerStep[] | undefined => {\n const chain: Element[] = []\n let node: Node | null = element\n\n while (node !== body) {\n if (!node || !isElement(node)) return undefined\n\n chain.push(node)\n node = node.parentNode\n }\n\n chain.reverse()\n\n const steps: XPointerStep[] = []\n let parent: Element = body\n let insideSvg = false\n\n for (const current of chain) {\n const step = elementStep(parent, current, insideSvg)\n\n if (!step) return undefined\n\n steps.push(step)\n\n if (current.localName.toLowerCase() === \"svg\") insideSvg = true\n\n parent = current\n }\n\n return steps\n}\n\n/** The kept piece of `text` holding UTF-16 `offset`, its 1-based rank among the parent's text pieces and whether it has siblings. */\nconst findTextPiece = (parent: Element, text: Text, offset: number) => {\n let found: Extract<CrengineChild, { kind: \"text\" }> | undefined\n let foundIndex = 0\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (child.kind !== \"text\") return true\n\n count++\n\n if (found === undefined && child.node === text) {\n const last = child.pieceIndex === child.pieceCount - 1\n\n if (offset < child.end || last) {\n found = child\n foundIndex = count\n }\n }\n\n // Once the piece is found, one more text sibling settles the shape.\n return found === undefined || count === 1\n })\n\n if (!found) return undefined\n\n return { piece: found, index: foundIndex, count }\n}\n\n/**\n * crengine's child index for the boundary before browser child `offset`:\n * every child crengine's element holds (an `autoBoxing` wrapper counting\n * once, each piece of a split text node counting) that starts before it. A\n * boundary inside a wrapper becomes the wrapper's start. `undefined` where\n * crengine rebuilds the children with wrappers of its own (ruby, MathML)\n * and a non-zero index cannot be known.\n */\nconst childIndexBefore = (parent: Element, offset: number) => {\n const context = getParentContext(parent)\n let index = 0\n\n for (const { child, lastChildIndex } of getCrengineBoxedChildren(\n parent,\n context,\n )) {\n if (child.childIndex >= offset || offset <= lastChildIndex) break\n\n index++\n }\n\n const unknowable = context.restructured || hasImproperTableChildren(parent)\n\n return unknowable && index > 0 ? undefined : index\n}\n\n/**\n * The classic V2 pointer for a DOM position, as `toStringV2`\n * (crengine/src/lvtinydom.cpp) writes it for a book opened with a DOM\n * version between 20200223 and 20260811: `[N]` only where the name is shared\n * by more than one sibling, `text()` without `[N]` when the parent holds a\n * single text node. The result resolves in crengine to the same node.\n *\n * `undefined` for positions crengine has no node for: a whitespace-only text\n * node it dropped, text under an element that takes no text, an element\n * outside the `<body>`, an offset past the end of a text node, anything\n * inside an SVG spine item but the item itself.\n */\nexport const generateXPointer = (\n position: DomPosition,\n spineItemIndex: number,\n): string | undefined => {\n if (!Number.isInteger(spineItemIndex) || spineItemIndex < 0) return undefined\n\n const { node, offset } = position\n const document = isDocument(node) ? node : node.ownerDocument\n\n if (!document) return undefined\n\n const body = getFragmentBody(document)\n const build = (\n steps: XPointerStep[] | undefined,\n point: number | undefined,\n ) =>\n steps === undefined\n ? undefined\n : serializeXPointer({\n spineItemIndex,\n steps,\n point,\n containsBoxingElements: false,\n })\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return isDocument(node) || node === document.documentElement\n ? build([], undefined)\n : undefined\n }\n\n if (isDocument(node)) return undefined\n\n if (isTextNode(node)) {\n const parent = node.parentNode\n const rawOffset = offset ?? 0\n\n if (\n !parent ||\n !isElement(parent) ||\n rawOffset < 0 ||\n rawOffset > node.data.length\n )\n return undefined\n\n const text = findTextPiece(parent, node, rawOffset)\n\n if (!text) return undefined\n\n const { piece } = text\n const steps = stepsTo(parent, body)\n\n if (!steps) return undefined\n\n steps.push({ kind: \"text\", index: text.count > 1 ? text.index : undefined })\n\n return build(\n steps,\n toCrengineOffset(\n node.data.slice(piece.start, piece.end),\n rawOffset - piece.start,\n piece.mode,\n ),\n )\n }\n\n if (!isElement(node)) return undefined\n\n if (offset === undefined) return build(stepsTo(node, body), undefined)\n\n if (offset < 0 || offset > node.childNodes.length) return undefined\n\n const point = childIndexBefore(node, offset)\n\n return point === undefined ? undefined : build(stepsTo(node, body), point)\n}\n","import type { ParsedXPointer, XPointerStep } from \"./types\"\n\n/**\n * Wrapper elements crengine inserts into its own DOM for layout\n * (crengine/include/fb2def.h, `EL_BOXING_START`..`EL_BOXING_END`) plus the\n * `pseudoElem` it creates for `::before` / `::after`. None exists in the source\n * XHTML, so a pointer naming one cannot be resolved in a browser DOM.\n */\nexport const XPOINTER_BOXING_ELEMENT_NAMES: readonly string[] = [\n \"autoBoxing\",\n \"tabularBox\",\n \"rubyBox\",\n \"mathBox\",\n \"floatBox\",\n \"inlineBox\",\n \"pseudoElem\",\n]\n\nconst boxingElementNames = new Set<string>(\n XPOINTER_BOXING_ELEMENT_NAMES.map((name) => name.toLowerCase()),\n)\n\nconst isDigit = (charCode: number) => charCode >= 48 && charCode <= 57\n\n/** An element name as crengine's XML parser can produce one (no `.`, which ends a step). */\nconst ELEMENT_NAME = /^[A-Za-z_][A-Za-z0-9_:-]*$/\n\nconst isStepBoundary = (char: string | undefined) =>\n char === undefined || char === \"/\" || char === \".\"\n\nconst readDigits = (input: string, from: number) => {\n let end = from\n\n while (end < input.length && isDigit(input.charCodeAt(end))) end++\n\n return end\n}\n\n/**\n * Port of `ParseXPathStep` (crengine/src/lvtinydom.cpp): a pointer is a\n * sequence of `/name`, `/name[N]`, `/text()`, `/text()[N]` or `/N` steps, with\n * an optional `.N` point that must end the string (`createXPointerV2` rejects\n * anything after it).\n *\n * Stricter than crengine on malformed input: `[N]` must be plain decimal\n * digits, where crengine's `atoi` would also take signs, hex or surrounding\n * blanks and silently turn garbage into an unresolvable index 0, and a step\n * name must be a plausible element name, where crengine would look up any\n * text (`text()x`, `p q`) and fail to find an element.\n */\nconst tokenize = (\n input: string,\n): { steps: XPointerStep[]; point: number | undefined } | undefined => {\n const steps: XPointerStep[] = []\n let position = 0\n\n while (position < input.length) {\n const prefix = input[position]\n\n if (prefix === \".\") {\n const digitsStart = position + 1\n const digitsEnd = readDigits(input, digitsStart)\n\n if (digitsEnd === digitsStart || digitsEnd !== input.length)\n return undefined\n\n return { steps, point: Number(input.slice(digitsStart, digitsEnd)) }\n }\n\n if (prefix !== \"/\") return undefined\n\n const stepStart = position + 1\n\n if (isDigit(input.charCodeAt(stepStart))) {\n const digitsEnd = readDigits(input, stepStart)\n\n if (!isStepBoundary(input[digitsEnd])) return undefined\n\n const index = Number(input.slice(stepStart, digitsEnd))\n\n if (index < 1) return undefined\n\n steps.push({ kind: \"nodeIndex\", index })\n position = digitsEnd\n\n continue\n }\n\n let nameEnd = stepStart\n\n while (nameEnd < input.length && !\"[/.\".includes(input[nameEnd] ?? \"\"))\n nameEnd++\n\n if (nameEnd === stepStart) return undefined\n\n const name = input.slice(stepStart, nameEnd)\n let index: number | undefined\n let stepEnd = nameEnd\n\n if (input[nameEnd] === \"[\") {\n const closing = input.indexOf(\"]\", nameEnd + 1)\n\n if (closing === -1) return undefined\n\n const digits = input.slice(nameEnd + 1, closing)\n\n if (digits.length === 0 || readDigits(digits, 0) !== digits.length)\n return undefined\n\n index = Number(digits)\n\n if (index < 1) return undefined\n\n stepEnd = closing + 1\n }\n\n if (!isStepBoundary(input[stepEnd])) return undefined\n\n if (name === \"text()\") {\n steps.push({ kind: \"text\", index })\n } else if (ELEMENT_NAME.test(name)) {\n steps.push({ kind: \"element\", name, index })\n } else {\n return undefined\n }\n position = stepEnd\n }\n\n return { steps, point: undefined }\n}\n\nconst isElementNamed = (\n step: XPointerStep | undefined,\n name: string,\n): step is Extract<XPointerStep, { kind: \"element\" }> =>\n step?.kind === \"element\" && step.name.toLowerCase() === name\n\n/**\n * Accepts every shape crengine has emitted (`toStringV1`, `toStringV2` and\n * its explicit-index variant for DOM versions >= 20260812) plus the bare\n * `/body/DocFragment[N]` third parties write for the start of a spine item.\n *\n * Returns `undefined` for anything crengine's own parser would reject or that\n * does not start with the `/body/DocFragment[N]/body` prefix of an EPUB\n * pointer. crengine's `#id` form is rejected too: it names an element of the\n * whole book DOM by id, which a spine item document cannot answer.\n */\nexport const parseXPointer = (input: string): ParsedXPointer | undefined => {\n const tokenized = tokenize(input)\n\n if (!tokenized) return undefined\n\n const [root, fragment, body, ...steps] = tokenized.steps\n\n if (!isElementNamed(root, \"body\") || (root.index ?? 1) !== 1) return undefined\n\n if (!isElementNamed(fragment, \"docfragment\")) return undefined\n\n const spineItemIndex = (fragment.index ?? 1) - 1\n\n // `/body/DocFragment[N]` alone, with or without a point (Kavita emits\n // `/body/DocFragment[N].0`): the start of the spine item.\n if (body === undefined) {\n return {\n spineItemIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n }\n }\n\n if (!isElementNamed(body, \"body\") || (body.index ?? 1) !== 1) return undefined\n\n return {\n spineItemIndex,\n steps,\n point: tokenized.point,\n containsBoxingElements: steps.some(\n (step) =>\n step.kind === \"element\" &&\n boxingElementNames.has(step.name.toLowerCase()),\n ),\n }\n}\n","import {\n type CrengineBoxedChild,\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport { toRawOffset } from \"./crengine/text\"\nimport { parseXPointer } from \"./parse\"\nimport type { DomPosition, ParsedXPointer, XPointerStep } from \"./types\"\n\ntype Current =\n | { kind: \"parent\"; node: Element }\n | Extract<CrengineChild, { kind: \"text\" }>\n\n/**\n * One step of `createXPointerV2` (crengine/src/lvtinydom.cpp): the N-th child\n * matching the step (first when the index is omitted), counted among the\n * children crengine kept. Element names compare case-insensitively.\n */\nconst findChild = (\n parent: Element,\n step: XPointerStep,\n): CrengineChild | undefined => {\n const wantedIndex = step.index ?? 1\n const wantedName =\n step.kind === \"element\" ? step.name.toLowerCase() : undefined\n let count = 0\n let found: CrengineChild | undefined\n\n visitCrengineChildren(parent, (child) => {\n const matches =\n step.kind === \"nodeIndex\" ||\n (step.kind === \"text\"\n ? child.kind === \"text\"\n : child.kind === \"element\" &&\n child.node.localName.toLowerCase() === wantedName)\n\n if (!matches) return true\n\n count++\n\n if (count === wantedIndex) {\n found = child\n\n return false\n }\n\n return true\n })\n\n return found\n}\n\nconst positionOfChild = (\n parent: Element,\n children: CrengineBoxedChild[],\n index: number,\n): DomPosition => {\n const child = children[index]?.child\n\n if (!child) return { node: parent, offset: parent.childNodes.length }\n\n // The boundary before a later piece of a split text node lies inside it.\n if (child.kind === \"text\" && child.pieceIndex > 0) {\n return { node: child.node, offset: child.start }\n }\n\n return { node: parent, offset: child.childIndex }\n}\n\n/**\n * Resolves a pointer inside the spine item's document, following\n * `createXPointerV2` (crengine/src/lvtinydom.cpp). Returns `undefined` when\n * crengine would fail too: a step without a match, a point past the end of\n * the text or of the children (nothing is clamped), a V1 pointer naming a\n * boxing element.\n *\n * A text position is returned with a UTF-16 offset into the text node,\n * ready for `Range.setStart`; a text step without a point is its start. In\n * a spine item without a `<body>` (an SVG document) only the item itself\n * resolves, to its root element.\n */\nexport const resolveXPointer = (\n xpointer: string | ParsedXPointer,\n document: Document,\n): DomPosition | undefined => {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed || parsed.containsBoxingElements) return undefined\n\n const body = getFragmentBody(document)\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return parsed.steps.length === 0\n ? { node: document.documentElement }\n : undefined\n }\n\n let current: Current = { kind: \"parent\", node: body }\n\n for (const step of parsed.steps) {\n if (current.kind === \"text\") return undefined\n\n const child = findChild(current.node, step)\n\n if (!child) return undefined\n\n current =\n child.kind === \"element\" ? { kind: \"parent\", node: child.node } : child\n }\n\n if (current.kind === \"text\") {\n if (parsed.point === undefined) {\n return { node: current.node, offset: current.start }\n }\n\n const rawOffset = toRawOffset(\n current.node.data.slice(current.start, current.end),\n parsed.point,\n current.mode,\n )\n\n return rawOffset === undefined\n ? undefined\n : { node: current.node, offset: current.start + rawOffset }\n }\n\n const parent = current.node\n\n if (parsed.point === undefined) return { node: parent }\n\n const children = getCrengineBoxedChildren(parent)\n\n if (parsed.point > children.length) return undefined\n\n return positionOfChild(parent, children, parsed.point)\n}\n","import {\n generate,\n getEpubCfiSpineItemref,\n isIndirectionOnly,\n parse,\n resolve,\n} from \"@prose-reader/cfi\"\nimport { isTextNode } from \"./crengine/children\"\nimport { isElement } from \"./crengine/elements\"\nimport { generateXPointer } from \"./generate\"\nimport { parseXPointer } from \"./parse\"\nimport { resolveXPointer } from \"./resolve\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, ParsedXPointer } from \"./types\"\n\nexport type XPointerSpineItem = {\n /** The spine item's content document, parsed as XHTML (what prose renders in its frames). */\n document: Document\n /** The `<itemref>` idref, written as the CFI's id assertion (`/6/14[chap05]!`) when given. */\n id?: string\n}\n\n/**\n * Hands out the document of a spine item by its 0-based index. A pointer\n * names its own spine item (`DocFragment[N]`) and is resolved in that\n * document, never in whichever one happens to be on screen: Readest shipped\n * that bug (readest/readest#5980) and landed a chapter away.\n */\nexport type XPointerSpineItemLookup = (\n spineItemIndex: number,\n) => XPointerSpineItem | undefined\n\n/**\n * The position a CFI can point to for a DOM position. A CFI cannot point\n * between an element's children, so a child index point becomes the start of\n * that child, and the end of the children the end of the last text child or\n * the element itself. Any other position is returned as it is.\n */\nexport const toCfiPosition = (position: DomPosition): DomPosition => {\n const { node, offset } = position\n\n if (!isElement(node) || offset === undefined) return position\n\n const child = node.childNodes[offset]\n\n if (child) {\n return isTextNode(child) ? { node: child, offset: 0 } : { node: child }\n }\n\n const last = node.lastChild\n\n return last && isTextNode(last)\n ? { node: last, offset: last.data.length }\n : { node }\n}\n\n/**\n * The CFI of a KOReader position, built with `@prose-reader/cfi` in the\n * spine item document the pointer names. `undefined` when the pointer is\n * malformed, its spine item is not available or it does not resolve; the\n * spine index alone is still available through `parseXPointer`.\n */\nexport const xPointerToCfi = (\n xpointer: string | ParsedXPointer,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed) return undefined\n\n const spineItem = getSpineItem(parsed.spineItemIndex)\n\n if (!spineItem) return undefined\n\n const position = resolveXPointer(parsed, spineItem.document)\n\n if (!position) return undefined\n\n const { node, offset } = toCfiPosition(position)\n\n return generate({\n node,\n offset: isTextNode(node) ? offset : undefined,\n spineIndex: parsed.spineItemIndex,\n spineId: spineItem.id,\n })\n } catch {\n return undefined\n }\n}\n\nconst firstOffset = (offset: number | number[] | undefined) =>\n Array.isArray(offset) ? offset[0] : offset\n\n/**\n * The KOReader pointer of a prose CFI: the CFI's start when it is a range,\n * the start of the spine item for a root CFI (`epubcfi(/6/14[chap05]!)`).\n * `undefined` when the CFI is malformed, its spine item is not available or\n * it does not resolve in that document.\n */\nexport const cfiToXPointer = (\n cfi: string,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const itemref = getEpubCfiSpineItemref(cfi)\n\n if (!itemref) return undefined\n\n const { spineIndex } = itemref\n\n if (!Number.isInteger(spineIndex) || spineIndex < 0) return undefined\n\n const parsed = parse(cfi)\n\n if (isIndirectionOnly(parsed)) {\n return serializeXPointer({\n spineItemIndex: spineIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n })\n }\n\n const spineItem = getSpineItem(spineIndex)\n\n if (!spineItem) return undefined\n\n const resolved = resolve(parsed, spineItem.document)\n\n if (!resolved.node) return undefined\n\n const position: DomPosition = resolved.isRange\n ? {\n node: resolved.node.startContainer,\n offset: resolved.node.startOffset,\n }\n : {\n node: resolved.node,\n offset: isTextNode(resolved.node)\n ? (firstOffset(resolved.offset) ?? 0)\n : undefined,\n }\n\n return generateXPointer(position, spineIndex)\n } catch {\n return undefined\n }\n}\n","import { md5HexDigest } from \"@prose-reader/shared\"\n\n/** Bytes `util.partialMD5` reads at each of its offsets. */\nconst PARTIAL_MD5_SAMPLE_SIZE = 1024\n\n/**\n * Where `util.partialMD5` (KOReader, frontend/util.lua) reads: at\n * `bit.lshift(1024, 2 * i)` for `i` from -1 to 10. LuaJIT keeps the low five\n * bits of a shift count, so `i = -1` shifts 1024 by 30 bits, out of 32 bits,\n * and reads at 0.\n */\nconst PARTIAL_MD5_SAMPLE_OFFSETS = [\n 0, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864,\n 268435456, 1073741824,\n]\n\n/**\n * The digest KOReader files a book's reading position under with its default\n * \"Binary\" document matching method: `util.partialMD5` (frontend/util.lua),\n * the MD5 of 1 KiB samples of the file at the offsets above, as lowercase hex.\n * A sample the end of the file cuts short is hashed as it is, and the samples\n * stop at the first offset at or past the end: at most 12 KiB of `file` are\n * read.\n *\n * KOReader computes it from the bytes it opens the first time and keeps it in\n * the book's settings, so the digests match only for the same bytes.\n */\nexport const getKoreaderFileDigest = async (file: Blob): Promise<string> => {\n const samples = await Promise.all(\n PARTIAL_MD5_SAMPLE_OFFSETS.filter((offset) => offset < file.size).map(\n async (offset) =>\n new Uint8Array(\n await file\n .slice(offset, offset + PARTIAL_MD5_SAMPLE_SIZE)\n .arrayBuffer(),\n ),\n ),\n )\n\n return md5HexDigest(samples)\n}\n\n/**\n * The digest KOReader files a book's reading position under with its\n * \"Filename\" document matching method (`KOSync:getFileNameDigest`,\n * plugins/kosync.koplugin/main.lua): the MD5 of the file name, as lowercase\n * hex.\n *\n * `fileName` is the base name, extension included, as the device's file\n * system stores it. KOReader hashes its UTF-8 bytes as they are, without\n * Unicode normalization: an `é` stored as one code point and one stored as an\n * `e` and a combining accent give two digests.\n */\nexport const getKoreaderFileNameDigest = (fileName: string): string =>\n md5HexDigest([new TextEncoder().encode(fileName)])\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["BLOCK_ELEMENTS","INLINE_BLOCK_ELEMENTS","INVISIBLE_ELEMENTS","NO_TEXT_ELEMENTS","PRE_ELEMENTS","SVG_TEXT_ELEMENTS","MATHML_ELEMENTS","MATHML_TOKEN_ELEMENTS","RAW_TEXT_ELEMENTS","PROPER_TABLE_CHILDREN","hasImproperTableChildren","proper","element","child","LEADING_NEWLINE_STRIPPING_ELEMENTS","ELEMENT_NODE","isElement","node","crengineElementName","insideSvg","TABLE_PART_ELEMENTS","getDisplayByName","name","getElementDisplay","getParentContext","parent","pre","insideMath","ancestor","ancestorName","allowsText","display","CRENGINE_TEXT_SPLIT_SIZE","TAB","LF","CR","SPACE","isSpaceLike","codePoint","isEmptySpace","index","text","charCode","codePointLength","visitCodePoints","mode","precedingSpaces","emitted","lastKept","length","units","visit","lastChar","column","stripNewline","newline","next","toCrengineOffset","offset","rawIndex","rawLength","rawOffset","toRawOffset","crengineOffset","splitTextPieces","pieces","start","windowEnd","codePoints","end","TEXT_NODE","CDATA_SECTION_NODE","isTextNode","parseChildren","children","childIndex","context","role","whitespace","isRunItem","isWhitespaceText","renderChildren","asNodes","hasBlockItems","hasInline","rendered","run","first","hasInlineNodes","hasNonEmptyText","removed","previous","getRenderedChildren","visitParsedChild","pieceIndex","piece","visitCrengineChildren","entry","getCrengineBoxedChildren","last","getFragmentBody","document","serializeXPointer","output","parsed","step","DOCUMENT_NODE","isDocument","elementStep","count","stepsTo","chain","body","steps","current","findTextPiece","found","foundIndex","childIndexBefore","lastChildIndex","generateXPointer","spineItemIndex","position","build","point","XPOINTER_BOXING_ELEMENT_NAMES","boxingElementNames","isDigit","ELEMENT_NAME","isStepBoundary","char","readDigits","from","input","tokenize","prefix","digitsStart","digitsEnd","stepStart","nameEnd","stepEnd","closing","digits","isElementNamed","parseXPointer","tokenized","root","fragment","findChild","wantedIndex","wantedName","positionOfChild","resolveXPointer","xpointer","toCfiPosition","xPointerToCfi","spineItem","getSpineItem","generate","firstOffset","cfiToXPointer","itemref","getEpubCfiSpineItemref","cfi","spineIndex","parse","isIndirectionOnly","resolved","resolve","PARTIAL_MD5_SAMPLE_SIZE","PARTIAL_MD5_SAMPLE_OFFSETS","getKoreaderFileDigest","samples","file","md5HexDigest","getKoreaderFileNameDigest","fileName","XPOINTER_BOOK_MEDIA_TYPES","PAGE_BOOK_MEDIA_TYPES","getKoreaderProgressKind","essence","mediaType"],"sources":["../src/crengine/elements.ts","../src/crengine/text.ts","../src/crengine/children.ts","../src/serialize.ts","../src/generate.ts","../src/parse.ts","../src/resolve.ts","../src/cfi.ts","../src/digest.ts","../src/progress.ts"],"sourcesContent":["/**\n * How crengine classifies elements while it loads a spine item, from its\n * built-in element table (crengine/include/fb2def.h) and the stylesheet\n * KOReader applies before any book CSS (data/html5.css, imported by\n * data/epub.css). These defaults decide what happens to text nodes in\n * `ldomElementWriter::onText` and `ldomNode::initNodeRendMethod`\n * (crengine/src/lvtinydom.cpp):\n *\n * - `display` — whether whitespace-only text children are dropped,\n * - `allow_text` — elements such as `<table>` or `<tr>` drop every text child,\n * - `white-space` — pre-like elements keep whitespace verbatim.\n *\n * Only these defaults are known here. crengine takes the values from the\n * computed style, so a book stylesheet that changes `display` or\n * `white-space` (class rules, inline styles) is not seen: this is the\n * package's documented approximation.\n */\n\n/**\n * Elements whose default `display` is neither `inline` nor `none`: block,\n * list-item, table and table parts. `isBlockNode` in crengine treats all of\n * them alike (`display > css_d_inline && display != css_d_none`). Unknown\n * elements are inline.\n */\nconst BLOCK_ELEMENTS = new Set([\n \"address\",\n \"annotation\",\n \"applet\",\n \"article\",\n \"aside\",\n \"audio\",\n \"author\",\n \"autoboxing\",\n \"blockquote\",\n \"body\",\n \"book-name\",\n \"book-title\",\n \"canvas\",\n \"caption\",\n \"center\",\n \"city\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"custom-info\",\n \"date\",\n \"dd\",\n \"details\",\n \"dialog\",\n \"dir\",\n \"div\",\n \"dl\",\n \"docfragment\",\n \"document-info\",\n \"dt\",\n \"email\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"fieldset\",\n \"figcaption\",\n \"figure\",\n \"floatbox\",\n \"footer\",\n \"form\",\n \"frame\",\n \"frameset\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"header\",\n \"hgroup\",\n \"history\",\n \"home-page\",\n \"hr\",\n \"html\",\n \"id\",\n \"iframe\",\n \"isbn\",\n \"keygen\",\n \"keywords\",\n \"lang\",\n \"legend\",\n \"li\",\n \"listing\",\n \"main\",\n \"map\",\n \"menu\",\n \"multicol\",\n \"nav\",\n \"nickname\",\n \"noframes\",\n \"noscript\",\n \"ol\",\n \"optgroup\",\n \"output\",\n \"p\",\n \"part\",\n \"plaintext\",\n \"poem\",\n \"pre\",\n \"program-used\",\n \"publish-info\",\n \"publisher\",\n \"search\",\n \"section\",\n \"select\",\n \"sequence\",\n \"source\",\n \"src-lang\",\n \"src-ocr\",\n \"src-title-info\",\n \"src-url\",\n \"stanza\",\n \"subtitle\",\n \"summary\",\n \"table\",\n \"tabularbox\",\n \"tbody\",\n \"td\",\n \"text-author\",\n \"textarea\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n \"ul\",\n \"v\",\n \"version\",\n \"video\",\n \"xmp\",\n \"year\",\n])\n\n/** `display: inline-block` in html5.css: a block for its own children, an inline item for its parent. */\nconst INLINE_BLOCK_ELEMENTS = new Set([\"button\", \"input\", \"marquee\"])\n\n/** `display: none` (fb2def.h and html5.css): invisible, not counted as inline nor block by the parent. */\nconst INVISIBLE_ELEMENTS = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"binary\",\n \"clonenode\",\n \"datalist\",\n \"description\",\n \"genre\",\n \"head\",\n \"link\",\n \"meta\",\n \"noembed\",\n \"param\",\n \"pseudoelem\",\n \"script\",\n \"style\",\n \"stylesheet\",\n \"template\",\n \"title\",\n])\n\n/** Elements defined with `allow_text = false` in fb2def.h: their text children are never inserted. */\nconst NO_TEXT_ELEMENTS = new Set([\n \"annotation\",\n \"applet\",\n \"area\",\n \"audio\",\n \"author\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"clonenode\",\n \"col\",\n \"colgroup\",\n \"coverpage\",\n \"description\",\n \"docfragment\",\n \"document-info\",\n \"embed\",\n \"empty-line\",\n \"epigraph\",\n \"fictionbook\",\n \"head\",\n \"html\",\n \"image\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"object\",\n \"param\",\n \"part\",\n \"poem\",\n \"pseudoelem\",\n \"publish-info\",\n \"source\",\n \"src-title-info\",\n \"stanza\",\n \"svg\",\n \"table\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"title-info\",\n \"tr\",\n \"track\",\n])\n\n/** Elements whose default `white-space` is `pre` or `pre-wrap` (fb2def.h, html5.css). */\nconst PRE_ELEMENTS = new Set([\n \"listing\",\n \"plaintext\",\n \"pre\",\n \"style\",\n \"textarea\",\n \"xmp\",\n])\n\n/**\n * Inside `<svg>` crengine only keeps text under these elements, verbatim\n * (`ldomElementWriter` constructor, `_insideSVG`).\n */\nconst SVG_TEXT_ELEMENTS = new Set([\n \"desc\",\n \"metadata\",\n \"style\",\n \"text\",\n \"textpath\",\n \"title\",\n \"tspan\",\n])\n\n/**\n * MathML elements crengine knows (fb2def.h, `EL_MATHML_START`..`EL_MATHML_END`).\n * Inside `<math>`, `MathMLHelper::getMathMLAdjustedText` (crengine/src/mathml.cpp)\n * drops the text of every one of them except the token elements, whose text\n * it trims; text under any other element is trimmed too.\n */\nconst MATHML_ELEMENTS = new Set([\n \"annotation-xml\",\n \"maction\",\n \"maligngroup\",\n \"malignmark\",\n \"math\",\n \"menclose\",\n \"merror\",\n \"mfenced\",\n \"mfrac\",\n \"mglyph\",\n \"mi\",\n \"mlabeledtr\",\n \"mlongdiv\",\n \"mmultiscripts\",\n \"mn\",\n \"mo\",\n \"mover\",\n \"mpadded\",\n \"mphantom\",\n \"mprescripts\",\n \"mroot\",\n \"mrow\",\n \"ms\",\n \"mscarries\",\n \"mscarry\",\n \"msgroup\",\n \"msline\",\n \"mspace\",\n \"msqrt\",\n \"msrow\",\n \"mstack\",\n \"mstyle\",\n \"msub\",\n \"msubsup\",\n \"msup\",\n \"mtable\",\n \"mtd\",\n \"mtext\",\n \"mtr\",\n \"munder\",\n \"munderover\",\n \"none\",\n \"semantics\",\n])\n\n/** `EL_MATHML_TOKEN_START`..`EL_MATHML_TOKEN_END`: the MathML elements that hold text. */\nconst MATHML_TOKEN_ELEMENTS = new Set([\n \"mglyph\",\n \"mi\",\n \"mn\",\n \"mo\",\n \"mtext\",\n \"mspace\",\n \"ms\",\n])\n\n/**\n * crengine's parser reads the content of these as one raw text node up to the\n * closing tag (`LVXMLParser::ReadText`, `m_in_html_style_tag` /\n * `m_in_html_script_tag`), so the elements an XML parser finds inside them\n * do not exist for it. Nothing is modelled inside them.\n */\nconst RAW_TEXT_ELEMENTS = new Set([\"script\", \"style\"])\n\n/**\n * The children a table part may hold without crengine wrapping them in a\n * `tabularBox` (`initTableRendMethods`, \"Generate missing child wrappers\"):\n * any other element child makes the part's child count unknowable.\n */\nconst PROPER_TABLE_CHILDREN: Record<string, Set<string>> = {\n table: new Set([\n \"caption\",\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"tfoot\",\n \"thead\",\n \"tr\",\n ]),\n tbody: new Set([\"tr\"]),\n tfoot: new Set([\"tr\"]),\n thead: new Set([\"tr\"]),\n tr: new Set([\"td\", \"th\"]),\n}\n\n/** Whether crengine had to complete this table part with wrappers of its own. */\nexport const hasImproperTableChildren = (element: Element) => {\n const proper = PROPER_TABLE_CHILDREN[element.localName.toLowerCase()]\n\n if (!proper) return false\n\n for (\n let child = element.firstElementChild;\n child;\n child = child.nextElementSibling\n ) {\n if (!proper.has(child.localName.toLowerCase())) return true\n }\n\n return false\n}\n\n/** `pre` and `textarea` lose a newline immediately following their start tag (`_stripLeadingNewlineChar`, DOM >= 20210904). */\nconst LEADING_NEWLINE_STRIPPING_ELEMENTS = new Set([\"pre\", \"textarea\"])\n\n/**\n * What an element is to crengine's rendering pass: `block` and `inlineBlock`\n * contain blocks or a final line box, `inline` flows in its parent's text,\n * `none` is invisible, `ruby` is inline with its own layout.\n */\nexport type ElementDisplay =\n | \"block\"\n | \"inlineBlock\"\n | \"inline\"\n | \"none\"\n | \"ruby\"\n\nexport type ParentContext = {\n display: ElementDisplay\n /** Whitespace-only first text child dropped at parse time (`_isBlock`). */\n block: boolean\n /** Text children inserted at all (`_allowText`). */\n allowsText: boolean\n /** Text kept verbatim, tabs expanded (`TXTFLG_PRE`). */\n pre: boolean\n stripsLeadingNewline: boolean\n /** Inside `<math>`: text trimmed of surrounding blanks, dropped when nothing is left. */\n trimsText: boolean\n /** Ruby and MathML: crengine rebuilds the children with its own wrappers, so child indexes are not knowable. */\n restructured: boolean\n /** `<style>` / `<script>`: raw text for crengine, no children modelled. */\n opaque: boolean\n /** Descendant of `<svg>`: crengine keeps element names case-sensitive there. */\n insideSvg: boolean\n}\n\nconst ELEMENT_NODE = 1\n\nexport const isElement = (node: Node): node is Element =>\n node.nodeType === ELEMENT_NODE\n\n/** The name crengine's XML parser stored: lowercased, except inside `<svg>` (`TXTFLG_CASE_SENSITIVE_TAGS_ATTRS`). */\nexport const crengineElementName = (element: Element, insideSvg: boolean) =>\n insideSvg ? element.localName : element.localName.toLowerCase()\n\n/** Table parts keep their display under `[hidden]` (html5.css re-sets it, the table algorithm needs them). */\nconst TABLE_PART_ELEMENTS = new Set([\n \"col\",\n \"colgroup\",\n \"tbody\",\n \"td\",\n \"tfoot\",\n \"th\",\n \"thead\",\n \"tr\",\n])\n\nconst getDisplayByName = (name: string): ElementDisplay => {\n if (INVISIBLE_ELEMENTS.has(name)) return \"none\"\n if (BLOCK_ELEMENTS.has(name)) return \"block\"\n if (INLINE_BLOCK_ELEMENTS.has(name)) return \"inlineBlock\"\n if (name === \"ruby\") return \"ruby\"\n\n return \"inline\"\n}\n\n/** The element's default display in KOReader, `[hidden]` included (html5.css). */\nexport const getElementDisplay = (element: Element): ElementDisplay => {\n const name = element.localName.toLowerCase()\n\n if (element.hasAttribute(\"hidden\") && !TABLE_PART_ELEMENTS.has(name))\n return \"none\"\n\n return getDisplayByName(name)\n}\n\nexport const getParentContext = (parent: Element): ParentContext => {\n const name = parent.localName.toLowerCase()\n let pre = false\n let insideSvg = false\n let insideMath = name === \"math\"\n\n for (\n let ancestor: Node | null = parent.parentNode;\n ancestor && isElement(ancestor);\n ancestor = ancestor.parentNode\n ) {\n const ancestorName = ancestor.localName.toLowerCase()\n\n if (PRE_ELEMENTS.has(ancestorName)) pre = true\n if (ancestorName === \"svg\") insideSvg = true\n if (ancestorName === \"math\") insideMath = true\n }\n\n if (insideSvg) {\n const allowsText = SVG_TEXT_ELEMENTS.has(name)\n\n return {\n display: \"inline\",\n block: false,\n allowsText,\n pre: pre || allowsText,\n stripsLeadingNewline: false,\n trimsText: false,\n restructured: false,\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: true,\n }\n }\n\n if (insideMath) {\n return {\n display: \"inline\",\n block: false,\n allowsText: MATHML_TOKEN_ELEMENTS.has(name) || !MATHML_ELEMENTS.has(name),\n pre,\n stripsLeadingNewline: false,\n trimsText: true,\n restructured: true,\n opaque: false,\n insideSvg: false,\n }\n }\n\n const display = getElementDisplay(parent)\n\n return {\n display,\n block: display === \"block\" || display === \"inlineBlock\",\n allowsText: !NO_TEXT_ELEMENTS.has(name),\n pre: pre || PRE_ELEMENTS.has(name),\n stripsLeadingNewline: LEADING_NEWLINE_STRIPPING_ELEMENTS.has(name),\n trimsText: false,\n restructured: display === \"ruby\",\n opaque: RAW_TEXT_ELEMENTS.has(name),\n insideSvg: name === \"svg\",\n }\n}\n","/**\n * What crengine makes of a text node's characters, and how its offsets map to\n * the raw DOM text. Every rule is from `PreProcessXmlString`, `ExpandTabs` and\n * `LVXMLParser::ReadText` in crengine/src/lvxml.cpp and\n * `ldomElementWriter::onText` in crengine/src/lvtinydom.cpp.\n *\n * crengine strings are UTF-32, so its offsets count code points; DOM offsets\n * count UTF-16 units. Both conversions live here.\n */\n\n/** `TEXT_SPLIT_SIZE` (crengine/src/lvxml.cpp): longer text becomes several text nodes. */\nexport const CRENGINE_TEXT_SPLIT_SIZE = 8192\n\nexport type TextMode = {\n /** `white-space: pre` and friends: whitespace kept, tabs expanded. */\n pre: boolean\n /** First text of a `<pre>` / `<textarea>`: a leading newline is dropped. */\n stripsLeadingNewline: boolean\n /** MathML token text: blanks at both ends removed after the whitespace pass. */\n trim?: boolean\n}\n\nexport type TextPiece = {\n /** UTF-16 range in the raw text node data. */\n start: number\n end: number\n}\n\nconst TAB = 9\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\nconst isSpaceLike = (codePoint: number) =>\n codePoint === SPACE ||\n codePoint === CR ||\n codePoint === LF ||\n codePoint === TAB\n\n/** `IsEmptySpace` (crengine/src/lvtinydom.cpp): only these four count as space; NBSP does not. */\nexport const isEmptySpace = (text: string): boolean => {\n for (let index = 0; index < text.length; index++) {\n const charCode = text.charCodeAt(index)\n\n if (\n charCode !== SPACE &&\n charCode !== CR &&\n charCode !== LF &&\n charCode !== TAB\n )\n return false\n }\n\n return true\n}\n\nconst codePointLength = (codePoint: number) => (codePoint > 0xffff ? 2 : 1)\n\n/**\n * Visits the raw text code point by code point with the number of characters\n * crengine keeps for it (`units`): 0 for a collapsed space or a dropped\n * carriage return, up to 8 for an expanded tab, 1 otherwise. Stops early when\n * the visitor returns `false`.\n */\nconst visitCodePoints = (\n text: string,\n mode: TextMode,\n visit: (rawIndex: number, rawLength: number, units: number) => boolean,\n) => {\n let index = 0\n\n if (!mode.pre) {\n // Outside pre: \\r, \\n and \\t become spaces and a run of spaces keeps its\n // first one. Trimming (MathML) then removes the space at either end.\n let precedingSpaces = 0\n let emitted = 0\n let lastKept = text.length\n\n if (mode.trim) {\n while (lastKept > 0 && isSpaceLike(text.charCodeAt(lastKept - 1)))\n lastKept--\n }\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n\n if (isSpaceLike(codePoint)) {\n units = precedingSpaces === 0 ? 1 : 0\n precedingSpaces++\n\n if (mode.trim && (emitted === 0 || index >= lastKept)) units = 0\n } else {\n precedingSpaces = 0\n }\n\n emitted += units\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n\n return\n }\n\n // Inside pre: \\r\\n and lone \\r become \\n, tabs expand to 8-column stops\n // (columns counted from the start of the text node, a newline itself\n // being column 1 of its line), and the first character is dropped when it\n // is the newline that follows a <pre> start tag.\n let lastChar = 0\n let column = 0\n let stripNewline = mode.stripsLeadingNewline\n\n while (index < text.length) {\n const codePoint = text.codePointAt(index) ?? 0\n const length = codePointLength(codePoint)\n let units = 1\n let newline = false\n\n if (codePoint === CR) {\n const next =\n index + length < text.length\n ? text.codePointAt(index + length)\n : undefined\n const kept = (index === 0 || lastChar !== LF) && next !== LF\n\n if (kept) {\n newline = true\n lastChar = LF\n } else {\n units = 0\n }\n } else if (codePoint === LF) {\n newline = true\n lastChar = LF\n } else if (codePoint === TAB) {\n units = 8 - (column & 7)\n column += units\n lastChar = TAB\n } else {\n column++\n lastChar = codePoint\n }\n\n if (newline) {\n // ExpandTabs resets the column, then counts the newline itself.\n column = 1\n }\n\n if (units > 0 && stripNewline) {\n stripNewline = false\n\n if (newline) units = 0\n }\n\n if (!visit(index, length, units)) return\n\n index += length\n }\n}\n\n/** Length of crengine's text, in code points. */\nexport const getCrengineTextLength = (text: string, mode: TextMode) => {\n let length = 0\n\n visitCodePoints(text, mode, (_rawIndex, _rawLength, units) => {\n length += units\n\n return true\n })\n\n return length\n}\n\n/**\n * crengine offset (code points of the normalised text) of a raw UTF-16\n * offset: the characters kept for every code point that ends at or before it.\n */\nexport const toCrengineOffset = (\n text: string,\n rawOffset: number,\n mode: TextMode,\n) => {\n let offset = 0\n\n visitCodePoints(text, mode, (rawIndex, rawLength, units) => {\n if (rawIndex + rawLength > rawOffset) return false\n\n offset += units\n\n return true\n })\n\n return offset\n}\n\n/**\n * Raw UTF-16 offset of a crengine offset, or `undefined` when it is past the\n * end of crengine's text. Characters crengine dropped are skipped, so the\n * position lands right before the next character it kept; an offset inside\n * an expanded tab lands before the tab.\n */\nexport const toRawOffset = (\n text: string,\n crengineOffset: number,\n mode: TextMode,\n): number | undefined => {\n if (crengineOffset < 0) return undefined\n\n let offset = 0\n let rawOffset: number | undefined\n\n visitCodePoints(text, mode, (rawIndex, _rawLength, units) => {\n if (units === 0) return true\n\n if (offset === crengineOffset || offset + units > crengineOffset) {\n rawOffset = rawIndex\n\n return false\n }\n\n offset += units\n\n return true\n })\n\n if (rawOffset !== undefined) return rawOffset\n\n return offset === crengineOffset ? text.length : undefined\n}\n\n/**\n * The pieces crengine's parser cuts a long text node into\n * (`LVXMLParser::ReadText`): once 8192 raw code points are buffered, the\n * buffer is emitted up to and including its last space (or its last lone\n * newline), and the rest starts the next piece. The rule is applied to the\n * decoded DOM text; crengine applies it to the source, so a long text with\n * entity references may split a few characters away.\n */\nexport const splitTextPieces = (text: string): TextPiece[] => {\n if (text.length < CRENGINE_TEXT_SPLIT_SIZE)\n return [{ start: 0, end: text.length }]\n\n const pieces: TextPiece[] = []\n let start = 0\n\n while (start < text.length) {\n let windowEnd = start\n let codePoints = 0\n\n while (windowEnd < text.length && codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n windowEnd += codePointLength(text.codePointAt(windowEnd) ?? 0)\n codePoints++\n }\n\n if (codePoints < CRENGINE_TEXT_SPLIT_SIZE) {\n pieces.push({ start, end: text.length })\n\n break\n }\n\n let end = windowEnd\n\n for (let index = windowEnd - 1; index >= start; index--) {\n const charCode = text.charCodeAt(index)\n\n if (charCode === SPACE) {\n end = index + 1\n\n break\n }\n\n if (charCode === CR || charCode === LF) {\n const next = index + 1 < windowEnd ? text.charCodeAt(index + 1) : 0\n\n if (\n (charCode === CR && next !== LF) ||\n (charCode === LF && next !== CR)\n ) {\n end = index + 1\n\n break\n }\n }\n }\n\n pieces.push({ start, end })\n start = end\n }\n\n return pieces\n}\n","import {\n getElementDisplay,\n getParentContext,\n isElement,\n type ParentContext,\n} from \"./elements\"\nimport { isEmptySpace, splitTextPieces, type TextMode } from \"./text\"\n\nconst TEXT_NODE = 3\nconst CDATA_SECTION_NODE = 4\n\nexport const isTextNode = (node: Node): node is Text =>\n node.nodeType === TEXT_NODE || node.nodeType === CDATA_SECTION_NODE\n\n/**\n * A child as crengine holds it: browser comments and processing instructions\n * do not exist, dropped text nodes do not exist, and a long text node is\n * several text nodes.\n */\nexport type CrengineChild =\n | { kind: \"element\"; node: Element; childIndex: number }\n | {\n kind: \"text\"\n node: Text\n childIndex: number\n start: number\n end: number\n pieceIndex: number\n pieceCount: number\n mode: TextMode\n }\n\n/** How the parent's rendering pass sees a child (`detectChildTypes`, `isInlineNode`). */\ntype ChildRole = \"block\" | \"inline\" | \"invisible\" | \"text\"\n\ntype ParsedChild = {\n node: Element | Text\n childIndex: number\n role: ChildRole\n whitespace: boolean\n /** First child inserted by the parser (for `_stripLeadingNewlineChar`). */\n first: boolean\n}\n\n/** Children as the parser inserted them (`ldomElementWriter::onText`, crengine/src/lvtinydom.cpp). */\nconst parseChildren = (parent: Element, context: ParentContext) => {\n const children: ParsedChild[] = []\n let childIndex = 0\n\n if (context.opaque) return children\n\n for (\n let node = parent.firstChild;\n node;\n node = node.nextSibling, childIndex++\n ) {\n if (isElement(node)) {\n const display = context.insideSvg ? \"inline\" : getElementDisplay(node)\n const role: ChildRole =\n display === \"block\"\n ? \"block\"\n : display === \"none\"\n ? \"invisible\"\n : \"inline\"\n\n children.push({\n node,\n childIndex,\n role,\n whitespace: false,\n first: children.length === 0,\n })\n\n continue\n }\n\n if (!isTextNode(node) || !context.allowsText) continue\n\n const whitespace = isEmptySpace(node.data)\n\n // A whitespace-only text that would open a block is not inserted, and\n // MathML text with nothing left once trimmed is not either.\n if (\n whitespace &&\n ((context.block && !context.pre && children.length === 0) ||\n context.trimsText)\n )\n continue\n\n children.push({\n node,\n childIndex,\n role: \"text\",\n whitespace,\n first: children.length === 0,\n })\n }\n\n return children\n}\n\nconst isRunItem = (child: ParsedChild) =>\n child.role === \"inline\" || child.role === \"text\"\n\nconst isWhitespaceText = (child: ParsedChild | undefined) =>\n child !== undefined && child.role === \"text\" && child.whitespace\n\n/**\n * A child once the element is closed: a node, or an `autoBoxing` wrapper\n * crengine put around a run of inline content that sits among block children.\n */\ntype RenderedChild =\n | { kind: \"node\"; child: ParsedChild }\n | { kind: \"box\"; children: ParsedChild[] }\n\n/**\n * What `ldomNode::initNodeRendMethod` (crengine/src/lvtinydom.cpp) does to\n * the children once the element is closed. A block holding both block\n * children and inline content gets every run of inline nodes wrapped in an\n * `autoBoxing`, and `autoboxChildren` first drops the whitespace-only text\n * nodes opening the run and the one closing it (a run made only of them\n * disappears) unless the block is preformatted. An inline element holding\n * block children loses the whitespace-only text nodes next to those blocks,\n * when it also holds inline elements or real text.\n */\nconst renderChildren = (\n children: ParsedChild[],\n context: ParentContext,\n): RenderedChild[] => {\n const asNodes = (): RenderedChild[] =>\n children.map((child) => ({ kind: \"node\", child }))\n\n if (context.display === \"block\" || context.display === \"inlineBlock\") {\n const hasBlockItems = children.some((child) => child.role === \"block\")\n const hasInline = children.some(isRunItem)\n\n if (!hasBlockItems || !hasInline) return asNodes()\n\n const rendered: RenderedChild[] = []\n let index = 0\n\n while (index < children.length) {\n const child = children[index]\n\n if (!child) break\n\n if (!isRunItem(child)) {\n rendered.push({ kind: \"node\", child })\n index++\n\n continue\n }\n\n let end = index\n\n while (end < children.length) {\n const next = children[end]\n\n if (!next || !isRunItem(next)) break\n\n end++\n }\n\n let run = children.slice(index, end)\n\n if (!context.pre) {\n let first = 0\n\n while (first < run.length && isWhitespaceText(run[first])) first++\n\n run = run.slice(first)\n\n if (run.length > 0 && isWhitespaceText(run[run.length - 1]))\n run = run.slice(0, -1)\n }\n\n if (run.length > 0) rendered.push({ kind: \"box\", children: run })\n\n index = end\n }\n\n return rendered\n }\n\n if (context.display === \"inline\" && !context.pre) {\n const hasBlockNodes = children.some((child) => child.role === \"block\")\n\n if (!hasBlockNodes) return asNodes()\n\n const hasInlineNodes = children.some((child) => child.role === \"inline\")\n const hasNonEmptyText = children.some(\n (child) => child.role === \"text\" && !child.whitespace,\n )\n\n if (!hasInlineNodes && !hasNonEmptyText) return asNodes()\n\n const removed = new Set<ParsedChild>()\n\n children.forEach((child, index) => {\n if (child.role !== \"block\") return\n\n const previous = children[index - 1]\n const next = children[index + 1]\n\n if (previous && isWhitespaceText(previous)) removed.add(previous)\n if (next && isWhitespaceText(next)) removed.add(next)\n })\n\n return children\n .filter((child) => !removed.has(child))\n .map((child) => ({ kind: \"node\", child }))\n }\n\n return asNodes()\n}\n\nconst getRenderedChildren = (parent: Element, context: ParentContext) =>\n renderChildren(parseChildren(parent, context), context)\n\nconst visitParsedChild = (\n child: ParsedChild,\n context: ParentContext,\n visit: (child: CrengineChild) => boolean,\n): boolean => {\n if (isElement(child.node)) {\n return visit({\n kind: \"element\",\n node: child.node,\n childIndex: child.childIndex,\n })\n }\n\n const pieces = splitTextPieces(child.node.data)\n\n for (let pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {\n const piece = pieces[pieceIndex]\n\n if (!piece) continue\n\n const keepGoing = visit({\n kind: \"text\",\n node: child.node,\n childIndex: child.childIndex,\n start: piece.start,\n end: piece.end,\n pieceIndex,\n pieceCount: pieces.length,\n mode: {\n pre: context.pre,\n stripsLeadingNewline:\n context.stripsLeadingNewline && child.first && pieceIndex === 0,\n trim: context.trimsText,\n },\n })\n\n if (!keepGoing) return false\n }\n\n return true\n}\n\n/**\n * Walks the nodes crengine kept, in order, stopping when `visit` returns\n * `false`: what its parser inserted, minus what its rendering pass removed,\n * with text longer than `TEXT_SPLIT_SIZE` split into pieces. `autoBoxing`\n * wrappers are walked through, as `getNodeByIndex` does for path steps.\n */\nexport const visitCrengineChildren = (\n parent: Element,\n visit: (child: CrengineChild) => boolean,\n context: ParentContext = getParentContext(parent),\n) => {\n for (const entry of getRenderedChildren(parent, context)) {\n const children = entry.kind === \"box\" ? entry.children : [entry.child]\n\n for (const child of children) {\n if (!visitParsedChild(child, context, visit)) return\n }\n }\n}\n\n/** One child as an element point (`.N`) counts it, with the browser index of the last node it covers. */\nexport type CrengineBoxedChild = {\n child: CrengineChild\n lastChildIndex: number\n}\n\n/**\n * The children an element point (`.N`) counts: crengine checks the point\n * against the element's real child count, where an `autoBoxing` wrapper is\n * one child. Each entry holds the first node the wrapper holds, or the node\n * itself, and where the wrapper ends.\n */\nexport const getCrengineBoxedChildren = (\n parent: Element,\n context: ParentContext = getParentContext(parent),\n): CrengineBoxedChild[] => {\n const children: CrengineBoxedChild[] = []\n\n for (const entry of getRenderedChildren(parent, context)) {\n if (entry.kind === \"box\") {\n const first = entry.children[0]\n const last = entry.children[entry.children.length - 1]\n\n if (first && last) {\n visitParsedChild(first, context, (child) => {\n children.push({ child, lastChildIndex: last.childIndex })\n\n return false\n })\n }\n\n continue\n }\n\n visitParsedChild(entry.child, context, (child) => {\n children.push({ child, lastChildIndex: child.childIndex })\n\n return true\n })\n }\n\n return children\n}\n\nexport const getCrengineChildren = (parent: Element): CrengineChild[] => {\n const children: CrengineChild[] = []\n\n visitCrengineChildren(parent, (child) => {\n children.push(child)\n\n return true\n })\n\n return children\n}\n\n/**\n * The `<body>` crengine's fragment body corresponds to. An SVG spine item\n * has none: crengine wraps such a file in a body of its own, and how it\n * parses what follows (the XML declaration becomes an element there) is not\n * something a DOM can show, so the item is handled as a whole.\n */\nexport const getFragmentBody = (document: Document): Element | undefined =>\n document.body ?? undefined\n","import type { ParsedXPointer } from \"./types\"\n\n/**\n * Prints a pointer in the shape `toStringV2` (crengine/src/lvtinydom.cpp)\n * produces for DOM versions 20200223..20260811, the one every third-party\n * consumer was written against: an index is written only where the parsed\n * structure carries one, and the spine item prefix is always\n * `/body/DocFragment[N]/body`.\n *\n * `serializeXPointer(parseXPointer(s))` is `s` for every pointer crengine\n * writes in that shape for a book with several spine items; the single-item\n * prefix `/body/DocFragment/body` and the explicit `body[1]` prefixes come\n * back normalised. `generateXPointer` decides where an index is needed; this\n * function never looks at a DOM.\n */\nexport const serializeXPointer = (parsed: ParsedXPointer): string => {\n let output = `/body/DocFragment[${parsed.spineItemIndex + 1}]/body`\n\n for (const step of parsed.steps) {\n if (step.kind === \"nodeIndex\") {\n output += `/${step.index}`\n } else {\n const name = step.kind === \"text\" ? \"text()\" : step.name\n\n output +=\n step.index === undefined ? `/${name}` : `/${name}[${step.index}]`\n }\n }\n\n return parsed.point === undefined ? output : `${output}.${parsed.point}`\n}\n","import {\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n isTextNode,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport {\n crengineElementName,\n getParentContext,\n hasImproperTableChildren,\n isElement,\n} from \"./crengine/elements\"\nimport { toCrengineOffset } from \"./crengine/text\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, XPointerStep } from \"./types\"\n\nconst DOCUMENT_NODE = 9\n\nconst isDocument = (node: Node): node is Document =>\n node.nodeType === DOCUMENT_NODE\n\n/**\n * Position of `node` among the same-name element children of `parent`, as\n * `toStringV2` (crengine/src/lvtinydom.cpp) writes it: the index is emitted\n * only when the parent holds more than one element of that name.\n */\nconst elementStep = (\n parent: Element,\n node: Element,\n insideSvg: boolean,\n): XPointerStep | undefined => {\n const name = crengineElementName(node, insideSvg)\n let index: number | undefined\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (\n child.kind !== \"element\" ||\n crengineElementName(child.node, insideSvg) !== name\n )\n return true\n\n count++\n\n if (child.node === node) index = count\n\n // Once the node is found, one more same-name sibling settles the shape.\n return index === undefined || count === 1\n })\n\n if (index === undefined) return undefined\n\n return { kind: \"element\", name, index: count > 1 ? index : undefined }\n}\n\n/**\n * Steps from the fragment body down to `element` (excluded when it is the\n * body). `undefined` when the element does not hang under the body crengine\n * knows (detached, or in `<head>`).\n */\nconst stepsTo = (\n element: Element,\n body: Element,\n): XPointerStep[] | undefined => {\n const chain: Element[] = []\n let node: Node | null = element\n\n while (node !== body) {\n if (!node || !isElement(node)) return undefined\n\n chain.push(node)\n node = node.parentNode\n }\n\n chain.reverse()\n\n const steps: XPointerStep[] = []\n let parent: Element = body\n let insideSvg = false\n\n for (const current of chain) {\n const step = elementStep(parent, current, insideSvg)\n\n if (!step) return undefined\n\n steps.push(step)\n\n if (current.localName.toLowerCase() === \"svg\") insideSvg = true\n\n parent = current\n }\n\n return steps\n}\n\n/** The kept piece of `text` holding UTF-16 `offset`, its 1-based rank among the parent's text pieces and whether it has siblings. */\nconst findTextPiece = (parent: Element, text: Text, offset: number) => {\n let found: Extract<CrengineChild, { kind: \"text\" }> | undefined\n let foundIndex = 0\n let count = 0\n\n visitCrengineChildren(parent, (child) => {\n if (child.kind !== \"text\") return true\n\n count++\n\n if (found === undefined && child.node === text) {\n const last = child.pieceIndex === child.pieceCount - 1\n\n if (offset < child.end || last) {\n found = child\n foundIndex = count\n }\n }\n\n // Once the piece is found, one more text sibling settles the shape.\n return found === undefined || count === 1\n })\n\n if (!found) return undefined\n\n return { piece: found, index: foundIndex, count }\n}\n\n/**\n * crengine's child index for the boundary before browser child `offset`:\n * every child crengine's element holds (an `autoBoxing` wrapper counting\n * once, each piece of a split text node counting) that starts before it. A\n * boundary inside a wrapper becomes the wrapper's start. `undefined` where\n * crengine rebuilds the children with wrappers of its own (ruby, MathML)\n * and a non-zero index cannot be known.\n */\nconst childIndexBefore = (parent: Element, offset: number) => {\n const context = getParentContext(parent)\n let index = 0\n\n for (const { child, lastChildIndex } of getCrengineBoxedChildren(\n parent,\n context,\n )) {\n if (child.childIndex >= offset || offset <= lastChildIndex) break\n\n index++\n }\n\n const unknowable = context.restructured || hasImproperTableChildren(parent)\n\n return unknowable && index > 0 ? undefined : index\n}\n\n/**\n * The classic V2 pointer for a DOM position, as `toStringV2`\n * (crengine/src/lvtinydom.cpp) writes it for a book opened with a DOM\n * version between 20200223 and 20260811: `[N]` only where the name is shared\n * by more than one sibling, `text()` without `[N]` when the parent holds a\n * single text node. The result resolves in crengine to the same node.\n *\n * `undefined` for positions crengine has no node for: a whitespace-only text\n * node it dropped, text under an element that takes no text, an element\n * outside the `<body>`, an offset past the end of a text node, anything\n * inside an SVG spine item but the item itself.\n */\nexport const generateXPointer = (\n position: DomPosition,\n spineItemIndex: number,\n): string | undefined => {\n if (!Number.isInteger(spineItemIndex) || spineItemIndex < 0) return undefined\n\n const { node, offset } = position\n const document = isDocument(node) ? node : node.ownerDocument\n\n if (!document) return undefined\n\n const body = getFragmentBody(document)\n const build = (\n steps: XPointerStep[] | undefined,\n point: number | undefined,\n ) =>\n steps === undefined\n ? undefined\n : serializeXPointer({\n spineItemIndex,\n steps,\n point,\n containsBoxingElements: false,\n })\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return isDocument(node) || node === document.documentElement\n ? build([], undefined)\n : undefined\n }\n\n if (isDocument(node)) return undefined\n\n if (isTextNode(node)) {\n const parent = node.parentNode\n const rawOffset = offset ?? 0\n\n if (\n !parent ||\n !isElement(parent) ||\n rawOffset < 0 ||\n rawOffset > node.data.length\n )\n return undefined\n\n const text = findTextPiece(parent, node, rawOffset)\n\n if (!text) return undefined\n\n const { piece } = text\n const steps = stepsTo(parent, body)\n\n if (!steps) return undefined\n\n steps.push({ kind: \"text\", index: text.count > 1 ? text.index : undefined })\n\n return build(\n steps,\n toCrengineOffset(\n node.data.slice(piece.start, piece.end),\n rawOffset - piece.start,\n piece.mode,\n ),\n )\n }\n\n if (!isElement(node)) return undefined\n\n if (offset === undefined) return build(stepsTo(node, body), undefined)\n\n if (offset < 0 || offset > node.childNodes.length) return undefined\n\n const point = childIndexBefore(node, offset)\n\n return point === undefined ? undefined : build(stepsTo(node, body), point)\n}\n","import type { ParsedXPointer, XPointerStep } from \"./types\"\n\n/**\n * Wrapper elements crengine inserts into its own DOM for layout\n * (crengine/include/fb2def.h, `EL_BOXING_START`..`EL_BOXING_END`) plus the\n * `pseudoElem` it creates for `::before` / `::after`. None exists in the source\n * XHTML, so a pointer naming one cannot be resolved in a browser DOM.\n */\nexport const XPOINTER_BOXING_ELEMENT_NAMES: readonly string[] = [\n \"autoBoxing\",\n \"tabularBox\",\n \"rubyBox\",\n \"mathBox\",\n \"floatBox\",\n \"inlineBox\",\n \"pseudoElem\",\n]\n\nconst boxingElementNames = new Set<string>(\n XPOINTER_BOXING_ELEMENT_NAMES.map((name) => name.toLowerCase()),\n)\n\nconst isDigit = (charCode: number) => charCode >= 48 && charCode <= 57\n\n/** An element name as crengine's XML parser can produce one (no `.`, which ends a step). */\nconst ELEMENT_NAME = /^[A-Za-z_][A-Za-z0-9_:-]*$/\n\nconst isStepBoundary = (char: string | undefined) =>\n char === undefined || char === \"/\" || char === \".\"\n\nconst readDigits = (input: string, from: number) => {\n let end = from\n\n while (end < input.length && isDigit(input.charCodeAt(end))) end++\n\n return end\n}\n\n/**\n * Port of `ParseXPathStep` (crengine/src/lvtinydom.cpp): a pointer is a\n * sequence of `/name`, `/name[N]`, `/text()`, `/text()[N]` or `/N` steps, with\n * an optional `.N` point that must end the string (`createXPointerV2` rejects\n * anything after it).\n *\n * Stricter than crengine on malformed input: `[N]` must be plain decimal\n * digits, where crengine's `atoi` would also take signs, hex or surrounding\n * blanks and silently turn garbage into an unresolvable index 0, and a step\n * name must be a plausible element name, where crengine would look up any\n * text (`text()x`, `p q`) and fail to find an element.\n */\nconst tokenize = (\n input: string,\n): { steps: XPointerStep[]; point: number | undefined } | undefined => {\n const steps: XPointerStep[] = []\n let position = 0\n\n while (position < input.length) {\n const prefix = input[position]\n\n if (prefix === \".\") {\n const digitsStart = position + 1\n const digitsEnd = readDigits(input, digitsStart)\n\n if (digitsEnd === digitsStart || digitsEnd !== input.length)\n return undefined\n\n return { steps, point: Number(input.slice(digitsStart, digitsEnd)) }\n }\n\n if (prefix !== \"/\") return undefined\n\n const stepStart = position + 1\n\n if (isDigit(input.charCodeAt(stepStart))) {\n const digitsEnd = readDigits(input, stepStart)\n\n if (!isStepBoundary(input[digitsEnd])) return undefined\n\n const index = Number(input.slice(stepStart, digitsEnd))\n\n if (index < 1) return undefined\n\n steps.push({ kind: \"nodeIndex\", index })\n position = digitsEnd\n\n continue\n }\n\n let nameEnd = stepStart\n\n while (nameEnd < input.length && !\"[/.\".includes(input[nameEnd] ?? \"\"))\n nameEnd++\n\n if (nameEnd === stepStart) return undefined\n\n const name = input.slice(stepStart, nameEnd)\n let index: number | undefined\n let stepEnd = nameEnd\n\n if (input[nameEnd] === \"[\") {\n const closing = input.indexOf(\"]\", nameEnd + 1)\n\n if (closing === -1) return undefined\n\n const digits = input.slice(nameEnd + 1, closing)\n\n if (digits.length === 0 || readDigits(digits, 0) !== digits.length)\n return undefined\n\n index = Number(digits)\n\n if (index < 1) return undefined\n\n stepEnd = closing + 1\n }\n\n if (!isStepBoundary(input[stepEnd])) return undefined\n\n if (name === \"text()\") {\n steps.push({ kind: \"text\", index })\n } else if (ELEMENT_NAME.test(name)) {\n steps.push({ kind: \"element\", name, index })\n } else {\n return undefined\n }\n position = stepEnd\n }\n\n return { steps, point: undefined }\n}\n\nconst isElementNamed = (\n step: XPointerStep | undefined,\n name: string,\n): step is Extract<XPointerStep, { kind: \"element\" }> =>\n step?.kind === \"element\" && step.name.toLowerCase() === name\n\n/**\n * Accepts every shape crengine has emitted (`toStringV1`, `toStringV2` and\n * its explicit-index variant for DOM versions >= 20260812) plus the bare\n * `/body/DocFragment[N]` third parties write for the start of a spine item.\n *\n * Returns `undefined` for anything crengine's own parser would reject or that\n * does not start with the `/body/DocFragment[N]/body` prefix of an EPUB\n * pointer. crengine's `#id` form is rejected too: it names an element of the\n * whole book DOM by id, which a spine item document cannot answer.\n */\nexport const parseXPointer = (input: string): ParsedXPointer | undefined => {\n const tokenized = tokenize(input)\n\n if (!tokenized) return undefined\n\n const [root, fragment, body, ...steps] = tokenized.steps\n\n if (!isElementNamed(root, \"body\") || (root.index ?? 1) !== 1) return undefined\n\n if (!isElementNamed(fragment, \"docfragment\")) return undefined\n\n const spineItemIndex = (fragment.index ?? 1) - 1\n\n // `/body/DocFragment[N]` alone, with or without a point (Kavita emits\n // `/body/DocFragment[N].0`): the start of the spine item.\n if (body === undefined) {\n return {\n spineItemIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n }\n }\n\n if (!isElementNamed(body, \"body\") || (body.index ?? 1) !== 1) return undefined\n\n return {\n spineItemIndex,\n steps,\n point: tokenized.point,\n containsBoxingElements: steps.some(\n (step) =>\n step.kind === \"element\" &&\n boxingElementNames.has(step.name.toLowerCase()),\n ),\n }\n}\n","import {\n type CrengineBoxedChild,\n type CrengineChild,\n getCrengineBoxedChildren,\n getFragmentBody,\n visitCrengineChildren,\n} from \"./crengine/children\"\nimport { toRawOffset } from \"./crengine/text\"\nimport { parseXPointer } from \"./parse\"\nimport type { DomPosition, ParsedXPointer, XPointerStep } from \"./types\"\n\ntype Current =\n | { kind: \"parent\"; node: Element }\n | Extract<CrengineChild, { kind: \"text\" }>\n\n/**\n * One step of `createXPointerV2` (crengine/src/lvtinydom.cpp): the N-th child\n * matching the step (first when the index is omitted), counted among the\n * children crengine kept. Element names compare case-insensitively.\n */\nconst findChild = (\n parent: Element,\n step: XPointerStep,\n): CrengineChild | undefined => {\n const wantedIndex = step.index ?? 1\n const wantedName =\n step.kind === \"element\" ? step.name.toLowerCase() : undefined\n let count = 0\n let found: CrengineChild | undefined\n\n visitCrengineChildren(parent, (child) => {\n const matches =\n step.kind === \"nodeIndex\" ||\n (step.kind === \"text\"\n ? child.kind === \"text\"\n : child.kind === \"element\" &&\n child.node.localName.toLowerCase() === wantedName)\n\n if (!matches) return true\n\n count++\n\n if (count === wantedIndex) {\n found = child\n\n return false\n }\n\n return true\n })\n\n return found\n}\n\nconst positionOfChild = (\n parent: Element,\n children: CrengineBoxedChild[],\n index: number,\n): DomPosition => {\n const child = children[index]?.child\n\n if (!child) return { node: parent, offset: parent.childNodes.length }\n\n // The boundary before a later piece of a split text node lies inside it.\n if (child.kind === \"text\" && child.pieceIndex > 0) {\n return { node: child.node, offset: child.start }\n }\n\n return { node: parent, offset: child.childIndex }\n}\n\n/**\n * Resolves a pointer inside the spine item's document, following\n * `createXPointerV2` (crengine/src/lvtinydom.cpp). Returns `undefined` when\n * crengine would fail too: a step without a match, a point past the end of\n * the text or of the children (nothing is clamped), a V1 pointer naming a\n * boxing element.\n *\n * A text position is returned with a UTF-16 offset into the text node,\n * ready for `Range.setStart`; a text step without a point is its start. In\n * a spine item without a `<body>` (an SVG document) only the item itself\n * resolves, to its root element.\n */\nexport const resolveXPointer = (\n xpointer: string | ParsedXPointer,\n document: Document,\n): DomPosition | undefined => {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed || parsed.containsBoxingElements) return undefined\n\n const body = getFragmentBody(document)\n\n // An SVG spine item is one page to crengine: only the item itself is addressable.\n if (!body) {\n return parsed.steps.length === 0\n ? { node: document.documentElement }\n : undefined\n }\n\n let current: Current = { kind: \"parent\", node: body }\n\n for (const step of parsed.steps) {\n if (current.kind === \"text\") return undefined\n\n const child = findChild(current.node, step)\n\n if (!child) return undefined\n\n current =\n child.kind === \"element\" ? { kind: \"parent\", node: child.node } : child\n }\n\n if (current.kind === \"text\") {\n if (parsed.point === undefined) {\n return { node: current.node, offset: current.start }\n }\n\n const rawOffset = toRawOffset(\n current.node.data.slice(current.start, current.end),\n parsed.point,\n current.mode,\n )\n\n return rawOffset === undefined\n ? undefined\n : { node: current.node, offset: current.start + rawOffset }\n }\n\n const parent = current.node\n\n if (parsed.point === undefined) return { node: parent }\n\n const children = getCrengineBoxedChildren(parent)\n\n if (parsed.point > children.length) return undefined\n\n return positionOfChild(parent, children, parsed.point)\n}\n","import {\n generate,\n getEpubCfiSpineItemref,\n isIndirectionOnly,\n parse,\n resolve,\n} from \"@prose-reader/cfi\"\nimport { isTextNode } from \"./crengine/children\"\nimport { isElement } from \"./crengine/elements\"\nimport { generateXPointer } from \"./generate\"\nimport { parseXPointer } from \"./parse\"\nimport { resolveXPointer } from \"./resolve\"\nimport { serializeXPointer } from \"./serialize\"\nimport type { DomPosition, ParsedXPointer } from \"./types\"\n\nexport type XPointerSpineItem = {\n /** The spine item's content document, parsed as XHTML (what prose renders in its frames). */\n document: Document\n /** The `<itemref>` idref, written as the CFI's id assertion (`/6/14[chap05]!`) when given. */\n id?: string\n}\n\n/**\n * Hands out the document of a spine item by its 0-based index. A pointer\n * names its own spine item (`DocFragment[N]`) and is resolved in that\n * document, never in whichever one happens to be on screen: Readest shipped\n * that bug (readest/readest#5980) and landed a chapter away.\n */\nexport type XPointerSpineItemLookup = (\n spineItemIndex: number,\n) => XPointerSpineItem | undefined\n\n/**\n * The position a CFI can point to for a DOM position. A CFI cannot point\n * between an element's children, so a child index point becomes the start of\n * that child, and the end of the children the end of the last text child or\n * the element itself. Any other position is returned as it is.\n */\nexport const toCfiPosition = (position: DomPosition): DomPosition => {\n const { node, offset } = position\n\n if (!isElement(node) || offset === undefined) return position\n\n const child = node.childNodes[offset]\n\n if (child) {\n return isTextNode(child) ? { node: child, offset: 0 } : { node: child }\n }\n\n const last = node.lastChild\n\n return last && isTextNode(last)\n ? { node: last, offset: last.data.length }\n : { node }\n}\n\n/**\n * The CFI of a KOReader position, built with `@prose-reader/cfi` in the\n * spine item document the pointer names. `undefined` when the pointer is\n * malformed, its spine item is not available or it does not resolve; the\n * spine index alone is still available through `parseXPointer`.\n */\nexport const xPointerToCfi = (\n xpointer: string | ParsedXPointer,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const parsed =\n typeof xpointer === \"string\" ? parseXPointer(xpointer) : xpointer\n\n if (!parsed) return undefined\n\n const spineItem = getSpineItem(parsed.spineItemIndex)\n\n if (!spineItem) return undefined\n\n const position = resolveXPointer(parsed, spineItem.document)\n\n if (!position) return undefined\n\n const { node, offset } = toCfiPosition(position)\n\n return generate({\n node,\n offset: isTextNode(node) ? offset : undefined,\n spineIndex: parsed.spineItemIndex,\n spineId: spineItem.id,\n })\n } catch {\n return undefined\n }\n}\n\nconst firstOffset = (offset: number | number[] | undefined) =>\n Array.isArray(offset) ? offset[0] : offset\n\n/**\n * The KOReader pointer of a prose CFI: the CFI's start when it is a range,\n * the start of the spine item for a root CFI (`epubcfi(/6/14[chap05]!)`).\n * `undefined` when the CFI is malformed, its spine item is not available or\n * it does not resolve in that document.\n */\nexport const cfiToXPointer = (\n cfi: string,\n getSpineItem: XPointerSpineItemLookup,\n): string | undefined => {\n try {\n const itemref = getEpubCfiSpineItemref(cfi)\n\n if (!itemref) return undefined\n\n const { spineIndex } = itemref\n\n if (!Number.isInteger(spineIndex) || spineIndex < 0) return undefined\n\n const parsed = parse(cfi)\n\n if (isIndirectionOnly(parsed)) {\n return serializeXPointer({\n spineItemIndex: spineIndex,\n steps: [],\n point: undefined,\n containsBoxingElements: false,\n })\n }\n\n const spineItem = getSpineItem(spineIndex)\n\n if (!spineItem) return undefined\n\n const resolved = resolve(parsed, spineItem.document)\n\n if (!resolved.node) return undefined\n\n const position: DomPosition = resolved.isRange\n ? {\n node: resolved.node.startContainer,\n offset: resolved.node.startOffset,\n }\n : {\n node: resolved.node,\n offset: isTextNode(resolved.node)\n ? (firstOffset(resolved.offset) ?? 0)\n : undefined,\n }\n\n return generateXPointer(position, spineIndex)\n } catch {\n return undefined\n }\n}\n","import { md5HexDigest } from \"@prose-reader/shared\"\n\n/** Bytes `util.partialMD5` reads at each of its offsets. */\nconst PARTIAL_MD5_SAMPLE_SIZE = 1024\n\n/**\n * Where `util.partialMD5` (KOReader, frontend/util.lua) reads: at\n * `bit.lshift(1024, 2 * i)` for `i` from -1 to 10. LuaJIT keeps the low five\n * bits of a shift count, so `i = -1` shifts 1024 by 30 bits, out of 32 bits,\n * and reads at 0.\n */\nconst PARTIAL_MD5_SAMPLE_OFFSETS = [\n 0, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864,\n 268435456, 1073741824,\n]\n\n/**\n * The digest KOReader files a book's reading position under with its default\n * \"Binary\" document matching method: `util.partialMD5` (frontend/util.lua),\n * the MD5 of 1 KiB samples of the file at the offsets above, as lowercase hex.\n * A sample the end of the file cuts short is hashed as it is, and the samples\n * stop at the first offset at or past the end: at most 12 KiB of `file` are\n * read.\n *\n * KOReader computes it from the bytes it opens the first time and keeps it in\n * the book's settings, so the digests match only for the same bytes.\n */\nexport const getKoreaderFileDigest = async (file: Blob): Promise<string> => {\n const samples = await Promise.all(\n PARTIAL_MD5_SAMPLE_OFFSETS.filter((offset) => offset < file.size).map(\n async (offset) =>\n new Uint8Array(\n await file\n .slice(offset, offset + PARTIAL_MD5_SAMPLE_SIZE)\n .arrayBuffer(),\n ),\n ),\n )\n\n return md5HexDigest(samples)\n}\n\n/**\n * The digest KOReader files a book's reading position under with its\n * \"Filename\" document matching method (`KOSync:getFileNameDigest`,\n * plugins/kosync.koplugin/main.lua): the MD5 of the file name, as lowercase\n * hex.\n *\n * `fileName` is the base name, extension included, as the device's file\n * system stores it. KOReader hashes its UTF-8 bytes as they are, without\n * Unicode normalization: an `é` stored as one code point and one stored as an\n * `e` and a combining accent give two digests.\n */\nexport const getKoreaderFileNameDigest = (fileName: string): string =>\n md5HexDigest([new TextEncoder().encode(fileName)])\n","/**\n * What a KOReader sync server's `progress` holds for a book: a crengine\n * xpointer, or a 1-based page number written as a string (\"12\").\n */\nexport type KoreaderProgressKind = \"xpointer\" | \"page\"\n\n/** Books KOReader opens with crengine, whose `progress` is an xpointer. */\nconst XPOINTER_BOOK_MEDIA_TYPES: ReadonlySet<string> = new Set([\n \"application/epub+zip\",\n \"application/epub\",\n])\n\n/**\n * Books KOReader opens with MuPDF, whose `progress` is a page number: a PDF\n * and a comic archive.\n */\nconst PAGE_BOOK_MEDIA_TYPES: ReadonlySet<string> = new Set([\n \"application/pdf\",\n \"application/vnd.comicbook+zip\",\n \"application/x-cbz\",\n \"application/vnd.comicbook-rar\",\n \"application/vnd.rar\",\n \"application/x-cbr\",\n \"application/vnd.comicbook+tar\",\n \"application/x-cbt\",\n])\n\n/**\n * The form of a book's `progress`, from the media type of its file. KOReader's\n * sync plugin picks the form from the engine that opened the book (`has_pages`\n * in `plugins/kosync.koplugin/main.lua`), and KOReader picks the engine from\n * the file's extension (`frontend/document/documentregistry.lua`):\n *\n * - `\"xpointer\"` for an EPUB, opened with crengine.\n * - `\"page\"` for a PDF or a CBZ, CBR or CBT, opened with MuPDF.\n * - `undefined` for anything else, `application/octet-stream` included. A\n * plain text file is opened with crengine too, but crengine builds its own\n * document from the text, with paragraphs and headings it detects, and no\n * xpointer into it can be computed from the text alone. A plain ZIP is\n * opened with the engine its first file calls for, which its media type\n * does not tell: a caller that knows a ZIP opens as a comic gives it a\n * comic's media type.\n *\n * A KOReader user can choose MuPDF for a single EPUB, which then syncs page\n * numbers; nothing in the file says so.\n */\nexport const getKoreaderProgressKind = (\n mediaType: string,\n): KoreaderProgressKind | undefined => {\n const essence = mediaType.split(\";\")[0]?.trim().toLowerCase() ?? \"\"\n\n if (XPOINTER_BOOK_MEDIA_TYPES.has(essence)) return \"xpointer\"\n if (PAGE_BOOK_MEDIA_TYPES.has(essence)) return \"page\"\n\n return undefined\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export type KoreaderProgressKind = "xpointer" | "page";
2
+ export declare const getKoreaderProgressKind: (mediaType: string) => KoreaderProgressKind | undefined;
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@prose-reader/koreader",
3
- "version": "29.0.0",
3
+ "version": "30.2.0",
4
+ "description": "Convert reading positions between EPUB CFIs and KOReader's crengine XPointers, and compute the document digests KOReader sync uses.",
5
+ "keywords": [
6
+ "prose-reader",
7
+ "koreader",
8
+ "kosync",
9
+ "xpointer",
10
+ "crengine",
11
+ "epubcfi",
12
+ "epub"
13
+ ],
14
+ "homepage": "https://doc.prose-reader.com/koreader",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/mbret/prose-reader.git",
18
+ "directory": "packages/koreader"
19
+ },
4
20
  "type": "module",
5
21
  "license": "MIT",
6
22
  "files": [
@@ -28,13 +44,13 @@
28
44
  "tsc": "tsc"
29
45
  },
30
46
  "dependencies": {
31
- "@prose-reader/cfi": "^29.0.0",
32
- "@prose-reader/shared": "^29.0.0"
47
+ "@prose-reader/cfi": "^30.2.0",
48
+ "@prose-reader/shared": "^30.2.0"
33
49
  },
34
50
  "devDependencies": {
35
51
  "jszip": "^3.10.1",
36
52
  "typescript": "*",
37
53
  "vite": "^8"
38
54
  },
39
- "gitHead": "92ed28067687013c3ef2fa18b4a9a9ce0d417aa8"
55
+ "gitHead": "b05eeb084f73b2af81a2fd5eb1bac5c03ff29364"
40
56
  }