diff --git a/package.json b/package.json index 74616d3..7c7849d 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,16 @@ "@rollup/plugin-node-resolve": "^7.0.0", "autoprefixer": "^9.7.6", "open": "^7.0.3", - "postcss": "^7.0.27", + "postcss": "^7.0.36", "postcss-load-config": "^2.1.0", - "rollup": "^1.20.0", - "rollup-plugin-livereload": "^1.0.0", - "rollup-plugin-svelte": "^5.0.3", - "rollup-plugin-terser": "^5.1.2", - "svelte": "^3.0.0", + "rollup": "^2.57.0", + "rollup-plugin-livereload": "^2.0.5", + "rollup-plugin-svelte": "^7.1.0", + "rollup-plugin-terser": "^7.0.2", + "svelte": "^3.42.6", "svelte-preprocess": "^3.7.4", "tailwindcss": "^1.3.5" + }, "dependencies": { "sirv-cli": "^0.4.4" diff --git a/public/index.html b/public/index.html index f725b3b..cf7c1c3 100644 --- a/public/index.html +++ b/public/index.html @@ -19,9 +19,9 @@ No Server, No Install, Mobile Friendly PDF Editor - PDF Editor - - - + + + diff --git a/public/overlay.svg b/public/overlay.svg new file mode 100644 index 0000000..85b7084 --- /dev/null +++ b/public/overlay.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/stroke.svg b/public/stroke.svg new file mode 100644 index 0000000..beb9fec --- /dev/null +++ b/public/stroke.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md index 4b288d6..0f47bdd 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ No install. No server. ¯\\\_(ツ)\_/¯ https://pdf-editor.now.sh ## How to use pdf-editor? 1. Click `Choose PDF` to upload a `.pdf` file. -2. Add images, signatures, text to your PDF. +2. Add images, signatures, text, freehand drawing to your PDF. 3. Click `Save`. 4. That's it! All is done **in your browser**. @@ -14,6 +14,7 @@ No install. No server. ¯\\\_(ツ)\_/¯ https://pdf-editor.now.sh - Resize and move everything. - Add signatures. +- Draw on page. - Adjust line height, font size, font family. - Mobile friendly. - Drag and drop to upload your PDF. diff --git a/rollup.config.js b/rollup.config.js index e66cf9e..4c74786 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -19,15 +19,17 @@ export default { plugins: [ svelte({ preprocess: sveltePreprocess({ postcss: true }), - // enable run-time checks when not in production - dev: !production, - // we'll extract any component CSS out into - // a separate file - better for performance - css: (css) => { - css.write('public/build/bundle.css'); - }, + emitCss: false, + compilerOptions: { + // enable run-time checks when not in production + dev: !production, + // we'll extract any component CSS out into + // a separate file - better for performance + css: (css) => { + css.write('public/build/bundle.css'); + } + } }), - resolve({ browser: true, dedupe: ['svelte'], @@ -49,12 +51,11 @@ function serve() { writeBundle() { if (!started) { started = true; - - require('child_process').spawn('yarn', ['start', '--dev'], { + require('child_process').spawn('npm', ['start', '--dev'], { stdio: ['ignore', 'inherit', 'inherit'], shell: true, }); - open('http://localhost:5000'); + open('http://localhost:5000/index.html'); } }, }; diff --git a/src/App.svelte b/src/App.svelte index b806b8a..e7ba214 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,363 +1,404 @@ - - - -
-
- - - + function updateObject(objectId, payload) { + allObjects = allObjects.map((objects, pIndex) => + pIndex == selectedPageIndex + ? objects.map(object => + object.id === objectId ? { ...object, ...payload } : object + ) + : objects + ); + } + function deleteObject(objectId) { + allObjects = allObjects.map((objects, pIndex) => + pIndex == selectedPageIndex + ? objects.filter(object => object.id !== objectId) + : objects + ); + } + function onMeasure(scale, i) { + pagesScale[i] = scale; + } + // FIXME: Should wait all objects finish their async work + async function savePDF() { + if (!pdfFile || saving || !pages.length) return; + saving = true; + try { + await save(pdfFile, allObjects, pdfName, pagesScale); + } catch (e) { + console.log(e); + } finally { + saving = false; + } + } + + + + +
- - - -
- - - - A GitHub icon leads to personal GitHub page - -
- {#if addingDrawing} -
- { - const { originWidth, originHeight, path } = e.detail; - let scale = 1; - if (originWidth > 500) { - scale = 500 / originWidth; - } - addDrawing(originWidth, originHeight, path, scale); - addingDrawing = false; - }} - on:cancel={() => (addingDrawing = false)} /> -
- {/if} - {#if pages.length} -
- a pen, edit pdf name + type="file" + name="pdf" + id="pdf" + on:change={onUploadPDF} + class="hidden" /> -
-
- {#each pages as page, pIndex (page)} + type="file" + id="image" + name="image" + class="hidden" + on:change={onUploadImage} /> + +
+ +
+ An icon for adding text +
+
+ An icon for adding drawing +
selectPage(pIndex)} - on:touchstart={() => selectPage(pIndex)}> + class="flex items-center justify-center h-full w-8 hover:bg-gray-500 + cursor-pointer" + on:click={onAddPageOverlay} + class:cursor-not-allowed={selectedPageIndex < 0} + class:bg-gray-500={selectedPageIndex < 0} + > + An icon for adding page overlay drawing +
+
+ + + + A GitHub icon leads to personal GitHub page + +
+ {#if addingDrawing} +
+ { + const { originWidth, originHeight, path, strokeWidth } = e.detail; + let scale = 1; + if (originWidth > 500) { + scale = 500 / originWidth; + } + addDrawing({originWidth, originHeight, path, scale, scalable: true, strokeWidth}); + addingDrawing = false; + }} + on:cancel={() => (addingDrawing = false)} /> +
+ {/if} + {#if pages.length} +
+ a pen, edit pdf name + +
+
+ {#each pages as page, pIndex (page)}
- onMeasure(e.detail.scale, pIndex)} - {page} /> + class="p-5 w-full flex flex-col items-center overflow-hidden" + on:mousedown={() => {addingPageOverlay || selectPage(pIndex)}} + on:touchstart={() => {addingPageOverlay || selectPage(pIndex)}}>
- {#each allObjects[pIndex] as object (object.id)} - {#if object.type === 'image'} - updateObject(object.id, e.detail)} - on:delete={() => deleteObject(object.id)} - file={object.file} - payload={object.payload} - x={object.x} - y={object.y} - width={object.width} - height={object.height} - pageScale={pagesScale[pIndex]} /> - {:else if object.type === 'text'} - updateObject(object.id, e.detail)} - on:delete={() => deleteObject(object.id)} - on:selectFont={selectFontFamily} - text={object.text} - x={object.x} - y={object.y} - size={object.size} - lineHeight={object.lineHeight} - fontFamily={object.fontFamily} - pageScale={pagesScale[pIndex]} /> - {:else if object.type === 'drawing'} - updateObject(object.id, e.detail)} - on:delete={() => deleteObject(object.id)} - path={object.path} - x={object.x} - y={object.y} - width={object.width} - originWidth={object.originWidth} - originHeight={object.originHeight} - pageScale={pagesScale[pIndex]} /> - {/if} - {/each} - + class="relative shadow-lg" + class:shadow-outline={pIndex === selectedPageIndex} + > + onMeasure(e.detail.scale, pIndex)} + {page} /> +
+ {#each allObjects[pIndex] as object (object.id)} + {#if object.type === "image"} + updateObject(object.id, e.detail)} + on:delete={() => deleteObject(object.id)} + file={object.file} + payload={object.payload} + x={object.x} + y={object.y} + width={object.width} + height={object.height} + pageScale={pagesScale[pIndex]} /> + {:else if object.type === "text"} + updateObject(object.id, e.detail)} + on:delete={() => deleteObject(object.id)} + on:selectFont={selectFontFamily} + text={object.text} + x={object.x} + y={object.y} + size={object.size} + lineHeight={object.lineHeight} + fontFamily={object.fontFamily} + pageScale={pagesScale[pIndex]} /> + {:else if object.type === "drawing"} + updateObject(object.id, e.detail)} + on:delete={() => deleteObject(object.id)} + path={object.path} + x={object.x} + y={object.y} + width={object.width} + originWidth={object.originWidth} + originHeight={object.originHeight} + strokeWidth={object.strokeWidth} + scalable={object.scalable} + pageScale={pagesScale[pIndex]} /> + {/if} + {/each} +
+ {#if addingPageOverlay && pIndex === selectedPageIndex} + { + const { originWidth, originHeight, path, strokeWidth } = e.detail; + let scale = 1; + addDrawing({originWidth, originHeight, path, scale, scalable: false, strokeWidth}); + addingPageOverlay = false; + }} + on:cancel={() => (addingPageOverlay = false)} /> + {/if}
-
- {/each} - - {:else} -
- Drag something here -
- {/if} -
+ {/each} + + {:else} +
+ {dict.DropPDF} +
+ {/if} + + \ No newline at end of file diff --git a/src/Drawing.svelte b/src/Drawing.svelte index fc527c5..4a15a44 100644 --- a/src/Drawing.svelte +++ b/src/Drawing.svelte @@ -9,6 +9,8 @@ export let y; export let pageScale = 1; export let path; + export let scalable = true; + export let strokeWidth = 5; const dispatch = createEventDispatcher(); let startX; let startY; @@ -92,7 +94,7 @@ class="absolute left-0 top-0 select-none" style="width: {width + dw}px; height: {(width + dw) / ratio}px; transform: translate({x + dx}px, {y + dy}px);"> -
-
-
+ {#if scalable} +
+
+ {/if}
- delete object + delete object
import { createEventDispatcher } from "svelte"; import { pannable } from "./utils/pannable.js"; + import {dict} from './lang.js' const dispatch = createEventDispatcher(); let canvas; let x = 0; let y = 0; let path = ""; + let strokeWidth = 5; let minX = Infinity; let maxX = 0; let minY = Infinity; @@ -51,7 +53,8 @@ originHeight: height, path: paths.reduce((acc, cur) => { return acc + cur[0] + (cur[1] + dx) + "," + (cur[2] + dy); - }, "") + }, ""), + strokeWidth: strokeWidth }); } function cancel() { @@ -69,20 +72,20 @@
- delete object + delete object
diff --git a/src/PageOverlay.svelte b/src/PageOverlay.svelte new file mode 100644 index 0000000..ff42e28 --- /dev/null +++ b/src/PageOverlay.svelte @@ -0,0 +1,126 @@ + + + +
+
+ Stroke width + +
+ + +
+
+
+ + + +
diff --git a/src/Text.svelte b/src/Text.svelte index 86d9136..0e57e34 100644 --- a/src/Text.svelte +++ b/src/Text.svelte @@ -174,7 +174,7 @@ class="h-full flex justify-center items-center bg-gray-300 border-b border-gray-400">
- Line height + Line height
- Font size + Font size
- Font family + Font family