mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
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.
This commit is contained in:
parent
055b2ce2fc
commit
22df750009
@ -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<float[]> 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++;
|
||||
}
|
||||
|
||||
@ -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<float[]> 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<float[]> 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<float[]> 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<float[]> 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<float[]> 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<float[]> 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<String> fonts = Arrays.asList();
|
||||
List<String> 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<String> shadings = Arrays.asList();
|
||||
List<String> shadings = List.of();
|
||||
|
||||
String shading = randomizer.selectRandomShading(shadings);
|
||||
|
||||
|
||||
@ -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<float[]> 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);
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -150,18 +150,6 @@
|
||||
<small class="form-text text-muted" th:text="#{watermark.advanced.position.help}">Enable random placement instead of grid layout</small>
|
||||
</div>
|
||||
|
||||
<div id="marginGroup" class="mb-3">
|
||||
<label for="margin" th:text="#{watermark.advanced.margin.label}">Margin from Edges (points)</label>
|
||||
<input type="number" id="margin" name="margin" class="form-control" value="10" min="0" max="500" step="0.1">
|
||||
<small class="form-text text-muted" th:text="#{watermark.advanced.margin.help}">Minimum distance from page edges (0-500)</small>
|
||||
</div>
|
||||
|
||||
<div id="boundsGroup" class="mb-3">
|
||||
<label for="bounds" th:text="#{watermark.advanced.bounds.label}">Bounding Box (optional)</label>
|
||||
<input type="text" id="bounds" name="bounds" class="form-control" placeholder="x,y,width,height">
|
||||
<small class="form-text text-muted" th:text="#{watermark.advanced.bounds.help}">Constrain placement to specific area (format: x,y,width,height)</small>
|
||||
</div>
|
||||
|
||||
<!-- Rotation Controls -->
|
||||
<div class="mb-3">
|
||||
<label th:text="#{watermark.advanced.rotation.mode.label}">Rotation Mode</label>
|
||||
@ -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);
|
||||
|
||||
@ -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<byte[]> 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<byte[]> 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);
|
||||
|
||||
|
||||
@ -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<ConstraintViolation<AddWatermarkRequest>> 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<ConstraintViolation<AddWatermarkRequest>> 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<ConstraintViolation<AddWatermarkRequest>> 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<ConstraintViolation<AddWatermarkRequest>> 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<ConstraintViolation<AddWatermarkRequest>> violations = validator.validate(request);
|
||||
|
||||
// Assert
|
||||
assertTrue(
|
||||
violations.stream()
|
||||
.noneMatch(v -> v.getPropertyPath().toString().equals("bounds")),
|
||||
"Should have no violations on 'bounds' field");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user