mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Merge dc1050cee5
into 8e8f0492c4
This commit is contained in:
commit
6120a5e529
@ -119,6 +119,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("PageOps", "scale-pages");
|
addEndpointToGroup("PageOps", "scale-pages");
|
||||||
addEndpointToGroup("PageOps", "adjust-contrast");
|
addEndpointToGroup("PageOps", "adjust-contrast");
|
||||||
addEndpointToGroup("PageOps", "crop");
|
addEndpointToGroup("PageOps", "crop");
|
||||||
|
addEndpointToGroup("PageOps", "removeHeaderFooter");
|
||||||
addEndpointToGroup("PageOps", "auto-split-pdf");
|
addEndpointToGroup("PageOps", "auto-split-pdf");
|
||||||
addEndpointToGroup("PageOps", "extract-page");
|
addEndpointToGroup("PageOps", "extract-page");
|
||||||
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
||||||
@ -236,6 +237,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Java", "auto-split-pdf");
|
addEndpointToGroup("Java", "auto-split-pdf");
|
||||||
addEndpointToGroup("Java", "sanitize-pdf");
|
addEndpointToGroup("Java", "sanitize-pdf");
|
||||||
addEndpointToGroup("Java", "crop");
|
addEndpointToGroup("Java", "crop");
|
||||||
|
addEndpointToGroup("Java", "removeHeaderFooter");
|
||||||
addEndpointToGroup("Java", "get-info-on-pdf");
|
addEndpointToGroup("Java", "get-info-on-pdf");
|
||||||
addEndpointToGroup("Java", "extract-page");
|
addEndpointToGroup("Java", "extract-page");
|
||||||
addEndpointToGroup("Java", "pdf-to-single-page");
|
addEndpointToGroup("Java", "pdf-to-single-page");
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.github.pixee.security.Filenames;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.general.RemoveHeaderFooterForm;
|
||||||
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RemoveHeaderFooterController {
|
||||||
|
|
||||||
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@PostMapping(value = "/remove-header-footer", consumes = "multipart/form-data")
|
||||||
|
@Operation(
|
||||||
|
summary = "Removes headers and/or footers from a PDF document",
|
||||||
|
description = "Remove header and/or footer")
|
||||||
|
public ResponseEntity<byte[]> removeHeaderFooter(@ModelAttribute RemoveHeaderFooterForm form)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
MultipartFile pdfFile = form.getFileInput();
|
||||||
|
|
||||||
|
String pagesToDelete = form.getPages();
|
||||||
|
List<Integer> pagesToRemove = new ArrayList<>();
|
||||||
|
PDDocument sourceDoc = pdfDocumentFactory.load(pdfFile);
|
||||||
|
PDDocument newDoc = new PDDocument();
|
||||||
|
LayerUtility layerUtility = new LayerUtility(newDoc);
|
||||||
|
|
||||||
|
String sufix;
|
||||||
|
// Respond with a message
|
||||||
|
if (form.isRemoveHeader()) {
|
||||||
|
if (form.isRemoveFooter()) {
|
||||||
|
sufix = "_removed_header_footer.pdf";
|
||||||
|
} else sufix = "_removed_header.pdf";
|
||||||
|
} else if (form.isRemoveFooter()) {
|
||||||
|
sufix = "_removed_footer.pdf";
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.body("No header or footer removal options selected".getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pagesToDelete == null || pagesToDelete.isEmpty()) {
|
||||||
|
for (int i = 0; i < sourceDoc.getNumberOfPages(); i++) {
|
||||||
|
pagesToRemove.add(i);
|
||||||
|
}
|
||||||
|
} else { // Split the page order string into an array of page numbers or range of numbers
|
||||||
|
String[] pageOrderArr = pagesToDelete.split(",");
|
||||||
|
|
||||||
|
pagesToRemove =
|
||||||
|
GeneralUtils.parsePageList(pageOrderArr, sourceDoc.getNumberOfPages(), false);
|
||||||
|
|
||||||
|
Collections.sort(pagesToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int pageIndex = 0; pageIndex < sourceDoc.getNumberOfPages(); pageIndex++) {
|
||||||
|
PDPage sourcePage = sourceDoc.getPage(pageIndex);
|
||||||
|
PDRectangle mediaBox = sourcePage.getMediaBox();
|
||||||
|
|
||||||
|
PDPage newPage = new PDPage(mediaBox);
|
||||||
|
newDoc.addPage(newPage);
|
||||||
|
|
||||||
|
try (PDPageContentStream cs =
|
||||||
|
new PDPageContentStream(newDoc, newPage, AppendMode.OVERWRITE, true, true)) {
|
||||||
|
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDoc, pageIndex);
|
||||||
|
|
||||||
|
// Save the current graphics state to restore later
|
||||||
|
cs.saveGraphicsState();
|
||||||
|
|
||||||
|
if (pagesToRemove.contains(pageIndex)) {
|
||||||
|
Float[][] zones = getRemovalZonesForPage(form, sourcePage);
|
||||||
|
if (zones != null && zones.length > 0) {
|
||||||
|
cs.addRect(0, 0, mediaBox.getWidth(), mediaBox.getHeight());
|
||||||
|
// Add rectangles for each zone to remove (header/footer areas)
|
||||||
|
// These will be subtracted from the base rectangle using even-odd clipping
|
||||||
|
// rule
|
||||||
|
for (Float[] zone : zones) {
|
||||||
|
if (zone != null && zone.length == 4) {
|
||||||
|
cs.addRect(zone[0], zone[1], zone[2], zone[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.clipEvenOdd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.drawForm(formXObject);
|
||||||
|
// Restore the graphics state to ensure the clipping is applied correctly
|
||||||
|
cs.restoreGraphicsState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
|
newDoc,
|
||||||
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ sufix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the zones for the header and footer removal based on the form data and the page.
|
||||||
|
*
|
||||||
|
* @param form The form containing the removal settings.
|
||||||
|
* @param page The PDF page to process.
|
||||||
|
* @return A 2D array of Float representing the zones to remove.
|
||||||
|
*/
|
||||||
|
private Float[][] getRemovalZonesForPage(RemoveHeaderFooterForm form, PDPage page) {
|
||||||
|
float w = page.getMediaBox().getWidth();
|
||||||
|
float h = page.getMediaBox().getHeight();
|
||||||
|
Float[][] zones = null;
|
||||||
|
|
||||||
|
boolean removeHeader = form.isRemoveHeader();
|
||||||
|
boolean removeFooter = form.isRemoveFooter();
|
||||||
|
zones = new Float[removeHeader && removeFooter ? 2 : 1][];
|
||||||
|
if (removeHeader) {
|
||||||
|
|
||||||
|
Float headerH = form.getHeaderMargin();
|
||||||
|
if (headerH == -1) {
|
||||||
|
headerH = form.getHeaderCustomValue(); // Default value if 'custom' is specified
|
||||||
|
}
|
||||||
|
zones[0] = new Float[] {0f, h - headerH, w, headerH};
|
||||||
|
}
|
||||||
|
if (removeFooter) {
|
||||||
|
|
||||||
|
Float footerH = form.getFooterMargin();
|
||||||
|
if (footerH == -1) {
|
||||||
|
footerH = form.getFooterCustomValue(); // Default value if 'custom' is specified
|
||||||
|
}
|
||||||
|
zones[zones[0] == null ? 0 : 1] = new Float[] {0f, 0f, w, footerH};
|
||||||
|
}
|
||||||
|
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
}
|
@ -298,6 +298,13 @@ public class GeneralWebController {
|
|||||||
return "crop";
|
return "crop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/remove-header-footer")
|
||||||
|
@Hidden
|
||||||
|
public String removeHeaderFooterForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "remove-header-footer");
|
||||||
|
return "remove-header-footer";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/auto-split-pdf")
|
@GetMapping("/auto-split-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String autoSPlitPDFForm(Model model) {
|
public String autoSPlitPDFForm(Model model) {
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package stirling.software.SPDF.model.api.general;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class RemoveHeaderFooterForm extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(description = "Pages to apply the removal to (e.g., ['1', '2-4'])")
|
||||||
|
private String pages;
|
||||||
|
|
||||||
|
@Schema(description = "Set to true to remove the header region")
|
||||||
|
private boolean removeHeader;
|
||||||
|
|
||||||
|
@Schema(description = "Set to true to remove the footer region")
|
||||||
|
private boolean removeFooter;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Margin from top for header removal (used in margin mode). If the value is '-1' it means a custom value was send.")
|
||||||
|
private Float headerMargin;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Margin from bottom for footer removal (used in margin mode). If the value is '-1' it means a custom value was send.")
|
||||||
|
private Float footerMargin;
|
||||||
|
|
||||||
|
@Schema(description = "Custom header height used if footerMargin is '-1'")
|
||||||
|
private Float headerCustomValue;
|
||||||
|
|
||||||
|
@Schema(description = "Custom footer height used if footerMargin is '-1'")
|
||||||
|
private Float footerCustomValue;
|
||||||
|
}
|
@ -763,6 +763,10 @@ home.validateSignature.title=Validate PDF Signature
|
|||||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
|
home.remove-header-footer.title=Remove PDF Headers and Footers
|
||||||
|
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-invert-color
|
||||||
replace-color.title=Advanced Colour options
|
replace-color.title=Advanced Colour options
|
||||||
replace-color.header=Replace-Invert Colour PDF
|
replace-color.header=Replace-Invert Colour PDF
|
||||||
@ -1008,6 +1012,27 @@ crop.title=Crop
|
|||||||
crop.header=Crop PDF
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
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
|
||||||
autoSplitPDF.title=Auto Split PDF
|
autoSplitPDF.title=Auto Split PDF
|
||||||
|
@ -763,6 +763,10 @@ home.validateSignature.title=Validate PDF Signature
|
|||||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
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-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
@ -1008,6 +1012,27 @@ crop.title=Crop
|
|||||||
crop.header=Crop PDF
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
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
|
||||||
autoSplitPDF.title=Auto Split PDF
|
autoSplitPDF.title=Auto Split PDF
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height:100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
height: 4rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,586 @@
|
|||||||
|
import * as pdfjsLib from '../../pdfjs-legacy/pdf.mjs';
|
||||||
|
import {PDFViewerApplication} from '../../pdfjs-legacy/js/viewer.mjs';
|
||||||
|
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
PDFViewerApplication.run();
|
||||||
|
const CUSTOM = "-1";
|
||||||
|
const fileInput = document.getElementById('fileInput-input');
|
||||||
|
const pagesInput = document.getElementById("pageNumbers");
|
||||||
|
const previewContainer = document.getElementById('previewContainer');
|
||||||
|
|
||||||
|
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 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 pageContainer = document.getElementById('page-container');
|
||||||
|
const outerContainer = document.getElementById('outerContainer');
|
||||||
|
|
||||||
|
let pdfFileUrl = null;
|
||||||
|
let loadedPdf = null;
|
||||||
|
let currentPage = 1;
|
||||||
|
let currentScale = 1.0;
|
||||||
|
|
||||||
|
const drawMarginLine = (ctx, y, width, type) => {
|
||||||
|
if (y < 0 || y > ctx.canvas.height) return;
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = 0.5;
|
||||||
|
ctx.fillStyle = type === "header" ? "red" : "blue";
|
||||||
|
ctx.fillRect(0, type === "header" ? 0 : y, width, type === "header" ? y : ctx.canvas.height - y);
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMarginValue(type, context = 'main') {
|
||||||
|
const select = context === 'main'
|
||||||
|
? document.querySelector(`select[name="${type}Margin"]`)
|
||||||
|
: document.getElementById(`overlay-${type}Margin`);
|
||||||
|
if (select.value === CUSTOM) {
|
||||||
|
const input = context === 'main'
|
||||||
|
? document.getElementById(`${type}CustomMarginInput`)
|
||||||
|
: document.getElementById(`overlay-${type}CustomMarginInput`);
|
||||||
|
return parseInt(input.value, 10) || 0;
|
||||||
|
}
|
||||||
|
return parseInt(select.value, 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPreview() {
|
||||||
|
if (!loadedPdf) {
|
||||||
|
if (preview) preview.remove();
|
||||||
|
document.querySelector("#editSection").style.display = "none";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
document.querySelector("#editSection").style.display = "block";
|
||||||
|
}
|
||||||
|
const mainHeaderMargin = getMarginValue('header');
|
||||||
|
const mainFooterMargin = getMarginValue('footer');
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
let scale;
|
||||||
|
if (page.rotate === 90 || page.rotate === 270) {
|
||||||
|
canvas.width = page.view[3];
|
||||||
|
canvas.height = page.view[2];
|
||||||
|
scale = canvas.height / page.view[2];
|
||||||
|
} else {
|
||||||
|
canvas.width = page.view[2];
|
||||||
|
canvas.height = page.view[3];
|
||||||
|
scale = 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 headerY = mainHeaderMargin * scale;
|
||||||
|
const footerY = canvas.height - mainFooterMargin * scale;
|
||||||
|
|
||||||
|
if (mainHeaderCheckbox.checked) {
|
||||||
|
drawMarginLine(ctx, headerY, canvas.width, "header");
|
||||||
|
}
|
||||||
|
if (mainFooterCheckbox.checked) {
|
||||||
|
drawMarginLine(ctx, footerY, canvas.width, "footer");
|
||||||
|
}
|
||||||
|
|
||||||
|
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(maxPage) {
|
||||||
|
const pageInput = pagesInput.value.trim();
|
||||||
|
if (!pageInput) return Array.from({length: maxPage}, (_, i) => i + 1);
|
||||||
|
const parts = pageInput.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 = getMarginValue('header', 'overlay');
|
||||||
|
const footerMargin = getMarginValue('footer', 'overlay');
|
||||||
|
|
||||||
|
const removeHeaderChecked = overlayHeaderCheckbox.checked;
|
||||||
|
const removeFooterChecked = overlayFooterCheckbox.checked;
|
||||||
|
const pagesToShow = parsePagesInput(loadedPdf.numPages);
|
||||||
|
|
||||||
|
const renderPage = (num, idx) => {
|
||||||
|
loadedPdf.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, "header");
|
||||||
|
}
|
||||||
|
if (removeFooterChecked) {
|
||||||
|
drawMarginLine(ctx, footerY, canvas.width, "footer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncMarginControls(fromOverlay) {
|
||||||
|
|
||||||
|
const overlay = {
|
||||||
|
check:{
|
||||||
|
headerCheckbox: overlayHeaderCheckbox,
|
||||||
|
footerCheckbox: overlayFooterCheckbox
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
headerMargin: overlayHeaderMargin,
|
||||||
|
footerMargin: overlayFooterMargin,
|
||||||
|
customFooterInput: customOverlayFooterInput,
|
||||||
|
customHeaderInput: customOverlayHeaderInput
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
headerMarginColumn: overlayHeaderMargin,
|
||||||
|
footerMarginColumn: overlayFooterMargin,
|
||||||
|
customHeaderWrapper: customOverlayHeaderWrapper,
|
||||||
|
customFooterWrapper: customOverlayFooterWrapper
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = {
|
||||||
|
check:{
|
||||||
|
headerCheckbox: mainHeaderCheckbox,
|
||||||
|
footerCheckbox: mainFooterCheckbox,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
headerMargin: mainHeaderMargin,
|
||||||
|
footerMargin: mainFooterMargin,
|
||||||
|
customFooterInput: customMainFooterInput,
|
||||||
|
customHeaderInput: customMainHeaderInput,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
headerMarginColumn: headerMarginColumn,
|
||||||
|
footerMarginColumn: footerMarginColumn,
|
||||||
|
customHeaderWrapper: customMainHeaderWrapper,
|
||||||
|
customFooterWrapper: customMainFooterWrapper,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const src = fromOverlay ? overlay : main;
|
||||||
|
const tgt = fromOverlay ? main : overlay;
|
||||||
|
|
||||||
|
Object.keys(src.check).forEach(key => {
|
||||||
|
if (src.check[key] && tgt.check[key]) {
|
||||||
|
tgt.check[key].checked = src.check[key].checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(src.value).forEach(key => {
|
||||||
|
if (src.value[key] && tgt.value[key]) {
|
||||||
|
tgt.value[key].value = src.value[key].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(src.style).forEach(key => {
|
||||||
|
if (src.style[key] && tgt.style[key]) {
|
||||||
|
tgt.style[key].style.display = src.style[key].style.display;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
|
headerMarginColumn.style.display = mainHeaderCheckbox.checked ? "block" : "none";
|
||||||
|
footerMarginColumn.style.display = mainFooterCheckbox.checked ? "block" : "none";
|
||||||
|
|
||||||
|
if(mainHeaderMargin.value == CUSTOM && mainHeaderCheckbox.checked){
|
||||||
|
customMainHeaderWrapper.style.display = "block";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
customMainHeaderWrapper.style.display = "none";
|
||||||
|
}
|
||||||
|
if(mainFooterMargin.value == CUSTOM && mainFooterCheckbox.checked){
|
||||||
|
customMainFooterWrapper.style.display = "block";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
customMainFooterWrapper.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedPdf) {
|
||||||
|
syncMarginControls(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkValue(inputBlock) {
|
||||||
|
|
||||||
|
const value = parseInt(inputBlock.value, 10) || 0;
|
||||||
|
const pagesToShow = parsePagesInput(loadedPdf.numPages);
|
||||||
|
const page = await loadedPdf.getPage(pagesToShow[0]);
|
||||||
|
if (!isNaN(value) && value > 0) {
|
||||||
|
if(value > page.view[3]) inputBlock.value = page.view[3];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (!loadedPdf) return 1.0;
|
||||||
|
const firstCanvas = viewer.querySelector('canvas');
|
||||||
|
if (!firstCanvas) return 1.0;
|
||||||
|
const container = viewerContainer;
|
||||||
|
const pageIndex = 1;
|
||||||
|
return loadedPdf.getPage(pageIndex).then(page => {
|
||||||
|
const viewport = page.getViewport({ scale: 1.0 });
|
||||||
|
|
||||||
|
if (mode === 'page-fit') {
|
||||||
|
return container.clientHeight / viewport.height;
|
||||||
|
} else if (mode === 'page-width') {
|
||||||
|
return container.clientWidth / viewport.width;
|
||||||
|
} else if (mode === 'page-actual') {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
return 1.0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
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 (loadedPdf) {
|
||||||
|
renderPreview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('removeHeaderFooterForm').addEventListener('submit', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const form = event.target;
|
||||||
|
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
const responseContainer = document.getElementById('responseContainer');
|
||||||
|
responseContainer.textContent = '';
|
||||||
|
responseContainer.className = '';
|
||||||
|
console.log("Form data:", Array.from(formData.entries()));
|
||||||
|
try {
|
||||||
|
const response = await fetch(form.action, {
|
||||||
|
method: form.method,
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
await response.text();
|
||||||
|
if (response.ok) {
|
||||||
|
if (formData.get('removeHeader')) {
|
||||||
|
if (formData.get('removeFooter')) {
|
||||||
|
responseContainer.textContent = 'Header and Footer removed successfully.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
responseContainer.textContent = 'Header removed successfully.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (formData.get('removeFooter')) {
|
||||||
|
responseContainer.textContent = 'Footer removed successfully.';
|
||||||
|
}
|
||||||
|
responseContainer.className = 'alert alert-success';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
responseContainer.textContent = 'An error occurred. Please try again.';
|
||||||
|
responseContainer.className = 'alert alert-danger';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
mainFooterCheckbox,
|
||||||
|
mainHeaderCheckbox,
|
||||||
|
mainFooterMargin,
|
||||||
|
mainHeaderMargin
|
||||||
|
].forEach(el => {
|
||||||
|
el.addEventListener("input", () => {
|
||||||
|
toggleMainMarginOptions();
|
||||||
|
if (loadedPdf)
|
||||||
|
renderPreview();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
[customMainHeaderInput, toggleMainMarginOptions, renderPreview],
|
||||||
|
[customMainFooterInput, toggleMainMarginOptions, renderPreview],
|
||||||
|
[customOverlayHeaderInput, toggleOverlayMarginOptions, renderAllPages],
|
||||||
|
[customOverlayFooterInput, toggleOverlayMarginOptions, renderAllPages]
|
||||||
|
].forEach(([el, toggleFn, renderFn]) => {
|
||||||
|
el.addEventListener("input", async () => {
|
||||||
|
if (await checkValue(el)) {
|
||||||
|
toggleFn();
|
||||||
|
renderFn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
if (loadedPdf) {
|
||||||
|
renderPreview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('zoomButton').addEventListener('click', async () => {
|
||||||
|
if (!fileInput.files[0]) return;
|
||||||
|
|
||||||
|
syncMarginControls(false);
|
||||||
|
outerContainer.style.display = 'block';
|
||||||
|
pageContainer.style.display = 'none';
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
currentScale = 1.0;
|
||||||
|
renderAllPages();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('closeOverlay').addEventListener('click', () => {
|
||||||
|
syncMarginControls(true);
|
||||||
|
outerContainer.style.display = 'none';
|
||||||
|
pageContainer.style.display = 'block';
|
||||||
|
viewer.innerHTML = '';
|
||||||
|
renderPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('next').addEventListener('click', () => {
|
||||||
|
if (currentPage < loadedPdf.numPages) {
|
||||||
|
currentPage++;
|
||||||
|
pageNumberInput.value = currentPage;
|
||||||
|
scrollToPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('previous').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 <= loadedPdf.numPages) {
|
||||||
|
currentPage = num;
|
||||||
|
scrollToPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
viewerContainer.addEventListener('scroll', () => {
|
||||||
|
const canvases = viewer.querySelectorAll('canvas');
|
||||||
|
let closest = 0;
|
||||||
|
let minDiff = Infinity;
|
||||||
|
|
||||||
|
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 (loadedPdf && pageNumberInput.value != (closest + 1)) {
|
||||||
|
pageNumberInput.value = closest + 1;
|
||||||
|
currentPage = closest + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('zoomIn').addEventListener('click', () => {
|
||||||
|
onZoomChange(currentScale + 0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('zoomOut').addEventListener('click', () => {
|
||||||
|
onZoomChange(currentScale - 0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleMainMarginOptions();
|
||||||
|
});
|
@ -67,6 +67,7 @@
|
|||||||
<link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}">
|
<link rel="stylesheet" th:href="@{'/css/licenses.css'}" th:if="${currentPage == 'licenses'}">
|
||||||
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
|
<link rel="stylesheet" th:href="@{'/css/multi-tool.css'}" th:if="${currentPage == 'multi-tool'}">
|
||||||
<link rel="stylesheet" th:href="@{'/css/rotate-pdf.css'}" th:if="${currentPage == 'rotate-pdf'}">
|
<link rel="stylesheet" th:href="@{'/css/rotate-pdf.css'}" th:if="${currentPage == 'rotate-pdf'}">
|
||||||
|
<link rel="stylesheet" th:href="@{'/css/remove-header-footer.css'}" th:if="${currentPage == 'remove-header-footer'}">
|
||||||
<link rel="stylesheet" th:href="@{'/css/stamp.css'}" th:if="${currentPage == 'stamp'}">
|
<link rel="stylesheet" th:href="@{'/css/stamp.css'}" th:if="${currentPage == 'stamp'}">
|
||||||
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
|
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
|
||||||
<link rel="stylesheet" th:href="@{'/css/footer.css'}">
|
<link rel="stylesheet" th:href="@{'/css/footer.css'}">
|
||||||
|
@ -19,6 +19,11 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('crop', 'crop', 'home.crop.title', 'home.crop.desc', 'crop.tags', 'organize')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('crop', 'crop', 'home.crop.title', 'home.crop.desc', 'crop.tags', 'organize')}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/navbarEntry :: navbarEntry('remove-header-footer', 'toolbar', 'home.remove-header-footer.title', 'home.remove-header-footer.desc', 'remove-header-footer.tags', 'organize')}">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-organizer', 'format_list_bulleted', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags', 'organize')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('pdf-organizer', 'format_list_bulleted', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc', 'pdfOrganiser.tags', 'organize')}">
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,6 +122,9 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', toolIcon='crop', tags=#{crop.tags}, toolGroup='organize')}">
|
th:replace="~{fragments/card :: card(id='crop', cardTitle=#{home.crop.title}, cardText=#{home.crop.desc}, cardLink='crop', toolIcon='crop', tags=#{crop.tags}, toolGroup='organize')}">
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/card :: card(id='remove-header-footer', cardTitle=#{home.remove-header-footer.title}, cardText=#{home.remove-header-footer.desc}, cardLink='remove-header-footer', toolIcon='page_header', tags=#{remove-header-footer.tags}, toolGroup='organize')}">
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', toolIcon='format_list_bulleted', tags=#{pdfOrganiser.tags}, toolGroup='organize')}">
|
th:replace="~{fragments/card :: card(id='pdf-organizer', cardTitle=#{home.pdfOrganiser.title}, cardText=#{home.pdfOrganiser.desc}, cardLink='pdf-organizer', toolIcon='format_list_bulleted', tags=#{pdfOrganiser.tags}, toolGroup='organize')}">
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,262 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{remove-header-footer.title}, header=#{remove-header-footer.header})}">
|
||||||
|
</th:block>
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<script th:src="@{'/js/thirdParty/popper.min.js'}"></script>
|
||||||
|
<script th:src="@{'/js/thirdParty/bootstrap.min.js'}"></script>
|
||||||
|
|
||||||
|
<script th:src="@{'/js/thirdParty/jquery.min.js'}"></script>
|
||||||
|
<script th:src="@{'/js/thirdParty/jquery.validate.min.js'}"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" th:href="@{'/css/theme/componentes.css'}">
|
||||||
|
<link rel="stylesheet" th:href="@{'/css/navbar.css'}">
|
||||||
|
<!-- This snippet is used in production (included from view-pdf.html) -->
|
||||||
|
<link rel="resource" type="application/l10n" th:href="@{'/pdfjs-legacy/locale/locale.json'}">
|
||||||
|
<script th:src="@{'/pdfjs-legacy/pdf.mjs'}" type="module"></script>
|
||||||
|
<link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}">
|
||||||
|
|
||||||
|
<script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script>
|
||||||
|
|
||||||
|
<script type="module" th:src="@{'/js/pages/remove-header-footer.js'}"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||||
|
<div id="page-container">
|
||||||
|
<br><br>
|
||||||
|
<div id="content-wrap">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 bg-card">
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="material-symbols-rounded tool-header-icon organize">toolbar</span>
|
||||||
|
<span class="tool-header-text" th:text="#{remove-header-footer.header}"></span>
|
||||||
|
</div>
|
||||||
|
<form id="removeHeaderFooterForm" th:action="@{/api/v1/general/remove-header-footer}" method="post" enctype="multipart/form-data">
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="pageNumbers" th:text="#{remove-header-footer.pages}"></label>
|
||||||
|
<input type="text" class="form-control" id="pageNumbers" name="pages" placeholder="1,3,5-10">
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="form-check" style="flex: 0 0 50%;">
|
||||||
|
<input type="checkbox" id="removeHeader" name="removeHeader">
|
||||||
|
<label for="removeHeader" th:text="#{remove-header-footer.removeHeader}"></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check" style="display: flex; align-items: center;">
|
||||||
|
<input type="checkbox" id="removeFooter" name="removeFooter">
|
||||||
|
<label for="removeFooter" th:text="#{remove-header-footer.removeFooter}"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="margin-options" class="row mb-3">
|
||||||
|
<div class="col" id="header-margin-column" style="display: none;">
|
||||||
|
<label th:text="#{remove-header-footer.headerMargin}"></label>
|
||||||
|
<select class="form-control" name="headerMargin">
|
||||||
|
<option value="-1">Custom</option>
|
||||||
|
<option value="20">20 pt</option>
|
||||||
|
<option value="30">30 pt</option>
|
||||||
|
<option value="40" selected>40 pt (default)</option>
|
||||||
|
<option value="50">50 pt</option>
|
||||||
|
<option value="60">60 pt</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="custom-margin-wrapper" id="headerCustomMarginWrapper" style="display: none; margin-top: 8px;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control custom-margin-input"
|
||||||
|
id="headerCustomMarginInput"
|
||||||
|
th:placeholder="#{remove-header-footer.enterValue}"
|
||||||
|
data-type="header"
|
||||||
|
min="0"
|
||||||
|
name="headerCustomValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col" id="footer-margin-column" style="display: none;">
|
||||||
|
<label th:text="#{remove-header-footer.footerMargin}"></label>
|
||||||
|
<select class="form-control" name="footerMargin">
|
||||||
|
<option value="-1">Custom</option>
|
||||||
|
<option value="20">20 pt</option>
|
||||||
|
<option value="30">30 pt</option>
|
||||||
|
<option value="40" selected>40 pt (default)</option>
|
||||||
|
<option value="50">50 pt</option>
|
||||||
|
<option value="60">60 pt</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="custom-margin-wrapper" id="footerCustomMarginWrapper" style="display: none; margin-top: 8px;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control custom-margin-input"
|
||||||
|
id="footerCustomMarginInput"
|
||||||
|
th:placeholder="#{remove-header-footer.enterValue}"
|
||||||
|
data-type="footer"
|
||||||
|
min="0"
|
||||||
|
name="footerCustomValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="editSection" style="display: none">
|
||||||
|
<div id="previewContainer">
|
||||||
|
<!-- PDF preview will be rendered here -->
|
||||||
|
<button type="button" id="zoomButton" th:title="#{remove-header-footer.view-more}">
|
||||||
|
<span class="material-symbols-rounded">open_in_full</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="buttonContainer">
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{remove-header-footer.submit}"></button>
|
||||||
|
</div>
|
||||||
|
<div id="responseContainer" class="mt-3"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
|
</div>
|
||||||
|
<!--PDF Viewer-->
|
||||||
|
<div id="outerContainer" style="display: none;">
|
||||||
|
<div id="sidebarContainer"></div>
|
||||||
|
<div id="mainContainer">
|
||||||
|
<div class="toolbar" style="position:sticky;">
|
||||||
|
<div id="toolbarContainer" style="height:4rem;" >
|
||||||
|
<div id="toolbarViewer">
|
||||||
|
<div id="toolbarViewerLeft">
|
||||||
|
<div class="splitToolbarButton hiddenSmallView">
|
||||||
|
<button class="toolbarButton btn-secondary" th:title="#{remove-header-footer.previousPage}" id="previous"
|
||||||
|
tabindex="13" data-l10n-id="pdfjs-previous-button">
|
||||||
|
</button>
|
||||||
|
<div class="splitToolbarButtonSeparator"></div>
|
||||||
|
<button class="toolbarButton btn-secondary" th:title="#{remove-header-footer.nextPage}" id="next" tabindex="14"
|
||||||
|
data-l10n-id="pdfjs-next-button">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="loadingInput start">
|
||||||
|
<input type="number" id="pageNumber" class="toolbarField" value="1" min="1" tabindex="15"
|
||||||
|
data-l10n-id="pdfjs-page-input" autocomplete="off">
|
||||||
|
</span>
|
||||||
|
<span id="numPages" class="toolbarLabel"></span>
|
||||||
|
<img class="main-icon user-select-none" th:src="@{'/favicon.svg'}" alt="icon">
|
||||||
|
</div>
|
||||||
|
<div id="toolbarViewerMiddle" class="d-flex align-items-center justify-content-between w-100" style="max-width:900px; margin:0 auto;">
|
||||||
|
<div id="overlay-margin-options" class="d-flex align-items-center">
|
||||||
|
<div class="form-check" style="margin-bottom: 0px;" >
|
||||||
|
<input type="checkbox" id="overlay-removeHeader" style="margin-bottom: 4px; margin-left: 7px;" class="form-check-input">
|
||||||
|
<label for="overlay-removeHeader" class="form-check-label" style="padding-right: 2px;" th:text="#{remove-header-footer.removeHeader}"></label>
|
||||||
|
</div>
|
||||||
|
<span id="overlay-headerMargin-container">
|
||||||
|
<select class="form-select me-3" id="overlay-headerMargin" style="width:auto;">
|
||||||
|
<option value="-1">Custom</option>
|
||||||
|
<option value="20">20 pt</option>
|
||||||
|
<option value="30">30 pt</option>
|
||||||
|
<option value="40" selected>40 pt</option>
|
||||||
|
<option value="50">50 pt</option>
|
||||||
|
<option value="60">60 pt</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="custom-margin-wrapper" id="overlay-headerCustomMarginWrapper" style="display: none; width: 150px;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control custom-margin-input"
|
||||||
|
id="overlay-headerCustomMarginInput"
|
||||||
|
th:placeholder="#{remove-header-footer.enterValue}"
|
||||||
|
data-type="header"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center flex-row">
|
||||||
|
<div class="splitToolbarButton mb-2 mb-md-0 me-md-2">
|
||||||
|
<button id="zoomOut" th:title="#{remove-header-footer.zoomOut}" class="toolbarButton btn-primary" tabindex="21"
|
||||||
|
data-l10n-id="pdfjs-zoom-out-button">
|
||||||
|
</button>
|
||||||
|
<div class="splitToolbarButtonSeparator"></div>
|
||||||
|
<button id="zoomIn" th:title="#{remove-header-footer.zoomIn}" class="toolbarButton btn-primary" tabindex="22"
|
||||||
|
data-l10n-id="pdfjs-zoom-in-button">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span id="scaleSelectContainer" class="dropdownToolbarButton">
|
||||||
|
<select id="scaleSelect" tabindex="23" data-l10n-id="pdfjs-zoom-select">
|
||||||
|
<option id="pageAutoOption" value="auto" selected="selected" data-l10n-id="pdfjs-page-scale-auto">
|
||||||
|
Automatic Zoom</option>
|
||||||
|
<option id="pageActualOption" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
|
||||||
|
Actual Size</option>
|
||||||
|
<option id="pageFitOption" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page
|
||||||
|
Fit
|
||||||
|
</option>
|
||||||
|
<option id="pageWidthOption" value="page-width" data-l10n-id="pdfjs-page-scale-width">
|
||||||
|
Page Width</option>
|
||||||
|
<option value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
|
||||||
|
50%</option>
|
||||||
|
<option value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>75%
|
||||||
|
</option>
|
||||||
|
<option value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
|
||||||
|
100%</option>
|
||||||
|
<option value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>125%
|
||||||
|
</option>
|
||||||
|
<option value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>150%
|
||||||
|
</option>
|
||||||
|
<option value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
|
||||||
|
200%</option>
|
||||||
|
<option value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
|
||||||
|
300%</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="overlay-margin-options" class="d-flex align-items-center">
|
||||||
|
<div class="form-check" style="margin-bottom: 0px;" >
|
||||||
|
<input type="checkbox" id="overlay-removeFooter" class="form-check-input" style=" margin-bottom: 4px; margin-left: 7px;">
|
||||||
|
<label for="overlay-removeFooter" class="form-check-label" style="padding-right: 2px; " th:text="#{remove-header-footer.removeFooter}"></label>
|
||||||
|
</div>
|
||||||
|
<span id="overlay-footerMargin-container">
|
||||||
|
<select class="form-select" id="overlay-footerMargin" style="width:auto;">
|
||||||
|
<option value="-1">Custom</option>
|
||||||
|
<option value="20">20 pt</option>
|
||||||
|
<option value="30">30 pt</option>
|
||||||
|
<option value="40" selected>40 pt</option>
|
||||||
|
<option value="50">50 pt</option>
|
||||||
|
<option value="60">60 pt</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="custom-margin-wrapper" id="overlay-footerCustomMarginWrapper" style="display: none;max-width: 150px;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control custom-margin-input"
|
||||||
|
id="overlay-footerCustomMarginInput"
|
||||||
|
th:placeholder="#{remove-header-footer.enterValue}"
|
||||||
|
data-type="footer"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="toolbarViewerRight">
|
||||||
|
<div id="splitToolbarButton">
|
||||||
|
<button id="closeOverlay" class="close-button me-2" aria-label="Close" th:title="#{remove-header-footer.close}">
|
||||||
|
<span class="material-symbols-rounded">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- Toolbar -->
|
||||||
|
</div>
|
||||||
|
<div id="viewerContainer" tabindex="0" class="overlay-content" style="inset: 4rem 0 0;">
|
||||||
|
<div id="viewer" class="pdfViewer ṕagesView">
|
||||||
|
<!-- PDF will be rendered here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -128,7 +128,7 @@ ui:
|
|||||||
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
|
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
|
||||||
|
|
||||||
endpoints: # All the possible endpoints are disabled
|
endpoints: # All the possible endpoints are disabled
|
||||||
toRemove: [crop, merge-pdfs, multi-page-layout, overlay-pdfs, pdf-to-single-page, rearrange-pages, remove-image-pdf, remove-pages, rotate-pdf, scale-pages, split-by-size-or-count, split-pages, split-pdf-by-chapters, split-pdf-by-sections, add-password, add-watermark, auto-redact, cert-sign, get-info-on-pdf, redact, remove-cert-sign, remove-password, sanitize-pdf, validate-signature, file-to-pdf, html-to-pdf, img-to-pdf, markdown-to-pdf, pdf-to-csv, pdf-to-html, pdf-to-img, pdf-to-markdown, pdf-to-pdfa, pdf-to-presentation, pdf-to-text, pdf-to-word, pdf-to-xml, url-to-pdf, add-image, add-page-numbers, add-stamp, auto-rename, auto-split-pdf, compress-pdf, decompress-pdf, extract-image-scans, extract-images, flatten, ocr-pdf, remove-blanks, repair, replace-invert-pdf, show-javascript, update-metadata, filter-contains-image, filter-contains-text, filter-file-size, filter-page-count, filter-page-rotation, filter-page-size] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
toRemove: [crop, merge-pdfs, multi-page-layout, overlay-pdfs, pdf-to-single-page, rearrange-pages, remove-image-pdf, remove-pages, rotate-pdf, scale-pages, split-by-size-or-count, split-pages, split-pdf-by-chapters, split-pdf-by-sections, add-password, add-watermark, auto-redact, cert-sign, get-info-on-pdf, redact, remove-cert-sign, remove-password, sanitize-pdf, validate-signature, file-to-pdf, html-to-pdf, img-to-pdf, markdown-to-pdf, pdf-to-csv, pdf-to-html, pdf-to-img, pdf-to-markdown, pdf-to-pdfa, pdf-to-presentation, pdf-to-text, pdf-to-word, pdf-to-xml, url-to-pdf, add-image, add-page-numbers, add-stamp, auto-rename, auto-split-pdf, compress-pdf, decompress-pdf, extract-image-scans, extract-images, flatten, ocr-pdf, remove-blanks, repair, replace-invert-pdf, show-javascript, update-metadata, filter-contains-image, filter-contains-text, filter-file-size, filter-page-count, filter-page-rotation, filter-page-size, remove-header-footer] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||||
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
|
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
|
@ -58,3 +58,4 @@
|
|||||||
/api/v1/general/multi-page-layout
|
/api/v1/general/multi-page-layout
|
||||||
/api/v1/general/merge-pdfs
|
/api/v1/general/merge-pdfs
|
||||||
/api/v1/general/crop
|
/api/v1/general/crop
|
||||||
|
/api/v1/general/remove-header-footer
|
||||||
|
@ -51,3 +51,4 @@
|
|||||||
/swagger-ui/index.html
|
/swagger-ui/index.html
|
||||||
/licenses
|
/licenses
|
||||||
/releases
|
/releases
|
||||||
|
/remove-header-footer
|
||||||
|
@ -62,4 +62,5 @@
|
|||||||
/stamp
|
/stamp
|
||||||
/validate-signature
|
/validate-signature
|
||||||
/view-pdf
|
/view-pdf
|
||||||
/swagger-ui/index.html
|
/swagger-ui/index.html
|
||||||
|
/remove-header-footer
|
||||||
|
Loading…
Reference in New Issue
Block a user