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    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        // obtain a CompatibleHardcopyWriter to do this
047        double margin = .5;
048        Dimension pagesize = null; // HardcopyWritter provides default page
049                                   // sizes for portrait and landscape
050
051        if (orientation.equals(Setup.HANDHELD) || orientation.equals(Setup.HALFPAGE)) {
052            isPrintHeader = false;
053            // add margins to page size
054            pagesize = new Dimension(getPageSize(orientation).width + PAPER_MARGINS.width,
055                    getPageSize(orientation).height + PAPER_MARGINS.height);
056        }
057        try (CompatibleHardcopyWriter writer = new CompatibleHardcopyWriter(new Frame(), name, fontSize, margin,
058                margin, margin, margin, isPreview, printerName, orientation.equals(Setup.LANDSCAPE), isPrintHeader, sides, pagesize);
059                BufferedReader in = new BufferedReader(new InputStreamReader(
060                        new FileInputStream(file), StandardCharsets.UTF_8));) {
061
062            // set font
063            if (!fontName.isEmpty()) {
064                writer.setFontName(fontName);
065            }
066
067            if (logoURL != null && !logoURL.equals(Setup.NONE)) {
068                ImageIcon icon = new ImageIcon(logoURL);
069                if (icon.getIconWidth() == -1) {
070                    log.error("Logo not found: {}", logoURL);
071                } else {
072                    writer.write(icon.getImage(), new JLabel(icon));
073                }
074            }
075
076            List<String> lines = new ArrayList<>();
077            String line;
078            while (true) {
079                line = in.readLine();
080                if (line == null) {
081                    if (isPreview) {
082                        // need to do this in case the input file was empty to create preview
083                        writer.write(" ");
084                    }
085                    break;
086                }
087                lines.add(line);
088                if (line.isBlank()) {
089                    print(writer, lines, false);
090                }
091            }
092            print(writer, lines, true);
093        } catch (FileNotFoundException e) {
094            log.error("Build file doesn't exist", e);
095        } catch (CompatibleHardcopyWriter.PrintCanceledException ex) {
096            log.debug("Print canceled");
097        } catch (IOException e) {
098            log.warn("Exception printing: {}", e.getLocalizedMessage());
099        }
100    }
101
102    private static void print(CompatibleHardcopyWriter writer, List<String> lines, boolean lastBlock)
103            throws IOException {
104        int lineSize = getNumberOfLines(lines);
105        if (Setup.isPrintNoPageBreaksEnabled() &&
106                writer.getCurrentLineNumber() != 0 &&
107                writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) {
108            writer.pageBreak();
109        }
110        // check for exact page break
111        if (writer.getLinesPerPage() - writer.getCurrentLineNumber() == lineSize) {
112            // eliminate blank line after page break
113            String s = lines.get(lines.size() - 1);
114            if (s.isBlank()) {
115                lines.remove(lines.size() - 1);
116            }
117        }
118        // use line feed for all lines?
119        if (lastBlock && writer.getLinesPerPage() - writer.getCurrentLineNumber() < lineSize) {
120            lastBlock = false; // yes
121        }
122
123        Color color = null;
124        boolean printingColor = false;
125        for (String line : lines) {
126            // determine if there's a line separator
127            if (printHorizontialLineSeparator(writer, line)) {
128                color = null;
129                continue;
130            }
131            // color text?
132            if (line.contains(TEXT_COLOR_START)) {
133                color = getTextColor(line);
134                if (line.contains(TEXT_COLOR_END)) {
135                    printingColor = false;
136                } else {
137                    // printing multiple lines in color
138                    printingColor = true;
139                }
140                // could be a color change when using two column format
141                if (line.contains(Character.toString(VERTICAL_LINE_CHAR))) {
142                    String s = line.substring(0, line.indexOf(VERTICAL_LINE_CHAR));
143                    s = getTextColorString(s);
144                    writer.write(color, s); // 1st half of line printed
145                    // get the new color and text
146                    line = line.substring(line.indexOf(VERTICAL_LINE_CHAR));
147                    color = getTextColor(line);
148                    // pad out string
149                    line = tabString(getTextColorString(line), s.length());
150                } else {
151                    // simple case only one color
152                    line = getTextColorString(line);
153                }
154            } else if (line.contains(TEXT_COLOR_END)) {
155                printingColor = false;
156                line = getTextColorString(line);
157            } else if (!printingColor) {
158                color = null;
159            }
160
161            printVerticalLineSeparator(writer, line);
162            line = line.replace(VERTICAL_LINE_CHAR, SPACE);
163
164            if (color != null) {
165                writer.write(color, line + NEW_LINE);
166                continue;
167            }
168            writer.write(line);
169            // no line feed if last line of file, eliminates blank page
170            if (!lastBlock ||
171                    writer.getCurrentLineNumber() < writer.getLinesPerPage() - 1) {
172                writer.write(NEW_LINE);
173            }
174        }
175        lines.clear();
176    }
177
178    /*
179     * When determining the number of lines to print, we need to ignore any
180     * horizontal lines.
181     */
182    private static int getNumberOfLines(List<String> lines) {
183        int numberLines = lines.size();
184        for (String line : lines) {
185            for (char c : line.toCharArray()) {
186                if (c == HORIZONTAL_LINE_CHAR) {
187                    numberLines--;
188                    break;
189                }
190            }
191        }
192        return numberLines;
193    }
194
195    /*
196     * Returns true if horizontal line was printed, or line length = 0
197     */
198    private static boolean printHorizontialLineSeparator(CompatibleHardcopyWriter writer, String line) {
199        boolean horizontialLineSeparatorFound = true;
200        if (line.length() > 0) {
201            for (int i = 0; i < line.length(); i++) {
202                if (line.charAt(i) != HORIZONTAL_LINE_CHAR) {
203                    horizontialLineSeparatorFound = false;
204                    break;
205                }
206            }
207            if (horizontialLineSeparatorFound) {
208                int endCol = writer.getCharactersPerLine() + 1;
209                writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber(),
210                        endCol);
211            }
212        }
213        return horizontialLineSeparatorFound;
214    }
215
216    private static void printVerticalLineSeparator(CompatibleHardcopyWriter writer, String line) {
217        for (int i = 0; i < line.length(); i++) {
218            if (line.charAt(i) == VERTICAL_LINE_CHAR) {
219                // make a frame (two column format)
220                if (Setup.isTabEnabled()) {
221                    int endCol = writer.getCharactersPerLine() + 1;
222                    writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber() + 1, 0);
223                    writer.write(writer.getCurrentLineNumber(), endCol,
224                            writer.getCurrentLineNumber() + 1, endCol);
225                }
226                writer.write(writer.getCurrentLineNumber(), i + 1, writer.getCurrentLineNumber() + 1,
227                        i + 1);
228            }
229        }
230    }
231
232    private final static Logger log = LoggerFactory.getLogger(TrainPrintManifest.class);
233}