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      */
TextLayoutStrategy()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      */
insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)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      */
removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)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      */
changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)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      */
layout(FlowView fv)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      */
layoutRow(FlowView fv, int rowIndex, int p0)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      */
adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x)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      */
createView(FlowView fv, int startOffset, int spanLeft, int rowIndex)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      */
getLimitingOffset(View v, int startOffset, int spanLeft, boolean requireNextWord)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      */
sync(FlowView fv)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 
AttributedSegment()346         AttributedSegment() {
347         }
348 
getView()349         View getView() {
350             return v;
351         }
352 
setView(View v)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          */
getFontBoundary(int childIndex, int dir)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          */
getFont(int childIndex)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 
toModelPosition(int index)401         int toModelPosition(int index) {
402             return v.getStartOffset() + (index - getBeginIndex());
403         }
404 
toIteratorIndex(int pos)405         int toIteratorIndex(int pos) {
406             return pos - v.getStartOffset() + getBeginIndex();
407         }
408 
setShaper(Object shaper)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          */
getRunStart()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          */
getRunStart(AttributedCharacterIterator.Attribute attribute)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          */
getRunStart(Set<? extends Attribute> attributes)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          */
getRunLimit()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          */
getRunLimit(AttributedCharacterIterator.Attribute attribute)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          */
getRunLimit(Set<? extends Attribute> attributes)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          */
getAttributes()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          */
getAttribute(AttributedCharacterIterator.Attribute attribute)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          */
getAllAttributeKeys()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 }
555