diff --git a/src/core.js b/src/core.js index 09d665972..fa9747c72 100644 --- a/src/core.js +++ b/src/core.js @@ -265,46 +265,87 @@ var Page = (function pagePage() { } }, 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 annotations = xref.fetchIfRef(this.annotations) || []; var i, n = annotations.length; - var links = []; + var items = []; for (i = 0; i < n; ++i) { var annotation = xref.fetch(annotations[i]); if (!isDict(annotation)) continue; var subtype = annotation.get('Subtype'); - if (!isName(subtype) || subtype.name != 'Link') + if (!isName(subtype)) continue; var rect = annotation.get('Rect'); var topLeftCorner = this.rotatePoint(rect[0], rect[1]); var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); - var link = {}; - link.x = Math.min(topLeftCorner.x, bottomRightCorner.x); - link.y = Math.min(topLeftCorner.y, bottomRightCorner.y); - link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); - link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); - var a = this.xref.fetchIfRef(annotation.get('A')); - if (a) { - switch (a.get('S').name) { - case 'URI': - link.url = a.get('URI'); + var item = {}; + item.type = subtype.name; + item.x = Math.min(topLeftCorner.x, bottomRightCorner.x); + item.y = Math.min(topLeftCorner.y, bottomRightCorner.y); + item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); + item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); + switch (subtype.name) { + case 'Link': + var a = this.xref.fetchIfRef(annotation.get('A')); + 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 = annotation.get('FT'); + if (!isName(fieldType)) 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; + item.fieldType = fieldType.name; + var fieldName = []; + var name = stringToPDFString(annotation.get('T')); + if (name) + fieldName.push(name) + var parent = xref.fetchIfRef(annotation.get('Parent')); + while (parent) { + name = stringToPDFString(parent.get('T')); + if (name) + fieldName.unshift(name); + parent = xref.fetchIfRef(parent.get('Parent')); + } + item.fullName = fieldName.join('.'); + var alternativeText = stringToPDFString(annotation.get('TU') || ''); + item.alternativeText = alternativeText; + var da = annotation.get('DA') || ''; + var m = /([\d\.]+)\sTf/.exec(da); + if (m) + item.fontSize = parseFloat(m[1]); + item.flags = annotation.get('Ff') || 0; + break; } - links.push(link); + items.push(item); } - return links; + return items; }, startRendering: function pageStartRendering(ctx, callback) { this.ctx = ctx; @@ -342,6 +383,7 @@ var PDFDocModel = (function pdfDoc() { assertWellFormed(stream.length > 0, 'stream must have data'); this.stream = stream; this.setup(); + this.acroForm = this.xref.fetchIfRef(this.catalog.catDict.get('AcroForm')); } function find(stream, needle, limit, backwards) { diff --git a/web/viewer.css b/web/viewer.css index 27ad0638a..456d3eb94 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -231,6 +231,23 @@ canvas { -webkit-box-shadow: 0px 2px 10px #ff0; } +.page > .inputHint { + opacity: 0.2; + background: #ccc; + position: absolute; +} + +.page > .inputControl { + background: transparent; + border: 0px none; + position: absolute; + margin: auto; +} + +.page > .inputControl[type='checkbox'] { + margin: 0px; +} + #viewer { margin: 44px 0px 0px; padding: 8px 0px; @@ -281,6 +298,10 @@ canvas { display: block; page-break-after: always; } + + .inputHint { + display: none; + } } #loading { diff --git a/web/viewer.js b/web/viewer.js index 1ab2c555c..5d9e083d7 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -33,6 +33,7 @@ var PDFView = { thumbnails: [], currentScale: kDefaultScale, initialBookmark: document.location.hash.substring(1), + formFields: [], setScale: function pdfViewSetScale(val, resetAutoSettings) { var pages = this.pages; @@ -362,7 +363,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, div.removeAttribute('data-loaded'); }; - function setupLinks(content, scale) { + function setupAnnotations(content, scale) { function bindLink(link, dest) { link.href = PDFView.getDestinationHash(dest); link.onclick = function pageViewSetupLinksOnclick() { @@ -371,18 +372,82 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, return false; }; } + function bindInputItem(input, item) { + if (input.name in PDFView.formFields) { + var value = PDFView.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') + PDFView.formFields[input.name] = input.checked; + else if (!input.type || input.type == 'text') + PDFView.formFields[input.name] = input.value; + }; + } + 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; + } + function assignFontStyle(element, item) { + var fontStyles = ''; + if ('fontSize' in item) + fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px'; + element.setAttribute('style', element.getAttribute('style') + fontStyles); + } - var links = content.getLinks(); - for (var i = 0; i < links.length; i++) { - var link = document.createElement('a'); - link.style.left = (Math.floor(links[i].x - view.x) * scale) + 'px'; - link.style.top = (Math.floor(links[i].y - view.y) * scale) + 'px'; - link.style.width = Math.ceil(links[i].width * scale) + 'px'; - link.style.height = Math.ceil(links[i].height * scale) + 'px'; - link.href = links[i].url || ''; - if (!links[i].url) - bindLink(link, ('dest' in links[i]) ? links[i].dest : null); - div.appendChild(link); + var items = content.getAnnotations(); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'Link': + var link = createElementWithStyle('a', item); + link.href = item.url || ''; + if (!item.url) + bindLink(link, ('dest' in item) ? item.dest : null); + div.appendChild(link); + break; + 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'; + TODO('radio button is not supported'); + } else if (item.flags & 65536) { + input.type = 'button'; + TODO('pushbutton is not supported'); + } else { + input.type = 'checkbox'; + } + } + if (item.fieldType == 'Ch') { + input = createElementWithStyle('select', item); + TODO('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; + } } } @@ -489,7 +554,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, stats.begin = Date.now(); this.content.startRendering(ctx, this.updateStats); - setupLinks(this.content, this.scale); + setupAnnotations(this.content, this.scale); div.setAttribute('data-loaded', true); return true; @@ -738,6 +803,8 @@ window.addEventListener('pagechange', function pagechange(evt) { window.addEventListener('keydown', function keydown(evt) { var curElement = document.activeElement; + if (curElement && curElement.tagName == 'INPUT') + return; var controlsElement = document.getElementById('controls'); while (curElement) { if (curElement === controlsElement)