diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 34345a3af..d65c17f59 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -15,7 +15,8 @@ * limitations under the License. */ /* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale, - TextLayerBuilder, AnnotationsLayerBuilder, Promise */ + TextLayerBuilder, AnnotationsLayerBuilder, Promise, + approximateFraction, roundToDivide */ 'use strict'; @@ -315,7 +316,7 @@ var PDFPageView = (function PDFPageViewClosure() { var outputScale = getOutputScale(ctx); if (PDFJS.useOnlyCssZoom) { - var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); + var actualSizeViewport = viewport.clone({scale: CSS_UNITS}); // Use a scale that will make the canvas be the original intended size // of the page. outputScale.sx *= actualSizeViewport.width / viewport.width; @@ -336,10 +337,12 @@ var PDFPageView = (function PDFPageViewClosure() { } } - canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; - canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; - canvas.style.width = Math.floor(viewport.width) + 'px'; - canvas.style.height = Math.floor(viewport.height) + 'px'; + var sfx = approximateFraction(outputScale.sx); + var sfy = approximateFraction(outputScale.sy); + canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]); + canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]); + canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px'; + canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px'; // Add the viewport so it's known what it was originally drawn with. canvas._viewport = viewport; diff --git a/web/ui_utils.js b/web/ui_utils.js index d20d55f8a..e389129ae 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -237,6 +237,55 @@ function binarySearchFirstItem(items, condition) { return minIndex; /* === maxIndex */ } +/** + * Approximates float number as a fraction using Farey sequence (max order + * of 8). + * @param {number} x - Positive float number. + * @returns {Array} Estimated fraction: the first array item is a numerator, + * the second one is a denominator. + */ +function approximateFraction(x) { + // Fast paths for int numbers or their inversions. + if (Math.floor(x) === x) { + return [x, 1]; + } + var xinv = 1 / x; + var limit = 8; + if (xinv > limit) { + return [1, limit]; + } else if (Math.floor(xinv) === xinv) { + return [1, xinv]; + } + + var x_ = x > 1 ? xinv : x; + // a/b and c/d are neighbours in Farey sequence. + var a = 0, b = 1, c = 1, d = 1; + // Limiting search to order 8. + while (true) { + // Generating next term in sequence (order of q). + var p = a + c, q = b + d; + if (q > limit) { + break; + } + if (x_ <= p / q) { + c = p; d = q; + } else { + a = p; b = q; + } + } + // Select closest of the neighbours to x. + if (x_ - a / b < c / d - x_) { + return x_ === x ? [a, b] : [b, a]; + } else { + return x_ === x ? [c, d] : [d, c]; + } +} + +function roundToDivide(x, div) { + var r = x % div; + return r === 0 ? x : Math.round(x - r + div); +} + /** * Generic helper to find out what elements are visible within a scroll pane. */