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}