555 lines · 20.6 KB · Java
RawDownload
/* * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */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.JComponent;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 */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 fv 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();        }         Object shaper = null;        if (c instanceof JComponent) {            shaper = ((JComponent) c).getClientProperty(                                            TextAttribute.NUMERIC_SHAPING);        }        text.setShaper(shaper);         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();        }         private void setShaper(Object shaper) {            this.shaper = shaper;        }         // --- 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<Attribute, Object> getAttributes() {

Lines 1–500 of 555.