package de.am_soft.sm_mtg.view.report.text.data; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.stream.Collectors; import de.vandermeer.asciitable.AT_Cell; import de.vandermeer.asciitable.AT_CellContext; import de.vandermeer.asciitable.AT_ColumnWidthCalculator; import de.vandermeer.asciitable.AT_Row; import de.vandermeer.skb.interfaces.document.TableRowType; /** * Custom column width caluclator working around some limitations of spanning columns. *
* We prefer rendering tables using {@code CWC_LongestLine}, but that has some limitations in case * of spanning columns: In the original implementation, the width of spanning columns itself are * compared to the width of the same non-spanning columns to see which one is larger. The problem * with this approach is that the spanning columns get the space of the columns they span as well * during rendering and all the space of the spanning and spanned columns might be enough to render * the content. But the space of the spanning column itself might be larger than the one of non- * spanning columns, which leads to non-spanning columns might get more space than their longest * line really deserves. *
** Consider two rows, with the first being some header spanning all columns of the second row and * the second row containing actual tabular data. The header might be pretty long compared to the * individual columns of the second row, which e.g. might only provide short numbers of some kind. * The text for the header needs to be the last column of the first row, because that's how spans * work. If that text is e.g. 50 chars long, but the last column of the second row otherwise only 10 * chars, the last column gets the width of 50 even if e.g. 10 columns with 10 chars each exist in * the second row. Those 10 * 10 chars would easily be enough for 50 chars of the header if that * column would span all other columns, but nevertheless its width is taken on its own. *
** So this calculator has been implemented working around those limitations by taking the length of * all spanned columns into account as well when deciding the width of the one spanning column. The * other possible workaround would be to keep using {@code CWC_LongestLine} and set a maximum width * for columns which would otherwise be calculated too long. The problem with that is that one needs * to know the exact width of all those columns, which might be different for different content e.g. * because of differently localized texts etc. Calculating the width properly in the first place is * the better approach. *
** This CWC doesn't support minimum and maximum cell lengths currently, because we simply didn't * need those yet. *
*@see Spanning Sizes Last Column Too Wide */ class VwrDvResultsCwc implements AT_ColumnWidthCalculator { /** * The width of one individual cell. ** A cell either has a width not, because it's {@code null} and this way by convention consumed * by some other non-{@code null} cell spanning all individual {@code null}-cell on its left. *
*/ private static class CellWidth { /** * Singleton for all cases in which no width is available because cells are spanned by other * cells. */ private static final CellWidth NONE = new CellWidth(null); /** * The width of the cell or {@code null} if none is available. */ private final Integer width; /** * CTOR simply storing the given arg. * * @param width The width or {@code null}. */ private CellWidth(Integer width) { this.width = width; } /** * Check if the associated cell has some width. * * @return {@code true} if some width is available, {@code false} otherwise. */ private boolean hasWidth() { return this.width != null; } /** * Calculate the width of the given cell. * * @param cell to calculate the width for, not {@code null}. * @return The width of the cell, which might be {@link #NONE}. */ private static CellWidth of(AT_Cell cell) { cell = Objects.requireNonNull(cell, "No cell given."); Object content = cell.getContent(); AT_CellContext context = cell.getContext(); if (content == null) { return CellWidth.NONE; } String[] lines = new VwrDvResultsCwcStringifier().apply(content); int retVal = 0; // We follow the approach of CWC_LongestLine and take each individual line of a cell // into account. for (String line : lines) { int padding = context.getPaddingLeft() + context.getPaddingRight(); int lineWidth = line.length() + padding; retVal = Math.max(retVal, lineWidth); } return new CellWidth(retVal); } /** * Remapper if cell widths are stored in some map and need to be replaced using {@code merge}. */ private static class Remapper implements BiFunction* This should be used with content-rows only, we don't need to care about other rows, and it * doesn't filter things like spanning or spanned cells yet. It's really all widths for all * cells, though cells might have no width at all here because they get spanned. That piece of * information is later used to handle those cases specially. *
*/ @SuppressWarnings("serial") private static class CellWidths extends ArrayList* All widths of all cells in all rows are needed to decide at some point if the space occupied * by spanning columns fits into the combined space of the spanned columns or if the spanning * columns define maximum column width instead. One needs to know the widht of all individual * columns, which cols don't have a width because they are spanned etc. *
*/ @SuppressWarnings("serial") private static class CellWidthsAll extends ArrayList* To be able to calculate the width of spanning cells, one needs the underlying non-spanning * ones and this class manages those by their index. When processing spanning cells, this way * one can easily retrieve the widths of the spanned cells this way, combine them and check if * the resulting width is enough for the spanning column or not. *
*/ @SuppressWarnings("serial") private static class CellWidthsNonSpan extends HashMap* To decide if spanning cells fit into the width of their spanned cells, one needs the width of * the spanning cells themself as well of course and this class provides those. *
*/ @SuppressWarnings("serial") private static class CellWidthsSpan extends HashMap* This class provides all those spanning cells, which don't fit into the space provided by * their spanned cells, so that the spanning cells provide the width of their associated content * cell instead. *
*/ @SuppressWarnings("serial") private static class CellWidthsSpanIf extends HashMap* This class provides the resulting widths of all cells, containing spanning and non-spanning * ones as necessary, with the spanning ones possibly overwriting non-spanning. *
*/ @SuppressWarnings("serial") private static class CellWidthsResult extends TreeMap* This class takes the indexes of all the cells part of one spanning cell in one row. Because * by convention the rightmost index is the only cell with the content, an ordered structure of * indexes is used, so that one can simply access the highest index and get the content-cell. *
*/ @SuppressWarnings("serial") private static class CellSpan extends TreeSet* Different cells in different rows can span differently and all of those spanning cells are * necessary to check if their content fits into the space provided by the non-spanning cells * the spanning ones span. One doesn't need to care in which row those spanning cells are placed * at that point, we only need to know all of those. *
*/ @SuppressWarnings("serial") private static class CellSpans extends ArrayList