@prose-reader/koreader 1.372.0 → 1.373.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");var t=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(`.`)),n=new Set([`button`,`input`,`marquee`]),r=new Set([`area`,`base`,`basefont`,`bgsound`,`binary`,`clonenode`,`datalist`,`description`,`genre`,`head`,`link`,`meta`,`noembed`,`param`,`pseudoelem`,`script`,`style`,`stylesheet`,`template`,`title`]),i=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(`.`)),a=new Set([`listing`,`plaintext`,`pre`,`style`,`textarea`,`xmp`]),o=new Set([`desc`,`metadata`,`style`,`text`,`textpath`,`title`,`tspan`]),s=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(`.`)),c=new Set([`mglyph`,`mi`,`mn`,`mo`,`mtext`,`mspace`,`ms`]),l=new Set([`script`,`style`]),u={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`])},d=e=>{let t=u[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},f=new Set([`pre`,`textarea`]),ee=1,p=e=>e.nodeType===ee,m=(e,t)=>t?e.localName:e.localName.toLowerCase(),te=new Set([`col`,`colgroup`,`tbody`,`td`,`tfoot`,`th`,`thead`,`tr`]),h=e=>r.has(e)?`none`:t.has(e)?`block`:n.has(e)?`inlineBlock`:e===`ruby`?`ruby`:`inline`,g=e=>{let t=e.localName.toLowerCase();return e.hasAttribute(`hidden`)&&!te.has(t)?`none`:h(t)},_=e=>{let t=e.localName.toLowerCase(),n=!1,r=!1,u=t===`math`;for(let t=e.parentNode;t&&p(t);t=t.parentNode){let e=t.localName.toLowerCase();a.has(e)&&(n=!0),e===`svg`&&(r=!0),e===`math`&&(u=!0)}if(r){let e=o.has(t);return{display:`inline`,block:!1,allowsText:e,pre:n||e,stripsLeadingNewline:!1,trimsText:!1,restructured:!1,opaque:l.has(t),insideSvg:!0}}if(u)return{display:`inline`,block:!1,allowsText:c.has(t)||!s.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:!i.has(t),pre:n||a.has(t),stripsLeadingNewline:f.has(t),trimsText:!1,restructured:d===`ruby`,opaque:l.has(t),insideSvg:t===`svg`}},ne=8192,v=9,y=10,b=13,x=32,S=e=>e===x||e===b||e===y||e===v,C=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},w=e=>e>65535?2:1,T=(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=w(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=w(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}},re=(e,t,n)=>{let r=0;return T(e,n,(e,n,i)=>e+n>t?!1:(r+=i,!0)),r},ie=(e,t,n)=>{if(t<0)return;let r=0,i;return T(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},E=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+=w(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},D=3,O=4,k=e=>e.nodeType===D||e.nodeType===O,ae=(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(!k(i)||!t.allowsText)continue;let e=C(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(ae(e,t),t),P=(e,t,n)=>{if(p(e.node))return n({kind:`element`,node:e.node,childIndex:e.childIndex});let r=E(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));continue}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?!0:(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||d(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(k(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,re(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`],q=new Set(K.map(e=>e.toLowerCase())),J=e=>e>=48&&e<=57,oe=/^[A-Za-z_][A-Za-z0-9_:-]*$/,Y=e=>e===void 0||e===`/`||e===`.`,X=(e,t)=>{let n=t;for(;n<e.length&&J(e.charCodeAt(n));)n++;return n},se=e=>{let t=[],n=0;for(;n<e.length;){let r=e[n];if(r===`.`){let r=n+1,i=X(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(J(e.charCodeAt(i))){let r=X(e,i);if(!Y(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||X(n,0)!==n.length||(s=Number(n),s<1))return;c=t+1}if(!Y(e[c]))return;if(o===`text()`)t.push({kind:`text`,index:s});else if(oe.test(o))t.push({kind:`element`,name:o,index:s});else return;n=c}return{steps:t,point:void 0}},Z=(e,t)=>e?.kind===`element`&&e.name.toLowerCase()===t,Q=e=>{let t=se(e);if(!t)return;let[n,r,i,...a]=t.steps;if(!Z(n,`body`)||(n.index??1)!==1||!Z(r,`docfragment`))return;let o=(r.index??1)-1;if(i===void 0)return{spineItemIndex:o,steps:[],point:void 0,containsBoxingElements:!1};if(!(!Z(i,`body`)||(i.index??1)!==1))return{spineItemIndex:o,steps:a,point:t.point,containsBoxingElements:a.some(e=>e.kind===`element`&&q.has(e.name.toLowerCase()))}},ce=(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):!0),a},le=(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}},$=(e,t)=>{let n=typeof e==`string`?Q(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=ce(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=ie(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 le(a,o,n.point)},ue=e=>{let{node:t,offset:n}=e;if(!p(t)||n===void 0)return e;let r=t.childNodes[n];if(r)return k(r)?{node:r,offset:0}:{node:r};let i=t.lastChild;return i&&k(i)?{node:i,offset:i.data.length}:{node:t}},de=(t,n)=>{try{let r=typeof t==`string`?Q(t):t;if(!r)return;let i=n(r.spineItemIndex);if(!i)return;let a=$(r,i.document);if(!a)return;let{node:o,offset:s}=ue(a);return(0,e.generate)({node:o,offset:k(o)?s:void 0,spineIndex:r.spineItemIndex,spineId:i.id})}catch{return}},fe=e=>Array.isArray(e)?e[0]:e,pe=(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:k(s.node)?fe(s.offset)??0:void 0},i):void 0}catch{return}};exports.CRENGINE_TEXT_SPLIT_SIZE=ne,exports.XPOINTER_BOXING_ELEMENT_NAMES=K,exports.cfiToXPointer=pe,exports.generateXPointer=G,exports.parseXPointer=Q,exports.resolveXPointer=$,exports.serializeXPointer=R,exports.xPointerToCfi=de;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/cfi");var t=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(`.`)),n=new Set([`button`,`input`,`marquee`]),r=new Set([`area`,`base`,`basefont`,`bgsound`,`binary`,`clonenode`,`datalist`,`description`,`genre`,`head`,`link`,`meta`,`noembed`,`param`,`pseudoelem`,`script`,`style`,`stylesheet`,`template`,`title`]),i=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(`.`)),a=new Set([`listing`,`plaintext`,`pre`,`style`,`textarea`,`xmp`]),o=new Set([`desc`,`metadata`,`style`,`text`,`textpath`,`title`,`tspan`]),s=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(`.`)),c=new Set([`mglyph`,`mi`,`mn`,`mo`,`mtext`,`mspace`,`ms`]),l=new Set([`script`,`style`]),u={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`])},d=e=>{let t=u[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},f=new Set([`pre`,`textarea`]),ee=1,p=e=>e.nodeType===ee,m=(e,t)=>t?e.localName:e.localName.toLowerCase(),te=new Set([`col`,`colgroup`,`tbody`,`td`,`tfoot`,`th`,`thead`,`tr`]),h=e=>r.has(e)?`none`:t.has(e)?`block`:n.has(e)?`inlineBlock`:e===`ruby`?`ruby`:`inline`,g=e=>{let t=e.localName.toLowerCase();return e.hasAttribute(`hidden`)&&!te.has(t)?`none`:h(t)},_=e=>{let t=e.localName.toLowerCase(),n=!1,r=!1,u=t===`math`;for(let t=e.parentNode;t&&p(t);t=t.parentNode){let e=t.localName.toLowerCase();a.has(e)&&(n=!0),e===`svg`&&(r=!0),e===`math`&&(u=!0)}if(r){let e=o.has(t);return{display:`inline`,block:!1,allowsText:e,pre:n||e,stripsLeadingNewline:!1,trimsText:!1,restructured:!1,opaque:l.has(t),insideSvg:!0}}if(u)return{display:`inline`,block:!1,allowsText:c.has(t)||!s.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:!i.has(t),pre:n||a.has(t),stripsLeadingNewline:f.has(t),trimsText:!1,restructured:d===`ruby`,opaque:l.has(t),insideSvg:t===`svg`}},ne=8192,v=9,y=10,b=13,x=32,S=e=>e===x||e===b||e===y||e===v,C=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},w=e=>e>65535?2:1,T=(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=w(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=w(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}},re=(e,t,n)=>{let r=0;return T(e,n,(e,n,i)=>e+n>t?!1:(r+=i,!0)),r},ie=(e,t,n)=>{if(t<0)return;let r=0,i;return T(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},E=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+=w(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},D=3,O=4,k=e=>e.nodeType===D||e.nodeType===O,ae=(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(!k(i)||!t.allowsText)continue;let e=C(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(ae(e,t),t),P=(e,t,n)=>{if(p(e.node))return n({kind:`element`,node:e.node,childIndex:e.childIndex});let r=E(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));continue}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||d(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(k(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,re(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`],q=new Set(K.map(e=>e.toLowerCase())),J=e=>e>=48&&e<=57,oe=/^[A-Za-z_][A-Za-z0-9_:-]*$/,Y=e=>e===void 0||e===`/`||e===`.`,X=(e,t)=>{let n=t;for(;n<e.length&&J(e.charCodeAt(n));)n++;return n},se=e=>{let t=[],n=0;for(;n<e.length;){let r=e[n];if(r===`.`){let r=n+1,i=X(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(J(e.charCodeAt(i))){let r=X(e,i);if(!Y(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||X(n,0)!==n.length||(s=Number(n),s<1))return;c=t+1}if(!Y(e[c]))return;if(o===`text()`)t.push({kind:`text`,index:s});else if(oe.test(o))t.push({kind:`element`,name:o,index:s});else return;n=c}return{steps:t,point:void 0}},Z=(e,t)=>e?.kind===`element`&&e.name.toLowerCase()===t,Q=e=>{let t=se(e);if(!t)return;let[n,r,i,...a]=t.steps;if(!Z(n,`body`)||(n.index??1)!==1||!Z(r,`docfragment`))return;let o=(r.index??1)-1;if(i===void 0)return{spineItemIndex:o,steps:[],point:void 0,containsBoxingElements:!1};if(Z(i,`body`)&&(i.index??1)===1)return{spineItemIndex:o,steps:a,point:t.point,containsBoxingElements:a.some(e=>e.kind===`element`&&q.has(e.name.toLowerCase()))}},ce=(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},le=(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}},$=(e,t)=>{let n=typeof e==`string`?Q(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=ce(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=ie(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 le(a,o,n.point)},ue=e=>{let{node:t,offset:n}=e;if(!p(t)||n===void 0)return e;let r=t.childNodes[n];if(r)return k(r)?{node:r,offset:0}:{node:r};let i=t.lastChild;return i&&k(i)?{node:i,offset:i.data.length}:{node:t}},de=(t,n)=>{try{let r=typeof t==`string`?Q(t):t;if(!r)return;let i=n(r.spineItemIndex);if(!i)return;let a=$(r,i.document);if(!a)return;let{node:o,offset:s}=ue(a);return(0,e.generate)({node:o,offset:k(o)?s:void 0,spineIndex:r.spineItemIndex,spineId:i.id})}catch{return}},fe=e=>Array.isArray(e)?e[0]:e,pe=(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:k(s.node)?fe(s.offset)??0:void 0},i):void 0}catch{return}};exports.CRENGINE_TEXT_SPLIT_SIZE=ne,exports.XPOINTER_BOXING_ELEMENT_NAMES=K,exports.cfiToXPointer=pe,exports.generateXPointer=G,exports.parseXPointer=Q,exports.resolveXPointer=$,exports.serializeXPointer=R,exports.xPointerToCfi=de;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"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"],"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 * A CFI cannot point between an element's children, so a child index point\n * becomes the start of that child, and the end of the children the end of the\n * last text child or the element itself.\n */\nconst toCfiPosition = (position: 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"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"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"],"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 * A CFI cannot point between an element's children, so a child index point\n * becomes the start of that child, and the end of the children the end of the\n * last text child or the element itself.\n */\nconst toCfiPosition = (position: 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"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -330,7 +330,7 @@ var a = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
330
330
  return e.point === void 0 ? t : `${t}.${e.point}`;
331
331
  }, V = 9, H = (e) => e.nodeType === V, U = (e, t, n) => {
332
332
  let r = g(t, n), i, a = 0;
333
- if (L(e, (e) => e.kind !== "element" || g(e.node, n) !== r ? !0 : (a++, e.node === t && (i = a), i === void 0 || a === 1)), i !== void 0) return {
333
+ if (L(e, (e) => e.kind !== "element" || g(e.node, n) !== r || (a++, e.node === t && (i = a), i === void 0 || a === 1)), i !== void 0) return {
334
334
  kind: "element",
335
335
  name: r,
336
336
  index: a > 1 ? i : void 0
@@ -474,7 +474,7 @@ var a = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
474
474
  point: void 0,
475
475
  containsBoxingElements: !1
476
476
  };
477
- if (!(!Z(i, "body") || (i.index ?? 1) !== 1)) return {
477
+ if (Z(i, "body") && (i.index ?? 1) === 1) return {
478
478
  spineItemIndex: o,
479
479
  steps: a,
480
480
  point: t.point,
@@ -482,7 +482,7 @@ var a = /* @__PURE__ */ new Set(/* @__PURE__ */ "address.annotation.applet.artic
482
482
  };
483
483
  }, fe = (e, t) => {
484
484
  let n = t.index ?? 1, r = t.kind === "element" ? t.name.toLowerCase() : void 0, i = 0, a;
485
- return L(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) : !0), a;
485
+ return L(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;
486
486
  }, pe = (e, t, n) => {
487
487
  let r = t[n]?.child;
488
488
  return r ? r.kind === "text" && r.pieceIndex > 0 ? {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"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"],"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 * A CFI cannot point between an element's children, so a child index point\n * becomes the start of that child, and the end of the children the end of the\n * last text child or the element itself.\n */\nconst toCfiPosition = (position: 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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"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"],"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 * A CFI cannot point between an element's children, so a child index point\n * becomes the start of that child, and the end of the children the end of the\n * last text child or the element itself.\n */\nconst toCfiPosition = (position: 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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/koreader",
3
- "version": "1.372.0",
3
+ "version": "1.373.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -28,7 +28,7 @@
28
28
  "tsc": "tsc"
29
29
  },
30
30
  "dependencies": {
31
- "@prose-reader/cfi": "^1.372.0"
31
+ "@prose-reader/cfi": "^1.373.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "jsdom": "^29.0.0",
@@ -36,5 +36,5 @@
36
36
  "typescript": "*",
37
37
  "vite": "^8"
38
38
  },
39
- "gitHead": "e3da49016a2c67accabcb8c0c63959991a44b73c"
39
+ "gitHead": "a58b61c522a90addce2baaab1b268d2e9b8fed73"
40
40
  }