Add spread modes to web viewer

This builds on the scrolling mode work to add three buttons for joining
page spreads together: one for the default view, with no page spreads,
and two for spreads starting on odd-numbered or even-numbered pages.
This commit is contained in:
Ryan Hendrickson 2018-05-14 23:10:32 -04:00
parent 91cbc185da
commit 3d83c646c6
16 changed files with 168 additions and 25 deletions

View File

@ -72,6 +72,13 @@ scroll_horizontal_label=Horizontal Scrolling
scroll_wrapped.title=Use Wrapped Scrolling
scroll_wrapped_label=Wrapped Scrolling
spread_none.title=Do not join page spreads
spread_none_label=No Spreads
spread_odd.title=Join page spreads starting with odd-numbered pages
spread_odd_label=Odd Spreads
spread_even.title=Join page spreads starting with even-numbered pages
spread_even_label=Even Spreads
# Document properties dialog box
document_properties.title=Document Properties…
document_properties_label=Document Properties…

View File

@ -1386,6 +1386,7 @@ let PDFViewerApplication = {
eventBus.on('rotatecw', webViewerRotateCw);
eventBus.on('rotateccw', webViewerRotateCcw);
eventBus.on('switchscrollmode', webViewerSwitchScrollMode);
eventBus.on('switchspreadmode', webViewerSwitchSpreadMode);
eventBus.on('documentproperties', webViewerDocumentProperties);
eventBus.on('find', webViewerFind);
eventBus.on('findfromurlhash', webViewerFindFromUrlHash);
@ -1453,6 +1454,7 @@ let PDFViewerApplication = {
eventBus.off('rotatecw', webViewerRotateCw);
eventBus.off('rotateccw', webViewerRotateCcw);
eventBus.off('switchscrollmode', webViewerSwitchScrollMode);
eventBus.off('switchspreadmode', webViewerSwitchSpreadMode);
eventBus.off('documentproperties', webViewerDocumentProperties);
eventBus.off('find', webViewerFind);
eventBus.off('findfromurlhash', webViewerFindFromUrlHash);
@ -1965,6 +1967,9 @@ function webViewerRotateCcw() {
function webViewerSwitchScrollMode(evt) {
PDFViewerApplication.pdfViewer.setScrollMode(evt.mode);
}
function webViewerSwitchSpreadMode(evt) {
PDFViewerApplication.pdfViewer.setSpreadMode(evt.mode);
}
function webViewerDocumentProperties() {
PDFViewerApplication.pdfDocumentProperties.open();
}

View File

@ -35,6 +35,12 @@ const ScrollMode = {
WRAPPED: 2,
};
const SpreadMode = {
NONE: 0, // The default value.
ODD: 1,
EVEN: 2,
};
/**
* @typedef {Object} PDFViewerOptions
* @property {HTMLDivElement} container - The container for the viewer element.
@ -71,6 +77,10 @@ const ScrollMode = {
* document pages should be laid out within the scrolling container. The
* constants from {ScrollMode} should be used. The default value is
* `ScrollMode.VERTICAL`.
* @property {number} spreadMode - (optional) If not `SpreadMode.NONE`, groups
* pages into spreads, starting with odd- or even-numbered pages. The
* constants from {SpreadMode} should be used. The default value is
* `SpreadMode.NONE`.
*/
function PDFPageViewBuffer(size) {
@ -153,6 +163,7 @@ class BaseViewer {
this.maxCanvasPixels = options.maxCanvasPixels;
this.l10n = options.l10n || NullL10n;
this.scrollMode = options.scrollMode || ScrollMode.VERTICAL;
this.spreadMode = options.spreadMode || SpreadMode.NONE;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
@ -428,6 +439,9 @@ class BaseViewer {
bindOnAfterAndBeforeDraw(pageView);
this._pages.push(pageView);
}
if (this.spreadMode !== SpreadMode.NONE) {
this._regroupSpreads();
}
// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
@ -1020,9 +1034,20 @@ class BaseViewer {
classList.toggle('scrollHorizontal', mode === ScrollMode.HORIZONTAL);
classList.toggle('scrollWrapped', mode === ScrollMode.WRAPPED);
}
setSpreadMode(mode) {
if (mode !== this.spreadMode) {
this.spreadMode = mode;
this.eventBus.dispatch('spreadmodechanged', { mode, });
this._regroupSpreads();
}
}
_regroupSpreads() {}
}
export {
BaseViewer,
ScrollMode,
SpreadMode,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

View File

@ -46,29 +46,40 @@
border: none;
}
.pdfViewer.scrollHorizontal, .pdfViewer.scrollWrapped {
.pdfViewer.scrollHorizontal, .pdfViewer.scrollWrapped, .spread {
margin-left: 3.5px;
margin-right: 3.5px;
text-align: center;
}
.pdfViewer.scrollHorizontal {
.pdfViewer.scrollHorizontal, .spread {
white-space: nowrap;
}
.pdfViewer.removePageBorders {
.pdfViewer.removePageBorders,
.pdfViewer.scrollHorizontal .spread,
.pdfViewer.scrollWrapped .spread {
margin-left: 0;
margin-right: 0;
}
.spread .page,
.pdfViewer.scrollHorizontal .page,
.pdfViewer.scrollWrapped .page {
.pdfViewer.scrollWrapped .page,
.pdfViewer.scrollHorizontal .spread,
.pdfViewer.scrollWrapped .spread {
display: inline-block;
margin-left: -3.5px;
margin-right: -3.5px;
vertical-align: middle;
}
.spread .page,
.pdfViewer.scrollHorizontal .page,
.pdfViewer.scrollWrapped .page {
margin-left: -3.5px;
margin-right: -3.5px;
}
.pdfViewer.removePageBorders .spread .page,
.pdfViewer.removePageBorders.scrollHorizontal .page,
.pdfViewer.removePageBorders.scrollWrapped .page {
margin-left: 5px;
@ -99,7 +110,8 @@
margin-right: 0;
}
.pdfPresentationMode .pdfViewer .page {
.pdfPresentationMode .pdfViewer .page,
.pdfPresentationMode .pdfViewer .spread {
display: block;
}

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { BaseViewer, ScrollMode } from './base_viewer';
import { BaseViewer, ScrollMode, SpreadMode } from './base_viewer';
import { getVisibleElements, scrollIntoView } from './ui_utils';
import { shadow } from 'pdfjs-lib';
@ -23,8 +23,14 @@ class PDFViewer extends BaseViewer {
}
_scrollIntoView({ pageDiv, pageSpot = null, }) {
if (!pageSpot && this.scrollMode === ScrollMode.HORIZONTAL) {
pageSpot = { left: 0, top: 0, };
if (!pageSpot) {
const left = pageDiv.offsetLeft + pageDiv.clientLeft;
const right = left + pageDiv.clientWidth;
const { scrollLeft, clientWidth, } = this.container;
if (this.scrollMode === ScrollMode.HORIZONTAL ||
left < scrollLeft || right > scrollLeft + clientWidth) {
pageSpot = { left: 0, top: 0, };
}
}
scrollIntoView(pageDiv, pageSpot);
}
@ -80,6 +86,34 @@ class PDFViewer extends BaseViewer {
location: this._location,
});
}
_regroupSpreads() {
const container = this._setDocumentViewerElement, pages = this._pages;
while (container.firstChild) {
container.firstChild.remove();
}
if (this.spreadMode === SpreadMode.NONE) {
for (let i = 0, iMax = pages.length; i < iMax; ++i) {
container.appendChild(pages[i].div);
}
} else {
const parity = this.spreadMode - 1;
let spread = null;
for (let i = 0, iMax = pages.length; i < iMax; ++i) {
if (spread === null) {
spread = document.createElement('div');
spread.className = 'spread';
container.appendChild(spread);
} else if (i % 2 === parity) {
spread = spread.cloneNode(false);
container.appendChild(spread);
}
spread.appendChild(pages[i].div);
}
}
this.scrollPageIntoView({ pageNumber: this._currentPageNumber, });
this.update();
}
}
export {

View File

@ -13,9 +13,9 @@
* limitations under the License.
*/
import { ScrollMode, SpreadMode } from './base_viewer';
import { CursorTool } from './pdf_cursor_tools';
import { SCROLLBAR_PADDING } from './ui_utils';
import { ScrollMode } from './base_viewer';
/**
* @typedef {Object} SecondaryToolbarOptions
@ -83,6 +83,12 @@ class SecondaryToolbar {
eventDetails: { mode: ScrollMode.HORIZONTAL, }, close: true, },
{ element: options.scrollWrappedButton, eventName: 'switchscrollmode',
eventDetails: { mode: ScrollMode.WRAPPED, }, close: true, },
{ element: options.spreadNoneButton, eventName: 'switchspreadmode',
eventDetails: { mode: SpreadMode.NONE, }, close: true, },
{ element: options.spreadOddButton, eventName: 'switchspreadmode',
eventDetails: { mode: SpreadMode.ODD, }, close: true, },
{ element: options.spreadEvenButton, eventName: 'switchspreadmode',
eventDetails: { mode: SpreadMode.EVEN, }, close: true, },
{ element: options.documentPropertiesButton,
eventName: 'documentproperties', close: true, },
];
@ -102,10 +108,12 @@ class SecondaryToolbar {
this.reset();
// Bind the event listeners for click, cursor tool, and scroll mode actions.
// Bind the event listeners for click, cursor tool, and scroll/spread mode
// actions.
this._bindClickListeners();
this._bindCursorToolsListener(options);
this._bindScrollModeListener(options);
this._bindSpreadModeListener(options);
// Bind the event listener for adjusting the 'max-height' of the toolbar.
this.eventBus.on('resize', this._setMaxHeight.bind(this));
@ -191,6 +199,17 @@ class SecondaryToolbar {
});
}
_bindSpreadModeListener(buttons) {
this.eventBus.on('spreadmodechanged', function(evt) {
buttons.spreadNoneButton.classList.toggle('toggled',
evt.mode === SpreadMode.NONE);
buttons.spreadOddButton.classList.toggle('toggled',
evt.mode === SpreadMode.ODD);
buttons.spreadEvenButton.classList.toggle('toggled',
evt.mode === SpreadMode.EVEN);
});
}
open() {
if (this.opened) {
return;

View File

@ -316,9 +316,10 @@ function getPageSizeInches({ view, userUnit, rotate, }) {
*/
function backtrackBeforeAllVisibleElements(index, views, top) {
// binarySearchFirstItem's assumption is that the input is ordered, with only
// one index where the conditions flips from false to true:
// [false ..., true...]. With wrapped scrolling, it is possible to have
// [false ..., true, false, true ...].
// one index where the conditions flips from false to true: [false ...,
// true...]. With vertical scrolling and spreads, it is possible to have
// [false ..., true, false, true ...]. With wrapped scrolling we can have a
// similar sequence, with many more mixed true and false in the middle.
//
// So there is no guarantee that the binary search yields the index of the
// first visible element. It could have been any of the other visible elements
@ -451,10 +452,11 @@ function getVisibleElements(scrollEl, views, sortByVisibility = false,
isElementBottomAfterViewTop);
if (views.length > 0 && !horizontal) {
// In wrapped scrolling, with some page sizes, isElementBottomAfterViewTop
// doesn't satisfy the binary search condition: there can be pages with
// bottoms above the view top between pages with bottoms below. This
// function detects and corrects that error; see it for more comments.
// In wrapped scrolling (or vertical scrolling with spreads), with some page
// sizes, isElementBottomAfterViewTop doesn't satisfy the binary search
// condition: there can be pages with bottoms above the view top between
// pages with bottoms below. This function detects and corrects that error;
// see it for more comments.
firstVisibleElementInd =
backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
}
@ -462,11 +464,11 @@ function getVisibleElements(scrollEl, views, sortByVisibility = false,
// lastEdge acts as a cutoff for us to stop looping, because we know all
// subsequent pages will be hidden.
//
// When using wrapped scrolling, we can't simply stop the first time we reach
// a page below the bottom of the view; the tops of subsequent pages on the
// same row could still be visible. In horizontal scrolling, we don't have
// that issue, so we can stop as soon as we pass `right`, without needing the
// code below that handles the -1 case.
// When using wrapped scrolling or vertical scrolling with spreads, we can't
// simply stop the first time we reach a page below the bottom of the view;
// the tops of subsequent pages on the same row could still be visible. In
// horizontal scrolling, we don't have that issue, so we can stop as soon as
// we pass `right`, without needing the code below that handles the -1 case.
let lastEdge = horizontal ? right : -1;
for (let i = firstVisibleElementInd, ii = views.length; i < ii; i++) {

View File

@ -978,6 +978,18 @@ html[dir="rtl"] .secondaryToolbarButton > span {
content: url(images/secondaryToolbarButton-scrollWrapped.png);
}
.secondaryToolbarButton.spreadNone::before {
content: url(images/secondaryToolbarButton-spreadNone.png);
}
.secondaryToolbarButton.spreadOdd::before {
content: url(images/secondaryToolbarButton-spreadOdd.png);
}
.secondaryToolbarButton.spreadEven::before {
content: url(images/secondaryToolbarButton-spreadEven.png);
}
.secondaryToolbarButton.documentProperties::before {
content: url(images/secondaryToolbarButton-documentProperties.png);
}
@ -1713,6 +1725,18 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
content: url(images/secondaryToolbarButton-scrollWrapped@2x.png);
}
.secondaryToolbarButton.spreadNone::before {
content: url(images/secondaryToolbarButton-spreadNone@2x.png);
}
.secondaryToolbarButton.spreadOdd::before {
content: url(images/secondaryToolbarButton-spreadOdd@2x.png);
}
.secondaryToolbarButton.spreadEven::before {
content: url(images/secondaryToolbarButton-spreadEven@2x.png);
}
.secondaryToolbarButton.documentProperties::before {
content: url(images/secondaryToolbarButton-documentProperties@2x.png);
}

View File

@ -180,7 +180,19 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="horizontalToolbarSeparator"></div>
<button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="65" data-l10n-id="document_properties">
<button id="spreadNone" class="secondaryToolbarButton spreadNone toggled" title="Do not join page spreads" tabindex="65" data-l10n-id="spread_none">
<span data-l10n-id="spread_none_label">No Spreads</span>
</button>
<button id="spreadOdd" class="secondaryToolbarButton spreadOdd" title="Join page spreads starting with odd-numbered pages" tabindex="66" data-l10n-id="spread_odd">
<span data-l10n-id="spread_odd_label">Odd Spreads</span>
</button>
<button id="spreadEven" class="secondaryToolbarButton spreadEven" title="Join page spreads starting with even-numbered pages" tabindex="67" data-l10n-id="spread_even">
<span data-l10n-id="spread_even_label">Even Spreads</span>
</button>
<div class="horizontalToolbarSeparator"></div>
<button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="68" data-l10n-id="document_properties">
<span data-l10n-id="document_properties_label">Document Properties…</span>
</button>
</div>

View File

@ -99,6 +99,9 @@ function getViewerConfiguration() {
scrollVerticalButton: document.getElementById('scrollVertical'),
scrollHorizontalButton: document.getElementById('scrollHorizontal'),
scrollWrappedButton: document.getElementById('scrollWrapped'),
spreadNoneButton: document.getElementById('spreadNone'),
spreadOddButton: document.getElementById('spreadOdd'),
spreadEvenButton: document.getElementById('spreadEven'),
documentPropertiesButton: document.getElementById('documentProperties'),
},
fullscreen: {