Skip to content

Refactor PDF exports for headless printing #891

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

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
@@ -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'
}
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)));
}
}

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
@@ -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) {
7 changes: 4 additions & 3 deletions src/megameklab/com/printing/PrintMech.java
Original file line number Diff line number Diff line change
@@ -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;

@@ -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;
@@ -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;
@@ -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;
}
14 changes: 9 additions & 5 deletions src/megameklab/com/printing/PrintRecordSheet.java
Original file line number Diff line number Diff line change
@@ -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;
@@ -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);
@@ -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();
}

/**
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));
}
}
73 changes: 19 additions & 54 deletions src/megameklab/com/printing/RecordSheetTask.java
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@
package megameklab.com.printing;

import java.awt.print.*;
import java.io.File;
import java.util.*;
import java.util.concurrent.ExecutionException;

@@ -23,11 +22,6 @@

import megamek.common.util.EncodeControl;
import megameklab.com.MegaMekLab;
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;

/**
* Renders one or more record sheets as a background task. The task is created using
@@ -39,12 +33,12 @@
public abstract class RecordSheetTask extends SwingWorker<Void, Integer> {

private final ProgressPopup popup;
protected final List<PrintRecordSheet> sheets;
protected final RecordSheetBook book;

private RecordSheetTask(List<PrintRecordSheet> sheets) {
this.sheets = sheets;
private RecordSheetTask(RecordSheetBook book) {
this.book = book;
int pages = 0;
for (PrintRecordSheet sheet : sheets) {
for (PrintRecordSheet sheet : book.getSheets()) {
sheet.setCallback(this::publish);
pages += sheet.getPageCount();
}
@@ -61,9 +55,9 @@ private RecordSheetTask(List<PrintRecordSheet> sheets) {
* @param pageFormat The page format
* @return A {@link SwingWorker} task
*/
public static RecordSheetTask createPrintTask(List<PrintRecordSheet> sheets, PrinterJob job,
public static RecordSheetTask createPrintTask(RecordSheetBook book, PrinterJob job,
PrintRequestAttributeSet aset, PageFormat pageFormat) {
return new PrintTask(sheets, job, aset, pageFormat);
return new PrintTask(book, job, aset, pageFormat);
}

/**
@@ -75,9 +69,9 @@ public static RecordSheetTask createPrintTask(List<PrintRecordSheet> sheets, Pri
* @param pathName The path to the PDF output file
* @return A {@link SwingWorker} task
*/
public static RecordSheetTask createExportTask(List<PrintRecordSheet> sheets, PageFormat pageFormat,
public static RecordSheetTask createExportTask(RecordSheetBook book, PageFormat pageFormat,
String pathName) {
return new ExportTask(sheets, pageFormat, pathName);
return new ExportTask(book, pageFormat, pathName);
}

/**
@@ -133,15 +127,14 @@ private static class PrintTask extends RecordSheetTask {
private final PrinterJob job;
private final PrintRequestAttributeSet aset;

public PrintTask(List<PrintRecordSheet> sheets, PrinterJob job, PrintRequestAttributeSet aset,
public PrintTask(RecordSheetBook book, PrinterJob job, PrintRequestAttributeSet aset,
PageFormat pageFormat) {
super(sheets);
super(book);
this.job = job;
this.aset = aset;

RSBook book = new RSBook(sheets, pageFormat);
sheets.clear();
job.setPageable(book);
PageableRecordSheetBook pageableRSBook = new PageableRecordSheetBook(book, pageFormat);
job.setPageable(pageableRSBook);
}

@Override
@@ -162,8 +155,8 @@ private static class ExportTask extends RecordSheetTask {
private final PageFormat pageFormat;
private final String fileName;

public ExportTask(List<PrintRecordSheet> sheets, PageFormat pageFormat, String fileName) {
super(sheets);
public ExportTask(RecordSheetBook book, PageFormat pageFormat, String fileName) {
super(book);
this.pageFormat = pageFormat;
this.fileName = fileName;
}
@@ -177,36 +170,8 @@ protected String popupLabel() {

@Override
public Void doInBackground() throws Exception {
PDFMergerUtility merger = new PDFMergerUtility();
merger.setDestinationFileName(fileName);
Map<Integer, List<String>> bookmarkNames = new HashMap<>();
Iterator<PrintRecordSheet> iter = sheets.iterator();
while (iter.hasNext()) {
final PrintRecordSheet rs = iter.next();
bookmarkNames.put(rs.getFirstPage(), rs.getBookmarkNames());
for (int i = 0; i < rs.getPageCount(); i++) {
merger.addSource(rs.exportPDF(i, pageFormat));
}
iter.remove();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sheet instances take up a lot more memory when processed, and large jobs can easily run out of memory. I tried clearing generated data from the PrintRecordSheet instance without success. The only way I found to handle the memory problems is to remove the sheets once they're added to the document. I don't see any reason the book needs to be reusable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll test this with a large document tonight, good to know that was the issue.

I found some other items that kept large amounts of memory rooted and will put up a separate PR.

}
merger.mergeDocuments(MemoryUsageSetting.setupTempFileOnly());

// Load newly created document, add an outline, then write back to the file.
File file = new File(fileName);
PDDocument doc = PDDocument.load(file);
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();
doc.save(file);
doc.close();
PdfRecordSheetExporter exporter = new PdfRecordSheetExporter();
exporter.exportToFile(book, pageFormat, fileName);
return null;
}
}
@@ -215,13 +180,13 @@ public Void doInBackground() throws Exception {
* Implementation of Pageable that removes the record sheet objects as they are processed
* (when the next one is accessed) to conserve memory.
*/
private static class RSBook implements Pageable {
private static class PageableRecordSheetBook implements Pageable {
private final TreeMap<Integer, PrintRecordSheet> pages = new TreeMap<>();
private final PageFormat pageFormat;

RSBook(List<PrintRecordSheet> sheets, PageFormat pageFormat) {
PageableRecordSheetBook(RecordSheetBook book, PageFormat pageFormat) {
this.pageFormat = pageFormat;
for (PrintRecordSheet rs : sheets) {
for (PrintRecordSheet rs : book.getSheets()) {
for (int p = rs.getFirstPage(); p < rs.getFirstPage() + rs.getPageCount(); p++) {
pages.put(p, rs);
}
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/Aero/StatusBar.java
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestAero;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -186,7 +187,7 @@ private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path",
FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath()
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + ImageHelper.imageMech
+ File.separatorChar);
fDialog.setLocationRelativeTo(this);
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/BattleArmor/StatusBar.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestBattleArmor;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -161,7 +162,7 @@ private void getFluffImage() {
// copied from structureTab
final FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path",
FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath()
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + ImageHelper.imageMech
+ File.separatorChar);
fDialog.setLocationRelativeTo(this);
5 changes: 3 additions & 2 deletions src/megameklab/com/ui/Infantry/StatusBar.java
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
import javax.swing.JLabel;

import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -109,7 +110,7 @@ public void refresh() {
private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path", FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
/*
//This does not seem to be working
if (getMech().getFluff().getMMLImagePath().trim().length() > 0) {
@@ -119,7 +120,7 @@ private void getFluffImage() {
fDialog.setDirectory(fullPath);
fDialog.setFile(imageName);
} else {
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setFile(getMech().getChassis() + " " + getMech().getModel() + ".png");
}
*/
5 changes: 3 additions & 2 deletions src/megameklab/com/ui/Mek/StatusBar.java
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestMech;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -215,7 +216,7 @@ public double calculateTotalHeat() {
private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path", FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
/*
//This does not seem to be working
if (getMech().getFluff().getMMLImagePath().trim().length() > 0) {
@@ -225,7 +226,7 @@ private void getFluffImage() {
fDialog.setDirectory(fullPath);
fDialog.setFile(imageName);
} else {
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setFile(getMech().getChassis() + " " + getMech().getModel() + ".png");
}
*/
5 changes: 3 additions & 2 deletions src/megameklab/com/ui/Vehicle/StatusBar.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestTank;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -188,7 +189,7 @@ public void refresh() {
private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path", FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
/*
//This does not seem to be working
if (getMech().getFluff().getMMLImagePath().trim().length() > 0) {
@@ -198,7 +199,7 @@ private void getFluffImage() {
fDialog.setDirectory(fullPath);
fDialog.setFile(imageName);
} else {
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setFile(getMech().getChassis() + " " + getMech().getModel() + ".png");
}
*/
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/aerospace/AdvancedAeroStatusBar.java
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestAdvancedAerospace;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -190,7 +191,7 @@ private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path",
FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath()
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + ImageHelper.imageMech
+ File.separatorChar);
fDialog.setLocationRelativeTo(this);
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/aerospace/DropshipStatusBar.java
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestSmallCraft;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -186,7 +187,7 @@ private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path",
FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath()
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + ImageHelper.imageMech
+ File.separatorChar);
fDialog.setLocationRelativeTo(this);
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/protomek/ProtomekStatusBar.java
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestProtomech;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.RefreshListener;
@@ -137,7 +138,7 @@ public void refresh() {
private void getFluffImage() {
//copied from mech StatusBar
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path", FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setLocationRelativeTo(this);

fDialog.setVisible(true);
3 changes: 2 additions & 1 deletion src/megameklab/com/ui/supportvehicle/SVStatusBar.java
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
import megamek.common.verifier.EntityVerifier;
import megamek.common.verifier.TestSupportVehicle;
import megameklab.com.ui.MegaMekLabMainUI;
import megameklab.com.util.CConfig;
import megameklab.com.util.ITab;
import megameklab.com.util.ImageHelper;
import megameklab.com.util.UnitUtil;
@@ -175,7 +176,7 @@ public void refresh() {
private void getFluffImage() {
//copied from structureTab
FileDialog fDialog = new FileDialog(getParentFrame(), "Image Path", FileDialog.LOAD);
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + ImageHelper.imageMech + File.separatorChar);
fDialog.setLocationRelativeTo(this);

fDialog.setVisible(true);
19 changes: 19 additions & 0 deletions src/megameklab/com/util/CConfig.java
Original file line number Diff line number Diff line change
@@ -102,6 +102,7 @@ public String shortName() {
public static final String CONFIG_SAVE_LOC = "Save-Location-Default";
public static final String CONFIG_PLAF = "lookAndFeel";

public static final String RS_DATA_LOC = "rs_data_loc";
public static final String RS_PAPER_SIZE = "rs_paper_size";
public static final String RS_COLOR = "rs_color";
public static final String RS_FONT = "rs_font";
@@ -116,6 +117,8 @@ public String shortName() {
public static final String RS_SCALE_FACTOR = "rs_scale_factor";
public static final String RS_SCALE_UNITS = "rs_scale_units";

public static final String FLUFF_DATA_LOC = "fluff_data_loc";

/**
* Player configuration values.
*/
@@ -140,6 +143,7 @@ private static Properties getDefaults() {
new File(System.getProperty("user.dir")
+ "/data/mechfiles/").getAbsolutePath());
defaults.setProperty(SUMMARY_FORMAT_TRO, Boolean.toString(true));
defaults.setProperty(RS_DATA_LOC, "data/images/recordsheets");
defaults.setProperty(RS_PROGRESS_BAR, Boolean.toString(true));
defaults.setProperty(RS_COLOR, Boolean.toString(true));
defaults.setProperty(RS_SHOW_QUIRKS, Boolean.toString(true));
@@ -148,6 +152,7 @@ private static Properties getDefaults() {
defaults.setProperty(RS_SHOW_PILOT_DATA, Boolean.toString(true));
defaults.setProperty(RS_SCALE_FACTOR, "1");
defaults.setProperty(RS_SCALE_UNITS, RSScale.HEXES.toString());
defaults.setProperty(FLUFF_DATA_LOC, "data/images/fluff");

return defaults;
}
@@ -409,4 +414,18 @@ public static String formatScale(double val, boolean showUnits) {
return Integer.toString(retVal);
}
}

/**
* Gets the path on disk to the record sheets.
*/
public static String getRecordSheetsPath() {
return getParam(RS_DATA_LOC);
}

/**
* Gets the path on disk to the fluff images.
*/
public static String getFluffImagesPath() {
return getParam(FLUFF_DATA_LOC);
}
}
9 changes: 3 additions & 6 deletions src/megameklab/com/util/ImageHelper.java
Original file line number Diff line number Diff line change
@@ -24,9 +24,6 @@
import megamek.common.Entity;

public class ImageHelper {
public static String fluffPath = "./data/images/fluff/";
public static String imagePath = "./data/images/";

public static String imageMech = "mech";
public static String imageAero = "aero";
public static String imageBattleArmor = "BattleArmor";
@@ -50,7 +47,7 @@ public class ImageHelper {
* @return A file to use for the fluff image, or null if no file is found.
*/
public static File getFluffFile(Entity unit, String dir) {
String path = new File(fluffPath).getAbsolutePath();
String path = new File(CConfig.getFluffImagesPath()).getAbsolutePath();
File f;

if (unit.getFluff().getMMLImagePath().length() > 0) {
@@ -93,7 +90,7 @@ public static Image getFluffImage(String image) {

Image fluff;

String path = new File(fluffPath).getAbsolutePath()
String path = new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + image;

if (!(new File(path).exists())) {
@@ -110,7 +107,7 @@ public static Image getFluffImage(String image) {
public static Image getFluffImage(Entity unit, String dir) {
Image fluff;

String path = new File(fluffPath).getAbsolutePath()
String path = new File(CConfig.getFluffImagesPath()).getAbsolutePath()
+ File.separatorChar + dir + File.separatorChar;

fluff = ImageHelper.getFluffImage(unit.getFluff().getMMLImagePath());
2 changes: 1 addition & 1 deletion src/megameklab/com/util/MenuBarCreator.java
Original file line number Diff line number Diff line change
@@ -716,7 +716,7 @@ private void jMenuInsertImageFile_actionPerformed() {
fDialog.setDirectory(fullPath);
fDialog.setFile(imageName);
} else {
fDialog.setDirectory(new File(ImageHelper.fluffPath).getAbsolutePath() + File.separatorChar + "mech" + File.separatorChar);
fDialog.setDirectory(new File(CConfig.getFluffImagesPath()).getAbsolutePath() + File.separatorChar + "mech" + File.separatorChar);
fDialog.setFile(parentFrame.getEntity().getChassis() + " " + parentFrame.getEntity().getModel() + ".png");
}

97 changes: 12 additions & 85 deletions src/megameklab/com/util/UnitPrintManager.java
Original file line number Diff line number Diff line change
@@ -160,101 +160,28 @@ private static File getExportFile(Frame parent, String suggestedFileName) {
return f.getSelectedFile();
}

private static List<PrintRecordSheet> createSheets(List<Entity> entities, boolean singlePrint,
private static RecordSheetBook createSheets(List<Entity> entities, boolean singlePrint,
RecordSheetOptions options) {
List<PrintRecordSheet> sheets = new ArrayList<>();
List<Infantry> infList = new ArrayList<>();
List<BattleArmor> baList = new ArrayList<>();
List<Protomech> protoList = new ArrayList<>();
List<Entity> unprintable = new ArrayList<>();
Tank tank1 = null;

int pageCount = 0;
for (Entity unit : entities) {
if (unit instanceof Mech) {
UnitUtil.removeOneShotAmmo(unit);
UnitUtil.expandUnitMounts((Mech) unit);
sheets.add(new PrintMech((Mech) unit, pageCount++, options));
} else if ((unit instanceof Tank) && ((unit.getMovementMode() == EntityMovementMode.NAVAL) || (unit.getMovementMode() == EntityMovementMode.SUBMARINE) || (unit.getMovementMode() == EntityMovementMode.HYDROFOIL))) {
sheets.add(new PrintTank((Tank) unit, pageCount++, options));
} else if (unit instanceof Tank) {
if (singlePrint || options.showReferenceCharts()) {
sheets.add(new PrintCompositeTankSheet((Tank) unit, null, pageCount++, options));
} else if (null != tank1) {
sheets.add(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();
sheets.add(pcs);
} else if (unit instanceof Dropship) {
PrintDropship pds = new PrintDropship((Aero) unit, pageCount, options);
pageCount += pds.getPageCount();
sheets.add(pds);
} else {
sheets.add(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();
sheets.add(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();
sheets.add(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();
sheets.add(prs);
protoList = new ArrayList<>();
}
} else {
//TODO: show a message dialog that lists the unprintable units
unprintable.add(unit);
}
}
RecordSheetBook book = new RecordSheetBookBuilder()
.setSinglePrint(singlePrint)
.setRecordSheetOptions(options)
.addEntities(entities).build();

if (unprintable.size() > 0) {
if (book.hasUnprintableEntities()) {
JOptionPane.showMessageDialog(null, "Exporting is not currently supported for the following units:\n"
+ unprintable.stream().map(en -> en.getChassis() + " " + en.getModel())
+ book.getUnprintableEntities().stream().map(en -> en.getChassis() + " " + en.getModel())
.collect(Collectors.joining("\n")));
}

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

public static void exportUnits(List<Entity> units, File exportFile, boolean singlePrint) {
RecordSheetOptions options = new RecordSheetOptions();
List<PrintRecordSheet> sheets = createSheets(units, singlePrint, options);
RecordSheetBook book = createSheets(units, singlePrint, options);
PageFormat pageFormat = new PageFormat();
pageFormat.setPaper(options.getPaperSize().createPaper());
RecordSheetTask task = RecordSheetTask.createExportTask(sheets, pageFormat, exportFile.getAbsolutePath());
RecordSheetTask task = RecordSheetTask.createExportTask(book, pageFormat, exportFile.getAbsolutePath());
task.execute(CConfig.getBooleanParam(CConfig.RS_PROGRESS_BAR));
}

@@ -290,15 +217,15 @@ public static void printAllUnits(List<Entity> loadedUnits, boolean singlePrint,
// If something besides letter and A4 is selected, use the template that's closest to the aspect
// ratio of the paper size.
options.setPaperSize(PaperSize.closestToAspect(pageFormat.getWidth(), pageFormat.getHeight()));
List<PrintRecordSheet> sheets = createSheets(loadedUnits, singlePrint, options);
RecordSheetBook book = createSheets(loadedUnits, singlePrint, options);

if (loadedUnits.size() > 1) {
masterPrintJob.setJobName(loadedUnits.get(0).getShortNameRaw() + " etc");
} else if (loadedUnits.size() > 0) {
masterPrintJob.setJobName(loadedUnits.get(0).getShortNameRaw());
}

RecordSheetTask task = RecordSheetTask.createPrintTask(sheets, masterPrintJob, aset, pageFormat);
RecordSheetTask task = RecordSheetTask.createPrintTask(book, masterPrintJob, aset, pageFormat);
task.execute(CConfig.getBooleanParam(CConfig.RS_PROGRESS_BAR));
}