diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 2965870bb..fb7a55b79 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -40,7 +40,8 @@ public class ScalePagesController { private final CustomPDFDocumentFactory pdfDocumentFactory; private final TempFileManager tempFileManager; - private static PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) { + private static PDRectangle getTargetSize( + String targetPDRectangle, String orientation, PDDocument sourceDocument) { if ("KEEP".equals(targetPDRectangle)) { if (sourceDocument.getNumberOfPages() == 0) { throw ExceptionUtils.createInvalidPageSizeException("KEEP"); @@ -57,18 +58,19 @@ public class ScalePagesController { } Map sizeMap = getSizeMap(); - - if (sizeMap.containsKey(targetPDRectangle)) { - return sizeMap.get(targetPDRectangle); + PDRectangle base = sizeMap.get(targetPDRectangle); + if (base == null) { + throw ExceptionUtils.createInvalidPageSizeException(targetPDRectangle); } - throw ExceptionUtils.createInvalidPageSizeException(targetPDRectangle); + if ("LANDSCAPE".equalsIgnoreCase(orientation)) { + return new PDRectangle(base.getHeight(), base.getWidth()); + } + return base; } private static Map getSizeMap() { Map sizeMap = new HashMap<>(); - - // Portrait sizes (A0-A6) sizeMap.put("A0", PDRectangle.A0); sizeMap.put("A1", PDRectangle.A1); sizeMap.put("A2", PDRectangle.A2); @@ -76,42 +78,8 @@ public class ScalePagesController { sizeMap.put("A4", PDRectangle.A4); sizeMap.put("A5", PDRectangle.A5); sizeMap.put("A6", PDRectangle.A6); - - // Landscape sizes (A0-A6) - sizeMap.put( - "A0_LANDSCAPE", - new PDRectangle(PDRectangle.A0.getHeight(), PDRectangle.A0.getWidth())); - sizeMap.put( - "A1_LANDSCAPE", - new PDRectangle(PDRectangle.A1.getHeight(), PDRectangle.A1.getWidth())); - sizeMap.put( - "A2_LANDSCAPE", - new PDRectangle(PDRectangle.A2.getHeight(), PDRectangle.A2.getWidth())); - sizeMap.put( - "A3_LANDSCAPE", - new PDRectangle(PDRectangle.A3.getHeight(), PDRectangle.A3.getWidth())); - sizeMap.put( - "A4_LANDSCAPE", - new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); - sizeMap.put( - "A5_LANDSCAPE", - new PDRectangle(PDRectangle.A5.getHeight(), PDRectangle.A5.getWidth())); - sizeMap.put( - "A6_LANDSCAPE", - new PDRectangle(PDRectangle.A6.getHeight(), PDRectangle.A6.getWidth())); - - // Portrait US sizes sizeMap.put("LETTER", PDRectangle.LETTER); sizeMap.put("LEGAL", PDRectangle.LEGAL); - - // Landscape US sizes - sizeMap.put( - "LETTER_LANDSCAPE", - new PDRectangle(PDRectangle.LETTER.getHeight(), PDRectangle.LETTER.getWidth())); - sizeMap.put( - "LEGAL_LANDSCAPE", - new PDRectangle(PDRectangle.LEGAL.getHeight(), PDRectangle.LEGAL.getWidth())); - return sizeMap; } @@ -128,13 +96,14 @@ public class ScalePagesController { throws IOException { MultipartFile file = request.getFileInput(); String targetPDRectangle = request.getPageSize(); + String orientation = request.getOrientation(); float scaleFactor = request.getScaleFactor(); try (PDDocument sourceDocument = pdfDocumentFactory.load(file); PDDocument outputDocument = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument)) { - PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument); + PDRectangle targetSize = getTargetSize(targetPDRectangle, orientation, sourceDocument); // Create LayerUtility once outside the loop for better performance LayerUtility layerUtility = new LayerUtility(outputDocument); diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java b/app/core/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java index 83501fb35..a8095ac75 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/PDFWithPageSize.java @@ -18,4 +18,11 @@ public class PDFWithPageSize extends PDFFile { requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "LETTER", "LEGAL", "KEEP"}) private String pageSize; + + @Schema( + description = + "Orientation to apply to the target page size. Ignored when pageSize is KEEP.", + defaultValue = "PORTRAIT", + allowableValues = {"PORTRAIT", "LANDSCAPE"}) + private String orientation = "PORTRAIT"; } diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java index 815309b88..c3d0d969e 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/ScalePagesControllerTest.java @@ -237,7 +237,8 @@ class ScalePagesControllerTest { ScalePagesRequest request = new ScalePagesRequest(); request.setFileInput(file); - request.setPageSize("A4_LANDSCAPE"); + request.setPageSize("A4"); + request.setOrientation("LANDSCAPE"); request.setScaleFactor(1.0f); setupFactory(); diff --git a/engine/src/stirling/models/tool_models.py b/engine/src/stirling/models/tool_models.py index 742d1a796..91ff27b5b 100644 --- a/engine/src/stirling/models/tool_models.py +++ b/engine/src/stirling/models/tool_models.py @@ -1094,6 +1094,15 @@ class SanitizePdfParams(ApiModel): remove_xmp_metadata: bool = Field(False, description="Remove XMP metadata from the PDF") +class Orientation1(StrEnum): + """ + Orientation to apply to the target page size. Ignored when pageSize is KEEP. + """ + + portrait = "PORTRAIT" + landscape = "LANDSCAPE" + + class PageSize(StrEnum): """ The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL, KEEP. @@ -1112,6 +1121,10 @@ class PageSize(StrEnum): class ScalePagesParams(ApiModel): + orientation: Orientation1 = Field( + Orientation1.portrait, + description="Orientation to apply to the target page size. Ignored when pageSize is KEEP.", + ) page_size: PageSize = Field( ..., description="The scale of pages in the output PDF. Acceptable values are A0-A6, LETTER, LEGAL, KEEP." ) diff --git a/frontend/editor/public/locales/en-GB/translation.toml b/frontend/editor/public/locales/en-GB/translation.toml index 80cf7fd58..db6e0499d 100644 --- a/frontend/editor/public/locales/en-GB/translation.toml +++ b/frontend/editor/public/locales/en-GB/translation.toml @@ -494,6 +494,11 @@ title = "Adjust Page Scale" [adjustPageScale.error] failed = "An error occurred while adjusting the page scale." +[adjustPageScale.orientation] +label = "Page orientation" +landscape = "Landscape" +portrait = "Portrait" + [adjustPageScale.pageSize] keep = "Keep Original Size" label = "Target Page Size" diff --git a/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.test.tsx b/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.test.tsx index 6f11446ed..6ea93a2cc 100644 --- a/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.test.tsx +++ b/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.test.tsx @@ -24,6 +24,7 @@ describe("AdjustPageScaleSettings", () => { const defaultParameters: AdjustPageScaleParameters = { scaleFactor: 1.0, pageSize: PageSize.KEEP, + orientation: "PORTRAIT", }; const mockOnParameterChange = vi.fn(); @@ -51,6 +52,7 @@ describe("AdjustPageScaleSettings", () => { const customParameters: AdjustPageScaleParameters = { scaleFactor: 2.5, pageSize: PageSize.A4, + orientation: "LANDSCAPE", }; render( diff --git a/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.tsx b/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.tsx index 460988bac..4012909f2 100644 --- a/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.tsx +++ b/frontend/editor/src/core/components/tools/adjustPageScale/AdjustPageScaleSettings.tsx @@ -1,7 +1,8 @@ -import { Stack, NumberInput, Select } from "@mantine/core"; +import { Stack, NumberInput, Select, SegmentedControl } from "@mantine/core"; import { useTranslation } from "react-i18next"; import { AdjustPageScaleParameters, + Orientation, PageSize, } from "@app/hooks/tools/adjustPageScale/useAdjustPageScaleParameters"; import { Z_INDEX_AUTOMATE_DROPDOWN } from "@app/styles/zIndex"; @@ -22,6 +23,8 @@ const AdjustPageScaleSettings = ({ }: AdjustPageScaleSettingsProps) => { const { t } = useTranslation(); + const isKeepSelected = parameters.pageSize === PageSize.KEEP; + const pageSizeOptions = [ { value: PageSize.KEEP, @@ -44,6 +47,17 @@ const AdjustPageScaleSettings = ({ }, ]; + const orientationOptions = [ + { + value: "PORTRAIT", + label: t("adjustPageScale.orientation.portrait", "Portrait"), + }, + { + value: "LANDSCAPE", + label: t("adjustPageScale.orientation.landscape", "Landscape"), + }, + ]; + return ( { - if (value && Object.values(PageSize).includes(value as PageSize)) { - onParameterChange("pageSize", value as PageSize); - } + if (!value) return; + const next = value as PageSize; + if (!Object.values(PageSize).includes(next)) return; + onParameterChange("pageSize", next); }} data={pageSizeOptions} disabled={disabled} @@ -77,6 +92,17 @@ const AdjustPageScaleSettings = ({ zIndex: Z_INDEX_AUTOMATE_DROPDOWN, }} /> + + + onParameterChange("orientation", value as Orientation) + } + data={orientationOptions} + disabled={disabled || isKeepSelected} + fullWidth + /> ); }; diff --git a/frontend/editor/src/core/hooks/tools/adjustPageScale/adjustPageScaleFormData.ts b/frontend/editor/src/core/hooks/tools/adjustPageScale/adjustPageScaleFormData.ts new file mode 100644 index 000000000..95ee6f329 --- /dev/null +++ b/frontend/editor/src/core/hooks/tools/adjustPageScale/adjustPageScaleFormData.ts @@ -0,0 +1,13 @@ +import { AdjustPageScaleParameters } from "@app/hooks/tools/adjustPageScale/useAdjustPageScaleParameters"; + +export const buildAdjustPageScaleFormData = ( + parameters: AdjustPageScaleParameters, + file: File, +): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + formData.append("scaleFactor", parameters.scaleFactor.toString()); + formData.append("pageSize", parameters.pageSize); + formData.append("orientation", parameters.orientation); + return formData; +}; diff --git a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.test.ts b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.test.ts new file mode 100644 index 000000000..264c71c94 --- /dev/null +++ b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { buildAdjustPageScaleFormData } from "@app/hooks/tools/adjustPageScale/adjustPageScaleFormData"; +import { + defaultParameters, + PageSize, +} from "@app/hooks/tools/adjustPageScale/useAdjustPageScaleParameters"; + +const stubFile = () => + new File(["data"], "doc.pdf", { type: "application/pdf" }); + +describe("buildAdjustPageScaleFormData", () => { + it("includes scaleFactor, pageSize, and orientation by default", () => { + const formData = buildAdjustPageScaleFormData( + defaultParameters, + stubFile(), + ); + expect(formData.get("scaleFactor")).toBe( + defaultParameters.scaleFactor.toString(), + ); + expect(formData.get("pageSize")).toBe(defaultParameters.pageSize); + expect(formData.get("orientation")).toBe(defaultParameters.orientation); + }); + + it("forwards orientation=LANDSCAPE to the backend", () => { + const formData = buildAdjustPageScaleFormData( + { ...defaultParameters, pageSize: PageSize.A4, orientation: "LANDSCAPE" }, + stubFile(), + ); + expect(formData.get("orientation")).toBe("LANDSCAPE"); + expect(formData.get("pageSize")).toBe("A4"); + }); + + it("forwards orientation=PORTRAIT to the backend", () => { + const formData = buildAdjustPageScaleFormData( + { + ...defaultParameters, + pageSize: PageSize.LEGAL, + orientation: "PORTRAIT", + }, + stubFile(), + ); + expect(formData.get("orientation")).toBe("PORTRAIT"); + expect(formData.get("pageSize")).toBe("LEGAL"); + }); + + it("attaches the uploaded file under fileInput", () => { + const file = stubFile(); + const formData = buildAdjustPageScaleFormData(defaultParameters, file); + expect(formData.get("fileInput")).toBe(file); + }); +}); diff --git a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.ts b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.ts index d19e55ac5..c71073772 100644 --- a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.ts +++ b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleOperation.ts @@ -8,17 +8,9 @@ import { AdjustPageScaleParameters, defaultParameters, } from "@app/hooks/tools/adjustPageScale/useAdjustPageScaleParameters"; +import { buildAdjustPageScaleFormData } from "@app/hooks/tools/adjustPageScale/adjustPageScaleFormData"; -export const buildAdjustPageScaleFormData = ( - parameters: AdjustPageScaleParameters, - file: File, -): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - formData.append("scaleFactor", parameters.scaleFactor.toString()); - formData.append("pageSize", parameters.pageSize); - return formData; -}; +export { buildAdjustPageScaleFormData }; export const adjustPageScaleOperationConfig = { toolType: ToolType.singleFile, diff --git a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.test.ts b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.test.ts index 16d4cb781..e484989d9 100644 --- a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.test.ts +++ b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.test.ts @@ -23,6 +23,8 @@ describe("useAdjustPageScaleParameters", () => { { paramName: "pageSize" as const, value: PageSize.A4 }, { paramName: "pageSize" as const, value: PageSize.LETTER }, { paramName: "pageSize" as const, value: PageSize.LEGAL }, + { paramName: "orientation" as const, value: "LANDSCAPE" as const }, + { paramName: "orientation" as const, value: "PORTRAIT" as const }, ])("should update parameter $paramName to $value", ({ paramName, value }) => { const { result } = renderHook(() => useAdjustPageScaleParameters()); diff --git a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.ts b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.ts index 09d9a1cd2..b099bcc39 100644 --- a/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.ts +++ b/frontend/editor/src/core/hooks/tools/adjustPageScale/useAdjustPageScaleParameters.ts @@ -17,14 +17,18 @@ export enum PageSize { LEGAL = "LEGAL", } +export type Orientation = "PORTRAIT" | "LANDSCAPE"; + export interface AdjustPageScaleParameters extends BaseParameters { scaleFactor: number; pageSize: PageSize; + orientation: Orientation; } export const defaultParameters: AdjustPageScaleParameters = { scaleFactor: 1.0, pageSize: PageSize.KEEP, + orientation: "PORTRAIT", }; export type AdjustPageScaleParametersHook =