Skip to content

Commit

Permalink
Introduce new constructor Canvas(PdfPage, Rectangle)
Browse files Browse the repository at this point in the history
Using this constructor allows to add link annotations and destinations via Canvas.

DEVSIX-2486
  • Loading branch information
yulian-gaponenko committed Jan 3, 2019
1 parent 70def83 commit 4da833d
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 7 deletions.
2 changes: 2 additions & 0 deletions io/src/main/java/com/itextpdf/io/LogMessageConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public final class LogMessageConstant {
public static final String MAPPING_IN_STRUCT_ROOT_OVERWRITTEN = "Existing mapping for {0} in structure tree root role map was {1} and it was overwritten with {2}.";
public static final String METHOD_IS_NOT_IMPLEMENTED_BY_DEFAULT_OTHER_METHOD_WILL_BE_USED = "Method {0} is not implemented by default: please, override and implement it. {1} will be used instead.";
public static final String NAME_ALREADY_EXISTS_IN_THE_NAME_TREE = "Name \"{0}\" already exists in the name tree; old value will be replaced by the new one.";
public static final String UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN = "Unable to apply page dependent property, because the page on which element is drawn is unknown. Usually this means that element was added to the Canvas instance that was created not with constructor taking PdfPage as argument. Not processed property: {0}";
public static final String NOT_TAGGED_PAGES_IN_TAGGED_DOCUMENT = "Not tagged pages are copied to the tagged document. Destination document now may contain not tagged content.";
public static final String NO_FIELDS_IN_ACROFORM = "Required AcroForm entry /Fields does not exist in the document. Empty array /Fields will be created.";
public static final String NUM_TREE_SHALL_NOT_END_WITH_KEY = "Number tree ends with a key which is invalid according to the PDF specification.";
Expand All @@ -131,6 +132,7 @@ public final class LogMessageConstant {
public static final String ONLY_ONE_OF_ARTBOX_OR_TRIMBOX_CAN_EXIST_IN_THE_PAGE = "Only one of artbox or trimbox can exist on the page. The trimbox will be deleted";
public static final String OPENTYPE_GDEF_TABLE_ERROR = "OpenType GDEF table error: {0}";
public static final String PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE = "Page tree is broken. Failed to retrieve page number {0}. Null will be returned.";
public static final String PASSED_PAGE_SHALL_BE_ON_WHICH_CANVAS_WILL_BE_RENDERED = "The page passed to Canvas#enableAutoTagging(PdfPage) method shall be the one on which this canvas will be rendered. However the actual passed PdfPage instance sets not such page. This might lead to creation of malformed PDF document.";
public static final String PATH_KEY_IS_PRESENT_VERTICES_WILL_BE_IGNORED = "Path key is present. Vertices will be ignored";
public static final String PDF_OBJECT_FLUSHING_NOT_PERFORMED = "PdfObject flushing is not performed: PdfDocument is opened in append mode and the object is not marked as modified ( see PdfObject#setModified() ).";
public static final String PDF_READER_CLOSING_FAILED = "PdfReader closing failed due to the error occurred!";
Expand Down
53 changes: 50 additions & 3 deletions layout/src/main/java/com/itextpdf/layout/Canvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.layout;

import com.itextpdf.io.LogMessageConstant;
import com.itextpdf.kernel.PdfException;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
Expand All @@ -52,6 +54,8 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.renderer.CanvasRenderer;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.RootRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This class is used for adding content directly onto a specified {@link PdfCanvas}.
Expand All @@ -71,8 +75,29 @@ public class Canvas extends RootElement<Canvas> {
*/
protected PdfPage page;

private boolean isCanvasOfPage;

/**
* Creates a new Canvas to manipulate a specific document and page.
* Creates a new Canvas to manipulate a specific page content stream. The given page shall not be flushed:
* drawing on flushed pages is impossible because their content is already written to the output stream.
* Use this constructor to be able to add {@link com.itextpdf.layout.element.Link} elements on it
* (using any other constructor would result in inability to add PDF annotations, based on which, for example, links work).
* <p>
* If the {@link PdfDocument#isTagged()} is true, using this constructor would automatically enable
* the tagging for the content. Regarding tagging the effect is the same as using {@link #enableAutoTagging(PdfPage)}.
*
* @param page the page on which this canvas will be rendered, shall not be flushed (see {@link PdfPage#isFlushed()}).
* @param rootArea the maximum area that the Canvas may write upon
*/
public Canvas(PdfPage page, Rectangle rootArea) {
this(initPdfCanvasOrThrowIfPageIsFlushed(page), page.getDocument(), rootArea);
this.enableAutoTagging(page);
this.isCanvasOfPage = true;
}

/**
* Creates a new Canvas to manipulate a specific document and content stream, which might be for example a page
* or {@link PdfFormXObject} stream.
*
* @param pdfCanvas the low-level content stream writer
* @param pdfDocument the document that the resulting content stream will be written to
Expand Down Expand Up @@ -142,8 +167,8 @@ public void setRenderer(CanvasRenderer canvasRenderer) {
}

/**
* Returned value is not null only in case when autotagging is enabled.
* @return the page, on which this canvas will be rendered, or null if autotagging is not enabled.
* The page on which this canvas will be rendered.
* @return the specified {@link PdfPage} instance, might be null if this the page was not set.
*/
public PdfPage getPage() {
return page;
Expand All @@ -154,6 +179,10 @@ public PdfPage getPage() {
* @param page the page, on which this canvas will be rendered.
*/
public void enableAutoTagging(PdfPage page) {
if (isCanvasOfPage() && this.page != page) {
Logger logger = LoggerFactory.getLogger(Canvas.class);
logger.error(LogMessageConstant.PASSED_PAGE_SHALL_BE_ON_WHICH_CANVAS_WILL_BE_RENDERED);
}
this.page = page;
}

Expand All @@ -164,6 +193,17 @@ public boolean isAutoTaggingEnabled() {
return page != null;
}

/**
* Defines if the canvas is exactly the direct content of the page. This is known definitely only if
* this instance was created by {@link Canvas#Canvas(PdfPage, Rectangle)} constructor overload,
* otherwise this method returns false.
* @return true if the canvas on which this instance performs drawing is directly the canvas of the page;
* false if the instance of this class was created not with {@link Canvas#Canvas(PdfPage, Rectangle)} constructor overload.
*/
public boolean isCanvasOfPage() {
return isCanvasOfPage;
}

/**
* Performs an entire recalculation of the element flow on the canvas,
* taking into account all its current child elements. May become very
Expand Down Expand Up @@ -214,4 +254,11 @@ protected RootRenderer ensureRootRendererNotNull() {
return rootRenderer;
}

private static PdfCanvas initPdfCanvasOrThrowIfPageIsFlushed(PdfPage page) {
if (page.isFlushed()) {
throw new PdfException(PdfException.CannotDrawElementsOnAlreadyFlushedPages);
}
return new PdfCanvas(page);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1653,8 +1653,15 @@ protected void applyRelativePositioningTranslation(boolean reverse) {
protected void applyDestination(PdfDocument document) {
String destination = this.<String>getProperty(Property.DESTINATION);
if (destination != null) {
int pageNumber = occupiedArea.getPageNumber();
if (pageNumber < 1 || pageNumber > document.getNumberOfPages()) {
Logger logger = LoggerFactory.getLogger(AbstractRenderer.class);
String logMessageArg = "Property.DESTINATION, which specifies this element location as destination, see ElementPropertyContainer.setDestination.";
logger.warn(MessageFormatUtil.format(LogMessageConstant.UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN, logMessageArg));
return;
}
PdfArray array = new PdfArray();
array.add(document.getPage(occupiedArea.getPageNumber()).getPdfObject());
array.add(document.getPage(pageNumber).getPdfObject());
array.add(PdfName.XYZ);
array.add(new PdfNumber(occupiedArea.getBBox().getX()));
array.add(new PdfNumber(occupiedArea.getBBox().getY() + occupiedArea.getBBox().getHeight()));
Expand Down Expand Up @@ -1686,14 +1693,21 @@ protected void applyAction(PdfDocument document) {
protected void applyLinkAnnotation(PdfDocument document) {
PdfLinkAnnotation linkAnnotation = this.<PdfLinkAnnotation>getProperty(Property.LINK_ANNOTATION);
if (linkAnnotation != null) {
int pageNumber = occupiedArea.getPageNumber();
if (pageNumber < 1 || pageNumber > document.getNumberOfPages()) {
Logger logger = LoggerFactory.getLogger(AbstractRenderer.class);
String logMessageArg = "Property.LINK_ANNOTATION, which specifies a link associated with this element content area, see com.itextpdf.layout.element.Link.";
logger.warn(MessageFormatUtil.format(LogMessageConstant.UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN, logMessageArg));
return;
}
Rectangle pdfBBox = calculateAbsolutePdfBBox();
if (linkAnnotation.getPage() != null) {
PdfDictionary oldAnnotation = (PdfDictionary) linkAnnotation.getPdfObject().clone();
linkAnnotation = (PdfLinkAnnotation) PdfAnnotation.makeAnnotation(oldAnnotation);
}
linkAnnotation.setRectangle(new PdfArray(pdfBBox));

PdfPage page = document.getPage(occupiedArea.getPageNumber());
PdfPage page = document.getPage(pageNumber);
page.addAnnotation(linkAnnotation);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,13 @@ public void draw(DrawContext drawContext) {

if (processOverflow) {
drawContext.getCanvas().saveState();
Rectangle clippedArea = drawContext.getDocument().getPage(occupiedArea.getPageNumber()).getPageSize();
int pageNumber = occupiedArea.getPageNumber();
Rectangle clippedArea;
if (pageNumber < 1 || pageNumber > drawContext.getDocument().getNumberOfPages()) {
clippedArea = new Rectangle(-INF / 2 , -INF / 2, INF, INF);
} else {
clippedArea = drawContext.getDocument().getPage(pageNumber).getPageSize();
}
Rectangle area = getBorderAreaBBox();
if (overflowXHidden) {
clippedArea.setX(area.getX()).setWidth(area.getWidth());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ protected void flushSingleRenderer(IRenderer resultRenderer) {
@Override
protected LayoutArea updateCurrentArea(LayoutResult overflowResult) {
if (currentArea == null) {
currentArea = new RootLayoutArea(0, canvas.getRootArea().clone());
int pageNumber = canvas.isCanvasOfPage() ? canvas.getPdfDocument().getPageNumber(canvas.getPage()) : 0;
currentArea = new RootLayoutArea(pageNumber, canvas.getRootArea().clone());
} else {
setProperty(Property.FULL, true);
currentArea = null;
Expand Down
152 changes: 152 additions & 0 deletions layout/src/test/java/com/itextpdf/layout/CanvasTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.itextpdf.layout;

import com.itextpdf.io.LogMessageConstant;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.action.PdfAction;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.layout.element.Link;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.LogMessage;
import com.itextpdf.test.annotations.LogMessages;
import com.itextpdf.test.annotations.type.IntegrationTest;
import java.io.IOException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(IntegrationTest.class)
public class CanvasTest extends ExtendedITextTest {

public static final String sourceFolder = "./src/test/resources/com/itextpdf/layout/CanvasTest/";
public static final String destinationFolder = "./target/test/com/itextpdf/layout/CanvasTest/";

@BeforeClass
public static void beforeClass() {
createOrClearDestinationFolder(destinationFolder);
}

@Test
@LogMessages(messages = @LogMessage(messageTemplate = LogMessageConstant.UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN))
public void canvasNoPageLinkTest() throws IOException, InterruptedException {
String testName = "canvasNoPageLinkTest";
String out = destinationFolder + testName + ".pdf";
String cmp = sourceFolder + "cmp_" + testName + ".pdf";
PdfDocument pdf = new PdfDocument(new PdfWriter(out));
PdfPage page = pdf.addNewPage();
Rectangle pageSize = page.getPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
Rectangle rectangle = new Rectangle(
pageSize.getX() + 36,
pageSize.getTop() - 80,
pageSize.getWidth() - 72,
50);

Canvas canvas = new Canvas(pdfCanvas, pdf, rectangle);
canvas.add(
new Paragraph(
new Link("Google link!", PdfAction.createURI("https://www.google.com"))
.setUnderline()
.setFontColor(ColorConstants.BLUE)));
canvas.close();
pdf.close();

Assert.assertNull(new CompareTool().compareByContent(out, cmp, destinationFolder));
}

@Test
public void canvasWithPageLinkTest() throws IOException, InterruptedException {
String testName = "canvasWithPageLinkTest";
String out = destinationFolder + testName + ".pdf";
String cmp = sourceFolder + "cmp_" + testName + ".pdf";
PdfDocument pdf = new PdfDocument(new PdfWriter(out));
PdfPage page = pdf.addNewPage();
Rectangle pageSize = page.getPageSize();
Rectangle rectangle = new Rectangle(
pageSize.getX() + 36,
pageSize.getTop() - 80,
pageSize.getWidth() - 72,
50);

Canvas canvas = new Canvas(page, rectangle);
canvas.add(
new Paragraph(
new Link("Google link!", PdfAction.createURI("https://www.google.com"))
.setUnderline()
.setFontColor(ColorConstants.BLUE)));
canvas.close();
pdf.close();

Assert.assertNull(new CompareTool().compareByContent(out, cmp, destinationFolder));
}

@Test
public void canvasWithPageEnableTaggingTest01() throws IOException, InterruptedException {
String testName = "canvasWithPageEnableTaggingTest01";
String out = destinationFolder + testName + ".pdf";
String cmp = sourceFolder + "cmp_" + testName + ".pdf";
PdfDocument pdf = new PdfDocument(new PdfWriter(out));

pdf.setTagged();

PdfPage page = pdf.addNewPage();
Rectangle pageSize = page.getPageSize();
Rectangle rectangle = new Rectangle(
pageSize.getX() + 36,
pageSize.getTop() - 80,
pageSize.getWidth() - 72,
50);

Canvas canvas = new Canvas(page, rectangle);
canvas.add(
new Paragraph(
new Link("Google link!", PdfAction.createURI("https://www.google.com"))
.setUnderline()
.setFontColor(ColorConstants.BLUE)));
canvas.close();
pdf.close();

Assert.assertNull(new CompareTool().compareByContent(out, cmp, destinationFolder));
}

@Test
@LogMessages(messages = {@LogMessage(messageTemplate = LogMessageConstant.UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN),
@LogMessage(messageTemplate = LogMessageConstant.PASSED_PAGE_SHALL_BE_ON_WHICH_CANVAS_WILL_BE_RENDERED)})
public void canvasWithPageEnableTaggingTest02() throws IOException, InterruptedException {
String testName = "canvasWithPageEnableTaggingTest02";
String out = destinationFolder + testName + ".pdf";
String cmp = sourceFolder + "cmp_" + testName + ".pdf";
PdfDocument pdf = new PdfDocument(new PdfWriter(out));

pdf.setTagged();

PdfPage page = pdf.addNewPage();
Rectangle pageSize = page.getPageSize();
Rectangle rectangle = new Rectangle(
pageSize.getX() + 36,
pageSize.getTop() - 80,
pageSize.getWidth() - 72,
50);

Canvas canvas = new Canvas(page, rectangle);

// This will disable tagging and also prevent annotations addition. Created tagged document is invalid. Expected log message.
canvas.enableAutoTagging(null);

canvas.add(
new Paragraph(
new Link("Google link!", PdfAction.createURI("https://www.google.com"))
.setUnderline()
.setFontColor(ColorConstants.BLUE)));
canvas.close();
pdf.close();

Assert.assertNull(new CompareTool().compareByContent(out, cmp, destinationFolder));
}
}
Loading

0 comments on commit 4da833d

Please sign in to comment.