/* * Copyright 2014 Jocki Hendry * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package simple.escp.dom; import simple.escp.dom.line.EmptyLine; import simple.escp.dom.line.TextLine; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; /** * DOM class to represent a print result in form of collection of empty, one or more <code>Page</code>. * <code>Report</code> is usually the output of parsing stage. To initiate filling stage, * call <code>fill()</code> method of this class. */ public class Report implements Iterable<Page> { private static final Logger LOG = Logger.getLogger("simple.escp"); private List<Page> pages = new ArrayList<>(); private int lastPageNumber = 0; private PageFormat pageFormat; private Page currentPage; private TextLine[] header; private TextLine[] footer; private TextLine[] lastPageFooter; private boolean lineBreak; /** * Create a clone from another report. * * @param anotherReport a <code>Report</code> to clone. */ public Report(Report anotherReport) { init(anotherReport.getPageFormat(), anotherReport.getHeader(), anotherReport.getFooter(), anotherReport.getLastPageFooter()); pages = new ArrayList<>(); for (Page page : anotherReport) { pages.add(new Page(page, anotherReport.getPageFormat().getPageLength())); } if (!pages.isEmpty()) { currentPage = pages.get(pages.size() - 1); } lastPageNumber = anotherReport.getLastPageNumber(); } /** * Create a new instance of <code>Report</code>. * * @see #Report(PageFormat, simple.escp.dom.line.TextLine[], simple.escp.dom.line.TextLine[], simple.escp.dom.line.TextLine[]) * @param pageFormat the <code>PageFormat</code> for this <code>Report</code>. * @param header header for all pages in this <code>Report</code>. * @param footer footer for all pages in this <code>Report</code>. * */ public Report(PageFormat pageFormat, TextLine[] header, TextLine[] footer) { this(pageFormat, header, footer, null); } /** * Create a new instance of <code>Report</code>. * * @param pageFormat the <code>PageFormat</code> for this <code>Report</code>. * @param header header for all pages in this <code>Report</code>. * @param footer footer for all pages in this <code>Report</code>. * @param lastPageFooter footer for the last page in this <code>Report</code>. */ public Report(PageFormat pageFormat, TextLine[] header, TextLine[] footer, TextLine[] lastPageFooter) { init(pageFormat, header, footer, lastPageFooter); } /** * Create a new instance of <code>Report</code>. * * @see #Report(int, simple.escp.dom.line.TextLine[], simple.escp.dom.line.TextLine[], simple.escp.dom.line.TextLine[]) * * @param pageLength length of every pages in this report in number of lines. * @param header header for all pages in this <code>Report</code>. * @param footer footer for all pages in this <code>Report</code>. */ public Report(int pageLength, TextLine[] header, TextLine[] footer) { this(pageLength, header, footer, null); } /** * Create a new instance of <code>Report</code>. * * @param pageLength length of every pages in this report in number of lines. * @param header header for all pages in this <code>Report</code>. * @param footer footer for all pages in this <code>Report</code>. * @param lastPageFooter footer for the last page in this <code>Report</code>. */ public Report(int pageLength, TextLine[] header, TextLine[] footer, TextLine[] lastPageFooter) { pageFormat = new PageFormat(); pageFormat.setPageLength(pageLength); init(pageFormat, header, footer, lastPageFooter); } /** * Initialization setup this report. * * @param pageFormat the <code>PageFormat</code> for this <code>Report</code>. * @param header header for all of pages in this <code>Report</code>. * @param footer footer for all of pages in this <code>Report</code>. * @param lastPageFooter footer that will be displayed only in the last page of this <code>Report</code>. */ private void init(PageFormat pageFormat, TextLine[] header, TextLine[] footer, TextLine[] lastPageFooter) { this.pageFormat = pageFormat; if ((pageFormat.getPageLength() == null) && (!pageFormat.isUsePageLengthFromPrinter())) { throw new IllegalArgumentException("Invalid page format with pageLength undefined when " + "isUsePageLengthFromPrinter is false."); } this.header = (header == null) ? new TextLine[0] : Arrays.copyOf(header, header.length); this.footer = (footer == null) ? new TextLine[0] : Arrays.copyOf(footer, footer.length); this.lastPageFooter = (lastPageFooter == null) ? new TextLine[0] : Arrays.copyOf(lastPageFooter, lastPageFooter.length); this.lineBreak = false; } /** * Get the <code>PageFormat</code> for this report. * * @return <code>PageFormat</code> for this report. */ public PageFormat getPageFormat() { return pageFormat; } /** * Get the header for this page. * * @return header for this page. */ public TextLine[] getHeader() { return Arrays.copyOf(header, header.length); } /** * Create a copy of every <code>TextLine</code> in header. The modification to the copy will not * affect the original header stored in this report. * * @return the copy of current header. */ public TextLine[] copyHeader() { TextLine[] result = new TextLine[header.length]; for (int i = 0; i < result.length; i++) { result[i] = new TextLine(header[i]); } return result; } /** * Get the footer for this page. * * @return footer for this page. */ public TextLine[] getFooter() { return Arrays.copyOf(footer, footer.length); } /** * Create a copy of every <code>TextLine</code> in footer. The modification to the copy will not * affect the original footer stored in this report. * * @return the copy of current footer. */ public TextLine[] copyFooter() { TextLine[] result = new TextLine[footer.length]; for (int i = 0; i < result.length; i++) { result[i] = new TextLine(footer[i]); } return result; } /** * Get the footer for the last page of this <code>Report</code>. * * @return last page footer. */ public TextLine[] getLastPageFooter() { return Arrays.copyOf(lastPageFooter, lastPageFooter.length); } /** * Create a copy of every <code>TextLine</code> in last page footer. The modification to the copy will not * affect the original footer stored in this report. * * @return the copy of current last page footer. */ public TextLine[] copyLastPageFooter() { TextLine[] result = new TextLine[lastPageFooter.length]; for (int i = 0; i < result.length; i++) { result[i] = new TextLine(lastPageFooter[i]); } return result; } /** * Get current page number for this report. * * @return current page number in this report starting from 1. */ public int getLastPageNumber() { return lastPageNumber; } /** * Get current page in this report. * * @return the latest <code>Page</code> for this report. */ public Page getCurrentPage() { return currentPage; } /** * Get a <code>Page</code> based on its <code>pageNumber</code>. * * @param pageNumber the page number for the page that will be retrieved. * @return a <code>Page</code> or throws <code>IllegalArgumentException</code> if <code>pageNumber</code> * is not valid. */ public Page getPage(int pageNumber) { if (pageNumber < 1 || pageNumber > getLastPageNumber()) { throw new IllegalArgumentException("Invalid page: " + pageNumber); } return pages.get(pageNumber - 1); } /** * Get the first <code>Page</code> in this report that has <code>TableLine</code>. * * @return a <code>Page</code> that has at least one <code>TableLine</code>, or <code>null</code> if no page * in this report has <code>TableLine</code>. */ public Page getFirstPageWithTableLines() { for (Page page : pages) { if (!page.getTableLines().isEmpty()) { return page; } } return null; } /** * Get the first <code>Page</code> in this report that has <code>ListLine</code>. * * @return a <code>Page</code> that has at least one <code>ListLine</code>, or <code>null</code> if no page * in this report has <code>ListLine</code>. */ public Page getFirstPageWithListLines() { for (Page page : pages) { if (!page.getListLines().isEmpty()) { return page; } } return null; } /** * Get number of pages in this report. * * @return number of pages in this report. */ public int getNumberOfPages() { return pages.size(); } /** * Get next page of the specified page. * * @param page find the next page of this <code>page</code>. * @return a <code>Page</code> or <code>null</code> if <code>page</code> is the last page. */ public Page nextPage(Page page) { if (page.getPageNumber() == pages.size()) { return null; } // Note that page is 1-based while the list is 0-based. // page + 1 to list index is: page + 1 - 1 = page + 0. return pages.get(page.getPageNumber()); } /** * Get previous page of the specified page. * * @param page find the previous page of this <code>page</code>. * @return a <code>Page</code> or <code>null</code> if <code>page</code> is the first page. */ public Page previousPage(Page page) { if (page.getPageNumber() == 1) { return null; } // Note that page is 1-based while the list is 0-based. // page - 1 to list index is: page - 1 - 1 = page - 2. return pages.get(page.getPageNumber() - 2); } /** * Create a new page for this report. * * @param plain if <code>true</code> will ignore header and footer. Set this to <code>false</code> to create * a <code>Page</code> that doesn't have header and footer. * @return the created <code>Page</code>. */ public Page newPage(boolean plain) { lineBreak = false; lastPageNumber++; Page page; if (plain) { LOG.fine("Creating a new page without any header and footer."); page = new Page(new ArrayList<Line>(), null, null, lastPageNumber, pageFormat.getPageLength()); } else { LOG.fine("Creating a new page that has report's header and footer."); page = new Page(new ArrayList<Line>(), copyHeader(), copyFooter(), lastPageNumber, pageFormat.getPageLength()); } pages.add(page); currentPage = page; return page; } /** * Create a new page for this report that start at specified line. The line before the specified line * will be filled by an <code>EmptyLine</code>. * * @param plain if <code>true</code> will ignore header and footer. Set this to <code>false</code> to create * a <code>Page</code> that doesn't have header and footer. * @param startAtLineNumber start appending from this line number and ignore lines before this line number. * @return the created <code>Page</code>. */ public Page newPage(boolean plain, int startAtLineNumber) { Page page = newPage(plain); page.appendEmptyLineUntil(startAtLineNumber); return page; } /** * Start a line break. The next call of <code>append()</code> will create a new page and write to this new * page instead of current page. */ public void lineBreak() { this.lineBreak = true; } /** * Add a new single page to the last page of this report. * * @param content the content of the page. * @param plain set <code>true</code> to include header and footer, or <code>false</code> if otherwise. * @return the created <code>Page</code>. */ public Page appendSinglePage(List<Line> content, boolean plain) { Page page = newPage(plain); page.setContent(content); return page; } /** * Add a new single page to the last page of this report. * * @param content the content of the page. * @param plain set <code>true</code> to include header and footer, or <code>false</code> if otherwise. * @return the created <code>Page</code>. */ public Page appendSinglePage(Line[] content, boolean plain) { List<Line> contentInList = new ArrayList<>(content.length); for (Line s : content) { contentInList.add(s); } return appendSinglePage(contentInList, plain); } /** * Add a new line to this report. If current page is full, this method will create a new page and write * to the new page. * * @param line a new line to be inserted to the last page of this report. * @param plain set <code>true</code> to include header and footer, or <code>false</code> if otherwise. */ public void append(Line line, boolean plain) { if (lineBreak || (currentPage == null) || currentPage.isFull()) { newPage(plain); } currentPage.append(line); } /** * Insert a new line at certain page and certain position. This may causes a new page to be created if necessary. * * @param line a new line to be inserted to this report. * @param pageNumber the page number in which the new line will be inserted. * @param lineNumber the line number in the page where the new line will be inserted. * @param newPageFirstLines these lines will be added to the new page if this insertion creates new page. */ public void insert(Line line, int pageNumber, int lineNumber, List<? extends Line> newPageFirstLines) { if (pageNumber < 1 || pageNumber > pages.size()) { throw new IllegalArgumentException("Invalid page number: " + pageNumber); } Page startPage = pages.get(pageNumber - 1); Line discardedLine = startPage.insert(line, lineNumber); Page currentPage = nextPage(startPage); while (discardedLine != null) { if (currentPage == null) { currentPage = newPage(false); if (newPageFirstLines != null) { currentPage.append(newPageFirstLines); } } discardedLine = currentPage.insert(discardedLine, header.length + 1 + (newPageFirstLines == null ? 0 : newPageFirstLines.size())); LOG.fine("Discarded line for next page is [" + discardedLine + "]"); currentPage = nextPage(currentPage); } } /** * Insert a new line at certain page and certain position. This may causes a new page to be created if necessary. * * @param line a new line to be inserted to this report. * @param pageNumber the page number in which the new line will be inserted. * @param lineNumber the line number in the page where the new line will be inserted. */ public void insert(Line line, int pageNumber, int lineNumber) { insert(line, pageNumber, lineNumber, null); } /** * Determine if one or more pages in this report has one or more dynamic lines. * * @return <code>true</code> if this report contains dynamic line or <code>false</code> if otherwise. */ public boolean hasDynamicLine() { for (Page page : pages) { if (page.hasDynamicLine()) { return true; } } return false; } /** * Get all lines in this report combined as one big page. <code>EmptyLine</code> will be ignored. * * @return all lines in this report. */ public List<Line> getFlatLines() { List<Line> result = new ArrayList<>(); for (Page page : pages) { for (Line line : page.getLines()) { if (line instanceof EmptyLine) { continue; } result.add(line); } } return result; } /** * Get the length of content for each page. Content is the area within the page that doesn't include * header and footer. Methods such as {@link #append(Line, boolean)} or * {@link #insert(Line, int, int)} works only within this area. * * @return the length of content page in number of lines. */ public int getContentLinesPerPage() { return pageFormat.getPageLength() - header.length - footer.length; } /** * Get line number that indicates the first line of footer. * * @return line number for the first line of footer. If this report doesn't have footer, the returned value * is line number for the last line. */ public int getStartOfFooter() { return header.length + getContentLinesPerPage(); } @Override public Iterator<Page> iterator() { int globalLineNumber = 1; for (Page page : pages) { for (Line line : page.getLines()) { if (line != null) { line.setGlobalLineNumber(globalLineNumber++); } } } return pages.iterator(); } }