From 4df34b28e52c8aec8dd9eb3c886cd0257805077d Mon Sep 17 00:00:00 2001 From: j Date: Mon, 10 Jun 2024 14:05:17 +0100 Subject: [PATCH] add highlight annotations --- static/css/oml.css | 4 + static/js/annotation.js | 11 +- static/js/viewer.js | 4 +- static/reader/pdf.css | 62 ++++ static/reader/pdf.js | 308 ++++++++++++++++++-- static/reader/pdf/toolbarButton-crop.png | Bin 0 -> 5587 bytes static/reader/pdf/toolbarButton-crop.svg | 4 + static/reader/pdf/toolbarButton-crop@10.png | Bin 0 -> 10905 bytes static/reader/pdf/toolbarButton-crop@2x.png | Bin 0 -> 5850 bytes 9 files changed, 359 insertions(+), 34 deletions(-) create mode 100644 static/reader/pdf.css create mode 100644 static/reader/pdf/toolbarButton-crop.png create mode 100644 static/reader/pdf/toolbarButton-crop.svg create mode 100644 static/reader/pdf/toolbarButton-crop@10.png create mode 100644 static/reader/pdf/toolbarButton-crop@2x.png diff --git a/static/css/oml.css b/static/css/oml.css index 375341b..dabc0a9 100644 --- a/static/css/oml.css +++ b/static/css/oml.css @@ -10,6 +10,10 @@ font-size: 14px; line-height: 21px; } +.OMLQuote img { + max-width: 100%; + margin: auto; +} .OMLAnnotation .OMLQuoteBackground { position: absolute; diff --git a/static/js/annotation.js b/static/js/annotation.js index 6df2090..d82a584 100644 --- a/static/js/annotation.js +++ b/static/js/annotation.js @@ -1,9 +1,18 @@ 'use strict'; +oml.SELECTION = 0 +oml.HIGHLIGHT = 1 + oml.ui.annotation = function(annotation, $iframe) { + var value = Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '
') + if (annotation.type == oml.HIGHLIGHT) { + let coord = annotation.coords[0].map(p => parseInt(p)).join(',') + let image = `/${oml.user.ui.item}/2048p${parseInt(annotation.page)},${coord}.jpg` + value = `` + } var $quoteText = Ox.Element() .addClass('OxSelectable OMLQuote') - .html(Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '
')) + .html(value) .on({ click: function(event) { var id diff --git a/static/js/viewer.js b/static/js/viewer.js index d621087..89bc798 100644 --- a/static/js/viewer.js +++ b/static/js/viewer.js @@ -151,7 +151,7 @@ oml.ui.viewer = function() { height: '100%', border: 0 }).onMessage(function(data, event) { - console.log('got', event, data) + // console.log('got', event, data, data.page) if (event == 'addAnnotation') { addAnnotation(data); var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents) @@ -217,7 +217,7 @@ oml.ui.viewer = function() { } var map = {} map[sortKey] = function(value) { - return value.toString(); + return value ? value.toString() : ''; } annotations = Ox.sortBy(annotations, sortKey, map) oml.$ui.annotationFolder.empty(); diff --git a/static/reader/pdf.css b/static/reader/pdf.css new file mode 100644 index 0000000..467b305 --- /dev/null +++ b/static/reader/pdf.css @@ -0,0 +1,62 @@ + +.toolbarButton.cropFile::before, + .secondaryToolbarButton.cropFile::before { + mask-image: url(pdf/toolbarButton-crop.png); +} +.toolbarButton.embedPage::before, + .secondaryToolbarButton.embedPage::before { + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGxpbmUgeDE9Ijg4IiB5MT0iNTYiIHgyPSIyNCIgeTI9IjEyOCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48bGluZSB4MT0iMjQiIHkxPSIxMjgiIHgyPSI4OCIgeTI9IjIwMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48bGluZSB4MT0iMTY4IiB5MT0iNTYiIHgyPSIyMzIiIHkyPSIxMjgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjQ4Ii8+PGxpbmUgeDE9IjIzMiIgeTE9IjEyOCIgeDI9IjE2OCIgeTI9IjIwMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48L3N2Zz48IS0teyJjb2xvciI6ImRlZmF1bHQiLCJuYW1lIjoic3ltYm9sRW1iZWQiLCJ0aGVtZSI6Im94bWVkaXVtIn0tLT4=); +} +@media screen and (min-resolution: 2dppx) { + .toolbarButton.cropFile::before, + .secondaryToolbarButton.cropFile::before { + mask-image: url(pdf/toolbarButton-crop@2x.png); + } + .toolbarButton.embedPage::before, + .secondaryToolbarButton.embedPage::before { + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGxpbmUgeDE9Ijg4IiB5MT0iNTYiIHgyPSIyNCIgeTI9IjEyOCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48bGluZSB4MT0iMjQiIHkxPSIxMjgiIHgyPSI4OCIgeTI9IjIwMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48bGluZSB4MT0iMTY4IiB5MT0iNTYiIHgyPSIyMzIiIHkyPSIxMjgiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjQ4Ii8+PGxpbmUgeDE9IjIzMiIgeTE9IjEyOCIgeDI9IjE2OCIgeTI9IjIwMCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iNDgiLz48L3N2Zz48IS0teyJjb2xvciI6ImRlZmF1bHQiLCJuYW1lIjoic3ltYm9sRW1iZWQiLCJ0aGVtZSI6Im94bWVkaXVtIn0tLT4=); + } +} + +.verticalToolbarSeparator.hiddenMediumView, +#print, +#secondaryPrint, +#openFile, +#secondaryOpenFile, +#editorModeButtons { + display: none !important; +} + +.page .crop-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + //background: rgba(0,0,0,0.5); + cursor: crosshair; + z-index: 100; +} +.page .crop-overlay.inactive { + pointer-events: none; + cursor: default; +} + +.page .crop-overlay canvas { + width: 100%; + height: 100%; +} +.page .highlights { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + //background: rgba(0,0,0,0.5); + z-index: 101; + pointer-events: none; +} +.page .highlights canvas { + width: 100%; + height: 100%; +} diff --git a/static/reader/pdf.js b/static/reader/pdf.js index 0a0b50b..8ac01eb 100644 --- a/static/reader/pdf.js +++ b/static/reader/pdf.js @@ -1,6 +1,97 @@ var id = document.location.pathname.split('/')[1]; var annotations = []; var currentPage = 1, rendered = false +var highlightInactive = true +var selectedAnnotation + +const SELECTION = 0 +const HIGHLIGHT = 1 + + +var div = document.createElement("div") +div.innerHTML = ` + +` +var cropFile = div.querySelector("#cropFile") + +document.querySelector('#toolbarViewerRight').insertBefore(cropFile, document.querySelector('#toolbarViewerRight').firstChild) + +// secondary menu +div.innerHTML = ` + +` +var secondaryCropFile = div.querySelector("#secondaryCropFile") +document.querySelector('#secondaryToolbarButtonContainer').insertBefore( + secondaryCropFile, + document.querySelector('#secondaryToolbarButtonContainer').firstChild +) + +function initOverlay() { + document.querySelectorAll('#cropFile,.secondaryToolbarButton.cropFile').forEach(btn => { + btn.addEventListener('click', event=> { + if (highlightInactive) { + event.target.style.background = 'red' + highlightInactive = false + document.querySelectorAll('.crop-overlay.inactive').forEach(element => { + element.classList.remove('inactive') + }) + } else { + event.target.style.background = '' + highlightInactive = true + document.querySelectorAll('.crop-overlay').forEach(element => { + element.classList.add('inactive') + }) + } + }) + }) + PDFViewerApplication.initializedPromise.then(function() { + PDFViewerApplication.pdfViewer.eventBus.on("pagesinit", function(event) { + /* + document.querySelector('#viewerContainer').addEventListener('scroll', event => { + if (window.parent && window.parent.postMessage) { + if (first) { + first = false + } else { + window.parent.postMessage({event: 'scrolled', top: event.target.scrollTop}) + } + } + }) + */ + }) + PDFViewerApplication.pdfViewer.eventBus.on("pagerender", function(event) { + var page = event.pageNumber.toString() + var div = event.source.div + var overlay = document.createElement('div') + overlay.classList.add('crop-overlay') + overlay.id = 'overlay' + page + if (highlightInactive) { + overlay.classList.add('inactive') + } + div.appendChild(overlay) + + renderHighlightSelectionOverlay(overlay, id, page, event.source) + var highlights = document.createElement('div') + highlights.classList.add('highlights') + highlights.id = 'highlights' + page + var canvas = document.createElement('canvas') + highlights.appendChild(canvas) + div.appendChild(highlights) + renderHighlights(page) + }) + PDFViewerApplication.eventBus.on('pagerendered', function(event) { + loadAnnotations(event.pageNumber) + }) + }) +} + +document.addEventListener('DOMContentLoaded', function() { + window.PDFViewerApplication ? initOverlay() : document.addEventListener("webviewerloaded", initOverlay) +}) + Ox.load({ 'UI': { @@ -25,7 +116,15 @@ Ox.load({ document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64; } }, delay) + var oldSelection = selectedAnnotation + selectedAnnotation = data.id selectAnnotation(data.id) + if (oldSelection) { + var old = annotations.filter(function(a) { return a.id == oldSelection })[0] + if (old && old.type == HIGHLIGHT) { + renderHighlights(old.page) + } + } } else if (event == 'addAnnotation') { createAnnotation() } else if (event == 'addAnnotations') { @@ -37,7 +136,11 @@ Ox.load({ } data.annotations.forEach(function(annotation) { annotations.push(annotation) - renderAnnotation(annotation) + if (annotation.type == HIGHLIGHT) { + renderHighlights(annotation.page) + } else { + renderAnnotation(annotation) + } }) } else if (event == 'removeAnnotation') { removeAnnotation(data.id) @@ -75,18 +178,6 @@ window.addEventListener('mouseup', function(event) { } }) -function bindEvents() { - if (!window.PDFViewerApplication || !window.PDFViewerApplication.eventBus) { - setTimeout(bindEvents, 10) - return - } - PDFViewerApplication.eventBus.on('pagerendered', function(event) { - loadAnnotations(event.pageNumber) - }) -} - -bindEvents() - function getHighlight() { var pageNumber = PDFViewerApplication.pdfViewer.currentPageNumber; var pageIndex = pageNumber - 1; @@ -102,6 +193,7 @@ function getHighlight() { var text = selection.toString(); var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]); return { + type: SELECTION, page: pageNumber, pageLabel: PDFViewerApplication.pdfViewer.currentPageLabel, position: position, @@ -134,25 +226,32 @@ function renderAnnotation(annotation) { } var pageElement = page.canvas.parentElement.parentElement; var viewport = page.viewport; - annotation.coords.forEach(function (rect) { - var bounds = viewport.convertToViewportRectangle(rect); - var el = document.createElement('div'); - el.classList.add('oml-annotation') - el.classList.add('a' + annotation.id) - el.classList.add('page' + annotation.page) - el.dataset.id = annotation.id - el.setAttribute('style', 'position: absolute; background-color: yellow;opacity:0.3;' + - 'left:' + Math.min(bounds[0], bounds[2]) + 'px; top:' + Math.min(bounds[1], bounds[3]) + 'px;' + - 'width:' + Math.abs(bounds[0] - bounds[2]) + 'px; height:' + Math.abs(bounds[1] - bounds[3]) + 'px;'); + if (annotation.type == HIGHLIGHT) { + renderHighlights(annotation.page) + } else if (annotation.coords) { + pageElement.querySelectorAll('.oml-annotation').forEach(el => el.remove()) + annotation.coords.forEach(function (rect) { + var bounds = viewport.convertToViewportRectangle(rect); + var el = document.createElement('div'); + el.classList.add('oml-annotation') + el.classList.add('a' + annotation.id) + el.classList.add('page' + annotation.page) + el.dataset.id = annotation.id + el.setAttribute('style', 'position: absolute; background-color: yellow;opacity:0.3;' + + 'left:' + Math.min(bounds[0], bounds[2]) + 'px; top:' + Math.min(bounds[1], bounds[3]) + 'px;' + + 'width:' + Math.abs(bounds[0] - bounds[2]) + 'px; height:' + Math.abs(bounds[1] - bounds[3]) + 'px;'); - el.addEventListener('click', function() { - if (!el.classList.contains('selected')) { - selectAnnotation(annotation.id) - Ox.$parent.postMessage('selectAnnotation', {id: annotation.id}) - } + el.addEventListener('click', function() { + if (!el.classList.contains('selected')) { + selectAnnotation(annotation.id) + Ox.$parent.postMessage('selectAnnotation', {id: annotation.id}) + } + }); + pageElement.appendChild(el); }); - pageElement.appendChild(el); - }); + } else { + // console.log("annotation without position", annotation) + } } function addAnnotation(annotation) { @@ -169,6 +268,11 @@ function selectAnnotation(id) { g.classList.add('selected') g.style.backgroundColor = 'blue' }) + annotations.forEach(a => { + if (a.id == id && a.type == HIGHLIGHT) { + renderHighlights(a.page) + } + }) } function deselectAnnotation(id) { @@ -207,7 +311,7 @@ function loadAnnotations(page) { e.remove() }) annotations.filter(function(a) { - return a.page == page + return a.page == page && !a.type == HIGHLIGHT }).forEach(function(annot) { renderAnnotation(annot) }) @@ -220,3 +324,145 @@ function isInView(element) { var elementBottom = elementTop + $(element).height(); return elementTop < docViewBottom && elementBottom > docViewTop; } + +function renderHighlightSelectionOverlay(root, documentId, page, source) { + var canvas = document.createElement('canvas') + root.appendChild(canvas) + canvas.width = canvas.clientWidth + canvas.height = canvas.clientHeight + var ctx = canvas.getContext('2d'); + var viewerContainer = document.querySelector('#viewerContainer') + var bounds = root.getBoundingClientRect(); + var base = 2048 + var scale = Math.max(bounds.height, bounds.width) / base + var last_mousex = last_mousey = 0; + var mousex = mousey = 0; + var mousedown = false; + var p = { + top: 0, + left: 0, + bottom: 0, + right: 0 + } + var inside = false + + canvas.addEventListener('mousedown', function(e) { + if (inside) { + const coords = [ + [p.left, p.top, p.right, p.bottom] + ] + addAnnotation({ + type: HIGHLIGHT, + id: Ox.SHA1(pageNumber.toString() + JSON.stringify(p)), + text: "", + page: parseInt(page), + pageLabel: source.pageLabel, + coords: coords, + }) + return + } + let bounds = root.getBoundingClientRect(); + last_mousex = e.clientX - bounds.left; + last_mousey = e.clientY - bounds.top; + p.top = parseInt(last_mousey / scale) + p.left = parseInt(last_mousex / scale) + mousedown = true; + }); + + document.addEventListener('mouseup', function(e) { + if (mousedown) { + mousedown = false; + p.bottom = parseInt(mousey / scale) + p.right = parseInt(mousex / scale) + + if (p.top > p.bottom) { + var t = p.top + p.top = p.bottom + p.bottom = t + } + if (p.left > p.right) { + var t = p.left + p.left = p.right + p.right = t + } + /* + var url = `${baseUrl}/documents/${documentId}/2048p${page},${p.left},${p.top},${p.right},${p.bottom}.jpg` + info.url = `${baseUrl}/document/${documentId}/${page}` + info.page = page + if (p.left != p.right && p.top != p.bottom) { + var context = formatOutput(info, url) + copyToClipboard(context) + addToRecent({ + document: documentId, + page: parseInt(page), + title: info.title, + type: 'fragment', + link: `${baseUrl}/documents/${documentId}/${page}`, + src: url + }) + } + */ + } + }); + + canvas.addEventListener('mousemove', function(e) { + let bounds = root.getBoundingClientRect(); + mousex = e.clientX - bounds.left; + mousey = e.clientY - bounds.top; + + if(mousedown) { + ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight) + ctx.beginPath() + var width = mousex - last_mousex + var height = mousey - last_mousey + ctx.rect(last_mousex, last_mousey, width, height) + ctx.strokeStyle = 'black'; + ctx.lineWidth = 2; + ctx.stroke(); + } else { + let py = parseInt(mousey / scale) + let px = parseInt(mousex / scale) + if (py > p.top && py < p.bottom && px > p.left && px < p.right) { + inside = true + canvas.style.cursor = 'pointer' + canvas.title = 'Click to add highlight' + } else { + inside = false + canvas.style.cursor = '' + canvas.title = '' + } + } + }); +} +function renderHighlights(page) { + var pageAnnotations = annotations.filter(annotation => { + return annotation.type == HIGHLIGHT && (!page || (annotation.page == page)) + }) + pageAnnotations.forEach(annotation => { + let page = annotation.page + var canvas = document.querySelector(`#highlights${page} canvas`) + if (canvas) { + canvas.width = canvas.clientWidth + canvas.height = canvas.clientHeight + var ctx = canvas.getContext('2d'); + var viewerContainer = document.querySelector('#viewerContainer') + var bounds = canvas.parentElement.getBoundingClientRect(); + var base = 2048 + var scale = Math.max(bounds.height, bounds.width) / base + ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight) + pageAnnotations.forEach(annotation => { + ctx.beginPath() + ctx.strokeStyle = annotation.id == selectedAnnotation ? 'blue' : 'yellow'; + ctx.lineWidth = 2; + annotation.coords.forEach(coord => { + const width = coord[2] - coord[0], + height = coord[3] - coord[1]; + ctx.rect(coord[0] * scale, coord[1] * scale, width * scale, height * scale) + }) + ctx.stroke(); + }) + + } + }) + +} diff --git a/static/reader/pdf/toolbarButton-crop.png b/static/reader/pdf/toolbarButton-crop.png new file mode 100644 index 0000000000000000000000000000000000000000..00c56d5254ca262922d54dd3da34b95047c07bf1 GIT binary patch literal 5587 zcmeHKX;f3!7EY>y1`$PQ6>Np1q4^^33CeI?B9;Ckn!AETUJRrpXfENEWykuv@-zpQJ#cc6K} z;sAbG%B4}aU7SB;S(S}AmAbx_1|>{)$vE?}cy;$)`ry`B!~K;}w{sgW?6Q31=xY1v z;bk|tTW`j|*t1=SaI&LI`}ah9$qus~1-iu7@HZx1O|Wa*jhm1z zvxy7zuC|ck31TO;v+p?jRJxs8=B*(^Pc16#$k`r<8}~2R@j8E-`f{iH$uEYCj7{f% zDafg5y!n~mOMcO*9rsU6ZBOgCus39ys=0YLJdlRXbc-9)bl&h#dvb;0qgZib=7Qt4AI<+E$LfcoLoGqB<;89#t&NfH zXV*F`+&4G8X}v>O$>H1c;>)ohvaWFMmIp`Lchr~io}`94Rfzr!%P*QSm6cz3vQ8gb zTSR-{9zSld#8NN?ZH&m^CZlV>lJLlu|-%fI>6c%{3wh&gs>90|8UGYze&32O|whqn8!9Nkc8}Gtw+Ls)&Frj%1bte+| zOX*?8(}h!;wst>X&?#ASW>RcS#+f^lyQ43hIDVr5E9zRl>#23O?w0uM!#e@~=Zcnd z>I*D09&|mN`AJ&mAMDG2W?h+jEoN}{0p3$&-L)r@%nRB*2a7!T-EJGNJ!>#*+HkIA ziRfP0jh@_HoZ7q|MOj)-Ze0H50>#EH8|J>WWdFY7?%I_v44>RK39;i1%S~f4R)_csV>$@|;+&|t&Y{}Zeoioxt@ke*N z9lpi(bG6Xn$2QxT&TfQw#gsOPyyW!>t> zCCQn1e6~J!>~7&PAFoQx(eP~Tu6@pX>+aMcF7vaxvfDE16^2ZTl^0{7*`o4G zH8U+Ma!N*pjH-_i7Nt&TD{oBBtoXE~{Ab=DR<+4#=of9w)!B8VT%ym4bce>`>KTTl z?RHieH>9g;bxakXvN8sRR8zhrSnqGM>{))j@&IAe=-4n%yr# zj1~rS5rvEbixnc2qL(Q_r^MmBrt6h3l7MQ6A~X(@^GNMSt4Ksl%p*mygpg3_hsI-p zDJnEPWmW`|l7Mi;r0LTLUV1Jdkf9owsFz9QYObC~GVyZ3vvHbABAOtY1Rg0`7)tb0 zs8Ax4!lXcCe?68&Cru*|y;NceH;g}{j{=N%q?sF`!TNkB@p1QpZ_)VE4@sy zm~`dx6^Pd8RKVwvjPY{C3Ir2#O^-A-6N113GAVUl5%*c0(YnGzO* zVMZm8t2MA3L5);^oPq%!wgX9@$oPOVWT3;t$P|AF)BP1itRt57AI`42}Edq=$uNs5`IA`(qa zfD0qN@~hz_RBSo|pkwb65)aGcP|$7rO6}D=_J%Y@#3BaWgAJ2CJUAkd#vBgWlf^;E zEKd)b1Y#i!nxr>Z`q|YAiAD#jP@g!E1K^xNX_?NM=xWM7x7TCQ#iJlcCPQ>C1d)1= zm*_<`Hq=+!^D?#^p^$4XfS0i)aRtWA3)d=@QVdnSF3hV$`7by#`@W?7J9BebueF~- znGA|HUK65|zt#O4fV~XC7=p^xinoPs4(XM}?A`$}_s)U)3|x=Y{`;{nEsT}(4}SX6 z?H}|2sNXwzCwA?Fl2cLf( zyn9=Sf&%!aw{YAaen*#pr(w##IcglvYP9i-$89r=0h5**fzaQwm0)9U{ef-4N)DK+ z67YQ@JWp@WDYrfj+3^cz?+FdB9vm95dF+t278m#9rQYYbB_$ + + + diff --git a/static/reader/pdf/toolbarButton-crop@10.png b/static/reader/pdf/toolbarButton-crop@10.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc05e5d95e4ce5d746fc2c36b98c8aaae4d0786 GIT binary patch literal 10905 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 literal 0 HcmV?d00001 diff --git a/static/reader/pdf/toolbarButton-crop@2x.png b/static/reader/pdf/toolbarButton-crop@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..950d275800387b69f7d80d933b66ef685d98b567 GIT binary patch literal 5850 zcmeHLX;f3!77h;;4WLk&MGbK%h?x^Ilz z6ZjNFv_Mf25D^qa860q^RZuCSlxh_LLF>B-sQBu$&$V9v$yy=zob&B(@AI9#_u;b9 zZ-JLF(guk@AdK1G?p*jArTvT@1OHaU#PlE#`gdXigOyxBg_6mo0-*>(DK#<(1*wGs z1Va5&!NNt`YwV3)-*o65yWQQSwrX`>@0i%v7}?I~Co7sZX*g!w1K(r|2cEXhecLv1 zqi3U+p+nO6ee_|0_Id%97yro0@xOmpPtImG(uz z;?6$9icmvxF~fr1Ait8Aqf2Jt>f1Iux<=u0JL~FiJly?C-@&FsbFSuI zCJ>2xbbD%t<=X2ZuX&5tH$0cPlwqf&7ymKOI(1%V$2G{Z?dlJ<5zKjAZa=KQR3bLC zH+4;4DVlM>$}Yyh&n235Gsh?^xb4lkG_S@Gbwu}BeH0^edbYW>`<*P|_tRNWYD>#G zkL;O~Th87>#F!Ts0rv$|ib8*qQMwTuu+O(OY+A{0Td~%wxmghi`pg!mUUx~+18A>j<3CK7t?~5RN?zl* zzGhwj#zqs??R?d}!L}UgzPRx=A5gTwHwRsM3QxMc)XeEOl00KsJqRTqv`Z&G+2BlAM@Vxz zMDTUC`+g?%m4nZUA_q<>>Uy%pqPTH0EFAONQz?fsZvZH^yhgGZ1TQYoU;6+u;$ zJ^l68RugMOofnQVY39m`Z#TJ)d`VU47=9wu|TKpg9 zU+&s)X5Ha}sU<%~A93RgZr{mky5Dlb<1`ZM{n-9RfKmxO3NPPvFQ@F8WkdCR^Vm&I zd*YYH5trCCXZ}(V+a#%oHt$Yx%O+%ZU=|LL6Pto>=3JZ_ z@=wu$)~hRd-AiL)sjXT z=9>n=*1g|e>mxVkl=p|aw}y4@=+{gf+Y{S&%m2z#;x})Mh2MI(R?ez*Syi>~{E;;&#mB}UOGNeSKWOhf*omI8a);TH8nsL{;c5KS zOLtYPnsf0DPizdxcb%?XOxoE{kjy+j)-F+R2}ms7L}$;dQpMe!cDApK*7W@NprR*F zlcJ}Wq26wNh*R$7Tp^s4)weO{==A)WM>XKgrJMURx{_<9St(dUXHtaV>WdnGUg52( zc?QK>@=SeAYXdoloqCG3-bzZFkcx~YvQ5IF7o<}8uWIQTT-G;eePM$tmr*v zhC+sKfB*WBN_Xtq5C|`yy&q{@)MfB?O>0F7a_hupT$-M;lGa!|wz=4l+?Dns{#RPV zL{z^~$YXIriQeV7Ez6{lh0{<)#vvgmDbR#dKkJRzXLS4fr4HT!_GYX4NCSs#n-dI^ zZj_%XX1_WY4iV$wQ!5kk3 zC>3J?zLW=H)nXakmJtYNSG5cPBOxV<2ZaeGO!U*TN;FEyXQCHTICzeX1%(T}W8_dk z%z{8LCK9Cc(XK8?XEg&R5JO4;r51}M3Wl1A*6}jnXYDi&jnYAskxX8jiL?Q-8 zU=$jO5>R6#ifLMkAr5y)0m_9krBEtCX*mI&G)l=tqv3VbFgd&mdZ^*aVF%i=j$WbU zO10R$QbAP`6pPe8@< zsl*XfY>7e%NI*zS1(RciFpq$Q7Z7*^K8D8U126?Z!T@-J07D?rd1RP_N25|lQ25G) za3uhukx^->_%IcbN_OItK^}%EpinVnkOX4r1R{t5C=i*<=krJeCz6gzdkzdYKQ+l={O$pim42C;_c#1SdR^N_QerNJIjaLLh$tErjF>ILWn~ z1U!~Fv~NDh@PruwIN5|^APmCEBw;!e?XfW6&A`F}+S-7bb@T9E7%VviD5dg1sZ_*7 zYhy)eDRm`vo=<__GjZWwr%=i@?7!I5f8d;l($yQbmC7|E{0BfQhDJjTNhH)s zg+l3?00RJrGfvx3-)+xX+j2M@#z+C2wJnLk)@EKnluRZPLh_G=d6y{v1vkQeI4S?id?akh znkAKK;Gzvz`l=+Kb^i(A5QC2pgd_^-=RzL|8Ionhy#vQQGzZ^j@b!p$e?JbVg|>43 z#-HJI`x`yL)L)%^k-lH$`YP8KDey($ui5ogt}jyHi@;yA>;EPf^1~Z8B!U0XQ^D`g zza1aj3%`4h;m!AQ*S&=!Ua`s&;HPmi@5KrP!f>MYqleg)Wd%2HSdT^ z-jkSZ{)b-F>2I+-W#Ek#P)1`Lele=chR!Cx4hT}E#7+kBKkU67(w1weWWGAN>IJtw z-Zb-8+73HvG+2;cT4y-jG^MNEt36|Md6~u5puVg$`qSVpFtiCs(EB;4fBxzNClRKa z6HdRZzZC3#=-*kNA%@v zupLAO>&L84Oc0-^NgDcAr5Y4(?c(Xvif%|&KfjeAd_Hymqul*&j?1emS8aPZDcyd# VUG+E+J^%!Q?Xkf9*xb;>e*@!w#Pt9G literal 0 HcmV?d00001