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}