Java Graphics2D と各種ライブラリでベクターグラフィックス画像 (PDF,SVG,PPT,EPS,SWF) を出力する

## 概要

- Java 標準ライブラリの Graphics2D クラスと外部ライブラリを使用して、ベクターグラフィックス画像 (PDF,SVG,PPT,EPS,SWF) を出力する
- 具体的な描画処理は Graphics2D を使用して共通化する
- 各種ベクター形式画像フォーマットへの出力は外部ライブラリを使用する
- 今回のプログラムは Graphics2D を使って描画しているような振る舞いをするが、実際には各種フォーマットに対応した Graphics2D のサブクラスが処理をしている
- 確認用に PNG (Portable Network Graphics) 画像も出力する

## 使用ライブラリ

- PDF (Portable Document Format): [Orson PDF](http://www.object-refinery.com/orsonpdf/)
- SVG (Scalable Vector Graphics): [Apache Batik](https://xmlgraphics.apache.org/batik/)
- PPT (PowerPoint): [Apache POI](https://poi.apache.org/)
- EPS (Encapsulated PostScript): [VectorGraphics2D](https://github.com/eseifert/vectorgraphics2d)
- SWF (Small Web Format / Shockwave Flash): [FreeHEP VectorGraphics](http://freehep.github.io/freehep-vectorgraphics/)

## 今回の動作環境

- macOS Mojave
- OpenJDK 11.0.2
- Apache Maven 3.6.1

## ソースコード

### ソースコード一覧

- pom.xml: Maven のビルド用設定ファイル
- App.java: 各種フォーマットへ出力するプログラム

```
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── App.java
```

### pom.xml: Maven のビルド用設定ファイル

- 各種外部ライブラリを dependencies に追加している
- 依存ライブラリを含めた実行可能な JAR ファイルを生成するため Maven Assembly Plugin を導入している

```xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>my-app</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- For PDF -->
    <!-- https://mvnrepository.com/artifact/com.orsonpdf/orsonpdf -->
    <dependency>
      <groupId>com.orsonpdf</groupId>
      <artifactId>orsonpdf</artifactId>
      <version>1.9</version>
    </dependency>
    <!-- For SVG -->
    <!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-all -->
    <dependency>
      <groupId>org.apache.xmlgraphics</groupId>
      <artifactId>batik-all</artifactId>
      <version>1.11</version>
      <type>pom</type>
    </dependency>
    <!-- For PPT -->
    <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-scratchpad -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-scratchpad</artifactId>
      <version>4.1.0</version>
    </dependency>
    <!-- For EPS -->
    <!-- https://mvnrepository.com/artifact/de.erichseifert.vectorgraphics2d/VectorGraphics2D -->
    <dependency>
      <groupId>de.erichseifert.vectorgraphics2d</groupId>
      <artifactId>VectorGraphics2D</artifactId>
      <version>0.13</version>
    </dependency>
    <!-- For SWF -->
    <!-- https://mvnrepository.com/artifact/org.freehep/freehep-graphicsio-swf -->
    <dependency>
      <groupId>org.freehep</groupId>
      <artifactId>freehep-graphicsio-swf</artifactId>
      <version>2.4</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- http://maven.apache.org/plugins/maven-assembly-plugin/ -->
      <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>3.1.1</version>
      <executions>
        <execution>
        <phase>package</phase>
        <goals>
          <goal>single</goal>
        </goals>
        </execution>
      </executions>
      <configuration>
        <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
        <manifest>
          <mainClass>com.example.App</mainClass>
        </manifest>
        </archive>
      </configuration>
      </plugin>
    </plugins>
  </build>

</project>
```

### App.java: 各種フォーマットへ出力するプログラム

```java
package com.example;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import javax.imageio.ImageIO;
import org.w3c.dom.DOMImplementation;

// PDF
import com.orsonpdf.Page;
import com.orsonpdf.PDFDocument;
import com.orsonpdf.PDFGraphics2D;

// SVG
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;

// PPT
import org.apache.poi.hslf.model.PPGraphics2D;
import org.apache.poi.hslf.usermodel.HSLFGroupShape;
import org.apache.poi.hslf.usermodel.HSLFSlide;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;

// EPS
import de.erichseifert.vectorgraphics2d.Document;
import de.erichseifert.vectorgraphics2d.Processor;
import de.erichseifert.vectorgraphics2d.Processors;
import de.erichseifert.vectorgraphics2d.VectorGraphics2D;
import de.erichseifert.vectorgraphics2d.intermediate.CommandSequence;
import de.erichseifert.vectorgraphics2d.util.PageSize;

// SWF
import org.freehep.graphicsio.swf.SWFGraphics2D;

public class App {

  public static void main(String[] args) throws Exception {
    createPNG();
    createPDF();
    createSVG();
    createPPT();
    createEPS();
    createSWF();
  }

  /**
   * 描画します。
   *
   * @param g Graphics2D オブジェクト
   * @param w 描画範囲の横幅
   * @param h 描画範囲の縦幅
   */
  private static void draw(Graphics2D g, double w, double h) {
    // 線の太さ
    g.setStroke(new BasicStroke((float) (w + h) / 20.0f));
    // 背景
    g.setPaint(Color.LIGHT_GRAY);
    g.fill(new Rectangle2D.Double(0, 0, w, h));
    // 枠線
    g.setPaint(Color.BLUE);
    g.draw(new Rectangle2D.Double(0, 0, w, h));
    // 円
    g.setPaint(Color.RED);
    g.fill(new Ellipse2D.Double(w / 4, h / 4, w / 2, h / 2));
    // 線
    g.setPaint(new Color(255, 255, 0, 100));
    Line2D line = new Line2D.Double(w / 8, h / 8, w / 2, h / 2);
    g.draw(line);
  }

  /**
   * PNG (Portable Network Graphics) ファイルを出力します。
   */
  private static void createPNG() throws IOException {
    int w = 297 * 2;
    int h = 210 * 2;
    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = image.createGraphics();
    draw(g, w, h);
    ImageIO.write(image, "png", new File("png.png"));
  }

  /**
   * PDF (Portable Document Format) ファイルを出力します。
   */
  private static void createPDF() throws Exception {
    // 1 inch = 25.4mm = 72pt
    double w = 297 * 72 / 25.4;
    double h = 210 * 72 / 25.4;
    PDFDocument pdfDoc = new PDFDocument();
    Page page = pdfDoc.createPage(new Rectangle2D.Double(0, 0, w, h));
    PDFGraphics2D g = page.getGraphics2D();
    draw(g, w, h);
    pdfDoc.writeToFile(new File("pdf.pdf"));
  }

  /**
   * SVG (Scalable Vector Graphics) ファイルを出力します。
   */
  private static void createSVG() throws IOException {
    int w = 297 * 2;
    int h = 210 * 2;
    DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
    String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
    org.w3c.dom.Document doc = domImpl.createDocument(svgNS, "svg", null);
    SVGGraphics2D g = new SVGGraphics2D(doc);
    g.setSVGCanvasSize(new Dimension(w, h));
    draw(g, w, h);
    g.stream("svg.svg");
  }

  /**
   * PPT (PowerPoint) ファイルを出力します。
   */
  private static void createPPT() throws IOException {
    HSLFSlideShow ppt = new HSLFSlideShow();
    Dimension pageSize = ppt.getPageSize();
    double w = 297;
    double h = 210;
    double coef = Math.min(pageSize.getWidth() / w, pageSize.getHeight() / h);
    w = w * coef;
    h = h * coef;
    HSLFSlide slide = ppt.createSlide();
    HSLFGroupShape group = slide.createGroup();
    Rectangle2D bounds = group.getAnchor();
    bounds.setRect(0, 0, w, h);
    PPGraphics2D g = new PPGraphics2D(group);
    draw(g, w, h);
    try (FileOutputStream out = new FileOutputStream("ppt.ppt")) {
      ppt.write(out);
    }
  }

  /**
   * EPS (Encapsulated PostScript) ファイルを出力します。
   */
  private static void createEPS() throws IOException {
    int w = 297 * 2;
    int h = 210 * 2;
    VectorGraphics2D g = new VectorGraphics2D();
    draw(g, w, h);
    CommandSequence commands = g.getCommands();
    Processor processor = Processors.get("eps");
    PageSize size = new PageSize(w, h);
    Document doc = processor.getDocument(commands, size);
    doc.writeTo(new FileOutputStream("eps.eps"));
  }

  /**
   * SWF (Small Web Format / Shockwave Flash) ファイルを出力します。
   */
  private static void createSWF() throws IOException {
    int w = 297 * 2;
    int h = 210 * 2;
    SWFGraphics2D g = new SWFGraphics2D(new File("swf.swf"), new Dimension(w, h));
    g.writeHeader();
    g.startExport();
    draw(g, w, h);
    g.endExport();
    g.dispose();
  }
}
```

## プログラムをビルドして実行

Maven で実行可能な JAR ファイルを生成する。

```
$ mvn package
```

java コマンドで JAR ファイルを指定して実行する。

```
$ java -jar target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
```

これで PNG, PDF, SVG, PPT, EPS, SWF の6種類のファイルが出力できる。

## 出力された各種ベクターグラフィックス画像ファイル

### PNG (Portable Network Graphics)

PNG はベクター形式の画像フォーマットではないが比較用に載せておく。

PNG ファイルの情報をコマンドで確認する。

```
$ file png.png
png.png: PNG image data, 594 x 420, 8-bit/color RGBA, non-interlaced

$ pngcheck png.png
OK: png.png (594x420, 32-bit RGB+alpha, non-interlaced, 99.3%).
```

画像は macOS Mojave のプレビューアプリで閲覧したもの。

<img width="552" alt="png.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/7a5648cc-8709-fc3f-0dad-f46f21423b20.png">

### PDF (Portable Document Format)

PDF ファイルの情報をコマンドで確認する。

```
$ file pdf.pdf
pdf.pdf: PDF document, version 1.4

$ pdfinfo pdf.pdf
Producer:       OrsonPDF 1.7
CreationDate:   Sun Jul 28 15:24:27 2019
ModDate:        Sun Jul 28 15:24:27 2019
Tagged:         no
Form:           none
Pages:          1
Encrypted:      no
Page size:      841.89 x 595.276 pts (A4) (rotated 0 degrees)
File size:      1138 bytes
Optimized:      no
PDF version:    1.4
```

画像は Adobe Acrobat Reader DC で閲覧したもの。

<img width="482" alt="pdf.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/bcdedbad-67a1-e15e-4a65-a5a8abf1a67a.png">

### SVG (Scalable Vector Graphics)

SVG ファイルの情報をコマンドで確認する。

```
$ file svg.svg
svg.svg: SVG Scalable Vector Graphics image

$ identify svg.svg
svg.svg SVG 594x420 594x420+0+0 16-bit sRGB 1379B 0.000u 0:00.012
```

画像は Inkscape で閲覧したもの。ベクターデータのオブジェクトが確認できる。

<img width="505" alt="svg.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/b40a4d9d-e9a8-5902-bebe-1bd210228ec2.png">

SVG は XML 文書なので中身がテキストデータになっており確認しやすい。生成された SVG ファイルの中身をここに載せておく。

```svg
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.0//EN'
          'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd'>
<svg stroke-dasharray="none" shape-rendering="auto" xmlns="http://www.w3.org/2000/svg" font-family="'Dialog'" width="594" text-rendering="auto" fill-opacity="1" contentScriptType="text/ecmascript" color-interpolation="auto" color-rendering="auto" preserveAspectRatio="xMidYMid meet" font-size="12px" fill="black" xmlns:xlink="http://www.w3.org/1999/xlink" stroke="black" image-rendering="auto" stroke-miterlimit="10" zoomAndPan="magnify" version="1.0" stroke-linecap="square" stroke-linejoin="miter" contentStyleType="text/css" font-style="normal" height="420" stroke-width="1" stroke-dashoffset="0" font-weight="normal" stroke-opacity="1"
><!--Generated by the Batik Graphics2D SVG Generator--><defs id="genericDefs"
  /><g
  ><g fill="silver" stroke-width="50.7" stroke="silver"
    ><rect x="0" width="594" height="420" y="0" stroke="none"
      /><rect fill="none" x="0" width="594" height="420" y="0" stroke="blue"
      /><ellipse rx="148.5" fill="red" ry="105" cx="297" cy="210" stroke="none"
    /></g
    ><g fill="rgb(255,255,0)" fill-opacity="0.3922" stroke-width="50.7" stroke-opacity="0.3922" stroke="rgb(255,255,0)"
    ><line y2="210" fill="none" x1="74.25" x2="297" y1="52.5"
    /></g
  ></g
></svg
>
```

### PPT (PowerPoint)

PPT ファイルの情報をコマンドで確認する。

```
$ file ppt.ppt
ppt.ppt: Composite Document File V2 Document, Little Endian, Os: Windows, Version 5.1, Code page: 1252, Title: PowerPoint Presentation, Revision Number: 0, Name of Creating Application: Microsoft PowerPoint, Last Saved Time/Date: Mon Apr  7 20:47:14 2003, Number of Words: 0
```

画像は Keynote で閲覧したもの。ベクターデータのオブジェクトが確認できる。

<img width="557" alt="ppt.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/280ab19e-0ec9-1550-56ff-fa228f120a09.png">

### EPS (Encapsulated PostScript)

EPS ファイルの情報をコマンドで確認する。

```
$ file eps.eps
eps.eps: PostScript document text conforming DSC level 3.0, type EPS, Level 3
```

画像は macOS Mojave のプレビューアプリで閲覧したもの。ベクターデータのオブジェクトが確認できる。

<img width="552" alt="eps.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/d61e6e97-4510-835a-a2c0-55713ca6a0b1.png">

### SWF (Small Web Format / Shockwave Flash)

SWF ファイルの情報をコマンドで確認する。

```
$ file swf.swf
swf.swf: Macromedia Flash data (compressed), version 8
```

画像は Elmedia Video Player で閲覧したもの。

<img width="399" alt="swf.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/2646c400-df7e-920e-c29d-e01d8721c82f.png">

## 参考資料

### OrsonPDF
- [Orson PDF](http://www.object-refinery.com/orsonpdf/)
- [GitHub \- jfree/orsonpdf: A fast, lightweight PDF generator for the Java platform](https://github.com/jfree/orsonpdf)
- [Overview \(OrsonPDF version 1\.7\)](http://www.object-refinery.com/orsonpdf/javadoc/)

### Apache Batik
- [Apache\(tm\) Batik SVG Toolkit \- a Java\-based toolkit for applications or applets that want to use images in the Scalable Vector Graphics \(SVG\)](https://xmlgraphics.apache.org/batik/)
- [SVG Generator: SVGGraphics2D](https://xmlgraphics.apache.org/batik/using/svg-generator.html)
- [Overview \(Apache Batik Javadoc\)](https://xmlgraphics.apache.org/batik/javadoc/)

### Apache POI
- [POI\-HSLF and and POI\-XLSF \- Java API To Access Microsoft Powerpoint Format Files](https://poi.apache.org/components/slideshow/)
- [Busy Developers' Guide to HSLF drawing layer](https://poi.apache.org/components/slideshow/how-to-shapes.html)
- [Overview \(POI API Documentation\)](https://poi.apache.org/apidocs/4.1/)

### VectorGraphics2D
- [VectorGraphics2D](http://trac.erichseifert.de/vectorgraphics2d/)
- [GitHub \- eseifert/vectorgraphics2d: Graphics2D implementations to export various vector file formats](https://github.com/eseifert/vectorgraphics2d)
- [vectorgraphics2d/VectorGraphics2D\.java at master · eseifert/vectorgraphics2d · GitHub](https://github.com/eseifert/vectorgraphics2d/blob/master/src/main/java/de/erichseifert/vectorgraphics2d/VectorGraphics2D.java)

### FreeHEP VectorGraphics
- [freehep \- FreeHEP VectorGraphics \- Overview](http://java.freehep.org/vectorgraphics/)
- [freehep\-vectorgraphics/SWFGraphics2D\.java at master · freehep/freehep\-vectorgraphics · GitHub](https://github.com/freehep/freehep-vectorgraphics/blob/master/freehep-graphicsio-swf/src/main/java/org/freehep/graphicsio/swf/SWFGraphics2D.java)
- [Overview \(FreeHEP Vector Graphics 2\.2\.2\-SNAPSHOT API\)](http://freehep.github.io/freehep-vectorgraphics/apidocs/)