@progress/kendo-pdfviewer-common 0.2.11 → 0.3.0-dev.202409251459

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ import { createElement, scrollToPage } from './utils';
2
+ class Annotation {
3
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
4
+ this.container = container;
5
+ this.viewport = viewport;
6
+ this.zoom = zoom;
7
+ this.annotation = annotation;
8
+ this.pdfDoc = pdfDoc;
9
+ this.rootElement = rootElement;
10
+ }
11
+ destroy() {
12
+ }
13
+ }
14
+ export class LinkAnnotation extends Annotation {
15
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
16
+ super(container, viewport, annotation, pdfDoc, zoom, rootElement);
17
+ this.onLinkClick = (e) => {
18
+ const destination = e.target.getAttribute('href');
19
+ if (destination && destination.indexOf('#') === 0) {
20
+ this.navigateToDestination(destination);
21
+ e.preventDefault();
22
+ }
23
+ };
24
+ this.linkElement = null;
25
+ this.renderAnnotation();
26
+ this.bindEvents();
27
+ }
28
+ destroy() {
29
+ this.unbindEvents();
30
+ }
31
+ bindEvents() {
32
+ this.linkElement.addEventListener('click', this.onLinkClick);
33
+ }
34
+ unbindEvents() {
35
+ this.linkElement.removeEventListener('click', this.onLinkClick);
36
+ }
37
+ navigateToDestination(destination) {
38
+ const dest = destination.split('#')[1];
39
+ this.pdfDoc.getDestination(dest).then(r => {
40
+ this.pdfDoc.getPageIndex(r[0]).then(i => this.goToPage(i));
41
+ });
42
+ }
43
+ goToPage(pageNumber) {
44
+ scrollToPage(this.rootElement, pageNumber);
45
+ }
46
+ renderAnnotation() {
47
+ const annotation = this.annotation, viewport = this.viewport, rect = annotation.rect, boundingRect = [
48
+ [rect[0], rect[1]],
49
+ [rect[2], rect[3]]
50
+ ], rawHeight = viewport.rawDims.pageHeight;
51
+ const left = Math.min(boundingRect[0][0], boundingRect[1][0]) * this.zoom, top = (rawHeight - Math.max(boundingRect[0][1], boundingRect[1][1])) * this.zoom, width = (boundingRect[1][0] - boundingRect[0][0]) * this.zoom, height = (boundingRect[1][1] - boundingRect[0][1]) * this.zoom;
52
+ const url = annotation.url || (annotation.dest && `#${encodeURI(annotation.dest)}`);
53
+ const annotationElement = createElement('span', '', {
54
+ position: 'absolute',
55
+ left: left + 'pt',
56
+ top: top + 'pt'
57
+ });
58
+ const linkElement = this.linkElement = createElement('a', '', {
59
+ width: width + 'pt',
60
+ height: height + 'pt',
61
+ display: 'inline-block',
62
+ pointerEvents: 'auto'
63
+ });
64
+ linkElement.setAttribute('href', url);
65
+ annotationElement.append(linkElement);
66
+ this.container.append(annotationElement);
67
+ }
68
+ }
@@ -1,4 +1,5 @@
1
1
  import Draggable from '@progress/kendo-draggable';
2
+ import { renderPage, currentPage } from './utils';
2
3
  const throttle = function (func, wait, options = {}) {
3
4
  let timeout, context, args, result;
4
5
  let previous = 0;
@@ -81,6 +82,21 @@ export class Scroller {
81
82
  this.state.trackNextElementScroll = true;
82
83
  }
83
84
  };
85
+ this.onDemandScroll = () => {
86
+ const pages = this.options.pages, currentPageIndex = currentPage(this.element.parentElement), page = pages[currentPageIndex], nextPage = pages[currentPageIndex + 1], previousPage = pages[currentPageIndex - 1], pageInfo = page === null || page === void 0 ? void 0 : page._pageInfo, nextPageInfo = nextPage === null || nextPage === void 0 ? void 0 : nextPage._pageInfo, previousPageInfo = previousPage === null || previousPage === void 0 ? void 0 : previousPage._pageInfo, error = (e) => {
87
+ throw new Error(e);
88
+ };
89
+ // Render the current and surrounding pages if they have not been rendered already.
90
+ if (page && !pageInfo.rendered && !pageInfo.renderInProgress) {
91
+ renderPage(page, pageInfo.emptyPage, error);
92
+ }
93
+ if (nextPage && !nextPageInfo.rendered && !nextPageInfo.renderInProgress) {
94
+ renderPage(nextPage, nextPageInfo.emptyPage, error);
95
+ }
96
+ if (previousPage && !previousPageInfo.rendered && !previousPageInfo.renderInProgress) {
97
+ renderPage(previousPage, previousPageInfo.emptyPage, error);
98
+ }
99
+ };
84
100
  this.onDragStart = (e) => {
85
101
  this.state.dragStarted = false;
86
102
  if (!this.shouldTrackPanEvents()) {
@@ -175,6 +191,9 @@ export class Scroller {
175
191
  this.throttledOnElementScroll = this.onElementScroll;
176
192
  }
177
193
  this.element.addEventListener(SCROLL, this.throttledOnElementScroll);
194
+ if (this.options.loadOnDemand) {
195
+ this.element.addEventListener(SCROLL, this.onDemandScroll);
196
+ }
178
197
  }
179
198
  unbindEvents() {
180
199
  this.unbindElementScroll();
@@ -189,6 +208,9 @@ export class Scroller {
189
208
  this.throttledOnElementScroll.cancel();
190
209
  this.throttledOnElementScroll = null;
191
210
  }
211
+ if (this.options.loadOnDemand) {
212
+ this.element.removeEventListener(SCROLL, this.onDemandScroll);
213
+ }
192
214
  this.element.removeEventListener(SCROLL, this.throttledOnElementScroll);
193
215
  }
194
216
  setState(newState) {
package/dist/es/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { saveAs } from '@progress/kendo-file-saver';
2
2
  import { detectDesktopBrowser, detectMobileOS } from '@progress/kendo-common';
3
3
  import { getDocument, TextLayer } from 'pdfjs-dist/legacy/build/pdf.mjs';
4
+ import { LinkAnnotation } from './annotations';
4
5
  const MAX_CANVAS_WIDTH_HEIGHT_CHROME = 65535;
5
6
  const MAX_CANVAS_AREA_CHROME_SAFARI = 268435456;
6
7
  const MAX_CANVAS_WIDTH_HEIGHT_FIREFOX = 32767;
@@ -83,7 +84,8 @@ export const download = (options, fileName = 'Document', saveOptions = {}, onDow
83
84
  */
84
85
  export const loadPDF = (options) => {
85
86
  const params = getDocumentParameters(options);
86
- const { dom, zoom, done, error } = options;
87
+ const { dom, zoom, done, error, rootElement, enableAnnotations } = options;
88
+ const loadOnDemandPageSize = options.loadOnDemandPageSize || 2;
87
89
  getDocument(params)
88
90
  .promise.then((pdfDoc) => {
89
91
  const pages = [];
@@ -94,7 +96,18 @@ export const loadPDF = (options) => {
94
96
  }).then(({ pages, pdfDoc }) => {
95
97
  Promise.all(pages)
96
98
  .then((all) => all.map((page, i) => {
97
- appendPage(dom, renderPage(page, zoom, error), i);
99
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
100
+ appendPage(dom, emptyPage.pageElement, i);
101
+ if (options.loadOnDemand) {
102
+ // If LOD is enabled, render the first two(default) or X pages initially.
103
+ if (i < loadOnDemandPageSize) {
104
+ renderPage(page, emptyPage, error);
105
+ }
106
+ }
107
+ else {
108
+ renderPage(page, emptyPage, error);
109
+ }
110
+ page._pageInfo.emptyPage = emptyPage;
98
111
  return page;
99
112
  })).then((pdfPages) => {
100
113
  // required by new PDFJS version
@@ -122,17 +135,39 @@ export const loadPDF = (options) => {
122
135
  * @hidden
123
136
  */
124
137
  export const reloadDocument = (params) => {
125
- const { pdfDoc, zoom, dom, done, error } = params;
138
+ const { pdfDoc, zoom, dom, done, error, loadOnDemand, rootElement, enableAnnotations } = params;
126
139
  const pages = [];
140
+ let currentPageIndex = 0;
141
+ // Save the index of the current page in view before we reload the document.
142
+ if (loadOnDemand && rootElement) {
143
+ currentPageIndex = currentPage(rootElement);
144
+ }
127
145
  for (let i = 1; i <= pdfDoc.numPages; i++) {
128
146
  pages.push(pdfDoc.getPage(i));
129
147
  }
130
148
  Promise.all(pages)
131
149
  .then((all) => all.map((page, i) => {
132
- appendPage(dom, renderPage(page, zoom, error), i);
150
+ // Set 'rendered' back to false so that the pages can be re-rendered when scrolling.
151
+ page._pageInfo.rendered = false;
152
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
153
+ appendPage(dom, emptyPage.pageElement, i);
154
+ // If on demand is not enabled, proceed as usual.
155
+ if (!loadOnDemand) {
156
+ renderPage(page, emptyPage, error);
157
+ }
158
+ page._pageInfo.emptyPage = emptyPage;
133
159
  return page;
134
160
  }))
135
- .then(done)
161
+ .then((pdfPages) => {
162
+ /* If on demand is enabled, render the page that was previously in view.
163
+ Then scroll to that page. */
164
+ if (loadOnDemand) {
165
+ const page = pdfPages[currentPageIndex];
166
+ renderPage(page, page._pageInfo.emptyPage, error);
167
+ scrollToPage(rootElement, currentPageIndex);
168
+ }
169
+ done(pdfPages);
170
+ })
136
171
  .catch(error);
137
172
  };
138
173
  /**
@@ -177,6 +212,19 @@ const openPrintDialog = (dom, width, height, done, onError) => {
177
212
  printDialog.addEventListener('afterprint', onAfterPrint);
178
213
  }
179
214
  };
215
+ const createEmptyPage = (page, zoom, pdfDoc, enableAnnotations, rootElement) => {
216
+ const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
217
+ return {
218
+ canvasContext,
219
+ viewport,
220
+ pageElement,
221
+ styles,
222
+ zoom,
223
+ pdfDoc,
224
+ enableAnnotations,
225
+ rootElement
226
+ };
227
+ };
180
228
  const renderCanvas = (page, done, error) => {
181
229
  const { canvasContext, viewport, scaleNum, canvas, pageElement } = createCanvas(page);
182
230
  page.render({ canvasContext, viewport })
@@ -192,7 +240,7 @@ const renderCanvas = (page, done, error) => {
192
240
  .catch(error);
193
241
  return viewport;
194
242
  };
195
- const createElement = function (name, className, styles) {
243
+ export const createElement = function (name, className, styles) {
196
244
  const element = document.createElement(name);
197
245
  if (className) {
198
246
  element.className = className;
@@ -206,8 +254,9 @@ const transforms = {
206
254
  '180': 'rotate(180deg) translate(-100%, -100%)',
207
255
  '270': 'rotate(270deg) translateX(-100%)'
208
256
  };
209
- const renderPage = (page, zoom, error) => {
210
- const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
257
+ export const renderPage = (page, emptyPage, error) => {
258
+ const { canvasContext, viewport, pageElement, styles, zoom, pdfDoc, enableAnnotations, rootElement } = emptyPage;
259
+ page._pageInfo.renderInProgress = true;
211
260
  page.render({ canvasContext, viewport })
212
261
  .promise.then(() => {
213
262
  page.getTextContent().then((textContent) => {
@@ -229,9 +278,36 @@ const renderPage = (page, zoom, error) => {
229
278
  el.style.fontSize = el.style.fontSize.replace(/px/g, 'pt');
230
279
  }
231
280
  });
232
- pageElement.appendChild(textLayer);
281
+ pageElement.prepend(textLayer); // Use prepend to ensure the element is always inserted before the annotation layer.
233
282
  }).catch(error);
234
283
  });
284
+ if (enableAnnotations) {
285
+ page.getAnnotations({ intent: 'display' }).then((annotations) => {
286
+ const annotationLayer = createElement('div', 'k-annotations-layer', {
287
+ position: 'absolute',
288
+ top: '0',
289
+ left: '0',
290
+ overflow: 'hidden',
291
+ height: styles.height,
292
+ width: styles.width,
293
+ pointerEvents: 'none'
294
+ });
295
+ pageElement.appendChild(annotationLayer);
296
+ for (const annotation of annotations) {
297
+ switch (annotation.subtype) {
298
+ case 'Link':
299
+ new LinkAnnotation(annotationLayer, viewport, annotation, pdfDoc, zoom, rootElement);
300
+ break;
301
+ default:
302
+ null;
303
+ }
304
+ }
305
+ });
306
+ }
307
+ })
308
+ .then(() => {
309
+ page._pageInfo.rendered = true;
310
+ page._pageInfo.renderInProgress = false;
235
311
  })
236
312
  .catch(error);
237
313
  return pageElement;
@@ -0,0 +1,68 @@
1
+ import { createElement, scrollToPage } from './utils';
2
+ class Annotation {
3
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
4
+ this.container = container;
5
+ this.viewport = viewport;
6
+ this.zoom = zoom;
7
+ this.annotation = annotation;
8
+ this.pdfDoc = pdfDoc;
9
+ this.rootElement = rootElement;
10
+ }
11
+ destroy() {
12
+ }
13
+ }
14
+ export class LinkAnnotation extends Annotation {
15
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
16
+ super(container, viewport, annotation, pdfDoc, zoom, rootElement);
17
+ this.onLinkClick = (e) => {
18
+ const destination = e.target.getAttribute('href');
19
+ if (destination && destination.indexOf('#') === 0) {
20
+ this.navigateToDestination(destination);
21
+ e.preventDefault();
22
+ }
23
+ };
24
+ this.linkElement = null;
25
+ this.renderAnnotation();
26
+ this.bindEvents();
27
+ }
28
+ destroy() {
29
+ this.unbindEvents();
30
+ }
31
+ bindEvents() {
32
+ this.linkElement.addEventListener('click', this.onLinkClick);
33
+ }
34
+ unbindEvents() {
35
+ this.linkElement.removeEventListener('click', this.onLinkClick);
36
+ }
37
+ navigateToDestination(destination) {
38
+ const dest = destination.split('#')[1];
39
+ this.pdfDoc.getDestination(dest).then(r => {
40
+ this.pdfDoc.getPageIndex(r[0]).then(i => this.goToPage(i));
41
+ });
42
+ }
43
+ goToPage(pageNumber) {
44
+ scrollToPage(this.rootElement, pageNumber);
45
+ }
46
+ renderAnnotation() {
47
+ const annotation = this.annotation, viewport = this.viewport, rect = annotation.rect, boundingRect = [
48
+ [rect[0], rect[1]],
49
+ [rect[2], rect[3]]
50
+ ], rawHeight = viewport.rawDims.pageHeight;
51
+ const left = Math.min(boundingRect[0][0], boundingRect[1][0]) * this.zoom, top = (rawHeight - Math.max(boundingRect[0][1], boundingRect[1][1])) * this.zoom, width = (boundingRect[1][0] - boundingRect[0][0]) * this.zoom, height = (boundingRect[1][1] - boundingRect[0][1]) * this.zoom;
52
+ const url = annotation.url || (annotation.dest && `#${encodeURI(annotation.dest)}`);
53
+ const annotationElement = createElement('span', '', {
54
+ position: 'absolute',
55
+ left: left + 'pt',
56
+ top: top + 'pt'
57
+ });
58
+ const linkElement = this.linkElement = createElement('a', '', {
59
+ width: width + 'pt',
60
+ height: height + 'pt',
61
+ display: 'inline-block',
62
+ pointerEvents: 'auto'
63
+ });
64
+ linkElement.setAttribute('href', url);
65
+ annotationElement.append(linkElement);
66
+ this.container.append(annotationElement);
67
+ }
68
+ }
@@ -1,4 +1,5 @@
1
1
  import Draggable from '@progress/kendo-draggable';
2
+ import { renderPage, currentPage } from './utils';
2
3
  const throttle = function (func, wait, options = {}) {
3
4
  let timeout, context, args, result;
4
5
  let previous = 0;
@@ -81,6 +82,21 @@ export class Scroller {
81
82
  this.state.trackNextElementScroll = true;
82
83
  }
83
84
  };
85
+ this.onDemandScroll = () => {
86
+ const pages = this.options.pages, currentPageIndex = currentPage(this.element.parentElement), page = pages[currentPageIndex], nextPage = pages[currentPageIndex + 1], previousPage = pages[currentPageIndex - 1], pageInfo = page === null || page === void 0 ? void 0 : page._pageInfo, nextPageInfo = nextPage === null || nextPage === void 0 ? void 0 : nextPage._pageInfo, previousPageInfo = previousPage === null || previousPage === void 0 ? void 0 : previousPage._pageInfo, error = (e) => {
87
+ throw new Error(e);
88
+ };
89
+ // Render the current and surrounding pages if they have not been rendered already.
90
+ if (page && !pageInfo.rendered && !pageInfo.renderInProgress) {
91
+ renderPage(page, pageInfo.emptyPage, error);
92
+ }
93
+ if (nextPage && !nextPageInfo.rendered && !nextPageInfo.renderInProgress) {
94
+ renderPage(nextPage, nextPageInfo.emptyPage, error);
95
+ }
96
+ if (previousPage && !previousPageInfo.rendered && !previousPageInfo.renderInProgress) {
97
+ renderPage(previousPage, previousPageInfo.emptyPage, error);
98
+ }
99
+ };
84
100
  this.onDragStart = (e) => {
85
101
  this.state.dragStarted = false;
86
102
  if (!this.shouldTrackPanEvents()) {
@@ -175,6 +191,9 @@ export class Scroller {
175
191
  this.throttledOnElementScroll = this.onElementScroll;
176
192
  }
177
193
  this.element.addEventListener(SCROLL, this.throttledOnElementScroll);
194
+ if (this.options.loadOnDemand) {
195
+ this.element.addEventListener(SCROLL, this.onDemandScroll);
196
+ }
178
197
  }
179
198
  unbindEvents() {
180
199
  this.unbindElementScroll();
@@ -189,6 +208,9 @@ export class Scroller {
189
208
  this.throttledOnElementScroll.cancel();
190
209
  this.throttledOnElementScroll = null;
191
210
  }
211
+ if (this.options.loadOnDemand) {
212
+ this.element.removeEventListener(SCROLL, this.onDemandScroll);
213
+ }
192
214
  this.element.removeEventListener(SCROLL, this.throttledOnElementScroll);
193
215
  }
194
216
  setState(newState) {
@@ -1,6 +1,7 @@
1
1
  import { saveAs } from '@progress/kendo-file-saver';
2
2
  import { detectDesktopBrowser, detectMobileOS } from '@progress/kendo-common';
3
3
  import { getDocument, TextLayer } from 'pdfjs-dist/legacy/build/pdf.mjs';
4
+ import { LinkAnnotation } from './annotations';
4
5
  const MAX_CANVAS_WIDTH_HEIGHT_CHROME = 65535;
5
6
  const MAX_CANVAS_AREA_CHROME_SAFARI = 268435456;
6
7
  const MAX_CANVAS_WIDTH_HEIGHT_FIREFOX = 32767;
@@ -83,7 +84,8 @@ export const download = (options, fileName = 'Document', saveOptions = {}, onDow
83
84
  */
84
85
  export const loadPDF = (options) => {
85
86
  const params = getDocumentParameters(options);
86
- const { dom, zoom, done, error } = options;
87
+ const { dom, zoom, done, error, rootElement, enableAnnotations } = options;
88
+ const loadOnDemandPageSize = options.loadOnDemandPageSize || 2;
87
89
  getDocument(params)
88
90
  .promise.then((pdfDoc) => {
89
91
  const pages = [];
@@ -94,7 +96,18 @@ export const loadPDF = (options) => {
94
96
  }).then(({ pages, pdfDoc }) => {
95
97
  Promise.all(pages)
96
98
  .then((all) => all.map((page, i) => {
97
- appendPage(dom, renderPage(page, zoom, error), i);
99
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
100
+ appendPage(dom, emptyPage.pageElement, i);
101
+ if (options.loadOnDemand) {
102
+ // If LOD is enabled, render the first two(default) or X pages initially.
103
+ if (i < loadOnDemandPageSize) {
104
+ renderPage(page, emptyPage, error);
105
+ }
106
+ }
107
+ else {
108
+ renderPage(page, emptyPage, error);
109
+ }
110
+ page._pageInfo.emptyPage = emptyPage;
98
111
  return page;
99
112
  })).then((pdfPages) => {
100
113
  // required by new PDFJS version
@@ -122,17 +135,39 @@ export const loadPDF = (options) => {
122
135
  * @hidden
123
136
  */
124
137
  export const reloadDocument = (params) => {
125
- const { pdfDoc, zoom, dom, done, error } = params;
138
+ const { pdfDoc, zoom, dom, done, error, loadOnDemand, rootElement, enableAnnotations } = params;
126
139
  const pages = [];
140
+ let currentPageIndex = 0;
141
+ // Save the index of the current page in view before we reload the document.
142
+ if (loadOnDemand && rootElement) {
143
+ currentPageIndex = currentPage(rootElement);
144
+ }
127
145
  for (let i = 1; i <= pdfDoc.numPages; i++) {
128
146
  pages.push(pdfDoc.getPage(i));
129
147
  }
130
148
  Promise.all(pages)
131
149
  .then((all) => all.map((page, i) => {
132
- appendPage(dom, renderPage(page, zoom, error), i);
150
+ // Set 'rendered' back to false so that the pages can be re-rendered when scrolling.
151
+ page._pageInfo.rendered = false;
152
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
153
+ appendPage(dom, emptyPage.pageElement, i);
154
+ // If on demand is not enabled, proceed as usual.
155
+ if (!loadOnDemand) {
156
+ renderPage(page, emptyPage, error);
157
+ }
158
+ page._pageInfo.emptyPage = emptyPage;
133
159
  return page;
134
160
  }))
135
- .then(done)
161
+ .then((pdfPages) => {
162
+ /* If on demand is enabled, render the page that was previously in view.
163
+ Then scroll to that page. */
164
+ if (loadOnDemand) {
165
+ const page = pdfPages[currentPageIndex];
166
+ renderPage(page, page._pageInfo.emptyPage, error);
167
+ scrollToPage(rootElement, currentPageIndex);
168
+ }
169
+ done(pdfPages);
170
+ })
136
171
  .catch(error);
137
172
  };
138
173
  /**
@@ -177,6 +212,19 @@ const openPrintDialog = (dom, width, height, done, onError) => {
177
212
  printDialog.addEventListener('afterprint', onAfterPrint);
178
213
  }
179
214
  };
215
+ const createEmptyPage = (page, zoom, pdfDoc, enableAnnotations, rootElement) => {
216
+ const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
217
+ return {
218
+ canvasContext,
219
+ viewport,
220
+ pageElement,
221
+ styles,
222
+ zoom,
223
+ pdfDoc,
224
+ enableAnnotations,
225
+ rootElement
226
+ };
227
+ };
180
228
  const renderCanvas = (page, done, error) => {
181
229
  const { canvasContext, viewport, scaleNum, canvas, pageElement } = createCanvas(page);
182
230
  page.render({ canvasContext, viewport })
@@ -192,7 +240,7 @@ const renderCanvas = (page, done, error) => {
192
240
  .catch(error);
193
241
  return viewport;
194
242
  };
195
- const createElement = function (name, className, styles) {
243
+ export const createElement = function (name, className, styles) {
196
244
  const element = document.createElement(name);
197
245
  if (className) {
198
246
  element.className = className;
@@ -206,8 +254,9 @@ const transforms = {
206
254
  '180': 'rotate(180deg) translate(-100%, -100%)',
207
255
  '270': 'rotate(270deg) translateX(-100%)'
208
256
  };
209
- const renderPage = (page, zoom, error) => {
210
- const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
257
+ export const renderPage = (page, emptyPage, error) => {
258
+ const { canvasContext, viewport, pageElement, styles, zoom, pdfDoc, enableAnnotations, rootElement } = emptyPage;
259
+ page._pageInfo.renderInProgress = true;
211
260
  page.render({ canvasContext, viewport })
212
261
  .promise.then(() => {
213
262
  page.getTextContent().then((textContent) => {
@@ -229,9 +278,36 @@ const renderPage = (page, zoom, error) => {
229
278
  el.style.fontSize = el.style.fontSize.replace(/px/g, 'pt');
230
279
  }
231
280
  });
232
- pageElement.appendChild(textLayer);
281
+ pageElement.prepend(textLayer); // Use prepend to ensure the element is always inserted before the annotation layer.
233
282
  }).catch(error);
234
283
  });
284
+ if (enableAnnotations) {
285
+ page.getAnnotations({ intent: 'display' }).then((annotations) => {
286
+ const annotationLayer = createElement('div', 'k-annotations-layer', {
287
+ position: 'absolute',
288
+ top: '0',
289
+ left: '0',
290
+ overflow: 'hidden',
291
+ height: styles.height,
292
+ width: styles.width,
293
+ pointerEvents: 'none'
294
+ });
295
+ pageElement.appendChild(annotationLayer);
296
+ for (const annotation of annotations) {
297
+ switch (annotation.subtype) {
298
+ case 'Link':
299
+ new LinkAnnotation(annotationLayer, viewport, annotation, pdfDoc, zoom, rootElement);
300
+ break;
301
+ default:
302
+ null;
303
+ }
304
+ }
305
+ });
306
+ }
307
+ })
308
+ .then(() => {
309
+ page._pageInfo.rendered = true;
310
+ page._pageInfo.renderInProgress = false;
235
311
  })
236
312
  .catch(error);
237
313
  return pageElement;
@@ -0,0 +1,23 @@
1
+ import { PageViewport, PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
2
+ declare class Annotation {
3
+ rootElement: HTMLElement;
4
+ container: HTMLElement;
5
+ viewport: PageViewport;
6
+ zoom: number;
7
+ annotation: any;
8
+ pdfDoc: PDFDocumentProxy;
9
+ constructor(container: any, viewport: any, annotation: any, pdfDoc: any, zoom: any, rootElement: any);
10
+ destroy(): any;
11
+ }
12
+ export declare class LinkAnnotation extends Annotation {
13
+ linkElement: HTMLAnchorElement;
14
+ constructor(container: any, viewport: any, annotation: any, pdfDoc: any, zoom: any, rootElement: any);
15
+ onLinkClick: (e: any) => void;
16
+ destroy(): any;
17
+ bindEvents(): any;
18
+ unbindEvents(): any;
19
+ navigateToDestination(destination: any): void;
20
+ goToPage(pageNumber: any): void;
21
+ renderAnnotation(): void;
22
+ }
23
+ export {};
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LinkAnnotation = void 0;
4
+ const utils_1 = require("./utils");
5
+ class Annotation {
6
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
7
+ this.container = container;
8
+ this.viewport = viewport;
9
+ this.zoom = zoom;
10
+ this.annotation = annotation;
11
+ this.pdfDoc = pdfDoc;
12
+ this.rootElement = rootElement;
13
+ }
14
+ destroy() {
15
+ }
16
+ }
17
+ class LinkAnnotation extends Annotation {
18
+ constructor(container, viewport, annotation, pdfDoc, zoom, rootElement) {
19
+ super(container, viewport, annotation, pdfDoc, zoom, rootElement);
20
+ this.onLinkClick = (e) => {
21
+ const destination = e.target.getAttribute('href');
22
+ if (destination && destination.indexOf('#') === 0) {
23
+ this.navigateToDestination(destination);
24
+ e.preventDefault();
25
+ }
26
+ };
27
+ this.linkElement = null;
28
+ this.renderAnnotation();
29
+ this.bindEvents();
30
+ }
31
+ destroy() {
32
+ this.unbindEvents();
33
+ }
34
+ bindEvents() {
35
+ this.linkElement.addEventListener('click', this.onLinkClick);
36
+ }
37
+ unbindEvents() {
38
+ this.linkElement.removeEventListener('click', this.onLinkClick);
39
+ }
40
+ navigateToDestination(destination) {
41
+ const dest = destination.split('#')[1];
42
+ this.pdfDoc.getDestination(dest).then(r => {
43
+ this.pdfDoc.getPageIndex(r[0]).then(i => this.goToPage(i));
44
+ });
45
+ }
46
+ goToPage(pageNumber) {
47
+ (0, utils_1.scrollToPage)(this.rootElement, pageNumber);
48
+ }
49
+ renderAnnotation() {
50
+ const annotation = this.annotation, viewport = this.viewport, rect = annotation.rect, boundingRect = [
51
+ [rect[0], rect[1]],
52
+ [rect[2], rect[3]]
53
+ ], rawHeight = viewport.rawDims.pageHeight;
54
+ const left = Math.min(boundingRect[0][0], boundingRect[1][0]) * this.zoom, top = (rawHeight - Math.max(boundingRect[0][1], boundingRect[1][1])) * this.zoom, width = (boundingRect[1][0] - boundingRect[0][0]) * this.zoom, height = (boundingRect[1][1] - boundingRect[0][1]) * this.zoom;
55
+ const url = annotation.url || (annotation.dest && `#${encodeURI(annotation.dest)}`);
56
+ const annotationElement = (0, utils_1.createElement)('span', '', {
57
+ position: 'absolute',
58
+ left: left + 'pt',
59
+ top: top + 'pt'
60
+ });
61
+ const linkElement = this.linkElement = (0, utils_1.createElement)('a', '', {
62
+ width: width + 'pt',
63
+ height: height + 'pt',
64
+ display: 'inline-block',
65
+ pointerEvents: 'auto'
66
+ });
67
+ linkElement.setAttribute('href', url);
68
+ annotationElement.append(linkElement);
69
+ this.container.append(annotationElement);
70
+ }
71
+ }
72
+ exports.LinkAnnotation = LinkAnnotation;
@@ -24,6 +24,7 @@ export declare class Scroller {
24
24
  disablePanEventsTracking(): void;
25
25
  shouldTrackPanEvents(): boolean;
26
26
  onElementScroll: () => void;
27
+ onDemandScroll: () => void;
27
28
  onDragStart: (e: any) => void;
28
29
  onDrag: (e: any) => void;
29
30
  onDragEnd: () => void;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Scroller = void 0;
4
4
  const kendo_draggable_1 = require("@progress/kendo-draggable");
5
+ const utils_1 = require("./utils");
5
6
  const throttle = function (func, wait, options = {}) {
6
7
  let timeout, context, args, result;
7
8
  let previous = 0;
@@ -84,6 +85,21 @@ class Scroller {
84
85
  this.state.trackNextElementScroll = true;
85
86
  }
86
87
  };
88
+ this.onDemandScroll = () => {
89
+ const pages = this.options.pages, currentPageIndex = (0, utils_1.currentPage)(this.element.parentElement), page = pages[currentPageIndex], nextPage = pages[currentPageIndex + 1], previousPage = pages[currentPageIndex - 1], pageInfo = page === null || page === void 0 ? void 0 : page._pageInfo, nextPageInfo = nextPage === null || nextPage === void 0 ? void 0 : nextPage._pageInfo, previousPageInfo = previousPage === null || previousPage === void 0 ? void 0 : previousPage._pageInfo, error = (e) => {
90
+ throw new Error(e);
91
+ };
92
+ // Render the current and surrounding pages if they have not been rendered already.
93
+ if (page && !pageInfo.rendered && !pageInfo.renderInProgress) {
94
+ (0, utils_1.renderPage)(page, pageInfo.emptyPage, error);
95
+ }
96
+ if (nextPage && !nextPageInfo.rendered && !nextPageInfo.renderInProgress) {
97
+ (0, utils_1.renderPage)(nextPage, nextPageInfo.emptyPage, error);
98
+ }
99
+ if (previousPage && !previousPageInfo.rendered && !previousPageInfo.renderInProgress) {
100
+ (0, utils_1.renderPage)(previousPage, previousPageInfo.emptyPage, error);
101
+ }
102
+ };
87
103
  this.onDragStart = (e) => {
88
104
  this.state.dragStarted = false;
89
105
  if (!this.shouldTrackPanEvents()) {
@@ -178,6 +194,9 @@ class Scroller {
178
194
  this.throttledOnElementScroll = this.onElementScroll;
179
195
  }
180
196
  this.element.addEventListener(SCROLL, this.throttledOnElementScroll);
197
+ if (this.options.loadOnDemand) {
198
+ this.element.addEventListener(SCROLL, this.onDemandScroll);
199
+ }
181
200
  }
182
201
  unbindEvents() {
183
202
  this.unbindElementScroll();
@@ -192,6 +211,9 @@ class Scroller {
192
211
  this.throttledOnElementScroll.cancel();
193
212
  this.throttledOnElementScroll = null;
194
213
  }
214
+ if (this.options.loadOnDemand) {
215
+ this.element.removeEventListener(SCROLL, this.onDemandScroll);
216
+ }
195
217
  this.element.removeEventListener(SCROLL, this.throttledOnElementScroll);
196
218
  }
197
219
  setState(newState) {
@@ -21,14 +21,18 @@ export interface PDFReadParameters {
21
21
  data?: string;
22
22
  arrayBuffer?: ArrayBuffer;
23
23
  typedArray?: TypedArray;
24
+ loadOnDemand?: boolean;
25
+ loadOnDemandPageSize?: number;
24
26
  error: ErrorFn;
25
27
  }
26
28
  /**
27
29
  * @hidden
28
30
  */
29
31
  export interface PDFReadOptions extends PDFReadParameters {
32
+ rootElement?: HTMLElement | null;
30
33
  dom: HTMLDivElement;
31
34
  zoom: number;
35
+ enableAnnotations: boolean;
32
36
  done: DoneFn;
33
37
  }
34
38
  /**
@@ -38,8 +42,11 @@ export interface PDFReloadParameters {
38
42
  pdfDoc: PDFDocumentProxy;
39
43
  zoom: number;
40
44
  dom: HTMLElement;
45
+ rootElement?: HTMLElement | null;
46
+ loadOnDemand?: boolean;
41
47
  done: (pdfPages: PDFPageProxy[]) => void;
42
48
  error: ErrorFn;
49
+ enableAnnotations: boolean;
43
50
  }
44
51
  /**
45
52
  * @hidden
@@ -68,6 +75,10 @@ export declare const reloadDocument: (params: PDFReloadParameters) => void;
68
75
  * @hidden
69
76
  */
70
77
  export declare const print: (pages: PDFPageProxy[], done: () => void, error: ErrorFn) => void;
78
+ export declare const createElement: <T>(name: string, className: string, styles: {
79
+ [key: string]: string;
80
+ }) => T;
81
+ export declare const renderPage: (page: PDFPageProxy, emptyPage: any, error: ErrorFn) => any;
71
82
  /**
72
83
  * @hidden
73
84
  */
package/dist/npm/utils.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.currentPage = exports.scrollToPage = exports.calculateZoomLevel = exports.goToPreviousSearchMatch = exports.goToNextSearchMatch = exports.print = exports.reloadDocument = exports.loadPDF = exports.download = exports.removeChildren = exports.DEFAULT_ZOOM_LEVEL = void 0;
3
+ exports.currentPage = exports.scrollToPage = exports.calculateZoomLevel = exports.goToPreviousSearchMatch = exports.goToNextSearchMatch = exports.renderPage = exports.createElement = exports.print = exports.reloadDocument = exports.loadPDF = exports.download = exports.removeChildren = exports.DEFAULT_ZOOM_LEVEL = void 0;
4
4
  const kendo_file_saver_1 = require("@progress/kendo-file-saver");
5
5
  const kendo_common_1 = require("@progress/kendo-common");
6
6
  const pdf_mjs_1 = require("pdfjs-dist/legacy/build/pdf.mjs");
7
+ const annotations_1 = require("./annotations");
7
8
  const MAX_CANVAS_WIDTH_HEIGHT_CHROME = 65535;
8
9
  const MAX_CANVAS_AREA_CHROME_SAFARI = 268435456;
9
10
  const MAX_CANVAS_WIDTH_HEIGHT_FIREFOX = 32767;
@@ -88,7 +89,8 @@ exports.download = download;
88
89
  */
89
90
  const loadPDF = (options) => {
90
91
  const params = getDocumentParameters(options);
91
- const { dom, zoom, done, error } = options;
92
+ const { dom, zoom, done, error, rootElement, enableAnnotations } = options;
93
+ const loadOnDemandPageSize = options.loadOnDemandPageSize || 2;
92
94
  (0, pdf_mjs_1.getDocument)(params)
93
95
  .promise.then((pdfDoc) => {
94
96
  const pages = [];
@@ -99,7 +101,18 @@ const loadPDF = (options) => {
99
101
  }).then(({ pages, pdfDoc }) => {
100
102
  Promise.all(pages)
101
103
  .then((all) => all.map((page, i) => {
102
- appendPage(dom, renderPage(page, zoom, error), i);
104
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
105
+ appendPage(dom, emptyPage.pageElement, i);
106
+ if (options.loadOnDemand) {
107
+ // If LOD is enabled, render the first two(default) or X pages initially.
108
+ if (i < loadOnDemandPageSize) {
109
+ (0, exports.renderPage)(page, emptyPage, error);
110
+ }
111
+ }
112
+ else {
113
+ (0, exports.renderPage)(page, emptyPage, error);
114
+ }
115
+ page._pageInfo.emptyPage = emptyPage;
103
116
  return page;
104
117
  })).then((pdfPages) => {
105
118
  // required by new PDFJS version
@@ -128,17 +141,39 @@ exports.loadPDF = loadPDF;
128
141
  * @hidden
129
142
  */
130
143
  const reloadDocument = (params) => {
131
- const { pdfDoc, zoom, dom, done, error } = params;
144
+ const { pdfDoc, zoom, dom, done, error, loadOnDemand, rootElement, enableAnnotations } = params;
132
145
  const pages = [];
146
+ let currentPageIndex = 0;
147
+ // Save the index of the current page in view before we reload the document.
148
+ if (loadOnDemand && rootElement) {
149
+ currentPageIndex = (0, exports.currentPage)(rootElement);
150
+ }
133
151
  for (let i = 1; i <= pdfDoc.numPages; i++) {
134
152
  pages.push(pdfDoc.getPage(i));
135
153
  }
136
154
  Promise.all(pages)
137
155
  .then((all) => all.map((page, i) => {
138
- appendPage(dom, renderPage(page, zoom, error), i);
156
+ // Set 'rendered' back to false so that the pages can be re-rendered when scrolling.
157
+ page._pageInfo.rendered = false;
158
+ const emptyPage = createEmptyPage(page, zoom, pdfDoc, enableAnnotations, rootElement);
159
+ appendPage(dom, emptyPage.pageElement, i);
160
+ // If on demand is not enabled, proceed as usual.
161
+ if (!loadOnDemand) {
162
+ (0, exports.renderPage)(page, emptyPage, error);
163
+ }
164
+ page._pageInfo.emptyPage = emptyPage;
139
165
  return page;
140
166
  }))
141
- .then(done)
167
+ .then((pdfPages) => {
168
+ /* If on demand is enabled, render the page that was previously in view.
169
+ Then scroll to that page. */
170
+ if (loadOnDemand) {
171
+ const page = pdfPages[currentPageIndex];
172
+ (0, exports.renderPage)(page, page._pageInfo.emptyPage, error);
173
+ (0, exports.scrollToPage)(rootElement, currentPageIndex);
174
+ }
175
+ done(pdfPages);
176
+ })
142
177
  .catch(error);
143
178
  };
144
179
  exports.reloadDocument = reloadDocument;
@@ -185,6 +220,19 @@ const openPrintDialog = (dom, width, height, done, onError) => {
185
220
  printDialog.addEventListener('afterprint', onAfterPrint);
186
221
  }
187
222
  };
223
+ const createEmptyPage = (page, zoom, pdfDoc, enableAnnotations, rootElement) => {
224
+ const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
225
+ return {
226
+ canvasContext,
227
+ viewport,
228
+ pageElement,
229
+ styles,
230
+ zoom,
231
+ pdfDoc,
232
+ enableAnnotations,
233
+ rootElement
234
+ };
235
+ };
188
236
  const renderCanvas = (page, done, error) => {
189
237
  const { canvasContext, viewport, scaleNum, canvas, pageElement } = createCanvas(page);
190
238
  page.render({ canvasContext, viewport })
@@ -208,18 +256,20 @@ const createElement = function (name, className, styles) {
208
256
  Object.keys(styles).forEach((key) => (element.style[key] = styles[key]));
209
257
  return element;
210
258
  };
259
+ exports.createElement = createElement;
211
260
  const transforms = {
212
261
  '0': '',
213
262
  '90': 'rotate(90deg) translateY(-100%)',
214
263
  '180': 'rotate(180deg) translate(-100%, -100%)',
215
264
  '270': 'rotate(270deg) translateX(-100%)'
216
265
  };
217
- const renderPage = (page, zoom, error) => {
218
- const { canvasContext, viewport, pageElement, styles } = createCanvas(page, zoom, 'k-page');
266
+ const renderPage = (page, emptyPage, error) => {
267
+ const { canvasContext, viewport, pageElement, styles, zoom, pdfDoc, enableAnnotations, rootElement } = emptyPage;
268
+ page._pageInfo.renderInProgress = true;
219
269
  page.render({ canvasContext, viewport })
220
270
  .promise.then(() => {
221
271
  page.getTextContent().then((textContent) => {
222
- const textLayer = createElement('div', 'k-text-layer', styles);
272
+ const textLayer = (0, exports.createElement)('div', 'k-text-layer', styles);
223
273
  new pdf_mjs_1.TextLayer({
224
274
  textContentSource: textContent,
225
275
  container: textLayer,
@@ -237,13 +287,41 @@ const renderPage = (page, zoom, error) => {
237
287
  el.style.fontSize = el.style.fontSize.replace(/px/g, 'pt');
238
288
  }
239
289
  });
240
- pageElement.appendChild(textLayer);
290
+ pageElement.prepend(textLayer); // Use prepend to ensure the element is always inserted before the annotation layer.
241
291
  }).catch(error);
242
292
  });
293
+ if (enableAnnotations) {
294
+ page.getAnnotations({ intent: 'display' }).then((annotations) => {
295
+ const annotationLayer = (0, exports.createElement)('div', 'k-annotations-layer', {
296
+ position: 'absolute',
297
+ top: '0',
298
+ left: '0',
299
+ overflow: 'hidden',
300
+ height: styles.height,
301
+ width: styles.width,
302
+ pointerEvents: 'none'
303
+ });
304
+ pageElement.appendChild(annotationLayer);
305
+ for (const annotation of annotations) {
306
+ switch (annotation.subtype) {
307
+ case 'Link':
308
+ new annotations_1.LinkAnnotation(annotationLayer, viewport, annotation, pdfDoc, zoom, rootElement);
309
+ break;
310
+ default:
311
+ null;
312
+ }
313
+ }
314
+ });
315
+ }
316
+ })
317
+ .then(() => {
318
+ page._pageInfo.rendered = true;
319
+ page._pageInfo.renderInProgress = false;
243
320
  })
244
321
  .catch(error);
245
322
  return pageElement;
246
323
  };
324
+ exports.renderPage = renderPage;
247
325
  const searchMatchScrollLeftOffset = 0;
248
326
  const searchMatchScrollTopOffset = -64;
249
327
  const scrollToSearchMatch = (matchElement, ref) => {
@@ -400,8 +478,8 @@ const createCanvas = (page, zoom = 1, cssClass = '') => {
400
478
  width: Math.floor(viewport.width / scaleNum) * zoom + 'pt',
401
479
  height: Math.floor(viewport.height / scaleNum) * zoom + 'pt'
402
480
  };
403
- const pageElement = createElement('div', cssClass, styles);
404
- const canvas = createElement('canvas', '', {
481
+ const pageElement = (0, exports.createElement)('div', cssClass, styles);
482
+ const canvas = (0, exports.createElement)('canvas', '', {
405
483
  width: '100%',
406
484
  height: '100%'
407
485
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@progress/kendo-pdfviewer-common",
3
3
  "description": "Kendo UI TypeScript package exporting functions for PDFViewer component",
4
- "version": "0.2.11",
4
+ "version": "0.3.0-dev.202409251459",
5
5
  "keywords": [
6
6
  "Kendo UI"
7
7
  ],