/*
Copyright 2008-2010 Gephi
Authors : Jeremy Subtil <jeremy.subtil@gephi.org>,
Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.io.exporter.preview;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.DefaultFontMapper;
import com.itextpdf.text.pdf.FontMapper;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import org.gephi.io.exporter.spi.ByteExporter;
import org.gephi.io.exporter.spi.VectorExporter;
import org.gephi.preview.api.BidirectionalEdge;
import org.gephi.preview.api.Color;
import org.gephi.preview.api.CubicBezierCurve;
import org.gephi.preview.api.DirectedEdge;
import org.gephi.preview.api.Edge;
import org.gephi.preview.api.EdgeArrow;
import org.gephi.preview.api.EdgeLabel;
import org.gephi.preview.api.EdgeMiniLabel;
import org.gephi.preview.api.Graph;
import org.gephi.preview.api.GraphRenderer;
import org.gephi.preview.api.GraphSheet;
import org.gephi.preview.api.Node;
import org.gephi.preview.api.NodeLabel;
import org.gephi.preview.api.NodeLabelBorder;
import org.gephi.preview.api.Point;
import org.gephi.preview.api.PreviewController;
import org.gephi.preview.api.SelfLoop;
import org.gephi.preview.api.UndirectedEdge;
import org.gephi.preview.api.UnidirectionalEdge;
import org.gephi.utils.longtask.spi.LongTask;
import org.gephi.utils.progress.Progress;
import org.gephi.utils.progress.ProgressTicket;
import org.gephi.project.api.Workspace;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.Utilities;
import sun.font.FontManager;
/**
* Class exporting the preview graph as a PDF file.
*
* @author Jérémy Subtil <jeremy.subtil@gephi.org>
* @author Mathieu Bastian
*/
public class PDFExporter implements GraphRenderer, ByteExporter, VectorExporter, LongTask {
private ProgressTicket progress;
private Workspace workspace;
private OutputStream stream;
private boolean cancel = false;
private PdfContentByte cb;
private Document document;
//Parameters
private float marginTop = 18f;
private float marginBottom = 18f;
private float marginLeft = 18f;
private float marginRight = 18f;
private boolean landscape = false;
private Rectangle pageSize = PageSize.A4;
private FontMapper fontMapper;
static {
//Required in Headless to not get NPE on FontManager.getFileNameForFontName()
GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
e.getAllFonts();
}
public boolean execute() {
// fetches the preview graph sheet
PreviewController controller = Lookup.getDefault().lookup(PreviewController.class);
GraphSheet graphSheet = controller.getGraphSheet();
Graph graph = graphSheet.getGraph();
try {
exportData(graph);
} catch (Exception e) {
throw new RuntimeException(e);
}
return !cancel;
}
/**
* Does export the preview graph as an SVG image.
*
* @param file the output SVG file
* @throws Exception
*/
private void exportData(Graph graph) throws Exception {
Progress.start(progress);
// calculates progress units count
int max = 0;
if (graph.showNodes()) {
max += graph.countNodes();
}
if (graph.showEdges()) {
max += graph.countUnidirectionalEdges() + graph.countBidirectionalEdges();
if (graph.showSelfLoops()) {
max += graph.countSelfLoops();
}
}
Progress.switchToDeterminate(progress, max);
Rectangle size = new Rectangle(pageSize);
if (landscape) {
size = new Rectangle(pageSize.rotate());
}
size.setBackgroundColor(new BaseColor(Lookup.getDefault().lookup(PreviewController.class).getModel().getBackgroundColor()));
document = new Document(size);
PdfWriter pdfWriter = PdfWriter.getInstance(document, stream);
document.open();
cb = pdfWriter.getDirectContent();
cb.saveState();
//Limits
float minX = Float.POSITIVE_INFINITY;
float maxX = Float.NEGATIVE_INFINITY;
float minY = Float.POSITIVE_INFINITY;
float maxY = Float.NEGATIVE_INFINITY;
for (Node n : graph.getNodes()) {
minX = Math.min(minX, n.getPosition().getX() - n.getRadius() - n.getBorderWidth());
maxX = Math.max(maxX, n.getPosition().getX() + n.getRadius() + n.getBorderWidth());
minY = Math.min(minY, -n.getPosition().getY() - n.getRadius() - n.getBorderWidth());
maxY = Math.max(maxY, -n.getPosition().getY() + n.getRadius() + n.getBorderWidth());
}
double graphWidth = maxX - minX;
double graphHeight = maxY - minY;
double centerX = minX + graphWidth / 2.;
double centerY = minY + graphHeight / 2.;
//Transform
double pageWidth = size.getWidth() - marginLeft - marginRight;
double pageHeight = size.getHeight() - marginTop - marginBottom;
double ratioWidth = pageWidth / graphWidth;
double ratioHeight = pageHeight / graphHeight;
double scale = 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));
renderGraph(graph);
Progress.switchToIndeterminate(progress);
cb.restoreState();
document.close();
Progress.finish(progress);
}
public boolean cancel() {
cancel = true;
return true;
}
public void renderGraph(Graph graph) {
if (graph.showEdges()) {
renderGraphEdges(graph);
}
if (graph.showNodes()) {
renderGraphNodes(graph);
}
renderGraphLabels(graph);
renderGraphLabelBorders(graph);
}
public void renderGraphEdges(Graph graph) {
renderGraphUnidirectionalEdges(graph);
renderGraphBidirectionalEdges(graph);
renderGraphUndirectedEdges(graph);
if (graph.showSelfLoops()) {
renderGraphSelfLoops(graph);
}
}
public void renderGraphSelfLoops(Graph graph) {
for (SelfLoop sl : graph.getSelfLoops()) {
renderSelfLoop(sl);
}
}
public void renderGraphUnidirectionalEdges(Graph graph) {
for (UnidirectionalEdge edge : graph.getUnidirectionalEdges()) {
renderDirectedEdge(edge);
}
}
public void renderGraphBidirectionalEdges(Graph graph) {
for (BidirectionalEdge edge : graph.getBidirectionalEdges()) {
renderDirectedEdge(edge);
}
}
public void renderGraphUndirectedEdges(Graph graph) {
for (UndirectedEdge e : graph.getUndirectedEdges()) {
renderEdge(e);
}
}
public void renderGraphNodes(Graph graph) {
for (Node n : graph.getNodes()) {
renderNode(n);
}
}
public void renderGraphLabels(Graph graph) {
for (UnidirectionalEdge e : graph.getUnidirectionalEdges()) {
if (!e.isCurved()) {
if (e.showLabel() && e.hasLabel() && e.getLabel().getFont() != null) {
renderEdgeLabel(e.getLabel());
}
if (e.showMiniLabels()) {
renderEdgeMiniLabels(e);
}
}
}
for (BidirectionalEdge e : graph.getBidirectionalEdges()) {
if (!e.isCurved()) {
if (e.showLabel() && e.hasLabel() && e.getLabel().getFont() != null) {
renderEdgeLabel(e.getLabel());
}
if (e.showMiniLabels()) {
renderEdgeMiniLabels(e);
}
}
}
for (UndirectedEdge e : graph.getUndirectedEdges()) {
if (e.showLabel() && !e.isCurved() && e.hasLabel() && e.getLabel().getFont() != null) {
renderEdgeLabel(e.getLabel());
}
}
for (Node n : graph.getNodes()) {
if (n.showLabel() && n.hasLabel() && n.getLabel().getFont() != null) {
renderNodeLabel(n.getLabel());
}
}
}
public void renderGraphLabelBorders(Graph graph) {
}
public void renderNode(Node node) {
Point center = node.getPosition();
Color c = node.getColor();
Color bc = node.getBorderColor();
cb.setRGBColorStroke(bc.getRed(), bc.getGreen(), bc.getBlue());
cb.setLineWidth(node.getBorderWidth());
cb.setRGBColorFill(c.getRed(), c.getGreen(), c.getBlue());
cb.circle(center.getX(), -center.getY(), node.getRadius());
cb.fillStroke();
}
public void renderNodeLabel(NodeLabel label) {
Point p = label.getPosition();
Font font = label.getFont();
setFillColor(label.getColor());
try {
BaseFont bf = genBaseFont(font);
float ascent = bf.getAscentPoint(label.getValue(), font.getSize());
float descent = bf.getDescentPoint(label.getValue(), font.getSize());
float textHeight = (ascent - descent) / 2f;
cb.beginText();
cb.setFontAndSize(bf, font.getSize());
cb.showTextAligned(PdfContentByte.ALIGN_CENTER, label.getValue(), p.getX(), -p.getY() - textHeight, 0);
cb.endText();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
public void renderNodeLabelBorder(NodeLabelBorder border) {
}
public void renderSelfLoop(SelfLoop selfLoop) {
cubicBezierCurve(selfLoop.getCurve());
setStrokeColor(selfLoop.getColor());
cb.setLineWidth(selfLoop.getThickness() * selfLoop.getScale());
cb.stroke();
}
public void renderDirectedEdge(DirectedEdge edge) {
renderEdge(edge);
if (!edge.isCurved() && edge.showArrows()) {
renderEdgeArrows(edge);
}
}
public void renderEdge(Edge edge) {
if (edge.isCurved()) {
renderCurvedEdge(edge);
} else {
renderStraightEdge(edge);
}
Progress.progress(progress);
}
public void renderStraightEdge(Edge edge) {
line(edge.getNode1().getPosition(), edge.getNode2().getPosition());
setStrokeColor(edge.getColor());
cb.setLineWidth(edge.getThickness() * edge.getScale());
cb.stroke();
}
public void renderCurvedEdge(Edge edge) {
for (CubicBezierCurve c : edge.getCurves()) {
cubicBezierCurve(c);
setStrokeColor(edge.getColor());
cb.setLineWidth(edge.getThickness() * edge.getScale());
cb.stroke();
}
}
public void renderEdgeArrows(DirectedEdge edge) {
for (EdgeArrow a : edge.getArrows()) {
renderEdgeArrow(a);
}
}
public void renderEdgeMiniLabels(DirectedEdge edge) {
for (EdgeMiniLabel ml : edge.getMiniLabels()) {
renderEdgeMiniLabel(ml);
}
}
public void renderEdgeArrow(EdgeArrow arrow) {
Point pt1 = arrow.getPt1();
Point pt2 = arrow.getPt2();
Point pt3 = arrow.getPt3();
cb.moveTo(pt1.getX(), -pt1.getY());
cb.lineTo(pt2.getX(), -pt2.getY());
cb.lineTo(pt3.getX(), -pt3.getY());
cb.closePath();
setFillColor(arrow.getColor());
cb.fill();
}
public void renderEdgeLabel(EdgeLabel label) {
Point p = label.getPosition();
Font font = label.getFont();
setFillColor(label.getColor());
try {
BaseFont bf = genBaseFont(font);
cb.beginText();
cb.setFontAndSize(bf, font.getSize());
cb.showTextAligned(PdfContentByte.ALIGN_CENTER, label.getValue(), p.getX(), -p.getY(), (float) (Math.toDegrees(-label.getAngle())));
cb.endText();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
public void renderEdgeMiniLabel(EdgeMiniLabel miniLabel) {
Point p = miniLabel.getPosition();
Font font = miniLabel.getFont();
setFillColor(miniLabel.getColor());
try {
BaseFont bf = genBaseFont(font);
cb.beginText();
cb.setFontAndSize(bf, font.getSize());
cb.showTextAligned(miniLabel.getHAlign().toIText(), miniLabel.getValue(), p.getX(), -p.getY(), (float) (Math.toDegrees(-miniLabel.getAngle())));
cb.endText();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* Generates an iText BaseFont object from a Java Font one.
*
* @param font the reference font
* @return the generated BaseFont
* @throws DocumentException
* @throws IOException
*/
private BaseFont genBaseFont(java.awt.Font font) throws DocumentException, IOException {
if (font != null) {
try {
loadFont(font);
} catch (Exception e) {
e.printStackTrace();
return BaseFont.createFont();
}
return fontMapper.awtToPdf(font);
}
return BaseFont.createFont();
}
private void loadFont(java.awt.Font font) throws Exception {
if (fontMapper == null) {
fontMapper = new DefaultFontMapper();
}
if (fontMapper instanceof DefaultFontMapper) {
if (Utilities.isMac()) {
String fontName = font.getName();
if (!((DefaultFontMapper) fontMapper).getMapper().containsKey(fontName)) {
File homeLibraryFonts = new File(System.getProperty("user.home") + "/Library/Fonts");
File systemLibraryFonts = new File("/System/Library/Fonts");
File libraryFonts = new File("/Library/Fonts");
File fontResult = lookFor(fontName, homeLibraryFonts);
fontResult = fontResult != null ? fontResult : lookFor(fontName, systemLibraryFonts);
fontResult = fontResult != null ? fontResult : lookFor(fontName, libraryFonts);
if (fontResult != null) {
fontName = fontResult.getName();
String fontFilePath = fontResult.getAbsolutePath();
loadFont(fontName, fontFilePath);
}
}
} else {
String fontName = FontManager.getFileNameForFontName(font.getFontName());
if (!((DefaultFontMapper) fontMapper).getMapper().containsKey(fontName)) {
String fontFilePath = FontManager.getFontPath(false);
fontFilePath = fontFilePath + "/" + fontName;
loadFont(fontName, fontFilePath);
}
}
}
}
private void loadFont(String fontName, String fontFilePath) throws Exception {
if (fontName != null && !fontName.isEmpty()) {
fontName = fontName.toLowerCase();
if (fontFilePath != null && !fontFilePath.isEmpty()) {
if (fontName.endsWith(".ttf") || fontName.endsWith(".otf") || fontName.endsWith(".afm")) {
Object allNames[] = BaseFont.getAllFontNames(fontFilePath, BaseFont.CP1252, null);
((DefaultFontMapper) fontMapper).insertNames(allNames, fontFilePath);
} else if (fontName.endsWith(".ttc")) {
String ttcs[] = BaseFont.enumerateTTCNames(fontFilePath);
for (int j = 0; j < ttcs.length; ++j) {
String nt = fontFilePath + "," + j;
Object allNames[] = BaseFont.getAllFontNames(nt, BaseFont.CP1252, null);
((DefaultFontMapper) fontMapper).insertNames(allNames, nt);
}
}
}
}
}
private File lookFor(String fileName, File folder) {
if (!folder.isDirectory()) {
return null;
}
File lookFile = new File(folder, fileName + ".ttf");
if (lookFile.exists()) {
return lookFile;
}
lookFile = new File(folder, fileName + ".otf");
if (lookFile.exists()) {
return lookFile;
}
lookFile = new File(folder, fileName + ".ttc");
if (lookFile.exists()) {
return lookFile;
}
lookFile = new File(folder, fileName + ".afm");
if (lookFile.exists()) {
return lookFile;
}
return null;
}
/**
* Draws a line.
*
* @param start the start of the line to draw
* @param end the end of the line to draw
*/
private void line(Point start, Point end) {
cb.moveTo(start.getX(), -start.getY());
cb.lineTo(end.getX(), -end.getY());
}
/**
* Draws a cubic bezier curve.
*
* @param curve the curve to draw
*/
private void cubicBezierCurve(CubicBezierCurve curve) {
Point pt1 = curve.getPt1();
Point pt2 = curve.getPt2();
Point pt3 = curve.getPt3();
Point pt4 = curve.getPt4();
cb.moveTo(pt1.getX(), -pt1.getY());
cb.curveTo(pt2.getX(), -pt2.getY(), pt3.getX(), -pt3.getY(), pt4.getX(), -pt4.getY());
}
/**
* Defines the stroke color.
*
* @param color the stroke color to set
*/
private void setStrokeColor(Color color) {
cb.setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue());
}
/**
* Defines the filling color.
*
* @param color the filling color to set
*/
private void setFillColor(Color color) {
cb.setRGBColorFill(color.getRed(), color.getGreen(), color.getBlue());
}
public float getMarginBottom() {
return marginBottom;
}
public void setMarginBottom(float marginBottom) {
this.marginBottom = marginBottom;
}
public float getMarginLeft() {
return marginLeft;
}
public void setMarginLeft(float marginLeft) {
this.marginLeft = marginLeft;
}
public float getMarginRight() {
return marginRight;
}
public void setMarginRight(float marginRight) {
this.marginRight = marginRight;
}
public float getMarginTop() {
return marginTop;
}
public void setMarginTop(float marginTop) {
this.marginTop = marginTop;
}
public boolean isLandscape() {
return landscape;
}
public void setLandscape(boolean landscape) {
this.landscape = landscape;
}
public Rectangle getPageSize() {
return pageSize;
}
public void setPageSize(Rectangle pageSize) {
this.pageSize = pageSize;
}
public void setProgressTicket(ProgressTicket progressTicket) {
this.progress = progressTicket;
}
public void setOutputStream(OutputStream stream) {
this.stream = stream;
}
public Workspace getWorkspace() {
return workspace;
}
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
public void setFontMapper(FontMapper fontMapper) {
this.fontMapper = fontMapper;
}
public FontMapper getFontMapper() {
return fontMapper;
}
}