Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]
    1   /*
    2    * Copyright 1999-2005 Sun Microsystems, Inc.  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.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any 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.event.DocumentEvent;
   34   import sun.font.BidiUtils;
   35   
   36   /**
   37    * A flow strategy that uses java.awt.font.LineBreakMeasureer to
   38    * produce java.awt.font.TextLayout for i18n capable rendering.
   39    * If the child view being placed into the flow is of type
   40    * GlyphView and can be rendered by TextLayout, a GlyphPainter
   41    * that uses TextLayout is plugged into the GlyphView.
   42    *
   43    * @author  Timothy Prinzing
   44    */
   45   class TextLayoutStrategy extends FlowView.FlowStrategy {
   46   
   47       /**
   48        * Constructs a layout strategy for paragraphs based
   49        * upon java.awt.font.LineBreakMeasurer.
   50        */
   51       public TextLayoutStrategy() {
   52           text = new AttributedSegment();
   53       }
   54   
   55       // --- FlowStrategy methods --------------------------------------------
   56   
   57       /**
   58        * Gives notification that something was inserted into the document
   59        * in a location that the given flow view is responsible for.  The
   60        * strategy should update the appropriate changed region (which
   61        * depends upon the strategy used for repair).
   62        *
   63        * @param e the change information from the associated document
   64        * @param alloc the current allocation of the view inside of the insets.
   65        *   This value will be null if the view has not yet been displayed.
   66        * @see View#insertUpdate
   67        */
   68       public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
   69           sync(fv);
   70           super.insertUpdate(fv, e, alloc);
   71       }
   72   
   73       /**
   74        * Gives notification that something was removed from the document
   75        * in a location that the given flow view is responsible for.
   76        *
   77        * @param e the change information from the associated document
   78        * @param alloc the current allocation of the view inside of the insets.
   79        * @see View#removeUpdate
   80        */
   81       public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
   82           sync(fv);
   83           super.removeUpdate(fv, e, alloc);
   84       }
   85   
   86       /**
   87        * Gives notification from the document that attributes were changed
   88        * in a location that this view is responsible for.
   89        *
   90        * @param changes the change information from the associated document
   91        * @param a the current allocation of the view
   92        * @param f the factory to use to rebuild if the view has children
   93        * @see View#changedUpdate
   94        */
   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 v the view to reflow
  107        */
  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 row the row to fill in with views.  This is assumed
  120        *   to be empty on entry.
  121        * @param pos  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        */
  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 r 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        */
  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        */
  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        */
  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        */
  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           measurer = new LineBreakMeasurer(text, iter, frc);
  305   
  306           // If the children of the FlowView's logical view are GlyphViews, they
  307           // need to have their painters updated.
  308           int n = lv.getViewCount();
  309           for( int i=0; i<n; i++ ) {
  310               View child = lv.getView(i);
  311               if( child instanceof GlyphView ) {
  312                   int p0 = child.getStartOffset();
  313                   int p1 = child.getEndOffset();
  314                   measurer.setPosition(text.toIteratorIndex(p0));
  315                   TextLayout layout
  316                       = measurer.nextLayout( Float.MAX_VALUE,
  317                                              text.toIteratorIndex(p1), false );
  318                   ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout));
  319               }
  320           }
  321   
  322           // Reset measurer.
  323           measurer.setPosition(text.getBeginIndex());
  324   
  325       }
  326   
  327       // --- variables -------------------------------------------------------
  328   
  329       private LineBreakMeasurer measurer;
  330       private AttributedSegment text;
  331   
  332       /**
  333        * Implementation of AttributedCharacterIterator that supports
  334        * the GlyphView attributes for rendering the glyphs through a
  335        * TextLayout.
  336        */
  337       static class AttributedSegment extends Segment implements AttributedCharacterIterator {
  338   
  339           AttributedSegment() {
  340           }
  341   
  342           View getView() {
  343               return v;
  344           }
  345   
  346           void setView(View v) {
  347               this.v = v;
  348               Document doc = v.getDocument();
  349               int p0 = v.getStartOffset();
  350               int p1 = v.getEndOffset();
  351               try {
  352                   doc.getText(p0, p1 - p0, this);
  353               } catch (BadLocationException bl) {
  354                   throw new IllegalArgumentException("Invalid view");
  355               }
  356               first();
  357           }
  358   
  359           /**
  360            * Get a boundary position for the font.
  361            * This is implemented to assume that two fonts are
  362            * equal if their references are equal (i.e. that the
  363            * font came from a cache).
  364            *
  365            * @return the location in model coordinates.  This is
  366            *  not the same as the Segment coordinates.
  367            */
  368           int getFontBoundary(int childIndex, int dir) {
  369               View child = v.getView(childIndex);
  370               Font f = getFont(childIndex);
  371               for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
  372                    childIndex += dir) {
  373                   Font next = getFont(childIndex);
  374                   if (next != f) {
  375                       // this run is different
  376                       break;
  377                   }
  378                   child = v.getView(childIndex);
  379               }
  380               return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
  381           }
  382   
  383           /**
  384            * Get the font at the given child index.
  385            */
  386           Font getFont(int childIndex) {
  387               View child = v.getView(childIndex);
  388               if (child instanceof GlyphView) {
  389                   return ((GlyphView)child).getFont();
  390               }
  391               return null;
  392           }
  393   
  394           int toModelPosition(int index) {
  395               return v.getStartOffset() + (index - getBeginIndex());
  396           }
  397   
  398           int toIteratorIndex(int pos) {
  399               return pos - v.getStartOffset() + getBeginIndex();
  400           }
  401   
  402           // --- AttributedCharacterIterator methods -------------------------
  403   
  404           /**
  405            * Returns the index of the first character of the run
  406            * with respect to all attributes containing the current character.
  407            */
  408           public int getRunStart() {
  409               int pos = toModelPosition(getIndex());
  410               int i = v.getViewIndex(pos, Position.Bias.Forward);
  411               View child = v.getView(i);
  412               return toIteratorIndex(child.getStartOffset());
  413           }
  414   
  415           /**
  416            * Returns the index of the first character of the run
  417            * with respect to the given attribute containing the current character.
  418            */
  419           public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
  420               if (attribute instanceof TextAttribute) {
  421                   int pos = toModelPosition(getIndex());
  422                   int i = v.getViewIndex(pos, Position.Bias.Forward);
  423                   if (attribute == TextAttribute.FONT) {
  424                       return toIteratorIndex(getFontBoundary(i, -1));
  425                   }
  426               }
  427               return getBeginIndex();
  428           }
  429   
  430           /**
  431            * Returns the index of the first character of the run
  432            * with respect to the given attributes containing the current character.
  433            */
  434           public int getRunStart(Set<? extends Attribute> attributes) {
  435               int index = getBeginIndex();
  436               Object[] a = attributes.toArray();
  437               for (int i = 0; i < a.length; i++) {
  438                   TextAttribute attr = (TextAttribute) a[i];
  439                   index = Math.max(getRunStart(attr), index);
  440               }
  441               return Math.min(getIndex(), index);
  442           }
  443   
  444           /**
  445            * Returns the index of the first character following the run
  446            * with respect to all attributes containing the current character.
  447            */
  448           public int getRunLimit() {
  449               int pos = toModelPosition(getIndex());
  450               int i = v.getViewIndex(pos, Position.Bias.Forward);
  451               View child = v.getView(i);
  452               return toIteratorIndex(child.getEndOffset());
  453           }
  454   
  455           /**
  456            * Returns the index of the first character following the run
  457            * with respect to the given attribute containing the current character.
  458            */
  459           public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
  460               if (attribute instanceof TextAttribute) {
  461                   int pos = toModelPosition(getIndex());
  462                   int i = v.getViewIndex(pos, Position.Bias.Forward);
  463                   if (attribute == TextAttribute.FONT) {
  464                       return toIteratorIndex(getFontBoundary(i, 1));
  465                   }
  466               }
  467               return getEndIndex();
  468           }
  469   
  470           /**
  471            * Returns the index of the first character following the run
  472            * with respect to the given attributes containing the current character.
  473            */
  474           public int getRunLimit(Set<? extends Attribute> attributes) {
  475               int index = getEndIndex();
  476               Object[] a = attributes.toArray();
  477               for (int i = 0; i < a.length; i++) {
  478                   TextAttribute attr = (TextAttribute) a[i];
  479                   index = Math.min(getRunLimit(attr), index);
  480               }
  481               return Math.max(getIndex(), index);
  482           }
  483   
  484           /**
  485            * Returns a map with the attributes defined on the current
  486            * character.
  487            */
  488           public Map getAttributes() {
  489               Object[] ka = keys.toArray();
  490               Hashtable h = new Hashtable();
  491               for (int i = 0; i < ka.length; i++) {
  492                   TextAttribute a = (TextAttribute) ka[i];
  493                   Object value = getAttribute(a);
  494                   if (value != null) {
  495                       h.put(a, value);
  496                   }
  497               }
  498               return h;
  499           }
  500   
  501           /**
  502            * Returns the value of the named attribute for the current character.
  503            * Returns null if the attribute is not defined.
  504            * @param attribute the key of the attribute whose value is requested.
  505            */
  506           public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
  507               int pos = toModelPosition(getIndex());
  508               int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
  509               if (attribute == TextAttribute.FONT) {
  510                   return getFont(childIndex);
  511               } else if( attribute == TextAttribute.RUN_DIRECTION ) {
  512                   return
  513                       v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
  514               }
  515               return null;
  516           }
  517   
  518           /**
  519            * Returns the keys of all attributes defined on the
  520            * iterator's text range. The set is empty if no
  521            * attributes are defined.
  522            */
  523           public Set getAllAttributeKeys() {
  524               return keys;
  525           }
  526   
  527           View v;
  528   
  529           static Set keys;
  530   
  531           static {
  532               keys = new HashSet();
  533               keys.add(TextAttribute.FONT);
  534               keys.add(TextAttribute.RUN_DIRECTION);
  535           }
  536   
  537       }
  538   
  539   }

Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]