Leveraging Cairo’s Recording Surface for Multi‑Resolution Rendering
Learn how Cairo’s recording surface lets you record drawing commands once and replay them to any target—PNG, PDF, or screen—without re‑drawing. The guide covers bounded vs unbounded extents, ink extents, a practical example, and trade‑offs for memory and performance.
14 May 2026, 17:36 UTC

Problem: One Draw, Many Outputs
When a UI or document engine needs to render the same scene at different resolutions—screen (1×), high‑DPI display (2×), printer (300 dpi), PDF export—re‑drawing every time is wasteful. Traditional raster surfaces tie the drawing to a specific pixel grid, so you must redraw for each target size. Cairo’s cairo_recording_surface_t solves this by storing drawing commands instead of pixels.
Thesis: Record Once, Replay Anywhere
A recording surface captures all drawing operations as an immutable command stream. You can replay that stream onto any surface type, apply arbitrary transforms, and the backend decides how to rasterize or vector‑ize. This pattern is ideal for thumbnails, print previews, and document pipelines.
Key Concepts
Bounded vs Unbounded Recordings
When you create a recording surface you may specify a rectangle of extents. If you pass NULL, the surface is "unbounded" and will record all commands regardless of coordinates. An unbounded recording is useful when you want to replay at any scale. A bounded recording clips during replay to its rectangle, which can save memory if you know the scene fits a fixed area.
Ink Extents
From Cairo 1.10 onward, cairo_recording_surface_ink_extents() returns the tightest bounding box that contains all stroked and filled ink. This is especially handy when generating PDF pages: you can use the ink extents to size the page automatically, avoiding manual calculations.
Immutability and Thread Safety
Once a recording surface is finished, it is read‑only. You can replay it from any thread, but you must not use the same cairo_t context for recording and replay concurrently. The command stream itself is safe to read from multiple threads.
Concrete Example
Below is a minimal C program that records a simple path, queries its ink extents, then replays the recording to a PNG and a PDF. Replace <PLACEHOLDER> with actual file paths.
#include <cairo.h>
int main(void) {
cairo_surface_t *recording;
cairo_t *cr;
/* 1. Create an unbounded recording surface */
recording = cairo_recording_surface_create(CAIRO_CONTENT_COLOR, NULL);
cr = cairo_create(recording);
/* 2. Draw a simple shape */
cairo_move_to(cr, 10, 10);
cairo_line_to(cr, 200, 10);
cairo_line_to(cr, 200, 200);
cairo_close_path(cr);
cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); // blue
cairo_fill(cr);
/* 3. Finish recording */
cairo_destroy(cr);
/* 4. Query ink extents for page sizing */
double x0, y0, x1, y1;
cairo_recording_surface_ink_extents(recording, &x0, &y0, &x1, &y1);
printf("Ink extents: (%f, %f) – (%f, %f)\n", x0, y0, x1, y1);
/* 5. Replay to PNG */
cairo_surface_t *png = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
(int)(x1 - x0),
(int)(y1 - y0));
cairo_t *png_cr = cairo_create(png);
cairo_set_source_surface(png_cr, recording, -x0, -y0); // shift to origin
cairo_paint(png_cr);
cairo_surface_write_to_png(png, <PLACEHOLDER_FOR_PNG>);
cairo_destroy(png_cr);
cairo_surface_destroy(png);
/* 6. Replay to PDF */
cairo_surface_t *pdf = cairo_pdf_surface_create(<PLACEHOLDER_FOR_PDF>, 0, 0);
cairo_t *pdf_cr = cairo_create(pdf);
cairo_set_source_surface(pdf_cr, recording, -x0, -y0);
cairo_paint(pdf_cr);
cairo_surface_destroy(pdf);
cairo_destroy(pdf_cr);
cairo_surface_destroy(recording);
return 0;
}
Key points:
- Recording is unbounded; the ink extents give you the minimal page size.
- When replaying, shift the recording so its origin aligns with the target surface’s origin.
- Replaying to PDF preserves vector data (text, paths) because the PDF backend interprets the command stream directly.
Trade‑offs and Limitations
Memory vs Replay Latency
The command stream’s size grows with the number of drawing operations, not pixel count. A scene with 10,000 tiny rectangles may consume less memory than a 300 dpi raster surface of the same visual area. However, replaying a very large recording can incur noticeable latency, especially on low‑end CPUs or when replaying to a backend that performs heavy rasterization (e.g., PNG).
Unbounded Recordings and Coordinate Systems
Unbounded recordings replay in the target surface’s coordinate system. If you record in a coordinate space that differs from the target (e.g., Y‑axis direction in PDF vs. X11), you must apply an explicit transform before painting. Otherwise the image will appear flipped or mis‑aligned.
Limited State Capture
Only drawing commands are stored; surface‑specific state such as device transforms, clip regions, or filter parameters are not recorded. These are applied at replay time, so you must set them on the replay context if needed.
Font Subsetting and Text Preservation
When replaying to a PDF or SVG, Cairo preserves text as selectable glyphs. If you first replay to an image surface and then embed that image into a PDF, the text becomes rasterized and loses metadata. For document pipelines that need searchable text, replay directly to the PDF backend.
Actionable Closing
To adopt recording surfaces in your pipeline:
- Record UI elements at the highest necessary DPI once per frame.
- Cache the recording surface if the scene is static; replay it for each output.
- Use
cairo_recording_surface_ink_extents()to size PDF pages automatically. - Measure memory usage: record a representative scene, then compare the recording surface’s size to a raster surface of the same visual content.
- Profile replay time on your target hardware; if latency is an issue, consider tiling large recordings or simplifying the path data.
Recording surfaces are a powerful tool for any application that needs to generate multiple renditions from the same vector data. By decoupling drawing from rasterization, you gain flexibility, lower memory footprints, and cleaner code paths for both screen and print output.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.