// Fixed server-side code. WatermarkInput is decoded from the job's structured JSON data. builderJS.OpenFile(WatermarkInput.sourceUrl); var outputFormat = 'docx'; var outputName = 'Watermark.docx'; try { var doc = Api.GetDocument(); if (typeof doc.AddDrawingToPage !== 'function') throw new Error('UNSUPPORTED_VERSION'); var pageCount = doc.GetPageCount(); var plans = []; var pageAnchors = {}; var endPosition = doc.GetRange().GetEndPos(); function pageAnchor(page) { if (pageAnchors[page]) return pageAnchors[page]; // GoToPage can leave the cursor on an earlier page of the same paragraph. // Locate the first text position on the physical page independently of that cursor. var low = 0; var high = endPosition; while (low < high) { var middle = Math.floor((low + high) / 2); if (doc.GetRange(middle, middle).GetStartPage() < page - 1) low = middle + 1; else high = middle; } var range = doc.GetRange(low, low); if (range.GetStartPage() !== page - 1) throw new Error('PAGE_ANCHOR_NOT_FOUND|' + page); pageAnchors[page] = { range: range, position: low }; return pageAnchors[page]; } WatermarkInput.overlays.forEach(function (overlay, groupIndex) { overlay.pages.forEach(function (page) { // The stored Word page count can differ from OnlyOffice pagination. // Ignore watermark targets that do not exist in the rendered document. if (page > pageCount) return; var anchor = pageAnchor(page); if (!anchor.range.MoveCursorToPos(anchor.position)) throw new Error('PAGE_ANCHOR_NOT_FOUND|' + page); var paragraph = doc.GetCurrentParagraph(); var section = paragraph && paragraph.GetSection(); if (!section) throw new Error('PAGE_SECTION_NOT_FOUND|' + page); var width = section.GetPageWidth() * 25.4 / 1440; var height = section.GetPageHeight() * 25.4 / 1440; var variant = overlay.variants.find(function (item) { return Math.abs(item.widthMm - width) < 0.2 && Math.abs(item.heightMm - height) < 0.2; }); if (!variant) throw new Error('PAGE_SIZE_NOT_FOUND|' + page); plans.push({ page: page, group: groupIndex, variant: variant, position: anchor.position }); }); }); var tracked = doc.IsTrackRevisions(); doc.SetTrackRevisions(false); // Earlier character positions remain stable when drawings are inserted from the last page. plans.sort(function (left, right) { return right.page - left.page; }); plans.forEach(function (plan) { if (doc.GetPageCount() !== pageCount) throw new Error('PAGINATION_CHANGED'); if (!doc.GetRange(plan.position, plan.position).MoveCursorToPos(plan.position)) { throw new Error('PAGE_ANCHOR_NOT_FOUND|' + plan.page); } var variant = plan.variant; var drawingWidthMm = variant.drawingWidthMm || variant.widthMm; var drawingHeightMm = variant.drawingHeightMm || variant.heightMm; var offsetXMm = variant.offsetXMm || 0; var offsetYMm = variant.offsetYMm || 0; var drawing = Api.CreateImage('data:image/png;base64,' + variant.pngBase64, Math.round(drawingWidthMm * 36000), Math.round(drawingHeightMm * 36000)); drawing.SetName(WatermarkInput.markerPrefix + '_' + plan.group + '_' + plan.page); drawing.SetWrappingStyle('behind'); if (!doc.AddDrawingToPage(drawing, plan.page - 1, Math.round(offsetXMm * 36000), Math.round(offsetYMm * 36000))) { throw new Error('INSERT_FAILED|' + plan.page); } }); doc.SetTrackRevisions(tracked); if (doc.GetPageCount() !== pageCount) throw new Error('PAGINATION_CHANGED'); } catch (error) { // Return a bounded diagnostic as a separate artifact, never a partly watermarked document. var failedDoc = Api.GetDocument(); failedDoc.SetTrackRevisions(false); failedDoc.RemoveAllElements(); var diagnostic = Api.CreateParagraph(); diagnostic.AddText('DMS_WATERMARK_ERROR:' + String(error.message).slice(0, 300)); failedDoc.Push(diagnostic); outputFormat = 'txt'; outputName = 'WatermarkError.txt'; } builderJS.SaveFile(outputFormat, outputName); builderJS.CloseFile();