public void processContent(
      final ReportElement element, final Object computedValue, final Object rawValue) {
    if (computedValue == null) {
      final StyleSheet resolvedStyle = element.getComputedStyle();
      final RenderBox parentRenderBox = this.context.getRenderBox();
      if (parentRenderBox.isEmptyNodesHaveSignificance()
          || metaData.isExtraContentElement(resolvedStyle, element.getAttributes())) {
        ensureEmptyChildIsAdded(parentRenderBox, element);
        this.context.setEmpty(false);
      }
      return;
    }

    if (String.class.equals(computedValue.getClass())) {
      processText(element, (String) computedValue, rawValue);
    } else if (computedValue instanceof Shape) {
      final StyleSheet resolvedStyle = element.getComputedStyle();
      final Shape shape = (Shape) computedValue;
      final ReportDrawable reportDrawable =
          new ShapeDrawable(
              shape, resolvedStyle.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO));
      processReportDrawable(element, reportDrawable, rawValue);
    } else if (computedValue instanceof ReportDrawable) {
      processReportDrawable(element, (ReportDrawable) computedValue, rawValue);
    } else if (computedValue instanceof ImageContainer
        || computedValue instanceof DrawableWrapper) {
      processReplacedContent(element, computedValue, rawValue);
    } else if (DrawableWrapper.isDrawable(computedValue)) {
      processReplacedContent(element, new DrawableWrapper(computedValue), rawValue);
    } else {
      processText(element, String.valueOf(computedValue), rawValue);
    }
  }
  protected void processInlineLevelNode(final RenderNode node) {
    if (lineBreakState.isInsideParagraph() == false) {
      throw new InvalidReportStateException(
          "A inline-level box outside of a paragraph box is not allowed.");
    }

    final int nodeType = node.getNodeType();
    if (nodeType == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE) {
      final FinishedRenderNode finNode = (FinishedRenderNode) node;
      node.setCachedWidth(finNode.getLayoutedWidth());
      return;
    }

    if (nodeType == LayoutNodeTypes.TYPE_NODE_TEXT) {
      lineBreakState.add(TextSequenceElement.INSTANCE, node);
    } else if (nodeType == LayoutNodeTypes.TYPE_NODE_SPACER) {
      final StyleSheet styleSheet = node.getStyleSheet();
      if (WhitespaceCollapse.PRESERVE.equals(
              styleSheet.getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE))
          && styleSheet.getBooleanStyleProperty(TextStyleKeys.TRIM_TEXT_CONTENT) == false) {
        // bug-alert: This condition could indicate a workaround for a logic-flaw in the
        // text-processor
        lineBreakState.add(SpacerSequenceElement.INSTANCE, node);
      } else if (lineBreakState.isContainsContent()) {
        lineBreakState.add(SpacerSequenceElement.INSTANCE, node);
      }
    } else {
      lineBreakState.add(InlineNodeSequenceElement.INSTANCE, node);
    }
  }
  public void startSubFlow(final ReportElement element) {
    final StyleSheet resolverStyleSheet = element.getComputedStyle();

    final RenderBox box;
    if (metaData.isFeatureSupported(OutputProcessorFeature.STRICT_COMPATIBILITY)) {
      final StyleSheet styleSheet =
          new SubReportStyleSheet(
              resolverStyleSheet.getBooleanStyleProperty(BandStyleKeys.PAGEBREAK_BEFORE),
              (resolverStyleSheet.getBooleanStyleProperty(BandStyleKeys.PAGEBREAK_AFTER)));

      final SimpleStyleSheet reportStyle = new SimpleStyleSheet(styleSheet);
      final BoxDefinition boxDefinition = renderNodeFactory.getBoxDefinition(reportStyle);
      box =
          new BlockRenderBox(
              reportStyle,
              element.getObjectID(),
              boxDefinition,
              SubReportType.INSTANCE,
              element.getAttributes(),
              null);
    } else {
      box =
          renderNodeFactory.produceRenderBox(
              element, resolverStyleSheet, BandStyleKeys.LAYOUT_BLOCK, stateKey);
    }

    box.getStaticBoxLayoutProperties()
        .setPlaceholderBox(StaticBoxLayoutProperties.PlaceholderType.SECTION);
    if (element.getName() != null) {
      box.setName("Banded-SubReport-Section" + ": name=" + element.getName());
    } else {
      box.setName("Banded-SubReport-Section");
    }

    pushBoxToContext(box, false);
  }
  public RenderBox next() {
    cleanFirstSpacers();

    Arrays.fill(elementDimensions, 0);
    Arrays.fill(elementPositions, 0);

    int lastPosition = iterate(sequenceElements, sequenceFill);
    if (lastPosition == 0) {
      // This could evolve into an infinite loop. Thats evil.
      // We have two choices to prevent that:
      // (1) Try to break the element.
      //      if (getBreakableIndex() >= 0)
      //      {
      //        // Todo: Breaking is not yet implemented ..
      //      }
      if (getSkipIndex() >= 0) {
        // This causes an overflow ..
        performSkipAlignment(getSkipIndex());
        lastPosition = getSkipIndex();
      } else {
        // Skip the complete line. Oh, thats not good, really!
        lastPosition = sequenceFill;
      }
    }

    // now, build the line and update the array ..
    pendingElements.clear();
    contexts.clear();
    RenderBox firstBox = null;
    RenderBox box = null;
    for (int i = 0; i < lastPosition; i++) {
      final RenderNode node = nodes[i];
      final InlineSequenceElement element = sequenceElements[i];
      if (element instanceof EndSequenceElement) {
        contexts.pop();
        final long boxX2 = (elementPositions[i] + elementDimensions[i]);
        box.setCachedWidth(boxX2 - box.getCachedX());

        if (contexts.isEmpty()) {
          box = null;
        } else {
          final RenderNode tmpnode = box;
          box = contexts.peek();
          box.addGeneratedChild(tmpnode);
        }
        continue;
      }

      if (element instanceof StartSequenceElement) {
        box = (RenderBox) node.derive(false);
        box.setCachedX(elementPositions[i]);
        contexts.push(box);
        if (firstBox == null) {
          firstBox = box;
        }
        continue;
      }

      if (box == null) {
        throw new IllegalStateException(
            "Invalid sequence: " + "Cannot have elements before we open the box context.");
      }

      // Content element: Perform a deep-deriveForAdvance, so that we preserve the
      // possibly existing sub-nodes.
      final RenderNode child = node.derive(true);
      child.setCachedX(elementPositions[i]);
      child.setCachedWidth(elementDimensions[i]);
      if (box.getStaticBoxLayoutProperties().isPreserveSpace()
          && box.getStyleSheet().getBooleanStyleProperty(TextStyleKeys.TRIM_TEXT_CONTENT)
              == false) {
        // Take a shortcut as we know that we will never have any pending elements if preserve is
        // true and
        // trim-content is false.
        box.addGeneratedChild(child);
        continue;
      }

      if (child.isIgnorableForRendering()) {
        pendingElements.add(child);
      } else {
        for (int j = 0; j < pendingElements.size(); j++) {
          final RenderNode pendingNode = pendingElements.get(j);
          box.addGeneratedChild(pendingNode);
        }
        pendingElements.clear();
        box.addGeneratedChild(child);
      }
    }

    // Remove all spacers and other non printable content that might
    // look ugly at the beginning of a new line ..
    for (; lastPosition < sequenceFill; lastPosition++) {
      final RenderNode node = nodes[lastPosition];
      final StyleSheet styleSheet = node.getStyleSheet();
      if (WhitespaceCollapse.PRESERVE.equals(
              styleSheet.getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE))
          && styleSheet.getBooleanStyleProperty(TextStyleKeys.TRIM_TEXT_CONTENT) == false) {
        break;
      }

      if (node.isIgnorableForRendering() == false) {
        break;
      }
    }

    // If there are open contexts, then add the split-result to the new line
    // and update the width of the current line
    RenderBox previousContext = null;
    final int openContexts = contexts.size();
    for (int i = 0; i < openContexts; i++) {
      final RenderBox renderBox = contexts.get(i);
      final long cachedWidth = getEndOfLine() - renderBox.getCachedX();
      renderBox.setCachedWidth(cachedWidth);

      final InlineRenderBox rightBox =
          (InlineRenderBox) renderBox.split(RenderNode.HORIZONTAL_AXIS);
      sequenceElements[i] = StartSequenceElement.INSTANCE;
      nodes[i] = rightBox;
      if (previousContext != null) {
        previousContext.addGeneratedChild(renderBox);
      }
      previousContext = renderBox;
    }

    final int length = sequenceFill - lastPosition;
    System.arraycopy(sequenceElements, lastPosition, sequenceElements, openContexts, length);
    System.arraycopy(nodes, lastPosition, nodes, openContexts, length);
    sequenceFill = openContexts + length;
    Arrays.fill(sequenceElements, sequenceFill, sequenceElements.length, null);
    Arrays.fill(nodes, sequenceFill, nodes.length, null);

    return firstBox;
  }
  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;
  }
  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);
    }
  }
  public void draw(final Graphics2D graphics2D, final Rectangle2D bounds) {
    Graphics2D g = (Graphics2D) graphics2D.create();
    g.setColor((Color) styleSheet.getStyleProperty(ElementStyleKeys.PAINT, Color.BLACK));
    if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.DRAW_SHAPE)) {
      g.draw(bounds);
    }

    if (styleSheet.getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE)) {
      Graphics2D g2 = (Graphics2D) g.create();
      Color fillColor =
          (Color) styleSheet.getStyleProperty(ElementStyleKeys.FILL_COLOR, Color.WHITE);
      g2.setColor(fillColor);
      g2.fill(bounds);
      g2.dispose();
    }

    if (vectorImageBackground != null) {
      Graphics2D g2 = (Graphics2D) g.create();
      vectorImageBackground.draw(g2, bounds);
      g2.dispose();
    }
    if (rasterImageBackground != null) {
      Graphics2D g2 = (Graphics2D) g.create();

      Image image = rasterImageBackground.getImage();
      WaitingImageObserver obs = new WaitingImageObserver(image);
      obs.waitImageLoaded();

      g.setColor(Color.WHITE);
      g.setBackground(Color.WHITE);

      while (g2.drawImage(
              image,
              (int) bounds.getX(),
              (int) bounds.getY(),
              (int) bounds.getWidth(),
              (int) bounds.getHeight(),
              null)
          == false) {
        obs.waitImageLoaded();
        if (obs.isError()) {
          logger.warn("Error while loading the image during the rendering.");
          break;
        }
      }
      g2.dispose();
    }

    if (StringUtils.isEmpty(textToPrint) == false) {
      AttributedCharacterIterator paragraph = new AttributedString(textToPrint).getIterator();
      int paragraphStart = paragraph.getBeginIndex();
      int paragraphEnd = paragraph.getEndIndex();
      FontRenderContext frc = g.getFontRenderContext();
      LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);

      float breakWidth = (float) bounds.getWidth();
      float drawPosY = 0;
      // Set position to the index of the first character in the paragraph.
      lineMeasurer.setPosition(paragraphStart);

      while (lineMeasurer.getPosition() < paragraphEnd) {
        TextLayout layout = lineMeasurer.nextLayout(breakWidth).getJustifiedLayout(breakWidth);
        float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance();

        drawPosY += layout.getAscent();

        layout.draw(g, drawPosX, drawPosY);

        drawPosY += layout.getDescent() + layout.getLeading();
      }
    }
  }