protected void drawHyperlink(
      final RenderNode box, final String target, final String window, final String title) {
    if (box.isNodeVisible(getDrawArea()) == false) {
      return;
    }

    final PdfAction action = createActionForLink(target);

    final AffineTransform affineTransform = getGraphics().getTransform();
    final float translateX = (float) affineTransform.getTranslateX();

    final float leftX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX()));
    final float rightX =
        translateX + (float) (StrictGeomUtility.toExternalValue(box.getX() + box.getWidth()));
    final float lowerY =
        (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY() + box.getHeight()));
    final float upperY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));

    if (action != null) {
      final PdfAnnotation annotation =
          new PdfAnnotation(writer, leftX, lowerY, rightX, upperY, action);
      writer.addAnnotation(annotation);
    } else if (StringUtils.isEmpty(title) == false) {
      final Rectangle rect = new Rectangle(leftX, lowerY, rightX, upperY);
      final PdfAnnotation commentAnnotation =
          PdfAnnotation.createText(writer, rect, "Tooltip", title, false, null);
      commentAnnotation.setAppearance(
          PdfAnnotation.APPEARANCE_NORMAL,
          writer.getDirectContent().createAppearance(rect.getWidth(), rect.getHeight()));
      writer.addAnnotation(commentAnnotation);
    }
  }
  protected boolean drawPdfScript(final RenderNode box) {
    final Object attribute =
        box.getAttributes()
            .getAttribute(AttributeNames.Pdf.NAMESPACE, AttributeNames.Pdf.SCRIPT_ACTION);
    if (attribute == null) {
      return false;
    }

    final String attributeText = String.valueOf(attribute);
    final PdfAction action = PdfAction.javaScript(attributeText, writer, false);

    final AffineTransform affineTransform = getGraphics().getTransform();
    final float translateX = (float) affineTransform.getTranslateX();

    final float leftX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX()));
    final float rightX =
        translateX + (float) (StrictGeomUtility.toExternalValue(box.getX() + box.getWidth()));
    final float lowerY =
        (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY() + box.getHeight()));
    final float upperY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));
    final PdfAnnotation annotation =
        new PdfAnnotation(writer, leftX, lowerY, rightX, upperY, action);
    writer.addAnnotation(annotation);
    return true;
  }
  public void processLogicalPage(final LogicalPageKey key, final LogicalPageBox logicalPage)
      throws DocumentException {

    final float width = (float) StrictGeomUtility.toExternalValue(logicalPage.getPageWidth());
    final float height = (float) StrictGeomUtility.toExternalValue(logicalPage.getPageHeight());

    final Rectangle pageSize = new Rectangle(width, height);

    final Document document = getDocument();
    document.setPageSize(pageSize);
    document.setMargins(0, 0, 0, 0);

    if (awaitOpenDocument) {
      document.open();
      awaitOpenDocument = false;
    }

    final Graphics2D graphics =
        new PdfGraphics2D(writer.getDirectContent(), width, height, metaData);
    // and now process the box ..
    final PdfLogicalPageDrawable logicalPageDrawable =
        new PdfLogicalPageDrawable(
            logicalPage, metaData, writer, null, resourceManager, imageCache, version);
    logicalPageDrawable.draw(graphics, new Rectangle2D.Double(0, 0, width, height));

    graphics.dispose();

    document.newPage();
  }
  protected void drawImageMap(final RenderableReplacedContentBox content) {
    if (version < '6') {
      return;
    }

    final ImageMap imageMap = RenderUtility.extractImageMap(content);
    // only generate a image map, if the user does not specify their own onw via the override.
    // Of course, they would have to provide the map by other means as well.

    if (imageMap == null) {
      return;
    }

    final ImageMapEntry[] imageMapEntries = imageMap.getMapEntries();
    for (int i = 0; i < imageMapEntries.length; i++) {
      final ImageMapEntry imageMapEntry = imageMapEntries[i];
      final String link = imageMapEntry.getAttribute(LibXmlInfo.XHTML_NAMESPACE, "href");
      final String tooltip = imageMapEntry.getAttribute(LibXmlInfo.XHTML_NAMESPACE, "title");
      if (StringUtils.isEmpty(tooltip)) {
        continue;
      }

      final AffineTransform affineTransform = getGraphics().getTransform();
      final float translateX = (float) affineTransform.getTranslateX();
      final int x = (int) (translateX + StrictGeomUtility.toExternalValue(content.getX()));
      final int y = (int) StrictGeomUtility.toExternalValue(content.getY());
      final float[] translatedCoords =
          translateCoordinates(imageMapEntry.getAreaCoordinates(), x, y);

      final PolygonAnnotation polygonAnnotation = new PolygonAnnotation(writer, translatedCoords);
      polygonAnnotation.put(PdfName.CONTENTS, new PdfString(tooltip, PdfObject.TEXT_UNICODE));
      writer.addAnnotation(polygonAnnotation);
    }
  }
  private void updateCellStyle(final Cell cell, final CellBackground background) {

    final Color backgroundColor = background.getBackgroundColor();
    if (backgroundColor != null) {
      cell.setBackgroundColor(backgroundColor);
    }
    final BorderEdge top = background.getTop();
    if (BorderEdge.EMPTY.equals(top) == false) {
      cell.setBorderColorTop(top.getColor());
      cell.setBorderWidthTop((float) StrictGeomUtility.toExternalValue(top.getWidth()));
    }

    final BorderEdge left = background.getLeft();
    if (BorderEdge.EMPTY.equals(left) == false) {
      cell.setBorderColorLeft(left.getColor());
      cell.setBorderWidthLeft((float) StrictGeomUtility.toExternalValue(left.getWidth()));
    }

    final BorderEdge bottom = background.getBottom();
    if (BorderEdge.EMPTY.equals(bottom) == false) {
      cell.setBorderColorBottom(bottom.getColor());
      cell.setBorderWidthBottom((float) StrictGeomUtility.toExternalValue(bottom.getWidth()));
    }

    final BorderEdge right = background.getRight();
    if (BorderEdge.EMPTY.equals(right) == false) {
      cell.setBorderColorRight(right.getColor());
      cell.setBorderWidthRight((float) StrictGeomUtility.toExternalValue(right.getWidth()));
    }
  }
  public void resizeProportional() {
    final float originalPageWidth = originalPageDefinition.getWidth();
    final float currentPageWidth = currentPageDefinition.getWidth();
    final float scaleFactor = currentPageWidth / originalPageWidth;

    for (int i = 0; i < visualElements.length; i++) {

      // Resize the element.
      final CachedLayoutData cachedLayoutData = ModelUtility.getCachedLayoutData(visualElements[i]);

      final double elementWidth = StrictGeomUtility.toExternalValue(cachedLayoutData.getWidth());
      final Element theElement = visualElements[i];
      final ElementStyleSheet styleSheet = theElement.getStyle();
      styleSheet.setStyleProperty(
          ElementStyleKeys.MIN_WIDTH, new Float(elementWidth * scaleFactor));

      // Reposition the element.
      final double origin = StrictGeomUtility.toExternalValue(cachedLayoutData.getX());
      final double destination = scaleFactor * origin;
      final int theShift = (int) (destination - origin);

      final Element[] theElements = new Element[1];
      theElements[0] = theElement;
      align(theShift, theElements);
    }
    registerChanges();
  }
  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());
  }
 public void alignRight() {
   final double theCurrentPageWidth = currentPageDefinition.getWidth();
   final int theShiftRight =
       (int) (theCurrentPageWidth - StrictGeomUtility.toExternalValue(computeFarRightPostion()));
   align(theShiftRight, visualElements);
   registerChanges();
 }
  @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);
  }
  protected void drawBookmark(final RenderNode box, final String bookmark) {
    if (box.isNodeVisible(getDrawArea()) == false) {
      return;
    }
    final PdfOutline root = writer.getDirectContent().getRootOutline();

    final AffineTransform affineTransform = getGraphics().getTransform();
    final float translateX = (float) affineTransform.getTranslateX();

    final float upperY =
        translateX + (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));
    final float leftX = (float) (StrictGeomUtility.toExternalValue(box.getX()));
    final PdfDestination dest = new PdfDestination(PdfDestination.FIT, leftX, upperY, 0);
    new PdfOutline(root, dest, bookmark);
    // destination will always point to the 'current' page
    // todo: Make this a hierarchy ..
  }
  protected void drawAnchor(final RenderNode content) {
    if (content.isNodeVisible(getDrawArea()) == false) {
      return;
    }
    final String anchorName =
        (String) content.getStyleSheet().getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
    if (anchorName == null) {
      return;
    }
    final AffineTransform affineTransform = getGraphics().getTransform();
    final float translateX = (float) affineTransform.getTranslateX();

    final float upperY =
        translateX + (float) (globalHeight - StrictGeomUtility.toExternalValue(content.getY()));
    final float leftX = (float) (StrictGeomUtility.toExternalValue(content.getX()));
    final PdfDestination dest = new PdfDestination(PdfDestination.FIT, leftX, upperY, 0);
    writer.getDirectContent().localDestination(anchorName, dest);
  }
  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);
  }
  /** Invoked when an action occurs. */
  public void actionPerformed(final ActionEvent e) {
    final ReportSelectionModel model = getSelectionModel();
    if (model == null) {
      return;
    }
    final Element[] visualElements = model.getSelectedVisualElements();
    if (visualElements.length <= 1) {
      return;
    }
    final Element[] carrier = new Element[1];
    final Element[] objects = ModelUtility.filterParents(visualElements);
    final MassElementStyleUndoEntryBuilder builder = new MassElementStyleUndoEntryBuilder(objects);

    long minY = Long.MAX_VALUE;
    long maxY = Long.MIN_VALUE;
    for (int j = 0; j < objects.length; j++) {
      final Element object = objects[j];
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(object);
      final long y1 = data.getY();
      final long y2 = y1 + data.getHeight();
      if (y2 > maxY) {
        maxY = y2;
      }
      if (y1 < minY) {
        minY = y1;
      }
    }

    final long centerPoint = minY + (maxY - minY) / 2;

    for (int j = 0; j < objects.length; j++) {
      final Element object = objects[j];
      final CachedLayoutData data = ModelUtility.getCachedLayoutData(object);
      final long elementCenter = data.getY() + data.getHeight() / 2;

      final long delta = centerPoint - elementCenter;
      if (delta == 0) {
        continue;
      }

      carrier[0] = object;
      final MoveDragOperation mop =
          new MoveDragOperation(
              carrier, ORIGIN_POINT, EmptySnapModel.INSTANCE, EmptySnapModel.INSTANCE);
      mop.update(new Point2D.Double(0, StrictGeomUtility.toExternalValue(delta)), 1);
      mop.finish();
    }
    final MassElementStyleUndoEntry massElementStyleUndoEntry = builder.finish();
    getActiveContext()
        .getUndo()
        .addChange(
            ActionMessages.getString("AlignMiddleAction.UndoName"), massElementStyleUndoEntry);
  }
  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();
  }
  /**
   * 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 init(
      final LogicalPageBox rootBox,
      final PdfOutputProcessorMetaData metaData,
      final ResourceManager resourceManager,
      final PhysicalPageBox page) {
    super.init(rootBox, metaData, resourceManager);

    if (page != null) {
      this.globalHeight =
          (float)
              StrictGeomUtility.toExternalValue(
                  page.getHeight() - page.getImageableY() + page.getGlobalY());
    } else {
      this.globalHeight = rootBox.getPageHeight();
    }
    this.globalEmbed = getMetaData().isFeatureSupported(OutputProcessorFeature.EMBED_ALL_FONTS);
  }
  public void processPhysicalPage(
      final PageGrid pageGrid,
      final LogicalPageBox logicalPage,
      final int row,
      final int col,
      final PhysicalPageKey pageKey)
      throws DocumentException {
    final PhysicalPageBox page = pageGrid.getPage(row, col);
    if (page == null) {
      return;
    }

    final float width = (float) StrictGeomUtility.toExternalValue(page.getWidth());
    final float height = (float) StrictGeomUtility.toExternalValue(page.getHeight());

    final Rectangle pageSize = new Rectangle(width, height);

    final float marginLeft = (float) StrictGeomUtility.toExternalValue(page.getImageableX());
    final float marginRight =
        (float)
            StrictGeomUtility.toExternalValue(
                page.getWidth() - page.getImageableWidth() - page.getImageableX());
    final float marginTop = (float) StrictGeomUtility.toExternalValue(page.getImageableY());
    final float marginBottom =
        (float)
            StrictGeomUtility.toExternalValue(
                page.getHeight() - page.getImageableHeight() - page.getImageableY());

    final Document document = getDocument();
    document.setPageSize(pageSize);
    document.setMargins(marginLeft, marginRight, marginTop, marginBottom);

    if (awaitOpenDocument) {
      document.open();
      awaitOpenDocument = false;
    }

    final PdfContentByte directContent = writer.getDirectContent();
    final Graphics2D graphics = new PdfGraphics2D(directContent, width, height, metaData);
    final PdfLogicalPageDrawable logicalPageDrawable =
        new PdfLogicalPageDrawable(
            logicalPage, metaData, writer, page, resourceManager, imageCache, version);
    final PhysicalPageDrawable drawable = new PhysicalPageDrawable(logicalPageDrawable, page);
    drawable.draw(graphics, new Rectangle2D.Double(0, 0, width, height));

    graphics.dispose();

    document.newPage();
  }
  @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);
  }
/**
 * 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;
  }
}
  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());
  }
  protected boolean drawImage(
      final RenderableReplacedContentBox content,
      final Image image,
      final com.lowagie.text.Image itextImage) {
    final StyleSheet layoutContext = content.getStyleSheet();
    final boolean shouldScale = layoutContext.getBooleanStyleProperty(ElementStyleKeys.SCALE);

    final int x = (int) StrictGeomUtility.toExternalValue(content.getX());
    final int y = (int) StrictGeomUtility.toExternalValue(content.getY());
    final int width = (int) StrictGeomUtility.toExternalValue(content.getWidth());
    final int height = (int) StrictGeomUtility.toExternalValue(content.getHeight());

    if (width == 0 || height == 0) {
      PdfLogicalPageDrawable.logger.debug("Error: Image area is empty: " + content);
      return false;
    }

    final WaitingImageObserver obs = new WaitingImageObserver(image);
    obs.waitImageLoaded();
    final int imageWidth = image.getWidth(obs);
    final int imageHeight = image.getHeight(obs);
    if (imageWidth < 1 || imageHeight < 1) {
      return false;
    }

    final Rectangle2D.Double drawAreaBounds = new Rectangle2D.Double(x, y, width, height);
    final AffineTransform scaleTransform;

    final Graphics2D g2;
    if (shouldScale == false) {
      double deviceScaleFactor = 1;
      final double devResolution =
          getMetaData().getNumericFeatureValue(OutputProcessorFeature.DEVICE_RESOLUTION);
      if (getMetaData().isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING)) {
        if (devResolution != 72.0 && devResolution > 0) {
          // Need to scale the device to its native resolution before attempting to draw the image..
          deviceScaleFactor = (72.0 / devResolution);
        }
      }

      final int clipWidth = Math.min(width, (int) Math.ceil(deviceScaleFactor * imageWidth));
      final int clipHeight = Math.min(height, (int) Math.ceil(deviceScaleFactor * imageHeight));
      final ElementAlignment horizontalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
      final ElementAlignment verticalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
      final int alignmentX =
          (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
      final int alignmentY =
          (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);

      g2 = (Graphics2D) getGraphics().create();
      g2.clip(drawAreaBounds);
      g2.translate(x, y);
      g2.translate(alignmentX, alignmentY);
      g2.clip(new Rectangle2D.Float(0, 0, clipWidth, clipHeight));
      g2.scale(deviceScaleFactor, deviceScaleFactor);

      scaleTransform = null;
    } else {
      g2 = (Graphics2D) getGraphics().create();
      g2.clip(drawAreaBounds);
      g2.translate(x, y);
      g2.clip(new Rectangle2D.Float(0, 0, width, height));

      final double scaleX;
      final double scaleY;

      final boolean keepAspectRatio =
          layoutContext.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO);
      if (keepAspectRatio) {
        final double scaleFactor =
            Math.min(width / (double) imageWidth, height / (double) imageHeight);
        scaleX = scaleFactor;
        scaleY = scaleFactor;
      } else {
        scaleX = width / (double) imageWidth;
        scaleY = height / (double) imageHeight;
      }

      final int clipWidth = (int) (scaleX * imageWidth);
      final int clipHeight = (int) (scaleY * imageHeight);

      final ElementAlignment horizontalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
      final ElementAlignment verticalAlignment =
          (ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
      final int alignmentX =
          (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
      final int alignmentY =
          (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);

      g2.translate(alignmentX, alignmentY);
      scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
    }

    final PdfGraphics2D pdfGraphics2D = (PdfGraphics2D) g2;
    pdfGraphics2D.drawPdfImage(itextImage, image, scaleTransform, null);
    g2.dispose();
    return true;
  }
  /** @noinspection IOResourceOpenedButNotSafelyClosed */
  public void print(
      final LogicalPageKey logicalPageKey,
      final LogicalPageBox logicalPage,
      final TableContentProducer contentProducer,
      final RTFOutputProcessorMetaData metaData,
      final boolean incremental)
      throws ContentProcessingException {
    final int startRow = contentProducer.getFinishedRows();
    final int finishRow = contentProducer.getFilledRows();
    if (incremental && startRow == finishRow) {
      return;
    }

    if (document == null) {
      this.cellBackgroundProducer =
          new CellBackgroundProducer(
              metaData.isFeatureSupported(AbstractTableOutputProcessor.TREAT_ELLIPSE_AS_RECTANGLE),
              metaData.isFeatureSupported(OutputProcessorFeature.UNALIGNED_PAGEBANDS));

      final PhysicalPageBox pageFormat = logicalPage.getPageGrid().getPage(0, 0);
      final float urx = (float) StrictGeomUtility.toExternalValue(pageFormat.getWidth());
      final float ury = (float) StrictGeomUtility.toExternalValue(pageFormat.getHeight());

      final float marginLeft =
          (float) StrictGeomUtility.toExternalValue(pageFormat.getImageableX());
      final float marginRight =
          (float)
              StrictGeomUtility.toExternalValue(
                  pageFormat.getWidth()
                      - pageFormat.getImageableWidth()
                      - pageFormat.getImageableX());
      final float marginTop = (float) StrictGeomUtility.toExternalValue(pageFormat.getImageableY());
      final float marginBottom =
          (float)
              StrictGeomUtility.toExternalValue(
                  pageFormat.getHeight()
                      - pageFormat.getImageableHeight()
                      - pageFormat.getImageableY());
      final Rectangle pageSize = new Rectangle(urx, ury);

      document = new Document(pageSize, marginLeft, marginRight, marginTop, marginBottom);
      imageCache = new RTFImageCache(resourceManager);

      // rtf does not support PageFormats or other meta data...
      final PatchRtfWriter2 instance =
          PatchRtfWriter2.getInstance(document, new NoCloseOutputStream(outputStream));
      instance.getDocumentSettings().setAlwaysUseUnicode(true);

      final String author =
          config.getConfigProperty(
              "org.pentaho.reporting.engine.classic.core.modules.output.table.rtf.Author");
      if (author != null) {
        document.addAuthor(author);
      }

      final String title =
          config.getConfigProperty(
              "org.pentaho.reporting.engine.classic.core.modules.output.table.rtf.Title");
      if (title != null) {
        document.addTitle(title);
      }

      document.addProducer();
      document.addCreator(RTFPrinter.CREATOR);

      try {
        document.addCreationDate();
      } catch (Exception e) {
        RTFPrinter.logger.debug("Unable to add creation date. It will have to work without it.", e);
      }

      document.open();
    }

    // Start a new page.
    try {
      final SheetLayout sheetLayout = contentProducer.getSheetLayout();
      final int columnCount = contentProducer.getColumnCount();
      if (table == null) {
        final int rowCount = contentProducer.getRowCount();
        table = new Table(columnCount, rowCount);
        table.setAutoFillEmptyCells(false);
        table.setWidth(100); // span the full page..
        // and finally the content ..

        final float[] cellWidths = new float[columnCount];
        for (int i = 0; i < columnCount; i++) {
          cellWidths[i] =
              (float) StrictGeomUtility.toExternalValue(sheetLayout.getCellWidth(i, i + 1));
        }
        table.setWidths(cellWidths);
      }

      // logger.debug ("Processing: " + startRow + " " + finishRow + " " + incremental);

      for (int row = startRow; row < finishRow; row++) {
        for (short col = 0; col < columnCount; col++) {
          final RenderBox content = contentProducer.getContent(row, col);
          final CellMarker.SectionType sectionType = contentProducer.getSectionType(row, col);

          if (content == null) {
            final RenderBox backgroundBox = contentProducer.getBackground(row, col);
            final CellBackground background;
            if (backgroundBox != null) {
              background =
                  cellBackgroundProducer.getBackgroundForBox(
                      logicalPage, sheetLayout, col, row, 1, 1, true, sectionType, backgroundBox);
            } else {
              background =
                  cellBackgroundProducer.getBackgroundAt(
                      logicalPage, sheetLayout, col, row, true, sectionType);
            }
            if (background == null) {
              // An empty cell .. ignore
              final PatchRtfCell cell = new PatchRtfCell();
              cell.setBorderWidth(0);
              cell.setMinimumHeight(
                  (float) StrictGeomUtility.toExternalValue(sheetLayout.getRowHeight(row)));
              table.addCell(cell, row, col);
              continue;
            }

            // A empty cell with a defined background ..
            final PatchRtfCell cell = new PatchRtfCell();
            cell.setBorderWidth(0);
            cell.setMinimumHeight(
                (float) StrictGeomUtility.toExternalValue(sheetLayout.getRowHeight(row)));
            updateCellStyle(cell, background);
            table.addCell(cell, row, col);
            continue;
          }

          if (content.isCommited() == false) {
            throw new InvalidReportStateException("Uncommited content encountered");
          }

          final long contentOffset = contentProducer.getContentOffset(row, col);
          final long colPos = sheetLayout.getXPosition(col);
          final long rowPos = sheetLayout.getYPosition(row);
          if (content.getX() != colPos || (content.getY() + contentOffset) != rowPos) {
            // A spanned cell ..
            continue;
          }

          final int colSpan = sheetLayout.getColSpan(col, content.getX() + content.getWidth());
          final int rowSpan =
              sheetLayout.getRowSpan(row, content.getY() + content.getHeight() + contentOffset);

          final CellBackground realBackground =
              cellBackgroundProducer.getBackgroundForBox(
                  logicalPage,
                  sheetLayout,
                  col,
                  row,
                  colSpan,
                  rowSpan,
                  false,
                  sectionType,
                  content);

          final PatchRtfCell cell = new PatchRtfCell();
          cell.setRowspan(rowSpan);
          cell.setColspan(colSpan);
          cell.setBorderWidth(0);
          cell.setMinimumHeight(
              (float) StrictGeomUtility.toExternalValue(sheetLayout.getRowHeight(row)));
          if (realBackground != null) {
            updateCellStyle(cell, realBackground);
          }

          computeCellStyle(content, cell);

          // export the cell and all content ..
          final RTFTextExtractor etx = new RTFTextExtractor(metaData);
          etx.compute(content, cell, imageCache);

          table.addCell(cell, row, col);
          content.setFinishedTable(true);
          // logger.debug("set Finished to cell (" + col + ", " + row + "," + content.getName() +
          // ")");
        }
      }

      if (incremental == false) {
        document.add(table);
        table = null;
      }
    } catch (DocumentException e) {
      throw new ContentProcessingException("Failed to generate RTF-Document", e);
    }
  }
  protected void drawText(final RenderableText renderableText, final long contentX2) {
    if (renderableText.getLength() == 0) {
      return;
    }

    final long posX = renderableText.getX();
    final long posY = renderableText.getY();
    final float x1 = (float) (StrictGeomUtility.toExternalValue(posX));

    final PdfContentByte cb;
    PdfTextSpec textSpec = (PdfTextSpec) getTextSpec();
    if (textSpec == null) {
      final StyleSheet layoutContext = renderableText.getStyleSheet();

      // The code below may be weird, but at least it is predictable weird.
      final String fontName =
          getMetaData()
              .getNormalizedFontFamilyName(
                  (String) layoutContext.getStyleProperty(TextStyleKeys.FONT));
      final String encoding = (String) layoutContext.getStyleProperty(TextStyleKeys.FONTENCODING);
      final float fontSize =
          (float) layoutContext.getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 10);

      final boolean embed =
          globalEmbed || layoutContext.getBooleanStyleProperty(TextStyleKeys.EMBEDDED_FONT);
      final boolean bold = layoutContext.getBooleanStyleProperty(TextStyleKeys.BOLD);
      final boolean italics = layoutContext.getBooleanStyleProperty(TextStyleKeys.ITALIC);

      final BaseFontFontMetrics fontMetrics =
          getMetaData()
              .getBaseFontFontMetrics(fontName, fontSize, bold, italics, encoding, embed, false);

      final PdfGraphics2D g2 = (PdfGraphics2D) getGraphics();
      final Color cssColor = (Color) layoutContext.getStyleProperty(ElementStyleKeys.PAINT);
      g2.setPaint(cssColor);
      g2.setFillPaint();
      g2.setStrokePaint();
      // final float translateY = (float) affineTransform.getTranslateY();

      cb = g2.getRawContentByte();

      textSpec = new PdfTextSpec(layoutContext, getMetaData(), g2, fontMetrics, cb);
      setTextSpec(textSpec);

      cb.beginText();
      cb.setFontAndSize(fontMetrics.getBaseFont(), fontSize);
    } else {
      cb = textSpec.getContentByte();
    }

    final BaseFontFontMetrics baseFontRecord = textSpec.getFontMetrics();
    final BaseFont baseFont = baseFontRecord.getBaseFont();
    final float ascent = baseFont.getFontDescriptor(BaseFont.BBOXURY, textSpec.getFontSize());
    final float y2 = (float) (StrictGeomUtility.toExternalValue(posY) + ascent);
    final float y = globalHeight - y2;

    final AffineTransform affineTransform = textSpec.getGraphics().getTransform();
    final float translateX = (float) affineTransform.getTranslateX();

    final FontNativeContext nativeContext = baseFontRecord.getNativeContext();
    if (baseFontRecord.isTrueTypeFont()
        && textSpec.isBold()
        && nativeContext.isNativeBold() == false) {
      final float strokeWidth = textSpec.getFontSize() / 30.0f; // right from iText ...
      if (strokeWidth == 1) {
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
      } else {
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE);
        cb.setLineWidth(strokeWidth);
      }
    } else {
      cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
    }

    // if the font does not declare to be italics already, emulate it ..
    if (baseFontRecord.isTrueTypeFont()
        && textSpec.isItalics()
        && nativeContext.isNativeItalics() == false) {
      final float italicAngle =
          baseFont.getFontDescriptor(BaseFont.ITALICANGLE, textSpec.getFontSize());
      if (italicAngle == 0) {
        // italics requested, but the font itself does not supply italics gylphs.
        cb.setTextMatrix(1, 0, PdfLogicalPageDrawable.ITALIC_ANGLE, 1, x1 + translateX, y);
      } else {
        cb.setTextMatrix(x1 + translateX, y);
      }
    } else {
      cb.setTextMatrix(x1 + translateX, y);
    }

    final OutputProcessorMetaData metaData = getMetaData();
    final GlyphList gs = renderableText.getGlyphs();
    final int offset = renderableText.getOffset();

    final CodePointBuffer codePointBuffer = getCodePointBuffer();
    if (metaData.isFeatureSupported(OutputProcessorFeature.FAST_FONTRENDERING)
        && isNormalTextSpacing(renderableText)) {
      final int maxLength = renderableText.computeMaximumTextSize(contentX2);
      final String text = gs.getText(renderableText.getOffset(), maxLength, codePointBuffer);

      cb.showText(text);
    } else {
      final PdfTextArray textArray = new PdfTextArray();
      final StringBuilder buffer = new StringBuilder(gs.getSize());
      final int maxPos = offset + renderableText.computeMaximumTextSize(contentX2);

      for (int i = offset; i < maxPos; i++) {
        final Glyph g = gs.getGlyph(i);
        final Spacing spacing = g.getSpacing();
        if (i != offset) {
          final float optimum = (float) StrictGeomUtility.toFontMetricsValue(spacing.getMinimum());
          if (optimum != 0) {
            textArray.add(buffer.toString());
            textArray.add(-optimum / textSpec.getFontSize());
            buffer.setLength(0);
          }
        }

        final String text = gs.getGlyphAsString(i, codePointBuffer);
        buffer.append(text);
      }
      if (buffer.length() > 0) {
        textArray.add(buffer.toString());
      }
      cb.showText(textArray);
    }
  }
 static {
   final long value = StrictGeomUtility.toInternalValue(1);
   conversionFactor =
       value / org.pentaho.reporting.libraries.fonts.tools.StrictGeomUtility.toInternalValue(1);
 }
  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();
    }
  }
 public void alignLeft() {
   final int theShiftLeft =
       (int) (0 - StrictGeomUtility.toExternalValue(computeFarLeftPosition()));
   align(theShiftLeft, visualElements);
   registerChanges();
 }