add highlight annotations
This commit is contained in:
parent
7e1a282ad6
commit
4df34b28e5
9 changed files with 359 additions and 34 deletions
|
@ -10,6 +10,10 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
}
|
}
|
||||||
|
.OMLQuote img {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.OMLAnnotation .OMLQuoteBackground {
|
.OMLAnnotation .OMLQuoteBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
oml.SELECTION = 0
|
||||||
|
oml.HIGHLIGHT = 1
|
||||||
|
|
||||||
oml.ui.annotation = function(annotation, $iframe) {
|
oml.ui.annotation = function(annotation, $iframe) {
|
||||||
|
var value = Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '<br/>')
|
||||||
|
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 = `<img src="${image}">`
|
||||||
|
}
|
||||||
var $quoteText = Ox.Element()
|
var $quoteText = Ox.Element()
|
||||||
.addClass('OxSelectable OMLQuote')
|
.addClass('OxSelectable OMLQuote')
|
||||||
.html(Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '<br/>'))
|
.html(value)
|
||||||
.on({
|
.on({
|
||||||
click: function(event) {
|
click: function(event) {
|
||||||
var id
|
var id
|
||||||
|
|
|
@ -151,7 +151,7 @@ oml.ui.viewer = function() {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
border: 0
|
border: 0
|
||||||
}).onMessage(function(data, event) {
|
}).onMessage(function(data, event) {
|
||||||
console.log('got', event, data)
|
// console.log('got', event, data, data.page)
|
||||||
if (event == 'addAnnotation') {
|
if (event == 'addAnnotation') {
|
||||||
addAnnotation(data);
|
addAnnotation(data);
|
||||||
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents)
|
||||||
|
@ -217,7 +217,7 @@ oml.ui.viewer = function() {
|
||||||
}
|
}
|
||||||
var map = {}
|
var map = {}
|
||||||
map[sortKey] = function(value) {
|
map[sortKey] = function(value) {
|
||||||
return value.toString();
|
return value ? value.toString() : '';
|
||||||
}
|
}
|
||||||
annotations = Ox.sortBy(annotations, sortKey, map)
|
annotations = Ox.sortBy(annotations, sortKey, map)
|
||||||
oml.$ui.annotationFolder.empty();
|
oml.$ui.annotationFolder.empty();
|
||||||
|
|
62
static/reader/pdf.css
Normal file
62
static/reader/pdf.css
Normal file
|
@ -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%;
|
||||||
|
}
|
|
@ -1,6 +1,97 @@
|
||||||
var id = document.location.pathname.split('/')[1];
|
var id = document.location.pathname.split('/')[1];
|
||||||
var annotations = [];
|
var annotations = [];
|
||||||
var currentPage = 1, rendered = false
|
var currentPage = 1, rendered = false
|
||||||
|
var highlightInactive = true
|
||||||
|
var selectedAnnotation
|
||||||
|
|
||||||
|
const SELECTION = 0
|
||||||
|
const HIGHLIGHT = 1
|
||||||
|
|
||||||
|
|
||||||
|
var div = document.createElement("div")
|
||||||
|
div.innerHTML = `
|
||||||
|
<button id="cropFile" class="toolbarButton cropFile hiddenLargeView" title="Highlight" tabindex="30" data-l10n-id="crop_file">
|
||||||
|
<span data-l10n-id="crop_file_label">Highlight</span>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
var cropFile = div.querySelector("#cropFile")
|
||||||
|
|
||||||
|
document.querySelector('#toolbarViewerRight').insertBefore(cropFile, document.querySelector('#toolbarViewerRight').firstChild)
|
||||||
|
|
||||||
|
// secondary menu
|
||||||
|
div.innerHTML = `
|
||||||
|
<button id="secondaryCropFile" class="secondaryToolbarButton visibleMediumView cropFile" title="Highlight" tabindex="50" data-l10n-id="crop">
|
||||||
|
<span data-l10n-id="crop_label">Highlight</span>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
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({
|
Ox.load({
|
||||||
'UI': {
|
'UI': {
|
||||||
|
@ -25,7 +116,15 @@ Ox.load({
|
||||||
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
|
document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64;
|
||||||
}
|
}
|
||||||
}, delay)
|
}, delay)
|
||||||
|
var oldSelection = selectedAnnotation
|
||||||
|
selectedAnnotation = data.id
|
||||||
selectAnnotation(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') {
|
} else if (event == 'addAnnotation') {
|
||||||
createAnnotation()
|
createAnnotation()
|
||||||
} else if (event == 'addAnnotations') {
|
} else if (event == 'addAnnotations') {
|
||||||
|
@ -37,7 +136,11 @@ Ox.load({
|
||||||
}
|
}
|
||||||
data.annotations.forEach(function(annotation) {
|
data.annotations.forEach(function(annotation) {
|
||||||
annotations.push(annotation)
|
annotations.push(annotation)
|
||||||
renderAnnotation(annotation)
|
if (annotation.type == HIGHLIGHT) {
|
||||||
|
renderHighlights(annotation.page)
|
||||||
|
} else {
|
||||||
|
renderAnnotation(annotation)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else if (event == 'removeAnnotation') {
|
} else if (event == 'removeAnnotation') {
|
||||||
removeAnnotation(data.id)
|
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() {
|
function getHighlight() {
|
||||||
var pageNumber = PDFViewerApplication.pdfViewer.currentPageNumber;
|
var pageNumber = PDFViewerApplication.pdfViewer.currentPageNumber;
|
||||||
var pageIndex = pageNumber - 1;
|
var pageIndex = pageNumber - 1;
|
||||||
|
@ -102,6 +193,7 @@ function getHighlight() {
|
||||||
var text = selection.toString();
|
var text = selection.toString();
|
||||||
var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]);
|
var position = [pageNumber].concat(Ox.sort(selected.map(function(c) { return [c[1], c[0]]}))[0]);
|
||||||
return {
|
return {
|
||||||
|
type: SELECTION,
|
||||||
page: pageNumber,
|
page: pageNumber,
|
||||||
pageLabel: PDFViewerApplication.pdfViewer.currentPageLabel,
|
pageLabel: PDFViewerApplication.pdfViewer.currentPageLabel,
|
||||||
position: position,
|
position: position,
|
||||||
|
@ -134,25 +226,32 @@ function renderAnnotation(annotation) {
|
||||||
}
|
}
|
||||||
var pageElement = page.canvas.parentElement.parentElement;
|
var pageElement = page.canvas.parentElement.parentElement;
|
||||||
var viewport = page.viewport;
|
var viewport = page.viewport;
|
||||||
annotation.coords.forEach(function (rect) {
|
if (annotation.type == HIGHLIGHT) {
|
||||||
var bounds = viewport.convertToViewportRectangle(rect);
|
renderHighlights(annotation.page)
|
||||||
var el = document.createElement('div');
|
} else if (annotation.coords) {
|
||||||
el.classList.add('oml-annotation')
|
pageElement.querySelectorAll('.oml-annotation').forEach(el => el.remove())
|
||||||
el.classList.add('a' + annotation.id)
|
annotation.coords.forEach(function (rect) {
|
||||||
el.classList.add('page' + annotation.page)
|
var bounds = viewport.convertToViewportRectangle(rect);
|
||||||
el.dataset.id = annotation.id
|
var el = document.createElement('div');
|
||||||
el.setAttribute('style', 'position: absolute; background-color: yellow;opacity:0.3;' +
|
el.classList.add('oml-annotation')
|
||||||
'left:' + Math.min(bounds[0], bounds[2]) + 'px; top:' + Math.min(bounds[1], bounds[3]) + 'px;' +
|
el.classList.add('a' + annotation.id)
|
||||||
'width:' + Math.abs(bounds[0] - bounds[2]) + 'px; height:' + Math.abs(bounds[1] - bounds[3]) + 'px;');
|
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() {
|
el.addEventListener('click', function() {
|
||||||
if (!el.classList.contains('selected')) {
|
if (!el.classList.contains('selected')) {
|
||||||
selectAnnotation(annotation.id)
|
selectAnnotation(annotation.id)
|
||||||
Ox.$parent.postMessage('selectAnnotation', {id: 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) {
|
function addAnnotation(annotation) {
|
||||||
|
@ -169,6 +268,11 @@ function selectAnnotation(id) {
|
||||||
g.classList.add('selected')
|
g.classList.add('selected')
|
||||||
g.style.backgroundColor = 'blue'
|
g.style.backgroundColor = 'blue'
|
||||||
})
|
})
|
||||||
|
annotations.forEach(a => {
|
||||||
|
if (a.id == id && a.type == HIGHLIGHT) {
|
||||||
|
renderHighlights(a.page)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deselectAnnotation(id) {
|
function deselectAnnotation(id) {
|
||||||
|
@ -207,7 +311,7 @@ function loadAnnotations(page) {
|
||||||
e.remove()
|
e.remove()
|
||||||
})
|
})
|
||||||
annotations.filter(function(a) {
|
annotations.filter(function(a) {
|
||||||
return a.page == page
|
return a.page == page && !a.type == HIGHLIGHT
|
||||||
}).forEach(function(annot) {
|
}).forEach(function(annot) {
|
||||||
renderAnnotation(annot)
|
renderAnnotation(annot)
|
||||||
})
|
})
|
||||||
|
@ -220,3 +324,145 @@ function isInView(element) {
|
||||||
var elementBottom = elementTop + $(element).height();
|
var elementBottom = elementTop + $(element).height();
|
||||||
return elementTop < docViewBottom && elementBottom > docViewTop;
|
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();
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
BIN
static/reader/pdf/toolbarButton-crop.png
Normal file
BIN
static/reader/pdf/toolbarButton-crop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
4
static/reader/pdf/toolbarButton-crop.svg
Normal file
4
static/reader/pdf/toolbarButton-crop.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="54" width="54">
|
||||||
|
<path stroke-linejoin="round" stroke="#333" stroke-linecap="round" stroke-width="5" fill="none" d="m13.2 39 35-34.6m-45 8.1h36.6v38m-27-47v36.6h38"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 243 B |
BIN
static/reader/pdf/toolbarButton-crop@10.png
Normal file
BIN
static/reader/pdf/toolbarButton-crop@10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
static/reader/pdf/toolbarButton-crop@2x.png
Normal file
BIN
static/reader/pdf/toolbarButton-crop@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Loading…
Reference in a new issue