Skip to main content

Working with Vector Graphics

PDFDancer allows you to work with vector graphics elements (paths) in PDFs. You can both select existing paths and create new ones - draw lines, shapes, bezier curves, and complex vector graphics.


Selecting Paths

Paths are vector graphics elements in PDFs (lines, shapes, drawings).

from pdfdancer import PDFDancer

with PDFDancer.open("document.pdf") as pdf:
# Get all paths on a specific page
paths = pdf.page(3).select_paths()

# Get paths at specific coordinates
paths_at_point = pdf.page(3).select_paths_at(x=150, y=320)

for path in paths:
print(f"Path ID: {path.internal_id}")

Understanding Vector Paths

Vector paths in PDFs can represent:

  • Lines and curves
  • Shapes (rectangles, circles, polygons)
  • Complex drawings and diagrams
  • Borders and decorative elements

Working with Paths

from pdfdancer import PDFDancer

with PDFDancer.open("document.pdf") as pdf:
# Select paths on a page
paths = pdf.page(1).select_paths()

print(f"Found {len(paths)} paths on page 0")

# You can iterate through paths
for i, path in enumerate(paths):
print(f"Path {i}: {path.internal_id}")

Modifying Path Colors

Modify the stroke and fill colors of existing paths. Use the edit() method on a path reference to obtain a PathEdit builder, then chain color methods and call apply() to persist changes.

API Version

Path color modification is available via API v1. The v1 API uses 1-based page indexing.

Finalize Method: apply() vs add()

The new path color modification API (v1) uses .apply() to finalize builder operations, while the existing drawing API (v0) uses .add(). This is an intentional API version difference.

Basic Color Modification

from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
# Select a path to modify
paths = pdf.page(1).select_paths()
path = paths[0]

# Modify stroke color (RGB + alpha transparency)
path.edit().stroke_color(Color(255, 0, 0, 255)).apply()

# Modify fill color
path.edit().fill_color(Color(0, 255, 0, 200)).apply()

# Set both at once
path.edit() \
.stroke_color(Color(0, 0, 255)) \
.fill_color(Color(255, 255, 0)) \
.apply()

pdf.save("output.pdf")

Color Transparency

The alpha component (0-255) controls transparency. Alpha defaults to 255 (fully opaque) when not specified.

# Semi-transparent red stroke (alpha = 128)
path.edit().stroke_color(Color(255, 0, 0, 128)).apply()

# Fully opaque (alpha = 255, default)
path.edit().stroke_color(Color(255, 0, 0)).apply()

Partial Updates

Omit a color to leave it unchanged. Setting a color to null is not supported.

# Only change stroke, leave fill unchanged
path.edit().stroke_color(Color(0, 0, 255)).apply()

# Only change fill, leave stroke unchanged
path.edit().fill_color(Color(255, 0, 0)).apply()
Clearing Colors

Clearing stroke or fill colors is intentionally not supported. Passing null or omitting a color means "don't change". To remove a color from a path, consider recreating the path without that color property.

Error Handling

Handle errors that may occur when modifying path colors, such as invalid path references or color values.

from pdfdancer import PDFDancer, Color
from pdfdancer.exceptions import PathNotFoundException, InvalidColorException

with PDFDancer.open("document.pdf") as pdf:
try:
paths = pdf.page(1).select_paths()
path = paths[0]

# Modify stroke color
path.edit().stroke_color(Color(255, 0, 0)).apply()
print("Color modified successfully")

except PathNotFoundException:
print("Error: Path does not exist")
except InvalidColorException:
print("Error: Invalid color value")
except Exception as e:
print(f"Error modifying path color: {e}")

Reading Path Colors

After modifying a path, retrieve the updated color information using the v1 API snapshot which includes path styling data.

PathReference vs PathObjectRefV1

selectPaths() returns lightweight PathReference objects that support editing operations. To read styling properties like colors, use getSnapshotV1() which returns PathObjectRefV1 objects containing full path metadata including stroke/fill colors and dash patterns.

with PDFDancer.open("document.pdf") as pdf:
# Get v1 snapshot to see path colors
snapshot = pdf.page(1).get_snapshot(api_version=1)

for element in snapshot.elements:
if element.type == "PATH":
path_obj = element
print(f"Path {path_obj.internal_id}:")
print(f" Stroke: {path_obj.stroke_color}")
print(f" Fill: {path_obj.fill_color}")
print(f" Stroke width: {path_obj.stroke_width}")

PathObjectRefV1 Properties

When using the v1 API, paths are returned as PathObjectRefV1 objects containing styling information:

PropertyTypeDescription
internal_id / internalId / getInternalId()StringUnique identifier for the path
typeStringObject type, always "PATH"
positionPositionPage and coordinates (v1 uses 1-based page numbers)
stroke_color / strokeColor / getStrokeColor()ColorStroke outline color (may be null)
fill_color / fillColor / getFillColor()ColorFill color (may be null)
stroke_width / strokeWidth / getStrokeWidth()DoubleStroke width in points (may be null)
dash_array / dashArray / getDashArray()double[]Dash pattern for dashed strokes
dash_phase / dashPhase / getDashPhase()DoubleDash pattern offset

Path Grouping

Group multiple vector paths together and manipulate them as a unit. Path groups support moving, scaling, rotating, resizing, and removing all grouped paths at once.

Creating Path Groups

You can create path groups by specifying explicit path IDs or by selecting all paths within a bounding region.

from pdfdancer import PDFDancer, BoundingRect

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)

# Select paths to group
paths = page.select_paths()
path_ids = [paths[0].internal_id, paths[1].internal_id]

# Group by explicit path IDs
group = page.group_paths(path_ids)

# Or group all paths within a bounding region
region = BoundingRect(x=70, y=710, width=100, height=100)
group = page.group_paths_in_region(region)

print(f"Group contains {group.path_count} paths")

Listing Path Groups

groups = pdf.page(1).get_path_groups()

for group in groups:
print(f"Group {group.group_id}: {group.path_count} paths at ({group.x}, {group.y})")

Manipulating Path Groups

Once grouped, paths can be moved, scaled, rotated, resized, or removed as a unit.

# Move the group to a new position
group.move_to(200.0, 300.0)

# Scale the group by a factor
group.scale(2.0)

# Rotate the group by degrees
group.rotate(90.0)

# Resize the group to specific dimensions
group.resize(50.0, 50.0)

# Remove the group and all its paths
group.remove()

Clearing Path Clipping

Some PDFs use clipping paths to hide vector artwork. If a path or grouped logo is still present in the document model but no longer visible after you move it, clear the inherited clipping before saving.

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)
path = page.select_paths()[0]

# Detach clipping from a single path
path.clear_clipping()

# Detach clipping from a grouped set of paths
group = page.group_paths([path.internal_id])
group.clear_clipping()

pdf.save("output.pdf")

Path Group Properties

PropertyPythonTypeScriptJava
Group IDgroup.group_idgroup.groupIdgroup.getGroupId()
Path countgroup.path_countgroup.pathCountgroup.getPathCount()
Bounding boxgroup.bounding_boxgroup.boundingBoxgroup.getBoundingBox()
X positiongroup.xgroup.xgroup.getX()
Y positiongroup.ygroup.ygroup.getY()

Drawing Lines

Create straight lines on a page:

from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)

# Draw a simple line
page.new_line() \
.from_point(100, 100) \
.to_point(400, 100) \
.stroke_color(Color(0, 0, 0)) \
.stroke_width(2) \
.add()

# Draw a dashed line
page.new_line() \
.from_point(100, 150) \
.to_point(400, 150) \
.stroke_color(Color(255, 0, 0)) \
.stroke_width(1) \
.dash_pattern([5, 3]) \
.add()

pdf.save("output.pdf")

Drawing Rectangles

Create rectangles with stroke and fill:

from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)

# Draw a stroked rectangle
page.new_rectangle() \
.at_coordinates(100, 200) \
.with_size(200, 100) \
.stroke_color(Color(0, 0, 255)) \
.stroke_width(3) \
.add()

# Draw a filled rectangle
page.new_rectangle() \
.at_coordinates(350, 200) \
.with_size(200, 100) \
.fill_color(Color(255, 200, 0)) \
.add()

# Draw a rectangle with both stroke and fill
page.new_rectangle() \
.at_coordinates(100, 350) \
.with_size(200, 100) \
.stroke_color(Color(0, 0, 0)) \
.stroke_width(2) \
.fill_color(Color(200, 200, 255)) \
.add()

pdf.save("output.pdf")

Drawing Bezier Curves

Create smooth curves using cubic Bezier curves:

from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)

# Draw a bezier curve
page.new_bezier() \
.from_point(100, 100) \
.control_point_1(200, 200) \
.control_point_2(300, 200) \
.to_point(400, 100) \
.stroke_color(Color(0, 128, 0)) \
.stroke_width(2) \
.add()

pdf.save("output.pdf")

PathBuilder: Programmatic Path Creation

The newPath() method returns a builder that lets you create complex vector paths programmatically. This builder pattern allows you to chain commands to construct shapes, curves, and complex drawings.

TypeScript PathBuilder Class

In TypeScript SDK v1.0.22+, newPath() returns a PathBuilder instance that provides a fluent API for constructing vector paths. You can also import and use the PathBuilder class directly:

import { PathBuilder } from 'pdfdancer-client-typescript';

Builder Pattern Basics

from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
# new_path() returns a builder - chain commands and call add()
pdf.page(1).new_path() \
.add_line(Point(100, 100), Point(200, 200)) \
.stroke_color(Color(0, 0, 0)) \
.stroke_width(2) \
.add() # Finalizes and adds the path to the page

Advanced Fill Rules

For complex shapes with overlapping regions, you can control the fill behavior:

// Even-odd fill rule for complex shapes
await pdf.page(1).newPath()
.moveTo(100, 100)
.lineTo(200, 100)
.lineTo(200, 200)
.lineTo(100, 200)
.closePath()
.fillColor(new Color(255, 0, 0))
.evenOddFill(true) // Use even-odd winding rule
.apply();
Even-Odd Fill Rule

The even-odd rule determines whether a point is inside a path by drawing a line from that point to infinity and counting how many times it crosses the path. If the count is odd, the point is inside; if even, it's outside. This is useful for creating shapes with holes or complex overlapping regions.

PathBuilder API Reference

Complete list of PathBuilder methods:

Drawing Commands:

  • moveTo(x, y) - Move to a point without drawing (sets the current point)
  • lineTo(x, y) - Draw a line from current point to (x, y)
  • bezierTo(cp1x, cp1y, cp2x, cp2y, x, y) - Draw a cubic Bezier curve
  • closePath() - Draw a line back to the starting point

Styling:

  • strokeColor(Color) - Set the outline color
  • fillColor(Color) - Set the fill color
  • strokeWidth(number) - Set outline width in points
  • dashPattern([on, off, ...]) - Set dash pattern for strokes

Advanced:

  • dashPhase(number) - Set the offset for dash patterns
  • evenOddFill(boolean) - Use even-odd fill rule (TypeScript)

Finalize:

  • at(pageNumber, x, y) / at(x, y) - Set position and optionally page index
  • add() / apply() - Add the path to the document
Python Method Names

Python uses snake_case: move_to(), line_to(), stroke_color(), etc.


Drawing Complex Paths

Create complex vector graphics using path commands:

from pdfdancer import PDFDancer, Color, Point

with PDFDancer.open("document.pdf") as pdf:
page = pdf.page(1)

# Draw a complex path (triangle)
page.new_path() \
.add_line(Point(250, 100), Point(350, 250)) \
.add_line(Point(350, 250), Point(150, 250)) \
.add_line(Point(150, 250), Point(250, 100)) \
.stroke_color(Color(128, 0, 128)) \
.stroke_width(3) \
.fill_color(Color(255, 200, 255)) \
.add()

# Draw a path with curves
page.new_path() \
.add_line(Point(100, 400), Point(200, 400)) \
.add_bezier(Point(200, 400), Point(250, 450), Point(300, 450), Point(350, 400)) \
.add_line(Point(350, 400), Point(450, 400)) \
.stroke_color(Color(255, 100, 0)) \
.stroke_width(2) \
.add()

pdf.save("output.pdf")

Path Commands

Available path commands:

  • move_to(x, y) / moveTo(x, y) - Move to a point without drawing
  • line_to(x, y) / lineTo(x, y) - Draw a straight line to a point
  • add_bezier(...) / bezierTo(cp1x, cp1y, cp2x, cp2y, x, y) / curveTo(...) - Draw a cubic Bezier curve
  • close_path() / closePath() - Close the current path by drawing a line to the start point

Styling Options

All vector graphics support these styling options:

Stroke (Outline)

  • stroke_color(Color) / strokeColor(Color) - Set the outline color
  • stroke_width(number) / strokeWidth(number) - Set the outline width in points
  • dash_pattern([on, off, ...]) / dashPattern([on, off, ...]) - Create dashed lines

Fill

  • fill_color(Color) / fillColor(Color) - Set the fill color for closed shapes
Stroke vs Fill
  • Use stroke for outlines and lines
  • Use fill for solid shapes
  • Use both for outlined shapes with a fill color
  • Omit both to create invisible paths (useful for clipping regions)

Next Steps