This fixes a couple of layout issues with the HTML renderer, effectively improving the ability to wrap paragraphs correctly:
http://kennke.org/~roman/planet4.png http://kennke.org/~roman/japi3.png It also contains performance fixes in Utilities, so rendering should be a little snappier now. 2006-11-16 Roman Kennke <[EMAIL PROTECTED]> * javax/swing/text/FlowView.java (LogicalView.getPreferredSpan): Calculate maximum correctly. * javax/swing/text/GlyphView.java (tabExpander): New field. (tabX): New field. (breakView): Set tabX on broken view. (getPartialSpan): Let the painter fetch the span. (getTabbedSpan): Update the tab expander field. Maybe trigger relayout. (getTabExpander): Simply return the stored expander. * javax/swing/text/Utilities.java (getTabbedTextOffset): Made algoritm a little smarter and more efficient. (getTabbedTextWidth): Don't add single char widths, instead add chunks of characters. * javax/swing/text/html/ParagraphView.java (calculateMinorAxisRequirements): Adjust margin only when the CSS span is not fixed. /Roman
Index: javax/swing/text/FlowView.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/FlowView.java,v retrieving revision 1.19 diff -u -1 -5 -r1.19 FlowView.java --- javax/swing/text/FlowView.java 6 Nov 2006 16:02:54 -0000 1.19 +++ javax/swing/text/FlowView.java 16 Nov 2006 12:59:46 -0000 @@ -476,31 +476,31 @@ return false; } public float getPreferredSpan(int axis) { float max = 0; float pref = 0; int n = getViewCount(); for (int i = 0; i < n; i++) { View v = getView(i); pref += v.getPreferredSpan(axis); if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) { - max = Math.max(pref, pref); + max = Math.max(max, pref); pref = 0; } } max = Math.max(max, pref); return max; } public float getMinimumSpan(int axis) { float max = 0; float min = 0; boolean wrap = true; int n = getViewCount(); for (int i = 0; i < n; i++) { Index: javax/swing/text/GlyphView.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/GlyphView.java,v retrieving revision 1.24 diff -u -1 -5 -r1.24 GlyphView.java --- javax/swing/text/GlyphView.java 6 Nov 2006 16:02:54 -0000 1.24 +++ javax/swing/text/GlyphView.java 16 Nov 2006 12:59:47 -0000 @@ -485,30 +485,40 @@ * The GlyphPainer used for painting the glyphs. */ GlyphPainter glyphPainter; /** * The start offset within the document for this view. */ private int offset; /** * The end offset within the document for this view. */ private int length; /** + * The x location against which the tab expansion is done. + */ + private float tabX; + + /** + * The tab expander that is used in this view. + */ + private TabExpander tabExpander; + + /** * Creates a new <code>GlyphView</code> for the given <code>Element</code>. * * @param element the element that is rendered by this GlyphView */ public GlyphView(Element element) { super(element); offset = 0; length = 0; } /** * Returns the <code>GlyphPainter</code> that is used by this * <code>GlyphView</code>. If no <code>GlyphPainer</code> has been installed * <code>null</code> is returned. @@ -646,82 +656,69 @@ */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { checkPainter(); GlyphPainter painter = getGlyphPainter(); return painter.viewToModel(this, x, y, a, b); } /** * Return the [EMAIL PROTECTED] TabExpander} to use. * * @return the [EMAIL PROTECTED] TabExpander} to use */ public TabExpander getTabExpander() { - TabExpander te = null; - View parent = getParent(); - - if (parent instanceof TabExpander) - te = (TabExpander) parent; - - return te; + return tabExpander; } /** * Returns the preferred span of this view for tab expansion. * * @param x the location of the view * @param te the tab expander to use * * @return the preferred span of this view for tab expansion */ public float getTabbedSpan(float x, TabExpander te) { checkPainter(); + TabExpander old = tabExpander; + tabExpander = te; + if (tabExpander != old) + { + // Changing the tab expander will lead to a relayout in the X_AXIS. + preferenceChanged(null, true, false); + } + tabX = x; return getGlyphPainter().getSpan(this, getStartOffset(), - getEndOffset(), te, x); + getEndOffset(), tabExpander, x); } /** * Returns the span of a portion of the view. This is used in TAB expansion * for fragments that don't contain TABs. * * @param p0 the start index * @param p1 the end index * * @return the span of the specified portion of the view */ public float getPartialSpan(int p0, int p1) { - Element el = getElement(); - Document doc = el.getDocument(); - Segment seg = new Segment(); - try - { - doc.getText(p0, p1 - p0, seg); - } - catch (BadLocationException ex) - { - AssertionError ae; - ae = new AssertionError("BadLocationException must not be thrown " - + "here"); - ae.initCause(ex); - throw ae; - } - FontMetrics fm = null; // Fetch font metrics somewhere. - return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0); + checkPainter(); + return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX); } /** * Returns the start offset in the document model of the portion * of text that this view is responsible for. * * @return the start offset in the document model of the portion * of text that this view is responsible for */ public int getStartOffset() { Element el = getElement(); int offs = el.getStartOffset(); if (length > 0) offs += offset; @@ -926,30 +923,32 @@ * possible */ public View breakView(int axis, int p0, float pos, float len) { View brokenView = this; if (axis == X_AXIS) { checkPainter(); int end = glyphPainter.getBoundedPosition(this, p0, pos, len); int breakLoc = getBreakLocation(p0, end); if (breakLoc != -1) end = breakLoc; if (p0 != getStartOffset() || end != getEndOffset()) { brokenView = createFragment(p0, end); + if (brokenView instanceof GlyphView) + ((GlyphView) brokenView).tabX = pos; } } return brokenView; } /** * Determines how well the specified view location is suitable for inserting * a line break. If <code>axis</code> is <code>View.Y_AXIS</code>, then * this method forwards to the superclass, if <code>axis</code> is * <code>View.X_AXIS</code> then this method returns * [EMAIL PROTECTED] View#ExcellentBreakWeight} if there is a suitable break location * (usually whitespace) within the specified view span, or * [EMAIL PROTECTED] View#GoodBreakWeight} if not. * * @param axis the axis along which the break weight is requested Index: javax/swing/text/Utilities.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/Utilities.java,v retrieving revision 1.39 diff -u -1 -5 -r1.39 Utilities.java --- javax/swing/text/Utilities.java 2 Nov 2006 11:20:21 -0000 1.39 +++ javax/swing/text/Utilities.java 16 Nov 2006 12:59:47 -0000 @@ -151,61 +151,60 @@ * into account. */ public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x, TabExpander e, int startOffset) { // This buffers the chars to be drawn. char[] buffer = s.array; // The current x coordinate. int pixelX = x; // The current maximum width. int maxWidth = 0; - for (int offset = s.offset; offset < (s.offset + s.count); ++offset) + int end = s.offset + s.count; + int count = 0; + for (int offset = s.offset; offset < end; offset++) { switch (buffer[offset]) { case '\t': // In case we have a tab, we just 'jump' over the tab. // When we have no tab expander we just use the width of 'm'. if (e != null) pixelX = (int) e.nextTabStop(pixelX, startOffset + offset - s.offset); else pixelX += metrics.charWidth(' '); break; case '\n': // In case we have a newline, we must 'draw' // the buffer and jump on the next line. - pixelX += metrics.charWidth(buffer[offset]); - maxWidth = Math.max(maxWidth, pixelX - x); - pixelX = x; - break; - default: - // Here we draw the char. - pixelX += metrics.charWidth(buffer[offset]); - break; - } + pixelX += metrics.charsWidth(buffer, offset - count, count); + count = 0; + break; + default: + count++; + } } // Take the last line into account. - maxWidth = Math.max(maxWidth, pixelX - x); + pixelX += metrics.charsWidth(buffer, end - count, count); - return maxWidth; + return pixelX - x; } /** * Provides a facility to map screen coordinates into a model location. For a * given text fragment and start location within this fragment, this method * determines the model location so that the resulting fragment fits best * into the span <code>[x0, x]</code>. * * The parameter <code>round</code> controls which model location is returned * if the view coordinates are on a character: If <code>round</code> is * <code>true</code>, then the result is rounded up to the next character, so * that the resulting fragment is the smallest fragment that is larger than * the specified span. If <code>round</code> is <code>false</code>, then the * resulting fragment is the largest fragment that is smaller than the * specified span. @@ -216,67 +215,65 @@ * @param x the target screen location at which the requested fragment should * end * @param te the tab expander to use; if this is <code>null</code>, TABs are * expanded to one space character * @param p0 the starting model location * @param round if <code>true</code> round up to the next location, otherwise * round down to the current location * * @return the model location, so that the resulting fragment fits within the * specified span */ public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0, int x, TabExpander te, int p0, boolean round) { - // At the end of the for loop, this holds the requested model location - int pos; + int found = s.count; int currentX = x0; - int width = 0; + int nextX = currentX; - for (pos = 0; pos < s.count; pos++) + int end = s.offset + s.count; + for (int pos = s.offset; pos < end && found == s.count; pos++) { - char nextChar = s.array[s.offset+pos]; - - if (nextChar == 0) - break; + char nextChar = s.array[pos]; if (nextChar != '\t') - width = fm.charWidth(nextChar); + nextX += fm.charWidth(nextChar); else { if (te == null) - width = fm.charWidth(' '); + nextX += fm.charWidth(' '); else - width = ((int) te.nextTabStop(currentX, pos)) - currentX; + nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset)); } - if (round) + if (x >= currentX && x < nextX) { - if (currentX + (width>>1) > x) - break; - } - else - { - if (currentX + width > x) - break; + // Found position. + if ((! round) || ((x - currentX) < (nextX - x))) + { + found = pos - s.offset; + } + else + { + found = pos + 1 - s.offset; + } } - - currentX += width; + currentX = nextX; } - return pos; + return found; } /** * Provides a facility to map screen coordinates into a model location. For a * given text fragment and start location within this fragment, this method * determines the model location so that the resulting fragment fits best * into the span <code>[x0, x]</code>. * * This method rounds up to the next location, so that the resulting fragment * will be the smallest fragment of the text, that is greater than the * specified span. * * @param s the text segment * @param fm the font metrics to use * @param x0 the starting screen location Index: javax/swing/text/html/ParagraphView.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/text/html/ParagraphView.java,v retrieving revision 1.4 diff -u -1 -5 -r1.4 ParagraphView.java --- javax/swing/text/html/ParagraphView.java 5 Nov 2006 20:23:12 -0000 1.4 +++ javax/swing/text/html/ParagraphView.java 16 Nov 2006 12:59:47 -0000 @@ -175,52 +175,38 @@ /** * Calculates the minor axis requirements of this view. This is implemented * to return the super class'es requirements and modifies the minimumSpan * slightly so that it is not smaller than the length of the longest word. * * @param axis the axis * @param r the SizeRequirements object to be used as return parameter; * if <code>null</code> a new one will be created * * @return the requirements along the minor layout axis */ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { r = super.calculateMinorAxisRequirements(axis, r); - if (setCSSSpan(r, axis)) + if (! setCSSSpan(r, axis)) { - // If we have set the span from CSS, then we need to adjust - // the margins. - SizeRequirements parent = super.calculateMinorAxisRequirements(axis, - null); int margin = axis == X_AXIS ? getLeftInset() + getRightInset() : getTopInset() + getBottomInset(); r.minimum -= margin; r.preferred -= margin; r.maximum -= margin; } - else - { - float min = 0; - int n = getLayoutViewCount(); - for (int i = 0; i < n; i++) - min = Math.max(getLayoutView(i).getMinimumSpan(axis), min); - r.minimum = (int) min; - r.preferred = Math.max(r.preferred, r.minimum); - r.maximum = Math.max(r.maximum, r.preferred); - } return r; } /** * Sets the span on the SizeRequirements object according to the * according CSS span value, when it is set. * * @param r the size requirements * @param axis the axis * * @return <code>true</code> when the CSS span has been set, * <code>false</code> otherwise */ private boolean setCSSSpan(SizeRequirements r, int axis) {