You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
20 KiB
556 lines
20 KiB
/*
|
|
* Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE 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.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() {
|
|
Object[] ka = keys.toArray();
|
|
Hashtable<Attribute, Object> h = new Hashtable<Attribute, Object>();
|
|
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);
|
|
} else if (attribute == TextAttribute.NUMERIC_SHAPING) {
|
|
return shaper;
|
|
}
|
|
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<Attribute> getAllAttributeKeys() {
|
|
return keys;
|
|
}
|
|
|
|
View v;
|
|
|
|
static Set<Attribute> keys;
|
|
|
|
static {
|
|
keys = new HashSet<Attribute>();
|
|
keys.add(TextAttribute.FONT);
|
|
keys.add(TextAttribute.RUN_DIRECTION);
|
|
keys.add(TextAttribute.NUMERIC_SHAPING);
|
|
}
|
|
|
|
private Object shaper = null;
|
|
}
|
|
|
|
}
|