Explorar el Código

Allow for iframe control

Alan Colon hace 7 años
padre
commit
c5b5c17f9e
Se han modificado 6 ficheros con 3139 adiciones y 219 borrados
  1. 3031 208
      package-lock.json
  2. 4 0
      package.json
  3. 8 0
      public/edit.css
  4. 65 1
      public/edit.js
  5. 1 1
      public/index.html
  6. 30 9
      server.js

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3031 - 208
package-lock.json


+ 4 - 0
package.json

@@ -9,7 +9,11 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "@alancnet/pdf2htmlex": "0.0.2",
+    "asfs": "^2.0.3",
     "body-parser": "^1.18.3",
+    "cors": "^2.8.4",
+    "data-urls": "^1.0.1",
     "express": "^4.16.3",
     "html-pdf-chrome": "^0.5.0",
     "multer": "^1.3.1",

+ 8 - 0
public/edit.css

@@ -1,6 +1,14 @@
 @media screen {
     body {
         caret-color: black;
+        background-color: black;
+    }
+
+    .saving {
+        opacity: .5;
+    }
+    .saving * {
+        pointer-events: none!important;
     }
 
     .c {

+ 65 - 1
public/edit.js

@@ -7,6 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
     const saveForm = document.getElementById('saveForm')
     const htmlInput = document.getElementById('htmlInput')
     const pageContainer = document.getElementById('page-container')
+    let host = null
 
     let mode = null
     const toggleMode = newMode => {
@@ -29,16 +30,60 @@ document.addEventListener('DOMContentLoaded', () => {
             const range = document.createRange()
             range.selectNode(image)
             selection.addRange(range)
-            console.log('selected?')
         })
     })
 
     saveButton.addEventListener('click', () => {
+        htmlInput.value = ""
         htmlInput.value = document.documentElement.outerHTML
         saveForm.submit()
+        htmlInput.value = ""
+    })
+
+    const save = () => new Promise((resolve, reject) => {
+        document.documentElement.classList.add('saving')
+        const req = new XMLHttpRequest()
+        req.open('POST', 'save', true)
+        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
+        req.responseType = 'arraybuffer'
+        htmlInput.value = ""
+        const body = `html=${encodeURIComponent(document.documentElement.outerHTML)}`
+
+        req.onload = () => {
+            resolve(req.response)
+            //const reader = new FileReader()
+            // reader.readAsDataURL(req.response)
+            // reader.addEventListener('load', () => {
+            //     resolve(reader.result)
+            // })
+            // reader.addEventListener('error', (ev) => {
+            //     reject(ev.error)
+            // })
+        }
+
+        req.onerror = (ev) => {
+            reject(ev.error)
+        }
+
+        req.send(body)
+        
+    }).finally(() => {
+        document.documentElement.classList.remove('saving')
     })
 
     // Get screen to print ratio
+    /*
+        The editing process deals with documents in several different dimensions and units of distance:
+        - Paper size (A4, Legal, etc)
+        - Print size (Font size in points, applied using the '@media print' rule)
+        - Screen size (Font size in pixels, applied using the '@media screen' rule)
+        - Zoom (Display transformation of the document preview, applied using transform-matrix)
+        - Browser size (1:1 ratio for the browser itself)
+
+        The following code reads evidence of these different values from the CSS, and determines how to transform from
+        browser vector space, to preview vector space, to print vector space, so our edits and the preview are accurately
+        represented in the resulting PDF.
+    */
     const localStyleSheets = Array.from(document.styleSheets).filter(x => !x.href)
     const pageOnePixelWidth = localStyleSheets.map(ss =>
         Array.from(ss.rules)
@@ -58,6 +103,7 @@ document.addEventListener('DOMContentLoaded', () => {
     const screenToPrintRatio = pageOnePrintPixelWidth / pageOnePixelWidth
     const printToScreenRatio = pageOnePixelWidth / pageOnePrintPixelWidth
 
+    // Replacements are constructed in print vector space, so they must be scaled down in screen.
     const myStyle = document.createElement('style')
     myStyle.type = 'text/css'
     myStyle.innerHTML = `
@@ -130,6 +176,12 @@ document.addEventListener('DOMContentLoaded', () => {
         r2.top > r1.bottom ||
         r2.bottom < r1.top)
 
+    /*
+        The common whiteout approach of drawing an opaque white box over sensitive content is quite ineffective. 
+        A user can simply select the text from behind the whiteout, and copy & paste it into another program.
+        This method finds every text character within the selected rectangle, and replaces it with a placeholder.
+        It also modifies the background image by drawing a white box, replacing the pixels within the rectangle.
+    */
     const whiteout = rect => {
         const elements = []
         const walk = (element) => {
@@ -187,6 +239,7 @@ document.addEventListener('DOMContentLoaded', () => {
                 element.src = canvas.toDataURL()
             }
         })
+        window.editor.changed = true
     }
 
     // Edit Text
@@ -198,6 +251,7 @@ document.addEventListener('DOMContentLoaded', () => {
         document.querySelectorAll('.pc').forEach(page => {
             page.setAttribute('contenteditable', 'true')
         })
+        window.editor.changed = true
     }
 
     const disableEditText = () => {
@@ -207,4 +261,14 @@ document.addEventListener('DOMContentLoaded', () => {
             page.setAttribute('contenteditable', 'false')
         })
     }
+
+    const attach = hostController => {
+        host = hostController
+    }
+
+    window.editor = {
+        attach,
+        save,
+        changed: false
+    }
 })

+ 1 - 1
public/index.html

@@ -3,7 +3,7 @@
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" />
     <body>
         <h1>PDF Editor</h1>
-        <form action="/edit" method="POST" enctype="multipart/form-data">
+        <form action="edit" method="POST" enctype="multipart/form-data">
             <div class="form-group">
                 <label>PDF File</label>
                 <input type="file" name="document" class="form-control" />

+ 30 - 9
server.js

@@ -1,29 +1,51 @@
 const fs = require('fs')
+const asfs = require('asfs')
 const express = require('express')
 const multer = require('multer')
 const childProcess = require('child_process')
 const bodyParser = require('body-parser')
-const PDF2HTMLEX_PATH = 'C:/Users/alan.colon/Downloads/pdf2htmlEX-win32-0.14.6-upx-with-poppler-data/pdf2htmlEX.exe'
+const pdf2htmlexPath = `${__dirname}/node_modules/@alancnet/pdf2htmlex/bin-win/pdf2htmlEX.exe`
 const phantom = require('phantom')
 const uuid = require('uuid')
+const parseDataUrl = require('data-urls')
+const cors = require('cors')
 
 const upload = multer({
     dest: 'temp/'
 })
 
 const app = express()
+app.use(cors({
+    origin: '*'
+}))
 app.use(bodyParser.urlencoded({extended: false, limit: '100mb'}))
 app.use(express.static('./public'))
 
-app.post('/edit', upload.single('document'), (req, res) => {
-    const pdfFile = req.file.path
-    const htmlFile = `${req.file.path}.html`
-    childProcess.exec(`"${PDF2HTMLEX_PATH}" --hdpi 200 --vdpi 200 --font-format ttf --no-drm 1 "${pdfFile}" "${htmlFile}"`, (err, stdout, stderr) => {
+app.post('/edit', upload.single('document'), async (req, res) => {
+    let pdfFile, htmlFile
+    if (req.file) {
+        pdfFile = req.file.path
+        htmlFile = `${req.file.path}.html`
+    } else if (req.body.url) {
+        pdfFile = `temp/${uuid()}.pdf`
+        htmlFile = `${pdfFile}.html`
+        const pdf = parseDataUrl(req.body.url)
+        await asfs.writeFileAsync(pdfFile, pdf.body)
+    }
+    /*
+        Executes pdf2htmlex[.exe] with:
+        - 200 horizontal and vertical DPI
+        - TrueType Font format (because woff does not render in PhantomJS)
+        - No DRM, overriding any PDF settings forbidding copying or modifying
+        - The path to the source PDF file
+        - The path to the output HTML file
+    */
+    childProcess.exec(`"${pdf2htmlexPath}" --hdpi 200 --vdpi 200 --font-format ttf --no-drm 1 "${pdfFile}" "${htmlFile}"`, (err, stdout, stderr) => {
         if (err) {
             res.status(500).send(`<pre>${err}\n\n${stdout}\n\n${stderr}</pre>`)
         } else {
             fs.readFile('public/edit.html', (err, editHtml) => {
-                fs.readFile(`${req.file.path}.html`, 'utf8', (err, data) => {
+                fs.readFile(htmlFile, 'utf8', (err, data) => {
                     if (err) {
                         res.status(500).send(`<pre>${err}\n\n${stdout}\n\n${stderr}</pre>`)
                     } else {
@@ -69,7 +91,7 @@ app.post('/save', (req, res) => {
                             console.error(err)
                             res.status(500).send(err)
                         }
-                        fs.unlink(htmlPath)
+                        //fs.unlink(htmlPath)
                         //fs.unlink(pdfPath)
                     })
                 }, 5000)
@@ -79,5 +101,4 @@ app.post('/save', (req, res) => {
         })
     })
 })
-app.listen('3003')
-console.log('http://localhost:3003')
+app.listen(process.env.PORT || 3000)

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio