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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user