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:
parent
91cbc185da
commit
3d83c646c6
@ -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…
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
BIN
web/images/secondaryToolbarButton-spreadEven.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadEven.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 347 B |
BIN
web/images/secondaryToolbarButton-spreadEven@2x.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadEven@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 694 B |
BIN
web/images/secondaryToolbarButton-spreadNone.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadNone.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 B |
BIN
web/images/secondaryToolbarButton-spreadNone@2x.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadNone@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 261 B |
BIN
web/images/secondaryToolbarButton-spreadOdd.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadOdd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 344 B |
BIN
web/images/secondaryToolbarButton-spreadOdd@2x.png
Normal file
BIN
web/images/secondaryToolbarButton-spreadOdd@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 621 B |
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,9 +23,15 @@ class PDFViewer extends BaseViewer {
|
||||
}
|
||||
|
||||
_scrollIntoView({ pageDiv, pageSpot = null, }) {
|
||||
if (!pageSpot && this.scrollMode === ScrollMode.HORIZONTAL) {
|
||||
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 {
|
||||
|
@ -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;
|
||||
|
@ -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++) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user