2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeEditor.printing;
18 import com.intellij.codeInsight.daemon.LineMarkerInfo;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.editor.Editor;
21 import com.intellij.openapi.editor.RangeMarker;
22 import com.intellij.openapi.editor.ex.DocumentEx;
23 import com.intellij.openapi.editor.ex.LineIterator;
24 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
25 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
26 import com.intellij.openapi.editor.markup.TextAttributes;
27 import com.intellij.openapi.fileTypes.FileType;
28 import com.intellij.openapi.progress.ProgressManager;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Computable;
31 import com.intellij.openapi.util.Ref;
32 import com.intellij.psi.PsiFile;
33 import com.intellij.psi.codeStyle.CodeStyleSettings;
34 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
35 import com.intellij.util.containers.IntArrayList;
36 import com.intellij.util.ui.UIUtil;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
41 import java.awt.font.FontRenderContext;
42 import java.awt.font.GlyphVector;
43 import java.awt.font.LineMetrics;
44 import java.awt.geom.Area;
45 import java.awt.geom.Point2D;
46 import java.awt.geom.Rectangle2D;
47 import java.awt.print.PageFormat;
48 import java.awt.print.PrinterException;
49 import java.text.SimpleDateFormat;
50 import java.util.Date;
51 import java.util.List;
53 class TextPainter extends BasePainter {
54 private final DocumentEx myDocument;
55 private RangeMarker myRangeToPrint;
56 private int myOffset = 0;
57 private int myLineNumber = 1;
58 private float myLineHeight = -1;
59 private float myDescent = -1;
60 private double myCharWidth = -1;
61 private final Font myPlainFont;
62 private final Font myBoldFont;
63 private final Font myItalicFont;
64 private final Font myBoldItalicFont;
65 private final Font myHeaderFont;
66 private final EditorHighlighter myHighlighter;
67 private final PrintSettings myPrintSettings;
68 private final String myFullFileName;
69 private final String myShortFileName;
70 private int myPageIndex;
71 private int myNumberOfPages = -1;
72 private int mySegmentEnd;
73 private final LineMarkerInfo[] myMethodSeparators;
74 private int myCurrentMethodSeparator;
75 private final CodeStyleSettings myCodeStyleSettings;
76 private final FileType myFileType;
77 private boolean myPerformActualDrawing;
79 private final String myPrintDate;
80 private final String myPrintTime;
82 @NonNls private static final String DEFAULT_MEASURE_HEIGHT_TEXT = "A";
83 @NonNls private static final String DEFAULT_MEASURE_WIDTH_TEXT = "w";
85 @NonNls private static final String HEADER_TOKEN_PAGE = "PAGE";
86 @NonNls private static final String HEADER_TOKEN_TOTALPAGES = "TOTALPAGES";
87 @NonNls private static final String HEADER_TOKEN_FILE = "FILE";
88 @NonNls private static final String HEADER_TOKEN_FILENAME = "FILENAME";
89 @NonNls private static final String HEADER_TOKEN_DATE = "DATE";
90 @NonNls private static final String HEADER_TOKEN_TIME = "TIME";
92 @NonNls private static final String DATE_FORMAT = "yyyy-MM-dd";
93 @NonNls private static final String TIME_FORMAT = "HH:mm:ss";
95 public TextPainter(@NotNull DocumentEx editorDocument,
96 EditorHighlighter highlighter,
99 @NotNull PsiFile psiFile,
102 this(editorDocument, highlighter, fullFileName, shortFileName, psiFile.getProject(), fileType,
103 FileSeparatorProvider.getInstance().getFileSeparators(psiFile, editorDocument, editor));
106 public TextPainter(@NotNull DocumentEx editorDocument,
107 EditorHighlighter highlighter,
109 String shortFileName,
112 List<LineMarkerInfo> separators) {
113 myCodeStyleSettings = CodeStyleSettingsManager.getSettings(project);
114 myDocument = editorDocument;
115 myPrintSettings = PrintSettings.getInstance();
116 String fontName = myPrintSettings.FONT_NAME;
117 int fontSize = myPrintSettings.FONT_SIZE;
118 myPlainFont = new Font(fontName, Font.PLAIN, fontSize);
119 myBoldFont = new Font(fontName, Font.BOLD, fontSize);
120 myItalicFont = new Font(fontName, Font.ITALIC, fontSize);
121 myBoldItalicFont = new Font(fontName, Font.BOLD | Font.ITALIC, fontSize);
122 myHighlighter = highlighter;
123 myHeaderFont = new Font(myPrintSettings.FOOTER_HEADER_FONT_NAME, Font.PLAIN, myPrintSettings.FOOTER_HEADER_FONT_SIZE);
124 myFullFileName = fullFileName;
125 myShortFileName = shortFileName;
126 myRangeToPrint = editorDocument.createRangeMarker(0, myDocument.getTextLength());
127 myFileType = fileType;
128 myMethodSeparators = separators != null ? separators.toArray(new LineMarkerInfo[separators.size()]) : new LineMarkerInfo[0];
129 myCurrentMethodSeparator = 0;
130 Date date = new Date();
131 myPrintDate = new SimpleDateFormat(DATE_FORMAT).format(date);
132 myPrintTime = new SimpleDateFormat(TIME_FORMAT).format(date);
135 public void setSegment(int segmentStart, int segmentEnd) {
136 setSegment(myDocument.createRangeMarker(segmentStart, segmentEnd));
139 private void setSegment(RangeMarker marker) {
140 if (myRangeToPrint != null) {
141 myRangeToPrint.dispose();
143 myRangeToPrint = marker;
146 private float getLineHeight(Graphics g) {
147 if (myLineHeight >= 0) {
150 FontRenderContext fontRenderContext = ((Graphics2D) g).getFontRenderContext();
151 LineMetrics lineMetrics = myPlainFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
152 myLineHeight = lineMetrics.getHeight();
156 private float getDescent(Graphics g) {
157 if (myDescent >= 0) {
160 FontRenderContext fontRenderContext = ((Graphics2D) g).getFontRenderContext();
161 LineMetrics lineMetrics = myPlainFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
162 myDescent = lineMetrics.getDescent();
166 private Font getFont(int type) {
167 if (type == Font.BOLD)
169 else if (type == Font.ITALIC)
171 else if (type == Font.ITALIC + Font.BOLD)
172 return myBoldItalicFont;
177 boolean isPrintingPass = true;
180 public int print(final Graphics g, final PageFormat pageFormat, final int pageIndex) throws PrinterException {
181 if (myProgress.isCanceled()) {
185 final Graphics2D g2d = (Graphics2D)g;
187 if (myNumberOfPages < 0) {
188 myProgress.setText(CodeEditorBundle.message("print.file.calculating.number.of.pages.progress"));
190 myPerformActualDrawing = false;
192 if (!calculateNumberOfPages(g2d, pageFormat)) {
197 myPerformActualDrawing = true;
199 return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
201 public Integer compute() {
202 if (!isValidRange(myRangeToPrint)) {
206 isPrintingPass = !isPrintingPass;
207 if (!isPrintingPass) {
211 myProgress.setText(CodeEditorBundle.message("print.file.page.progress", myShortFileName, (pageIndex + 1), myNumberOfPages));
212 myPageIndex = pageIndex;
214 RangeMarker newRange = printPage(g2d, pageFormat, myRangeToPrint);
215 setSegment(newRange);
222 private boolean calculateNumberOfPages(final Graphics2D g2d, final PageFormat pageFormat) {
224 final Ref<Boolean> firstPage = new Ref<Boolean>(Boolean.TRUE);
225 final Ref<RangeMarker> tmpMarker = new Ref<RangeMarker>();
226 while (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
228 public Boolean compute() {
229 if (firstPage.get()) {
230 if (!isValidRange(myRangeToPrint)) {
233 tmpMarker.set(myDocument.createRangeMarker(myRangeToPrint.getStartOffset(), myRangeToPrint.getEndOffset()));
234 firstPage.set(Boolean.FALSE);
236 RangeMarker range = tmpMarker.get();
237 if (!isValidRange(range)) {
240 tmpMarker.set(printPage(g2d, pageFormat, range));
245 if (myProgress.isCanceled()) {
250 if (!tmpMarker.isNull()) {
251 tmpMarker.get().dispose();
256 private static boolean isValidRange(RangeMarker range) {
257 return range != null && range.isValid() && range.getStartOffset() < range.getEndOffset();
261 * Prints a pageful of text from a given range. Return a remaining range to print, or null if there's nothing left.
263 private RangeMarker printPage(Graphics2D g2d, PageFormat pageFormat, RangeMarker range) {
264 assert isValidRange(range);
265 int startOffset = range.getStartOffset();
266 int endOffset = range.getEndOffset();
268 myOffset = startOffset;
269 mySegmentEnd = endOffset;
270 myLineNumber = myDocument.getLineNumber(myOffset) + 1;
271 Rectangle2D.Double clip = new Rectangle2D.Double(pageFormat.getImageableX(), pageFormat.getImageableY(),
272 pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
276 return myOffset > startOffset && myOffset < endOffset ? myDocument.createRangeMarker(myOffset, endOffset) : null;
279 private void draw(Graphics2D g2D, Rectangle2D.Double clip) {
280 double headerHeight = drawHeader(g2D, clip);
281 clip.y += headerHeight;
282 clip.height -= headerHeight;
283 double footerHeight = drawFooter(g2D, clip);
284 clip.height -= footerHeight;
286 Rectangle2D.Double border = (Rectangle2D.Double) clip.clone();
287 clip.x += getCharWidth(g2D) / 2;
288 clip.width -= getCharWidth(g2D);
289 if (myPrintSettings.PRINT_LINE_NUMBERS) {
290 double numbersStripWidth = calcNumbersStripWidth(g2D, clip) + getCharWidth(g2D) / 2;
291 clip.x += numbersStripWidth;
292 clip.width -= numbersStripWidth;
294 clip.x += getCharWidth(g2D) / 2;
295 clip.width -= getCharWidth(g2D);
297 drawBorder(g2D, border);
300 private void drawBorder(Graphics2D g, Rectangle2D clip) {
301 if (myPrintSettings.DRAW_BORDER && myPerformActualDrawing) {
302 Color save = g.getColor();
303 g.setColor(Color.black);
309 private double getCharWidth(Graphics2D g) {
310 if (myCharWidth < 0) {
311 FontRenderContext fontRenderContext = (g).getFontRenderContext();
312 myCharWidth = myPlainFont.getStringBounds(DEFAULT_MEASURE_WIDTH_TEXT, fontRenderContext).getWidth();
317 private void setForegroundColor(Graphics2D g, Color color) {
318 if (color == null || !myPrintSettings.COLOR_PRINTING || !myPrintSettings.SYNTAX_PRINTING) {
324 private void setBackgroundColor(Graphics2D g, Color color) {
325 if (color == null || !myPrintSettings.COLOR_PRINTING || !myPrintSettings.SYNTAX_PRINTING) {
331 private void setFont(Graphics2D g, Font font) {
332 if (!myPrintSettings.SYNTAX_PRINTING) {
338 private void drawText(Graphics2D g, Rectangle2D clip) {
339 float lineHeight = getLineHeight(g);
340 HighlighterIterator hIterator = myHighlighter.createIterator(myOffset);
341 if (hIterator.atEnd()) {
342 myOffset = mySegmentEnd;
345 LineIterator lIterator = myDocument.createLineIterator();
346 lIterator.start(myOffset);
347 if (lIterator.atEnd()) {
348 myOffset = mySegmentEnd;
351 TextAttributes attributes = hIterator.getTextAttributes();
352 Color currentColor = attributes.getForegroundColor();
353 Color backColor = attributes.getBackgroundColor();
354 Color underscoredColor = attributes.getEffectColor();
355 Font currentFont = getFont(attributes.getFontType());
356 setForegroundColor(g, currentColor);
357 setFont(g, currentFont);
358 g.translate(clip.getX(), 0);
359 Point2D position = new Point2D.Double(0, clip.getY());
360 double lineY = position.getY();
362 if (myPerformActualDrawing) {
363 setInitialMethodSeparatorIndex(lIterator.getEnd());
366 while (!hIterator.atEnd() && !lIterator.atEnd()) {
367 int hEnd = hIterator.getEnd();
368 int lEnd = lIterator.getEnd();
369 int lStart = lIterator.getStart();
371 if (!drawString(g, lEnd - lIterator.getSeparatorLength(), lEnd - lStart, position, clip, backColor,
373 drawLineNumber(g, 0, lineY);
376 drawLineNumber(g, 0, lineY);
380 if (myPerformActualDrawing) {
381 LineMarkerInfo marker = getMethodSeparator(lEnd);
382 if (marker != null) {
383 Color save = g.getColor();
384 setForegroundColor(g, marker.separatorColor);
385 UIUtil.drawLine(g, 0, (int)lineY, (int)clip.getWidth(), (int)lineY);
386 setForegroundColor(g, save);
390 position.setLocation(0, position.getY() + lineHeight);
391 lineY = position.getY();
393 if (position.getY() > clip.getY() + clip.getHeight() - lineHeight) {
397 if (hEnd > lEnd - lIterator.getSeparatorLength()) {
398 if (!drawString(g, lEnd - lIterator.getSeparatorLength(), lEnd - lStart, position, clip, backColor,
400 drawLineNumber(g, 0, lineY);
404 if (!drawString(g, hEnd, lEnd - lStart, position, clip, backColor, underscoredColor)) {
405 drawLineNumber(g, 0, lineY);
410 attributes = hIterator.getTextAttributes();
411 Color color = attributes.getForegroundColor();
415 if (color != currentColor) {
416 setForegroundColor(g, color);
417 currentColor = color;
419 backColor = attributes.getBackgroundColor();
420 underscoredColor = attributes.getEffectColor();
421 Font font = getFont(attributes.getFontType());
422 if (font != currentFont) {
430 g.translate(-clip.getX(), 0);
433 private void setInitialMethodSeparatorIndex(int initialOffset) {
434 while (myCurrentMethodSeparator < myMethodSeparators.length) {
435 LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
436 if (marker != null && marker.startOffset >= initialOffset) break;
437 myCurrentMethodSeparator++;
441 private LineMarkerInfo getMethodSeparator(int currentOffset) {
442 if (myCurrentMethodSeparator < myMethodSeparators.length) {
443 LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
444 if (marker != null && marker.startOffset < currentOffset) {
445 myCurrentMethodSeparator++;
452 private double drawHeader(Graphics2D g, Rectangle2D clip) {
453 LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
454 double w = clip.getWidth();
455 double x = clip.getX();
456 double y = clip.getY();
458 boolean wasDrawn = false;
460 String headerText1 = myPrintSettings.FOOTER_HEADER_TEXT1;
461 if (headerText1 != null && headerText1.length() > 0 &&
462 PrintSettings.HEADER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT1)) {
463 h = drawHeaderOrFooterLine(g, x, y, w, headerText1, myPrintSettings.FOOTER_HEADER_ALIGNMENT1);
468 String headerText2 = myPrintSettings.FOOTER_HEADER_TEXT2;
469 if (headerText2 != null && headerText2.length() > 0 &&
470 PrintSettings.HEADER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT2)) {
471 if (PrintSettings.LEFT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT1) &&
472 PrintSettings.RIGHT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT2) &&
476 h = drawHeaderOrFooterLine(g, x, y, w, headerText2, myPrintSettings.FOOTER_HEADER_ALIGNMENT2);
480 return wasDrawn ? y - clip.getY() + lineMetrics.getHeight() / 3 : 0;
483 private double drawFooter(Graphics2D g, Rectangle2D clip) {
484 LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
485 double w = clip.getWidth();
486 double x = clip.getX();
487 double y = clip.getY() + clip.getHeight();
488 boolean wasDrawn = false;
490 y -= lineMetrics.getHeight();
491 String headerText2 = myPrintSettings.FOOTER_HEADER_TEXT2;
492 if (headerText2 != null && headerText2.length() > 0 &&
493 PrintSettings.FOOTER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT2)) {
494 h = drawHeaderOrFooterLine(g, x, y, w, headerText2, myPrintSettings.FOOTER_HEADER_ALIGNMENT2);
498 String headerText1 = myPrintSettings.FOOTER_HEADER_TEXT1;
499 if (headerText1 != null && headerText1.length() > 0 &&
500 PrintSettings.FOOTER.equals(myPrintSettings.FOOTER_HEADER_PLACEMENT1)) {
501 y -= lineMetrics.getHeight();
502 if (PrintSettings.LEFT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT1) &&
503 PrintSettings.RIGHT.equals(myPrintSettings.FOOTER_HEADER_ALIGNMENT2) &&
507 drawHeaderOrFooterLine(g, x, y, w, headerText1, myPrintSettings.FOOTER_HEADER_ALIGNMENT1);
510 return wasDrawn ? clip.getY() + clip.getHeight() - y + lineMetrics.getHeight() / 4 : 0;
513 private double drawHeaderOrFooterLine(Graphics2D g, double x, double y, double w, String headerText,
515 FontRenderContext fontRenderContext = g.getFontRenderContext();
516 LineMetrics lineMetrics = getHeaderFooterLineMetrics(g);
517 float lineHeight = lineMetrics.getHeight();
518 if (myPerformActualDrawing) {
519 headerText = convertHeaderText(headerText);
520 g.setFont(myHeaderFont);
521 g.setColor(Color.black);
522 float descent = lineMetrics.getDescent();
523 double width = myHeaderFont.getStringBounds(headerText, fontRenderContext).getWidth() + getCharWidth(g);
524 float yPos = (float) (lineHeight - descent + y);
525 if (PrintSettings.LEFT.equals(alignment)) {
526 drawStringToGraphics(g, headerText, x, yPos);
527 } else if (PrintSettings.CENTER.equals(alignment)) {
528 drawStringToGraphics(g, headerText, (float) (x + (w - width) / 2), yPos);
529 } else if (PrintSettings.RIGHT.equals(alignment)) {
530 drawStringToGraphics(g, headerText, (float) (x + w - width), yPos);
536 private String convertHeaderText(String s) {
537 StringBuilder result = new StringBuilder("");
539 boolean isExpression = false;
540 for (int i = 0; i < s.length(); i++) {
541 char c = s.charAt(i);
543 String token = s.substring(start, i);
545 if (HEADER_TOKEN_PAGE.equals(token)) {
546 result.append(myPageIndex + 1);
547 } else if (HEADER_TOKEN_TOTALPAGES.equals(token)) {
548 result.append(myNumberOfPages);
549 } else if (HEADER_TOKEN_FILE.equals(token)) {
550 result.append(myFullFileName);
551 } else if (HEADER_TOKEN_FILENAME.equals(token)) {
552 result.append(myShortFileName);
553 } else if (HEADER_TOKEN_DATE.equals(token)) {
554 result.append(myPrintDate);
555 } else if (HEADER_TOKEN_TIME.equals(token)) {
556 result.append(myPrintTime);
559 result.append(token);
561 isExpression = !isExpression;
565 if (!isExpression && start < s.length()) {
566 result.append(s.substring(start, s.length()));
568 return result.toString();
571 private LineMetrics getHeaderFooterLineMetrics(Graphics2D g) {
572 FontRenderContext fontRenderContext = g.getFontRenderContext();
573 return myHeaderFont.getLineMetrics(DEFAULT_MEASURE_HEIGHT_TEXT, fontRenderContext);
576 private double calcNumbersStripWidth(Graphics2D g, Rectangle2D clip) {
577 if (!myPrintSettings.PRINT_LINE_NUMBERS) {
580 int maxLineNumber = myLineNumber + (int) (clip.getHeight() / getLineHeight(g));
581 FontRenderContext fontRenderContext = (g).getFontRenderContext();
582 double numbersStripWidth = 0;
583 for (int i = myLineNumber; i < maxLineNumber; i++) {
584 double width = myPlainFont.getStringBounds(String.valueOf(i), fontRenderContext).getWidth();
585 if (numbersStripWidth < width) {
586 numbersStripWidth = width;
589 return numbersStripWidth;
592 private void drawLineNumber(Graphics2D g, double x, double y) {
593 if (!myPrintSettings.PRINT_LINE_NUMBERS || !myPerformActualDrawing) {
596 FontRenderContext fontRenderContext = (g).getFontRenderContext();
597 double width = myPlainFont.getStringBounds(String.valueOf(myLineNumber), fontRenderContext).getWidth() + getCharWidth(g);
598 Color savedColor = g.getColor();
599 Font savedFont = g.getFont();
600 g.setColor(Color.black);
601 g.setFont(myPlainFont);
602 drawStringToGraphics(g, String.valueOf(myLineNumber), x - width, getLineHeight(g) - getDescent(g) + y);
603 g.setColor(savedColor);
604 g.setFont(savedFont);
607 private boolean drawString(Graphics2D g, int end, int colNumber, Point2D position, Rectangle2D clip, Color backColor,
608 Color underscoredColor) {
609 ProgressManager.checkCanceled();
612 char[] text = myDocument.getCharsSequence().toString().toCharArray(); //TODO: Make drawTabbedString work with CharSequence instead.
613 boolean isInClip = (getLineHeight(g) + position.getY() >= clip.getY()) &&
614 (position.getY() <= clip.getY() + clip.getHeight());
617 return drawTabbedString(g, text, end - myOffset, position, clip, colNumber, backColor, underscoredColor);
620 private boolean drawTabbedString(final Graphics2D g, char[] text, int length, Point2D position, Rectangle2D clip,
621 int colNumber, Color backColor, Color underscoredColor) {
623 if (myOffset + length >= mySegmentEnd) {
625 length = mySegmentEnd - myOffset;
627 if (length <= 0) { // can happen in recursive invocations below
630 if (myPrintSettings.WRAP) {
631 double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g);
632 if (position.getX() + w > clip.getWidth()) {
633 IntArrayList breakOffsets = LineWrapper.calcBreakOffsets(text, myOffset, myOffset + length, colNumber, position.getX(),
634 clip.getWidth(), new LineWrapper.WidthProvider() {
636 public double getWidth(char[] text, int start, int count, double x) {
637 return getTextSegmentWidth(text, start, count, x, g);
640 int startOffset = myOffset;
641 for (int i = 0; i < breakOffsets.size(); i++) {
642 int breakOffset = breakOffsets.get(i);
643 drawTabbedString(g, text, breakOffset - myOffset, position, clip, colNumber, backColor, underscoredColor);
644 position.setLocation(0, position.getY() + getLineHeight(g));
645 if (position.getY() > clip.getY() + clip.getHeight() - getLineHeight(g)) {
649 drawTabbedString(g, text, startOffset + length - myOffset, position, clip, colNumber, backColor, underscoredColor);
653 double xStart = position.getX();
654 double x = position.getX();
655 double y = getLineHeight(g) - getDescent(g) + position.getY();
656 if (backColor != null && myPerformActualDrawing) {
657 Color savedColor = g.getColor();
658 setBackgroundColor(g, backColor);
659 double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g);
660 g.fill(new Area(new Rectangle2D.Double(position.getX(),
661 y - getLineHeight(g) + getDescent(g),
664 g.setColor(savedColor);
667 int start = myOffset;
669 for (int i = myOffset; i < myOffset + length; i++) {
673 String s = new String(text, start, i - start);
674 x += drawStringToGraphics(g, s, x, y);
676 x = nextTabStop(g, x);
680 if (myOffset + length > start) {
681 String s = new String(text, start, myOffset + length - start);
682 x += drawStringToGraphics(g, s, x, y);
685 if (underscoredColor != null && myPerformActualDrawing) {
686 Color savedColor = g.getColor();
687 setForegroundColor(g, underscoredColor);
688 double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g);
689 UIUtil.drawLine(g, (int)position.getX(), (int)y + 1, (int)(xStart + w), (int)(y + 1));
690 g.setColor(savedColor);
692 position.setLocation(x, position.getY());
697 private double drawStringToGraphics(Graphics2D g, String s, double x, double y) {
698 if (!myPrintSettings.PRINT_AS_GRAPHICS) {
699 if (myPerformActualDrawing) {
700 g.drawString(s, (float)x, (float)y);
702 return g.getFontMetrics().stringWidth(s);
704 GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s);
705 if (myPerformActualDrawing) {
707 g.fill(v.getOutline());
711 return v.getLogicalBounds().getWidth();
714 private double getTextSegmentWidth(char[] text, int offset, int length, double x, Graphics2D g) {
718 for (int i = offset; i < offset + length; i++) {
723 x += getStringWidth(g, text, start, i - start);
725 x = nextTabStop(g, x);
729 if (offset + length > start) {
730 x += getStringWidth(g, text, start, offset + length - start);
736 private static double getStringWidth(Graphics2D g, char[] text, int start, int count) {
737 String s = new String(text, start, count);
738 GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s);
740 return v.getLogicalBounds().getWidth();
743 public double nextTabStop(Graphics2D g, double x) {
744 double tabSize = myCodeStyleSettings.getTabSize(myFileType);
749 tabSize *= g.getFont().getStringBounds(" ", g.getFontRenderContext()).getWidth();
751 int nTabs = (int) (x / tabSize);
752 return (nTabs + 1) * tabSize;