From 61729d324c35fd688640770258c7d37f67642153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Antunes=20Santos?= Date: Mon, 2 Jun 2025 17:43:33 +0100 Subject: [PATCH] feat(#3453): Add frontend for Remove Header/Footer feature - Implement form submission with options to remove header, footer, or both - Add margin mode selection with visual preview of selected header/footer bounds - Integrate PDF.js for live PDF view in margin mode - Create dedicated CSS for styling the feature page - Add localization keys for en-GB and en-US - Implement JavaScript to handle PDF rendering, preview overlays, and submission logic --- .../main/resources/messages_en_GB.properties | 17 + .../main/resources/messages_en_US.properties | 24 + .../static/css/remove-header-footer.css | 157 +++++ .../static/js/pages/remove-header-footer.js | 603 ++++++++++++++++++ .../resources/templates/fragments/common.html | 1 + .../templates/remove-header-footer.html | 287 +++++++-- 6 files changed, 1044 insertions(+), 45 deletions(-) create mode 100644 stirling-pdf/src/main/resources/static/css/remove-header-footer.css create mode 100644 stirling-pdf/src/main/resources/static/js/pages/remove-header-footer.js diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties index 20d45916c..5e58fc529 100644 --- a/stirling-pdf/src/main/resources/messages_en_GB.properties +++ b/stirling-pdf/src/main/resources/messages_en_GB.properties @@ -1004,6 +1004,23 @@ crop.submit=Submit remove-header-footer.title=Remove Header/Footer remove-header-footer.header=Remove Header/Footer remove-header-footer.submit=Remove +remove-header-footer.removeHeader=Header +remove-header-footer.removeFooter=Footer +remove-header-footer.headerMargin=Header Margin +remove-header-footer.footerMargin=Footer Margin +remove-header-footer.pages=Select Pages +remove-header-footer.selectMode=Select Mode +remove-header-footer.view-more=View More +remove-header-footer.previousPage=Previous Page +remove-header-footer.nextPage=Next Page +remove-header-footer.zoomOut=Zoom Out +remove-header-footer.zoomIn=Zoom In +remove-header-footer.close=Close +remove-header-footer.margin=Margin +remove-header-footer.auto=Auto +remove-header-footer.manual=Manual +remove-header-footer.enterValue=Enter Value + #autoSplitPDF autoSplitPDF.title=Auto Split PDF diff --git a/stirling-pdf/src/main/resources/messages_en_US.properties b/stirling-pdf/src/main/resources/messages_en_US.properties index 162196c53..71b9c1b9f 100644 --- a/stirling-pdf/src/main/resources/messages_en_US.properties +++ b/stirling-pdf/src/main/resources/messages_en_US.properties @@ -751,6 +751,10 @@ home.validateSignature.title=Validate PDF Signature home.validateSignature.desc=Verify digital signatures and certificates in PDF documents validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate +home.remove-header-footer.title=Remove Header/Footer +home.remove-header-footer.desc=Remove the headers and/or footers from a PDF document +remove-header-footer.tags=remove headers, remove footers, remove, header, footer + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -996,6 +1000,26 @@ crop.title=Crop crop.header=Crop PDF crop.submit=Submit +#remove-header-footer +remove-header-footer.title=Remove Header/Footer +remove-header-footer.header=Remove Header/Footer +remove-header-footer.submit=Remove +remove-header-footer.removeHeader=Header +remove-header-footer.removeFooter=Footer +remove-header-footer.headerMargin=Header Margin +remove-header-footer.footerMargin=Footer Margin +remove-header-footer.pages=Select Pages +remove-header-footer.selectMode=Select Mode +remove-header-footer.view-more=View More +remove-header-footer.previousPage=Previous Page +remove-header-footer.nextPage=Next Page +remove-header-footer.zoomOut=Zoom Out +remove-header-footer.zoomIn=Zoom In +remove-header-footer.close=Close +remove-header-footer.margin=Margin +remove-header-footer.auto=Auto +remove-header-footer.manual=Manual +remove-header-footer.enterValue=Enter Value #autoSplitPDF autoSplitPDF.title=Auto Split PDF diff --git a/stirling-pdf/src/main/resources/static/css/remove-header-footer.css b/stirling-pdf/src/main/resources/static/css/remove-header-footer.css new file mode 100644 index 000000000..5bfbc684e --- /dev/null +++ b/stirling-pdf/src/main/resources/static/css/remove-header-footer.css @@ -0,0 +1,157 @@ +#previewContainer { + aspect-ratio: 1; + width: 100%; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; + margin: 1rem 0; + padding: 15px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +#pdf-preview { + max-width: calc(100% - 30px); + max-height: calc(100% - 30px); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.buttonContainer { + display: flex; + justify-content: space-around; +} + +.preview-wrapper { + position: absolute; + top: 35%; + left: 35%; + transform: translate(-25%, -33%); +} + +.margin-line { + position: absolute; + height: 0; + border-top: 2px dashed red; + z-index: 10; + pointer-events: none; +} + +/* Fullscreen overlay */ +#pdfOverlay { + display: none; /* Make it hidden by default */ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(30, 30, 30, 0.514); + display: none; + z-index: 1000; + overflow-y: auto; + flex-direction: column; + padding: 60px 0px 0px; +} + +#mainContainer { + display: flex; + flex-direction: column; + height:100%; + width: 100%; + overflow: hidden; +} + +.toolbar { + height: 4rem; + flex-shrink: 0; + z-index: 100; +} + +.toolbarContainer { + height: 4rem !important; +} + +.overlay-content { + flex-grow: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + position: relative; +} + +.pagesView { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + position: relative; +} +.pagesView canvas { + margin-bottom: 24px; +} + +#zoomButton { + position: absolute; + top: 10px; + right: 10px; + color: white; + background: rgba(0, 0, 0, 0); + border: 1px solid #ffffff00; + border-radius: 50%; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + cursor: pointer; + z-index: 20; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.253); + transition: background 0.2s; +} + +#zoomButton:hover { + background: rgba(0, 29, 41, 0.9); +} + +button.close-button { + padding: 0; + border: none; + background: rgb(169 201 246); + width: 3rem; + height: 3rem; + border-radius: 15px; + margin-top: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.15s ease-in-out; +} + +button.close-button .material-symbols-rounded { + font-size: 2rem; + line-height: 1; + color: rgb(58 94 134); +} + +button.close-button:hover { + background: rgb(102, 102, 103); +} + +button.close-button:hover .material-symbols-rounded { + color: #fff; +} + +@media (max-width: 1125px) { + #toolbarViewerRight { + display: flex !important; + } +} diff --git a/stirling-pdf/src/main/resources/static/js/pages/remove-header-footer.js b/stirling-pdf/src/main/resources/static/js/pages/remove-header-footer.js new file mode 100644 index 000000000..edd477a38 --- /dev/null +++ b/stirling-pdf/src/main/resources/static/js/pages/remove-header-footer.js @@ -0,0 +1,603 @@ +import * as pdfjsLib from '../../pdfjs-legacy/pdf.mjs'; +import {PDFViewerApplication} from '../../pdfjs-legacy/js/viewer.mjs'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; +let loadedPdf = null; +let pdfFileUrl = null; +let pdfBlobUrl = null; +document.getElementById('removeHeaderFooterForm').addEventListener('submit', async function (event) { + event.preventDefault(); + + const form = event.target; + const formData = new FormData(form); + const responseContainer = document.getElementById('responseContainer'); + + try { + const response = await fetch(form.action, { + method: form.method, + body: formData, + }); + + const responseText = await response.text(); + + responseContainer.textContent = responseText; + responseContainer.className = 'alert alert-success'; + } catch (error) { + responseContainer.textContent = 'An error occurred. Please try again.'; + responseContainer.className = 'alert alert-danger'; + } +}); + +document.addEventListener('DOMContentLoaded', () => { + + PDFViewerApplication.run(); + const fileInput = document.getElementById('fileInput-input'); + const pagesInput = document.getElementById("pageNumbers"); + const previewContainer = document.getElementById('previewContainer'); + const modeSelect = document.querySelector('select[name="mode"]'); + + const overlayHeaderCheckbox = document.getElementById('overlay-removeHeader'); + const overlayFooterCheckbox = document.getElementById('overlay-removeFooter'); + const overlayHeaderMargin = document.getElementById('overlay-headerMargin'); + const overlayFooterMargin = document.getElementById('overlay-footerMargin'); + const customOverlayHeaderWrapper = document.getElementById("overlay-headerCustomMarginWrapper"); + const customOverlayFooterWrapper = document.getElementById("overlay-footerCustomMarginWrapper"); + const customOverlayHeaderInput = document.getElementById("overlay-headerCustomMarginInput"); + const customOverlayFooterInput = document.getElementById("overlay-footerCustomMarginInput"); + + const marginOptions = document.getElementById("margin-options"); + const headerMarginColumn = document.getElementById("header-margin-column"); + const footerMarginColumn = document.getElementById("footer-margin-column"); + const mainHeaderCheckbox = document.getElementById("removeHeader"); + const mainFooterCheckbox = document.getElementById("removeFooter"); + const mainHeaderMargin = document.querySelector('select[name="headerMargin"]'); + const mainFooterMargin = document.querySelector('select[name="footerMargin"]'); + const customMainHeaderWrapper = document.getElementById("headerCustomMarginWrapper"); + const customMainFooterWrapper = document.getElementById("footerCustomMarginWrapper"); + const customMainHeaderInput = document.getElementById("headerCustomMarginInput"); + const customMainFooterInput = document.getElementById("footerCustomMarginInput"); + + const viewerContainer = document.getElementById('viewerContainer'); + const viewer = document.getElementById('viewer'); + const pageNumberInput = document.getElementById('pageNumber'); + const numPagesLabel = document.getElementById('numPages'); + const scaleSelect = document.getElementById('scaleSelect'); + const zoomInBtn = document.getElementById('zoomIn'); + const zoomOutBtn = document.getElementById('zoomOut'); + const nextBtn = document.getElementById('next'); + const prevBtn = document.getElementById('previous'); + const closeBtn = document.getElementById('closeOverlay'); + const zoomButton = document.getElementById('zoomButton'); + + const pageContainer = document.getElementById('page-container'); + const outerContainer = document.getElementById('outerContainer'); + + let pdfDoc = null; + let currentPage = 1; + let currentScale = 1.0; + + // Sync margin controls between overlay and main form + function syncMarginControls(fromOverlay) { + + const copyState = (source, target) => { + target.headerCheckbox.checked = source.headerCheckbox.checked; + target.footerCheckbox.checked = source.footerCheckbox.checked; + target.headerMargin.value = source.headerMargin.value; + target.footerMargin.value = source.footerMargin.value; + target.headerMarginColumn.style.display = source.headerMarginColumn.style.display; + target.footerMarginColumn.style.display = source.footerMarginColumn.style.display; + target.customHeaderWrapper.style.display = source.customHeaderWrapper.style.display; + target.customHeaderInput.value = source.customHeaderInput.value; + target.customFooterWrapper.style.display = source.customFooterWrapper.style.display; + target.customFooterInput.value = source.customFooterInput.value; + }; + + const overlay = { + headerCheckbox: overlayHeaderCheckbox, + footerCheckbox: overlayFooterCheckbox, + headerMargin: overlayHeaderMargin, + footerMargin: overlayFooterMargin, + headerMarginColumn: overlayHeaderMargin, + footerMarginColumn: overlayFooterMargin, + customHeaderWrapper: customOverlayHeaderWrapper, + customHeaderInput: customOverlayHeaderInput, + customFooterWrapper: customOverlayFooterWrapper, + customFooterInput: customOverlayFooterInput + }; + + const main = { + headerCheckbox: mainHeaderCheckbox, + footerCheckbox: mainFooterCheckbox, + headerMargin: mainHeaderMargin, + footerMargin: mainFooterMargin, + headerMarginColumn: headerMarginColumn, + footerMarginColumn: footerMarginColumn, + customHeaderWrapper: customMainHeaderWrapper, + customHeaderInput: customMainHeaderInput, + customFooterWrapper: customMainFooterWrapper, + customFooterInput: customMainFooterInput + }; + + if (fromOverlay) { + copyState(overlay, main); + } else { + copyState(main, overlay); + } + } + + function toggleOverlayMarginOptions() { + overlayHeaderMargin.style.display = overlayHeaderCheckbox.checked ? "block" : "none"; + overlayFooterMargin.style.display = overlayFooterCheckbox.checked ? "block" : "none"; + if(overlayHeaderMargin.value == "custom" && overlayHeaderCheckbox.checked){ + customOverlayHeaderWrapper.style.display = "block"; + } + else{ + customOverlayHeaderWrapper.style.display = "none"; + } + if(overlayFooterMargin.value == "custom" && overlayFooterCheckbox.checked){ + customOverlayFooterWrapper.style.display = "block"; + } + else{ + customOverlayFooterWrapper.style.display = "none"; + } + syncMarginControls(true); + } + + function toggleMainMarginOptions() { + if (modeSelect.value === "margin") { + marginOptions.style.display = "flex"; + headerMarginColumn.style.display = mainHeaderCheckbox.checked ? "block" : "none"; + footerMarginColumn.style.display = mainFooterCheckbox.checked ? "block" : "none"; + if(mainHeaderMargin.value == "custom"){ + customMainHeaderWrapper.style.display = "block"; + } + else{ + customMainHeaderWrapper.style.display = "none"; + } + if(mainFooterMargin.value == "custom"){ + customMainFooterWrapper.style.display = "block"; + } + else{ + customMainFooterWrapper.style.display = "none"; + } + + if (loadedPdf) { + syncMarginControls(false); + } + } else { + marginOptions.style.display = "none"; + } + } + + // Initialize correctly on page load + toggleMainMarginOptions(); + + // Update when mode changes + modeSelect.addEventListener("change", toggleMainMarginOptions); + + + [ + overlayHeaderCheckbox, + overlayFooterCheckbox, + overlayHeaderMargin, + overlayFooterMargin + ].forEach(el => { + el.addEventListener('change', () => { + toggleOverlayMarginOptions(); + renderAllPages(); + }); + }); + + fileInput.addEventListener("change", async function () { + + const existingPreview = document.getElementById("pdf-preview"); + if (existingPreview) existingPreview.remove(); + + const file = fileInput.files[0]; + if (!file || file.type !== 'application/pdf') return; + + + if (pdfFileUrl) URL.revokeObjectURL(pdfFileUrl); + pdfFileUrl = URL.createObjectURL(file); + + loadedPdf = await pdfjsLib.getDocument(pdfFileUrl).promise; + + renderPreview(); + }); + + pagesInput.addEventListener("input", () => { + if (modeSelect.value == "margin" && loadedPdf) { + renderPreview(); + } + }); + + modeSelect.addEventListener("change", () => { + const preview = document.getElementById("pdf-preview"); + + if (modeSelect.value == "margin" && loadedPdf){ + document.querySelector("#editSection").style.display = "block"; + renderPreview(); + } + else { + if (preview) preview.remove(); + document.querySelector("#editSection").style.display = "none"; + } + }); + + [ + mainFooterCheckbox, + mainHeaderCheckbox, + mainFooterMargin, + mainHeaderMargin + ].forEach(el => { + el.addEventListener("input", () => { + toggleMainMarginOptions(); + if (modeSelect.value == "margin" && loadedPdf) + renderPreview(); + }); + }); + + [ + customMainHeaderInput, + customMainFooterInput + ].forEach(el => { + el.addEventListener("input", () => { + if (checkValue(el)) { + toggleMainMarginOptions(); + renderPreview(); + } + }); + }); + + [ + customOverlayHeaderInput, + customOverlayFooterInput + ].forEach(el => { + el.addEventListener("input", () => { + if (checkValue(el)) { + toggleOverlayMarginOptions(); + renderAllPages(); + } + }); + }); + + function checkValue(inputBlock) { + const value = parseInt(inputBlock.value, 10) || 0; + if (!isNaN(value) && value > 0) { + return true; + } + return false; + } + + window.addEventListener("resize", () => { + if (modeSelect.value == "margin" && loadedPdf) { + renderPreview(); + } + }); + + function drawMarginLine(ctx, y, width) { + if (y < 0 || y > ctx.canvas.height) return; + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(width, y); + ctx.stroke(); + } + + async function renderPreview() { + if (modeSelect.value != "margin" || !loadedPdf) { + if (preview) preview.remove(); + document.querySelector("#editSection").style.display = "none"; + return; + } else { + document.querySelector("#editSection").style.display = "block"; + } + document.querySelectorAll(".margin-line").forEach(e => e.remove()); + + + const mainHeaderMargin = document.querySelector('select[name="headerMargin"]').value === 'custom' + ? parseInt(document.getElementById('headerCustomMarginInput').value, 10) || 0 + : parseInt(document.querySelector('select[name="headerMargin"]').value, 10) || 0; + const mainFooterMargin = document.querySelector('select[name="footerMargin"]').value === 'custom' + ? parseInt(document.getElementById('footerCustomMarginInput').value, 10) || 0 + : parseInt(document.querySelector('select[name="footerMargin"]').value, 10) || 0; + + const existingPreview = document.getElementById("pdf-preview"); + if (existingPreview) existingPreview.remove(); + + + const pageInput = pagesInput.value.trim(); + let firstPageNumber = 1; + + if (pageInput) { + const pages = pageInput.split(',').flatMap(part => { + if (part.includes('-')) { + const [start, end] = part.split('-').map(Number); + return Array.from({ length: end - start + 1 }, (_, i) => start + i); + } + return [Number(part)]; + }).filter(n => !isNaN(n) && n > 0); + + if (pages.length > 0) { + firstPageNumber = pages[0]; + } + } + + const page = await loadedPdf.getPage(firstPageNumber); + const canvas = document.createElement("canvas"); + + + if (page.rotate == 90 || page.rotate == 270) { + canvas.width = page.view[3]; + canvas.height = page.view[2]; + } else { + canvas.width = page.view[2]; + canvas.height = page.view[3]; + } + + const ctx = canvas.getContext("2d"); + + const renderContext = { + canvasContext: ctx, + viewport: page.getViewport({ scale: 1 }), + }; + + await page.render(renderContext).promise; + + const scale = canvas.height / page.view[3]; // PDF pts to pixels + const headerY = mainHeaderMargin * scale; + const footerY = canvas.height - mainFooterMargin * scale; + + + ctx.strokeStyle = "red"; + ctx.lineWidth = 2; + ctx.setLineDash([5, 5]); + + if (mainHeaderCheckbox.checked) { + drawMarginLine(ctx, headerY, canvas.width); + } + if (mainFooterCheckbox.checked) { + drawMarginLine(ctx, footerY, canvas.width); + } + + const preview = document.createElement("img"); + preview.id = "pdf-preview"; + preview.alt = "preview"; + preview.src = canvas.toDataURL(); + preview.style.position = "absolute"; + preview.style.top = "50%"; + preview.style.left = "50%"; + preview.style.transform = "translate(-50%, -50%)"; + + previewContainer.appendChild(preview); + + URL.revokeObjectURL(pdfFileUrl); + }; + + function parsePagesInput(input, maxPage) { + if (!input) return Array.from({length: maxPage}, (_, i) => i + 1); + const parts = input.split(','); + const pages = new Set(); + for (const part of parts) { + if (part.includes('-')) { + const [start, end] = part.split('-').map(Number); + if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start && end <= maxPage) { + for (let i = start; i <= end; i++) pages.add(i); + } + } else { + const n = Number(part); + if (!isNaN(n) && n > 0 && n <= maxPage) pages.add(n); + } + } + return Array.from(pages).sort((a, b) => a - b); + } + + function renderAllPages() { + viewer.innerHTML = ''; + + const headerMargin = document.querySelector('select[name="headerMargin"]').value === 'custom' + ? parseInt(document.getElementById('headerCustomMarginInput').value, 10) || 0 + : parseInt(document.querySelector('select[name="headerMargin"]').value, 10) || 0; + const footerMargin = document.querySelector('select[name="footerMargin"]').value === 'custom' + ? parseInt(document.getElementById('footerCustomMarginInput').value, 10) || 0 + : parseInt(document.querySelector('select[name="footerMargin"]').value, 10) || 0; + + const removeHeaderChecked = document.getElementById('removeHeader').checked; + const removeFooterChecked = document.getElementById('removeFooter').checked; + + const pageInput = document.getElementById('pageNumbers').value.trim(); + const pagesToShow = parsePagesInput(pageInput, pdfDoc.numPages); + + const renderPage = (num, idx) => { + pdfDoc.getPage(num).then(page => { + const viewport = page.getViewport({ scale: currentScale }); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; + canvas.style.display = 'block'; + canvas.style.margin = '0 auto 16px auto'; + viewer.appendChild(canvas); + page.render({ canvasContext: ctx, viewport: viewport }).promise.then(() => { + if (idx === 0) { + pageNumberInput.value = pagesToShow[0]; + numPagesLabel.textContent = `/ ${pagesToShow.length}`; + } + + const scale = canvas.height / page.view[3]; + const headerY = headerMargin * scale; + const footerY = canvas.height - footerMargin * scale; + ctx.strokeStyle = "red"; + ctx.lineWidth = 2; + ctx.setLineDash([5, 5]); + if (removeHeaderChecked) { + drawMarginLine(ctx, headerY, canvas.width); + } + if (removeFooterChecked) { + drawMarginLine(ctx, footerY, canvas.width); + } + }); + }); + }; + pagesToShow.forEach((pageNum, idx) => renderPage(pageNum, idx)); + } + + function scrollToPage(num) { + const canvases = viewer.querySelectorAll('canvas'); + if (canvases[num - 1]) { + canvases[num - 1].scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } + + zoomButton.addEventListener('click', async () => { + if (!fileInput.files[0]) return; + if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl); + pdfBlobUrl = URL.createObjectURL(fileInput.files[0]); + + syncMarginControls(false); + outerContainer.style.display = 'block'; + pageContainer.style.display = 'none'; + + pdfjsLib.getDocument(pdfBlobUrl).promise.then(pdf => { + pdfDoc = pdf; + currentPage = 1; + currentScale = 1.0; + renderAllPages(); + }); + }); + + closeBtn.addEventListener('click', () => { + syncMarginControls(true); + outerContainer.style.display = 'none'; + pageContainer.style.display = 'block'; + viewer.innerHTML = ''; + if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl); + pdfDoc = null; + renderPreview(); + }); + + nextBtn.addEventListener('click', () => { + if (currentPage < pdfDoc.numPages) { + currentPage++; + pageNumberInput.value = currentPage; + scrollToPage(currentPage); + } + }); + + prevBtn.addEventListener('click', () => { + if (currentPage > 1) { + currentPage--; + pageNumberInput.value = currentPage; + scrollToPage(currentPage); + } + }); + + scaleSelect.addEventListener('change', async e => { + let val = e.target.value; + if (val === 'page-fit' || val === 'page-width' || val === 'page-actual') { + const scale = await getPageScale(val); + onZoomChange(scale); + scaleSelect.value = val; + } else if (val === 'auto') { + onZoomChange(1.25); + scaleSelect.value = val; + } else { + onZoomChange(Number(val)); + } + }); + + pageNumberInput.addEventListener('change', e => { + let num = parseInt(e.target.value, 10); + if (!isNaN(num) && num >= 1 && num <= pdfDoc.numPages) { + currentPage = num; + scrollToPage(currentPage); + } + }); + + function setScaleSelectValue(scale) { + + let found = false; + for (const opt of scaleSelect.options) { + if (Number(opt.value) === scale) { + scaleSelect.value = opt.value; + const customOpt = scaleSelect.querySelector('#customScaleOption'); + if (customOpt) { + customOpt.hidden = true; + customOpt.disabled = true; + } + found = true; + break; + } + } + if (!found) { + + let customOpt = scaleSelect.querySelector('#customScaleOption'); + if (!customOpt) { + customOpt = document.createElement('option'); + customOpt.id = 'customScaleOption'; + scaleSelect.appendChild(customOpt); + } + customOpt.value = 'custom'; + customOpt.textContent = `${Math.round(scale * 100)}%`; + customOpt.hidden = false; + customOpt.disabled = false; + scaleSelect.value = 'custom'; + } + } + + function onZoomChange(newScale) { + currentScale = newScale; + setScaleSelectValue(currentScale); + renderAllPages(); + } + + function getPageScale(mode) { + + if (!pdfDoc) return 1.0; + const firstCanvas = viewer.querySelector('canvas'); + if (!firstCanvas) return 1.0; + const container = viewerContainer; + const pageIndex = 1; + return pdfDoc.getPage(pageIndex).then(page => { + const viewport = page.getViewport({ scale: 1.0 }); + if (mode === 'page-fit') { + // Fit page height to container height + return container.clientHeight / viewport.height; + } else if (mode === 'page-width') { + // Fit page width to container width + return container.clientWidth / viewport.width; + } else if (mode === 'page-actual') { + return 1.0; + } + return 1.0; + }); + } + + // Updates the page number with the visible page number during scroll + viewerContainer.addEventListener('scroll', () => { + const canvases = viewer.querySelectorAll('canvas'); + let closest = 0; + let minDiff = Infinity; + const containerTop = viewerContainer.scrollTop; + for (let i = 0; i < canvases.length; i++) { + const rect = canvases[i].getBoundingClientRect(); + const diff = Math.abs(rect.top - viewerContainer.getBoundingClientRect().top); + if (diff < minDiff) { + minDiff = diff; + closest = i; + } + } + if (pdfDoc && pageNumberInput.value != (closest + 1)) { + pageNumberInput.value = closest + 1; + currentPage = closest + 1; + } + }); + + zoomInBtn.addEventListener('click', () => { + onZoomChange(currentScale + 0.1); + }); + + zoomOutBtn.addEventListener('click', () => { + onZoomChange(currentScale - 0.1); + }); + +}); diff --git a/stirling-pdf/src/main/resources/templates/fragments/common.html b/stirling-pdf/src/main/resources/templates/fragments/common.html index 02d919b2b..c4cf3c996 100644 --- a/stirling-pdf/src/main/resources/templates/fragments/common.html +++ b/stirling-pdf/src/main/resources/templates/fragments/common.html @@ -67,6 +67,7 @@ + diff --git a/stirling-pdf/src/main/resources/templates/remove-header-footer.html b/stirling-pdf/src/main/resources/templates/remove-header-footer.html index 402a12a8d..665a62a3a 100644 --- a/stirling-pdf/src/main/resources/templates/remove-header-footer.html +++ b/stirling-pdf/src/main/resources/templates/remove-header-footer.html @@ -1,14 +1,32 @@ - + + + + + + + + + + + + + + + + + + + +
+

- -

@@ -16,55 +34,234 @@ toolbar
-

- remove_header_footer test -
-
- - + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
-
- - - - -
-
+ + +