From 22df7500092838283c3834c7a36c91bb5413ef47 Mon Sep 17 00:00:00 2001 From: antonarhipov Date: Fri, 31 Oct 2025 19:46:19 +0200 Subject: [PATCH] Remove margin and bounding box constraints from watermark configuration - Simplified random position generation by removing `margin` and `bounds` parameters. - Updated `WatermarkRandomizer` to avoid use of obsolete constraints. - Refactored tests to validate new approach while ensuring watermarks stay within page boundaries. - Adjusted UI and localization files to remove unused margin and bounds options. --- .../common/util/WatermarkRandomizer.java | 85 +++--- .../common/util/WatermarkRandomizerTest.java | 270 ++++++++++++++---- .../api/security/WatermarkController.java | 78 ++--- .../api/security/AddWatermarkRequest.java | 15 - .../main/resources/messages_en_GB.properties | 4 - .../main/resources/messages_en_US.properties | 4 - .../templates/security/add-watermark.html | 31 +- .../WatermarkControllerIntegrationTest.java | 25 -- .../api/security/WatermarkValidationTest.java | 83 ------ 9 files changed, 274 insertions(+), 321 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/util/WatermarkRandomizer.java b/app/common/src/main/java/stirling/software/common/util/WatermarkRandomizer.java index 8bc93f1ba..922fa41cc 100644 --- a/app/common/src/main/java/stirling/software/common/util/WatermarkRandomizer.java +++ b/app/common/src/main/java/stirling/software/common/util/WatermarkRandomizer.java @@ -23,45 +23,24 @@ public class WatermarkRandomizer { } /** - * Generates a random position within the given bounds and margins. + * Generates a random position within the page. * * @param pageWidth Width of the page * @param pageHeight Height of the page * @param watermarkWidth Width of the watermark * @param watermarkHeight Height of the watermark - * @param margin Minimum margin from page edges - * @param boundsX Optional bounding box X coordinate (null for full page) - * @param boundsY Optional bounding box Y coordinate (null for full page) - * @param boundsWidth Optional bounding box width (null for full page) - * @param boundsHeight Optional bounding box height (null for full page) * @return Array with [x, y] coordinates */ public float[] generateRandomPosition( - float pageWidth, - float pageHeight, - float watermarkWidth, - float watermarkHeight, - float margin, - Float boundsX, - Float boundsY, - Float boundsWidth, - Float boundsHeight) { - - // Determine effective bounds - float effectiveX = (boundsX != null) ? boundsX : margin; - float effectiveY = (boundsY != null) ? boundsY : margin; - float effectiveWidth = (boundsWidth != null) ? boundsWidth : (pageWidth - 2 * margin); - float effectiveHeight = (boundsHeight != null) ? boundsHeight : (pageHeight - 2 * margin); + float pageWidth, float pageHeight, float watermarkWidth, float watermarkHeight) { // Calculate available space - float maxX = Math.max(effectiveX, effectiveX + effectiveWidth - watermarkWidth); - float maxY = Math.max(effectiveY, effectiveY + effectiveHeight - watermarkHeight); - float minX = effectiveX; - float minY = effectiveY; + float maxX = Math.max(0, pageWidth - watermarkWidth); + float maxY = Math.max(0, pageHeight - watermarkHeight); - // Generate random position within bounds - float x = minX + random.nextFloat() * Math.max(0, maxX - minX); - float y = minY + random.nextFloat() * Math.max(0, maxY - minY); + // Generate random position within page + float x = random.nextFloat() * maxX; + float y = random.nextFloat() * maxY; return new float[] {x, y}; } @@ -89,35 +68,49 @@ public class WatermarkRandomizer { List positions = new ArrayList<>(); - // Calculate how many rows and columns can fit on the page based on spacers - int maxRows = (int) (pageHeight / (watermarkHeight + heightSpacer) + 1); - int maxCols = (int) (pageWidth / (watermarkWidth + widthSpacer) + 1); + // Calculate automatic margins to keep watermarks within page boundaries + float maxX = Math.max(0, pageWidth - watermarkWidth); + float maxY = Math.max(0, pageHeight - watermarkHeight); + + // Calculate how many rows and columns can fit within the page + // Note: watermarkWidth/Height are the actual watermark dimensions (not including spacers) + // We need to account for the spacing between watermarks when calculating grid capacity + int maxRows = (int) Math.floor(maxY / (watermarkHeight + heightSpacer)); + int maxCols = (int) Math.floor(maxX / (watermarkWidth + widthSpacer)); if (count == 0) { - // Unlimited grid: fill entire page using spacer-based grid - for (int i = 0; i <= maxRows; i++) { - for (int j = 0; j <= maxCols; j++) { + // Unlimited grid: fill page using spacer-based grid + // Ensure watermarks stay within visible area + for (int i = 0; i < maxRows; i++) { + for (int j = 0; j < maxCols; j++) { float x = j * (watermarkWidth + widthSpacer); float y = i * (watermarkHeight + heightSpacer); + // Clamp to ensure within bounds + x = Math.min(x, maxX); + y = Math.min(y, maxY); positions.add(new float[] {x, y}); } } } else { - // Limited count: distribute evenly across the page using spacer-based grid - // Calculate optimal distribution - int cols = - Math.min((int) Math.ceil(Math.sqrt(count * pageWidth / pageHeight)), maxCols); - int rows = Math.min((int) Math.ceil((double) count / cols), maxRows); + // Limited count: distribute evenly across page + // Calculate optimal distribution based on page aspect ratio + // Don't use spacer-based limits; instead ensure positions fit within maxX/maxY + int cols = (int) Math.ceil(Math.sqrt(count * pageWidth / pageHeight)); + int rows = (int) Math.ceil((double) count / cols); - // Calculate step sizes to distribute watermarks evenly across available grid - int colStep = Math.max(1, maxCols / cols); - int rowStep = Math.max(1, maxRows / rows); + // Calculate spacing to distribute watermarks evenly within the visible area + // Account for watermark dimensions to prevent overflow at edges + float xSpacing = (cols > 1) ? maxX / (cols - 1) : 0; + float ySpacing = (rows > 1) ? maxY / (rows - 1) : 0; int generated = 0; - for (int i = 0; i < maxRows && generated < count; i += rowStep) { - for (int j = 0; j < maxCols && generated < count; j += colStep) { - float x = j * (watermarkWidth + widthSpacer); - float y = i * (watermarkHeight + heightSpacer); + for (int i = 0; i < rows && generated < count; i++) { + for (int j = 0; j < cols && generated < count; j++) { + float x = j * xSpacing; + float y = i * ySpacing; + // Clamp to ensure within bounds + x = Math.min(x, maxX); + y = Math.min(y, maxY); positions.add(new float[] {x, y}); generated++; } diff --git a/app/common/src/test/java/stirling/software/common/util/WatermarkRandomizerTest.java b/app/common/src/test/java/stirling/software/common/util/WatermarkRandomizerTest.java index f711c7913..b24a9e1ea 100644 --- a/app/common/src/test/java/stirling/software/common/util/WatermarkRandomizerTest.java +++ b/app/common/src/test/java/stirling/software/common/util/WatermarkRandomizerTest.java @@ -25,21 +25,16 @@ class WatermarkRandomizerTest { WatermarkRandomizer randomizer1 = new WatermarkRandomizer(TEST_SEED); WatermarkRandomizer randomizer2 = new WatermarkRandomizer(TEST_SEED); - float[] pos1 = - randomizer1.generateRandomPosition( - 800f, 600f, 100f, 50f, 10f, null, null, null, null); - float[] pos2 = - randomizer2.generateRandomPosition( - 800f, 600f, 100f, 50f, 10f, null, null, null, null); + float[] pos1 = randomizer1.generateRandomPosition(800f, 600f, 100f, 50f); + float[] pos2 = randomizer2.generateRandomPosition(800f, 600f, 100f, 50f); assertArrayEquals(pos1, pos2, "Same seed should produce same position"); } @Test - @DisplayName("Should respect margin constraints") - void testGenerateRandomPositionWithMargin() { + @DisplayName("Should keep watermark within page bounds") + void testGenerateRandomPositionWithinBounds() { WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); - float margin = 20f; float pageWidth = 800f; float pageHeight = 600f; float watermarkWidth = 100f; @@ -48,59 +43,41 @@ class WatermarkRandomizerTest { for (int i = 0; i < 10; i++) { float[] pos = randomizer.generateRandomPosition( - pageWidth, - pageHeight, - watermarkWidth, - watermarkHeight, - margin, - null, - null, - null, - null); + pageWidth, pageHeight, watermarkWidth, watermarkHeight); - assertTrue(pos[0] >= margin, "X position should respect margin"); - assertTrue(pos[1] >= margin, "Y position should respect margin"); + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); assertTrue( - pos[0] <= pageWidth - margin - watermarkWidth, - "X position should not exceed page width minus margin"); + pos[0] <= pageWidth - watermarkWidth, + "X position should not exceed page width minus watermark width"); assertTrue( - pos[1] <= pageHeight - margin - watermarkHeight, - "Y position should not exceed page height minus margin"); + pos[1] <= pageHeight - watermarkHeight, + "Y position should not exceed page height minus watermark height"); } } @Test - @DisplayName("Should respect custom bounds") - void testGenerateRandomPositionWithBounds() { + @DisplayName("Should handle small watermarks on large pages") + void testGenerateRandomPositionSmallWatermark() { WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); - float boundsX = 100f; - float boundsY = 100f; - float boundsWidth = 400f; - float boundsHeight = 300f; + float pageWidth = 800f; + float pageHeight = 600f; float watermarkWidth = 50f; float watermarkHeight = 30f; for (int i = 0; i < 10; i++) { float[] pos = randomizer.generateRandomPosition( - 800f, - 600f, - watermarkWidth, - watermarkHeight, - 10f, - boundsX, - boundsY, - boundsWidth, - boundsHeight); + pageWidth, pageHeight, watermarkWidth, watermarkHeight); - assertTrue(pos[0] >= boundsX, "X position should be within bounds"); - assertTrue(pos[1] >= boundsY, "Y position should be within bounds"); + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); assertTrue( - pos[0] <= boundsX + boundsWidth - watermarkWidth, - "X position should not exceed bounds"); + pos[0] <= pageWidth - watermarkWidth, + "X position should not exceed page width minus watermark width"); assertTrue( - pos[1] <= boundsY + boundsHeight - watermarkHeight, - "Y position should not exceed bounds"); + pos[1] <= pageHeight - watermarkHeight, + "Y position should not exceed page height minus watermark height"); } } @@ -114,7 +91,7 @@ class WatermarkRandomizerTest { float watermarkHeight = 100f; int widthSpacer = 50; int heightSpacer = 50; - int count = 5; + int count = 150; List positions = randomizer.generateGridPositions( @@ -137,20 +114,203 @@ class WatermarkRandomizerTest { } @Test - @DisplayName("Should generate unlimited grid when count is 0") - void testGenerateGridPositionsUnlimited() { + @DisplayName("Should keep watermarks within page boundaries for unlimited grid") + void testGenerateGridPositionsUnlimitedWithinBounds() { WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); - float pageWidth = 400f; - float pageHeight = 300f; + float pageWidth = 800f; + float pageHeight = 600f; float watermarkWidth = 100f; - float watermarkHeight = 100f; + float watermarkHeight = 80f; + int widthSpacer = 50; + int heightSpacer = 40; List positions = randomizer.generateGridPositions( - pageWidth, pageHeight, watermarkWidth, watermarkHeight, 50, 50, 0); + pageWidth, + pageHeight, + watermarkWidth, + watermarkHeight, + widthSpacer, + heightSpacer, + 0); assertNotNull(positions, "Positions should not be null"); - assertTrue(positions.size() > 0, "Should generate at least one position"); + assertFalse(positions.isEmpty(), "Should generate at least one position"); + + // Verify all watermarks fit within page boundaries + for (float[] pos : positions) { + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); + assertTrue( + pos[0] + watermarkWidth <= pageWidth, + String.format( + "Watermark right edge (%.2f) should not exceed page width (%.2f)", + pos[0] + watermarkWidth, pageWidth)); + assertTrue( + pos[1] + watermarkHeight <= pageHeight, + String.format( + "Watermark top edge (%.2f) should not exceed page height (%.2f)", + pos[1] + watermarkHeight, pageHeight)); + } + } + + @Test + @DisplayName("Should handle large watermarks on small pages") + void testGenerateGridPositionsLargeWatermark() { + WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); + float pageWidth = 300f; + float pageHeight = 200f; + float watermarkWidth = 150f; + float watermarkHeight = 100f; + int widthSpacer = 20; + int heightSpacer = 15; + int count = 4; + + List positions = + randomizer.generateGridPositions( + pageWidth, + pageHeight, + watermarkWidth, + watermarkHeight, + widthSpacer, + heightSpacer, + count); + + assertNotNull(positions, "Positions should not be null"); + + // Verify all watermarks fit within page boundaries + for (float[] pos : positions) { + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); + assertTrue( + pos[0] + watermarkWidth <= pageWidth, + String.format( + "Watermark right edge (%.2f) should not exceed page width (%.2f)", + pos[0] + watermarkWidth, pageWidth)); + assertTrue( + pos[1] + watermarkHeight <= pageHeight, + String.format( + "Watermark top edge (%.2f) should not exceed page height (%.2f)", + pos[1] + watermarkHeight, pageHeight)); + } + } + + @Test + @DisplayName("Should handle edge case with zero spacers") + void testGenerateGridPositionsZeroSpacers() { + WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); + float pageWidth = 600f; + float pageHeight = 400f; + float watermarkWidth = 100f; + float watermarkHeight = 80f; + int widthSpacer = 0; + int heightSpacer = 0; + int count = 6; + + List positions = + randomizer.generateGridPositions( + pageWidth, + pageHeight, + watermarkWidth, + watermarkHeight, + widthSpacer, + heightSpacer, + count); + + assertNotNull(positions, "Positions should not be null"); + + // Verify all watermarks fit within page boundaries + for (float[] pos : positions) { + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); + assertTrue( + pos[0] + watermarkWidth <= pageWidth, + String.format( + "Watermark right edge (%.2f) should not exceed page width (%.2f)", + pos[0] + watermarkWidth, pageWidth)); + assertTrue( + pos[1] + watermarkHeight <= pageHeight, + String.format( + "Watermark top edge (%.2f) should not exceed page height (%.2f)", + pos[1] + watermarkHeight, pageHeight)); + } + } + + @Test + @DisplayName("Should handle edge case with large spacers") + void testGenerateGridPositionsLargeSpacers() { + WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); + float pageWidth = 1000f; + float pageHeight = 800f; + float watermarkWidth = 80f; + float watermarkHeight = 60f; + int widthSpacer = 200; + int heightSpacer = 150; + int count = 0; // Unlimited + + List positions = + randomizer.generateGridPositions( + pageWidth, + pageHeight, + watermarkWidth, + watermarkHeight, + widthSpacer, + heightSpacer, + count); + + assertNotNull(positions, "Positions should not be null"); + + // Verify all watermarks fit within page boundaries + for (float[] pos : positions) { + assertTrue(pos[0] >= 0, "X position should be non-negative"); + assertTrue(pos[1] >= 0, "Y position should be non-negative"); + assertTrue( + pos[0] + watermarkWidth <= pageWidth, + String.format( + "Watermark right edge (%.2f) should not exceed page width (%.2f)", + pos[0] + watermarkWidth, pageWidth)); + assertTrue( + pos[1] + watermarkHeight <= pageHeight, + String.format( + "Watermark top edge (%.2f) should not exceed page height (%.2f)", + pos[1] + watermarkHeight, pageHeight)); + } + } + + @Test + @DisplayName("Should handle watermark exactly fitting page dimensions") + void testGenerateGridPositionsExactFit() { + WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); + float pageWidth = 400f; + float pageHeight = 300f; + float watermarkWidth = 400f; + float watermarkHeight = 300f; + int widthSpacer = 0; + int heightSpacer = 0; + int count = 1; + + List positions = + randomizer.generateGridPositions( + pageWidth, + pageHeight, + watermarkWidth, + watermarkHeight, + widthSpacer, + heightSpacer, + count); + + assertNotNull(positions, "Positions should not be null"); + assertEquals(1, positions.size(), "Should generate exactly one position"); + + float[] pos = positions.get(0); + assertEquals(0f, pos[0], 0.01f, "X position should be 0"); + assertEquals(0f, pos[1], 0.01f, "Y position should be 0"); + assertTrue( + pos[0] + watermarkWidth <= pageWidth, + "Watermark right edge should not exceed page width"); + assertTrue( + pos[1] + watermarkHeight <= pageHeight, + "Watermark top edge should not exceed page height"); } } @@ -332,7 +492,7 @@ class WatermarkRandomizerTest { @DisplayName("Should return default when font list is empty") void testSelectRandomFontEmpty() { WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); - List fonts = Arrays.asList(); + List fonts = List.of(); String font = randomizer.selectRandomFont(fonts); @@ -549,7 +709,7 @@ class WatermarkRandomizerTest { @DisplayName("Should return 'none' when shading list is empty") void testSelectRandomShadingEmpty() { WatermarkRandomizer randomizer = new WatermarkRandomizer(TEST_SEED); - List shadings = Arrays.asList(); + List shadings = List.of(); String shading = randomizer.selectRandomShading(shadings); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index 70612b6c7..e6c3913d8 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -335,7 +335,6 @@ public class WatermarkController { (request.getFontSizeMin() != null) ? request.getFontSizeMin() : fontSize; float fontSizeMax = (request.getFontSizeMax() != null) ? request.getFontSizeMax() : fontSize; - float margin = (request.getMargin() != null) ? request.getMargin() : 10f; // Extract per-letter configuration with defaults int perLetterFontCount = @@ -355,23 +354,6 @@ public class WatermarkController { ? request.getPerLetterOrientationMax() : 360f; - // Parse bounds if provided - Float boundsX = null, boundsY = null, boundsWidth = null, boundsHeight = null; - if (request.getBounds() != null && !request.getBounds().isEmpty()) { - String[] boundsParts = request.getBounds().split(","); - if (boundsParts.length == 4) { - try { - boundsX = Float.parseFloat(boundsParts[0].trim()); - boundsY = Float.parseFloat(boundsParts[1].trim()); - boundsWidth = Float.parseFloat(boundsParts[2].trim()); - boundsHeight = Float.parseFloat(boundsParts[3].trim()); - } catch (NumberFormatException e) { - log.error("Invalid bounds format: {}", request.getBounds(), e); - boundsX = boundsY = boundsWidth = boundsHeight = null; - } - } - } - String resourceDir = switch (alphabet) { case "arabic" -> "static/fonts/NotoSansArabic-Regular.ttf"; @@ -403,30 +385,31 @@ public class WatermarkController { // Determine positions based on a randomPosition flag java.util.List positions; + + // Calculate approximate watermark dimensions for positioning + // Estimate width based on average character width (more accurate than fixed 100) + float avgCharWidth = fontSize * 0.6f; // Approximate average character width + float maxLineWidth = 0; + for (String line : textLines) { + float lineWidth = line.length() * avgCharWidth; + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + } + float watermarkWidth = maxLineWidth; + float watermarkHeight = fontSize * textLines.length; + if (randomPosition) { // Generate random positions positions = new java.util.ArrayList<>(); for (int i = 0; i < count; i++) { - // Use approximate watermark dimensions for positioning - float approxWidth = widthSpacer + 100; // Approximate - float approxHeight = heightSpacer + fontSize * textLines.length; float[] pos = randomizer.generateRandomPosition( - pageWidth, - pageHeight, - approxWidth, - approxHeight, - margin, - boundsX, - boundsY, - boundsWidth, - boundsHeight); + pageWidth, pageHeight, watermarkWidth, watermarkHeight); positions.add(pos); } } else { // Generate grid positions (backward compatible) - float watermarkWidth = 100 * fontSize / 1000; - float watermarkHeight = fontSize * textLines.length; positions = randomizer.generateGridPositions( pageWidth, @@ -648,23 +631,6 @@ public class WatermarkController { (request.getRotationMin() != null) ? request.getRotationMin() : rotation; float rotationMax = (request.getRotationMax() != null) ? request.getRotationMax() : rotation; - float margin = (request.getMargin() != null) ? request.getMargin() : 10f; - - // Parse bounds if provided - Float boundsX = null, boundsY = null, boundsWidth = null, boundsHeight = null; - if (request.getBounds() != null && !request.getBounds().isEmpty()) { - String[] boundsParts = request.getBounds().split(","); - if (boundsParts.length == 4) { - try { - boundsX = Float.parseFloat(boundsParts[0].trim()); - boundsY = Float.parseFloat(boundsParts[1].trim()); - boundsWidth = Float.parseFloat(boundsParts[2].trim()); - boundsHeight = Float.parseFloat(boundsParts[3].trim()); - } catch (NumberFormatException e) { - log.warn("Invalid bounds format: {}", request.getBounds(), e); - } - } - } // Load the watermark image BufferedImage image = ImageIO.read(watermarkImage.getInputStream()); @@ -693,15 +659,7 @@ public class WatermarkController { for (int i = 0; i < count; i++) { float[] pos = randomizer.generateRandomPosition( - pageWidth, - pageHeight, - desiredPhysicalWidth, - desiredPhysicalHeight, - margin, - boundsX, - boundsY, - boundsWidth, - boundsHeight); + pageWidth, pageHeight, desiredPhysicalWidth, desiredPhysicalHeight); positions.add(pos); } } else { @@ -710,8 +668,8 @@ public class WatermarkController { randomizer.generateGridPositions( pageWidth, pageHeight, - desiredPhysicalWidth + widthSpacer, - desiredPhysicalHeight + heightSpacer, + desiredPhysicalWidth, + desiredPhysicalHeight, widthSpacer, heightSpacer, count); diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java index e5737f018..46916505c 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/security/AddWatermarkRequest.java @@ -8,7 +8,6 @@ import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.EqualsAndHashCode; @@ -187,20 +186,6 @@ public class AddWatermarkRequest extends PDFFile { @Schema(description = "Random seed for deterministic randomness (optional, for testing)") private Long seed; - @Schema(description = "Minimum margin from page edges in points", defaultValue = "10") - @DecimalMin(value = "0.0", message = "Margin must be >= 0.0") - @DecimalMax(value = "500.0", message = "Margin must be <= 500.0") - private Float margin; - - @Schema( - description = - "Bounding box constraint for watermark placement (format: x,y,width,height)") - @Pattern( - regexp = - "^(\\d+(\\.\\d+)?),\\s*(\\d+(\\.\\d+)?),\\s*(\\d+(\\.\\d+)?),\\s*(\\d+(\\.\\d+)?)$", - message = "Bounds must be in format: x,y,width,height") - private String bounds; - @Schema( description = "Scale factor for image watermarks (1.0 = original size)", defaultValue = "1.0") diff --git a/app/core/src/main/resources/messages_en_GB.properties b/app/core/src/main/resources/messages_en_GB.properties index 3bfa29262..518ebf6c8 100644 --- a/app/core/src/main/resources/messages_en_GB.properties +++ b/app/core/src/main/resources/messages_en_GB.properties @@ -1554,10 +1554,6 @@ watermark.advanced.count.label=Number of Watermarks watermark.advanced.count.help=Number of watermark instances per page (1-1000) watermark.advanced.position.label=Random Positioning watermark.advanced.position.help=Enable random placement instead of grid layout -watermark.advanced.margin.label=Margin from Edges (points) -watermark.advanced.margin.help=Minimum distance from page edges (0-500) -watermark.advanced.bounds.label=Bounding Box (optional) -watermark.advanced.bounds.help=Constrain placement to specific area (format: x,y,width,height) watermark.advanced.rotation.mode.label=Rotation Mode watermark.advanced.rotation.mode.fixed=Fixed Angle watermark.advanced.rotation.mode.range=Random Range diff --git a/app/core/src/main/resources/messages_en_US.properties b/app/core/src/main/resources/messages_en_US.properties index 592cc47df..0544c3b0b 100644 --- a/app/core/src/main/resources/messages_en_US.properties +++ b/app/core/src/main/resources/messages_en_US.properties @@ -1550,10 +1550,6 @@ watermark.advanced.count.label=Number of Watermarks watermark.advanced.count.help=Number of watermark instances per page (1-1000) watermark.advanced.position.label=Random Positioning watermark.advanced.position.help=Enable random placement instead of grid layout -watermark.advanced.margin.label=Margin from Edges (points) -watermark.advanced.margin.help=Minimum distance from page edges (0-500) -watermark.advanced.bounds.label=Bounding Box (optional) -watermark.advanced.bounds.help=Constrain placement to specific area (format: x,y,width,height) watermark.advanced.rotation.mode.label=Rotation Mode watermark.advanced.rotation.mode.fixed=Fixed Angle watermark.advanced.rotation.mode.range=Random Range diff --git a/app/core/src/main/resources/templates/security/add-watermark.html b/app/core/src/main/resources/templates/security/add-watermark.html index 5272544bd..3fafdf107 100644 --- a/app/core/src/main/resources/templates/security/add-watermark.html +++ b/app/core/src/main/resources/templates/security/add-watermark.html @@ -150,18 +150,6 @@ Enable random placement instead of grid layout -
- - - Minimum distance from page edges (0-500) -
- -
- - - Constrain placement to specific area (format: x,y,width,height) -
-
@@ -530,8 +518,8 @@ // Validate count const count = parseInt(document.getElementById('count').value); - if (count < 1 || count > 1000) { - errors.push('Count must be between 1 and 1000'); + if (count < 0 || count > 1000) { + errors.push('Count must be between 0 and 1000'); } // Validate rotation range @@ -557,21 +545,6 @@ } } - // Validate margin - const margin = parseFloat(document.getElementById('margin').value); - if (margin < 0 || margin > 500) { - errors.push('Margin must be between 0 and 500'); - } - - // Validate bounds format (if provided) - const bounds = document.getElementById('bounds').value.trim(); - if (bounds) { - const boundsPattern = /^(\d+(\.\d+)?),\s*(\d+(\.\d+)?),\s*(\d+(\.\d+)?),\s*(\d+(\.\d+)?)$/; - if (!boundsPattern.test(bounds)) { - errors.push('Bounds must be in format: x,y,width,height'); - } - } - // Validate mirroring probability if (document.getElementById('randomMirroring').checked) { const prob = parseFloat(document.getElementById('mirroringProbability').value); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerIntegrationTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerIntegrationTest.java index 8180cacb1..1d016e573 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerIntegrationTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkControllerIntegrationTest.java @@ -182,7 +182,6 @@ class WatermarkControllerIntegrationTest { request.setConvertPDFToImage(false); request.setRandomPosition(true); request.setCount(5); - request.setMargin(10f); request.setSeed(12345L); // Use seed for deterministic testing ResponseEntity response = watermarkController.addWatermark(request); @@ -467,29 +466,6 @@ class WatermarkControllerIntegrationTest { assertNotNull(response, "Response should not be null"); assertEquals(200, response.getStatusCode().value(), "Should return 200 OK"); } - - @Test - @DisplayName("Should apply text watermark with custom bounds") - void testTextWatermarkWithBounds() throws Exception { - AddWatermarkRequest request = new AddWatermarkRequest(); - request.setAlphabet("roman"); - request.setFileInput(testPdfFile); - request.setWatermarkType("text"); - request.setWatermarkText("Bounded"); - request.setOpacity(0.5f); - request.setFontSize(25f); - request.setCustomColor("#00FF00"); - request.setConvertPDFToImage(false); - request.setRandomPosition(true); - request.setCount(3); - request.setBounds("100,100,300,200"); - request.setSeed(99000L); - - ResponseEntity response = watermarkController.addWatermark(request); - - assertNotNull(response, "Response should not be null"); - assertEquals(200, response.getStatusCode().value(), "Should return 200 OK"); - } } @Nested @@ -625,7 +601,6 @@ class WatermarkControllerIntegrationTest { request.setOpacity(0.5f); request.setRandomPosition(true); request.setCount(5); - request.setMargin(20f); request.setSeed(45454L); request.setConvertPDFToImage(false); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkValidationTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkValidationTest.java index 24cf40722..641c86e09 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkValidationTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/WatermarkValidationTest.java @@ -925,56 +925,6 @@ class WatermarkValidationTest { "Should have no violations on 'perLetterColorCount' field"); } - @Test - @DisplayName("Should reject margin below 0.0") - void testMarginBelowBound() { - // Arrange - request.setMargin(-1f); - - // Act - Set> violations = validator.validate(request); - - // Assert - assertFalse(violations.isEmpty(), "Should have validation errors"); - assertTrue( - violations.stream() - .anyMatch(v -> v.getPropertyPath().toString().equals("margin")), - "Should have violation on 'margin' field"); - } - - @Test - @DisplayName("Should reject margin above 500.0") - void testMarginAboveBound() { - // Arrange - request.setMargin(501f); - - // Act - Set> violations = validator.validate(request); - - // Assert - assertFalse(violations.isEmpty(), "Should have validation errors"); - assertTrue( - violations.stream() - .anyMatch(v -> v.getPropertyPath().toString().equals("margin")), - "Should have violation on 'margin' field"); - } - - @Test - @DisplayName("Should accept valid margin value") - void testMarginValid() { - // Arrange - request.setMargin(10f); - - // Act - Set> violations = validator.validate(request); - - // Assert - assertTrue( - violations.stream() - .noneMatch(v -> v.getPropertyPath().toString().equals("margin")), - "Should have no violations on 'margin' field"); - } - @Test @DisplayName("Should reject imageScale below 0.1") void testImageScaleBelowBound() { @@ -1024,38 +974,5 @@ class WatermarkValidationTest { .noneMatch(v -> v.getPropertyPath().toString().equals("imageScale")), "Should have no violations on 'imageScale' field"); } - - @Test - @DisplayName("Should reject invalid bounds format") - void testBoundsInvalidFormat() { - // Arrange - request.setBounds("invalid"); - - // Act - Set> violations = validator.validate(request); - - // Assert - assertFalse(violations.isEmpty(), "Should have validation errors"); - assertTrue( - violations.stream() - .anyMatch(v -> v.getPropertyPath().toString().equals("bounds")), - "Should have violation on 'bounds' field"); - } - - @Test - @DisplayName("Should accept valid bounds format") - void testBoundsValidFormat() { - // Arrange - request.setBounds("100,100,200,200"); - - // Act - Set> violations = validator.validate(request); - - // Assert - assertTrue( - violations.stream() - .noneMatch(v -> v.getPropertyPath().toString().equals("bounds")), - "Should have no violations on 'bounds' field"); - } } }