PdfPig

repository·master·Indexed 25 days ago

https://github.com/uglytoad/pdfpig

A C# library for reading text and content from PDF files, performing advanced layout analysis, and basic PDF document creation. It provides tools for extracting words, images, and letters with precise positioning, as well as support for reading encrypted documents, extracting AcroForms, bookmarks, and embedded files. The library includes PdfDocumentBuilder for creating new PDFs with Standard 14 or TrueType fonts, and PdfMerger for combining multiple PDF files.

Tokens
5.9K
Snippets
10
Records
21
Agent score
82%

What's inside PdfPig

  1. Understand CIDFont glyph metrics and mapping

    master

    In CID-keyed fonts, glyphs are accessed via CIDs (Character Identifiers).

    Mapping

    • CMap: Maps character codes to CIDs.
    • CIDToGIDMap: In embedded font streams, this maps CIDs to Glyph Indexes (GIDs). For predefined external fonts, this map is not used; instead, a predefined CMap is used.

    Metrics

    Widths for CIDFonts are defined in the CIDFont dictionary using:

    • DW: The default width for glyphs not individually specified.
    • W: Defines widths for specific individual CIDs.

    Note: Every CIDFont must describe CID 0, which represents the .notdef character (used for missing characters).

  2. Understand the Letter abstraction for precise text positioning

    master

    Because PDF is a presentation format, text order in page.Text might not match visual reading order. To handle this, use page.Letters to get an IReadOnlyList<Letter>.

    Each Letter provides:

    • Value: The actual text character.
    • Location: The lower-left coordinate (PDF coordinates: origin is lower-left, higher Y is closer to top).
    • Width: The width of the letter.
    • FontSize: The font size in unscaled relative text units.
    • FontName: The name of the font used.
    • GlyphRectangle: The bounding box of the visible glyph.
    • StartBaseLine / EndBaseLine: Used to detect rotation via TextDirection.
  3. Understand PDF font types and hierarchies

    master

    PDF fonts are categorized into two main groups: Composite Fonts and Simple Fonts.

    Composite Fonts (Type 0)

    These are used for large character sets (like CJK - Chinese, Japanese, Korean) and use multiple-byte sequences to select glyphs. They rely on a CIDFont descendant and a CMap to map character codes to Character Identifiers (CIDs).

    • Type 0 fonts use a DescendantFonts array to point to a CIDFont.
    • CIDFont types include CIDFontType0 (Adobe Type 1) and CIDFontType2 (TrueType).

    Simple Fonts

    These select glyphs using single-byte character codes (indexing into a 256-entry table) and only support horizontal writing mode.

    • Type 1: Uses PostScript technology.
    • Type 3: Defines glyphs via streams of PDF graphics operations.
    • TrueType: Uses the standard TrueType font format.
  4. Read text from a PDF

    master

    To extract text from a PDF, open the document using PdfDocument.Open and iterate through the pages.

    Important: Avoid using page.Text directly, as it preserves internal content order which often does not match the visual reading order. Instead, use layout analysis tools like ContentOrderTextExtractor.GetText(page) for human-readable text or page.GetWords(...) for word-level extraction.

    using (PdfDocument document = PdfDocument.Open(@"C:\Documents\document.pdf"))
    {
        foreach (Page page in document.GetPages())
        {
            string text = ContentOrderTextExtractor.GetText(page);
            IEnumerable<Word> words = page.GetWords(NearestNeighbourWordExtractor.Instance);
        }
    }
  5. Install PdfPig via NuGet

    master

    You can install PdfPig using the NuGet package manager or the Package Manager Console. Note that because the current version is below 1.0.0, minor versions may change the public API without warning.

    Install-Package PdfPig
  6. Create a PDF document with PdfDocumentBuilder

    master

    Use PdfDocumentBuilder to create new PDF files.

    Key requirements:

    • Use builder.AddPage(PageSize) to add pages.
    • Font Registration: You must register fonts with the PdfDocumentBuilder (e.g., using AddStandard14Font) before using them in page.AddText. This prevents duplication and allows pages to share font resources.
    • Supported fonts include Standard 14 fonts and TrueType fonts (.ttf).

    Limitations: Document creation does not support editing forms, changing annotations/metadata, or adding/removing text with existing fonts.

    PdfDocumentBuilder builder = new PdfDocumentBuilder();
    
    PdfPageBuilder page = builder.AddPage(PageSize.A4);
    
    // Fonts must be registered with the document builder prior to use to prevent duplication.
    PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica);
    
    page.AddText("Hello World!", 12, new PdfPoint(25, 700), font);
    
    byte[] documentBytes = builder.Build();
    
    File.WriteAllBytes(@"C:\git\newPdf.pdf", documentBytes);
  7. Create a new PDF document with text and fonts

    master

    Use PdfDocumentBuilder to construct a new PDF. You can add standard 14 fonts (like Helvetica) or register TrueType (.ttf) fonts to support non-ASCII characters.

    Note on Coordinates: PDF coordinates run from the bottom-left of the page upwards. The Y coordinate of the top of the page is higher than 0.

    using System.IO;
    using UglyToad.PdfPig.Content;
    using UglyToad.PdfPig.Core;
    using UglyToad.PdfPig.Fonts.Standard14Fonts;
    using UglyToad.PdfPig.Writer;
    
    public static class Program
    {
        public static void Main()
        {
            PdfDocumentBuilder builder = new PdfDocumentBuilder();
    
            PdfDocumentBuilder.AddedFont helvetica = builder.AddStandard14Font(Standard14Font.Helvetica);
            PdfDocumentBuilder.AddedFont helveticaBold = builder.AddStandard14Font(Standard14Font.HelveticaBold);
    
            PdfPageBuilder page = builder.AddPage(PageSize.A4);
    
            PdfPoint closeToTop = new PdfPoint(15, page.PageSize.Top - 25);
    
            page.AddText("My first PDF document!", 12, closeToTop, helvetica);
            page.AddText("Hello World!", 10, closeToTop.Translate(0, -15), helveticaBold);
    
            File.WriteAllBytes(@"C:\temp\file.pdf", builder.Build());
        }
    }

    Using TrueType Fonts:

    PdfDocumentBuilder builder = new PdfDocumentBuilder();
    byte[] robotoBytes = File.ReadAllBytes(@"C:\fonts\roboto.ttf");
    PdfDocumentBuilder.AddedFont roboto = builder.AddTrueTypeFont(robotoBytes);
    using System.IO;
    using UglyToad.PdfPig.Content;
    using UglyToad.PdfPig.Core;
    using UglyToad.PdfPig.Fonts.Standard14Fonts;
    using UglyToad.PdfPig.Writer;
    
    public static class Program
    {
        public static void Main()
        {
            PdfDocumentBuilder builder = new PdfDocumentBuilder();
    
            PdfDocumentBuilder.AddedFont helvetica = builder.AddStandard14Font(Standard14Font.Helvetica);
            PdfDocumentBuilder.AddedFont helveticaBold = builder.AddStandard14Font(Standard14Font.HelveticaBold);
    
            PdfPageBuilder page = builder.AddPage(PageSize.A4);
    
            PdfPoint closeToTop = new PdfPoint(15, page.PageSize.Top - 25);
    
            page.AddText("My first PDF document!", 12, closeToTop, helvetica);
    
            page.AddText("Hello World!", 10, closeToTop.Translate(0, -15), helveticaBold);
    
            File.WriteAllBytes(@"C:\temp\file.pdf", builder.Build());
        }
    }
  8. Access PdfDocument metadata, forms, and structure

    master

    The PdfDocument class provides access to various internal structures including AcroForms, XMP metadata, bookmarks, and embedded files.

    using System;
    using System.Collections.Generic;
    using System.Xml.Linq;
    using UglyToad.PdfPig;
    using UglyToad.PdfPig.AcroForms;
    using UglyToad.PdfPig.AcroForms.Fields;
    using UglyToad.PdfPig.Content;
    using UglyToad.PdfPig.Outline;
    
    public static class Program
    {
        public static void Main()
        {
            using (PdfDocument document = PdfDocument.Open(@"C:\temp\file.pdf"))
            {
                Console.WriteLine($"Document has {document.NumberOfPages} pages.");
    
                if (document.TryGetForm(out AcroForm form))
                {
                    foreach (AcroFieldBase field in form.GetFieldsForPage(1))
                    {
                        switch (field)
                        {
                            case AcroCheckboxField cb:
                                if (cb.IsChecked)
                                {
                                    Console.WriteLine($"Checkbox was checked: {cb.Information.MappingName}.");
                                }
                                break;
                        }
                    }
                }
    
                if (document.TryGetXmpMetadata(out XmpMetadata metadata))
                {
                    XDocument xmp = metadata.GetXDocument();
                }
                
                if (document.TryGetBookmarks(out Bookmarks bookmarks))
                {
                    Console.WriteLine($"Document contained bookmarks with {bookmarks.Roots.Count} root nodes.");
                }
    
                Console.WriteLine($"Document uses version {document.Version} of the PDF specification.");
    
                if (document.Advanced.TryGetEmbeddedFiles(out IReadOnlyList<EmbeddedFile> embeddedFiles))
                {
                    Console.WriteLine($"Document contains {embeddedFiles.Count} embedded files.");
                }
            }
        }
    }
    using System;
    using System.Collections.Generic;
    using System.Xml.Linq;
    using UglyToad.PdfPig;
    using UglyToad.PdfPig.AcroForms;
    using UglyToad.PdfPig.AcroForms.Fields;
    using UglyToad.PdfPig.Content;
    using UglyToad.PdfPig.Outline;
    
    public static class Program
    {
        public static void Main()
        {
            using (PdfDocument document = PdfDocument.Open(@"C:\temp\file.pdf"))
            {
                Console.WriteLine($"Document has {document.NumberOfPages} pages.");
    
                if (document.TryGetForm(out AcroForm form))
                {
                    foreach (AcroFieldBase field in form.GetFieldsForPage(1))
                    {
                        switch (field)
                        {
                            case AcroCheckboxField cb:
                                if (cb.IsChecked)
                                {
                                    Console.WriteLine($"Checkbox was checked: {cb.Information.MappingName}.");
                                }
                                break;
                        }
                    }
                }
    
                if (document.TryGetXmpMetadata(out XmpMetadata metadata))
                {
                    XDocument xmp = metadata.GetXDocument();
                }
                
                if (document.TryGetBookmarks(out Bookmarks bookmarks))
                {
                    Console.WriteLine($"Document contained bookmarks with {bookmarks.Roots.Count} root nodes.");
                }
    
                Console.WriteLine($"Document uses version {document.Version} of the PDF specification.");
    
                if (document.Advanced.TryGetEmbeddedFiles(out IReadOnlyList<EmbeddedFile> embeddedFiles))
                {
                    Console.WriteLine($"Document contains {embeddedFiles.Count} embedded files.");
                }
            }
        }
    }
  9. Perform advanced document extraction and layout analysis

    master

    For complex extraction, you can combine several layout analysis tools to identify words, segment them into blocks, and determine reading order. This process can be used to create a 'debug' version of a PDF that visualizes bounding boxes and reading order.

    Typical workflow:

    1. Extract Words: Use NearestNeighbourWordExtractor.Instance.GetWords(letters).
    2. Segment Page: Use DocstrumBoundingBoxes.Instance.GetBlocks(words) to group words into blocks.
    3. Determine Reading Order: Use UnsupervisedReadingOrderDetector.Instance.Get(textBlocks) to order the blocks.
    4. Visualize/Export: Use PdfDocumentBuilder to draw rectangles (DrawRectangle) and text labels over the original content based on the extracted metadata.
    var sourcePdfPath = "";
    var outputPath = "";
    var pageNumber = 1;
    using (var document = PdfDocument.Open(sourcePdfPath))
    {
        var builder = new PdfDocumentBuilder { };
        PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica);
        var pageBuilder = builder.AddPage(document, pageNumber);
        pageBuilder.SetStrokeColor(0, 255, 0);
        var page = document.GetPage(pageNumber);
    
        var letters = page.Letters; // no preprocessing
    
        // 1. Extract words
        var wordExtractor = NearestNeighbourWordExtractor.Instance;
    
        var words = wordExtractor.GetWords(letters);
    
        // 2. Segment page
        var pageSegmenter = DocstrumBoundingBoxes.Instance;
    
        var textBlocks = pageSegmenter.GetBlocks(words);
    
        // 3. Postprocessing
        var readingOrder = UnsupervisedReadingOrderDetector.Instance;
        var orderedTextBlocks = readingOrder.Get(textBlocks);
    
        // 4. Add debug info - Bounding boxes and reading order
        foreach (var block in orderedTextBlocks)
        {
            var bbox = block.BoundingBox;
            pageBuilder.DrawRectangle(bbox.BottomLeft, bbox.Width, bbox.Height);
            pageBuilder.AddText(block.ReadingOrder.ToString(), 8, bbox.TopLeft, font);
        }
    
        // 5. Write result to a file
        byte[] fileBytes = builder.Build();
        File.WriteAllBytes(outputPath, fileBytes); // save to file
    }
  10. Open password protected PDF documents

    master

    For encrypted PDF files, provide a ParsingOptions object containing a list of potential passwords to PdfDocument.Open().

    ParsingOptions parsingOptions = new ParsingOptions
    {
        Passwords = new List<string> {"a password", "password123"}
    };
    
    using (PdfDocument document = PdfDocument.Open(@"C:\path\to\pdffile\file.pdf", parsingOptions))
    {
        // Access document metadata or pages
        Console.WriteLine(document.Information.Title);
                    
        foreach (Page page in document.GetPages())
        {
            IReadOnlyList<Letter> letters = page.Letters;
            Console.WriteLine(letters.Count);
        }
    }
    ParsingOptions parsingOptions = new ParsingOptions
    {
        Passwords = new List<string> {"a password", "password123"}
    };
    
    using (PdfDocument document = PdfDocument.Open(@"C:\path\to\pdffile\file.pdf", parsingOptions))
    {
        // Get the title from the document metadata.
        Console.WriteLine(document.Information.Title);
                    
        foreach (Page page in document.GetPages())
        {
            IReadOnlyList<Letter> letters = page.Letters;
            Console.WriteLine(letters.Count);
        }
    }
  11. Open and read text, words, and images from a PDF

    master

    To read content from a PDF, use PdfDocument.Open(). You can iterate through pages and access Letters, Words, and Images.

    using System.Collections.Generic;
    using System.Linq;
    using UglyToad.PdfPig;
    using UglyToad.PdfPig.Content;
    
    public static class Program
    {
        public static void Main()
        {
            using (PdfDocument document = PdfDocument.Open(@"C:\path\to\pdffile\file.pdf"))
            {
                foreach (Page page in document.GetPages())
                {
                    IReadOnlyList<Letter> letters = page.Letters;
                    string example = string.Join(string.Empty, letters.Select(x => x.Value));
    
                    IEnumerable<Word> words = page.GetWords();
    
                    IEnumerable<IPdfImage> images = page.GetImages();
                }
            }
        }
    }
    using System.Collections.Generic;
    using System.Linq;
    using UglyToad.PdfPig;
    using UglyToad.PdfPig.Content;
    
    public static class Program
    {
        public static void Main()
        {
            using (PdfDocument document = PdfDocument.Open(@"C:\path\to\pdffile\file.pdf"))
            {
                foreach (Page page in document.GetPages())
                {
                    IReadOnlyList<Letter> letters = page.Letters;
                    string example = string.Join(string.Empty, letters.Select(x => x.Value));
    
                    IEnumerable<Word> words = page.GetWords();
    
                    IEnumerable<IPdfImage> images = page.GetImages();
                }
            }
        }
    }