Add Multi-Row Text and Image Watermarks to Word Documents with JavaScript in React

Microsoft Word's built-in watermark feature can place only one watermark at a time, and its position and number are fixed by the built-in layout, so it cannot tile the whole page with multiple rows and columns. For contracts, internal documents and presentation drafts that need full-page coverage, the watermark can be implemented by adding a shape or a picture to the header and then cloning it by row and column.

This article adds multi-row, multi-column text watermarks and image watermarks to Word documents in the browser with Spire.Doc for JavaScript. The whole process runs in the browser on WebAssembly, requires no Office installation and depends on no backend service, and the file always remains on the user's device.

This article covers the following topics:

The two kinds of watermarks are implemented as follows:

Watermark type Carrier object How it is created Key properties
Text watermark WordArt shape new ShapeObject(doc, ShapeType.TextPlainText) WordArt.Text, WordArt.FontFamily, Rotation, FillColor
Image watermark Picture object new DocPicture(doc) with LoadImage() TextWrappingStyle, Width, Height

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume that Spire.Doc is installed and the WebAssembly module is initialized. The examples read Sample.docx and logo.png from the project's public/data directory and the font file from the public/font directory, so put the corresponding files into these two directories before running them.


Add Multi-Row Text Watermarks

To add a multi-row, multi-column text watermark, a WordArt shape is created in the header, then cloned in a loop over rows and columns, and each copy is given its own vertical and horizontal coordinates. The watermark object lives in the header, so it is repeated on every page together with the header and does not have to be added page by page.

The following members are involved in adding multi-row text watermarks:

Member Purpose
ShapeObject Shape object that carries the watermark text
ShapeType.TextPlainText Sets the shape to ordinary WordArt
ShapeObject.Width, Height Width and height of the shape, in points
ShapeObject.VerticalPosition, HorizontalPosition Vertical and horizontal coordinates of the shape on the page
ShapeObject.Rotation Rotation angle, in degrees
WordArt.Text, WordArt.FontFamily WordArt content and font
ShapeObject.FillColor, StrokeColor Text fill color and outline color
ShapeObject.Clone() Clones the shape for tiling
HeaderFooter.AddParagraph() Adds a paragraph in the header to carry the watermark
Paragraph.ChildObjects.Add() Adds the shape to the header paragraph

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the sample document and the font into the virtual file system (VFS), create a Document object and load the document.
  3. Create a ShapeObject, set its shape type to ShapeType.TextPlainText, and set its width, height, rotation angle, WordArt content, font, fill color and outline color.
  4. Iterate over every section of the document, get the header through HeadersFooters.Header, and add a paragraph with AddParagraph().
  5. Clone the shape with Clone() in a loop over rows and columns, give each copy its own VerticalPosition and HorizontalPosition, and add it to the ChildObjects of the header paragraph.
  6. Save the result file with SaveToFile(), read it from the VFS and trigger a browser download.

The code for adding multi-row text watermarks is as follows:

function App() {
  const addTextWatermark = async () => {
    // Get the Spire.Doc WASM module
    const wasmModule = window.wasmModule?.spiredoc;

    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the sample document and the font
    await window.spire.FetchFileToVFS('Sample.docx', '', `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('arialbd.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a Document instance and load the document
    const doc = new wasmModule.Document();
    doc.LoadFromFile('Sample.docx');

    // Create a WordArt shape and set its size, position, rotation and style
    let shape = new wasmModule.ShapeObject(doc, wasmModule.ShapeType.TextPlainText);
    shape.Width = 60;
    shape.Height = 15;
    shape.VerticalPosition = 25;
    shape.HorizontalPosition = 20;
    shape.Rotation = 320;
    shape.WordArt.Text = 'INTERNAL USE';
    shape.WordArt.FontFamily = 'Arial';
    shape.FillColor = wasmModule.Color.FromArgb(255, 255, 0, 0);
    shape.StrokeColor = wasmModule.Color.FromArgb(255, 255, 0, 0);

    // Iterate over all sections and add the watermark to the header of each section
    for (let n = 0; n < doc.Sections.Count; n++) {
      const section = doc.Sections.get_Item(n);
      const header = section.HeadersFooters.Header;
      const paragraph = header.AddParagraph();

      // 5 rows and 6 columns: clone the shape and set the coordinates of each copy
      for (let i = 0; i < 5; i++) {
        for (let j = 0; j < 6; j++) {
          shape = shape.Clone();
          shape.VerticalPosition = 50 + 150 * i;
          shape.HorizontalPosition = 20 + 85 * j;
          paragraph.ChildObjects.Add(shape);
        }
      }
    }

    // Save the result file
    const outputFileName = 'AddTextWatermark.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
    doc.Dispose();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Multi-Row Text Watermarks</h1>
      <button onClick={addTextWatermark}>Generate Document</button>
    </div>
  );
}

export default App;

After adding multi-row, multi-column text watermarks, the document looks like this:

Document after adding multi-row, multi-column text watermarks


Add Multi-Row Image Watermarks

To add a multi-row, multi-column image watermark, a picture is loaded into the header, then cloned by row and column and given its own coordinates. Compared with a text watermark, the appearance of an image watermark is determined by the picture itself, and the code only has to control the wrapping style and the arrangement of the copies.

The following members are involved in adding multi-row image watermarks:

Member Purpose
DocPicture Object that carries the watermark image
DocPicture.LoadImage(path) Loads the watermark image from the VFS
TextWrappingStyle.Behind Places the picture behind the body text
DocPicture.VerticalPosition, HorizontalPosition Vertical and horizontal coordinates of each copy
DocPicture.Clone() Clones the picture object for tiling
HeaderFooter.AddParagraph() Adds a paragraph in the header to carry the watermark
Paragraph.ChildObjects.Add() Adds the picture to the header paragraph

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the sample document and the watermark image into the virtual file system (VFS), create a Document object and load the document.
  3. Iterate over every section of the document, get the header through HeadersFooters.Header, and add a paragraph with AddParagraph().
  4. Create a DocPicture, call LoadImage() to load the watermark image, and set TextWrappingStyle to TextWrappingStyle.Behind.
  5. Clone the picture with Clone() in a loop over rows and columns, give each copy its own VerticalPosition and HorizontalPosition, and add it to the ChildObjects of the header paragraph.
  6. Save the result file with SaveToFile(), read it from the VFS and trigger a browser download.

The code for adding multi-row image watermarks is as follows:

function App() {
  const addImageWatermark = async () => {
    // Get the Spire.Doc WASM module
    const wasmModule = window.wasmModule?.spiredoc;

    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the sample document and the watermark image
    await window.spire.FetchFileToVFS('Sample.docx', '', `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the document
    const doc = new wasmModule.Document();
    doc.LoadFromFile('Sample.docx');

    // Iterate over all sections and add the watermark to the header of each section
    for (let n = 0; n < doc.Sections.Count; n++) {
      const section = doc.Sections.get_Item(n);
      const header = section.HeadersFooters.Header;
      const paragraph = header.AddParagraph();

      // Create a picture object and load the watermark image
      const picture = new wasmModule.DocPicture(doc);
      picture.LoadImage('logo.png');
      picture.TextWrappingStyle = wasmModule.TextWrappingStyle.Behind;

      // 5 rows and 3 columns: clone the picture and set the coordinates of each copy
      for (let p = 0; p < 5; p++) {
        for (let q = 0; q < 3; q++) {
          const pic = picture.Clone();
          pic.VerticalPosition = 50 + 150 * p;
          pic.HorizontalPosition = 10 + 150 * q;
          paragraph.ChildObjects.Add(pic);
        }
      }
    }

    // Save the result file
    const outputFileName = 'AddImageWatermark.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
    doc.Dispose();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Multi-Row Image Watermarks</h1>
      <button onClick={addImageWatermark}>Generate Document</button>
    </div>
  );
}

export default App;

After adding multi-row, multi-column image watermarks, the document looks like this:

Document after adding multi-row, multi-column image watermarks


Adjust the Tiling Rows, Columns and Spacing

Adjusting the tiling rows, columns and spacing means changing the loop counts and the two coefficients in the coordinate formula: the number of rows and columns determines how many watermarks there are, while the row gap and column gap determine the distance between neighboring watermarks. Together they decide how dense the tiling is on the page.

The coordinate formula used for tiling is as follows:

Parameter Example value Purpose
Rows rows 4 Number of watermarks arranged vertically
Columns cols 5 Number of watermarks arranged horizontally
Row gap rowGap 180 Vertical distance between two neighboring rows, in points
Column gap colGap 100 Horizontal distance between two neighboring columns, in points
Start coordinates 40, 20 Vertical and horizontal coordinates of the first row and first column

The coordinates of each watermark are calculated as follows:

  • VerticalPosition = start vertical coordinate + row gap × row index
  • HorizontalPosition = start horizontal coordinate + column gap × column index

The specific steps are as follows:

  1. Estimate the width and height available for the watermarks from the page size and the page margins.
  2. Decide the number of rows and columns, and the start coordinates of the first row and first column.
  3. Change the loop counts and the row gap and column gap in the coordinate formula, and adjust the width and height of the shape as needed.
  4. Save the file and preview it, then fine-tune the spacing according to the actual result.

The code for adjusting the tiling rows, columns and spacing is as follows. The example uses 4 rows and 5 columns, a row gap of 180 points and a column gap of 100 points, and changes the watermark to dark gray:

// Number of rows, columns and the spacing
const rows = 4;
const cols = 5;
const rowGap = 180;
const colGap = 100;

// Create the base shape and clone it later with Clone()
const base = new wasmModule.ShapeObject(doc, wasmModule.ShapeType.TextPlainText);
base.Width = 80;
base.Height = 20;
base.Rotation = 315;
base.WordArt.Text = 'CONFIDENTIAL';
base.WordArt.FontFamily = 'Arial';
base.FillColor = wasmModule.Color.FromArgb(255, 70, 70, 70);
base.StrokeColor = wasmModule.Color.FromArgb(255, 70, 70, 70);

for (let n = 0; n < doc.Sections.Count; n++) {
  const header = doc.Sections.get_Item(n).HeadersFooters.Header;
  const paragraph = header.AddParagraph();

  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      const shape = base.Clone();
      shape.VerticalPosition = 40 + rowGap * i;
      shape.HorizontalPosition = 20 + colGap * j;
      paragraph.ChildObjects.Add(shape);
    }
  }
}

After adjusting the rows, columns and spacing, the text watermark looks like this:

Text watermark after adjusting the rows, columns and spacing


FAQ

Why does the watermark not appear on the first page

Cause: Headers are set per section. The example writes to HeadersFooters.Header, which applies to the normal pages of the section. When a document uses "Different first page", the first page uses HeadersFooters.FirstPageHeader instead; when it uses "Different odd and even pages", even pages use HeadersFooters.EvenHeader. The sample document uses neither option, so the watermark appears on every page.

Solution: Add the watermark to HeadersFooters.FirstPageHeader or HeadersFooters.EvenHeader as well when the corresponding option is enabled.

Why do the rightmost columns fall outside the page

Cause: HorizontalPosition is measured in points and starts from the left page margin. With text watermarks 60 points wide, a column gap of 85 points and 6 columns, the right edge of the last column is about 20 + 85 × 5 + 60 = 505 points, which is already close to the right edge of the page once the left margin is added. If the column gap or the number of columns is increased further, the rightmost objects go beyond the page.

Solution: Before adjusting, estimate the total width as "start coordinate + gap × (count − 1) + object width" and compare it with the page width.

Why does the set position differ from the actual position on the page

Cause: Shapes in the header are positioned absolutely: horizontally from the left page margin and vertically from the top of the header area. The sample document has a left margin of 90 points and the header sits 36 points from the top of the page, so with HorizontalPosition = 20 the watermark actually appears at about 110 points from the left edge of the page.

Solution: After changing the template or adjusting the page margins, re-estimate the start coordinates.

Do duplicated image watermarks significantly increase the file size

Usually not. The copies share the same image: Clone() duplicates the picture object and the layout properties, while the image itself is stored only once in the document. The example produces 15 image watermarks, and the word/media/ directory of the docx still contains a single image file, which the other copies reference. The increase in file size comes mainly from the shape and positioning data rather than from the image data.

What if the image watermark covers the body text

The example places the picture behind the body text with TextWrappingStyle.Behind, so the pictures have a negative z-order in the generated document and the body text stays on top. If the watermark is still too prominent, reduce its opacity or use a lighter version when preparing the image; the example uses a semi-transparent, light-colored PNG.


Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.