/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeEditor.printing;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.LineIterator;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.util.containers.IntArrayList;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
class TextPainter extends BasePainter {
private final DocumentEx myDocument;
private RangeMarker myRangeToPrint;
private int myOffset = 0;
private int myLineNumber = 1;
private float myLineHeight = -1;
private float myDescent = -1;
private double myCharWidth = -1;
private final Font myPlainFont;
private final Font myBoldFont;
private final Font myItalicFont;
private final Font myBoldItalicFont;
private final Font myHeaderFont;
private final EditorHighlighter myHighlighter;
private final PrintSettings myPrintSettings;
private final String myFullFileName;
private final String myShortFileName;
private int myPageIndex = -1;
private int myNumberOfPages = -1;
private int mySegmentEnd;
private final LineMarkerInfo[] myMethodSeparators;
private int myCurrentMethodSeparator;
private final CodeStyleSettings myCodeStyleSettings;
private final FileType myFileType;
private boolean myPerformActualDrawing;
private final String myPrintDate;
private final String myPrintTime;
@NonNls private static final String DEFAULT_MEASURE_HEIGHT_TEXT = "A";
@NonNls private static final String DEFAULT_MEASURE_WIDTH_TEXT = "w";
@NonNls private static final String HEADER_TOKEN_PAGE = "PAGE";
@NonNls private static final String HEADER_TOKEN_TOTALPAGES = "TOTALPAGES";
@NonNls private static final String HEADER_TOKEN_FILE = "FILE";
@NonNls private static final String HEADER_TOKEN_FILENAME = "FILENAME";
@NonNls private static final String HEADER_TOKEN_DATE = "DATE";
@NonNls private static final String HEADER_TOKEN_TIME = "TIME";
@NonNls private static final String DATE_FORMAT = "yyyy-MM-dd";
@NonNls private static final String TIME_FORMAT = "HH:mm:ss";
public TextPainter(@NotNull DocumentEx editorDocument,
EditorHighlighter highlighter,
String fullFileName,
String shortFileName,
@NotNull PsiFile psiFile,
FileType fileType) {
this(editorDocument, highlighter, fullFileName, shortFileName, psiFile.getProject(), fileType,
FileSeparatorProvider.getFileSeparators(psiFile, editorDocument));
}
public TextPainter(@NotNull DocumentEx editorDocument,
EditorHighlighter highlighter,
String fullFileName,
String shortFileName,
Project project,
FileType fileType,
List<LineMarkerInfo> separators) {
myCodeStyleSettings = CodeStyleSettingsManager.getSettings(project);
myDocument = editorDocument;
myPrintSettings = PrintSettings.getInstance();
String fontName = myPrintSettings.FONT_NAME;
/*
Printing Graphics is constructed with scale corresponding to the printer DPI settings (~600dpi),
the font size is expected to be in 96 dpi, so we should normalize it.
*/
int fontSize = Math.round(myPrintSettings.FONT_SIZE / UISettings.getDefFontScale());
myPlainFont = new Font(fontName, Font.PLAIN, fontSize);
myBoldFont = new Font(fontName, Font.BOLD, fontSize);
myItalicFont = new Font(fontName, Font.ITALIC, fontSize);
myBoldItalicFont = new Font(fontName, Font.BOLD | Font.ITALIC, fontSize);
myHighlighter = highlighter;
myHeaderFont = new Font(myPrintSettings.FOOTER_HEADER_FONT_NAME, Font.PLAIN, myPrintSettings.FOOTER_HEADER_FONT_SIZE);
myFullFileName = fullFileName;
myShortFileName = shortFileName;
myRangeToPrint = editorDocument.createRangeMarker(0, myDocument.getTextLength());
myFileType = fileType;
myMethodSeparators = separators != null ? separators.toArray(new LineMarkerInfo[separators.size()]) : new LineMarkerInfo[0];
myCurrentMethodSeparator = 0;
Date date = new Date();
myPrintDate = new SimpleDateFormat(DATE_FORMAT).format(date);
myPrintTime = new SimpleDateFormat(TIME_FORMAT).format(date);
}
public void setSegment(int segmentStart, int segmentEnd) {
setSegment(myDocument.createRangeMarker(segmentStart, segmentEnd));
}
private void setSegment(RangeMarker marker) {
if (myRangeToPrint != null) {
myRangeToPrint.dispose();
}
myRangeToPrint = marker;
}
private float getLineHeight(Graphics g) {
if (myLineHeight >= 0) {
return myLineHeight;
}
FontRenderContext fontRenderContext = ((Graphics2D) g).getFontRenderContext();
LineMetrics lineMetrics = myPlainFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
myLineHeight = lineMetrics.getHeight();
return myLineHeight;
}
private float getDescent(Graphics g) {
if (myDescent >= 0) {
return myDescent;
}
FontRenderContext fontRenderContext = ((Graphics2D) g).getFontRenderContext();
LineMetrics lineMetrics = myPlainFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
myDescent = lineMetrics.getDescent();
return myDescent;
}
private Font getFont(int type) {
if (type == Font.BOLD)
return myBoldFont;
else if (type == Font.ITALIC)
return myItalicFont;
else if (type == Font.ITALIC + Font.BOLD)
return myBoldItalicFont;
else
return myPlainFont;
}
boolean isPrintingPass = true;
@Override
public int print(final Graphics g, final PageFormat pageFormat, final int pageIndex) throws PrinterException {
myPerformActualDrawing = false;
if (myProgress.isCanceled()) {
return NO_SUCH_PAGE;
}
final Graphics2D g2d = (Graphics2D)g;
if (myNumberOfPages < 0) {
myProgress.setText(CodeEditorBundle.message("print.file.calculating.number.of.pages.progress"));
if (!calculateNumberOfPages(g2d, pageFormat)) {
return NO_SUCH_PAGE;
}
}
if (pageIndex >= myNumberOfPages) {
return NO_SUCH_PAGE;
}
isPrintingPass = !isPrintingPass;
if (!isPrintingPass) {
while(++myPageIndex < pageIndex) {
if (!printPageInReadAction(g2d, pageFormat, "print.skip.page.progress")) {
return NO_SUCH_PAGE;
}
}
return ReadAction.compute(() -> isValidRange(myRangeToPrint) ? PAGE_EXISTS : NO_SUCH_PAGE);
}
else {
myPerformActualDrawing = true;
printPageInReadAction(g2d, pageFormat, "print.file.page.progress");
return PAGE_EXISTS;
}
}
private boolean printPageInReadAction(final Graphics2D g2d, final PageFormat pageFormat, final String progressMessageKey) {
return ReadAction.compute(() -> {
if (!isValidRange(myRangeToPrint)) {
return false;
}
myProgress.setText(CodeEditorBundle.message(progressMessageKey, myShortFileName, (myPageIndex + 1), myNumberOfPages));
setSegment(printPage(g2d, pageFormat, myRangeToPrint));
return true;
});
}
private boolean calculateNumberOfPages(final Graphics2D g2d, final PageFormat pageFormat) {
myNumberOfPages = 0;
final Ref<Boolean> firstPage = new Ref<>(Boolean.TRUE);
final Ref<RangeMarker> tmpMarker = new Ref<>();
while (ReadAction.compute(() -> {
if (firstPage.get()) {
if (!isValidRange(myRangeToPrint)) {
return false;
}
tmpMarker.set(myDocument.createRangeMarker(myRangeToPrint.getStartOffset(), myRangeToPrint.getEndOffset()));
firstPage.set(Boolean.FALSE);
}
RangeMarker range = tmpMarker.get();
if (!isValidRange(range)) {
return false;
}
tmpMarker.set(printPage(g2d, pageFormat, range));
range.dispose();
return true;
})) {
if (myProgress.isCanceled()) {
return false;
}
myNumberOfPages++;
}
if (!tmpMarker.isNull()) {
tmpMarker.get().dispose();
}
return true;
}
private static boolean isValidRange(RangeMarker range) {
return range != null && range.isValid() && range.getStartOffset() < range.getEndOffset();
}
/**
* Prints a pageful of text from a given range. Return a remaining range to print, or null if there's nothing left.
*/
private RangeMarker printPage(Graphics2D g2d, PageFormat pageFormat, RangeMarker range) {
assert isValidRange(range);
int startOffset = range.getStartOffset();
int endOffset = range.getEndOffset();
myOffset = startOffset;
mySegmentEnd = endOffset;
myLineNumber = myDocument.getLineNumber(myOffset) + 1;
Rectangle2D.Double clip = new Rectangle2D.Double(pageFormat.getImageableX(), pageFormat.getImageableY(),
pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
draw(g2d, clip);
return myOffset > startOffset && myOffset < endOffset ? myDocument.createRangeMarker(myOffset, endOffset) : null;
}
private void draw(Graphics2D g2D, Rectangle2D.Double clip) {
double headerHeight = drawHeader(g2D, clip);
clip.y += headerHeight;
clip.height -= headerHeight;
double footerHeight = drawFooter(g2D, clip);
clip.height -= footerHeight;
Rectangle2D.Double border = (Rectangle2D.Double) clip.clone();
clip.x += getCharWidth(g2D) / 2;
clip.width -= getCharWidth(g2D);
if (myPrintSettings.PRINT_LINE_NUMBERS) {
double numbersStripWidth = calcNumbersStripWidth(g2D, clip) + getCharWidth(g2D) / 2;
clip.x += numbersStripWidth;
clip.width -= numbersStripWidth;
}
clip.x += getCharWidth(g2D) / 2;
clip.width -= getCharWidth(g2D);
drawText(g2D, clip);
drawBorder(g2D, border);
}
private void drawBorder(Graphics2D g, Rectangle2D clip) {
if (myPrintSettings.DRAW_BORDER && myPerformActualDrawing) {
Color save = g.getColor();
g.setColor(Color.black);
g.draw(clip);
g.setColor(save);
}
}
private double getCharWidth(Graphics2D g) {
if (myCharWidth < 0) {
FontRenderContext fontRenderContext = (g).getFontRenderContext();
myCharWidth = myPlainFont.getStringBounds(DEFAULT_MEASURE_WIDTH_TEXT, fontRenderContext).getWidth();
}
return myCharWidth;
}
private void setForegroundColor(Graphics2D g, Color color) {
if (color == null || !myPrintSettings.COLOR_PRINTING || !myPrintSettings.SYNTAX_PRINTING) {
color = Color.black;
}
g.setColor(color);
}
private void setBackgroundColor(Graphics2D g, Color color) {
if (color == null || !myPrintSettings.COLOR_PRINTING || !myPrintSettings.SYNTAX_PRINTING) {
color = Color.white;
}
g.setColor(color);
}
private void setFont(Graphics2D g, Font font) {
if (!myPrintSettings.SYNTAX_PRINTING) {
font = myPlainFont;
}
g.setFont(font);
}
private void drawText(Graphics2D g, Rectangle2D clip) {
float lineHeight = getLineHeight(g);
HighlighterIterator hIterator = myHighlighter.createIterator(myOffset);
if (hIterator.atEnd()) {
myOffset = mySegmentEnd;
return;
}
LineIterator lIterator = myDocument.createLineIterator();
lIterator.start(myOffset);
if (lIterator.atEnd()) {
myOffset = mySegmentEnd;
return;
}
TextAttributes attributes = hIterator.getTextAttributes();
Color currentColor = attributes.getForegroundColor();
Color backColor = attributes.getBackgroundColor();
Color underscoredColor = attributes.getEffectColor();
Font currentFont = getFont(attributes.getFontType());
setForegroundColor(g, currentColor);
setFont(g, currentFont);
g.translate(clip.getX(), 0);
Point2D position = new Point2D.Double(0, clip.getY());
double lineY = position.getY();
if (myPerformActualDrawing) {
getMethodSeparator(lIterator.getLineNumber());
}
char[] text = myDocument.getCharsSequence().toString().toCharArray();
while (!hIterator.atEnd() && !lIterator.atEnd()) {
int hEnd = hIterator.getEnd();
int lEnd = lIterator.getEnd();
int lStart = lIterator.getStart();
if (hEnd >= lEnd) {
if (!drawString(g, text, lEnd - lIterator.getSeparatorLength(), myOffset == lStart, position, clip, backColor,
underscoredColor)) {
drawLineNumber(g, 0, lineY);
break;
}
drawLineNumber(g, 0, lineY);
lIterator.advance();
myLineNumber++;
position.setLocation(0, position.getY() + lineHeight);
lineY = position.getY();
myOffset = lEnd;
if (myPerformActualDrawing) {
LineMarkerInfo marker = getMethodSeparator(lIterator.getLineNumber());
if (marker != null) {
Color save = g.getColor();
setForegroundColor(g, marker.separatorColor);
UIUtil.drawLine(g, 0, (int)lineY, (int)clip.getWidth(), (int)lineY);
setForegroundColor(g, save);
}
}
if (position.getY() > clip.getY() + clip.getHeight() - lineHeight) {
break;
}
} else {
if (hEnd > lEnd - lIterator.getSeparatorLength()) {
if (!drawString(g, text, lEnd - lIterator.getSeparatorLength(), myOffset == lStart, position, clip, backColor,
underscoredColor)) {
drawLineNumber(g, 0, lineY);
break;
}
} else {
if (!drawString(g, text, hEnd, myOffset == lStart, position, clip, backColor, underscoredColor)) {
drawLineNumber(g, 0, lineY);
break;
}
}
hIterator.advance();
attributes = hIterator.getTextAttributes();
Color color = attributes.getForegroundColor();
if (color == null) {
color = Color.black;
}
if (color != currentColor) {
setForegroundColor(g, color);
currentColor = color;
}
backColor = attributes.getBackgroundColor();
underscoredColor = attributes.getEffectColor();
Font font = getFont(attributes.getFontType());
if (font != currentFont) {
setFont(g, font);
currentFont = font;
}
myOffset = hEnd;
}
}
g.translate(-clip.getX(), 0);
}
private LineMarkerInfo getMethodSeparator(int line) {
LineMarkerInfo marker = null;
LineMarkerInfo tmpMarker;
while (myCurrentMethodSeparator < myMethodSeparators.length &&
(tmpMarker = myMethodSeparators[myCurrentMethodSeparator]) != null &&
FileSeparatorProvider.getDisplayLine(tmpMarker, myDocument) <= line) {
marker = tmpMarker;
myCurrentMethodSeparator++;
}
return marker;
}
private double drawHeader(Graphics2D g, Rectangle2D clip) {
LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
double w = clip.getWidth();
double x = clip.getX();
double y = clip.getY();
double h = 0;
boolean wasDrawn = false;
String headerText1 = myPrintSettings.FOOTER_HEADER_TEXT1;
if (headerText1 != null && headerText1.length() > 0 &&
PrintSettings.HEADER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT1)) {
h = drawHeaderOrFooterLine(g, x, y, w, headerText1, myPrintSettings.FOOTER_HEADER_ALIGNMENT1);
wasDrawn = true;
y += h;
}
String headerText2 = myPrintSettings.FOOTER_HEADER_TEXT2;
if (headerText2 != null && headerText2.length() > 0 &&
PrintSettings.HEADER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT2)) {
if (PrintSettings.LEFT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT1) &&
PrintSettings.RIGHT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT2) &&
wasDrawn) {
y -= h;
}
h = drawHeaderOrFooterLine(g, x, y, w, headerText2, myPrintSettings.FOOTER_HEADER_ALIGNMENT2);
y += h;
wasDrawn = true;
}
return wasDrawn ? y - clip.getY() + lineMetrics.getHeight() / 3 : 0;
}
private double drawFooter(Graphics2D g, Rectangle2D clip) {
LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
double w = clip.getWidth();
double x = clip.getX();
double y = clip.getY() + clip.getHeight();
boolean wasDrawn = false;
double h = 0;
y -= lineMetrics.getHeight();
String headerText2 = myPrintSettings.FOOTER_HEADER_TEXT2;
if (headerText2 != null && headerText2.length() > 0 &&
PrintSettings.FOOTER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT2)) {
h = drawHeaderOrFooterLine(g, x, y, w, headerText2, myPrintSettings.FOOTER_HEADER_ALIGNMENT2);
wasDrawn = true;
}
String headerText1 = myPrintSettings.FOOTER_HEADER_TEXT1;
if (headerText1 != null && headerText1.length() > 0 &&
PrintSettings.FOOTER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT1)) {
y -= lineMetrics.getHeight();
if (PrintSettings.LEFT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT1) &&
PrintSettings.RIGHT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT2) &&
wasDrawn) {
y += h;
}
drawHeaderOrFooterLine(g, x, y, w, headerText1, myPrintSettings.FOOTER_HEADER_ALIGNMENT1);
wasDrawn = true;
}
return wasDrawn ? clip.getY() + clip.getHeight() - y + lineMetrics.getHeight() / 4 : 0;
}
private double drawHeaderOrFooterLine(Graphics2D g, double x, double y, double w, String headerText,
String alignment) {
FontRenderContext fontRenderContext = g.getFontRenderContext();
LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
float lineHeight = lineMetrics.getHeight();
if (myPerformActualDrawing) {
headerText = convertHeaderText(headerText);
g.setFont(myHeaderFont);
g.setColor(Color.black);
float descent = lineMetrics.getDescent();
double width = myHeaderFont.getStringBounds(headerText, fontRenderContext).getWidth() + getCharWidth(g);
float yPos = (float) (lineHeight - descent + y);
if (PrintSettings.LEFT.equals(alignment)) {
drawStringToGraphics(g, headerText, x, yPos);
} else if (PrintSettings.CENTER.equals(alignment)) {
drawStringToGraphics(g, headerText, (float) (x + (w - width) / 2), yPos);
} else if (PrintSettings.RIGHT.equals(alignment)) {
drawStringToGraphics(g, headerText, (float) (x + w - width), yPos);
}
}
return lineHeight;
}
private String convertHeaderText(String s) {
StringBuilder result = new StringBuilder("");
int start = 0;
boolean isExpression = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '$') {
String token = s.substring(start, i);
if (isExpression) {
if (HEADER_TOKEN_PAGE.equals(token)) {
result.append(myPageIndex + 1);
} else if (HEADER_TOKEN_TOTALPAGES.equals(token)) {
result.append(myNumberOfPages);
} else if (HEADER_TOKEN_FILE.equals(token)) {
result.append(myFullFileName);
} else if (HEADER_TOKEN_FILENAME.equals(token)) {
result.append(myShortFileName);
} else if (HEADER_TOKEN_DATE.equals(token)) {
result.append(myPrintDate);
} else if (HEADER_TOKEN_TIME.equals(token)) {
result.append(myPrintTime);
}
} else {
result.append(token);
}
isExpression = !isExpression;
start = i + 1;
}
}
if (!isExpression && start < s.length()) {
result.append(s.substring(start, s.length()));
}
return result.toString();
}
private LineMetrics getHeaderFooterLineMetrics(Graphics2D g) {
FontRenderContext fontRenderContext = g.getFontRenderContext();
return myHeaderFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
}
private double calcNumbersStripWidth(Graphics2D g, Rectangle2D clip) {
if (!myPrintSettings.PRINT_LINE_NUMBERS) {
return 0;
}
int maxLineNumber = myLineNumber + (int) (clip.getHeight() / getLineHeight(g));
FontRenderContext fontRenderContext = (g).getFontRenderContext();
double numbersStripWidth = 0;
for (int i = myLineNumber; i < maxLineNumber; i++) {
double width = myPlainFont.getStringBounds(String.valueOf(i), fontRenderContext).getWidth();
if (numbersStripWidth < width) {
numbersStripWidth = width;
}
}
return numbersStripWidth;
}
private void drawLineNumber(Graphics2D g, double x, double y) {
if (!myPrintSettings.PRINT_LINE_NUMBERS || !myPerformActualDrawing) {
return;
}
FontRenderContext fontRenderContext = (g).getFontRenderContext();
double width = myPlainFont.getStringBounds(String.valueOf(myLineNumber), fontRenderContext).getWidth() + getCharWidth(g);
Color savedColor = g.getColor();
Font savedFont = g.getFont();
g.setColor(Color.black);
g.setFont(myPlainFont);
drawStringToGraphics(g, String.valueOf(myLineNumber), x - width, getLineHeight(g) - getDescent(g) + y);
g.setColor(savedColor);
g.setFont(savedFont);
}
private boolean drawString(Graphics2D g, char[] text, int end, boolean lineStart, Point2D position, Rectangle2D clip,
Color backColor, Color underscoredColor) {
boolean toContinue = true;
if (end >= mySegmentEnd) {
end = mySegmentEnd;
toContinue = false;
}
if (myOffset >= end) return toContinue;
boolean isInClip = (getLineHeight(g) + position.getY() >= clip.getY()) && (position.getY() <= clip.getY() + clip.getHeight());
if (!isInClip) return toContinue;
if (myPrintSettings.WRAP) {
double w = getTextSegmentWidth(text, myOffset, end - myOffset, position.getX(), g);
if (position.getX() + w > clip.getWidth()) {
IntArrayList breakOffsets = LineWrapper.calcBreakOffsets(text, myOffset, end, lineStart, position.getX(), clip.getWidth(),
(t, start, count, x) -> getTextSegmentWidth(t, start, count, x, g));
for (int i = 0; i < breakOffsets.size(); i++) {
int breakOffset = breakOffsets.get(i);
drawTabbedString(g, text, breakOffset - myOffset, position, backColor, underscoredColor);
position.setLocation(0, position.getY() + getLineHeight(g));
if (position.getY() > clip.getY() + clip.getHeight() - getLineHeight(g)) {
return false;
}
}
}
}
drawTabbedString(g, text, end - myOffset, position, backColor, underscoredColor);
return toContinue;
}
private void drawTabbedString(final Graphics2D g, char[] text, int length, Point2D position, Color backColor, Color underscoredColor) {
ProgressManager.checkCanceled();
if (length <= 0) return;
double xStart = position.getX();
double x = position.getX();
double y = getLineHeight(g) - getDescent(g) + position.getY();
if (backColor != null && myPerformActualDrawing) {
Color savedColor = g.getColor();
setBackgroundColor(g, backColor);
double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g);
g.fill(new Area(new Rectangle2D.Double(position.getX(),
y - getLineHeight(g) + getDescent(g),
w,
getLineHeight(g))));
g.setColor(savedColor);
}
int start = myOffset;
for (int i = myOffset; i < myOffset + length; i++) {
if (text[i] != '\t')
continue;
if (i > start) {
String s = new String(text, start, i - start);
x += drawStringToGraphics(g, s, x, y);
}
x = nextTabStop(g, x);
start = i + 1;
}
if (myOffset + length > start) {
String s = new String(text, start, myOffset + length - start);
x += drawStringToGraphics(g, s, x, y);
}
if (underscoredColor != null && myPerformActualDrawing) {
Color savedColor = g.getColor();
setForegroundColor(g, underscoredColor);
double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g);
UIUtil.drawLine(g, (int)position.getX(), (int)y + 1, (int)(xStart + w), (int)(y + 1));
g.setColor(savedColor);
}
position.setLocation(x, position.getY());
myOffset += length;
}
private double drawStringToGraphics(Graphics2D g, String s, double x, double y) {
if (!myPrintSettings.PRINT_AS_GRAPHICS) {
if (myPerformActualDrawing) {
g.drawString(s, (float)x, (float)y);
}
return g.getFontMetrics().stringWidth(s);
} else {
GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s);
if (myPerformActualDrawing) {
g.translate(x, y);
g.fill(v.getOutline());
g.translate(-x, -y);
}
return v.getLogicalBounds().getWidth();
}
}
private double getTextSegmentWidth(char[] text, int offset, int length, double x, Graphics2D g) {
int start = offset;
double startX = x;
for (int i = offset; i < offset + length; i++) {
if (text[i] != '\t')
continue;
if (i > start) {
x += getStringWidth(g, text, start, i - start);
}
x = nextTabStop(g, x);
start = i + 1;
}
if (offset + length > start) {
x += getStringWidth(g, text, start, offset + length - start);
}
return x - startX;
}
private static double getStringWidth(Graphics2D g, char[] text, int start, int count) {
String s = new String(text, start, count);
GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s);
return v.getLogicalBounds().getWidth();
}
public double nextTabStop(Graphics2D g, double x) {
double tabSize = myCodeStyleSettings.getTabSize(myFileType);
if (tabSize <= 0) {
tabSize = 1;
}
tabSize *= g.getFont().getStringBounds(" ", g.getFontRenderContext()).getWidth();
int nTabs = (int) (x / tabSize);
return (nTabs + 1) * tabSize;
}
@Override
void dispose() {
setSegment(null);
}
}