LibPDF

Drawing

Draw text, shapes, and images on PDF pages.

Drawing

This guide covers drawing operations on PDF pages.

Coordinate System

PDF uses a coordinate system where:

  • Origin (0, 0) is at the bottom-left corner
  • X increases going right
  • Y increases going up
  • Units are points (1 inch = 72 points)
(0, 792) ─────────────── (612, 792)
    │                         │
    │     US Letter Page      │
    │                         │
(0, 0) ─────────────────  (612, 0)

Colors

Import the color helpers:

import { rgb, cmyk, grayscale } from "@libpdf/core";

// RGB (values 0-1)
const red = rgb(1, 0, 0);
const blue = rgb(0, 0, 1);
const custom = rgb(0.2, 0.4, 0.8);

// CMYK (values 0-1)
const processBlack = cmyk(0, 0, 0, 1);

// Grayscale (0 = black, 1 = white)
const gray = grayscale(0.5);
const black = grayscale(0);
const white = grayscale(1);

Draw Text

Basic Text

page.drawText("Hello, World!", {
  x: 50,
  y: 700,
  size: 24,
});

Text Options

page.drawText("Styled Text", {
  x: 50,
  y: 650,
  size: 16,
  color: rgb(0.2, 0.4, 0.8),
  font: customFont,           // Embedded font
  lineHeight: 24,             // Line height in points
  maxWidth: 200,              // Word wrap width
  opacity: 0.8,               // 0-1
});

Multi-line Text

page.drawText("Line 1\nLine 2\nLine 3", {
  x: 50,
  y: 600,
  size: 12,
  lineHeight: 18,  // Line height in points
});

Word Wrap

const longText = "This is a very long paragraph that will automatically wrap to fit within the specified maximum width.";

page.drawText(longText, {
  x: 50,
  y: 550,
  size: 12,
  maxWidth: 200,  // Wrap at 200 points
  lineHeight: 16, // Line height in points
});

Text with Custom Fonts

const fontBytes = await readFile("fonts/OpenSans-Regular.ttf");
const font = pdf.embedFont(fontBytes);  // Synchronous

page.drawText("Custom Font", {
  x: 50,
  y: 500,
  size: 14,
  font,
});

Draw Shapes

Rectangle

// Filled rectangle
page.drawRectangle({
  x: 50,
  y: 400,
  width: 200,
  height: 100,
  color: rgb(0.9, 0.9, 0.9),
});

// Outlined rectangle
page.drawRectangle({
  x: 50,
  y: 280,
  width: 200,
  height: 100,
  borderColor: rgb(0, 0, 0),
  borderWidth: 2,
});

// Filled with border
page.drawRectangle({
  x: 300,
  y: 400,
  width: 200,
  height: 100,
  color: rgb(1, 0.9, 0.9),
  borderColor: rgb(0.8, 0, 0),
  borderWidth: 1,
});

Rounded Rectangle

page.drawRectangle({
  x: 50,
  y: 150,
  width: 200,
  height: 100,
  color: rgb(0.9, 0.95, 1),
  borderColor: rgb(0.3, 0.5, 0.8),
  borderWidth: 1,
  cornerRadius: 10,
});

Circle

page.drawCircle({
  x: 150,      // Center X
  y: 500,      // Center Y
  radius: 50,
  color: rgb(0.8, 0.9, 1),
  borderColor: rgb(0, 0.3, 0.6),
  borderWidth: 2,
});

Ellipse

page.drawEllipse({
  x: 150,      // Center X
  y: 400,      // Center Y
  xRadius: 80,
  yRadius: 40,
  color: rgb(1, 0.9, 0.8),
  borderColor: rgb(0.6, 0.3, 0),
  borderWidth: 1,
});

Line

page.drawLine({
  start: { x: 50, y: 300 },
  end: { x: 250, y: 350 },
  color: rgb(0, 0, 0),
  thickness: 1,
});

// Dashed line
page.drawLine({
  start: { x: 50, y: 280 },
  end: { x: 250, y: 280 },
  color: rgb(0.5, 0.5, 0.5),
  thickness: 1,
  dashArray: [5, 3],  // 5pt dash, 3pt gap
});

Draw Images

Embed and Draw

// JPEG
const jpegBytes = await readFile("photo.jpg");
const jpegImage = await pdf.embedJpeg(jpegBytes);

page.drawImage(jpegImage, {
  x: 50,
  y: 100,
  width: 200,
  height: 150,
});

// PNG (with transparency support)
const pngBytes = await readFile("logo.png");
const pngImage = await pdf.embedPng(pngBytes);

page.drawImage(pngImage, {
  x: 300,
  y: 100,
  width: 100,
  height: 100,
});

Preserve Aspect Ratio

// Specify only width - height calculated automatically
page.drawImage(image, {
  x: 50,
  y: 100,
  width: 200,
});

// Or only height
page.drawImage(image, {
  x: 50,
  y: 100,
  height: 150,
});

Image Opacity

page.drawImage(image, {
  x: 50,
  y: 100,
  width: 200,
  height: 150,
  opacity: 0.5,
});

Custom Paths

For complex shapes, use the path builder:

// Triangle using the fluent path API
page.drawPath()
  .moveTo(100, 100)
  .lineTo(150, 200)
  .lineTo(50, 200)
  .close()
  .fillAndStroke({
    color: rgb(0.8, 0.2, 0.2),
    borderColor: rgb(0.4, 0, 0),
    borderWidth: 2,
  });

Curves

// Quadratic curve (converted to cubic internally)
page.drawPath()
  .moveTo(50, 300)
  .quadraticCurveTo(100, 400, 150, 300)  // Control point, end point
  .stroke({ borderColor: rgb(0, 0, 0), borderWidth: 2 });

// Cubic bezier curve
page.drawPath()
  .moveTo(200, 300)
  .curveTo(225, 200, 275, 400, 300, 300)  // Two control points, end point
  .stroke({ borderColor: rgb(0, 0, 0), borderWidth: 2 });

Drawing Order

Content is drawn in order - later drawings appear on top:

// Background first
page.drawRectangle({
  x: 0, y: 0,
  width: 612, height: 792,
  color: rgb(0.95, 0.95, 0.95),
});

// Then content
page.drawText("On top of background", {
  x: 50,
  y: 700,
  size: 16,
});

Draw on Existing Pages

const pdf = await PDF.load(existingBytes);
const page = await pdf.getPage(0);

// Add watermark
page.drawText("CONFIDENTIAL", {
  x: 200,
  y: 400,
  size: 60,
  color: rgb(1, 0, 0),
  opacity: 0.3,
});

const bytes = await pdf.save();

Transformations

Rotation

Rotate content using the rotate option:

import { degrees } from "@libpdf/core";

// Rotate text around its position
page.drawText("Rotated Text", {
  x: 280,
  y: 400,
  size: 14,
  rotate: degrees(45),  // 45 degrees counter-clockwise
});

Complete Example

import { writeFile } from "fs/promises";
import { PDF, rgb } from "@libpdf/core";

const pdf = PDF.create();
const page = pdf.addPage({ size: "a4" });
const { width, height } = page;

// Background
page.drawRectangle({
  x: 0, y: 0,
  width, height,
  color: rgb(0.98, 0.98, 1),
});

// Header bar
page.drawRectangle({
  x: 0, y: height - 60,
  width, height: 60,
  color: rgb(0.2, 0.4, 0.8),
});

page.drawText("Document Title", {
  x: 50,
  y: height - 40,
  size: 24,
  color: rgb(1, 1, 1),
});

// Content
page.drawText("Section 1", {
  x: 50,
  y: height - 120,
  size: 18,
  color: rgb(0.2, 0.2, 0.2),
});

page.drawLine({
  start: { x: 50, y: height - 130 },
  end: { x: width - 50, y: height - 130 },
  color: rgb(0.8, 0.8, 0.8),
  thickness: 1,
});

page.drawText("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", {
  x: 50,
  y: height - 160,
  size: 12,
  maxWidth: width - 100,
  lineHeight: 18,
});

await writeFile("styled-document.pdf", await pdf.save());

Next Steps

On this page