Generate a Vector PDF with Cairo and FreeType
Use Cairo's PDF backend and FreeType to draw vector shapes and Unicode text into a portable PDF without rasterization.
12 Nov 2025, 06:53 UTC

Generating a report or certificate as a PDF often forces a choice between embedding a raster image or producing true vector output. Cairo’s PDF backend writes PDF 1.7 vector operations directly, so rectangles, curves, and text remain scalable in the file. FreeType supplies the font data, and Cairo converts it to glyph outlines rather than a bitmap. The practical takeaway is to create a Cairo PDF surface, bind a FreeType face to it, draw vector paths and text, then finish the surface before viewers can open the file.
Desired outcome
A single PDF file containing vector paths, filled shapes, and Unicode text drawn from a TrueType or OpenType font. The text should remain selectable where the viewer supports embedded glyph outlines, and the page should scale without pixelation.
Prerequisites
- Cairo development headers and shared library with the PDF backend enabled. On Debian-like systems install
cairo-dev; the PDF backend is included in Cairo, not a separate plugin. - FreeType development headers and library, typically
libfreetype6-devon Debian-like systems. Cairo’s font integration usescairo_ft_font_face_create_for_ft_face, which builds a Cairo font face from anFT_Faceobject. - A C compiler and
pkg-config. Run build commands as a normal user in a writable directory. Root access is only needed to install missing system packages. - A readable TrueType or OpenType font file, for example
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf. Substitute a path you control.
Missing FreeType headers produce linker errors for cairo_ft_font_face_create_for_ft_face. Confirm the headers exist before compiling.
Focused procedure
Create and configure the PDF surface
A Cairo surface is the destination for drawing commands. A PDF surface writes PDF operators to a file instead of pixels to memory. Create it with the page size in points (1 point equals 1/72 inch).
cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", 595, 842);
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
/* handle error: file may be unwritable or path invalid */
}
cairo_t *cr = cairo_create(surface);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_GRAY);
cairo_pdf_surface_set_compression((cairo_pdf_surface_t *)surface, 9);
Run this code in your application’s render function, not in a shell. cairo_set_antialias controls edge smoothing; CAIRO_ANTIALIAS_GRAY is a safe default for print and PDF. cairo_pdf_surface_set_compression applies zlib compression to the PDF stream; level 9 is maximum, but some older viewers handle lower levels more reliably.
Load a FreeType face and bind it to Cairo
FreeType parses the font file; Cairo uses the parsed face to draw text. Initialize FreeType, open the face, set a size, then create the Cairo font face.
FT_Library ft_lib;
FT_Face ft_face;
FT_Error ft_error = FT_Init_FreeType(&ft_lib);
if (ft_error) { /* handle FreeType initialization failure */ }
ft_error = FT_New_Face(ft_lib, "/path/to/font.ttf", 0, &ft_face);
if (ft_error) { /* handle missing or unreadable font file */ }
FT_Set_Pixel_Sizes(ft_face, 0, 48);
cairo_font_face_t *font_face = cairo_ft_font_face_create_for_ft_face(ft_face, 0);
cairo_set_font_face(cr, font_face);
cairo_set_font_size(cr, 48);
Compile with -lcairo -lfreetype, or preferably $(pkg-config --cflags --libs cairo freetype2). Keep the FT_Face alive until the surface is finished; Cairo references it during drawing.
Draw vector shapes and text
Paths and fills are recorded as PDF vector instructions. This example draws a translucent rectangle and a line of Unicode text.
cairo_rectangle(cr, 50, 50, 500, 300);
cairo_set_source_rgba(cr, 0.2, 0.4, 0.8, 0.6);
cairo_fill(cr);
cairo_move_to(cr, 70, 200);
cairo_show_text(cr, "Cairo PDF with FreeType text");
cairo_show_text renders the current UTF-8 string using the bound FreeType font. Cairo writes the text as glyph outlines in the PDF, not as a raster image.
Finish the surface and release resources
Drawing commands are buffered. cairo_surface_finish flushes the PDF trailer and closes the file.
cairo_destroy(cr);
cairo_surface_finish(surface);
cairo_surface_destroy(surface);
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_lib);
Do not destroy the surface before finishing it, or the PDF may be incomplete. Writing to output.pdf changes state by creating or overwriting a file; write to a temporary name and rename after verification if you must preserve an existing file.
Build command
Save the code to make_pdf.c and compile as a normal user:
gcc -o make_pdf make_pdf.c $(pkg-config --cflags --libs cairo freetype2)
Expected result: an executable named make_pdf with no linker errors. If the compiler reports undefined references to FreeType or Cairo font functions, install the development packages and confirm pkg-config can locate both libraries.
Expected checks
- Immediately after surface creation, check
cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS. A non-success status means the file could not be opened. - After
cairo_surface_finish, confirmoutput.pdfexists and is non-empty:ls -l output.pdf. - Run
file output.pdf. It should identify a PDF document, for example starting with%PDF. - Open the PDF in a viewer. Zoom in on the rectangle and text; edges should stay smooth because the content is vector, not rasterized.
- To validate layout independently, draw the same commands to an image surface and call
cairo_surface_write_to_png, then compare the PNG visually to the PDF page.
Limitations and recovery options
Cairo’s PDF backend does not support interactive annotations, hyperlinks, or form fields. Add those after generation with a PDF manipulation library if required.
Compression level 9 can reduce file size, but not every viewer handles every PDF compression filter identically. If a viewer reports a corrupt or unreadable file, call cairo_pdf_surface_set_compression(surface, 0) and regenerate.
If text appears blank or missing, verify the font path is readable and FT_New_Face returns 0. Fall back to a different system font path if necessary.
Because this operation overwrites output.pdf, keep a backup copy before running if the previous file matters. Otherwise, write to a new filename and replace the original only after the checks above pass.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.