#wp-pdf-watermark-tool{ --bg:#f6f8fb; --card:#ffffff; --line:#d9e0ea; --text:#1d2433; --muted:#667085; --accent:#2563eb; --soft:#eff5ff; --toolbar-blue:#2563eb; --toolbar-line:#b9d3ff; --toolbar-bg:#ffffff; } #wp-pdf-watermark-tool,#wp-pdf-watermark-tool *{box-sizing:border-box} #wp-pdf-watermark-tool{margin:0;font-family:Arial,Helvetica,sans-serif;background:var(--bg);color:var(--text)} #wp-pdf-watermark-tool .wrap{max-width:1400px;margin:0 auto;padding:24px} #wp-pdf-watermark-tool h1{margin:0 0 8px;font-size:30px} #wp-pdf-watermark-tool .sub{color:var(--muted);margin-bottom:18px} #wp-pdf-watermark-tool .card{background:var(--card);border:1px solid var(--line);border-radius:18px;box-shadow:0 8px 28px rgba(16,24,40,.05);overflow:hidden} #wp-pdf-watermark-tool .top-card{padding:18px;margin-bottom:18px} #wp-pdf-watermark-tool .options-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px} #wp-pdf-watermark-tool .uploader-panel{ display:flex; gap:16px; align-items:stretch; padding:16px; border:1px solid var(--line); border-radius:18px; background:linear-gradient(180deg,#ffffff,#f8fbff); margin-bottom:18px; flex-wrap:wrap; } #wp-pdf-watermark-tool .uploader-main{flex:1 1 520px;min-width:280px} #wp-pdf-watermark-tool .panel-title{font-size:18px;font-weight:800;margin:0 0 6px} #wp-pdf-watermark-tool .panel-sub{font-size:13px;color:var(--muted);margin:0 0 12px} #wp-pdf-watermark-tool .section-card{ border:1px solid var(--line); border-radius:18px; background:#fff; padding:16px; box-shadow:0 8px 24px rgba(16,24,40,.04); } #wp-pdf-watermark-tool .section-title{font-size:16px;font-weight:800;margin:0 0 6px} #wp-pdf-watermark-tool .section-sub{font-size:12px;color:var(--muted);margin:0 0 14px} #wp-pdf-watermark-tool .thumb-card{ display:flex; gap:12px; align-items:center; border:1px solid var(--line); border-radius:14px; background:#fff; padding:12px; min-height:86px; } #wp-pdf-watermark-tool .thumb-preview{ width:64px;height:64px;border-radius:12px;border:1px solid var(--line); background:#f5f7fb;display:flex;align-items:center;justify-content:center; overflow:hidden;flex:0 0 auto; } #wp-pdf-watermark-tool .thumb-preview img{max-width:100%;max-height:100%;display:block} #wp-pdf-watermark-tool .thumb-meta{min-width:0;flex:1} #wp-pdf-watermark-tool .thumb-name{font-size:14px;font-weight:800;color:var(--text);word-break:break-word} #wp-pdf-watermark-tool .thumb-sub{margin-top:4px;font-size:12px;color:var(--muted);word-break:break-word} #wp-pdf-watermark-tool .upload-progress{ display:none; margin-top:12px; border:1px solid #dbe6ff; background:#f8fbff; border-radius:14px; padding:10px 12px; } #wp-pdf-watermark-tool .upload-progress.active{display:block} #wp-pdf-watermark-tool .upload-progress-top{ display:flex;justify-content:space-between;gap:10px;align-items:center;margin-bottom:8px; font-size:12px;font-weight:700;color:#1248b5; } #wp-pdf-watermark-tool .progress-track{ width:100%;height:10px;border-radius:999px;background:#e7eefc;overflow:hidden; } #wp-pdf-watermark-tool .progress-fill{ width:0%;height:100%;border-radius:999px;background:linear-gradient(90deg,#2563eb,#60a5fa); transition:width .18s ease; } #wp-pdf-watermark-tool .inline-upload-box{display:flex;flex-direction:column;gap:10px} #wp-pdf-watermark-tool .inline-check{display:flex;gap:10px;align-items:center} #wp-pdf-watermark-tool .field{min-width:0}#wp-pdf-watermark-tool .field.full{grid-column:1/-1}#wp-pdf-watermark-tool .field.two{grid-column:span 2} #wp-pdf-watermark-tool label{display:block;font-size:13px;font-weight:700;margin-bottom:6px} #wp-pdf-watermark-tool input[type="text"],#wp-pdf-watermark-tool input[type="number"],#wp-pdf-watermark-tool select{width:100%;padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:#fff} #wp-pdf-watermark-tool input[type="color"]{width:100%;height:42px;border:1px solid var(--line);border-radius:10px;padding:4px;background:#fff} #wp-pdf-watermark-tool input[type="range"]{width:100%} #wp-pdf-watermark-tool .file-btn,#wp-pdf-watermark-tool button{appearance:none;border:none;border-radius:12px;padding:12px 14px;font-weight:700;cursor:pointer;text-align:center} #wp-pdf-watermark-tool .file-btn{display:inline-block;width:100%;background:var(--soft);color:#1248b5;border:1px solid #cfe0ff} #wp-pdf-watermark-tool button.primary{background:var(--accent);color:#fff;border:1px solid var(--accent)} #wp-pdf-watermark-tool button.secondary{background:#fff;color:var(--text);border:1px solid var(--line)} #wp-pdf-watermark-tool button.soft{background:var(--soft);color:#1248b5;border:1px solid #cfe0ff} #wp-pdf-watermark-tool input[type="file"]{display:none} #wp-pdf-watermark-tool .helper{font-size:12px;color:var(--muted);margin-top:4px} #wp-pdf-watermark-tool .check-row{display:flex;gap:10px;align-items:center;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:#fff;min-height:42px} #wp-pdf-watermark-tool .quick-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px} #wp-pdf-watermark-tool .quick-grid button{padding:10px;font-size:12px} #wp-pdf-watermark-tool .actions{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;align-items:end} #wp-pdf-watermark-tool .preview-head{padding:16px 18px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap} #wp-pdf-watermark-tool .preview-wrap{padding:0;min-height:760px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;overflow:auto;background:#eef2f7} #wp-pdf-watermark-tool .canvas-stage{position:relative;display:inline-block;flex:0 0 auto;width:auto;min-width:0;max-width:none;margin:0;background:#fff;border:1px solid var(--line);box-shadow:0 10px 30px rgba(0,0,0,.08)} #wp-pdf-watermark-tool #pdfCanvas{display:block;width:auto;max-width:none;height:auto;margin:0;position:relative;z-index:1;background:transparent}
#wp-pdf-watermark-tool .overlay{ position:absolute; transform:translate(-50%,-50%); transform-origin:center center; user-select:none; z-index:9; } #wp-pdf-watermark-tool .overlay.hidden{display:none} #wp-pdf-watermark-tool #textOverlay{z-index:9} #wp-pdf-watermark-tool #imageOverlay{z-index:20}
#wp-pdf-watermark-tool .text-visual,#wp-pdf-watermark-tool .image-visual{ position:relative; display:inline-flex; align-items:center; justify-content:center; cursor:move; } #wp-pdf-watermark-tool .text-visual span{ display:inline-block; line-height:1.1; white-space:nowrap; } #wp-pdf-watermark-tool .text-visual span[contenteditable="true"]{ cursor:move; outline:none; min-width:20px; padding:2px 6px; border-radius:6px; pointer-events:auto; } #wp-pdf-watermark-tool .text-visual.editing span[contenteditable="true"]{ cursor:text; background:rgba(255,255,255,.35); } #wp-pdf-watermark-tool .text-visual.editing span[contenteditable="true"]:focus{ outline:2px solid rgba(37,99,235,.25); } #wp-pdf-watermark-tool .image-visual img{ display:block; pointer-events:none; max-width:none; max-height:none; } #wp-pdf-watermark-tool .text-visual.has-link span[contenteditable="true"]{ text-decoration-style:dotted; text-underline-offset:3px; } #wp-pdf-watermark-tool .text-visual.has-link:hover span[contenteditable="true"]{ box-shadow:0 0 0 2px rgba(37,99,235,.14); background:rgba(37,99,235,.05); } #wp-pdf-watermark-tool .image-visual.has-link:hover .selection-ring, #wp-pdf-watermark-tool .text-visual.has-link:hover .selection-ring{ border-color:#0f766e; } #wp-pdf-watermark-tool .selection-ring{ position:absolute; inset:-8px; border:2px dashed #2563eb; border-radius:10px; pointer-events:none; } #wp-pdf-watermark-tool .handle{ position:absolute;width:26px;height:26px;border-radius:999px;border:1px solid #cfe0ff;background:#2563eb; color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;padding:0; cursor:pointer;box-shadow:0 4px 10px rgba(0,0,0,.12);z-index:2 } #wp-pdf-watermark-tool .rotate-handle{top:-18px;left:50%;transform:translateX(-50%);cursor:grab} #wp-pdf-watermark-tool .resize-handle{right:-18px;bottom:-18px;cursor:nwse-resize} #wp-pdf-watermark-tool .small{font-size:12px;color:var(--muted)} #wp-pdf-watermark-tool .pages{display:flex;gap:8px;align-items:center;flex-wrap:wrap} #wp-pdf-watermark-tool .pill{padding:8px 10px;border:1px solid var(--line);border-radius:999px;background:#fff;font-size:12px}
#wp-pdf-watermark-tool .page-jump{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; } #wp-pdf-watermark-tool .page-jump input{ width:86px; padding:9px 10px; border:1px solid var(--line); border-radius:10px; }
#wp-pdf-watermark-tool .wm-toolbar-shell{ position:absolute; display:none; z-index:40; pointer-events:auto; padding-top:38px; } #wp-pdf-watermark-tool .wm-toolbar-shell.open{display:block} #wp-pdf-watermark-tool #textToolbarShell,#wp-pdf-watermark-tool #imageToolbarShell{width:min(620px, calc(100% - 16px))} #wp-pdf-watermark-tool .wm-toolbar{ position:relative; display:flex; flex-direction:column; gap:6px; padding:6px; background:var(--toolbar-bg); border:1px solid var(--toolbar-line); border-radius:12px; box-shadow:0 10px 24px rgba(37,99,235,.14); overflow:visible; width:100%; max-width:100%; pointer-events:auto; } #wp-pdf-watermark-tool .wm-toolbar.open{display:flex} #wp-pdf-watermark-tool .wm-toolbar,#wp-pdf-watermark-tool .wm-toolbar *{color:var(--toolbar-blue) !important;opacity:1 !important} #wp-pdf-watermark-tool .wm-toolbar button,#wp-pdf-watermark-tool .wm-toolbar select,#wp-pdf-watermark-tool .wm-toolbar input[type="color"],#wp-pdf-watermark-tool .wm-toolbar input[type="number"],#wp-pdf-watermark-tool .wm-toolbar input[type="range"]{ background:var(--toolbar-bg) !important;color:var(--toolbar-blue) !important } #wp-pdf-watermark-tool .wm-toolbar .tool-row{ display:flex; align-items:center; flex-wrap:wrap; gap:6px; } #wp-pdf-watermark-tool .wm-toolbar .tool-row-extra{display:none} #wp-pdf-watermark-tool .wm-toolbar-shell.expanded .tool-row-extra{display:flex} #wp-pdf-watermark-tool #textToolbarShell.expanded #textToolbar .tool-row-extra{ display:grid; grid-template-columns:minmax(112px,1fr) minmax(150px,1.5fr) auto; align-items:center; gap:6px; } #wp-pdf-watermark-tool .wm-toolbar .tool-btn,#wp-pdf-watermark-tool .wm-toolbar .tool-select,#wp-pdf-watermark-tool .wm-toolbar .tool-number,#wp-pdf-watermark-tool .wm-toolbar .tool-color-wrap,#wp-pdf-watermark-tool .wm-toolbar .tool-range-wrap{height:34px} #wp-pdf-watermark-tool .wm-toolbar .tool-btn{ padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid var(--toolbar-line); border-radius:10px;min-width:auto;font-weight:600;white-space:nowrap } #wp-pdf-watermark-tool .wm-toolbar .tool-btn.active{background:#eff5ff !important} #wp-pdf-watermark-tool .wm-toolbar .tool-btn.danger{color:#b42318 !important} #wp-pdf-watermark-tool .wm-toolbar .tool-icon{font-size:14px;line-height:1;color:var(--toolbar-blue) !important} #wp-pdf-watermark-tool .wm-toolbar .tool-color-wrap{display:flex;align-items:center;gap:6px;padding:0 10px;border:1px solid var(--toolbar-line);border-radius:10px} #wp-pdf-watermark-tool .wm-toolbar .tool-color-wrap input[type="color"]{width:22px;height:22px;padding:0;border:none;background:transparent !important} #wp-pdf-watermark-tool .wm-toolbar .tool-select{min-width:0;padding:0 10px;border:1px solid var(--toolbar-line);outline:none;max-width:190px;border-radius:10px} #wp-pdf-watermark-tool .wm-toolbar .tool-select.tool-compact-select{min-width:104px;max-width:116px} #wp-pdf-watermark-tool .wm-toolbar .tool-number{width:64px;padding:0 8px;border:1px solid var(--toolbar-line);outline:none;border-radius:10px} #wp-pdf-watermark-tool .wm-toolbar .tool-range-wrap{display:flex;align-items:center;gap:6px;padding:0 8px;border:1px solid var(--toolbar-line);border-radius:10px} #wp-pdf-watermark-tool .wm-toolbar .tool-range-wrap input[type="range"]{width:60px;margin:0} #wp-pdf-watermark-tool .wm-toolbar .tool-btn.tool-more-btn{min-width:70px;justify-content:center} #wp-pdf-watermark-tool .wm-toolbar .tool-check{ height:34px; display:inline-flex; align-items:center; gap:6px; padding:0 10px; border:1px solid var(--toolbar-line); border-radius:10px; background:#fff; font-size:12px; font-weight:700; margin:0; white-space:nowrap; } #wp-pdf-watermark-tool .wm-toolbar .tool-check input{width:auto;margin:0} #wp-pdf-watermark-tool #toolbarBoldBtn,#wp-pdf-watermark-tool #toolbarItalicBtn,#wp-pdf-watermark-tool #toolbarUnderlineBtn{width:34px;padding:0;justify-content:center} #wp-pdf-watermark-tool #textToolbarShell.expanded #textToolbarMoreBtn, #wp-pdf-watermark-tool #imageToolbarShell.expanded #imageToolbarMoreBtn{display:none} #wp-pdf-watermark-tool .toolbar-close-btn{ position:absolute; top:0; right:6px; width:32px; height:32px; border-radius:999px; border:1px solid #d5e3ff; background:#fff; color:#1248b5; box-shadow:0 8px 18px rgba(16,24,40,.12); font-size:18px; font-weight:700; line-height:1; padding:0; z-index:2; } #wp-pdf-watermark-tool #toolbarTextLayerBtn,#wp-pdf-watermark-tool #toolbarImageLayerBtn{min-width:102px;justify-content:center} #wp-pdf-watermark-tool #toolbarLanguage{min-width:112px;max-width:140px} #wp-pdf-watermark-tool #toolbarFont{min-width:150px;max-width:190px} #wp-pdf-watermark-tool #toolbarLogoPageRange{min-width:132px;max-width:172px} #wp-pdf-watermark-tool #toolbarCenterBtn,#wp-pdf-watermark-tool #toolbarFontStylePreview,#wp-pdf-watermark-tool #toolbarRemoveTextBtn{display:none !important} #wp-pdf-watermark-tool #imageToolbarShell{max-width:calc(100% - 16px)} #wp-pdf-watermark-tool #imageToolbar{ width:100%; flex-direction:column; align-items:stretch; flex-wrap:nowrap; } #wp-pdf-watermark-tool #imageToolbar .tool-row{ display:flex; flex-wrap:wrap; align-items:center; } #wp-pdf-watermark-tool #imageToolbar .tool-row-extra{display:none;flex-wrap:wrap;align-items:center} #wp-pdf-watermark-tool #imageToolbarShell.expanded #imageToolbar .tool-row-extra{display:flex}
@media (max-width:760px){ #wp-pdf-watermark-tool #textToolbarShell.expanded #textToolbar .tool-row-extra{ grid-template-columns:repeat(2,minmax(0,1fr)); } #wp-pdf-watermark-tool #toolbarTextLayerBtn,#wp-pdf-watermark-tool #toolbarImageLayerBtn,#wp-pdf-watermark-tool .wm-toolbar .tool-btn.tool-more-btn{min-width:0} }
#wp-pdf-watermark-tool /* ── Mobile: text preview overlay UX ── */ @media (max-width:600px){ /* Scale down the watermark text in preview so it fits on small screens */ #textPreview{ max-width:min(160px, 45vw); overflow:hidden; text-overflow:ellipsis; }
/* Toolbar shell: anchor to bottom of viewport on mobile for thumb reach */ .wm-toolbar-shell{ position:fixed !important; left:0 !important; right:0 !important; bottom:0 !important; top:auto !important; width:100% !important; max-width:100% !important; border-radius:18px 18px 0 0 !important; box-shadow:0 -4px 24px rgba(37,99,235,.18) !important; z-index:9999 !important; } .wm-toolbar{ padding:10px 12px 18px !important; border-radius:18px 18px 0 0 !important; border-left:none !important; border-right:none !important; border-bottom:none !important; overflow-x:auto !important; -webkit-overflow-scrolling:touch; } .wm-toolbar .tool-row{ flex-wrap:nowrap !important; overflow-x:auto; -webkit-overflow-scrolling:touch; padding-bottom:4px; } /* Larger touch targets on mobile */ .wm-toolbar .tool-btn, .wm-toolbar .tool-select, .wm-toolbar .tool-number, .wm-toolbar .tool-color-wrap, .wm-toolbar .tool-range-wrap{ height:42px !important; font-size:14px; } /* Handles bigger for fingers */ .handle.rotate-handle,.handle.resize-handle{ width:32px !important; height:32px !important; font-size:18px !important; } /* Tap-to-edit hint badge on the text overlay */ #tapEditHint{ display:flex; align-items:center; gap:4px; position:absolute; bottom:-28px; left:50%; transform:translateX(-50%); background:rgba(37,99,235,.9); color:#fff; font-size:11px; font-weight:600; padding:3px 10px; border-radius:999px; white-space:nowrap; pointer-events:none; z-index:30; } } @media (min-width:601px){ #wp-pdf-watermark-tool #tapEditHint{display:none} }
#wp-pdf-watermark-tool .slider-row{ display:flex; gap:10px; align-items:center; } #wp-pdf-watermark-tool .slider-row input[type="range"]{ flex:1; } #wp-pdf-watermark-tool .slider-tag{ min-width:48px; text-align:center; padding:6px 8px; border-radius:999px; background:#f3f6fb; border:1px solid var(--line); font-size:12px; font-weight:700; color:#1248b5; }
#wp-pdf-watermark-tool .repeat-presets{ display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:10px; margin-top:8px; } #wp-pdf-watermark-tool .repeat-preset{ border:1px solid var(--line); background:#fff; border-radius:14px; padding:10px; text-align:left; display:flex; flex-direction:column; gap:8px; transition:.18s ease; } #wp-pdf-watermark-tool .repeat-preset:hover{border-color:#8eb6ff;box-shadow:0 8px 18px rgba(37,99,235,.08);transform:translateY(-1px)} #wp-pdf-watermark-tool .repeat-preset.active{border-color:var(--accent);background:#f7faff;box-shadow:0 0 0 2px rgba(37,99,235,.08)} #wp-pdf-watermark-tool .repeat-preset strong{font-size:12px;line-height:1.2} #wp-pdf-watermark-tool .repeat-preset span{font-size:11px;color:var(--muted);line-height:1.25} #wp-pdf-watermark-tool .preset-mini{ height:62px; border-radius:10px; border:1px solid #d9e3f5; background:linear-gradient(180deg,#ffffff,#f7faff); position:relative; overflow:hidden; } #wp-pdf-watermark-tool .preset-mini::before,#wp-pdf-watermark-tool .preset-mini::after{ content:""; position:absolute; inset:0; opacity:.9; } #wp-pdf-watermark-tool .preset-mini.grid::before{ background: radial-gradient(circle at 12px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 31px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 50px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 12px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 31px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 50px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 12px 50px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 31px 50px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 50px 50px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.dense::before{ background:repeating-radial-gradient(circle at 0 0, rgba(37,99,235,.9) 0 1.6px, transparent 2px 12px); transform:translate(11px,11px); } #wp-pdf-watermark-tool .preset-mini.wide::before{ background: radial-gradient(circle at 10px 10px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 42px 10px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 10px 42px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 42px 42px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.diagonal::before{ background:repeating-linear-gradient(135deg, transparent 0 12px, rgba(37,99,235,.22) 12px 18px, transparent 18px 30px); } #wp-pdf-watermark-tool .preset-mini.diagonal::after{ background: radial-gradient(circle at 8px 50px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 22px 36px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 36px 22px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 50px 8px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.checker::before{ background: radial-gradient(circle at 12px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 36px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 24px 30px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 48px 30px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 12px 48px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 36px 48px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.ripple::before{ background: radial-gradient(circle at center, rgba(37,99,235,.18) 0 8px, transparent 9px 18px, rgba(37,99,235,.15) 19px 24px, transparent 25px); } #wp-pdf-watermark-tool .preset-mini.ripple::after{ background: radial-gradient(circle at center, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.stair::before{ background: radial-gradient(circle at 10px 50px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 22px 38px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 34px 26px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 46px 14px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.brick::before{ background: radial-gradient(circle at 8px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 28px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 48px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 18px 30px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 38px 30px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 8px 48px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 28px 48px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 48px 48px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.cross::before{ background: repeating-linear-gradient(45deg, transparent 0 8px, rgba(37,99,235,.18) 8px 10px, transparent 10px 18px), repeating-linear-gradient(-45deg, transparent 0 8px, rgba(37,99,235,.18) 8px 10px, transparent 10px 18px); } #wp-pdf-watermark-tool .preset-mini.cross::after{ background: radial-gradient(circle at 12px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 36px 36px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 12px 36px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 36px 12px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.diamond::before{ background: radial-gradient(circle at 31px 8px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 8px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 31px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 54px 31px, rgba(37,99,235,.85) 0 2px, transparent 2.5px), radial-gradient(circle at 31px 54px, rgba(37,99,235,.85) 0 2px, transparent 2.5px); } #wp-pdf-watermark-tool .preset-mini.diamond::after{ background: repeating-linear-gradient(45deg, transparent 0 14px, rgba(37,99,235,.10) 14px 16px, transparent 16px 30px), repeating-linear-gradient(-45deg, transparent 0 14px, rgba(37,99,235,.10) 14px 16px, transparent 16px 30px); }
#wp-pdf-watermark-tool .repeat-shortcuts{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px} #wp-pdf-watermark-tool .repeat-chip{ padding:8px 10px; border-radius:999px; border:1px solid #cfe0ff; background:#eff5ff; color:#1248b5; font-size:12px; font-weight:700; cursor:pointer; }
#wp-pdf-watermark-tool .pdf-drop-area{ border:2px dashed #cfe0ff; background:#f8fbff; border-radius:14px; padding:14px; text-align:center; transition:.2s ease; } #wp-pdf-watermark-tool .pdf-drop-area.dragover{ border-color:#2563eb; background:#eff5ff; box-shadow:0 0 0 4px rgba(37,99,235,.08); } #wp-pdf-watermark-tool .pdf-file-name{margin-top:10px;font-weight:700;color:#2563eb;word-break:break-word;display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap} #wp-pdf-watermark-tool .pdf-file-name .pdf-badge{display:inline-flex;align-items:center;justify-content:center;width:28px;height:32px;border-radius:6px;background:#e53935;color:#fff;font-size:10px;font-weight:800;line-height:1;letter-spacing:.4px;box-shadow:0 4px 10px rgba(229,57,53,.18);position:relative;text-transform:uppercase} #wp-pdf-watermark-tool .pdf-file-name .pdf-badge::before{content:"";position:absolute;top:0;right:0;border-top:10px solid #ffcdd2;border-left:10px solid transparent;width:0;height:0;border-top-right-radius:6px} #wp-pdf-watermark-tool .pdf-file-meta{margin-top:4px;color:#667085} #wp-pdf-watermark-tool .pdf-drop-text{margin-top:8px;font-size:12px;color:#667085} #wp-pdf-watermark-tool .pdf-remove-btn{margin-top:12px;min-width:140px}
#wp-pdf-watermark-tool .repeat-image-layer{ position:absolute; inset:0; pointer-events:none; overflow:hidden; } #wp-pdf-watermark-tool .repeat-text-layer{ position:absolute; inset:0; pointer-events:none; overflow:hidden; z-index:15; } #wp-pdf-watermark-tool .repeat-image-layer.hidden,#wp-pdf-watermark-tool .repeat-text-layer.hidden{display:none} #wp-pdf-watermark-tool .repeat-image-item{ position:absolute; transform-origin:center center; pointer-events:none; user-select:none; -webkit-user-drag:none; }
#wp-pdf-watermark-tool .font-style-preview{ display:inline-flex; align-items:center; justify-content:center; min-width:28px; height:28px; padding:0 8px; border-radius:999px; border:1px solid var(--toolbar-line); font-size:11px; font-weight:700; white-space:nowrap; background:#eff5ff; } #wp-pdf-watermark-tool .btn-loading{ display:inline-flex; align-items:center; justify-content:center; gap:8px; } #wp-pdf-watermark-tool .spinner{ width:14px; height:14px; border:2px solid rgba(255,255,255,.35); border-top-color:#fff; border-radius:50%; animation:spin .8s linear infinite; } #wp-pdf-watermark-tool .secondary .spinner{ border-color:rgba(37,99,235,.22); border-top-color:#2563eb; } @keyframes spin{ to{transform:rotate(360deg)} } #wp-pdf-watermark-tool .hidden-native{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; } #wp-pdf-watermark-tool .font-panel{ display:flex; flex-direction:column; gap:10px; padding:12px; border:1px solid var(--line); border-radius:12px; background:linear-gradient(180deg,#ffffff,#f8fbff); } #wp-pdf-watermark-tool .lang-switch{ display:flex; gap:8px; flex-wrap:wrap; } #wp-pdf-watermark-tool .lang-dropdown{ min-width:220px; } #wp-pdf-watermark-tool .lang-chip{ appearance:none; border:1px solid #cfe0ff; background:#fff; color:#1248b5; border-radius:999px; padding:9px 14px; font-size:12px; font-weight:700; cursor:pointer; transition:.18s ease; } #wp-pdf-watermark-tool .lang-chip.active{ background:#2563eb; border-color:#2563eb; color:#fff; box-shadow:0 8px 18px rgba(37,99,235,.18); } #wp-pdf-watermark-tool .lang-chip .lang-note{ opacity:.7; font-weight:600; margin-inline-start:4px; } #wp-pdf-watermark-tool .font-meta{ display:flex; justify-content:space-between; gap:10px; align-items:center; flex-wrap:wrap; } #wp-pdf-watermark-tool .font-context{ font-size:12px; color:var(--muted); } #wp-pdf-watermark-tool .font-sample{ font-size:13px; color:#1248b5; background:#eff5ff; border:1px solid #cfe0ff; padding:6px 10px; border-radius:999px; white-space:nowrap; } #wp-pdf-watermark-tool .font-panel.rtl #fontFamily{ direction:ltr; text-align:left; } #wp-pdf-watermark-tool .font-panel.rtl #fontFamily option{ direction:ltr; text-align:left; } #wp-pdf-watermark-tool .font-panel.rtl .font-sample{ direction:rtl; text-align:right; }
#wp-pdf-watermark-tool /* ── Custom Repeat Style Picker ── */ .repeat-style-picker{ position:relative; display:inline-flex; align-items:center; height:34px; } #wp-pdf-watermark-tool .repeat-style-trigger{ display:inline-flex; align-items:center; gap:6px; height:34px; padding:0 8px; border:1px solid var(--toolbar-line); border-radius:10px; background:var(--toolbar-bg) !important; color:var(--toolbar-blue) !important; font-size:12px; font-weight:600; cursor:pointer; white-space:nowrap; min-width:110px; max-width:140px; } #wp-pdf-watermark-tool .repeat-style-trigger .rs-mini{ width:22px;height:18px;border-radius:4px;border:1px solid #d9e3f5; background:linear-gradient(180deg,#fff,#f7faff); flex:0 0 auto;position:relative;overflow:hidden; } #wp-pdf-watermark-tool .repeat-style-trigger .rs-label{ flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; } #wp-pdf-watermark-tool .repeat-style-trigger .rs-arrow{font-size:10px;opacity:.7} #wp-pdf-watermark-tool .repeat-style-dropdown{ position:absolute; top:calc(100% + 4px); left:0; z-index:9999; background:#fff; border:1px solid #cfe0ff; border-radius:12px; box-shadow:0 12px 28px rgba(37,99,235,.16); padding:6px; display:none; flex-direction:column; gap:3px; min-width:188px; } #wp-pdf-watermark-tool .repeat-style-dropdown.open{display:flex} #wp-pdf-watermark-tool .rs-option{ display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:8px; cursor:pointer; transition:.14s ease; border:1px solid transparent; } #wp-pdf-watermark-tool .rs-option:hover{background:#eff5ff;border-color:#cfe0ff} #wp-pdf-watermark-tool .rs-option.active{background:#eff5ff;border-color:var(--accent)} #wp-pdf-watermark-tool .rs-option .rs-demo{ width:44px;height:34px;border-radius:6px;border:1px solid #d9e3f5; background:linear-gradient(180deg,#fff,#f7faff); flex:0 0 auto;position:relative;overflow:hidden; } #wp-pdf-watermark-tool /* reuse preset-mini patterns on rs-demo */ .rs-demo.grid::before,#wp-pdf-watermark-tool .rs-demo.dense::before,#wp-pdf-watermark-tool .rs-demo.wide::before, #wp-pdf-watermark-tool .rs-demo.diagonal::before,#wp-pdf-watermark-tool .rs-demo.diagonal::after, #wp-pdf-watermark-tool .rs-demo.checker::before,#wp-pdf-watermark-tool .rs-demo.ripple::before,#wp-pdf-watermark-tool .rs-demo.ripple::after, #wp-pdf-watermark-tool .rs-demo.stair::before{ content:"";position:absolute;inset:0;opacity:.9; } #wp-pdf-watermark-tool .rs-demo.grid::before{ background: radial-gradient(circle at 8px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 22px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 36px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 8px 20px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 22px 20px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 36px 20px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 8px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 22px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 36px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.dense::before{ background:repeating-radial-gradient(circle at 0 0,rgba(37,99,235,.9) 0 1.2px,transparent 1.5px 9px); transform:translate(8px,8px); } #wp-pdf-watermark-tool .rs-demo.wide::before{ background: radial-gradient(circle at 8px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 30px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 8px 26px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 30px 26px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.diagonal::before{ background:repeating-linear-gradient(135deg,transparent 0 9px,rgba(37,99,235,.2) 9px 13px,transparent 13px 22px); } #wp-pdf-watermark-tool .rs-demo.diagonal::after{ background: radial-gradient(circle at 6px 34px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 17px 23px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 28px 12px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 39px 2px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.checker::before{ background: radial-gradient(circle at 9px 9px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 27px 9px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 18px 22px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 36px 22px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 9px 34px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 27px 34px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.ripple::before{ background: radial-gradient(circle at center,rgba(37,99,235,.16) 0 6px,transparent 7px 14px,rgba(37,99,235,.12) 15px 18px,transparent 19px); } #wp-pdf-watermark-tool .rs-demo.ripple::after{ background:radial-gradient(circle at center,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.stair::before{ background: radial-gradient(circle at 7px 30px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 17px 20px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 27px 11px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 37px 2px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.brick::before{ background: radial-gradient(circle at 6px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 20px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 34px 8px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 13px 21px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 27px 21px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 41px 21px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 6px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 20px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 34px 32px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.cross::before{ background: repeating-linear-gradient(45deg,transparent 0 6px,rgba(37,99,235,.18) 6px 8px,transparent 8px 14px), repeating-linear-gradient(-45deg,transparent 0 6px,rgba(37,99,235,.18) 6px 8px,transparent 8px 14px); } #wp-pdf-watermark-tool .rs-demo.cross::after{ background: radial-gradient(circle at 9px 9px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 28px 28px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 9px 28px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 28px 9px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.diamond::before{ background: radial-gradient(circle at 22px 6px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 6px 22px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 22px 22px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 38px 22px,rgba(37,99,235,.85) 0 1.5px,transparent 2px), radial-gradient(circle at 22px 38px,rgba(37,99,235,.85) 0 1.5px,transparent 2px); } #wp-pdf-watermark-tool .rs-demo.diamond::after{ background: repeating-linear-gradient(45deg,transparent 0 10px,rgba(37,99,235,.10) 10px 12px,transparent 12px 22px), repeating-linear-gradient(-45deg,transparent 0 10px,rgba(37,99,235,.10) 10px 12px,transparent 12px 22px); } #wp-pdf-watermark-tool .rs-option-name{font-size:12px;font-weight:700;color:#1248b5} #wp-pdf-watermark-tool .rs-option-desc{font-size:10px;color:#667085} #wp-pdf-watermark-tool /* toolbar fit improvements */ .wm-toolbar .tool-row{flex-wrap:wrap;gap:5px} #wp-pdf-watermark-tool #imageToolbar .tool-row{flex-wrap:wrap} #wp-pdf-watermark-tool .repeat-style-picker{flex-shrink:0} #wp-pdf-watermark-tool #toolbarFont, #wp-pdf-watermark-tool #toolbarFont option{ direction:ltr; text-align:left; } @media (max-width:1080px){ #wp-pdf-watermark-tool .options-grid{grid-template-columns:repeat(2,1fr)} #wp-pdf-watermark-tool .actions{grid-template-columns:repeat(2,1fr)} #wp-pdf-watermark-tool .quick-grid{grid-template-columns:repeat(2,1fr)} #wp-pdf-watermark-tool .repeat-presets{grid-template-columns:repeat(3,minmax(0,1fr))} #wp-pdf-watermark-tool .uploader-panel{flex-direction:column} } @media (max-width:720px){ #wp-pdf-watermark-tool .options-grid,#wp-pdf-watermark-tool .actions,#wp-pdf-watermark-tool .quick-grid{grid-template-columns:1fr} #wp-pdf-watermark-tool .field.two{grid-column:auto} #wp-pdf-watermark-tool .preview-wrap{min-height:460px} #wp-pdf-watermark-tool .repeat-presets{grid-template-columns:repeat(2,minmax(0,1fr))} #wp-pdf-watermark-tool .thumb-card{align-items:flex-start} #wp-pdf-watermark-tool .page-jump input{width:70px} }
Advanced PDF Watermark Tool
0%
const pdfjsLib = window.pdfjsLib || window['pdfjs-dist/build/pdf']; if (!window.PDFLib || !pdfjsLib) { alert('PDF tools could not load. Please check your internet connection and reload this page.'); throw new Error('PDF libraries failed to load.'); } pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
const { PDFDocument, PDFName, PDFArray, PDFString } = window.PDFLib; const LANGUAGE_OPTIONS = { english: { label: 'English', sample: 'Sample: Confidential', rtl: false }, arabic: { label: 'Arabic', sample: '\u0645\u062b\u0627\u0644: \u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629', rtl: true }, hebrew: { label: 'Hebrew', sample: '\u05d3\u05d5\u05d2\u05de\u05d4: \u05e1\u05d5\u05d3\u05d9', rtl: true }, hindi: { label: 'Hindi', sample: '\u0909\u0926\u093e\u0939\u0930\u0923: \u0917\u094b\u092a\u0928\u0940\u092f', rtl: false }, urdu: { label: 'Urdu', sample: '\u0645\u062b\u0627\u0644: \u062e\u0641\u06cc\u06c1', rtl: true }, persian: { label: 'Persian', sample: '\u0646\u0645\u0648\u0646\u0647: \u0645\u062d\u0631\u0645\u0627\u0646\u0647', rtl: true }, russian: { label: 'Russian', sample: '\u041f\u0440\u0438\u043c\u0435\u0440: \u0421\u0435\u043a\u0440\u0435\u0442\u043d\u043e', rtl: false }, japanese: { label: 'Japanese', sample: '\u4f8b: \u6a5f\u5bc6', rtl: false }, chinese: { label: 'Chinese', sample: '\u793a\u4f8b\uff1a\u673a\u5bc6', rtl: false }, german: { label: 'German', sample: 'Beispiel: Vertraulich', rtl: false }, spanish: { label: 'Spanish', sample: 'Ejemplo: Confidencial', rtl: false } }; const ARABIC_SCRIPT_FONT_CHOICES = [ { value: 'Tajawal', label: 'Tajawal - Clean Arabic Sans', stack: '"Tajawal", sans-serif' }, { value: 'Amiri', label: 'Amiri - Traditional Arabic Serif', stack: '"Amiri", serif' }, { value: 'Almarai', label: 'Almarai - Balanced Arabic Sans', stack: '"Almarai", sans-serif' }, { value: 'Noto Naskh Arabic', label: 'Noto Naskh Arabic - Book Style', stack: '"Noto Naskh Arabic", serif' }, { value: 'Cairo', label: 'Cairo - Modern Arabic Sans', stack: '"Cairo", sans-serif' }, { value: 'Changa', label: 'Changa - Display Arabic', stack: '"Changa", sans-serif' }, { value: 'Reem Kufi', label: 'Reem Kufi - Kufi Style', stack: '"Reem Kufi", sans-serif' }, { value: 'Scheherazade New', label: 'Scheherazade New - Classical Arabic', stack: '"Scheherazade New", serif' } ]; const FONT_CHOICES = { english: [ { value: 'Arial', label: 'Arial - Standard Sans', stack: 'Arial, Helvetica, sans-serif' }, { value: 'Helvetica', label: 'Helvetica - Classic Sans', stack: 'Helvetica, Arial, sans-serif' }, { value: 'Roboto', label: 'Roboto - Modern Sans', stack: '"Roboto", Arial, sans-serif' }, { value: 'Inter', label: 'Inter - UI Sans', stack: '"Inter", Arial, sans-serif' }, { value: 'DM Sans', label: 'DM Sans - Friendly Sans', stack: '"DM Sans", Arial, sans-serif' }, { value: 'Nunito', label: 'Nunito - Rounded Sans', stack: '"Nunito", Arial, sans-serif' }, { value: 'Comic Neue', label: 'Comic Neue - Comic Sans Style', stack: '"Comic Neue", "Comic Sans MS", cursive' }, { value: 'Georgia', label: 'Georgia - Editorial Serif', stack: 'Georgia, "Times New Roman", serif' }, { value: 'Lora', label: 'Lora - Elegant Serif', stack: '"Lora", Georgia, serif' }, { value: 'Playfair Display', label: 'Playfair Display - Premium Serif', stack: '"Playfair Display", Georgia, serif' }, { value: 'Merriweather', label: 'Merriweather - Reading Serif', stack: '"Merriweather", Georgia, serif' }, { value: 'Times New Roman', label: 'Times New Roman - Classic Serif', stack: '"Times New Roman", Times, serif' }, { value: 'Courier New', label: 'Courier New - Monospace', stack: '"Courier New", Courier, monospace' }, { value: 'Verdana', label: 'Verdana - Wide Sans', stack: 'Verdana, Arial, sans-serif' } ], arabic: ARABIC_SCRIPT_FONT_CHOICES, hebrew: [ { value: 'Heebo', label: 'Heebo - Modern Hebrew Sans', stack: '"Heebo", sans-serif' }, { value: 'Assistant', label: 'Assistant - UI Hebrew Sans', stack: '"Assistant", sans-serif' }, { value: 'Rubik', label: 'Rubik - Rounded Hebrew Sans', stack: '"Rubik", sans-serif' }, { value: 'Noto Sans Hebrew', label: 'Noto Sans Hebrew - Neutral Sans', stack: '"Noto Sans Hebrew", sans-serif' } ], hindi: [ { value: 'Hind', label: 'Hind - Clean Devanagari Sans', stack: '"Hind", sans-serif' }, { value: 'Noto Sans Devanagari', label: 'Noto Sans Devanagari - Balanced Sans', stack: '"Noto Sans Devanagari", sans-serif' }, { value: 'Teko', label: 'Teko - Display Devanagari', stack: '"Teko", sans-serif' } ], urdu: [ { value: 'Noto Nastaliq Urdu', label: 'Noto Nastaliq Urdu - Classic Nastaliq', stack: '"Noto Nastaliq Urdu", serif' }, { value: 'Lateef', label: 'Lateef - Traditional Urdu Script', stack: '"Lateef", serif' }, { value: 'Noto Naskh Arabic', label: 'Noto Naskh Arabic - Clean Naskh', stack: '"Noto Naskh Arabic", serif' }, { value: 'Tajawal', label: 'Tajawal - Modern Sans', stack: '"Tajawal", sans-serif' } ], persian: [ { value: 'Vazirmatn', label: 'Vazirmatn - Modern Persian Sans', stack: '"Vazirmatn", sans-serif' }, { value: 'Markazi Text', label: 'Markazi Text - Persian Serif', stack: '"Markazi Text", serif' }, { value: 'Mirza', label: 'Mirza - Traditional Persian Display', stack: '"Mirza", serif' }, { value: 'Amiri', label: 'Amiri - Elegant Persian/Arabic Serif', stack: '"Amiri", serif' } ], russian: [ { value: 'Roboto', label: 'Roboto - Modern Cyrillic Sans', stack: '"Roboto", Arial, sans-serif' }, { value: 'Rubik', label: 'Rubik - Rounded Cyrillic Sans', stack: '"Rubik", Arial, sans-serif' }, { value: 'PT Sans', label: 'PT Sans - Classic Russian Sans', stack: '"PT Sans", Arial, sans-serif' }, { value: 'PT Serif', label: 'PT Serif - Editorial Cyrillic Serif', stack: '"PT Serif", Georgia, serif' }, { value: 'Noto Sans', label: 'Noto Sans - Neutral Cyrillic Sans', stack: '"Noto Sans", Arial, sans-serif' } ], japanese: [ { value: 'Noto Sans JP', label: 'Noto Sans JP - Balanced Japanese Sans', stack: '"Noto Sans JP", sans-serif' }, { value: 'M PLUS 1p', label: 'M PLUS 1p - UI Japanese Sans', stack: '"M PLUS 1p", sans-serif' }, { value: 'Zen Kaku Gothic New', label: 'Zen Kaku Gothic New - Clear Gothic', stack: '"Zen Kaku Gothic New", sans-serif' }, { value: 'Kosugi Maru', label: 'Kosugi Maru - Rounded Japanese', stack: '"Kosugi Maru", sans-serif' }, { value: 'Sawarabi Mincho', label: 'Sawarabi Mincho - Japanese Serif', stack: '"Sawarabi Mincho", serif' } ], chinese: [ { value: 'Noto Sans SC', label: 'Noto Sans SC - Modern Simplified Chinese', stack: '"Noto Sans SC", sans-serif' }, { value: 'ZCOOL QingKe HuangYou', label: 'ZCOOL QingKe HuangYou - Display Chinese', stack: '"ZCOOL QingKe HuangYou", sans-serif' }, { value: 'ZCOOL XiaoWei', label: 'ZCOOL XiaoWei - Elegant Chinese Serif', stack: '"ZCOOL XiaoWei", serif' }, { value: 'Ma Shan Zheng', label: 'Ma Shan Zheng - Brush Chinese', stack: '"Ma Shan Zheng", cursive' } ], german: [ { value: 'Inter', label: 'Inter - Clean Modern Sans', stack: '"Inter", Arial, sans-serif' }, { value: 'Montserrat', label: 'Montserrat - Popular Display Sans', stack: '"Montserrat", Arial, sans-serif' }, { value: 'Source Sans 3', label: 'Source Sans 3 - Readable UI Sans', stack: '"Source Sans 3", Arial, sans-serif' }, { value: 'Merriweather', label: 'Merriweather - Editorial Serif', stack: '"Merriweather", Georgia, serif' }, { value: 'Poppins', label: 'Poppins - Geometric Sans', stack: '"Poppins", Arial, sans-serif' } ], spanish: [ { value: 'Roboto', label: 'Roboto - Modern Sans', stack: '"Roboto", Arial, sans-serif' }, { value: 'Nunito', label: 'Nunito - Friendly Rounded Sans', stack: '"Nunito", Arial, sans-serif' }, { value: 'Montserrat', label: 'Montserrat - Popular Headline Sans', stack: '"Montserrat", Arial, sans-serif' }, { value: 'Lora', label: 'Lora - Elegant Serif', stack: '"Lora", Georgia, serif' }, { value: 'Poppins', label: 'Poppins - Clean Geometric Sans', stack: '"Poppins", Arial, sans-serif' } ] };
const pdfInput = byId('pdfInput'); const wmImageInput = byId('wmImageInput');
const pageRangeMode = byId('pageRangeMode'); const pageRange = byId('pageRange'); const pageRangeHelper = byId('pageRangeHelper');
const imagePageRangeMode = byId('imagePageRangeMode'); const imagePageRange = byId('imagePageRange'); const imagePageRangeHelper = byId('imagePageRangeHelper');
const showTextWatermark = byId('showTextWatermark'); const showLogoWatermark = byId('showLogoWatermark');
const textBold = byId('textBold'); const textItalic = byId('textItalic'); const textUnderline = byId('textUnderline'); const repeatWatermark = byId('repeatWatermark'); const repeatLogoWatermark = byId('repeatLogoWatermark'); const repeatGap = byId('repeatGap'); const wmText = byId('wmText'); const enableTextLink = byId('enableTextLink'); const wmTextLink = byId('wmTextLink'); const enableImageLink = byId('enableImageLink'); const wmImageLink = byId('wmImageLink'); const fontSize = byId('fontSize'); const fontSizeRange = byId('fontSizeRange'); const fontSizeTag = byId('fontSizeTag'); const languageSelect = byId('languageSelect'); const languageSwitch = byId('languageSwitch'); const extraLanguageSelect = byId('extraLanguageSelect'); const fontPanel = watermarkToolRoot.querySelector('.font-panel'); const fontFamily = byId('fontFamily'); const fontContext = byId('fontContext'); const fontSample = byId('fontSample'); const fontColor = byId('fontColor'); const textRotation = byId('textRotation'); const textLayerMode = byId('textLayerMode'); const imageWidth = byId('imageWidth'); const imageWidthRange = byId('imageWidthRange'); const imageWidthTag = byId('imageWidthTag'); const imageRotation = byId('imageRotation'); const textOpacity = byId('textOpacity'); const textOpacityLabel = byId('textOpacityLabel'); const imageOpacity = byId('imageOpacity'); const imageOpacityLabel = byId('imageOpacityLabel'); const imageLayerMode = byId('imageLayerMode'); const generateBtn = byId('generateBtn'); const downloadBtn = byId('downloadBtn'); const resetTextPosBtn = byId('resetTextPosBtn'); const resetImagePosBtn = byId('resetImagePosBtn'); const actionStatus = byId('actionStatus');
const pdfLabel = byId('pdfLabel'); const pdfFileName = byId('pdfFileName'); const pdfFileMeta = byId('pdfFileMeta'); const pdfDropArea = byId('pdfDropArea'); const pdfDropText = byId('pdfDropText'); const removePdfBtn = byId('removePdfBtn'); const pdfUploadProgress = byId('pdfUploadProgress'); const pdfUploadStatus = byId('pdfUploadStatus'); const pdfUploadPercent = byId('pdfUploadPercent'); const pdfUploadBar = byId('pdfUploadBar');
const logoThumbPreview = byId('logoThumbPreview'); const logoFileName = byId('logoFileName'); const logoFileMeta = byId('logoFileMeta'); const removeLogoBtn = byId('removeLogoBtn');
const pdfCanvas = byId('pdfCanvas'); const stage = byId('stage'); const previewStatus = byId('previewStatus'); const pageControls = byId('pageControls'); const prevPage = byId('prevPage'); const nextPage = byId('nextPage'); const pageNum = byId('pageNum'); const pageCount = byId('pageCount'); const pageJumpInput = byId('pageJumpInput'); const pageJumpBtn = byId('pageJumpBtn');
const textOverlay = byId('textOverlay'); const textVisual = byId('textVisual'); const textPreview = byId('textPreview'); const textRotateHandle = byId('textRotateHandle'); const textResizeHandle = byId('textResizeHandle');
const textToolbarShell = byId('textToolbarShell'); const textToolbar = byId('textToolbar'); const toolbarBoldBtn = byId('toolbarBoldBtn'); const toolbarItalicBtn = byId('toolbarItalicBtn'); const toolbarUnderlineBtn = byId('toolbarUnderlineBtn'); const toolbarColor = byId('toolbarColor'); const toolbarOpacity = byId('toolbarOpacity'); const toolbarLanguage = byId('toolbarLanguage'); const toolbarFont = byId('toolbarFont'); const toolbarFontStylePreview = byId('toolbarFontStylePreview'); const toolbarSize = byId('toolbarSize'); const toolbarSizeRange = byId('toolbarSizeRange'); const toolbarCenterBtn = byId('toolbarCenterBtn'); const toolbarTextLayerBtn = byId('toolbarTextLayerBtn'); const textLayerText = byId('textLayerText'); const textToolbarMoreBtn = byId('textToolbarMoreBtn'); const toolbarRemoveTextBtn = byId('toolbarRemoveTextBtn'); const textToolbarCloseBtn = byId('textToolbarCloseBtn'); let textToolbarLessBtn = byId('textToolbarLessBtn');
if (textToolbarMoreBtn) { textToolbarMoreBtn.querySelector('.tool-icon').innerHTML = '▾'; } if (!textToolbarLessBtn && toolbarRemoveTextBtn) { toolbarRemoveTextBtn.insertAdjacentHTML('afterend', ''); textToolbarLessBtn = byId('textToolbarLessBtn'); } if (textToolbarMoreBtn) { [ toolbarSize, toolbarSizeRange?.closest('.tool-range-wrap'), toolbarOpacity?.closest('.tool-range-wrap'), toolbarTextLayerBtn ].filter(Boolean).forEach(control => { textToolbarMoreBtn.parentElement.insertBefore(control, textToolbarMoreBtn); }); }
const imageOverlay = byId('imageOverlay'); const imageVisual = byId('imageVisual'); const imagePreview = byId('imagePreview'); const imageRotateHandle = byId('imageRotateHandle'); const imageResizeHandle = byId('imageResizeHandle'); const repeatImageLayer = byId('repeatImageLayer'); const repeatTextLayer = byId('repeatTextLayer');
const imageToolbarShell = byId('imageToolbarShell'); const imageToolbar = byId('imageToolbar'); const toolbarImageLayerBtn = byId('toolbarImageLayerBtn'); const imageLayerText = byId('imageLayerText'); const toolbarImageOpacity = byId('toolbarImageOpacity'); const toolbarImageSize = byId('toolbarImageSize'); const toolbarImageSizeRange = byId('toolbarImageSizeRange'); const imageToolbarMoreBtn = byId('imageToolbarMoreBtn'); const toolbarRemoveImageBtn = byId('toolbarRemoveImageBtn'); const imageToolbarCloseBtn = byId('imageToolbarCloseBtn'); let imageToolbarLessBtn = byId('imageToolbarLessBtn');
if (imageToolbarMoreBtn) { imageToolbarMoreBtn.querySelector('.tool-icon').innerHTML = '▾'; } if (!imageToolbarLessBtn && toolbarRemoveImageBtn) { toolbarRemoveImageBtn.insertAdjacentHTML('afterend', ''); imageToolbarLessBtn = byId('imageToolbarLessBtn'); }
const QUICK_POSITION_OPTIONS = [ { value: 'custom', label: 'Position' }, { value: 'top-left', label: 'Top Left' }, { value: 'top-middle', label: 'Top Middle' }, { value: 'center', label: 'Center' }, { value: 'bottom-right', label: 'Bottom Right' } ]; const PAGE_RANGE_OPTIONS = [ { value: 'all', label: 'All pages' }, { value: 'first', label: 'First page only' }, { value: 'first2', label: 'First and second page only' }, { value: 'first3', label: 'First 3 pages' }, { value: 'firstLast', label: 'First and last page' }, { value: 'custom', label: 'Page range' } ];
function createToolbarSelect(id, title, options) { const select = document.createElement('select'); select.id = id; select.title = title; select.className = 'tool-select tool-compact-select'; options.forEach(item => { const option = document.createElement('option'); option.value = item.value; option.textContent = item.label; if (item.gap) option.dataset.gap = item.gap; select.appendChild(option); }); return select; }
function createRepeatStylePicker(id, title, options) { const wrapper = document.createElement('div'); wrapper.className = 'repeat-style-picker'; wrapper.id = id + 'Wrapper';
// first option desc lookup from section cards function getDesc(pattern) { const btn = watermarkToolRoot.querySelector(`.repeat-preset[data-pattern="${pattern}"]`); return btn ? (btn.querySelector('span')?.textContent || '') : ''; }
const firstOpt = options[0] || {}; const trigger = document.createElement('button'); trigger.type = 'button'; trigger.className = 'repeat-style-trigger tool-btn'; trigger.title = title; trigger.innerHTML = `
${firstOpt.label || ''}▾`;
const dropdown = document.createElement('div'); dropdown.className = 'repeat-style-dropdown';
options.forEach((item, idx) => { const opt = document.createElement('div'); opt.className = 'rs-option' + (idx === 0 ? ' active' : ''); opt.dataset.value = item.value; opt.dataset.gap = item.gap || ''; const desc = getDesc(item.value); opt.innerHTML = `
`; dropdown.appendChild(opt); });
wrapper.appendChild(trigger); wrapper.appendChild(dropdown);
// hidden select for full compatibility with existing event listeners const hiddenSelect = document.createElement('select'); hiddenSelect.id = id; hiddenSelect.style.display = 'none'; options.forEach(item => { const o = document.createElement('option'); o.value = item.value; o.textContent = item.label; if (item.gap) o.dataset.gap = item.gap; hiddenSelect.appendChild(o); }); wrapper.appendChild(hiddenSelect);
trigger.addEventListener('click', (e) => { e.stopPropagation(); // close all other open dropdowns watermarkToolRoot.querySelectorAll('.repeat-style-dropdown.open').forEach(d => { if (d !== dropdown) d.classList.remove('open'); }); dropdown.classList.toggle('open'); });
dropdown.addEventListener('click', (e) => { const optEl = e.target.closest('.rs-option'); if (!optEl) return; dropdown.querySelectorAll('.rs-option').forEach(o => o.classList.remove('active')); optEl.classList.add('active'); const val = optEl.dataset.value; const label = optEl.querySelector('.rs-option-name').textContent; trigger.querySelector('.rs-mini').className = `rs-mini ${val}`; trigger.querySelector('.rs-label').textContent = label; hiddenSelect.value = val; hiddenSelect.dispatchEvent(new Event('change', { bubbles: true })); dropdown.classList.remove('open'); });
document.addEventListener('click', () => dropdown.classList.remove('open'));
wrapper._hiddenSelect = hiddenSelect; return wrapper; }
function createToolbarCheck(id, labelText, title) { const label = document.createElement('label'); label.className = 'tool-check'; label.title = title; const input = document.createElement('input'); input.id = id; input.type = 'checkbox'; const span = document.createElement('span'); span.textContent = labelText; label.append(input, span); return { label, input }; }
function getRepeatStyleOptions(group) { return Array.from(watermarkToolRoot.querySelectorAll(`.repeat-preset[data-group="${group}"]`)).map(btn => ({ value: btn.dataset.pattern, label: btn.dataset.label || btn.querySelector('strong')?.textContent || btn.textContent.trim(), gap: btn.dataset.gap })); }
const toolbarTextQuickPosition = createToolbarSelect('toolbarTextQuickPosition', 'Text Quick Position', QUICK_POSITION_OPTIONS); const toolbarLogoQuickPosition = createToolbarSelect('toolbarLogoQuickPosition', 'Logo Quick Position', QUICK_POSITION_OPTIONS); const toolbarLogoPageRange = createToolbarSelect('toolbarLogoPageRange', 'Logo Pages', PAGE_RANGE_OPTIONS); const toolbarTextRepeatStylePicker = createRepeatStylePicker('toolbarTextRepeatStyle', 'Text Repeat Styles', getRepeatStyleOptions('text')); const toolbarLogoRepeatStylePicker = createRepeatStylePicker('toolbarLogoRepeatStyle', 'Logo Repeat Styles', getRepeatStyleOptions('logo')); const toolbarTextRepeatStyle = toolbarTextRepeatStylePicker.querySelector('#toolbarTextRepeatStyle'); const toolbarLogoRepeatStyle = toolbarLogoRepeatStylePicker.querySelector('#toolbarLogoRepeatStyle'); const toolbarTextRepeatControl = createToolbarCheck('toolbarTextRepeatToggle', 'Repeat', 'Text Repeat Watermark'); const toolbarLogoRepeatControl = createToolbarCheck('toolbarLogoRepeatToggle', 'Repeat', 'Logo Repeat Watermark'); const toolbarTextRepeatToggle = toolbarTextRepeatControl.input; const toolbarLogoRepeatToggle = toolbarLogoRepeatControl.input;
if (textToolbarMoreBtn && toolbarSize?.parentElement) { [toolbarTextQuickPosition, toolbarTextRepeatControl.label, toolbarTextRepeatStylePicker].forEach(control => { toolbarSize.parentElement.insertBefore(control, toolbarSize); }); }
if (imageToolbarMoreBtn?.parentElement) { [toolbarLogoQuickPosition, toolbarLogoRepeatControl.label, toolbarLogoRepeatStylePicker].forEach(control => { imageToolbarMoreBtn.parentElement.insertBefore(control, imageToolbarMoreBtn); }); } if (toolbarImageLayerBtn?.parentElement) { toolbarImageLayerBtn.parentElement.insertBefore(toolbarLogoPageRange, toolbarImageLayerBtn); }
let pdfBytes = null; let pdfDoc = null; let pdfPageIndex = 1; let previewReady = false; let currentFileName = 'watermarked-pdf'; let generatedPdfBlob = null; let generatedPdfUrl = ''; let wmImageBytes = null; let wmImageMime = null; let wmImageObjectUrl = null;
const textState = { xRatio: 0.5, yRatio: 0.15, rotation: 0, size: 48, opacity: 0.80, language: 'english', overPdf: true }; const textRepeatState = { xRatio: 0.5, yRatio: 0.5 }; const imageState = { xRatio: 0.5, yRatio: 0.5, rotation: 0, width: 140, overPdf: true, opacity: 0.80 }; const textRepeatSettings = { pattern: 'grid', label: 'Classic Grid' }; const logoRepeatSettings = { pattern: 'grid', label: 'Classic Grid' };
let activeDrag = null, dragOffsetX = 0, dragOffsetY = 0; let activeResize = null, resizeStartDistance = 0, resizeStartValue = 0; let activeRotate = null;
textPreview.contentEditable = 'false';
function getFontChoices(language = 'english') { return FONT_CHOICES[language] || FONT_CHOICES.english; }
function getLanguageSample(language) { return LANGUAGE_OPTIONS[language]?.sample || LANGUAGE_OPTIONS.english.sample; }
function isRtlLanguage(language) { return Boolean(LANGUAGE_OPTIONS[language]?.rtl); }
function updateLanguageUi(language) { languageSwitch.querySelectorAll('[data-language]').forEach(btn => { btn.classList.toggle('active', btn.dataset.language === language); }); if (!['hebrew', 'hindi', 'urdu', 'persian', 'russian', 'japanese', 'chinese', 'german', 'spanish'].includes(language)) { extraLanguageSelect.value = ''; } else { extraLanguageSelect.value = language; } fontPanel.classList.toggle('rtl', isRtlLanguage(language)); fontContext.textContent = `${LANGUAGE_OPTIONS[language]?.label || 'English'} font collection`; fontSample.textContent = getLanguageSample(language); fontSample.style.fontFamily = getPreviewFontStack(fontFamily.value); fontSample.dir = isRtlLanguage(language) ? 'rtl' : 'ltr'; fontContext.dir = 'ltr'; }
function updateSelectPreviewStyle(selectEl, familyValue) { const source = Object.values(FONT_CHOICES).flat(); const meta = source.find(font => font.value === familyValue); if (meta) selectEl.style.fontFamily = meta.stack; selectEl.style.direction = 'ltr'; selectEl.style.textAlign = 'left'; }
function getStyleLabel() { const bits = []; bits.push(textBold.checked ? 'Bold' : 'Regular'); if (textItalic.checked) bits.push('Italic'); if (textUnderline.checked) bits.push('Underline'); return bits.join(' / '); }
function populateFontMenus(language = 'english', preferredValue = '') { const fonts = getFontChoices(language); const fallback = fonts[0]?.value || ''; const nextValue = fonts.some(font => font.value === preferredValue) ? preferredValue : fallback;
[fontFamily, toolbarFont].forEach(selectEl => { selectEl.innerHTML = ''; fonts.forEach(font => { const option = document.createElement('option'); option.value = font.value; option.textContent = font.label; option.style.fontFamily = font.stack; selectEl.appendChild(option); }); selectEl.value = nextValue; updateSelectPreviewStyle(selectEl, nextValue); });
textState.language = language; languageSelect.value = language; toolbarLanguage.value = language; updateLanguageUi(language); }
function syncLanguageAndFonts(language = languageSelect.value, preferredFont = fontFamily.value) { populateFontMenus(language, preferredFont); applyTextDirection(wmText.value || ''); applyTextOverlay(); }
syncLanguageAndFonts('english', 'Arial'); applyTextDirection(wmText.value || ''); syncHyperlinkUi(); updateLogoThumbUi(null, ''); syncSizeInputs();
function syncSizeInputs() { fontSizeRange.value = fontSize.value; fontSizeTag.textContent = fontSize.value; toolbarSize.value = fontSize.value; toolbarSizeRange.value = fontSize.value;
imageWidthRange.value = imageWidth.value; imageWidthTag.textContent = imageWidth.value; toolbarImageSize.value = imageWidth.value; toolbarImageSizeRange.value = imageWidth.value; }
function containsArabic(text) { return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text || ''); }
function isLikelyRtlText(text) { return /[\u0590-\u08FF]/.test(text || ''); }
function preparePdfText(text) { return String(text || '').replace(/\s+/g, ' ').trim(); }
function applyTextDirection(text) { const forceRtl = isRtlLanguage(languageSelect.value); const rtl = forceRtl || isLikelyRtlText(text); textPreview.dir = rtl ? 'rtl' : 'ltr'; textPreview.style.direction = rtl ? 'rtl' : 'ltr'; textPreview.style.unicodeBidi = rtl ? 'plaintext' : 'normal'; textPreview.style.textAlign = rtl ? 'right' : 'left'; fontSample.dir = rtl ? 'rtl' : 'ltr'; }
function normalizeHyperlink(value) { const raw = String(value || '').trim(); if (!raw) return ''; if (/^www\./i.test(raw)) return `https://${raw}`; if (/^[a-z][a-z0-9+.-]*:/i.test(raw)) return raw; return `https://${raw}`; }
function getValidatedHyperlink(inputValue) { const normalized = normalizeHyperlink(inputValue); if (!normalized) return ''; try { const parsed = new URL(normalized); if (['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return normalized; } catch (err) {} throw new Error('Invalid hyperlink. Use http(s), mailto, tel, or a valid domain.'); }
function getWatermarkHyperlink() { if (!showTextWatermark.checked || !enableTextLink.checked) return ''; return getValidatedHyperlink(wmTextLink.value); }
function getImageWatermarkHyperlink() { if (!showLogoWatermark.checked || !enableImageLink.checked) return ''; return getValidatedHyperlink(wmImageLink.value); }
function syncHyperlinkUi() { wmTextLink.disabled = !enableTextLink.checked || !showTextWatermark.checked; wmImageLink.disabled = !enableImageLink.checked || !showLogoWatermark.checked; if (!enableTextLink.checked) wmTextLink.blur(); if (!enableImageLink.checked) wmImageLink.blur(); }
function getRotatedBoundingRect(x, y, width, height, rotationDeg) { const radians = degreesToRadians(rotationDeg); const cos = Math.cos(radians); const sin = Math.sin(radians); const points = [ { x, y }, { x: x + width * cos, y: y + width * sin }, { x: x - height * sin, y: y + height * cos }, { x: x + width * cos - height * sin, y: y + width * sin + height * cos } ]; const xs = points.map(point => point.x); const ys = points.map(point => point.y); return { x1: Math.min(...xs), y1: Math.min(...ys), x2: Math.max(...xs), y2: Math.max(...ys) }; }
function addHyperlinkAnnotation(pdf, page, rect, url) { const normalized = normalizeHyperlink(url); if (!normalized) return;
let annots = page.node.lookupMaybe(PDFName.of('Annots'), PDFArray); if (!annots) { annots = pdf.context.obj([]); page.node.set(PDFName.of('Annots'), annots); }
const linkAnnotation = pdf.context.register( pdf.context.obj({ Type: PDFName.of('Annot'), Subtype: PDFName.of('Link'), Rect: [rect.x1, rect.y1, rect.x2, rect.y2], Border: [0, 0, 0], H: PDFName.of('I'), A: pdf.context.obj({ Type: PDFName.of('Action'), S: PDFName.of('URI'), URI: PDFString.of(normalized) }) }) );
annots.push(linkAnnotation); }
function freshPdfBytes() { return new Uint8Array(pdfBytes); }
function getCanvasRect() { return pdfCanvas.getBoundingClientRect(); }
function formatFileSize(bytes) { if (!bytes || bytes < 1) return '0 KB'; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); const value = bytes / Math.pow(1024, i); return `${value.toFixed(value >= 10 || i === 0 ? 0 : 1)} ${sizes[i]}`; }
function escapeHtml(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }
function setProgressUi(kind, percent, statusText = '') { if (kind !== 'pdf') return; pdfUploadProgress.classList.add('active'); const safePercent = Math.max(0, Math.min(100, Math.round(percent || 0))); if (statusText) pdfUploadStatus.textContent = statusText; pdfUploadPercent.textContent = `${safePercent}%`; pdfUploadBar.style.width = `${safePercent}%`; }
function hideProgressUi(kind, delay = 500) { if (kind !== 'pdf') return; window.setTimeout(() => pdfUploadProgress.classList.remove('active'), delay); }
function readFileWithProgress(file, kind, startText, endText) { return new Promise((resolve, reject) => { const reader = new FileReader(); if (kind === 'pdf') setProgressUi(kind, 0, startText);
reader.onprogress = (event) => { if (kind === 'pdf' && event.lengthComputable) { setProgressUi(kind, (event.loaded / event.total) * 100, startText); } };
reader.onloadstart = () => { if (kind === 'pdf') setProgressUi(kind, 0, startText); };
reader.onload = () => { if (kind === 'pdf') { setProgressUi(kind, 100, endText); hideProgressUi(kind, 700); } resolve(reader.result); };
reader.onerror = () => { if (kind === 'pdf') hideProgressUi(kind, 0); reject(reader.error || new Error('File could not be read.')); };
reader.readAsArrayBuffer(file); }); }
function updateLogoThumbUi(file, objectUrl = '') { const hasFile = !!(file && objectUrl); logoFileName.textContent = hasFile ? file.name : 'No logo selected'; logoFileMeta.textContent = hasFile ? `${(file.type || 'image').toUpperCase().replace('IMAGE/', '')} • ${formatFileSize(file.size)}` : 'PNG or JPG only'; logoThumbPreview.style.display = hasFile ? 'block' : 'none'; if (hasFile) logoThumbPreview.src = objectUrl; else logoThumbPreview.removeAttribute('src'); }
function resetRepeatedImagePreview() { repeatImageLayer.innerHTML = ''; repeatImageLayer.classList.add('hidden'); repeatImageLayer.style.pointerEvents = 'none'; repeatImageLayer.style.cursor = 'default'; }
function resetRepeatedTextPreview() { repeatTextLayer.innerHTML = ''; repeatTextLayer.classList.add('hidden'); repeatTextLayer.style.pointerEvents = 'none'; }
function renderRepeatedTextPreview() { resetRepeatedTextPreview();
if (!previewReady || !showTextWatermark.checked || !repeatWatermark.checked || !isCurrentPageSelectedForText()) { return; }
const text = preparePdfText(wmText.value); if (!text) return;
// Use getBoundingClientRect for the actual displayed (DOM) dimensions // canvas.width may differ from displayed size due to pdf.js scaling const rect = pdfCanvas.getBoundingClientRect(); const domW = rect.width; const domH = rect.height;
const gap = parseFloat(repeatGap.value || 120); const rotation = textState.rotation; const opacity = textState.opacity; const size = textState.size; const color = fontColor.value; const weight = textBold.checked ? '700' : '400'; const style = textItalic.checked ? 'italic' : 'normal'; const decoration = textUnderline.checked ? 'underline' : 'none'; const fontStack = getPreviewFontStack(fontFamily.value);
repeatTextLayer.classList.remove('hidden'); repeatTextLayer.style.zIndex = textState.overPdf ? '18' : '8'; repeatTextLayer.style.mixBlendMode = textState.overPdf ? 'normal' : 'multiply'; repeatTextLayer.style.pointerEvents = 'auto'; repeatTextLayer.style.cursor = 'move';
// Estimate text dimensions for spacing (approximate rendered width) const approxCharWidth = size * 0.6; const estimatedTextW = Math.max(text.length * approxCharWidth, size); const estimatedTextH = size * 1.2;
// Scale gap to DOM coordinate space (matches how logo repeat works) const scaleX = domW / (pdfCanvas.width || domW); const scaleY = domH / (pdfCanvas.height || domH); const scaledGap = gap * Math.min(scaleX, scaleY);
// Use the shared getRepeatPositions helper (same as logo repeat) // so both text and logo use identical coordinate logic const { offsetX, offsetY } = getRepeatTextOffset(domW, domH); const positions = getRepeatPositions( domW, domH, estimatedTextW, estimatedTextH, scaledGap, textRepeatSettings, offsetX, offsetY );
const frag = document.createDocumentFragment(); const clipPad = size * 0.7;
positions.forEach(({ x, y }) => { // Skip elements whose center is clearly outside the visible page if (x < -clipPad || x > domW + clipPad) return; if (y < -clipPad || y > domH + clipPad) return;
const el = document.createElement('span'); el.style.position = 'absolute'; el.style.left = x + 'px'; el.style.top = y + 'px'; el.style.transform = `translate(-50%, -50%) rotate(${rotation}deg)`; el.style.transformOrigin = 'center center'; el.style.whiteSpace = 'nowrap'; el.style.pointerEvents = 'none'; el.style.userSelect = 'none'; el.style.fontSize = size + 'px'; el.style.fontFamily = fontStack; el.style.fontWeight = weight; el.style.fontStyle = style; el.style.textDecoration = decoration; el.style.color = color; el.style.opacity = opacity; el.style.lineHeight = '1.1'; el.textContent = text; frag.appendChild(el); });
repeatTextLayer.appendChild(frag); }
function invalidateGeneratedPdf() { generatedPdfBlob = null; if (generatedPdfUrl) { URL.revokeObjectURL(generatedPdfUrl); generatedPdfUrl = ''; } if (downloadBtn) downloadBtn.style.display = 'none'; if (generateBtn) { generateBtn.disabled = false; generateBtn.innerHTML = 'Add Watermark'; } if (actionStatus) { actionStatus.textContent = 'Single click to select or drag to reposition. Double-click text to edit. Repeat watermark is also draggable. Preview updates in real-time.'; } }
function setGenerateLoading(isLoading) { generateBtn.disabled = isLoading; generateBtn.innerHTML = isLoading ? 'Preparing watermark...' : 'Add Watermark'; }
function removeTextWatermark() { showTextWatermark.checked = false; wmText.value = ''; textPreview.textContent = ''; closeToolbars(); applyAllOverlays(); }
function removeLogoWatermark() { showLogoWatermark.checked = false; resetLogoUi(); closeToolbars(); applyAllOverlays(); }
function resetLogoUi() { invalidateGeneratedPdf(); wmImageInput.value = ''; wmImageBytes = null; wmImageMime = null; if (wmImageObjectUrl) { URL.revokeObjectURL(wmImageObjectUrl); wmImageObjectUrl = null; } imagePreview.removeAttribute('src'); imageOverlay.classList.add('hidden'); imageToolbar.classList.remove('open'); repeatLogoWatermark.checked = false; resetRepeatedImagePreview(); resetRepeatedTextPreview(); removeLogoBtn.style.display = 'none'; enableImageLink.checked = false; wmImageLink.value = ''; syncHyperlinkUi(); updateLogoThumbUi(null, ''); }
function resetPdfUi() { invalidateGeneratedPdf(); pdfInput.value = ''; pdfFileName.textContent = 'No file selected'; pdfFileMeta.textContent = 'Size: — | Pages: —'; removePdfBtn.style.display = 'none'; pdfLabel.textContent = 'Select PDF File'; pdfDropText.style.display = 'block'; pdfUploadBar.style.width = '0%'; pdfUploadPercent.textContent = '0%'; pdfUploadStatus.textContent = 'Uploading PDF...'; pdfUploadProgress.classList.remove('active');
pdfBytes = null; pdfDoc = null; pdfPageIndex = 1; previewReady = false; currentFileName = 'watermarked-pdf';
const ctx = pdfCanvas.getContext('2d'); ctx.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); pdfCanvas.width = 0; pdfCanvas.height = 0;
stage.style.width = 'auto'; stage.style.height = 'auto'; pageControls.style.display = 'none'; previewStatus.style.display = 'block'; previewStatus.textContent = 'Upload a PDF to see the Editable PDF View here.';
closeToolbars(); textOverlay.classList.add('hidden'); imageOverlay.classList.add('hidden'); resetRepeatedImagePreview(); resetRepeatedTextPreview(); }
function getCenterFromState(state) { const rect = getCanvasRect(); return { x: rect.left + state.xRatio * rect.width, y: rect.top + state.yRatio * rect.height }; }
function getPreviewFontStack(selectedFamily) { const source = Object.values(FONT_CHOICES).flat(); const meta = source.find(font => font.value === selectedFamily); return meta?.stack || 'Arial, Helvetica, sans-serif'; }
function placeCaretAtEnd(el) { const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(el); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); }
function startInlineTextEdit() { if (!showTextWatermark.checked || !preparePdfText(wmText.value)) return; textVisual.classList.add('editing'); textPreview.contentEditable = 'true'; textPreview.focus(); placeCaretAtEnd(textPreview); openTextToolbar(); }
function stopInlineTextEdit() { const cleanText = textPreview.textContent.replace(/\s+/g, ' ').trim(); wmText.value = cleanText; textPreview.textContent = cleanText; applyTextDirection(cleanText); textPreview.contentEditable = 'false'; textVisual.classList.remove('editing'); if (!cleanText) showTextWatermark.checked = false; applyTextOverlay(); }
function syncTextToolbar() { toolbarColor.value = fontColor.value; toolbarOpacity.value = textOpacity.value; toolbarLanguage.value = languageSelect.value; toolbarFont.value = fontFamily.value; toolbarSize.value = fontSize.value; toolbarSizeRange.value = fontSize.value; textLayerMode.value = textState.overPdf ? 'overPdf' : 'belowPdf'; textLayerText.textContent = textState.overPdf ? 'Over PDF' : 'Below PDF'; toolbarTextLayerBtn.classList.toggle('active', textState.overPdf); toolbarBoldBtn.classList.toggle('active', textBold.checked); toolbarItalicBtn.classList.toggle('active', textItalic.checked); toolbarUnderlineBtn.classList.toggle('active', textUnderline.checked); toolbarTextQuickPosition.value = getQuickPositionValue(textState); toolbarTextRepeatToggle.checked = repeatWatermark.checked; toolbarTextRepeatStyle.value = textRepeatSettings.pattern; syncRepeatPickerTrigger(toolbarTextRepeatStylePicker, textRepeatSettings.pattern, textRepeatSettings.label); textOpacityLabel.textContent = Number(textOpacity.value).toFixed(2); fontSizeTag.textContent = fontSize.value; updateSelectPreviewStyle(fontFamily, fontFamily.value); updateSelectPreviewStyle(toolbarFont, toolbarFont.value); if (toolbarFontStylePreview) { toolbarFontStylePreview.textContent = getStyleLabel(); toolbarFontStylePreview.style.fontFamily = getPreviewFontStack(fontFamily.value); toolbarFontStylePreview.style.fontWeight = textBold.checked ? '700' : '400'; toolbarFontStylePreview.style.fontStyle = textItalic.checked ? 'italic' : 'normal'; toolbarFontStylePreview.style.textDecoration = textUnderline.checked ? 'underline' : 'none'; } fontSample.style.fontFamily = getPreviewFontStack(fontFamily.value); fontSample.style.fontWeight = textBold.checked ? '700' : '400'; fontSample.style.fontStyle = textItalic.checked ? 'italic' : 'normal'; fontSample.style.textDecoration = textUnderline.checked ? 'underline' : 'none'; }
function syncImageToolbar() { toolbarImageOpacity.value = imageOpacity.value; toolbarImageSize.value = imageWidth.value; toolbarImageSizeRange.value = imageWidth.value; imageLayerMode.value = imageState.overPdf ? 'overPdf' : 'belowPdf'; imageLayerText.textContent = imageState.overPdf ? 'Over PDF' : 'Below PDF'; toolbarImageLayerBtn.classList.toggle('active', imageState.overPdf); toolbarLogoQuickPosition.value = getQuickPositionValue(imageState); toolbarLogoPageRange.value = imagePageRangeMode.value || 'all'; toolbarLogoRepeatToggle.checked = repeatLogoWatermark.checked; toolbarLogoRepeatStyle.value = logoRepeatSettings.pattern; syncRepeatPickerTrigger(toolbarLogoRepeatStylePicker, logoRepeatSettings.pattern, logoRepeatSettings.label); imageOpacityLabel.textContent = Number(imageOpacity.value).toFixed(2); imageWidthTag.textContent = imageWidth.value; }
function positionTextToolbar() { if (!previewReady) return; const rect = getCanvasRect(); let x, y;
if (repeatWatermark.checked) { // In repeat mode, anchor toolbar to top-center of the canvas x = rect.width / 2; y = 40; } else { if (textOverlay.classList.contains('hidden')) return; x = textState.xRatio * rect.width; y = textState.yRatio * rect.height; }
const gap = 40; const toolbarHeight = Math.max(40, textToolbarShell.offsetHeight || 40); const shellWidth = Math.max(260, textToolbarShell.offsetWidth || 260); const shouldPlaceAbove = rect.height - y < (toolbarHeight + gap + 10) && y > (toolbarHeight + gap + 10);
textToolbarShell.style.left = `${Math.min(Math.max(x - shellWidth / 2, 8), Math.max(rect.width - shellWidth - 8, 8))}px`; textToolbarShell.style.top = shouldPlaceAbove ? `${Math.max(y - toolbarHeight - gap, 8)}px` : `${Math.min(y + gap, Math.max(rect.height - toolbarHeight - 8, 8))}px`; }
function positionImageToolbar() { if (!previewReady) return; const rect = getCanvasRect(); const x = imageState.xRatio * rect.width; const y = imageState.yRatio * rect.height; const gap = 46; const toolbarHeight = Math.max(40, imageToolbarShell.offsetHeight || 40); const shouldPlaceAbove = rect.height - y < (toolbarHeight + gap + 10) && y > (toolbarHeight + gap + 10);
const shellWidth = Math.max(220, imageToolbarShell.offsetWidth || 220); imageToolbarShell.style.left = `${Math.min(Math.max(x - shellWidth / 2, 8), Math.max(rect.width - shellWidth - 8, 8))}px`; imageToolbarShell.style.top = shouldPlaceAbove ? `${Math.max(y - toolbarHeight - gap, 8)}px` : `${Math.min(y + gap, Math.max(rect.height - toolbarHeight - 8, 8))}px`; }
function openTextToolbar() { const text = preparePdfText(wmText.value); if (!previewReady || !showTextWatermark.checked || !isCurrentPageSelectedForText() || !text) return; textToolbarShell.classList.add('open'); positionTextToolbar(); textToolbar.classList.add('open'); }
function openImageToolbar() { if (!previewReady || !showLogoWatermark.checked || !wmImageBytes || !isCurrentPageSelectedForImage()) return; imageToolbarShell.classList.add('open'); positionImageToolbar(); imageToolbar.classList.add('open'); }
function closeToolbars() { textToolbarShell.classList.remove('open'); imageToolbarShell.classList.remove('open'); textToolbar.classList.remove('open'); imageToolbar.classList.remove('open'); }
function toggleToolbarExpanded(target, forceExpanded = null) { const shell = target === 'text' ? textToolbarShell : imageToolbarShell; const moreBtn = target === 'text' ? textToolbarMoreBtn : imageToolbarMoreBtn; const shouldExpand = forceExpanded === null ? !shell.classList.contains('expanded') : forceExpanded; shell.classList.toggle('expanded', shouldExpand); const expanded = shell.classList.contains('expanded'); if (moreBtn) moreBtn.classList.toggle('active', expanded); if (target === 'text' && textToolbar.classList.contains('open')) positionTextToolbar(); if (target === 'image' && imageToolbar.classList.contains('open')) positionImageToolbar(); }
function updatePageRangeUi(modeEl, rangeEl, helperEl, target = 'text') { const mode = modeEl.value; const isCustom = mode === 'custom'; rangeEl.disabled = !isCustom; rangeEl.style.display = isCustom ? 'block' : 'none';
const helperMap = target === 'text' ? { all: 'Apply watermark to all pages.', first: 'Apply watermark to page 1 only.', first2: 'Apply watermark to pages 1 and 2 only.', first3: 'Apply watermark to the first 3 pages.', firstLast: 'Apply watermark to the first and last page.', custom: 'Examples: 1-3, 2,4,7' } : { all: 'Apply logo watermark to all pages.', first: 'Apply logo watermark to page 1 only.', first2: 'Apply logo watermark to pages 1 and 2 only.', first3: 'Apply logo watermark to the first 3 pages.', firstLast: 'Apply logo watermark to the first and last page.', custom: 'Examples: 1-3, 2,4,7' };
helperEl.textContent = helperMap[mode] || helperMap.custom; }
function parseCustomPageRange(rawValue, totalPages) { const raw = String(rawValue || '').trim().toLowerCase(); const out = new Set(); const parts = raw.split(',').map(s => s.trim()).filter(Boolean);
for (const part of parts) { if (part.includes('-')) { const [a, b] = part.split('-').map(v => parseInt(v.trim(), 10)); if (Number.isFinite(a) && Number.isFinite(b)) { const start = Math.max(1, Math.min(a, b)); const end = Math.min(totalPages, Math.max(a, b)); for (let p = start; p <= end; p++) out.add(p); } } else { const p = parseInt(part, 10); if (Number.isFinite(p) && p >= 1 && p <= totalPages) out.add(p); } } if (!out.size) throw new Error('Invalid custom page range. Use values like 1-3,5'); return out; } function parsePageRange(modeEl, rangeEl, totalPages) { const mode = modeEl.value; if (mode === 'all') return new Set(Array.from({ length: totalPages }, (_, i) => i + 1)); if (mode === 'first') return new Set([1]); if (mode === 'first2') return new Set(Array.from({ length: Math.min(2, totalPages) }, (_, i) => i + 1)); if (mode === 'first3') return new Set(Array.from({ length: Math.min(3, totalPages) }, (_, i) => i + 1)); if (mode === 'firstLast') return new Set(totalPages > 1 ? [1, totalPages] : [1]); return parseCustomPageRange(rangeEl.value, totalPages); }
function getSelectedImagePages(totalPages) { const pages = parsePageRange(imagePageRangeMode, imagePageRange, totalPages); const mode = imagePageRangeMode.value || 'all'; if (['all', 'first', 'first2', 'first3', 'firstLast'].includes(mode)) pages.add(1); return pages; }
function isCurrentPageSelectedForText() { if (!pdfDoc) return true; try { return parsePageRange(pageRangeMode, pageRange, pdfDoc.numPages).has(pdfPageIndex); } catch { return true; } }
function isCurrentPageSelectedForImage() { if (!pdfDoc) return true; try { return parsePageRange(imagePageRangeMode, imagePageRange, pdfDoc.numPages).has(pdfPageIndex); } catch { return true; } }
// applyTextOverlay is defined later (complete version with overPdf + repeat support)
function getRepeatPositions(pageWidth, pageHeight, itemWidth, itemHeight, gap, settings, offsetX = 0, offsetY = 0) { const positions = []; let stepX = Math.max(itemWidth + gap, 30); let stepY = Math.max(itemHeight + gap, 30);
if (settings.pattern === 'dense') { stepX = Math.max(itemWidth + gap * 0.6, 24); stepY = Math.max(itemHeight + gap * 0.6, 24); } if (settings.pattern === 'wide') { stepX = Math.max(itemWidth + gap * 1.25, 40); stepY = Math.max(itemHeight + gap * 1.25, 40); } if (settings.pattern === 'diamond') { stepX = Math.max(itemWidth + gap * 1.1, 36); stepY = Math.max(itemHeight + gap * 1.1, 36); }
// Clamp: only draw within the page boundary (with a small safe margin) const margin = Math.max(itemWidth * 0.7, itemHeight * 0.7, 20); const startX = -stepX + offsetX; const endX = pageWidth + stepX + offsetX; const startY = -stepY + offsetY; const endY = pageHeight + stepY + offsetY;
let y = startY; let rowIndex = 0;
while (y <= endY) { let xStart = startX; if (settings.pattern === 'checker') xStart += rowIndex % 2 ? stepX / 2 : 0; if (settings.pattern === 'brick') xStart += rowIndex % 2 ? stepX / 2 : 0; if (settings.pattern === 'diagonal') xStart += rowIndex * Math.max(gap * 0.34, itemWidth * 0.18, 12); if (settings.pattern === 'stair') xStart += rowIndex * Math.max(stepX * 0.2, 12); if (settings.pattern === 'cross') xStart += rowIndex % 2 ? stepX / 2 : 0; if (settings.pattern === 'diamond') xStart += rowIndex % 2 ? stepX / 2 : 0; for (let x = xStart; x <= endX; x += stepX) { let yOffset = 0; if (settings.pattern === 'ripple') { yOffset = Math.sin((x / Math.max(stepX, 1)) * 0.7) * Math.max(10, gap * 0.18); } const px = x - offsetX; const py = y + yOffset - offsetY; // Hard-clip: skip positions whose center is outside the page with margin if (px < -margin || px > pageWidth + margin) continue; if (py < -margin || py > pageHeight + margin) continue;
positions.push({ x, y: y + yOffset }); }
rowIndex++; y += stepY; }
return positions; }
function getRepeatImageOffset(pageWidth, pageHeight) { return { offsetX: (imageState.xRatio - 0.5) * pageWidth, offsetY: (imageState.yRatio - 0.5) * pageHeight }; }
function getRepeatTextOffset(pageWidth, pageHeight) { return { offsetX: (textRepeatState.xRatio - 0.5) * pageWidth, offsetY: (textRepeatState.yRatio - 0.5) * pageHeight }; }
function renderRepeatedImagePreview() { resetRepeatedImagePreview();
if (!previewReady || !showLogoWatermark.checked || !wmImageBytes || !wmImageObjectUrl || !repeatLogoWatermark.checked || !isCurrentPageSelectedForImage()) { return; }
const width = parseFloat(imageWidth.value || 140); const naturalWidth = imagePreview.naturalWidth || 1; const naturalHeight = imagePreview.naturalHeight || 1; const ratio = naturalHeight / naturalWidth; const height = width * ratio; const gap = parseFloat(repeatGap.value || 120); const { offsetX, offsetY } = getRepeatImageOffset(pdfCanvas.width, pdfCanvas.height); const positions = getRepeatPositions(pdfCanvas.width, pdfCanvas.height, width, height, gap, logoRepeatSettings, offsetX, offsetY);
repeatImageLayer.classList.remove('hidden'); repeatImageLayer.style.zIndex = imageState.overPdf ? '19' : '2'; repeatImageLayer.style.mixBlendMode = imageState.overPdf ? 'normal' : 'multiply'; repeatImageLayer.style.pointerEvents = 'auto'; repeatImageLayer.style.cursor = 'move';
positions.forEach((pos) => { const item = document.createElement('img'); item.src = wmImageObjectUrl; item.className = 'repeat-image-item'; item.style.width = `${width}px`; item.style.left = `${pos.x}px`; item.style.top = `${pos.y}px`; item.style.opacity = String(parseFloat(imageOpacity.value || 0.80)); item.style.transform = `translate(-50%, -50%) rotate(${parseFloat(imageRotation.value || 0)}deg)`; repeatImageLayer.appendChild(item); }); }
function applyImageOverlay() { imageState.rotation = parseFloat(imageRotation.value || 0); imageState.width = parseFloat(imageWidth.value || 140); imageState.opacity = parseFloat(imageOpacity.value || 0.80);
imageOverlay.style.left = `${imageState.xRatio * pdfCanvas.width}px`; imageOverlay.style.top = `${imageState.yRatio * pdfCanvas.height}px`; imageOverlay.style.transform = `translate(-50%, -50%) rotate(${imageState.rotation}deg)`; imagePreview.style.width = `${imageState.width}px`; imagePreview.style.height = 'auto'; imagePreview.style.opacity = String(imageState.opacity);
if (imageState.overPdf) { imageOverlay.style.zIndex = '20'; imageOverlay.style.mixBlendMode = 'normal'; } else { imageOverlay.style.zIndex = '12'; imageOverlay.style.mixBlendMode = 'multiply'; }
const liveImageLink = showLogoWatermark.checked && enableImageLink.checked ? normalizeHyperlink(wmImageLink.value) : ''; imageVisual.classList.toggle('has-link', !!liveImageLink); imageVisual.style.cursor = liveImageLink ? 'pointer' : 'move'; imageVisual.title = liveImageLink || '';
if (wmImageBytes && previewReady && showLogoWatermark.checked && !repeatLogoWatermark.checked && isCurrentPageSelectedForImage()) { imageOverlay.classList.remove('hidden'); } else { imageOverlay.classList.add('hidden'); }
if (!(wmImageBytes && previewReady && showLogoWatermark.checked && isCurrentPageSelectedForImage())) { imageToolbar.classList.remove('open'); }
syncImageToolbar(); renderRepeatedImagePreview(); positionImageToolbar(); }
function applyAllOverlays() { invalidateGeneratedPdf(); syncHyperlinkUi(); syncSizeInputs(); applyTextOverlay(); applyImageOverlay(); }
function quickPosition(state, key) { const map = { 'top-left': [0.16, 0.14], 'top-middle': [0.50, 0.14], 'center': [0.50, 0.50], 'bottom-right': [0.84, 0.86] }; [state.xRatio, state.yRatio] = map[key] || [0.5, 0.5]; }
function syncRepeatPickerTrigger(pickerEl, pattern, label) { if (!pickerEl) return; const trigger = pickerEl.querySelector(".repeat-style-trigger"); if (trigger) { trigger.querySelector(".rs-mini").className = "rs-mini " + pattern; trigger.querySelector(".rs-label").textContent = label; } pickerEl.querySelectorAll(".rs-option").forEach(o => o.classList.toggle("active", o.dataset.value === pattern)); }
function setActiveRepeatPreset(group, pattern, label) { const settings = group === 'logo' ? logoRepeatSettings : textRepeatSettings; settings.pattern = pattern; settings.label = label; watermarkToolRoot.querySelectorAll(`.repeat-preset[data-group="${group}"]`).forEach(btn => { btn.classList.toggle('active', btn.dataset.pattern === pattern); }); if (group === 'text' && toolbarTextRepeatStyle) { toolbarTextRepeatStyle.value = pattern; syncRepeatPickerTrigger(toolbarTextRepeatStylePicker, pattern, label); } if (group === 'logo' && toolbarLogoRepeatStyle) { toolbarLogoRepeatStyle.value = pattern; syncRepeatPickerTrigger(toolbarLogoRepeatStylePicker, pattern, label); } }
function getQuickPositionValue(state) { const positions = { 'top-left': [0.16, 0.14], 'top-middle': [0.50, 0.14], center: [0.50, 0.50], 'bottom-right': [0.84, 0.86] };
const match = Object.entries(positions).find(([, coords]) => ( Math.abs(state.xRatio - coords[0]) < 0.025 && Math.abs(state.yRatio - coords[1]) < 0.025 )); return match ? match[0] : 'custom'; } function applyToolbarQuickPosition(target, value) { if (!value || value === 'custom') return; if (target === 'text') { quickPosition(textState, value); applyAllOverlays(); openTextToolbar(); } else { quickPosition(imageState, value); applyAllOverlays(); openImageToolbar(); } } function applyToolbarRepeatStyle(group, pattern) { const preset = watermarkToolRoot.querySelector(`.repeat-preset[data-group="${group}"][data-pattern="${pattern}"]`); if (!preset) return; if (group === 'text') repeatWatermark.checked = true; else repeatLogoWatermark.checked = true; repeatGap.value = preset.dataset.gap; setActiveRepeatPreset(group, preset.dataset.pattern, preset.dataset.label); applyAllOverlays(); if (group === 'text') openTextToolbar(); else openImageToolbar(); } async function renderPdfPage() { if (!pdfDoc) return; try { previewStatus.style.display = 'block'; previewStatus.textContent = 'Rendering PDF preview...'; const page = await pdfDoc.getPage(pdfPageIndex); const viewport = page.getViewport({ scale: 1.4 }); const ctx = pdfCanvas.getContext('2d'); pdfCanvas.width = viewport.width; pdfCanvas.height = viewport.height; stage.style.width = `${viewport.width}px`; stage.style.height = `${viewport.height}px`; await page.render({ canvasContext: ctx, viewport }).promise; pageNum.textContent = pdfPageIndex; pageCount.textContent = pdfDoc.numPages; pageJumpInput.value = pdfPageIndex; pageControls.style.display = 'flex'; previewReady = true; previewStatus.style.display = 'none'; applyAllOverlays(); } catch (err) { previewReady = false; previewStatus.style.display = 'block'; previewStatus.textContent = 'PDF preview could not be loaded. Try another PDF.'; console.error(err); } } async function loadPdf(file) { try { previewStatus.style.display = 'block'; previewStatus.textContent = 'Loading PDF...'; currentFileName = file.name.replace(/\.pdf$/i, '') || 'watermarked-pdf'; const rawBuffer = await readFileWithProgress(file, 'pdf', 'Uploading PDF...', 'PDF upload complete'); pdfBytes = new Uint8Array(rawBuffer); await PDFDocument.load(freshPdfBytes(), { ignoreEncryption: true }); pdfDoc = await pdfjsLib.getDocument({ data: freshPdfBytes() }).promise; pdfPageIndex = 1; previewReady = false; pdfFileName.innerHTML = `PDF${escapeHtml(file.name)}`; pdfFileMeta.textContent = `Size: ${formatFileSize(file.size)} | Pages: ${pdfDoc.numPages}`; pdfLabel.textContent = 'Replace PDF File'; removePdfBtn.style.display = 'inline-block'; pdfDropText.style.display = 'none';
await renderPdfPage(); } catch (err) { pdfBytes = null; pdfDoc = null; previewReady = false; pageControls.style.display = 'none'; previewStatus.style.display = 'block'; previewStatus.textContent = 'This PDF is not compatible for editing. Try another PDF.'; pdfFileName.textContent = 'No file selected'; pdfFileMeta.textContent = 'Size: — | Pages: —'; removePdfBtn.style.display = 'none'; console.error(err); alert('This PDF is restricted, damaged, or not compatible for editing.'); } }
async function loadWatermarkImage(file) { try { if (!file.type.startsWith('image/')) return alert('Please upload a valid image file.'); if (file.type === 'image/webp') return alert('WEBP is not supported. Please use PNG or JPG.');
const rawBuffer = await readFileWithProgress(file, 'image', 'Uploading logo...', 'Logo upload complete'); wmImageBytes = new Uint8Array(rawBuffer); wmImageMime = file.type || 'image/png';
if (wmImageObjectUrl) URL.revokeObjectURL(wmImageObjectUrl); wmImageObjectUrl = URL.createObjectURL(file);
imagePreview.src = wmImageObjectUrl; updateLogoThumbUi(file, wmImageObjectUrl); removeLogoBtn.style.display = 'inline-block'; showLogoWatermark.checked = true; applyImageOverlay(); } catch (err) { resetLogoUi(); console.error(err); alert('Could not load watermark image.'); } }
function getPdfFontKey() { const family = (fontFamily.value || 'Arial').toLowerCase(); const bold = textBold.checked; const italic = textItalic.checked;
if (family === 'times new roman' || family === 'georgia' || family === 'lora' || family === 'playfair display' || family === 'merriweather') { if (bold && italic) return PDFLib.StandardFonts.TimesRomanBoldItalic; if (bold) return PDFLib.StandardFonts.TimesRomanBold; if (italic) return PDFLib.StandardFonts.TimesRomanItalic; return PDFLib.StandardFonts.TimesRoman; }
if (family === 'courier new') { if (bold && italic) return PDFLib.StandardFonts.CourierBoldOblique; if (bold) return PDFLib.StandardFonts.CourierBold; if (italic) return PDFLib.StandardFonts.CourierOblique; return PDFLib.StandardFonts.Courier; }
if (bold && italic) return PDFLib.StandardFonts.HelveticaBoldOblique; if (bold) return PDFLib.StandardFonts.HelveticaBold; if (italic) return PDFLib.StandardFonts.HelveticaOblique; return PDFLib.StandardFonts.Helvetica; }
function hexToRgb(hex) { const clean = (hex || '#000000').replace('#', '').trim(); const full = clean.length === 3 ? clean.split('').map(ch => ch + ch).join('') : clean.padEnd(6, '0').slice(0, 6); const int = parseInt(full, 16); return { r: ((int >> 16) & 255) / 255, g: ((int >> 8) & 255) / 255, b: (int & 255) / 255 }; }
function degreesToRadians(deg) { return deg * Math.PI / 180; }
function getPreviewToPdfScale(pageWidth, pageHeight) { return { x: pageWidth / Math.max(1, pdfCanvas.width), y: pageHeight / Math.max(1, pdfCanvas.height) }; }
function getCanvasFontDeclaration(sizePx) { const weight = textBold.checked ? '700' : '400'; const style = textItalic.checked ? 'italic' : 'normal'; return `${style} ${weight} ${sizePx}px ${getPreviewFontStack(fontFamily.value)}`; }
async function ensurePreviewFontLoaded(sizePx, text) { if (!document.fonts?.load) return; const sample = String(text || 'A'); try { await document.fonts.load(getCanvasFontDeclaration(sizePx), sample); await document.fonts.ready; } catch (err) {} }
function blobToUint8Array(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(new Uint8Array(reader.result)); reader.onerror = () => reject(reader.error || new Error('Blob could not be read.')); reader.readAsArrayBuffer(blob); }); }
function isNonLatinScript(text, language) { // Non-Latin scripts that cannot be encoded by PDF WinAnsi standard fonts const nonLatinLanguages = ['arabic', 'hebrew', 'hindi', 'urdu', 'persian', 'japanese', 'chinese']; if (nonLatinLanguages.includes(language)) return true; // Also detect by unicode range for auto-detected scripts if (/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text)) return true; // Arabic/Urdu/Persian if (/[\u0590-\u05FF]/.test(text)) return true; // Hebrew if (/[\u0900-\u097F]/.test(text)) return true; // Devanagari/Hindi if (/[\u3000-\u9FFF\uF900-\uFAFF]/.test(text)) return true; // CJK if (/[\u0400-\u04FF]/.test(text)) return true; // Cyrillic/Russian return false; }
async function createTextWatermarkAsset(pdf) { const text = preparePdfText(wmText.value); const language = languageSelect.value; const sizePt = parseFloat(fontSize.value || 48); const useCanvas = isNonLatinScript(text, language);
if (!useCanvas) { // Latin text: embed a standard PDF font (native searchable text) const fontKey = getPdfFontKey(); const pdfFont = await pdf.embedFont(fontKey); const textWidth = pdfFont.widthOfTextAtSize(text, sizePt); const textHeight = pdfFont.heightAtSize(sizePt); return { type: 'native', font: pdfFont, text, sizePt, textWidth, textHeight }; }
// Non-Latin: render to canvas then embed as PNG (only way to support these scripts in pdf-lib) const sizePx = sizePt; // 1pt ≈ 1px at screen resolution; scale handles the rest const rtl = isRtlLanguage(language) || isLikelyRtlText(text); const qualityScale = 3; const padding = Math.max(10, sizePx * 0.28);
await ensurePreviewFontLoaded(sizePx, text);
const measureCanvas = document.createElement('canvas'); const measureCtx = measureCanvas.getContext('2d'); measureCtx.font = getCanvasFontDeclaration(sizePx); measureCtx.direction = rtl ? 'rtl' : 'ltr'; const metrics = measureCtx.measureText(text); const textWidth = Math.max(1, Math.ceil(metrics.width || sizePx)); const ascent = Math.ceil(metrics.actualBoundingBoxAscent || sizePx * 0.82); const descent = Math.ceil(metrics.actualBoundingBoxDescent || sizePx * 0.28); const underlineExtra = textUnderline.checked ? Math.ceil(sizePx * 0.18) : 0; const logicalWidth = textWidth + Math.ceil(padding * 2); const logicalHeight = ascent + descent + Math.ceil(padding * 2) + underlineExtra;
const canvas = document.createElement('canvas'); canvas.width = Math.max(1, Math.ceil(logicalWidth * qualityScale)); canvas.height = Math.max(1, Math.ceil(logicalHeight * qualityScale));
const ctx = canvas.getContext('2d'); ctx.scale(qualityScale, qualityScale); ctx.clearRect(0, 0, logicalWidth, logicalHeight); ctx.font = getCanvasFontDeclaration(sizePx); ctx.fillStyle = fontColor.value; ctx.textBaseline = 'alphabetic'; ctx.textAlign = rtl ? 'right' : 'left'; ctx.direction = rtl ? 'rtl' : 'ltr';
const textX = rtl ? logicalWidth - padding : padding; const textY = padding + ascent; ctx.fillText(text, textX, textY);
if (textUnderline.checked) { const underlineOffset = Math.max(2, sizePx * 0.1); const underlineThickness = Math.max(1, sizePx * 0.05); const startX = rtl ? logicalWidth - padding - textWidth : padding; const endX = startX + textWidth; const lineY = textY + underlineOffset; ctx.beginPath(); ctx.moveTo(startX, lineY); ctx.lineTo(endX, lineY); ctx.lineWidth = underlineThickness; ctx.strokeStyle = fontColor.value; ctx.stroke(); }
const blob = await new Promise((resolve, reject) => { canvas.toBlob((result) => { if (result) resolve(result); else reject(new Error('Text watermark image could not be created.')); }, 'image/png'); });
const pngBytes = await blobToUint8Array(blob); const embeddedImage = await pdf.embedPng(pngBytes); return { type: 'image', image: embeddedImage, width: logicalWidth, height: logicalHeight, sizePt }; }
function getImageDrawSizeForPdf(img, pageWidth, pageHeight) { const scale = getPreviewToPdfScale(pageWidth, pageHeight); const drawWidth = parseFloat(imageWidth.value || 140) * scale.x; const ratio = img.height / img.width; return { width: drawWidth, height: drawWidth * ratio }; }
function getTextAnchorForPdf(pageWidth, pageHeight) { return { x: textState.xRatio * pageWidth, y: (1 - textState.yRatio) * pageHeight }; }
function getImageAnchorForPdf(pageWidth, pageHeight) { return { x: imageState.xRatio * pageWidth, y: (1 - imageState.yRatio) * pageHeight }; }
function getTextImageDrawSizeForPdf(asset, pageWidth, pageHeight) { const scale = getPreviewToPdfScale(pageWidth, pageHeight); return { width: asset.width * scale.x, height: asset.height * scale.y }; }
function drawSingleTextWatermarkNative(pdf, page, asset, pageWidth, pageHeight, anchorX, anchorY, hyperlink = '') { const scale = getPreviewToPdfScale(pageWidth, pageHeight); const rotationDeg = parseFloat(textRotation.value || 0); const opacity = parseFloat(textOpacity.value || 0.80);
if (asset.type === 'native') { const sizePt = asset.sizePt * Math.min(scale.x, scale.y); const textWidth = asset.font.widthOfTextAtSize(asset.text, sizePt); const textHeight = asset.font.heightAtSize(sizePt); const color = hexToRgb(fontColor.value); const x = anchorX - textWidth / 2; const y = anchorY - textHeight / 2;
page.drawText(asset.text, { x, y, size: sizePt, font: asset.font, color: PDFLib.rgb(color.r, color.g, color.b), opacity, rotate: PDFLib.degrees(rotationDeg) });
if (hyperlink) addHyperlinkAnnotation(pdf, page, getRotatedBoundingRect(x, y, textWidth, textHeight, rotationDeg), hyperlink); return { width: textWidth, height: textHeight };
} else { // Canvas-image fallback for non-Latin scripts const { width, height } = getTextImageDrawSizeForPdf(asset, pageWidth, pageHeight); const x = anchorX - width / 2; const y = anchorY - height / 2;
page.drawImage(asset.image, { x, y, width, height, opacity, rotate: PDFLib.degrees(rotationDeg) });
if (hyperlink) addHyperlinkAnnotation(pdf, page, getRotatedBoundingRect(x, y, width, height, rotationDeg), hyperlink); return { width, height }; } }
function drawRepeatedTextWatermarkNative(pdf, page, asset, pageWidth, pageHeight, hyperlink = '') { const scale = getPreviewToPdfScale(pageWidth, pageHeight); let width, height;
if (asset.type === 'native') { const sizePt = asset.sizePt * Math.min(scale.x, scale.y); width = asset.font.widthOfTextAtSize(asset.text, sizePt); height = asset.font.heightAtSize(sizePt); } else { const dims = getTextImageDrawSizeForPdf(asset, pageWidth, pageHeight); width = dims.width; height = dims.height; }
const gap = parseFloat(repeatGap.value || 120) * scale.x; const { offsetX, offsetY } = getRepeatTextOffset(pageWidth, pageHeight); const positions = getRepeatPositions(pageWidth, pageHeight, width, height, gap, textRepeatSettings, offsetX, -offsetY); const padding = Math.max(width * 0.7, height * 0.7, 20);
for (const pos of positions) { if (pos.x < -padding || pos.x > pageWidth + padding) continue; if (pos.y < -padding || pos.y > pageHeight + padding) continue; drawSingleTextWatermarkNative(pdf, page, asset, pageWidth, pageHeight, pos.x, pos.y, hyperlink); } }
async function embedWatermarkImage(pdf) { if (!wmImageBytes) return null; if (wmImageMime === 'image/png') return await pdf.embedPng(wmImageBytes); if (wmImageMime === 'image/jpeg' || wmImageMime === 'image/jpg') return await pdf.embedJpg(wmImageBytes); throw new Error('Unsupported image format. Use PNG or JPG.'); }
function drawSingleImageWatermark(pdf, page, img, pageWidth, pageHeight, anchorX, anchorY, hyperlink = '') { const { width, height } = getImageDrawSizeForPdf(img, pageWidth, pageHeight); const rotationDeg = parseFloat(imageRotation.value || 0); const x = anchorX - width / 2; const y = anchorY - height / 2;
page.drawImage(img, { x, y, width, height, opacity: parseFloat(imageOpacity.value || 0.80), rotate: PDFLib.degrees(rotationDeg) });
if (hyperlink) addHyperlinkAnnotation(pdf, page, getRotatedBoundingRect(x, y, width, height, rotationDeg), hyperlink); return { width, height }; }
function drawRepeatedImageWatermarks(pdf, page, img, pageWidth, pageHeight, hyperlink = '') { const { width, height } = getImageDrawSizeForPdf(img, pageWidth, pageHeight); const scale = getPreviewToPdfScale(pageWidth, pageHeight); const gap = parseFloat(repeatGap.value || 120) * scale.x; const { offsetX, offsetY } = getRepeatImageOffset(pageWidth, pageHeight); // getRepeatPositions uses top-down Y; flip offsetY for PDF bottom-up coords const positions = getRepeatPositions(pageWidth, pageHeight, width, height, gap, logoRepeatSettings, offsetX, -offsetY); const padding = Math.max(width * 0.7, height * 0.7, 20);
for (const pos of positions) { if (pos.x < -padding || pos.x > pageWidth + padding) continue; if (pos.y < -padding || pos.y > pageHeight + padding) continue; // Flip Y: PDF origin is bottom-left, preview origin is top-left const pdfY = pageHeight - pos.y; drawSingleImageWatermark(pdf, page, img, pageWidth, pageHeight, pos.x, pdfY, hyperlink); } }
async function buildWatermarkedPdfBlob() { if (!pdfBytes) return alert('Please upload a PDF first.'); if (!previewReady) return alert('Please wait for preview.');
const sourcePdf = await PDFDocument.load(freshPdfBytes(), { ignoreEncryption: true }); const outPdf = await PDFDocument.create();
const textPages = parsePageRange(pageRangeMode, pageRange, sourcePdf.getPageCount()); const imagePages = getSelectedImagePages(sourcePdf.getPageCount());
const preparedText = showTextWatermark.checked ? preparePdfText(wmText.value) : ''; const textHyperlink = getWatermarkHyperlink(); const imageHyperlink = getImageWatermarkHyperlink();
const textAsset = preparedText ? await createTextWatermarkAsset(outPdf) : null; const imageEmbed = showLogoWatermark.checked && wmImageBytes ? await embedWatermarkImage(outPdf) : null;
const sourcePages = sourcePdf.getPages(); const embeddedPages = await outPdf.embedPages(sourcePages);
for (let i = 0; i < sourcePages.length; i++) { const pageNumber = i + 1; const sourcePage = sourcePages[i]; const embeddedPage = embeddedPages[i]; const { width, height } = sourcePage.getSize(); const page = outPdf.addPage([width, height]); const shouldDrawImageOnPage = imagePages.has(pageNumber) && imageEmbed; const keepFirstPageLogoVisible = pageNumber === 1; if (shouldDrawImageOnPage && !imageState.overPdf && !keepFirstPageLogoVisible) { if (repeatLogoWatermark.checked) { drawRepeatedImageWatermarks(outPdf, page, imageEmbed, width, height, imageHyperlink); } else { const anchor = getImageAnchorForPdf(width, height); drawSingleImageWatermark(outPdf, page, imageEmbed, width, height, anchor.x, anchor.y, imageHyperlink); } } page.drawPage(embeddedPage, { x: 0, y: 0, width, height }); if (textPages.has(pageNumber) && textAsset && preparedText) { if (repeatWatermark.checked) { drawRepeatedTextWatermarkNative(outPdf, page, textAsset, width, height, textHyperlink); } else { const anchor = getTextAnchorForPdf(width, height); drawSingleTextWatermarkNative(outPdf, page, textAsset, width, height, anchor.x, anchor.y, textHyperlink); } } if (shouldDrawImageOnPage && (imageState.overPdf || keepFirstPageLogoVisible)) { if (repeatLogoWatermark.checked) { drawRepeatedImageWatermarks(outPdf, page, imageEmbed, width, height, imageHyperlink); } else { const anchor = getImageAnchorForPdf(width, height); drawSingleImageWatermark(outPdf, page, imageEmbed, width, height, anchor.x, anchor.y, imageHyperlink); } } } const outBytes = await outPdf.save(); return new Blob([outBytes], { type: 'application/pdf' }); } async function generateWatermarkedPdf() { try { setGenerateLoading(true); actionStatus.textContent = 'Preparing watermark with current preview and PDF placement. Please wait...'; const blob = await buildWatermarkedPdfBlob(); if (!blob) return; generatedPdfBlob = blob; if (generatedPdfUrl) URL.revokeObjectURL(generatedPdfUrl); generatedPdfUrl = URL.createObjectURL(blob); downloadBtn.style.display = 'inline-block'; actionStatus.textContent = 'Watermarked PDF is ready. Review the preview above and click Download PDF.'; } catch (err) { console.error(err); const msg = String((err && err.message) || err || ''); if (msg.includes('Invalid hyperlink')) { alert('Invalid hyperlink. Use a full URL like https://example.com, or mailto:someone@example.com.'); } else { alert(`Error while creating watermarked PDF: ${msg}`); } actionStatus.textContent = 'Could not prepare the watermarked PDF. Please review your settings and try again.'; } finally { setGenerateLoading(false); } } function downloadGeneratedPdf() { if (!generatedPdfBlob || !generatedPdfUrl) return alert('Click Add Watermark first.'); const a = document.createElement('a'); a.href = generatedPdfUrl; a.download = `${currentFileName || 'watermarked-pdf'}-watermarked.pdf`; document.body.appendChild(a); a.click(); a.remove(); } function clientPointFromEvent(e) { if (e.touches && e.touches[0]) return { x: e.touches[0].clientX, y: e.touches[0].clientY }; if (e.changedTouches && e.changedTouches[0]) return { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY }; return { x: e.clientX, y: e.clientY }; } function openForTarget(target) { closeToolbars(); if (target === 'text') openTextToolbar(); if (target === 'image') openImageToolbar(); } function clampState(state) { state.xRatio = Math.max(0.02, Math.min(0.98, state.xRatio)); state.yRatio = Math.max(0.02, Math.min(0.98, state.yRatio)); } function startDrag(target, e) { if (!previewReady) return; const point = clientPointFromEvent(e); const isRepeat = target === 'text' ? repeatWatermark.checked : repeatLogoWatermark.checked; const state = target === 'text' ? (isRepeat ? textRepeatState : textState) : imageState; let centerX, centerY; if (isRepeat) { const rect = getCanvasRect(); centerX = rect.left + state.xRatio * rect.width; centerY = rect.top + state.yRatio * rect.height; } else { const overlay = target === 'text' ? textOverlay : imageOverlay; const overlayRect = overlay.getBoundingClientRect(); centerX = overlayRect.left + overlayRect.width / 2; centerY = overlayRect.top + overlayRect.height / 2; } activeDrag = target; dragOffsetX = point.x - centerX; dragOffsetY = point.y - centerY; openForTarget(target); e.preventDefault(); } function startResize(target, e) { if (!previewReady) return; if (target === 'image' && repeatLogoWatermark.checked) return; const point = clientPointFromEvent(e); const center = getCenterFromState(target === 'text' ? textState : imageState); resizeStartDistance = Math.hypot(point.x - center.x, point.y - center.y) || 1; resizeStartValue = target === 'text' ? parseFloat(fontSize.value || 48) : parseFloat(imageWidth.value || 140); activeResize = target; openForTarget(target); e.preventDefault(); e.stopPropagation(); } function startRotate(target, e) { if (!previewReady) return; if (target === 'image' && repeatLogoWatermark.checked) return; activeRotate = target; openForTarget(target); e.preventDefault(); e.stopPropagation(); } function handlePointerMove(e) { if (!previewReady) return; const point = clientPointFromEvent(e); const rect = getCanvasRect(); if (activeDrag) { const isRepeat = activeDrag === 'text' ? repeatWatermark.checked : repeatLogoWatermark.checked; const state = activeDrag === 'text' ? (isRepeat ? textRepeatState : textState) : imageState; const localX = point.x - rect.left - dragOffsetX; const localY = point.y - rect.top - dragOffsetY; state.xRatio = localX / rect.width; state.yRatio = localY / rect.height; clampState(state); if (activeDrag === 'text') applyTextOverlay(); else applyImageOverlay(); e.preventDefault(); return; } if (activeResize) { const center = getCenterFromState(activeResize === 'text' ? textState : imageState); const distance = Math.hypot(point.x - center.x, point.y - center.y) || 1; const ratio = distance / resizeStartDistance; if (activeResize === 'text') { fontSize.value = String(Math.max(8, Math.min(300, Math.round(resizeStartValue * ratio)))); applyAllOverlays(); } else { imageWidth.value = String(Math.max(20, Math.min(500, Math.round(resizeStartValue * ratio)))); applyAllOverlays(); } e.preventDefault(); return; } if (activeRotate) { const state = activeRotate === 'text' ? textState : imageState; const center = getCenterFromState(state); const angle = Math.atan2(point.y - center.y, point.x - center.x) * 180 / Math.PI + 90; if (activeRotate === 'text') { textRotation.value = String(Math.round(angle)); applyTextOverlay(); } else { imageRotation.value = String(Math.round(angle)); applyImageOverlay(); } e.preventDefault(); } } function endPointerAction() { activeDrag = null; activeResize = null; activeRotate = null; } async function jumpToPage() { if (!pdfDoc) return; const total = pdfDoc.numPages; const requested = parseInt(pageJumpInput.value, 10); if (!Number.isFinite(requested) || requested < 1 || requested > total) { alert(`Enter a page number between 1 and ${total}.`); pageJumpInput.value = pdfPageIndex; return; } pdfPageIndex = requested; await renderPdfPage(); }
pdfInput.addEventListener('change', async (e) => { const file = e.target.files?.[0]; if (file) { await loadPdf(file); // Reset input value so the same file can be re-uploaded after removal pdfInput.value = ''; } });
wmImageInput.addEventListener('change', async (e) => { const file = e.target.files?.[0]; if (file) { await loadWatermarkImage(file); wmImageInput.value = ''; } });
['dragenter', 'dragover'].forEach(eventName => { pdfDropArea.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); pdfDropArea.classList.add('dragover'); }); });
['dragleave', 'drop'].forEach(eventName => { pdfDropArea.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); pdfDropArea.classList.remove('dragover'); }); });
pdfDropArea.addEventListener('drop', async (e) => { const file = e.dataTransfer?.files?.[0]; if (!file) return; if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { alert('Please drop a valid PDF file.'); return; } await loadPdf(file); });
removePdfBtn.addEventListener('click', resetPdfUi); removeLogoBtn.addEventListener('click', removeLogoWatermark);
pageRangeMode.addEventListener('change', () => { updatePageRangeUi(pageRangeMode, pageRange, pageRangeHelper, 'text'); applyAllOverlays(); }); imagePageRangeMode.addEventListener('change', () => { updatePageRangeUi(imagePageRangeMode, imagePageRange, imagePageRangeHelper, 'image'); applyAllOverlays(); });
updatePageRangeUi(pageRangeMode, pageRange, pageRangeHelper, 'text'); updatePageRangeUi(imagePageRangeMode, imagePageRange, imagePageRangeHelper, 'image');
[ showTextWatermark, showLogoWatermark, wmText, enableTextLink, wmTextLink, enableImageLink, wmImageLink, fontSize, fontFamily, fontColor, textRotation, textBold, textItalic, textUnderline, textOpacity, imageWidth, imageRotation, imageOpacity, languageSelect, repeatWatermark, repeatLogoWatermark, repeatGap, pageRangeMode, pageRange, imagePageRangeMode, imagePageRange ].forEach(el => { el.addEventListener('input', applyAllOverlays); el.addEventListener('change', applyAllOverlays); });
languageSwitch.querySelectorAll('[data-language]').forEach(btn => { btn.addEventListener('click', () => { languageSelect.value = btn.dataset.language; syncLanguageAndFonts(btn.dataset.language, fontFamily.value); openTextToolbar(); }); });
extraLanguageSelect.addEventListener('change', () => { if (!extraLanguageSelect.value) return; languageSelect.value = extraLanguageSelect.value; syncLanguageAndFonts(extraLanguageSelect.value, fontFamily.value); openTextToolbar(); });
languageSelect.addEventListener('change', () => { syncLanguageAndFonts(languageSelect.value, fontFamily.value); openTextToolbar(); });
fontFamily.addEventListener('change', () => { updateSelectPreviewStyle(fontFamily, fontFamily.value); toolbarFont.value = fontFamily.value; updateSelectPreviewStyle(toolbarFont, toolbarFont.value); applyAllOverlays(); openTextToolbar(); });
fontSizeRange.addEventListener('input', () => { fontSize.value = fontSizeRange.value; applyAllOverlays(); });
imageWidthRange.addEventListener('input', () => { imageWidth.value = imageWidthRange.value; applyAllOverlays(); });
watermarkToolRoot.querySelectorAll('.quickPos').forEach(btn => { btn.addEventListener('click', () => { if (btn.dataset.target === 'text') quickPosition(textState, btn.dataset.pos); else quickPosition(imageState, btn.dataset.pos); applyAllOverlays(); }); });
watermarkToolRoot.querySelectorAll('.repeat-preset').forEach(btn => { btn.addEventListener('click', () => { const group = btn.dataset.group; if (group === 'text') repeatWatermark.checked = true; else repeatLogoWatermark.checked = true;
repeatGap.value = btn.dataset.gap; setActiveRepeatPreset(group, btn.dataset.pattern, btn.dataset.label); applyAllOverlays(); }); });
watermarkToolRoot.querySelectorAll('.repeat-chip').forEach(btn => { btn.addEventListener('click', () => { const group = btn.dataset.group; if (group === 'text') repeatWatermark.checked = true; else repeatLogoWatermark.checked = true;
repeatGap.value = btn.dataset.gapOnly; applyAllOverlays(); }); });
toolbarTextQuickPosition.addEventListener('change', () => { applyToolbarQuickPosition('text', toolbarTextQuickPosition.value); });
toolbarLogoQuickPosition.addEventListener('change', () => { applyToolbarQuickPosition('logo', toolbarLogoQuickPosition.value); });
toolbarLogoPageRange.addEventListener('change', () => { imagePageRangeMode.value = toolbarLogoPageRange.value; updatePageRangeUi(imagePageRangeMode, imagePageRange, imagePageRangeHelper, 'image'); applyAllOverlays(); openImageToolbar(); });
toolbarTextRepeatToggle.addEventListener('change', () => { repeatWatermark.checked = toolbarTextRepeatToggle.checked; applyAllOverlays(); openTextToolbar(); });
toolbarLogoRepeatToggle.addEventListener('change', () => { repeatLogoWatermark.checked = toolbarLogoRepeatToggle.checked; applyAllOverlays(); openImageToolbar(); });
toolbarTextRepeatStyle.addEventListener('change', () => { applyToolbarRepeatStyle('text', toolbarTextRepeatStyle.value); });
toolbarLogoRepeatStyle.addEventListener('change', () => { applyToolbarRepeatStyle('logo', toolbarLogoRepeatStyle.value); });
generateBtn.addEventListener('click', generateWatermarkedPdf); downloadBtn.addEventListener('click', downloadGeneratedPdf);
toolbarBoldBtn.addEventListener('click', () => { textBold.checked = !textBold.checked; applyAllOverlays(); openTextToolbar(); }); toolbarItalicBtn.addEventListener('click', () => { textItalic.checked = !textItalic.checked; applyAllOverlays(); openTextToolbar(); }); toolbarUnderlineBtn.addEventListener('click', () => { textUnderline.checked = !textUnderline.checked; applyAllOverlays(); openTextToolbar(); });
toolbarColor.addEventListener('input', () => { fontColor.value = toolbarColor.value; applyAllOverlays(); openTextToolbar(); });
toolbarLanguage.addEventListener('change', () => { languageSelect.value = toolbarLanguage.value; syncLanguageAndFonts(toolbarLanguage.value, fontFamily.value); openTextToolbar(); });
toolbarFont.addEventListener('change', () => { fontFamily.value = toolbarFont.value; updateSelectPreviewStyle(fontFamily, fontFamily.value); updateSelectPreviewStyle(toolbarFont, toolbarFont.value); applyAllOverlays(); openTextToolbar(); });
toolbarSize.addEventListener('input', () => { fontSize.value = toolbarSize.value || '48'; applyAllOverlays(); openTextToolbar(); });
toolbarSizeRange.addEventListener('input', () => { fontSize.value = toolbarSizeRange.value; applyAllOverlays(); openTextToolbar(); });
toolbarOpacity.addEventListener('input', () => { textOpacity.value = toolbarOpacity.value; applyTextOverlay(); openTextToolbar(); });
if (toolbarCenterBtn) { toolbarCenterBtn.addEventListener('click', () => { textState.xRatio = 0.5; textState.yRatio = 0.15; applyAllOverlays(); openTextToolbar(); }); }
toolbarRemoveTextBtn.addEventListener('click', removeTextWatermark);
toolbarImageOpacity.addEventListener('input', () => { imageOpacity.value = toolbarImageOpacity.value; applyImageOverlay(); openImageToolbar(); });
toolbarImageSize.addEventListener('input', () => { imageWidth.value = toolbarImageSize.value || '140'; applyImageOverlay(); openImageToolbar(); });
toolbarImageSizeRange.addEventListener('input', () => { imageWidth.value = toolbarImageSizeRange.value; applyImageOverlay(); openImageToolbar(); });
toolbarImageLayerBtn.addEventListener('click', () => { imageLayerMode.value = imageLayerMode.value === 'belowPdf' ? 'overPdf' : 'belowPdf'; applyImageOverlay(); openImageToolbar(); });
toolbarRemoveImageBtn.addEventListener('click', removeLogoWatermark);
resetTextPosBtn.addEventListener('click', () => { textState.xRatio = 0.5; textState.yRatio = 0.15; applyAllOverlays(); });
resetImagePosBtn.addEventListener('click', () => { imageState.xRatio = 0.5; imageState.yRatio = 0.5; applyAllOverlays(); });
prevPage.addEventListener('click', async () => { if (!pdfDoc || pdfPageIndex <= 1) return; pdfPageIndex--; await renderPdfPage(); }); nextPage.addEventListener('click', async () => { if (!pdfDoc || pdfPageIndex >= pdfDoc.numPages) return; pdfPageIndex++; await renderPdfPage(); });
pageJumpBtn.addEventListener('click', jumpToPage); pageJumpInput.addEventListener('keydown', async (e) => { if (e.key === 'Enter') { e.preventDefault(); await jumpToPage(); } });
wmText.addEventListener('input', () => { let detectedLanguage = languageSelect.value; if (/[\u0590-\u05FF]/.test(wmText.value)) detectedLanguage = 'hebrew'; else if (/[\u0900-\u097F]/.test(wmText.value)) detectedLanguage = 'hindi'; else if (containsArabic(wmText.value)) { detectedLanguage = ['arabic', 'urdu', 'persian'].includes(languageSelect.value) ? languageSelect.value : 'arabic'; } else { detectedLanguage = 'english'; }
if (detectedLanguage !== languageSelect.value) { syncLanguageAndFonts(detectedLanguage, fontFamily.value); } if (wmText.value.trim()) showTextWatermark.checked = true; if (!textVisual.classList.contains('editing')) applyTextOverlay(); });
wmTextLink.addEventListener('input', applyTextOverlay); wmImageLink.addEventListener('input', applyImageOverlay);
enableTextLink.addEventListener('change', () => { syncHyperlinkUi(); applyTextOverlay(); });
enableImageLink.addEventListener('change', () => { syncHyperlinkUi(); applyImageOverlay(); });
textOverlay.addEventListener('pointerdown', (e) => { if (textVisual.classList.contains('editing') || !showTextWatermark.checked) return; if (e.target === textRotateHandle || e.target === textResizeHandle) return; startDrag('text', e); });
imageOverlay.addEventListener('pointerdown', (e) => { if (!showLogoWatermark.checked || repeatLogoWatermark.checked) return; if (e.target === imageRotateHandle || e.target === imageResizeHandle) return; startDrag('image', e); });
repeatImageLayer.addEventListener('pointerdown', (e) => { if (!showLogoWatermark.checked || !repeatLogoWatermark.checked || repeatImageLayer.classList.contains('hidden')) return; startDrag('image', e); });
repeatTextLayer.addEventListener('pointerdown', (e) => { if (!showTextWatermark.checked || !repeatWatermark.checked || repeatTextLayer.classList.contains('hidden')) return; startDrag('text', e); });
textOverlay.addEventListener('click', (e) => { if (textToolbar.contains(e.target)) return; openTextToolbar(); // On touch/mobile, a single tap also triggers inline text editing for convenience if (window.matchMedia('(max-width:600px)').matches && !textVisual.classList.contains('editing')) { startInlineTextEdit(); } });
imageOverlay.addEventListener('click', (e) => { if (imageToolbar.contains(e.target)) return; openImageToolbar(); });
repeatImageLayer.addEventListener('click', (e) => { if (imageToolbar.contains(e.target)) return; openImageToolbar(); });
repeatTextLayer.addEventListener('click', (e) => { if (textToolbar.contains(e.target)) return; openTextToolbar(); });
textRotateHandle.addEventListener('pointerdown', (e) => startRotate('text', e)); imageRotateHandle.addEventListener('pointerdown', (e) => startRotate('image', e)); textResizeHandle.addEventListener('pointerdown', (e) => startResize('text', e)); imageResizeHandle.addEventListener('pointerdown', (e) => startResize('image', e));
textPreview.addEventListener('dblclick', (e) => { e.stopPropagation(); startInlineTextEdit(); });
textPreview.addEventListener('blur', () => { if (textVisual.classList.contains('editing')) stopInlineTextEdit(); });
textPreview.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); textPreview.blur(); } if (e.key === 'Escape') { e.preventDefault(); textPreview.textContent = wmText.value || ''; textPreview.blur(); } });
function keepToolbarInteractive(toolbar, reopenFn) { ['pointerdown', 'mousedown', 'touchstart', 'click'].forEach(evtName => { toolbar.addEventListener(evtName, (e) => { e.stopPropagation(); reopenFn(); }, { passive: false }); }); }
keepToolbarInteractive(textToolbar, openTextToolbar); keepToolbarInteractive(imageToolbar, openImageToolbar);
document.addEventListener('pointermove', handlePointerMove, { passive: false }); document.addEventListener('pointerup', endPointerAction); document.addEventListener('pointercancel', endPointerAction);
stage.addEventListener('pointerdown', (e) => { if (textToolbar.contains(e.target)) { openTextToolbar(); return; } if (imageToolbar.contains(e.target)) { openImageToolbar(); return; }
const insideText = textOverlay.contains(e.target) || repeatTextLayer.contains(e.target); const insideImage = imageOverlay.contains(e.target); const insideRepeatLayer = repeatImageLayer.contains(e.target);
if (!insideText && !insideImage && !insideRepeatLayer) closeToolbars(); else if (insideText && !insideImage && !insideRepeatLayer) { closeToolbars(); openTextToolbar(); } else if ((insideImage || insideRepeatLayer) && !insideText) { closeToolbars(); openImageToolbar(); } });
window.addEventListener('resize', () => { applyAllOverlays(); if (textToolbar.classList.contains('open')) positionTextToolbar(); if (imageToolbar.classList.contains('open')) positionImageToolbar(); });
function applyTextOverlay() { textState.rotation = parseFloat(textRotation.value || 0); textState.size = parseFloat(fontSize.value || 48); textState.opacity = parseFloat(textOpacity.value || 0.80); textState.language = languageSelect.value; textState.overPdf = textLayerMode.value !== 'belowPdf';
const nextText = preparePdfText(wmText.value); applyTextDirection(nextText); if (document.activeElement !== textPreview) textPreview.textContent = nextText;
textOverlay.style.left = `${textState.xRatio * pdfCanvas.width}px`; textOverlay.style.top = `${textState.yRatio * pdfCanvas.height}px`; textOverlay.style.transform = `translate(-50%, -50%) rotate(${textState.rotation}deg)`;
textPreview.style.fontSize = `${textState.size}px`; textPreview.style.fontFamily = getPreviewFontStack(fontFamily.value); textPreview.style.color = fontColor.value; textPreview.style.fontWeight = textBold.checked ? '700' : '400'; textPreview.style.fontStyle = textItalic.checked ? 'italic' : 'normal'; const liveLink = showTextWatermark.checked && enableTextLink.checked ? normalizeHyperlink(wmTextLink.value) : ''; textPreview.style.textDecoration = textUnderline.checked ? 'underline' : (liveLink ? 'underline dotted' : 'none'); textPreview.style.opacity = String(textState.opacity); textPreview.style.cursor = liveLink ? 'pointer' : 'move'; textPreview.title = liveLink || ''; textVisual.classList.toggle('has-link', !!liveLink); textOverlay.style.zIndex = textState.overPdf ? '18' : '8'; textOverlay.style.mixBlendMode = textState.overPdf ? 'normal' : 'multiply';
// Show single overlay only when NOT in repeat mode if (previewReady && showTextWatermark.checked && nextText && isCurrentPageSelectedForText() && !repeatWatermark.checked) { textOverlay.classList.remove('hidden'); } else { textOverlay.classList.add('hidden'); // Only close toolbar if NOT in repeat mode — repeat mode keeps toolbar open if (!repeatWatermark.checked) { textToolbarShell.classList.remove('open'); textToolbar.classList.remove('open'); } }
// In repeat mode, ensure toolbar stays open/visible as a standalone toolbar if (previewReady && showTextWatermark.checked && nextText && isCurrentPageSelectedForText() && repeatWatermark.checked) { textToolbarShell.classList.add('open'); textToolbar.classList.add('open'); }
syncTextToolbar(); renderRepeatedTextPreview(); positionTextToolbar(); }
function applyImageOverlay() { imageState.rotation = parseFloat(imageRotation.value || 0); imageState.width = parseFloat(imageWidth.value || 140); imageState.opacity = parseFloat(imageOpacity.value || 0.80); imageState.overPdf = imageLayerMode.value !== 'belowPdf';
imageOverlay.style.left = `${imageState.xRatio * pdfCanvas.width}px`; imageOverlay.style.top = `${imageState.yRatio * pdfCanvas.height}px`; imageOverlay.style.transform = `translate(-50%, -50%) rotate(${imageState.rotation}deg)`; imagePreview.style.width = `${imageState.width}px`; imagePreview.style.height = 'auto'; imagePreview.style.opacity = String(imageState.opacity);
if (imageState.overPdf) { imageOverlay.style.zIndex = '20'; imageOverlay.style.mixBlendMode = 'normal'; } else { imageOverlay.style.zIndex = '12'; imageOverlay.style.mixBlendMode = 'multiply'; }
const liveImageLink = showLogoWatermark.checked && enableImageLink.checked ? normalizeHyperlink(wmImageLink.value) : ''; imageVisual.classList.toggle('has-link', !!liveImageLink); imageVisual.style.cursor = liveImageLink ? 'pointer' : 'move'; imageVisual.title = liveImageLink || '';
if (wmImageBytes && previewReady && showLogoWatermark.checked && !repeatLogoWatermark.checked && isCurrentPageSelectedForImage()) { imageOverlay.classList.remove('hidden'); } else { imageOverlay.classList.add('hidden'); }
if (!(wmImageBytes && previewReady && showLogoWatermark.checked && isCurrentPageSelectedForImage())) { imageToolbarShell.classList.remove('open'); imageToolbar.classList.remove('open'); }
syncImageToolbar(); renderRepeatedImagePreview(); positionImageToolbar(); }
async function buildWatermarkedPdfBlob() { if (!pdfBytes) return alert('Please upload a PDF first.'); if (!previewReady) return alert('Please wait for preview.');
const sourcePdf = await PDFDocument.load(freshPdfBytes(), { ignoreEncryption: true }); const outPdf = await PDFDocument.create();
const textPages = parsePageRange(pageRangeMode, pageRange, sourcePdf.getPageCount()); const imagePages = getSelectedImagePages(sourcePdf.getPageCount());
const preparedText = showTextWatermark.checked ? preparePdfText(wmText.value) : ''; const textHyperlink = getWatermarkHyperlink(); const imageHyperlink = getImageWatermarkHyperlink();
const textAsset = preparedText ? await createTextWatermarkAsset(outPdf) : null; const imageEmbed = showLogoWatermark.checked && wmImageBytes ? await embedWatermarkImage(outPdf) : null;
const sourcePages = sourcePdf.getPages(); const embeddedPages = await outPdf.embedPages(sourcePages);
for (let i = 0; i < sourcePages.length; i++) { const pageNumber = i + 1; const sourcePage = sourcePages[i]; const embeddedPage = embeddedPages[i]; const { width, height } = sourcePage.getSize(); const page = outPdf.addPage([width, height]); const shouldDrawImageOnPage = imagePages.has(pageNumber) && imageEmbed; const keepFirstPageLogoVisible = pageNumber === 1; if (textPages.has(pageNumber) && textAsset && preparedText && !textState.overPdf) { if (repeatWatermark.checked) { drawRepeatedTextWatermarkNative(outPdf, page, textAsset, width, height, textHyperlink); } else { const anchor = getTextAnchorForPdf(width, height); drawSingleTextWatermarkNative(outPdf, page, textAsset, width, height, anchor.x, anchor.y, textHyperlink); } } if (shouldDrawImageOnPage && !imageState.overPdf && !keepFirstPageLogoVisible) { if (repeatLogoWatermark.checked) { drawRepeatedImageWatermarks(outPdf, page, imageEmbed, width, height, imageHyperlink); } else { const anchor = getImageAnchorForPdf(width, height); drawSingleImageWatermark(outPdf, page, imageEmbed, width, height, anchor.x, anchor.y, imageHyperlink); } } page.drawPage(embeddedPage, { x: 0, y: 0, width, height }); if (textPages.has(pageNumber) && textAsset && preparedText && textState.overPdf) { if (repeatWatermark.checked) { drawRepeatedTextWatermarkNative(outPdf, page, textAsset, width, height, textHyperlink); } else { const anchor = getTextAnchorForPdf(width, height); drawSingleTextWatermarkNative(outPdf, page, textAsset, width, height, anchor.x, anchor.y, textHyperlink); } } if (shouldDrawImageOnPage && (imageState.overPdf || keepFirstPageLogoVisible)) { if (repeatLogoWatermark.checked) { drawRepeatedImageWatermarks(outPdf, page, imageEmbed, width, height, imageHyperlink); } else { const anchor = getImageAnchorForPdf(width, height); drawSingleImageWatermark(outPdf, page, imageEmbed, width, height, anchor.x, anchor.y, imageHyperlink); } } } const outBytes = await outPdf.save(); return new Blob([outBytes], { type: 'application/pdf' }); } textLayerMode.addEventListener('change', () => { applyTextOverlay(); if (showTextWatermark.checked) openTextToolbar(); });
imageLayerMode.addEventListener('change', () => { applyImageOverlay(); if (showLogoWatermark.checked) openImageToolbar(); });
toolbarTextLayerBtn.addEventListener('click', () => { textLayerMode.value = textLayerMode.value === 'belowPdf' ? 'overPdf' : 'belowPdf'; applyTextOverlay(); openTextToolbar(); });
textToolbarMoreBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleToolbarExpanded('text', true); openTextToolbar(); });
if (textToolbarLessBtn) { textToolbarLessBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleToolbarExpanded('text', false); openTextToolbar(); }); }
if (imageToolbarMoreBtn) { imageToolbarMoreBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleToolbarExpanded('image', true); openImageToolbar(); }); }
if (imageToolbarLessBtn) { imageToolbarLessBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleToolbarExpanded('image', false); openImageToolbar(); }); }
textToolbarCloseBtn.addEventListener('click', (e) => { e.stopPropagation(); closeToolbars(); });
imageToolbarCloseBtn.addEventListener('click', (e) => { e.stopPropagation(); closeToolbars(); });
setActiveRepeatPreset('text', 'grid', 'Classic Grid'); setActiveRepeatPreset('logo', 'grid', 'Classic Grid'); applyAllOverlays(); })();