New in Chilkat v11.7.0

PDF Watermarks: Pdf.Watermark

Add text, image or PDF-page watermarks to a PDF's pages with full control over placement, styling and how they are stored; then detect, list and remove watermarks — Chilkat's own, Acrobat's, and the standard forms other tools write.

bool Watermark(string op, JsonObject jsonIn, JsonObject jsonOut)

The operations

opWhat it doesjsonIn holdsjsonOut returns
addAdds one watermark to the selected pages (a text watermark plus a logo is two calls)Appearance, placement and storage optionsid of the new watermark, pages affected
detectAnswers whether any watermark is presentOptional filter (pages, approach, source, id, text)found, count
listEnumerates watermarks with their propertiesOptional filtercount, watermarks[]
removeRemoves matching watermarksFilter; an empty filter is refused unless "all": trueremoved count

Finding nothing is still success: detect returns true with found false, and remove returns true with removed 0. Changes are made in memory and written by WriteFile or WriteBd.

Quick start

A diagonal CONFIDENTIAL across every page, with an id so that it can be removed later. Only type and its content are required; everything else has a default.

Chilkat.Pdf pdf = new Chilkat.Pdf();
pdf.LoadFile("report.pdf");

Chilkat.JsonObject jsonIn = new Chilkat.JsonObject();
Chilkat.JsonObject jsonOut = new Chilkat.JsonObject();
jsonIn.UpdateString("type", "text");
jsonIn.UpdateString("text", "CONFIDENTIAL");
jsonIn.UpdateString("id", "conf");
jsonIn.UpdateString("color", "#C00000");
jsonIn.UpdateNumber("opacity", "0.25");
jsonIn.UpdateString("rotation", "diagonal");
jsonIn.UpdateString("scale.fit", "page");
jsonIn.UpdateInt("scale.fitPercent", 80);

if (pdf.Watermark("add", jsonIn, jsonOut)) {
    pdf.WriteFile("report_confidential.pdf");
}

jsonIn

{
  "type": "text",
  "text": "CONFIDENTIAL",
  "id": "conf",
  "color": "#C00000",
  "opacity": 0.25,
  "rotation": "diagonal",
  "scale": { "fit": "page", "fitPercent": 80 }
}

jsonOut

{ "id": "conf", "pages": "1-12" }

Remove it later

jsonIn

{ "id": "conf" }

jsonOut (remove)

{ "removed": 1 }

How a watermark is stored: approach

The approach member selects how an added watermark is stored, which decides whether it can be found and removed later and how viewers treat it. Default: artifact.

approachStored asVisible inRemovable laterText extractable
artifactMarked content tagged as a Watermark or Background artifactAll viewersyesNo (artifacts are skipped)
annotationA Watermark annotationMost viewers; always on topyesNo
layerAn optional-content layerAll viewers; can be hidden or made print-onlyyesDepends on the extractor
contentDrawn into the page content, unmarkedAll viewersno (by design)Yes

For every approach except content, Chilkat stores a private identifier with the watermark; that is what lets list and remove target it later. content watermarks are deliberately untagged: they are the tamper-resistant choice.

add: general options

Members are written as dotted paths, as used with the JsonObject update methods (scale.fit is the member fit of the object scale).

MemberValues (default in bold)Description
approachcontent, annotation, artifact, layerSee above.
idstring (generated)Returned in jsonOut; used by list and remove. An id already present in the document is refused. Not stored for content.
labelstring (none)A free-form description stored with the watermark and returned by list.
zOrderfront, backIn front of or behind the existing content. Ignored for annotation, which is always in front.
pagesall, first, last, odd, even, or a range list such as 1,3,5-9,12-The pages to stamp.
excludePagesrange list (none)Applied after pages.
unitspt, in, mm, cmThe units of the image size, wrap width, padding, margins, offsets, maximum sizes and tile spacing. Font size, outline width and border width are always in points.
pageBoxcrop, mediaThe page box placement is measured against.
followPageRotationtrue, falseKeeps the watermark upright for the reader on pages with a /Rotate setting.

jsonIn: odd pages only, except the cover, in millimetres

{
  "type": "text", "text": "SAMPLE",
  "pages": "odd", "excludePages": "1",
  "units": "mm",
  "position": { "align": "bottom", "margin": 12 }
}

jsonOut

{ "id": "wm1", "pages": "3,5,7,9,11" }

jsonIn: behind the content, with a label

{
  "type": "text", "text": "DRAFT",
  "zOrder": "back",
  "label": "Draft banner, added by the review tool"
}

jsonOut

{ "id": "wm2", "pages": "1-12" }

add: content (type = text, image, pdfPage)

MemberValues (default in bold)Description
typetext, image, pdfPageRequired. Any type can be stored with any approach. (barcode is reserved for a later release; an existing barcode or QR image can be added as an image.)
textstringRequired for text. \n breaks lines. Placeholders are replaced per page: {page}, {pages}, {date:FORMAT}, {filename}; {{ for a literal brace.
image.path / image.base64file path, or base64 dataRequired for image. JPEG and PNG; PNG transparency is kept (interlaced PNG is not supported).
image.width, image.heightnumber (natural size)Give one to keep the aspect ratio.
pdf.path / pdf.base64file path, or base64 dataRequired for pdfPage: letterheads, cover overlays, vector logos saved as PDF.
pdf.passwordstring (none)For an encrypted source PDF.
pdf.page1, a page number, or matchmatch maps source page n onto target page n, cycling if the source is shorter.

The {date:FORMAT} placeholder

The current local date and time, formatted with yyyy or yy (year), M/MM (month), d/dd (day), H/HH (hour, 24-hour), h/hh (hour, 12-hour), m/mm (minute), s/ss (second) and tt (AM/PM); any other character is copied, and there are no month or weekday names. {date} alone means {date:yyyy-MM-dd}.

jsonIn: page numbers and a date in the footer

{
  "type": "text",
  "text": "Page {page} of {pages}  -  printed {date:MM/dd/yyyy h:mm tt}",
  "font": { "size": 9 },
  "color": "#404040",
  "position": { "align": "bottom", "margin": 24 }
}

jsonOut

{ "id": "wm3", "pages": "1-12" }

jsonIn: a logo from a PNG file

{
  "type": "image",
  "image": { "path": "logo.png", "width": 108 },
  "position": { "align": "topRight", "margin": 36 }
}

jsonOut

{ "id": "wm4", "pages": "1-12" }

jsonIn: an image given inline

{
  "type": "image",
  "image": { "base64": "iVBORw0KGgoAAAANSUhEUgAA...", "height": 72 },
  "opacity": 0.5
}

jsonOut

{ "id": "wm5", "pages": "1-12" }

jsonIn: a two-page overlay matched page for page

{
  "type": "pdfPage",
  "pdf": { "path": "overlay.pdf", "page": "match", "password": "" },
  "scale": { "fit": "page" }
}

jsonOut

{ "id": "wm6", "pages": "1-12" }

add: text styling (type = text)

MemberValues (default in bold)Description
font.nameHelvetica, any standard-14 name, or a font file pathStandard-14: Helvetica, Times-Roman, Courier and their faces. TTF/OTF files are embedded as a subset. Symbol and ZapfDingbats are not supported.
font.sizenumber (48) or autoauto sizes the text to the scale.fit box.
font.bold, font.italic, font.underline, font.strikeoutfalseBold/italic pick the matching standard-14 face, or are synthesized for an embedded font file.
color#808080, #RRGGBB, #RGB, or a color nameThe fill color. Names: black, white, red, green, blue, yellow, cyan, magenta, gray, silver, maroon, olive, lime, teal, navy, purple, orange, darkgray, lightgray, brown, pink, gold.
renderfill, stroke, fillStroke, invisiblestroke gives outlined (hollow) text. invisible is present and searchable but unseen.
outline.color, outline.width#000000, 1Used by stroke and fillStroke; the width is in points.
textAlignleft, center, rightAlignment of multi-line text within its own box.
lineSpacing1.2A multiple of the font size.
wrapWidthnumber (no wrapping)Wraps long text to this width.
pageNumber.start1The first value of {page}.
pageNumber.styledecimal, romanUpper, romanLower, letterUpper, letterLowerThe format of {page} and {pages}.
background.color, background.opacity, background.paddingnone, 1.0, 0A box filled behind the text or image, its opacity, and the space between the content and the box edge.
border.color, border.widthnone, 1A box stroked around the stamp; the width is in points.

jsonIn: hollow DRAFT, tamper-resistant

{
  "approach": "content",
  "type": "text", "text": "DRAFT",
  "font": { "name": "Helvetica", "bold": true, "size": 120 },
  "render": "stroke",
  "outline": { "color": "#808080", "width": 2 },
  "rotation": 45
}

jsonOut

{ "id": "wm7", "pages": "1-12" }

jsonIn: a boxed, wrapped notice in an embedded font

{
  "type": "text",
  "text": "This document is provided for evaluation only and may not be redistributed.",
  "font": { "name": "C:/fonts/OpenSans-Regular.ttf", "size": 11, "italic": true },
  "wrapWidth": 300, "textAlign": "left", "lineSpacing": 1.3,
  "color": "navy",
  "background": { "color": "#FFF8E1", "opacity": 0.9, "padding": 8 },
  "border": { "color": "#C9A200", "width": 1 },
  "position": { "align": "bottomLeft", "margin": 36 }
}

jsonOut

{ "id": "wm8", "pages": "1-12" }

jsonIn: roman page numbers starting at iv

{
  "type": "text", "text": "{page}",
  "pageNumber": { "start": 4, "style": "romanLower" },
  "font": { "name": "Times-Roman", "size": 10 },
  "position": { "align": "bottom", "margin": 30 }
}

jsonOut

{ "id": "wm9", "pages": "1-12" }

jsonIn: invisible, searchable text

{
  "type": "text", "text": "ACME-DOC-77812",
  "render": "invisible",
  "position": { "align": "topLeft" }
}

jsonOut

{ "id": "wm10", "pages": "1-12" }

add: appearance

MemberValues (default in bold)Description
opacity0.0 to 1.0 (1.0)Applies to the whole stamp.
rotationdegrees counter-clockwise (0), diagonal, diagonalReversediagonal runs bottom-left to top-right and is computed per page size.
blendModenormal, multiply, darken, lighten, screen, overlaymultiply makes a front watermark read as if it were behind scanned pages.

jsonIn: over a scanned document

{
  "type": "text", "text": "COPY",
  "opacity": 0.4, "rotation": "diagonalReverse",
  "blendMode": "multiply",
  "color": "#1F4E9A"
}

jsonOut

{ "id": "wm11", "pages": "1-12" }

jsonIn: a fixed angle

{ "type": "text", "text": "VOID", "rotation": 30, "opacity": 0.3 }

jsonOut

{ "id": "wm12", "pages": "1-12" }

add: placement, scaling and tiling

Placement is relative to the page: alignment, margins and offsets.

MemberValues (default in bold)Description
position.aligncenter, top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRightThe 9-position grid.
position.marginnumber, or an object with left/top/right/bottom (0)The inset from the page edges, used for alignment and fitting.
position.offsetX, position.offsetY0A nudge from the aligned position; positive is right and down.
scale.factor1.0Multiplies the natural size.
scale.fitnone, page, width, heightFits the rotated stamp inside the margin box.
scale.fitPercent100Fills this share of the fit box.
scale.maxWidth, scale.maxHeightnoneCaps the final size.
tile.enabledfalse, trueRepeats the stamp across the page; overrides position.align.
tile.spacingX, tile.spacingY72The gaps between tiles.
tile.staggertrue, falseOffsets alternate rows by half a tile.

jsonIn: tiled user ID behind the content

{
  "type": "text",
  "text": "jdoe@example.com  {date:yyyy-MM-dd}",
  "zOrder": "back",
  "font": { "size": 14 },
  "opacity": 0.15,
  "rotation": 30,
  "tile": { "enabled": true, "spacingX": 60, "spacingY": 80 }
}

jsonOut

{ "id": "wm13", "pages": "1-12" }

jsonIn: a logo capped at 1.5 inches, nudged in from the corner

{
  "type": "image",
  "image": { "path": "logo.png" },
  "units": "in",
  "scale": { "maxWidth": 1.5, "maxHeight": 1.5 },
  "position": { "align": "bottomRight",
                "margin": { "left": 0, "top": 0, "right": 0.5, "bottom": 0.75 },
                "offsetX": -0.1 }
}

jsonOut

{ "id": "wm14", "pages": "1-12" }

add: approach-specific options and links

MemberValues (default in bold)Description
annotation.viewtrue, falsefalse with annotation.print true gives a print-only watermark.
annotation.printtrue, false
annotation.lockedtrue, falseUsers cannot move or delete it in viewers.
annotation.fixedPrintfalse, trueKeeps the printed size and position fixed whatever the paper size.
artifact.subtypeWatermark, BackgroundThe artifact subtype written to the marked content.
artifact.acrobatCompatiblefalse, trueAlso writes the structure Acrobat's own watermark tool uses, so that Acrobat can update or remove the watermark.
layer.nameWatermarkThe name shown in the viewer's layers panel; an existing layer of the same name is reused.
layer.view, layer.printon, offview off with print on gives a print-only watermark.
layer.lockedfalse, truePrevents users from toggling it in the layers panel.
link.urlURL (none)Adds a link annotation over the stamp's area on each page.

jsonIn: company logo bottom-right, printed only

{
  "approach": "layer",
  "type": "image",
  "image": { "path": "logo.png", "width": 1.5 },
  "units": "in",
  "layer": { "name": "Print logo", "view": "off", "print": "on" },
  "position": { "align": "bottomRight", "margin": 0.5 }
}

jsonOut

{ "id": "wm15", "pages": "1-12" }

jsonIn: a locked, print-only annotation with a link

{
  "approach": "annotation",
  "type": "text", "text": "www.example.com",
  "annotation": { "view": false, "print": true, "locked": true, "fixedPrint": true },
  "link": { "url": "https://www.example.com/" },
  "position": { "align": "top", "margin": 18 }
}

jsonOut

{ "id": "wm16", "pages": "1-12" }

jsonIn: letterhead behind page 1 from another PDF

{
  "approach": "artifact",
  "artifact": { "subtype": "Background" },
  "type": "pdfPage",
  "zOrder": "back",
  "pages": "first",
  "pdf": { "path": "letterhead.pdf", "page": 1 },
  "scale": { "fit": "page" }
}

jsonOut

{ "id": "wm17", "pages": "1" }

jsonIn: an artifact Acrobat can edit

{
  "approach": "artifact",
  "artifact": { "subtype": "Watermark", "acrobatCompatible": true },
  "type": "text", "text": "DRAFT",
  "opacity": 0.3, "rotation": "diagonal"
}

jsonOut

{ "id": "wm18", "pages": "1-12" }

add: worked examples in code

// 1. Diagonal CONFIDENTIAL across every page, removable later
jsonIn.Load("{ \"type\": \"text\", \"text\": \"CONFIDENTIAL\", \"id\": \"conf\", \"color\": \"#C00000\", " +
            "  \"opacity\": 0.25, \"rotation\": \"diagonal\", \"scale\": { \"fit\": \"page\", \"fitPercent\": 80 } }");
pdf.Watermark("add", jsonIn, jsonOut);

// 2. Page numbers in the footer, every page
jsonIn.Load("{ \"type\": \"text\", \"text\": \"Page {page} of {pages}\", \"font\": { \"size\": 9 }, " +
            "  \"color\": \"#404040\", \"position\": { \"align\": \"bottom\", \"margin\": 24 } }");
pdf.Watermark("add", jsonIn, jsonOut);

// 3. A logo, print-only, on its own layer
jsonIn.Load("{ \"approach\": \"layer\", \"type\": \"image\", \"image\": { \"path\": \"logo.png\", \"width\": 1.5 }, " +
            "  \"units\": \"in\", \"layer\": { \"name\": \"Print logo\", \"view\": \"off\", \"print\": \"on\" }, " +
            "  \"position\": { \"align\": \"bottomRight\", \"margin\": 0.5 } }");
pdf.Watermark("add", jsonIn, jsonOut);

pdf.WriteFile("stamped.pdf");

detect, list and remove: the filter

The three operations share one filter in jsonIn; an empty jsonIn means every watermark in the document. They recognize Chilkat's own watermarks and the standard forms other tools write:

  • anything Chilkat added with an approach other than content;
  • Watermark annotations from any producer;
  • marked content tagged as an artifact of subtype Watermark or Background;
  • Acrobat watermarks and backgrounds (Acrobat headers and footers are not watermarks and are ignored);
  • optional-content layers whose name contains "watermark".
MemberValues (default in bold)Description
pagesall or a range listSame syntax as add; restricts which pages are examined.
approachany, or one approach keyword
sourceany, chilkat, acrobat, otherWho produced it.
idstringExact match on the watermark's id (a Chilkat id, or an assigned extN id).
labelstringExact match on a Chilkat label.
textContainsstringCase-insensitive match on the watermark's text.
heuristicfalse, trueReserved for untagged page content that looks like a watermark; accepted but not implemented in this release.
allfalse, trueremove only: required to remove with an otherwise empty filter.

jsonIn: is there any watermark?

{ }

jsonOut (detect)

{ "found": true, "count": 2 }

jsonIn: only Acrobat's, on the first ten pages

{ "source": "acrobat", "pages": "1-10" }

jsonOut (detect)

{ "found": true, "count": 1 }

jsonIn: by text

{ "textContains": "draft" }

jsonOut (detect)

{ "found": true, "count": 1 }

The list report and removal

jsonIn (list)

{ }

jsonOut

{
  "count": 3,
  "watermarks": [
    { "id": "conf", "label": "Draft banner", "source": "chilkat",
      "approach": "artifact", "type": "text", "text": "CONFIDENTIAL",
      "zOrder": "front", "opacity": 0.25, "rotation": 54.7,
      "removable": true, "pages": "1-12" },
    { "id": "wm2", "source": "chilkat", "approach": "layer",
      "type": "image", "zOrder": "front", "opacity": 1,
      "removable": true, "pages": "1-12" },
    { "id": "ext1", "source": "acrobat", "approach": "artifact",
      "type": "text", "text": "DRAFT", "zOrder": "front", "opacity": 0.3,
      "rotation": 45, "removable": true, "pages": "1-4" }
  ]
}
MemberMeaning
idThe watermark's id. Watermarks from other producers have none, so Chilkat assigns a read-only one (ext1, ext2, ...) that stays valid until the PDF is changed, so that a list followed by a remove by id works.
label, source, approach, type, text, zOrder, opacity, rotationThe watermark's properties. Properties that cannot be read back (the opacity of a scanned-in watermark, for example) are omitted rather than guessed.
removableWhether remove can delete it.
pagesThe pages it appears on, such as 1-12 or 1,3,5.

Removal

  • annotation: the annotation is deleted.
  • artifact: the marked content, and any form XObject used only by it, are deleted.
  • layer: the layer's content is deleted, and the layer itself once nothing else uses it.
  • content: never removable.

jsonIn: strip every Acrobat watermark

{ "source": "acrobat" }

jsonOut (remove)

{ "removed": 1 }

jsonIn: remove everything

{ "all": true }

jsonOut (remove)

{ "removed": 3 }
// Find and strip every Acrobat watermark
Chilkat.Pdf pdf = new Chilkat.Pdf();
pdf.LoadFile("in.pdf");
Chilkat.JsonObject jsonIn = new Chilkat.JsonObject();
Chilkat.JsonObject jsonOut = new Chilkat.JsonObject();
jsonIn.UpdateString("source", "acrobat");
if (pdf.Watermark("remove", jsonIn, jsonOut) && jsonOut.IntOf("removed") > 0) {
    pdf.WriteFile("clean.pdf");
}

Signed and encrypted PDFs

  • Signed PDFs. With the PreserveSignatures property true (the default), only the annotation approach can be added, and nothing can be removed, because any other change to the page content would invalidate the signatures. Set it to false to allow such changes and accept that the signatures become invalid.
  • Encrypted PDFs. The PDF must have been opened (UserPassword / OwnerPassword), and adding or removing requires owner rights or the modify permission (see Protection). detect and list only read.
  • Watermarks are excluded from ExtractText output by its artifacts filter, and Optimize removes the empty "Watermark" layer Acrobat leaves behind after removing a watermark.

Option reference (op = add)

{
  "approach": "artifact",           // content | annotation | artifact | layer
  "id": "", "label": "",
  "zOrder": "front",                // front | back
  "pages": "all", "excludePages": "",
  "units": "pt",                    // pt | in | mm | cm
  "pageBox": "crop",                // crop | media
  "followPageRotation": true,

  "type": "text",                   // text | image | pdfPage
  "text": "",
  "image": { "path": "", "base64": "", "width": 0, "height": 0 },
  "pdf": { "path": "", "base64": "", "password": "", "page": 1 },

  "font": { "name": "Helvetica", "size": 48, "bold": false, "italic": false, "underline": false, "strikeout": false },
  "color": "#808080",
  "render": "fill",                 // fill | stroke | fillStroke | invisible
  "outline": { "color": "#000000", "width": 1 },
  "textAlign": "center", "lineSpacing": 1.2, "wrapWidth": 0,
  "pageNumber": { "start": 1, "style": "decimal" },
  "background": { "color": "", "opacity": 1.0, "padding": 0 },
  "border": { "color": "", "width": 1 },

  "opacity": 1.0, "rotation": 0, "blendMode": "normal",

  "position": { "align": "center", "margin": 0, "offsetX": 0, "offsetY": 0 },
  "scale": { "factor": 1.0, "fit": "none", "fitPercent": 100, "maxWidth": 0, "maxHeight": 0 },
  "tile": { "enabled": false, "spacingX": 72, "spacingY": 72, "stagger": true },

  "annotation": { "view": true, "print": true, "locked": true, "fixedPrint": false },
  "artifact": { "subtype": "Watermark", "acrobatCompatible": false },
  "layer": { "name": "Watermark", "view": "on", "print": "on", "locked": false },
  "link": { "url": "" }
}

Reference documentation: Pdf.Watermark. Related: ExtractText, TextReplace, Protection, Optimize, Pdf class overview.

New in Chilkat v11.7.0.