From f3ee85efab5d081d6576afb8e6edda5e736805ac Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Wed, 2 Nov 2011 20:46:39 -0500 Subject: [PATCH 01/42] Simple AcroForms support --- src/core.js | 90 +++++++++++++++++++++++++++++++++++------------- web/viewer.css | 21 ++++++++++++ web/viewer.js | 93 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 167 insertions(+), 37 deletions(-) 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) From 3d36a05d7f4fbddbccae1c416f9905ff62c72de5 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Wed, 2 Nov 2011 22:00:33 -0500 Subject: [PATCH 02/42] Text alignment inside input controls --- src/core.js | 1 + web/viewer.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index fa9747c72..e410c9d52 100644 --- a/src/core.js +++ b/src/core.js @@ -340,6 +340,7 @@ var Page = (function pagePage() { var m = /([\d\.]+)\sTf/.exec(da); if (m) item.fontSize = parseFloat(m[1]); + item.textAlignment = annotation.get('Q'); item.flags = annotation.get('Ff') || 0; break; } diff --git a/web/viewer.js b/web/viewer.js index 5d9e083d7..0c9b6c2c9 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -398,7 +398,18 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, function assignFontStyle(element, item) { var fontStyles = ''; if ('fontSize' in item) - fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px'; + 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); } From b5ba29be1335c201d54c66e7bf6890324d97a8f7 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Wed, 2 Nov 2011 22:20:53 -0500 Subject: [PATCH 03/42] lint error fix --- src/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index e410c9d52..7e53df430 100644 --- a/src/core.js +++ b/src/core.js @@ -325,7 +325,7 @@ var Page = (function pagePage() { var fieldName = []; var name = stringToPDFString(annotation.get('T')); if (name) - fieldName.push(name) + fieldName.push(name); var parent = xref.fetchIfRef(annotation.get('Parent')); while (parent) { name = stringToPDFString(parent.get('T')); From 1d8a40b372580fc2e51e066d583602348676b21c Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 12 Nov 2011 15:39:23 -0600 Subject: [PATCH 04/42] Fixing inheritable forms input control properties --- src/core.js | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/core.js b/src/core.js index 231671542..6c447dafe 100644 --- a/src/core.js +++ b/src/core.js @@ -274,11 +274,22 @@ var Page = (function pagePage() { }, getAnnotations: function pageGetAnnotations() { 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 i, n = annotations.length; var items = []; for (i = 0; i < n; ++i) { - var annotation = xref.fetch(annotations[i]); + var annotationRef = annotations[i]; + var annotation = xref.fetch(annotationRef); if (!isDict(annotation)) continue; var subtype = annotation.get('Subtype'); @@ -315,30 +326,46 @@ var Page = (function pagePage() { } break; case 'Widget': - var fieldType = annotation.get('FT'); + var fieldType = getInheritableProperty(annotation, 'FT'); if (!isName(fieldType)) break; item.fieldType = fieldType.name; + // Building the full field name by collecting the field and + // its ancestors 'T' properties and joining them using '.'. 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')); + var namedItem = annotation, ref = annotationRef; + while (namedItem) { + var parentRef = namedItem.get('Parent'); + var parent = xref.fetchIfRef(parentRef); + var name = namedItem.get('T'); if (name) - fieldName.unshift(name); - parent = xref.fetchIfRef(parent.get('Parent')); + 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 = annotation.get('DA') || ''; + var da = getInheritableProperty(annotation, 'DA') || ''; var m = /([\d\.]+)\sTf/.exec(da); if (m) item.fontSize = parseFloat(m[1]); - item.textAlignment = annotation.get('Q'); - item.flags = annotation.get('Ff') || 0; + item.textAlignment = getInheritableProperty(annotation, 'Q'); + item.flags = getInheritableProperty(annotation, 'Ff') || 0; break; } items.push(item); From 6011b5b4db5597fff08d72e85293c23986dbc742 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 19 Dec 2011 20:00:50 -0600 Subject: [PATCH 05/42] Moving forms UI into examples --- examples/acroforms/forms.js | 141 ++++++++++++++++++++++++++++++++++ examples/acroforms/index.html | 51 ++++++++++++ web/viewer.css | 11 --- web/viewer.js | 67 ---------------- 4 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 examples/acroforms/forms.js create mode 100644 examples/acroforms/index.html diff --git a/examples/acroforms/forms.js b/examples/acroforms/forms.js new file mode 100644 index 000000000..6ec92766d --- /dev/null +++ b/examples/acroforms/forms.js @@ -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); + }); +}); + diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html new file mode 100644 index 000000000..d07121167 --- /dev/null +++ b/examples/acroforms/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/web/viewer.css b/web/viewer.css index 28418f290..8b2b0cc4d 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -238,17 +238,6 @@ canvas { position: absolute; } -.page > .inputControl { - background: transparent; - border: 0px none; - position: absolute; - margin: auto; -} - -.page > .inputControl[type='checkbox'] { - margin: 0px; -} - .textLayer { position: absolute; left: 0; diff --git a/web/viewer.js b/web/viewer.js index e52d56acd..e1ca9ee36 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -33,7 +33,6 @@ var PDFView = { thumbnails: [], currentScale: kDefaultScale, initialBookmark: document.location.hash.substring(1), - formFields: [], setScale: function pdfViewSetScale(val, resetAutoSettings) { var pages = this.pages; @@ -468,21 +467,6 @@ 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'; @@ -491,23 +475,6 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, 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++) { @@ -520,40 +487,6 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, 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; } } } From 8288b17edcd63543caee83a3131794ab86fe906f Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 19 Dec 2011 20:04:16 -0600 Subject: [PATCH 06/42] move inputHint to examples --- examples/acroforms/index.html | 1 + web/viewer.css | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html index d07121167..5fad4648a 100644 --- a/examples/acroforms/index.html +++ b/examples/acroforms/index.html @@ -39,6 +39,7 @@ .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; } diff --git a/web/viewer.css b/web/viewer.css index 8b2b0cc4d..a1ef92810 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -232,12 +232,6 @@ canvas { -webkit-box-shadow: 0px 2px 10px #ff0; } -.page > .inputHint { - opacity: 0.2; - background: #ccc; - position: absolute; -} - .textLayer { position: absolute; left: 0; @@ -341,10 +335,6 @@ canvas { display: block; page-break-after: always; } - - .inputHint { - display: none; - } } #loading { From b3fdfb230f26bbc1a17e871dfaac6240c7ce8cce Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Wed, 21 Dec 2011 14:15:18 -0500 Subject: [PATCH 07/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e5d2eeb3..da70a4f57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview From 4a661e17353d231b81aa7e1b50eba2e840d26c79 Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Wed, 21 Dec 2011 23:22:07 +0100 Subject: [PATCH 08/42] Implemented Comment and Check annotation. Correcting some typos in last commit --- src/core.js | 18 +++++++++++++++--- web/images/check.svg | 3 +++ web/images/comment.svg | 3 +++ web/viewer.css | 33 +++++++++++++++++++++++++++++++++ web/viewer.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 web/images/check.svg create mode 100644 web/images/comment.svg diff --git a/src/core.js b/src/core.js index 71c18f178..f78b4f7ec 100644 --- a/src/core.js +++ b/src/core.js @@ -323,10 +323,10 @@ var Page = (function PageClosure() { if (a) { switch (a.get('S').name) { case 'URI': - link.url = a.get('URI'); + item.url = a.get('URI'); break; case 'GoTo': - link.dest = a.get('D'); + item.dest = a.get('D'); break; default: TODO('other link types'); @@ -334,7 +334,7 @@ var Page = (function PageClosure() { } else if (annotation.has('Dest')) { // simple destination link var dest = annotation.get('Dest'); - link.dest = isName(dest) ? dest.name : dest; + item.dest = isName(dest) ? dest.name : dest; } break; case 'Widget': @@ -379,6 +379,18 @@ var Page = (function PageClosure() { item.textAlignment = getInheritableProperty(annotation, 'Q'); item.flags = getInheritableProperty(annotation, 'Ff') || 0; break; + + case 'Text': + var content = annotation.get('Contents'); + var title = annotation.get('T'); + item.content = (typeof content == 'string') ? stringToPDFString(content) : null; + item.title = (typeof title == 'string') ? stringToPDFString(title) : null; + item.name = annotation.get('Name').name; + break; + + default: + TODO('unimplemented annotation type: ' + subtype.name); + break; } items.push(item); } diff --git a/web/images/check.svg b/web/images/check.svg new file mode 100644 index 000000000..e0e1590a9 --- /dev/null +++ b/web/images/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/images/comment.svg b/web/images/comment.svg new file mode 100644 index 000000000..84feef1c8 --- /dev/null +++ b/web/images/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/viewer.css b/web/viewer.css index a1ef92810..3cfb163b0 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -247,6 +247,39 @@ canvas { line-height:1.3; } +.annotComment > div { + position: absolute; +} + +.annotComment > .annotImageComment { + background: transparent url('./images/text.svg') no-repeat left top; + background-size: 75% 75%; +} + +.annotComment > .annotImageCheck { + background: transparent url('./images/check.svg') no-repeat left top; + background-size: 75% 75%; +} + +.annotComment > .annotImage:hover { + cursor: pointer; + opacity: 0.7; +} + +.annotComment > .annotDetails { + display: none; + padding: 0.2em; + max-width: 20em; + background-color: #F1E47B; +} + +.annotComment > .annotDetails > h1 { + font-weight: normal; + font-size: 1.2em; + border-bottom: 1px solid #000000; + margin: 0px; +} + /* TODO: file FF bug to support ::-moz-selection:window-inactive so we can override the opaque grey background when the window is inactive; see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */ diff --git a/web/viewer.js b/web/viewer.js index 32b3101c2..eba6d93e6 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -475,6 +475,41 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, element.style.height = Math.ceil(item.height * scale) + 'px'; return element; } + function createCommentAnnotation(type, item) { + var annotContainer = document.createElement('section'); + annotContainer.className = 'annotComment'; + + var annotImage = createElementWithStyle('div', item); + annotImage.className = 'annotImage annotImage' + type; + var annotDetails = document.createElement('div'); + annotDetails.className = 'annotDetails'; + var annotTitle = document.createElement('h1'); + var annotContent = document.createElement('p'); + + annotDetails.style.left = (Math.floor(item.x - view.x + item.width) * scale) + 'px'; + annotDetails.style.top = (Math.floor(item.y - view.y) * scale) + 'px'; + annotTitle.textContent = item.title; + + if(!item.content) { + annotContent.style.display = 'none'; + } else { + annotContent.innerHTML = item.content.replace('\n', '
'); + annotImage.addEventListener('mouseover', function() { + this.nextSibling.style.display = 'block'; + }, true); + + annotImage.addEventListener('mouseout', function() { + this.nextSibling.style.display = 'none'; + }, true); + } + + annotDetails.appendChild(annotTitle); + annotDetails.appendChild(annotContent); + annotContainer.appendChild(annotImage); + annotContainer.appendChild(annotDetails); + + return annotContainer; + } var items = content.getAnnotations(); for (var i = 0; i < items.length; i++) { @@ -487,6 +522,13 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, bindLink(link, ('dest' in item) ? item.dest : null); div.appendChild(link); break; + + case 'Text': + case 'Check': + var comment = createCommentAnnotation(item.name, item); + if(comment) + div.appendChild(comment); + break; } } } From c714c782cc0bae35ea33e5915ed714c9b2978e60 Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Wed, 21 Dec 2011 23:37:52 +0100 Subject: [PATCH 09/42] Lint nits --- src/core.js | 6 +++--- web/viewer.js | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/core.js b/src/core.js index f78b4f7ec..e28ec4d21 100644 --- a/src/core.js +++ b/src/core.js @@ -383,12 +383,12 @@ var Page = (function PageClosure() { case 'Text': var content = annotation.get('Contents'); var title = annotation.get('T'); - item.content = (typeof content == 'string') ? stringToPDFString(content) : null; - item.title = (typeof title == 'string') ? stringToPDFString(title) : null; + item.content = stringToPDFString(content || ''); + item.title = stringToPDFString(title || ''); item.name = annotation.get('Name').name; break; - default: + default: TODO('unimplemented annotation type: ' + subtype.name); break; } diff --git a/web/viewer.js b/web/viewer.js index eba6d93e6..533a84dc3 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -476,7 +476,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, return element; } function createCommentAnnotation(type, item) { - var annotContainer = document.createElement('section'); + var annotContainer = document.createElement('section'); annotContainer.className = 'annotComment'; var annotImage = createElementWithStyle('div', item); @@ -486,20 +486,21 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, var annotTitle = document.createElement('h1'); var annotContent = document.createElement('p'); - annotDetails.style.left = (Math.floor(item.x - view.x + item.width) * scale) + 'px'; + var offsetPos = Math.floor(item.x - view.x + item.width); + annotDetails.style.left = (offsetPos * scale) + 'px'; annotDetails.style.top = (Math.floor(item.y - view.y) * scale) + 'px'; annotTitle.textContent = item.title; - if(!item.content) { + if (!item.content) { annotContent.style.display = 'none'; } else { annotContent.innerHTML = item.content.replace('\n', '
'); annotImage.addEventListener('mouseover', function() { - this.nextSibling.style.display = 'block'; + this.nextSibling.style.display = 'block'; }, true); annotImage.addEventListener('mouseout', function() { - this.nextSibling.style.display = 'none'; + this.nextSibling.style.display = 'none'; }, true); } @@ -526,7 +527,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, case 'Text': case 'Check': var comment = createCommentAnnotation(item.name, item); - if(comment) + if (comment) div.appendChild(comment); break; } From 12e2dcd775345c7c0909a8307e67ba87f7b8fa9f Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Thu, 22 Dec 2011 11:29:27 +0100 Subject: [PATCH 10/42] Addressing notmasteryet's comments --- src/core.js | 2 -- web/viewer.css | 20 ++++++++------------ web/viewer.js | 51 ++++++++++++++++++++++++-------------------------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/core.js b/src/core.js index e28ec4d21..93cbc72ac 100644 --- a/src/core.js +++ b/src/core.js @@ -379,7 +379,6 @@ var Page = (function PageClosure() { item.textAlignment = getInheritableProperty(annotation, 'Q'); item.flags = getInheritableProperty(annotation, 'Ff') || 0; break; - case 'Text': var content = annotation.get('Contents'); var title = annotation.get('T'); @@ -387,7 +386,6 @@ var Page = (function PageClosure() { item.title = stringToPDFString(title || ''); item.name = annotation.get('Name').name; break; - default: TODO('unimplemented annotation type: ' + subtype.name); break; diff --git a/web/viewer.css b/web/viewer.css index 3cfb163b0..0b64d9d86 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -251,29 +251,25 @@ canvas { position: absolute; } -.annotComment > .annotImageComment { - background: transparent url('./images/text.svg') no-repeat left top; - background-size: 75% 75%; +.annotComment > img { + position: absolute; } -.annotComment > .annotImageCheck { - background: transparent url('./images/check.svg') no-repeat left top; - background-size: 75% 75%; -} - -.annotComment > .annotImage:hover { +.annotComment > img:hover { cursor: pointer; opacity: 0.7; } -.annotComment > .annotDetails { - display: none; +.annotComment > div { padding: 0.2em; max-width: 20em; background-color: #F1E47B; + box-shadow: 0px 2px 10px #333; + -moz-box-shadow: 0px 2px 10px #333; + -webkit-box-shadow: 0px 2px 10px #333; } -.annotComment > .annotDetails > h1 { +.annotComment > div > h1 { font-weight: normal; font-size: 1.2em; border-bottom: 1px solid #000000; diff --git a/web/viewer.js b/web/viewer.js index 533a84dc3..1c8f6c03d 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -476,40 +476,39 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, return element; } function createCommentAnnotation(type, item) { - var annotContainer = document.createElement('section'); - annotContainer.className = 'annotComment'; - - var annotImage = createElementWithStyle('div', item); - annotImage.className = 'annotImage annotImage' + type; - var annotDetails = document.createElement('div'); - annotDetails.className = 'annotDetails'; - var annotTitle = document.createElement('h1'); - var annotContent = document.createElement('p'); + var container = document.createElement('section'); + container.className = 'annotComment'; + var image = createElementWithStyle('img', item); + image.src = './images/' + type.toLowerCase() + '.svg'; + var content = document.createElement('div'); + content.setAttribute('hidden', true); + var title = document.createElement('h1'); + var text = document.createElement('p'); var offsetPos = Math.floor(item.x - view.x + item.width); - annotDetails.style.left = (offsetPos * scale) + 'px'; - annotDetails.style.top = (Math.floor(item.y - view.y) * scale) + 'px'; - annotTitle.textContent = item.title; + content.style.left = (offsetPos * scale) + 'px'; + content.style.top = (Math.floor(item.y - view.y) * scale) + 'px'; + title.textContent = item.title; if (!item.content) { - annotContent.style.display = 'none'; + content.setAttribute('hidden', true); } else { - annotContent.innerHTML = item.content.replace('\n', '
'); - annotImage.addEventListener('mouseover', function() { - this.nextSibling.style.display = 'block'; - }, true); + text.innerHTML = item.content.replace('\n', '
'); + image.addEventListener('mouseover', function annotationImageOver() { + this.nextSibling.removeAttribute('hidden'); + }, false); - annotImage.addEventListener('mouseout', function() { - this.nextSibling.style.display = 'none'; - }, true); + image.addEventListener('mouseout', function annotationImageOut() { + this.nextSibling.setAttribute('hidden', true); + }, false); } - annotDetails.appendChild(annotTitle); - annotDetails.appendChild(annotContent); - annotContainer.appendChild(annotImage); - annotContainer.appendChild(annotDetails); + content.appendChild(title); + content.appendChild(text); + container.appendChild(image); + container.appendChild(content); - return annotContainer; + return container; } var items = content.getAnnotations(); @@ -523,9 +522,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, bindLink(link, ('dest' in item) ? item.dest : null); div.appendChild(link); break; - case 'Text': - case 'Check': var comment = createCommentAnnotation(item.name, item); if (comment) div.appendChild(comment); From 33e8d988c46bd9419f5a67b1ad8422802e7c5444 Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Thu, 22 Dec 2011 14:44:08 +0100 Subject: [PATCH 11/42] Image directory as a constant, adding myself to LICENSE file --- LICENSE | 1 + web/viewer.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index f8a848205..db52dec8e 100644 --- a/LICENSE +++ b/LICENSE @@ -9,6 +9,7 @@ Yury Delendik Kalervo Kujala Adil Allawi <@ironymark> + Jakob Miland Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/web/viewer.js b/web/viewer.js index 1c8f6c03d..e2ffcef29 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -11,7 +11,7 @@ var kCssUnits = 96.0 / 72.0; var kScrollbarPadding = 40; var kMinScale = 0.25; var kMaxScale = 4.0; - +var kImageDirectory = './images/'; var Cache = function cacheCache(size) { var data = []; @@ -480,7 +480,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, container.className = 'annotComment'; var image = createElementWithStyle('img', item); - image.src = './images/' + type.toLowerCase() + '.svg'; + image.src = kImageDirectory + type.toLowerCase() + '.svg'; var content = document.createElement('div'); content.setAttribute('hidden', true); var title = document.createElement('h1'); From d44f9f207437384e1ed7004e3843829a5fece0af Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Thu, 22 Dec 2011 22:29:01 +0100 Subject: [PATCH 12/42] Implemented Settings manager. Now remembering scroll positions --- src/core.js | 10 +++++++- src/obj.js | 2 +- web/viewer.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/core.js b/src/core.js index 93cbc72ac..ac140dafe 100644 --- a/src/core.js +++ b/src/core.js @@ -527,6 +527,14 @@ var PDFDocModel = (function PDFDocModelClosure() { this.startXRef, this.mainXRefEntriesOffset); this.catalog = new Catalog(this.xref); + + if(this.xref.trailer && this.xref.trailer.has('ID')) { + var fileID = ''; + this.xref.trailer.get('ID')[0].split('').forEach(function(el) { + fileID += Number(el.charCodeAt(0)).toString(16); + }); + this.fileID = fileID; + } }, get numPages() { var linearization = this.linearization; @@ -560,7 +568,7 @@ var PDFDoc = (function PDFDocClosure() { this.data = data; this.stream = stream; this.pdf = new PDFDocModel(stream); - + this.fileID = this.pdf.fileID; this.catalog = this.pdf.catalog; this.objs = new PDFObjects(); diff --git a/src/obj.js b/src/obj.js index 453014a91..c0e67efbb 100644 --- a/src/obj.js +++ b/src/obj.js @@ -273,7 +273,7 @@ var XRef = (function XRefClosure() { this.entries = []; this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); - + this.trailer = trailerDict; // prepare the XRef cache this.cache = []; diff --git a/web/viewer.js b/web/viewer.js index e2ffcef29..5e8ec773f 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -25,6 +25,61 @@ var Cache = function cacheCache(size) { }; }; +// Settings Manager - This is a utility for saving settings +// First we see if localStorage is available, which isn't pt. in FF due to bug #495747 +// If not, we use FUEL in FF and fallback to Cookies for other browsers. +(function(parent) { +var COOKIE_WORKS = (function() { + document.cookie = 'they=work'; + return document.cookie.length > 0; +})(); + +var LOCALSTORAGE_WORKS = (function() { + try { + if(typeof localStorage != 'undefined') { + return true; + } + } catch(e) { + return false; + } + return true; +})(); + +var extPrefix = 'extensions.uriloader@pdf.js'; + +var Settings = { + set: function(name, val) { + if(location.protocol == 'chrome:' && !LOCALSTORAGE_WORKS) { + Application.prefs.setValue(extPrefix + '.' + name, val); + } else if(LOCALSTORAGE_WORKS) { + localStorage.setItem(name, val); + } else if(COOKIE_WORKS) { + var cookieString = name + '=' + escape(val); + var expire = (new Date((new Date().getTime())+1000*60*60*24*365)).toGMTString(); + cookieString += '; expires='+expire; + document.cookie = cookieString; + } + }, + + get: function(name, defaultValue) { + if(location.protocol == 'chrome:' && !LOCALSTORAGE_WORKS) { + return Application.prefs.getValue(extPrefix + '.' + name, defaultValue); + } else if(LOCALSTORAGE_WORKS) { + return localStorage.getItem(name) || defaultValue; + } else if(COOKIE_WORKS) { + var res = document.cookie.match ( '(^|;) ?' + name + '=([^;]*)(;|$)' ); + if (res) { + return unescape(res[2]); + } else { + return fallback; + } + } + } +}; + +parent.Settings = Settings; +})(this); + var cache = new Cache(kCacheSize); var currentPageNumber = 1; @@ -292,6 +347,15 @@ var PDFView = { pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; } + var id = pdf.fileID; + if (id) { + var scroll = Settings.get(id + '.scroll', -1); + if (scroll != -1) { + setTimeout(function scrollWindow() { + window.scrollTo(0, scroll); + }, 0); + } + } this.pagesRefMap = pagesRefMap; this.destinations = pdf.catalog.destinations; this.setScale(scale || kDefaultScale, true); @@ -831,6 +895,10 @@ function updateViewarea() { window.addEventListener('scroll', function webViewerScroll(evt) { updateViewarea(); + var fileID; + if((fileID = PDFView.pages[0].content.pdf.fileID)) { + Settings.set(fileID+'.scroll', window.pageYOffset); + } }, true); @@ -888,7 +956,6 @@ window.addEventListener('change', function webViewerChange(evt) { // implemented in Firefox. var file = files[0]; fileReader.readAsBinaryString(file); - document.title = file.name; // URL does not reflect proper document location - hiding some icons. From 0de0e92bc4fa1c09c19fdf4178e589a18ee46051 Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Thu, 22 Dec 2011 23:44:42 +0100 Subject: [PATCH 13/42] Added #getFingerprint method to PDFDocModel --- src/core.js | 17 ++++++++++++++++- web/viewer.js | 8 ++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/core.js b/src/core.js index ac140dafe..664ecf33c 100644 --- a/src/core.js +++ b/src/core.js @@ -542,6 +542,21 @@ var PDFDocModel = (function PDFDocModelClosure() { // shadow the prototype getter return shadow(this, 'numPages', num); }, + getFingerprint: function pdfDocGetFingerprint() { + if(this.fileID) { + return this.fileID; + } else { + // If we got no fileID, then we generate one, from the first 100 bytes of PDF + var data = this.stream.bytes.subarray(0, 100); + var hash = calculateMD5(data, 0, data.length); + var strHash = ''; + for(var i = 0, length = hash.length; i < length; i++) { + strHash += Number(hash[i]).toString(16); + } + + return strHash; + } + }, getPage: function pdfDocGetPage(n) { return this.catalog.getPage(n); } @@ -568,7 +583,7 @@ var PDFDoc = (function PDFDocClosure() { this.data = data; this.stream = stream; this.pdf = new PDFDocModel(stream); - this.fileID = this.pdf.fileID; + this.fingerprint = this.pdf.getFingerprint(); this.catalog = this.pdf.catalog; this.objs = new PDFObjects(); diff --git a/web/viewer.js b/web/viewer.js index 5e8ec773f..d4afa8faa 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -347,7 +347,7 @@ var PDFView = { pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; } - var id = pdf.fileID; + var id = pdf.fingerprint; if (id) { var scroll = Settings.get(id + '.scroll', -1); if (scroll != -1) { @@ -895,9 +895,9 @@ function updateViewarea() { window.addEventListener('scroll', function webViewerScroll(evt) { updateViewarea(); - var fileID; - if((fileID = PDFView.pages[0].content.pdf.fileID)) { - Settings.set(fileID+'.scroll', window.pageYOffset); + var id; + if((id = PDFView.pages[0].content.pdf.fingerprint)) { + Settings.set(id+'.scroll', window.pageYOffset); } }, true); From d7754a402e726c15275c3580a053cb9a51ca6f87 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Thu, 22 Dec 2011 17:43:14 -0600 Subject: [PATCH 14/42] Correct stroke width for text; convert intel-load test to eq-test --- src/canvas.js | 3 +++ test/test_manifest.json | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index cd49c88b1..00858c937 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -672,6 +672,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.translate(current.x, current.y); ctx.scale(textHScale, 1); + ctx.lineWidth /= current.textMatrix[0]; if (textSelection) { this.save(); @@ -708,6 +709,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else { ctx.save(); this.applyTextTransforms(); + ctx.lineWidth /= current.textMatrix[0] * fontMatrix[0]; + if (textSelection) text.geom = this.getTextGeometry(); diff --git a/test/test_manifest.json b/test/test_manifest.json index 5b88b3136..5a1efd75d 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -17,12 +17,13 @@ "rounds": 1, "type": "load" }, - { "id": "intelisa-load", + { "id": "intelisa-eq", "file": "pdfs/intelisa.pdf", "md5": "f5712097d29287a97f1278839814f682", "link": true, + "pageLimit": 100, "rounds": 1, - "type": "load" + "type": "eq" }, { "id": "pdfspec-load", "file": "pdfs/pdf.pdf", From ac8f0e2c87b66f2ff51bf0b4ef5a1a60814a6c4c Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Fri, 23 Dec 2011 23:36:37 +0100 Subject: [PATCH 15/42] Address yury's comments, and remove unnecessary hash settings --- web/viewer.js | 100 +++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 55 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index d4afa8faa..4eeb3a46e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -28,57 +28,49 @@ var Cache = function cacheCache(size) { // Settings Manager - This is a utility for saving settings // First we see if localStorage is available, which isn't pt. in FF due to bug #495747 // If not, we use FUEL in FF and fallback to Cookies for other browsers. -(function(parent) { -var COOKIE_WORKS = (function() { - document.cookie = 'they=work'; - return document.cookie.length > 0; -})(); +var Settings = (function settingsClosure() { + var isCookiesEnabled = (function() { + document.cookie = 'they=work'; + return document.cookie.length > 0; + })(); -var LOCALSTORAGE_WORKS = (function() { - try { - if(typeof localStorage != 'undefined') { - return true; + var isLocalStorageEnabled = (function localStorageEnabledTest() { + try { + localStorage; + } catch(e) { + return false; } - } catch(e) { - return false; - } - return true; -})(); + return true; + })(); -var extPrefix = 'extensions.uriloader@pdf.js'; + var extPrefix = 'extensions.uriloader@pdf.js'; -var Settings = { - set: function(name, val) { - if(location.protocol == 'chrome:' && !LOCALSTORAGE_WORKS) { - Application.prefs.setValue(extPrefix + '.' + name, val); - } else if(LOCALSTORAGE_WORKS) { - localStorage.setItem(name, val); - } else if(COOKIE_WORKS) { - var cookieString = name + '=' + escape(val); - var expire = (new Date((new Date().getTime())+1000*60*60*24*365)).toGMTString(); - cookieString += '; expires='+expire; - document.cookie = cookieString; - } - }, + return { + set: function settingsSet(name, val) { + if(location.protocol == 'chrome:' && !isLocalStorageEnabled) { + Application.prefs.setValue(extPrefix + '.' + name, val); + } else if(isLocalStorageEnabled) { + localStorage.setItem(name, val); + } else if(isCookiesEnabled) { + var cookieString = name + '=' + escape(val); + var expire = (new Date((new Date().getTime())+1000*60*60*24*365)).toGMTString(); + cookieString += '; expires='+expire; + document.cookie = cookieString; + } + }, - get: function(name, defaultValue) { - if(location.protocol == 'chrome:' && !LOCALSTORAGE_WORKS) { - return Application.prefs.getValue(extPrefix + '.' + name, defaultValue); - } else if(LOCALSTORAGE_WORKS) { - return localStorage.getItem(name) || defaultValue; - } else if(COOKIE_WORKS) { - var res = document.cookie.match ( '(^|;) ?' + name + '=([^;]*)(;|$)' ); - if (res) { - return unescape(res[2]); - } else { - return fallback; + get: function settingsGet(name, defaultValue) { + if(location.protocol == 'chrome:' && !isLocalStorageEnabled) { + return Application.prefs.getValue(extPrefix + '.' + name, defaultValue); + } else if(isLocalStorageEnabled) { + return localStorage.getItem(name) || defaultValue; + } else if(isCookiesEnabled) { + var res = document.cookie.match ( '(^|;) ?' + name + '=([^;]*)(;|$)' ); + return res ? unescape(res[2]) : defaultValue; } } - } -}; - -parent.Settings = Settings; -})(this); + }; +})(); var cache = new Cache(kCacheSize); var currentPageNumber = 1; @@ -347,15 +339,6 @@ var PDFView = { pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; } - var id = pdf.fingerprint; - if (id) { - var scroll = Settings.get(id + '.scroll', -1); - if (scroll != -1) { - setTimeout(function scrollWindow() { - window.scrollTo(0, scroll); - }, 0); - } - } this.pagesRefMap = pagesRefMap; this.destinations = pdf.catalog.destinations; this.setScale(scale || kDefaultScale, true); @@ -371,8 +354,15 @@ var PDFView = { this.setHash(this.initialBookmark); this.initialBookmark = null; } - else - this.page = 1; + else { + var scroll = Settings.get(pdf.fingerprint + '.scroll', -1); + if (scroll != -1) { + setTimeout(function scrollWindow() { + window.scrollTo(0, scroll); + }, 0); + } else + this.page = 1; + } }, setHash: function pdfViewSetHash(hash) { From c7375745ae4c1332763ff01ccd52c58a75458d35 Mon Sep 17 00:00:00 2001 From: Saebekassebil Date: Fri, 23 Dec 2011 23:56:01 +0100 Subject: [PATCH 16/42] Too rash. Fixes gjslint errors --- web/viewer.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index 4eeb3a46e..f04023ae8 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -26,7 +26,7 @@ var Cache = function cacheCache(size) { }; // Settings Manager - This is a utility for saving settings -// First we see if localStorage is available, which isn't pt. in FF due to bug #495747 +// First we see if localStorage is available, FF bug #495747 // If not, we use FUEL in FF and fallback to Cookies for other browsers. var Settings = (function settingsClosure() { var isCookiesEnabled = (function() { @@ -37,7 +37,7 @@ var Settings = (function settingsClosure() { var isLocalStorageEnabled = (function localStorageEnabledTest() { try { localStorage; - } catch(e) { + } catch (e) { return false; } return true; @@ -47,25 +47,27 @@ var Settings = (function settingsClosure() { return { set: function settingsSet(name, val) { - if(location.protocol == 'chrome:' && !isLocalStorageEnabled) { - Application.prefs.setValue(extPrefix + '.' + name, val); - } else if(isLocalStorageEnabled) { + if (location.protocol == 'chrome:' && !isLocalStorageEnabled) { + Application.prefs.setValue(extPrefix + '.' + name, val); + } else if (isLocalStorageEnabled) { localStorage.setItem(name, val); - } else if(isCookiesEnabled) { + } else if (isCookiesEnabled) { var cookieString = name + '=' + escape(val); - var expire = (new Date((new Date().getTime())+1000*60*60*24*365)).toGMTString(); - cookieString += '; expires='+expire; - document.cookie = cookieString; - } + var expire = new Date(); + expire.setTime(expire.getTime() + 1000 * 60 * 60 * 24 * 365); + cookieString += '; expires=' + expire.toGMTString(); + document.cookie = cookieString; + } }, get: function settingsGet(name, defaultValue) { - if(location.protocol == 'chrome:' && !isLocalStorageEnabled) { - return Application.prefs.getValue(extPrefix + '.' + name, defaultValue); - } else if(isLocalStorageEnabled) { + if (location.protocol == 'chrome:' && !isLocalStorageEnabled) { + var preferenceName = extPrefix + '.' + name; + return Application.prefs.getValue(preferenceName, defaultValue); + } else if (isLocalStorageEnabled) { return localStorage.getItem(name) || defaultValue; - } else if(isCookiesEnabled) { - var res = document.cookie.match ( '(^|;) ?' + name + '=([^;]*)(;|$)' ); + } else if (isCookiesEnabled) { + var res = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); return res ? unescape(res[2]) : defaultValue; } } @@ -360,7 +362,7 @@ var PDFView = { setTimeout(function scrollWindow() { window.scrollTo(0, scroll); }, 0); - } else + } else this.page = 1; } }, @@ -885,10 +887,8 @@ function updateViewarea() { window.addEventListener('scroll', function webViewerScroll(evt) { updateViewarea(); - var id; - if((id = PDFView.pages[0].content.pdf.fingerprint)) { - Settings.set(id+'.scroll', window.pageYOffset); - } + var fingerprint = PDFView.pages[0].content.pdf.fingerprint; + Settings.set(fingerprint + '.scroll', window.pageYOffset); }, true); From 1089c30b56659d46dee2fd570e3c4957028cab19 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 23 Dec 2011 19:41:12 -0800 Subject: [PATCH 17/42] Adding type4 postscript function support. --- src/function.js | 515 ++++++++++++++++++++++++++++++++++++- test/unit/function_spec.js | 223 ++++++++++++++++ test/unit/unit_test.html | 19 ++ 3 files changed, 751 insertions(+), 6 deletions(-) create mode 100644 test/unit/function_spec.js diff --git a/src/function.js b/src/function.js index 6b0063218..e9099a68a 100644 --- a/src/function.js +++ b/src/function.js @@ -336,16 +336,519 @@ var PDFFunction = (function PDFFunctionClosure() { }; }, - constructPostScript: function pdfFunctionConstructPostScript() { - return [CONSTRUCT_POSTSCRIPT]; + constructPostScript: function pdfFunctionConstructPostScript(fn, dict, xref) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain) + error('No domain.'); + + if(!range) + error('No range.') + + var lexer = new PostScriptLexer(fn); + var parser = new PostScriptParser(lexer); + var code = parser.parse(); + + return [CONSTRUCT_POSTSCRIPT, domain, range, code]; }, - constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() { - TODO('unhandled type of function'); - return function constructPostScriptFromIRResult() { - return [255, 105, 180]; + constructPostScriptFromIR: + function pdfFunctionConstructPostScriptFromIR(IR) { + var domain = IR[1]; + var range = IR[2]; + var code = IR[3]; + var numOutputs = range.length / 2; + var evaluator = new PostScriptEvaluator(code); + // Cache the values for a big speed up, the cache size is limited though + // since the number of possible values can be huge from a PS function. + var cache = new FunctionCache(); + return function constructPostScriptFromIRResult(args) { + var initialStack = []; + for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { + initialStack.push(args[i]); + } + + var key = initialStack.join('_'); + if (cache.has(key)) + return cache.get(key); + + var stack = evaluator.execute(initialStack); + var transformed = new Array(numOutputs); + for (i = numOutputs - 1; i >= 0; --i) { + var out = stack.pop(); + var rangeIndex = 2 * i; + if (out < range[rangeIndex]) + out = range[rangeIndex]; + else if (out > range[rangeIndex + 1]) + out = range[rangeIndex + 1]; + transformed[i] = out; + } + cache.set(key, transformed); + return transformed; }; } }; })(); +var FunctionCache = (function FunctionCache() { + var MAX_CACHE_SIZE = 1024; + function FunctionCache() { + this.cache = {}; + this.total = 0; + } + FunctionCache.prototype = { + has: function(key) { + return key in this.cache + }, + get: function(key) { + return this.cache[key]; + }, + set: function(key, value) { + if (this.total < MAX_CACHE_SIZE) { + this.cache[key] = value; + this.total++; + } + } + }; + return FunctionCache; +})(); + +var PostScriptStack = (function PostScriptStack() { + var MAX_STACK_SIZE = 100; + function PostScriptStack(initialStack) { + this.stack = initialStack || []; + } + + PostScriptStack.prototype = { + push: function push(value) { + if (this.stack.length >= MAX_STACK_SIZE) + error('PostScript function stack overflow.'); + this.stack.push(value); + }, + pop: function pop() { + if (this.stack.length <= 0) + error('PostScript function stack underflow.'); + return this.stack.pop(); + }, + copy: function copy(n) { + if (this.stack.length + n >= MAX_STACK_SIZE) + error('PostScript function stack overflow.'); + var part = this.stack.slice(this.stack.length - n); + this.stack = this.stack.concat(part); + }, + index: function index(n) { + this.push(this.stack[this.stack.length - n - 1]); + }, + roll: function roll(n, p) { + // rotate the last n stack elements p times + var a = this.stack.splice(this.stack.length - n, n); + // algorithm from http://jsfromhell.com/array/rotate + var l = a.length, p = (Math.abs(p) >= l && (p %= l), + p < 0 && (p += l), p), i, x; + for(; p; p = (Math.ceil(l / p) - 1) * p - l + (l = p)) + for(i = l; i > p; x = a[--i], a[i] = a[i - p], a[i - p] = x); + this.stack = this.stack.concat(a); + } + }; + return PostScriptStack; +})(); +var PostScriptEvaluator = (function PostScriptEvaluator() { + function PostScriptEvaluator(code) { + this.code = code; + console.log(code); + } + PostScriptEvaluator.prototype = { + execute: function(initialStack) { + var stack = new PostScriptStack(initialStack); + var counter = 0; + var code = this.code; + var a, b; + while (counter < this.code.length) { + var instruction = this.code[counter++]; + var operator = instruction[0]; + switch (operator) { + // non standard ps operators + case 'push': + stack.push(instruction[1]); + break; + case 'jz': // jump if false + a = stack.pop(); + if (!a) + counter = instruction[1]; + break; + case 'j': // jump + counter = instruction[1]; + break; + + // all ps operators in alphabetical order (excluding if/ifelse) + case 'abs': + a = stack.pop(); + stack.push(Math.abs(a)); + break; + case 'add': + b = stack.pop(); + a = stack.pop(); + stack.push(a + b); + break; + case 'and': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a && b); + else + stack.push(a & b); + break; + case 'atan': + a = stack.pop(); + stack.push(Math.atan(a)); + break; + case 'bitshift': + b = stack.pop(); + a = stack.pop(); + if (a > 0) + stack.push(a << b); + else + stack.push(a >> b); + break; + case 'ceiling': + a = stack.pop(); + stack.push(Math.ceil(a)); + break; + case 'copy': + a = stack.pop(); + stack.copy(a); + break; + case 'cos': + a = stack.pop(); + stack.push(Math.cos(a)); + break; + case 'cvi': + a = stack.pop(); + if (a >= 0) + stack.push(Math.floor(a)); + else + stack.push(Math.ceil(a)); + break; + case 'cvr': + // noop + break; + case 'div': + b = stack.pop(); + a = stack.pop(); + stack.push(a / b); + break; + case 'dup': + stack.copy(1); + break; + case 'eq': + b = stack.pop(); + a = stack.pop(); + stack.push(a == b); + break; + case 'exch': + stack.roll(2, 1); + break; + case 'exp': + b = stack.pop(); + a = stack.pop(); + stack.push(Math.pow(a, b)); + break; + case 'false': + stack.push(false); + break; + case 'floor': + a = stack.pop(); + stack.push(Math.floor(a)); + break; + case 'ge': + b = stack.pop(); + a = stack.pop(); + stack.push(a >= b); + break; + case 'gt': + b = stack.pop(); + a = stack.pop(); + stack.push(a > b); + break; + case 'idiv': + b = stack.pop(); + a = stack.pop(); + stack.push(Math.floor(a / b)); + break; + case 'index': + a = stack.pop(); + stack.index(a); + break; + case 'le': + b = stack.pop(); + a = stack.pop(); + stack.push(a <= b); + break; + case 'ln': + a = stack.pop(); + stack.push(Math.log(a)); + break; + case 'log': + a = stack.pop(); + stack.push(Math.log(a) / Math.LN10); + break; + case 'lt': + b = stack.pop(); + a = stack.pop(); + stack.push(a < b); + break; + case 'mod': + b = stack.pop(); + a = stack.pop(); + stack.push(a % b); + break; + case 'mul': + b = stack.pop(); + a = stack.pop(); + stack.push(a * b); + break; + case 'ne': + b = stack.pop(); + a = stack.pop(); + stack.push(a != b); + break; + case 'neg': + a = stack.pop(); + stack.push(-1 * b); + break; + case 'not': + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a && b); + else + stack.push(a & b); + break; + case 'or': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a || b); + else + stack.push(a | b); + break; + case 'pop': + stack.pop(); + break; + case 'roll': + b = stack.pop(); + a = stack.pop(); + stack.roll(a, b); + break; + case 'round': + a = stack.pop(); + stack.push(Math.round(a)); + break; + case 'sin': + a = stack.pop(); + stack.push(Math.sin(a)); + break; + case 'sqrt': + a = stack.pop(); + stack.push(Math.sqrt(a)); + break; + case 'sub': + b = stack.pop(); + a = stack.pop(); + stack.push(a - b); + break; + case 'true': + stack.push(true); + break; + case 'truncate': + a = stack.pop(); + if (a >= 0) + stack.push(Math.floor(a)); + else + stack.push(Math.ceil(a)); + break; + case 'xor': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push((a ^ b) ? true : false); + else + stack.push(a ^ b); + break; + default: + error('Unknown operator ' + operator); + break + } + } + return stack.stack; + } + } + return PostScriptEvaluator; +})(); + +var PostScriptParser = (function PostScriptParser() { + function PostScriptParser(lexer) { + this.lexer = lexer; + this.code = []; + this.token; + this.prev; + } + PostScriptParser.prototype = { + nextToken: function nextToken() { + this.prev = this.token; + this.token = this.lexer.getToken(); + }, + accept: function accept(type) { + if (this.token.type == type) { + this.nextToken(); + return true; + } + return false; + }, + expect: function expect(type) { + if (this.accept(type)) + return true; + error('Unexpected symbol: found ' + this.token.type + ' expected ' + + type + '.'); + }, + parse: function parse() { + this.nextToken(); + this.expect(PostScriptTokenTypes.LBRACE); + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + return this.code; + }, + parseBlock: function parseBlock() { + while (true) { + if (this.accept(PostScriptTokenTypes.NUMBER)) { + this.code.push(['push', this.prev.value]); + } else if (this.accept(PostScriptTokenTypes.OPERATOR)) { + this.code.push([this.prev.value]); + } else if (this.accept(PostScriptTokenTypes.LBRACE)) { + this.parseCondition(); + } else { + return; + } + } + }, + parseCondition: function parseCondition() { + var counter = this.code.length - 1; + var condition = []; + this.code.push(condition); + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + if (this.accept(PostScriptTokenTypes.IF)) { + // The true block is right after the 'if' so it just falls through on + // true else it jumps and skips the true block. + condition.push('jz', this.code.length); + } else if(this.accept(PostScriptTokenTypes.LBRACE)) { + var jump = []; + this.code.push(jump); + var endOfTrue = this.code.length; + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + this.expect(PostScriptTokenTypes.IFELSE); + // The jump is added at the end of the true block to skip the false + // block. + jump.push('j', this.code.length); + condition.push('jz', endOfTrue); + } else { + error('PS Function: error parsing conditional.'); + } + } + }; + return PostScriptParser; +})(); + +var PostScriptTokenTypes = { + LBRACE: 0, + RBRACE: 1, + NUMBER: 2, + OPERATOR: 3, + IF: 4, + IFELSE: 5 +}; + +var PostScriptToken = (function PostScriptToken() { + function PostScriptToken(type, value) { + this.type = type; + this.value = value; + } + return PostScriptToken; +})(); + +var PostScriptLexer = (function PostScriptLexer() { + function PostScriptLexer(stream) { + this.stream = stream; + } + PostScriptLexer.prototype = { + getToken: function getToken() { + var s = ''; + var ch; + var comment = false; + var stream = this.stream; + + // skip comments + while (true) { + if (!(ch = stream.getChar())) + return EOF; + + if (comment) { + if (ch == '\x0a' || ch == '\x0d') + comment = false; + } else if (ch == '%') { + comment = true; + } else if (!Lexer.isSpace(ch)) { + break; + } + } + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '+': case '-': case '.': + return new PostScriptToken(PostScriptTokenTypes.NUMBER, + this.getNumber(ch)); + case '{': + return new PostScriptToken(PostScriptTokenTypes.LBRACE, '{'); + case '}': + return new PostScriptToken(PostScriptTokenTypes.RBRACE, '}'); + } + // operator + var str = ch.toLowerCase(); + while (true) { + ch = stream.lookChar().toLowerCase(); + if (ch >= 'a' && ch <= 'z') + str += ch; + else + break; + stream.skip(); + } + switch (str) { + case 'if': + return new PostScriptToken(PostScriptTokenTypes.IF, str); + case 'ifelse': + return new PostScriptToken(PostScriptTokenTypes.IFELSE, str); + default: + return new PostScriptToken(PostScriptTokenTypes.OPERATOR, str); + } + }, + getNumber: function getNumber(ch) { + var str = ch; + var stream = this.stream; + while (true) { + ch = stream.lookChar(); + if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.') + str += ch; + else + break; + stream.skip(); + } + var value = parseFloat(str); + if (isNaN(value)) + error('Invalid floating point number: ' + value); + return value; + } + }; + return PostScriptLexer; +})(); + diff --git a/test/unit/function_spec.js b/test/unit/function_spec.js new file mode 100644 index 000000000..7c336a65d --- /dev/null +++ b/test/unit/function_spec.js @@ -0,0 +1,223 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +describe('function', function() { + beforeEach(function () { + this.addMatchers({ + toMatchArray: function(expected) { + var actual = this.actual; + if (actual.length != expected.length) + return false; + for (var i = 0; i < expected.length; i++) { + var a = actual[i], b = expected[i]; + if (isArray(b)) { + if (a.length != b.length) + return false; + for (var j = 0; j < a.length; j++) { + var suba = a[j], subb = b[j]; + if (suba !== subb) + return false + } + } else { + if (a !== b) + return false; + } + } + return true; + } + }); + }); + + describe('PostScriptParser', function() { + function parse(program) { + var stream = new StringStream(program); + var parser = new PostScriptParser(new PostScriptLexer(stream)); + return parser.parse(); + } + it('parses empty programs', function() { + var output = parse('{}'); + expect(output.length).toEqual(0); + }); + it('parses positive numbers', function() { + var number = 999; + var program = parse('{ ' + number + ' }'); + var expectedProgram = [ + ['push', number] + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('parses negative numbers', function() { + var number = -999; + var program = parse('{ ' + number + ' }'); + var expectedProgram = [ + ['push', number] + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('parses negative floats', function() { + var number = 3.3; + var program = parse('{ ' + number + ' }'); + var expectedProgram = [ + ['push', number] + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('parses operators', function() { + var program = parse('{ sub }'); + var expectedProgram = [ + ['sub'] + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('parses if statements', function() { + var program = parse('{ { 99 } if }'); + var expectedProgram = [ + ['jz', 2], + ['push', 99] + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('parses ifelse statements', function() { + var program = parse('{ { 99 } { 44 } ifelse }'); + var expectedProgram = [ + ['jz', 3], + ['push', 99], + ['j', 4], + ['push', 44], + ]; + expect(program).toMatchArray(expectedProgram); + }); + it('handles missing brackets', function() { + expect(function() { parse('{'); }).toThrow( + new Error('Unexpected symbol: found undefined expected 1.')); + }); + }); + + describe('PostScriptEvaluator', function() { + function evaluate(program) { + var stream = new StringStream(program); + var parser = new PostScriptParser(new PostScriptLexer(stream)); + var code = parser.parse(); + var evaluator = new PostScriptEvaluator(code); + var output = evaluator.execute(); + console.log(output); + return output; + } + it('pushes stack', function() { + var stack = evaluate('{ 99 }'); + var expectedStack = [99]; + expect(stack).toMatchArray(expectedStack); + }); + it('handles if with true', function() { + var stack = evaluate('{ 1 {99} if }'); + var expectedStack = [99]; + expect(stack).toMatchArray(expectedStack); + }); + it('handles if with false', function() { + var stack = evaluate('{ 0 {99} if }'); + var expectedStack = []; + expect(stack).toMatchArray(expectedStack); + }); + it('handles ifelse with true', function() { + var stack = evaluate('{ 1 {99} {77} ifelse }'); + var expectedStack = [99]; + expect(stack).toMatchArray(expectedStack); + }); + it('handles ifelse with false', function() { + var stack = evaluate('{ 0 {99} {77} ifelse }'); + var expectedStack = [77]; + expect(stack).toMatchArray(expectedStack); + }); + it('handles nested if', function() { + var stack = evaluate('{ 1 {1 {77} if} if }'); + var expectedStack = [77]; + expect(stack).toMatchArray(expectedStack); + }); + + it('abs', function() { + var stack = evaluate('{ -2 abs }'); + var expectedStack = [2]; + expect(stack).toMatchArray(expectedStack); + }); + it('adds', function() { + var stack = evaluate('{ 1 2 add }'); + var expectedStack = [3]; + expect(stack).toMatchArray(expectedStack); + }); + it('boolean ands', function() { + var stack = evaluate('{ true false and }'); + var expectedStack = [false]; + expect(stack).toMatchArray(expectedStack); + }); + it('bitwise ands', function() { + var stack = evaluate('{ 254 1 and }'); + var expectedStack = [254 & 1]; + expect(stack).toMatchArray(expectedStack); + }); + // TODO atan + // TODO bitshift + // TODO ceiling + // TODO copy + // TODO cos + // TODO cvi + // TODO cvr + // TODO div + it('duplicates', function() { + var stack = evaluate('{ 99 dup }'); + var expectedStack = [99, 99]; + expect(stack).toMatchArray(expectedStack); + }); + // TODO eq + it('exchanges', function() { + var stack = evaluate('{ 44 99 exch }'); + var expectedStack = [99, 44]; + expect(stack).toMatchArray(expectedStack); + }); + // TODO exp + // TODO false + // TODO floor + // TODO ge + // TODO gt + // TODO idiv + it('duplicates index', function() { + var stack = evaluate('{ 4 3 2 1 2 index }'); + var expectedStack = [4, 3, 2, 1, 3]; + expect(stack).toMatchArray(expectedStack); + }); + // TODO le + // TODO ln + // TODO log + // TODO lt + // TODO mod + // TODO mul + // TODO ne + // TODO neg + // TODO not + // TODO or + it('pops stack', function() { + var stack = evaluate('{ 1 2 pop }'); + var expectedStack = [1]; + expect(stack).toMatchArray(expectedStack); + }); + it('rolls stack right', function() { + var stack = evaluate('{ 1 3 2 2 4 1 roll }'); + var expectedStack = [2, 1, 3, 2]; + expect(stack).toMatchArray(expectedStack); + }); + it('rolls stack left', function() { + var stack = evaluate('{ 1 3 2 2 4 -1 roll }'); + var expectedStack = [3, 2, 2, 1]; + expect(stack).toMatchArray(expectedStack); + }); + // TODO round + // TODO sin + // TODO sqrt + // TODO sub + // TODO true + // TODO truncate + // TODO xor + }); +}); + diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index 1fc28ef83..8d0af03d6 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -11,9 +11,28 @@ + + + + + + + + + + + + + + + + + + +