Package javax.swing.text

Source Code of javax.swing.text.TextLayoutStrategy

/*
* @(#)TextLayoutStrategy.java  1.25 05/11/30
*
* Copyright 2006 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.25 11/30/05
*/
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 = (viewBuffer.size() == 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 = sun.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);
  }

    }

}
TOP

Related Classes of javax.swing.text.TextLayoutStrategy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.