| /* |
| * Copyright 2000-2014 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.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.Editor; |
| 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.Computable; |
| 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.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 myFileName; |
| private int myPageIndex; |
| private int mySegmentEnd; |
| private final LineMarkerInfo[] myMethodSeparators; |
| private int myCurrentMethodSeparator; |
| private final CodeStyleSettings myCodeStyleSettings; |
| private final FileType myFileType; |
| |
| @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_FILE = "FILE"; |
| |
| public TextPainter(@NotNull DocumentEx editorDocument, |
| EditorHighlighter highlighter, |
| String fileName, |
| @NotNull PsiFile psiFile, |
| FileType fileType, |
| Editor editor) { |
| this(editorDocument, highlighter, fileName, psiFile.getProject(), fileType, |
| FileSeparatorProvider.getInstance().getFileSeparators(psiFile, editorDocument, editor)); |
| } |
| |
| public TextPainter(@NotNull DocumentEx editorDocument, |
| EditorHighlighter highlighter, |
| String fileName, |
| Project project, |
| FileType fileType, |
| List<LineMarkerInfo> separators) { |
| myCodeStyleSettings = CodeStyleSettingsManager.getSettings(project); |
| myDocument = editorDocument; |
| myPrintSettings = PrintSettings.getInstance(); |
| String fontName = myPrintSettings.FONT_NAME; |
| int fontSize = myPrintSettings.FONT_SIZE; |
| 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); |
| myFileName = fileName; |
| myRangeToPrint = editorDocument.createRangeMarker(0, myDocument.getTextLength()); |
| myFileType = fileType; |
| myMethodSeparators = separators != null ? separators.toArray(new LineMarkerInfo[separators.size()]) : new LineMarkerInfo[0]; |
| myCurrentMethodSeparator = 0; |
| } |
| |
| public void setSegment(int segmentStart, int segmentEnd) { |
| if (myRangeToPrint != null) { |
| myRangeToPrint.dispose(); |
| } |
| myRangeToPrint = myDocument.createRangeMarker(segmentStart, segmentEnd); |
| } |
| |
| 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 { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { |
| @Override |
| public Integer compute() { |
| if (myProgress.isCanceled() || myRangeToPrint == null || !myRangeToPrint.isValid()) { |
| return NO_SUCH_PAGE; |
| } |
| int startOffset = myRangeToPrint.getStartOffset(); |
| myOffset = startOffset; |
| mySegmentEnd = myRangeToPrint.getEndOffset(); |
| myLineNumber = myDocument.getLineNumber(myOffset) + 1; |
| |
| if (myOffset >= mySegmentEnd) { |
| return NO_SUCH_PAGE; |
| } |
| isPrintingPass = !isPrintingPass; |
| if (!isPrintingPass) { |
| return PAGE_EXISTS; |
| } |
| |
| myProgress.setText(CodeEditorBundle.message("print.file.page.progress", myFileName, (pageIndex + 1))); |
| myPageIndex = pageIndex; |
| Graphics2D g2D = (Graphics2D) g; |
| Rectangle2D.Double clip = new Rectangle2D.Double(pageFormat.getImageableX(), pageFormat.getImageableY(), |
| pageFormat.getImageableWidth(), |
| pageFormat.getImageableHeight()); |
| |
| draw(g2D, clip); |
| |
| myRangeToPrint.dispose(); |
| // stop printing if there was no progress (to avoid an infinite loop) or if the whole range was processed |
| myRangeToPrint = myOffset > startOffset && myOffset < mySegmentEnd ? myDocument.createRangeMarker(myOffset, mySegmentEnd) : null; |
| return PAGE_EXISTS; |
| } |
| }); |
| } |
| |
| 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) { |
| 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(); |
| |
| while (myCurrentMethodSeparator < myMethodSeparators.length) { |
| LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; |
| if (marker != null && marker.startOffset >= lIterator.getEnd()) break; |
| myCurrentMethodSeparator++; |
| } |
| |
| while (!hIterator.atEnd() && !lIterator.atEnd()) { |
| int hEnd = hIterator.getEnd(); |
| int lEnd = lIterator.getEnd(); |
| int lStart = lIterator.getStart(); |
| if (hEnd >= lEnd) { |
| if (!drawString(g, lEnd - lIterator.getSeparatorLength(), lEnd - lStart, position, clip, backColor, |
| underscoredColor)) { |
| drawLineNumber(g, 0, lineY); |
| break; |
| } |
| drawLineNumber(g, 0, lineY); |
| lIterator.advance(); |
| myLineNumber++; |
| |
| if (myCurrentMethodSeparator < myMethodSeparators.length) { |
| LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; |
| if (marker != null && marker.startOffset < lEnd) { |
| Color save = g.getColor(); |
| setForegroundColor(g, marker.separatorColor); |
| UIUtil.drawLine(g, 0, (int)lineY, (int)clip.getWidth(), (int)lineY); |
| setForegroundColor(g, save); |
| myCurrentMethodSeparator++; |
| } |
| } |
| |
| position.setLocation(0, position.getY() + lineHeight); |
| lineY = position.getY(); |
| myOffset = lEnd; |
| if (position.getY() > clip.getY() + clip.getHeight() - lineHeight) { |
| break; |
| } |
| } else { |
| if (hEnd > lEnd - lIterator.getSeparatorLength()) { |
| if (!drawString(g, lEnd - lIterator.getSeparatorLength(), lEnd - lStart, position, clip, backColor, |
| underscoredColor)) { |
| drawLineNumber(g, 0, lineY); |
| break; |
| } |
| } else { |
| if (!drawString(g, hEnd, lEnd - 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 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) { |
| headerText = convertHeaderText(headerText); |
| g.setFont(myHeaderFont); |
| g.setColor(Color.black); |
| FontRenderContext fontRenderContext = g.getFontRenderContext(); |
| LineMetrics lineMetrics = getHeaderFooterLineMetrics(g); |
| float lineHeight = lineMetrics.getHeight(); |
| 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_FILE.equals(token)) { |
| result.append(myFileName); |
| } |
| } 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) { |
| 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, int end, int colNumber, Point2D position, Rectangle2D clip, Color backColor, |
| Color underscoredColor) { |
| ProgressManager.checkCanceled(); |
| if (myOffset >= end) |
| return true; |
| char[] text = myDocument.getCharsSequence().toString().toCharArray(); //TODO: Make drawTabbedString work with CharSequence instead. |
| boolean isInClip = (getLineHeight(g) + position.getY() >= clip.getY()) && |
| (position.getY() <= clip.getY() + clip.getHeight()); |
| if (!isInClip) |
| return true; |
| return drawTabbedString(g, text, end - myOffset, position, clip, colNumber, backColor, underscoredColor); |
| } |
| |
| private boolean drawTabbedString(final Graphics2D g, char[] text, int length, Point2D position, Rectangle2D clip, |
| int colNumber, Color backColor, Color underscoredColor) { |
| boolean ret = true; |
| if (myOffset + length >= mySegmentEnd) { |
| ret = false; |
| length = mySegmentEnd - myOffset; |
| } |
| if (length <= 0) { // can happen in recursive invocations below |
| return false; |
| } |
| if (myPrintSettings.WRAP) { |
| double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g); |
| if (position.getX() + w > clip.getWidth()) { |
| IntArrayList breakOffsets = LineWrapper.calcBreakOffsets(text, myOffset, myOffset + length, colNumber, position.getX(), |
| clip.getWidth(), new LineWrapper.WidthProvider() { |
| @Override |
| public double getWidth(char[] text, int start, int count, double x) { |
| return getTextSegmentWidth(text, start, count, x, g); |
| } |
| }); |
| int startOffset = myOffset; |
| for (int i = 0; i < breakOffsets.size(); i++) { |
| int breakOffset = breakOffsets.get(i); |
| drawTabbedString(g, text, breakOffset - myOffset, position, clip, colNumber, backColor, underscoredColor); |
| position.setLocation(0, position.getY() + getLineHeight(g)); |
| if (position.getY() > clip.getY() + clip.getHeight() - getLineHeight(g)) { |
| return false; |
| } |
| } |
| drawTabbedString(g, text, startOffset + length - myOffset, position, clip, colNumber, backColor, underscoredColor); |
| return ret; |
| } |
| } |
| double xStart = position.getX(); |
| double x = position.getX(); |
| double y = getLineHeight(g) - getDescent(g) + position.getY(); |
| if (backColor != null) { |
| 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) { |
| 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; |
| return ret; |
| } |
| |
| private double drawStringToGraphics(Graphics2D g, String s, double x, double y) { |
| if (!myPrintSettings.PRINT_AS_GRAPHICS) { |
| g.drawString(s, (float) x, (float) y); |
| return g.getFontMetrics().stringWidth(s); |
| } else { |
| GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s); |
| 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() { |
| if (myRangeToPrint != null) { |
| myRangeToPrint.dispose(); |
| myRangeToPrint = null; |
| } |
| } |
| } |