- /*
- * @(#)TextLayoutStrategy.java 1.11 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
- package javax.swing.text;
-
- import java.util.*;
- import java.awt.*;
- import java.text.AttributedCharacterIterator;
- import java.awt.font.*;
- import java.awt.geom.AffineTransform;
- import javax.swing.event.DocumentEvent;
-
- /**
- * A flow strategy that uses java.awt.font.LineBreakMeasureer to
- * produce java.awt.font.TextLayout for i18n capable rendering.
- * If the child view being placed into the flow is of type
- * GlyphView and can be rendered by TextLayout, a GlyphPainter
- * that uses TextLayout is plugged into the GlyphView.
- *
- * @author Timothy Prinzing
- * @version 1.11 02/02/00
- */
- class TextLayoutStrategy extends FlowView.FlowStrategy {
-
- /**
- * Constructs a layout strategy for paragraphs based
- * upon java.awt.font.LineBreakMeasurer.
- */
- public TextLayoutStrategy() {
- text = new AttributedSegment();
- }
-
- // --- FlowStrategy methods --------------------------------------------
-
- /**
- * Gives notification that something was inserted into the document
- * in a location that the given flow view is responsible for. The
- * strategy should update the appropriate changed region (which
- * depends upon the strategy used for repair).
- *
- * @param e the change information from the associated document
- * @param alloc the current allocation of the view inside of the insets.
- * This value will be null if the view has not yet been displayed.
- * @see View#insertUpdate
- */
- public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
- sync(fv);
- super.insertUpdate(fv, e, alloc);
- }
-
- /**
- * Gives notification that something was removed from the document
- * in a location that the given flow view is responsible for.
- *
- * @param e the change information from the associated document
- * @param alloc the current allocation of the view inside of the insets.
- * @see View#removeUpdate
- */
- public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
- sync(fv);
- super.removeUpdate(fv, e, alloc);
- }
-
- /**
- * Gives notification from the document that attributes were changed
- * in a location that this view is responsible for.
- *
- * @param changes the change information from the associated document
- * @param a the current allocation of the view
- * @param f the factory to use to rebuild if the view has children
- * @see View#changedUpdate
- */
- public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
- sync(fv);
- super.changedUpdate(fv, e, alloc);
- }
-
- /**
- * Does a a full layout on the given View. This causes all of
- * the rows (child views) to be rebuilt to match the given
- * constraints for each row. This is called by a FlowView.layout
- * to update the child views in the flow.
- *
- * @param v the view to reflow
- */
- public void layout(FlowView fv) {
- super.layout(fv);
- }
-
- /**
- * Creates a row of views that will fit within the
- * layout span of the row. This is implemented to execute the
- * superclass functionality (which fills the row with child
- * views or view fragments) and follow that with bidi reordering
- * of the unidirectional view fragments.
- *
- * @param row the row to fill in with views. This is assumed
- * to be empty on entry.
- * @param pos The current position in the children of
- * this views element from which to start.
- * @returns the position to start the next row
- */
- protected int layoutRow(FlowView fv, int rowIndex, int p0) {
- int p1 = super.layoutRow(fv, rowIndex, p0);
- View row = fv.getView(rowIndex);
- Document doc = fv.getDocument();
- Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
- if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
- int n = row.getViewCount();
- if (n > 1) {
- AbstractDocument d = (AbstractDocument)fv.getDocument();
- Element bidiRoot = d.getBidiRootElement();
- byte[] levels = new byte[n];
- View[] reorder = new View[n];
-
- for( int i=0; i<n; i++ ) {
- View v = row.getView(i);
- int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset());
- Element bidiElem = bidiRoot.getElement( bidiIndex );
- levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes());
- reorder[i] = v;
- }
-
- Bidi.reorderVisually( levels, reorder );
- row.replace(0, n, reorder);
- }
- }
- return p1;
- }
-
- /**
- * Adjusts the given row if possible to fit within the
- * layout span. Since all adjustments were already
- * calculated by the LineBreakMeasurer, this is implemented
- * to do nothing.
- *
- * @param r the row to adjust to the current layout
- * span.
- * @param desiredSpan the current layout span >= 0
- * @param x the location r starts at.
- */
- protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
- }
-
- /**
- * Creates a unidirectional view that can be used to represent the
- * current chunk. This can be either an entire view from the
- * logical view, or a fragment of the view.
- *
- * @param fv the view holding the flow
- * @param startOffset the start location for the view being created
- * @param spanLeft the about of span left to fill in the row
- * @param rowIndex the row the view will be placed into
- */
- protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
- // Get the child view that contains the given starting position
- View lv = getLogicalView(fv);
- View row = fv.getView(rowIndex);
- boolean requireNextWord = (row.getViewCount() == 0) ? false : true;
- int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
- View v = lv.getView(childIndex);
-
- int endOffset = getLimitingOffset(v, startOffset, spanLeft, requireNextWord);
- if (endOffset == startOffset) {
- return null;
- }
-
- View frag;
- if ((startOffset==v.getStartOffset()) && (endOffset == v.getEndOffset())) {
- // return the entire view
- frag = v;
- } else {
- // return a unidirectional fragment.
- frag = v.createFragment(startOffset, endOffset);
- }
-
- if ((frag instanceof GlyphView) && (measurer != null)) {
- // install a TextLayout based renderer if the view is responsible
- // for glyphs. If the view represents a tab, the default
- // glyph painter is used (may want to handle tabs differently).
- boolean isTab = false;
- int p0 = frag.getStartOffset();
- int p1 = frag.getEndOffset();
- if ((p1 - p0) == 1) {
- // check for tab
- Segment s = ((GlyphView)frag).getText(p0, p1);
- char ch = s.first();
- if (ch == '\t') {
- isTab = true;
- }
- }
- TextLayout tl = (isTab) ? null :
- measurer.nextLayout(spanLeft, text.toIteratorIndex(endOffset),
- requireNextWord);
- if (tl != null) {
- ((GlyphView)frag).setGlyphPainter(new GlyphPainter2(tl));
- }
- }
- return frag;
- }
-
- /**
- * Calculate the limiting offset for the next view fragment.
- * At most this would be the entire view (i.e. the limiting
- * offset would be the end offset in that case). If the range
- * contains a tab or a direction change, that will limit the
- * offset to something less. This value is then fed to the
- * LineBreakMeasurer as a limit to consider in addition to the
- * remaining span.
- *
- * @param v the logical view representing the starting offset.
- * @param startOffset the model location to start at.
- */
- int getLimitingOffset(View v, int startOffset, int spanLeft, boolean requireNextWord) {
- int endOffset = v.getEndOffset();
-
- // check for direction change
- Document doc = v.getDocument();
- if (doc instanceof AbstractDocument) {
- AbstractDocument d = (AbstractDocument) doc;
- Element bidiRoot = d.getBidiRootElement();
- if( bidiRoot.getElementCount() > 1 ) {
- int bidiIndex = bidiRoot.getElementIndex( startOffset );
- Element bidiElem = bidiRoot.getElement( bidiIndex );
- endOffset = Math.min( bidiElem.getEndOffset(), endOffset );
- }
- }
-
- // check for tab
- if (v instanceof GlyphView) {
- Segment s = ((GlyphView)v).getText(startOffset, endOffset);
- char ch = s.first();
- if (ch == '\t') {
- // if the first character is a tab, create a dedicated
- // view for just the tab
- endOffset = startOffset + 1;
- } else {
- for (ch = s.next(); ch != Segment.DONE; ch = s.next()) {
- if (ch == '\t') {
- // found a tab, don't include it in the text
- endOffset = startOffset + s.getIndex() - s.getBeginIndex();
- break;
- }
- }
- }
- }
-
- // determine limit from LineBreakMeasurer
- int limitIndex = text.toIteratorIndex(endOffset);
- if (measurer != null) {
- int index = text.toIteratorIndex(startOffset);
- if (measurer.getPosition() != index) {
- measurer.setPosition(index);
- }
- limitIndex = measurer.nextOffset(spanLeft, limitIndex, requireNextWord);
- }
- int pos = text.toModelPosition(limitIndex);
- return pos;
- }
-
- /**
- * Synchronize the strategy with its FlowView. Allows the strategy
- * to update its state to account for changes in that portion of the
- * model represented by the FlowView. Also allows the strategy
- * to update the FlowView in response to these changes.
- */
- void sync(FlowView fv) {
- View lv = getLogicalView(fv);
- text.setView(lv);
-
- Graphics2D g2d =(Graphics2D) fv.getContainer().getGraphics();
- FontRenderContext frc;
- try {
- // FontRenderContexts exist to allow text to be measured at times
- // when a Graphics object is not available. A TextLayout is
- // drawn using the settings of the FRC it was created with in
- // preferrence to the settings of the Graphics it is drawn with.
- // This keeps measuring and drawing consistent but may be
- // surprising to some users. We should probably ensure that
- // these two stay in sync.
- if( g2d != null ) {
- frc = g2d.getFontRenderContext();
- } else {
- // As a practical matter, this FRC will almost always
- // be the right one.
- AffineTransform xf
- = GraphicsEnvironment.getLocalGraphicsEnvironment()
- .getDefaultScreenDevice().getDefaultConfiguration()
- .getDefaultTransform();
- frc = new FontRenderContext(xf, false, false);
- }
- } finally {
- if( g2d != null )
- g2d.dispose();
- }
-
- measurer = new LineBreakMeasurer(text, frc);
-
- // If the children of the FlowView's logical view are GlyphViews, they
- // need to have their painters updated.
- int n = lv.getViewCount();
- for( int i=0; i<n; i++ ) {
- View child = lv.getView(i);
- if( child instanceof GlyphView ) {
- int p0 = child.getStartOffset();
- int p1 = child.getEndOffset();
- measurer.setPosition(text.toIteratorIndex(p0));
- TextLayout layout
- = measurer.nextLayout( Float.MAX_VALUE,
- text.toIteratorIndex(p1), false );
- ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout));
- }
- }
-
- // Reset measurer.
- measurer.setPosition(text.getBeginIndex());
-
- }
-
- // --- variables -------------------------------------------------------
-
- private LineBreakMeasurer measurer;
- private AttributedSegment text;
-
- /**
- * Implementation of AttributedCharacterIterator that supports
- * the GlyphView attributes for rendering the glyphs through a
- * TextLayout.
- */
- static class AttributedSegment extends Segment implements AttributedCharacterIterator {
-
- AttributedSegment() {
- }
-
- View getView() {
- return v;
- }
-
- void setView(View v) {
- this.v = v;
- Document doc = v.getDocument();
- int p0 = v.getStartOffset();
- int p1 = v.getEndOffset();
- try {
- doc.getText(p0, p1 - p0, this);
- } catch (BadLocationException bl) {
- throw new IllegalArgumentException("Invalid view");
- }
- first();
- }
-
- /**
- * Get a boundary position for the font.
- * This is implemented to assume that two fonts are
- * equal if their references are equal (i.e. that the
- * font came from a cache).
- *
- * @returns the location in model coordinates. This is
- * not the same as the Segment coordinates.
- */
- int getFontBoundary(int childIndex, int dir) {
- View child = v.getView(childIndex);
- Font f = getFont(childIndex);
- for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
- childIndex += dir) {
- Font next = getFont(childIndex);
- if (next != f) {
- // this run is different
- break;
- }
- child = v.getView(childIndex);
- }
- return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
- }
-
- /**
- * Get the font at the given child index.
- */
- Font getFont(int childIndex) {
- View child = v.getView(childIndex);
- if (child instanceof GlyphView) {
- return ((GlyphView)child).getFont();
- }
- return null;
- }
-
- int toModelPosition(int index) {
- return v.getStartOffset() + (index - getBeginIndex());
- }
-
- int toIteratorIndex(int pos) {
- return pos - v.getStartOffset() + getBeginIndex();
- }
-
- // --- AttributedCharacterIterator methods -------------------------
-
- /**
- * Returns the index of the first character of the run
- * with respect to all attributes containing the current character.
- */
- public int getRunStart() {
- int pos = toModelPosition(getIndex());
- int i = v.getViewIndex(pos, Position.Bias.Forward);
- View child = v.getView(i);
- return toIteratorIndex(child.getStartOffset());
- }
-
- /**
- * Returns the index of the first character of the run
- * with respect to the given attribute containing the current character.
- */
- public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
- if (attribute instanceof TextAttribute) {
- int pos = toModelPosition(getIndex());
- int i = v.getViewIndex(pos, Position.Bias.Forward);
- if (attribute == TextAttribute.FONT) {
- return toIteratorIndex(getFontBoundary(i, -1));
- }
- }
- return getBeginIndex();
- }
-
- /**
- * Returns the index of the first character of the run
- * with respect to the given attributes containing the current character.
- */
- public int getRunStart(Set attributes) {
- int index = getBeginIndex();
- Object[] a = attributes.toArray();
- for (int i = 0; i < a.length; i++) {
- TextAttribute attr = (TextAttribute) a[i];
- index = Math.max(getRunStart(attr), index);
- }
- return Math.min(getIndex(), index);
- }
-
- /**
- * Returns the index of the first character following the run
- * with respect to all attributes containing the current character.
- */
- public int getRunLimit() {
- int pos = toModelPosition(getIndex());
- int i = v.getViewIndex(pos, Position.Bias.Forward);
- View child = v.getView(i);
- return toIteratorIndex(child.getEndOffset());
- }
-
- /**
- * Returns the index of the first character following the run
- * with respect to the given attribute containing the current character.
- */
- public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
- if (attribute instanceof TextAttribute) {
- int pos = toModelPosition(getIndex());
- int i = v.getViewIndex(pos, Position.Bias.Forward);
- if (attribute == TextAttribute.FONT) {
- return toIteratorIndex(getFontBoundary(i, 1));
- }
- }
- return getEndIndex();
- }
-
- /**
- * Returns the index of the first character following the run
- * with respect to the given attributes containing the current character.
- */
- public int getRunLimit(Set attributes) {
- int index = getEndIndex();
- Object[] a = attributes.toArray();
- for (int i = 0; i < a.length; i++) {
- TextAttribute attr = (TextAttribute) a[i];
- index = Math.min(getRunLimit(attr), index);
- }
- return Math.max(getIndex(), index);
- }
-
- /**
- * Returns a map with the attributes defined on the current
- * character.
- */
- public Map getAttributes() {
- Object[] ka = keys.toArray();
- Hashtable h = new Hashtable();
- for (int i = 0; i < ka.length; i++) {
- TextAttribute a = (TextAttribute) ka[i];
- Object value = getAttribute(a);
- if (value != null) {
- h.put(a, value);
- }
- }
- return h;
- }
-
- /**
- * Returns the value of the named attribute for the current character.
- * Returns null if the attribute is not defined.
- * @param attribute the key of the attribute whose value is requested.
- */
- public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
- int pos = toModelPosition(getIndex());
- int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
- if (attribute == TextAttribute.FONT) {
- return getFont(childIndex);
- } else if( attribute == TextAttribute.RUN_DIRECTION ) {
- return
- v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
- }
- return null;
- }
-
- /**
- * Returns the keys of all attributes defined on the
- * iterator's text range. The set is empty if no
- * attributes are defined.
- */
- public Set getAllAttributeKeys() {
- return keys;
- }
-
- View v;
-
- static Set keys;
-
- static {
- keys = new HashSet();
- keys.add(TextAttribute.FONT);
- keys.add(TextAttribute.RUN_DIRECTION);
- }
-
- }
-
- }