Merge pull request #749 from notmasteryet/forms-1
Simple AcroForms support
This commit is contained in:
commit
ca7d44c646
141
examples/acroforms/forms.js
Normal file
141
examples/acroforms/forms.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||||
|
|
||||||
|
//
|
||||||
|
// Basic AcroForms input controls rendering
|
||||||
|
//
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var formFields = {};
|
||||||
|
|
||||||
|
function setupForm(div, content, scale) {
|
||||||
|
function bindInputItem(input, item) {
|
||||||
|
if (input.name in formFields) {
|
||||||
|
var value = formFields[input.name];
|
||||||
|
if (input.type == 'checkbox')
|
||||||
|
input.checked = value;
|
||||||
|
else if (!input.type || input.type == 'text')
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
input.onchange = function pageViewSetupInputOnBlur() {
|
||||||
|
if (input.type == 'checkbox')
|
||||||
|
formFields[input.name] = input.checked;
|
||||||
|
else if (!input.type || input.type == 'text')
|
||||||
|
formFields[input.name] = input.value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function createElementWithStyle(tagName, item) {
|
||||||
|
var element = document.createElement(tagName);
|
||||||
|
element.style.left = (item.x * scale) + 'px';
|
||||||
|
element.style.top = (item.y * scale) + 'px';
|
||||||
|
element.style.width = Math.ceil(item.width * scale) + 'px';
|
||||||
|
element.style.height = Math.ceil(item.height * scale) + 'px';
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
function assignFontStyle(element, item) {
|
||||||
|
var fontStyles = '';
|
||||||
|
if ('fontSize' in item)
|
||||||
|
fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px;';
|
||||||
|
switch (item.textAlignment) {
|
||||||
|
case 0:
|
||||||
|
fontStyles += 'text-align: left;';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
fontStyles += 'text-align: center;';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
fontStyles += 'text-align: right;';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
element.setAttribute('style', element.getAttribute('style') + fontStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = content.getAnnotations();
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var item = items[i];
|
||||||
|
switch (item.type) {
|
||||||
|
case 'Widget':
|
||||||
|
if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
|
||||||
|
item.fieldType != 'Ch')
|
||||||
|
break;
|
||||||
|
var inputDiv = createElementWithStyle('div', item);
|
||||||
|
inputDiv.className = 'inputHint';
|
||||||
|
div.appendChild(inputDiv);
|
||||||
|
var input;
|
||||||
|
if (item.fieldType == 'Tx') {
|
||||||
|
input = createElementWithStyle('input', item);
|
||||||
|
}
|
||||||
|
if (item.fieldType == 'Btn') {
|
||||||
|
input = createElementWithStyle('input', item);
|
||||||
|
if (item.flags & 32768) {
|
||||||
|
input.type = 'radio';
|
||||||
|
// radio button is not supported
|
||||||
|
} else if (item.flags & 65536) {
|
||||||
|
input.type = 'button';
|
||||||
|
// pushbutton is not supported
|
||||||
|
} else {
|
||||||
|
input.type = 'checkbox';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.fieldType == 'Ch') {
|
||||||
|
input = createElementWithStyle('select', item);
|
||||||
|
// select box is not supported
|
||||||
|
}
|
||||||
|
input.className = 'inputControl';
|
||||||
|
input.name = item.fullName;
|
||||||
|
input.title = item.alternativeText;
|
||||||
|
assignFontStyle(input, item);
|
||||||
|
bindInputItem(input, item);
|
||||||
|
div.appendChild(input);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPage(div, pdf, pageNumber, callback) {
|
||||||
|
var page = pdf.getPage(pageNumber);
|
||||||
|
var scale = 1.5;
|
||||||
|
|
||||||
|
var pageDisplayWidth = page.width * scale;
|
||||||
|
var pageDisplayHeight = page.height * scale;
|
||||||
|
|
||||||
|
var pageDivHolder = document.createElement('div');
|
||||||
|
pageDivHolder.className = 'pdfpage';
|
||||||
|
pageDivHolder.style.width = pageDisplayWidth + 'px';
|
||||||
|
pageDivHolder.style.height = pageDisplayHeight + 'px';
|
||||||
|
div.appendChild(pageDivHolder);
|
||||||
|
|
||||||
|
// Prepare canvas using PDF page dimensions
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var context = canvas.getContext('2d');
|
||||||
|
canvas.width = pageDisplayWidth;
|
||||||
|
canvas.height = pageDisplayHeight;
|
||||||
|
pageDivHolder.appendChild(canvas);
|
||||||
|
|
||||||
|
|
||||||
|
// Render PDF page into canvas context
|
||||||
|
page.startRendering(context, callback);
|
||||||
|
|
||||||
|
// Prepare and populate form elements layer
|
||||||
|
var formDiv = document.createElement('div');
|
||||||
|
pageDivHolder.appendChild(formDiv);
|
||||||
|
|
||||||
|
setupForm(formDiv, page, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFJS.getPdf(pdfWithFormsPath, function getPdfForm(data) {
|
||||||
|
// Instantiate PDFDoc with PDF data
|
||||||
|
var pdf = new PDFJS.PDFDoc(data);
|
||||||
|
|
||||||
|
// Rendering all pages starting from first
|
||||||
|
var viewer = document.getElementById('viewer');
|
||||||
|
var pageNumber = 1;
|
||||||
|
renderPage(viewer, pdf, pageNumber++, function pageRenderingComplete() {
|
||||||
|
if (pageNumber > pdf.numPages)
|
||||||
|
return; // All pages rendered
|
||||||
|
// Continue rendering of the next page
|
||||||
|
renderPage(viewer, pdf, pageNumber++, pageRenderingComplete);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
52
examples/acroforms/index.html
Normal file
52
examples/acroforms/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- In production, only one script (pdf.js) is necessary -->
|
||||||
|
<!-- In production, change the content of PDFJS.workerSrc below -->
|
||||||
|
<script type="text/javascript" src="../../src/core.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/util.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/canvas.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/obj.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/function.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/charsets.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/cidmaps.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/colorspace.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/crypto.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/evaluator.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/fonts.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/glyphlist.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/image.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/metrics.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/parser.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/pattern.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/stream.js"></script>
|
||||||
|
<script type="text/javascript" src="../../src/worker.js"></script>
|
||||||
|
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Specify the main script used to create a new PDF.JS web worker.
|
||||||
|
// In production, change this to point to the combined `pdf.js` file.
|
||||||
|
PDFJS.workerSrc = '../../src/worker_loader.js';
|
||||||
|
|
||||||
|
// Specify the PDF with AcroForm here
|
||||||
|
var pdfWithFormsPath = '../../test/pdfs/f1040.pdf';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pdfpage { position:relative; top: 0; left: 0; border: solid 1px black; margin: 10px; }
|
||||||
|
.pdfpage > canvas { position: absolute; top: 0; left: 0; }
|
||||||
|
.pdfpage > div { position: absolute; top: 0; left: 0; }
|
||||||
|
.inputControl { background: transparent; border: 0px none; position: absolute; margin: auto; }
|
||||||
|
.inputControl[type='checkbox'] { margin: 0px; }
|
||||||
|
.inputHint { opacity: 0.2; background: #ccc; position: absolute; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="forms.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="viewer"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
120
src/core.js
120
src/core.js
@ -274,46 +274,115 @@ var Page = (function PageClosure() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getLinks: function pageGetLinks() {
|
getLinks: function pageGetLinks() {
|
||||||
|
var links = [];
|
||||||
|
var annotations = pageGetAnnotations();
|
||||||
|
var i, n = annotations.length;
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
if (annotations[i].type != 'Link')
|
||||||
|
continue;
|
||||||
|
links.push(annotations[i]);
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
},
|
||||||
|
getAnnotations: function pageGetAnnotations() {
|
||||||
var xref = this.xref;
|
var xref = this.xref;
|
||||||
|
function getInheritableProperty(annotation, name) {
|
||||||
|
var item = annotation;
|
||||||
|
while (item && !item.has(name)) {
|
||||||
|
item = xref.fetchIfRef(item.get('Parent'));
|
||||||
|
}
|
||||||
|
if (!item)
|
||||||
|
return null;
|
||||||
|
return item.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
var annotations = xref.fetchIfRef(this.annotations) || [];
|
var annotations = xref.fetchIfRef(this.annotations) || [];
|
||||||
var i, n = annotations.length;
|
var i, n = annotations.length;
|
||||||
var links = [];
|
var items = [];
|
||||||
for (i = 0; i < n; ++i) {
|
for (i = 0; i < n; ++i) {
|
||||||
var annotation = xref.fetch(annotations[i]);
|
var annotationRef = annotations[i];
|
||||||
|
var annotation = xref.fetch(annotationRef);
|
||||||
if (!isDict(annotation))
|
if (!isDict(annotation))
|
||||||
continue;
|
continue;
|
||||||
var subtype = annotation.get('Subtype');
|
var subtype = annotation.get('Subtype');
|
||||||
if (!isName(subtype) || subtype.name != 'Link')
|
if (!isName(subtype))
|
||||||
continue;
|
continue;
|
||||||
var rect = annotation.get('Rect');
|
var rect = annotation.get('Rect');
|
||||||
var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
|
var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
|
||||||
var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
|
var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
|
||||||
|
|
||||||
var link = {};
|
var item = {};
|
||||||
link.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
|
item.type = subtype.name;
|
||||||
link.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
|
item.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
|
||||||
link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
|
item.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
|
||||||
link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
|
item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
|
||||||
var a = this.xref.fetchIfRef(annotation.get('A'));
|
item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
|
||||||
if (a) {
|
switch (subtype.name) {
|
||||||
switch (a.get('S').name) {
|
case 'Link':
|
||||||
case 'URI':
|
var a = this.xref.fetchIfRef(annotation.get('A'));
|
||||||
link.url = a.get('URI');
|
if (a) {
|
||||||
|
switch (a.get('S').name) {
|
||||||
|
case 'URI':
|
||||||
|
link.url = a.get('URI');
|
||||||
|
break;
|
||||||
|
case 'GoTo':
|
||||||
|
link.dest = a.get('D');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
TODO('other link types');
|
||||||
|
}
|
||||||
|
} else if (annotation.has('Dest')) {
|
||||||
|
// simple destination link
|
||||||
|
var dest = annotation.get('Dest');
|
||||||
|
link.dest = isName(dest) ? dest.name : dest;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Widget':
|
||||||
|
var fieldType = getInheritableProperty(annotation, 'FT');
|
||||||
|
if (!isName(fieldType))
|
||||||
break;
|
break;
|
||||||
case 'GoTo':
|
item.fieldType = fieldType.name;
|
||||||
link.dest = a.get('D');
|
// Building the full field name by collecting the field and
|
||||||
break;
|
// its ancestors 'T' properties and joining them using '.'.
|
||||||
default:
|
var fieldName = [];
|
||||||
TODO('other link types');
|
var namedItem = annotation, ref = annotationRef;
|
||||||
}
|
while (namedItem) {
|
||||||
} else if (annotation.has('Dest')) {
|
var parentRef = namedItem.get('Parent');
|
||||||
// simple destination link
|
var parent = xref.fetchIfRef(parentRef);
|
||||||
var dest = annotation.get('Dest');
|
var name = namedItem.get('T');
|
||||||
link.dest = isName(dest) ? dest.name : dest;
|
if (name)
|
||||||
|
fieldName.unshift(stringToPDFString(name));
|
||||||
|
else {
|
||||||
|
// The field name is absent, that means more than one field
|
||||||
|
// with the same name may exist. Replacing the empty name
|
||||||
|
// with the '`' plus index in the parent's 'Kids' array.
|
||||||
|
// This is not in the PDF spec but necessary to id the
|
||||||
|
// the input controls.
|
||||||
|
var kids = xref.fetchIfRef(parent.get('Kids'));
|
||||||
|
var j, jj;
|
||||||
|
for (j = 0, jj = kids.length; j < jj; j++) {
|
||||||
|
if (kids[j].num == ref.num && kids[j].gen == ref.gen)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fieldName.unshift('`' + j);
|
||||||
|
}
|
||||||
|
namedItem = parent;
|
||||||
|
ref = parentRef;
|
||||||
|
}
|
||||||
|
item.fullName = fieldName.join('.');
|
||||||
|
var alternativeText = stringToPDFString(annotation.get('TU') || '');
|
||||||
|
item.alternativeText = alternativeText;
|
||||||
|
var da = getInheritableProperty(annotation, 'DA') || '';
|
||||||
|
var m = /([\d\.]+)\sTf/.exec(da);
|
||||||
|
if (m)
|
||||||
|
item.fontSize = parseFloat(m[1]);
|
||||||
|
item.textAlignment = getInheritableProperty(annotation, 'Q');
|
||||||
|
item.flags = getInheritableProperty(annotation, 'Ff') || 0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
links.push(link);
|
items.push(item);
|
||||||
}
|
}
|
||||||
return links;
|
return items;
|
||||||
},
|
},
|
||||||
startRendering: function pageStartRendering(ctx, callback, textLayer) {
|
startRendering: function pageStartRendering(ctx, callback, textLayer) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
@ -352,6 +421,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
|
|||||||
assertWellFormed(stream.length > 0, 'stream must have data');
|
assertWellFormed(stream.length > 0, 'stream must have data');
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.setup();
|
this.setup();
|
||||||
|
this.acroForm = this.xref.fetchIfRef(this.catalog.catDict.get('AcroForm'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function find(stream, needle, limit, backwards) {
|
function find(stream, needle, limit, backwards) {
|
||||||
|
@ -458,7 +458,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
|
|||||||
delete this.canvas;
|
delete this.canvas;
|
||||||
};
|
};
|
||||||
|
|
||||||
function setupLinks(content, scale) {
|
function setupAnnotations(content, scale) {
|
||||||
function bindLink(link, dest) {
|
function bindLink(link, dest) {
|
||||||
link.href = PDFView.getDestinationHash(dest);
|
link.href = PDFView.getDestinationHash(dest);
|
||||||
link.onclick = function pageViewSetupLinksOnclick() {
|
link.onclick = function pageViewSetupLinksOnclick() {
|
||||||
@ -467,18 +467,27 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function createElementWithStyle(tagName, item) {
|
||||||
|
var element = document.createElement(tagName);
|
||||||
|
element.style.left = (Math.floor(item.x - view.x) * scale) + 'px';
|
||||||
|
element.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
|
||||||
|
element.style.width = Math.ceil(item.width * scale) + 'px';
|
||||||
|
element.style.height = Math.ceil(item.height * scale) + 'px';
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
var links = content.getLinks();
|
var items = content.getAnnotations();
|
||||||
for (var i = 0; i < links.length; i++) {
|
for (var i = 0; i < items.length; i++) {
|
||||||
var link = document.createElement('a');
|
var item = items[i];
|
||||||
link.style.left = (Math.floor(links[i].x - view.x) * scale) + 'px';
|
switch (item.type) {
|
||||||
link.style.top = (Math.floor(links[i].y - view.y) * scale) + 'px';
|
case 'Link':
|
||||||
link.style.width = Math.ceil(links[i].width * scale) + 'px';
|
var link = createElementWithStyle('a', item);
|
||||||
link.style.height = Math.ceil(links[i].height * scale) + 'px';
|
link.href = item.url || '';
|
||||||
link.href = links[i].url || '';
|
if (!item.url)
|
||||||
if (!links[i].url)
|
bindLink(link, ('dest' in item) ? item.dest : null);
|
||||||
bindLink(link, ('dest' in links[i]) ? links[i].dest : null);
|
div.appendChild(link);
|
||||||
div.appendChild(link);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,7 +607,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
|
|||||||
}).bind(this), textLayer
|
}).bind(this), textLayer
|
||||||
);
|
);
|
||||||
|
|
||||||
setupLinks(this.content, this.scale);
|
setupAnnotations(this.content, this.scale);
|
||||||
div.setAttribute('data-loaded', true);
|
div.setAttribute('data-loaded', true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -889,6 +898,8 @@ window.addEventListener('pagechange', function pagechange(evt) {
|
|||||||
|
|
||||||
window.addEventListener('keydown', function keydown(evt) {
|
window.addEventListener('keydown', function keydown(evt) {
|
||||||
var curElement = document.activeElement;
|
var curElement = document.activeElement;
|
||||||
|
if (curElement && curElement.tagName == 'INPUT')
|
||||||
|
return;
|
||||||
var controlsElement = document.getElementById('controls');
|
var controlsElement = document.getElementById('controls');
|
||||||
while (curElement) {
|
while (curElement) {
|
||||||
if (curElement === controlsElement)
|
if (curElement === controlsElement)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user