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