/* PDF Tool — conversion dialogs "Export Images" : renders the open document's pages to JPEG/PNG, including any overlays currently placed (what you see is what you get). "From Images" : builds a brand new PDF out of a list of images via pdf-lib, either onto page-sized "base plates" or pages matching each image. The result can be opened straight in the editor without ever touching the server. */ (function (T) { 'use strict'; var FONT = "'Helvetica Neue', Arial, sans-serif"; /* Page sizes in PDF points (72 per inch) */ var PAGE_PRESETS = { auto: null, a4p: [595.28, 841.89], a4l: [841.89, 595.28], a3p: [841.89, 1190.55], a3l: [1190.55, 841.89], letterp: [612, 792], letterl: [792, 612], legalp: [612, 1008] }; /* Draw the editor's overlays onto an export canvas at its own resolution. */ function drawOverlays(cx, overlays, W, H, imgMap) { (overlays || []).forEach(function (o) { if (o.type === 'text') { var fontPx = o.fontFrac * H; cx.fillStyle = o.color || '#000'; cx.textBaseline = 'top'; cx.font = fontPx + 'px ' + FONT; String(o.t).split('\n').forEach(function (ln, i) { cx.fillText(ln, o.fx * W, o.fy * H + i * fontPx * 1.25); }); } else if (o.type === 'image' && imgMap[o.src]) { cx.drawImage(imgMap[o.src], o.fx * W, o.fy * H, o.fwFrac * W, o.fhFrac * H); } }); } function preloadOverlayImages(overlaysByPage) { var srcs = {}; Object.keys(overlaysByPage || {}).forEach(function (k) { (overlaysByPage[k] || []).forEach(function (o) { if (o.type === 'image') { srcs[o.src] = 1; } }); }); var map = {}; return Promise.all(Object.keys(srcs).map(function (src) { return T.loadImage(src).then(function (img) { map[src] = img; }, function () { /* skip an image that will not load */ }); })).then(function () { return map; }); } /* ── Export pages as images ─────────────────────────────────────── */ T.openExportImages = function (doc) { if (!doc || !doc.pdf) { return; } if (!doc.savePath) { T.toast('Save this document first so the images have somewhere to go.', true); return; } var dlg = T.modal({ title: 'Export Pages as Images', bodyHtml: '
' + 'Format' + '
' + '' + '' + '
' + '
' + '
' + 'JPEG quality: 90%' + '' + '
' + '
' + 'Resolution' + '' + '
' + '

Saved next to the document. Text, images and chops you have placed are included.

' + '
', actions: [ { label: 'Cancel', onClick: function (c) { c.close(); } }, { label: 'Export', primary: true, onClick: run } ] }); var qField = dlg.body.querySelector('#exQField'); var qRange = dlg.body.querySelector('#exQ'); qRange.addEventListener('input', function () { dlg.body.querySelector('#exQVal').textContent = this.value; }); Array.prototype.forEach.call(dlg.body.querySelectorAll('input[name=exFmt]'), function (r) { r.addEventListener('change', function () { qField.style.display = this.value === 'jpg' ? '' : 'none'; }); }); function run(ctx) { var fmt = dlg.body.querySelector('input[name=exFmt]:checked').value; var quality = parseInt(qRange.value, 10) / 100; var scale = parseFloat(dlg.body.querySelector('#exScale').value); var mime = fmt === 'png' ? 'image/png' : 'image/jpeg'; var ext = fmt === 'png' ? '.png' : '.jpg'; var outDir = T.dirOf(doc.savePath); var base = T.basenameNoExt(doc.savePath); var prog = dlg.body.querySelector('#exProg'); ctx.button.disabled = true; prog.innerHTML = '
Preparing…
'; preloadOverlayImages(doc.overlays).then(function (imgMap) { var n = doc.pdf.numPages; function fail(err) { prog.innerHTML = '
Error: ' + T.escHtml(String(err && err.message ? err.message : err)) + '
'; ctx.button.disabled = false; } function next(i) { if (i > n) { prog.innerHTML = '
' + n + ' image' + (n !== 1 ? 's' : '') + ' saved to ' + T.escHtml(outDir) + '
'; ctx.button.disabled = false; return; } prog.innerHTML = '
Rendering page ' + i + ' of ' + n + '…
'; doc.pdf.getPage(i).then(function (page) { var vp = page.getViewport({ scale: scale }); var c = document.createElement('canvas'); c.width = vp.width; c.height = vp.height; var cx = c.getContext('2d'); if (fmt !== 'png') { cx.fillStyle = '#FFF'; cx.fillRect(0, 0, c.width, c.height); } page.render({ canvasContext: cx, viewport: vp }).promise.then(function () { drawOverlays(cx, doc.overlays[i], c.width, c.height, imgMap); c.toBlob(function (blob) { T.uploadBlob(blob, base + '_page' + i + ext, outDir) .then(function () { next(i + 1); }, fail); }, mime, quality); }, fail); }, fail); } next(1); }); } }; /* ── Build a PDF from images ────────────────────────────────────── */ T.openImagesToPdf = function () { var images = []; var overrideDir = null; var dlg = T.modal({ title: 'Create PDF from Images', bodyHtml: '
' + 'Images' + '
' + '' + '
' + '
' + 'Page size' + '' + '

Images are scaled to fit and centred on the page. ' + 'Nothing is cropped and the aspect ratio is kept.

' + '
' + '
' + 'Output filename' + '' + '
' + '
' + 'Save to folder' + '
' + '' + 'Same folder as the first image' + '
' + '
' + '
', actions: [ { label: 'Cancel', onClick: function (c) { c.close(); } }, { label: 'Open in Editor', onClick: openInEditor }, { label: 'Save to Server', primary: true, onClick: saveToServer } ] }); var listEl = dlg.body.querySelector('#i2pList'); var progEl = dlg.body.querySelector('#i2pProg'); function renderList() { if (!images.length) { listEl.innerHTML = '
No images added yet.
'; return; } var html = '
'; images.forEach(function (img, idx) { html += '
' + '' + '' + T.escHtml(img.filename) + '' + '' + (idx > 0 ? '' : '') + (idx < images.length - 1 ? '' : '') + '' + '' + '
'; }); listEl.innerHTML = html + '
'; Array.prototype.forEach.call(listEl.querySelectorAll('[data-mv]'), function (b) { b.addEventListener('click', function () { var i = parseInt(this.getAttribute('data-i'), 10); var j = i + parseInt(this.getAttribute('data-mv'), 10); if (j < 0 || j >= images.length) { return; } var tmp = images[i]; images[i] = images[j]; images[j] = tmp; renderList(); }); }); Array.prototype.forEach.call(listEl.querySelectorAll('[data-rm]'), function (b) { b.addEventListener('click', function () { images.splice(parseInt(this.getAttribute('data-rm'), 10), 1); renderList(); }); }); } dlg.body.querySelector('#i2pAdd').addEventListener('click', function () { ao_module_openFileSelector('_ptI2pAddCb', 'user:/', 'file', true, { filter: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'] }); }); dlg.body.querySelector('#i2pDir').addEventListener('click', function () { ao_module_openFileSelector('_ptI2pDirCb', 'user:/', 'folder', false); }); window._ptI2pAddCb = function (files) { if (!files || !files.length) { return; } files.forEach(function (f) { if (!images.some(function (x) { return x.filepath === f.filepath; })) { images.push({ filepath: f.filepath, filename: f.filename }); } }); renderList(); }; window._ptI2pDirCb = function (files) { if (!files || !files.length) { return; } overrideDir = files[0].filepath; dlg.body.querySelector('#i2pDirLbl').textContent = overrideDir; }; function outputName() { var name = dlg.body.querySelector('#i2pName').value.trim() || 'output'; return /\.pdf$/i.test(name) ? name : name + '.pdf'; } /* Build the document in memory and hand back its bytes. */ function build(ctx) { if (!images.length) { progEl.innerHTML = '
Add at least one image first.
'; return Promise.reject(null); } var preset = PAGE_PRESETS[dlg.body.querySelector('#i2pSize').value] || null; ctx.button.disabled = true; progEl.innerHTML = '
Loading pdf-lib…
'; return T.loadPdfLib().then(function () { return PDFLib.PDFDocument.create(); }).then(function (pdfDoc) { return images.reduce(function (chain, im, i) { return chain.then(function () { progEl.innerHTML = '
Adding image ' + (i + 1) + ' of ' + images.length + '…
'; return T.embedImage(pdfDoc, T.mediaUrl(im.filepath)).then(function (emb) { if (!preset) { // One page per image, exactly the image's own size var p = pdfDoc.addPage([emb.width, emb.height]); p.drawImage(emb, { x: 0, y: 0, width: emb.width, height: emb.height }); return; } // Fixed base plate: scale to fit inside, keep aspect, centre it var pw = preset[0], ph = preset[1]; var s = Math.min(pw / emb.width, ph / emb.height); var w = emb.width * s, h = emb.height * s; var page = pdfDoc.addPage([pw, ph]); page.drawImage(emb, { x: (pw - w) / 2, y: (ph - h) / 2, width: w, height: h }); }); }); }, Promise.resolve()).then(function () { progEl.innerHTML = '
Writing PDF…
'; return pdfDoc.save(); }); }).catch(function (err) { if (err) { progEl.innerHTML = '
Error: ' + T.escHtml(String(err.message || err)) + '
'; } ctx.button.disabled = false; throw err; }); } /* Straight into the editor — never round-trips through the server */ function openInEditor(ctx) { build(ctx).then(function (bytes) { ctx.button.disabled = false; ctx.close(); T.openBytes(bytes, outputName(), null); }).catch(function () { /* message already shown */ }); } function saveToServer(ctx) { var name = outputName(); build(ctx).then(function (bytes) { var outDir = overrideDir || T.dirOf(images[0].filepath); progEl.innerHTML = '
Uploading…
'; return T.uploadBlob(new Blob([bytes], { type: 'application/pdf' }), name, outDir) .then(function () { progEl.innerHTML = '
' + T.escHtml(name) + ' created in ' + T.escHtml(outDir) + '
'; ctx.button.disabled = false; }); }).catch(function (err) { if (err) { progEl.innerHTML = '
Error: ' + T.escHtml(String(err.message || err)) + '
'; } ctx.button.disabled = false; }); } renderList(); }; })(window.PDFTool);