From 11c33d1995dbaa8fed2e99842c7697a4f9b3bd52 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 25 Nov 2025 17:25:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=BC=A9=E6=94=BE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BootstrapBlazor.PdfReader/PdfReader.razor | 10 +- .../PdfReader.razor.cs | 36 +++ .../PdfReader.razor.css | 4 + .../PdfReader.razor.js | 299 ++++-------------- .../PdfReaderOptions.cs | 5 + 5 files changed, 114 insertions(+), 240 deletions(-) diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor index 936b3d4d..3f6526c6 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor @@ -13,12 +13,12 @@
/
-
- -
+
+ +
-
-
+
+
diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs index 46db80c6..2a42c5a3 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs @@ -37,6 +37,9 @@ public partial class PdfReader private bool _isFitToPage; private uint _currentPage; private string? _url; + private string? _currentScale; + + private readonly HashSet AllowedScaleValues = ["page-actual", "page-width", "page-height", "page-fit", "auto"]; private string CurrentPageString { @@ -52,6 +55,33 @@ private void SetCurrentPage(string value) } } + private string CurrentScaleString + { + get => $"{Options.CurrentScale ?? "100"}%"; + set => SetCurrentScale(value); + } + + private void SetCurrentScale(string value) + { + if (string.IsNullOrEmpty(value)) + { + Options.CurrentScale = "100"; + } + else if (float.TryParse(value.TrimEnd("%"), out var v)) + { + if (v > 500) + { + v = 500; + } + else if (v < 25) + { + v = 25; + } + + Options.CurrentScale = v.ToString(CultureInfo.InvariantCulture); + } + } + /// /// /// @@ -82,6 +112,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _isFitToPage = Options.IsFitToPage; _currentPage = Options.CurrentPage; _url = Options.Url; + _currentScale = Options.CurrentScale; } if (_url != Options.Url) @@ -100,6 +131,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _currentPage = Options.CurrentPage; await NavigateToPageAsync(_currentPage); } + if (_currentScale != Options.CurrentScale) + { + _currentScale = Options.CurrentScale; + await InvokeVoidAsync("scale", Id, _currentScale); + } } /// diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css index c15865c9..fb26b844 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css @@ -31,6 +31,10 @@ cursor: pointer; } + .bb-view-icon.disabled { + color: #6c757d; + } + .bb-view-bar { margin-inline-end: 2rem; } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js index 07a451d3..36ed74a8 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js @@ -2,6 +2,7 @@ import './lib/pdf.min.mjs' import './lib/pdf_viewer.mjs' import { addLink } from '../BootstrapBlazor/modules/utility.js'; import Data from '../BootstrapBlazor/modules/data.js'; +import EventHandler from '../BootstrapBlazor/modules/event-handler.js'; if (pdfjsLib != null) { pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.min.mjs'; @@ -42,30 +43,30 @@ export async function init(id, invoke, options) { eventBus }); - addEventListener(pdfViewer, eventBus, invoke, options); + addEventListener(el, pdfViewer, eventBus, invoke, options); const pdfDocument = await loadingTask.promise; pdfViewer.setDocument(pdfDocument); - Data.set(id, pdfViewer); + Data.set(id, { el, pdfViewer }); } export function fitToWidth(id) { - const pdfViewer = Data.get(id); + const { pdfViewer } = Data.get(id); if (pdfViewer) { - pdfViewer.currentScaleValue = 1.0; + pdfViewer.currentScaleValue = "page-height"; } } export function fitToPage(id) { - const pdfViewer = Data.get(id); + const { pdfViewer } = Data.get(id); if (pdfViewer) { pdfViewer.currentScaleValue = "page-width"; } } export function rotate(id, step) { - const pdfViewer = Data.get(id); + const { pdfViewer } = Data.get(id); if (pdfViewer) { let rotate = pdfViewer.pagesRotation || 360; rotate += step; @@ -73,11 +74,35 @@ export function rotate(id, step) { } } -export function dispose(id) { - Data.get(id); +export function navigateToPage(id, pageNumber) { + const { pdfViewer } = Data.get(id); + if (pdfViewer) { + pdfViewer.currentPageNumber = pageNumber; + } } -const addEventListener = (pdfViewer, eventBus, invoke, options) => { +export function scale(id, scale) { + const { el, 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"); + } + } +} + +const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { eventBus.on("pagesinit", async () => { if (options.isFitToPage) { pdfViewer.currentScaleValue = "page-width"; @@ -86,7 +111,6 @@ const addEventListener = (pdfViewer, eventBus, invoke, options) => { pdfViewer.currentScaleValue = 1.0; } - const el = pdfViewer.container.parentElement; const numPages = pdfViewer.pagesCount; const countEl = el.querySelector(".bb-view-pagesCount"); if (countEl) { @@ -105,7 +129,6 @@ const addEventListener = (pdfViewer, eventBus, invoke, options) => { eventBus.on("pagechanging", async evt => { const page = evt.pageNumber; - const el = evt.source.container.parentElement; const pageNumberEl = el.querySelector(".bb-view-num"); if (pageNumberEl) { pageNumberEl.value = page; @@ -115,240 +138,46 @@ const addEventListener = (pdfViewer, eventBus, invoke, options) => { await invoke.invokeMethodAsync("pageChanged", page); } }, true); -} - -function getCanvas(item) { - if (isDomSupported() && typeof item === 'string') { - item = document.getElementById(item); - } else if (item && item.length) { - // support for array based queries - item = item[0]; - } - - if (item && item.canvas !== undefined && item.canvas) { - // support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - return item; -} - -const getPdf = (key) => { - const canvas = getCanvas(key); - return Object.values(pdfInstances).filter((c) => c.canvas === canvas).pop(); -}; - -const pdfInstances = {}; + const minus = el.querySelector(".bb-page-minus"); + const plus = el.querySelector(".bb-page-plus"); + const scaleEl = el.querySelector(".bb-view-scale"); -class Pdf { - static instances = pdfInstances; - static getPdf = getPdf; + eventBus.on("scalechanging", evt => { + scaleEl.value = `${Math.round(evt.scale * 100, 0)}%`; + }) - constructor(item) { - const canvas = getCanvas(item); - - this.id = canvas.id; - this.canvas = canvas; - this.ctx = canvas.getContext('2d'); - this.pdfDoc = null; - this.pageNum = 1; - this.pagesCount = 0; - this.pageRendering = false; - this.pageNumPending = null; - this.scale = 1; - this.rotation = 0; - - pdfInstances[this.id] = this; - } + EventHandler.on(minus, "click", e => updateScale(pdfViewer, e.target, -1)); + EventHandler.on(plus, "click", e => updateScale(pdfViewer, e.target, 1)); } -export function navigateToPage(id, pageNumber) { - const pdf = Data.get(id); - pdf.currentPageNumber = pageNumber; -} - -export function firstPage(invoke, elementId) { - const pdf = getPdf(elementId); - - if (pdf == null || pdf.pageNum === 1) - return; - - if (pdf.pageNum > 1) - pdf.pageNum = 1; - - queueRenderPage(pdf, pdf.pageNum); - - setPdfViewerMetaData(invoke, pdf); -} - -export function gotoPage(invoke, elementId, gotoPageNum) { - const pdf = getPdf(elementId); - - if (pdf == null || gotoPageNum < 1 || gotoPageNum > pdf.pagesCount) - return; - - pdf.pageNum = gotoPageNum; - - queueRenderPage(pdf, pdf.pageNum); - - setPdfViewerMetaData(invoke, pdf); -} - -export function lastPage(invoke, elementId) { - const pdf = getPdf(elementId); - - if (pdf == null || (pdf.pageNum === 1 && pdf.pageNum === pdf.pagesCount)) +const updateScale = (pdfViewer, button, rate) => { + if (button.classList.contains('disabled')) { return; - - if (pdf.pageNum < pdf.pagesCount) - pdf.pageNum = pdf.pagesCount; - - queueRenderPage(pdf, pdf.pageNum); - - setPdfViewerMetaData(invoke, pdf); -} - -export function nextPage(invoke, elementId) { - const pdf = getPdf(elementId); - - if (pdf == null || pdf.pageNum === pdf.pagesCount) - return; - - if (pdf.pageNum < pdf.pagesCount) - pdf.pageNum += 1; - - queueRenderPage(pdf, pdf.pageNum); - - setPdfViewerMetaData(invoke, pdf); -} - -export function previousPage(invoke, elementId) { - const pdf = getPdf(elementId); - - if (pdf == null || pdf.pageNum === 0 || pdf.pageNum === 1) - return; - - if (pdf.pageNum > 0) - pdf.pageNum -= 1; - - queueRenderPage(pdf, pdf.pageNum); - - setPdfViewerMetaData(invoke, pdf); -} - -export async function print(invoke, elementId, url) { - const pdfDoc = await pdfjsLib.getDocument(url).promise; - const pageRange = [1, 2, 3, 4]; // TODO: update this - - const iframeEl = document.createElement('iframe'); - iframeEl.style = 'display:none'; - document.body.appendChild(iframeEl); - - for (const pageNumber of pageRange) { - const page = await pdfDoc.getPage(pageNumber); - - const viewport = page.getViewport({ scale: 1.5 }); - const canvas = document.createElement("canvas"); - canvas.height = viewport.height; - canvas.width = viewport.width; - - const ctx = canvas.getContext('2d'); - - const renderContext = { - //intent: 'print', - canvasContext: ctx, - viewport: viewport - }; - await page.render(renderContext).promise; - - const iframeDoc = iframeEl.contentWindow.document; - iframeDoc.body.appendChild(canvas); } - setTimeout(() => { - iframeEl.contentWindow.print(); - iframeEl.remove(); - }, - 1000); -} - -//export function rotate(invoke, elementId, rotation) { -// const pdf = getPdf(elementId); - -// if (pdf == null || Number.isNaN(rotation) || rotation % 90 !== 0) -// return; - -// pdf.rotation = rotation; - -// queueRenderPage(pdf, pdf.pageNum); -//} - -export function zoomInOut(invoke, elementId, scale) { - const pdf = getPdf(elementId); - - if (pdf == null) - return; - - if (!Number.isNaN(scale)) - pdf.scale = scale; - - queueRenderPage(pdf, pdf.pageNum); -} - -function isDomSupported() { - return typeof window !== 'undefined' && typeof document !== 'undefined'; -} - -function queueRenderPage(pdf, num) { - if (pdf.pageRendering) { - pdf.pageNumPending = num; - } else { - renderPage(pdf, num); + const scale = pdfViewer.currentScale; + const current = Math.round(parseFloat(scale * 100), 0); + const step = [25, 33, 50, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200, 250, 300, 400, 500]; + const findValues = step.filter(s => rate > 0 ? current < s : current > s); + let v = 100; + if (rate > 0) { + v = findValues.shift(); } + else { + v = findValues.pop(); + } + pdfViewer.currentScaleValue = v / 100; } -function renderPage(pdf, num) { - pdf.pageRendering = true; - - // Using promise to fetch the page - pdf.pdfDoc.getPage(num).then((page) => { - const viewport = page.getViewport({ scale: pdf.scale, rotation: pdf.rotation }); - const dpr = window.devicePixelRatio || 1; - - pdf.canvas.height = viewport.height * dpr; - pdf.canvas.width = viewport.width * dpr; - - pdf.ctx.scale(dpr, dpr); - - pdf.canvas.style.height = viewport.height + 'px'; - pdf.canvas.style.width = viewport.width + 'px'; - - // Render PDF page into canvas context - const renderContext = { - canvasContext: pdf.ctx, - viewport: viewport - }; - - const renderTask = page.render(renderContext); - - // Wait for rendering to finish - renderTask.promise.then(() => { - pdf.pageRendering = false; - if (pdf.pageNumPending !== null) { - // New page rendering is pending - renderPage(pdf, pdf.pageNumPending); - pdf.pageNumPending = null; - } - }) - .catch((error) => { - // TODO: track exception - }); - }); -} - -function setPdfViewerMetaData(invoke, pdf) { - if (invoke == null) - return; +export function dispose(id) { + Data.remove(id); - invoke.invokeMethodAsync('SetPdfViewerMetaData', { pagesCount: pdf.pagesCount, pageNumber: pdf.pageNum }); + const el = document.getElementById(id); + if (el) { + const minus = el.querySelector(".bb-page-minus"); + const plus = el.querySelector(".bb-page-plus"); + EventHandler.off(minus, "click"); + EventHandler.off(plus, "click"); + } } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs index cc688f00..8dfb1ec3 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs @@ -29,6 +29,11 @@ public class PdfReaderOptions /// public uint CurrentPage { get; set; } + /// + /// 获得/设置 当前缩放倍率 默认 null 使用 100% + /// + public string? CurrentScale { get; set; } + /// /// 获得/设置 是否适配当前页面宽度 默认 false ///