-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat(PdfReader): add thumbnails function #723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f3483f1
5557e16
aab6e2f
809e03d
256c846
5e629c6
e480e2f
90f4e3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -82,23 +82,9 @@ export function navigateToPage(id, pageNumber) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function scale(id, scale) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { el, pdfViewer } = Data.get(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { pdfViewer } = Data.get(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pdfViewer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pdfViewer.currentScaleValue = scale / 100; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const minus = el.querySelector(".bb-page-minus"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const plus = el.querySelector(".bb-page-plus"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (scale === "25") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minus.classList.add("disabled"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else if (scale === "500") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plus.classList.add("disabled"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minus.classList.remove("disabled"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plus.classList.remove("disabled"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -139,13 +125,63 @@ const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventBus.on("pagesloaded", async e => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (options.enableThumbnails) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const thumbnailsContainer = el.querySelector(".bb-view-thumbnails"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pdfViewer.getPagesOverview().map(async (p, i) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const item = document.createElement("div"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.classList.add("bb-view-thumbnail-item"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pdfViewer.currentPageNumber === i + 1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.classList.add("active"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.setAttribute("data-bb-page", `${i + 1}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thumbnailsContainer.appendChild(item); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const page = await pdfViewer.pdfDocument.getPage(i + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const canvas = await makeThumb(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const img = document.createElement("img"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| img.src = canvas.toDataURL(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.appendChild(img); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EventHandler.on(thumbnailsContainer, "click", ".bb-view-thumbnail-item", e => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const active = thumbnailsContainer.querySelector('.active'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (active) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active.classList.remove('active'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const item = e.delegateTarget; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item.classList.add("active"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const index = parseInt(item.getAttribute("data-bb-page")) || 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pdfViewer.currentPageNumber = index; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+131
to
+158
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pdfViewer.getPagesOverview().map(async (p, i) => { | |
| const item = document.createElement("div"); | |
| item.classList.add("bb-view-thumbnail-item"); | |
| if (pdfViewer.currentPageNumber === i + 1) { | |
| item.classList.add("active"); | |
| } | |
| item.setAttribute("data-bb-page", `${i + 1}`); | |
| thumbnailsContainer.appendChild(item); | |
| const page = await pdfViewer.pdfDocument.getPage(i + 1); | |
| const canvas = await makeThumb(page); | |
| const img = document.createElement("img"); | |
| img.src = canvas.toDataURL(); | |
| item.appendChild(img); | |
| }); | |
| EventHandler.on(thumbnailsContainer, "click", ".bb-view-thumbnail-item", e => { | |
| const active = thumbnailsContainer.querySelector('.active'); | |
| if (active) { | |
| active.classList.remove('active'); | |
| } | |
| const item = e.delegateTarget; | |
| item.classList.add("active"); | |
| const index = parseInt(item.getAttribute("data-bb-page")) || 1; | |
| pdfViewer.currentPageNumber = index; | |
| }) | |
| if (thumbnailsContainer) { | |
| pdfViewer.getPagesOverview().map(async (p, i) => { | |
| const item = document.createElement("div"); | |
| item.classList.add("bb-view-thumbnail-item"); | |
| if (pdfViewer.currentPageNumber === i + 1) { | |
| item.classList.add("active"); | |
| } | |
| item.setAttribute("data-bb-page", `${i + 1}`); | |
| thumbnailsContainer.appendChild(item); | |
| const page = await pdfViewer.pdfDocument.getPage(i + 1); | |
| const canvas = await makeThumb(page); | |
| const img = document.createElement("img"); | |
| img.src = canvas.toDataURL(); | |
| item.appendChild(img); | |
| }); | |
| EventHandler.on(thumbnailsContainer, "click", ".bb-view-thumbnail-item", e => { | |
| const active = thumbnailsContainer.querySelector('.active'); | |
| if (active) { | |
| active.classList.remove('active'); | |
| } | |
| const item = e.delegateTarget; | |
| item.classList.add("active"); | |
| const index = parseInt(item.getAttribute("data-bb-page")) || 1; | |
| pdfViewer.currentPageNumber = index; | |
| }) | |
| } |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference error: The code assumes active element exists without null checking before calling classList.remove(). If no active element is found (e.g., on first page change or if thumbnails were dynamically removed), this will throw an error. Add a null check: if (active) { active.classList.remove('active'); }
| active.classList.remove('active'); | |
| if (active) { | |
| active.classList.remove('active'); | |
| } |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference error: The code assumes the thumbnail item element exists without null checking before calling classList.add() and scrollIntoView(). If the thumbnail for the page doesn't exist yet (e.g., still loading), this will throw an error. Add a null check: if (item) { item.classList.add("active"); item.scrollIntoView(...); }
| item.classList.add("active"); | |
| item.scrollIntoView({ behavior: 'smooth', block: "nearest", inline: "start" }); | |
| if (item) { | |
| item.classList.add("active"); | |
| item.scrollIntoView({ behavior: 'smooth', block: "nearest", inline: "start" }); | |
| } |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Floating-point comparison issue: Comparing scale === 25 and scale === 500 with exact equality is unreliable for floating-point numbers. Due to precision issues with multiplication (e.g., evt.scale * 100), the comparison may fail to match even when the value is logically 25 or 500. Use threshold checks instead: if (scale <= 25) and if (scale >= 500) to ensure the buttons are properly disabled at the boundaries.
| if (scale === 25) { | |
| minus.classList.add("disabled"); | |
| } | |
| else if (scale === 500) { | |
| if (scale <= 25) { | |
| minus.classList.add("disabled"); | |
| } | |
| else if (scale >= 500) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Toggling thumbnails will throw when thumbnails are disabled.
The click handler is always attached to .bb-view-bar, but .bb-view-thumbnails only exists when Options.EnableThumbnails is true. When thumbnails are disabled, thumbnailsEl will be null and thumbnailsEl.classList.toggle("show") will throw. Add a null check (or avoid registering the handler when thumbnails are disabled).
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference error: The code assumes thumbnailsEl exists without null checking before calling classList.toggle(). If EnableThumbnails is false or the element doesn't exist for any reason, this will throw an error. Add a null check: if (thumbnailsEl) { thumbnailsEl.classList.toggle("show"); }
| thumbnailsEl.classList.toggle("show"); | |
| if (thumbnailsEl) { | |
| thumbnailsEl.classList.toggle("show"); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (performance): Thumbnail rendering may be overkill in resolution relative to displayed size.
Here you render the thumbnail canvas at devicePixelRatio * viewport with scaleSize = 1, but CSS constrains the thumbnail to width: 128px. On large PDFs and high-DPI displays this can cause unnecessary memory and CPU use. Please consider basing the render scale on the intended thumbnail width (e.g., 128px * devicePixelRatio) or reducing scaleSize accordingly to keep quality acceptable while lowering rendering cost.
| const updateScale = (pdfViewer, button, rate) => { | |
| const updateScale = (pdfViewer, button, rate) => { | |
| pdfViewer.currentScaleValue = v / 100; | |
| } | |
| const makeThumb = async page => { | |
| const outputScale = window.devicePixelRatio || 1; | |
| const THUMBNAIL_CSS_WIDTH = 128; | |
| // First get an unscaled viewport to inspect the page width in CSS pixels. | |
| const baseViewport = page.getViewport({ scale: 1 }); | |
| // Compute a scale so that the rendered thumbnail is ~THUMBNAIL_CSS_WIDTH wide in CSS pixels, | |
| // then multiply by devicePixelRatio so the backing buffer is high-DPI without being excessive. | |
| const targetScale = Math.min( | |
| 1, | |
| (THUMBNAIL_CSS_WIDTH * outputScale) / baseViewport.width | |
| ); | |
| const viewport = page.getViewport({ scale: targetScale }); | |
| const canvas = document.createElement("canvas"); | |
| canvas.width = viewport.width; | |
| canvas.height = viewport.height; | |
| await page.render({ | |
| canvasContext: canvas.getContext("2d"), | |
| viewport | |
| }).promise; | |
| return canvas; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,11 @@ public class PdfReaderOptions | |
| /// </summary> | ||
| public bool ShowToolbar { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// 获得/设置 是否显示缩略图 默认 true 显示 | ||
| /// </summary> | ||
| public bool EnableThumbnails { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// 获得/设置 PDF 文档路径 | ||
| /// </summary> | ||
|
|
@@ -52,7 +57,12 @@ public class PdfReaderOptions | |
| /// <summary> | ||
| /// 页面初始化回调方法 | ||
| /// </summary> | ||
| public Func<int, Task>? OnInitAsync { get; set; } | ||
| public Func<int, Task>? OnPagesInitAsync { get; set; } | ||
|
||
|
|
||
| /// <summary> | ||
| /// 页面加载完毕回调方法 | ||
| /// </summary> | ||
| public Func<int, Task>? OnPagesLoadedAsync { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// 页面初始化回调方法 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance issue: Using
.map()with async callbacks doesn't wait for the promises to complete. This can cause race conditions where thumbnails are added to the DOM out of order or the event handler is registered before all thumbnails are rendered. Useawait Promise.all(pdfViewer.getPagesOverview().map(async (p, i) => { ... }))or afor...ofloop to ensure proper sequencing.