---
title: "Printer-friendly version"
description: "Generating a printer-friendly version of a signed PDF with PKI SDK: PdfMarker stamps every page with the signers and a verification code and adds a summary page."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-sdk/printer-friendly-version"
locale: "en"
product: "PKI SDK"
last_updated: "2026-10-12"
---

# Printer-friendly version

A printed copy of a signed document does not carry its digital signatures. A printer-friendly version makes up for it:
each page shows who signed the document and a document verification code, so that whoever receives the paper can go to
your website, enter the code and get the digitally signed file back. 

With PKI SDK, `AlphaCode` generates the verification code and `PdfMarker` (namespace `Lacuna.Pki.Pdf`) writes marks, made of
text and images, on the pages of a PDF. 

## How it works

1. The first time a document is requested, generate its verification code with `AlphaCode.Generate()` and store it with
   the document. The code is unformatted: show it to users with `AlphaCode.Format`, for instance as `XXXX-XXXX-XXXX-XXXX`.
   See [Generating alphanumeric codes](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/alpha-codes).
1. Open the signed PDF with `PadesSignature.Open` and read its `Signers`, for instance the name on each signing
   certificate and the signing time.
1. Create a `PdfMarker` and add one `PdfMark` per stamp with `AddMark`.
1. Set `ThrowIfSignedPdf` to `false`. By default it is `true`, and `PdfMarker` throws `SignedPdfToMarkException`
   (namespace `Lacuna.Pki.Exceptions`) when the PDF has any signature.
1. Call `WriteMarks(pdfContent)`, which returns the PDF with the marks. All signatures are removed from the file before the
   marks are applied, so the printer-friendly version is not a signed file.
1. On your verification page, turn the code the user typed back into the stored form with `AlphaCode.Parse`, look up the
   document and validate its signatures as usual, with `PadesSignature.Open` and `ValidateSignature`. The sample waits two
   seconds before answering an unknown code, to slow down brute-force attempts.

## Example

Adapted from the sample: a summary on the bottom margin of every page, and a new page at the end of the document with the
verification code.

```cs
var formattedCode = AlphaCode.Format(verificationCode);

// Names of the signers, for the summary
var signature = PadesSignature.Open(pdfContent);
var signerNames = string.Join(", ", signature.Signers.Select(s => s.Signer.SigningCertificate.SubjectName.CommonName));
var summary = $"This document was digitally signed by {signerNames}.\nTo verify the signatures go to {verificationSite} and inform the code {formattedCode}";

var pdfMarker = new PdfMarker();

// Summary on the bottom margin of every page
pdfMarker.AddMark(new PdfMark() {
    PageOption = PdfMarkPageOptions.AllPages,
    Container = new PadesVisualRectangle() { Height = 2, Bottom = 0, Left = 1.5, Right = 3.5 },
    Elements = new List<PdfMarkElement>() {
        new PdfMarkText() {
            Texts = new List<PdfTextSection>() {
                new PdfTextSection() { Style = PdfTextStyle.Normal, Text = summary }
            }
        }
    }
});

// New page at the end of the document, with 1-inch margins
var summaryPage = new PdfMark() {
    PageOption = PdfMarkPageOptions.NewPage,
    Container = new PadesVisualRectangle() { Top = 2.54, Bottom = 2.54, Left = 2.54, Right = 2.54 }
};
summaryPage.Elements.Add(new PdfMarkText() {
    RelativeContainer = new PadesVisualRectangle() { Top = 0, Height = 2, Left = 0, Right = 0 },
    Align = PadesHorizontalAlign.Center,
    Texts = new List<PdfTextSection>() {
        new PdfTextSection() { Style = PdfTextStyle.Normal, FontSize = 14, Text = $"Verification code: {formattedCode}" }
    }
});
pdfMarker.AddMark(summaryPage);

pdfMarker.ThrowIfSignedPdf = false;   // the PDF is signed: don't throw SignedPdfToMarkException
var pfvContent = pdfMarker.WriteMarks(pdfContent);
```

The sample's summary page also has the ICP-Brasil logo, a QR code of the verification link (a PNG made with the QRCoder
library), and one line per signer with a green or red icon for the result of `ValidateSignature`. It also puts the summary
on the right margin of every page, rotated with `PdfMarkRotation.D90` so that the text reads upward. 

## Marks

A `PdfMark` is a stamp applied on the pages of the PDF:

| Property | What it sets |
| --- | --- |
| `PageOption` | The pages that get the mark, a `PdfMarkPageOptions`: `AllPages`; `NewPage`, a new page added at the end of the document; or `SinglePage` and `SinglePageFromEnd`, together with the `PageOptionNumber` property. |
| `Container` | The position of the mark on the page, a `PadesVisualRectangle`: the class that positions signature visual representations, explained in [Defining a container](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/signatures/pades/visual-rep/containers). |
| `MeasurementUnits` | `PadesMeasurementUnits.Centimeters` or `PadesMeasurementUnits.PdfPoints`. The sample does not set it, and its measures are in centimeters (2.54 for 1-inch margins). |
| `Elements` | The text and images of the mark: a list of `PdfMarkText` and `PdfMarkImage`. |
| `BorderWidth`, `BorderColor`, `BackgroundColor` | The border and the background of the mark. |

Every element has a `RelativeContainer`, its position inside the mark's `Container`; a `Rotation`, `D0`, `D90`, `D180` or
`D270` (in degrees); and an `Opacity`, 100 (opaque) by default. Then:

- `PdfMarkText` has `Texts`, a list of `PdfTextSection`, each with its `Text`, `Style` (`Normal`, `Bold`, `Italic` or
  `Underline`, which can be combined), `FontSize` and `Color` (black by default); and `Align`, `VerticalAlign` and
  `LineSpacing`, a multiplier of the font size.
- `PdfMarkImage` has `ImageContent`, the bytes of the image; the sample uses PNG images.

## More options

- `PreserveSignaturesVisualRepresentation`: keep the images of the signatures' visual representations on the PDF, when
  present.
- `NewPagesTemplate`: a `PdfPageTemplate` with the size and orientation of the pages that `NewPage` marks create. If not
  set, the most common paper size of the PDF's pages is used, in portrait.
- `CreateContext` and `ComputeRequiredArea`: compute the area that a text needs before you place it, then call
  `WriteMarks(context)`.
- `WriteMarks(Stream, Stream)` reads the PDF from a stream and writes the result to another.

## CAdES signatures

A CMS/CAdES file is not a PDF, so the CAdES sample builds the printer-friendly version as a new document: it reads the
signers with `CadesSignature.Open` and writes the summary mark on a blank PDF that ships with the sample. 

## See also

* [Generating alphanumeric codes](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/alpha-codes)
* [PAdES signatures](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/signatures/pades)
* [`PdfMarker` in the API reference](https://docs.lacunasoftware.com/api/Lacuna.Pki.Pdf/PdfMarker)
* [Lacuna PKI SDK](https://docs.lacunasoftware.com/en-us/articles/pki-sdk)

**Demos and samples**

- Live demo: [Make a printer-friendly version](https://try.lacunasoftware.com/en/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyPadesSdkController.cs)
- Make a printer-friendly version of a CMS/CAdES signature using PKI SDK: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PrinterFriendlyCadesSdkController.cs)
