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)
   {

Reply via email to