Generating Vector PDFs with Cairo’s PDF Surface Backend
Learn how to create vector PDFs with Cairo’s PDF surface backend, see a minimal C example, and understand font, transparency, and version trade‑offs.
31 Aug 2026, 21:32 UTC

Problem: You need resolution‑independent output for reports or diagrams
When a program must produce printable graphics that stay sharp at any zoom level, raster images (PNG, JPEG) fall short. Cairo can draw to many backends, but the PDF surface lets you keep text and paths as vector objects inside a PDF file.
Thesis: Using cairo_pdf_surface_create gives you true vector PDF output with minimal code, while you must handle font embedding and transparency differences yourself.
How the PDF surface works
Cairo treats the PDF surface like any other drawing target: you create it, issue Cairo operators (move_to, line_to, show_text, etc.), then finish and destroy the surface. The backend writes PDF commands that describe paths, fills, and text rather than pixel data.
Key points
- Available since Cairo 1.0; the function signature is
cairo_surface_t *cairo_pdf_surface_create(const char *filename, double width_in_points, double height_in_points). - Width and height are in points (1 point = 1/72 inch). For an A4 page use 595 × 842.
- All standard Cairo operators are supported, including groups, clipping, and patterns.
- Text can be drawn via the toy API (
cairo_show_text) or via Pango for complex layout. - Errors from the PDF backend may not appear until
cairo_surface_finishorcairo_surface_write_to_png(if you later rasterize).
Worked example: A simple A4 page with a rotated title and a line chart
The following C program creates output.pdf. Compile with gcc -o demo demo.c $(pkg-config --cflags --libs cairo).
#include <cairo.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* A4 size in points */
const double width = 595;
const double height = 842;
cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height);
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Failed to create PDF surface\n");
return EXIT_FAILURE;
}
cairo_t *cr = cairo_create(surface);
/* Background */
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
/* Rotated title */
cairo_save(cr);
cairo_translate(cr, width / 2.0, height * 0.1);
cairo_rotate(cr, -M_PI / 6); /* 30 degrees */
cairo_set_source_rgb(cr, 0.2, 0.2, 0.8);
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 48);
cairo_move_to(cr, -100, 0);
cairo_show_text(cr, "Vector PDF Demo");
cairo_restore(cr);
/* Simple line chart */
cairo_set_line_width(cr, 2);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_move_to(cr, 100, height * 0.7);
for (int i = 0; i < 10; ++i) {
double x = 100 + i * 40;
double y = height * 0.7 - (i * i * 5); /* arbitrary parabola */
cairo_line_to(cr, x, y);
}
cairo_stroke(cr);
cairo_destroy(cr);
/* Finish writes the PDF trailer; required before destroying the surface */
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Error during drawing: %s\n", cairo_status_to_string(cairo_surface_status(surface)));
}
cairo_surface_finish(surface);
cairo_surface_destroy(surface);
return EXIT_SUCCESS;
}
After running the program, open output.pdf in a viewer (e.g., Evince, Adobe Reader, or macOS Preview). The text should be selectable, and the line chart stays crisp when you zoom in.
Trade‑offs and limitations
- Font embedding: The toy text API does not embed fonts by default. If you use a glyph not present in the viewer’s fallback fonts, the text may be substituted or missing. For reliable embedding, use Pango with Cairo’s PangoFT2 backend or embed fonts manually.
- Transparency and blend modes: PDF 1.4 (the version Cairo targets) handles alpha differently from screen compositing. Complex overlapping translucent shapes may look darker or lighter in some viewers. Test in the target applications.
- File size: Pure vector output stays small for line‑based graphics, but embedding raster images (e.g., via
cairo_set_source_surfacewith a PNG) increases the PDF size because the image data is stored inside the file. - Version differences: The PDF surface API is stable in Cairo 1.x. Experimental 2.x branches may change the backend; verify your library version with
pkg-config --modversion cairo.
Actionable checklist
- Confirm Cairo version ≥ 1.0:
pkg-config --modversion cairo. - Compile and run the example above.
- Open the generated PDF in two different viewers (e.g., Evince and Adobe Reader) and verify:
- Text is selectable.
- Vector lines stay sharp at high zoom.
- No unexpected font substitutions.
#include <pango/pangocairo.h>
/* after creating cr */
PangoLayout *layout = pango_cairo_create_layout(cr);
pango_layout_set_text(layout, "Hello, PDF", -1);
pango_font_description_set_family(pango_font_description_from_string("Serif"), "Serif");
pango_layout_set_font_description(layout, pango_font_description_from_string("Serif 24"));
cairo_update_layout(cr, layout);
pango_cairo_show_layout(cr, layout);
g_object_unref(layout);
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { … }.By following these steps you get a portable, vector‑based PDF that leverages Cairo’s familiar drawing API while being aware of the backend’s specific constraints.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.