public int computeMaximumTextSize(final long contentX2) {
    final int length = getLength();
    final long x = getX();
    if (contentX2 >= (x + getWidth())) {
      return length;
    }

    final GlyphList gs = getGlyphs();
    long runningPos = x;
    final int offset = getOffset();
    final int maxPos = offset + length;

    for (int i = offset; i < maxPos; i++) {
      final Glyph g = gs.getGlyph(i);
      runningPos += RenderableText.convert(g.getWidth());
      if (i != offset) {
        runningPos += g.getSpacing().getMinimum();
      }
      if (runningPos > contentX2) {
        return Math.max(0, i - offset);
      }
    }
    return length;
  }
  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);
    }
  }
  protected void initialize(
      final GlyphList glyphs,
      final int offset,
      final int length,
      final ExtendedBaselineInfo baselineInfo,
      final int script,
      final boolean forceLinebreak) {
    if (glyphs == null) {
      throw new NullPointerException();
    }
    if (forceLinebreak == false && length == 0) {
      throw new IllegalArgumentException("Do not create zero-length renderable text!");
    }
    if (glyphs.getSize() < (offset + length)) {
      throw new IllegalArgumentException();
    }

    this.baselineInfo = baselineInfo;
    this.ltr = true; // this depends on the script value
    this.script = script;

    this.glyphs = glyphs;
    this.offset = offset;
    this.length = length;
    this.forceLinebreak = forceLinebreak;

    normalTextSpacing = true;
    long wordMinChunkWidth = 0;

    //    long heightAbove = 0;
    //    long heightBelow = 0;
    long minimumChunkWidth = 0;

    long realCharTotal = 0;
    long spacerMin = 0;
    long spacerMax = 0;
    long spacerOpt = 0;

    final int lastPos = Math.min(glyphs.getSize(), offset + length);
    for (int i = offset; i < lastPos; i++) {
      final Glyph glyph = glyphs.getGlyph(i);
      //      heightAbove = Math.max(glyph.getBaseLine(), heightAbove);
      //      heightBelow = Math.max(glyph.getHeight() - glyph.getBaseLine(), heightBelow);
      final int kerning = glyph.getKerning();
      final int width = glyph.getWidth();
      final long realCharSpace = convert(width - kerning);
      realCharTotal += realCharSpace;
      wordMinChunkWidth += realCharSpace;
      if (i != (lastPos - 1)) {
        final Spacing spacing = glyph.getSpacing();
        spacerMax += spacing.getMaximum();
        spacerMin += spacing.getMinimum();
        spacerOpt += spacing.getOptimum();
        if (normalTextSpacing == true && Spacing.EMPTY_SPACING.equals(spacing) == false) {
          normalTextSpacing = false;
        }

        wordMinChunkWidth += spacing.getMinimum();
      }

      if (glyph.getBreakWeight() > BreakOpportunityProducer.BREAK_CHAR) {
        minimumChunkWidth = Math.max(minimumChunkWidth, wordMinChunkWidth);
        wordMinChunkWidth = 0;

        // Paranoid sanity checks: The word- and linebreaks should have been
        // replaced by other definitions in the text factory.
        if (glyph.getBreakWeight() == BreakOpportunityProducer.BREAK_LINE) {
          throw new IllegalStateException(
              "A renderable text cannot and must " + "not contain linebreaks.");
        }
      }
    }

    final long wordMinWidth = spacerMin + realCharTotal;
    final long wordPrefWidth = spacerOpt + realCharTotal;
    final long wordMaxWidth = spacerMax + realCharTotal;

    minimumChunkWidth = Math.max(minimumChunkWidth, wordMinChunkWidth);
    minimumWidth = wordMinWidth;
    preferredWidth = wordPrefWidth;

    setMaximumBoxWidth(wordMaxWidth);
    setMinimumChunkWidth(minimumChunkWidth);
  }