/* * 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.ListLine; import simple.escp.dom.line.TableLine; import simple.escp.dom.line.TextLine; import simple.escp.util.EscpUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; /** * DOM class to represent one page. A <code>Page</code> may contains header and footer. * A page also has its page number. */ public class Page { private static final Logger LOG = Logger.getLogger("simple.escp"); private TextLine[] header; private TextLine[] footer; private List<Line> content; private Integer pageNumber; private Integer pageLength; /** * Create a clone from another Page. * * @param anotherPage a <code>Page</code> to clone. * @param pageLength maximum number of lines for this page. Set <code>null</code> for unlimited lines in this * page. */ public Page(Page anotherPage, Integer pageLength) { content = new ArrayList<>(); for (Line line : anotherPage.content) { content.add(line); } header = Arrays.copyOf(anotherPage.getHeader(), anotherPage.getHeader().length); footer = Arrays.copyOf(anotherPage.getFooter(), anotherPage.getFooter().length); pageNumber = anotherPage.getPageNumber(); this.pageLength = pageLength; } /** * Create a new <code>Page</code>. * * @param content the content of this <code>Page</code>. * @param header the header for this <code>Page</code>. Set <code>null</code> if this page doesn't have * header. * @param footer the footer for this <code>Page</code>. Set <code>null</code> if this page doesn't have * footer. * @param pageNumber the page number for this page. The page number for first page is 1, the second page is 2, * and so on. * @param pageLength maximum number of lines for this page. Set <code>null</code> for unlimited lines in this * page. */ public Page(List<Line> content, TextLine[] header, TextLine[] footer, Integer pageNumber, Integer pageLength) { this.content = content; this.header = header == null ? new TextLine[0] : header; this.footer = footer == null ? new TextLine[0] : footer; this.pageNumber = pageNumber; this.pageLength = pageLength; } /** * Get the header for this page. * * @return header for this page. */ public TextLine[] getHeader() { return Arrays.copyOf(header, header.length); } /** * Set a new header for this page. * @param header new header for this page. */ public void setHeader(TextLine[] header) { this.header = Arrays.copyOf(header, header.length); } /** * Get the footer for this page. * * @return footer for this page. */ public TextLine[] getFooter() { return Arrays.copyOf(footer, footer.length); } /** * Set a new footer for this page. * @param footer new footer for this page. */ public void setFooter(TextLine[] footer) { this.footer = Arrays.copyOf(footer, footer.length); } /** * Get the content of this page. * * @return content of this page. */ public List<Line> getContent() { return content; } /** * Set the new content for this page. * * @param content the new content for this page. */ public void setContent(List<Line> content) { if (pageLength != null) { int numberOfLines = header.length + footer.length; if (numberOfLines + content.size() > pageLength) { throw new IllegalArgumentException("Page overflow."); } } this.content = content; } /** * Get the page number for this page. * * @return page number for this page. */ public Integer getPageNumber() { return pageNumber; } /** * Set a new page number for this page. * * @param pageNumber new page number for this page. */ public void setPageNumber(Integer pageNumber) { this.pageNumber = pageNumber; } /** * Get maximum number of lines for this page. * * @return maximum number of lines for this page. */ public Integer getPageLength() { return pageLength; } /** * Check if this page is full and no new line can be written anymore. * * @return <code>true</code> if this page is full and no write operation is supported, or <code>false</code> if * this page is not full. */ public boolean isFull() { if (pageLength == null) { return false; } else { return (header.length + footer.length + content.size()) >= pageLength; } } /** * Check if this page is overflow. The difference with this method and {@link #isFull()} is that this method * will return <code>true</code> only if number of lines is more than allowed length. * * @return <code>true</code> if this page is overflowed or <code>false</code> if otherwise. */ public boolean isOverflow() { if (pageLength == null) { return false; } else { return (header.length + footer.length + content.size()) > pageLength; } } /** * Add a new line to this page from a string. The string will be converted to a line. * See also {@link #append(Line)}. * * @param text the string that will be added to this page. */ public void append(String text) { if (isFull()) { throw new IllegalStateException("Page is full."); } content.add(new TextLine(text)); } /** * Add a new line to this page. The line will be inserted after the last line of this page. * * @param line the line that will be added to this page. */ public void append(Line line) { if (isFull()) { throw new IllegalStateException("Page is full."); } content.add(line); } /** * Add multipe lines to this page. The lines will be inserted after the last line of this page. * * @param lines the lines that will be added to this page. */ public void append(List<? extends Line> lines) { for (Line line : lines) { append(line); } } /** * Add empty line to this page. */ public void appendEmptyLine() { append(new EmptyLine()); } /** * Add empty lines from current line to the specified line number. * * @param lineNumber destination line number. This line is exclusive (the lines before this line will be * empty line but not including this line). */ public void appendEmptyLineUntil(int lineNumber) { if (lineNumber <= header.length) { throw new IllegalArgumentException("Can't append empty line before header: " + lineNumber); } if (lineNumber > (pageLength - footer.length)) { throw new IllegalArgumentException("Can't append empty line after footer: " + lineNumber); } int currentLine = header.length + content.size(); if (currentLine >= lineNumber) { throw new IllegalArgumentException("Destination line number is less than current (" + currentLine + ")"); } for (int i = currentLine + 1; i < lineNumber; i++) { appendEmptyLine(); } } /** * Insert a new <code>Line</code> at the specified <code>lineNumber</code> position. If the page is full * after insertion, the last line of the content (<strong>not</strong> including footer) will be removed * and returned. * * @param line the <code>Line</code> that will be inserted. * @param lineNumber the line number position in which the new line will be inserted. * @return discarded <code>Line</code> if insertion causes overflow and a line is removed, or * <code>null</code> if no line is discarded. */ public Line insert(Line line, int lineNumber) { Line result = null; if (lineNumber < header.length) { throw new IllegalArgumentException("Line number can't be inserted before header: " + lineNumber); } if ((pageLength != null) && (lineNumber > pageLength)) { throw new IllegalArgumentException("Invalid line number: " + lineNumber); } content.add(lineNumber - header.length - 1, line); if (isOverflow()) { result = content.get(content.size() - 1); LOG.fine("Content overflow and the last line will be removed [" + result + "]"); content.remove(content.size() - 1); } return result; } /** * Change the content of a line. Line number for the first line (starting from header) is <code>1</code>. * * @param lineNumber the line number position in which the new line will be replaced. * @param line a new line to replace old line. */ public void setLine(int lineNumber, Line line) { if (lineNumber < 1 || lineNumber > getNumberOfLines()) { throw new IllegalArgumentException("Invalid line number: " + lineNumber); } if (lineNumber <= header.length) { header[lineNumber - 1] = (TextLine) line; } else if (lineNumber > header.length + content.size()) { footer[lineNumber - header.length - content.size() - 1] = (TextLine) line; } else { content.set(lineNumber - header.length - 1, line); } } /** * Get current number of lines in this page that have been written. * * @return number of lines of this page (excluding empty lines). */ public int getNumberOfLines() { return header.length + content.size() + footer.length; } /** * Get all lines in this page. * * @return all lines of this page (excluding empty lines). */ public Line[] getLines() { Line[] result = new Line[getNumberOfLines()]; int index = 0; for (TextLine line : header) { line.setLineNumber(index + 1); result[index++] = line; } for (Line line : content) { if (line != null) { line.setLineNumber(index + 1); } result[index++] = line; } for (Line line : footer) { line.setLineNumber(index + 1); result[index++] = line; } return result; } /** * Get the text of certain line number in this page. * * @param lineNumber a line number starting from 1. * @return the text for the specified line number. */ public Line getLine(int lineNumber) { if (lineNumber < 0 || lineNumber > getNumberOfLines()) { throw new IllegalArgumentException("Number of lines [" + lineNumber + "] is out of range."); } if (lineNumber <= header.length) { return header[lineNumber - 1]; } lineNumber = lineNumber - header.length; if (lineNumber <= content.size()) { return content.get(lineNumber - 1); } lineNumber = lineNumber - content.size(); return footer[lineNumber - 1]; } /** * Remove a <code>Line</code> from this <code>Page</code>. * * @param line a <code>Line</code> that will be removed. * @return <code>true</code> if this page contained the specified line */ public boolean removeLine(Line line) { return content.remove(line); } /** * Remove a <code>Line</code> by its line number. This method will only delete line located at detail (content). * Header and footer can't be deleted by using this method. * * @param lineNumber a line number starting from 1 starting from header (if exists). The line number * <strong>should not</strong> be part of header or footer. * @return the deleted <code>Line</code> or <code>null</code> if nothing is deleted. */ public Line removeLine(int lineNumber) { if ((lineNumber <= header.length) || (lineNumber > header.length + content.size())) { throw new IllegalArgumentException("Line number is not valid: [" + lineNumber + "]"); } int index = lineNumber - header.length - 1; return content.remove(index); } /** * Convert this page into ESC/P string that can be printed. This is <strong>>not</strong> including filling * operation to substitute placeholder with values from data source or evaluating functions. * * @param autoLinefeed set <code>true</code> if auto line-feed is enabled (CR will be used as line separator) or * set <code>false</code> if auto line-feed is disabled (CRLR will be used as line separator). * @param autoFormfeed set <code>true</code> if auto form-feed is enabled (CRFF will be added automatically at * the end of page) or set <code>false</code> to not add CRFF automatically at the end of page. * @return conversion result that may contains ESC/P string. */ public String convertToString(boolean autoLinefeed, boolean autoFormfeed) { StringBuilder result = new StringBuilder(); for (Line line: getLines()) { if (line instanceof TextLine) { result.append(((TextLine) line).getText()); result.append(autoLinefeed ? EscpUtil.CR : EscpUtil.CRLF); } } if (autoFormfeed) { result.append(EscpUtil.CRFF); } return result.toString(); } /** * Check if this page contains one or more dynamic lines. * * @return <code>true</code> if this page contains dynamic line or <code>false</code> if otherwise. */ public boolean hasDynamicLine() { for (Line line: content) { if (line != null && line.isDynamic()) { return true; } } return false; } /** * Get all <code>TableLine</code> in this page. This method also stores line number for each returned * <code>TableLine</code>. To inspect line number for <code>TableLine</code>, * use <code>TableLine.getLineNumber()</code> method. * * @return <code>List</code> that contains <code>TableLine</code> in this page. If no <code>TableLine</code> * exists in this page, it will return an empty <code>List</code>. */ public List<TableLine> getTableLines() { List<TableLine> result = new ArrayList<>(); int offset = header.length + 1; for (int i = 0; i < content.size(); i++) { if (content.get(i) instanceof TableLine) { TableLine tableLine = (TableLine) content.get(i); tableLine.setLineNumber(offset + i); result.add(tableLine); } } return result; } /** * Get all <code>ListLine</code> in this page. This method also stores line number for each returned * <code>ListLine</code>. To inspect line number for <code>ListLine</code>, * use <code>ListLine.getLineNumber()</code> method. * * @return <code>List</code> that contains <code>TableLine</code> in this page. If no <code>TableLine</code> * exists in this page, it will return an empty <code>List</code>. */ public List<ListLine> getListLines() { List<ListLine> result = new ArrayList<>(); int offset = header.length + 1; for (int i = 0; i < content.size(); i++) { if (content.get(i) instanceof ListLine) { ListLine listLine = (ListLine) content.get(i); listLine.setLineNumber(offset + i); result.add(listLine); } } return result; } }