Wasserzeichen und Kopfzeilen mit dem ContentsCreatorPageSegment

Gültig ab jadice documentplatform 5.5.0.4 / jadice web toolkit 5.7.0.0
Dieser Artikel beschreibt, wie Seitenmodifikationen mit dem ContentsCreatorPageSegment durchgeführt werden können



Dieser Artikel gibt eine Einführung zur Erzeugung eigener PageSegments, die beim Druck oder bei der Anzeige verwendet werden können, um zusätzliche Informationen auf ein Dokument aufzubringen. Hierzu wird gezeigt, wie mit der API des ContentsCreatorPageSegmentBuilder Inhalte eines PageSegment definiert werden und anschließend damit ein PageSegment erzeugt wird.


Anforderungen

Es soll ein PageSegment erzeugt werden, das Informationen enthält, mit denen ein Dokument bei Anzeige oder Druck angereichert wird. Mögliche Anwendungsfälle für ein solches PageSegment sind beispielsweise

  • Das Aufbringen einer Kopfzeile auf dem ursprünglichen Dokument
  • Das Aufbringen eines Wasserzeichen
  • Verkleinern des eigentlichen Seiteninhalts, um Zusatzinformationen aufzubringen


Lösungsansätze


Überblick

Um diese Anforderungen zu erfüllen, stellt jadice das ContentsCreatorPageSegment bereit. Zur Erzeugung eines ContentsCreatorPageSegment wird zunächst mit der API des ContentsCreatorPageSegmentBuilder der Inhalt des PageSegments definiert. Beim Aufruf der finish()-Methode des Builders wird dann ein PageSegment mit den definierten Inhalten erzeugt. Das so erzeugte ContentsCreatorPageSegment implementiert das jadice PageSegment-Interface und kann entsprechend verwendet werden. Durch die Definition des Dokumentenlayers, in dem das PageSegment eingebunden wird, werden Sichtbarkeit und Überdeckung des PageSegment festgelegt. Detaillierte Informationen zum jadice-Dokumentenmodell finden sich in der jadice-Dokumentation.



Beispielimplementierungen

Um den Beispielcode besser lesbar zu machen, findet das Laden der Seite und des verwendeten Fonts in separaten Klassen statt. Diese können rechts heruntergeladen werden.


Hinzufügen einer Kopfzeile auf einer Seite

In diesem Beispiel wird eine Kopfzeile auf einer Seite aufgebracht. Dazu wird zunächst mit dem ContentsCreatorPageSegmentBuilder ein ContentsCreatorPageSegment erzeugt, das den Text der Kopfzeile enthält. Anschließend wird dieses ContentsCreatorPageSegment auf die Seite aufgebracht. Eventuell vorhandene Inhalte des Ur-Dokuments werden dabei durch die Kopfzeile überdeckt.

Durch Ausführen der main()-Methode wird die angeforderte Seite aus der gegebenen Datei gelesen und das in createSimpleTextPageSegment() erzeugte PageSegment auf diese Seite aufgebracht. Anschließend wird die Seite mit dem neuen PageSegment in ein BufferedImage gerendert und in einem JFrame dargestellt.

HeadlineExample.java
package org.jadice.examples;

import java.awt.Color;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.levigo.jadice.document.DocumentLayer;
import com.levigo.jadice.document.Page;
import com.levigo.jadice.document.PageSegment;
import com.levigo.jadice.document.Unit;
import com.levigo.jadice.document.contentscreator.ContentsCreatorControls;
import com.levigo.jadice.document.contentscreator.ContentsCreatorPageSegmentBuilder;
import com.levigo.jadice.document.font.Font;

public class HeadlineExample {

  public static void main(String[] args) {
	
	//The file to read the document from
    File fileToReadFrom;

    // The SimplePageProvider encapsulates the reading of a document. SimplePageProvider#get() then 
	// provides the first page of the document read.
    Page basePage = new SimplePageProvider(fileToReadFrom).get();

    // position the top left corner of the text 1cm away from the left edge and the top edge.
    int offsetX = (int) Unit.Centimeter.convertTo(Unit.JadiceDocumentUnit, 1);
    int offsetY = (int) Unit.Centimeter.convertTo(Unit.JadiceDocumentUnit, 1);

    // Creates a String containing the current timestamp e.g. 2018-07-27 13:37:42
    String dateString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

    // create the page segment containing the headline.
    PageSegment headlinePageSegment = createSimpleTextPageSegment("Printed on " + dateString, offsetX, offsetY);

    // Get the DocumentLayer to add the headline to.
    DocumentLayer headlineLayer = DocumentLayer.get("HeadlineLayer", 100);

    // add the page segment to the layer defined
    basePage.add(headlineLayer, headlinePageSegment);

    SimplePageDisplay.display(basePage);
  }

  /**
   * Creates a new {@link PageSegment} containing the given text at the given coordinates using the
   * {@link ContentsCreatorPageSegmentBuilder}.
   *
   * @param headlineText the text to be contained in the headline
   * @param offsetX the x offset in jadice document units of the top left corner of the text's bounds
   * @param offsetY the y offset in jadice document units of the top left corner of the text's bounds
   * @return a {@link PageSegment} containing the given text at the given coordinates.
   */
  public static PageSegment createSimpleTextPageSegment(String headlineText, int offsetX, int offsetY) {

    // ContentsCreatorControls define how the contents to be added to the
    // ContentsCreatorPageSegmentBuilder are rendered
    ContentsCreatorControls headlineControls = new ContentsCreatorControls();

    // The ColorSettings define the colors to be used to render the element they are applied to. 
	// Text color is defined by the non stroking color only.
    Color textColor = Color.BLACK;
    headlineControls.getColorSettings().withNonStrokingColor(textColor);

    // The TextSettings define the font and the font size used to render the text. 
    int fontSize = 15;
    Font font = FontProvider.get();
    headlineControls.getTextSettings().withFontSizeInPoint(fontSize).withFont(font);


    // Create a new ContentsCreatorPageSegmentBuilder, add the text and retrieve the PageSegment.
    ContentsCreatorPageSegmentBuilder pageSegmentBuilder = new ContentsCreatorPageSegmentBuilder();
    pageSegmentBuilder.addText(headlineText, headlineControls, offsetX, offsetY);

    // Get the page segment by finishing the builder.
    PageSegment contentsCreatorPageSegment = pageSegmentBuilder.finish();

    return contentsCreatorPageSegment;
  }
}

Die Ur-Seite:
Darstellung einer gerenderten Seite ohne Modifikationen
Seite mit Kopfzeile:
Darstellung einer gerenderten Seite mit einer Kopfzeile in schwarzer Schrift die die Dokumenteninhalte überdeckt.



Wasserzeichen auf einer Seite

In diesem Beispiel wird ein Wasserzeichen auf einer Seite aufgebracht. Dazu wird wieder mit dem ContentsCreatorPageSegmentBuilder ein ContentsCreatorPageSegment erzeugt, das das Wasserzeichen enthält. Anschließend wird dieses PageSegment auf die Seite aufgebracht.

Das Wasserzeichen soll folgende Eigenschaften haben:

  • Der Text des Wasserzeichens lautet "COPY"
  • Die Farbe des Texts ist halbtransparent 
  • Der Text ist um 45 Grad gedreht 
  • Der Text ist so eingepasst, dass er maximal 80% der Breite oder der Höhe der Seite einnimmt.

Für PrintDecorations gibt es zwei vordefinierte Layer:

  • Elemente im Layer PrintDecorations.PREDECORATION werden in den Hintergrund gelegt. Die Inhalte der Seite werden nicht verdeckt.
  • Elemente im Layer PrintDecorations.POSTDECORATION werden in den Vordergrund gelegt. Die Inhalte der Seite werden eventuell verdeckt


Hierzu wird ähnlich wie im Beispiel  "Hinzufügen einer Kopfzeile" ein PageSegment erzeugt, das einen Text enthält. Um diesen einzupassen und zu drehen, werden verschiedene Transformationen angewendet.
Zur Berechnung der Transformationen werden ContentsCreatorControls definiert, die den Text gedreht in einer bestimmten Textgröße rendern würden. Die so ermittelte Dimension dient dann als Grundlage zur Berechnung der Skalierung auf 80% der Höhe oder der Breite. 


Durch Ausführen der main()-Methode wird die angeforderte Seite aus der gegebenen Datei gelesen und das Wasserzeichen als PageSegment auf diese Seite aufgebracht. Anschließend wird die Seite mit dem neuen PageSegment in ein BufferedImage gerendert und in einem JFrame dargestellt.

WatermarkExample.java
package org.jadice.examples;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.File;

import com.levigo.jadice.demo.kbartikel.FontProvider;
import com.levigo.jadice.document.Page;
import com.levigo.jadice.document.PageSegment;
import com.levigo.jadice.document.contentscreator.ContentsCreatorControls;
import com.levigo.jadice.document.contentscreator.ContentsCreatorPageSegmentBuilder;
import com.levigo.jadice.document.contentscreator.ContentsCreatorUtils;
import com.levigo.jadice.document.font.Font;
import com.levigo.jadice.printer.PrintDecorations;

public class WatermarkExample {

  public static void main(String[] args) {

    File fileToReadFrom = null;

    // The SimplePageProvider encapsulates the reading of a document. SimplePageProvider#get() 
	// then provides the first page of the document read.
    Page basePage = SimplePageProvider.get(fileToReadFrom, 1);

    // Create the page segment containing the watermark.
    PageSegment watermarkPageSegment = createWatermarkPageSegment("COPY", basePage.getSize());

    // Add the page segment to the layer defined
    basePage.add(PrintDecorations.PREDECORATION, watermarkPageSegment);

    SimplePageDisplay.display(basePage);
  }

  /**
   * Creates a PageSegment containing the text "COPY" written in grey which is rotated by 45° and
   * then scaled to fit 80% of the height or the width of the page (whichever requires the smaller
   * scaling factor).
   * 
   * @param pageDimension the dimension to fit the watermark into
   * @param watermarkText the text to be used as watermark
   * @return a {@link PageSegment} containing the watermark.
   */
  public static PageSegment createWatermarkPageSegment(String watermarkText, Dimension2D pageDimension) {

    // the base font size is only used to calculate the scale factor.
    int baseFontSize = 100;

    Font font = FontProvider.get();

    ContentsCreatorControls textControls = new ContentsCreatorControls();

    // Text color shall be black, with alpha of 0.5 (the parameter then is (int)255*0.5 )
    textControls.getColorSettings().withNonStrokingColor(new Color(0, 0, 0, 127));
    textControls.getTextSettings().withFontSizeInPoint(baseFontSize).withFont(font);

    // rotate by 45 degrees
    AffineTransform rotation = AffineTransform.getRotateInstance(-Math.PI / 4);
    textControls.getTransformationSettings().concatenate(rotation);

    ContentsCreatorPageSegmentBuilder builder = new ContentsCreatorPageSegmentBuilder();

    // The dimensions of a bounding box which fits the given text.
    Dimension2D textBaseDimensions = ContentsCreatorUtils.calculateTextBounds(watermarkText, textControls);

    // The scale factor that has to be applied to the text to fit into the page in a way that its
    // bounding box fits maximum 80% of the height or the width.
    double scale = calculateMaximumScaling(textBaseDimensions, pageDimension, .8);
   
    // Scale the text to fit 80%.
    AffineTransform scaleTransformation = AffineTransform.getScaleInstance(scale, scale);
    textControls.getTransformationSettings().concatenate(scaleTransformation);

    // Define the offset of the upper left corner of the text's bounding box to center the watermark
    int offsetY = (int) (pageDimension.getHeight() - textBaseDimensions.getHeight() * scale) / 2;
    int offsetX = (int) (pageDimension.getWidth() - textBaseDimensions.getWidth() * scale) / 2;

    // Add the rotated, scaled, centered text to the builder and return the PageSegment
    builder.sized(pageDimension).addText(watermarkText, textControls, offsetX, offsetY);
    return builder.finish();
  }

  /**
   * Static helper method that calculates the scaling factor that has to be applied to fit something
   * with a given baseDimension into the targetDimension so that the scaled object covers not more than the given proportion of the targetDimension.
   * 
   * @param baseDimension the dimension of the object to fit into the targetDimension
   * @param targetDimension the dimension fit the object in
   * @param proportion the maximum proportion of the width/height of the target to be occupied by the scaled object.
   * @return the scale factor to be applied to the object to fit into a proportion of the targetDimension.
   */
  public static double calculateMaximumScaling(Dimension2D baseDimension, Dimension2D targetDimension,
      double proportion) {
    double baseDimensionRatio = baseDimension.getHeight() / baseDimension.getHeight();
    // calculate and return the ratio
    return baseDimensionRatio > 1.0D
        ? ((targetDimension.getHeight() * proportion / (baseDimension.getHeight())))
        : ((targetDimension.getWidth() * proportion / (baseDimension.getWidth())));
  }
}
Wasserzeichen im Layer PREDECORATION
Wasserzeichen im Layer POSTDECORATION

Verkleinern der ursprünglichen Seite um zusätzliche Informationen aufzubringen

In diesem Beispiel wird auf einer Seite der Inhalt einer eingelesenen Seite mit neuen Inhalten aus einem ContentsCreatorPageSegment kombiniert um eine Kopfzeile zu erzeugen die keine Seiteninhalte verdeckt. Dazu wird ein CompositePageSegment verwendet, in dem die verschiedenen PageSegments miteinander kombiniert werden können. 

Zunächst werden die PageSegments einer eingelesenen Seite skaliert und in ein CompositePageSegment für jede DocumentLayer-Instanz eingefügt. Anschließend wird ein Rahmen darum gezeichnet (Methode addScaledPageSegmentsToPage()).

Danach wird ein ContentsCreatorPageSegment definiert, das den verbleibenden Platz oberhalb der skalierten Seite einnimmt. In diese Kopfzeile werden verschiedene Informationen eingefügt (Methode createHeadlinePageSegment()).

Die skalierten CompositePageSegmente werden auf der neu erstellten Zielseite eingefügt.


Durch Ausführen der main()-Methode wird die angeforderte Seite aus der gegebenen Datei gelesen und zusammen mit einem ContentsCreatorPageSegment in ein CompositePageSegment eingefügt. Anschließend wird die so erzeugte Seite in ein BufferedImage gerendert und in einem JFrame dargestellt.

CompositeExample.java
package org.jadice.examples;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.levigo.jadice.document.BasicPage;
import com.levigo.jadice.document.DocumentLayer;
import com.levigo.jadice.document.Page;
import com.levigo.jadice.document.PageSegment;
import com.levigo.jadice.document.Unit;
import com.levigo.jadice.document.composite.CompositePageSegment;
import com.levigo.jadice.document.contentscreator.ContentsCreatorControls;
import com.levigo.jadice.document.contentscreator.ContentsCreatorPageSegmentBuilder;
import com.levigo.jadice.document.contentscreator.settings.ContentsCreatorDrawModeSettings;
import com.levigo.jadice.document.font.Font;
import com.levigo.jadice.printer.PrintDecorations;

public class CompositeExample {

  public static void main(String[] args) {
    File fileToReadFrom = new File(args[0]);

    // The SimplePageProvider encapsulates the reading of a document. get() then provides the
    // first page of the document read.
    Page basePage = SimplePageProvider.get(fileToReadFrom, 0);

    // The Page to which we will add the scaled base page and the headline.
    Page targetPage = new BasicPage();

    float scale = 0.9f;
    // Create the scaled version of the page.
    addScaledPageSegmentsToPage(basePage, targetPage, scale);

    // Calculate the dimension for the headline pagesegment
    int width = (int) basePage.getSize().getWidth();
    int height = (int) (basePage.getSize().getHeight() * (1 - scale));

    // Create the headline pagesegment
    PageSegment headlinePageSegment = createHeadlinePageSegment(new Dimension(width, height));

    // Add the scaled version to the default layer
    // targetPage.add(DocumentLayer.DEFAULT, scaledPageSegment);

    // Add the headline pagesegment to the postdecoration layer
    targetPage.add(PrintDecorations.POSTDECORATION, headlinePageSegment);

    SimplePageDisplay.display(targetPage);
  }

  /**
   *
   * Creates pagesegments containing the given {@link Page} scaled by the given factor. The created
   * {@link PageSegment} instances will have the same size as the given page with the scaled page
   * placed centered horizontally on the bottom of the page. In addition a border will be drawn
   * around the scaled page.
   *
   * @param pageToScale the page to include scaled
   * @param targetPage the page containing all scaled pagesegments
   * @param scale the factor by which the page shall be scaled
   */
  public static void addScaledPageSegmentsToPage(Page pageToScale, Page targetPage, float scale) {
    // Create the scale transformation
    AffineTransform transformation = AffineTransform.getScaleInstance(scale, scale);

    // Get the dimension of the base page since we need this at some points.
    Dimension2D basePageDimension = pageToScale.getSize();

    // Calculate and add the offset to center the scaled page and place it on the bottom of the new
    // page. The additional "/ scale" is needed to compensate for the scaling.
    double offsetX = ((1 - scale) / scale * basePageDimension.getWidth()) / 2;
    double offsetY = ((1 - scale) / scale * basePageDimension.getHeight());
    transformation.concatenate(AffineTransform.getTranslateInstance(offsetX, offsetY));

    // Add every PageSegment of the base page with the transformation calculated before.
    for (final DocumentLayer documentLayer : pageToScale.getLayers()) {
      final PageSegment pageSegment = pageToScale.getPageSegment(documentLayer);
      // The new CompositePageSegment that will contain the layer specific pageSegment from the base
      // page
      final CompositePageSegment compositePageSegment = new CompositePageSegment(basePageDimension);
      compositePageSegment.addPageSegment(pageSegment, transformation);
      targetPage.add(documentLayer, compositePageSegment);
    }

    // Create a PageSegment containing the border.
    ContentsCreatorPageSegmentBuilder builder = new ContentsCreatorPageSegmentBuilder();
    builder.sized(basePageDimension);

    ContentsCreatorControls rectangleControls = new ContentsCreatorControls();
    rectangleControls.getColorSettings().withColors(Color.BLACK, Color.BLACK);

    // Add a thin border
    float strokeWidth = Unit.Millimeter.convertTo(Unit.JadiceDocumentUnit, 0.1f);
    rectangleControls.getDrawModeSettings().withDrawMode(ContentsCreatorDrawModeSettings.DrawMode.DRAW).withStroke(
        new BasicStroke(strokeWidth));

    // The border will at first have the same dimensions as the base page. We will fix this later
    // using
    // a transformation.
    Rectangle rectangle = new Rectangle(0, 0, (int) basePageDimension.getWidth(), (int) basePageDimension.getHeight());
    builder.addShape(rectangle, rectangleControls);
    PageSegment borderPageSegment = builder.finish();

    // Add the border page segment with the same transformation as the scaled page to make sure it
    // surrounds the scaled page.
    final CompositePageSegment compositePageSegment = new CompositePageSegment(basePageDimension);
    compositePageSegment.addPageSegment(borderPageSegment, transformation);
    targetPage.add(DocumentLayer.get("AdditionalContent", 100), compositePageSegment);
  }

  public static PageSegment createHeadlinePageSegment(Dimension2D dimension) {
    // Three lines of text for the headline
    String fileInfo = "File: JWT+Info+Sheet.pdf";
    String dateString = "Print time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    String pageInfo = "Page 2 of 6";

    // The ContentsCreatorControls define how the contents to be added to the
    // ContentsCreatorPageSegmentBuilder are rendered
    ContentsCreatorControls textControls = new ContentsCreatorControls();

    // The ContentsCreatorColorSettings define the colors the element they are apply to is rendered.
    Color textColor = Color.RED;
    textControls.getColorSettings().withNonStrokingColor(textColor);

    // The ContentsCreatorTextSettings define the settings how to render text.
    int fontSize = 12;
    Font font = FontProvider.get();
    textControls.getTextSettings().withFontSizeInPoint(fontSize).withFont(font);

    // Position the top left corner of the text 1cm away from the left edge and the top edge.
    int offsetX = (int) Unit.Centimeter.convertTo(Unit.JadiceDocumentUnit, 1);
    int offsetY = (int) Unit.Centimeter.convertTo(Unit.JadiceDocumentUnit, 1);

    // Calculate the mean vertical space for each line of text
    int lineHeight = (int) ((dimension.getHeight() - offsetX) / 3);

    // Create a new ContentsCreatorPageSegmentBuilder, add the text, and retrieve the PageSegment to
    // return it.
    ContentsCreatorPageSegmentBuilder pageSegmentBuilder = new ContentsCreatorPageSegmentBuilder();
    // Set a fixed size for the PageSegment created
    pageSegmentBuilder.sized(dimension);

    // Add the three lines of texts.
    pageSegmentBuilder.addText(fileInfo, textControls, offsetX, offsetY);
    pageSegmentBuilder.addText(dateString, textControls, offsetX, offsetY + lineHeight);
    pageSegmentBuilder.addText(pageInfo, textControls, offsetX, offsetY + 2 * lineHeight);

    // Get the page segment by finishing the builder.
    PageSegment contentsCreatorPageSegment = pageSegmentBuilder.finish();

    return contentsCreatorPageSegment;
  }
}

Skalierte Seite mit Kopfzeilen