5 Replies Latest reply on Nov 28, 2007 4:11 AM by darrylburke

    Using LineBreakMeasurer causes line to break badly

    843799
      Hello all

      I'm having a problem with some printing code. First I determine the min/maxs required to display some column data using <metrics>.stringWidth(). Once I figure out how much space can be in a column I go through and use LineBreakMeasurer to format each line to within the specified column space. Unfortunately in one instance the data is being broken when it shouldn't. Here are the routines...
          int breakLines(final String text, final float width, final Font font,
                            final ArrayList list)
          {
              if (0 < text.length()) {
               final String[] tokens = text.split("\\\n");
               for (int i = 0; i < tokens.length; i++) {
                final AttributedString attText = new AttributedString(tokens);
           attText.addAttribute(TextAttribute.FONT, font);
           final LineBreakMeasurer lineBreaker =
      new LineBreakMeasurer(attText.getIterator(),
                                         new FontRenderContext(null, true, true));
                     
                     TextLayout layout = lineBreaker.nextLayout(width);
                     do {
                          list.add(layout);
                          layout = lineBreaker.nextLayout(width);
                     } while (null != layout);
                }
           }
           return list.size();
      }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
        • 1. Re: Using LineBreakMeasurer causes line to break badly
          843799
          Okay - the editor messed me up. Took my partial e-mail and posted it.
          Here's the code (again).
              private void findMinMax(final int idx, final String text,
                        final FontMetrics metrics)
              {
                  double width = metrics.stringWidth(text);
                  mMax[idx] = Math.max(mMax[idx], (int)Math.ceil(X_PAD + width));
                  final String[] tokens = text.split(" ");
                  for (int i = 0; i < tokens.length; i++) {
                   width = metrics.stringWidth(tokens);
               mMin[idx] = Math.max(mMin[idx], (int)Math.ceil(X_PAD + width));
          }
          }
          int breakLines(final String text, final float width, final Font font,
                         final ArrayList list)
          {
          if (0 < text.length()) {
               final String[] tokens = text.split("\\\n");
               for (int i = 0; i < tokens.length; i++) {
               final AttributedString attText = new AttributedString(tokens[i]);
               attText.addAttribute(TextAttribute.FONT, font);
               final LineBreakMeasurer lineBreaker =
                    new LineBreakMeasurer(attText.getIterator(),
                              new FontRenderContext(null, true, true));
               TextLayout layout = lineBreaker.nextLayout(width);
          do {
               list.add(layout);
               layout = lineBreaker.nextLayout(width);
               } while (null != layout);
               }
          }
          return list.size();
          }
          The problem is when a single word is the only string in a column (like "Sticks"). The last character 's' is ending up on another line!
          
          Why? The 'width' passed in is the value determined from 'findMinMax' minus the X_PAD. The metrics passed into 'findMinMax' are metrics retrieved using the font passed into 'breakLines'.
          
          Any help appreciated.
          Lori <*>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
          • 2. Re: Using LineBreakMeasurer causes line to break badly
            darrylburke
            Disclaimer: I don't have a jdk on this office machine, so the posted code may contain any or many typos.
            Disclaimer2: All suggestions are from reading the APIs, I haven't actually worked with these classes.

            Lori, I suggest you sprinkle your code with sysouts to help see what is really happening. I've come up with a few, maybe you can think of more.
                int breakLines(final String text, final float width, final Font font,
                                  final ArrayList list)
                {
                  if (0 < text.length())
                  {
                    final String[] tokens = text.split("\\\n");
                    
                    FontMetrics metrics = new FontMetrics (font); // added this
                    
                    System.out.println ("text: " + text);
                    System.out.println ("");
                    
                    for (int i = 0; i < tokens.length; i++)
                    {
                   final AttributedString attText = new AttributedString(tokens);
            attText.addAttribute(TextAttribute.FONT, font);


                 final LineBreakMeasurer lineBreaker =
            new LineBreakMeasurer(attText.getIterator(),
            new FontRenderContext(null, true, true));
            TextLayout layout = lineBreaker.nextLayout(width);

            System.out.println ("token: " + tokens[i] ());
            System.out.println ("characters in token: " + tokens[i].length ());
            System.out.println ("characters in layout: " + layout.getCharacterCount ());
            System.out.println ("");
            System.out.println ("Width: " + width);
            System.out.println ("metric width " + metrics.stringWidth (tokens[i]));
            System.out.println ("layout width: " + layout.getBounds ().getWidth ());
            System.out.println ("");

            do
            {
            list.add(layout);
            layout = lineBreaker.nextLayout(width);
            } while (null != layout);
            }
            }
            return list.size();
            }Luck, Darryl
            • 3. Re: Using LineBreakMeasurer causes line to break badly
              darrylburke
              FontMetrics metrics = new FontMetrics (font);
              Sorry about that, like I said I don't work with these classes. That should have been
              FontMetrics metrics = getFontMetrics (getFont ());
              The problem is when a single word is the only string in a column (like "Sticks"). The last character 's' is ending up on another line!
              But not for all words! Reading the sysouts I observed that this happens only when the int metric width is within 1 pixel of the double layout width for the word. This is the SSCCE I put together for testing:
              package loricook;
              
              import java.awt.Font;
              import java.awt.FontMetrics;
              import java.awt.font.FontRenderContext;
              import java.awt.font.LineBreakMeasurer;
              import java.awt.font.TextAttribute;
              import java.awt.font.TextLayout;
              import java.text.AttributedString;
              import java.util.ArrayList;
              import javax.swing.JTextField;
              
              public class TestLineBreak extends JTextField {
                  
                  private int[] mMax = new int [5];
                  private int[] mMin = new int [5];
                  private double X_PAD;
                  ArrayList<TextLayout> myList = new ArrayList<TextLayout> ();
                  
                  
                  public TestLineBreak () {
                      
                      String [] mytext = {"Butter\nBottle", "Butter", "Bottle" };
                      for (int i = 0; i < mytext.length; i++) {
                          findMinMax (i, mytext , getFontMetrics (getFont ()));
              breakLines (mytext [i], mMax [i], getFont (), myList);
              }
              }


              private void findMinMax (final int idx, final String text,
              final FontMetrics metrics) {
              double width = metrics.stringWidth (text);
              mMax[idx] = Math.max (mMax[idx], (int)Math.ceil (X_PAD + width));
              final String[] tokens = text.split (" ");
              for (int i = 0; i < tokens.length; i++) {
              width = metrics.stringWidth (tokens[i]);
              mMin[idx] = Math.max (mMin[idx], (int)Math.ceil (X_PAD + width));
              }
              }


              int breakLines (final String text, final float width, final Font font,
              final ArrayList<TextLayout> list) {
              if (0 < text.length ()) {
              final String[] tokens = text.split ("\\\n");

              FontMetrics metrics = getFontMetrics (getFont ()); // added this
              System.out.println ("-------------------------------------");

              for (int i = 0; i < tokens.length; i++) {
              final AttributedString attText = new AttributedString (tokens[i]);
              attText.addAttribute (TextAttribute.FONT, font);

              final LineBreakMeasurer lineBreaker =
              new LineBreakMeasurer (attText.getIterator (),
              new FontRenderContext (null, true, true));
              TextLayout layout = lineBreaker.nextLayout (width);

              System.out.println ("token: " + tokens[i]);
              System.out.println ("width: " + width);
              System.out.println ("metric width " + metrics.stringWidth (tokens[i]));
              System.out.println ("layout width: " + layout.getBounds ().getWidth ());
              System.out.println ("");
              if (tokens [i].length () != layout.getCharacterCount ()) {
              System.out.println ("!!! Anomaly !!!");
              System.out.println ("characters in token: " + tokens[i].length ());
              System.out.println ("characters in layout: " + layout.getCharacterCount ());
              }

              do
              {
              list.add (layout);
              layout = lineBreaker.nextLayout (width);
              } while (null != layout);
              }
              }
              return list.size ();
              }


              public static void main (String[] args) {
              new TestLineBreak ();
              }

              }This was the output (comments added):
              -------------------------------------
              token: Butter
              width: 63.0
              metric width 32
              layout width: 31.30078125 // within 1 px diff

              token: Bottle
              width: 63.0
              metric width 31
              layout width: 29.30859375 // more than 1 px diff

              -------------------------------------
              token: Butter
              width: 32.0
              metric width 32
              layout width: 26.642578125

              !!! Anomaly !!!
              characters in token: 6
              characters in layout: 5
              -------------------------------------
              token: Bottle
              width: 31.0
              metric width 31
              layout width: 29.30859375
              And this is a workaround, if 1 pixel deviation is acceptable:
                              //TextLayout layout = lineBreaker.nextLayout (width);
                              TextLayout layout = lineBreaker.nextLayout (width + 1);
              My guess is that the problem lies in the mixed integer and floating point math used in these classes, and I shall look into the sources of LineBreakMeasurer, TextLayout and FontMetrics when I'm not too busy doing something else.

              It would be nice to know whether the suggested workaround solves your problem!

              ciao, Darryl
              • 4. Re: Using LineBreakMeasurer causes line to break badly
                843799
                Yep, that work around fixed the 'problem'. Frustrating that it wasn't working though :-)

                Thanks, Darryl!

                Lori <>
                • 5. Re: Using LineBreakMeasurer causes line to break badly
                  darrylburke
                  You're welcome. It was a good exercise for me too, getting to know about these classes.

                  Sysouts can often -- I might say usually -- pinpoint the cause of a problem. Use them well.

                  Luck, Darryl