private int computeTextWidth(@NotNull Font font, final boolean mainTextOnly) {
    int result = 0;
    int baseSize = font.getSize();
    boolean wasSmaller = false;
    for (int i = 0; i < myAttributes.size(); i++) {
      SimpleTextAttributes attributes = myAttributes.get(i);
      boolean isSmaller = attributes.isSmaller();
      if (font.getStyle() != attributes.getFontStyle()
          || isSmaller != wasSmaller) { // derive font only if it is necessary
        font =
            font.deriveFont(
                attributes.getFontStyle(),
                isSmaller ? UIUtil.getFontSize(UIUtil.FontSize.SMALL) : baseSize);
      }
      wasSmaller = isSmaller;

      result += computeStringWidth(i, font);

      final int fixedWidth = myFragmentPadding.get(i);
      if (fixedWidth > 0 && result < fixedWidth) {
        result = fixedWidth;
      }
      if (mainTextOnly && myMainTextLastIndex >= 0 && i == myMainTextLastIndex) break;
    }
    return result;
  }
  /**
   * Returns the index of text fragment at the specified X offset.
   *
   * @param x the offset
   * @return the index of the fragment, {@link #FRAGMENT_ICON} if the icon is at the offset, or -1
   *     if nothing is there.
   */
  public int findFragmentAt(int x) {
    int curX = myIpad.left;
    if (myIcon != null && !myIconOnTheRight) {
      final int iconRight = myIcon.getIconWidth() + myIconTextGap;
      if (x < iconRight) {
        return FRAGMENT_ICON;
      }
      curX += iconRight;
    }

    Font font = getBaseFont();

    int baseSize = font.getSize();
    boolean wasSmaller = false;
    for (int i = 0; i < myAttributes.size(); i++) {
      SimpleTextAttributes attributes = myAttributes.get(i);
      boolean isSmaller = attributes.isSmaller();
      if (font.getStyle() != attributes.getFontStyle()
          || isSmaller != wasSmaller) { // derive font only if it is necessary
        font =
            font.deriveFont(
                attributes.getFontStyle(),
                isSmaller ? UIUtil.getFontSize(UIUtil.FontSize.SMALL) : baseSize);
      }
      wasSmaller = isSmaller;

      final int curWidth = computeStringWidth(i, font);
      if (x >= curX && x < curX + curWidth) {
        return i;
      }
      curX += curWidth;
      final int fragmentPadding = myFragmentPadding.get(i);
      if (fragmentPadding > 0 && curX < fragmentPadding) {
        curX = fragmentPadding;
      }
    }

    if (myIcon != null && myIconOnTheRight) {
      curX += myIconTextGap;
      if (x >= curX && x < curX + myIcon.getIconWidth()) {
        return FRAGMENT_ICON;
      }
    }
    return -1;
  }
  protected int doPaintText(Graphics2D g, int offset, boolean focusAroundIcon) {
    // If there is no icon, then we have to add left internal padding
    if (offset == 0) {
      offset = myIpad.left;
    }

    int textStart = offset;
    if (myBorder != null) {
      offset += myBorder.getBorderInsets(this).left;
    }

    final List<Object[]> searchMatches = new ArrayList<>();

    applyAdditionalHints(g);
    final Font baseFont = getBaseFont();
    g.setFont(baseFont);
    offset += computeTextAlignShift(baseFont);
    int baseSize = baseFont.getSize();
    FontMetrics baseMetrics = g.getFontMetrics();
    Rectangle area = computePaintArea();
    final int textBaseline = area.y + getTextBaseLine(baseMetrics, area.height);
    boolean wasSmaller = false;
    for (int i = 0; i < myFragments.size(); i++) {
      final SimpleTextAttributes attributes = myAttributes.get(i);

      Font font = g.getFont();
      boolean isSmaller = attributes.isSmaller();
      if (font.getStyle() != attributes.getFontStyle()
          || isSmaller != wasSmaller) { // derive font only if it is necessary
        font =
            font.deriveFont(
                attributes.getFontStyle(),
                isSmaller ? UIUtil.getFontSize(UIUtil.FontSize.SMALL) : baseSize);
      }
      wasSmaller = isSmaller;

      g.setFont(font);
      final FontMetrics metrics = g.getFontMetrics(font);

      final int fragmentWidth = computeStringWidth(i, font);

      final int fragmentPadding = myFragmentPadding.get(i);

      final Color bgColor = attributes.isSearchMatch() ? null : attributes.getBgColor();
      if ((attributes.isOpaque() || isOpaque()) && bgColor != null) {
        g.setColor(bgColor);
        g.fillRect(offset, 0, fragmentWidth, getHeight());
      }

      Color color = attributes.getFgColor();
      if (color
          == null) { // in case if color is not defined we have to get foreground color from Swing
                     // hierarchy
        color = getForeground();
      }
      if (!isEnabled()) {
        color = UIUtil.getInactiveTextColor();
      }
      g.setColor(color);

      final int fragmentAlignment = myFragmentAlignment.get(i);

      final int endOffset;
      if (fragmentPadding > 0 && fragmentPadding > fragmentWidth) {
        endOffset = fragmentPadding;
        if (fragmentAlignment == SwingConstants.RIGHT
            || fragmentAlignment == SwingConstants.TRAILING) {
          offset = fragmentPadding - fragmentWidth;
        }
      } else {
        endOffset = offset + fragmentWidth;
      }

      if (!attributes.isSearchMatch()) {
        if (shouldDrawMacShadow()) {
          g.setColor(SHADOW_COLOR);
          doDrawString(g, i, offset, textBaseline + 1);
        }

        if (shouldDrawDimmed()) {
          color = ColorUtil.dimmer(color);
        }

        g.setColor(color);
        doDrawString(g, i, offset, textBaseline);
      }

      // for some reason strokeState here may be incorrect, resetting the stroke helps
      g.setStroke(g.getStroke());

      // 1. Strikeout effect
      if (attributes.isStrikeout() && !attributes.isSearchMatch()) {
        EffectPainter.STRIKE_THROUGH.paint(
            g, offset, textBaseline, fragmentWidth, getCharHeight(g), font);
      }
      // 2. Waved effect
      if (attributes.isWaved()) {
        if (attributes.getWaveColor() != null) {
          g.setColor(attributes.getWaveColor());
        }
        EffectPainter.WAVE_UNDERSCORE.paint(
            g, offset, textBaseline + 1, fragmentWidth, Math.max(2, metrics.getDescent()), font);
      }
      // 3. Underline
      if (attributes.isUnderline()) {
        EffectPainter.LINE_UNDERSCORE.paint(
            g, offset, textBaseline, fragmentWidth, metrics.getDescent(), font);
      }
      // 4. Bold Dotted Line
      if (attributes.isBoldDottedLine()) {
        final int dottedAt = SystemInfo.isMac ? textBaseline : textBaseline + 1;
        final Color lineColor = attributes.getWaveColor();
        UIUtil.drawBoldDottedLine(
            g, offset, offset + fragmentWidth, dottedAt, bgColor, lineColor, isOpaque());
      }

      if (attributes.isSearchMatch()) {
        searchMatches.add(
            new Object[] {
              offset,
              offset + fragmentWidth,
              textBaseline,
              myFragments.get(i),
              g.getFont(),
              attributes
            });
      }

      offset = endOffset;
    }

    // Paint focus border around the text and icon (if necessary)
    if (myPaintFocusBorder && myBorder != null) {
      if (focusAroundIcon) {
        myBorder.paintBorder(this, g, 0, 0, getWidth(), getHeight());
      } else {
        myBorder.paintBorder(this, g, textStart, 0, getWidth() - textStart, getHeight());
      }
    }

    // draw search matches after all
    for (final Object[] info : searchMatches) {
      Integer x1 = (Integer) info[0];
      Integer x2 = (Integer) info[1];
      UIUtil.drawSearchMatch(g, x1, x2, getHeight());
      g.setFont((Font) info[4]);

      Integer baseline = (Integer) info[2];
      String text = (String) info[3];
      if (shouldDrawMacShadow()) {
        g.setColor(SHADOW_COLOR);
        g.drawString(text, x1, baseline + 1);
      }

      g.setColor(new JBColor(Gray._50, Gray._0));
      g.drawString(text, x1, baseline);

      if (((SimpleTextAttributes) info[5]).isStrikeout()) {
        EffectPainter.STRIKE_THROUGH.paint(g, x1, baseline, x2 - x1, getCharHeight(g), g.getFont());
      }
    }
    return offset;
  }