Low-level Canvas & Graphics

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

The layout elements of Layout Elements and Tables are convenient, but they all end up as the same thing: a page content stream, a sequence of PDF operators such as q, cm, re, f and Q (see How a PDF File Works). PdfCanvas, in com.itextpdf.kernel.pdf.canvas, is the class that writes those operators for you. You reach for it when the layout model has no element for what you need: rules and frames that are not borders, logos and stamps at an exact position, clipped or translucent artwork, custom charts, backgrounds, or content that must be switched on and off in a viewer. The canvas is also what every renderer of the layout module uses internally when it draws, so everything learned here applies to custom renderers. Versions are stated as of 2026-10-09: iText Core 9.8.0 on Java 21, and every example below was compiled and run against exactly that version.

The running scenario is the Bookshelf Billing invoice. Here the canvas draws the letterhead band, the separators and the "PAID" or "DRAFT" stamps that surround the table and paragraphs produced by the layout API. The Spring Boot code that returns the bytes to a client is in Getting Started with Spring Boot, and the lifecycle of the PdfDocument used below is explained in Document Lifecycle.

What changed since the old PdfContentByte and PdfGraphics2D

Books written for iText 1 and 2 (including the classic 2006 text iText in Action, consulted for the concept order of this page and not reproduced) draw through PdfContentByte, obtained from PdfWriter.getDirectContent(), and bridge Swing and AWT drawing through PdfGraphics2D, createGraphics and PdfTemplate. In iText 7, 8 and 9:

  • PdfContentByte is replaced by PdfCanvas, created explicitly for a PdfPage, a PdfFormXObject or a raw PdfStream, with a fluent API: every drawing method returns the canvas, so calls chain naturally.

  • PdfTemplate is replaced by PdfFormXObject, created once and painted any number of times.

  • PdfGraphics2D and the Swing, applet and Batik export route are not part of the iText 7+ core modules. To draw a chart or any vector graphic, either use the PdfCanvas operators of this page or render the chart to SVG and convert it with the svg module, which is covered in Colours, Images & SVG (outdated finding O7 of this section’s source analysis).

  • The "direct content" and "under content" split of the old PdfWriter is now explicit: you choose which content stream of the page you append to, as shown in the section on drawing under and over layout content.

Java 21 idioms are used throughout the examples of this section (outdated finding O28): try-with-resources closes the PdfDocument and, with it, flushes every page, records and text blocks are used where data is modelled, and generics replace the raw collections of the old books. The code below uses try-with-resources and the typed, fluent API. The Maven coordinates are those of the rest of the section. iText is licensed under the AGPLv3 or under a commercial licence; the bouncy-castle-adapter is only needed for encryption and signing and is restated for completeness.

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-core</artifactId>
    <version>9.8.0</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>bouncy-castle-adapter</artifactId>
    <version>9.8.0</version>
</dependency>

The coordinate system and the current transformation matrix

A PDF page is a plane measured in points (1 point is 1/72 inch). The origin (0, 0) is the bottom-left corner of the page, x grows to the right and y grows upwards, which is the opposite of most screen and image APIs. An A4 page is 595 by 842 points, so the top edge is pageSize.getTop(), which is 842. The page box is not always at the origin: a page can define a crop box or media box starting at another point, so portable code derives positions from page.getPageSize() (a Rectangle with getLeft(), getBottom(), getRight(), getTop()) instead of hard-coding numbers. A page can also carry a /Rotate entry (a multiple of 90 degrees), in which case the viewer rotates the whole page after drawing and your coordinates stay in the unrotated space.

The PDF coordinate system with the origin at the bottom-left of the page, a rectangle at user-space coordinates, a second rectangle drawn after a cm operator moved and rotated the origin, the matrix formula and the graphics state stack

Coordinates passed to a drawing method are user space coordinates. The current transformation matrix (CTM) maps user space to the page: it starts as the identity, and the cm operator, exposed as concatMatrix(a, b, c, d, e, f), multiplies it by a new matrix. The six numbers are the first two columns of a 3 by 3 affine matrix: e and f translate, a and d scale, and a, b, c, d together rotate or skew (a rotation by an angle t uses cos t, sin t, -sin t and cos t). Anything drawn afterwards, including text and images, is transformed by it. Matrices compose by multiplication, so the order of successive concatMatrix calls matters: the last call is applied to the coordinates first. The old trick of moving the origin to the centre of a stamp, rotating, and drawing at (0, 0) is exactly this.

Two relatives are worth knowing. An image or a form XObject is a unit square or its own bounding box that you place with a matrix (addXObjectAt, addXObjectWithTransformationMatrix), and text has its own matrix, set per text object with setTextMatrix and moveText, independent from the CTM (the CTM is applied on top of it).

The graphics state and the state stack

At any moment the canvas has a graphics state: CTM, fill and stroke colours, line width, cap, join, miter limit and dash pattern, the clipping path, text state (font, size, spacing, rendering mode) and the extended graphics state (transparency, blend mode, soft mask). saveState() writes q, which pushes a copy of the state, and restoreState() writes Q, which pops it and restores everything at once. The rule that makes canvas code robust is simple: change the state only between a saveState() and a restoreState() that you wrote yourself, on the same canvas and the same content stream. Forgetting the restoreState() leaks a clip, a translucent fill or a rotation into everything drawn after it, including the layout content that follows. The current point and the current path, in contrast, are not part of the saved state: they are consumed by the painting operator.

The graphics state stack is limited in depth by viewers (28 levels is the commonly cited minimum), so deeply nested saveState() calls in a loop that never restores are a defect, not a style.

Paths, painting and line styles

Drawing is a two-step process in PDF: first you construct a path with moveTo, lineTo, curveTo (a cubic Bezier with two control points), rectangle, roundRectangle, circle, arc or ellipse and closePath, and then you paint it with one painting operator: stroke() (outline), fill() (interior, non-zero winding rule), eoFill() (even-odd rule), fillStroke() and eoFillStroke(), or endPath() which paints nothing and is used to end a path that was only meant to become a clipping path. Painting without an open path does nothing.

Line styles are state: setLineWidth (a width of 0 is the thinnest line the device can render, which is rarely what you want), setLineCapStyle (0 butt, 1 round, 2 projecting square), setLineJoinStyle (0 miter, 1 round, 2 bevel), setMiterLimit and setLineDash(phase), setLineDash(on, phase), setLineDash(on, off, phase) or setLineDash(pattern[], phase). Colours are set with setFillColor and setStrokeColor taking a Color object (DeviceRgb, DeviceCmyk, DeviceGray, spot, pattern and the ColorConstants shortcuts, described in Colours, Images & SVG); the low-level setFillColorRgb, setFillColorGray and setFillColorCmyk variants take raw component values.

The first example draws the letterhead of the Bookshelf Billing invoice. The coordinate origin is the bottom-left, so the band is placed at getTop() - 70. It shows the fluent API, a text object (beginText and endText, whose operators cannot be nested), a dashed rule with round caps, a Bezier curve closed and filled and stroked with a single operator, and a transformation matrix that translates and rotates a "PAID" box. Note that text is shown with the font object returned by PdfFontFactory; fonts, encodings and subsetting are the subject of Fonts & Unicode.

package com.example.billing;

import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;

import java.io.IOException;

/** Draws the Bookshelf Billing invoice letterhead with raw PdfCanvas operators. */
public final class CanvasBasics {

    public static void main(String[] args) throws IOException {
        try (PdfDocument pdf = new PdfDocument(new PdfWriter("target/canvas-basics.pdf"))) {
            PdfPage page = pdf.addNewPage(PageSize.A4);
            PdfCanvas canvas = new PdfCanvas(page);
            PdfFont helvetica = PdfFontFactory.createFont(StandardFonts.HELVETICA);
            PdfFont bold = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);

            // 1. Filled rectangle band across the top (origin is the bottom-left corner)
            Rectangle box = page.getPageSize();
            canvas.saveState()
                    .setFillColor(ColorConstants.DARK_GRAY)
                    .rectangle(0, box.getTop() - 70, box.getWidth(), 70)
                    .fill()
                    .restoreState();

            // 2. Text uses its own text object: BT ... ET (white fill kept inside q ... Q)
            canvas.saveState()
                    .beginText()
                    .setFontAndSize(bold, 22)
                    .setFillColor(ColorConstants.WHITE)
                    .moveText(40, box.getTop() - 45)
                    .showText("Bookshelf Billing")
                    .endText()
                    .restoreState();

            // 3. Dashed rule, round caps, 1.5 pt
            canvas.saveState()
                    .setStrokeColor(ColorConstants.GRAY)
                    .setLineWidth(1.5f)
                    .setLineCapStyle(1)          // round
                    .setLineDash(6, 4, 0)        // 6 on, 4 off, phase 0
                    .moveTo(40, box.getTop() - 100)
                    .lineTo(box.getRight() - 40, box.getTop() - 100)
                    .stroke()
                    .restoreState();

            // 4. A Bezier curve and a closed path filled and stroked in one operator
            canvas.saveState()
                    .setStrokeColor(ColorConstants.BLUE)
                    .setFillColor(ColorConstants.YELLOW)
                    .setLineWidth(2)
                    .moveTo(60, 600)
                    .curveTo(100, 680, 180, 520, 240, 600)
                    .lineTo(240, 560)
                    .closePath()
                    .fillStroke()
                    .restoreState();

            // 5. The CTM: translate + rotate, then draw at the new origin
            canvas.saveState()
                    .concatMatrix(Math.cos(Math.PI / 6), Math.sin(Math.PI / 6),
                            -Math.sin(Math.PI / 6), Math.cos(Math.PI / 6), 350, 560)
                    .setStrokeColor(ColorConstants.RED)
                    .rectangle(0, 0, 120, 40)
                    .stroke()
                    .beginText()
                    .setFontAndSize(helvetica, 12)
                    .moveText(8, 14)
                    .showText("PAID")
                    .endText()
                    .restoreState();
        }
    }
}

The three-argument setLineDash(6, 4, 0) sets a dash pattern of 6 points painted and 4 points skipped, starting at phase 0. The two-argument overload is setLineDash(unitsOn, phase), equal on and off lengths plus a phase, so setLineDash(6, 4) would draw 6-point dashes and 6-point gaps shifted by 4 points. The white fill of the title is set inside saveState()/restoreState() so that it does not leak into the later PAID text, which would otherwise be painted white on white. Derived from the iText Knowledge Base chapter on adding low-level content, Chapter 2: Adding low-level content, and from the PdfCanvas API reference. The jump-start tutorial counterpart is iText Jump-Start Tutorial: Chapter 2. The tutorials may show older constructor or colour-constant spellings than iText 9.8; the compiled example above is the reference for this page.

Clipping

A clipping path restricts where later painting is visible. You build a path, call clip() (non-zero winding rule) or eoClip() (even-odd), and then end the path with endPath(). From that point until the matching restoreState(), everything is painted only inside the path, and clipping can only ever shrink the visible area, never grow it, which is the reason it must be wrapped in saveState(). Typical uses are circular photos, cropping a chart to its plot area and clipping a gradient (shown in the soft-mask example below).

Transparency and blend modes

Transparency is part of the extended graphics state (PdfExtGState), applied with canvas.setExtGState(...):

  • setFillOpacity(float) and setStrokeOpacity(float) set the constant alpha for filling and stroking (0 is invisible, 1 opaque);

  • setBlendMode(PdfName) selects how the paint combines with what is already on the page: PdfExtGState.BM_NORMAL and the other BM_* constants, or the PdfName constants such as PdfName.Multiply, PdfName.Screen, PdfName.Overlay and PdfName.Darken. Blend modes require PDF 1.4, which every current writer produces, and a viewer that supports transparency;

  • setSoftMask(PdfObject) applies a soft mask, described in the next section.

Transparency is a feature of the PDF/A-2 and later conformance levels but is forbidden in PDF/A-1; see PDF Standards & Conformance before using it in an archival invoice. The following example combines clipping, constant alpha, a blend mode, a reusable stamp and layers across two pages. Read the code first and then the sections on form XObjects and layers that explain the two remaining pieces.

Reusable content with PdfFormXObject

A PdfFormXObject (the successor of the old PdfTemplate) is a self-contained piece of content with its own bounding box, resources and optionally a matrix. You draw it once with a PdfCanvas created from the XObject and then paint it as many times as needed with addXObjectAt(xobject, x, y), addXObjectFittedIntoRectangle or addXObjectWithTransformationMatrix, each of which writes a single Do operator. The saving is real: a logo or a watermark drawn on 5,000 invoice pages is stored once, and each page only holds a reference. Form XObjects are also the basis of the "page X of Y" placeholder technique used in the page on page events of this section (a placeholder created early and filled when the total is known), and of the group used in a soft mask. A page of another PDF can be imported as a form XObject too, which is how stamping and N-up layouts work (see the page on manipulating existing PDFs of this section).

Always call release() on a PdfCanvas you created for an XObject or content stream when you are done with it, so the buffered operators are written; a canvas created for a page is flushed when the page is flushed.

Optional content: layers

Optional content groups (OCGs, shown by viewers as layers) let a viewer show or hide parts of a page: a draft stamp that is visible on screen and hidden in print, internal notes, a technical drawing with selectable layers. In iText you create a PdfLayer(name, pdf), choose its initial state with setOn(boolean), and wrap the drawing between canvas.beginLayer(layer) and canvas.endLayer(), which write marked-content operators. The viewer lists them in its layers panel. Layers can be nested (addChild), exclusive (PdfLayer.createTitle and radio groups through PdfLayer.addOCGRadioGroup), and can have usage metadata (setPrint, setView, setZoom) so that a layer prints but is not shown, or the reverse. OCGs require PDF 1.5 and are not permitted in PDF/A-1; PDF/A-2 and later allow them under restrictions (all the configurations in the OCProperties must be valid, and /AS is not allowed).

package com.example.billing;

import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.kernel.pdf.layer.PdfLayer;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;

import java.io.IOException;

/** Clipping, transparency, blend modes, reusable form XObjects and optional content layers. */
public final class CanvasAdvanced {

    public static void main(String[] args) throws IOException {
        try (PdfDocument pdf = new PdfDocument(new PdfWriter("target/canvas-advanced.pdf"))) {
            PdfFont bold = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);

            // A reusable stamp: drawn once, painted many times
            PdfFormXObject stamp = new PdfFormXObject(new Rectangle(0, 0, 120, 40));
            PdfCanvas stampCanvas = new PdfCanvas(stamp, pdf);
            stampCanvas.setStrokeColor(ColorConstants.RED).setLineWidth(2)
                    .rectangle(1, 1, 118, 38).stroke()
                    .beginText().setFontAndSize(bold, 18).setFillColor(ColorConstants.RED)
                    .moveText(14, 12).showText("DRAFT").endText();
            stampCanvas.release();

            // Optional content: two layers, one hidden by default
            PdfLayer notes = new PdfLayer("Internal notes", pdf);
            notes.setOn(false);
            PdfLayer logo = new PdfLayer("Letterhead", pdf);

            for (int i = 0; i < 2; i++) {
                PdfPage page = pdf.addNewPage(PageSize.A4);
                PdfCanvas canvas = new PdfCanvas(page);

                // Clipping: everything painted until restoreState() is limited to the circle
                canvas.saveState()
                        .circle(150, 700, 50).clip().endPath()
                        .setFillColor(ColorConstants.ORANGE).rectangle(90, 640, 120, 120).fill()
                        .setFillColor(ColorConstants.BLUE).rectangle(150, 700, 120, 120).fill()
                        .restoreState();

                // Transparency: constant alpha through an ExtGState
                canvas.saveState()
                        .setExtGState(new PdfExtGState().setFillOpacity(0.4f).setStrokeOpacity(0.4f))
                        .setFillColor(ColorConstants.GREEN).rectangle(300, 650, 150, 100).fill()
                        .restoreState();

                // Blend mode: multiply the magenta box with whatever is below
                canvas.saveState()
                        .setFillColor(ColorConstants.CYAN).rectangle(60, 450, 120, 100).fill()
                        .setExtGState(new PdfExtGState().setBlendMode(PdfName.Multiply))
                        .setFillColor(ColorConstants.MAGENTA).rectangle(120, 480, 120, 100).fill()
                        .restoreState();

                // Reuse the form XObject; each call writes only a "Do" operator
                canvas.addXObjectAt(stamp, 300, 450 - 100 * i);

                // Layers: begin/end marked content around the objects of a layer
                canvas.beginLayer(logo)
                        .beginText().setFontAndSize(bold, 12).moveText(40, 800)
                        .showText("Bookshelf Billing").endText()
                        .endLayer();
                canvas.beginLayer(notes)
                        .beginText().setFontAndSize(bold, 12).moveText(40, 300)
                        .showText("Internal: check credit limit").endText()
                        .endLayer();
            }
        }
    }
}

The example was run and the resulting file contains an /OCProperties dictionary with the "Internal notes" layer listed among the off layers. The blend and opacity example uses the extended graphics state described in PdfExtGState, the layers use PdfLayer, and the stamp uses PdfFormXObject. All are derived from the low-level content chapter of the iText Knowledge Base and the PdfCanvas API (beginLayer, endLayer, addXObjectAt, setExtGState). Layers have no equivalent in the rest of the layout API, so a layer inside flowed content is written with a custom renderer (see the page on renderers) that opens and closes the layer around its own drawing.

Soft masks

A soft mask gives every pixel its own opacity instead of one constant alpha. The mask is a transparency group whose content is converted either to its luminosity (black is transparent, white is opaque, grey is partial) or to its alpha; the mask is attached through the SMask entry of an ExtGState. iText has no dedicated soft-mask class: you build a PdfFormXObject for the group, draw into it (typically a gradient), build the small dictionary with /Type /Mask, /S /Luminosity (or /Alpha) and /G pointing to the group, and pass it to PdfExtGState.setSoftMask(PdfObject). The example fades a blue box in from left to right using an axial shading as the mask. The shading classes (PdfAxialShading, PdfRadialShading and the mesh variants) live in com.itextpdf.kernel.pdf.colorspace.shading; the same classes cover the other shading types.

package com.example.billing;

import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
import com.itextpdf.kernel.pdf.colorspace.shading.PdfAxialShading;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;

import java.io.IOException;

/** A luminosity soft mask: black hides, white shows, grey is partially transparent. */
public final class SoftMaskExample {

    public static void main(String[] args) throws IOException {
        try (PdfDocument pdf = new PdfDocument(new PdfWriter("target/soft-mask.pdf"))) {
            PdfPage page = pdf.addNewPage(PageSize.A4);
            PdfCanvas canvas = new PdfCanvas(page);

            // 1. The mask group: a horizontal gradient from black (x=100) to white (x=400)
            PdfFormXObject maskGroup = new PdfFormXObject(page.getPageSize());
            PdfCanvas maskCanvas = new PdfCanvas(maskGroup, pdf);
            PdfAxialShading gradient = new PdfAxialShading(new PdfDeviceCs.Rgb(),
                    100, 0, new float[] {0f, 0f, 0f}, 400, 0, new float[] {1f, 1f, 1f});
            maskCanvas.saveState()
                    .rectangle(100, 500, 300, 120).clip().endPath()
                    .paintShading(gradient)
                    .restoreState();
            maskCanvas.release();

            // 2. The soft-mask dictionary: /S /Luminosity, /G the group
            PdfDictionary mask = new PdfDictionary();
            mask.put(PdfName.Type, PdfName.Mask);
            mask.put(PdfName.S, PdfName.Luminosity);
            mask.put(PdfName.G, maskGroup.getPdfObject());

            // 3. Paint through the mask; the rectangle fades in from left to right
            canvas.saveState()
                    .setExtGState(new PdfExtGState().setSoftMask(mask))
                    .setFillColor(ColorConstants.BLUE)
                    .rectangle(100, 500, 300, 120)
                    .fill()
                    .restoreState();
        }
    }
}

The file was generated, and its content stream refers to an extended graphics state with /SMask whose /S is /Luminosity. Soft masks follow the transparency model of ISO 32000, described in the ISO 32000 sponsored standards page of the PDF Association; the API for the mask group is PdfExtGState and the gradient is PdfAxialShading. Images with an alpha channel or an image mask create soft masks for you, so build one by hand only when the mask is generated artwork; see the image masks in Colours, Images & SVG.

Drawing under and over layout content

The layout Document writes its content to the page content stream as it lays out elements. To draw something that stays behind the text (a letterhead background, a watermark) or on top of it (a stamp), create a canvas on a fresh content stream that PDF places before or after the existing ones: page.newContentStreamBefore() and page.newContentStreamAfter(), wrapped as new PdfCanvas(stream, page.getResources(), pdf). This replaces the old getDirectContentUnder() and getDirectContent() pair. The first overload new PdfCanvas(page) appends to the last content stream and is right when you draw before the layout content or on a page you build yourself.

The example lays out an invoice header with the layout API and then adds a grey band under it and a translucent red circle above it. The drawing happens after layout, on pdf.getFirstPage(). In real code this would run from an end-of-page event handler (see the page on page events of this section), which runs for every page after its content is complete.

package com.example.billing;

import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;

import java.io.IOException;

/** Mixes the layout API with low-level drawing underneath and on top of the flowed content. */
public final class UnderlayExample {

    public static void main(String[] args) throws IOException {
        try (PdfDocument pdf = new PdfDocument(new PdfWriter("target/underlay.pdf"));
             Document document = new Document(pdf, PageSize.A4)) {
            document.add(new Paragraph("Invoice INV-2026-0001").setFontSize(18));
            document.add(new Paragraph("Total: 42.00 EUR"));

            PdfPage page = pdf.getFirstPage();

            // Content stream placed BEFORE the page content: a background that text can sit on
            PdfCanvas under = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdf);
            under.saveState()
                    .setFillColor(ColorConstants.LIGHT_GRAY)
                    .rectangle(36, page.getPageSize().getTop() - 100, page.getPageSize().getWidth() - 72, 64)
                    .fill()
                    .restoreState();
            under.release();

            // Content stream placed AFTER the page content: a translucent stamp above everything
            PdfCanvas over = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdf);
            over.saveState()
                    .setExtGState(new PdfExtGState().setFillOpacity(0.25f))
                    .setFillColor(ColorConstants.RED)
                    .circle(300, 500, 80)
                    .fill()
                    .restoreState();
            over.release();
        }
    }
}

The content stream methods are listed in the PdfPage API reference.

Layout content on a fixed area: Canvas

The layout module has a class that goes the other way: com.itextpdf.layout.Canvas takes a PdfCanvas (or a form XObject) and a Rectangle, and lays out layout elements such as Paragraph and Table inside that fixed area, with no page breaking. It is the tool for boxes at an exact position: the address window of an envelope letter, a label, a signature block, or a QR code caption. Anything that does not fit is clipped silently, so size it generously or check the result of layout through a renderer. See Layout Elements for Canvas with paragraphs and Document Lifecycle for how it relates to Document.

Operator reference

The table maps the methods used on this page to the PDF content-stream operators they write; knowing them is useful when you read a content stream in a debugging tool or in How a PDF File Works.

PdfCanvas method Operator Effect

saveState, restoreState

q, Q

Push and pop the graphics state

concatMatrix

cm

Multiply the CTM

moveTo, lineTo, curveTo, closePath

m, l, c, h

Build a path

rectangle

re

Append a rectangle to the path

stroke, fill, fillStroke, endPath

S, f, B, n

Paint or discard the path

clip, eoClip

W, W*

Intersect the clipping path with the current path

setLineWidth, setLineDash

w, d

Line style

setExtGState

gs

Apply transparency, blend mode, soft mask

addXObjectAt

Do

Paint a form XObject or image

beginLayer, endLayer

BDC, EMC

Start and end optional content

beginText, endText

BT, ET

Text object boundaries

Common mistakes

  • Unbalanced state. Every saveState() needs a restoreState() on the same stream; an unbalanced pair is one of the most common causes of content that is clipped, translucent or rotated for the rest of the page.

  • Wrong origin. Using a top-left origin copied from a screen API puts the content upside down or off the page; subtract from pageSize.getTop().

  • Drawing before layout content that clears it. Content added with new PdfCanvas(page) after layout appears on top; use newContentStreamBefore() for backgrounds.

  • Not releasing the canvas. Forgetting release() on a canvas for an XObject leaves buffered operators unwritten.

  • Transparency and layers in archival files. Check the conformance level first, as noted above.