diff --git a/examples/svgviewer/index.html b/examples/svgviewer/index.html index 931b595a3..70bf357ed 100644 --- a/examples/svgviewer/index.html +++ b/examples/svgviewer/index.html @@ -2,21 +2,24 @@ + + + PDF.js SVG viewer example + + - - SVG Viewer Example + diff --git a/examples/svgviewer/viewer.js b/examples/svgviewer/viewer.js index 51f5ad2dd..7d330419e 100644 --- a/examples/svgviewer/viewer.js +++ b/examples/svgviewer/viewer.js @@ -1,40 +1,29 @@ 'use strict'; +var DEFAULT_SCALE = 1.5; + // Parse query string to extract some parameters (it can fail for some input) var query = document.location.href.replace(/^[^?]*(\?([^#]*))?(#.*)?/, '$2'); var queryParams = query ? JSON.parse('{' + query.split('&').map(function (a) { return a.split('=').map(decodeURIComponent).map(JSON.stringify).join(': '); }).join(',') + '}') : {}; -var url = queryParams.file || '../../test/pdfs/liveprogramming.pdf'; -var scale = +queryParams.scale || 1.5; +var url = queryParams.file || '../../web/compressed.tracemonkey-pldi-09.pdf'; function renderDocument(pdf, svgLib) { - var numPages = pdf.numPages; - // Using promise to fetch the page - - // For testing only. - var MAX_NUM_PAGES = 50; - var ii = Math.min(MAX_NUM_PAGES, numPages); - var promise = Promise.resolve(); - for (var i = 1; i <= ii; i++) { - var anchor = document.createElement('a'); - anchor.setAttribute('name', 'page=' + i); - anchor.setAttribute('title', 'Page ' + i); - document.body.appendChild(anchor); - + for (var i = 1; i <= pdf.numPages; i++) { // Using promise to fetch and render the next page - promise = promise.then(function (pageNum, anchor) { + promise = promise.then(function (pageNum) { return pdf.getPage(pageNum).then(function (page) { - var viewport = page.getViewport(scale); + var viewport = page.getViewport(DEFAULT_SCALE); var container = document.createElement('div'); container.id = 'pageContainer' + pageNum; container.className = 'pageContainer'; container.style.width = viewport.width + 'px'; container.style.height = viewport.height + 'px'; - anchor.appendChild(container); + document.body.appendChild(container); return page.getOperatorList().then(function (opList) { var svgGfx = new svgLib.SVGGraphics(page.commonObjs, page.objs); @@ -43,7 +32,7 @@ function renderDocument(pdf, svgLib) { }); }); }); - }.bind(null, i, anchor)); + }.bind(null, i)); } } diff --git a/src/display/svg.js b/src/display/svg.js index 8d9f5e7f5..41fbc25f6 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -294,16 +294,6 @@ var SVGExtraState = (function SVGExtraStateClosure() { })(); var SVGGraphics = (function SVGGraphicsClosure() { - function createScratchSVG(width, height) { - var NS = 'http://www.w3.org/2000/svg'; - var svg = document.createElementNS(NS, 'svg:svg'); - svg.setAttributeNS(null, 'version', '1.1'); - svg.setAttributeNS(null, 'width', width + 'px'); - svg.setAttributeNS(null, 'height', height + 'px'); - svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height); - return svg; - } - function opListToTree(opList) { var opTree = []; var tmp = []; @@ -410,9 +400,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { this.transformMatrix = this.transformStack.pop(); this.current = this.extraStack.pop(); - this.tgrp = document.createElementNS(NS, 'svg:g'); - this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); - this.pgrp.appendChild(this.tgrp); + this.tgrp = null; }, group: function SVGGraphics_group(items) { @@ -454,28 +442,18 @@ var SVGGraphics = (function SVGGraphicsClosure() { var transformMatrix = [a, b, c, d, e, f]; this.transformMatrix = Util.transform(this.transformMatrix, transformMatrix); - - this.tgrp = document.createElementNS(NS, 'svg:g'); - this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); + this.tgrp = null; }, getSVG: function SVGGraphics_getSVG(operatorList, viewport) { - this.svg = createScratchSVG(viewport.width, viewport.height); this.viewport = viewport; + var svgElement = this._initialize(viewport); return this.loadDependencies(operatorList).then(function () { this.transformMatrix = IDENTITY_MATRIX; - this.pgrp = document.createElementNS(NS, 'svg:g'); // Parent group - this.pgrp.setAttributeNS(null, 'transform', pm(viewport.transform)); - this.tgrp = document.createElementNS(NS, 'svg:g'); // Transform group - this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); - this.defs = document.createElementNS(NS, 'svg:defs'); - this.pgrp.appendChild(this.defs); - this.pgrp.appendChild(this.tgrp); - this.svg.appendChild(this.pgrp); var opTree = this.convertOpList(operatorList); this.executeOpTree(opTree); - return this.svg; + return svgElement; }.bind(this)); }, @@ -634,7 +612,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { this.group(opTree[x].items); break; default: - warn('Unimplemented method '+ fn); + warn('Unimplemented operator ' + fn); break; } } @@ -760,8 +738,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { current.txtElement.appendChild(current.tspan); current.txtgrp.appendChild(current.txtElement); - this.tgrp.appendChild(current.txtElement); - + this._ensureTransformGroup().appendChild(current.txtElement); }, setLeadingMoveText: function SVGGraphics_setLeadingMoveText(x, y) { @@ -818,16 +795,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { current.xcoords = []; }, - endText: function SVGGraphics_endText() { - if (this.current.pendingClip) { - this.cgrp.appendChild(this.tgrp); - this.pgrp.appendChild(this.cgrp); - } else { - this.pgrp.appendChild(this.tgrp); - } - this.tgrp = document.createElementNS(NS, 'svg:g'); - this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); - }, + endText: function SVGGraphics_endText() {}, // Path properties setLineWidth: function SVGGraphics_setLineWidth(width) { @@ -925,30 +893,15 @@ var SVGGraphics = (function SVGGraphicsClosure() { pf(current.dashPhase) + 'px'); current.path.setAttributeNS(null, 'fill', 'none'); - this.tgrp.appendChild(current.path); - if (current.pendingClip) { - this.cgrp.appendChild(this.tgrp); - this.pgrp.appendChild(this.cgrp); - } else { - this.pgrp.appendChild(this.tgrp); - } + this._ensureTransformGroup().appendChild(current.path); + // Saving a reference in current.element so that it can be addressed // in 'fill' and 'stroke' current.element = current.path; current.setCurrentPoint(x, y); }, - endPath: function SVGGraphics_endPath() { - var current = this.current; - if (current.pendingClip) { - this.cgrp.appendChild(this.tgrp); - this.pgrp.appendChild(this.cgrp); - } else { - this.pgrp.appendChild(this.tgrp); - } - this.tgrp = document.createElementNS(NS, 'svg:g'); - this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); - }, + endPath: function SVGGraphics_endPath() {}, clip: function SVGGraphics_clip(type) { var current = this.current; @@ -967,12 +920,12 @@ var SVGGraphics = (function SVGGraphicsClosure() { this.clippath.appendChild(clipElement); this.defs.appendChild(this.clippath); - // Create a new group with that attribute + // Create a clipping group that references the clipping path. current.pendingClip = true; this.cgrp = document.createElementNS(NS, 'svg:g'); this.cgrp.setAttributeNS(null, 'clip-path', 'url(#' + current.clipId + ')'); - this.pgrp.appendChild(this.cgrp); + this.svg.appendChild(this.cgrp); }, closePath: function SVGGraphics_closePath() { @@ -1016,20 +969,11 @@ var SVGGraphics = (function SVGGraphicsClosure() { case 'D': this.setDash(value[0], value[1]); break; - case 'RI': - break; - case 'FL': - break; case 'Font': this.setFont(value); break; - case 'CA': - break; - case 'ca': - break; - case 'BM': - break; - case 'SMask': + default: + warn('Unimplemented graphic state ' + key); break; } } @@ -1083,11 +1027,11 @@ var SVGGraphics = (function SVGGraphicsClosure() { rect.setAttributeNS(null, 'width', '1px'); rect.setAttributeNS(null, 'height', '1px'); rect.setAttributeNS(null, 'fill', current.fillColor); - this.tgrp.appendChild(rect); + + this._ensureTransformGroup().appendChild(rect); }, paintJpegXObject: function SVGGraphics_paintJpegXObject(objId, w, h) { - var current = this.current; var imgObj = this.objs.get(objId); var imgEl = document.createElementNS(NS, 'svg:image'); imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgObj.src); @@ -1098,13 +1042,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { imgEl.setAttributeNS(null, 'transform', 'scale(' + pf(1 / w) + ' ' + pf(-1 / h) + ')'); - this.tgrp.appendChild(imgEl); - if (current.pendingClip) { - this.cgrp.appendChild(this.tgrp); - this.pgrp.appendChild(this.cgrp); - } else { - this.pgrp.appendChild(this.tgrp); - } + this._ensureTransformGroup().appendChild(imgEl); }, paintImageXObject: function SVGGraphics_paintImageXObject(objId) { @@ -1118,7 +1056,6 @@ var SVGGraphics = (function SVGGraphicsClosure() { paintInlineImageXObject: function SVGGraphics_paintInlineImageXObject(imgData, mask) { - var current = this.current; var width = imgData.width; var height = imgData.height; @@ -1128,7 +1065,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { cliprect.setAttributeNS(null, 'y', '0'); cliprect.setAttributeNS(null, 'width', pf(width)); cliprect.setAttributeNS(null, 'height', pf(height)); - current.element = cliprect; + this.current.element = cliprect; this.clip('nonzero'); var imgEl = document.createElementNS(NS, 'svg:image'); imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgSrc); @@ -1142,13 +1079,7 @@ var SVGGraphics = (function SVGGraphicsClosure() { if (mask) { mask.appendChild(imgEl); } else { - this.tgrp.appendChild(imgEl); - } - if (current.pendingClip) { - this.cgrp.appendChild(this.tgrp); - this.pgrp.appendChild(this.cgrp); - } else { - this.pgrp.appendChild(this.tgrp); + this._ensureTransformGroup().appendChild(imgEl); } }, @@ -1171,15 +1102,14 @@ var SVGGraphics = (function SVGGraphicsClosure() { rect.setAttributeNS(null, 'fill', fillColor); rect.setAttributeNS(null, 'mask', 'url(#' + current.maskId +')'); this.defs.appendChild(mask); - this.tgrp.appendChild(rect); + + this._ensureTransformGroup().appendChild(rect); this.paintInlineImageXObject(imgData, mask); }, paintFormXObjectBegin: function SVGGraphics_paintFormXObjectBegin(matrix, bbox) { - this.save(); - if (isArray(matrix) && matrix.length === 6) { this.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); @@ -1201,8 +1131,54 @@ var SVGGraphics = (function SVGGraphicsClosure() { }, paintFormXObjectEnd: - function SVGGraphics_paintFormXObjectEnd() { - this.restore(); + function SVGGraphics_paintFormXObjectEnd() {}, + + /** + * @private + */ + _initialize: function SVGGraphics_initialize(viewport) { + // Create the SVG element. + var svg = document.createElementNS(NS, 'svg:svg'); + svg.setAttributeNS(null, 'version', '1.1'); + svg.setAttributeNS(null, 'width', viewport.width + 'px'); + svg.setAttributeNS(null, 'height', viewport.height + 'px'); + svg.setAttributeNS(null, 'viewBox', '0 0 ' + viewport.width + + ' ' + viewport.height); + + // Create the definitions element. + var definitions = document.createElementNS(NS, 'svg:defs'); + svg.appendChild(definitions); + this.defs = definitions; + + // Create the root group element, which acts a container for all other + // groups and applies the viewport transform. + var rootGroup = document.createElementNS(NS, 'svg:g'); + rootGroup.setAttributeNS(null, 'transform', pm(viewport.transform)); + svg.appendChild(rootGroup); + + // For the construction of the SVG image we are only interested in the + // root group, so we expose it as the entry point of the SVG image for + // the other code in this class. + this.svg = rootGroup; + + return svg; + }, + + /** + * @private + */ + _ensureTransformGroup: function SVGGraphics_ensureTransformGroup() { + if (!this.tgrp) { + this.tgrp = document.createElementNS(NS, 'svg:g'); + this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix)); + + if (this.current.pendingClip) { + this.cgrp.appendChild(this.tgrp); + } else { + this.svg.appendChild(this.tgrp); + } + } + return this.tgrp; } }; return SVGGraphics;