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 0000000..00c56d5
Binary files /dev/null and b/static/reader/pdf/toolbarButton-crop.png differ
diff --git a/static/reader/pdf/toolbarButton-crop.svg b/static/reader/pdf/toolbarButton-crop.svg
new file mode 100644
index 0000000..618195a
--- /dev/null
+++ b/static/reader/pdf/toolbarButton-crop.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/static/reader/pdf/toolbarButton-crop@10.png b/static/reader/pdf/toolbarButton-crop@10.png
new file mode 100644
index 0000000..7cc05e5
Binary files /dev/null and b/static/reader/pdf/toolbarButton-crop@10.png differ
diff --git a/static/reader/pdf/toolbarButton-crop@2x.png b/static/reader/pdf/toolbarButton-crop@2x.png
new file mode 100644
index 0000000..950d275
Binary files /dev/null and b/static/reader/pdf/toolbarButton-crop@2x.png differ