← Inicio
Guía rápida

Tu primer PDF en dos minutos

Majorsilence.Pdf es una librería PDF autocontenida y sin dependencias para .NET 8 y 10. Un solo paquete, sin binarios nativos: crea documentos, dibuja texto, formas e imágenes, incrusta fuentes TrueType y guarda en un archivo o flujo (stream).


Instalación

Un único paquete NuGet: no se requieren librerías complementarias:

# .NET CLI
dotnet add package Majorsilence.Pdf

# Package Manager Console
Install-Package Majorsilence.Pdf

Hola, PDF

Crea un documento con PdfDocument.Create(), agrega una página, dibuja en el lienzo (canvas) y guarda. Las coordenadas están en puntos PDF (1 pt = 1/72 pulgadas), con la esquina superior izquierda como origen: Y aumenta hacia abajo.

using Majorsilence.Pdf;

PdfDocument.Create()
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Hello, PDF!", 72, 72,
            TextStyle.Default.WithSize(24).WithBold());
        canvas.DrawLine(72, 96, 520, 96);
    })
    .Save("hello.pdf");

Estilos de API: callback vs. incremental

Elige el que mejor se adapte a la estructura de tu código:

Estilo callback (cadena fluida)

Pasa una lambda de dibujo a AddPage. El documento se devuelve para que puedas seguir encadenando llamadas. Ideal para documentos cortos construidos en una sola pasada.

PdfDocument.Create()
    .WithTitle("My Report")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Chapter 1", 72, 72,
            TextStyle.Default.WithSize(18).WithBold());
    })
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Chapter 2", 72, 72,
            TextStyle.Default.WithSize(18).WithBold());
    })
    .Save("report.pdf");

Estilo incremental

AddPage sin un callback devuelve directamente el PdfCanvas. Útil al construir contenido de forma imperativa, por ejemplo dentro de un bucle.

var doc = PdfDocument.Create().WithTitle("My Report");

foreach (var chapter in chapters)
{
    var canvas = doc.AddPage(PageSizes.A4);
    canvas.DrawText(chapter.Title, 72, 72,
        TextStyle.Default.WithSize(18).WithBold());
    // ... draw body ...
}

doc.Save("report.pdf");

Texto y TextStyle

TextStyle es inmutable. Cada método With* devuelve una nueva instancia; el original no cambia. Construye un estilo base una vez y luego deriva variantes a partir de él.

var heading = TextStyle.Default
    .WithFamily("Helvetica")
    .WithSize(22)
    .WithBold()
    .WithColor(PdfColor.FromHex("#1A56A0"));

var body = TextStyle.Default
    .WithFamily("Times-Roman")
    .WithSize(11);

var mono = TextStyle.Default
    .WithFamily("Courier")
    .WithSize(10);

canvas.DrawText("Invoice #1024", 72, 72, heading);
canvas.DrawText("Due: July 17, 2026", 72, 106, body);

// Alignment
canvas.DrawText("Right-aligned", 540, 72,
    body.WithAlignment(TextAlignment.Right));

// Decorations
canvas.DrawText("underlined",     72, 140, body.WithUnderline());
canvas.DrawText("strikethrough",  72, 160, body.WithStrikethrough());
canvas.DrawText("overline",       72, 180, body.WithOverline());

Referencia de TextStyle

MétodoDescripción
.WithFamily(name)Fuente estándar por nombre. Integradas: Helvetica, Times-Roman, Courier, Symbol, ZapfDingbats. También acepta cualquier familia registrada en FontRegistry.
.WithFontFile(path)Incrusta una fuente TrueType / OpenType mediante una ruta de archivo absoluta. Tiene prioridad sobre WithFamily.
.WithSize(pts)Tamaño de fuente en puntos PDF. Predeterminado: 12.
.WithColor(color)Color de primer plano del texto. Predeterminado: PdfColor.Black.
.WithBold()Peso en negrita.
.WithItalic()Estilo itálico.
.WithAlignment(a)TextAlignment.Left (predeterminado), Center, Right. X es el punto de referencia.
.WithUnderline()Decoración de subrayado.
.WithStrikethrough()Decoración de tachado.
.WithOverline()Decoración de sobrelínea.
.WithVertical()Rota el texto 90° en sentido antihorario.
.WithRightToLeft()Invierte el orden de los puntos de código para el renderizado visual RTL (hebreo, árabe). Úsalo junto con TextAlignment.Right para que X sea el ancla derecha.

PdfColor

// Named colours
PdfColor.Black    PdfColor.White    PdfColor.Red
PdfColor.Green    PdfColor.Blue     PdfColor.Yellow
PdfColor.Orange   PdfColor.Gray     PdfColor.LightGray   PdfColor.DarkGray

// From CSS hex string
PdfColor.FromHex("#1A56A0")

// From RGB bytes (0–255)
PdfColor.FromRgb(26, 86, 160)

// Constructor form — same as FromRgb
new PdfColor(26, 86, 160)

Medir el ancho del texto

Usa MeasureTextWidth para posicionar etiquetas en relación con otro contenido:

float w = canvas.MeasureTextWidth("Hello", body);
canvas.DrawText("World", 72 + w + 4, 72, body);

Ajuste de texto en varias líneas

DrawTextBox ajusta el texto por palabras dentro de un cuadro delimitador. Los saltos de línea forzados (\n) obligan un salto de línea; las palabras nunca se dividen a la mitad. El método devuelve el índice de la cadena donde el texto desbordó el cuadro, para que puedas continuar el párrafo en un segundo cuadro o en la página siguiente.

var body = TextStyle.Default.WithFamily("LiberationSans").WithSize(11);

string text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
              "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n" +
              "Second paragraph starts here.";

// Render into a 400 × 120 pt box; returns index of first character that did not fit
int overflow = canvas.DrawTextBox(text, x: 72, y: 72,
    width: 400, height: 120, body);

if (overflow < text.Length)
{
    // Continue overflowed text in a second box below
    canvas.DrawTextBox(text.Substring(overflow), x: 72, y: 210,
        width: 400, height: 120, body);
}

// Centre- and right-aligned boxes
canvas.DrawTextBox("Centred text\nSecond line", 72, 350, 300, 80,
    body.WithAlignment(TextAlignment.Center));

canvas.DrawTextBox("Right-aligned\nAll lines snap right", 72, 450, 300, 80,
    body.WithAlignment(TextAlignment.Right));

Referencia de DrawTextBox

ParámetroDescripción
textLa cadena a renderizar. \n fuerza un salto de línea en esa posición.
x, yEsquina superior izquierda del cuadro delimitador en puntos PDF.
width, heightDimensiones del cuadro. El texto que se extendería más allá de y + height se omite.
styleTextStyle: se respetan la fuente, el tamaño, el color y la alineación. TextAlignment es relativo al ancho del cuadro.
returns intÍndice del primer carácter que no cupo. Es igual a text.Length si todo el texto cupo.

Formas

ShapeStyle controla el relleno y el trazo de forma independiente. Todos los métodos de forma aceptan un ShapeStyle opcional; el predeterminado es un trazo negro de 1 pt sin relleno.

// Rectangles
canvas.DrawRectangle(50, 50, 200, 80,
    ShapeStyle.Filled(PdfColor.LightGray));

canvas.DrawRectangle(50, 150, 200, 80,
    ShapeStyle.Stroked(PdfColor.Black, width: 2));

canvas.DrawRectangle(50, 250, 200, 80,
    ShapeStyle.Filled(PdfColor.FromHex("#E8F4FF"))
              .WithStroke(PdfColor.Blue, width: 1));

canvas.DrawRectangle(50, 350, 200, 80,
    ShapeStyle.Stroked(PdfColor.DarkGray).Dashed());

// Ellipses (same ShapeStyle options)
canvas.DrawEllipse(300, 50, 150, 80,
    ShapeStyle.Filled(PdfColor.Blue));

// Polygons
var triangle = new List<(float x, float y)>
{
    (300, 200), (400, 350), (200, 350)
};
canvas.DrawPolygon(triangle,
    ShapeStyle.Filled(PdfColor.Orange)
              .WithStroke(PdfColor.DarkGray));

Referencia de ShapeStyle

Fábrica / métodoDescripción
ShapeStyle.Filled(color)Relleno sólido, sin trazo.
ShapeStyle.Stroked(color, width)Solo trazo, sin relleno. El ancho predeterminado es 1.
.WithFill(color)Agrega o reemplaza el color de relleno.
.WithStroke(color, width)Agrega o reemplaza el color y el ancho del trazo.
.WithNoFill()Elimina el relleno.
.WithNoStroke()Elimina el trazo.
.Dashed()Estilo de línea discontinua para el trazo.
.Dotted()Estilo de línea punteada para el trazo.
.WithFillOpacity(alpha)Transparencia del relleno. 1.0 = opaco (predeterminado); 0.0 = totalmente transparente. Ver Opacidad.
.WithStrokeOpacity(alpha)Transparencia del trazo. Misma escala que WithFillOpacity.

Opacidad y transparencia

Configura la transparencia de relleno y trazo de forma independiente en las formas, o establece la opacidad general de las líneas mediante StrokeStyle. Todos los valores de opacidad van de 0.0 (totalmente transparente) a 1.0 (totalmente opaco, el valor predeterminado).

// Fill opacity ramp — five blue squares from opaque to 20 % opacity
for (int i = 0; i < 5; i++)
    canvas.DrawRectangle(72 + i * 50, 100, 40, 40,
        ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(1f - i * 0.2f));

// Stroke opacity
canvas.DrawRectangle(72, 170, 200, 40,
    ShapeStyle.Stroked(PdfColor.Red, 3f).WithStrokeOpacity(0.4f));

// Overlapping semi-transparent shapes — backgrounds show through
canvas.DrawRectangle(80, 240, 150, 100, ShapeStyle.Filled(PdfColor.Red));
canvas.DrawRectangle(155, 265, 150, 100,
    ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(0.5f));
canvas.DrawEllipse(115, 300, 150, 80,
    ShapeStyle.Filled(PdfColor.Green).WithFillOpacity(0.4f));

// Line opacity via StrokeStyle
canvas.DrawLine(72, 400, 450, 400,
    StrokeStyle.Default.WithWidth(4).WithColor(PdfColor.DarkGray).WithOpacity(0.5f));

Resumen de la API de opacidad

MétodoDescripción
ShapeStyle.WithFillOpacity(alpha)Transparencia de relleno para rectángulos, elipses y polígonos.
ShapeStyle.WithStrokeOpacity(alpha)Transparencia de trazo para los mismos tipos de forma.
StrokeStyle.WithOpacity(alpha)Opacidad para líneas dibujadas con DrawLine y DrawCurve.

Líneas y curvas

StrokeStyle es a las líneas lo que ShapeStyle es a las formas rellenas.

// Straight lines
canvas.DrawLine(72, 100, 520, 100);  // 1 pt solid black

canvas.DrawLine(72, 130, 520, 130,
    StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Blue));

canvas.DrawLine(72, 160, 520, 160,
    StrokeStyle.Default.WithWidth(1).Dashed());

// Smooth curve through control points (Catmull-Rom spline)
var pts = new List<(float, float)>
{
    (72, 300), (150, 260), (250, 320),
    (350, 270), (450, 310), (520, 280)
};
canvas.DrawCurve(pts,
    StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Red));

Imágenes

DrawImage acepta bytes JPEG sin procesar o bytes RGB24 sin procesar (3 bytes por píxel, orden por filas). La imagen se coloca con su esquina superior izquierda en (x, y) y se escala a las dimensiones especificadas.

// JPEG from disk
byte[] jpeg = File.ReadAllBytes("logo.jpg");
canvas.DrawImage(jpeg, pixelWidth: 400, pixelHeight: 200,
    isJpeg: true, x: 72, y: 72, width: 200, height: 100);

// Raw RGB24 (e.g. a gradient generated in code)
const int W = 200, H = 150;
var rgb = new byte[W * H * 3];
for (int row = 0; row < H; row++)
    for (int col = 0; col < W; col++)
    {
        int i = (row * W + col) * 3;
        rgb[i]     = (byte)(col * 255 / W);   // R
        rgb[i + 1] = (byte)(row * 255 / H);   // G
        rgb[i + 2] = 128;                     // B
    }
canvas.DrawImage(rgb, W, H, isJpeg: false,
    x: 72, y: 72, width: 200, height: 150);

Tablas

PdfTable organiza una cuadrícula con una fila de encabezado, un fondo alternado opcional por fila, ajuste automático del texto de celda y bordes configurables. Pásala a canvas.DrawTable() para renderizarla en una posición determinada.

// Column widths in PDF points
var table = new PdfTable(new float[] { 180, 80, 90, 90 })
    .WithHeaderBackground(new PdfColor(26, 86, 160))
    .WithAlternateRowBackground(new PdfColor(240, 245, 252))
    .WithBorder(new PdfColor(200, 200, 200), 0.5f)
    .WithCellPadding(5f)
    .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10))
    .WithHeaderTextStyle(
        TextStyle.Default.WithFamily("LiberationSans").WithSize(10)
                         .WithBold().WithColor(PdfColor.White));

// First AddRow call becomes the header row
table.AddRow("Product",         "Qty", "Unit Price", "Total");
table.AddRow("PDF Library Pro",  "3",   "$400.00",    "$1 200.00");
table.AddRow("Report Designer",  "1",   "$250.00",    "$250.00");
table.AddRow("Support (12 mo.)", "1",   "$500.00",    "$500.00");

// tableBottom receives the Y coordinate just below the last rendered row
canvas.DrawTable(table, x: 72, y: 72, out float tableBottom);

// Border-less "report" style
var report = new PdfTable(new float[] { 200, 100, 100 })
    .WithNoBorder()
    .WithHeaderBackground(new PdfColor(245, 245, 245))
    .WithCellPadding(4f)
    .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10))
    .WithHeaderTextStyle(
        TextStyle.Default.WithFamily("LiberationSans").WithSize(10).WithBold());

report.AddRow("Category",      "Revenue", "Growth");
report.AddRow("North America",  "$1.24M",  "+12%");
report.AddRow("Europe",         "$0.89M",  "+8%");

canvas.DrawTable(report, 72, 72 + tableBottom + 20);

Referencia de PdfTable

MétodoDescripción
new PdfTable(widths)Anchos de columna en puntos PDF. El número de elementos define la cantidad de columnas.
.WithHeaderBackground(color)Color de fondo para la primera fila (encabezado).
.WithAlternateRowBackground(color)Color de fondo para cada fila de datos par.
.WithBorder(color, width)Color y ancho de las líneas de la cuadrícula.
.WithNoBorder()Suprime todas las líneas de la cuadrícula.
.WithCellPadding(pts)Relleno interno (todos los lados) aplicado a cada celda.
.WithCellTextStyle(style)TextStyle predeterminado para las celdas de datos.
.WithHeaderTextStyle(style)TextStyle para la fila de encabezado.
.AddRow(col1, col2, …)Agrega una fila. Pasa una cadena por columna. La primera llamada produce la fila de encabezado; las siguientes producen filas de datos. El texto largo de celda se ajusta automáticamente.
canvas.DrawTable(table, x, y)Renderiza la tabla en la posición indicada.
canvas.DrawTable(table, x, y, out float bottom)Lo mismo, pero también devuelve la coordenada Y justo debajo de la última fila, útil para colocar contenido debajo de la tabla.

Enlaces y tooltips

Agrega hipervínculos en los que se pueda hacer clic o tooltips que aparezcan al pasar el cursor sobre cualquier área rectangular. El área se define como la esquina superior izquierda (x, y) más width y height.

var linkStyle = TextStyle.Default
    .WithColor(PdfColor.Blue).WithUnderline();

canvas.DrawText("Visit majorsilence.com", 72, 110, linkStyle);
canvas.AddLink(72, 96, width: 220, height: 18,
    uri: "https://majorsilence.com");

// Tooltip (shows in supporting PDF viewers on hover)
canvas.DrawRectangle(72, 140, 200, 40,
    ShapeStyle.Filled(PdfColor.LightGray).WithStroke(PdfColor.Gray));
canvas.DrawText("Hover for info", 82, 165, TextStyle.Default);
canvas.AddTooltip(72, 140, 200, 40,
    tooltip: "This text appears on hover.");

Fuentes TrueType

Las cinco fuentes estándar integradas (Helvetica, Times-Roman, Courier, Symbol, ZapfDingbats) cubren ASCII + Latin-1. Para texto Unicode, emoji o CJK, incrusta una fuente TrueType.

Incrustar por ruta de archivo

La opción más simple: apunta TextStyle directamente a un archivo .ttf o .otf. La fuente se lee una vez por documento y se almacena en caché.

var custom = TextStyle.Default
    .WithFontFile("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf")
    .WithSize(14);

canvas.DrawText("café résumé naïve", 72, 72, custom);
canvas.DrawText("Bold variant",      72, 96, custom.WithBold());

FontRegistry: familias con nombre + cadena de reserva

Registra una o más familias bajo nombres lógicos, define la cadena de reserva (usada cuando la fuente principal no tiene el glifo) y asocia el registro al documento. El lienzo (canvas) segmenta el texto automáticamente, enrutando cada carácter a la primera fuente capaz de renderizarlo.

var fonts = new FontRegistry()
    // Register individual variants
    .AddFamily("LiberationSans",
        regular:    "Fonts/LiberationSans-Regular.ttf",
        bold:       "Fonts/LiberationSans-Bold.ttf",
        italic:     "Fonts/LiberationSans-Italic.ttf",
        boldItalic: "Fonts/LiberationSans-BoldItalic.ttf")
    // Or scan a directory — detects variants from filenames
    .AddDirectory("Fonts/")
    // NotoSans fills in any glyph LiberationSans is missing
    .AddFallback("NotoSans");

PdfDocument.Create()
    .WithFontRegistry(fonts)
    .AddPage(PageSizes.A4, canvas =>
    {
        var style = TextStyle.Default
            .WithFamily("LiberationSans").WithSize(14);

        canvas.DrawText("Hello κόσμε мир", 72, 72, style);
        canvas.DrawText("Bold text",  72, 96, style.WithBold());
        canvas.DrawText("Italic text", 72, 118, style.WithItalic());
    })
    .Save("output.pdf");
AddDirectory espera nombres de archivo con el patrón FamilyName-Regular.ttf, FamilyName-Bold.ttf, etc. Los caracteres que ninguna fuente registrada puede renderizar se emiten como recuadros .notdef: no se lanza ninguna excepción. Usa registry.Contains("FamilyName") para comprobar si una familia se cargó correctamente antes de usarla.

Documentos multipágina

Llama a AddPage tantas veces como sea necesario. Las páginas se escriben en el orden en que se agregan.

var doc = PdfDocument.Create()
    .WithTitle("Report")
    .WithAuthor("Majorsilence")
    .WithFontRegistry(fonts);

string[] chapters = { "Introduction", "Methods", "Results" };
var title  = TextStyle.Default.WithSize(24).WithBold();
var footer = TextStyle.Default.WithSize(9).WithColor(PdfColor.Gray);

for (int i = 0; i < chapters.Length; i++)
{
    int pageNum = i + 1;
    doc.AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText(chapters[i], 72, 72, title);
        canvas.DrawText(
            $"Page {pageNum} of {chapters.Length}",
            PageSizes.A4.Width / 2, PageSizes.A4.Height - 30,
            footer.WithAlignment(TextAlignment.Center));
    });
}

doc.Save("report.pdf");
Páginas en horizontal (landscape) — llama a PageSizes.Letter.Landscape() (o cualquier tamaño) para intercambiar el ancho y el alto. Combina orientaciones libremente entre las páginas del mismo documento.

Opciones de guardado

// Write to a file
doc.Save("report.pdf");

// Write to any Stream (e.g. an ASP.NET response body)
doc.Save(Response.Body);

// Get bytes — useful for returning from an API endpoint
byte[] bytes = doc.ToBytes();
return File(bytes, "application/pdf", "report.pdf");
Las tres sobrecargas son síncronas. Envuélvelas en Task.Run si necesitas llamarlas desde código asíncrono sin bloquear el grupo de subprocesos (thread pool).

Metadatos y versión de PDF

Los metadatos aparecen en las propiedades del documento del visor. La versión de PDF controla el encabezado del archivo y el formato de la tabla de referencias cruzadas.

PdfDocument.Create()
    .WithTitle("Invoice #INV-2026-0042")
    .WithAuthor("Majorsilence Corp")
    .WithSubject("Sales invoice")
    .WithCreator("MyApp 1.0")
    // PDF 1.4 (default) — broadest reader compatibility
    .WithVersion(PdfVersion.Pdf14)
    // PDF 2.0 — adds XMP metadata stream + compressed xref
    // .WithVersion(PdfVersion.Pdf20)
    .AddPage(PageSizes.Letter, canvas => { /* ... */ })
    .Save("invoice.pdf");
VersiónNotas
PdfVersion.Pdf14Predeterminado. Encabezado %PDF-1.4. Tabla de referencias cruzadas tradicional. Compatible con prácticamente todos los visores de PDF.
PdfVersion.Pdf20ISO 32000-2. Encabezado %PDF-2.0. Flujo de metadatos XMP adjunto al catálogo del documento. Flujo de referencias cruzadas comprimido en lugar de una tabla xref simple.

Conformidad PDF/A

WithConformance marca un documento como PDF/A, un subconjunto ISO de PDF diseñado para archivado a largo plazo. La librería inserta automáticamente el flujo de metadatos XMP requerido y un intento de salida ICC sRGB incrustado. Todas las fuentes deben incrustarse mediante FontRegistry; las fuentes Type 1 estándar, el cifrado y la transparencia (para el nivel A-1b) no están permitidos.

var fonts = new FontRegistry().AddDirectory("Fonts/").AddFallback("NotoSans");

PdfDocument.Create()
    .WithConformance(PdfConformance.PdfA2b)   // sets PDF 1.7 header automatically
    .WithFontRegistry(fonts)                   // embedded fonts are required
    .WithTitle("Archived Document")
    .WithAuthor("Majorsilence")
    .WithSubject("Archival demonstration")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Archival content", 72, 72,
            TextStyle.Default.WithFamily("LiberationSans").WithSize(14));
    })
    .Save("archive.pdf");

Niveles de PdfConformance

NivelBasado enNotas
PdfConformance.PdfA1bPDF 1.4Máxima compatibilidad con visores. Sin transparencia. Ampliamente usado para archivado.
PdfConformance.PdfA2bPDF 1.7Permite transparencia. Recomendado para archivos nuevos.
PdfConformance.PdfA3bPDF 1.7Igual que PDF/A-2b, además de admitir archivos adjuntos incrustados.
Nivel B (reproducibilidad visual) requiere fuentes incrustadas, metadatos XMP y un intento de salida ICC, todo insertado automáticamente por WithConformance. Usa un validador de PDF/A como veraPDF para confirmar la conformidad total de tu salida. PDF/A no puede combinarse con protección por contraseña ni cifrado de clave pública.

Texto de derecha a izquierda

Llama a .WithRightToLeft() en un TextStyle para invertir el orden de los puntos de código y lograr un renderizado visual RTL. Combínalo con TextAlignment.Right para que la coordenada X sea el ancla derecha. El hebreo se renderiza correctamente sin un motor de conformación; el árabe muestra una inversión visual (las ligaduras correctas requieren un conformador externo como HarfBuzz).

using Majorsilence.Pdf;

// Build a registry with Hebrew and Arabic script fonts
var reg = new FontRegistry()
    .AddDirectory("Fonts/")          // picks up NotoSansHebrew, NotoSansArabic, etc.
    .AddFallback("NotoSans")
    .AddFallback("NotoSansHebrew")
    .AddFallback("NotoSansArabic");

// Check whether the optional script font actually loaded
string hebrewFamily = reg.Contains("NotoSansHebrew") ? "NotoSansHebrew" : "NotoSans";

float pageW      = PageSizes.A4.Width;
float rightEdge  = pageW - 72;  // right margin anchor

var rtl = TextStyle.Default
    .WithFamily(hebrewFamily)
    .WithSize(16)
    .WithRightToLeft()
    .WithAlignment(TextAlignment.Right);

PdfDocument.Create()
    .WithFontRegistry(reg)
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("שלום עולם", rightEdge, 72, rtl.WithSize(24).WithBold());
        canvas.DrawText("ספר, ישראל, שלום, אהבה", rightEdge, 110, rtl);

        // Mix LTR and RTL on the same line
        var ltr = TextStyle.Default.WithFamily("LiberationSans").WithSize(14);
        canvas.DrawText("Hello, World!", 72, 160, ltr);
        canvas.DrawText("!שלום, עולם", rightEdge, 160, rtl.WithSize(14));
    })
    .Save("rtl.pdf");
NotoSansHebrew y NotoSansArabic están incluidas en las fuentes empaquetadas de Majorsilence.Drawing.Common y son detectadas automáticamente por AddDirectory. Los caracteres que ninguna fuente registrada puede renderizar aparecen como recuadros .notdef.

Protección con contraseña

Cifra un documento para que los lectores deban proporcionar una contraseña para abrirlo. También puedes restringir lo que el lector puede hacer con el documento (imprimir, copiar texto, etc.).

El cifrado utiliza el Standard Security Handler Rev 4, AES-128-CBC: el algoritmo definido en PDF 1.5–1.7 y compatible con todos los visores principales.

using Majorsilence.Pdf;
using Majorsilence.Pdf.Security;

var security = PdfSecurity
    .Protect(userPassword: "open123", ownerPassword: "owner456")
    .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText);

PdfDocument.Create()
    .WithSecurity(security)
    .WithTitle("Confidential Report")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Confidential document", 72, 72,
            TextStyle.Default.WithSize(18).WithBold());
    })
    .Save("protected.pdf");

Referencia de PdfSecurity y PdfPermissions

MiembroDescripción
PdfSecurity.Protect(user, owner)Crea un descriptor de seguridad. user es la contraseña necesaria para abrir el archivo; owner desbloquea todas las restricciones.
.WithPermissions(flags)Campo de bits de indicadores PdfPermissions a otorgar. Predeterminado: sin permisos (solo lectura).
PdfPermissions.PrintPermitir la impresión.
PdfPermissions.CopyTextPermitir la selección y copia de texto.
doc.WithSecurity(security)Asocia el descriptor de seguridad al documento antes de guardar.

Firmas digitales

Incrusta una firma digital PKCS#7 separada (adbe.pkcs7.detached, SHA-256) usando un X509Certificate2 que contenga una clave privada. En producción, carga el certificado desde X509Store o un archivo .pfx emitido por una CA de confianza.

using Majorsilence.Pdf;
using Majorsilence.Pdf.Security;
using System.Security.Cryptography.X509Certificates;

// Load a certificate with a private key (from file, store, HSM, etc.)
X509Certificate2 cert = X509CertificateLoader.LoadPkcs12(
    File.ReadAllBytes("signer.pfx"), password: "pfxpassword",
    X509KeyStorageFlags.Exportable);

var sigOpts = new PdfSignatureOptions(cert)
    .WithReason("Document approval")
    .WithSignerName("Jane Smith")
    .WithLocation("Toronto, ON");

PdfDocument.Create()
    .WithSignature(sigOpts)
    .WithTitle("Signed Report")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Digitally signed document", 72, 72,
            TextStyle.Default.WithSize(18).WithBold());
    })
    .Save("signed.pdf");

Referencia de PdfSignatureOptions

MiembroDescripción
new PdfSignatureOptions(cert)Crea opciones de firma a partir de un X509Certificate2 con una clave privada exportable.
.WithReason(text)Motivo de la firma escrito en el diccionario de firma (aparece en el panel de firmas del visor).
.WithSignerName(name)Nombre legible del firmante.
.WithLocation(location)Ubicación física o lógica de la firma.
doc.WithSignature(opts)Asocia las opciones de firma al documento. La firma se calcula y se incrusta al momento de guardar.
La firma usa SHA-256 y AES-128 (Rev 4), que cuenta con amplio soporte. Abre el archivo resultante en Adobe Acrobat o un visor compatible para verificar el panel de firmas. Un certificado autofirmado aparecerá como no confiable a menos que se agregue al almacén de confianza del visor.

Apariencia visible de la firma

Llama a .WithAppearance() para renderizar un cuadro de firma visible en la página, útil para documentos que requieren un campo visible de "firmado aquí". El cuadro muestra un borde, "Digitally Signed" y el nombre del firmante. Sin esto, la firma está presente criptográficamente pero es invisible.

var sigOpts = new PdfSignatureOptions(cert)
    .WithReason("Document approved")
    .WithSignerName("Jane Smith")
    .WithLocation("Toronto, ON")
    .WithAppearance(x: 72, y: 690, width: 220, height: 60);

PdfDocument.Create()
    .WithSignature(sigOpts)
    .AddPage(PageSizes.A4, canvas => { /* ... content ... */ })
    .Save("signed.pdf");

Cifrado basado en certificado

Cifra un PDF de modo que solo los titulares de la clave privada de un certificado X.509 específico puedan abrirlo, sin necesidad de una contraseña compartida. Usa /Filter /Adobe.PubSec, V=4, AES-128. Se admiten múltiples certificados de destinatario; cualquier titular puede abrir el documento.

using Majorsilence.Pdf;
using Majorsilence.Pdf.Security;
using System.Security.Cryptography.X509Certificates;

// Load the recipient's public certificate (no private key needed at encryption time)
X509Certificate2 cert = new X509Certificate2("recipient.cer");

var security = PdfPublicKeySecurity.ForRecipients(cert)
    .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText);

PdfDocument.Create()
    .WithPublicKeySecurity(security)
    .WithTitle("Confidential Report")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Only the certificate holder can open this.", 72, 72,
            TextStyle.Default.WithSize(14));
    })
    .Save("encrypted.pdf");

// Multiple recipients — any one of them can open the document
var multi = PdfPublicKeySecurity.ForRecipients(cert1, cert2, cert3);

Referencia de PdfPublicKeySecurity

MiembroDescripción
PdfPublicKeySecurity.ForRecipients(cert, …)Crea un descriptor de seguridad de clave pública para uno o más certificados de destinatario. La clave pública de cada destinatario cifra una copia de la clave del documento mediante CMS EnvelopedData (RSA).
.WithPermissions(flags)Campo de bits de indicadores PdfPermissions a otorgar (los mismos indicadores que la protección con contraseña).
doc.WithPublicKeySecurity(security)Asocia el descriptor al documento. Es mutuamente excluyente con WithSecurity (contraseña) y WithConformance (PDF/A).
En producción, usa certificados reales de una CA de confianza. Para pruebas puedes generar un certificado autofirmado en tiempo de ejecución usando System.Security.Cryptography.X509Certificates.CertificateRequest; consulta el ejemplo 23 en el proyecto de ejemplos para ver el patrón.

Combinación de PDF

PdfMerger concatena cualquier cantidad de arreglos de bytes PDF producidos de forma independiente en un único documento. Cada PDF de origen puede tener distintos tamaños de página, orientaciones y fuentes incrustadas; todos se conservan en la salida.

using Majorsilence.Pdf;

// Produce two source documents as byte arrays
byte[] coverPage = PdfDocument.Create()
    .WithTitle("Quarterly Report")
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawRectangle(0, 0, PageSizes.A4.Width, PageSizes.A4.Height,
            ShapeStyle.Filled(new PdfColor(30, 80, 160)));
        canvas.DrawText("Quarterly Report — Q2 2026", 72, 280,
            TextStyle.Default.WithSize(36).WithBold().WithColor(PdfColor.White));
    })
    .ToBytes();

byte[] contentPages = PdfDocument.Create()
    .AddPage(PageSizes.A4, canvas =>
    {
        canvas.DrawText("Financial Summary", 72, 72,
            TextStyle.Default.WithSize(22).WithBold());
        canvas.DrawText("Net Income: $1,500,000", 72, 120,
            TextStyle.Default.WithSize(12));
    })
    .ToBytes();

// Merge into a single document
byte[] merged = new PdfMerger()
    .Add(coverPage)
    .Add(contentPages)
    .WithTitle("Quarterly Report Q2 2026")
    .WithAuthor("Example Corp")
    .WithSubject("Financial Summary")
    .WithCreator("MyApp 2.0")
    .Merge();

File.WriteAllBytes("merged.pdf", merged);

Referencia de PdfMerger

MétodoDescripción
new PdfMerger()Crea un nuevo combinador (merger). No se requieren argumentos.
.Add(pdfBytes)Agrega las páginas de un PDF (como byte[]) a la cola de combinación. Llámalo tantas veces como sea necesario; las páginas se generan en orden.
.WithTitle(text)Título para los metadatos del documento combinado.
.WithAuthor(text)Autor para los metadatos del documento combinado.
.WithSubject(text)Asunto para los metadatos del documento combinado.
.WithCreator(text)Cadena de la aplicación creadora para los metadatos del documento combinado.
.Merge()Ejecuta la combinación y devuelve el PDF combinado como byte[]. La salida siempre es PDF 1.4.
La salida de PdfMerger siempre es PDF 1.4, sin importar la versión de los documentos de origen.