/*
* 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;
}
}