Skip to content

Commit

Permalink
Update: Handle non uniform thumbnail sizes (box#896)
Browse files Browse the repository at this point in the history
  • Loading branch information
Conrad Chan authored and Conrad Chan committed Feb 19, 2019
1 parent 5d67e63 commit 2dc3174
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 30 deletions.
81 changes: 64 additions & 17 deletions src/lib/ThumbnailsSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ class ThumbnailsSidebar {
*/
constructor(element, pdfViewer) {
this.anchorEl = element;
this.currentThumbnails = [];
this.pdfViewer = pdfViewer;
this.thumbnailImageCache = {};
this.currentThumbnails = [];

this.createImageEl = this.createImageEl.bind(this);
this.createPlaceholderThumbnail = this.createPlaceholderThumbnail.bind(this);
this.requestThumbnailImage = this.requestThumbnailImage.bind(this);
this.createThumbnailImage = this.createThumbnailImage.bind(this);
this.generateThumbnailImages = this.generateThumbnailImages.bind(this);
this.getThumbnailDataURL = this.getThumbnailDataURL.bind(this);
this.requestThumbnailImage = this.requestThumbnailImage.bind(this);
this.thumbnailClickHandler = this.thumbnailClickHandler.bind(this);

this.anchorEl.addEventListener('click', this.thumbnailClickHandler);
Expand All @@ -68,7 +70,7 @@ class ThumbnailsSidebar {
if (target.classList.contains(CLASS_BOX_PREVIEW_THUMBNAIL)) {
// Get the page number
const { bpPageNum: pageNumStr } = target.dataset;
const pageNum = Number.parseInt(pageNumStr, 10);
const pageNum = parseInt(pageNumStr, 10);

if (this.onClickHandler) {
this.onClickHandler(pageNum);
Expand Down Expand Up @@ -218,30 +220,75 @@ class ThumbnailsSidebar {
return Promise.resolve(this.thumbnailImageCache[itemIndex]);
}

return this.getThumbnailDataURL(itemIndex + 1)
.then(this.createImageEl)
.then((imageEl) => {
// Cache this image element for future use
this.thumbnailImageCache[itemIndex] = imageEl;

return imageEl;
});
}

/**
* Given a page number, generates the image data URL for the image of the page
* @param {number} pageNum - The page number of the document
* @return {string} The data URL of the page image
*/
getThumbnailDataURL(pageNum) {
const canvas = document.createElement('canvas');

return this.pdfViewer.pdfDocument
.getPage(itemIndex + 1)
.getPage(pageNum)
.then((page) => {
const viewport = page.getViewport(1);
canvas.width = THUMBNAIL_WIDTH_MAX;
canvas.height = THUMBNAIL_WIDTH_MAX / this.pageRatio;
const scale = THUMBNAIL_WIDTH_MAX / viewport.width;
const { width, height } = page.getViewport(1);
// Get the current page w:h ratio in case it differs from the first page
const curPageRatio = width / height;

// Handle the case where the current page's w:h ratio is less than the
// `pageRatio` which means that this page is probably more portrait than
// landscape
if (curPageRatio < this.pageRatio) {
// Set the canvas height to that of the thumbnail max height
canvas.height = THUMBNAIL_WIDTH_MAX / this.pageRatio;
// Find the canvas width based on the curent page ratio
canvas.width = canvas.height * curPageRatio;
} else {
// In case the current page ratio is same as the first page
// or in case it's larger (which means that it's wider), keep
// the width at the max thumbnail width
canvas.width = THUMBNAIL_WIDTH_MAX;
// Find the height based on the current page ratio
canvas.height = THUMBNAIL_WIDTH_MAX / curPageRatio;
}

// The amount for which to scale down the current page
const { width: canvasWidth } = canvas;
const scale = canvasWidth / width;
return page.render({
canvasContext: canvas.getContext('2d'),
viewport: page.getViewport(scale)
});
})
.then(() => {
const imageEl = document.createElement('img');
imageEl.classList.add(CLASS_BOX_PREVIEW_THUMBNAIL_IMAGE);
imageEl.src = canvas.toDataURL();
.then(() => canvas.toDataURL());
}

// Cache this image element for future use
this.thumbnailImageCache[itemIndex] = imageEl;
/**
* Creates the image element
* @param {string} dataUrl - The image data URL for the thumbnail
* @return {HTMLElement} - The image element
*/
createImageEl(dataUrl) {
const imageEl = document.createElement('div');
imageEl.classList.add(CLASS_BOX_PREVIEW_THUMBNAIL_IMAGE);
imageEl.style.backgroundImage = `url('${dataUrl}')`;

return imageEl;
});
// Add the height and width to the image to be the same as the thumbnail
// so that the css `background-image` rules will work
imageEl.style.width = `${DEFAULT_THUMBNAILS_SIDEBAR_WIDTH}px`;
imageEl.style.height = `${DEFAULT_THUMBNAILS_SIDEBAR_WIDTH / this.pageRatio}px`;

return imageEl;
}

/**
Expand Down Expand Up @@ -280,7 +327,7 @@ class ThumbnailsSidebar {
*/
applyCurrentPageSelection() {
this.currentThumbnails.forEach((thumbnailEl) => {
const parsedPageNum = Number.parseInt(thumbnailEl.dataset.bpPageNum, 10);
const parsedPageNum = parseInt(thumbnailEl.dataset.bpPageNum, 10);
if (parsedPageNum === this.currentPage) {
thumbnailEl.classList.add(CLASS_BOX_PREVIEW_THUMBNAIL_IS_SELECTED);
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/VirtualScroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ class VirtualScroller {
const { firstElementChild, lastElementChild, children } = this.listEl;

// Parse the row index from the data-attribute
let curStartOffset = firstElementChild ? Number.parseInt(firstElementChild.dataset.bpVsRowIndex, 10) : -1;
let curEndOffset = lastElementChild ? Number.parseInt(lastElementChild.dataset.bpVsRowIndex, 10) : -1;
let curStartOffset = firstElementChild ? parseInt(firstElementChild.dataset.bpVsRowIndex, 10) : -1;
let curEndOffset = lastElementChild ? parseInt(lastElementChild.dataset.bpVsRowIndex, 10) : -1;

// If the data-attribute value is not present default to invalid -1
curStartOffset = isFinite(curStartOffset) ? curStartOffset : -1;
Expand Down
51 changes: 45 additions & 6 deletions src/lib/__tests__/ThumbnailsSidebar-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,28 +196,67 @@ describe('ThumbnailsSidebar', () => {
});
});

describe('createThumbnailImage()', () => {
describe('createThumbnailImage', () => {
beforeEach(() => {
stubs.getThumbnailDataURL = sandbox
.stub(thumbnailsSidebar, 'getThumbnailDataURL')
.returns(Promise.resolve());
stubs.createImageEl = sandbox.stub(thumbnailsSidebar, 'createImageEl');
});

it('should resolve immediately if the image is in cache', () => {
const cachedImage = {};
thumbnailsSidebar.thumbnailImageCache = { 1: cachedImage };

return thumbnailsSidebar.createThumbnailImage(1).then(() => {
expect(stubs.getPage).not.to.be.called;
expect(stubs.createImageEl).not.to.be.called;
});
});

it('should create an image element if not in cache', () => {
const cachedImage = {};
thumbnailsSidebar.thumbnailImageCache = { 1: cachedImage };
stubs.createImageEl.returns(cachedImage);

return thumbnailsSidebar.createThumbnailImage(0).then((imageEl) => {
expect(stubs.createImageEl).to.be.called;
expect(thumbnailsSidebar.thumbnailImageCache[0]).to.be.eql(imageEl);
});
});
});

describe('getThumbnailDataURL()', () => {
it('should scale canvas the same as the first page if page ratio is the same', () => {
const cachedImage = {};
thumbnailsSidebar.thumbnailImageCache = { 1: cachedImage };
thumbnailsSidebar.pageRatio = 1;

stubs.getViewport.returns({ width: 10, height: 10 });
// Current page has same ratio
stubs.getViewport.withArgs(1).returns({ width: 10, height: 10 });
stubs.render.returns(Promise.resolve());

return thumbnailsSidebar.createThumbnailImage(0).then((imageEl) => {
const expScale = 21; // Should be THUMBNAIL_WIDTH_MAX(210) / 10 = 21

return thumbnailsSidebar.getThumbnailDataURL(1).then(() => {
expect(stubs.getPage).to.be.called;
expect(stubs.getViewport).to.be.called;
expect(thumbnailsSidebar.thumbnailImageCache[0]).to.be.eql(imageEl);
expect(stubs.getViewport.withArgs(expScale)).to.be.called;
});
});

it('should handle non-uniform page ratios', () => {
const cachedImage = {};
thumbnailsSidebar.thumbnailImageCache = { 1: cachedImage };
thumbnailsSidebar.pageRatio = 1;

// Current page has ratio of 0.5 instead of 1
stubs.getViewport.withArgs(1).returns({ width: 10, height: 20 });
stubs.render.returns(Promise.resolve());

const expScale = 10.5; // Should be 10.5 instead of 21 because the viewport ratio above is 0.5 instead of 1

return thumbnailsSidebar.createThumbnailImage(0).then(() => {
expect(stubs.getPage).to.be.called;
expect(stubs.getViewport.withArgs(expScale)).to.be.called;
});
});
});
Expand Down
1 change: 1 addition & 0 deletions src/lib/_boxuiVariables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ $tendemob-grey: #64686d !default;
$sunset-grey: #464a4f !default;
$seventy-sixers: #767676 !default;
$approx-mischka-grey: #a3adb9 !default;
$d-eight: #d8d8d8 !default;
15 changes: 10 additions & 5 deletions src/lib/viewers/doc/_docBase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ $thumbnail-border-radius: 4px;
.bp-thumbnails-container {
border-right: solid 1px $seesee;
display: flex;
flex: 0 0 181px; // Accounts for the 1px border on the right so the remaining width is actually 180
flex: 0 0 201px; // Accounts for the 1px border on the right so the remaining width is actually 180
}

.bp-thumbnail {
align-items: center;
background-color: #d8d8d8;
background-color: $d-eight;
border: 0;
border-radius: $thumbnail-border-radius;
display: flex;
flex: 1 1 auto;
flex: 1 0 auto;
justify-content: center;
margin: 0 15px;
margin: 0 25px;
padding: 0;
position: relative;
width: 150px;
}

.bp-thumbnail-page-number {
Expand All @@ -28,17 +29,21 @@ $thumbnail-border-radius: 4px;
bottom: 10px;
color: $white;
font-size: 11px;
left: 50%;
line-height: 16px;
opacity: .7;
padding: 0 5px;
pointer-events: none;
position: absolute;
transform: translateX(-50%);
}

.bp-thumbnail-image {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
border-radius: $thumbnail-border-radius;
pointer-events: none;
width: 100%;
}

// Applies a border *outside* the thumbnail button itself rather than
Expand Down

0 comments on commit 2dc3174

Please sign in to comment.