1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.awt.Component;
  28 import java.awt.Rectangle;
  29 import java.awt.Graphics;
  30 import java.awt.FontMetrics;
  31 import java.awt.Shape;
  32 import java.awt.Graphics2D;
  33 import java.awt.font.TextAttribute;
  34 import java.awt.geom.Rectangle2D;
  35 
  36 import java.text.*;
  37 import javax.swing.JComponent;
  38 import javax.swing.SwingConstants;
  39 import javax.swing.text.ParagraphView.Row;
  40 import sun.swing.SwingUtilities2;
  41 import static sun.swing.SwingUtilities2.drawChars;
  42 import static sun.swing.SwingUtilities2.getFontCharWidth;
  43 import static sun.swing.SwingUtilities2.getFontCharsWidth;
  44 
  45 /**
  46  * A collection of methods to deal with various text
  47  * related activities.
  48  *
  49  * @author  Timothy Prinzing
  50  */
  51 public class Utilities {
  52     /**
  53      * If <code>view</code>'s container is a <code>JComponent</code> it
  54      * is returned, after casting.
  55      */
  56     static JComponent getJComponent(View view) {
  57         if (view != null) {
  58             Component component = view.getContainer();
  59             if (component instanceof JComponent) {
  60                 return (JComponent)component;
  61             }
  62         }
  63         return null;
  64     }
  65 
  66     /**
  67      * Draws the given text, expanding any tabs that are contained
  68      * using the given tab expansion technique.  This particular
  69      * implementation renders in a 1.1 style coordinate system
  70      * where ints are used and 72dpi is assumed.
  71      *
  72      * @param s  the source of the text
  73      * @param x  the X origin &gt;= 0
  74      * @param y  the Y origin &gt;= 0
  75      * @param g  the graphics context
  76      * @param e  how to expand the tabs.  If this value is null,
  77      *   tabs will be expanded as a space character.
  78      * @param startOffset starting offset of the text in the document &gt;= 0
  79      * @return  the X location at the end of the rendered text
  80      *
  81      * @deprecated replaced by
  82      * {@link #drawTabbedText(Segment, float, float, Graphics2D, TabExpander, int)}
  83      */
  84     @Deprecated(since = "9")
  85     public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
  86                                            TabExpander e, int startOffset) {
  87         return drawTabbedText(null, s, x, y, g, e, startOffset);
  88     }
  89 
  90     /**
  91      * Draws the given text, expanding any tabs that are contained
  92      * using the given tab expansion technique.
  93      *
  94      * @param s  the source of the text
  95      * @param x  the X origin {@code >= 0}
  96      * @param y  the Y origin {@code >= 0}
  97      * @param g  the graphics context
  98      * @param e  how to expand the tabs.  If this value is null,
  99      *           tabs will be expanded as a space character.
 100      * @param startOffset starting offset of the text in the document {@code >= 0}
 101      * @return  the X location at the end of the rendered text
 102      *
 103      * @since 9
 104      */
 105     public static final float drawTabbedText(Segment s, float x, float y,
 106                                              Graphics2D g,
 107                                              TabExpander e,
 108                                              int startOffset)
 109     {
 110         return drawTabbedText(null, s, x, y, g, e, startOffset, null, true);
 111     }
 112 
 113     /**
 114      * Draws the given text, expanding any tabs that are contained
 115      * using the given tab expansion technique.  This particular
 116      * implementation renders in a 1.1 style coordinate system
 117      * where ints are used and 72dpi is assumed.
 118      *
 119      * @param view View requesting rendering, may be null.
 120      * @param s  the source of the text
 121      * @param x  the X origin &gt;= 0
 122      * @param y  the Y origin &gt;= 0
 123      * @param g  the graphics context
 124      * @param e  how to expand the tabs.  If this value is null,
 125      *   tabs will be expanded as a space character.
 126      * @param startOffset starting offset of the text in the document &gt;= 0
 127      * @return  the X location at the end of the rendered text
 128      */
 129     static final int drawTabbedText(View view,
 130                                 Segment s, int x, int y, Graphics g,
 131                                 TabExpander e, int startOffset) {
 132         return drawTabbedText(view, s, x, y, g, e, startOffset, null);
 133     }
 134 
 135     // In addition to the previous method it can extend spaces for
 136     // justification.
 137     //
 138     // all params are the same as in the preious method except the last
 139     // one:
 140     // @param justificationData justificationData for the row.
 141     // if null not justification is needed
 142     static final int drawTabbedText(View view,
 143                                 Segment s, int x, int y, Graphics g,
 144                                 TabExpander e, int startOffset,
 145                                 int [] justificationData) {
 146         return (int) drawTabbedText(view, s, x, y, g, e, startOffset,
 147                                     justificationData, false);
 148     }
 149 
 150     static final float drawTabbedText(View view,
 151                                 Segment s, float x, float y, Graphics g,
 152                                 TabExpander e, int startOffset,
 153                                 int [] justificationData,
 154                                 boolean useFPAPI)
 155     {
 156         JComponent component = getJComponent(view);
 157         FontMetrics metrics = SwingUtilities2.getFontMetrics(component, g);
 158         float nextX = x;
 159         char[] txt = s.array;
 160         int txtOffset = s.offset;
 161         int flushLen = 0;
 162         int flushIndex = s.offset;
 163         int spaceAddon = 0;
 164         int spaceAddonLeftoverEnd = -1;
 165         int startJustifiableContent = 0;
 166         int endJustifiableContent = 0;
 167         if (justificationData != null) {
 168             int offset = - startOffset + txtOffset;
 169             View parent = null;
 170             if (view != null
 171                   && (parent = view.getParent()) != null) {
 172                 offset += parent.getStartOffset();
 173             }
 174             spaceAddon =
 175                 justificationData[Row.SPACE_ADDON];
 176             spaceAddonLeftoverEnd =
 177                 justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
 178             startJustifiableContent =
 179                 justificationData[Row.START_JUSTIFIABLE] + offset;
 180             endJustifiableContent =
 181                 justificationData[Row.END_JUSTIFIABLE] + offset;
 182         }
 183         int n = s.offset + s.count;
 184         for (int i = txtOffset; i < n; i++) {
 185             if (txt[i] == '\t'
 186                 || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
 187                     && (txt[i] == ' ')
 188                     && startJustifiableContent <= i
 189                     && i <= endJustifiableContent
 190                     )) {
 191                 if (flushLen > 0) {
 192                     nextX = drawChars(component, g, txt, flushIndex, flushLen, x, y);
 193                     flushLen = 0;
 194                 }
 195                 flushIndex = i + 1;
 196                 if (txt[i] == '\t') {
 197                     if (e != null) {
 198                         nextX = e.nextTabStop(nextX, startOffset + i - txtOffset);
 199                     } else {
 200                         nextX += getFontCharWidth(' ', metrics, useFPAPI);
 201                     }
 202                 } else if (txt[i] == ' ') {
 203                     float spaceWidth = getFontCharWidth(' ', metrics, useFPAPI);
 204                     nextX += spaceWidth + spaceAddon;
 205                     if (i <= spaceAddonLeftoverEnd) {
 206                         nextX++;
 207                     }
 208                 }
 209                 x = nextX;
 210             } else if ((txt[i] == '\n') || (txt[i] == '\r')) {
 211                 if (flushLen > 0) {
 212                     nextX = drawChars(component, g, txt, flushIndex, flushLen,
 213                                       x, y, useFPAPI);
 214                     flushLen = 0;
 215                 }
 216                 flushIndex = i + 1;
 217                 x = nextX;
 218             } else {
 219                 flushLen += 1;
 220             }
 221         }
 222         if (flushLen > 0) {
 223             nextX = drawChars(component, g,txt, flushIndex, flushLen, x, y, useFPAPI);
 224         }
 225         return nextX;
 226     }
 227 
 228     /**
 229      * Determines the width of the given segment of text taking tabs
 230      * into consideration.  This is implemented in a 1.1 style coordinate
 231      * system where ints are used and 72dpi is assumed.
 232      *
 233      * @param s  the source of the text
 234      * @param metrics the font metrics to use for the calculation
 235      * @param x  the X origin &gt;= 0
 236      * @param e  how to expand the tabs.  If this value is null,
 237      *   tabs will be expanded as a space character.
 238      * @param startOffset starting offset of the text in the document &gt;= 0
 239      * @return  the width of the text
 240      *
 241      * @deprecated replaced by
 242      *     {@link #getTabbedTextWidth(Segment, FontMetrics, float, TabExpander, int)}
 243      */
 244     @Deprecated(since = "9")
 245     public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x,
 246                                                TabExpander e, int startOffset) {
 247         return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null);
 248     }
 249 
 250     /**
 251      * Determines the width of the given segment of text taking tabs
 252      * into consideration.
 253      *
 254      * @param s  the source of the text
 255      * @param metrics the font metrics to use for the calculation
 256      * @param x  the X origin {@code >= 0}
 257      * @param e  how to expand the tabs.  If this value is null,
 258      *   tabs will be expanded as a space character.
 259      * @param startOffset starting offset of the text in the document {@code >= 0}
 260      * @return  the width of the text
 261      *
 262      * @since 9
 263      */
 264     public static final float getTabbedTextWidth(Segment s, FontMetrics metrics,
 265                                                  float x, TabExpander e,
 266                                                  int startOffset) {
 267         return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null);
 268     }
 269 
 270     // In addition to the previous method it can extend spaces for
 271     // justification.
 272     //
 273     // all params are the same as in the preious method except the last
 274     // one:
 275     // @param justificationData justificationData for the row.
 276     // if null not justification is needed
 277     static final int getTabbedTextWidth(View view, Segment s,
 278                                         FontMetrics metrics, int x,
 279                                         TabExpander e, int startOffset,
 280                                         int[] justificationData)
 281     {
 282         return (int) getTabbedTextWidth(view, s, metrics, x, e, startOffset,
 283                                         justificationData, false);
 284 
 285     }
 286 
 287     static final float getTabbedTextWidth(View view, Segment s,
 288                                         FontMetrics metrics, float x,
 289                                         TabExpander e, int startOffset,
 290                                         int[] justificationData)
 291     {
 292         return  getTabbedTextWidth(view, s, metrics, x, e, startOffset,
 293                                    justificationData, true);
 294 
 295     }
 296 
 297     static final float getTabbedTextWidth(View view, Segment s,
 298                                         FontMetrics metrics, float x,
 299                                         TabExpander e, int startOffset,
 300                                         int[] justificationData,
 301                                         boolean useFPAPI) {
 302         float nextX = x;
 303         char[] txt = s.array;
 304         int txtOffset = s.offset;
 305         int n = s.offset + s.count;
 306         int charCount = 0;
 307         int spaceAddon = 0;
 308         int spaceAddonLeftoverEnd = -1;
 309         int startJustifiableContent = 0;
 310         int endJustifiableContent = 0;
 311         if (justificationData != null) {
 312             int offset = - startOffset + txtOffset;
 313             View parent = null;
 314             if (view != null
 315                   && (parent = view.getParent()) != null) {
 316                 offset += parent.getStartOffset();
 317             }
 318             spaceAddon =
 319                 justificationData[Row.SPACE_ADDON];
 320             spaceAddonLeftoverEnd =
 321                 justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
 322             startJustifiableContent =
 323                 justificationData[Row.START_JUSTIFIABLE] + offset;
 324             endJustifiableContent =
 325                 justificationData[Row.END_JUSTIFIABLE] + offset;
 326         }
 327 
 328         for (int i = txtOffset; i < n; i++) {
 329             if (txt[i] == '\t'
 330                 || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
 331                     && (txt[i] == ' ')
 332                     && startJustifiableContent <= i
 333                     && i <= endJustifiableContent
 334                     )) {
 335                 nextX += metrics.charsWidth(txt, i-charCount, charCount);
 336                 charCount = 0;
 337                 if (txt[i] == '\t') {
 338                     if (e != null) {
 339                         nextX = e.nextTabStop(nextX, startOffset + i - txtOffset);
 340                     } else {
 341                         nextX += getFontCharWidth(' ', metrics, useFPAPI);
 342                     }
 343                 } else if (txt[i] == ' ') {
 344                     float spaceWidth = getFontCharWidth(' ', metrics, useFPAPI);
 345                     nextX += spaceWidth + spaceAddon;
 346                     if (i <= spaceAddonLeftoverEnd) {
 347                         nextX++;
 348                     }
 349                 }
 350             } else if(txt[i] == '\n') {
 351             // Ignore newlines, they take up space and we shouldn't be
 352             // counting them.
 353                 nextX += getFontCharsWidth(txt, i - charCount, charCount,
 354                                            metrics, useFPAPI);
 355                 charCount = 0;
 356             } else {
 357                 charCount++;
 358         }
 359         }
 360         nextX += getFontCharsWidth(txt, n - charCount, charCount,
 361                                    metrics, useFPAPI);
 362         return nextX - x;
 363     }
 364 
 365     /**
 366      * Determines the relative offset into the given text that
 367      * best represents the given span in the view coordinate
 368      * system.  This is implemented in a 1.1 style coordinate
 369      * system where ints are used and 72dpi is assumed.
 370      *
 371      * @param s  the source of the text
 372      * @param metrics the font metrics to use for the calculation
 373      * @param x0 the starting view location representing the start
 374      *   of the given text &gt;= 0.
 375      * @param x  the target view location to translate to an
 376      *   offset into the text &gt;= 0.
 377      * @param e  how to expand the tabs.  If this value is null,
 378      *   tabs will be expanded as a space character.
 379      * @param startOffset starting offset of the text in the document &gt;= 0
 380      * @return  the offset into the text &gt;= 0
 381      *
 382      * @deprecated replaced by
 383      *     {@link #getTabbedTextOffset(Segment, FontMetrics, float, float,
 384      *                                 TabExpander, int, boolean)}
 385      */
 386     @Deprecated(since = "9")
 387     public static final int getTabbedTextOffset(Segment s, FontMetrics metrics,
 388                                              int x0, int x, TabExpander e,
 389                                              int startOffset) {
 390         return getTabbedTextOffset(s, metrics, x0, x, e, startOffset, true);
 391     }
 392 
 393     static final int getTabbedTextOffset(View view, Segment s, FontMetrics metrics,
 394                                          int x0, int x, TabExpander e,
 395                                          int startOffset,
 396                                          int[] justificationData) {
 397         return getTabbedTextOffset(view, s, metrics, x0, x, e, startOffset, true,
 398                                    justificationData, false);
 399     }
 400 
 401     /**
 402      * Determines the relative offset into the given text that
 403      * best represents the given span in the view coordinate
 404      * system.
 405      *
 406      * @param s  the source of the text
 407      * @param metrics the font metrics to use for the calculation
 408      * @param x0 the starting view location representing the start
 409      *   of the given text &gt;= 0.
 410      * @param x  the target view location to translate to an
 411      *   offset into the text &gt;= 0.
 412      * @param e  how to expand the tabs.  If this value is null,
 413      *   tabs will be expanded as a space character.
 414      * @param startOffset starting offset of the text in the document &gt;= 0
 415      * @param round whether or not to round
 416      * @return  the offset into the text &gt;= 0
 417      *
 418      * @deprecated replaced by
 419      *     {@link #getTabbedTextOffset(Segment, FontMetrics, float, float,
 420      *                                 TabExpander, int, boolean)}
 421      */
 422     @Deprecated(since = "9")
 423     public static final int getTabbedTextOffset(Segment s,
 424                                                 FontMetrics metrics,
 425                                                 int x0, int x, TabExpander e,
 426                                                 int startOffset,
 427                                                 boolean round) {
 428         return getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset,
 429                                    round, null, false);
 430     }
 431 
 432     /**
 433      * Determines the relative offset into the given text that
 434      * best represents the given span in the view coordinate
 435      * system.
 436      *
 437      * @param s  the source of the text
 438      * @param metrics the font metrics to use for the calculation
 439      * @param x0 the starting view location representing the start
 440      *   of the given text {@code >= 0}.
 441      * @param x  the target view location to translate to an
 442      *   offset into the text {@code >= 0}.
 443      * @param e  how to expand the tabs.  If this value is null,
 444      *   tabs will be expanded as a space character.
 445      * @param startOffset starting offset of the text in the document {@code >= 0}
 446      * @param round whether or not to round
 447      * @return  the offset into the text {@code >= 0}
 448      *
 449      * @since 9
 450      */
 451     public static final int getTabbedTextOffset(Segment s,
 452                                                 FontMetrics metrics,
 453                                                 float x0, float x,
 454                                                 TabExpander e,
 455                                                 int startOffset,
 456                                                 boolean round)
 457     {
 458         return getTabbedTextOffset(null, s, metrics, x0, x, e,
 459                                    startOffset, round, null, true);
 460     }
 461 
 462     // In addition to the previous method it can extend spaces for
 463     // justification.
 464     //
 465     // all params are the same as in the preious method except the last
 466     // one:
 467     // @param justificationData justificationData for the row.
 468     // if null not justification is needed
 469     static final int getTabbedTextOffset(View view,
 470                                          Segment s,
 471                                          FontMetrics metrics,
 472                                          float x0, float x, TabExpander e,
 473                                          int startOffset,
 474                                          boolean round,
 475                                          int[] justificationData,
 476                                          boolean useFPAPI) {
 477         if (x0 >= x) {
 478             // x before x0, return.
 479             return 0;
 480         }
 481         float nextX = x0;
 482         // s may be a shared segment, so it is copied prior to calling
 483         // the tab expander
 484         char[] txt = s.array;
 485         int txtOffset = s.offset;
 486         int txtCount = s.count;
 487         int spaceAddon = 0 ;
 488         int spaceAddonLeftoverEnd = -1;
 489         int startJustifiableContent = 0 ;
 490         int endJustifiableContent = 0;
 491         if (justificationData != null) {
 492             int offset = - startOffset + txtOffset;
 493             View parent = null;
 494             if (view != null
 495                   && (parent = view.getParent()) != null) {
 496                 offset += parent.getStartOffset();
 497             }
 498             spaceAddon =
 499                 justificationData[Row.SPACE_ADDON];
 500             spaceAddonLeftoverEnd =
 501                 justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset;
 502             startJustifiableContent =
 503                 justificationData[Row.START_JUSTIFIABLE] + offset;
 504             endJustifiableContent =
 505                 justificationData[Row.END_JUSTIFIABLE] + offset;
 506         }
 507         int n = s.offset + s.count;
 508         for (int i = s.offset; i < n; i++) {
 509             if (txt[i] == '\t'
 510                 || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd)
 511                     && (txt[i] == ' ')
 512                     && startJustifiableContent <= i
 513                     && i <= endJustifiableContent
 514                     )){
 515                 if (txt[i] == '\t') {
 516                     if (e != null) {
 517                         nextX = e.nextTabStop(nextX, startOffset + i - txtOffset);
 518                     } else {
 519                         nextX += getFontCharWidth(' ', metrics, useFPAPI);
 520                     }
 521                 } else if (txt[i] == ' ') {
 522                     nextX += getFontCharWidth(' ', metrics, useFPAPI);
 523                     nextX += spaceAddon;
 524                     if (i <= spaceAddonLeftoverEnd) {
 525                         nextX++;
 526                     }
 527                 }
 528             } else {
 529                 nextX += getFontCharWidth(txt[i], metrics, useFPAPI);
 530             }
 531             if (x < nextX) {
 532                 // found the hit position... return the appropriate side
 533                 int offset;
 534 
 535                 // the length of the string measured as a whole may differ from
 536                 // the sum of individual character lengths, for example if
 537                 // fractional metrics are enabled; and we must guard from this.
 538                 if (round) {
 539                     offset = i + 1 - txtOffset;
 540 
 541                     float width = getFontCharsWidth(txt, txtOffset, offset,
 542                                                     metrics, useFPAPI);
 543                     float span = x - x0;
 544 
 545                     if (span < width) {
 546                         while (offset > 0) {
 547                             float charsWidth = getFontCharsWidth(txt, txtOffset,
 548                                     offset - 1, metrics, useFPAPI);
 549                             float nextWidth = offset > 1 ? charsWidth : 0;
 550 
 551                             if (span >= nextWidth) {
 552                                 if (span - nextWidth < width - span) {
 553                                     offset--;
 554                                 }
 555 
 556                                 break;
 557                             }
 558 
 559                             width = nextWidth;
 560                             offset--;
 561                         }
 562                     }
 563                 } else {
 564                     offset = i - txtOffset;
 565 
 566                     while (offset > 0 && getFontCharsWidth(txt, txtOffset, offset,
 567                                                            metrics, useFPAPI)
 568                             > (x - x0)) {
 569                         offset--;
 570                     }
 571                 }
 572 
 573                 return offset;
 574             }
 575         }
 576 
 577         // didn't find, return end offset
 578         return txtCount;
 579     }
 580 
 581     /**
 582      * Determine where to break the given text to fit
 583      * within the given span. This tries to find a word boundary.
 584      * @param s  the source of the text
 585      * @param metrics the font metrics to use for the calculation
 586      * @param x0 the starting view location representing the start
 587      *   of the given text.
 588      * @param x  the target view location to translate to an
 589      *   offset into the text.
 590      * @param e  how to expand the tabs.  If this value is null,
 591      *   tabs will be expanded as a space character.
 592      * @param startOffset starting offset in the document of the text
 593      * @return  the offset into the given text
 594      *
 595      * @deprecated replaced by
 596      *     {@link #getBreakLocation(Segment, FontMetrics, float, float,
 597      *                              TabExpander, int)}
 598      */
 599     @Deprecated(since = "9")
 600     public static final int getBreakLocation(Segment s, FontMetrics metrics,
 601                                              int x0, int x, TabExpander e,
 602                                              int startOffset) {
 603         return getBreakLocation(s, metrics, x0, x, e, startOffset, false);
 604     }
 605 
 606     static final int getBreakLocation(Segment s, FontMetrics metrics,
 607                                       float x0, float x, TabExpander e,
 608                                       int startOffset, boolean useFPIAPI) {
 609         char[] txt = s.array;
 610         int txtOffset = s.offset;
 611         int txtCount = s.count;
 612         int index = getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset,
 613                                         false, null, useFPIAPI);
 614 
 615         if (index >= txtCount - 1) {
 616             return txtCount;
 617         }
 618 
 619         for (int i = txtOffset + index; i >= txtOffset; i--) {
 620             char ch = txt[i];
 621             if (ch < 256) {
 622                 // break on whitespace
 623                 if (Character.isWhitespace(ch)) {
 624                     index = i - txtOffset + 1;
 625                     break;
 626                 }
 627             } else {
 628                 // a multibyte char found; use BreakIterator to find line break
 629                 BreakIterator bit = BreakIterator.getLineInstance();
 630                 bit.setText(s);
 631                 int breakPos = bit.preceding(i + 1);
 632                 if (breakPos > txtOffset) {
 633                     index = breakPos - txtOffset;
 634                 }
 635                 break;
 636             }
 637         }
 638         return index;
 639     }
 640 
 641     /**
 642      * Determine where to break the given text to fit
 643      * within the given span. This tries to find a word boundary.
 644      * @param s  the source of the text
 645      * @param metrics the font metrics to use for the calculation
 646      * @param x0 the starting view location representing the start
 647      *        of the given text.
 648      * @param x  the target view location to translate to an
 649      *        offset into the text.
 650      * @param e  how to expand the tabs.  If this value is null,
 651      *        tabs will be expanded as a space character.
 652      * @param startOffset starting offset in the document of the text
 653      * @return  the offset into the given text
 654      *
 655      * @since 9
 656      */
 657     public static final int getBreakLocation(Segment s, FontMetrics metrics,
 658                                              float x0, float x, TabExpander e,
 659                                              int startOffset) {
 660         return getBreakLocation(s, metrics, x0, x, e, startOffset, false);
 661     }
 662 
 663     /**
 664      * Determines the starting row model position of the row that contains
 665      * the specified model position.  The component given must have a
 666      * size to compute the result.  If the component doesn't have a size
 667      * a value of -1 will be returned.
 668      *
 669      * @param c the editor
 670      * @param offs the offset in the document &gt;= 0
 671      * @return the position &gt;= 0 if the request can be computed, otherwise
 672      *  a value of -1 will be returned.
 673      * @exception BadLocationException if the offset is out of range
 674      */
 675     @SuppressWarnings("deprecation")
 676     public static final int getRowStart(JTextComponent c, int offs) throws BadLocationException {
 677         Rectangle r = c.modelToView(offs);
 678         if (r == null) {
 679             return -1;
 680         }
 681         int lastOffs = offs;
 682         int y = r.y;
 683         while ((r != null) && (y == r.y)) {
 684             // Skip invisible elements
 685             if(r.height !=0) {
 686                 offs = lastOffs;
 687             }
 688             lastOffs -= 1;
 689             r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
 690         }
 691         return offs;
 692     }
 693 
 694     /**
 695      * Determines the ending row model position of the row that contains
 696      * the specified model position.  The component given must have a
 697      * size to compute the result.  If the component doesn't have a size
 698      * a value of -1 will be returned.
 699      *
 700      * @param c the editor
 701      * @param offs the offset in the document &gt;= 0
 702      * @return the position &gt;= 0 if the request can be computed, otherwise
 703      *  a value of -1 will be returned.
 704      * @exception BadLocationException if the offset is out of range
 705      */
 706     @SuppressWarnings("deprecation")
 707     public static final int getRowEnd(JTextComponent c, int offs) throws BadLocationException {
 708         Rectangle2D r = c.modelToView2D(offs);
 709         if (r == null) {
 710             return -1;
 711         }
 712         int n = c.getDocument().getLength();
 713         int lastOffs = offs;
 714         double y = r.getY();
 715         while ((r != null) && (y == r.getY())) {
 716             // Skip invisible elements
 717             if (r.getHeight() !=0) {
 718                 offs = lastOffs;
 719             }
 720             lastOffs += 1;
 721             r = (lastOffs <= n) ? c.modelToView(lastOffs) : null;
 722         }
 723         return offs;
 724     }
 725 
 726     /**
 727      * Determines the position in the model that is closest to the given
 728      * view location in the row above.  The component given must have a
 729      * size to compute the result.  If the component doesn't have a size
 730      * a value of -1 will be returned.
 731      *
 732      * @param c the editor
 733      * @param offs the offset in the document &gt;= 0
 734      * @param x the X coordinate &gt;= 0
 735      * @return the position &gt;= 0 if the request can be computed, otherwise
 736      *  a value of -1 will be returned.
 737      * @exception BadLocationException if the offset is out of range
 738      *
 739      * @deprecated replaced by
 740      *     {@link #getPositionAbove(JTextComponent, int, float)}
 741      */
 742     @Deprecated(since = "9")
 743     public static final int getPositionAbove(JTextComponent c, int offs, int x)
 744             throws BadLocationException
 745     {
 746         return getPositionAbove(c, offs, x, false);
 747     }
 748 
 749     @SuppressWarnings("deprecation")
 750     static final int getPositionAbove(JTextComponent c, int offs, float x,
 751                                       boolean useFPAPI) throws BadLocationException
 752     {
 753         int lastOffs = getRowStart(c, offs) - 1;
 754         if (lastOffs < 0) {
 755             return -1;
 756         }
 757         double bestSpan = Integer.MAX_VALUE;
 758         double y = 0;
 759         Rectangle2D r = null;
 760         if (lastOffs >= 0) {
 761             r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs);
 762             y = r.getY();
 763         }
 764         while ((r != null) && (y == r.getY())) {
 765             double span = Math.abs(r.getX() - x);
 766             if (span < bestSpan) {
 767                 offs = lastOffs;
 768                 bestSpan = span;
 769             }
 770             lastOffs -= 1;
 771 
 772             if ((lastOffs >= 0)) {
 773                 r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs);
 774             } else {
 775                 r = null;
 776             }
 777         }
 778         return offs;
 779     }
 780 
 781     /**
 782      * Determines the position in the model that is closest to the given
 783      * view location in the row above.  The component given must have a
 784      * size to compute the result.  If the component doesn't have a size
 785      * a value of -1 will be returned.
 786      *
 787      * @param c the editor
 788      * @param offs the offset in the document {@code >= 0}
 789      * @param x the X coordinate {@code >= 0}
 790      * @return the position {@code >= 0} if the request can be computed, otherwise
 791      *  a value of -1 will be returned.
 792      * @exception BadLocationException if the offset is out of range
 793      *
 794      * @since 9
 795      */
 796     public static final int getPositionAbove(JTextComponent c, int offs, float x)
 797             throws BadLocationException {
 798         return getPositionAbove(c, offs, x, true);
 799     }
 800 
 801     /**
 802      * Determines the position in the model that is closest to the given
 803      * view location in the row below.  The component given must have a
 804      * size to compute the result.  If the component doesn't have a size
 805      * a value of -1 will be returned.
 806      *
 807      * @param c the editor
 808      * @param offs the offset in the document &gt;= 0
 809      * @param x the X coordinate &gt;= 0
 810      * @return the position &gt;= 0 if the request can be computed, otherwise
 811      *  a value of -1 will be returned.
 812      * @exception BadLocationException if the offset is out of range
 813      *
 814      * @deprecated replaced by
 815      *     {@link #getPositionBelow(JTextComponent, int, float)}
 816      */
 817     @Deprecated(since = "9")
 818     public static final int getPositionBelow(JTextComponent c, int offs, int x)
 819             throws BadLocationException
 820     {
 821         return getPositionBelow(c, offs, x, false);
 822     }
 823 
 824     @SuppressWarnings("deprecation")
 825     static final int getPositionBelow(JTextComponent c, int offs, float x,
 826                                       boolean useFPAPI) throws BadLocationException
 827     {
 828         int lastOffs = getRowEnd(c, offs) + 1;
 829         if (lastOffs <= 0) {
 830             return -1;
 831         }
 832         double bestSpan = Integer.MAX_VALUE;
 833         int n = c.getDocument().getLength();
 834         double y = 0;
 835         Rectangle2D r = null;
 836         if (lastOffs <= n) {
 837             r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs);
 838             y = r.getY();
 839         }
 840         while ((r != null) && (y == r.getY())) {
 841             double span = Math.abs(x - r.getX());
 842             if (span < bestSpan) {
 843                 offs = lastOffs;
 844                 bestSpan = span;
 845             }
 846             lastOffs += 1;
 847 
 848             if (lastOffs <= n) {
 849                 r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs);
 850             } else {
 851                 r = null;
 852             }
 853         }
 854         return offs;
 855     }
 856 
 857     /**
 858      * Determines the position in the model that is closest to the given
 859      * view location in the row below.  The component given must have a
 860      * size to compute the result.  If the component doesn't have a size
 861      * a value of -1 will be returned.
 862      *
 863      * @param c the editor
 864      * @param offs the offset in the document {@code >= 0}
 865      * @param x the X coordinate {@code >= 0}
 866      * @return the position {@code >= 0} if the request can be computed, otherwise
 867      *  a value of -1 will be returned.
 868      * @exception BadLocationException if the offset is out of range
 869      *
 870      * @since 9
 871      */
 872     public static final int getPositionBelow(JTextComponent c, int offs, float x)
 873             throws BadLocationException {
 874         return getPositionBelow(c, offs, x, true);
 875     }
 876 
 877     /**
 878      * Determines the start of a word for the given model location.
 879      * Uses BreakIterator.getWordInstance() to actually get the words.
 880      *
 881      * @param c the editor
 882      * @param offs the offset in the document &gt;= 0
 883      * @return the location in the model of the word start &gt;= 0
 884      * @exception BadLocationException if the offset is out of range
 885      */
 886     public static final int getWordStart(JTextComponent c, int offs) throws BadLocationException {
 887         Document doc = c.getDocument();
 888         Element line = getParagraphElement(c, offs);
 889         if (line == null) {
 890             throw new BadLocationException("No word at " + offs, offs);
 891         }
 892         int lineStart = line.getStartOffset();
 893         int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
 894 
 895         Segment seg = SegmentCache.getSharedSegment();
 896         doc.getText(lineStart, lineEnd - lineStart, seg);
 897         if(seg.count > 0) {
 898             BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
 899             words.setText(seg);
 900             int wordPosition = seg.offset + offs - lineStart;
 901             if(wordPosition >= words.last()) {
 902                 wordPosition = words.last() - 1;
 903             }
 904             words.following(wordPosition);
 905             offs = lineStart + words.previous() - seg.offset;
 906         }
 907         SegmentCache.releaseSharedSegment(seg);
 908         return offs;
 909     }
 910 
 911     /**
 912      * Determines the end of a word for the given location.
 913      * Uses BreakIterator.getWordInstance() to actually get the words.
 914      *
 915      * @param c the editor
 916      * @param offs the offset in the document &gt;= 0
 917      * @return the location in the model of the word end &gt;= 0
 918      * @exception BadLocationException if the offset is out of range
 919      */
 920     public static final int getWordEnd(JTextComponent c, int offs) throws BadLocationException {
 921         Document doc = c.getDocument();
 922         Element line = getParagraphElement(c, offs);
 923         if (line == null) {
 924             throw new BadLocationException("No word at " + offs, offs);
 925         }
 926         int lineStart = line.getStartOffset();
 927         int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
 928 
 929         Segment seg = SegmentCache.getSharedSegment();
 930         doc.getText(lineStart, lineEnd - lineStart, seg);
 931         if(seg.count > 0) {
 932             BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
 933             words.setText(seg);
 934             int wordPosition = offs - lineStart + seg.offset;
 935             if(wordPosition >= words.last()) {
 936                 wordPosition = words.last() - 1;
 937             }
 938             offs = lineStart + words.following(wordPosition) - seg.offset;
 939         }
 940         SegmentCache.releaseSharedSegment(seg);
 941         return offs;
 942     }
 943 
 944     /**
 945      * Determines the start of the next word for the given location.
 946      * Uses BreakIterator.getWordInstance() to actually get the words.
 947      *
 948      * @param c the editor
 949      * @param offs the offset in the document &gt;= 0
 950      * @return the location in the model of the word start &gt;= 0
 951      * @exception BadLocationException if the offset is out of range
 952      */
 953     public static final int getNextWord(JTextComponent c, int offs) throws BadLocationException {
 954         int nextWord;
 955         Element line = getParagraphElement(c, offs);
 956         for (nextWord = getNextWordInParagraph(c, line, offs, false);
 957              nextWord == BreakIterator.DONE;
 958              nextWord = getNextWordInParagraph(c, line, offs, true)) {
 959 
 960             // didn't find in this line, try the next line
 961             offs = line.getEndOffset();
 962             line = getParagraphElement(c, offs);
 963         }
 964         return nextWord;
 965     }
 966 
 967     /**
 968      * Finds the next word in the given elements text.  The first
 969      * parameter allows searching multiple paragraphs where even
 970      * the first offset is desired.
 971      * Returns the offset of the next word, or BreakIterator.DONE
 972      * if there are no more words in the element.
 973      */
 974     static int getNextWordInParagraph(JTextComponent c, Element line, int offs, boolean first) throws BadLocationException {
 975         if (line == null) {
 976             throw new BadLocationException("No more words", offs);
 977         }
 978         Document doc = line.getDocument();
 979         int lineStart = line.getStartOffset();
 980         int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
 981         if ((offs >= lineEnd) || (offs < lineStart)) {
 982             throw new BadLocationException("No more words", offs);
 983         }
 984         Segment seg = SegmentCache.getSharedSegment();
 985         doc.getText(lineStart, lineEnd - lineStart, seg);
 986         BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
 987         words.setText(seg);
 988         if ((first && (words.first() == (seg.offset + offs - lineStart))) &&
 989             (! Character.isWhitespace(seg.array[words.first()]))) {
 990 
 991             return offs;
 992         }
 993         int wordPosition = words.following(seg.offset + offs - lineStart);
 994         if ((wordPosition == BreakIterator.DONE) ||
 995             (wordPosition >= seg.offset + seg.count)) {
 996                 // there are no more words on this line.
 997                 return BreakIterator.DONE;
 998         }
 999         // if we haven't shot past the end... check to
1000         // see if the current boundary represents whitespace.
1001         // if so, we need to try again
1002         char ch = seg.array[wordPosition];
1003         if (! Character.isWhitespace(ch)) {
1004             return lineStart + wordPosition - seg.offset;
1005         }
1006 
1007         // it was whitespace, try again.  The assumption
1008         // is that it must be a word start if the last
1009         // one had whitespace following it.
1010         wordPosition = words.next();
1011         if (wordPosition != BreakIterator.DONE) {
1012             offs = lineStart + wordPosition - seg.offset;
1013             if (offs != lineEnd) {
1014                 return offs;
1015             }
1016         }
1017         SegmentCache.releaseSharedSegment(seg);
1018         return BreakIterator.DONE;
1019     }
1020 
1021 
1022     /**
1023      * Determine the start of the prev word for the given location.
1024      * Uses BreakIterator.getWordInstance() to actually get the words.
1025      *
1026      * @param c the editor
1027      * @param offs the offset in the document &gt;= 0
1028      * @return the location in the model of the word start &gt;= 0
1029      * @exception BadLocationException if the offset is out of range
1030      */
1031     public static final int getPreviousWord(JTextComponent c, int offs) throws BadLocationException {
1032         int prevWord;
1033         Element line = getParagraphElement(c, offs);
1034         for (prevWord = getPrevWordInParagraph(c, line, offs);
1035              prevWord == BreakIterator.DONE;
1036              prevWord = getPrevWordInParagraph(c, line, offs)) {
1037 
1038             // didn't find in this line, try the prev line
1039             offs = line.getStartOffset() - 1;
1040             line = getParagraphElement(c, offs);
1041         }
1042         return prevWord;
1043     }
1044 
1045     /**
1046      * Finds the previous word in the given elements text.  The first
1047      * parameter allows searching multiple paragraphs where even
1048      * the first offset is desired.
1049      * Returns the offset of the next word, or BreakIterator.DONE
1050      * if there are no more words in the element.
1051      */
1052     static int getPrevWordInParagraph(JTextComponent c, Element line, int offs) throws BadLocationException {
1053         if (line == null) {
1054             throw new BadLocationException("No more words", offs);
1055         }
1056         Document doc = line.getDocument();
1057         int lineStart = line.getStartOffset();
1058         int lineEnd = line.getEndOffset();
1059         if ((offs > lineEnd) || (offs < lineStart)) {
1060             throw new BadLocationException("No more words", offs);
1061         }
1062         Segment seg = SegmentCache.getSharedSegment();
1063         doc.getText(lineStart, lineEnd - lineStart, seg);
1064         BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
1065         words.setText(seg);
1066         if (words.following(seg.offset + offs - lineStart) == BreakIterator.DONE) {
1067             words.last();
1068         }
1069         int wordPosition = words.previous();
1070         if (wordPosition == (seg.offset + offs - lineStart)) {
1071             wordPosition = words.previous();
1072         }
1073 
1074         if (wordPosition == BreakIterator.DONE) {
1075             // there are no more words on this line.
1076             return BreakIterator.DONE;
1077         }
1078         // if we haven't shot past the end... check to
1079         // see if the current boundary represents whitespace.
1080         // if so, we need to try again
1081         char ch = seg.array[wordPosition];
1082         if (! Character.isWhitespace(ch)) {
1083             return lineStart + wordPosition - seg.offset;
1084         }
1085 
1086         // it was whitespace, try again.  The assumption
1087         // is that it must be a word start if the last
1088         // one had whitespace following it.
1089         wordPosition = words.previous();
1090         if (wordPosition != BreakIterator.DONE) {
1091             return lineStart + wordPosition - seg.offset;
1092         }
1093         SegmentCache.releaseSharedSegment(seg);
1094         return BreakIterator.DONE;
1095     }
1096 
1097     /**
1098      * Determines the element to use for a paragraph/line.
1099      *
1100      * @param c the editor
1101      * @param offs the starting offset in the document &gt;= 0
1102      * @return the element
1103      */
1104     public static final Element getParagraphElement(JTextComponent c, int offs) {
1105         Document doc = c.getDocument();
1106         if (doc instanceof StyledDocument) {
1107             return ((StyledDocument)doc).getParagraphElement(offs);
1108         }
1109         Element map = doc.getDefaultRootElement();
1110         int index = map.getElementIndex(offs);
1111         Element paragraph = map.getElement(index);
1112         if ((offs >= paragraph.getStartOffset()) && (offs < paragraph.getEndOffset())) {
1113             return paragraph;
1114         }
1115         return null;
1116     }
1117 
1118     static boolean isComposedTextElement(Document doc, int offset) {
1119         Element elem = doc.getDefaultRootElement();
1120         while (!elem.isLeaf()) {
1121             elem = elem.getElement(elem.getElementIndex(offset));
1122         }
1123         return isComposedTextElement(elem);
1124     }
1125 
1126     static boolean isComposedTextElement(Element elem) {
1127         AttributeSet as = elem.getAttributes();
1128         return isComposedTextAttributeDefined(as);
1129     }
1130 
1131     static boolean isComposedTextAttributeDefined(AttributeSet as) {
1132         return ((as != null) &&
1133                 (as.isDefined(StyleConstants.ComposedTextAttribute)));
1134     }
1135 
1136     /**
1137      * Draws the given composed text passed from an input method.
1138      *
1139      * @param view View hosting text
1140      * @param attr the attributes containing the composed text
1141      * @param g  the graphics context
1142      * @param x  the X origin
1143      * @param y  the Y origin
1144      * @param p0 starting offset in the composed text to be rendered
1145      * @param p1 ending offset in the composed text to be rendered
1146      * @return  the new insertion position
1147      */
1148     static int drawComposedText(View view, AttributeSet attr, Graphics g,
1149                                 int x, int y, int p0, int p1)
1150             throws BadLocationException
1151     {
1152         return (int) drawComposedText(view, attr, g, x, y, p0, p1, false);
1153     }
1154 
1155     static float drawComposedText(View view, AttributeSet attr, Graphics g,
1156                                   float x, float y, int p0, int p1)
1157             throws BadLocationException
1158     {
1159         return drawComposedText(view, attr, g, x, y, p0, p1, true);
1160     }
1161 
1162     static float drawComposedText(View view, AttributeSet attr, Graphics g,
1163                                   float x, float y, int p0, int p1,
1164                                   boolean useFPAPI)
1165             throws BadLocationException
1166     {
1167         Graphics2D g2d = (Graphics2D)g;
1168         AttributedString as = (AttributedString)attr.getAttribute(
1169             StyleConstants.ComposedTextAttribute);
1170         as.addAttribute(TextAttribute.FONT, g.getFont());
1171 
1172         if (p0 >= p1)
1173             return x;
1174 
1175         AttributedCharacterIterator aci = as.getIterator(null, p0, p1);
1176         return x + SwingUtilities2.drawString(getJComponent(view), g2d, aci, x, y);
1177     }
1178 
1179     /**
1180      * Paints the composed text in a GlyphView
1181      */
1182     static void paintComposedText(Graphics g, Rectangle alloc, GlyphView v) {
1183         if (g instanceof Graphics2D) {
1184             Graphics2D g2d = (Graphics2D) g;
1185             int p0 = v.getStartOffset();
1186             int p1 = v.getEndOffset();
1187             AttributeSet attrSet = v.getElement().getAttributes();
1188             AttributedString as =
1189                 (AttributedString)attrSet.getAttribute(StyleConstants.ComposedTextAttribute);
1190             int start = v.getElement().getStartOffset();
1191             int y = alloc.y + alloc.height - (int)v.getGlyphPainter().getDescent(v);
1192             int x = alloc.x;
1193 
1194             //Add text attributes
1195             as.addAttribute(TextAttribute.FONT, v.getFont());
1196             as.addAttribute(TextAttribute.FOREGROUND, v.getForeground());
1197             if (StyleConstants.isBold(v.getAttributes())) {
1198                 as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
1199             }
1200             if (StyleConstants.isItalic(v.getAttributes())) {
1201                 as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
1202             }
1203             if (v.isUnderline()) {
1204                 as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
1205             }
1206             if (v.isStrikeThrough()) {
1207                 as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
1208             }
1209             if (v.isSuperscript()) {
1210                 as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
1211             }
1212             if (v.isSubscript()) {
1213                 as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
1214             }
1215 
1216             // draw
1217             AttributedCharacterIterator aci = as.getIterator(null, p0 - start, p1 - start);
1218             SwingUtilities2.drawString(getJComponent(v),
1219                                        g2d,aci,x,y);
1220         }
1221     }
1222 
1223     /*
1224      * Convenience function for determining ComponentOrientation.  Helps us
1225      * avoid having Munge directives throughout the code.
1226      */
1227     static boolean isLeftToRight( java.awt.Component c ) {
1228         return c.getComponentOrientation().isLeftToRight();
1229     }
1230 
1231 
1232     /**
1233      * Provides a way to determine the next visually represented model
1234      * location that one might place a caret.  Some views may not be visible,
1235      * they might not be in the same order found in the model, or they just
1236      * might not allow access to some of the locations in the model.
1237      * <p>
1238      * This implementation assumes the views are layed out in a logical
1239      * manner. That is, that the view at index x + 1 is visually after
1240      * the View at index x, and that the View at index x - 1 is visually
1241      * before the View at x. There is support for reversing this behavior
1242      * only if the passed in <code>View</code> is an instance of
1243      * <code>CompositeView</code>. The <code>CompositeView</code>
1244      * must then override the <code>flipEastAndWestAtEnds</code> method.
1245      *
1246      * @param v View to query
1247      * @param pos the position to convert &gt;= 0
1248      * @param a the allocated region to render into
1249      * @param direction the direction from the current position that can
1250      *  be thought of as the arrow keys typically found on a keyboard;
1251      *  this may be one of the following:
1252      *  <ul>
1253      *  <li><code>SwingConstants.WEST</code>
1254      *  <li><code>SwingConstants.EAST</code>
1255      *  <li><code>SwingConstants.NORTH</code>
1256      *  <li><code>SwingConstants.SOUTH</code>
1257      *  </ul>
1258      * @param biasRet an array contain the bias that was checked
1259      * @return the location within the model that best represents the next
1260      *  location visual position
1261      * @exception BadLocationException
1262      * @exception IllegalArgumentException if <code>direction</code> is invalid
1263      */
1264     static int getNextVisualPositionFrom(View v, int pos, Position.Bias b,
1265                                           Shape alloc, int direction,
1266                                           Position.Bias[] biasRet)
1267                              throws BadLocationException {
1268         if (v.getViewCount() == 0) {
1269             // Nothing to do.
1270             return pos;
1271         }
1272         boolean top = (direction == SwingConstants.NORTH ||
1273                        direction == SwingConstants.WEST);
1274         int retValue;
1275         if (pos == -1) {
1276             // Start from the first View.
1277             int childIndex = (top) ? v.getViewCount() - 1 : 0;
1278             View child = v.getView(childIndex);
1279             Shape childBounds = v.getChildAllocation(childIndex, alloc);
1280             retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
1281                                                        direction, biasRet);
1282             if (retValue == -1 && !top && v.getViewCount() > 1) {
1283                 // Special case that should ONLY happen if first view
1284                 // isn't valid (can happen when end position is put at
1285                 // beginning of line.
1286                 child = v.getView(1);
1287                 childBounds = v.getChildAllocation(1, alloc);
1288                 retValue = child.getNextVisualPositionFrom(-1, biasRet[0],
1289                                                            childBounds,
1290                                                            direction, biasRet);
1291             }
1292         }
1293         else {
1294             int increment = (top) ? -1 : 1;
1295             int childIndex;
1296             if (b == Position.Bias.Backward && pos > 0) {
1297                 childIndex = v.getViewIndex(pos - 1, Position.Bias.Forward);
1298             }
1299             else {
1300                 childIndex = v.getViewIndex(pos, Position.Bias.Forward);
1301             }
1302             View child = v.getView(childIndex);
1303             Shape childBounds = v.getChildAllocation(childIndex, alloc);
1304             retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
1305                                                        direction, biasRet);
1306             if ((direction == SwingConstants.EAST ||
1307                  direction == SwingConstants.WEST) &&
1308                 (v instanceof CompositeView) &&
1309                 ((CompositeView)v).flipEastAndWestAtEnds(pos, b)) {
1310                 increment *= -1;
1311             }
1312             childIndex += increment;
1313             if (retValue == -1 && childIndex >= 0 &&
1314                                   childIndex < v.getViewCount()) {
1315                 child = v.getView(childIndex);
1316                 childBounds = v.getChildAllocation(childIndex, alloc);
1317                 retValue = child.getNextVisualPositionFrom(
1318                                      -1, b, childBounds, direction, biasRet);
1319                 // If there is a bias change, it is a fake position
1320                 // and we should skip it. This is usually the result
1321                 // of two elements side be side flowing the same way.
1322                 if (retValue == pos && biasRet[0] != b) {
1323                     return getNextVisualPositionFrom(v, pos, biasRet[0],
1324                                                      alloc, direction,
1325                                                      biasRet);
1326                 }
1327             }
1328             else if (retValue != -1 && biasRet[0] != b &&
1329                      ((increment == 1 && child.getEndOffset() == retValue) ||
1330                       (increment == -1 &&
1331                        child.getStartOffset() == retValue)) &&
1332                      childIndex >= 0 && childIndex < v.getViewCount()) {
1333                 // Reached the end of a view, make sure the next view
1334                 // is a different direction.
1335                 child = v.getView(childIndex);
1336                 childBounds = v.getChildAllocation(childIndex, alloc);
1337                 Position.Bias originalBias = biasRet[0];
1338                 int nextPos = child.getNextVisualPositionFrom(
1339                                     -1, b, childBounds, direction, biasRet);
1340                 if (biasRet[0] == b) {
1341                     retValue = nextPos;
1342                 }
1343                 else {
1344                     biasRet[0] = originalBias;
1345                 }
1346             }
1347         }
1348         return retValue;
1349     }
1350 }