All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.discovery.jasperreports.jasper2word.J2WGridPageLayout Maven / Gradle / Ivy

The newest version!
package it.discovery.jasperreports.jasper2word;

import it.discovery.jasperreports.jasper2word.J2WAbstractPrintElementVisitorContext.DocxDocumentPart;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.export.ExporterFilter;
import net.sf.jasperreports.engine.type.BandTypeEnum;
import net.sf.jasperreports.export.ReportExportConfiguration;

import java.awt.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class represent the output page layout for the document. Every jasper report page has is own layout.
 * The layout is building according the elements in the page, via the following algorithm:
 * 
    *
  1. * The elements are grouping by its document {@link J2WAbstractPrintElementVisitorContext.DocxDocumentPart part}. *
  2. *
  3. * A grid is built with the original element positions (x, y, width, height). With a {@link ISimplifierGrid simplifier} * the grid can be simplified for reducing the spacings, the gaps and the breaks between the elements. * Then, for each part: *
  4. *
      *
    1. * The page elements are ordering by its coordinate in the page, in the same order they must be printed in the document. * So, the elements with minor y position precede the elements with greatest y, and the elements with minor x precede the * elements with greatest x. *
    2. *
    3. * The elements are grouping by relative and absolute positions. So texts, images and tables have relative position, * because they can't overlap, but they must be appended only in the document. Drawing objects have absolute position * because it can specify their coordinates in the document. *
    4. *
    5. * Elements with relative positions can't be aligned horizontally: two text paragraphs can't be in the same line without using a text area. * This exporter doesn't want to use text areas. So, a horizontal conflict detecting is performed. All the elements in a conflict * are inserted in an ad hoc table. *
    6. *
    7. * Two or more consecutive tables are merged into a single table. *
    8. *
    *
  5. * Finally, all the free paragraphs, the tables and the elements with absolute positions are printed out in the document. *
  6. *
* @author discovery * @date 10/08/15 */ public class J2WGridPageLayout { /** A logger for debug */ private static final Logger log = Logger.getLogger(J2WGridPageLayout.class.getName()); /** The computed positions of the elements */ private final Map elementPositions; /** All the elements sorted by position, and grouped in theri frame (if one) */ private final Map> sortedElements; /** All the table in the document */ private final NavigableMap foundTables; /** The page info */ private final HeaderFooterPageInfo pageInfo; /** * Semplifier for the page layout grid. */ public interface ISimplifierGrid { /** * Return {@code true} if {@code row1} and {@code row2} can be assumed as the same row in the grid. * @param row1 The row1. * @param row2 The row2. * @return {@code true} if the same row, {@code false} otherwise. */ boolean canSimplifyRows(int row1, int row2); /** * Return {@code true} if {@code col1} and {@code col2} can be assumed as the same column in the grid. * @param col1 The col1. * @param col2 The col2. * @return {@code true} if the same column, {@code false} otherwise. */ boolean canSimplifyColumns(int col1, int col2); } /** * Construct and build the grid. * @param page The jasper report page to export. * @param configuration The global exporter configuration. * @param simplifier The simplifier for the grid. * @param report The report to export. */ public J2WGridPageLayout(JRPrintPage page, ReportExportConfiguration configuration, ISimplifierGrid simplifier, JasperPrint report) { this.elementPositions = new IdentityHashMap<>(); this.sortedElements = new IdentityHashMap<>(); this.foundTables = new TreeMap<>(); // Compute the page size this.pageInfo = new HeaderFooterPageInfo(); if (report.getBottomMargin() != null) this.pageInfo.bottomMargin = report.getBottomMargin(); if (report.getTopMargin() != null) this.pageInfo.topMargin = report.getTopMargin(); if (report.getLeftMargin() != null) this.pageInfo.leftMargin = report.getLeftMargin(); if (report.getRightMargin() != null) this.pageInfo.rightMargin = report.getRightMargin(); this.pageInfo.pageSize = new Dimension(report.getPageWidth(), report.getPageHeight()); // Build grid this.fillPositions(page, configuration, simplifier); } /** * List all the elements in the frame {@code parent}, ordered by their position. * @param parent The frame of the elements, or {@code null} for the elements in the root page. * @return The elements in the frame or in the root of the page. */ public Collection listJRPrintElements(JRPrintFrame parent) { NavigableMap elements = this.sortedElements.get(parent); if (elements == null) return null; else return elements.values(); } /** * Return the element position in the grid layout. * @param element The elemento to find. * @return The position in the grid. */ public ComponentPosition getElementPosition(JRPrintElement element) { return this.elementPositions.get(element); } /** * List all the tables in the document. * @return The tables in the document. */ public Collection listTables() { return this.foundTables.values(); } /** * Build the grid. * @param page The jasper report page. * @param configuration The global exporter configuration. * @param simplifier The grid simplifier. */ private void fillPositions(JRPrintPage page, ReportExportConfiguration configuration, ISimplifierGrid simplifier) { ExporterFilter filter = new DefaultElementFilter(configuration.getExporterFilter()); FoundPositionsStatus foundPositionsStatus = new FoundPositionsStatus(); // Place every elements in the grid page this.findPositions(foundPositionsStatus, page.getElements(), filter, simplifier, this.elementPositions, null, 0, 0); // Grouping elements by frame: updating before for eventually table in table Set> entries = this.elementPositions.entrySet(); Map frameForElement = new IdentityHashMap<>(); for (Entry entry : entries) { JRPrintElement element = entry.getKey(); ComponentPosition position = entry.getValue(); if (element instanceof JRPrintFrame) { JRPrintFrame frame = (JRPrintFrame) element; for (JRPrintElement subElement : (frame).getElements()) { if (filter.isToExport(subElement)) { ComponentPosition subPosition = this.elementPositions.get(subElement); subPosition.setParent(position); frameForElement.put(subElement, frame); if (subPosition instanceof ComponentPositionInTable && position instanceof ComponentPositionInTable) ((ComponentPositionInTable) subPosition).getTableInfo().setParent(((ComponentPositionInTable) position).getTableInfo()); } } } } // Sorting the elements by their position and compute the bands height (header, footer and body) int elementIndex = 0; for (Entry entry : this.elementPositions.entrySet()) { JRPrintElement element = entry.getKey(); ComponentPosition position = entry.getValue(); JRPrintFrame frame = frameForElement.get(element); NavigableMap groupSortedElements = this.sortedElements.get(frame); if (groupSortedElements == null) { groupSortedElements = new TreeMap<>(); this.sortedElements.put(frame, groupSortedElements); } groupSortedElements.put(new JRPrintElementComparableKey(element, position, elementIndex++), element); if (position.getDocumentPart() != null) { if (position.getDocumentPart() == DocxDocumentPart.FOOTER) { if (this.pageInfo.footerHeight == null) this.pageInfo.footerHeight = this.pageInfo.pageSize.height - position.getOriginalY() - this.pageInfo.bottomMargin; else this.pageInfo.footerHeight = Math.max(this.pageInfo.footerHeight, this.pageInfo.pageSize.height - position.getOriginalY() - this.pageInfo.bottomMargin); if (this.pageInfo.minFooter == null) this.pageInfo.minFooter = this.pageInfo.pageSize.height - position.getOriginalY(); else this.pageInfo.minFooter = Math.min(this.pageInfo.minFooter, this.pageInfo.pageSize.height - position.getOriginalY()); if (this.pageInfo.maxFooter == null) this.pageInfo.maxFooter = this.pageInfo.pageSize.height - position.getOriginalY() + position.getHeight(); else this.pageInfo.maxFooter = Math.min(this.pageInfo.maxFooter, this.pageInfo.pageSize.height - position.getOriginalY() + position.getHeight()); } else if (position.getDocumentPart() == DocxDocumentPart.HEADER) { if (this.pageInfo.headerHeight == null) this.pageInfo.headerHeight = position.getOriginalY() + position.getHeight() - this.pageInfo.topMargin; else this.pageInfo.headerHeight = Math.max(this.pageInfo.headerHeight, position.getOriginalY() + position.getHeight() - this.pageInfo.topMargin); if (this.pageInfo.minHeader == null) this.pageInfo.minHeader = position.getOriginalY(); else this.pageInfo.minHeader = Math.min(this.pageInfo.minHeader, position.getOriginalY()); if (this.pageInfo.maxHeader == null) this.pageInfo.maxHeader = position.getOriginalY() + position.getHeight(); else this.pageInfo.maxHeader = Math.max(this.pageInfo.maxHeader, position.getOriginalY() + position.getHeight()); } } } // Dump the grid to log for (Entry> entry : this.sortedElements.entrySet()) { JRPrintFrame frame = entry.getKey(); if (log.isLoggable(Level.FINE)) { if (frame == null) log.fine("Master"); else log.fine("Frame " + frame.hashCode()); } for (Entry elementEntry : entry.getValue().entrySet()) { if (log.isLoggable(Level.FINE)) { JRPrintElement value = elementEntry.getValue(); String sValue; if (value instanceof JRPrintText) sValue = ((JRPrintText) value).getFullText(); else sValue = value.getClass().getSimpleName(); log.fine(MessageFormat.format("\t{5} - > {6}\t({0},{1}) [sid={2}, pid={3}, uuid={4}]", value.getX(), value.getY(), value.getSourceElementId(), value.getPrintElementId(), value.getUUID(), elementEntry.getKey(), sValue)); } } } } /** * Place the elements in the grid. * @param foundPositionsStatus The status of the current stack indexes of elements. * @param elements The elements to place. * @param filter The (optional) element filter. * @param simplifier The simplifier for the grid. * @param outputPositions Input/output parameter to store the elements position. * @param parent The parent of this stack ({@code null} for the root page, or the jasper report frame). * @param offsetX Optional offsetX. * @param offsetY Optional offsetY. */ private void findPositions(FoundPositionsStatus foundPositionsStatus, List elements, ExporterFilter filter, ISimplifierGrid simplifier, Map outputPositions, ComponentPosition parent, int offsetX, int offsetY) { // Original grid rows TreeSet rows = new TreeSet<>(); // Original grid columns TreeSet columns = new TreeSet<>(); // Computed grid rows TreeMap remapRows = new TreeMap<>(); // Computed grid columns TreeMap remapColumns = new TreeMap<>(); // Build the grid this.buildRawGrid(elements, filter, rows, columns, offsetX, offsetY); // Simplify the grid this.simplifyGrid(rows, columns, remapRows, remapColumns, simplifier); Map currentStackPositions = new IdentityHashMap<>(); // Place the elements into the simplified grid for (JRPrintElement element : elements) { if (filter == null || filter.isToExport(element)) { // Compute the document part DocxDocumentPart part; JROrigin origin = element.getOrigin(); if (origin == null) { origin = new JROrigin(BandTypeEnum.TITLE); } if (origin.getReportName() == null) { if (origin.getBandTypeValue() == BandTypeEnum.PAGE_HEADER) part = DocxDocumentPart.HEADER; else if (origin.getBandTypeValue() == BandTypeEnum.PAGE_FOOTER || origin.getBandTypeValue() == BandTypeEnum.LAST_PAGE_FOOTER) part = DocxDocumentPart.FOOTER; else part = DocxDocumentPart.BODY; } else part = DocxDocumentPart.BODY; // Sub-reports always in document body // Compute the element position in simplified grid ComponentPosition position = new ComponentPosition( remapColumns.get(element.getX() + offsetX), remapRows.get(element.getY() + offsetY), remapColumns.get(element.getWidth() + element.getX() + offsetX) - remapColumns.get(element.getX() + offsetX), remapRows.get(element.getHeight() + element.getY() + offsetY) - remapRows.get(element.getY() + offsetY), part); position.setParent(parent); outputPositions.put(element, position); currentStackPositions.put(element, position); // for recursive invocation of this method // Recursive invocation in case of frame if (element instanceof JRPrintFrame) { JRPrintFrame frame = (JRPrintFrame) element; FoundPositionsStatus innerStatus = new FoundPositionsStatus(); // stack status indexes innerStatus.tableId = foundPositionsStatus.tableId; this.findPositions(innerStatus, frame.getElements(), filter, simplifier, outputPositions, position, position.x + offsetX, position.y + offsetY); foundPositionsStatus.tableId = innerStatus.tableId; } } } // Ordering elements according positions NavigableMap preOrderingElements = new TreeMap<>(); for (Entry entry : currentStackPositions.entrySet()) { preOrderingElements.put(new JRPrintElementComparableKey(entry.getKey(), entry.getValue(), foundPositionsStatus.elementIndex++), entry.getKey()); } // Detect horizonally conflicts SortedMap> conflicts = this.findConflicts(currentStackPositions, preOrderingElements); // Build tabled for conflicts foundPositionsStatus.tableId = this.findTables(conflicts, currentStackPositions, this.foundTables, foundPositionsStatus.tableId, parent); // Store computed positions outputPositions.putAll(currentStackPositions); } /** * Build the original grid based on elements coordinates. * @param elements The elements in the grid. * @param filter Elements filter. * @param rows Input/Output parameter which will contain the grid rows. * @param columns Input/Output parameter which will contain the grid columns. * @param offsetX Optional offsetX (included in output columns). * @param offsetY Optional offsetY (included in output rows). */ private void buildRawGrid(List elements, ExporterFilter filter, Set rows, Set columns, int offsetX, int offsetY) { for (JRPrintElement element : elements) { if (filter == null || filter.isToExport(element)) { rows.add(element.getY() + offsetY); rows.add(element.getY() + element.getHeight() + offsetY); columns.add(element.getX() + offsetX); columns.add(element.getX() + element.getWidth() + offsetX); } } } /** * Simplify the grid via {@code simplifier}. * @param rows Original grid rows. * @param columns Original grid columns. * @param remapRows Map the original row (key) into its correspondent computed row (value). * @param remapColumns Map the original column (key) into its correspondent computed column (value). * @param simplifier The grid simplifier. */ private void simplifyGrid(Set rows, Set columns, Map remapRows, Map remapColumns, ISimplifierGrid simplifier) { int oldValue = 0; for (Integer row : rows) { if (simplifier.canSimplifyRows(oldValue, row)) { remapRows.put(row, oldValue); } else { remapRows.put(row, row); oldValue = row; } } oldValue = 0; for (Integer column : columns) { if (simplifier.canSimplifyColumns(oldValue, column)) { remapColumns.put(column, oldValue); } else { remapColumns.put(column, column); oldValue = column; } } } /** * Find the horizontal conflicts. * @param rawElementPositions The elements position. * @param sortedElements The elements to check, sorted by its position. * @return A not {@code null} map with detected conflicts. The key identifies the conflict, * the value contains all the elements in that conflict. Elements that are not in conflict with other are placed * into a own entry map with a unique conflict id and a singleton set of elements that contains it as value. */ private SortedMap> findConflicts(Map rawElementPositions, NavigableMap sortedElements) { SortedMap> groupsById = new TreeMap<>(); Map groupByElement = new IdentityHashMap<>(); int nextIdConflict = 0; // Group by report band List> splitByBandTypeGroup = new ArrayList<>(); BandTypeEnum lastBandType = null; DocxDocumentPart lastDocxDocumentPart = null; List currentGroup = null; for (JRPrintElement element : sortedElements.values()) { JROrigin origin = element.getOrigin(); if (origin == null) origin = new JROrigin(BandTypeEnum.TITLE); BandTypeEnum bandType = origin.getBandTypeValue(); DocxDocumentPart docxDocumentPart = rawElementPositions.get(element).getDocumentPart(); boolean currentConflict = this.canBeInConflict(element); boolean newGroup = !bandType.equals(lastBandType) || !docxDocumentPart.equals(lastDocxDocumentPart); String extraLog; if (newGroup) { currentGroup = new ArrayList<>(); splitByBandTypeGroup.add(currentGroup); extraLog = ", New group"; } else extraLog = ""; log.fine(MessageFormat.format("Band = {0}, CanConflict = {1}{2}", bandType, currentConflict, extraLog)); currentGroup.add(element); lastBandType = bandType; lastDocxDocumentPart = docxDocumentPart; } // Forced conflicts Map conflictByName = new TreeMap<>(); for (List group : splitByBandTypeGroup) { for (JRPrintElement element : group) { ComponentPosition position1 = rawElementPositions.get(element); // Check for relative component position if (this.canBeInConflict(element)) { // Check for forced table (by report properties) String groupName = element.getPropertiesMap().getProperty("jasper2docx.grid.name"); if (groupName != null) { ConflictPosition idConflicts = conflictByName.get(groupName); ConflictPosition idConflictsOld = groupByElement.get(element); if (idConflicts == null) { idConflicts = new ConflictPosition(position1.getDocumentPart(), nextIdConflict++); conflictByName.put(groupName, idConflicts); idConflicts.name = groupName; } groupByElement.put(element, idConflicts); Set namedConflicts = groupsById.get(idConflicts); if (namedConflicts == null) { namedConflicts = new HashSet<>(); groupsById.put(idConflicts, namedConflicts); } namedConflicts.add(element); if (idConflictsOld != null && idConflicts.getIdConflict() != idConflicts.getIdConflict()) { Set movingElements = groupsById.remove(idConflictsOld); namedConflicts.addAll(movingElements); for (JRPrintElement movingElement : movingElements) { groupByElement.put(movingElement, idConflicts); } } } // Detect conflict between elements: one element with all others. for (JRPrintElement cmpElement : group) { if (element != cmpElement && this.canBeInConflict(cmpElement)) { ComponentPosition position2 = rawElementPositions.get(cmpElement); // Check for conflict if (this.checkForElementsConflict(position1, position2)) { ConflictPosition idConflicts1 = groupByElement.get(cmpElement); ConflictPosition idConflicts2 = groupByElement.get(element); ConflictPosition idConflicts; if (idConflicts1 == null && idConflicts2 == null) idConflicts = new ConflictPosition(position1.getDocumentPart(), nextIdConflict++); else if (idConflicts1 != null && idConflicts2 == null) idConflicts = idConflicts1; else if (idConflicts1 == null) idConflicts = idConflicts2; else if (idConflicts1.getIdConflict() != idConflicts2.getIdConflict()) { ConflictPosition idConflictsToMove; if (idConflicts1.getIdConflict() < idConflicts2.getIdConflict()) { idConflicts = idConflicts1; idConflictsToMove = idConflicts2; } else { idConflicts = idConflicts2; idConflictsToMove = idConflicts1; } Set elements = groupsById.get(idConflicts); Set movingElements = groupsById.remove(idConflictsToMove); elements.addAll(movingElements); for (JRPrintElement movingElement : movingElements) { groupByElement.put(movingElement, idConflicts); } } else idConflicts = null; if (idConflicts != null) { groupByElement.put(cmpElement, idConflicts); groupByElement.put(element, idConflicts); Set elements = groupsById.remove(idConflicts); if (elements == null) { elements = new HashSet<>(); groupsById.put(idConflicts, elements); } elements.add(cmpElement); elements.add(element); // Compute the area in conflict idConflicts.setOriginalX(Math.min(idConflicts.getOriginalX(), Math.min(position1.getX(), position2.getX()))); idConflicts.setOriginalY(Math.min(idConflicts.getOriginalY(), Math.min(position1.getY(), position2.getY()))); idConflicts.setWidth(Math.max(idConflicts.getWidth(), Math.max(position1.getOriginalX() + position1.getWidth(), position2.getOriginalX() + position2.getWidth()) - idConflicts.getOriginalX())); idConflicts.setHeight(Math.max(idConflicts.getHeight(), Math.max(position1.getOriginalY() + position1.getHeight(), position2.getOriginalY() + position2.getHeight()) - idConflicts.getOriginalY())); groupsById.put(idConflicts, elements); } } } } } if (!groupByElement.containsKey(element)) { // Non-conflict elements ConflictPosition idConflict = new ConflictPosition(position1.getDocumentPart(), nextIdConflict++); idConflict.setOriginalX(position1.getOriginalX()); idConflict.setOriginalY(position1.getOriginalY()); idConflict.setWidth(position1.getWidth()); idConflict.setHeight(position1.getHeight()); groupByElement.put(element, idConflict); HashSet noConflicts = new HashSet<>(); noConflicts.add(element); groupsById.put(idConflict, noConflicts); } } } return groupsById; } /** * Check for elements conflict: two elements are in conflict if one of this: *
    *
  • The the top of element1 is between the top (inclusive) and the bottom (exclusive) of element2.
  • *
  • The bottom of element1 is between the the top (exclusive) and the bottom (inclusice) of element2.
  • *
* @param position1 The element1 position. * @param position2 The element2 position. * @return {@code true} if {@code position1} and {@code position2} are in conflict, {@code false} otherwise. */ protected boolean checkForElementsConflict(ComponentPosition position1, ComponentPosition position2) { return ((position1.y >= position2.y && position1.y < position2.y + position2.height) || (position1.y + position1.height > position2.y && position1.y + position1.height <= position2.y + position2.height)); } /** * Check for element can be in conflict, that is when element has relative position. * @param element The element to check. * @return {@code true} if element can be in conflict with other, {@code false} otherwise */ protected boolean canBeInConflict(JRPrintElement element) { return element instanceof JRPrintImage || element instanceof JRPrintText || element instanceof JRPrintFrame; } /** * Build tables for conflicts. * @param conflicts The conflicts. * @param rawElementPositions The element positions. * @param tables Input/Output parameter for built tables. * @param nextTableId Unique start id for tables. * @param parent The parent of the tables (es. other table, frame, etc...) * @return The next free id for new tables. */ private int findTables(SortedMap> conflicts, Map rawElementPositions, Map tables, int nextTableId, ComponentPosition parent) { List> candidateTableGroups = new ArrayList<>(); List currentGroup = null; BandTypeEnum lastBandTypeEnum = null; boolean prevSingleElementGroup = false; // Group table by band. Merge table in column/group header of jasper report with their detail. // A new table will build if found a column/group header after a detail. for (Set elements : conflicts.values()) { boolean newGroup = false; if (elements.size() == 1) { newGroup = true; prevSingleElementGroup = true; } else { BandTypeEnum bandTypeEnum = elements.iterator().next().getOrigin().getBandTypeValue(); if (lastBandTypeEnum == null || lastBandTypeEnum.compareTo(bandTypeEnum) > 0) newGroup = true; else if (prevSingleElementGroup) newGroup = true; lastBandTypeEnum = bandTypeEnum; prevSingleElementGroup = false; } if (newGroup) { currentGroup = new ArrayList<>(); candidateTableGroups.add(currentGroup); } currentGroup.addAll(elements); } // Build the table (for conflicts with more then one element) for (List elements : candidateTableGroups) { if (elements.size() > 1) { NavigableSet rows = new TreeSet<>(); NavigableSet columns = new TreeSet<>(); boolean forceBreak = false; boolean forceRowHeight = true; DocxDocumentPart band = null; for (JRPrintElement element : elements) { if (this.canBeInConflict(element)) { ComponentPosition position = rawElementPositions.get(element); int col = position.getOriginalX(); int row = position.getOriginalY(); rows.add(row); rows.add(row + position.getHeight()); columns.add(col); columns.add(col + position.getWidth()); if (!forceBreak && element.getOrigin().getBandTypeValue() == BandTypeEnum.DETAIL) forceBreak = true; if (forceRowHeight && element.getOrigin().getBandTypeValue() == BandTypeEnum.DETAIL) forceRowHeight = false; } if (band == null) band = rawElementPositions.get(element).getDocumentPart(); } if (band == null) band = DocxDocumentPart.BODY; if (columns.size() > 2) { int tableRows[] = new int[rows.size() - 1]; int tableColumns[] = new int[columns.size() - 1]; Integer oldInt = null; int index = 0; for (Integer row : rows) { if (oldInt != null) tableRows[index++] = row - oldInt; oldInt = row; } oldInt = null; index = 0; for (Integer col : columns) { if (oldInt != null) tableColumns[index++] = col - oldInt; oldInt = col; } int checkMaxRow = 0; int checkMaxCol = 0; int rightCellMerged[][] = new int[tableRows.length][tableColumns.length]; for (int row = 0; row < tableRows.length; row++) { for (int col = 0; col < tableColumns.length; col++) { rightCellMerged[row][col] = col; } } ComponentTableInfo tableInfo = new ComponentTableInfo(band, nextTableId++, tableRows, tableColumns, rightCellMerged); tableInfo.setOriginalX(columns.first()); tableInfo.setOriginalY(rows.first()); tableInfo.setWidth(columns.last() - columns.first()); tableInfo.setHeight(rows.last() - rows.first()); tableInfo.setBordered(false); tableInfo.setForceRowHeight(true); tableInfo.setParent(parent); tables.put(tableInfo.getTableId(), tableInfo); for (JRPrintElement element : elements) { if (this.canBeInConflict(element)) { ComponentPositionInTable positionInTable = new ComponentPositionInTable(band, tableInfo); positionInTable.setParent(parent); ComponentPosition position = rawElementPositions.get(element); positionInTable.setOriginalX(position.getOriginalX()); positionInTable.setOriginalY(position.getOriginalY()); positionInTable.setWidth(position.getWidth()); positionInTable.setHeight(position.getHeight()); int originalCol = columns.headSet(position.getOriginalX(), false).size(); int originalRow = rows.headSet(position.getOriginalY(), false).size(); positionInTable.setCol(originalCol); positionInTable.setRow(originalRow); positionInTable.setColSpan(columns.headSet(position.getOriginalX() + position.getWidth(), false).size() - originalCol); positionInTable.setRowSpan(rows.headSet(position.getOriginalY() + position.getHeight(), false).size() - originalRow); checkMaxCol = Math.max(checkMaxCol, originalCol + positionInTable.getColSpan()); checkMaxRow = Math.max(checkMaxRow, originalRow + positionInTable.getRowSpan()); rawElementPositions.put(element, positionInTable); // Compute row/column span if (positionInTable.getColSpan() > 1 || positionInTable.getRowSpan() > 1) { Rectangle mergedRegion = new Rectangle(originalCol, originalRow, positionInTable.getColSpan(), positionInTable.getRowSpan()); tableInfo.mergedRegions.add(mergedRegion); if (mergedRegion.width > 1) { for (int rowOffset = 0; rowOffset < positionInTable.getRowSpan(); rowOffset++) { int row = positionInTable.getRow() + rowOffset; //noinspection ManualArrayCopy for (int col = originalCol + 1; col < originalCol + mergedRegion.width; col++) { rightCellMerged[row][col] = rightCellMerged[row][col - 1]; } for (int col = originalCol + mergedRegion.width; col < tableColumns.length; col++) { rightCellMerged[row][col] = rightCellMerged[row][col] - (mergedRegion.width - 1); } } } } } } if (checkMaxCol != tableColumns.length || checkMaxRow != tableRows.length) throw new JRRuntimeException("Build table error"); // Remap column index for merged cells for (JRPrintElement element : elements) { if (this.canBeInConflict(element)) { ComponentPositionInTable position = (ComponentPositionInTable) rawElementPositions.get(element); int colMerged = rightCellMerged[position.getRow()][position.getCol()]; position.setCol(colMerged); } } } } } return nextTableId; } /** * Return the page info for this layout. * @return The page info for this layout. */ public HeaderFooterPageInfo getPageInfo() { return pageInfo; } /** * Page info, like margin, size, header and footer band height. */ public static class HeaderFooterPageInfo { /** Header band height */ private Integer headerHeight; /** Footer band height */ private Integer footerHeight; /** The top edge for the header */ private Integer minHeader; /** The bottom edge for the header */ private Integer maxHeader; /** The top edge for the footer */ private Integer minFooter; /** The top edge for the footer */ private Integer maxFooter; /** Right page margin */ private int rightMargin; /** Left page margin */ private int leftMargin; /** Top page margin */ private int topMargin; /** Bottom page margin */ private int bottomMargin; /** The page size, margin inclusive */ private Dimension pageSize; /** Constructor */ private HeaderFooterPageInfo() { } /** * Return then header band height in points. * @return The header band height in points. */ public Integer getHeaderHeight() { return headerHeight; } /** * Return then footer band height in points. * @return The footer band height in points. */ public Integer getFooterHeight() { return footerHeight; } /** * Return the page size in points (width and height). Any modification of returned object is not backed to * the original one. * @return The page size in points (width and height). */ public Dimension getPageSize() { return new Dimension(pageSize); } /** * Return the right page margin in points. * @return The right page margin in points. */ public int getRightMargin() { return rightMargin; } /** * Return the left page margin in points. * @return The left page margin in points. */ public int getLeftMargin() { return leftMargin; } /** * Return the top page margin in points. * @return The top page margin in points. */ public int getTopMargin() { return topMargin; } /** * Return the bottom page margin in points. * @return The bottom page margin in points. */ public int getBottomMargin() { return bottomMargin; } /** * Return the top edge for the header in points. * @return The top edge for the header in points. */ public Integer getMinHeader() { return minHeader; } /** * Return the bottom edge for the header in points. * @return The bottom edge for the header in points. */ public Integer getMaxHeader() { return maxHeader; } /** * Return the top edge for the footer in points. * @return The top edge for the footer in points. */ public Integer getMinFooter() { return minFooter; } /** * Return the bottom edge for the footer in points. * @return The bottom edge for the footer in points. */ public Integer getMaxFooter() { return maxFooter; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof HeaderFooterPageInfo)) return false; HeaderFooterPageInfo that = (HeaderFooterPageInfo) o; if (headerHeight != null ? !headerHeight.equals(that.headerHeight) : that.headerHeight != null) return false; //noinspection SimplifiableIfStatement if (footerHeight != null ? !footerHeight.equals(that.footerHeight) : that.footerHeight != null) return false; return pageSize.equals(that.pageSize); } public int hashCode() { int result = headerHeight != null ? headerHeight.hashCode() : 0; result = 31 * result + (footerHeight != null ? footerHeight.hashCode() : 0); result = 31 * result + pageSize.hashCode(); return result; } /** * Return if there is an header or footer. * @return {@code true} is there is an header or footer. */ public boolean containsHeaderFooter() { return this.footerHeight != null || this.headerHeight != null; } } /** * The new coordinate for elements in simplified grid layout. */ public class ComponentPosition implements Comparable { /** The x position (in points) */ private int x; /** The y position (in points) */ private int y; /** The width (in points) */ private int width; /** The height (in points) */ private int height; /** Optional parent (ex.table) */ private ComponentPosition parent; /** The document part */ private final DocxDocumentPart documentPart; /** * Construct the coordinate with the document part. * @param documentPart The document part. */ public ComponentPosition(DocxDocumentPart documentPart) { this.documentPart = documentPart; } /** * Construct the coordinate. * @param x The x position * @param y The y position * @param width The width * @param height The height * @param documentPart The document part */ public ComponentPosition(int x, int y, int width, int height, DocxDocumentPart documentPart) { this.x = x; this.y = y; this.width = width; this.height = height; this.documentPart = documentPart; } /** * Return the x position relative to the right margin. * @return The x position relative to the right margin. */ public int getX() { return x; //- pageInfo.getRightMargin(); } /** * Set the x position. * @param x The x position. */ public void setOriginalX(int x) { this.x = x; } /** * Return the y position, relative the document part and the top and bottom margin. * @return The y position, relative the document part and the top and bottom margin. */ public int getY() { HeaderFooterPageInfo pageInfo = getPageInfo(); int header = 0; int footer = 0; if (pageInfo.getHeaderHeight() != null) header = pageInfo.getHeaderHeight(); if (pageInfo.getFooterHeight() != null) footer = pageInfo.getFooterHeight(); DocxDocumentPart calcBand = this.getDocumentPart(); if (calcBand == DocxDocumentPart.HEADER) return y + pageInfo.getTopMargin(); else if (calcBand == DocxDocumentPart.FOOTER) return y - (pageInfo.getPageSize().height - footer - pageInfo.getBottomMargin()); else return y - header - pageInfo.getTopMargin(); } /** * Set the y position. * @param y The y position. */ public void setOriginalY(int y) { this.y = y; } /** * Return the x position. * @return The x position. */ public int getOriginalX() { return this.x; } /** * Return the y position. * @return The y position. */ public int getOriginalY() { return this.y; } /** * Return the width. * @return The width. */ public int getWidth() { return width; } /** * Set the width. * @param width The width. */ public void setWidth(int width) { this.width = width; } /** * Return the height. * @return The height. */ public int getHeight() { return height; } /** * Set the height. * @param height The height. */ public void setHeight(int height) { this.height = height; } /** * Return the {@link DocxDocumentPart document part}. * @return The document part. */ public DocxDocumentPart getDocumentPart() { return this.documentPart; } /** * Return the container position of this, if any. * @return The position of the parent or {@code null} if the element is in the root of page. */ public ComponentPosition getParent() { return parent; } /** * Set the parent for this position. * @param parent The parent. */ public void setParent(ComponentPosition parent) { this.parent = parent; } /** * Sort the position. * @param o The other position. * @see J2WGridPageLayout */ @SuppressWarnings({"SuspiciousNameCombination", "NullableProblems"}) @Override public int compareTo(ComponentPosition o) { return this.compareResult( Integer.compare(this.y, o.y), Integer.compare(this.x, o.x), this.compareParents(o)); } /** * Safe parent comparison. * @param o The other position. * @return A negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. */ private int compareParents(ComponentPosition o) { if (this.parent == null && o.parent != null) return 1; else if (this.parent != null && o.parent == null) return -1; else if (this.parent == null) return 0; else return this.parent.compareTo(o.parent); } /** * Return first non zero integer or zero otherwise. * @param compareRes Comparison results. * @return The first non zero integer or zero otherwise. */ protected int compareResult(int ... compareRes) { for (int cmp: compareRes) { if (cmp != 0) return cmp; } return 0; } @Override public String toString() { return MessageFormat.format("[(x={0},y={1}) -> (w={2},h={3})]", this.x, this.y, this.width, this.height); } } /** * Position inside a table. Same as {@link ComponentPosition}, but with row, col, rowspan, colspan and table position. */ public class ComponentPositionInTable extends ComponentPosition { /** The row in the table (zero index based) */ private int row; /** The column in the table (zero index based) */ private int col; /** The row span (at least one) */ private int rowSpan; /** The column span (at least one) */ private int colSpan; /** The table position */ private final ComponentTableInfo tableInfo; /** * Construct a new position in table. * @param band The document part. * @param tableInfo The table position. */ public ComponentPositionInTable(DocxDocumentPart band, ComponentTableInfo tableInfo) { super(band); this.tableInfo = tableInfo; } /** * Return the row in the table (zero index based). * @return The row in table. */ public int getRow() { return row; } /** * Set the row in table (zero index based). * @param row The row in table. */ public void setRow(int row) { this.row = row; } /** * Return the column in table (zero index based). * @return The column in table. */ public int getCol() { return col; } /** * Set the column in table (zero index based). * @param col The column in table. */ public void setCol(int col) { this.col = col; } /** * Return the row span (at least one). * @return The row span. */ public int getRowSpan() { return rowSpan; } /** * Set the row span (at least one). * @param rowSpan The row span. */ public void setRowSpan(int rowSpan) { this.rowSpan = rowSpan; } /** * Return the column span (at least one). * @return The column span. */ public int getColSpan() { return colSpan; } /** * Set the column span (al least one). * @param colSpan The column span. */ public void setColSpan(int colSpan) { this.colSpan = colSpan; } /** * Return the table position. * @return The table position. */ public ComponentTableInfo getTableInfo() { return tableInfo; } @Override public int compareTo(ComponentPosition o) { if (o instanceof ComponentPositionInTable) { ComponentPositionInTable that = (ComponentPositionInTable) o; return this.compareResult(super.compareTo(o), Integer.compare(this.tableInfo.getTableId(), that.tableInfo.getTableId()), Integer.compare(this.row, that.row), Integer.compare(this.col, that.col)); } else return super.compareTo(o); } @Override public String toString() { return super.toString() + MessageFormat.format("[(c={0},r={1}) -> (w={2},h={3}), tableId={4}]", this.col, this.row, this.colSpan, this.rowSpan, this.tableInfo.getTableId()); } } /** * Table position. */ public class ComponentTableInfo extends ComponentPosition { /** A unique table id */ private final int tableId; /** Size of rows */ private final int rows[]; /** Size of columns */ private final int columns[]; /** Draw table border */ private boolean bordered; /** Exact row height */ private boolean forceRowHeight; /** Merged cells */ private final List mergedRegions; /** The name for the table */ private String name; /** The table cells coordinates for merged regions */ private final int mapMergedCell[][]; /** * Construct a table position. * @param band The document part. * @param tableId The unique table id. * @param rows The rows size, so {@code rows.length} is the number of rows and {@code rows[i]} is the size in points of {@code i-row}. * @param columns The columns size, so {@code columns.length} is the number of columns and {@code coluns[i]} is the size in points of {@code i-column}. * @param mapMergedCell Remap table cell coordinates for merged cell. */ private ComponentTableInfo(DocxDocumentPart band, int tableId, int[] rows, int[] columns, int mapMergedCell[][]) { super(band); this.tableId = tableId; this.rows = rows; this.columns = columns; this.mergedRegions = new ArrayList<>(); this.mapMergedCell = mapMergedCell; } /** * Return the unique id of the table. * @return The unique id of the table. */ public int getTableId() { return tableId; } /** * Return the rows size.
* {@code rows.length} is the number of rows and {@code rows[i]} is the size in points of {@code i-row}. * @return The rows size */ public int[] getRows() { return rows; } /** * Return the rows size.
* {@code columns.length} is the number of columns and {@code coluns[i]} is the size in points of {@code i-column}. * @return The columns size */ public int[] getColumns() { return columns; } /** * Return if table has borders. * @return {@code true} if table has borders, {@code false} otherwise. */ public boolean isBordered() { return bordered; } /** * Set if table has borders. * @param bordered {@code true} if table has borders, {@code false} otherwise. */ public void setBordered(boolean bordered) { this.bordered = bordered; } /** * Return if the table row size must be exact. * @return {@code true} if the row size must be exact, {@code false} for automatic. */ public boolean isForceRowHeight() { return forceRowHeight; } /** * Set if the table row size must be exact. * @param forceRowHeight {@code true} if the row size must be exact, {@code false} for automatic. */ public void setForceRowHeight(boolean forceRowHeight) { this.forceRowHeight = forceRowHeight; } /** * Return the cells merged of the table. A region is a rectangle with its x and y coordinates are the column and row of the table and * the width and height are the column span and row span. * @return The cells merged of the table. */ public List getMergedRegions() { return Collections.unmodifiableList(mergedRegions); } /** * Convert absolute coordinates of table into relating ones, in case of merged cells. * @param column The absolute column. * @param row The absolute row. * @return The relating coordinates in table. */ public Point getCellCoordinate(int column, int row) { return new Point(this.mapMergedCell[row][column], row); } /** * Convert absolute coordinates of table into relating ones, in case of merged cells. * @param coord The absolute coordinates. * @return The relating coordinates in table. */ public Point getCellCoordinate(Point coord) { return this.getCellCoordinate(coord.x, coord.y); } /** * Convert relating coordinates in table into absolute ones, in case of merged cells. * @param column The relative column. * @param row The relative row. * @return The absolute coordinates in table. */ public Point getInverseCellCoordinate(int column, int row) { int[] ints = this.mapMergedCell[row]; for (int i = 0; i < ints.length; i++) { int col = ints[i]; if (col == column) return new Point(i, row); } throw new IllegalArgumentException("Invalid cell coordinate: (c=" + column + ", r=" + row + ")"); } /** * Convert relating coordinates in table into absolute ones, in case of merged cells. * @param coord The relative coordinates. * @return The absolute coordinates in table. */ public Point getInverseCellCoordinate(Point coord) { return this.getInverseCellCoordinate(coord.x, coord.y); } /** * Return the name of the table. * @return Table's name */ public String getName() { return name; } @Override public String toString() { return super.toString() + MessageFormat.format(" [rows={0},columns={1}]", this.rows.length, this.columns.length); } } /** * The conflict area. */ private class ConflictPosition extends ComponentPosition { /** Unique id conflict */ private final int idConflict; /** Optiona conflict name */ private String name; /** * Construct a conflict. * @param documentPart The document part. * @param idConflict The unique id conflict. */ public ConflictPosition(DocxDocumentPart documentPart, int idConflict) { super(documentPart); this.idConflict = idConflict; this.setOriginalX(Integer.MAX_VALUE); this.setOriginalY(Integer.MAX_VALUE); this.setWidth(0); this.setHeight(0); } /** * Return the name of the conflict. * @return The name of the conflict. */ public String getName() { return name; } /** * Return the unique id conflict. * @return The unique id conflict. */ public int getIdConflict() { return idConflict; } @Override public int compareTo(ComponentPosition o) { int res = super.compareTo(o); if (res == 0 && o instanceof ConflictPosition) res = Integer.compare(this.idConflict, ((ConflictPosition) o).idConflict); return res; } } /** * A simplifier grid class, that reduce gaps. */ public static class GapSimplifierGrid implements ISimplifierGrid { /** Minimum x gap */ private final int gapX; /** Minimum y gap */ private final int gapY; /** * Construct a grid simplifier with minimum gaps indicated. * @param gapX The minimum x gap between elements. * @param gapY The minimum y gap between elements. */ public GapSimplifierGrid(int gapX, int gapY) { this.gapX = gapX; this.gapY = gapY; } /** * Return the minimum x gap between elements. Two x coordinates distant from each other less then {@code gapX} are * merged into the minimum x. * @return The minimum x gap between elements. */ public int getGapX() { return gapX; } /** * Return the minimum y gap between elements. Two y coordinates distant from each other less then {@code gapY} are * merged into the minimum y. * @return The minimum y gap between elements. */ public int getGapY() { return gapY; } public boolean canSimplifyRows(int row1, int row2) { return Math.abs(row1 - row2) < this.gapY; } public boolean canSimplifyColumns(int col1, int col2) { return Math.abs(col1 - col2) < this.gapX; } } /** * A percentage simplifier grid. Two coordinates are similar if the ratio between their gaps and the reference base is less then * the reference percentage.
* Ex.
*     {@code x1} similar {@code x2} if only if {@code abs(x1 - x2) / baseX} <= {@code percentX}
* or else
*     {@code y1} similar {@code y2} if only if {@code abs(y1 - y2) / baseY} <= {@code percentY}. */ public static class PercentageSimplifierGrid implements ISimplifierGrid { /** The x percentage tolerance */ private final double percentX; /** The y percentage tolerance */ private final double percentY; /** Grid x base */ private final int baseX; /** Grid y base */ private final int baseY; /** * Construct a percent simplifier grid with bases and percentages. * @param percentX The x percentage tolerance. * @param percentY The y percentage tolerance. * @param baseX The grid x base. * @param baseY The grid y base. */ public PercentageSimplifierGrid(double percentX, double percentY, int baseX, int baseY) { this.percentX = percentX; this.percentY = percentY; this.baseX = baseX; this.baseY = baseY; } public boolean canSimplifyRows(int row1, int row2) { if (row1 == row2) return true; else { int diff = Math.abs(row1 - row2); return ((double) diff / (double) this.baseX) < this.percentY; } } public boolean canSimplifyColumns(int col1, int col2) { if (col1 == col2) return true; else { int diff = Math.abs(col1 - col2); return ((double) diff / (double) this.baseY) < this.percentX; } } } /** * Sorting key for element positions. */ private static class JRPrintElementComparableKey implements Comparable { /** Extra id unique value */ private final int index; /** The element origin */ private final JROriginKey origin; /** The element type */ private final JRTypeKey type; /** The element position */ private final ComponentPosition position; /** * Construct the sorting key. * @param element The jasper element. * @param position The element position. * @param index An extra unique id. */ public JRPrintElementComparableKey(JRPrintElement element, ComponentPosition position, int index) { this.index = index; this.origin = new JROriginKey(element.getOrigin()); this.position = position; if (element instanceof JRPrintText) this.type = JRTypeKey.TEXT; else if (element instanceof JRPrintImage) this.type = JRTypeKey.IMAGE; else if (element instanceof JRPrintRectangle || element instanceof JRPrintLine || element instanceof JRPrintEllipse) this.type =JRTypeKey.GRAPHICS; else if (element instanceof JRPrintFrame) this.type = JRTypeKey.FRAME; else this.type = JRTypeKey.OTHER; } public int compareTo(JRPrintElementComparableKey o) { return this.compareResult(o, this.position.compareTo(o.position)); } /** * Return first non-zero values. * @param other The other value to compare. * @param compareRes The previous comparison result. * @return The first non-zero values, or zero otherwise. */ private int compareResult(JRPrintElementComparableKey other, int ... compareRes) { for (int cmp: compareRes) { if (cmp != 0) return cmp; } return Integer.compare(this.index, other.index); } @Override public String toString() { return MessageFormat.format("[origin={0}, type={1}, position={2}, index={3}]", this.origin.toString(), this.type.toString(), this.position.toString(), this.index); } } /** * Origin sorting key. */ private static class JROriginKey implements Comparable { /** Jasper element origin */ private final JROrigin origin; /** * Construct the key. * @param origin The element origin. */ public JROriginKey(JROrigin origin) { if (origin != null) this.origin = origin; else this.origin = new JROrigin(BandTypeEnum.UNKNOWN); } public int compareTo(JROriginKey o) { return this.compareResult( this.origin.getBandTypeValue().compareTo(o.origin.getBandTypeValue()), this.compareNullString(this.origin.getReportName(), o.origin.getReportName()), this.compareNullString(this.origin.getGroupName(), o.origin.getGroupName())); } /** * Return first non-zero values. * @param compareRes The previous comparison result. * @return The first non-zero values, or zero otherwise. */ private int compareResult(int ... compareRes) { for (int cmp: compareRes) { if (cmp != 0) return cmp; } return 0; } /** * String safe comparator. * @param s1 The first string. * @param s2 The second string. * @return The string comparison: a {@code null} value is less then a not {@code null} value. */ private int compareNullString(String s1, String s2) { if (s1 == null && s2 == null) return 0; else if (s1 == null) return -1; else if (s2 == null) return 1; else return s1.compareTo(s2); } @Override public String toString() { return MessageFormat.format("[b={0}, r={1}, g={2}]", this.origin.getBandTypeValue(), this.origin.getReportName(), this.origin.getGroupName()); } } /** * Element type sorting key. */ private enum JRTypeKey implements Comparable { /** A text element */ TEXT, /** An image element */ IMAGE, /** A generic graphics element (line, ellipse, rectangle) */ GRAPHICS, /** A jasper report frame */ FRAME, /** Other... */ OTHER } /** * A simple status index class. */ private static class FoundPositionsStatus { /** The next table id */ private int tableId ; /** The next unique element id */ private int elementIndex; /** * Constructor. */ public FoundPositionsStatus() { this.tableId = 0; this.elementIndex = 0; } } private static class DefaultElementFilter implements ExporterFilter { private final ExporterFilter defaultFilter; private DefaultElementFilter(ExporterFilter defaultFilter) { this.defaultFilter = defaultFilter; } public boolean isToExport(JRPrintElement element) { boolean res = element != null && !(element instanceof JRGenericPrintElement); if (false && this.defaultFilter != null) res = this.defaultFilter.isToExport(element); return res; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy