001package jmri.jmrit.operations.trains; 002 003import java.awt.*; 004import java.io.*; 005import java.nio.charset.StandardCharsets; 006import java.util.ArrayList; 007import java.util.List; 008 009import javax.print.attribute.standard.Sides; 010import javax.swing.ImageIcon; 011import javax.swing.JLabel; 012 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016import jmri.jmrit.operations.setup.Setup; 017import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 018import jmri.util.davidflanagan.CompatibleHardcopyWriter; 019 020/** 021 * Used for train Manifests and switch lists. 022 * 023 * @author Daniel Boudreau (C) 2025 024 */ 025public class TrainPrintManifest extends TrainCommon { 026 027 protected static final char SPACE = ' '; 028 029 /** 030 * Print or preview a train Manifest or switch list. 031 * 032 * @param file File to be printed or previewed 033 * @param name Title of document 034 * @param isPreview true if preview 035 * @param fontName optional font to use when printing document 036 * @param logoURL optional pathname for logo 037 * @param printerName optional default printer name 038 * @param orientation Setup.LANDSCAPE, Setup.PORTRAIT, or Setup.HANDHELD 039 * @param fontSize font size 040 * @param isPrintHeader when true print page header 041 * @param sides two sides long or short can be null 042 */ 043 public static void printReport(File file, String name, boolean isPreview, String fontName, String logoURL, 044 String printerName, String orientation, int fontSize, boolean isPrintHeader, Sides sides) { 045 046 double leftmargin = .5; 047 double rightmargin = .5; 048 double topmargin = .5; 049 double bottommargin = .5; 050 051 // get hand held or half page dimensions in DPI 052 Dimension pageSize = getFullPageSizeDPI(orientation); 053 054 if (orientation.equals(Setup.RECEIPT)) { 055 leftmargin = .1; 056 rightmargin = .1; 057 } 058 059 try (CompatibleHardcopyWriter writer = new CompatibleHardcopyWriter(new Frame(), name, fontSize, leftmargin, 060 rightmargin, topmargin, bottommargin, isPreview, printerName, orientation.equals(Setup.LANDSCAPE), isPrintHeader, sides, pageSize); 061 BufferedReader in = new BufferedReader(new InputStreamReader( 062 new FileInputStream(file), StandardCharsets.UTF_8));) { 063 064 // set font 065 if (!fontName.isEmpty()) { 066 writer.setFontName(fontName); 067 } 068 069 if (logoURL != null && !logoURL.equals(Setup.NONE)) { 070 ImageIcon icon = new ImageIcon(logoURL); 071 if (icon.getIconWidth() == -1) { 072 log.error("Logo not found: {}", logoURL); 073 } else { 074 writer.write(icon.getImage(), new JLabel(icon)); 075 } 076 } 077 078 List<String> lines = new ArrayList<>(); 079 String line; 080 while (true) { 081 line = in.readLine(); 082 if (line == null) { 083 if (isPreview) { 084 // need to do this in case the input file was empty to create preview 085 writer.write(" "); 086 } 087 break; 088 } 089 lines.add(line); 090 if (line.isBlank()) { 091 print(writer, lines, false); 092 } 093 } 094 print(writer, lines, true); 095 } catch (FileNotFoundException e) { 096 log.error("Build file doesn't exist", e); 097 } catch (CompatibleHardcopyWriter.PrintCanceledException ex) { 098 log.debug("Print canceled"); 099 } catch (IOException e) { 100 log.warn("Exception printing: {}", e.getLocalizedMessage()); 101 } 102 } 103 104 private static void print(CompatibleHardcopyWriter writer, List<String> lines, boolean lastBlock) 105 throws IOException { 106 int lineSize = getNumberOfLines(lines); 107 if (Setup.isPrintNoPageBreaksEnabled() && 108 writer.getCurrentLineNumber() != 0 && 109 writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) { 110 writer.pageBreak(); 111 } 112 // check for exact page break 113 if (writer.getLinesPerPage() - writer.getCurrentLineNumber() == lineSize) { 114 // eliminate blank line after page break 115 String s = lines.get(lines.size() - 1); 116 if (s.isBlank()) { 117 lines.remove(lines.size() - 1); 118 } 119 } 120 // use line feed for all lines? 121 if (lastBlock && writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) { 122 lastBlock = false; // yes 123 } 124 125 Color color = null; 126 boolean printingColor = false; 127 for (String line : lines) { 128 // determine if there's a line separator 129 if (printHorizontialLineSeparator(writer, line)) { 130 color = null; 131 continue; 132 } 133 // color text? 134 if (line.contains(TEXT_COLOR_START)) { 135 color = getTextColor(line); 136 if (line.contains(TEXT_COLOR_END)) { 137 printingColor = false; 138 } else { 139 // printing multiple lines in color 140 printingColor = true; 141 } 142 // could be a color change when using two column format 143 if (line.contains(Character.toString(VERTICAL_LINE_CHAR))) { 144 String s = line.substring(0, line.indexOf(VERTICAL_LINE_CHAR)); 145 s = getTextColorString(s); 146 writer.write(color, s); // 1st half of line printed 147 // get the new color and text 148 line = line.substring(line.indexOf(VERTICAL_LINE_CHAR)); 149 color = getTextColor(line); 150 // pad out string 151 line = tabString(getTextColorString(line), s.length()); 152 } else { 153 // simple case only one color 154 line = getTextColorString(line); 155 } 156 } else if (line.contains(TEXT_COLOR_END)) { 157 printingColor = false; 158 line = getTextColorString(line); 159 } else if (!printingColor) { 160 color = null; 161 } 162 163 printVerticalLineSeparator(writer, line); 164 line = line.replace(VERTICAL_LINE_CHAR, SPACE); 165 166 if (color != null) { 167 writer.write(color, line + NEW_LINE); 168 continue; 169 } 170 writer.write(line); 171 // no line feed if last line of file, eliminates blank page 172 if (!lastBlock || 173 writer.getCurrentLineNumber() < writer.getLinesPerPage() - 1) { 174 writer.write(NEW_LINE); 175 } 176 } 177 lines.clear(); 178 } 179 180 /* 181 * When determining the number of lines to print, we need to ignore any 182 * horizontal lines. 183 */ 184 private static int getNumberOfLines(List<String> lines) { 185 int numberLines = lines.size(); 186 for (String line : lines) { 187 for (char c : line.toCharArray()) { 188 if (c == HORIZONTAL_LINE_CHAR) { 189 numberLines--; 190 break; 191 } 192 } 193 } 194 return numberLines; 195 } 196 197 /* 198 * Returns true if horizontal line was printed, or line length = 0 199 */ 200 private static boolean printHorizontialLineSeparator(CompatibleHardcopyWriter writer, String line) { 201 boolean horizontialLineSeparatorFound = true; 202 if (line.length() > 0) { 203 for (int i = 0; i < line.length(); i++) { 204 if (line.charAt(i) != HORIZONTAL_LINE_CHAR) { 205 horizontialLineSeparatorFound = false; 206 break; 207 } 208 } 209 if (horizontialLineSeparatorFound) { 210 int endCol = writer.getCharactersPerLine() + 2; 211 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber(), 212 endCol); 213 } 214 } 215 return horizontialLineSeparatorFound; 216 } 217 218 private static void printVerticalLineSeparator(CompatibleHardcopyWriter writer, String line) { 219 for (int i = 0; i < line.length(); i++) { 220 if (line.charAt(i) == VERTICAL_LINE_CHAR) { 221 // make a frame (two column format) 222 if (Setup.isTabEnabled()) { 223 int endCol = writer.getCharactersPerLine() + 1; 224 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber() + 1, 0); 225 writer.write(writer.getCurrentLineNumber(), endCol, 226 writer.getCurrentLineNumber() + 1, endCol); 227 } 228 writer.write(writer.getCurrentLineNumber(), i + 1, writer.getCurrentLineNumber() + 1, 229 i + 1); 230 } 231 } 232 } 233 234 private static final Logger log = LoggerFactory.getLogger(TrainPrintManifest.class); 235}