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

    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   }

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