1 /*
   2  * Copyright (c) 1999, 2008, 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.util.*;
  28 import java.awt.*;
  29 import java.text.AttributedCharacterIterator;
  30 import java.text.BreakIterator;
  31 import java.awt.font.*;
  32 import java.awt.geom.AffineTransform;
  33 import javax.swing.JComponent;
  34 import javax.swing.event.DocumentEvent;
  35 import sun.font.BidiUtils;
  36 
  37 /**
  38  * A flow strategy that uses java.awt.font.LineBreakMeasureer to
  39  * produce java.awt.font.TextLayout for i18n capable rendering.
  40  * If the child view being placed into the flow is of type
  41  * GlyphView and can be rendered by TextLayout, a GlyphPainter
  42  * that uses TextLayout is plugged into the GlyphView.
  43  *
  44  * @author  Timothy Prinzing
  45  */
  46 class TextLayoutStrategy extends FlowView.FlowStrategy {
  47 
  48     /**
  49      * Constructs a layout strategy for paragraphs based
  50      * upon java.awt.font.LineBreakMeasurer.
  51      */
  52     public TextLayoutStrategy() {
  53         text = new AttributedSegment();
  54     }
  55 
  56     // --- FlowStrategy methods --------------------------------------------
  57 
  58     /**
  59      * Gives notification that something was inserted into the document
  60      * in a location that the given flow view is responsible for.  The
  61      * strategy should update the appropriate changed region (which
  62      * depends upon the strategy used for repair).
  63      *
  64      * @param e the change information from the associated document
  65      * @param alloc the current allocation of the view inside of the insets.
  66      *   This value will be null if the view has not yet been displayed.
  67      * @see View#insertUpdate
  68      */
  69     public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
  70         sync(fv);
  71         super.insertUpdate(fv, e, alloc);
  72     }
  73 
  74     /**
  75      * Gives notification that something was removed from the document
  76      * in a location that the given flow view is responsible for.
  77      *
  78      * @param e the change information from the associated document
  79      * @param alloc the current allocation of the view inside of the insets.
  80      * @see View#removeUpdate
  81      */
  82     public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
  83         sync(fv);
  84         super.removeUpdate(fv, e, alloc);
  85     }
  86 
  87     /**
  88      * Gives notification from the document that attributes were changed
  89      * in a location that this view is responsible for.
  90      *
  91      * @param changes the change information from the associated document
  92      * @param a the current allocation of the view
  93      * @param f the factory to use to rebuild if the view has children
  94      * @see View#changedUpdate
  95      */
  96     public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
  97         sync(fv);
  98         super.changedUpdate(fv, e, alloc);
  99     }
 100 
 101     /**
 102      * Does a a full layout on the given View.  This causes all of
 103      * the rows (child views) to be rebuilt to match the given
 104      * constraints for each row.  This is called by a FlowView.layout
 105      * to update the child views in the flow.
 106      *
 107      * @param fv the view to reflow
 108      */
 109     public void layout(FlowView fv) {
 110         super.layout(fv);
 111     }
 112 
 113     /**
 114      * Creates a row of views that will fit within the
 115      * layout span of the row.  This is implemented to execute the
 116      * superclass functionality (which fills the row with child
 117      * views or view fragments) and follow that with bidi reordering
 118      * of the unidirectional view fragments.
 119      *
 120      * @param row the row to fill in with views.  This is assumed
 121      *   to be empty on entry.
 122      * @param pos  The current position in the children of
 123      *   this views element from which to start.
 124      * @return the position to start the next row
 125      */
 126     protected int layoutRow(FlowView fv, int rowIndex, int p0) {
 127         int p1 = super.layoutRow(fv, rowIndex, p0);
 128         View row = fv.getView(rowIndex);
 129         Document doc = fv.getDocument();
 130         Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
 131         if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
 132             int n = row.getViewCount();
 133             if (n > 1) {
 134                 AbstractDocument d = (AbstractDocument)fv.getDocument();
 135                 Element bidiRoot = d.getBidiRootElement();
 136                 byte[] levels = new byte[n];
 137                 View[] reorder = new View[n];
 138 
 139                 for( int i=0; i<n; i++ ) {
 140                     View v = row.getView(i);
 141                     int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset());
 142                     Element bidiElem = bidiRoot.getElement( bidiIndex );
 143                     levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes());
 144                     reorder[i] = v;
 145                 }
 146 
 147                 BidiUtils.reorderVisually( levels, reorder );
 148                 row.replace(0, n, reorder);
 149             }
 150         }
 151         return p1;
 152     }
 153 
 154     /**
 155      * Adjusts the given row if possible to fit within the
 156      * layout span.  Since all adjustments were already
 157      * calculated by the LineBreakMeasurer, this is implemented
 158      * to do nothing.
 159      *
 160      * @param r the row to adjust to the current layout
 161      *  span.
 162      * @param desiredSpan the current layout span >= 0
 163      * @param x the location r starts at.
 164      */
 165     protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
 166     }
 167 
 168     /**
 169      * Creates a unidirectional view that can be used to represent the
 170      * current chunk.  This can be either an entire view from the
 171      * logical view, or a fragment of the view.
 172      *
 173      * @param fv the view holding the flow
 174      * @param startOffset the start location for the view being created
 175      * @param spanLeft the about of span left to fill in the row
 176      * @param rowIndex the row the view will be placed into
 177      */
 178     protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
 179         // Get the child view that contains the given starting position
 180         View lv = getLogicalView(fv);
 181         View row = fv.getView(rowIndex);
 182         boolean requireNextWord = (viewBuffer.size() == 0) ? false : true;
 183         int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
 184         View v = lv.getView(childIndex);
 185 
 186         int endOffset = getLimitingOffset(v, startOffset, spanLeft, requireNextWord);
 187         if (endOffset == startOffset) {
 188             return null;
 189         }
 190 
 191         View frag;
 192         if ((startOffset==v.getStartOffset()) && (endOffset == v.getEndOffset())) {
 193             // return the entire view
 194             frag = v;
 195         } else {
 196             // return a unidirectional fragment.
 197             frag = v.createFragment(startOffset, endOffset);
 198         }
 199 
 200         if ((frag instanceof GlyphView) && (measurer != null)) {
 201             // install a TextLayout based renderer if the view is responsible
 202             // for glyphs.  If the view represents a tab, the default
 203             // glyph painter is used (may want to handle tabs differently).
 204             boolean isTab = false;
 205             int p0 = frag.getStartOffset();
 206             int p1 = frag.getEndOffset();
 207             if ((p1 - p0) == 1) {
 208                 // check for tab
 209                 Segment s = ((GlyphView)frag).getText(p0, p1);
 210                 char ch = s.first();
 211                 if (ch == '\t') {
 212                     isTab = true;
 213                 }
 214             }
 215             TextLayout tl = (isTab) ? null :
 216                 measurer.nextLayout(spanLeft, text.toIteratorIndex(endOffset),
 217                                     requireNextWord);
 218             if (tl != null) {
 219                 ((GlyphView)frag).setGlyphPainter(new GlyphPainter2(tl));
 220             }
 221         }
 222         return frag;
 223     }
 224 
 225     /**
 226      * Calculate the limiting offset for the next view fragment.
 227      * At most this would be the entire view (i.e. the limiting
 228      * offset would be the end offset in that case).  If the range
 229      * contains a tab or a direction change, that will limit the
 230      * offset to something less.  This value is then fed to the
 231      * LineBreakMeasurer as a limit to consider in addition to the
 232      * remaining span.
 233      *
 234      * @param v the logical view representing the starting offset.
 235      * @param startOffset the model location to start at.
 236      */
 237     int getLimitingOffset(View v, int startOffset, int spanLeft, boolean requireNextWord) {
 238         int endOffset = v.getEndOffset();
 239 
 240         // check for direction change
 241         Document doc = v.getDocument();
 242         if (doc instanceof AbstractDocument) {
 243             AbstractDocument d = (AbstractDocument) doc;
 244             Element bidiRoot = d.getBidiRootElement();
 245             if( bidiRoot.getElementCount() > 1 ) {
 246                 int bidiIndex = bidiRoot.getElementIndex( startOffset );
 247                 Element bidiElem = bidiRoot.getElement( bidiIndex );
 248                 endOffset = Math.min( bidiElem.getEndOffset(), endOffset );
 249             }
 250         }
 251 
 252         // check for tab
 253         if (v instanceof GlyphView) {
 254             Segment s = ((GlyphView)v).getText(startOffset, endOffset);
 255             char ch = s.first();
 256             if (ch == '\t') {
 257                 // if the first character is a tab, create a dedicated
 258                 // view for just the tab
 259                 endOffset = startOffset + 1;
 260             } else {
 261                 for (ch = s.next(); ch != Segment.DONE; ch = s.next()) {
 262                     if (ch == '\t') {
 263                         // found a tab, don't include it in the text
 264                         endOffset = startOffset + s.getIndex() - s.getBeginIndex();
 265                         break;
 266                     }
 267                 }
 268             }
 269         }
 270 
 271         // determine limit from LineBreakMeasurer
 272         int limitIndex = text.toIteratorIndex(endOffset);
 273         if (measurer != null) {
 274             int index = text.toIteratorIndex(startOffset);
 275             if (measurer.getPosition() != index) {
 276                 measurer.setPosition(index);
 277             }
 278             limitIndex = measurer.nextOffset(spanLeft, limitIndex, requireNextWord);
 279         }
 280         int pos = text.toModelPosition(limitIndex);
 281         return pos;
 282     }
 283 
 284     /**
 285      * Synchronize the strategy with its FlowView.  Allows the strategy
 286      * to update its state to account for changes in that portion of the
 287      * model represented by the FlowView.  Also allows the strategy
 288      * to update the FlowView in response to these changes.
 289      */
 290     void sync(FlowView fv) {
 291         View lv = getLogicalView(fv);
 292         text.setView(lv);
 293 
 294         Container container = fv.getContainer();
 295         FontRenderContext frc = sun.swing.SwingUtilities2.
 296                                     getFontRenderContext(container);
 297         BreakIterator iter;
 298         Container c = fv.getContainer();
 299         if (c != null) {
 300             iter = BreakIterator.getLineInstance(c.getLocale());
 301         } else {
 302             iter = BreakIterator.getLineInstance();
 303         }
 304 
 305         Object shaper = null;
 306         if (c instanceof JComponent) {
 307             shaper = ((JComponent) c).getClientProperty(
 308                                             TextAttribute.NUMERIC_SHAPING);
 309         }
 310         text.setShaper(shaper);
 311 
 312         measurer = new LineBreakMeasurer(text, iter, frc);
 313 
 314         // If the children of the FlowView's logical view are GlyphViews, they
 315         // need to have their painters updated.
 316         int n = lv.getViewCount();
 317         for( int i=0; i<n; i++ ) {
 318             View child = lv.getView(i);
 319             if( child instanceof GlyphView ) {
 320                 int p0 = child.getStartOffset();
 321                 int p1 = child.getEndOffset();
 322                 measurer.setPosition(text.toIteratorIndex(p0));
 323                 TextLayout layout
 324                     = measurer.nextLayout( Float.MAX_VALUE,
 325                                            text.toIteratorIndex(p1), false );
 326                 ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout));
 327             }
 328         }
 329 
 330         // Reset measurer.
 331         measurer.setPosition(text.getBeginIndex());
 332 
 333     }
 334 
 335     // --- variables -------------------------------------------------------
 336 
 337     private LineBreakMeasurer measurer;
 338     private AttributedSegment text;
 339 
 340     /**
 341      * Implementation of AttributedCharacterIterator that supports
 342      * the GlyphView attributes for rendering the glyphs through a
 343      * TextLayout.
 344      */
 345     static class AttributedSegment extends Segment implements AttributedCharacterIterator {
 346 
 347         AttributedSegment() {
 348         }
 349 
 350         View getView() {
 351             return v;
 352         }
 353 
 354         void setView(View v) {
 355             this.v = v;
 356             Document doc = v.getDocument();
 357             int p0 = v.getStartOffset();
 358             int p1 = v.getEndOffset();
 359             try {
 360                 doc.getText(p0, p1 - p0, this);
 361             } catch (BadLocationException bl) {
 362                 throw new IllegalArgumentException("Invalid view");
 363             }
 364             first();
 365         }
 366 
 367         /**
 368          * Get a boundary position for the font.
 369          * This is implemented to assume that two fonts are
 370          * equal if their references are equal (i.e. that the
 371          * font came from a cache).
 372          *
 373          * @return the location in model coordinates.  This is
 374          *  not the same as the Segment coordinates.
 375          */
 376         int getFontBoundary(int childIndex, int dir) {
 377             View child = v.getView(childIndex);
 378             Font f = getFont(childIndex);
 379             for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
 380                  childIndex += dir) {
 381                 Font next = getFont(childIndex);
 382                 if (next != f) {
 383                     // this run is different
 384                     break;
 385                 }
 386                 child = v.getView(childIndex);
 387             }
 388             return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
 389         }
 390 
 391         /**
 392          * Get the font at the given child index.
 393          */
 394         Font getFont(int childIndex) {
 395             View child = v.getView(childIndex);
 396             if (child instanceof GlyphView) {
 397                 return ((GlyphView)child).getFont();
 398             }
 399             return null;
 400         }
 401 
 402         int toModelPosition(int index) {
 403             return v.getStartOffset() + (index - getBeginIndex());
 404         }
 405 
 406         int toIteratorIndex(int pos) {
 407             return pos - v.getStartOffset() + getBeginIndex();
 408         }
 409 
 410         private void setShaper(Object shaper) {
 411             this.shaper = shaper;
 412         }
 413 
 414         // --- AttributedCharacterIterator methods -------------------------
 415 
 416         /**
 417          * Returns the index of the first character of the run
 418          * with respect to all attributes containing the current character.
 419          */
 420         public int getRunStart() {
 421             int pos = toModelPosition(getIndex());
 422             int i = v.getViewIndex(pos, Position.Bias.Forward);
 423             View child = v.getView(i);
 424             return toIteratorIndex(child.getStartOffset());
 425         }
 426 
 427         /**
 428          * Returns the index of the first character of the run
 429          * with respect to the given attribute containing the current character.
 430          */
 431         public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
 432             if (attribute instanceof TextAttribute) {
 433                 int pos = toModelPosition(getIndex());
 434                 int i = v.getViewIndex(pos, Position.Bias.Forward);
 435                 if (attribute == TextAttribute.FONT) {
 436                     return toIteratorIndex(getFontBoundary(i, -1));
 437                 }
 438             }
 439             return getBeginIndex();
 440         }
 441 
 442         /**
 443          * Returns the index of the first character of the run
 444          * with respect to the given attributes containing the current character.
 445          */
 446         public int getRunStart(Set<? extends Attribute> attributes) {
 447             int index = getBeginIndex();
 448             Object[] a = attributes.toArray();
 449             for (int i = 0; i < a.length; i++) {
 450                 TextAttribute attr = (TextAttribute) a[i];
 451                 index = Math.max(getRunStart(attr), index);
 452             }
 453             return Math.min(getIndex(), index);
 454         }
 455 
 456         /**
 457          * Returns the index of the first character following the run
 458          * with respect to all attributes containing the current character.
 459          */
 460         public int getRunLimit() {
 461             int pos = toModelPosition(getIndex());
 462             int i = v.getViewIndex(pos, Position.Bias.Forward);
 463             View child = v.getView(i);
 464             return toIteratorIndex(child.getEndOffset());
 465         }
 466 
 467         /**
 468          * Returns the index of the first character following the run
 469          * with respect to the given attribute containing the current character.
 470          */
 471         public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
 472             if (attribute instanceof TextAttribute) {
 473                 int pos = toModelPosition(getIndex());
 474                 int i = v.getViewIndex(pos, Position.Bias.Forward);
 475                 if (attribute == TextAttribute.FONT) {
 476                     return toIteratorIndex(getFontBoundary(i, 1));
 477                 }
 478             }
 479             return getEndIndex();
 480         }
 481 
 482         /**
 483          * Returns the index of the first character following the run
 484          * with respect to the given attributes containing the current character.
 485          */
 486         public int getRunLimit(Set<? extends Attribute> attributes) {
 487             int index = getEndIndex();
 488             Object[] a = attributes.toArray();
 489             for (int i = 0; i < a.length; i++) {
 490                 TextAttribute attr = (TextAttribute) a[i];
 491                 index = Math.min(getRunLimit(attr), index);
 492             }
 493             return Math.max(getIndex(), index);
 494         }
 495 
 496         /**
 497          * Returns a map with the attributes defined on the current
 498          * character.
 499          */
 500         public Map<Attribute, Object> getAttributes() {
 501             Object[] ka = keys.toArray();
 502             Hashtable<Attribute, Object> h = new Hashtable<Attribute, Object>();
 503             for (int i = 0; i < ka.length; i++) {
 504                 TextAttribute a = (TextAttribute) ka[i];
 505                 Object value = getAttribute(a);
 506                 if (value != null) {
 507                     h.put(a, value);
 508                 }
 509             }
 510             return h;
 511         }
 512 
 513         /**
 514          * Returns the value of the named attribute for the current character.
 515          * Returns null if the attribute is not defined.
 516          * @param attribute the key of the attribute whose value is requested.
 517          */
 518         public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
 519             int pos = toModelPosition(getIndex());
 520             int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
 521             if (attribute == TextAttribute.FONT) {
 522                 return getFont(childIndex);
 523             } else if( attribute == TextAttribute.RUN_DIRECTION ) {
 524                 return
 525                     v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
 526             } else if (attribute == TextAttribute.NUMERIC_SHAPING) {
 527                 return shaper;
 528             }
 529             return null;
 530         }
 531 
 532         /**
 533          * Returns the keys of all attributes defined on the
 534          * iterator's text range. The set is empty if no
 535          * attributes are defined.
 536          */
 537         public Set<Attribute> getAllAttributeKeys() {
 538             return keys;
 539         }
 540 
 541         View v;
 542 
 543         static Set<Attribute> keys;
 544 
 545         static {
 546             keys = new HashSet<Attribute>();
 547             keys.add(TextAttribute.FONT);
 548             keys.add(TextAttribute.RUN_DIRECTION);
 549             keys.add(TextAttribute.NUMERIC_SHAPING);
 550         }
 551 
 552         private Object shaper = null;
 553     }
 554 
 555 }