mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Merge babd115ca7 into be824b126f
This commit is contained in:
commit
58ffa02663
@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
import stirling.software.common.util.FormUtils;
|
import stirling.software.common.util.FormUtils;
|
||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
@ -49,26 +50,113 @@ public class MultiPageLayoutController {
|
|||||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
||||||
@ModelAttribute MergeMultiplePagesRequest request) throws IOException {
|
@ModelAttribute MergeMultiplePagesRequest request) throws IOException {
|
||||||
|
|
||||||
int pagesPerSheet = request.getPagesPerSheet();
|
int MAX_PAGES = 100000;
|
||||||
MultipartFile file = request.getFileInput();
|
int MAX_COLS = 300;
|
||||||
boolean addBorder = Boolean.TRUE.equals(request.getAddBorder());
|
int MAX_ROWS = 300;
|
||||||
|
|
||||||
if (pagesPerSheet != 2
|
String mode = request.getMode();
|
||||||
&& pagesPerSheet != 3
|
if (mode == null || mode.trim().isEmpty()) {
|
||||||
&& pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
|
mode = "DEFAULT";
|
||||||
throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cols =
|
int rows;
|
||||||
|
int cols;
|
||||||
|
int pagesPerSheet;
|
||||||
|
switch (mode) {
|
||||||
|
case "DEFAULT":
|
||||||
|
pagesPerSheet = request.getPagesPerSheet();
|
||||||
|
if (pagesPerSheet != 2
|
||||||
|
&& pagesPerSheet != 3
|
||||||
|
&& pagesPerSheet
|
||||||
|
!= (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidFormat",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"pagesPerSheet",
|
||||||
|
"only 2, 3, and perfect squares are supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
cols =
|
||||||
pagesPerSheet == 2 || pagesPerSheet == 3
|
pagesPerSheet == 2 || pagesPerSheet == 3
|
||||||
? pagesPerSheet
|
? pagesPerSheet
|
||||||
: (int) Math.sqrt(pagesPerSheet);
|
: (int) Math.sqrt(pagesPerSheet);
|
||||||
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
rows =
|
||||||
|
pagesPerSheet == 2 || pagesPerSheet == 3
|
||||||
|
? 1
|
||||||
|
: (int) Math.sqrt(pagesPerSheet);
|
||||||
|
break;
|
||||||
|
case "CUSTOM":
|
||||||
|
rows = request.getRows();
|
||||||
|
cols = request.getCols();
|
||||||
|
if (rows <= 0 || cols <= 0) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidFormat",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"rows and cols",
|
||||||
|
"only strictly positive values are allowed");
|
||||||
|
}
|
||||||
|
pagesPerSheet = cols * rows;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidFormat",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"mode",
|
||||||
|
"only 'DEFAULT' and 'CUSTOM' are supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pagesPerSheet > MAX_PAGES) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidArgument",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"pagesPerSheet",
|
||||||
|
"must be less than " + MAX_PAGES);
|
||||||
|
}
|
||||||
|
if (cols > MAX_COLS) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidArgument",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"cols",
|
||||||
|
"must be less than " + MAX_COLS);
|
||||||
|
}
|
||||||
|
if (rows > MAX_ROWS) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidArgument",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"rows",
|
||||||
|
"must be less than " + MAX_ROWS);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultipartFile file = request.getFileInput();
|
||||||
|
String orientation = request.getOrientation();
|
||||||
|
if (orientation == null || orientation.trim().isEmpty()) {
|
||||||
|
orientation = "PORTRAIT";
|
||||||
|
}
|
||||||
|
if (!"PORTRAIT".equals(orientation) && !"LANDSCAPE".equals(orientation)) {
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidFormat",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"orientation",
|
||||||
|
"only 'PORTRAIT' and 'LANDSCAPE' are supported");
|
||||||
|
}
|
||||||
|
String pageOrder = request.getPageOrder();
|
||||||
|
if (pageOrder == null || pageOrder.trim().isEmpty()) {
|
||||||
|
pageOrder = "LR_TD";
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean addBorder = Boolean.TRUE.equals(request.getAddBorder());
|
||||||
|
|
||||||
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
||||||
PDDocument newDocument =
|
PDDocument newDocument =
|
||||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
PDPage newPage = new PDPage(PDRectangle.A4);
|
|
||||||
|
// Create a new A4 landscape rectangle that we use when orientation is landscape
|
||||||
|
PDRectangle a4Landscape =
|
||||||
|
new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth());
|
||||||
|
PDPage newPage =
|
||||||
|
"PORTRAIT".equals(orientation)
|
||||||
|
? new PDPage(PDRectangle.A4)
|
||||||
|
: new PDPage(a4Landscape);
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
int totalPages = sourceDocument.getNumberOfPages();
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
@ -88,7 +176,10 @@ public class MultiPageLayoutController {
|
|||||||
if (i != 0 && i % pagesPerSheet == 0) {
|
if (i != 0 && i % pagesPerSheet == 0) {
|
||||||
// Close the current content stream and create a new page and content stream
|
// Close the current content stream and create a new page and content stream
|
||||||
contentStream.close();
|
contentStream.close();
|
||||||
newPage = new PDPage(PDRectangle.A4);
|
newPage =
|
||||||
|
"PORTRAIT".equals(orientation)
|
||||||
|
? new PDPage(PDRectangle.A4)
|
||||||
|
: new PDPage(a4Landscape);
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
contentStream =
|
contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
@ -108,8 +199,36 @@ public class MultiPageLayoutController {
|
|||||||
int adjustedPageIndex =
|
int adjustedPageIndex =
|
||||||
i % pagesPerSheet; // Close the current content stream and create a new
|
i % pagesPerSheet; // Close the current content stream and create a new
|
||||||
// page and content stream
|
// page and content stream
|
||||||
int rowIndex = adjustedPageIndex / cols;
|
int rowIndex;
|
||||||
int colIndex = adjustedPageIndex % cols;
|
int colIndex;
|
||||||
|
|
||||||
|
switch (pageOrder) {
|
||||||
|
case "LR_TD": // Left→Right, then Top→Down
|
||||||
|
rowIndex = adjustedPageIndex / cols;
|
||||||
|
colIndex = adjustedPageIndex % cols;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "RL_TD": // Right→Left, then Top→Down
|
||||||
|
rowIndex = adjustedPageIndex / cols;
|
||||||
|
colIndex = cols - 1 - (adjustedPageIndex % cols);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "TD_LR": // Top→Down, then Left→Right
|
||||||
|
colIndex = adjustedPageIndex / rows;
|
||||||
|
rowIndex = adjustedPageIndex % rows;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "TD_RL": // Top→Down, then Right→Left
|
||||||
|
colIndex = cols - 1 - (adjustedPageIndex / rows);
|
||||||
|
rowIndex = adjustedPageIndex % rows;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ExceptionUtils.createIllegalArgumentException(
|
||||||
|
"error.invalidFormat",
|
||||||
|
"Invalid {0} format: {1}",
|
||||||
|
"pageOrder",
|
||||||
|
"only 'LR_TD', 'RL_TD', 'TD_LR', and 'TD_RL' are supported");
|
||||||
|
}
|
||||||
|
|
||||||
float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
||||||
float y =
|
float y =
|
||||||
@ -139,7 +258,7 @@ public class MultiPageLayoutController {
|
|||||||
|
|
||||||
// If any source page is rotated, skip form copying/transformation entirely
|
// If any source page is rotated, skip form copying/transformation entirely
|
||||||
boolean hasRotation = FormUtils.hasAnyRotatedPage(sourceDocument);
|
boolean hasRotation = FormUtils.hasAnyRotatedPage(sourceDocument);
|
||||||
if (hasRotation) {
|
if (hasRotation || "LANDSCAPE".equals(orientation)) {
|
||||||
log.info("Source document has rotated pages; skipping form field copying.");
|
log.info("Source document has rotated pages; skipping form field copying.");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -10,15 +10,53 @@ import stirling.software.common.model.api.PDFFile;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class MergeMultiplePagesRequest extends PDFFile {
|
public class MergeMultiplePagesRequest extends PDFFile {
|
||||||
|
@Schema(
|
||||||
|
description = "Input mode: DEFAULT uses pagesPerSheet; CUSTOM uses explicit cols×rows.",
|
||||||
|
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||||
|
type = "string",
|
||||||
|
defaultValue = "DEFAULT",
|
||||||
|
allowableValues = {"DEFAULT", "CUSTOM"})
|
||||||
|
private String mode;
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "The number of pages to fit onto a single sheet in the output PDF.",
|
description = "The number of pages to fit onto a single sheet in the output PDF.",
|
||||||
type = "number",
|
type = "number",
|
||||||
defaultValue = "2",
|
defaultValue = "2",
|
||||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
|
||||||
allowableValues = {"2", "3", "4", "9", "16"})
|
allowableValues = {"2", "3", "4", "9", "16"})
|
||||||
private int pagesPerSheet;
|
private int pagesPerSheet;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Options for the ordering of pages",
|
||||||
|
type = "string",
|
||||||
|
defaultValue = "LR_TD",
|
||||||
|
allowableValues = {"LR_TD", "RL_TD", "TD_LR", "TD_RL"})
|
||||||
|
private String pageOrder;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Number of rows",
|
||||||
|
type = "number",
|
||||||
|
defaultValue = "1",
|
||||||
|
maximum = "300",
|
||||||
|
minimum = "1",
|
||||||
|
example = "3")
|
||||||
|
private Integer rows;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Number of columns",
|
||||||
|
type = "number",
|
||||||
|
defaultValue = "2",
|
||||||
|
maximum = "300",
|
||||||
|
minimum = "1",
|
||||||
|
example = "2")
|
||||||
|
private Integer cols;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "The orientation of the output PDF pages",
|
||||||
|
type = "string",
|
||||||
|
defaultValue = "PORTRAIT",
|
||||||
|
allowableValues = {"PORTRAIT", "LANDSCAPE"})
|
||||||
|
private String orientation;
|
||||||
|
|
||||||
@Schema(description = "Boolean for if you wish to add border around the pages")
|
@Schema(description = "Boolean for if you wish to add border around the pages")
|
||||||
private Boolean addBorder;
|
private Boolean addBorder;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1166,7 +1166,20 @@ pipeline.title=Pipeline
|
|||||||
#pageLayout
|
#pageLayout
|
||||||
pageLayout.title=Multi Page Layout
|
pageLayout.title=Multi Page Layout
|
||||||
pageLayout.header=Multi Page Layout
|
pageLayout.header=Multi Page Layout
|
||||||
|
pageLayout.mode=Mode
|
||||||
|
pageLayout.custom=Custom
|
||||||
pageLayout.pagesPerSheet=Pages per sheet:
|
pageLayout.pagesPerSheet=Pages per sheet:
|
||||||
|
pageLayout.rows=Rows
|
||||||
|
pageLayout.columns=Columns
|
||||||
|
pageLayout.advancedSettings=Advanced settings
|
||||||
|
pageLayout.pageOrder=Page order :
|
||||||
|
pageLayout.leftRightTopDown=Left -> Right, then Top -> Bottom
|
||||||
|
pageLayout.rightLeftTopDown=Right -> Left, then Top -> Bottom
|
||||||
|
pageLayout.topDownLeftRight=Top -> Bottom, then Left -> Right
|
||||||
|
pageLayout.topDownRightLeft=Top -> Bottom, then Right -> Left
|
||||||
|
pageLayout.orientation=Orientation :
|
||||||
|
pageLayout.portrait=Portrait
|
||||||
|
pageLayout.landscape=Landscape
|
||||||
pageLayout.addBorder=Add Borders
|
pageLayout.addBorder=Add Borders
|
||||||
pageLayout.submit=Submit
|
pageLayout.submit=Submit
|
||||||
|
|
||||||
|
|||||||
@ -616,6 +616,10 @@ home.cbrToPdf.title=CBR to PDF
|
|||||||
home.cbrToPdf.desc=Convert CBR comic book archives to PDF format.
|
home.cbrToPdf.desc=Convert CBR comic book archives to PDF format.
|
||||||
cbrToPdf.tags=conversion,comic,book,archive,cbr,rar
|
cbrToPdf.tags=conversion,comic,book,archive,cbr,rar
|
||||||
|
|
||||||
|
home.ebookToPdf.title=eBook to PDF
|
||||||
|
home.ebookToPdf.desc=Convert eBook files (EPUB, MOBI, AZW3, FB2, TXT, DOCX) to PDF using Calibre.
|
||||||
|
ebookToPdf.tags=conversion,ebook,calibre,epub,mobi,azw3
|
||||||
|
|
||||||
home.pdfToCbz.title=PDF to CBZ
|
home.pdfToCbz.title=PDF to CBZ
|
||||||
home.pdfToCbz.desc=Convert PDF files to CBZ comic book archives.
|
home.pdfToCbz.desc=Convert PDF files to CBZ comic book archives.
|
||||||
pdfToCbz.tags=conversion,comic,book,archive,cbz,pdf
|
pdfToCbz.tags=conversion,comic,book,archive,cbz,pdf
|
||||||
@ -1162,7 +1166,20 @@ pipeline.title=Pipeline
|
|||||||
#pageLayout
|
#pageLayout
|
||||||
pageLayout.title=Multi Page Layout
|
pageLayout.title=Multi Page Layout
|
||||||
pageLayout.header=Multi Page Layout
|
pageLayout.header=Multi Page Layout
|
||||||
|
pageLayout.mode=Mode
|
||||||
|
pageLayout.custom=Custom
|
||||||
pageLayout.pagesPerSheet=Pages per sheet:
|
pageLayout.pagesPerSheet=Pages per sheet:
|
||||||
|
pageLayout.rows=Rows
|
||||||
|
pageLayout.columns=Columns
|
||||||
|
pageLayout.advancedSettings=Advanced settings
|
||||||
|
pageLayout.pageOrder=Page order :
|
||||||
|
pageLayout.leftRightTopDown=Left -> Right, then Top -> Bottom
|
||||||
|
pageLayout.rightLeftTopDown=Right -> Left, then Top -> Bottom
|
||||||
|
pageLayout.topDownLeftRight=Top -> Bottom, then Left -> Right
|
||||||
|
pageLayout.topDownRightLeft=Top -> Bottom, then Right -> Left
|
||||||
|
pageLayout.orientation=Orientation :
|
||||||
|
pageLayout.portrait=Portrait
|
||||||
|
pageLayout.landscape=Landscape
|
||||||
pageLayout.addBorder=Add Borders
|
pageLayout.addBorder=Add Borders
|
||||||
pageLayout.submit=Submit
|
pageLayout.submit=Submit
|
||||||
|
|
||||||
@ -1490,6 +1507,17 @@ cbrToPDF.submit=Convert to PDF
|
|||||||
cbrToPDF.selectText=Select CBR file
|
cbrToPDF.selectText=Select CBR file
|
||||||
cbrToPDF.optimizeForEbook=Optimize PDF for ebook readers (uses Ghostscript)
|
cbrToPDF.optimizeForEbook=Optimize PDF for ebook readers (uses Ghostscript)
|
||||||
|
|
||||||
|
#ebookToPDF
|
||||||
|
ebookToPDF.title=eBook to PDF
|
||||||
|
ebookToPDF.header=eBook to PDF
|
||||||
|
ebookToPDF.submit=Convert to PDF
|
||||||
|
ebookToPDF.selectText=Select eBook file
|
||||||
|
ebookToPDF.embedAllFonts=Embed all fonts in the output PDF (may increase file size)
|
||||||
|
ebookToPDF.includeTableOfContents=Add a generated table of contents to the PDF
|
||||||
|
ebookToPDF.includePageNumbers=Add page numbers to the generated PDF
|
||||||
|
ebookToPDF.optimizeForEbook=Optimize PDF for ebook readers (uses Ghostscript)
|
||||||
|
ebookToPDF.calibreDisabled=Calibre support is disabled. Enable the Calibre tool group or install Calibre to use this feature.
|
||||||
|
|
||||||
#pdfToCBR
|
#pdfToCBR
|
||||||
pdfToCBR.title=PDF to CBR
|
pdfToCBR.title=PDF to CBR
|
||||||
pdfToCBR.header=PDF to CBR
|
pdfToCBR.header=PDF to CBR
|
||||||
|
|||||||
@ -616,6 +616,10 @@ home.cbrToPdf.title=CBR en PDF
|
|||||||
home.cbrToPdf.desc=Convertissez une archive de bande dessinée CBR en PDF.
|
home.cbrToPdf.desc=Convertissez une archive de bande dessinée CBR en PDF.
|
||||||
cbrToPdf.tags=bande dessinée,convertir,conversion,comic,book,archive,cbr,rar
|
cbrToPdf.tags=bande dessinée,convertir,conversion,comic,book,archive,cbr,rar
|
||||||
|
|
||||||
|
home.ebookToPdf.title=eBook to PDF
|
||||||
|
home.ebookToPdf.desc=Convert eBook files (EPUB, MOBI, AZW3, FB2, TXT, DOCX) to PDF using Calibre.
|
||||||
|
ebookToPdf.tags=conversion,ebook,calibre,epub,mobi,azw3
|
||||||
|
|
||||||
home.pdfToCbz.title=PDF en CBZ
|
home.pdfToCbz.title=PDF en CBZ
|
||||||
home.pdfToCbz.desc=Convertissez un fichier PDF en archive de bande dessinée CBZ.
|
home.pdfToCbz.desc=Convertissez un fichier PDF en archive de bande dessinée CBZ.
|
||||||
pdfToCbz.tags=bande dessinée,convertir,conversion,comic,book,archive,cbz,pdf
|
pdfToCbz.tags=bande dessinée,convertir,conversion,comic,book,archive,cbz,pdf
|
||||||
@ -1162,7 +1166,20 @@ pipeline.title=Pipeline
|
|||||||
#pageLayout
|
#pageLayout
|
||||||
pageLayout.title=Fusionner des pages
|
pageLayout.title=Fusionner des pages
|
||||||
pageLayout.header=Fusionner des pages
|
pageLayout.header=Fusionner des pages
|
||||||
|
pageLayout.mode=Mode
|
||||||
|
pageLayout.custom=Custom
|
||||||
pageLayout.pagesPerSheet=Pages par feuille
|
pageLayout.pagesPerSheet=Pages par feuille
|
||||||
|
pageLayout.rows=Rows
|
||||||
|
pageLayout.columns=Columns
|
||||||
|
pageLayout.advancedSettings=Advanced settings
|
||||||
|
pageLayout.pageOrder=Page order :
|
||||||
|
pageLayout.leftRightTopDown=Left -> Right, then Top -> Bottom
|
||||||
|
pageLayout.rightLeftTopDown=Right -> Left, then Top -> Bottom
|
||||||
|
pageLayout.topDownLeftRight=Top -> Bottom, then Left -> Right
|
||||||
|
pageLayout.topDownRightLeft=Top -> Bottom, then Right -> Left
|
||||||
|
pageLayout.orientation=Orientation :
|
||||||
|
pageLayout.portrait=Portrait
|
||||||
|
pageLayout.landscape=Paysage
|
||||||
pageLayout.addBorder=Ajouter des bordures
|
pageLayout.addBorder=Ajouter des bordures
|
||||||
pageLayout.submit=Fusionner
|
pageLayout.submit=Fusionner
|
||||||
|
|
||||||
@ -1490,6 +1507,17 @@ cbrToPDF.submit=Convertir en PDF
|
|||||||
cbrToPDF.selectText=Sélectionnez un fichier CBR
|
cbrToPDF.selectText=Sélectionnez un fichier CBR
|
||||||
cbrToPDF.optimizeForEbook=Optimiser un PDF pour une liseuse (utilise Ghostscript)
|
cbrToPDF.optimizeForEbook=Optimiser un PDF pour une liseuse (utilise Ghostscript)
|
||||||
|
|
||||||
|
#ebookToPDF
|
||||||
|
ebookToPDF.title=eBook to PDF
|
||||||
|
ebookToPDF.header=eBook to PDF
|
||||||
|
ebookToPDF.submit=Convert to PDF
|
||||||
|
ebookToPDF.selectText=Select eBook file
|
||||||
|
ebookToPDF.embedAllFonts=Embed all fonts in the output PDF (may increase file size)
|
||||||
|
ebookToPDF.includeTableOfContents=Add a generated table of contents to the PDF
|
||||||
|
ebookToPDF.includePageNumbers=Add page numbers to the generated PDF
|
||||||
|
ebookToPDF.optimizeForEbook=Optimize PDF for ebook readers (uses Ghostscript)
|
||||||
|
ebookToPDF.calibreDisabled=Calibre support is disabled. Enable the Calibre tool group or install Calibre to use this feature.
|
||||||
|
|
||||||
#pdfToCBR
|
#pdfToCBR
|
||||||
pdfToCBR.title=PDF en CBR
|
pdfToCBR.title=PDF en CBR
|
||||||
pdfToCBR.header=PDF en CBR
|
pdfToCBR.header=PDF en CBR
|
||||||
|
|||||||
@ -18,9 +18,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<form id="multiPdfForm" th:action="@{'/api/v1/general/multi-page-layout'}" method="post" enctype="multipart/form-data">
|
<form id="multiPdfForm" th:action="@{'/api/v1/general/multi-page-layout'}" method="post" enctype="multipart/form-data">
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||||
<div class="mb-3">
|
<div class="form-check mb-3">
|
||||||
|
<input type="checkbox" id="modeCustom">
|
||||||
|
<label for="modeCustom" th:text="#{pageLayout.custom}"></label>
|
||||||
|
</div>
|
||||||
|
<div id="defaultSection" class="mb-3" style="border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
|
||||||
<label for="pagesPerSheet" th:text="#{pageLayout.pagesPerSheet}"></label>
|
<label for="pagesPerSheet" th:text="#{pageLayout.pagesPerSheet}"></label>
|
||||||
<select class="form-control" id="pagesPerSheet" name="pagesPerSheet">
|
<select class="form-control" id="pagesPerSheet" name="pagesPerSheet" required>
|
||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
<option value="3">3</option>
|
<option value="3">3</option>
|
||||||
<option value="4">4</option>
|
<option value="4">4</option>
|
||||||
@ -28,12 +32,91 @@
|
|||||||
<option value="16">16</option>
|
<option value="16">16</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="customSection" class="mb-3" style="border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rows" th:text="#{pageLayout.rows}"></label>
|
||||||
|
<input class="form-control" type="number" id="rows" name="rows" step="1" min="1" max="300" value="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cols" th:text="#{pageLayout.columns}"></label>
|
||||||
|
<input class="form-control" type="number" id="cols" name="cols" step="1" min="1" max="300" value="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input id="advancedSettingsToggle" type="checkbox">
|
||||||
|
<label for="advancedSettingsToggle" th:text="#{pageLayout.advancedSettings}"></label>
|
||||||
|
</div>
|
||||||
|
<div id="advancedSettings" style="display:none; border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="pageOrder" th:text="#{pageLayout.pageOrder}"></label>
|
||||||
|
<select class="form-control" id="pageOrder" name="pageOrder">
|
||||||
|
<option value="LR_TD" th:text="#{pageLayout.leftRightTopDown}"></option>
|
||||||
|
<option value="RL_TD" th:text="#{pageLayout.rightLeftTopDown}"></option>
|
||||||
|
<option value="TD_LR" th:text="#{pageLayout.topDownLeftRight}"></option>
|
||||||
|
<option value="TD_RL" th:text="#{pageLayout.topDownRightLeft}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="orientation" th:text="#{pageLayout.orientation}"></label>
|
||||||
|
<select class="form-control" id="orientation" name="orientation">
|
||||||
|
<option value="PORTRAIT" th:text="#{pageLayout.portrait}"></option>
|
||||||
|
<option value="LANDSCAPE" th:text="#{pageLayout.landscape}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input id="addBorder" name="addBorder" type="checkbox">
|
<input id="addBorder" name="addBorder" type="checkbox">
|
||||||
<label for="addBorder" th:text="#{pageLayout.addBorder}"></label>
|
<label for="addBorder" th:text="#{pageLayout.addBorder}"></label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageLayout.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pageLayout.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const modeCheckbox = document.getElementById('modeCustom');
|
||||||
|
const defaultSection = document.getElementById('defaultSection');
|
||||||
|
const customSection = document.getElementById('customSection');
|
||||||
|
const pagesPerSheet = document.getElementById('pagesPerSheet');
|
||||||
|
const rowsInput = document.getElementById('rows');
|
||||||
|
const colsInput = document.getElementById('cols');
|
||||||
|
|
||||||
|
function sync() {
|
||||||
|
const mode = modeCheckbox.checked? 'CUSTOM' : 'DEFAULT';
|
||||||
|
const isDefault = mode === 'DEFAULT';
|
||||||
|
|
||||||
|
// Enable/disable relevant fields
|
||||||
|
pagesPerSheet.disabled = !isDefault;
|
||||||
|
rowsInput.disabled = isDefault;
|
||||||
|
colsInput.disabled = isDefault;
|
||||||
|
|
||||||
|
if (isDefault) {
|
||||||
|
defaultSection.style.display = 'block'; // show Default
|
||||||
|
customSection.style.display = 'none'; // hide Custom
|
||||||
|
} else {
|
||||||
|
defaultSection.style.display = 'none'; // hide Default
|
||||||
|
customSection.style.display = 'block'; // show Custom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modeCheckbox.addEventListener('change', sync);
|
||||||
|
|
||||||
|
// Show/hide advanced settings
|
||||||
|
const advancedToggle = document.getElementById('advancedSettingsToggle');
|
||||||
|
const advancedSettings = document.getElementById('advancedSettings');
|
||||||
|
|
||||||
|
if (advancedToggle && advancedSettings) {
|
||||||
|
advancedToggle.addEventListener('change', function() {
|
||||||
|
advancedSettings.style.display = this.checked ? 'block' : 'none';
|
||||||
|
advancedSettings.querySelector('#addBorder').checked = false;
|
||||||
|
advancedSettings.querySelector('#pageOrder').value = 'LR_TD';
|
||||||
|
advancedSettings.querySelector('#orientation').value = 'PORTRAIT';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// initial state
|
||||||
|
sync();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user