/**
  * Determines the model location that represents the maximum advance that fits within the given
  * span. This could be used to break the given view. The result should be a location just shy of
  * the given advance. This differs from viewToModel which returns the closest position which might
  * be proud of the maximum advance.
  *
  * @param v the view to find the model location to break at.
  * @param p0 the location in the model where the fragment should start it's representation >= 0.
  * @param pos the graphic location along the axis that the broken view would occupy >= 0. This may
  *     be useful for things like tab calculations.
  * @param len specifies the distance into the view where a potential break is desired >= 0.
  * @return the maximum model location possible for a break.
  * @see View#breakView
  */
 public int getBoundedPosition(GlyphView v, int p0, float x, float len) {
   if (len < 0) throw new IllegalArgumentException("Length must be >= 0.");
   // note: this only works because swing uses TextLayouts that are
   // only pure rtl or pure ltr
   TextHitInfo hit;
   if (layout.isLeftToRight()) {
     hit = layout.hitTestChar(len, 0);
   } else {
     hit = layout.hitTestChar(layout.getAdvance() - len, 0);
   }
   return v.getStartOffset() + hit.getCharIndex();
 }
  /**
   * Provides a mapping from the view coordinate space to the logical coordinate space of the model.
   *
   * @param v the view containing the view coordinates
   * @param x the X coordinate
   * @param y the Y coordinate
   * @param a the allocated region to render into
   * @param biasReturn either <code>Position.Bias.Forward</code> or <code>Position.Bias.Backward
   *     </code> is returned as the zero-th element of this array
   * @return the location within the model that best represents the given point of view
   * @see View#viewToModel
   */
  public int viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasReturn) {

    Rectangle2D alloc = (a instanceof Rectangle2D) ? (Rectangle2D) a : a.getBounds2D();
    // Move the y co-ord of the hit onto the baseline.  This is because TextLayout supports
    // italic carets and we do not.
    TextHitInfo hit = layout.hitTestChar(x - (float) alloc.getX(), 0);
    int pos = hit.getInsertionIndex();

    if (pos == v.getEndOffset()) {
      pos--;
    }

    biasReturn[0] = hit.isLeadingEdge() ? Position.Bias.Forward : Position.Bias.Backward;
    return pos + v.getStartOffset();
  }
  public Shape modelToView(GlyphView v, int pos, Position.Bias bias, Shape a)
      throws BadLocationException {
    int offs = pos - v.getStartOffset();
    Rectangle2D alloc = a.getBounds2D();
    TextHitInfo hit =
        (bias == Position.Bias.Forward)
            ? TextHitInfo.afterOffset(offs)
            : TextHitInfo.beforeOffset(offs);
    float[] locs = layout.getCaretInfo(hit);

    // vertical at the baseline, should use slope and check if glyphs
    // are being rendered vertically.
    alloc.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight());
    return alloc;
  }
  /**
   * Determine the span the glyphs given a start location (for tab expansion). This implementation
   * assumes it has no tabs (i.e. TextLayout doesn't deal with tab expansion).
   */
  public float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x) {

    if ((p0 == v.getStartOffset()) && (p1 == v.getEndOffset())) {
      return layout.getAdvance();
    }
    int p = v.getStartOffset();
    int index0 = p0 - p;
    int index1 = p1 - p;

    TextHitInfo hit0 = TextHitInfo.afterOffset(index0);
    TextHitInfo hit1 = TextHitInfo.beforeOffset(index1);
    float[] locs = layout.getCaretInfo(hit0);
    float x0 = locs[0];
    locs = layout.getCaretInfo(hit1);
    float x1 = locs[0];
    return (x1 > x0) ? x1 - x0 : x0 - x1;
  }
  public void sendInputMethodEvent(
      int id,
      long when,
      String text,
      int[] clauseBoundary,
      String[] clauseReading,
      int[] attributeBoundary,
      byte[] attributeValue,
      int commitedTextLength,
      int caretPos,
      int visiblePos) {

    AttributedCharacterIterator iterator = null;

    if (text != null) {

      // construct AttributedString
      AttributedString attrStr = new AttributedString(text);

      // set Language Information
      attrStr.addAttribute(Attribute.LANGUAGE, Locale.getDefault(), 0, text.length());

      // set Clause and Reading Information
      if (clauseBoundary != null
          && clauseReading != null
          && clauseReading.length != 0
          && clauseBoundary.length == clauseReading.length + 1
          && clauseBoundary[0] == 0
          && clauseBoundary[clauseReading.length] == text.length()) {
        for (int i = 0; i < clauseBoundary.length - 1; i++) {
          attrStr.addAttribute(
              Attribute.INPUT_METHOD_SEGMENT,
              new Annotation(null),
              clauseBoundary[i],
              clauseBoundary[i + 1]);
          attrStr.addAttribute(
              Attribute.READING,
              new Annotation(clauseReading[i]),
              clauseBoundary[i],
              clauseBoundary[i + 1]);
        }
      } else {
        // if (clauseBoundary != null)
        //    System.out.println("Invalid clause information!");

        attrStr.addAttribute(
            Attribute.INPUT_METHOD_SEGMENT, new Annotation(null), 0, text.length());
        attrStr.addAttribute(Attribute.READING, new Annotation(""), 0, text.length());
      }

      // set Hilight Information
      if (attributeBoundary != null
          && attributeValue != null
          && attributeValue.length != 0
          && attributeBoundary.length == attributeValue.length + 1
          && attributeBoundary[0] == 0
          && attributeBoundary[attributeValue.length] == text.length()) {
        for (int i = 0; i < attributeBoundary.length - 1; i++) {
          InputMethodHighlight highlight;
          switch (attributeValue[i]) {
            case ATTR_TARGET_CONVERTED:
              highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
              break;
            case ATTR_CONVERTED:
              highlight = InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT;
              break;
            case ATTR_TARGET_NOTCONVERTED:
              highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
              break;
            case ATTR_INPUT:
            case ATTR_INPUT_ERROR:
            default:
              highlight = InputMethodHighlight.UNSELECTED_RAW_TEXT_HIGHLIGHT;
              break;
          }
          attrStr.addAttribute(
              TextAttribute.INPUT_METHOD_HIGHLIGHT,
              highlight,
              attributeBoundary[i],
              attributeBoundary[i + 1]);
        }
      } else {
        // if (attributeBoundary != null)
        //    System.out.println("Invalid attribute information!");

        attrStr.addAttribute(
            TextAttribute.INPUT_METHOD_HIGHLIGHT,
            InputMethodHighlight.UNSELECTED_CONVERTED_TEXT_HIGHLIGHT,
            0,
            text.length());
      }

      // get iterator
      iterator = attrStr.getIterator();
    }

    Component source = getClientComponent();
    if (source == null) return;

    InputMethodEvent event =
        new InputMethodEvent(
            source,
            id,
            when,
            iterator,
            commitedTextLength,
            TextHitInfo.leading(caretPos),
            TextHitInfo.leading(visiblePos));
    WToolkit.postEvent(WToolkit.targetToAppContext(source), event);
  }
  /**
   * Provides a way to determine the next visually represented model location that one might place a
   * caret. Some views may not be visible, they might not be in the same order found in the model,
   * or they just might not allow access to some of the locations in the model.
   *
   * @param v the view to use
   * @param pos the position to convert >= 0
   * @param a the allocated region to render into
   * @param direction the direction from the current position that can be thought of as the arrow
   *     keys typically found on a keyboard. This may be SwingConstants.WEST, SwingConstants.EAST,
   *     SwingConstants.NORTH, or SwingConstants.SOUTH.
   * @return the location within the model that best represents the next location visual position.
   * @exception BadLocationException
   * @exception IllegalArgumentException for an invalid direction
   */
  public int getNextVisualPositionFrom(
      GlyphView v, int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet)
      throws BadLocationException {

    int startOffset = v.getStartOffset();
    int endOffset = v.getEndOffset();
    Segment text;
    AbstractDocument doc;
    boolean viewIsLeftToRight;
    TextHitInfo currentHit, nextHit;

    switch (direction) {
      case View.NORTH:
        break;
      case View.SOUTH:
        break;
      case View.EAST:
        doc = (AbstractDocument) v.getDocument();
        viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset);

        if (startOffset == doc.getLength()) {
          if (pos == -1) {
            biasRet[0] = Position.Bias.Forward;
            return startOffset;
          }
          // End case for bidi text where newline is at beginning
          // of line.
          return -1;
        }
        if (pos == -1) {
          // Entering view from the left.
          if (viewIsLeftToRight) {
            biasRet[0] = Position.Bias.Forward;
            return startOffset;
          } else {
            text = v.getText(endOffset - 1, endOffset);
            char c = text.array[text.offset];
            SegmentCache.releaseSharedSegment(text);
            if (c == '\n') {
              biasRet[0] = Position.Bias.Forward;
              return endOffset - 1;
            }
            biasRet[0] = Position.Bias.Backward;
            return endOffset;
          }
        }
        if (b == Position.Bias.Forward) currentHit = TextHitInfo.afterOffset(pos - startOffset);
        else currentHit = TextHitInfo.beforeOffset(pos - startOffset);
        nextHit = layout.getNextRightHit(currentHit);
        if (nextHit == null) {
          return -1;
        }
        if (viewIsLeftToRight != layout.isLeftToRight()) {
          // If the layout's base direction is different from
          // this view's run direction, we need to use the weak
          // carrat.
          nextHit = layout.getVisualOtherHit(nextHit);
        }
        pos = nextHit.getInsertionIndex() + startOffset;

        if (pos == endOffset) {
          // A move to the right from an internal position will
          // only take us to the endOffset in a left to right run.
          text = v.getText(endOffset - 1, endOffset);
          char c = text.array[text.offset];
          SegmentCache.releaseSharedSegment(text);
          if (c == '\n') {
            return -1;
          }
          biasRet[0] = Position.Bias.Backward;
        } else {
          biasRet[0] = Position.Bias.Forward;
        }
        return pos;
      case View.WEST:
        doc = (AbstractDocument) v.getDocument();
        viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset);

        if (startOffset == doc.getLength()) {
          if (pos == -1) {
            biasRet[0] = Position.Bias.Forward;
            return startOffset;
          }
          // End case for bidi text where newline is at beginning
          // of line.
          return -1;
        }
        if (pos == -1) {
          // Entering view from the right
          if (viewIsLeftToRight) {
            text = v.getText(endOffset - 1, endOffset);
            char c = text.array[text.offset];
            SegmentCache.releaseSharedSegment(text);
            if ((c == '\n') || Character.isSpaceChar(c)) {
              biasRet[0] = Position.Bias.Forward;
              return endOffset - 1;
            }
            biasRet[0] = Position.Bias.Backward;
            return endOffset;
          } else {
            biasRet[0] = Position.Bias.Forward;
            return startOffset;
          }
        }
        if (b == Position.Bias.Forward) currentHit = TextHitInfo.afterOffset(pos - startOffset);
        else currentHit = TextHitInfo.beforeOffset(pos - startOffset);
        nextHit = layout.getNextLeftHit(currentHit);
        if (nextHit == null) {
          return -1;
        }
        if (viewIsLeftToRight != layout.isLeftToRight()) {
          // If the layout's base direction is different from
          // this view's run direction, we need to use the weak
          // carrat.
          nextHit = layout.getVisualOtherHit(nextHit);
        }
        pos = nextHit.getInsertionIndex() + startOffset;

        if (pos == endOffset) {
          // A move to the left from an internal position will
          // only take us to the endOffset in a right to left run.
          text = v.getText(endOffset - 1, endOffset);
          char c = text.array[text.offset];
          SegmentCache.releaseSharedSegment(text);
          if (c == '\n') {
            return -1;
          }
          biasRet[0] = Position.Bias.Backward;
        } else {
          biasRet[0] = Position.Bias.Forward;
        }
        return pos;
      default:
        throw new IllegalArgumentException("Bad direction: " + direction);
    }
    return pos;
  }