Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 56 additions & 15 deletions src/pages/Watermark/Watermark.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export function Watermark() {
fontSize: 60,
rotation: 45,
offsetX: 0,
offsetY: 0
offsetY: 0,
tile: false,
tileSpacingX: 200,
tileSpacingY: 150,
tileStagger: false,
});

const [watermarkMode, setWatermarkMode] = useState("text");
Expand Down Expand Up @@ -239,20 +243,22 @@ export function Watermark() {
<h3>Style & Position</h3>
</div>

<div className="space-y-2">
<label className="text-xs text-zinc-500 uppercase tracking-wider">Position</label>
<select
value={options.position}
onChange={(e) => updateOption("position", e.target.value)}
className="w-full h-10 px-3 bg-black border border-white/10 text-white rounded-lg outline-none"
>
<option value="center">Center</option>
<option value="top-left">Top Left</option>
<option value="top-right">Top Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="bottom-right">Bottom Right</option>
</select>
</div>
{!options.tile && (
<div className="space-y-2">
<label className="text-xs text-zinc-500 uppercase tracking-wider">Position</label>
<select
value={options.position}
onChange={(e) => updateOption("position", e.target.value)}
className="w-full h-10 px-3 bg-black border border-white/10 text-white rounded-lg outline-none"
>
<option value="center">Center</option>
<option value="top-left">Top Left</option>
<option value="top-right">Top Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="bottom-right">Bottom Right</option>
</select>
</div>
)}

<div className="grid grid-cols-2 gap-4">
{watermarkMode === "text" && (
Expand Down Expand Up @@ -301,6 +307,41 @@ export function Watermark() {
/>
</div>
</div>
<div className="pt-4 border-t border-white/10 space-y-4">
<div className="flex items-center justify-between">
<span className="text-xs text-zinc-500 uppercase tracking-wider font-medium">Tile Watermark</span>
<button onClick={() => updateOption("tile", !options.tile)}
className={`w-11 h-6 rounded-full transition-colors ${options.tile ? "bg-white" : "bg-zinc-700"}`}>
<span className={`block w-4 h-4 rounded-full bg-black mx-auto transition-transform ${options.tile ? "translate-x-2.5" : "-translate-x-2.5"}`} />
</button>
</div>

{options.tile && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-xs text-zinc-500 uppercase">Spacing X: {options.tileSpacingX}px</label>
<input type="range" min="80" max="400" value={options.tileSpacingX}
onChange={(e) => updateOption("tileSpacingX", parseInt(e.target.value))}
className="w-full h-1.5 bg-zinc-800 rounded-lg appearance-none cursor-pointer accent-white" />
</div>
<div className="space-y-2">
<label className="text-xs text-zinc-500 uppercase">Spacing Y: {options.tileSpacingY}px</label>
<input type="range" min="80" max="400" value={options.tileSpacingY}
onChange={(e) => updateOption("tileSpacingY", parseInt(e.target.value))}
className="w-full h-1.5 bg-zinc-800 rounded-lg appearance-none cursor-pointer accent-white" />
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-zinc-500 uppercase tracking-wider">Stagger Rows</span>
<button onClick={() => updateOption("tileStagger", !options.tileStagger)}
className={`w-11 h-6 rounded-full transition-colors ${options.tileStagger ? "bg-white" : "bg-zinc-700"}`}>
<span className={`block w-4 h-4 rounded-full bg-black mx-auto transition-transform ${options.tileStagger ? "translate-x-2.5" : "-translate-x-2.5"}`} />
</button>
</div>
</div>
)}
</div>
</div>
</div>
)}
Expand Down
170 changes: 107 additions & 63 deletions src/services/pdf.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export const addWatermark = async (file, watermarkText = "CONFIDENTIAL", options
offsetX = 0,
offsetY = 0,
imageScale = 0.4,
tile = false,
tileSpacingX = 200,
tileSpacingY = 150,
tileStagger = false,
} = options;

const arrayBuffer = await file.arrayBuffer();
Expand Down Expand Up @@ -128,80 +132,120 @@ export const addWatermark = async (file, watermarkText = "CONFIDENTIAL", options
const scale = targetW / imgW;
const targetH = imgH * scale;

const margin = 40;
let x, y;
if (tile) {
const cols = Math.ceil(pageW / tileSpacingX) + 1;
const rows = Math.ceil(pageH / tileSpacingY) + 1;

for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const staggerOffset = tileStagger && row % 2 !== 0 ? tileSpacingX / 2 : 0;
page.drawImage(embeddedImage, {
x: col * tileSpacingX + staggerOffset + Number(offsetX),
y: row * tileSpacingY + Number(offsetY),
width: targetW,
height: targetH,
opacity: parseFloat(opacity),
});
}
}
} else {
const margin = 40;
let x, y;

switch (position) {
case "top-left":
x = margin; y = pageH - margin - targetH; break;
case "top-right":
x = pageW - margin - targetW; y = pageH - margin - targetH; break;
case "bottom-left":
x = margin; y = margin; break;
case "bottom-right":
x = pageW - margin - targetW; y = margin; break;
case "center":
default:
x = (pageW - targetW) / 2;
y = (pageH - targetH) / 2;
break;
}

page.drawImage(embeddedImage, {
x: x + Number(offsetX),
y: y + Number(offsetY),
width: targetW,
height: targetH,
opacity: parseFloat(opacity),
})
}

})

} else {

pages.forEach((page) => {
const { width, height } = page.getSize();

if (tile) {
const cols = Math.ceil(width / tileSpacingX) + 1;
const rows = Math.ceil(height / tileSpacingY) + 1;

for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const staggerOffset = tileStagger && row % 2 !== 0 ? tileSpacingX / 2 : 0;
page.drawText(watermarkText, {
x: col * tileSpacingX + staggerOffset + Number(offsetX),
y: height - (row * tileSpacingY) + Number(offsetY),
size: fontSize,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
opacity: parseFloat(opacity),
rotate: degrees(rotation),
});
}
}
} else {
const textWidth = helveticaFont.widthOfTextAtSize(watermarkText, fontSize);
const textHeight = helveticaFont.heightAtSize(fontSize);

// Convert degrees to radians for math
const rad = (rotation * Math.PI) / 180;

let x, y;
const margin = 40;

switch (position) {
case "top-left":
x = margin; y = pageH - margin - targetH; break;
x = margin;
y = height - margin - textHeight;
break;
case "top-right":
x = pageW - margin - targetW; y = pageH - margin - targetH; break;
x = width - margin - textWidth;
y = height - margin - textHeight;
break;
case "bottom-left":
x = margin; y = margin; break;
x = margin;
y = margin;
break;
case "bottom-right":
x = pageW - margin - targetW; y = margin; break;
x = width - margin - textWidth;
y = margin;
break;
case "center":
default:
x = (pageW - targetW) / 2;
y = (pageH - targetH) / 2;
x = (width / 2) - (Math.cos(rad) * textWidth / 2) + (Math.sin(rad) * textHeight / 2);
y = (height / 2) - (Math.sin(rad) * textWidth / 2) - (Math.cos(rad) * textHeight / 2);
break;
}

page.drawImage(embeddedImage, {
x: x + Number(offsetX),
y: y + Number(offsetY),
width: targetW,
height: targetH,
opacity: parseFloat(opacity),
})
})

} else {

pages.forEach((page) => {
const { width, height } = page.getSize();
const textWidth = helveticaFont.widthOfTextAtSize(watermarkText, fontSize);
const textHeight = helveticaFont.heightAtSize(fontSize);

// Convert degrees to radians for math
const rad = (rotation * Math.PI) / 180;

let x, y;
const margin = 40;

switch (position) {
case "top-left":
x = margin;
y = height - margin - textHeight;
break;
case "top-right":
x = width - margin - textWidth;
y = height - margin - textHeight;
break;
case "bottom-left":
x = margin;
y = margin;
break;
case "bottom-right":
x = width - margin - textWidth;
y = margin;
break;
case "center":
default:
x = (width / 2) - (Math.cos(rad) * textWidth / 2) + (Math.sin(rad) * textHeight / 2);
y = (height / 2) - (Math.sin(rad) * textWidth / 2) - (Math.cos(rad) * textHeight / 2);
break;
}
page.drawText(watermarkText, {
x: x + Number(offsetX),
y: y + Number(offsetY),
size: fontSize,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
opacity: parseFloat(opacity),
rotate: degrees(rotation),
});
}

page.drawText(watermarkText, {
x: x + Number(offsetX),
y: y + Number(offsetY),
size: fontSize,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
opacity: parseFloat(opacity),
rotate: degrees(rotation),
});
});
}

Expand Down
Loading