diff --git a/build.gradle b/build.gradle index 4b05d3c44..e43c47493 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } diff --git a/src/megameklab/com/printing/PdfRecordSheetExporter.java b/src/megameklab/com/printing/PdfRecordSheetExporter.java new file mode 100644 index 000000000..6a4f6516b --- /dev/null +++ b/src/megameklab/com/printing/PdfRecordSheetExporter.java @@ -0,0 +1,120 @@ +package megameklab.com.printing; + +import java.awt.print.PageFormat; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +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; + +/** + * Exports {@link RecordSheetBook} instances to a PDF. + */ +public class PdfRecordSheetExporter { + private final MemoryUsageSetting memoryUsageSetting; + private final Configuration cfg; + + /** + * Creates a new exporter using temp files for memory management. + */ + public PdfRecordSheetExporter() { + this(MemoryUsageSetting.setupTempFileOnly(), null); + } + + /** + * Creates a new exporter using the supplied memory usage settings and optional configuration. + * @param memoryUsageSetting The {@link MemoryUsageSetting} to use when exporting the PDF. + * @param cfg The {@link Configuration} to use when exporting the PDF, or {@code null} if the + * default configuration should be used. + */ + public PdfRecordSheetExporter(MemoryUsageSetting memoryUsageSetting, @Nullable Configuration cfg) { + this.memoryUsageSetting = Objects.requireNonNull(memoryUsageSetting); + this.cfg = cfg; + } + + /** + * Exports a {@link RecordSheetBook} to a PDF. + * @param book The {@link RecordSheetBook} to export. + * @param pageFormat The {@link PageFormat} to use with the resulting PDF. + * @param fileName The file name to save the resulting PDF. + * @throws IOException + * @throws ConfigurationException + * @throws TranscoderException + * @throws SAXException + */ + public void exportToFile(RecordSheetBook book, PageFormat pageFormat, String fileName) + throws IOException, ConfigurationException, TranscoderException, SAXException { + PDFMergerUtility merger = new PDFMergerUtility(); + merger.setDestinationFileName(fileName); + + Map> 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> bookmarkNames, PDDocument doc) { + PDDocumentOutline outline = new PDDocumentOutline(); + doc.getDocumentCatalog().setDocumentOutline(outline); + for (Map.Entry> 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> bookmarkNames) + throws TranscoderException, SAXException, IOException, ConfigurationException { + Iterator sheets = book.takeSheets().iterator(); + Configuration configuration = getOrCreateConfiguration(); + while (sheets.hasNext()) { + PrintRecordSheet rs = sheets.next(); + + // Ensure we do not hold onto the PrintRecordSheet instance any longer than necessary + sheets.remove(); + + bookmarkNames.put(rs.getFirstPage(), rs.getBookmarkNames()); + for (int i = 0; i < rs.getPageCount(); i++) { + merger.addSource(rs.exportPDF(i, pageFormat, configuration)); + } + } + + merger.mergeDocuments(memoryUsageSetting); + } + + private Configuration getOrCreateConfiguration() + throws ConfigurationException, SAXException, IOException { + if (cfg != null) { + return cfg; + } else { + DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder(); + return cfgBuilder.build(getClass().getResourceAsStream("fop-config.xml")); + } + } +} diff --git a/src/megameklab/com/printing/PrintEntity.java b/src/megameklab/com/printing/PrintEntity.java index 1cc1c33ad..4283358dc 100644 --- a/src/megameklab/com/printing/PrintEntity.java +++ b/src/megameklab/com/printing/PrintEntity.java @@ -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) { diff --git a/src/megameklab/com/printing/PrintMech.java b/src/megameklab/com/printing/PrintMech.java index a39bc09cd..669f36cd4 100644 --- a/src/megameklab/com/printing/PrintMech.java +++ b/src/megameklab/com/printing/PrintMech.java @@ -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; } diff --git a/src/megameklab/com/printing/PrintRecordSheet.java b/src/megameklab/com/printing/PrintRecordSheet.java index 8136f2b04..adc346f33 100644 --- a/src/megameklab/com/printing/PrintRecordSheet.java +++ b/src/megameklab/com/printing/PrintRecordSheet.java @@ -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(); } /** diff --git a/src/megameklab/com/printing/RecordSheetBook.java b/src/megameklab/com/printing/RecordSheetBook.java new file mode 100644 index 000000000..3a13a548e --- /dev/null +++ b/src/megameklab/com/printing/RecordSheetBook.java @@ -0,0 +1,89 @@ +package megameklab.com.printing; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +import megamek.common.Entity; + +/** + * Represents a book of {@link PrintRecordSheet} instances. + */ +public class RecordSheetBook { + private List sheets = new ArrayList<>(); + private List unprintable = new ArrayList<>(); + private int pageCount; + + /** + * Takes the {@link PrintRecordSheet} entries out of the book + * and resets the sheets and unprintable entities. Callers + * should save the list of unprintable entities prior to + * calling {@code takeSheets}. + * + * @return The list of {@link PrintRecordSheet} entries from this + * book. The caller now owns this reference. + */ + public List takeSheets() { + List taken = sheets; + + sheets = new ArrayList<>(); + unprintable = new ArrayList<>(); + pageCount = 0; + + return taken; + } + + /** + * Executes an action on each {@link PrintRecordSheet} in the book. + * + * @param consumer The action to execute on each sheet in the book. + */ + public void forEachSheet(Consumer consumer) { + for (PrintRecordSheet sheet : sheets) { + consumer.accept(sheet); + } + } + + /** + * Gets the total page count. + */ + public int getPageCount() { + return pageCount; + } + + /** + * Adds a record sheet to the book. + * @param recordSheet The {@link PrintRecordSheet} to add to the book. + */ + public void addSheet(PrintRecordSheet recordSheet) { + sheets.add(Objects.requireNonNull(recordSheet)); + pageCount += recordSheet.getPageCount(); + } + + /** + * Gets a value indicating whether or not the book + * included at least one unprintable entity. + */ + public boolean hasUnprintableEntities() { + return !unprintable.isEmpty(); + } + + /** + * Gets a list of the unprintable entities. + * @return A list of unprintable entities. + */ + public List getUnprintableEntities() { + return Collections.unmodifiableList(unprintable); + } + + /** + * Adds an unprintable entity to the book. + * @param entity An {@link Entity} which could not be + * converted into a {@link PrintRecordSheet}. + */ + public void addUnprintableEntity(Entity entity) { + unprintable.add(Objects.requireNonNull(entity)); + } +} diff --git a/src/megameklab/com/printing/RecordSheetBookBuilder.java b/src/megameklab/com/printing/RecordSheetBookBuilder.java new file mode 100644 index 000000000..4fa1ab7c8 --- /dev/null +++ b/src/megameklab/com/printing/RecordSheetBookBuilder.java @@ -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 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 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 infList = new ArrayList<>(); + List baList = new ArrayList<>(); + List 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)); + } +} diff --git a/src/megameklab/com/printing/RecordSheetTask.java b/src/megameklab/com/printing/RecordSheetTask.java index bc3d59626..9a7f95312 100644 --- a/src/megameklab/com/printing/RecordSheetTask.java +++ b/src/megameklab/com/printing/RecordSheetTask.java @@ -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,16 +33,16 @@ public abstract class RecordSheetTask extends SwingWorker { private final ProgressPopup popup; - protected final List sheets; + protected final RecordSheetBook book; - private RecordSheetTask(List sheets) { - this.sheets = sheets; - int pages = 0; - for (PrintRecordSheet sheet : sheets) { + private RecordSheetTask(RecordSheetBook book) { + this.book = Objects.requireNonNull(book); + + book.forEachSheet(sheet -> { sheet.setCallback(this::publish); - pages += sheet.getPageCount(); - } - popup = new ProgressPopup(pages, popupLabel()); + }); + + popup = new ProgressPopup(book.getPageCount(), popupLabel()); } /** @@ -61,9 +55,9 @@ private RecordSheetTask(List sheets) { * @param pageFormat The page format * @return A {@link SwingWorker} task */ - public static RecordSheetTask createPrintTask(List 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 sheets, Pri * @param pathName The path to the PDF output file * @return A {@link SwingWorker} task */ - public static RecordSheetTask createExportTask(List 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 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 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> bookmarkNames = new HashMap<>(); - Iterator 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(); - } - 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> 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 pages = new TreeMap<>(); private final PageFormat pageFormat; - RSBook(List sheets, PageFormat pageFormat) { + PageableRecordSheetBook(RecordSheetBook book, PageFormat pageFormat) { this.pageFormat = pageFormat; - for (PrintRecordSheet rs : sheets) { + for (PrintRecordSheet rs : book.takeSheets()) { for (int p = rs.getFirstPage(); p < rs.getFirstPage() + rs.getPageCount(); p++) { pages.put(p, rs); } diff --git a/src/megameklab/com/ui/Aero/StatusBar.java b/src/megameklab/com/ui/Aero/StatusBar.java index 8c4bba4e9..8ce93455e 100644 --- a/src/megameklab/com/ui/Aero/StatusBar.java +++ b/src/megameklab/com/ui/Aero/StatusBar.java @@ -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); diff --git a/src/megameklab/com/ui/BattleArmor/StatusBar.java b/src/megameklab/com/ui/BattleArmor/StatusBar.java index ca5f68e4e..607f35d75 100644 --- a/src/megameklab/com/ui/BattleArmor/StatusBar.java +++ b/src/megameklab/com/ui/BattleArmor/StatusBar.java @@ -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); diff --git a/src/megameklab/com/ui/Infantry/StatusBar.java b/src/megameklab/com/ui/Infantry/StatusBar.java index be315d0e8..416538202 100644 --- a/src/megameklab/com/ui/Infantry/StatusBar.java +++ b/src/megameklab/com/ui/Infantry/StatusBar.java @@ -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"); } */ diff --git a/src/megameklab/com/ui/Mek/StatusBar.java b/src/megameklab/com/ui/Mek/StatusBar.java index e03eaf793..c6a286f5b 100644 --- a/src/megameklab/com/ui/Mek/StatusBar.java +++ b/src/megameklab/com/ui/Mek/StatusBar.java @@ -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"); } */ diff --git a/src/megameklab/com/ui/Vehicle/StatusBar.java b/src/megameklab/com/ui/Vehicle/StatusBar.java index 535863f4f..feebe4756 100644 --- a/src/megameklab/com/ui/Vehicle/StatusBar.java +++ b/src/megameklab/com/ui/Vehicle/StatusBar.java @@ -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"); } */ diff --git a/src/megameklab/com/ui/aerospace/AdvancedAeroStatusBar.java b/src/megameklab/com/ui/aerospace/AdvancedAeroStatusBar.java index 123fc1f94..e8d0390b1 100644 --- a/src/megameklab/com/ui/aerospace/AdvancedAeroStatusBar.java +++ b/src/megameklab/com/ui/aerospace/AdvancedAeroStatusBar.java @@ -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); diff --git a/src/megameklab/com/ui/aerospace/DropshipStatusBar.java b/src/megameklab/com/ui/aerospace/DropshipStatusBar.java index 73effb8f6..06c79c747 100644 --- a/src/megameklab/com/ui/aerospace/DropshipStatusBar.java +++ b/src/megameklab/com/ui/aerospace/DropshipStatusBar.java @@ -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); diff --git a/src/megameklab/com/ui/protomek/ProtomekStatusBar.java b/src/megameklab/com/ui/protomek/ProtomekStatusBar.java index 03d30c5a8..27986de8b 100644 --- a/src/megameklab/com/ui/protomek/ProtomekStatusBar.java +++ b/src/megameklab/com/ui/protomek/ProtomekStatusBar.java @@ -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); diff --git a/src/megameklab/com/ui/supportvehicle/SVStatusBar.java b/src/megameklab/com/ui/supportvehicle/SVStatusBar.java index fbe4b5668..5a5ec2cbf 100644 --- a/src/megameklab/com/ui/supportvehicle/SVStatusBar.java +++ b/src/megameklab/com/ui/supportvehicle/SVStatusBar.java @@ -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); diff --git a/src/megameklab/com/util/CConfig.java b/src/megameklab/com/util/CConfig.java index da4291594..5db76cf98 100644 --- a/src/megameklab/com/util/CConfig.java +++ b/src/megameklab/com/util/CConfig.java @@ -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); + } } diff --git a/src/megameklab/com/util/ImageHelper.java b/src/megameklab/com/util/ImageHelper.java index 47f300def..1cafb8a63 100644 --- a/src/megameklab/com/util/ImageHelper.java +++ b/src/megameklab/com/util/ImageHelper.java @@ -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()); diff --git a/src/megameklab/com/util/MenuBarCreator.java b/src/megameklab/com/util/MenuBarCreator.java index 05953c75a..62e1e6328 100644 --- a/src/megameklab/com/util/MenuBarCreator.java +++ b/src/megameklab/com/util/MenuBarCreator.java @@ -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"); } diff --git a/src/megameklab/com/util/UnitPrintManager.java b/src/megameklab/com/util/UnitPrintManager.java index 40026a118..0890a52a0 100644 --- a/src/megameklab/com/util/UnitPrintManager.java +++ b/src/megameklab/com/util/UnitPrintManager.java @@ -160,101 +160,28 @@ private static File getExportFile(Frame parent, String suggestedFileName) { return f.getSelectedFile(); } - private static List createSheets(List entities, boolean singlePrint, + private static RecordSheetBook createSheets(List entities, boolean singlePrint, RecordSheetOptions options) { - List sheets = new ArrayList<>(); - List infList = new ArrayList<>(); - List baList = new ArrayList<>(); - List protoList = new ArrayList<>(); - List 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 units, File exportFile, boolean singlePrint) { RecordSheetOptions options = new RecordSheetOptions(); - List 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,7 +217,7 @@ public static void printAllUnits(List 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 sheets = createSheets(loadedUnits, singlePrint, options); + RecordSheetBook book = createSheets(loadedUnits, singlePrint, options); if (loadedUnits.size() > 1) { masterPrintJob.setJobName(loadedUnits.get(0).getShortNameRaw() + " etc"); @@ -298,7 +225,7 @@ public static void printAllUnits(List loadedUnits, boolean singlePrint, 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)); }