mirror of
https://github.com/24eme/signaturepdf
synced 2026-03-14 13:55:44 +01:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
edd19703ae
5 changed files with 156 additions and 43 deletions
|
|
@ -74,6 +74,10 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
#toolbox i {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
|
||||
font-size: 30px;
|
||||
cursor: move;
|
||||
|
|
@ -113,6 +117,10 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.input-metadata > label {
|
||||
width: 110%;
|
||||
}
|
||||
|
||||
.input-metadata:hover > .delete-metadata {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ async function loadPDF(pdfBlob) {
|
|||
const loadingTask = pdfjsLib.getDocument(url);
|
||||
const pdf = await loadingTask.promise;
|
||||
const metadata = await pdf.getMetadata()
|
||||
const attachments = await pdf.getAttachments();
|
||||
|
||||
for(fieldKey in defaultFields) {
|
||||
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
|
||||
|
|
@ -52,6 +53,40 @@ async function loadPDF(pdfBlob) {
|
|||
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", false);
|
||||
}
|
||||
|
||||
if (attachments) {
|
||||
Object.entries(attachments).forEach(([_key, value]) => {
|
||||
if (value.filename.startsWith('factur-x') === false) {
|
||||
return
|
||||
}
|
||||
const decodedAttachment = new TextDecoder().decode(value.content)
|
||||
const parser = new DOMParser();
|
||||
const xml = parser.parseFromString(decodedAttachment, "application/xml")
|
||||
const error = xml.querySelector('parseerror')
|
||||
if (error) {
|
||||
console.log(error)
|
||||
return
|
||||
}
|
||||
|
||||
const walker = xml.createTreeWalker(xml.firstChild, NodeFilter.SHOW_TEXT)
|
||||
while(walker.nextNode()) {
|
||||
const node = walker.currentNode
|
||||
|
||||
const treeKey = []
|
||||
treeKey.push(node.parentNode.localName)
|
||||
|
||||
let root = node.parentNode
|
||||
while (! (root.parentNode instanceof XMLDocument)) {
|
||||
root = root.parentNode
|
||||
treeKey.push(root.localName) // nodeName si on veut le namespace
|
||||
}
|
||||
|
||||
const newInput = addMetadata(treeKey.join(' « '), node.textContent.trim(), "text", false, true)
|
||||
newInput.dataset.fromAttachment = value.filename
|
||||
newInput.disabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
|
||||
pdf.getPage(pageNumber).then(function(page) {
|
||||
let pageIndex = (page.pageNumber - 1);
|
||||
|
|
@ -130,17 +165,19 @@ async function pageRender(pageIndex) {
|
|||
})
|
||||
}
|
||||
|
||||
function addMetadata(key, value, type, focus) {
|
||||
let input = document.querySelector('.input-metadata input[name="'+key+'"]');
|
||||
function addMetadata(key, value, type, focus, forceCreation = false) {
|
||||
if (! forceCreation) {
|
||||
let input = document.querySelector('.input-metadata input[name="'+key+'"]');
|
||||
|
||||
if(input && !input.value) {
|
||||
input.value = value;
|
||||
}
|
||||
if(input && focus) {
|
||||
input.focus();
|
||||
}
|
||||
if(input) {
|
||||
return;
|
||||
if(input && !input.value) {
|
||||
input.value = value;
|
||||
}
|
||||
if(input && focus) {
|
||||
input.focus();
|
||||
}
|
||||
if(input) {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
let div = document.createElement('div');
|
||||
|
|
@ -168,6 +205,8 @@ function addMetadata(key, value, type, focus) {
|
|||
if(focus) {
|
||||
input.focus();
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
function deleteMetadata(el) {
|
||||
|
|
@ -204,6 +243,10 @@ async function save() {
|
|||
const label = el.querySelector('label').innerText
|
||||
const input = el.querySelector('input').value
|
||||
|
||||
if ('fromAttachment' in el.querySelector('input').dataset) {
|
||||
return;
|
||||
}
|
||||
|
||||
pdf.getInfoDict().set(PDFName.of(label), PDFHexString.fromText(input));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ if(is_mobile()) {
|
|||
|
||||
let nbPDF = 0;
|
||||
let pages = [];
|
||||
let formats = [];
|
||||
let pdfRenderTasks = [];
|
||||
|
||||
async function loadPDF(pdfBlob, filename, pdfIndex) {
|
||||
|
|
@ -47,7 +48,11 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
|
|||
pdf.getPage(pageNumber).then(function(page) {
|
||||
let pageIndex = pdfLetter + "_" + (page.pageNumber - 1);
|
||||
pages[pageIndex] = page;
|
||||
|
||||
const viewportFormat = page.getViewport({ scale: 1 });
|
||||
const widthFormat = Math.round(viewportFormat.width * 25.4 / 72 * 10) / 10;
|
||||
const heightFormat = Math.round(viewportFormat.height * 25.4 / 72 * 10) / 10;
|
||||
formats[pageIndex] = widthFormat + " x " + heightFormat + " mm";
|
||||
let pageTitle = trad['Page'] + ' ' + page.pageNumber + ' - '+formats[pageIndex]+' - ' + filename;
|
||||
let pageHTML = '<div class="position-relative mt-0 ms-1 me-0 mb-1 canvas-container d-flex align-items-center justify-content-center bg-transparent bg-opacity-25 border border-2 border-transparent" id="canvas-container-' + pageIndex +'" draggable="true">';
|
||||
pageHTML += '<canvas class="canvas-pdf shadow-sm"></canvas>';
|
||||
pageHTML += '<div title="' + trad['Select this page'] + '" class="position-absolute top-0 start-50 translate-middle-x p-2 ps-3 pe-3 mt-2 rounded-circle btn-select d-none"><i class="bi bi-check-square"></i></div>';
|
||||
|
|
@ -57,7 +62,7 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
|
|||
pageHTML += '<div title="' + trad['Move here'] + '" class="position-absolute start-50 top-50 translate-middle p-2 ps-4 pe-4 container-resize btn-drag-here d-none"><i class="bi bi-arrows-expand-vertical"></i></div>';
|
||||
pageHTML += '<div title="' + trad['Turn this page'] + '" class="position-absolute top-50 end-0 translate-middle-y p-2 ps-3 pe-3 me-2 rounded-circle container-rotate btn-rotate d-none"><i class="bi bi-arrow-clockwise"></i></div>';
|
||||
pageHTML += '<div title="' + trad['Download this page'] + '" class="position-absolute bottom-0 start-50 translate-middle-x p-2 ps-3 pe-3 mb-3 rounded-circle btn-download d-none"><i class="bi bi-download"></i></div>';
|
||||
pageHTML += '<p class="page-title position-absolute text-center w-100 ps-2 pe-2 pb-0 pt-0 mb-1 bg-white opacity-75 d-none" style="bottom: -4px; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">' + trad['Page'] + ' ' + page.pageNumber + ' - ' + filename + '</p>';
|
||||
pageHTML += '<p class="page-title position-absolute text-center w-100 ps-2 pe-2 pb-0 pt-0 mb-1 bg-white opacity-75 d-none" style="bottom: -4px; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">' + pageTitle + '</p>';
|
||||
pageHTML += '<input form="form_pdf" class="checkbox-page d-none" role="switch" type="checkbox" checked="checked" value="'+pdfLetter+page.pageNumber+'" />';
|
||||
pageHTML += '<input type="hidden" class="input-rotate" value="0" id="input_rotate_'+pageIndex+'" />';
|
||||
pageHTML += '<input type="checkbox" class="input-select d-none" value="'+pdfLetter+page.pageNumber+'" id="input_select_'+pageIndex+'" />';
|
||||
|
|
@ -68,6 +73,7 @@ async function loadPDF(pdfBlob, filename, pdfIndex) {
|
|||
document.getElementById('container-pages').insertAdjacentHTML('beforeend', pageHTML);
|
||||
|
||||
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
|
||||
canvasContainer.title = pageTitle;
|
||||
canvasContainer.addEventListener('click', function(e) {
|
||||
canvasContainer.querySelector('.btn-select').click();
|
||||
});
|
||||
|
|
@ -214,6 +220,7 @@ async function pageRender(pageIndex) {
|
|||
scrollWidth = -4;
|
||||
}
|
||||
let page = pages[pageIndex];
|
||||
|
||||
let rotation = parseInt(document.querySelector('#input_rotate_'+pageIndex).value);
|
||||
let viewport = page.getViewport({scale: 1, rotation: rotation});
|
||||
let sizeWidth = Math.floor((document.getElementById('container-pages').offsetWidth - (8*(nbPagePerLine+1)) - scrollWidth) / nbPagePerLine);
|
||||
|
|
|
|||
|
|
@ -170,6 +170,20 @@ async function loadPDF(pdfBlob) {
|
|||
|
||||
toolBox.init(event.selected[0])
|
||||
});
|
||||
canvasEdition.on("selection:updated", function(event) {
|
||||
toolBox.reset()
|
||||
if (event.selected.length > 1 || event.selected.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
toolBox.init(event.selected[0])
|
||||
});
|
||||
canvasEdition.on("object:modified", function(event) {
|
||||
toolBox.init(event.target)
|
||||
});
|
||||
canvasEdition.on("selection:cleared", function(event) {
|
||||
toolBox.reset()
|
||||
});
|
||||
canvasEditions.push(canvasEdition);
|
||||
});
|
||||
}
|
||||
|
|
@ -1248,30 +1262,40 @@ function storePenColor(color) {
|
|||
}
|
||||
|
||||
const toolBox = (function () {
|
||||
const _coloricon = document.createElement('img')
|
||||
_coloricon.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-droplet-fill" viewBox="0 0 16 16"><path d="M8 16a6 6 0 0 0 6-6c0-1.655-1.122-2.904-2.432-4.362C10.254 4.176 8.75 2.503 8 0c0 0-6 5.686-6 10a6 6 0 0 0 6 6M6.646 4.646l.708.708c-.29.29-1.128 1.311-1.907 2.87l-.894-.448c.82-1.641 1.717-2.753 2.093-3.13"/></svg>'
|
||||
const _coloricon = document.createElement('i')
|
||||
_coloricon.classList.add('bi', 'bi-droplet-fill', 'mx-1')
|
||||
|
||||
function _renderIcon(icon) {
|
||||
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
|
||||
const size = this.cornerSize;
|
||||
ctx.save();
|
||||
ctx.translate(left, top);
|
||||
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
|
||||
ctx.drawImage(icon, -size/2, -size/2, size, size);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
const _trashicon = document.createElement('i')
|
||||
_trashicon.classList.add('bi', 'bi-trash3', 'float-end', 'border-start', 'border-2', 'mx-1', 'ps-1')
|
||||
|
||||
function _changeColor(eventData, transform) {
|
||||
const target = transform.target;
|
||||
const _copyicon = document.createElement('i')
|
||||
_copyicon.classList.add('bi', 'bi-copy', 'mx-1')
|
||||
|
||||
let _elSelected
|
||||
|
||||
const _elToolbox = document.createElement('div')
|
||||
_elToolbox.id = 'toolbox'
|
||||
_elToolbox.classList.add('position-absolute', 'border', 'p-1', 'bg-body-secondary', 'shadow-sm', 'ms-3', 'mt-3', 'd-none', 'd-md-block')
|
||||
_elToolbox.style['z-index'] = 1030
|
||||
_elToolbox.style.width = 'max-content'
|
||||
|
||||
_elToolbox.appendChild(_coloricon)
|
||||
_elToolbox.appendChild(_trashicon)
|
||||
_elToolbox.appendChild(_copyicon)
|
||||
|
||||
_coloricon.addEventListener('click', _changeColor)
|
||||
_trashicon.addEventListener('click', _delete)
|
||||
_copyicon.addEventListener('click', _copy)
|
||||
|
||||
function _changeColor() {
|
||||
const _colorpicker = document.createElement('input')
|
||||
_colorpicker.setAttribute('type', 'color')
|
||||
_colorpicker.value = penColor
|
||||
|
||||
_colorpicker.addEventListener('input', function (e) {
|
||||
target.set({ fill: e.target.value })
|
||||
target.canvas.requestRenderAll()
|
||||
if(target.type != "rect") {
|
||||
_elSelected.set({ fill: e.target.value })
|
||||
_elSelected.canvas.requestRenderAll()
|
||||
if(_elSelected.type != "rect") {
|
||||
storePenColor(e.target.value)
|
||||
}
|
||||
})
|
||||
|
|
@ -1280,22 +1304,53 @@ const toolBox = (function () {
|
|||
_colorpicker.remove()
|
||||
}
|
||||
|
||||
function init(el) {
|
||||
colorControl = new fabric.Control({
|
||||
x: 0.5,
|
||||
y: -0.5,
|
||||
offsetY: -16,
|
||||
offsetX: 16,
|
||||
cursorStyle: 'pointer',
|
||||
mouseUpHandler: _changeColor,
|
||||
render: _renderIcon(_coloricon),
|
||||
cornerSize: 24
|
||||
function _copy() {
|
||||
canvasEditions.forEach(function (canvas) {
|
||||
if (_elSelected.canvas === canvas) {
|
||||
return
|
||||
}
|
||||
|
||||
_elSelected.clone(function (clonedItem) {
|
||||
addObjectInCanvas(canvas, clonedItem)
|
||||
})
|
||||
})
|
||||
|
||||
el.controls.color = colorControl
|
||||
}
|
||||
|
||||
function _delete() {
|
||||
deleteActiveObject()
|
||||
reset()
|
||||
}
|
||||
|
||||
function init(el) {
|
||||
if (_elToolbox) {
|
||||
reset()
|
||||
}
|
||||
|
||||
_elSelected = el
|
||||
const container = document.getElementById('container-pages')
|
||||
container.appendChild(_elToolbox)
|
||||
|
||||
_elToolbox.style.left = (
|
||||
_elSelected.getBoundingRect().left
|
||||
+ _elSelected.getScaledWidth() / 2
|
||||
- _elToolbox.offsetWidth / 2
|
||||
+ +window.getComputedStyle(_elToolbox).getPropertyValue("margin-left").replace('px', '')
|
||||
) + 'px'
|
||||
_elToolbox.style.top = (
|
||||
Math.max(..._elSelected.getCoords().map((c) => c.y)) // on sélectionne le coin le plus bas
|
||||
+ _elSelected.canvas._offset.top // hauteur du canvas dans le viewport
|
||||
+ container.scrollTop // haut du container
|
||||
) + 'px'
|
||||
}
|
||||
|
||||
function reset() {
|
||||
_elSelected = null
|
||||
_elToolbox.remove()
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
init: init,
|
||||
reset: reset
|
||||
}
|
||||
})()
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
<div style="height: 65px;" class="d-md-none"></div>
|
||||
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100" dir="auto">
|
||||
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100 position-relative" dir="auto">
|
||||
</div>
|
||||
<div style="height: 55px;" class="d-md-none"></div>
|
||||
<div class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue