diff --git a/libs/remix-lib/src/types/ICompilerApi.ts b/libs/remix-lib/src/types/ICompilerApi.ts index 850c000744..168107e660 100644 --- a/libs/remix-lib/src/types/ICompilerApi.ts +++ b/libs/remix-lib/src/types/ICompilerApi.ts @@ -34,7 +34,7 @@ export interface ICompilerApi { resolveContentAndSave: (url: string) => Promise fileExists: (file: string) => Promise - writeFile: (file: string, content: string) => Promise + writeFile: (file: string, content: any) => Promise readFile: (file: string) => Promise open: (file: string) => void saveCurrentFile: () => void diff --git a/libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx b/libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx index aabc75ab00..0c043e0e2f 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx @@ -212,15 +212,14 @@ export const ContractSelection = (props: ContractSelectionProps) => { const currentFile = api.currentFile const ast = content4AST.length > 1 ? parser.parse(content4AST) : parser.parse(api.getCompilationResult().source.sources[currentFile].content) const payload = vizRenderStringSync(convertUmlClasses2Dot(convertAST2UmlClasses(ast, currentFile))) - - const domParser = new DOMParser() - const element = domParser.parseFromString(payload, 'image/svg+xml').querySelector('svg') - const fileName = `${api.currentFile.split('/')[0]}/resources/${api.currentFile.split('/')[1]}.pdf` - - pdfBuilder.addSvgAsImage(payload, 1.6, 1.6, 300, 300) - const result = pdfBuilder.output('datauristring', { filename: fileName }) - api.writeFile(fileName, result) - console.log({ result }) + const fileName = `${api.currentFile.split('/')[0]}/resources/${api.currentFile.split('/')[1].split('.')[0]}.pdf` + + const element = new DOMParser().parseFromString(payload, 'image/svg+xml') + .querySelector('svg') + domToPdf(element, { filename: `${api.currentFile.split('/')[1].split('.')[0]}.pdf`, scale: 1.2 }, (pdf: jsPDF) => { + + api.writeFile(fileName, pdf.output()) + }) setSVGPayload(payload) setShowViewer(!showViewer) } catch (error) { diff --git a/libs/remix-ui/solidity-compiler/src/lib/logic/pdfSaveLogic.ts b/libs/remix-ui/solidity-compiler/src/lib/logic/pdfSaveLogic.ts new file mode 100644 index 0000000000..665c9cc200 --- /dev/null +++ b/libs/remix-ui/solidity-compiler/src/lib/logic/pdfSaveLogic.ts @@ -0,0 +1,286 @@ +import domToImage from 'dom-to-image'; +import { jsPDF } from 'jspdf'; + +const _cloneNode = (node, javascriptEnabled) => { + let child = node.firstChild + const clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false) + while (child) { + if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { + clone.appendChild(_cloneNode(child, javascriptEnabled)) + } + child = child.nextSibling + } + if (node.nodeType === 1) { + if (node.nodeName === 'CANVAS') { + clone.width = node.width + clone.height = node.height + clone.getContext('2d').drawImage(node, 0, 0) + } else if (node.nodeName === 'TEXTAREA' || node.nodeName === 'SELECT') { + clone.value = node.value + } + clone.addEventListener('load', (() => { + clone.scrollTop = node.scrollTop + clone.scrollLeft = node.scrollLeft + }), true) + } + return clone +} + +const _createElement = (tagName, {className, innerHTML, style}) => { + let i + let scripts + const el = document.createElement(tagName) + if (className) { + el.className = className + } + if (innerHTML) { + el.innerHTML = innerHTML + scripts = el.getElementsByTagName('script') + i = scripts.length + while (i-- > 0) { + scripts[i].parentNode.removeChild(scripts[i]) + } + } + for (const key in style) { + el.style[key] = style[key]; + } + return el; +}; + +const _isCanvasBlank = canvas => { + const blank = document.createElement('canvas'); + blank.width = canvas.width; + blank.height = canvas.height; + const ctx = blank.getContext('2d'); + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, blank.width, blank.height); + return canvas.toDataURL() === blank.toDataURL(); +}; + +const downloadPdf = (dom, options, cb) => { + const a4Height = 841.89; + const a4Width = 595.28; + let overrideWidth; + let container; + let containerCSS; + let containerWidth; + let elements; + let excludeClassNames; + let excludeTagNames; + let filename; + let filterFn; + let innerRatio; + let overlay; + let overlayCSS; + let pageHeightPx; + let proxyUrl; + let compression = 'NONE'; + let scale; + let opts; + let offsetHeight; + let offsetWidth; + let scaleObj; + let style; + const transformOrigin = 'top left'; + const pdfOptions = { + orientation: 'l', + unit: 'pt', + format: 'a4' + }; + + ({filename, excludeClassNames = [], excludeTagNames = ['button', 'input', 'select'], overrideWidth, proxyUrl, compression, scale} = options); + + overlayCSS = { + position: 'fixed', + zIndex: 1000, + opacity: 0, + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: 'rgba(0,0,0,0.8)' + }; + if (overrideWidth) { + overlayCSS.width = `${overrideWidth}px`; + } + containerCSS = { + position: 'absolute', + left: 0, + right: 0, + top: 0, + height: 'auto', + margin: 'auto', + overflow: 'auto', + backgroundColor: 'white' + }; + overlay = _createElement('div', { + style: overlayCSS + }); + container = _createElement('div', { + style: containerCSS + }); + container.appendChild(_cloneNode(dom)); + overlay.appendChild(container); + document.body.appendChild(overlay); + innerRatio = a4Height / a4Width; + containerWidth = overrideWidth || container.getBoundingClientRect().width; + pageHeightPx = Math.floor(containerWidth * innerRatio); + elements = container.querySelectorAll('*'); + + for (let i = 0, len = excludeClassNames.length; i < len; i++) { + const clName = excludeClassNames[i]; + container.querySelectorAll(`.${clName}`).forEach(function(a) { + return a.remove(); + }); + } + + for (let j = 0, len1 = excludeTagNames.length; j < len1; j++) { + const tName = excludeTagNames[j]; + let els = container.getElementsByTagName(tName); + + for (let k = els.length - 1; k >= 0; k--) { + if (!els[k]) { + continue; + } + els[k].parentNode.removeChild(els[k]); + } + } + + Array.prototype.forEach.call(elements, el => { + let clientRect; + let endPage; + let nPages; + let pad; + let rules; + let startPage; + rules = { + before: false, + after: false, + avoid: true + }; + clientRect = el.getBoundingClientRect(); + if (rules.avoid && !rules.before) { + startPage = Math.floor(clientRect.top / pageHeightPx); + endPage = Math.floor(clientRect.bottom / pageHeightPx); + nPages = Math.abs(clientRect.bottom - clientRect.top) / pageHeightPx; + // Turn on rules.before if the el is broken and is at most one page long. + if (endPage !== startPage && nPages <= 1) { + rules.before = true; + } + // Before: Create a padding div to push the element to the next page. + if (rules.before) { + pad = _createElement('div', { + style: { + display: 'block', + height: `${pageHeightPx - clientRect.top % pageHeightPx}px` + } + }); + return el.parentNode.insertBefore(pad, el); + } + } + }); + + // Remove unnecessary elements from result pdf + filterFn = ({classList, tagName}) => { + let cName; + let j; + let len; + let ref; + if (classList) { + for (j = 0, len = excludeClassNames.length; j < len; j++) { + cName = excludeClassNames[j]; + if (Array.prototype.indexOf.call(classList, cName) >= 0) { + return false; + } + } + } + ref = tagName != null ? tagName.toLowerCase() : undefined; + return excludeTagNames.indexOf(ref) < 0; + }; + + opts = { + filter: filterFn, + proxy: proxyUrl + }; + + if (scale) { + offsetWidth = container.offsetWidth; + offsetHeight = container.offsetHeight; + style = { + transform: 'scale(' + scale + ')', + transformOrigin: transformOrigin, + width: offsetWidth + 'px', + height: offsetHeight + 'px' + }; + scaleObj = { + width: offsetWidth * scale, + height: offsetHeight * scale, + quality: 1, + style: style + }; + opts = Object.assign(opts, scaleObj); + } + + return domToImage.toCanvas(container, opts).then(canvas => { + let h; + let imgData; + let nPages; + let page; + let pageCanvas; + let pageCtx; + let pageHeight; + let pdf; + let pxFullHeight; + let w; + // Remove overlay + document.body.removeChild(overlay); + // Initialize the PDF. + pdf = new jsPDF(pdfOptions); + // Calculate the number of pages. + pxFullHeight = canvas.height; + nPages = Math.ceil(pxFullHeight / pageHeightPx); + // Define pageHeight separately so it can be trimmed on the final page. + pageHeight = a4Height; + pageCanvas = document.createElement('canvas'); + pageCtx = pageCanvas.getContext('2d'); + pageCanvas.width = canvas.width; + pageCanvas.height = pageHeightPx; + page = 0; + while (page < nPages) { + if (page === nPages - 1 && pxFullHeight % pageHeightPx !== 0) { + pageCanvas.height = pxFullHeight % pageHeightPx; + pageHeight = pageCanvas.height * a4Width / pageCanvas.width; + } + w = pageCanvas.width; + h = pageCanvas.height; + pageCtx.fillStyle = 'white'; + pageCtx.fillRect(0, 0, w, h); + pageCtx.drawImage(canvas, 0, page * pageHeightPx, w, h, 0, 0, w, h); + // Don't create blank pages + if (_isCanvasBlank(pageCanvas)) { + ++page; + continue; + } + // Add the page to the PDF. + if (page) { + pdf.addPage(); + } + imgData = pageCanvas.toDataURL('image/PNG'); + pdf.addImage(imgData, 'PNG', 0, 0, a4Width, pageHeight, undefined, compression); + ++page; + } + if (typeof cb === "function") { + cb(pdf); + } + return pdf.save(filename); + }).catch(error => { + // Remove overlay + document.body.removeChild(overlay); + if (typeof cb === "function") { + cb(null); + } + return console.error(error); + }); +}; + +module.exports = downloadPdf; \ No newline at end of file diff --git a/package.json b/package.json index 3730cb8e77..d4b4eed238 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,6 @@ "isomorphic-git": "^1.8.2", "jquery": "^3.3.1", "js-yaml": "^4.1.0", - "jspdf": "^2.5.1", "jszip": "^3.6.0", "latest-version": "^5.1.0", "merge": "^2.1.1", @@ -195,7 +194,6 @@ "signale": "^1.4.0", "sol2uml": "^2.4.2", "string-similarity": "^4.0.4", - "svg2pdf.js": "^2.2.1", "swarmgw": "^0.3.1", "time-stamp": "^2.2.0", "toml": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index bf28827bcb..94e6914bf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11924,9 +11924,9 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -"dom-to-image@git+https://github.com/dmapper/dom-to-image.git": +"dom-to-image@https://github.com/dmapper/dom-to-image": version "2.6.0" - resolved "git+https://github.com/dmapper/dom-to-image.git#a7c386a8ea813930f05449ac71ab4be0c262dff3" + resolved "https://github.com/dmapper/dom-to-image#a7c386a8ea813930f05449ac71ab4be0c262dff3" dom-to-pdf@^0.3.1: version "0.3.1" @@ -13816,11 +13816,6 @@ follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -font-family-papandreou@^0.2.0-patch1: - version "0.2.0-patch2" - resolved "https://registry.yarnpkg.com/font-family-papandreou/-/font-family-papandreou-0.2.0-patch2.tgz#c75b659e96ffbc7ab2af651cf7b4910b334e8dd2" - integrity sha512-l/YiRdBSH/eWv6OF3sLGkwErL+n0MqCICi9mppTZBOCL5vixWGDqCYvRcuxB2h7RGCTzaTKOHT2caHvCXQPRlw== - for-each@~0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -24828,11 +24823,6 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -specificity@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" - integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== - split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -25432,16 +25422,6 @@ svg-pathdata@^6.0.3: resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac" integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== -svg2pdf.js@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/svg2pdf.js/-/svg2pdf.js-2.2.1.tgz#fa81849be57c5a405c8394d35e4a4ea8aaaebeb5" - integrity sha512-gJsFT42tb+pYTuFudkKgpMws54DvsJW7wmzGRUY1b9CUJpRMoBU5B4HrCMUTlK2lpcdPL5cOyr84hy2BEj1/Ag== - dependencies: - cssesc "^3.0.0" - font-family-papandreou "^0.2.0-patch1" - specificity "^0.4.1" - svgpath "^2.3.0" - svgo@^2.7.0, svgo@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" @@ -25455,11 +25435,6 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" -svgpath@^2.3.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.6.0.tgz#5b160ef3d742b7dfd2d721bf90588d3450d7a90d" - integrity sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg== - swarm-js@^0.1.40: version "0.1.40" resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99"