FileDocCategorySizeDatePackage
TextLayoutStrategy.javaAPI DocJava SE 5 API17158Fri Aug 26 14:58:16 BST 2005javax.swing.text

TextLayoutStrategy.java

/*
 * @(#)TextLayoutStrategy.java	1.22 04/05/05
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package javax.swing.text;

import java.util.*;
import java.awt.*;
import java.text.AttributedCharacterIterator;
import java.text.BreakIterator;
import java.awt.font.*;
import java.awt.geom.AffineTransform;
import javax.swing.event.DocumentEvent;
import sun.font.BidiUtils;

/**
 * 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.22 05/05/04
 */
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.  
     * @return 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;
		}
		
		BidiUtils.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);

        Container container = fv.getContainer();
        FontRenderContext frc = com.sun.java.swing.SwingUtilities2.
                                    getFontRenderContext(container);
        BreakIterator iter;
        Container c = fv.getContainer();
        if (c != null) {
            iter = BreakIterator.getLineInstance(c.getLocale());
        } else {
            iter = BreakIterator.getLineInstance();
        }

        measurer = new LineBreakMeasurer(text, iter, 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).
	 *
	 * @return 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<? extends Attribute> 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<? extends Attribute> 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);
	}

    }

}