Esta página puede contener texto traducido automáticamente.
Tamaño, posición y renderizado de contenedores
El contenido es la parte más importante de un documento. No hay duda de ello. Otra parte crucial es el formato, que crea una comunicación clara, profesional y eficaz. Un documento con el formato adecuado resulta visualmente atractivo, legible y fácil de navegar.
Quizá ya sepa cómo organizar contenido usando contenedores. Y cómo aplicarles un color de fondo. Este artículo describe cómo especificar el tamaño y la posición de los contenedores. También cubre capacidades avanzadas como el renderizado condicional del contenido. Hay información sobre la compatibilidad con direcciones de contenido de derecha a izquierda.

La clase LayoutContainer proporciona todo lo necesario para organizar sus contenedores de forma profesional. Al aplicar relleno y alineación, puede crear un documento que deje una impresión positiva en sus usuarios.
Este artículo forma parte de una serie sobre Layout API para la generación de PDF. Si es nuevo en la API, lea primero la parte Primeros pasos con Layout API.
Tamaño
De forma predeterminada, los contenedores ocupan el área más pequeña necesaria para su contenido. Dicho de otro modo, el tamaño del contenedor es igual al tamaño intrínseco de su contenido.
El tamaño intrínseco de una imagen está determinado por las dimensiones del propio archivo de imagen. El tamaño intrínseco de un fragmento de texto es el tamaño del área que cubre todos los glifos del fragmento.
El tamaño de un contenedor compuesto como Column o Table depende del tamaño de las partes del contenedor.
Width & Height
Es posible especificar un ancho y una altura exactos de un contenedor mediante los métodos Width y Height. Esto resulta muy conveniente para contenedores de marcador de posición.
Un tamaño exacto también funciona bien para imágenes. Esto se debe a que Layout API las escala para ajustarlas o rellenar el contenedor según su ImageContentMode.
Debe tener cuidado con los tamaños exactos para contenedores compuestos y contenedores con texto. Obtendrá una LayoutException cuando no sea posible ajustar un contenido al tamaño proporcionado.
Hay casos en los que solo desea establecer una restricción de ancho o de altura. Puede establecer restricciones mediante los métodos MinWidth, MinHeight, MaxWidth y MaxHeight.
Tenga en cuenta que la biblioteca lanzará una LayoutException cuando no sea posible cumplir las restricciones.
Extend
Un contenedor puede extenderse para ocupar el máximo espacio disponible. Esto resulta útil cuando no conoce un tamaño exacto ni restricciones de tamaño.
Use el método ExtendHorizontal cuando quiera que el contenedor ocupe todo el espacio disponible solo en la dirección horizontal. El método ExtendVertical es útil cuando quiere extender el contenedor solo en la dirección vertical. El método Extend hace que el contenedor ocupe todo el espacio disponible en ambas direcciones.
var gray = new PdfGrayColor(75);
var text = "Content goes here";
var size = new PdfSize(150, 50);
PdfDocumentBuilder.Create().Generate("positioning-extend.pdf", doc =>
{
for (int i = 0; i < 4; i++)
{
doc.Pages(page =>
{
page.Size(size);
page.Content().Row(r =>
{
var container = r.AutoItem().Background(gray);
switch (i)
{
case 0:
container.Text(text);
break;
case 1:
container.ExtendHorizontal().Text(text);
break;
case 2:
container.ExtendVertical().Text(text);
break;
case 3:
container.Extend().Text(text);
break;
}
});
});
}
});
El resultado del código anterior está en positioning-extend.pdf. Como puede ver, cada una de las cuatro páginas contiene el mismo texto sobre un fondo gris. Pero el tamaño del contenedor es diferente en cada página.
MinimalBox
La clase LayoutContainer proporciona el método MinimalBox. Es una especie de opuesto de los métodos Extend. El método MinimalBox produce contenedores anidados que solo usan el espacio mínimo necesario para el contenido.
var gray = new PdfGrayColor(75);
var size = new PdfSize(150, 50);
PdfDocumentBuilder.Create().Generate("positioning-minimalbox.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(size);
page.Content().MinimalBox().Background(gray).Text("I don't want more space");
});
doc.Pages(page =>
{
page.Size(size);
page.Content().Background(gray).Text("I'll take everything");
});
});
El resultado del código anterior está en positioning-minimalbox.pdf. Debido a la llamada a MinimalBox, el texto de la primera página ocupa solo el espacio requerido. En la segunda página, el texto cubre toda la página.
Scale
Es posible escalar cualquier contenido de un contenedor. El método Scale afecta al contenido tanto en dirección horizontal como vertical. Use el método ScaleHorizontal o ScaleVertical para cambiar el contenido solo en una dirección. Los dos últimos métodos no conservan la relación de aspecto del contenido.
Los valores de escala menores que 1 reducen el área ocupada por un contenedor. Los valores mayores que 1 aumentan el área. Para voltear el contenido de un contenedor, use un valor de escala negativo. Por ejemplo, ScaleVertical(-1) produce una versión invertida verticalmente del contenido original.
PdfDocumentBuilder.Create().Generate("positioning-scale.pdf", doc =>
{
doc.Pages(page =>
{
page.Content()
.MinimalBox()
.Column(column =>
{
var scales = new[] { 0.5f, 0.75f, 1, 1.3f, 1.5f };
foreach (var scale in scales)
{
var percent = (int)(scale * 100);
column.Item()
.Scale(scale)
.Text(FormattableString.Invariant($"Scale equals {scale} ({percent}%)."))
.FontSize(20);
column.Item().LineHorizontal(0.5);
}
});
});
});
El resultado del código anterior está en positioning-scale.pdf.
ScaleToFit
Puede reducir el contenido para que quepa en un espacio disponible. Por ejemplo, cuando tiene un área fija para mostrar el nombre o la dirección de una persona. Por supuesto, puede aumentar el área en caso de un nombre más largo. Pero un enfoque más sencillo podría ser reducir un poco el texto. Use el método ScaleToFit para reducir la escala del contenido.
ScaleToFit conserva la relación de aspecto del contenido. El método nunca agranda el contenido. Tenga en cuenta que este método realiza cálculos iterativos. Esto puede ralentizar el proceso de generación de PDF.
PdfDocumentBuilder.Create().Generate("positioning-scaletofit.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().Column(column =>
{
for (int i = 0; i < 5; i++)
{
column.Item()
.Width(230 - 20 * i)
.Height(20)
.ScaleToFit()
.Border(b => b.Thickness(0.5))
.Text(" This text should fit into the changing width.");
}
});
});
});
El resultado del código anterior está en positioning-scaletofit.pdf.
AspectRatio
La relación de aspecto define la relación proporcional entre el ancho y la altura de un contenedor. Para averiguar la relación de aspecto de un contenedor, divida su ancho por su altura.
Use el método AspectRatio para especificar la relación de aspecto de un contenedor. Ayuda cuando diseña un contenedor reutilizable para diferentes diseños o tamaños de página.
PdfDocumentBuilder.Create().Generate("positioning-aspectratio.pdf", doc =>
{
var ratios = new double[] { 0.25, 0.5, 1, 2 };
foreach (var ratio in ratios)
{
var ratioText = ratio.ToString(CultureInfo.InvariantCulture);
doc.Pages(page =>
{
page.Size(200, 200);
page.Content().Column(column =>
{
column.Item()
.AspectRatio(ratio)
.Background(new PdfGrayColor(75))
.Text($"Width / Heigth = {ratioText}");
});
});
}
});
El resultado del código anterior está en positioning-aspectratio.pdf.
El método tiene el parámetro opcional de tipo AspectRatioMode. Use este parámetro para especificar cómo redimensionar el contenido manteniendo la relación de aspecto.
Un contenedor con una relación de aspecto especificada ocupa tanto espacio como sea posible. Según el modo, el contenedor intentará ocupar toda el área disponible (valor predeterminado), el ancho o la altura.
Tenga en cuenta que la biblioteca puede lanzar una LayoutException. Esto ocurre cuando no puede satisfacer los requisitos de tamaño, relación de aspecto y modo de relación de aspecto.
Unconstrained
Los contenedores pueden no tener ninguna restricción de tamaño. Use el método Unconstrained para eliminar todas las restricciones de tamaño de un contenedor.
El contenido en un contenedor sin restricciones ocupa un espacio con un tamaño igual al tamaño intrínseco del contenido. El propio contenedor sin restricciones no ocupa espacio. Como resultado, los contenedores hermanos pueden cubrir el contenido del contenedor sin restricciones.
PdfDocumentBuilder.Create().Generate("positioning-unconstrained.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().MinimalBox()
.Border(b => b.Thickness(0.5))
.Column(column =>
{
column.Item().Text("First item");
column.Item().Unconstrained()
.Text("Second item ignores all size constraints");
// usando una línea en blanco para el tercer elemento
column.Item().Text(new string(' ', 20))
.BackgroundColor(new PdfRgbColor(187, 237, 237), 50);
column.Item().Text("Fourth item");
});
});
});
El resultado del código anterior está en positioning-unconstrained.pdf. En el código, usé una línea de espacios con un fondo semitransparente para el tercer elemento de la columna. Como puede ver, el tercer elemento cubre parcialmente el segundo elemento (sin restricciones).
Posición
La posición de un contenedor depende de varias cosas. Algunas son la alineación, el relleno, la posición del contenedor principal y la dirección del contenido. De forma predeterminada, cualquier contenedor se adhiere a la posición disponible más a la izquierda y más arriba.
Padding
Uno de los requisitos más comunes es agregar espacio alrededor del contenido del contenedor. La clase LayoutContainer proporciona un conjunto de métodos para configurar el área de padding de un contenedor. El área de padding es el espacio entre el contenido y su borde. Dicho de otro modo, el padding representa el espacio interior que rodea el contenido.
El método Padding establece el padding en los cuatro lados de un contenedor a la vez. Use el método PaddingHorizontal para especificar el padding solo en los lados izquierdo y derecho. PaddingVertical hace lo mismo solo en los lados superior e inferior. Para configurar el padding individualmente en un lado, use uno de los métodos PaddingTop/Bottom/Left/Right.
Align
Para cambiar la posición de un contenedor, use métodos de alineación. Los métodos AlignLeft/AlignCenter/AlignRight aplican una alineación horizontal y devuelven un contenedor anidado. Los métodos AlignTop/AlignMiddle/AlignBottom devuelven un contenedor anidado con la alineación vertical correspondiente.
Un contenedor con alineación aplicada explícitamente ocupa el área con el ancho y/o la altura mínimos requeridos. El siguiente código crea una columna con dos elementos. Un elemento tiene alineación aplicada explícitamente.
PdfDocumentBuilder.Create().Generate("positioning-alignment.pdf", doc =>
{
var color = new PdfRgbColor(187, 237, 237);
var text = "Hello";
doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Column(c =>
{
c.Item().Extend().Background(color).Text(text);
c.Item().Extend().AlignLeft().Background(color).Text(text);
});
});
});
La llamada a Extend hace que ambos elementos ocupen toda la página. Llamo al método AlignLeft en el segundo elemento. Esta llamada no cambia la posición porque el contenedor del elemento alinea el texto a la izquierda de forma predeterminada. Pero la alineación aplicada explícitamente cambia el área ocupada por el segundo elemento.
El resultado del código anterior está en positioning-alignment.pdf.
Translate
Para reposicionar un contenedor horizontal y/o verticalmente, use los métodos Translate/TranslateX/TranslateY. El primero mueve contenedores tanto horizontal como verticalmente. Los otros dos mueven contenedores en una sola dirección.
Todos estos métodos sobrescriben la posición, pero conservan las restricciones de tamaño. El contenedor desplazado puede superponerse con otros contenedores. Use valores de parámetros negativos para mover hacia la izquierda y/o hacia arriba. Los valores positivos causan un desplazamiento hacia la derecha y/o hacia abajo.
PdfDocumentBuilder.Create().Generate("positioning-translate.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
r.ConstantItem(50)
.Background(new PdfRgbColor(187, 237, 237))
.Text("Left");
r.ConstantItem(50)
// mover este elemento 10 puntos a la izquierda y 5 puntos hacia abajo
.Translate(-10, 5)
.Background(new PdfRgbColor(15, 130, 9))
.Text("Right");
});
});
});
El resultado del código anterior está en positioning-translate.pdf.
Rotate
El contenido rotado, especialmente el texto, puede mejorar sus documentos de diferentes maneras. Por ejemplo, puede ahorrar espacio y hacer que su documento resulte más atractivo y agradable estéticamente.
La clase LayoutContainer proporciona dos enfoques para rotar contenido. Independientemente del enfoque que use, el contenedor con el contenido rotado respeta las restricciones de posición y tamaño.
Rotate by 90 degrees
Los métodos RotateRight y RotateLeft rotan el contenido 90 grados en sentido horario y antihorario, respectivamente.
El siguiente código muestra cómo crear un documento con texto vertical junto al contenido principal de la página.
PdfDocumentBuilder.Create().Generate("positioning-rotate.pdf", doc =>
{
var lightGray = new PdfGrayColor(90);
doc.Pages(page =>
{
page.Size(298, 210);
page.Content().Row(r =>
{
r.AutoItem()
.RotateLeft()
.Background(lightGray)
.Text("This content goes up");
r.RelativeItem(1)
.ExtendVertical()
.PaddingHorizontal(10)
.Column(t =>
{
for (int i = 0; i < 15; i++)
t.Item().Text("The main content line goes here");
});
r.AutoItem()
.RotateRight()
.Background(lightGray)
.Text("This content goes down");
});
});
});
El resultado del código anterior está en positioning-rotate.pdf.
Rotate to any angle
El método Rotate rota el contenido un número arbitrario de grados. Los números positivos provocan rotación en sentido horario. Los números negativos son para rotación en sentido antihorario.
El punto de origen de la rotación es la esquina superior izquierda del contenedor. El contenido rotado puede superponerse con otros contenedores.
PdfDocumentBuilder.Create().Generate("positioning-rotate2.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(298, 210);
page.Content()
.Padding(25)
.Background(new PdfGrayColor(70)) // gris
.AlignCenter()
.AlignMiddle()
.Background(new PdfGrayColor(100)) // blanco
.Rotate(30)
.Width(100)
.Height(100)
.Background(new PdfRgbColor(187, 237, 237)); // azul
});
});
El resultado del código anterior está en positioning-rotate2.pdf.
Para cambiar el punto de origen de la rotación, llame a uno de los métodos Translate antes de la llamada a Rotate. No olvide volver a trasladar el origen después de la llamada.
// trasladar el punto de origen de la rotación
.TranslateX(50)
.TranslateY(50)
.Rotate(30)
// trasladar de vuelta
.TranslateX(-50)
.TranslateY(-50)
Diseño condicional
El diseño de su documento PDF puede depender de una condición. Por ejemplo, puede usar distinta alineación o color de fondo para las filas pares e impares de una columna.
Use el método Container para insertar un contenedor anidado con un diseño que dependa de una condición. Llamar a este método no romperá la cadena de llamadas.
PdfDocumentBuilder.Create().Generate("positioning-container.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().Column(c =>
{
for (int i = 0; i < 15; i++)
{
c.Item()
.TextStyle(TextStyle.Parent.FontSize(14))
.Container(x => i % 2 == 0 ? x.Background(new PdfGrayColor(70)) : x)
.Text($"Row {i + 1}");
}
});
});
});
El resultado del código anterior está en positioning-container.pdf.
DSL
Algunas partes de un documento pueden usar el mismo diseño. Por ejemplo, pueden establecer un borde de aspecto similar o usar el mismo formato. Según el principio de «no te repitas», recomiendo extraer el código común a un método.
Usar un método de extensión para el código común proporciona dos ventajas:
- Puede usar el método en cadenas de llamadas a métodos
- Puede tener un nombre significativo para un conjunto de llamadas a métodos
Con estas ventajas, puede construir un lenguaje específico de dominio (DSL). Con el DSL, su código de diseño puede ser más corto y más fácil de entender.
static class LayoutHelpers
{
public static LayoutContainer NumberCell(this Table table)
=> table.Cell().Border(b => b.Thickness(0.5)).PaddingHorizontal(10);
}
PdfDocumentBuilder.Create().Generate("positioning-dsl.pdf", doc => doc.Pages(page =>
{
page.Content().Table(t =>
{
t.Columns(c =>
{
for (int i = 0; i < 4; ++i)
c.ConstantColumn(50);
});
for (int i = 0; i < 16; i++)
t.NumberCell().Text($"{i + 1}");
});
}));
Proceso de renderizado
El complemento Layout organiza cualquier contenido que coloque en un contenedor según las restricciones de tamaño y posición. Parte del contenido puede abarcar varias páginas. Existe un orden estricto en el que se renderiza el contenido. El flujo de contenido es otro nombre para este orden.
La clase LayoutContainer proporciona algunos métodos para ajustar el flujo de contenido. Puede que no los necesite siempre, pero hay casos en los que no hay otra manera de conseguir el diseño requerido.
PageBreak
Cuando necesite comenzar un bloque de contenido en una página nueva, use el método PageBreak. Por ejemplo, puede usar el método para iniciar un elemento de Column en una página nueva.
Aquí hay un código de ejemplo que divide una columna para que cada página contenga solo dos filas.
PdfDocumentBuilder.Create().Generate("positioning-pagebreak.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Column(c =>
{
for (int i = 1; i <= 10; ++i)
{
c.Item().Text($"Item {i}");
if (i % 2 == 0)
c.Item().PageBreak();
}
});
}));
El resultado del código anterior está en positioning-pagebreak.pdf.
ShowIf
Según una condición, puede necesitar mostrar u ocultar un contenedor. Existe el método ShowIf, que en esencia es una forma de azúcar sintáctico para este caso especial de diseño condicional.
En el siguiente código, uso el método ShowIf para insertar una línea vertical después de cada 5 elementos de una fila.
PdfDocumentBuilder.Create().Generate("positioning-showif.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
for (int i = 0; i < 10; ++i)
{
r.AutoItem().Text(i.ToString());
r.AutoItem().ShowIf(i > 0 && (i + 1) % 5 == 0).LineVertical(0.5);
}
});
}));
El resultado del código anterior está en positioning-showif.pdf.
ShowOnce
Puede impedir que una parte del contenido se repita en las páginas siguientes. Use el método ShowOnce para indicar al motor de diseño que renderice el contenido completo solo una vez.
Revise el siguiente código para ver cómo la llamada a ShowOnce impide que "Environment" aparezca en la segunda página.
PdfDocumentBuilder.Create().Generate("positioning-showonce.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
r.RelativeItem()
.Background(new PdfGrayColor(75))
.Border(b => b.Thickness(0.5))
.Padding(5)
.ShowOnce()
.Text("Environment");
r.RelativeItem()
.Border(b => b.Thickness(0.5))
.Padding(5)
.Column(c =>
{
c.Item().Text(Environment.OSVersion.VersionString);
c.Item().Text(string.Empty);
c.Item().Text(
Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")
?? string.Empty
);
});
});
}));
El resultado del código anterior está en positioning-showonce.pdf.
ShowEntire
El comportamiento predeterminado es dividir el contenido entre páginas cuando no cabe en una sola. Use el método ShowEntire para renderizar el contenedor completo en una sola página.
Tenga en cuenta que la biblioteca lanza una LayoutException cuando no es posible ajustar todo el contenido en una sola página.
Debido a la llamada a ShowEntire en el siguiente código, el texto del segundo elemento comienza en la segunda página. Sin la llamada, comenzaría en la primera página, inmediatamente después del texto del primer elemento.
PdfDocumentBuilder.Create().Generate("positioning-showentire.pdf", doc => doc.Pages(page =>
{
page.Size(100, 100);
page.Content().Column(c =>
{
c.Item().Text(t =>
{
for (var i = 0; i < 4; i++)
t.Line($"First item line {i + 1}");
});
c.Item()
.Background(new PdfRgbColor(250, 123, 5))
.ShowEntire()
.Text(t =>
{
for (var i = 0; i < 4; i++)
t.Line($"Second item line {i + 1}");
});
});
}));
El resultado del código anterior está en positioning-showentire.pdf.
EnsureSpace
En cierto sentido, EnsureSpace es un caso especial del método ShowEntire. La diferencia es que EnsureSpace no requiere que todo el contenido quepa en una página. El método solo intenta ajustar una parte del contenido con la altura especificada. Todo lo demás irá a la página siguiente.
Si un área no ocupada de la página actual tiene una altura menor que la solicitada, todo el contenido se renderizará en una página nueva. Aquí, el método producirá el mismo resultado que ShowEntire.
StopPaging
Use el método StopPaging para producir salida como máximo en una página. Llamar a este método en un contenedor impide su paginación. El complemento Layout no dividirá el contenido de este contenedor entre páginas. No renderizará ningún dato que no quepa en una página.
El siguiente código de ejemplo agrega dos conjuntos de páginas al documento. Ambos conjuntos usan como contenido una lista de nombres de días de la semana. El primer conjunto tiene solo una página, porque el código llama al método StopPaging para el contenedor de contenido de las páginas.
PdfDocumentBuilder.Create().Generate("positioning-stoppaging.pdf", doc =>
{
static Action<TextContainer> produceText(string heading)
{
var text = string.Join('\n', DateTimeFormatInfo.InvariantInfo.DayNames);
return t =>
{
t.Line(heading).BackgroundColor(new PdfRgbColor(250, 123, 5));
t.Span(text);
};
}
doc.Pages(page =>
{
page.Size(100, 100);
page.Content()
.StopPaging()
.Text(produceText("Without paging:"));
});
doc.Pages(page =>
{
page.Size(100, 100);
page.Content()
.Text(produceText("Default behaviour:"));
});
});
El resultado del código anterior está en positioning-stoppaging.pdf.
SkipOnce
Puede posponer la aparición de contenido. Si un contenedor aparece en más de una página, use SkipOnce para omitir la primera página y renderizar el contenido en todas las páginas a partir de la segunda.
Esta capacidad es útil para todo tipo de encabezados, pero también puede usarla con otro contenido. Revise el siguiente código para ver cómo la llamada a SkipOnce impide que el encabezado aparezca en la primera página.
PdfDocumentBuilder.Create().Generate("positioning-skiponce.pdf", doc => doc.Pages(page =>
{
page.Size(298, 210);
page.Header()
.SkipOnce()
.Text("This header will appear starting from page 2")
.Style(TextStyle.Parent.Underline());
page.Content().Column(c =>
{
for (int i = 0; i < 5; i++)
{
if (i > 0)
c.Item().PageBreak();
c.Item().Text($"Page {i + 1}");
}
});
}));
El resultado del código anterior está en positioning-skiponce.pdf.
Dirección del contenido
La dirección predeterminada del contenido es de izquierda a derecha. Los contenedores alinean su texto y demás contenido a la izquierda.
Pero hay idiomas que se escriben de derecha a izquierda, por ejemplo, árabe y hebreo. Al crear contenido en estos idiomas, use el método ContentFromRightToLeft. Una llamada a este método cambiará la dirección del contenido del contenedor a de derecha a izquierda. El método también cambiará la alineación predeterminada.
Si la mayor parte del contenido de sus páginas está en un idioma RTL, puede establecer la dirección predeterminada del contenido de las páginas como de derecha a izquierda. Para ello, use el método PageLayout.ContentFromRightToLeft. Después, para sobrescribir la dirección predeterminada del contenido de contenedores seleccionados, use el método ContentFromLeftToRight.
Tenga en cuenta que la dirección del contenido no afecta a la alineación especificada explícitamente. Por ejemplo, el contenido alineado a la derecha permanecerá en el lado derecho sin importar la dirección del contenido que establezca para su contenedor. El orden visual de los elementos secundarios será diferente según la dirección del contenido.