private void paintWhitespace(float x, int y, int start, int end,
                                 LineWhitespacePaintingStrategy whitespacePaintingStrategy,
                                 VisualLineFragmentsIterator.Fragment fragment, BasicStroke stroke, float scale) {
      if (!whitespacePaintingStrategy.showAnyWhitespace()) return;

      boolean restoreStroke = false;
      Stroke defaultStroke = myGraphics.getStroke();
      Color whitespacesColor = myEditor.getColorsScheme().getColor(EditorColors.WHITESPACES_COLOR);
      Color tabsColor = myEditor.getColorsScheme().getColor(EditorColors.TABS_COLOR);

      boolean isRtl = fragment.isRtl();
      int baseStartOffset = fragment.getStartOffset();
      int startOffset = isRtl ? baseStartOffset - start : baseStartOffset + start;
      int yToUse = y - 1;

      for (int i = start; i < end; i++) {
        int charOffset = isRtl ? baseStartOffset - i - 1 : baseStartOffset + i;
        char c = myText.charAt(charOffset);
        if (" \t\u3000".indexOf(c) >= 0 && whitespacePaintingStrategy.showWhitespaceAtOffset(charOffset, myCaretDataInView)) {
          int startX = (int)fragment.offsetToX(x, startOffset, isRtl ? baseStartOffset - i : baseStartOffset + i);
          int endX = (int)fragment.offsetToX(x, startOffset, isRtl ? baseStartOffset - i - 1 : baseStartOffset + i + 1);

          if (c == ' ') {
            // making center point lie at the center of device pixel
            float dotX = roundToPixelCenter((startX + endX) / 2.) - scale / 2;
            float dotY = roundToPixelCenter(yToUse + 1 - myAscent + myLineHeight / 2.) - scale / 2;
            myTextDrawingTasks.add(g -> {
              CachingPainter.paint(g, dotX, dotY, scale, scale,
                                   _g -> {
                                     _g.setColor(whitespacesColor);
                                     _g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                                     _g.fill(new Ellipse2D.Float(0, 0, scale, scale));
                                   }, ourCachedDot, whitespacesColor);
            });
          }
          else if (c == '\t') {
            double strokeWidth = Math.max(scale, PaintUtil.devPixel(myGraphics));
            switch (AdvancedSettings.getEnum(EDITOR_TAB_PAINTING, TabCharacterPaintMode.class)) {
              case LONG_ARROW -> {
                int tabEndX = endX - (int)(myView.getPlainSpaceWidth() / 4);
                int height = myView.getCharHeight();
                Color tabColor = tabsColor == null ? null : ColorUtil.mix(myBackgroundColor, tabsColor, 0.7);
                myTextDrawingTasks.add(g -> {
                  int halfHeight = height / 2;
                  int yMid = yToUse - halfHeight;
                  int yTop = yToUse - height;
                  g.setColor(tabColor);
                  LinePainter2D.paint(g, startX, yMid, tabEndX, yMid, LinePainter2D.StrokeType.INSIDE, strokeWidth);
                  LinePainter2D.paint(g, tabEndX, yToUse, tabEndX, yTop, LinePainter2D.StrokeType.INSIDE, strokeWidth);
                  g.fillPolygon(new int[]{tabEndX - halfHeight, tabEndX - halfHeight, tabEndX}, new int[]{yToUse, yTop, yMid}, 3);
                });
              }
              case ARROW -> {
                int tabLineHeight = calcFeatureSize(4, scale);
                int tabLineWidth = Math.min(endX - startX, calcFeatureSize(3, scale));
                int xToUse = Math.min(endX - tabLineWidth, startX + tabLineWidth);
                myTextDrawingTasks.add(g -> {
                  g.setColor(tabsColor);
                  g.setStroke(stroke);
                  Object oldHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
                  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                  g.drawLine(xToUse, yToUse, xToUse + tabLineWidth, yToUse - tabLineHeight);
                  g.drawLine(xToUse, yToUse - tabLineHeight * 2, xToUse + tabLineWidth, yToUse - tabLineHeight);
                  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldHint);
                });
                restoreStroke = true;
              }
              default -> {
                int yMid = yToUse - myView.getCharHeight() / 2;
                int tabEndX = Math.max(startX + 1, endX - getTabGap(scale));
                myTextDrawingTasks.add(g -> {
                  g.setColor(tabsColor);
                  LinePainter2D.paint(g, startX, yMid, tabEndX, yMid, LinePainter2D.StrokeType.INSIDE, strokeWidth);
                });
              }
            }
          }
          else if (c == '\u3000') { // ideographic space
            int charHeight = myView.getCharHeight();
            int strokeWidth = Math.round(stroke.getLineWidth());
            myTextDrawingTasks.add(g -> {
              g.setColor(whitespacesColor);
              g.setStroke(stroke);
              g.drawRect(startX + JBUIScale.scale(2) + strokeWidth / 2, yToUse - charHeight + strokeWidth / 2,
                                  endX - startX - JBUIScale.scale(4) - (strokeWidth - 1), charHeight - (strokeWidth - 1));
            });
            restoreStroke = true;
          }
        }
      }
      if (restoreStroke) {
        myTextDrawingTasks.add((g) -> {
          g.setStroke(defaultStroke);
        });
      }
    }
private void paintWhitespace(float x, int y, int start, int end,
                                 LineWhitespacePaintingStrategy whitespacePaintingStrategy,
                                 VisualLineFragmentsIterator.Fragment fragment, BasicStroke stroke, float scale)