/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.text;
import java.text.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.
Rectangle2D;
/**
* A class to perform rendering of the glyphs.
* This can be implemented to be stateless, or
* to hold some information as a cache to
* facilitate faster rendering and model/view
* translation. At a minimum, the GlyphPainter
* allows a View implementation to perform its
* duties independent of a particular version
* of JVM and selection of capabilities (i.e.
* shaping for i18n, etc).
* <p>
* This implementation is intended for operation
* under the JDK. It uses the
* java.awt.font.TextLayout class to do i18n capable
* rendering.
*
* @author Timothy Prinzing
* @see GlyphView
*/
class
GlyphPainter2 extends
GlyphView.
GlyphPainter {
public
GlyphPainter2(
TextLayout layout) {
this.
layout =
layout;
}
/**
* Create a painter to use for the given GlyphView.
*/
public
GlyphView.
GlyphPainter getPainter(
GlyphView v, int
p0, int
p1) {
return null;
}
/**
* Determine the span the glyphs given a start location
* (for tab expansion). This implementation assumes it
* has no tabs (i.e. TextLayout doesn't deal with tab
* expansion).
*/
public float
getSpan(
GlyphView v, int
p0, int
p1,
TabExpander e, float
x) {
if ((
p0 ==
v.
getStartOffset()) && (
p1 ==
v.
getEndOffset())) {
return
layout.
getAdvance();
}
int
p =
v.
getStartOffset();
int
index0 =
p0 -
p;
int
index1 =
p1 -
p;
TextHitInfo hit0 =
TextHitInfo.
afterOffset(
index0);
TextHitInfo hit1 =
TextHitInfo.
beforeOffset(
index1);
float[]
locs =
layout.
getCaretInfo(
hit0);
float
x0 =
locs[0];
locs =
layout.
getCaretInfo(
hit1);
float
x1 =
locs[0];
return (
x1 >
x0) ?
x1 -
x0 :
x0 -
x1;
}
public float
getHeight(
GlyphView v) {
return
layout.
getAscent() +
layout.
getDescent() +
layout.
getLeading();
}
/**
* Fetch the ascent above the baseline for the glyphs
* corresponding to the given range in the model.
*/
public float
getAscent(
GlyphView v) {
return
layout.
getAscent();
}
/**
* Fetch the descent below the baseline for the glyphs
* corresponding to the given range in the model.
*/
public float
getDescent(
GlyphView v) {
return
layout.
getDescent();
}
/**
* Paint the glyphs for the given view. This is implemented
* to only render if the Graphics is of type Graphics2D which
* is required by TextLayout (and this should be the case if
* running on the JDK).
*/
public void
paint(
GlyphView v,
Graphics g,
Shape a, int
p0, int
p1) {
if (
g instanceof
Graphics2D) {
Rectangle2D alloc =
a.
getBounds2D();
Graphics2D g2d = (
Graphics2D)
g;
float
y = (float)
alloc.
getY() +
layout.
getAscent() +
layout.
getLeading();
float
x = (float)
alloc.
getX();
if(
p0 >
v.
getStartOffset() ||
p1 <
v.
getEndOffset() ) {
try {
//TextLayout can't render only part of it's range, so if a
//partial range is required, add a clip region.
Shape s =
v.
modelToView(
p0,
Position.
Bias.
Forward,
p1,
Position.
Bias.
Backward,
a);
Shape savedClip =
g.
getClip();
g2d.
clip(
s);
layout.
draw(
g2d,
x,
y);
g.
setClip(
savedClip);
} catch (
BadLocationException e) {}
} else {
layout.
draw(
g2d,
x,
y);
}
}
}
public
Shape modelToView(
GlyphView v, int
pos,
Position.
Bias bias,
Shape a) throws
BadLocationException {
int
offs =
pos -
v.
getStartOffset();
Rectangle2D alloc =
a.
getBounds2D();
TextHitInfo hit = (
bias ==
Position.
Bias.
Forward) ?
TextHitInfo.
afterOffset(
offs) :
TextHitInfo.
beforeOffset(
offs);
float[]
locs =
layout.
getCaretInfo(
hit);
// vertical at the baseline, should use slope and check if glyphs
// are being rendered vertically.
alloc.
setRect(
alloc.
getX() +
locs[0],
alloc.
getY(), 1,
alloc.
getHeight());
return
alloc;
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param v the view containing the view coordinates
* @param x the X coordinate
* @param y the Y coordinate
* @param a the allocated region to render into
* @param biasReturn either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code> is returned as the
* zero-th element of this array
* @return the location within the model that best represents the
* given point of view
* @see View#viewToModel
*/
public int
viewToModel(
GlyphView v, float
x, float
y,
Shape a,
Position.
Bias[]
biasReturn) {
Rectangle2D alloc = (
a instanceof
Rectangle2D) ? (
Rectangle2D)
a :
a.
getBounds2D();
//Move the y co-ord of the hit onto the baseline. This is because TextLayout supports
//italic carets and we do not.
TextHitInfo hit =
layout.
hitTestChar(
x - (float)
alloc.
getX(), 0);
int
pos =
hit.
getInsertionIndex();
if (
pos ==
v.
getEndOffset()) {
pos--;
}
biasReturn[0] =
hit.
isLeadingEdge() ?
Position.
Bias.
Forward :
Position.
Bias.
Backward;
return
pos +
v.
getStartOffset();
}
/**
* Determines the model location that represents the
* maximum advance that fits within the given span.
* This could be used to break the given view. The result
* should be a location just shy of the given advance. This
* differs from viewToModel which returns the closest
* position which might be proud of the maximum advance.
*
* @param v the view to find the model location to break at.
* @param p0 the location in the model where the
* fragment should start it's representation >= 0.
* @param pos the graphic location along the axis that the
* broken view would occupy >= 0. This may be useful for
* things like tab calculations.
* @param len specifies the distance into the view
* where a potential break is desired >= 0.
* @return the maximum model location possible for a break.
* @see View#breakView
*/
public int
getBoundedPosition(
GlyphView v, int
p0, float
x, float
len) {
if(
len < 0 )
throw new
IllegalArgumentException("Length must be >= 0.");
// note: this only works because swing uses TextLayouts that are
// only pure rtl or pure ltr
TextHitInfo hit;
if (
layout.
isLeftToRight()) {
hit =
layout.
hitTestChar(
len, 0);
} else {
hit =
layout.
hitTestChar(
layout.
getAdvance() -
len, 0);
}
return
v.
getStartOffset() +
hit.
getCharIndex();
}
/**
* Provides a way to determine the next visually represented model
* location that one might place a caret. Some views may not be
* visible, they might not be in the same order found in the model, or
* they just might not allow access to some of the locations in the
* model.
*
* @param v the view to use
* @param pos the position to convert >= 0
* @param a the allocated region to render into
* @param direction the direction from the current position that can
* be thought of as the arrow keys typically found on a keyboard.
* This may be SwingConstants.WEST, SwingConstants.EAST,
* SwingConstants.NORTH, or SwingConstants.SOUTH.
* @return the location within the model that best represents the next
* location visual position.
* @exception BadLocationException
* @exception IllegalArgumentException for an invalid direction
*/
public int
getNextVisualPositionFrom(
GlyphView v, int
pos,
Position.
Bias b,
Shape a,
int
direction,
Position.
Bias[]
biasRet)
throws
BadLocationException {
Document doc =
v.
getDocument();
int
startOffset =
v.
getStartOffset();
int
endOffset =
v.
getEndOffset();
Segment text;
boolean
viewIsLeftToRight;
TextHitInfo currentHit,
nextHit;
switch (
direction) {
case
View.
NORTH:
break;
case
View.
SOUTH:
break;
case
View.
EAST:
viewIsLeftToRight =
AbstractDocument.
isLeftToRight(
doc,
startOffset,
endOffset);
if(
startOffset ==
doc.
getLength()) {
if(
pos == -1) {
biasRet[0] =
Position.
Bias.
Forward;
return
startOffset;
}
// End case for bidi text where newline is at beginning
// of line.
return -1;
}
if(
pos == -1) {
// Entering view from the left.
if(
viewIsLeftToRight ) {
biasRet[0] =
Position.
Bias.
Forward;
return
startOffset;
} else {
text =
v.
getText(
endOffset - 1,
endOffset);
char
c =
text.
array[
text.
offset];
SegmentCache.
releaseSharedSegment(
text);
if(
c == '\n') {
biasRet[0] =
Position.
Bias.
Forward;
return
endOffset-1;
}
biasRet[0] =
Position.
Bias.
Backward;
return
endOffset;
}
}
if(
b==
Position.
Bias.
Forward )
currentHit =
TextHitInfo.
afterOffset(
pos-
startOffset);
else
currentHit =
TextHitInfo.
beforeOffset(
pos-
startOffset);
nextHit =
layout.
getNextRightHit(
currentHit);
if(
nextHit == null ) {
return -1;
}
if(
viewIsLeftToRight !=
layout.
isLeftToRight() ) {
// If the layout's base direction is different from
// this view's run direction, we need to use the weak
// carrat.
nextHit =
layout.
getVisualOtherHit(
nextHit);
}
pos =
nextHit.
getInsertionIndex() +
startOffset;
if(
pos ==
endOffset) {
// A move to the right from an internal position will
// only take us to the endOffset in a left to right run.
text =
v.
getText(
endOffset - 1,
endOffset);
char
c =
text.
array[
text.
offset];
SegmentCache.
releaseSharedSegment(
text);
if(
c == '\n') {
return -1;
}
biasRet[0] =
Position.
Bias.
Backward;
}
else {
biasRet[0] =
Position.
Bias.
Forward;
}
return
pos;
case
View.
WEST:
viewIsLeftToRight =
AbstractDocument.
isLeftToRight(
doc,
startOffset,
endOffset);
if(
startOffset ==
doc.
getLength()) {
if(
pos == -1) {
biasRet[0] =
Position.
Bias.
Forward;
return
startOffset;
}
// End case for bidi text where newline is at beginning
// of line.
return -1;
}
if(
pos == -1) {
// Entering view from the right
if(
viewIsLeftToRight ) {
text =
v.
getText(
endOffset - 1,
endOffset);
char
c =
text.
array[
text.
offset];
SegmentCache.
releaseSharedSegment(
text);
if ((
c == '\n') ||
Character.
isSpaceChar(
c)) {
biasRet[0] =
Position.
Bias.
Forward;
return
endOffset - 1;
}
biasRet[0] =
Position.
Bias.
Backward;
return
endOffset;
} else {
biasRet[0] =
Position.
Bias.
Forward;
return
startOffset;
}
}
if(
b==
Position.
Bias.
Forward )
currentHit =
TextHitInfo.
afterOffset(
pos-
startOffset);
else
currentHit =
TextHitInfo.
beforeOffset(
pos-
startOffset);
nextHit =
layout.
getNextLeftHit(
currentHit);
if(
nextHit == null ) {
return -1;
}
if(
viewIsLeftToRight !=
layout.
isLeftToRight() ) {
// If the layout's base direction is different from
// this view's run direction, we need to use the weak
// carrat.
nextHit =
layout.
getVisualOtherHit(
nextHit);
}
pos =
nextHit.
getInsertionIndex() +
startOffset;
if(
pos ==
endOffset) {
// A move to the left from an internal position will
// only take us to the endOffset in a right to left run.
text =
v.
getText(
endOffset - 1,
endOffset);
char
c =
text.
array[
text.
offset];
SegmentCache.
releaseSharedSegment(
text);
if(
c == '\n') {
return -1;
}
biasRet[0] =
Position.
Bias.
Backward;
}
else {
biasRet[0] =
Position.
Bias.
Forward;
}
return
pos;
default:
throw new
IllegalArgumentException("Bad direction: " +
direction);
}
return
pos;
}
// --- variables ---------------------------------------------
TextLayout layout;
}