notas de estudio de itext7-Capítulo 3

Prefacio

    ¿Recuerda que en el primer capítulo de esta serie, creamos tamaños de página específicos, márgenes de página específicos (definidos explícita o implícitamente) Documenty cuando Documentagregamos bloques de dibujo básicos al objeto, como Paragraphsy Lists, iText se asegurará de que el contenido esté bien organizado en la página. Al mismo tiempo, también hemos creado un Tableobjeto para mostrar el contenido de un archivo CSV y el resultado se ha mostrado muy bien. Pero, ¿qué pasa si todo lo anterior no se ejecuta de manera eficiente? ¿Qué pasa si queremos controlar mejor el diseño del contenido en una página web? ¿Qué pasa si no está satisfecho con el borde rectangular dibujado por la clase Table? ¿Qué sucede si agrega contenido a una ubicación específica en cada página, sin importar cuántas páginas se creen?

    ¿Podemos resolver este problema con el método de dibujar todo en posiciones absolutas mencionado en el Capítulo 2? A través del ejemplo del dibujo del texto inicial de Star Wars en el Capítulo 2, nos dimos cuenta de que esto puede conducir a un código muy complicado (el código es difícil de mantener). Por supuesto, debe haber una manera de combinar las API de alto nivel de los componentes básicos con las API de nivel inferior para que podamos controlar aún más el diseño. Este es el contenido que se discute en este capítulo-Capítulo 3.

Presentar el renderizador de documentos

    Supongamos que queremos Documentagregar texto e imágenes a uno , pero no queremos que el texto ocupe todo el ancho del documento. En su lugar, queremos organizar el contenido en tres columnas, como se muestra en la Figura 1:

iText3-1

Figura 1. El texto y las imágenes están organizados en columnas

    Este ejemplo puede usar el siguiente código (los siguientes códigos son todos NewYorkTimesparte de la clase):

PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
PageSize ps = PageSize.A5;
Document document = new Document(pdf, ps);
//Set column parameters
float offSet = 36;
float columnWidth = (ps.getWidth() - offSet * 2 + 10) / 3;
float columnHeight = ps.getHeight() - offSet * 2;
//Define column areas
Rectangle[] columns = {
    new Rectangle(offSet - 5, offSet, columnWidth, columnHeight),
    new Rectangle(offSet + columnWidth, offSet, columnWidth, columnHeight),
    new Rectangle(
        offSet + columnWidth * 2 + 5, offSet, columnWidth, columnHeight)};
document.setRenderer(new ColumnDocumentRenderer(document, columns));
// adding content
Image inst = new Image(ImageDataFactory.getImage(INST_IMG)).setWidth(columnWidth);
String articleInstagram = new String(
    Files.readAllBytes(Paths.get(INST_TXT)), StandardCharsets.UTF_8);
NewYorkTimes.addArticle(document,
    "Instagram May Change Your Feed, Personalizing It With an Algorithm",
    "By MIKE ISAAC MARCH 15, 2016", inst, articleInstagram);
doc.close();

    Todo el mundo debería estar familiarizado con las tres primeras líneas, que se presentaron en el capítulo 1. Las líneas 5, 6 y 7 definen una serie de parámetros:

  • offsetVariable, use esta variable para definir el ancho de los bordes superior, inferior, izquierdo y derecho
  • El ancho de cada columna columnWidth, esto se calcula dividiendo la página calculable en 3 partes (nuestro objetivo es 3 columnas). El tamaño de la página calculable es el ancho de toda la página -2 * offset+10, y la función de +10 es asegurar Hay un espacio entre cada columna.
  • columnHeightEl tamaño es simplemente la altura de toda la página -2 *offset

    Usamos columnsesta variable de matriz para almacenar tres Rectangleobjetos (antes de revisar, tenga en cuenta que el sistema de coordenadas predeterminado está en la esquina inferior izquierda, con el eje x a la derecha y el eje y arriba a la derecha):

  • El primero Rectangle: las coordenadas de la esquina inferior izquierda son ( offset-5, offset), el ancho es columnWidthy la altura escolumnHeight
  • El segundo Rectangle: las coordenadas de la esquina inferior izquierda son ( offset+columnWidth, offset), el ancho es columnWidthy la altura escolumnHeight
  • Tercero Rectangle: las coordenadas de la esquina inferior izquierda son ( offset+2*columnWidth+5, offset), el ancho es columnWidthy la altura escolumnHeight

    A continuación, utilizamos columnseste objeto para crear ColumnDocumentRenderer, una vez declarada esta ColumnDocumentRenderercomo Documentuna DocumentRendererque a Documentseguir tres definimos todo el contenido se añadirá a Rectanglela pantalla de diseño.

    En la línea 16, creamos un Imageobjeto y escalamos la imagen proporcionalmente para que se ajuste al ancho del rectángulo. En las líneas 17 y 18, leemos un archivo de texto y lo guardamos String. Usamos estas variables como addArticle()parámetros, como se muestra a continuación:

public static void addArticle(
    Document doc, String title, String author, Image img, String text)
    throws IOException {
    Paragraph p1 = new Paragraph(title)
            .setFont(timesNewRomanBold)
            .setFontSize(14);
    doc.add(p1);
    doc.add(img);
    Paragraph p2 = new Paragraph()
            .setFont(timesNewRoman)
            .setFontSize(7)
            .setFontColor(Color.GRAY)
            .add(author);
    doc.add(p2);
    Paragraph p3 = new Paragraph()
            .setFont(timesNewRoman)
            .setFontSize(10)
            .add(text);
    doc.add(p3);
}

    timesNewRomanY los timesNewRomanBoldobjetos son NewYorkTimesvariables miembro estáticas de la clase, y el tipo lo es PdfFont. En general, este ejemplo es más simple que el del capítulo anterior. A continuación, veamos un ejemplo un poco más complicado:

Usar renderizador de bloques

    En el primer capítulo, cuando mostramos el contenido del archivo csv de la información de cada continente en los Estados Unidos en el PDF, creamos una serie de Cellobjetos y luego los agregamos a los Tableobjetos. No definimos Cellel color de fondo y el tamaño del borde de los objetos. Estamos usando el valor predeterminado.

Configuración predeterminada: un objeto Cell no tiene color de fondo y el tamaño del borde es de 0,5 unidades de usuario

    Ahora usamos otra fuente de datos y la colocamos en Tableella, como se muestra en la Figura 2 a continuación:

itext3-2

Figura 2. Una tabla con esquinas redondeadas y celdas de colores

    Ahora explica cómo escribir el código: el código al principio es similar al código anterior, lo único que vale la pena destacar es el siguiente código (toda la clase es una PremierLeagueclase):

PageSize ps = new PageSize(842, 680);

    Antes, usábamos papel estándar, como el PageSize.A4tamaño. En este ejemplo, usamos un tamaño de papel definido por nosotros mismos: 842x680 unidades de usuario (1 pulgada es igual a 72 unidades de usuario, que son 11,7x9,4 pulgadas). PremierLeagueEl código principal de la clase es el siguiente:

PdfFont font = PdfFontFactory.createFont(FontConstants.HELVETICA);
PdfFont bold = PdfFontFactory.createFont(FontConstants.HELVETICA_BOLD);
Table table = new Table(new float[]{1.5f, 7, 2, 2, 2, 2, 3, 4, 4, 2});
table.setWidthPercent(100)
        .setTextAlignment(Property.TextAlignment.CENTER)
        .setHorizontalAlignment(Property.HorizontalAlignment.CENTER);
BufferedReader br = new BufferedReader(new FileReader(DATA));
String line = br.readLine();
process(table, line, bold, true);
while ((line = br.readLine()) != null) {
    process(table, line, font, false);
}
br.close();
document.add(table);

    Hay solo algunas diferencias con el ejemplo anterior en el Capítulo 1 que muestra los estados de los Estados Unidos. En este ejemplo, configuramos el uso setTextAlignmenty el setHorizontalAlignmentmétodo para centrar el contenido en la tabla y centrar la tabla en sí (esto no tiene nada que ver con que la tabla ocupe el 100% del ancho disponible) . A continuación, echemos un vistazo a process()esta función más interesante:

public void process(Table table, String line, PdfFont font, boolean isHeader) {
    StringTokenizer tokenizer = new StringTokenizer(line, ";");
    int columnNumber = 0;
    while (tokenizer.hasMoreTokens()) {
        if (isHeader) {
            Cell cell = new Cell().add(new Paragraph(tokenizer.nextToken()));
            cell.setNextRenderer(new RoundedCornersCellRenderer(cell));
            cell.setPadding(5).setBorder(null);
            table.addHeaderCell(cell);
        } else {
            columnNumber++;
            Cell cell = new Cell().add(new Paragraph(tokenizer.nextToken()));
            cell.setFont(font).setBorder(new SolidBorder(Color.BLACK, 0.5f));
            switch (columnNumber) {
                case 4:
                    cell.setBackgroundColor(greenColor);
                    break;
                case 5:
                    cell.setBackgroundColor(yellowColor);
                    break;
                case 6:
                    cell.setBackgroundColor(redColor);
                    break;
                default:
                    cell.setBackgroundColor(blueColor);
                    break;
            }
            table.addCell(cell);
        }
    }
}

    Primero veamos oraciones ordinarias. En las filas 16, 19, 22 y 25, cambiamos el color de fondo de acuerdo con el número de columna. En la línea 13, establecemos Cellla fuente y setBorder()anulamos el borde predeterminado a través de la función Definimos el borde como un borde negro sólido con un ancho de 0.5 unidades de usuario.

SolidBorderHeredado de la Borderclase, que tiene muchos hermanos similares, tales como DashedBorder, DottedBordery DoubleBorderetc. Si iText no proporciona el borde de su elección, puede extender la Borderclase, puede usar implementaciones existentes como inspiración o puede crear la suya propia CellRenderer.

    Usamos personalizado en las líneas 7 y 8 RoundedCornersCellRenderer(), especificamos el tamaño de relleno y configuramos el borde en nul. De lo setBorder(null)contrario, se dibujarán dos bordes: uno lo dibuja iText y el otro es el borde dibujado por el renderizador de contenido que escribiremos. Echemos un vistazo al renderizador de contenido que definimos:

private class RoundedCornersCellRenderer extends CellRenderer {
    public RoundedCornersCellRenderer(Cell modelElement) {
        super(modelElement);
    }
 
    @Override
    public void drawBorder(DrawContext drawContext) {
        Rectangle rectangle = getOccupiedAreaBBox();
        float llx = rectangle.getX() + 1;
        float lly = rectangle.getY() + 1;
        float urx = rectangle.getX() + getOccupiedAreaBBox().getWidth() - 1;
        float ury = rectangle.getY() + getOccupiedAreaBBox().getHeight() - 1;
        PdfCanvas canvas = drawContext.getCanvas();
        float r = 4;
        float b = 0.4477f;
        canvas.moveTo(llx, lly).lineTo(urx, lly).lineTo(urx, ury - r)
                .curveTo(urx, ury - r * b, urx - r * b, ury, urx - r, ury)
                .lineTo(llx + r, ury)
                .curveTo(llx + r * b, ury, llx, ury - r * b, llx, ury - r)
                .lineTo(llx, lly).stroke();
        super.drawBorder(drawContext);
    }
}

    CellRendererUna clase es BlockRendereruna implementación especial de una clase.

BlockRendererLas clases se pueden utilizar BlockElementsen, como Paragraphy List. Estas clases de renderizado te permiten draw()crear funciones personalizadas reemplazando métodos. Por ejemplo: puede Paragraphcrear un fondo personalizado para usted. CellRendererTambién hay un drawBorder()método.

    Anulamos el drawBorder()método para dibujar un rectángulo con esquinas redondeadas en la parte superior (líneas 6-21). getOccupiedAreaBBox()El método devuelve un Rectangleobjeto, que podemos usar para encontrar BlockElementel cuadro delimitador (línea 8). Nosotros usamos getX(), getY(), getWidth()y getHeight()métodos para definir la esquina izquierda inferior de la celda y la esquina superior derecha coordenadas (líneas 9-12).
    drawContextEste parámetro nos permite acceder a la PdfCanvasinstancia (línea 13). Dibujamos el borde en forma de una serie de líneas y curvas (líneas 14-20). Este ejemplo demuestra cómo usar api de alto nivel (que Cellconsta de Table) y api de bajo nivel (creamos casi manualmente la sintaxis de PDF para dibujar bordes que satisfagan nuestras necesidades) estrechamente combinados.

El código para dibujar la curva requiere algunos conocimientos de matemáticas, pero no es tan difícil como la ciencia espacial. Los tipos de límites más comunes están en iText, por lo que no necesita preocuparse por cómo se calculan las fórmulas matemáticas en el motor de iText.

BlockRendererAún hay mucho conocimiento     sobre su clase de implementación, lo explicaremos en detalle en otro tutorial. Finalmente, terminaremos este capítulo con un ejemplo, demostrando cómo agregar automáticamente un fondo, encabezado (o pie de página), marca de agua y número de página a cada página creada.

Manejo de eventos (Manejo de eventos, agregar fondo, pie de página y marca de agua)

    Cuando agregamos al documento con muchas filas Table, es probable que esta tabla se distribuya en diferentes páginas. En la Figura 3 a continuación, vemos una ufo.csvlista de directorios UFO almacenados en . El fondo de cada página impar es verde y amarillo, y el fondo de cada página par es azul. Cada página tiene un encabezado “THE TRUTH IS OUT THERE”, hay una marca de agua en el contenido real de la página “CONFIDENTIAL”, en la parte inferior de cada página como centro, hay un número de página .

itext3-3

Figura 3. Color de fondo y marca de agua repetidos

    El siguiente es el código para generar la tabla del directorio OVNI, que es muy similar al código de visualización de la tabla en el Capítulo 1:

PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());
Document document = new Document(pdf);
Paragraph p = new Paragraph("List of reported UFO sightings in 20th century")
        .setTextAlignment(Property.TextAlignment.CENTER)
        .setFont(helveticaBold).setFontSize(14);
document.add(p);
Table table = new Table(new float[]{3, 5, 7, 4});
table.setWidthPercent(100);
BufferedReader br = new BufferedReader(new FileReader(DATA));
String line = br.readLine();
process(table, line, helveticaBold, true);
while ((line = br.readLine()) != null) {
    process(table, line, helvetica, false);
}
br.close();
document.add(table);
document.close();

    En el código, Property.TextAlignment.CENTERagregamos el centrado estableciendo la propiedad de alineación del texto en Paragraph, y luego hacemos un bucle en ufo.csv para mostrar el contenido, de la siguiente manera:

public void process(Table table, String line, PdfFont font, boolean isHeader) {
        StringTokenizer tokenizer = new StringTokenizer(line, ";");
        while (tokenizer.hasMoreTokens()) {
            if (isHeader) {
                table.addHeaderCell(new Cell().add(new Paragraph(tokenizer.nextToken()).setFont(font)).setFontSize(9).setBorder(new SolidBorder(Color.BLACK, 0.5f)));
            } else {
                table.addCell(new Cell().add(new Paragraph(tokenizer.nextToken()).setFont(font)).setFontSize(9).setBorder(new SolidBorder(Color.BLACK, 0.5f)));
            }
        }
    }

    pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());, ¿No lo habías visto antes? Estamos aquí para PdfDocumentagregar un controlador de eventos MyEventHandler, esta MyEventHandlerrealización (aplicar) la IEventHandlerinterfaz que es de una sola manera: handleEvent(). Este método PdfDocumentEvent.END_PAGEse activará siempre que ocurra este tipo de evento. Este tipo de evento se refiere a: siempre que iText haya terminado de agregar contenido a la página, ya sea porque se crea una nueva página, o porque la última página ha llegado y se completó.
    Luego echamos un vistazo a IEventHandlerla implementación:

protected class MyEventHandler implements IEventHandler {
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdfDoc = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdfDoc.getPageNumber(page);
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(
            page.newContentStreamBefore(), page.getResources(), pdfDoc);
 
        //Set background
        Color limeColor = new DeviceCmyk(0.208f, 0, 0.584f, 0);
        Color blueColor = new DeviceCmyk(0.445f, 0.0546f, 0, 0.0667f);
        pdfCanvas.saveState()
                .setFillColor(pageNumber % 2 == 1 ? limeColor : blueColor)
                .rectangle(pageSize.getLeft(), pageSize.getBottom(),
                    pageSize.getWidth(), pageSize.getHeight())
                .fill().restoreState();
        //Add header and footer
        pdfCanvas.beginText()
                .setFontAndSize(helvetica, 9)
                .moveText(pageSize.getWidth() / 2 - 60, pageSize.getTop() - 20)
                .showText("THE TRUTH IS OUT THERE")
                .moveText(60, -pageSize.getTop() + 30)
                .showText(String.valueOf(pageNumber))
                .endText();
        //Add watermark
        Canvas canvas = new Canvas(pdfCanvas, pdfDoc, page.getPageSize());
        canvas.setProperty(Property.FONT_COLOR, Color.WHITE);
        canvas.setProperty(Property.FONT_SIZE, 60);
        canvas.setProperty(Property.FONT, helveticaBold);
        canvas.showTextAligned(new Paragraph("CONFIDENTIAL"),
            298, 421, pdfDoc.getPageNumber(page),
            TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
 
        pdfCanvas.release();
    }
}

    En la línea 3, primero eventconvertimos este parámetro de función en PdfDocumentEvent, y luego lo llamamos getDocument()para obtenerlo PdfDocument, obtenemos el número de página de la página actual a través de estas variables, y hay PdfCanvasuna instancia del tamaño de página .

Diferentes rutas o formas pueden superponerse. La ruta o forma dibujada primero (que se guardará en el flujo de contenido) se dibujará primero en el lienzo y los gráficos dibujados más tarde cubrirán el contenido anterior (si hay partes superpuestas). Cada vez que el contenido de la página se representa por completo, tenemos que agregar un fondo, PdfPageuna matriz de cada flujo de contenido de seguimiento. Puede utilizar el índice como parámetro getContentStream()para obtener cada flujo de contenido individual. Puede usar getFirstContentStream()y getLastContentStream()obtener el primer y último flujo de contenido. También puede utilizar el método newContentStreamBefore()y newContentStreamAfter()para crear nuevos flujos de contenido.

    En la línea 8, construimos uno con los siguientes tres parámetros PdfCanvas:

  • page.newContentStreamBefore(): Si dibujamos un rectángulo opaco después de renderizar la página, el rectángulo cubrirá todo el contenido existente. Necesitamos acceder al flujo de contenido que se agregará antes del contenido de la página para que nuestro fondo y nuestra marca de agua no cubran el contenido de nuestra tabla.
  • page.getResources(): Cada flujo de contenido requiere recursos externos, como fuentes e imágenes. Cuando queremos agregar contenido nuevo a una página, es importante que iText pueda acceder al directorio de recursos de la página.
  • pdfDoc: Necesitamos poder obtener el PdfDocumentobjeto para que nuestro flujo de contenido recién agregado se pueda agregar PdfDocument.

    Luego, lo que canvasagregamos al objeto:

  • Líneas 11-18: definición limeColory blueColordos colores. Primero guarde el estado actual de la imagen (vea la explicación detallada en el Capítulo 2 para más detalles), y luego configure el color del bolígrafo de relleno según el número de páginas pares e impares para construir un rectángulo de todo el tamaño de la página, con las páginas impares en verde y amarillo, y las páginas pares en azul. Finalmente, restaure el estado de la imagen anterior sin afectar el color del contenido posteriormente.
  • Línea 20-26: comience a escribir texto, establezca un estilo de fuente y un tamaño de fuente, luego muévase a la mitad superior de la página, comience a escribir "THE TRUTH IS OUT THERE", luego mueva el cursor hacia la parte inferior, escriba el número de página y el encabezado y el pie de página están bien.
  • Línea 28-31: Aquí creamos Canvasuna instancia del tipo canvas. CanvasEs una PdfCanvasrepresentación de alto nivel, al igual que Documentun PdfDocumentalto nivel indica lo mismo. Aquí no usamos la sintaxis pdf (la sintaxis en el Capítulo 2) para cambiar la fuente, tamaño de fuente, color de fuente y otros atributos. Usamos el setProperty()método. De manera similar Document, puede usar el setProperty()método aquí, como cambiar la fuente predeterminada. Es un objeto puede ser utilizado para el mismo propósito, tal como Paragraph, List, Table.
  • Líneas 32-34: use el showTextAligned()método para agregar una Paragraphpantalla centrada, la coordenada es (298,421), inclinada a 45 grados.

    Una vez que agregamos el fondo, el encabezado, el pie de página y la marca de agua, liberamos el PdfCanvasobjeto.
    En este ejemplo, usamos dos métodos diferentes para agregar texto en posiciones absolutas. En cuanto al dibujo del encabezado y pie de página, usamos la API de bajo nivel que conocimos en el capítulo anterior, incluido el estado del texto, podemos usar un método similar para agregar marcas de agua. Sin embargo, queremos rotar el texto y colocarlo en el medio de la página, lo que requiere muchos cálculos matemáticos. Para evitar calcular la matriz de conversión que coloca el texto en las coordenadas requeridas, usamos un método conveniente: usar showTextAligned()iText ahorra muchas operaciones aquí.

para resumir

    Combinando los ejemplos de este capítulo, podemos comprender cuán importantes son las API de bajo nivel discutidas en el capítulo anterior. Podemos combinar esta función con componentes básicos para crear funciones personalizadas. Esto crea un borde personalizado del objeto de celda; agrega un color de fondo, encabezado y pie de página a la página; finalmente, cuando agregamos una marca de agua, encontramos que no necesitamos conocer todas las entradas y salidas de la sintaxis PDF, que se puede usar aquí Una forma conveniente de lidiar con la definición de la matriz de transformación para rotar y centrar el texto.
    En el próximo capítulo, aprenderemos sobre una forma diferente de contenido: las anotaciones. Nos centraremos en algunos tipos específicos de anotaciones que pueden crear formas interactivas. Espero que sigas prestando atención.

PD: Hay 7 capítulos en la serie iText7. El contenido de los primeros capítulos es relativamente básico y aburrido. ¡Los aspectos más destacados de los próximos capítulos serán más prácticos! Después de escribir estos 7 capítulos, ¡continuaremos actualizando las muestras en el sitio web oficial!

PS2: Es difícil traducir estos artículos y tenemos que practicar para ver si son correctos, especialmente para la fiesta estudiantil. Si crees que es bueno, ¡ felicítalo ! ¡Tu apoyo para mí es mi motivación para seguir actualizándome! ¡Espero que puedas entender los otros errores tipográficos!

Supongo que te gusta

Origin blog.csdn.net/u012397189/article/details/77540464
Recomendado
Clasificación