Embed, subset, and detect fonts in PDFs

This guide shows developers how to use PDFluent to manage fonts in PDF documents, ensuring compatibility and optimising file size.

Embed fonts fully into a PDF in Rust

Load font files from disk and embed them into a PDF so it renders correctly on any system.

rust
use pdfluent::{PdfDocument, Font};

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("input.pdf")?;

    // Load a font from disk and embed it
    let font = Font::from_file("fonts/NotoSans-Regular.ttf")?;
    doc.embed_font(&font)?;

    doc.save("with_embedded_font.pdf")?;
    Ok(())
}
  1. Load a font from a file

    PDFluent accepts TrueType (.ttf), OpenType (.otf), and TrueType Collection (.ttc) files. Pass the path to Font::from_file.

    rust
    use pdfluent::Font;
    
    let font = Font::from_file("fonts/NotoSans-Regular.ttf")?;
  2. Embed the font into the document

    embed_font adds the font program to the PDF as a stream object and registers it in the document font resources.

    rust
    doc.embed_font(&font)?;
  3. Use the font when drawing text

    After embedding, use the font handle when adding text to a page. The font name in the content stream references the embedded resource.

    rust
    use pdfluent::content::{TextBuilder, Color};
    
    let mut page = doc.add_page(pdfluent::PageSize::A4);
    page.draw_text(
        TextBuilder::new("Hello, world!")
            .font(&font)
            .size(14.0)
            .position(72.0, 700.0)
            .color(Color::black()),
    )?;
  4. Embed a font into an existing page resource dictionary

    To re-embed a font that a page already references by name but is not embedded, look it up by its PDF resource name.

    rust
    let font_data = std::fs::read("fonts/Arial.ttf")?;
    doc.embed_font_data("Arial", &font_data)?;
  5. Verify embedding succeeded

    List fonts after the operation to confirm each font shows is_embedded = true.

    rust
    for font in doc.fonts() {
        assert!(font.is_embedded(), "Font {} not embedded", font.name());
    }
    doc.save("with_embedded_font.pdf")?;
  • Embedded fonts make the PDF larger but guarantee consistent rendering on all platforms.
  • For web delivery, consider subsetting after embedding to keep file size down.
  • Standard 14 PDF fonts (Helvetica, Times, Courier, etc.) are not embedded by convention; viewers are required to supply them.
  • Font licensing applies to embedding. Fonts with the fsType embedding restriction bits set to 2 (no embedding) cannot legally be embedded.

Subset embedded fonts to reduce PDF size in Rust

Strip unused glyphs from embedded fonts so the PDF only carries the characters that actually appear in the document.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("report.pdf")?;
    let _report = doc.subset_fonts()?;
    doc.save("subset.pdf")?;
    Ok(())
}
  1. Open the PDF and inspect font usage

    Before subsetting, you can list embedded fonts and their sizes to understand what will change.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("input.pdf")?;
    println!("{} pages", doc.page_count());
  2. Build subsetting options

    FontSubsetOptions controls which font types are processed. Type 1 fonts often have limited glyph sets already; focus on TrueType and OpenType where gains are largest.

    rust
    // subset_fonts() removes unused glyphs from embedded fonts.
    // It is core-tier and takes no configuration in 1.0.
  3. Run the subsetter

    PDFluent scans every page content stream, collects the Unicode codepoints actually used, then rewrites each embedded font to contain only those glyphs.

    rust
    let report = doc.subset_fonts()?;
    println!(
        "Subsetted {} of {} fonts, saved {} bytes",
        report.fonts_subsetted, report.fonts_processed, report.bytes_saved,
    );
  4. Compare sizes

    Check the font sizes again after subsetting to measure the reduction.

    rust
    // The FontSubsetReport summarises the result:
    //   fonts_processed - fonts_subsetted - bytes_saved
    println!("bytes saved: {}", report.bytes_saved);
  5. Save the output

    Write the subsetted file. Combine with compress_streams() for maximum size reduction.

    rust
    use pdfluent::CompressOptions;
    
    doc.compress(CompressOptions::strict())?;
    doc.save("subsetted.pdf")?;
  • A font with 80,000 glyphs used for two characters will be reduced from several MB to a few KB after subsetting.
  • Subsetting marks the font with a 6-character prefix tag (e.g. ABCDEF+FontName) per the PDF spec.
  • Subsetting is safe for archiving: PDF/A-3 conformance allows subset fonts.
  • Do not subset fonts in documents where end users may add text later; they would need the full font to type new characters.

Detect missing or unembedded fonts in a PDF in Rust

Scan every page resource dictionary and identify fonts that are referenced but not embedded in the file.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("input.pdf")?;

    let missing: Vec<_> = doc
        .fonts()
        .filter(|f| !f.is_embedded() && !f.is_standard_14())
        .collect();

    if missing.is_empty() {
        println!("All fonts are embedded.");
    } else {
        for font in &missing {
            println!("Missing: {} ({:?})", font.name(), font.font_type());
        }
        std::process::exit(1);
    }

    Ok(())
}
  1. Open the PDF in read mode

    For an audit task you only need a read-only Document.

    rust
    let doc = PdfDocument::open("input.pdf")?;
  2. Iterate over all font references

    doc.fonts() returns an iterator over all font dictionaries referenced from any page resource dictionary in the document.

    rust
    for font in doc.fonts() {
        println!(
            "name={} type={:?} embedded={} standard14={}",
            font.name(),
            font.font_type(),
            font.is_embedded(),
            font.is_standard_14(),
        );
    }
  3. Filter for unembedded non-standard fonts

    Standard 14 fonts (Helvetica, Times-Roman, Courier, etc.) are provided by PDF viewers and do not need embedding. All other fonts should be embedded for reliable rendering.

    rust
    let unembedded: Vec<_> = doc
        .fonts()
        .filter(|f| !f.is_embedded() && !f.is_standard_14())
        .collect();
  4. Print a per-page report

    To know which page each font appears on, iterate page by page.

    rust
    for (i, page) in doc.pages().enumerate() {
        for font in page.fonts() {
            if !font.is_embedded() && !font.is_standard_14() {
                println!("Page {}: unembedded font {}", i + 1, font.name());
            }
        }
    }
  5. Return a non-zero exit code for CI gating

    Use the missing font list to fail a CI pipeline when required fonts are absent.

    rust
    if !unembedded.is_empty() {
        eprintln!("{} unembedded font(s) found", unembedded.len());
        std::process::exit(1);
    }
  • A font can be present in the resource dictionary but have an empty or missing font program stream. is_embedded() checks for the stream, not just the dictionary entry.
  • Subset fonts are still considered embedded. The 6-character prefix tag does not affect the is_embedded check.
  • Type3 fonts (custom glyph shapes) are always "embedded" by definition since the glyph procedures are in the PDF itself.
  • Combine this check with the PDF/A validator for a full compliance audit.