@Override
  public void paint(Graphics g) {
    super.paint(g);

    // check whether the height of this panel matches the height of the text component or not
    Dimension textComponentPreferredSize = textComponent.getPreferredSize();
    if (textComponentHeight != textComponentPreferredSize.height) {
      textComponentHeight = textComponentPreferredSize.height;
      updatePanelSize();
    }

    JViewport viewport = scrollPane.getViewport();
    Point viewPosition = viewport.getViewPosition();
    Dimension viewportSize = viewport.getSize();

    validateTextComponentDocument();
    Element defaultRootElement = document.getDefaultRootElement();


    // maybe able to get the value when font changed and cache them
    // however i'm not sure if there is any condition which will make the java.awt.FontMetrics get by getFontMetrics() from java.awt.Graphics is different from getFontMetrics() from java.awt.Component
    FontMetrics fontMetrics = g.getFontMetrics(getFont());
    int fontHeight = fontMetrics.getHeight();
    int fontAscent = fontMetrics.getAscent();
    int fontLeading = fontMetrics.getLeading();

    FontMetrics textPaneFontMetrics = g.getFontMetrics(textComponent.getFont());
    int textPaneFontHeight = textPaneFontMetrics.getHeight();


    // get the location of the document of the left top and right bottom point of the visible part of the text component
    int documentOffsetStart = textComponent.viewToModel(viewPosition);
    int documentOffsetEnd = textComponent.viewToModel(new Point(viewPosition.x + viewportSize.width, viewPosition.y + viewportSize.height));

    // convert the location to line number
    int startLine = defaultRootElement.getElementIndex(documentOffsetStart) + 1 + lineNumberOffset;
    int endLine = defaultRootElement.getElementIndex(documentOffsetEnd) + 1 + lineNumberOffset;


    // draw right border
    g.setColor(borderColor);
    g.fillRect(panelWidth - borderWidth, viewPosition.y, borderWidth, viewportSize.height);

    // draw line number
    int startY = -1, baselineOffset = -1;
    try {
      startY = textComponent.modelToView(documentOffsetStart).y;
      baselineOffset = (textPaneFontHeight / 2) + fontAscent - (fontHeight / 2) + fontLeading;
    } catch (BadLocationException ex) {
      LOG.log(Level.WARNING, null, ex);
      return;
    }

    // text anti-aliasing
    ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAntiAliasing);
    // preserve the foreground color (for recover the color after highlighing the line)
    Color foregroundColor = getForeground();

    g.setColor(foregroundColor);
    g.setFont(getFont());

    for (int i = startLine, y = startY + baselineOffset; i <= endLine; y += textPaneFontHeight, i++) {
      boolean highlighted = false;
      if (highlightedLineList.indexOf((Integer) i) != -1) {
        // highlight this line
        g.setColor(borderColor);
        g.fillRect(0, y - baselineOffset, panelWidth - borderWidth, textPaneFontHeight);
        g.setColor(highlightedColor);
        highlighted = true;
      }

      // draw the line number
      String lineNumberString = Integer.toString(i);
      int lineNumberStringWidth = fontMetrics.stringWidth(lineNumberString);
      g.drawString(lineNumberString, panelWidth - lineNumberStringWidth - paddingRight, y);

      // restore the line number text color
      if (highlighted) {
        g.setColor(foregroundColor);
      }
    }
  }