public void testOrphan4() throws Exception {
    final MasterReport masterReport =
        DebugReportRunner.parseGoldenSampleReport("Prd-2087-Orphan-4.prpt");

    final LogicalPageBox box = DebugReportRunner.layoutPage(masterReport, 0);
    final RenderNode srs[] = MatchFactory.findElementsByElementType(box, SubReportType.INSTANCE);
    assertEquals(1, srs.length);
    assertEquals(StrictGeomUtility.toInternalValue(20), srs[0].getY());
    final RenderNode elementByName = MatchFactory.findElementByName(box, "outer-group");
    assertEquals(StrictGeomUtility.toInternalValue(20), elementByName.getY());
  }
  @Test
  public void testCanvasWithPrePostPad() throws Exception {
    final MasterReport report = new MasterReport();
    report.setDataFactory(new TableDataFactory("query", new DefaultTableModel(10, 1)));
    report.setQuery("query");

    final Band table = TableTestUtil.createTable(1, 1, 6, new CustomProducer());
    table.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, 200f);
    table.getStyle().setStyleProperty(ElementStyleKeys.POS_X, 100f);
    table.getStyle().setStyleProperty(ElementStyleKeys.POS_Y, 10f);
    table.setName("table");
    report.getReportHeader().addElement(TableTestUtil.createDataItem("Pre-Padding", 100, 10));
    report.getReportHeader().addElement(table);

    Element postPaddingItem = TableTestUtil.createDataItem("Post-Padding", 100, 10);
    postPaddingItem.getStyle().setStyleProperty(ElementStyleKeys.POS_X, 300f);
    report.getReportHeader().addElement(postPaddingItem);
    report.getReportHeader().setLayout("canvas");

    PdfReportUtil.createPDF(report, "test-output/PRD-3857-output-canvas.pdf");

    List<LogicalPageBox> pages = DebugReportRunner.layoutPages(report, 0, 1, 2);
    assertPageValid(pages, 0, StrictGeomUtility.toInternalValue(10));
    assertPageValid(pages, 1);
    assertPageValid(pages, 2);
  }
  public void testPagebreakHonoredOnFirstPage() throws Exception {
    final MasterReport masterReport = DebugReportRunner.parseGoldenSampleReport("Pre-492.prpt");
    final LogicalPageBox page0 = DebugReportRunner.layoutPage(masterReport, 0);
    final RenderNode[] elementsByElementType =
        MatchFactory.findElementsByElementType(page0.getContentArea(), AutoLayoutBoxType.INSTANCE);
    assertEquals(31, elementsByElementType.length);
    assertEquals(
        StrictGeomUtility.toInternalValue(199),
        elementsByElementType[elementsByElementType.length - 1].getY());

    final LogicalPageBox page1 = DebugReportRunner.layoutPage(masterReport, 1);
    final RenderNode[] elementsPage1 =
        MatchFactory.findElementsByElementType(page1.getContentArea(), AutoLayoutBoxType.INSTANCE);
    assertEquals(34, elementsPage1.length);
    assertEquals(
        StrictGeomUtility.toInternalValue(211), elementsPage1[elementsPage1.length - 1].getY());
    //  ModelPrinter.INSTANCE.print(page1);
  }
  /**
   * Canvas elements do not shift content. Therefore the widow definition is not effective.
   *
   * @throws Exception
   */
  public void testOrphan5() throws Exception {
    final MasterReport masterReport =
        DebugReportRunner.parseGoldenSampleReport("Prd-2087-Orphan-5.prpt");
    // masterReport.setCompatibilityLevel(ClassicEngineBoot.computeVersionId(3, 8, 0));
    // DebugReportRunner.createXmlPageable(masterReport);

    final LogicalPageBox box = DebugReportRunner.layoutPage(masterReport, 0);
    final RenderNode elementByName = MatchFactory.findElementByName(box, "outer-group");
    assertEquals(StrictGeomUtility.toInternalValue(20), elementByName.getY());
    //    ModelPrinter.INSTANCE.print(box);
    //    DebugReportRunner.showDialog(masterReport);

  }
  public void alignCenter() {

    final long farLeftPostion = computeFarLeftPosition();
    final long farRightPostion = computeFarRightPostion();
    final long currentPageWidth =
        StrictGeomUtility.toInternalValue(currentPageDefinition.getWidth());
    final long remainingRightSpace = currentPageWidth - farRightPostion;
    final long normalizedSpace = (farLeftPostion + remainingRightSpace) / 2;

    long requiredShift = normalizedSpace - farLeftPostion;
    if (remainingRightSpace > farLeftPostion) {
      // move to the Right
      requiredShift = Math.abs(requiredShift);
    } else {
      // move to the Left
      requiredShift = 0 - Math.abs(requiredShift);
    }

    final int shiftInPoints = (int) StrictGeomUtility.toExternalValue(requiredShift);
    align(shiftInPoints, visualElements);
    registerChanges();
  }
  @Test
  public void testBlockWithPrePostPad() throws Exception {
    final MasterReport report = new MasterReport();
    report.setDataFactory(new TableDataFactory("query", new DefaultTableModel(10, 1)));
    report.setQuery("query");

    final Band table = TableTestUtil.createTable(1, 1, 6, true);
    table.setName("table");
    report.getReportHeader().addElement(TableTestUtil.createDataItem("Pre-Padding", 100, 10));
    report.getReportHeader().addElement(table);
    report.getReportHeader().addElement(TableTestUtil.createDataItem("Post-Padding", 100, 10));
    report.getReportHeader().setLayout("block");

    PdfReportUtil.createPDF(report, "test-output/PRD-3857-output-block.pdf");

    List<LogicalPageBox> pages = DebugReportRunner.layoutPages(report, 0, 1, 2);
    assertPageValid(pages, 0, StrictGeomUtility.toInternalValue(10));
    assertPageValid(pages, 1);
    assertPageValid(pages, 2);
    //    assertPageValid(report, 3);
    //    assertPageValid(report, 4);
  }
  public void testWeirdTocLayout() throws ReportProcessingException, ContentProcessingException {
    Element textField = new Element();
    textField.setName("textField");
    textField.getStyle().setStyleProperty(TextStyleKeys.FONT, "Arial");
    textField.getStyle().setStyleProperty(TextStyleKeys.FONTSIZE, 14);
    textField.getStyle().setStyleProperty(TextStyleKeys.TEXT_WRAP, TextWrap.NONE);
    textField.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, 97f);
    textField.getStyle().setStyleProperty(ElementStyleKeys.MIN_HEIGHT, 20f);
    textField.getStyle().setStyleProperty(ElementStyleKeys.POS_X, 0f);
    textField.getStyle().setStyleProperty(ElementStyleKeys.POS_Y, 0f);
    textField.setElementType(LabelType.INSTANCE);
    textField.setAttribute(
        AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE, "Classic Cars");

    Element dotField = new Element();
    dotField.setName("dotField");
    dotField.getStyle().setStyleProperty(TextStyleKeys.FONT, "Arial");
    dotField.getStyle().setStyleProperty(TextStyleKeys.FONTSIZE, 14);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.ALIGNMENT, ElementAlignment.RIGHT);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.VALIGNMENT, ElementAlignment.TOP);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.POS_X, 97f);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.POS_Y, 0f);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, 628.463f);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, 20f);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.WIDTH, 100f);
    dotField.getStyle().setStyleProperty(ElementStyleKeys.MAX_WIDTH, 100f);
    dotField.setElementType(LabelType.INSTANCE);
    dotField.setAttribute(
        AttributeNames.Core.NAMESPACE,
        AttributeNames.Core.VALUE,
        " . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
            + " . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
            + ". . . . . . . . . . . . . . . . . .");

    Band band = new Band();
    band.setName("outer-box");
    band.setLayout("inline");
    band.getStyle().setStyleProperty(ElementStyleKeys.BOX_SIZING, BoxSizing.CONTENT_BOX);
    band.getStyle().setStyleProperty(ElementStyleKeys.OVERFLOW_X, false);
    band.getStyle().setStyleProperty(ElementStyleKeys.OVERFLOW_Y, false);
    band.getStyle().setStyleProperty(TextStyleKeys.LINEHEIGHT, 1f);
    band.getStyle()
        .setStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE, WhitespaceCollapse.PRESERVE_BREAKS);
    band.getStyle().setStyleProperty(ElementStyleKeys.MIN_WIDTH, 708f);
    band.getStyle().setStyleProperty(ElementStyleKeys.MIN_HEIGHT, 12f);
    band.getStyle().setStyleProperty(ElementStyleKeys.MAX_HEIGHT, 20f);
    band.addElement(textField);
    band.addElement(dotField);

    final MasterReport report = new MasterReport();
    report.getReportHeader().addElement(band);

    LogicalPageBox logicalPageBox =
        DebugReportRunner.layoutSingleBand(report, report.getReportHeader(), false, false);
    // ModelPrinter.INSTANCE.print(logicalPageBox);

    RenderBox outerBox = (RenderBox) MatchFactory.findElementByName(logicalPageBox, "outer-box");
    RenderNode dotFieldBox = MatchFactory.findElementByName(logicalPageBox, "dotField");
    RenderNode textFieldBox = MatchFactory.findElementByName(logicalPageBox, "textField");
    assertNotNull(outerBox);
    assertNotNull(dotFieldBox);
    assertNotNull(textFieldBox);

    assertEquals(0, outerBox.getY());
    assertEquals(0, dotFieldBox.getY());
    assertEquals(0, textFieldBox.getY());

    // box only contains one line, and min-size is set to 8, max size = 20, so the line-height of
    // 14.024 is used.
    assertEquals(StrictGeomUtility.toInternalValue(14.024), outerBox.getHeight());
    assertEquals(StrictGeomUtility.toInternalValue(14.024), outerBox.getFirstChild().getHeight());
    assertEquals(StrictGeomUtility.toInternalValue(14), dotFieldBox.getHeight());
    assertEquals(StrictGeomUtility.toInternalValue(14), textFieldBox.getHeight());
  }
  public void update(final Point2D normalizedPoint, final double zoomFactor) {
    final SnapPositionsModel horizontalSnapModel = getHorizontalSnapModel();
    final Element[] selectedVisualElements = getSelectedVisualElements();
    final long originPointX = getOriginPointX();
    final long[] elementWidth = getElementWidth();
    final long px = StrictGeomUtility.toInternalValue(normalizedPoint.getX());
    final long dx = px - originPointX;

    for (int i = 0; i < selectedVisualElements.length; i++) {
      final Element element = selectedVisualElements[i];
      if (element instanceof RootLevelBand) {
        continue;
      }
      final ElementStyleSheet styleSheet = element.getStyle();
      final double elementMinWidth =
          styleSheet.getDoubleStyleProperty(ElementStyleKeys.MIN_WIDTH, 0);

      // this is where I want the element on a global scale...
      final long targetWidth = elementWidth[i] + dx;
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(element);
      final long elementX = data.getX();
      final long targetX2 = elementX + targetWidth;

      if (elementMinWidth >= 0) {
        // absolute position; resolving is easy here
        final long snapPosition =
            horizontalSnapModel.getNearestSnapPosition(targetX2, element.getObjectID());
        if (Math.abs(snapPosition - targetX2) > snapThreshold) {
          final long localWidth = Math.max(0, targetX2 - elementX);
          final float position = (float) StrictGeomUtility.toExternalValue(localWidth);
          styleSheet.setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(position));
        } else {
          final long localWidth = Math.max(0, snapPosition - elementX);
          final float position = (float) StrictGeomUtility.toExternalValue(localWidth);
          styleSheet.setStyleProperty(ElementStyleKeys.MIN_WIDTH, new Float(position));
        }
      } else {
        final Element parent = element.getParentSection();
        final CachedLayoutData parentData = ModelUtility.getCachedLayoutData(parent);

        final long parentBase = parentData.getWidth();
        if (parentBase > 0) {
          // relative position; resolve the percentage against the height of the parent.
          final long snapPosition =
              horizontalSnapModel.getNearestSnapPosition(targetX2, element.getObjectID());
          if (Math.abs(snapPosition - targetX2) > snapThreshold) {
            final long localWidth = Math.max(0, targetX2 - elementX);
            // strict geometry: all values are multiplied by 1000
            // percentages in the engine are represented by floats betwen 0 and 100.
            final long percentage =
                StrictGeomUtility.toInternalValue(localWidth * 100 / parentBase);
            styleSheet.setStyleProperty(
                ElementStyleKeys.MIN_WIDTH,
                new Float(StrictGeomUtility.toExternalValue(-percentage)));
          } else {
            final long localWidth = Math.max(0, snapPosition - elementX);
            // strict geometry: all values are multiplied by 1000
            // percentages in the engine are represented by floats betwen 0 and 100.
            final long percentage =
                StrictGeomUtility.toInternalValue(localWidth * 100 / parentBase);
            styleSheet.setStyleProperty(
                ElementStyleKeys.MIN_WIDTH,
                new Float(StrictGeomUtility.toExternalValue(-percentage)));
          }
        }
      }

      element.notifyNodePropertiesChanged();
    }
  }
 static {
   final long value = StrictGeomUtility.toInternalValue(1);
   conversionFactor =
       value / org.pentaho.reporting.libraries.fonts.tools.StrictGeomUtility.toInternalValue(1);
 }
/**
 * Another static processing step which validates the table structure and computes the cell
 * positions within the table.
 *
 * @author Thomas Morgner
 */
public class TableValidationStep extends IterateStructuralProcessStep {
  private static final long MAX_AUTO = StrictGeomUtility.toInternalValue(0x80000000000L);

  private static class TableInfoStructure {
    private TableRenderBox table;
    private TableInfoStructure parent;
    private TableColumnModel columnModel;
    private TableSectionRenderBox sectionRenderBox;
    private IntList rowSpans;
    private TableRowModel rowModel;
    protected int tableCellPosition;
    protected int rowCount;
    private boolean bodySection;

    public TableInfoStructure(final TableRenderBox table, final TableInfoStructure parent) {
      this.table = table;
      this.parent = parent;
      this.columnModel = table.getColumnModel();
      this.rowSpans = new IntList(10);
    }

    public void resetCellPosition() {
      this.tableCellPosition = 0;
    }

    private boolean isCellAreaClear(final int pos, final int colSpan) {
      final int maxIdx = Math.min(pos + colSpan, rowSpans.size());
      for (int i = pos; i < maxIdx; i++) {
        if (rowSpans.get(tableCellPosition) > 0) {
          return false;
        }
      }
      return true;
    }

    public int increaseCellPosition(final int colSpan, final int rowSpan) {
      // find insert-position for the cell. This skips cells that block the location via a row-span.
      while (true) {
        // we are past the point of defined cells. Adding new cells is guaranteed to not have
        // row-spans.
        if (tableCellPosition >= rowSpans.size()) {
          break;
        }

        if (isCellAreaClear(tableCellPosition, colSpan)) {
          break;
        }

        tableCellPosition += 1;
      }

      final int retval = tableCellPosition;
      // set the cell...
      for (int i = tableCellPosition; i < tableCellPosition + colSpan; i++) {
        if (i < rowSpans.size()) {
          rowSpans.set(i, Math.max(rowSpan, rowSpans.get(i)));
        } else {
          rowSpans.add(rowSpan);
        }
      }

      tableCellPosition += colSpan;
      return retval;
    }

    public TableInfoStructure pop() {
      return parent;
    }

    public TableSectionRenderBox getSectionRenderBox() {
      return sectionRenderBox;
    }

    public void setSectionRenderBox(final TableSectionRenderBox sectionRenderBox) {
      this.rowSpans.clear();
      this.sectionRenderBox = sectionRenderBox;
      if (this.sectionRenderBox != null) {
        this.rowModel = sectionRenderBox.getRowModel();
        this.bodySection = (sectionRenderBox.getDisplayRole() == TableSectionRenderBox.Role.BODY);
      } else {
        this.rowModel = null;
        this.bodySection = false;
      }
      this.rowCount = -1;
    }

    public boolean isBodySection() {
      return bodySection;
    }

    public TableRenderBox getTable() {
      return table;
    }

    public TableColumnModel getColumnModel() {
      return columnModel;
    }

    public void updateDefinedSize(final int rowSpan, final long preferredSize) {
      rowModel.updateDefinedSize(rowCount, rowSpan, preferredSize);
    }
  }

  private TableInfoStructure currentTable;
  private TableColumnGroup currentColumnGroup;

  public TableValidationStep() {}

  public void validate(final LogicalPageBox box) {
    currentTable = null;
    startProcessing(box);
    if (currentTable != null) {
      throw new IllegalStateException();
    }
  }

  private boolean abortIfNoTable(final RenderBox box) {
    if (box.getTableRefCount() == 0) {
      return false;
    }

    if (box.getTableValidationAge() == box.getChangeTracker()) {
      return false;
    }

    box.setTableValidationAge(box.getChangeTracker());
    return true;
  }

  protected boolean startCanvasBox(final CanvasRenderBox box) {
    return abortIfNoTable(box);
  }

  protected boolean startBlockBox(final BlockRenderBox box) {
    return abortIfNoTable(box);
  }

  protected boolean startInlineBox(final InlineRenderBox box) {
    return abortIfNoTable(box);
  }

  protected boolean startOtherBox(final RenderBox box) {
    return abortIfNoTable(box);
  }

  protected boolean startRowBox(final RenderBox box) {
    return abortIfNoTable(box);
  }

  protected boolean startAutoBox(final RenderBox box) {
    if (currentTable != null) {
      return true;
    }
    return abortIfNoTable(box);
  }

  protected boolean startTableBox(final TableRenderBox table) {
    final long changeTracker = table.getChangeTracker();
    final long age = table.getTableValidationAge();
    if (changeTracker == age) {
      return false;
    }

    currentTable = new TableInfoStructure(table, currentTable);
    return true;
  }

  protected void finishTableBox(final TableRenderBox table) {
    final long changeTracker = table.getChangeTracker();
    final long age = table.getTableValidationAge();
    if (changeTracker == age) {
      return;
    }

    //  currentTable.columnModel.validateSizes(table);
    table.setTableValidationAge(age);
    table.setPredefinedColumnsValidated(true);
    currentTable = currentTable.pop();
  }

  protected boolean startTableColumnGroupBox(final TableColumnGroupNode box) {
    if (currentTable == null) {
      return false;
    }

    if (currentTable.table.isPredefinedColumnsValidated()) {
      return false;
    }

    currentColumnGroup = new TableColumnGroup(box.getBoxDefinition().getBorder());
    currentColumnGroup.setColSpan(box.getColSpan());
    return true;
  }

  protected void processTableColumn(final TableColumnNode node) {
    if (currentTable == null) {
      return;
    }

    if (currentTable.table.isPredefinedColumnsValidated()) {
      return;
    }

    final Border border = node.getBoxDefinition().getBorder();
    final RenderLength length = node.getBoxDefinition().getMinimumWidth();

    if (currentColumnGroup != null) {
      currentColumnGroup.addColumn(new TableColumn(border, length, false));
    } else {
      final TableColumnGroup currentColumnGroup =
          new TableColumnGroup(BoxDefinition.EMPTY.getBorder());
      currentColumnGroup.addColumn(new TableColumn(border, length, false));
      currentTable.columnModel.addColumnGroup(currentColumnGroup);
    }
  }

  protected void finishTableColumnGroupBox(final TableColumnGroupNode box) {
    if (currentTable == null) {
      return;
    }

    if (currentTable.table.isPredefinedColumnsValidated()) {
      return;
    }

    while (currentColumnGroup.getColumnCount() < box.getColSpan()) {
      currentColumnGroup.addColumn(
          new TableColumn(currentColumnGroup.getBorder(), RenderLength.AUTO, false));
    }

    currentTable.columnModel.addColumnGroup(currentColumnGroup);
    currentColumnGroup = null;
  }

  protected boolean startTableSectionBox(final TableSectionRenderBox box) {
    if (currentTable == null) {
      return false;
    }
    if (currentTable.getSectionRenderBox() != null) {
      return true;
    }

    currentTable.setSectionRenderBox(box);
    box.getRowModel().initialize(currentTable.getTable());
    return true;
  }

  protected void finishTableSectionBox(final TableSectionRenderBox box) {
    if (currentTable == null) {
      return;
    }
    if (currentTable.getSectionRenderBox() != box) {
      return;
    }

    final IntList rowSpans = currentTable.rowSpans;
    int missingRows = 0;
    for (int i = 0; i < rowSpans.size(); i++) {
      final int value = rowSpans.get(i);
      if (missingRows < value) {
        missingRows = value;
      }
    }

    for (int i = 0; i < missingRows; i += 1) {
      currentTable.rowModel.addRow();
    }

    box.getRowModel().validateSizes(box);
    currentTable.setSectionRenderBox(null);
  }

  protected boolean startTableRowBox(final TableRowRenderBox box) {
    if (currentTable == null) {
      return false;
    }
    if (currentTable.getSectionRenderBox() == null) {
      return false;
    }

    currentTable.resetCellPosition();
    box.setBodySection(currentTable.isBodySection());

    // check if this is the first row ...
    if (currentTable.rowCount == -1) {
      if (box.getRowIndex() != -1) {
        currentTable.rowCount = box.getRowIndex();
        return true;
      }
    }

    currentTable.rowCount += 1;
    box.setRowIndex(currentTable.rowCount);
    if (currentTable.rowCount <= currentTable.rowModel.getRowCount()) {
      currentTable.rowModel.addRow();
    }
    return true;
  }

  protected void finishTableRowBox(final TableRowRenderBox box) {
    if (currentTable == null) {
      return;
    }
    if (currentTable.getSectionRenderBox() == null) {
      return;
    }

    final IntList rowSpans = currentTable.rowSpans;
    int maxRowSpan = 0;
    for (int i = 0; i < rowSpans.size(); i++) {
      final int value = rowSpans.get(i);
      maxRowSpan = Math.max(maxRowSpan, value);
    }

    for (int i = 0; i < rowSpans.size(); i++) {
      final int value = rowSpans.get(i);
      rowSpans.set(i, Math.max(0, value - 1));
    }
  }

  protected boolean startTableCellBox(final TableCellRenderBox box) {
    if (currentTable == null) {
      return false;
    }
    if (currentTable.getSectionRenderBox() == null) {
      return false;
    }
    final int rowSpan = box.getRowSpan();
    final int colSpan = box.getColSpan();

    final int startPos = currentTable.increaseCellPosition(colSpan, rowSpan);
    while (currentTable.columnModel.getColumnCount() <= startPos) {
      currentTable.columnModel.addAutoColumn();
    }

    box.setColumnIndex(startPos);
    box.setBodySection(currentTable.isBodySection());

    final BoxDefinition boxDefinition = box.getBoxDefinition();
    final long preferredHeight = boxDefinition.getPreferredHeight().resolve(0);
    final long minHeight = boxDefinition.getMinimumHeight().resolve(0);
    final long maxHeight = boxDefinition.getMaximumHeight().resolve(0, MAX_AUTO);

    final long preferredSize = ProcessUtility.computeLength(minHeight, maxHeight, preferredHeight);
    currentTable.updateDefinedSize(rowSpan, preferredSize);
    return true;
  }
}