Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor PDF exports for headless printing #891

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
17 changes: 10 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,20 @@ configurations {
dependencies {
implementation "org.megamek:megamek${mmBranchTag}:${version}"

implementation 'org.apache.xmlgraphics:batik-dom:1.10'
implementation 'org.apache.xmlgraphics:batik-codec:1.10'
implementation 'org.apache.xmlgraphics:batik-rasterizer:1.10'
implementation ('org.apache.xmlgraphics:batik-bridge:1.10') {
implementation 'org.apache.xmlgraphics:batik-dom:1.13'
implementation 'org.apache.xmlgraphics:batik-codec:1.13'
implementation 'org.apache.xmlgraphics:batik-rasterizer:1.13'
implementation ('org.apache.xmlgraphics:batik-bridge:1.13') {
// We don't need the python and javascript engine taking up space
exclude group: 'org.python', module: 'jython'
exclude group: 'org.mozilla', module: 'rhino'
}
implementation 'org.apache.xmlgraphics:batik-svggen:1.10'
implementation 'org.apache.xmlgraphics:fop:2.3'
implementation 'org.apache.pdfbox:pdfbox:2.0.19'
implementation 'org.apache.xmlgraphics:batik-svggen:1.13'
implementation ('org.apache.xmlgraphics:fop:2.5') {
// We don't need this proprietary module
exclude group: 'com.sun.media', module: 'jai-codec'
}
implementation 'org.apache.pdfbox:pdfbox:2.0.22'

jarbundler 'com.ultramixer.jarbundler:jarbundler-core:3.3.0'
}
Expand Down
91 changes: 91 additions & 0 deletions src/megameklab/com/printing/PdfRecordSheetExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package megameklab.com.printing;

import java.awt.print.PageFormat;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.xml.sax.SAXException;

import megamek.common.annotations.Nullable;

public class PdfRecordSheetExporter {
private final MemoryUsageSetting memoryUsageSetting;
private final Configuration cfg;

public PdfRecordSheetExporter() {
this(MemoryUsageSetting.setupTempFileOnly(), null);
}

public PdfRecordSheetExporter(MemoryUsageSetting memoryUsageSetting, @Nullable Configuration cfg) {
this.memoryUsageSetting = Objects.requireNonNull(memoryUsageSetting);
this.cfg = cfg;
}

public void exportToFile(RecordSheetBook book, PageFormat pageFormat, String fileName)
throws IOException, ConfigurationException, TranscoderException, SAXException {
PDFMergerUtility merger = new PDFMergerUtility();
merger.setDestinationFileName(fileName);

Map<Integer, List<String>> bookmarkNames = new HashMap<>();
addSheetsToPdf(merger, book, pageFormat, bookmarkNames);

// Load newly created document, add an outline, then write back to the file.
File file = new File(fileName);
try (PDDocument doc = PDDocument.load(file)) {
addBookmarksToDocument(bookmarkNames, doc);
doc.save(file);
}
}

private void addBookmarksToDocument(Map<Integer, List<String>> bookmarkNames, PDDocument doc) {
PDDocumentOutline outline = new PDDocumentOutline();
doc.getDocumentCatalog().setDocumentOutline(outline);
for (Map.Entry<Integer, List<String>> entry : bookmarkNames.entrySet()) {
for (String name : entry.getValue()) {
PDOutlineItem bookmark = new PDOutlineItem();
bookmark.setDestination(doc.getPage(entry.getKey()));
bookmark.setTitle(name);
outline.addLast(bookmark);
}
}

outline.openNode();
}

private void addSheetsToPdf(PDFMergerUtility merger, RecordSheetBook book, PageFormat pageFormat,
Map<Integer, List<String>> bookmarkNames)
throws TranscoderException, SAXException, IOException, ConfigurationException {
List<PrintRecordSheet> sheets = book.getSheets();
for (PrintRecordSheet rs : sheets) {
bookmarkNames.put(rs.getFirstPage(), rs.getBookmarkNames());
for (int i = 0; i < rs.getPageCount(); i++) {
merger.addSource(rs.exportPDF(i, pageFormat, getOrCreateConfiguration(rs)));
sixlettervariables marked this conversation as resolved.
Show resolved Hide resolved
}
}

merger.mergeDocuments(memoryUsageSetting);
}

private Configuration getOrCreateConfiguration(PrintRecordSheet sheet)
throws ConfigurationException, SAXException, IOException {
if (cfg != null) {
return cfg;
} else {
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
return cfgBuilder.build(sheet.getClass().getResourceAsStream("fop-config.xml"));
}
}
}
12 changes: 6 additions & 6 deletions src/megameklab/com/printing/PrintEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -453,17 +453,17 @@ protected void drawFluffImage() {
private void drawEraIcon() {
File iconFile;
if (getEntity().getYear() < 2781) {
iconFile = new File("data/images/recordsheets/era_starleague.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_starleague.png");
} else if (getEntity().getYear() < 3050) {
iconFile = new File("data/images/recordsheets/era_sw.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_sw.png");
} else if (getEntity().getYear() < 3061) {
iconFile = new File("data/images/recordsheets/era_claninvasion.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_claninvasion.png");
} else if (getEntity().getYear() < 3068) {
iconFile = new File("data/images/recordsheets/era_civilwar.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_civilwar.png");
} else if (getEntity().getYear() < 3086) {
iconFile = new File("data/images/recordsheets/era_jihad.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_jihad.png");
} else {
iconFile = new File("data/images/recordsheets/era_darkage.png");
iconFile = new File(CConfig.getRecordSheetsPath(), "era_darkage.png");
}
Element rect = getSVGDocument().getElementById(ERA_ICON);
if (rect instanceof SVGRectElement) {
Expand Down
7 changes: 4 additions & 3 deletions src/megameklab/com/printing/PrintMech.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import megamek.common.annotations.Nullable;
import megameklab.com.MegaMekLab;
import megameklab.com.util.CConfig;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.UnitUtil;

Expand Down Expand Up @@ -289,7 +290,7 @@ private boolean loadArmorPips(int loc, boolean rear) {
}
}

NodeList nl = loadPipSVG(String.format("data/images/recordsheets/biped_pips/Armor_%s_%d_Humanoid.svg",
NodeList nl = loadPipSVG(String.format("biped_pips/Armor_%s_%d_Humanoid.svg",
locAbbr, mech.getOArmor(loc, rear)));
if (null == nl) {
return false;
Expand All @@ -298,7 +299,7 @@ private boolean loadArmorPips(int loc, boolean rear) {
}

private boolean loadISPips() {
NodeList nl = loadPipSVG(String.format("data/images/recordsheets/biped_pips/BipedIS%d.svg",
NodeList nl = loadPipSVG(String.format("biped_pips/BipedIS%d.svg",
(int) mech.getWeight()));
if (null == nl) {
return false;
Expand All @@ -320,7 +321,7 @@ private boolean copyPipPattern(NodeList nl, String parentName) {
}

private @Nullable NodeList loadPipSVG(String filename) {
File f = new File(filename);
File f = new File(CConfig.getRecordSheetsPath(), filename);
if (!f.exists()) {
return null;
}
Expand Down
14 changes: 9 additions & 5 deletions src/megameklab/com/printing/PrintRecordSheet.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import megameklab.com.MegaMekLab;
import megameklab.com.printing.reference.ReferenceTable;
import megameklab.com.util.CConfig;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGLocatableSupport;
import org.apache.batik.bridge.BridgeContext;
Expand Down Expand Up @@ -342,9 +342,13 @@ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
}

public InputStream exportPDF(int pageNumber, PageFormat pageFormat) throws TranscoderException, SAXException, IOException, ConfigurationException {
createDocument(pageNumber + firstPage, pageFormat, true);
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = cfgBuilder.build(getClass().getResourceAsStream("fop-config.xml"));
return exportPDF(pageNumber, pageFormat, cfg);
}

public InputStream exportPDF(int pageNumber, PageFormat pageFormat, Configuration cfg) throws TranscoderException, SAXException, IOException, ConfigurationException {
createDocument(pageNumber + firstPage, pageFormat, true);
PDFTranscoder transcoder = new PDFTranscoder();
transcoder.configure(cfg);
transcoder.addTranscodingHint(PDFTranscoder.KEY_AUTO_FONTS, false);
Expand Down Expand Up @@ -402,7 +406,7 @@ protected void processImage(int pageNum, PageFormat pageFormat) {
}

String getSVGDirectoryName() {
return "data/images/recordsheets/" + options.getPaperSize().dirName;
return new File(CConfig.getRecordSheetsPath(), options.getPaperSize().dirName).getPath();
}

/**
Expand Down
33 changes: 33 additions & 0 deletions src/megameklab/com/printing/RecordSheetBook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package megameklab.com.printing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import megamek.common.Entity;

public class RecordSheetBook {
private final List<PrintRecordSheet> sheets = new ArrayList<>();
private final List<Entity> unprintable = new ArrayList<>();

public List<PrintRecordSheet> getSheets() {
return Collections.unmodifiableList(sheets);
}

public void addSheet(PrintRecordSheet recordSheet) {
sheets.add(Objects.requireNonNull(recordSheet));
}

public boolean hasUnprintableEntities() {
return !unprintable.isEmpty();
}

public List<Entity> getUnprintableEntities() {
return Collections.unmodifiableList(unprintable);
}

public void addUnprintableEntity(Entity entity) {
unprintable.add(Objects.requireNonNull(entity));
}
}
127 changes: 127 additions & 0 deletions src/megameklab/com/printing/RecordSheetBookBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package megameklab.com.printing;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import megamek.common.*;
import megameklab.com.util.UnitUtil;

public class RecordSheetBookBuilder {
private final List<Entity> entities = new ArrayList<>();
private boolean isSinglePrint;
private RecordSheetOptions recordSheetOptions;

public RecordSheetBookBuilder setSinglePrint(boolean singlePrint) {
isSinglePrint = singlePrint;
return this;
}

public RecordSheetBookBuilder setRecordSheetOptions(RecordSheetOptions options) {
this.recordSheetOptions = options;
return this;
}

public RecordSheetBookBuilder addEntity(Entity entity) {
entities.add(entity);
return this;
}

public RecordSheetBookBuilder addEntities(Collection<Entity> entities) {
this.entities.addAll(entities);
return this;
}

public RecordSheetBook build() {
final boolean singlePrint = this.isSinglePrint;
final RecordSheetOptions options = (this.recordSheetOptions != null)
? this.recordSheetOptions : new RecordSheetOptions();

RecordSheetBook book = new RecordSheetBook();

List<Infantry> infList = new ArrayList<>();
List<BattleArmor> baList = new ArrayList<>();
List<Protomech> protoList = new ArrayList<>();
Tank tank1 = null;

int pageCount = 0;
for (Entity unit : entities) {
if (unit instanceof Mech) {
UnitUtil.removeOneShotAmmo(unit);
UnitUtil.expandUnitMounts((Mech) unit);
book.addSheet(new PrintMech((Mech) unit, pageCount++, options));
} else if ((unit instanceof Tank) && isSingleTankRecordSheet(unit)) {
book.addSheet(new PrintTank((Tank) unit, pageCount++, options));
} else if (unit instanceof Tank) {
if (singlePrint || options.showReferenceCharts()) {
book.addSheet(new PrintCompositeTankSheet((Tank) unit, null, pageCount++, options));
} else if (null != tank1) {
book.addSheet(new PrintCompositeTankSheet(tank1, (Tank) unit, pageCount++, options));
tank1 = null;
} else {
tank1 = (Tank) unit;
}
} else if (unit.hasETypeFlag(Entity.ETYPE_AERO)) {
if (unit instanceof Jumpship) {
PrintCapitalShip pcs = new PrintCapitalShip((Jumpship) unit, pageCount, options);
pageCount += pcs.getPageCount();
book.addSheet(pcs);
} else if (unit instanceof Dropship) {
PrintDropship pds = new PrintDropship((Aero) unit, pageCount, options);
pageCount += pds.getPageCount();
book.addSheet(pds);
} else {
book.addSheet(new PrintAero((Aero) unit, pageCount++, options));
}
} else if (unit instanceof BattleArmor) {
baList.add((BattleArmor) unit);
if (singlePrint || baList.size() > 4) {
PrintRecordSheet prs = new PrintSmallUnitSheet(baList, pageCount, options);
pageCount += prs.getPageCount();
book.addSheet(prs);
baList = new ArrayList<>();
}
} else if (unit instanceof Infantry) {
infList.add((Infantry) unit);
if (singlePrint || infList.size() > (options.showReferenceCharts() ? 2 : 3)) {
PrintRecordSheet prs = new PrintSmallUnitSheet(infList, pageCount, options);
pageCount += prs.getPageCount();
book.addSheet(prs);
infList = new ArrayList<>();
}
} else if (unit instanceof Protomech) {
protoList.add((Protomech) unit);
if (singlePrint || protoList.size() > 4) {
PrintRecordSheet prs = new PrintSmallUnitSheet(protoList, pageCount, options);
pageCount += prs.getPageCount();
book.addSheet(prs);
protoList = new ArrayList<>();
}
} else if (unit != null) {
book.addUnprintableEntity(unit);
}
}

if (null != tank1) {
book.addSheet(new PrintCompositeTankSheet(tank1, null, pageCount++));
}
if (baList.size() > 0) {
book.addSheet(new PrintSmallUnitSheet(baList, pageCount++));
}
if (infList.size() > 0) {
book.addSheet(new PrintSmallUnitSheet(infList, pageCount++));
}
if (protoList.size() > 0) {
book.addSheet(new PrintSmallUnitSheet(protoList, pageCount));
}

return book;
}

private static boolean isSingleTankRecordSheet(Entity unit) {
return (unit instanceof Tank)
&& ((unit.getMovementMode() == EntityMovementMode.NAVAL)
|| (unit.getMovementMode() == EntityMovementMode.SUBMARINE)
|| (unit.getMovementMode() == EntityMovementMode.HYDROFOIL));
}
}
Loading