commit 3f53f343fe6dd78723091d94c608914c16dffd97 Author: Skylar Ittner Date: Wed Jun 30 02:32:55 2021 -0600 First commit: can open/save/sign/stamp PDFs diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..9aeb356 --- /dev/null +++ b/css/main.css @@ -0,0 +1,75 @@ +/* +Copyright 2021 Netsyms Technologies. +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +/* + Created on : Jun 27, 2021, 7:34:10 PM + Author : Skylar Ittner +*/ + +html, body { + height: 100%; + width: 100%; +} + +#page-canvas-container { + height: 90vh; + max-height: 90vh; + max-width: 80vw; + overflow: scroll; +} + +#page-canvas-container .page-canvas { + margin: 0.5em; + box-shadow: 6px 6px 7px -1px rgba(0,0,0,0.5); + height: 100%; + border: 1px solid gray; +} + +#page-canvas-container .page-canvas.active { + margin: 0.5em; + box-shadow: 0px 0px 7px 0px rgba(0,255,0,0.8); + height: 100%; + border: 1px solid blue; +} + +#page-canvas-container #placementguidebox { + opacity: 0.5; + position: absolute; + float: left; + margin-top: -40px; + padding: 0; +} + +#signature_pad { + border: 1px solid black; +} + + +.signature-wrapper { + background-color: white; + border-radius: 5px; + position: relative; + width: 400px; + height: 200px; + margin: 0 auto; + border: 1px solid rgba(0,0,0,0.5); + /* fix bug on iOS where image sticks out right side and makes entire page scroll horiz. */ + overflow: hidden; +} + +.signature-wrapper img { + position: absolute; + bottom: 0; + left: 0; +} + +.signature-wrapper canvas { + position: absolute; + left: 0; + top: 0; + width: 400px; + height: 200px; +} \ No newline at end of file diff --git a/img/signature-line.svg b/img/signature-line.svg new file mode 100644 index 0000000..b757c38 --- /dev/null +++ b/img/signature-line.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..e6fddb0 --- /dev/null +++ b/index.html @@ -0,0 +1,120 @@ + + +IPENtool + + + + + + + + + + + + +
+
+
Fit Height
+
Fit Width
+
Zoom Out
+
Zoom In
+
Stamp/Seal
+
Sign (Client)
+
Sign (Notary)
+
+
+ +
+
+ Page count: +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/drawtools.js b/js/drawtools.js new file mode 100644 index 0000000..9e1d37e --- /dev/null +++ b/js/drawtools.js @@ -0,0 +1,164 @@ +/* + * Copyright 2021 Netsyms Technologies. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +var activeDrawImage; +var signaturePadCallback = function () {}; +var clientSignatureSvg = ""; + +function getStampSvg(callback) { + $.get("templates/stamps/" + getStorage("notary_state") + ".svg", {}, function (data) { + data = data + ""; + data = data.replaceAll("[[[NAME]]]", getStorage("notary_name")); + data = data.replaceAll("[[[LOCATION]]]", getStorage("notary_location")); + data = data.replaceAll("[[[EXPIRES]]]", getStorage("notary_expires")); + data = data.replaceAll("[[[IDNUMBER]]]", getStorage("notary_idnumber")); + + callback(data); + }, "text"); +} + +function makeStampImage(callback) { + getStampSvg(function (data) { + svgToImage(data, function (err, image) { + if (err) { + callback(false); + return; + } + callback(image); + }); + }); +} + +function activateStampDrawTool() { + makeStampImage(function (image) { + activeDrawImage = image; + enableGuideBox(image); + }); +} + +function activateNotarySignatureTool() { + if (!inStorage("notary_signature")) { + alert("Please set a notary signature in the settings."); + return; + } + svgToImage(getStorage("notary_signature"), function (err, image) { + if (err) { + callback(false); + return; + } + activeDrawImage = image; + enableGuideBox(image); + }); +} + +function activateClientSignaturePad() { + initSignaturePad(); + signaturePadCallback = function () { + if (clientSignatureSvg != "" && signaturePad.isEmpty()) { + var signature = clientSignatureSvg; + } else { + var signature = signaturePad.toDataURL("image/svg+xml"); + signature = signature.replace("data:image/svg+xml;base64,", ""); + signature = atob(signature); + signature = trimAndShrinkSVG(signature); + clientSignatureSvg = signature; + } + + svgToImage(signature, function (err, image) { + if (err) { + callback(false); + return; + } + activeDrawImage = image; + enableGuideBox(image); + }); + }; +} + +function drawImageFromUrl(x, y, width, height, src, canvas) { + var ctx = canvas.getContext("2d"); + const image = new Image(); + image.src = src; + image.onload = () => { + ctx.drawImage(image, x, y, width, height); + } +} + +function drawImage(x, y, width, height, image, canvas) { + var ctx = canvas.getContext("2d"); + ctx.drawImage(image, x, y, width, height); +} + +$("#page-canvas-container").on("click", ".page-canvas", function (evt) { + $("#page-canvas-container .page-canvas").removeClass("active"); + $(this).addClass("active"); +}); + +$("#page-canvas-container").on("click", ".page-canvas.active", function (evt) { + if (typeof activeDrawImage == "undefined") { + return; + } + var canvas = $(this)[0]; + var coords = getMousePos(canvas, evt); + + var imageWidth = (activeDrawImage.width / 96) * pdfAssumedDPI * pdfPageScale; + var imageHeight = (activeDrawImage.height / 96) * pdfAssumedDPI * pdfPageScale; + drawImage(coords.x, coords.y, imageWidth, imageHeight, activeDrawImage, canvas); +}); + + +function enableGuideBox(image, scalecorrectionfactor) { + if (typeof scalecorrectionfactor == "undefined") { + scalecorrectionfactor = 1; + } + // disable first to clear contents + disableGuideBox(); + $("#placementguidebox").css("display", ""); + // calculate size of guide image + var pageWidthPx = $("#page-canvas-container .page-canvas")[0].getContext("2d").canvas.width; + var pageCanvasCurrentWidthPx = $("#page-canvas-container .page-canvas").css("width").replace("px", "") * 1; + var pageWidthInches = pageWidthPx / (pdfAssumedDPI * pdfPageScale); + var canvasCurrentDPI = pageCanvasCurrentWidthPx / pageWidthInches; + + var imageWidth = (image.width / (96 * scalecorrectionfactor)) * canvasCurrentDPI; + var imageHeight = (image.height / (96 * scalecorrectionfactor)) * canvasCurrentDPI; + + var canvas = $("#placementguidebox")[0]; + var ctx = canvas.getContext("2d"); + ctx.drawImage(image, 0, 0, imageWidth, imageHeight); +} + +function disableGuideBox() { + $("#placementguidebox").css("display", "none"); + + var context = $("#placementguidebox")[0].getContext('2d'); + context.clearRect(0, 0, $("#placementguidebox")[0].width, $("#placementguidebox")[0].height); +} + +$("#page-canvas-container").on("mousemove", function (evt) { + $("#placementguidebox").css({ + left: evt.pageX, + top: evt.pageY + }); +}); + +/** + * https://stackoverflow.com/a/17130415 + * @param {type} canvas + * @param {type} evt + * @returns {getMousePos.pdfAnonym$1} + */ +function getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect(), // abs. size of element + scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X + scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y + + return { + x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have + y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element + } +} \ No newline at end of file diff --git a/js/filesystem.js b/js/filesystem.js new file mode 100644 index 0000000..f271d95 --- /dev/null +++ b/js/filesystem.js @@ -0,0 +1,46 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +function openFileDialog(callback) { + $("#open-file-input").off("change"); + if (typeof callback != "undefined") { + $("#open-file-input").on("change", function () { + callback($("#open-file-input").val()); + }); + } + $("#open-file-input").click(); + +} + +function getFileAsString(path) { + const fs = require("fs"); + return fs.readFileSync(path, "utf8"); +} + +function getFileAsUint8Array(path) { + const fs = require("fs"); + return fs.readFileSync(path, null); +} + +function writeStringToFile(path, text) { + const fs = require("fs"); + fs.writeFileSync(path, text); +} + +function writeDataToFile(path, data) { + const fs = require("fs"); + fs.writeFileSync(path, data); +} + +function copyFile(source, dest) { + const fs = require("fs"); + fs.copyFileSync(source, dest); +} + +function getBasename(fullpath) { + var path = require("path"); + return path.basename(fullpath); +} \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..e111d7f --- /dev/null +++ b/js/main.js @@ -0,0 +1,124 @@ +/* + * Copyright 2021 Netsyms Technologies. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +var pdfjsLib = window['pdfjs-dist/build/pdf']; +pdfjsLib.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.min.js'; + +var signaturePad; + +function setupNotaryOptions(name, location, expires, idnumber, state) { + setStorage("notary_name", name); + setStorage("notary_location", location); + setStorage("notary_expires", expires); + setStorage("notary_idnumber", idnumber); + setStorage("notary_state", state); +} + +function openSettingsModal() { + $("#settingsModal #notary_name").val(getStorage("notary_name")); + $("#settingsModal #notary_location").val(getStorage("notary_location")); + $("#settingsModal #notary_expires").val(getStorage("notary_expires")); + $("#settingsModal #notary_idnumber").val(getStorage("notary_idnumber")); + $("#settingsModal #notary_state").val(getStorage("notary_state")); + + // show preview of stamp + if (inStorage("notary_state")) { + getStampSvg(function (svg) { + $("#settingsModal #stamp-preview").attr("src", "data:image/svg+xml;base64," + btoa(svg)); + }); + } + + // show signature + if (inStorage("notary_signature")) { + $("#settingsModal #signature-preview").attr("src", "data:image/svg+xml;base64," + btoa(getStorage("notary_signature"))); + } + + new bootstrap.Modal(document.getElementById('settingsModal')).show(); +} + +function saveSettingsModal() { + setupNotaryOptions( + $("#settingsModal #notary_name").val(), + $("#settingsModal #notary_location").val(), + $("#settingsModal #notary_expires").val(), + $("#settingsModal #notary_idnumber").val(), + $("#settingsModal #notary_state option:selected").val()); + // show preview of stamp + if (inStorage("notary_state")) { + getStampSvg(function (svg) { + $("#settingsModal #stamp-preview").attr("src", "data:image/svg+xml;base64," + btoa(svg)); + }); + } + // show signature + if (inStorage("notary_signature")) { + $("#settingsModal #signature-preview").attr("src", "data:image/svg+xml;base64," + btoa(getStorage("notary_signature"))); + } +} + +function initSignaturePad() { + var canvas = document.getElementById("signaturecanvas"); + signaturePad = new SignaturePad(canvas, { + backgroundColor: 'rgba(255, 255, 255, 0.5)', + onBegin: function () { + // stop page from jumping around if user starts drawing signature while a text box is focused + $("input").blur(); + } + }); + new bootstrap.Modal(document.getElementById('signatureModal')).show(); + + + $("#signatureModal").on("shown.bs.modal", resizeSignaturePadCanvas); +} + +function resizeSignaturePadCanvas() { + var canvas = document.getElementById("signaturecanvas"); + var ratio = Math.max(window.devicePixelRatio || 1, 1); + canvas.width = canvas.offsetWidth * ratio; + canvas.height = canvas.offsetHeight * ratio; + canvas.getContext("2d").scale(ratio, ratio); + if (signaturePad != null) { + signaturePad.clear(); // otherwise isEmpty() might return incorrect value + } +} + +function signaturePadUndo() { + var data = signaturePad.toData(); + + resizeSignaturePadCanvas(); + + if (data) { + data.pop(); // remove the last dot or line + signaturePad.fromData(data); + } +} + + +function activateNotarySignaturePad() { + initSignaturePad(); + signaturePadCallback = function () { + var signature = signaturePad.toDataURL("image/svg+xml"); + signature = signature.replace("data:image/svg+xml;base64,", ""); + signature = atob(signature); + setStorage("notary_signature", trimAndShrinkSVG(signature)); + $("#settingsModal #signature-preview").attr("src", "data:image/svg+xml;base64," + btoa(getStorage("notary_signature"))); + }; +} + +function trimAndShrinkSVG(svgstring) { + var div = document.getElementById('svgtrimbox'); + div.innerHTML = svgstring; + var svg = div.firstChild; + var bbox = svg.getBBox(); + var viewBox = [bbox.x, bbox.y, bbox.width, bbox.height].join(" "); + svg.setAttribute("viewBox", viewBox); + svg.setAttribute("width", 100); + svg.setAttribute("height", 50); + //console.log(svg.outerHTML); + div.innerHTML = ""; + return svg.outerHTML; +} \ No newline at end of file diff --git a/js/pdf.js b/js/pdf.js new file mode 100644 index 0000000..f1ab0c3 --- /dev/null +++ b/js/pdf.js @@ -0,0 +1,122 @@ +/* + * Copyright 2021 Netsyms Technologies. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +const {jsPDF} = window.jspdf; + +var pdfPageScale = 4; +var pdfAssumedDPI = 72; +var pdfDoc = null; +var pageNumber = 0; + +function addPDF() { + openFileDialog(function (path) { + var filedata = getFileAsUint8Array(path); + + /** + * Asynchronously downloads PDF. + */ + pdfjsLib.getDocument(filedata).promise.then(function (pdfDoc_) { + pdfDoc = pdfDoc_; + + renderAllPages(pdfDoc); + pdfZoom("fitwidth"); + // Initial/first page rendering + //renderPage(pageNum); + }); + }); +} + +function savePDF() { + var canvases = $("#page-canvas-container .page-canvas"); + const pdf = new jsPDF({ + unit: "in", + compress: true + }); + // creating a PDF creates a blank page that we don't want to use, + // as we haven't done the calculations yet + pdf.deletePage(1); + for (var i = 0; i < canvases.length; i++) { + var canvas = $("#page-canvas-container .page-canvas")[i]; + var widthpx = canvas.getContext("2d").canvas.width; + var heightpx = canvas.getContext("2d").canvas.height; + var pageWidthInches = widthpx / (pdfAssumedDPI * pdfPageScale); + var pageHeightInches = heightpx / (pdfAssumedDPI * pdfPageScale); + console.log(pageWidthInches + " x " + pageHeightInches); + var pageFormat = [pageWidthInches, pageHeightInches]; + var pageOrientation = (pageWidthInches > pageHeightInches ? "landscape" : "portrait"); + pdf.addPage(pageFormat, pageOrientation); + pdf.addImage($("#page-canvas-container .page-canvas")[i].toDataURL(), 0, 0, pageWidthInches, pageHeightInches); + } + pdf.save("signed.pdf"); +} + +function pdfZoom(str) { + disableGuideBox(); + var widthpx = $("#page-canvas-container .page-canvas").css("width").replace("px", "") * 1; + var zoomstep = 100; + console.log(widthpx); + switch (str) { + case "out": + $("#page-canvas-container .page-canvas").css("height", "auto"); + widthpx -= zoomstep; + $("#page-canvas-container .page-canvas").css("width", widthpx + "px"); + break; + case "in": + $("#page-canvas-container .page-canvas").css("height", "auto"); + widthpx += zoomstep; + $("#page-canvas-container .page-canvas").css("width", widthpx + "px"); + break; + case "fitwidth": + $("#page-canvas-container .page-canvas").css("width", "100%"); + $("#page-canvas-container .page-canvas").css("height", "auto"); + break; + case "fitheight": + $("#page-canvas-container .page-canvas").css("height", "100%"); + $("#page-canvas-container .page-canvas").css("width", "auto"); + break; + } +} + +function getNewCanvas(pagenumber) { + var canvas = document.createElement('canvas'); + canvas.id = "pdf-canvas-page-" + pagenumber; + canvas.className = "page-canvas"; + return canvas; +} + +function addPage() { + pageNumber++; + var canvas = getNewCanvas(pageNumber); + var prevPageCanvas = $("#page-canvas-container .page-canvas#pdf-canvas-page-" + (pageNumber - 1))[0]; + canvas.width = prevPageCanvas.getContext("2d").canvas.width; + canvas.height = prevPageCanvas.getContext("2d").canvas.height; + $("#page-canvas-container").append(canvas); +} + +function renderAllPages() { + var startingPageNumber = pageNumber; + var thisDocPageCount = pdfDoc.numPages; + for (var i = 1; i <= pdfDoc.numPages; i++) { + pdfDoc.getPage(i).then(function (page) { + var viewport = page.getViewport({scale: pdfPageScale}); + var canvas = getNewCanvas(page.pageNumber + startingPageNumber); + canvas.height = viewport.height; + canvas.width = viewport.width; + $("#page-canvas-container").append(canvas); + + // Render PDF page into canvas context + var renderContext = { + canvasContext: canvas.getContext("2d"), + viewport: viewport + }; + + page.render(renderContext); + }); + } + pageNumber = pageNumber + thisDocPageCount; + document.getElementById('page_count').textContent = pageNumber; +} \ No newline at end of file diff --git a/js/storage.js b/js/storage.js new file mode 100644 index 0000000..8fcc279 --- /dev/null +++ b/js/storage.js @@ -0,0 +1,51 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +/** + * Save something to persistent storage. + * @param {string} key + * @param {string} value non-string values are converted to strings. + * @returns {undefined} + */ +function setStorage(key, value) { + localStorage.setItem(key, value); +} + +/** + * Get an item from persistent storage. + * @param {type} key + * @returns {DOMString} + */ +function getStorage(key) { + return localStorage.getItem(key); +} + +/** + * Check if an item is in the persistent storage. + * @param {string} key + * @returns {Boolean} + */ +function inStorage(key) { + return localStorage.getItem(key) != null; +} + +/** + * Get all item from persistent storage. + * @returns {Array} [{key: "", value: ""},...] + */ +function getAllStorage() { + var all = []; + for (var key in localStorage) { + if (localStorage.hasOwnProperty(key)) { + all.push({ + key: key, + value: getStorage(key) + }); + } + } + return all; +} \ No newline at end of file diff --git a/js/svg-to-image.js b/js/svg-to-image.js new file mode 100644 index 0000000..d962c36 --- /dev/null +++ b/js/svg-to-image.js @@ -0,0 +1,103 @@ +/* + * https://github.com/Jam3/svg-to-image + * https://github.com/mattdesl/load-img + */ +function loadImage(src, opt, callback) { + if (typeof opt === 'function') { + callback = opt; + opt = null; + } + + var el = document.createElement('img'); + var locked; + + el.onload = function onLoaded() { + if (locked) + return; + locked = true; + + if (callback) + callback(undefined, el); + }; + + el.onerror = function onError() { + if (locked) + return; + locked = true; + + if (callback) + callback(new Error('Unable to load "' + src + '"'), el); + }; + + if (opt && opt.crossOrigin) { + el.crossOrigin = opt.crossOrigin; + } + + el.src = src; + + return el; +} + +function svgToImage(svg, opt, cb) { + + if (typeof opt === 'function') { + cb = opt + opt = {} + } + cb = cb || noop + opt = opt || {} + + if (typeof window === 'undefined') { + return bail('window global is undefined; not in a browser') + } + + var DOMURL = getURL() + if (!DOMURL || + typeof DOMURL.createObjectURL !== 'function' || + typeof DOMURL.revokeObjectURL !== 'function') { + return bail('browser does not support URL.createObjectURL') + } + + if (typeof window.Blob === 'undefined') { + return bail('browser does not support Blob constructor') + } + + if (!Array.isArray(svg)) { + svg = [svg] + } + + var blob + try { + blob = new window.Blob(svg, { + type: 'image/svg+xml;charset=utf-8' + }) + } catch (e) { + return bail(e) + } + + var url = DOMURL.createObjectURL(blob) + loadImage(url, opt, function (err, img) { + DOMURL.revokeObjectURL(url) + if (err) { + // try again for Safari 8.0, using simple encodeURIComponent + // this will fail with DOM content but at least it works with SVG + var url2 = 'data:image/svg+xml,' + encodeURIComponent(svg.join('')) + return loadImage(url2, opt, cb) + } + + cb(err, img) + }) + + function getURL() { + return window.URL || + window.webkitURL || + window.mozURL || + window.msURL + } + + function bail(msg) { + process.nextTick(function () { + cb(new Error(msg)) + }) + } +} \ No newline at end of file diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..e0819ee --- /dev/null +++ b/js/util.js @@ -0,0 +1,275 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/** + * Generate a UUID. + * From https://stackoverflow.com/a/2117523 + * @returns {String} + */ +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +/** + * Take a UNIX timestamp (seconds since Jan 1 1970) and format it. + * (Mostly) compatible with PHP's date() function. + * @param {String} date format string, see https://www.php.net/manual/en/function.date.php + * @param {Integer} timestamp UNIX timestamp + * @return {String} + */ +function formatTimestamp(format, timestamp) { + if (typeof timestamp == "undefined") { + timestamp = time(); + } + var date = new Date(timestamp * 1000); + + var out = ""; + + var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + for (var i = 0; i < format.length; i++) { + var c = format.charAt(i); + // Handle backslash-escaped characters + if (c == "\\" && i < format.length - 1) { + out += format.charAt(i + 1); + i++; + continue; + } + switch (c) { + case "d": + var d = date.getDate(); + if (d < 10) { + out += "0"; + } + out += d; + break; + case "D": + out += days[date.getDay()].substring(0, 3); + break; + case "j": + out += date.getDate(); + break; + case "l": + out += days[date.getDay()]; + break; + case "N": + // TODO + break; + case "S": + // TODO + break; + case "w": + out += date.getDay(); + break; + case "z": + // TODO + break; + case "W": + // TODO + break; + case "F": + out += months[date.getMonth()]; + break; + case "m": + var m = date.getMonth() + 1; + if (m < 10) { + out += "0"; + } + out += m; + break; + case "M": + out += months[date.getMonth()].substring(0, 3); + break; + case "n": + out += date.getMonth() + 1; + break; + case "t": + // TODO + break; + case "L": + // TODO + break; + case "o": + // TODO + break; + case "Y": + out += date.getFullYear(); + break; + case "y": + var y = (date.getFullYear() + ""); + out += y.substring(y.length - 2); + break; + case "a": + if (date.getHours() < 12) { + out += "am"; + } else { + out += "pm"; + } + break; + case "A": + if (date.getHours() < 12) { + out += "AM"; + } else { + out += "PM"; + } + break; + case "B": + // TODO + break; + case "g": + var h = date.getHours() % 12; + if (h == 0) { + h = 12; + } + out += h; + break; + case "G": + out += date.getHours(); + break; + case "h": + var h = date.getHours() % 12; + if (h == 0) { + h = 12; + } + if (h < 10) { + out += "0"; + } + out += h; + break; + case "H": + var h = date.getHours(); + if (h < 10) { + out += "0"; + } + out += h; + break; + case "i": + var ii = date.getMinutes(); + if (ii < 10) { + out += "0"; + } + out += ii; + break; + case "s": + var s = date.getSeconds(); + if (s < 10) { + out += "0"; + } + out += s; + break; + case "u": + out += date.getMilliseconds() * 1000; + break; + case "v": + out += date.getMilliseconds(); + break; + case "e": + // TODO + break; + case "I": + // TODO + break; + case "O": + var off = date.getTimezoneOffset(); + var m = off % 60; + var h = (off - m) / 60; + if (off >= 0) { + out += "+"; + } else { + out += "-"; + } + if (h < 10) { + out += "0"; + } + out += h; + if (m < 10) { + out += "0"; + } + out += m; + break; + case "P": + var off = date.getTimezoneOffset(); + var m = off % 60; + var h = (off - m) / 60; + if (off >= 0) { + out += "+"; + } else { + out += "-"; + } + if (h < 10) { + out += "0"; + } + out += h; + out += ":"; + if (m < 10) { + out += "0"; + } + out += m; + break; + case "T": + // TODO + break; + case "Z": + out += date.getTimezoneOffset() * 60; + break; + case "c": + out += formatTimestamp(timestamp, "Y-m-d\\TH:i:sP"); + break; + case "r": + out += formatTimestamp(timestamp, "D, j M Y G:i:s O"); + break; + case "U": + out += Math.round(timestamp); + break; + default: + out += c; + } + } + + return out; +} + +function timestampToDateTimeString(timestamp) { + return timestampToDateString(timestamp) + " " + timestampToTimeString(timestamp); +} + +function timestampToDateString(timestamp) { + var date = new Date(timestamp * 1000); + + return date.toLocaleDateString(); +} + +function timestampToTimeString(timestamp) { + var date = new Date(timestamp * 1000); + + var pm = date.getHours() >= 12; + var hours = date.getHours() > 12 ? date.getHours() - 12 : date.getHours(); + hours = (hours == 0 ? 12 : hours); + var minutes = date.getMinutes(); + var time = hours + ":" + (minutes < 10 ? "0" + minutes : minutes) + " " + (pm ? "PM" : "AM"); + + return time; +} + +/** + * Get the current UNIX timestamp in seconds. + * @returns {Number} + */ +function time() { + return Date.now() / 1000; +} + +/** + * Get the number of seconds between now and the given timestamp. + * @param {Number} compareto + * @returns {Number} + */ +function timeDiff(compareto) { + return time() - compareto; +} \ No newline at end of file diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..ea44320 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,6 @@ +file.reference.IPENtool-public_html=public_html +file.reference.IPENtool-test=test +file.reference.Sources-IPENtool=. +files.encoding=UTF-8 +project.license=mpl +site.root.folder=${file.reference.Sources-IPENtool} diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..f48d529 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,9 @@ + + + org.netbeans.modules.web.clientproject + + + IPENtool + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fece215 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,228 @@ +{ + "name": "IPENtool", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", + "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", + "optional": true, + "requires": { + "core-js-pure": "^3.15.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@fortawesome/fontawesome-free": { + "version": "5.15.3", + "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-free/-/5.15.3/fontawesome-free-5.15.3.tgz", + "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==" + }, + "@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "requires": { + "pako": "^1.0.6" + } + }, + "@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "requires": { + "pako": "^1.0.10" + } + }, + "@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "optional": true + }, + "bootstrap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", + "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==" + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, + "canvg": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.7.tgz", + "integrity": "sha512-4sq6iL5Q4VOXS3PL1BapiXIZItpxYyANVzsAKpTPS5oq4u3SKbGfUcbZh2gdLCQ3jWpG/y5wRkMlBBAJhXeiZA==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.9.6", + "@types/raf": "^3.4.0", + "raf": "^3.4.1", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^5.0.5" + } + }, + "core-js": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz", + "integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==", + "optional": true + }, + "core-js-pure": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.2.tgz", + "integrity": "sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==", + "optional": true + }, + "css-line-break": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", + "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", + "optional": true, + "requires": { + "base64-arraybuffer": "^0.2.0" + } + }, + "dompurify": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.9.tgz", + "integrity": "sha512-+9MqacuigMIZ+1+EwoEltogyWGFTJZWU3258Rupxs+2CGs4H914G9er6pZbsme/bvb5L67o2rade9n21e4RW/w==", + "optional": true + }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, + "html2canvas": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-rc.7.tgz", + "integrity": "sha512-yvPNZGejB2KOyKleZspjK/NruXVQuowu8NnV2HYG7gW7ytzl+umffbtUI62v2dCHQLDdsK6HIDtyJZ0W3neerA==", + "optional": true, + "requires": { + "css-line-break": "1.1.1" + } + }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, + "jspdf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.3.1.tgz", + "integrity": "sha512-1vp0USP1mQi1h7NKpwxjFgQkJ5ncZvtH858aLpycUc/M+r/RpWJT8PixAU7Cw/3fPd4fpC8eB/Bj42LnsR21YQ==", + "requires": { + "atob": "^2.1.2", + "btoa": "^1.2.1", + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "fflate": "^0.4.8", + "html2canvas": "^1.0.0-rc.5" + } + }, + "konva": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/konva/-/konva-8.1.0.tgz", + "integrity": "sha512-HkS5jB4oZlj+koSBmKWWWBOoaivnKDsEAZUkk+6xlOT+ryj5HFXkTfBZZKBS+IA0WRz3vDpko0y3LFAzuc86kA==" + }, + "load-img": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-img/-/load-img-1.0.0.tgz", + "integrity": "sha1-CVN0SYk8MqhwkHRkVWbExfqprCY=" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "pdf-lib": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.16.0.tgz", + "integrity": "sha512-P/1SSmElOBKrPlbc+Sn7UxikRQbzVA64+4Dh6/uczPscvq/NatP9eryoOguyBTpTnzICNiG8EnMH4Ziqp2TnFA==", + "requires": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "pdfjs-dist": { + "version": "2.8.335", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.8.335.tgz", + "integrity": "sha512-2IKw7wP1RnzzWJcpkeZwF+cKROFiQext+/WburB6cgKwt9zc8rOyDH7a3FepdcciSGs8SDs/AuWe8qVx+iI6pw==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "optional": true + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "optional": true + }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true + }, + "signature_pad": { + "version": "3.0.0-beta.4", + "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-3.0.0-beta.4.tgz", + "integrity": "sha512-cOf2NhVuTiuNqe2X/ycEmizvCDXk0DoemhsEpnkcGnA4kS5iJYTCqZ9As7tFBbsch45Q1EdX61833+6sjJ8rrw==" + }, + "stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true + }, + "svg-pathdata": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-5.0.5.tgz", + "integrity": "sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow==", + "optional": true + }, + "svg-to-image": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/svg-to-image/-/svg-to-image-1.1.3.tgz", + "integrity": "sha1-1v9NiDyo9+P3krQrIyixXL4vsPM=", + "requires": { + "load-img": "^1.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b025a9 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "IPENtool", + "main": "index.html", + "version": "1.0.0", + "keywords": [ + "util", + "functional", + "server", + "client", + "browser" + ], + "author": "Skylar Ittner", + "contributors": [], + "dependencies": { + "@fortawesome/fontawesome-free": "^5.15.3", + "bootstrap": "^5.0.2", + "jquery": "^3.6.0", + "jspdf": "^2.3.1", + "konva": "^8.1.0", + "pdf-lib": "^1.16.0", + "pdfjs-dist": "^2.8.335", + "signature_pad": "^3.0.0-beta.4", + "svg-to-image": "^1.1.3" + } +} diff --git a/templates/stamps/mt.svg b/templates/stamps/mt.svg new file mode 100644 index 0000000..c781db5 --- /dev/null +++ b/templates/stamps/mt.svg @@ -0,0 +1,2 @@ + +[[[NAME]]]Notary Public for theState of MontanaResiding at[[[LOCATION]]]My Commission Expires[[[EXPIRES]]]SEALNotarialState of Montana[[[NAME]]] diff --git a/test.pdf b/test.pdf new file mode 100644 index 0000000..598ee56 Binary files /dev/null and b/test.pdf differ