History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/TextLayoutStrategy.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/text/TextLayoutStrategy.java+555−0
@@ -0,0 +1,555 @@
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+}