/**
   * Return a StyledParagraph reflecting the insertion of a single character into the text. This
   * method will attempt to reuse the given paragraph, but may create a new paragraph.
   *
   * @param aci an iterator over the text. The text should be the same as the text used to create
   *     (or most recently update) oldParagraph, with the exception of inserting a single character
   *     at insertPos.
   * @param chars the characters in aci
   * @param insertPos the index of the new character in aci
   * @param oldParagraph a StyledParagraph for the text in aci before the insertion
   */
  public static StyledParagraph insertChar(
      AttributedCharacterIterator aci, char[] chars, int insertPos, StyledParagraph oldParagraph) {

    // If the styles at insertPos match those at insertPos-1,
    // oldParagraph will be reused.  Otherwise we create a new
    // paragraph.

    char ch = aci.setIndex(insertPos);
    int relativePos = Math.max(insertPos - aci.getBeginIndex() - 1, 0);

    Map attributes = addInputMethodAttrs(aci.getAttributes());
    Decoration d = Decoration.getDecoration(attributes);
    if (!oldParagraph.getDecorationAt(relativePos).equals(d)) {
      return new StyledParagraph(aci, chars);
    }
    Object f = getGraphicOrFont(attributes);
    if (f == null) {
      FontResolver resolver = FontResolver.getInstance();
      int fontIndex = resolver.getFontIndex(ch);
      f = resolver.getFont(fontIndex, attributes);
    }
    if (!oldParagraph.getFontOrGraphicAt(relativePos).equals(f)) {
      return new StyledParagraph(aci, chars);
    }

    // insert into existing paragraph
    oldParagraph.length += 1;
    if (oldParagraph.decorations != null) {
      insertInto(relativePos, oldParagraph.decorationStarts, oldParagraph.decorations.size());
    }
    if (oldParagraph.fonts != null) {
      insertInto(relativePos, oldParagraph.fontStarts, oldParagraph.fonts.size());
    }
    return oldParagraph;
  }
  /** Add a new Decoration run with the given Decoration at the given index. */
  private void addDecoration(Decoration d, int index) {

    if (decorations != null) {
      decorationStarts = addToVector(d, index, decorations, decorationStarts);
    } else if (decoration == null) {
      decoration = d;
    } else {
      if (!decoration.equals(d)) {
        decorations = new Vector(INITIAL_SIZE);
        decorations.addElement(decoration);
        decorations.addElement(d);
        decorationStarts = new int[INITIAL_SIZE];
        decorationStarts[0] = 0;
        decorationStarts[1] = index;
      }
    }
  }
  /**
   * Create a new StyledParagraph over the given styled text.
   *
   * @param aci an iterator over the text
   * @param chars the characters extracted from aci
   */
  public StyledParagraph(AttributedCharacterIterator aci, char[] chars) {

    int start = aci.getBeginIndex();
    int end = aci.getEndIndex();
    length = end - start;

    int index = start;
    aci.first();

    do {
      final int nextRunStart = aci.getRunLimit();
      final int localIndex = index - start;

      Map attributes = aci.getAttributes();
      attributes = addInputMethodAttrs(attributes);
      Decoration d = Decoration.getDecoration(attributes);
      addDecoration(d, localIndex);

      Object f = getGraphicOrFont(attributes);
      if (f == null) {
        addFonts(chars, attributes, localIndex, nextRunStart - start);
      } else {
        addFont(f, localIndex);
      }

      aci.setIndex(nextRunStart);
      index = nextRunStart;

    } while (index < end);

    // Add extra entries to starts arrays with the length
    // of the paragraph.  'this' is used as a dummy value
    // in the Vector.
    if (decorations != null) {
      decorationStarts = addToVector(this, length, decorations, decorationStarts);
    }
    if (fonts != null) {
      fontStarts = addToVector(this, length, fonts, fontStarts);
    }
  }