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 printing 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 private static boolean isPrintingBoldDone = false; 029 private static boolean isPrintingColor = false; 030 private static Color color; 031 032 /** 033 * Print or preview a train Manifest or switch list. 034 * 035 * @param file File to be printed or previewed 036 * @param name Title of document 037 * @param isPreview true if preview 038 * @param fontName optional font to use when printing document 039 * @param logoURL optional pathname for logo 040 * @param printerName optional default printer name 041 * @param orientation Setup.LANDSCAPE, Setup.PORTRAIT, or Setup.HANDHELD 042 * @param fontSize font size 043 * @param isPrintHeader when true print page header 044 * @param sides two sides long or short can be null 045 */ 046 public static void printReport(File file, String name, boolean isPreview, String fontName, String logoURL, 047 String printerName, String orientation, int fontSize, boolean isPrintHeader, Sides sides) { 048 049 double leftmargin = .5; 050 double rightmargin = .5; 051 double topmargin = .5; 052 double bottommargin = .5; 053 054 // get hand held or half page dimensions in DPI 055 Dimension pageSize = getFullPageSizeDPI(orientation); 056 057 if (orientation.equals(Setup.RECEIPT)) { 058 leftmargin = .2; 059 rightmargin = .2; 060 } 061 062 try (CompatibleHardcopyWriter writer = new CompatibleHardcopyWriter(new Frame(), name, fontSize, leftmargin, 063 rightmargin, topmargin, bottommargin, isPreview, printerName, orientation.equals(Setup.LANDSCAPE), 064 isPrintHeader, sides, pageSize); 065 BufferedReader in = new BufferedReader(new InputStreamReader( 066 new FileInputStream(file), StandardCharsets.UTF_8));) { 067 068 // set font 069 if (!fontName.isEmpty()) { 070 writer.setFontName(fontName); 071 } 072 073 if (logoURL != null && !logoURL.equals(Setup.NONE)) { 074 ImageIcon icon = new ImageIcon(logoURL); 075 if (icon.getIconWidth() == -1) { 076 log.error("Logo not found: {}", logoURL); 077 } else { 078 writer.write(icon.getImage(), new JLabel(icon)); 079 } 080 } 081 082 List<String> lines = new ArrayList<>(); 083 String line; 084 while (true) { 085 line = in.readLine(); 086 if (line == null) { 087 if (isPreview) { 088 // need to do this in case the input file was empty to create preview 089 writer.write(" "); 090 } 091 break; 092 } 093 lines.add(line); 094 if (line.isBlank()) { 095 print(writer, lines, false); 096 } 097 } 098 print(writer, lines, true); 099 } catch (FileNotFoundException e) { 100 log.error("Build file doesn't exist", e); 101 } catch (CompatibleHardcopyWriter.PrintCanceledException ex) { 102 log.debug("Print canceled"); 103 } catch (IOException e) { 104 log.warn("Exception printing: {}", e.getLocalizedMessage()); 105 } 106 } 107 108 private static void print(CompatibleHardcopyWriter writer, List<String> lines, boolean lastBlock) 109 throws IOException { 110 int lineSize = getNumberOfLines(lines); 111 if (Setup.isPrintNoPageBreaksEnabled() && 112 writer.getCurrentLineNumber() != 0 && 113 writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) { 114 writer.pageBreak(); 115 } 116 // check for exact page break 117 if (writer.getLinesPerPage() - writer.getCurrentLineNumber() == lineSize) { 118 // eliminate blank line after page break 119 String s = lines.get(lines.size() - 1); 120 if (s.isBlank()) { 121 lines.remove(lines.size() - 1); 122 } 123 } 124 // use line feed for all lines? 125 if (lastBlock && writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) { 126 lastBlock = false; // yes 127 } 128 129 isPrintingColor = false; 130 color = null; 131 132 for (String line : lines) { 133 // determine if there's a line separator 134 if (printHorizontialLineSeparator(writer, line)) { 135 color = null; 136 continue; 137 } 138 139 // bold text? 140 line = printBold(writer, line); 141 142 // color text? 143 line = printColor(writer, line); 144 145 line = printVerticalLineSeparator(writer, line); 146 147 if (color != null) { 148 writer.write(color, line); 149 } else { 150 writer.write(line); 151 } 152 153 // no line feed if last line of file, eliminates blank page 154 if (!lastBlock || 155 writer.getCurrentLineNumber() < writer.getLinesPerPage() - 1) { 156 writer.write(NEW_LINE); 157 } 158 159 // done bold text? 160 if (isPrintingBoldDone) { 161 writer.setFontStyle(Font.PLAIN); 162 isPrintingBoldDone = false; 163 } 164 } 165 lines.clear(); 166 } 167 168 /* 169 * When determining the number of lines to print, we need to ignore any 170 * horizontal lines. 171 */ 172 private static int getNumberOfLines(List<String> lines) { 173 int numberLines = lines.size(); 174 for (String line : lines) { 175 for (char c : line.toCharArray()) { 176 if (c == HORIZONTAL_LINE_CHAR) { 177 numberLines--; 178 break; 179 } 180 } 181 } 182 return numberLines; 183 } 184 185 /* 186 * Returns true if horizontal line was printed, or line length = 0 187 */ 188 private static boolean printHorizontialLineSeparator(CompatibleHardcopyWriter writer, String line) { 189 boolean horizontialLineSeparatorFound = true; 190 if (line.length() > 0) { 191 for (int i = 0; i < line.length(); i++) { 192 if (line.charAt(i) != HORIZONTAL_LINE_CHAR) { 193 horizontialLineSeparatorFound = false; 194 break; 195 } 196 } 197 if (horizontialLineSeparatorFound) { 198 int endCol = writer.getCharactersPerLine() + 1; 199 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber(), 200 endCol); 201 } 202 } 203 return horizontialLineSeparatorFound; 204 } 205 206 private static String printVerticalLineSeparator(CompatibleHardcopyWriter writer, String line) { 207 for (int i = 0; i < line.length(); i++) { 208 if (line.charAt(i) == VERTICAL_LINE_CHAR) { 209 // make a frame (two column format) 210 if (Setup.isTabEnabled()) { 211 int endCol = writer.getCharactersPerLine() + 1; 212 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber() + 1, 0); 213 writer.write(writer.getCurrentLineNumber(), endCol, 214 writer.getCurrentLineNumber() + 1, endCol); 215 } 216 writer.write(writer.getCurrentLineNumber(), i + 1, writer.getCurrentLineNumber() + 1, 217 i + 1); 218 } 219 } 220 line = line.replace(VERTICAL_LINE_CHAR, SPACE); 221 return line; 222 } 223 224 private static String printBold(CompatibleHardcopyWriter writer, String line) throws IOException { 225 if (line.contains(TEXT_BOLD_END)) { 226 isPrintingBoldDone = true; 227 } 228 // if monospaced font, it is possible to only bold a subset of words in the line 229 // can't combine color and bold words 230 if (writer.isMonospaced() && 231 line.contains(TEXT_BOLD) && 232 line.contains(TEXT_BOLD_END) && 233 !line.contains(TEXT_COLOR_START) && 234 !line.contains(TEXT_COLOR_END)) { 235 printBoldWords(writer, line); 236 line = ""; // done 237 } else { 238 if (line.contains(TEXT_BOLD)) { 239 writer.setFontStyle(Font.BOLD); // bold the entire line 240 } 241 if (line.contains(TEXT_BOLD) || line.contains(TEXT_BOLD_END)) { 242 line = getTextBoldString(line); // strip the bold characters 243 } 244 } 245 return line; 246 } 247 248 // where in the line to add words 249 private static int offset; 250 251 private static void printBoldWords(CompatibleHardcopyWriter writer, String line) throws IOException { 252 offset = 0; 253 // determine how many bold words to print 254 String[] strings = line.split(TEXT_BOLD); 255 for (String s : strings) { 256 if (s.contains(TEXT_BOLD_END)) { 257 writer.setFontStyle(Font.BOLD); 258 String text = s.substring(0, s.indexOf(TEXT_BOLD_END)); 259 writeWords(writer, text); // bold text 260 261 writer.setFontStyle(Font.PLAIN); 262 text = s.substring(s.indexOf(TEXT_BOLD_END) + TEXT_BOLD_END.length()); 263 writeWords(writer, text); // plain text 264 } else { 265 writeWords(writer, s); // plain text 266 } 267 } 268 } 269 270 private static void writeWords(CompatibleHardcopyWriter writer, String s) throws IOException { 271 String text = tabString(s, offset); 272 writer.write(text); 273 offset = +text.length(); 274 } 275 276 private static String printColor(CompatibleHardcopyWriter writer, String line) throws IOException { 277 if (line.contains(TEXT_COLOR_START)) { 278 color = getTextColor(line); 279 if (line.contains(TEXT_COLOR_END)) { 280 isPrintingColor = false; 281 } else { 282 // printing multiple lines in color 283 isPrintingColor = true; 284 } 285 // could be a color change when using two column format 286 if (line.contains(Character.toString(VERTICAL_LINE_CHAR))) { 287 String s = line.substring(0, line.indexOf(VERTICAL_LINE_CHAR)); 288 s = getOnlyText(s); 289 writer.write(color, s); // 1st half of line printed 290 // get the new color and text 291 line = line.substring(line.indexOf(VERTICAL_LINE_CHAR)); 292 color = getTextColor(line); 293 // pad out string 294 line = tabString(getOnlyText(line), s.length()); 295 } else { 296 // simple case only one color 297 line = getOnlyText(line); 298 } 299 } else if (line.contains(TEXT_COLOR_END)) { 300 isPrintingColor = false; 301 line = getOnlyText(line); 302 } else if (!isPrintingColor) { 303 color = null; 304 } 305 return line; 306 } 307 308 private static final Logger log = LoggerFactory.getLogger(TrainPrintManifest.class); 309}