/**
   * 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 deleting a single character
   *     at deletePos.
   * @param chars the characters in aci
   * @param deletePos the index where a character was removed
   * @param oldParagraph a StyledParagraph for the text in aci before the insertion
   */
  public static StyledParagraph deleteChar(
      AttributedCharacterIterator aci, char[] chars, int deletePos, StyledParagraph oldParagraph) {

    // We will reuse oldParagraph unless there was a length-1 run
    // at deletePos.  We could do more work and check the individual
    // Font and Decoration runs, but we don't right now...
    deletePos -= aci.getBeginIndex();

    if (oldParagraph.decorations == null && oldParagraph.fonts == null) {
      oldParagraph.length -= 1;
      return oldParagraph;
    }

    if (oldParagraph.getRunLimit(deletePos) == deletePos + 1) {
      if (deletePos == 0 || oldParagraph.getRunLimit(deletePos - 1) == deletePos) {
        return new StyledParagraph(aci, chars);
      }
    }

    oldParagraph.length -= 1;
    if (oldParagraph.decorations != null) {
      deleteFrom(deletePos, oldParagraph.decorationStarts, oldParagraph.decorations.size());
    }
    if (oldParagraph.fonts != null) {
      deleteFrom(deletePos, oldParagraph.fontStarts, oldParagraph.fonts.size());
    }
    return oldParagraph;
  }
  /**
   * 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;
  }