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
- Hola, PDF
- Estilos de API
- Texto y TextStyle
- Ajuste de texto
- Formas
- Opacidad
- Líneas y curvas
- Imágenes
- Tablas
- Enlaces y tooltips
- Fuentes TrueType
- Documentos multipágina
- Opciones de guardado
- Metadatos y versión
- Conformidad PDF/A
- Texto de derecha a izquierda
- Protección con contraseña
- Firmas digitales
- Cifrado de clave pública
- Combinación de PDF
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étodo | Descripció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ámetro | Descripción |
|---|---|
| text | La cadena a renderizar. \n fuerza un salto de línea en esa posición. |
| x, y | Esquina superior izquierda del cuadro delimitador en puntos PDF. |
| width, height | Dimensiones del cuadro. El texto que se extendería más allá de y + height se omite. |
| style | TextStyle: 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étodo | Descripció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étodo | Descripció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étodo | Descripció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");
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");
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ón | Notas |
|---|---|
| PdfVersion.Pdf14 | Predeterminado. Encabezado %PDF-1.4. Tabla de referencias cruzadas tradicional. Compatible con prácticamente todos los visores de PDF. |
| PdfVersion.Pdf20 | ISO 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
| Nivel | Basado en | Notas |
|---|---|---|
| PdfConformance.PdfA1b | PDF 1.4 | Máxima compatibilidad con visores. Sin transparencia. Ampliamente usado para archivado. |
| PdfConformance.PdfA2b | PDF 1.7 | Permite transparencia. Recomendado para archivos nuevos. |
| PdfConformance.PdfA3b | PDF 1.7 | Igual que PDF/A-2b, además de admitir archivos adjuntos incrustados. |
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
| Miembro | Descripció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.Print | Permitir la impresión. |
| PdfPermissions.CopyText | Permitir 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
| Miembro | Descripció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. |
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
| Miembro | Descripció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). |
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étodo | Descripció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. |
PdfMerger siempre es PDF 1.4, sin importar la versión de los documentos de origen.