├── .gitattributes ├── .gitignore ├── README.md ├── assets ├── css │ ├── app.css │ └── form.css ├── images │ └── ccac_page1.png └── js │ └── main.js ├── docs └── screen.png └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PDF Editor 2 | 3 |  4 | 5 | ## Motivation 6 | Creating PDF forms with Flying Saucer was always a pain, especially with CSS2 only support. Frequently used CSS attributes like border-radius are not available, and development speed is slow because it doesn't support HTML previewing of the PDFs. The solution is to treat the form as a background image, and place all text fields and checkboxes on top of it, essentially reducing the need of coding the form with CSS. 7 | 8 | ## Demo 9 | A demo version is available in https://pages.choy.in/pdf-editor. You can do basic functions such as positioning new textareas, input boxes, checkboxes, and select a different background form to base off of. 10 | -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: grey; 3 | } 4 | 5 | .toolbar { 6 | position: fixed; 7 | height: 550px; 8 | width: 61px; 9 | background-color: #d2d2d2; 10 | right: 538px; 11 | top: 36px; 12 | border-radius: 10px; 13 | } 14 | 15 | .action-group { 16 | height: 60px; 17 | width: 170px; 18 | } 19 | 20 | .action-button { 21 | border: 1px solid black; 22 | border-radius: 28px; 23 | height: 41px; 24 | width: 41px; 25 | font-size: 26px; 26 | font-weight: bold; 27 | margin: 10px; 28 | background-color: #91ff00; 29 | cursor: pointer; 30 | } 31 | 32 | .active-tool { 33 | background-color: red; 34 | } 35 | 36 | .selected { 37 | background-color: red; 38 | } 39 | 40 | .bordered { 41 | border: 1px solid blue; 42 | } 43 | 44 | .action-text { 45 | display: inline-block; 46 | color: white; 47 | } 48 | 49 | .output { 50 | position: fixed; 51 | right: 10px; 52 | background-color: red; 53 | height: 340px; 54 | width: 402px; 55 | top: 10px; 56 | } 57 | 58 | .output textarea { 59 | height: 100%; 60 | width: 100%; 61 | } 62 | -------------------------------------------------------------------------------- /assets/css/form.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | margin: 0; 3 | height: 1200px; 4 | } 5 | 6 | .page1 { 7 | background-image: url("../../assets/images/ccac_page1.png"); 8 | background-repeat: no-repeat; 9 | height: 100%; 10 | width: ; 11 | } 12 | 13 | .page1 p { 14 | position: absolute; 15 | font-size: 15px; 16 | padding: 0; 17 | margin: 0; 18 | } 19 | -------------------------------------------------------------------------------- /assets/images/ccac_page1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choyiny/pdf-editor/8d192feb492ba0443ed2010016d79b11fcb29b81/assets/images/ccac_page1.png -------------------------------------------------------------------------------- /assets/js/main.js: -------------------------------------------------------------------------------- 1 | var DEFAULT_TEXT = 'care_plan_fields[""]'; 2 | $(document).ready(function () { 3 | // Load document from storage if available 4 | loadDocument(); 5 | registerPageClickEvent(); 6 | setActiveTool("pointer"); 7 | 8 | $("#text_default").val(DEFAULT_TEXT); 9 | 10 | $( ".draggable" ).draggable(); 11 | 12 | // add new textbox and make it draggable 13 | $("#new-text").click(function () { 14 | addTextbox(100, 100); 15 | }); 16 | 17 | $("#reset").click(function () { 18 | $(".exportable").html('
'); 19 | exportThis(); 20 | registerPageClickEvent(); 21 | }); 22 | 23 | // export all the positions of the new textboxes 24 | $("#export").click(function () { 25 | exportThis(); 26 | }); 27 | 28 | $("#pointertool").click(function () { 29 | setActiveTool("pointer"); 30 | $(".exportable").css("cursor", "default"); 31 | }); 32 | 33 | $("#checktool").click(function () { 34 | setActiveTool("check"); 35 | $(".exportable").css("cursor", "crosshair"); 36 | }); 37 | 38 | $("#boxtool").click(function () { 39 | setActiveTool("box"); 40 | $(".exportable").css("cursor", "crosshair"); 41 | }); 42 | 43 | $("#deletetool").click(function () { 44 | setActiveTool("delete"); 45 | $(".exportable").css("cursor", "default"); 46 | 47 | // Click twice if necessary as to refresh the bounds for any new items added 48 | $("#showbounds").click(); 49 | if (!$("#showbounds").hasClass("selected")) { 50 | $("#showbounds").click(); 51 | } 52 | }); 53 | 54 | $("#areatool").click(function () { 55 | setActiveTool("area"); 56 | $(".exportable").css("cursor", "crosshair"); 57 | }); 58 | 59 | $("#showbounds").click(function () { 60 | if ($("#showbounds").hasClass("selected")) { 61 | $("#showbounds").removeClass("selected"); 62 | $(".textfield").removeClass("bordered"); 63 | } else { 64 | $("#showbounds").addClass("selected"); 65 | $(".textfield").addClass("bordered"); 66 | } 67 | }); 68 | 69 | $("#loadimg").click(function () { 70 | $(".page1").css("background-image", "url(" + $("#imgfield").val() + ")"); 71 | }); 72 | 73 | $("#use_text").click(function () { 74 | DEFAULT_TEXT = $("#text_default").val(); 75 | }); 76 | 77 | $("#defaultimg").click(function () { 78 | $(".page1").css("background-image", 'url("../assets/images/ccac_page1.png")') 79 | }); 80 | }); 81 | 82 | function setActiveTool(tool) { 83 | $(".tool").removeClass("active-tool").removeAttr("clicknum"); 84 | $("#" + tool + "tool").addClass("active-tool"); 85 | } 86 | 87 | $("#browseimg").change(function() { 88 | if(this.files[0]) { 89 | var reader = new FileReader(); 90 | reader.onload = function(e) { 91 | $(".page1").css("background-image", "url(" + e.target.result + ")"); 92 | }; 93 | reader.readAsDataURL(this.files[0]) 94 | } 95 | }); 96 | 97 | function getActiveTool() { 98 | return $(".active-tool")[0].id; 99 | } 100 | 101 | function registerPageClickEvent() { 102 | $(".page1").click(function (event) { 103 | switch(getActiveTool()) { 104 | case "areatool": 105 | if (event.target == this) { 106 | if ($("#areatool").attr("clicknum") != 1) { 107 | // First click -- set top left corner 108 | $("#areatool").attr({"top": event.pageY, "left": event.pageX, "clicknum": 1}); 109 | $(".exportable").css("cursor", "cell"); 110 | } else { 111 | // Second click -- create textbox 112 | $("#areatool").attr("clicknum", 0); 113 | var top = $("#areatool").attr("top"); 114 | var left = $("#areatool").attr("left"); 115 | addTextArea(top, left, event.pageY, event.pageX); 116 | $(".exportable").css("cursor", "crosshair"); 117 | } 118 | } 119 | break; 120 | case "deletetool": 121 | if (event.target.tagName == "P") { 122 | event.target.remove(); 123 | } 124 | break; 125 | case "boxtool": 126 | if (event.target == this) { 127 | addTextbox(event.pageY, event.pageX); 128 | } 129 | break; 130 | case "checktool": 131 | if (event.target == this) { 132 | // Offset to center the checkbox 133 | tickCheckbox(event.pageY - 8, event.pageX - 5); 134 | } 135 | break; 136 | } 137 | exportThis(); 138 | }); 139 | } 140 | 141 | function tickCheckbox(top, left) { 142 | var tickHtml = "\n✓
" 143 | $(".page1").append(tickHtml); 144 | $(".draggable").draggable(); 145 | } 146 | 147 | function addTextbox(top, left) { 148 | var textboxHTML = "\n"+ DEFAULT_TEXT +"
" 149 | $(".page1").append(textboxHTML); 150 | $(".draggable").draggable(); 151 | } 152 | 153 | function addTextArea(top, left, bottom, right) { 154 | var width = right - left; 155 | var height = bottom - top; 156 | var textAreaHTML = "\nNew Text Area
" 157 | $(".page1").append(textAreaHTML); 158 | $(".draggable").draggable(); 159 | } 160 | 161 | function loadDocument() { 162 | var doc = localStorage.getItem("document"); 163 | if (doc) { 164 | $(".exportable").html(doc); 165 | exportThis(); 166 | } 167 | } 168 | 169 | function saveDocument() { 170 | localStorage.setItem("document", $(".exportable").html()); 171 | } 172 | 173 | // Cleans all non-style related tags from the html to output 174 | function cleanHTML(element) { 175 | var divNode = element.clone(); 176 | divNode.find("div").removeAttr("style").find("p").removeAttr("class").removeAttr("contenteditable"); 177 | divNode.find("p").map(function (index, element) { 178 | element.innerHTML = "<%= " + element.innerHTML + " %>" 179 | }); 180 | return divNode[0].innerHTML.replace(new RegExp("<", "g"), "<") 181 | .replace(new RegExp(">", "g"), ">") 182 | .replace(new RegExp("\n{2,}", "g"), "\n"); 183 | } 184 | 185 | function exportThis() { 186 | saveDocument(); 187 | $(".output textarea").val(cleanHTML($(".exportable"))); 188 | } 189 | -------------------------------------------------------------------------------- /docs/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choyiny/pdf-editor/8d192feb492ba0443ed2010016d79b11fcb29b81/docs/screen.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 47 |