diff --git a/platform/lang-impl/src/com/intellij/codeEditor/printing/LineWrapper.java b/platform/lang-impl/src/com/intellij/codeEditor/printing/LineWrapper.java new file mode 100644 index 000000000000..b63052723820 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeEditor/printing/LineWrapper.java @@ -0,0 +1,85 @@ +/* + * 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.util.containers.IntArrayList; + +public class LineWrapper { + + public IntArrayList calcBreakOffsets(char[] text, int startOffset, int endOffset, int column, double x, double clipX, WidthProvider widthProvider) { + IntArrayList breakOffsets = new IntArrayList(); + int nextOffset = startOffset; + while (true) { + int prevOffset = nextOffset; + nextOffset = calcWordBreakOffset(text, nextOffset, endOffset, x, clipX, widthProvider); + if (nextOffset == prevOffset && column == 0) { + nextOffset = calcCharBreakOffset(text, nextOffset, endOffset, x, clipX, widthProvider); + if (nextOffset == prevOffset) { + nextOffset++; // extremal case when even one character doesn't fit into clip width + } + } + if (nextOffset >= endOffset) { + break; + } + breakOffsets.add(nextOffset); + column = 0; + x = 0; + } + return breakOffsets; + } + + private static int calcCharBreakOffset(char[] text, int offset, int endOffset, double x, double clipX, WidthProvider widthProvider) { + double newX = x; + int breakOffset = offset; + while (breakOffset < endOffset) { + int nextOffset = breakOffset + 1; + newX += widthProvider.getWidth(text, breakOffset, nextOffset - breakOffset, newX); + if (newX > clipX) { + return breakOffset; + } + breakOffset = nextOffset; + } + return breakOffset; + } + + private static int calcWordBreakOffset(char[] text, int offset, int endOffset, double x, double clipX, WidthProvider widthProvider) { + double newX = x; + int breakOffset = offset; + while (breakOffset < endOffset) { + int nextOffset = getNextWordBreak(text, breakOffset, endOffset); + newX += widthProvider.getWidth(text, breakOffset, nextOffset - breakOffset, newX); + if (newX > clipX) { + return breakOffset; + } + breakOffset = nextOffset; + } + return breakOffset; + } + + private static int getNextWordBreak(char[] text, int offset, int endOffset) { + boolean isId = Character.isJavaIdentifierPart(text[offset]); + for (int i = offset + 1; i < endOffset; i++) { + if (isId != Character.isJavaIdentifierPart(text[i])) { + return i; + } + } + return endOffset; + } + + interface WidthProvider { + double getWidth(char[] text, int start, int count, double x); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java b/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java index e46ea7b09c4d..3f208b7c4070 100644 --- a/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java +++ b/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -68,6 +68,8 @@ class TextPainter extends BasePainter { private final CodeStyleSettings myCodeStyleSettings; private final FileType myFileType; + private static final LineWrapper ourLineWrapper = new LineWrapper(); + @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"; @@ -485,20 +487,26 @@ class TextPainter extends BasePainter { return drawTabbedString(g, text, end - myOffset, position, clip, colNumber, backColor, underscoredColor); } - private boolean drawTabbedString(Graphics2D g, char[] text, int length, Point2D position, Rectangle2D clip, + 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) { // How it can be? + 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 = calcBreakOffsets(g, text, myOffset, myOffset + length, colNumber, position, clip); + IntArrayList breakOffsets = ourLineWrapper.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); @@ -508,10 +516,7 @@ class TextPainter extends BasePainter { return false; } } - if (myOffset > startOffset) { - drawTabbedString(g, text, startOffset + length - myOffset, position, clip, colNumber, backColor, - underscoredColor); - } + drawTabbedString(g, text, startOffset + length - myOffset, position, clip, colNumber, backColor, underscoredColor); return ret; } } @@ -572,69 +577,6 @@ class TextPainter extends BasePainter { return v.getLogicalBounds().getWidth(); } } - - private IntArrayList calcBreakOffsets(Graphics2D g, char[] text, int offset, int endOffset, int colNumber, - Point2D position, Rectangle2D clip) { - IntArrayList breakOffsets = new IntArrayList(); - int nextOffset = offset; - double x = position.getX(); - while (true) { - int prevOffset = nextOffset; - nextOffset = calcWordBreakOffset(g, text, nextOffset, endOffset, x, clip); - if (nextOffset == offset || nextOffset == prevOffset && colNumber == 0) { - nextOffset = calcCharBreakOffset(g, text, nextOffset, endOffset, x, clip); - if (nextOffset == prevOffset) { //it shouldn't be, but if clip.width is <= 1... - return breakOffsets; - } - } - if (nextOffset >= endOffset) { - break; - } - breakOffsets.add(nextOffset); - colNumber = 0; - x = 0; - } - return breakOffsets; - } - - private int calcCharBreakOffset(Graphics2D g, char[] text, int offset, int endOffset, double x, Rectangle2D clip) { - double newX = x; - int breakOffset = offset; - while (breakOffset < endOffset) { - int nextOffset = breakOffset + 1; - newX += getTextSegmentWidth(text, breakOffset, nextOffset - breakOffset, newX, g); - if (newX > clip.getWidth()) { - return breakOffset; - } - breakOffset = nextOffset; - } - return breakOffset; - } - - private int calcWordBreakOffset(Graphics2D g, char[] text, int offset, int endOffset, double x, Rectangle2D clip) { - double newX = x; - int breakOffset = offset; - while (breakOffset < endOffset) { - int nextOffset = getNextWordBreak(text, breakOffset, endOffset); - newX += getTextSegmentWidth(text, breakOffset, nextOffset - breakOffset, newX, g); - if (newX > clip.getWidth()) { - return breakOffset; - } - breakOffset = nextOffset; - } - return breakOffset; - } - - private static int getNextWordBreak(char[] text, int offset, int endOffset) { - boolean isId = Character.isJavaIdentifierPart(text[offset]); - for (int i = offset + 1; i < endOffset; i++) { - if (isId != Character.isJavaIdentifierPart(text[i])) { - return i; - } - } - return endOffset; - } - private double getTextSegmentWidth(char[] text, int offset, int length, double x, Graphics2D g) { int start = offset; double startX = x; diff --git a/platform/lang-impl/testSources/com/intellij/codeEditor/printing/LineWrapperTest.java b/platform/lang-impl/testSources/com/intellij/codeEditor/printing/LineWrapperTest.java new file mode 100644 index 000000000000..0dfeb7f0ad9d --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/codeEditor/printing/LineWrapperTest.java @@ -0,0 +1,75 @@ +/* + * 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.util.containers.IntArrayList; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class LineWrapperTest { + private final LineWrapper myWrapper = new LineWrapper(); + private final LineWrapper.WidthProvider myWidthProvider = new LineWrapper.WidthProvider() { + @Override + public double getWidth(char[] text, int start, int count, double x) { + return count; // char width of 1 pixel for all characters + } + }; + + @Test + public void testEmpty() { + doTest("", false, 0); + doTest("", true, 0); + doTest("", false, 1); + doTest("", true, 1); + } + + @Test + public void testSingleChar() { + doTest("a", false, 0.5, + 0); + doTest("a", true, 0.5); + } + + @Test + public void testCharSplit() { + doTest("aa", false, 1.5, + 0, 1); + doTest("aa", true, 1.5, + 1); + } + + @Test + public void testExtreme() { + doTest("aa", false, 0.5, + 0, 1); + doTest("aa", true, 0.5, + 1); + } + + @Test + public void testWordSplit() { + doTest("aa aa aa", false, 3.5, + 2, 4, 6, 8); + doTest("aa aa aa", true, 3.5, + 2, 4, 6, 8); + } + + private void doTest(String text, boolean atLineStart, double clipWidth, int... expectedBreaks) { + IntArrayList actualBreaks = myWrapper.calcBreakOffsets(text.toCharArray(), 0, text.length(), atLineStart ? 0 : 1, 0, clipWidth, myWidthProvider); + assertArrayEquals(expectedBreaks, actualBreaks.toArray()); + } +}