diff --git a/src/api.js b/src/api.js index a8bb5fb65..d6d0bc5c6 100644 --- a/src/api.js +++ b/src/api.js @@ -6,11 +6,8 @@ this.page = page; } PdfPageWrapper.prototype = { - get width() { - return this.page.width; - }, - get height() { - return this.page.height; + get rotate() { + return this.page.rotate; }, get stats() { return this.page.stats; @@ -21,8 +18,10 @@ get view() { return this.page.view; }, - rotatePoint: function(x, y) { - return this.page.rotatePoint(x, y); + getViewport: function(scale, rotate) { + if (arguments < 2) + rotate = this.rotate; + return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); }, getAnnotations: function() { var promise = new PDFJS.Promise(); @@ -33,6 +32,7 @@ render: function(renderContext) { var promise = new PDFJS.Promise(); this.page.startRendering(renderContext.canvasContext, + renderContext.viewport, function complete(error) { if (error) promise.reject(error); diff --git a/src/canvas.js b/src/canvas.js index 8f29051fd..9d470fbec 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -241,27 +241,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 'shadingFill': true }, - beginDrawing: function CanvasGraphics_beginDrawing(mediaBox) { - var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; + beginDrawing: function CanvasGraphics_beginDrawing(viewport) { + var transform = viewport.transform; this.ctx.save(); - switch (mediaBox.rotate) { - case 0: - this.ctx.transform(1, 0, 0, -1, 0, ch); - break; - case 90: - this.ctx.transform(0, 1, 1, 0, 0, 0); - break; - case 180: - this.ctx.transform(-1, 0, 0, 1, cw, 0); - break; - case 270: - this.ctx.transform(0, -1, -1, 0, cw, ch); - break; - } - // Scale so that canvas units are the same as PDF user space units - this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); - // Move the media left-top corner to the (0,0) canvas position - this.ctx.translate(-mediaBox.x, -mediaBox.y); + this.ctx.transform.apply(this.ctx, transform); if (this.textLayer) this.textLayer.beginLayout(); diff --git a/src/core.js b/src/core.js index 15cd147e2..f71880852 100644 --- a/src/core.js +++ b/src/core.js @@ -100,18 +100,10 @@ var Page = (function PageClosure() { return shadow(this, 'mediaBox', obj); }, get view() { - var cropBox = this.inheritPageProp('CropBox'); - var view = { - x: 0, - y: 0, - width: this.width, - height: this.height - }; - if (!isArray(cropBox) || cropBox.length !== 4) - return shadow(this, 'view', view); - var mediaBox = this.mediaBox; - var offsetX = mediaBox[0], offsetY = mediaBox[1]; + var cropBox = this.inheritPageProp('CropBox'); + if (!isArray(cropBox) || cropBox.length !== 4) + return shadow(this, 'view', mediaBox); // From the spec, 6th ed., p.963: // "The crop, bleed, trim, and art boxes should not ordinarily @@ -119,42 +111,13 @@ var Page = (function PageClosure() { // effectively reduced to their intersection with the media box." cropBox = Util.intersect(cropBox, mediaBox); if (!cropBox) - return shadow(this, 'view', view); + return shadow(this, 'view', mediaBox); - var tl = this.rotatePoint(cropBox[0] - offsetX, cropBox[1] - offsetY); - var br = this.rotatePoint(cropBox[2] - offsetX, cropBox[3] - offsetY); - view.x = Math.min(tl.x, br.x); - view.y = Math.min(tl.y, br.y); - view.width = Math.abs(tl.x - br.x); - view.height = Math.abs(tl.y - br.y); - - return shadow(this, 'view', view); + return shadow(this, 'view', cropBox); }, get annotations() { return shadow(this, 'annotations', this.inheritPageProp('Annots')); }, - get width() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var width; - if (rotate == 0 || rotate == 180) { - width = (mediaBox[2] - mediaBox[0]); - } else { - width = (mediaBox[3] - mediaBox[1]); - } - return shadow(this, 'width', width); - }, - get height() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var height; - if (rotate == 0 || rotate == 180) { - height = (mediaBox[3] - mediaBox[1]); - } else { - height = (mediaBox[2] - mediaBox[0]); - } - return shadow(this, 'height', height); - }, get rotate() { var rotate = this.inheritPageProp('Rotate') || 0; // Normalize rotation so it's a multiple of 90 and between 0 and 270 @@ -238,7 +201,7 @@ var Page = (function PageClosure() { ); }, - display: function Page_display(gfx, callback) { + display: function Page_display(gfx, viewport, callback) { var stats = this.stats; stats.time('Rendering'); var xref = this.xref; @@ -248,10 +211,7 @@ var Page = (function PageClosure() { gfx.xref = xref; gfx.res = resources; - gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], - width: this.width, - height: this.height, - rotate: this.rotate }); + gfx.beginDrawing(viewport); var startIdx = 0; var length = this.operatorList.fnArray.length; @@ -276,21 +236,6 @@ var Page = (function PageClosure() { } next(); }, - rotatePoint: function Page_rotatePoint(x, y, reverse) { - var rotate = reverse ? (360 - this.rotate) : this.rotate; - switch (rotate) { - case 180: - return {x: this.width - x, y: y}; - case 90: - return {x: this.width - y, y: this.height - x}; - case 270: - return {x: y, y: x}; - case 360: - case 0: - default: - return {x: x, y: this.height - y}; - } - }, getLinks: function Page_getLinks() { var links = []; var annotations = pageGetAnnotations(); @@ -342,15 +287,10 @@ var Page = (function PageClosure() { 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 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); + item.rect = rect; switch (subtype.name) { case 'Link': var a = annotation.get('A'); @@ -434,7 +374,8 @@ var Page = (function PageClosure() { } return items; }, - startRendering: function Page_startRendering(ctx, callback, textLayer) { + startRendering: function Page_startRendering(ctx, viewport, + callback, textLayer) { var stats = this.stats; stats.time('Overall'); // If there is no displayReadyPromise yet, then the operatorList was never @@ -449,7 +390,7 @@ var Page = (function PageClosure() { function pageDisplayReadyPromise() { var gfx = new CanvasGraphics(ctx, this.objs, textLayer); try { - this.display(gfx, callback); + this.display(gfx, viewport, callback); } catch (e) { if (callback) callback(e); diff --git a/src/util.js b/src/util.js index 30fc799f9..9989d9c74 100644 --- a/src/util.js +++ b/src/util.js @@ -97,6 +97,19 @@ var Util = (function UtilClosure() { return [xt, yt]; }; + Util.applyInverseTransform = function Util_applyTransform(p, m) { + var d = m[0] * m[3] - m[1] * m[2]; + var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; + var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; + return [xt, yt]; + }; + + Util.inverseTransform = function Util_inverseTransform(m) { + var d = m[0] * m[3] - m[1] * m[2]; + return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, + (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; + }; + // Apply a generic 3d matrix M on a 3-vector v: // | a b c | | X | // | d e f | x | Y | @@ -165,7 +178,7 @@ var Util = (function UtilClosure() { } return result; - } + }; Util.sign = function Util_sign(num) { return num < 0 ? -1 : 1; @@ -174,6 +187,79 @@ var Util = (function UtilClosure() { return Util; })(); +var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { + function PageViewport(viewBox, scale, rotate, offsetX, offsetY) { + // creating transform to convert pdf coordinate system to the normal + // canvas like coordinates taking in account scale and rotation + var centerX = (viewBox[2] + viewBox[0]) / 2; + var centerY = (viewBox[3] + viewBox[1]) / 2; + var rotateA, rotateB, rotateC, rotateD; + switch (rotate) { + case -180: + case 180: + rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; + break; + case -270: + case 90: + rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; + break; + case -90: + case 270: + rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; + break; + case 360: + case 0: + default: + rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; + break; + } + var offsetCanvasX, offsetCanvasY; + var width, height; + if (rotateA == 0) { + offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; + offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; + width = Math.abs(viewBox[3] - viewBox[1]) * scale; + height = Math.abs(viewBox[2] - viewBox[0]) * scale; + } else { + offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; + offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; + width = Math.abs(viewBox[2] - viewBox[0]) * scale; + height = Math.abs(viewBox[3] - viewBox[1]) * scale; + } + // creating transform for the following operations: + // translate(-centerX, -centerY), rotate and flip vertically, + // scale, and translate(offsetCanvasX, offsetCanvasY) + this.transform = [ + rotateA * scale, + rotateB * scale, + rotateC * scale, + rotateD * scale, + offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, + offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY + ]; + + this.offsetX = offsetX; + this.offsetY = offsetY; + this.width = width; + this.height = height; + } + PageViewport.prototype = { + convertPointToViewport: function PageViewport_convertPointToViewport(x, y) { + return Util.applyTransform([x, y], this.transform); + }, + convertRectangleToViewport: + function PageViewport_convertRectangeToViewport(rect) { + var tl = Util.applyTransform([rect[0], rect[1]], this.transform); + var br = Util.applyTransform([rect[2], rect[3]], this.transform); + return [tl[0], tl[1], br[0], br[1]]; + }, + convertViewportToPoint: function PageViewport_convertViewportToPoint(x, y) { + return Util.applyInverseTransform([x, y], this.transform); + } + }; + return PageViewport; +})(); + var PDFStringTranslateTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, diff --git a/web/viewer.js b/web/viewer.js index 3ca4f805f..9fe9a1714 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -358,6 +358,8 @@ var PDFView = { var destRef = dest[0]; var pageNumber = destRef instanceof Object ? this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); + if (pageNumber > this.pages.length) + pageNumber = this.pages.length; if (pageNumber) { this.page = pageNumber; var currentPage = this.pages[pageNumber - 1]; @@ -514,10 +516,9 @@ var PDFView = { pagesPromise.then(function(promisedPages) { for (var i = 1; i <= pagesCount; i++) { var page = promisedPages[i - 1]; - var pageView = new PageView(container, page, i, page.width, page.height, + var pageView = new PageView(container, page, i, scale, page.stats, self.navigateTo.bind(self)); - var thumbnailView = new ThumbnailView(sidebar, page, i, - page.width / page.height); + var thumbnailView = new ThumbnailView(sidebar, page, i); bindOnAfterDraw(pageView, thumbnailView); pages.push(pageView); @@ -664,7 +665,7 @@ var PDFView = { var windowTop = window.pageYOffset; for (var i = 1; i <= pages.length; ++i) { var page = pages[i - 1]; - var pageHeight = page.height * page.scale + kBottomMargin; + var pageHeight = page.height + kBottomMargin; if (currentHeight + pageHeight > windowTop) break; @@ -723,16 +724,13 @@ var PDFView = { } }; -var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, +var PageView = function pageView(container, pdfPage, id, scale, stats, navigateTo) { this.id = id; this.pdfPage = pdfPage; - var view = pdfPage.view; - this.x = view.x; - this.y = view.y; - this.width = view.width; - this.height = view.height; + this.scale = scale || 1.0; + this.viewport = this.pdfPage.getViewport(scale); var anchor = document.createElement('a'); anchor.name = '' + this.id; @@ -746,8 +744,13 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, this.update = function pageViewUpdate(scale) { this.scale = scale || this.scale; - div.style.width = (this.width * this.scale) + 'px'; - div.style.height = (this.height * this.scale) + 'px'; + var viewport = this.pdfPage.getViewport(this.scale); + + this.viewport = viewport; + this.width = viewport.width; + this.height = viewport.height; + div.style.width = viewport.width + 'px'; + div.style.height = viewport.height + 'px'; while (div.hasChildNodes()) div.removeChild(div.lastChild); @@ -760,7 +763,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, div.appendChild(this.loadingIconDiv); }; - function setupAnnotations(pdfPage, scale) { + function setupAnnotations(pdfPage, viewport) { function bindLink(link, dest) { link.href = PDFView.getDestinationHash(dest); link.onclick = function pageViewSetupLinksOnclick() { @@ -770,11 +773,13 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, }; } function createElementWithStyle(tagName, item) { + var rect = viewport.convertRectangleToViewport(item.rect); + rect = Util.normalizeRect(rect); 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'; + element.style.left = Math.floor(rect[0]) + 'px'; + element.style.top = Math.floor(rect[1]) + 'px'; + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; return element; } function createCommentAnnotation(type, item) { @@ -844,7 +849,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, this.getPagePoint = function pageViewGetPagePoint(x, y) { var scale = PDFView.currentScale; - return this.pdfPage.rotatePoint(x / scale, y / scale); + return this.viewport.convertPointToViewport(x, y); }; this.scrollIntoView = function pageViewScrollIntoView(dest) { @@ -892,8 +897,8 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } var boundingRect = [ - this.pdfPage.rotatePoint(x, y), - this.pdfPage.rotatePoint(x + width, y + height) + this.viewport.convertPointToViewport(x, y), + this.viewport.convertPointToViewport(x + width, y + height) ]; if (scale && scale !== PDFView.currentScale) @@ -904,18 +909,18 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, setTimeout(function pageViewScrollIntoViewRelayout() { // letting page to re-layout before scrolling var scale = PDFView.currentScale; - var x = Math.min(boundingRect[0].x, boundingRect[1].x); - var y = Math.min(boundingRect[0].y, boundingRect[1].y); - var width = Math.abs(boundingRect[0].x - boundingRect[1].x); - var height = Math.abs(boundingRect[0].y - boundingRect[1].y); + var x = Math.min(boundingRect[0][0], boundingRect[1][0]); + var y = Math.min(boundingRect[0][1], boundingRect[1][1]); + var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]); + var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]); // using temporary div to scroll it into view var tempDiv = document.createElement('div'); tempDiv.style.position = 'absolute'; - tempDiv.style.left = Math.floor(x * scale) + 'px'; - tempDiv.style.top = Math.floor(y * scale) + 'px'; - tempDiv.style.width = Math.ceil(width * scale) + 'px'; - tempDiv.style.height = Math.ceil(height * scale) + 'px'; + tempDiv.style.left = Math.floor(x) + 'px'; + tempDiv.style.top = Math.floor(y) + 'px'; + tempDiv.style.width = Math.ceil(width) + 'px'; + tempDiv.style.height = Math.ceil(height) + 'px'; div.appendChild(tempDiv); tempDiv.scrollIntoView(true); div.removeChild(tempDiv); @@ -947,16 +952,15 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null; - var scale = this.scale; - canvas.width = pageWidth * scale; - canvas.height = pageHeight * scale; + var scale = this.scale, viewport = this.viewport; + canvas.width = viewport.width; + canvas.height = viewport.height; var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - ctx.translate(-this.x * scale, -this.y * scale); // Rendering area @@ -981,6 +985,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, var renderContext = { canvasContext: ctx, + viewport: this.viewport, textLayer: textLayer }; this.pdfPage.render(renderContext).then( @@ -992,7 +997,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } ); - setupAnnotations(this.pdfPage, this.scale); + setupAnnotations(this.pdfPage, this.viewport); div.setAttribute('data-loaded', true); }; @@ -1004,7 +1009,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, }; }; -var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { +var ThumbnailView = function thumbnailView(container, pdfPage, id) { var anchor = document.createElement('a'); anchor.href = PDFView.getAnchorUrl('#page=' + id); anchor.onclick = function stopNivigation() { @@ -1012,9 +1017,10 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { return false; }; - var view = page.view; - this.width = view.width; - this.height = view.height; + var viewport = pdfPage.getViewport(1); + var pageWidth = viewport.width; + var pageHeight = viewport.height; + var pageRatio = pageWidth / pageHeight; this.id = id; var maxThumbSize = 134; @@ -1022,8 +1028,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { maxThumbSize * pageRatio; var canvasHeight = pageRatio <= 1 ? maxThumbSize : maxThumbSize / pageRatio; - var scaleX = this.scaleX = (canvasWidth / this.width); - var scaleY = this.scaleY = (canvasHeight / this.height); + var scaleX = this.scaleX = (canvasWidth / pageWidth); + var scaleY = this.scaleY = (canvasHeight / pageHeight); var div = document.createElement('div'); div.id = 'thumbnailContainer' + id; @@ -1048,15 +1054,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.restore(); - - var view = page.view; - ctx.translate(-view.x * scaleX, -view.y * scaleY); - div.style.width = (view.width * scaleX) + 'px'; - div.style.height = (view.height * scaleY) + 'px'; - div.style.lineHeight = (view.height * scaleY) + 'px'; - return ctx; } @@ -1071,9 +1070,11 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { } var ctx = getPageDrawContext(); - page.startRendering(ctx, function thumbnailViewDrawStartRendering() { - callback(); - }); + var drawViewport = pdfPage.getViewport(scaleX); + page.startRendering(ctx, drawViewport, + function thumbnailViewDrawStartRendering() { + callback(); + }); this.hasImage = true; }; @@ -1359,7 +1360,7 @@ function updateViewarea() { var currentPage = PDFView.pages[pageNumber - 1]; var topLeft = currentPage.getPagePoint(window.pageXOffset, window.pageYOffset - firstPage.y - kViewerTopMargin); - pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y); + pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]); var store = PDFView.store; store.set('exists', true);