Sooner or later, most business apps need PDFs. Reports and invoices have to be generated, forms filled from your data, files protected with a password, archived in a long term format, or signed before they go out.

sgcPDF .NET covers all of those jobs in one NuGet package. Everything happens in code, headless, with nothing to open on screen, so the same calls work in a console app or in a background service.

This tour has one section per task: create a document with text, a table and an image, build and fill a form, extract text and images, encrypt with AES-256, save as PDF/A-2b, and sign with PAdES B-B. Every sample was compiled against the published packages and run, and the results quoted below are what it printed.

Setup

Create a console app and add the packages:

dotnet new console dotnet add package esegece.sgcPDF.Community dotnet add package esegece.sgcPDF.sgcSign.Community 

You only need the second package for the signing sample at the end. It brings in esegece.sgcSign.Community, which supplies the signing key provider.

The packages target netstandard2.0, net472, net6.0, net8.0, net9.0 and net10.0. Through netstandard2.0 they also reach .NET Framework 4.6.2 and later, Mono and Xamarin. The samples use net8.0:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="esegece.sgcPDF.Community" Version="2026.10.0" /> <PackageReference Include="esegece.sgcPDF.sgcSign.Community" Version="2026.10.0" /> </ItemGroup> </Project> 

Each sample below is its own file, a static class with a Run() method, because C# allows top level statements in one file only. Drop the files into the project and call the one you want from Program.cs, for example CreateSample.Run();. Everything lives in the esegece.sgcPDF namespace, plus esegece.sgcSign for the signing key.

1. Create a PDF with text, a table and an image

TsgcPDFDocument is the entry point. Add a page, draw on it and save:

using esegece.sgcPDF; static class CreateSample { public static void Run() { using var doc = new TsgcPDFDocument(); doc.Info.Title = "Quarterly report"; doc.Info.Author = "sgcPDF sample"; // A4 page, sizes in points (1/72 inch), origin at the bottom left TsgcPDFPage page = doc.AddPage(595, 842); // Title and a paragraph page.DrawText("Quarterly report", 50, 780, TsgcPDFStandardFont.sfHelveticaBold, 24); page.DrawLine(50, 770, 545, 770, 1.0); page.DrawText("Sales grew in every region this quarter, led by the North office.", 50, 745, TsgcPDFStandardFont.sfHelvetica, 12); page.DrawText("The table below lists units sold and revenue per region.", 50, 728, TsgcPDFStandardFont.sfHelvetica, 12); // A 3 column table with a header row var table = new TsgcPDFTable(3); table.SetColumnWidths(new double[] { 215, 140, 140 }); table.SetHeaders("Region", "Units", "Revenue"); table.AddRowData("North", "1,240", "EUR 49,600"); table.AddRowData("South", "980", "EUR 39,200"); table.AddRowData("East", "1,115", "EUR 44,600"); table.AddRowData("West", "860", "EUR 34,400"); table.SetHeaderFont(TsgcPDFStandardFont.sfHelveticaBold, 11); table.SetDefaultFont(TsgcPDFStandardFont.sfHelvetica, 11); table.SetHeaderBackground(0.20, 0.35, 0.60); // header text is white table.SetAlternateRowColors(0.96, 0.96, 0.96); table.DrawOnPage(page, 50, 700); // x, y of the top left corner // An image from a PNG file: x, y (bottom left), width, height page.DrawText("Logo:", 50, 565, TsgcPDFStandardFont.sfHelveticaBold, 12); page.DrawImageFile("logo.png", 50, 435, 180, 120); doc.SaveToFile("01_create.pdf"); Console.WriteLine($"Saved 01_create.pdf, {doc.PageCount()} page(s)"); } } 

Positions and sizes are in points (1/72 inch), and the origin is the bottom left corner of the page, so y grows upward. The y you pass to DrawText is the text baseline. The table is the exception: DrawOnPage takes the top left corner, and the rows grow downward from there.

The table header text is white, so give SetHeaderBackground a dark colour. A light one leaves the header row unreadable.

The sample expects a logo.png in the working directory, but any PNG, JPEG, BMP or TIFF file will do. It prints Saved 01_create.pdf, 1 page(s) and writes a 21.7 KB file. Opened again, Info.Title reads back as "Quarterly report", and the PNG is stored as a compressed image object. Here is page 1, rendered to PNG with the library's own raster renderer, TsgcPDFRendererRaster:

Page 1 of the generated quarterly report, with a title, a paragraph, a table with a blue header and striped rows, and a logo

2. Build a form, then fill and flatten it

doc.AcroForm handles interactive forms. This sample builds a page with a text field and a check box, opens the file again to fill it, then saves a flattened copy in which the values become ordinary page content:

using esegece.sgcPDF; static class FormSample { public static void Run() { // Step 1: a PDF with a text field and a check box using (var doc = new TsgcPDFDocument()) { TsgcPDFPage page = doc.AddPage(595, 842); page.DrawText("Newsletter sign up", 50, 780, TsgcPDFStandardFont.sfHelveticaBold, 20); page.DrawText("Name:", 50, 735, TsgcPDFStandardFont.sfHelvetica, 12); // name, page index, then the field rectangle: x1, y1, x2, y2 doc.AcroForm.AddTextField("name", 0, 150, 728, 450, 750); page.DrawRectangle(150, 728, 300, 22, 0.5); // a visible box: x, y, width, height, line width page.DrawText("Subscribe:", 50, 700, TsgcPDFStandardFont.sfHelvetica, 12); doc.AcroForm.AddCheckBox("subscribe", 0, 150, 696, 166, 712); page.DrawRectangle(150, 696, 16, 16, 0.5); doc.SaveToFile("02_form.pdf"); } // Step 2: open it, fill the fields and save using (var doc = new TsgcPDFDocument()) { doc.LoadFromFile("02_form.pdf"); doc.AcroForm.FillField("name", "Ada Lovelace"); doc.AcroForm.SetCheckBox("subscribe", true); doc.SaveToFile("02_form_filled.pdf"); } // Optional: flatten, the values become plain page content and the fields go away using (var doc = new TsgcPDFDocument()) { doc.LoadFromFile("02_form_filled.pdf"); doc.AcroForm.FlattenFields(); doc.SaveToFile("02_form_flat.pdf"); } // Read the values back using (var doc = new TsgcPDFDocument()) { doc.LoadFromFile("02_form_filled.pdf"); Console.WriteLine($"name = {doc.AcroForm.GetFieldValue("name")}"); Console.WriteLine($"subscribe = {doc.AcroForm.GetFieldValue("subscribe")}"); } } } 

Two details are easy to miss. AddTextField and AddCheckBox take the two corners of the field (x1, y1, x2, y2), while DrawRectangle takes x, y, width and height. And generated fields have no visible border, which is why the sample draws a rectangle under each one, so the form reads well on screen and on paper.

The text field reads back as Ada Lovelace and the check box as Yes. In the flattened copy, AcroForm.FieldCount() returns 0 and the page text contains "Ada Lovelace", so the value is now part of the page itself. AcroForm also offers combo boxes, list boxes, radio groups, buttons and signature fields, and it exchanges field data as FDF or XFDF.

3. Extract text and images

This sample reads the report from sample 1 back, the text page by page and the images as PNG files:

using esegece.sgcPDF; static class ExtractSample { public static void Run() { using var doc = new TsgcPDFDocument(); doc.LoadFromFile("01_create.pdf"); // Text, page by page for (int i = 0; i < doc.PageCount(); i++) { Console.WriteLine($"--- page {i + 1} ---"); Console.WriteLine(doc.ExtractTextW(i)); } // Images, saved as PNG files using var images = new TsgcPDFImageExtractor(); images.Document = doc; images.Scan(); for (int i = 0; i < images.Count; i++) { TsgcPDFImageItem item = images.Items(i); Console.WriteLine($"image {i}: page {item.PageIndex + 1}, {item.Width} x {item.Height}, {item.ColorSpace}"); images.SaveAsPNG(i, $"03_image_{i}.png"); } } } 

Use ExtractTextW, which returns Unicode text. ExtractText returns an ANSI string. The text comes back line by line, from the title through the paragraph, and the table arrives as space separated rows such as North 1,240 EUR 49,600.

The extractor finds the one image and reports it as page 1, 120 x 80, DeviceRGB. The PNG it saved was byte identical to the original logo.png. It can also save JPEG files with SaveAsJPEG, or hand you the PNG bytes in memory with GetPNGBytes.

4. Encrypt with AES-256 and permissions

This sample encrypts the report with a user password to open it and an owner password for full rights, and allows the user nothing but printing. Then it opens the file twice, once with the password and once without:

using esegece.sgcPDF; static class EncryptSample { public static void Run() { // Encrypt the sample 1 PDF with AES-256: a user password to open it, an owner // password for full rights, and printing as the only permission for the user using (var doc = new TsgcPDFDocument()) { doc.LoadFromFile("01_create.pdf"); doc.Encryption.SetupEncryption(TsgcPDFEncryptionMethod.emAES_256, "reader", "owner-secret", TsgcPDFPermissions.ppPrint | TsgcPDFPermissions.ppPrintHigh); doc.SaveToFile("04_encrypted.pdf"); } // Open it with the user password using (var doc = new TsgcPDFDocument()) { doc.SetPassword("reader"); doc.LoadFromFile("04_encrypted.pdf"); Console.WriteLine($"With password: {doc.PageCount()} page(s), method {doc.Encryption.Method}, " + $"permissions {doc.Encryption.Permissions}"); Console.WriteLine(doc.ExtractTextW(0).Split('\n')[0]); } // And without it try { using var doc = new TsgcPDFDocument(); doc.LoadFromFile("04_encrypted.pdf"); Console.WriteLine($"Without password: loaded, text = '{doc.ExtractTextW(0).Split('\n')[0]}'"); } catch (Exception e) { Console.WriteLine($"Without password: {e.GetType().Name}: {e.Message}"); } } } 

Use doc.Encryption.SetupEncryption with a permissions value, as above. The shorter doc.SetEncryption(method, user, owner) also encrypts, but it grants every permission, and Encryption.Permissions is read only, so you can't narrow them afterwards. Give the owner password a different value from the user password, otherwise the restrictions don't hold.

To open an encrypted file, call SetPassword before LoadFromFile. With the user password the file opens with 1 page, method emAES_256 and permissions ppPrint, ppPrintHigh, and the first line of text reads "Quarterly report". Without a password, LoadFromFile throws EsgcPDFEncryptionError with the message "Invalid password for encrypted PDF document". A wrong password throws the same error. The owner password opens the file too, with Encryption.IsOwnerPassword set to true.

In the saved bytes the encryption dictionary reads /V 5 /R 6 /CFM /AESV3, which is AES-256 at revision 6, and the title no longer appears in plain text. The other methods are emRC4_40, emRC4_128 and emAES_128.

5. Save as PDF/A-2b and validate it

For long term archiving, set a PDF/A level on the document, then check the saved file with the built-in validator:

using esegece.sgcPDF; static class PdfASample { public static void Run() { using (var doc = new TsgcPDFDocument()) { doc.SetPDFALevel(TsgcPDFALevel.paA2b); doc.Info.Title = "Archived invoice"; doc.Info.Author = "sgcPDF sample"; // PDF/A needs embedded fonts, so use a TrueType font instead of the standard 14 int font = doc.AddTrueTypeFont("Arial", false, false); TsgcPDFPage page = doc.AddPage(595, 842); page.DrawTextTT("Archived invoice", 50, 780, font, 22); page.DrawTextTT("Saved as PDF/A-2b, with the font embedded and XMP metadata.", 50, 750, font, 12); doc.SaveToFile("05_pdfa.pdf"); } // Re-open and validate using (var doc = new TsgcPDFDocument()) { doc.LoadFromFile("05_pdfa.pdf"); var validator = new TsgcPDFAValidator(TsgcPDFALevel.paA2b); bool valid = validator.Validate(doc.Parser); Console.WriteLine($"PDFALevel = {doc.PDFALevel}, valid = {valid}, " + $"errors = {validator.Errors.Count}, warnings = {validator.Warnings.Count}"); for (int i = 0; i < validator.Errors.Count; i++) Console.WriteLine(" error: " + validator.Errors[i]); } } } 

PDF/A requires embedded fonts, and the standard 14 fonts are not embedded. That is why this sample registers a TrueType font with AddTrueTypeFont and draws with DrawTextTT. AddTrueTypeFont looks the font up by name among the installed system fonts, which was tested on Windows. On Linux or in a container, pass a .ttf path to AddTrueTypeFontFromFile instead.

The output is PDFALevel = paNone, valid = True, errors = 0, warnings = 0. The paNone is expected, because PDFALevel is not read back from the metadata of a loaded file. Check conformance with TsgcPDFAValidator instead. The file passes the library's own validator with no errors and no warnings. As a control, the same validator rejects the plain report from sample 1 with 7 errors, among them the missing metadata, the fonts that are not embedded and the missing output intent, so it really checks.

The saved file carries XMP metadata with PDF/A part 2 and conformance B, an sRGB output intent, the embedded font, and no encryption. Besides paA2b, TsgcPDFALevel offers paA1b, paA3b, paA1a, paA2a, paA3a, paA4 and paA4f.

6. Sign with PAdES B-B and verify the signature

Signing comes from the second package, esegece.sgcPDF.sgcSign.Community. This sample makes a self-signed test certificate in code with the BCL CertificateRequest, exports it to PFX bytes in memory, signs the report from sample 1 to PAdES B-B, and verifies the result. No certificate file touches the disk:

using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using esegece.sgcPDF; using esegece.sgcSign; static class SignSample { public static void Run() { // A self-signed test certificate, made in code (use your real one in production) using RSA rsa = RSA.Create(2048); var request = new CertificateRequest("CN=sgcPDF Demo Signer, O=Demo", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509KeyUsageExtension( X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, true)); using X509Certificate2 cert = request.CreateSelfSigned( DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1)); byte[] pfx = cert.Export(X509ContentType.Pfx, "test"); // Sign the sample 1 PDF to PAdES B-B var key = new TsgcPFXKeyProvider(); key.LoadFromBytes(pfx, "test"); var signer = new TsgcPDFPAdESSigner(); signer.KeyProvider = key; signer.Level = TsgcPDFPAdESLevel.paBB; signer.SignPDFFile("01_create.pdf", "06_signed.pdf"); // Verify it using var verifier = new TsgcPDFSignatureVerifier(); verifier.LoadDocument("06_signed.pdf"); for (int i = 0; i < verifier.GetSignatureCount(); i++) { bool valid = verifier.VerifySignature(i); TsgcPDFSignatureInfo info = verifier.GetSignature(i); Console.WriteLine($"signature {i}: valid = {valid}, subfilter = {info.SubFilter}, " + $"modified = {verifier.IsDocumentModified(i)}, trusted = {verifier.IsSignatureTrusted(i)}"); Console.WriteLine($" signer = {info.CertInfoSource:https://dev.to/esegece_com/create-fill-encrypt-and-sign-pdfs-in-net-with-one-free-nuget-package-1dk9

Related posts