Skip to content

Commit

Permalink
#2652 Migrate from iText to PDFBox (#2657)
Browse files Browse the repository at this point in the history
* Add library to CoreLibraryWrapper

* Migrate UI to use PDFBox

* Migrate node renderer

* Migrate edge and arrow renderer

* Migrate PDFTarget with font support

* Migrate PDFExporter

* Migrate node label renderer

* Fix issue when testing with labels

* Add PDFUtils

* Migrate edge label renderer

* Fix font loading issue

* Make export succeed in case of exception

* Remove embedded font

* Fix edge color

* Fix landscape mode

* Update API changes

* Formatting

* Refactor

* Remove dependency on iText

* Background color
  • Loading branch information
mbastian authored Oct 8, 2022
1 parent 9fa8500 commit fbf025e
Show file tree
Hide file tree
Showing 18 changed files with 417 additions and 396 deletions.
11 changes: 6 additions & 5 deletions modules/CoreLibraryWrapper/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<gephi.commons-csv.version>1.9.0</gephi.commons-csv.version>
<gephi.commons-math3.version>3.6.1</gephi.commons-math3.version>
<gephi.jfreechart.version>1.0.19</gephi.jfreechart.version>
<gephi.itextpdf.version>5.5.13.3</gephi.itextpdf.version>
<gephi.pdfbox.version>2.0.27</gephi.pdfbox.version>
<gephi.trove4j.version>3.0.3</gephi.trove4j.version>
<gephi.gson.version>2.9.1</gephi.gson.version>
</properties>
Expand Down Expand Up @@ -64,9 +64,9 @@
<version>${gephi.commons-csv.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${gephi.itextpdf.version}</version>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${gephi.pdfbox.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Expand All @@ -88,7 +88,8 @@
<publicPackage>org.jfree.*</publicPackage>
<publicPackage>gnu.trove.*</publicPackage>
<publicPackage>org.apache.commons.csv.*</publicPackage>
<publicPackage>com.itextpdf.text.*</publicPackage>
<publicPackage>org.apache.pdfbox.*</publicPackage>
<publicPackage>org.apache.fontbox.*</publicPackage>
<publicPackage>javanet.staxutils.*</publicPackage>
<publicPackage>com.google.gson.*</publicPackage>
</publicPackages>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,29 @@ Development and Distribution License("CDDL") (collectively, the

package org.gephi.preview;

import com.itextpdf.text.FontFactory;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import java.awt.geom.AffineTransform;
import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.FontMappers;
import org.apache.pdfbox.pdmodel.font.FontMapping;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.util.Matrix;
import org.gephi.preview.api.CanvasSize;
import org.gephi.preview.api.PDFTarget;
import org.gephi.preview.api.PreviewModel;
import org.gephi.preview.api.PreviewProperties;
import org.gephi.preview.api.PreviewProperty;
import org.gephi.preview.api.RenderTarget;
import org.gephi.preview.spi.RenderTargetBuilder;
import org.gephi.utils.progress.Progress;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.ServiceProvider;

/**
Expand All @@ -79,14 +88,17 @@ public RenderTarget buildRenderTarget(PreviewModel previewModel) {
float marginLeft = properties.getFloatValue(PDFTarget.MARGIN_LEFT);
float marginRight = properties.getFloatValue(PDFTarget.MARGIN_RIGHT);
float marginTop = properties.getFloatValue(PDFTarget.MARGIN_TOP);
com.itextpdf.text.Rectangle pageSize
= properties.getValue(PDFTarget.PAGESIZE);
final PDRectangle pageSize = properties.getValue(PDFTarget.PAGESIZE);
boolean landscape = properties.getBooleanValue(PDFTarget.LANDSCAPE);
PdfContentByte cb = properties.getValue(PDFTarget.PDF_CONTENT_BYTE);
Color backgroundColor = properties.getColorValue(PreviewProperty.BACKGROUND_COLOR);
PDPageContentStream cb = properties.getValue(PDFTarget.PDF_CONTENT_BYTE);
PDDocument doc = properties.getValue(PDFTarget.PDF_DOCUMENT);
PDFRenderTargetImpl renderTarget = new PDFRenderTargetImpl(
doc,
cb,
cs,
pageSize,
backgroundColor,
marginLeft,
marginRight,
marginTop,
Expand All @@ -97,32 +109,38 @@ public RenderTarget buildRenderTarget(PreviewModel previewModel) {

public static class PDFRenderTargetImpl extends AbstractRenderTarget implements PDFTarget {

private static boolean fontRegistered = false;
private final PdfContentByte cb;
private final PDPageContentStream cb;
private final PDDocument document;
//Parameters
private final float marginTop;
private final float marginBottom;
private final float marginLeft;
private final float marginRight;
private final boolean landscape;
private final com.itextpdf.text.Rectangle pageSize;
private final PDRectangle pageSize;

private Map<String, PDFont> fontMap;

public PDFRenderTargetImpl(
PdfContentByte cb,
PDDocument doc,
PDPageContentStream cb,
CanvasSize cs,
com.itextpdf.text.Rectangle size,
PDRectangle size,
Color backgroundColor,
float marginLeft,
float marginRight,
float marginTop,
float marginBottom,
boolean landscape) {
this.document = doc;
this.cb = cb;
this.marginTop = marginTop;
this.marginLeft = marginLeft;
this.marginBottom = marginBottom;
this.marginRight = marginRight;
this.pageSize = size;
this.landscape = landscape;
this.fontMap = new HashMap<>();

double centerX = cs.getX() + cs.getWidth() / 2;
double centerY = cs.getY() + cs.getHeight() / 2;
Expand All @@ -135,89 +153,53 @@ public PDFRenderTargetImpl(
double scale = (float) (ratioWidth < ratioHeight ? ratioWidth : ratioHeight);
double translateX = (marginLeft + pageWidth / 2.) / scale;
double translateY = (marginBottom + pageHeight / 2.) / scale;
cb.transform(AffineTransform.getTranslateInstance(-centerX * scale, centerY * scale));
cb.transform(AffineTransform.getScaleInstance(scale, scale));
cb.transform(AffineTransform.getTranslateInstance(translateX, translateY));
try {
// Background
if (backgroundColor != null) {
cb.setNonStrokingColor(backgroundColor);
cb.addRect(0, 0, size.getWidth(), size.getHeight());
cb.fill();
}

FontFactory.register("/org/gephi/preview/fonts/LiberationSans.ttf", "ArialMT");
// Transformations
cb.transform(Matrix.getTranslateInstance((float) (-centerX * scale), (float) (centerY * scale)));
cb.transform(Matrix.getScaleInstance((float) scale, (float) scale));
cb.transform(Matrix.getTranslateInstance((float) translateX, (float) translateY));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

@Override
public PdfContentByte getContentByte() {
public PDPageContentStream getContentStream() {
return this.cb;
}

@Override
public BaseFont getBaseFont(java.awt.Font font) {
try {
if (font != null) {
BaseFont baseFont;
if (!font.getFontName().equals(FontFactory.COURIER)
&& !font.getFontName().equals(FontFactory.COURIER_BOLD)
&& !font.getFontName().equals(FontFactory.COURIER_OBLIQUE)
&& !font.getFontName().equals(FontFactory.COURIER_BOLDOBLIQUE)
&& !font.getFontName().equals(FontFactory.HELVETICA)
&& !font.getFontName().equals(FontFactory.HELVETICA_BOLD)
&& !font.getFontName().equals(FontFactory.HELVETICA_BOLDOBLIQUE)
&& !font.getFontName().equals(FontFactory.HELVETICA_OBLIQUE)
&& !font.getFontName().equals(FontFactory.SYMBOL)
&& !font.getFontName().equals(FontFactory.TIMES_ROMAN)
&& !font.getFontName().equals(FontFactory.TIMES_BOLD)
&& !font.getFontName().equals(FontFactory.TIMES_ITALIC)
&& !font.getFontName().equals(FontFactory.TIMES_BOLDITALIC)
&& !font.getFontName().equals(FontFactory.ZAPFDINGBATS)
&& !font.getFontName().equals(FontFactory.COURIER_BOLD)
&& !font.getFontName().equals(FontFactory.COURIER_BOLD)
&& !font.getFontName().equals(FontFactory.COURIER_BOLD)) {

com.itextpdf.text.Font itextFont = FontFactory
.getFont(font.getFontName(), BaseFont.IDENTITY_H, font.getSize(), font.getStyle());
baseFont = itextFont.getBaseFont();
if (baseFont == null && !PDFRenderTargetImpl.fontRegistered) {

if (progressTicket != null) {
String displayName = progressTicket.getDisplayName();
Progress.setDisplayName(progressTicket, NbBundle
.getMessage(PDFRenderTargetImpl.class, "PDFRenderTargetImpl.font.registration"));
registerFonts();
Progress.setDisplayName(progressTicket, displayName);
}

itextFont = FontFactory
.getFont(font.getFontName(), BaseFont.IDENTITY_H, font.getSize(), font.getStyle());
baseFont = itextFont.getBaseFont();

PDFRenderTargetImpl.fontRegistered = true;
}
} else {
com.itextpdf.text.Font itextFont =
FontFactory.getFont(font.getFontName(), font.getSize(), font.getStyle());
baseFont = itextFont.getBaseFont();
}

if (baseFont != null) {
return baseFont;
public PDFont getPDFont(java.awt.Font font) {
final String fontKey = getFontKey(font);
return fontMap.computeIfAbsent(fontKey, (key) -> {
FontMapping<TrueTypeFont> mapping = FontMappers.instance().getTrueTypeFont(fontKey, null);
if (mapping != null) {
try {
return PDType0Font.load(document, mapping.getFont(), true);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return BaseFont.createFont();
}
return BaseFont.createFont();
} catch (Exception e) {
Exceptions.printStackTrace(e);
}
return null;
return PDType1Font.HELVETICA;
});
}

private void registerFonts() {
FontFactory.registerDirectories();
if (Utilities.isMac()) {
//Add user fonts folder
String userFonts = "/" + System.getProperty("user.home") + "/Library/Fonts";
FontFactory.registerDirectory(userFonts);

//Adobe font folder
String adobeFonts = "/Library/Application Support/Adobe/Fonts";
FontFactory.registerDirectory(adobeFonts);
private static String getFontKey(Font font) {
StringBuilder name = new StringBuilder(font.getName().replace(" ", "-"));
if (font.isBold()) {
name.append("-Bold");
}
if (font.isItalic()) {
name.append("-Italic");
}

return name.toString();
}

@Override
Expand Down Expand Up @@ -246,7 +228,7 @@ public boolean isLandscape() {
}

@Override
public com.itextpdf.text.Rectangle getPageSize() {
public PDRectangle getPageSize() {
return pageSize;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,11 @@ private synchronized void render(RenderTarget target, Renderer[] renderers, Prev
for (String type : previewModel.getItemTypes()) {
for (Item item : previewModel.getItems(type)) {
if (r.isRendererForitem(item, properties)) {
r.render(item, target, properties);
try {
r.render(item, target, properties);
} catch (Exception e) {
Exceptions.printStackTrace(e);
}
Progress.progress(progressTicket);
if (target instanceof AbstractRenderTarget) {
if (((AbstractRenderTarget) target).isCancelled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@ Development and Distribution License("CDDL") (collectively, the

package org.gephi.preview.api;

import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;

/**
* Rendering target to PDF format.
* <p>
* This target is used by renderers objects to render a graph to PDF and uses
* the <a href="http://itextpdf.com">iText</a> Java library.
* the <a href=https://pdfbox.apache.org/">PDFBox</a> Java library.
* <p>
* The target give access to the <code>PDFContentBype</code> object from itext to
* The target give access to the <code>PDPageContentStream</code> object from PDFBox to
* draw items.
* <p>
* When this target is instanciated it uses property values defined in the
* When this target is instantiated it uses property values defined in the
* {@link PreviewProperties}. Namely is uses <code>MARGIN_LEFT</code>,
* <code>MARGIN_TOP</code>, <code>MARGIN_BOTTOM</code>, <code>MARGIN_RIGHT</code>,
* <code>LANDCAPE</code> and <code>PAGESIZE</code>.
* <code>LANDSCAPE</code> and <code>PAGESIZE</code>.
*
* @author Yudi Xue, Mathieu Bastian
*/
Expand All @@ -71,29 +71,28 @@ public interface PDFTarget extends RenderTarget {
String MARGIN_RIGHT = "pdf.margin.right";
String LANDSCAPE = "pdf.landscape";
String PAGESIZE = "pdf.pagesize";
String PDF_DOCUMENT = "pdf.document";

/**
* Returns the <code>PDFContentBype</code> instance of the PDFTarget. PDFContentByte
* Returns the <code>PDPageContentStream</code> instance of the PDFTarget. PDPageContentStream
* offers a set of drawing functions which can be used by Renderer objects.
*
* @return a PDFContentBype object
* @return a PDPageContentStream object
*/
PdfContentByte getContentByte();
PDPageContentStream getContentStream();

/**
* Get a the equivalent in iText of the Java font. Base fonts are either
* Type 1 fonts (PDF default's font) or valid system fonts. The first time
* a base font which is not a Type 1 is requested the system will
* register the system fonts in order to find the right font. This might
* take some time up to a minute.
* Get the PDFBox equivalent the Java font.
* <p>
* If <code>font</code> can't be found in iText's default fonts or registered
* fonts it returns the default Helvetica font.
* If <code>font</code> can't be found it returns the default Helvetica font.
* <p>
* Note that each font (regardless of font size) is embedded in the PDF Document. Also note that some fonts might
* miss Unicode characters, which will prevent the text from being rendered properly.
*
* @param font the reference Java font
* @return the iText BaseFont, or Helvetica is not found
* @return the PDFont, or Helvetica is not found
*/
BaseFont getBaseFont(java.awt.Font font);
PDFont getPDFont(java.awt.Font font);

/**
* Returns the margin at the bottom of the page.
Expand Down Expand Up @@ -136,5 +135,5 @@ public interface PDFTarget extends RenderTarget {
*
* @return the page size
*/
Rectangle getPageSize();
PDRectangle getPageSize();
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ Development and Distribution License("CDDL") (collectively, the
/**
* RenderTarget is the graphic container the renderers draw into.
* <p>
* There are three types of targets: <b>Processing</b>, <b>PDF</b> or
* There are three types of targets: <b>G2D</b>, <b>PDF</b> or
* <b>SVG</b>. When the target is G2D, renderers obtain the {@link Graphics2D}
* object. For the SVG target, renderers obtain Batik's <a
* href="http://xmlgraphics.apache.org/batik/using/dom-api.html">Document</a>
* instance. As the PDF target rely on the iText library renderers obtain the <a
* href="http://api.itextpdf.com/itext/index.html?com/itextpdf/text/pdf/PdfContentByte.html">PdfContentByte</a>
* instance. As the PDF target rely on the PDFBox library, renderers obtain the {@link org.apache.pdfbox.pdmodel.PDPageContentStream}</a>
* object.
* <p>
* Render targets are not drawing anything. They just make accessible the canvas
Expand Down
Binary file not shown.
Loading

0 comments on commit fbf025e

Please sign in to comment.