Esta página puede contener texto traducido automáticamente.
Tamaño, posición y representación de los 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 efectiva. Un documento con el formato adecuado es visualmente atractivo, legible y fácil de navegar.
Quizás ya sepas cómo organizar el contenido mediante 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 la representación condicional de contenido. Hay información sobre la compatibilidad con direcciones de contenido de derecha a izquierda.
La clase LayoutContainer proporciona todo lo que necesita para organizar sus contenedores de manera profesional. Al aplicar relleno y alineación, puede crear un documento que deje una impresión positiva en sus usuarios.
Este artículo es parte de una serie sobre la API Layout para la generación de PDF. Si es nuevo en la API, lea primero la parte Introducción a la API de Layout.
9.5.17548-dev 9.5.17548-dev14,726 pasaron Descargas totales de NuGet 4,514,921
Tamaño
De forma predeterminada, los contenedores ocupan el área más pequeña requerida para su contenido. Dicho de otra manera, el tamaño del contenedor es igual al tamaño intrínseco de su contenido.
El tamaño intrínseco de una imagen determinado por las dimensiones del propio archivo de imagen. El tamaño intrínseco de un intervalo de texto es el tamaño del área que cubre todos los glifos del intervalo.
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 alto exactos de un contenedor utilizando los métodos Width y Height. Esto es muy conveniente para contenedores de marcador de posición.
Un tamaño exacto también funciona bien para las imágenes. Esto se debe a que Layout API los escala para que quepan o llenen el contenedor dependiendo de su ImageContentMode.
Debes tener cuidado con los tamaños exactos de los contenedores compuestos y de los 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 alto. Puede establecer restricciones utilizando los métodos MinWidth, MinHeight, MaxWidth y MaxHeight.
Tenga en cuenta que la biblioteca generará una LayoutException
cuando no sea posible satisfacer
las restricciones.
Extend
Un contenedor puede extenderse para ocupar el máximo espacio disponible. Esto resulta útil cuando no se conoce el tamaño exacto o las restricciones de tamaño.
Utilice el método ExtendHorizontal cuando desee que el contenedor ocupe todo el espacio disponible solo en la dirección horizontal. ExtendVertical es útil cuando desea 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
LayoutContainer proporciona el método
MinimalBox. Es algo opuesto a los
métodos Extend. MinimalBox
produce
contenedores anidados que solo utilizan el mínimo espacio 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 MinimalBox, el texto de la primera página ocupa sólo el espacio requerido. En la segunda página, el texto cubre toda la página.
Scale
Es posible escalar cualquier contenido en un contenedor. El método Scale afecta el contenido tanto en dirección horizontal como vertical. Utilice el método ScaleHorizontal o ScaleVertical para cambiar el contenido en una sola dirección. Los dos últimos métodos no conservan la relación de aspecto del contenido.
Los valores de escala inferiores a 1 reducen el área ocupada por un contenedor. Los valores mayores
que 1 aumentan el área. Para voltear el contenido de un contenedor, utilice un valor de escala
negativo. Por ejemplo, ScaleVertical(-1)
da como resultado una versión al revés 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, puedes aumentar el área en caso de un nombre más largo. Pero una solución más sencilla podría ser reducir un poco la escala del texto. Utilice el método ScaleToFit para reducir el 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 podría 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 el alto de un contenedor. Para conocer la relación de aspecto de un contenedor, divida su ancho por su alto.
Utilice el método AspectRatio para especificar la relación de aspecto de un contenedor. Es útil diseñar 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. Utilice este parámetro para especificar cómo cambiar el tamaño del contenido conservando la relación de aspecto.
Un contenedor con una relación de aspecto específica ocupa tanto espacio como sea posible. Dependiendo del modo, el contenedor intentará ocupar toda el área disponible (predeterminada), ancho o alto.
Tenga en cuenta que la biblioteca puede generar una LayoutException. Ocurre cuando no puede satisfacer los requisitos de tamaño, relación de aspecto y modo de relación de aspecto.
Unconstrained
Los contenedores no pueden tener ninguna restricción de tamaño. Utilice 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 contenedor sin restricciones en sí 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 vacía 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, utilicé 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. Algunos de ellos son alineación, relleno, posición del contenedor principal y dirección del contenido. De forma predeterminada, cualquier contenedor se mantiene en la posición disponible más a la izquierda y más arriba.
Padding
Uno de los requisitos más comunes es agregar algo de espacio alrededor del contenido del contenedor. LayoutContainer proporciona un conjunto de métodos para configurar el área de relleno de un contenedor. El área de relleno es el espacio entre su contenido y su borde. Para decirlo de otra manera, el relleno representa el espacio interior que rodea el contenido.
El método Padding establece el relleno en
los cuatro lados de un contenedor a la vez. Utilice el método
PaddingHorizontal para
especificar el relleno solo en los lados izquierdo y derecho.
PaddingVertical hace lo mismo
solo en los lados superior e inferior. Para configurar el relleno individualmente en un lado, use
uno de los métodos PaddingTop/Bottom/Left/Right
.
Align
Para cambiar la posición de un contenedor, utilice 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 una
alineación vertical correspondiente.
Un contenedor con alineación aplicada explícitamente toma el área con el ancho y/o alto mínimo requerido. El siguiente código crea una columna con dos elementos. Un elemento ha aplicado explícitamente la alineación.
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 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, utilice los métodos
Translate/TranslateX/TranslateY
. El primero mueve contenedores tanto horizontal como
verticalmente. Los otros dos mueven los contenedores en una sola dirección.
Todos estos métodos anulan la posición pero conservan las restricciones de tamaño. El contenedor desplazado puede superponerse con otros contenedores. Utilice valores de parámetros negativos para moverse hacia la izquierda y/o hacia la parte superior. Los valores positivos provocan 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)
// moviendo este elemento 10 puntos hacia 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 conservar espacio y hacer que su documento sea más atractivo y estéticamente agradable.
LayoutContainer proporciona dos métodos para rotar contenido. No importa qué enfoque utilice, el contenedor con el contenido rotado respeta las restricciones de posición y tamaño.
Girar 90 grados
Los métodos RotateRight y RotateLeft rotan el contenido 90 grados en el sentido de las agujas del reloj y en el sentido contrario a las agujas del reloj, respectivamente.
El siguiente código muestra cómo crear un documento con texto vertical al lado del contenido de la página principal.
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.
Girar a cualquier ángulo
El método Rotate rota el contenido un número arbitrario de grados. Los números positivos provocan una rotación en el sentido de las agujas del reloj. Los números negativos corresponden a la 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 Rotate. No olvides traducir el
origen después de la llamada.
// trasladar el punto de origen de la rotación
.TranslateX(50)
.TranslateY(50)
.Rotate(30)
// traducir de nuevo
.TranslateX(-50)
.TranslateY(-50)
Diseño condicional
El diseño de su documento PDF puede depender de una condición. Por ejemplo, puede utilizar una alineación o un color de fondo diferente para las filas pares e impares de una columna.
Utilice el método Container para insertar un contenedor anidado con un diseño que depende 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 utilizar el mismo diseño. Por ejemplo, pueden establecer el mismo borde o utilizar el mismo formato. De acuerdo con el principio de "no repetirse", recomiendo extraer código común en un método.
El uso de un método de extensión para el código común proporciona dos beneficios:
- Puedes usar el método en cadenas de llamadas a métodos.
- Puede tener un nombre significativo para un conjunto de llamadas a métodos
Teniendo en cuenta estos beneficios, puede construir un lenguaje de dominio específico (DSL). Con 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 restricciones de tamaño y posición. Algunos contenidos pueden abarcar varias páginas. Existe un orden estricto en el que se presenta el contenido. El flujo de contenido es otro nombre para este orden.
LayoutContainer proporciona algunos métodos para modificar el flujo de contenido. Puede que no los necesites siempre, pero hay casos en los que no hay otra forma de lograr el diseño requerido.
PageBreak
Cuando necesite iniciar un bloque de contenido desde una nueva página, utilice el método PageBreak. Por ejemplo, puede utilizar el método para iniciar un elemento de Column desde una página nueva.
Aquí hay un código de muestra 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
Dependiendo de una condición, es posible que necesites mostrar/ocultar un contenedor. Existe el método ShowIf que esencialmente es un azúcar sintáctico para este caso especial de diseño condicional.
En el siguiente código, utilizo el método ShowIf
para insertar una línea vertical después de cada
5 elementos seguidos.
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 evitar que un contenido se repita en las páginas siguientes. Utilice el método ShowOnce para indicarle al motor de diseño que represente completamente el contenido una sola vez.
Verifique el siguiente código para ver cómo ShowOnce
evita 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 página. Utilice el método ShowEntire para representar el contenedor completo en una página.
Tenga en cuenta que la biblioteca genera una LayoutException cuando no es posible colocar todo el contenido en una página.
Debido a la llamada ShowEntire
en el siguiente código, el texto del segundo elemento comienza
desde 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
sólo intenta ajustar una parte del contenido a la altura especificada. Todo lo demás irá en la
página siguiente.
Si un área desocupada de la página actual tiene una altura menor que la solicitada, todo el
contenido se representará en una página nueva. Aquí, el método producirá el mismo resultado que el
método ShowEntire
.
StopPaging
Utilice el método StopPaging para generar resultados en como máximo una página. Llamar a este método en un contenedor evita su paginación. El complemento Layout no dividirá el contenido de este contenedor entre páginas. No mostrará ningún dato que no quepa en una página.
El siguiente código de muestra agrega dos conjuntos de páginas al documento. Ambos conjuntos
utilizan una lista de nombres de días laborables como contenido. 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
Puedes posponer la aparición del contenido. Si un contenedor aparece en más de una página, use SkipOnce para omitir la primera página y representar el contenido en todas las páginas a partir de la segunda.
Esta capacidad es útil para todo tipo de encabezados, pero también puedes usarla con otro
contenido. Verifique el siguiente código para ver cómo SkipOnce
evita 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 de contenido
La dirección predeterminada del contenido es de izquierda a derecha. Los contenedores alinean su texto y otro 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, utilice el método ContentFromRightToLeft. Una llamada cambiará la dirección del contenido del contenedor 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 de derecha a izquierda como la dirección de contenido predeterminada para las páginas. Utilice el método PageLayout.ContentFromRightToLeft para esto. Luego, para anular la dirección del contenido predeterminada para los contenedores seleccionados, utilice el método ContentFromLeftToRight.
Tenga en cuenta que la dirección del contenido no afecta la alineación especificada explícitamente. Por ejemplo, el contenido alineado a la derecha se mantendrá 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.