pdfmark.js
Version:
Highlight keywords in pdfjs svg output. Based on mark.js.
8 lines (7 loc) • 15.6 kB
JavaScript
/*!***************************************************
* pdfmark.js v1.2.4
*
* Copyright (c) 2014–2018, Julian Kühnel
* Released under the MIT license https://git.io/vwTVl
*****************************************************/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):t.Pdfmark=e(t.jQuery)}(this,function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;class e{constructor(t,e=!0,s=[],r=5e3){this.ctx=t,this.iframes=e,this.exclude=s,this.iframesTimeout=r}static matches(t,e){const s="string"==typeof e?[e]:e,r=t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.webkitMatchesSelector;if(r){let e=!1;return s.every(s=>!r.call(t,s)||(e=!0,!1)),e}return!1}getContexts(){let t,e=[];return(t=void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(t=>{const s=e.filter(e=>e.contains(t)).length>0;-1!==e.indexOf(t)||s||e.push(t)}),e}getIframeContents(t,e,s=(()=>{})){let r;try{const e=t.contentWindow;if(r=e.document,!e||!r)throw new Error("iframe inaccessible")}catch(t){s()}r&&e(r)}isIframeBlank(t){const e="about:blank",s=t.getAttribute("src").trim();return t.contentWindow.location.href===e&&s!==e&&s}observeIframeLoad(t,e,s){let r=!1,i=null;const n=()=>{if(!r){r=!0,clearTimeout(i);try{this.isIframeBlank(t)||(t.removeEventListener("load",n),this.getIframeContents(t,e,s))}catch(t){s()}}};t.addEventListener("load",n),i=setTimeout(n,this.iframesTimeout)}onIframeReady(t,e,s){try{"complete"===t.contentWindow.document.readyState?this.isIframeBlank(t)?this.observeIframeLoad(t,e,s):this.getIframeContents(t,e,s):this.observeIframeLoad(t,e,s)}catch(t){s()}}waitForIframes(t,e){let s=0;this.forEachIframe(t,()=>!0,t=>{s++,this.waitForIframes(t.querySelector("html"),()=>{--s||e()})},t=>{t||e()})}forEachIframe(t,s,r,i=(()=>{})){let n=t.querySelectorAll("iframe"),o=n.length,a=0;n=Array.prototype.slice.call(n);const h=()=>{--o<=0&&i(a)};o||h(),n.forEach(t=>{e.matches(t,this.exclude)?h():this.onIframeReady(t,e=>{s(t)&&(a++,r(e)),h()},h)})}createIterator(t,e,s){return document.createNodeIterator(t,e,s,!1)}createInstanceOnIframe(t){return new e(t.querySelector("html"),this.iframes)}compareNodeIframe(t,e,s){if(t.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_PRECEDING){if(null===e)return!0;if(e.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}getIteratorNode(t){const e=t.previousNode();let s;return{prevNode:e,node:s=null===e?t.nextNode():t.nextNode()&&t.nextNode()}}checkIframeFilter(t,e,s,r){let i=!1,n=!1;return r.forEach((t,e)=>{t.val===s&&(i=e,n=t.handled)}),this.compareNodeIframe(t,e,s)?(!1!==i||n?!1===i||n||(r[i].handled=!0):r.push({val:s,handled:!0}),!0):(!1===i&&r.push({val:s,handled:!1}),!1)}handleOpenIframes(t,e,s,r){t.forEach(t=>{t.handled||this.getIframeContents(t.val,t=>{this.createInstanceOnIframe(t).forEachNode(e,s,r)})})}iterateThroughNodes(t,e,s,r,i){const n=this.createIterator(e,t,r);let o,a,h=[],c=[],l=()=>(({prevNode:a,node:o}=this.getIteratorNode(n)),o);for(;l();)this.iframes&&this.forEachIframe(e,t=>this.checkIframeFilter(o,a,t,h),e=>{this.createInstanceOnIframe(e).forEachNode(t,t=>c.push(t),r)}),c.push(o);c.forEach(t=>{s(t)}),this.iframes&&this.handleOpenIframes(h,t,s,r),i()}forEachNode(t,e,s,r=(()=>{})){const i=this.getContexts();let n=i.length;n||r(),i.forEach(i=>{const o=()=>{this.iterateThroughNodes(t,i,e,s,()=>{--n<=0&&r()})};this.iframes?this.waitForIframes(i,o):o()})}}class s{constructor(t){this.opt=Object.assign({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},t)}create(t){return"disabled"!==this.opt.wildcards&&(t=this.setupWildcardsRegExp(t)),t=this.escapeStr(t),Object.keys(this.opt.synonyms).length&&(t=this.createSynonymsRegExp(t)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(t=this.setupIgnoreJoinersRegExp(t)),this.opt.diacritics&&(t=this.createDiacriticsRegExp(t)),t=this.createMergedBlanksRegExp(t),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(t=this.createJoinersRegExp(t)),"disabled"!==this.opt.wildcards&&(t=this.createWildcardsRegExp(t)),t=this.createAccuracyRegExp(t),new RegExp(t,`gm${this.opt.caseSensitive?"":"i"}`)}escapeStr(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}createSynonymsRegExp(t){const e=this.opt.synonyms,s=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(let i in e)if(e.hasOwnProperty(i)){const n=e[i],o="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(n):this.escapeStr(n);""!==o&&""!==a&&(t=t.replace(new RegExp(`(${this.escapeStr(o)}|${this.escapeStr(a)})`,`gm${s}`),r+`(${this.processSynonyms(o)}|`+`${this.processSynonyms(a)})`+r))}return t}processSynonyms(t){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(t=this.setupIgnoreJoinersRegExp(t)),t}setupWildcardsRegExp(t){return(t=t.replace(/(?:\\)*\?/g,t=>"\\"===t.charAt(0)?"?":"")).replace(/(?:\\)*\*/g,t=>"\\"===t.charAt(0)?"*":"")}createWildcardsRegExp(t){let e="withSpaces"===this.opt.wildcards;return t.replace(/\u0001/g,e?"[\\S\\s]?":"\\S?").replace(/\u0002/g,e?"[\\S\\s]*?":"\\S*")}setupIgnoreJoinersRegExp(t){return t.replace(/[^(|)\\]/g,(t,e,s)=>{let r=s.charAt(e+1);return/[(|)\\]/.test(r)||""===r?t:t+"\0"})}createJoinersRegExp(t){let e=[];const s=this.opt.ignorePunctuation;return Array.isArray(s)&&s.length&&e.push(this.escapeStr(s.join(""))),this.opt.ignoreJoiners&&e.push("\\u00ad\\u200b\\u200c\\u200d"),e.length?t.split(/\u0000+/).join(`[${e.join("")}]*`):t}createDiacriticsRegExp(t){const e=this.opt.caseSensitive?"":"i",s=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"];let r=[];return t.split("").forEach(i=>{s.every(s=>{if(-1!==s.indexOf(i)){if(r.indexOf(s)>-1)return!1;t=t.replace(new RegExp(`[${s}]`,`gm${e}`),`[${s}]`),r.push(s)}return!0})}),t}createMergedBlanksRegExp(t){return t.replace(/[\s]+/gim,"[\\s]+")}createAccuracyRegExp(t){let e=this.opt.accuracy,s="string"==typeof e?e:e.value,r="string"==typeof e?[]:e.limiters,i="";switch(r.forEach(t=>{i+=`|${this.escapeStr(t)}`}),s){case"partially":default:return`()(${t})`;case"complementary":return`()([^${i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿"))}]*${t}[^${i}]*)`;case"exactly":return`(^|\\s${i})(${t})(?=$|\\s${i})`}}}const r=(t,e)=>{for(let s in e)t.setAttribute(s,e[s])};class i{constructor(t){this.ctx=t,this.ie=!1;const e=window.navigator.userAgent;(e.indexOf("MSIE")>-1||e.indexOf("Trident")>-1)&&(this.ie=!0)}set opt(t){this._opt=Object.assign({},{element:"",className:"",exclude:[],iframes:!1,iframesTimeout:5e3,separateWordSearch:!0,acrossElements:!1,ignoreGroups:0,each:()=>{},noMatch:()=>{},filter:()=>!0,done:()=>{},debug:!1,log:window.console},t)}get opt(){return this._opt}get iterator(){return new e(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(t,e="debug"){const s=this.opt.log;this.opt.debug&&"object"==typeof s&&"function"==typeof s[e]&&s[e](`mark.js: ${t}`)}getSeparatedKeywords(t){let e=[];return t.forEach(t=>{this.opt.separateWordSearch?t.split(" ").forEach(t=>{t.trim()&&-1===e.indexOf(t)&&e.push(t)}):t.trim()&&-1===e.indexOf(t)&&e.push(t)}),{keywords:e.sort((t,e)=>e.length-t.length),length:e.length}}isNumeric(t){return Number(parseFloat(t))==t}checkRanges(t){if(!Array.isArray(t)||"[object Object]"!==Object.prototype.toString.call(t[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(t),[];const e=[];let s=0;return t.sort((t,e)=>t.start-e.start).forEach(t=>{let{start:r,end:i,valid:n}=this.callNoMatchOnInvalidRanges(t,s);n&&(t.start=r,t.length=i-r,e.push(t),s=i)}),e}callNoMatchOnInvalidRanges(t,e){let s,r,i=!1;return t&&void 0!==t.start?(r=(s=parseInt(t.start,10))+parseInt(t.length,10),this.isNumeric(t.start)&&this.isNumeric(t.length)&&r-e>0&&r-s>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+`${JSON.stringify(t)}`),this.opt.noMatch(t))):(this.log(`Ignoring invalid range: ${JSON.stringify(t)}`),this.opt.noMatch(t)),{start:s,end:r,valid:i}}checkWhitespaceRanges(t,e,s){let r,i=!0,n=s.length,o=e-n,a=parseInt(t.start,10)-o;return(r=(a=a>n?n:a)+parseInt(t.length,10))>n&&(r=n,this.log(`End range automatically set to the max value of ${n}`)),a<0||r-a<0||a>n||r>n?(i=!1,this.log(`Invalid range: ${JSON.stringify(t)}`),this.opt.noMatch(t)):""===s.substring(a,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(t)),this.opt.noMatch(t)),{start:a,end:r,valid:i}}getTextNodes(t){let e="",s=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,t=>{s.push({start:e.length,end:(e+=t.textContent).length,node:t})},t=>this.matchesExclude(t.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT,()=>{t({value:e,nodes:s})})}matchesExclude(t){return e.matches(t,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapInHtmlTag(t,e,s){const r=this.opt.element?this.opt.element:"mark",i=t.splitText(e),n=i.splitText(s-e);let o=document.createElement(r);return o.setAttribute("data-markjs","true"),this.opt.className&&o.setAttribute("class",this.opt.className),o.textContent=i.textContent,i.parentNode.replaceChild(o,i),n}getTextNodeOffset(t,e){return t?this.getTextNodeOffset(t.previousSibling,t.length+e):e}addSvgRectangle(t,e,s){const i=t.parentNode,n=t.parentNode.parentNode,o=t.parentNode.parentNode.parentNode,a=i.getAttribute("x").split(" "),h=this.getTextNodeOffset(t.previousSibling,0),c=e+h,l=s+h,d=document.createElementNS("http://www.w3.org/2000/svg","rect");r(d,{x:`${a[c]}px`,y:`${i.getAttribute("y")-parseInt(i.getAttribute("font-size"))}`,width:`${i.getEndPositionOfChar(l-1).x-parseFloat(a[c])}px`,height:i.getAttribute("font-size"),fill:"yellow",transform:n.getAttribute("transform"),"data-markjs":"true"}),o.insertBefore(d,n);const p=i.cloneNode(!0);return r(p,{x:a.slice(c,l).join(" "),fill:"black","data-markjs":"true"}),p.textContent=i.textContent.slice(c,l),n.insertBefore(p,i.nextSibling),t.splitText(e).splitText(s-e)}highlightRangeInTextNode(t,e,s){return"svg:tspan"===t.parentNode.nodeName?this.addSvgRectangle(t,e,s):this.wrapInHtmlTag(t,e,s)}highlightRangeInMappedTextNode(t,e,s,r,i){t.nodes.every((n,o)=>{const a=t.nodes[o+1];if(void 0===a||a.start>e){if(!r(n.node))return!1;const a=e-n.start,h=(s>n.end?n.end:s)-n.start,c=t.value.substr(0,n.start),l=t.value.substr(h+n.start);if(n.node=this.highlightRangeInTextNode(n.node,a,h),t.value=c+l,t.nodes.forEach((e,s)=>{s>=o&&(t.nodes[s].start>0&&s!==o&&(t.nodes[s].start-=h),t.nodes[s].end-=h)}),s-=h,i(n.node.previousSibling,n.start),!(s>n.end))return!1;e=n.end}return!0})}highlightGroups(t,e,s,r){return r((t=this.highlightRangeInTextNode(t,e,e+s)).previousSibling),t}separateGroups(t,e,s,r,i){let n=e.length;for(let s=1;s<n;s++){let n=t.textContent.indexOf(e[s]);e[s]&&n>-1&&r(e[s],t)&&(t=this.highlightGroups(t,n,e[s].length,i))}return t}highlightMatches(t,e,s,r,i){const n=0===e?0:e+1;this.getTextNodes(e=>{e.nodes.forEach(e=>{let i;for(e=e.node;null!==(i=t.exec(e.textContent))&&""!==i[n];){if(this.opt.separateGroups)e=this.separateGroups(e,i,n,s,r);else{if(!s(i[n],e))continue;let t=i.index;if(0!==n)for(let e=1;e<n;e++)t+=i[e].length;e=this.highlightGroups(e,t,i[n].length,r)}t.lastIndex=0}}),i()})}highlightMatchesAcrossElements(t,e,s,r,i){const n=0===e?0:e+1;this.getTextNodes(e=>{let o;for(;null!==(o=t.exec(e.value))&&""!==o[n];){let i=o.index;if(0!==n)for(let t=1;t<n;t++)i+=o[t].length;const a=i+o[n].length;this.highlightRangeInMappedTextNode(e,i,a,t=>s(o[n],t),(e,s)=>{t.lastIndex=s,r(e)})}i()})}highlightRangeFromIndex(t,e,s,r){this.getTextNodes(i=>{const n=i.value.length;t.forEach((t,r)=>{let{start:o,end:a,valid:h}=this.checkWhitespaceRanges(t,n,i.value);h&&this.highlightRangeInMappedTextNode(i,o,a,s=>e(s,t,i.value.substring(o,a),r),e=>{s(e,t)})}),r()})}unhighlightMatches(t){const e=t.parentNode;let s=document.createDocumentFragment();for(;t.firstChild;)"svg:text"===e.nodeName?t.removeChild(t.firstChild):s.appendChild(t.removeChild(t.firstChild));e.replaceChild(s,t),this.ie?this.normalizeTextNode(e):e.normalize()}normalizeTextNode(t){if(t){if(3===t.nodeType)for(;t.nextSibling&&3===t.nextSibling.nodeType;)t.nodeValue+=t.nextSibling.nodeValue,t.parentNode.removeChild(t.nextSibling);else this.normalizeTextNode(t.firstChild);this.normalizeTextNode(t.nextSibling)}}markRegExp(t,e){this.opt=e,this.log(`Searching with expression "${t}"`);let s=0,r="highlightMatches";this.opt.acrossElements&&(r="highlightMatchesAcrossElements"),this[r](t,this.opt.ignoreGroups,(t,e)=>this.opt.filter(e,t,s),t=>{s++,this.opt.each(t)},()=>{0===s&&this.opt.noMatch(t),this.opt.done(s)})}mark(t,e){this.opt=e;let r=0,i="highlightMatches";const{keywords:n,length:o}=this.getSeparatedKeywords("string"==typeof t?[t]:t),a=t=>{const e=new s(this.opt).create(t);let h=0;this.log(`Searching with expression "${e}"`),this[i](e,1,(e,s)=>this.opt.filter(s,t,r,h),t=>{h++,r++,this.opt.each(t)},()=>{0===h&&this.opt.noMatch(t),n[o-1]===t?this.opt.done(r):a(n[n.indexOf(t)+1])})};this.opt.acrossElements&&(i="highlightMatchesAcrossElements"),0===o?this.opt.done(r):a(n[0])}markRanges(t,e){this.opt=e;let s=0,r=this.checkRanges(t);r&&r.length?(this.log("Starting to mark with the following ranges: "+JSON.stringify(r)),this.highlightRangeFromIndex(r,(t,e,s,r)=>this.opt.filter(t,e,s,r),(t,e)=>{s++,this.opt.each(t,e)},()=>{this.opt.done(s)})):this.opt.done(s)}unmark(t){this.opt=t;let s=this.opt.element?this.opt.element:"*";s+="[data-markjs]",this.opt.className&&(s+=`.${this.opt.className}`),this.log(`Removal selector "${s}"`),this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,t=>{this.unhighlightMatches(t)},t=>{const r=e.matches(t,s),i=this.matchesExclude(t);return!r||i?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},this.opt.done)}}return t.fn.mark=function(t,e){return new i(this.get()).mark(t,e),this},t.fn.markRegExp=function(t,e){return new i(this.get()).markRegExp(t,e),this},t.fn.markRanges=function(t,e){return new i(this.get()).markRanges(t,e),this},t.fn.unmark=function(t){return new i(this.get()).unmark(t),this},t});