CaretManagerpublic class CaretManager extends Object This class provides functionality for creating caret and highlight shapes
(bidirectional text is also supported, but, unfortunately, not tested yet). |
Fields Summary |
---|
private TextRunBreaker | breaker |
Constructors Summary |
---|
public CaretManager(TextRunBreaker breaker)
this.breaker = breaker;
|
Methods Summary |
---|
private void | checkHit(java.awt.font.TextHitInfo info)Checks if TextHitInfo is not out of the text range and throws the
IllegalArgumentException if it is.
int idx = info.getInsertionIndex();
if (idx < 0 || idx > breaker.getCharCount()) {
// awt.42=TextHitInfo out of range
throw new IllegalArgumentException(Messages.getString("awt.42")); //$NON-NLS-1$
}
| java.awt.geom.GeneralPath | connectCarets(java.awt.geom.Line2D caret1, java.awt.geom.Line2D caret2)Connects two carets to produce a highlight shape.
GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
path.moveTo((float) caret1.getX1(), (float) caret1.getY1());
path.lineTo((float) caret2.getX1(), (float) caret2.getY1());
path.lineTo((float) caret2.getX2(), (float) caret2.getY2());
path.lineTo((float) caret1.getX2(), (float) caret1.getY2());
path.closePath();
return path;
| public float[] | getCaretInfo(java.awt.font.TextHitInfo hitInfo)Creates caret info. Required for the getCaretInfo
methods of the TextLayout
checkHit(hitInfo);
float res[] = new float[2];
int visual = getVisualFromHitInfo(hitInfo);
float advance, angle;
TextRunSegment seg;
if (visual < breaker.getCharCount()) {
int logIdx = breaker.getLogicalFromVisual(visual);
int segmentIdx = breaker.logical2segment[logIdx];
seg = breaker.runSegments.get(segmentIdx);
advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx);
angle = seg.metrics.italicAngle;
} else { // Last character
int logIdx = breaker.getLogicalFromVisual(visual-1);
int segmentIdx = breaker.logical2segment[logIdx];
seg = breaker.runSegments.get(segmentIdx);
advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx+1);
}
angle = seg.metrics.italicAngle;
res[0] = advance;
res[1] = angle;
return res;
| public java.awt.geom.Line2D | getCaretShape(java.awt.font.TextHitInfo hitInfo, java.awt.font.TextLayout layout, boolean useItalic, boolean useBounds, java.awt.geom.Rectangle2D bounds)Creates a caret shape.
checkHit(hitInfo);
float x1, x2, y1, y2;
int charIdx = hitInfo.getCharIndex();
if (charIdx >= 0 && charIdx < breaker.getCharCount()) {
TextRunSegment segment = breaker.runSegments.get(breaker.logical2segment[charIdx]);
y1 = segment.metrics.descent;
y2 = - segment.metrics.ascent - segment.metrics.leading;
x1 = x2 = segment.getCharPosition(charIdx) + (hitInfo.isLeadingEdge() ?
0 : segment.getCharAdvance(charIdx));
// Decided that straight cursor looks better even for italic fonts,
// especially combined with highlighting
/*
// Not graphics, need to check italic angle and baseline
if (layout.getBaseline() >= 0) {
if (segment.metrics.italicAngle != 0 && useItalic) {
x1 -= segment.metrics.italicAngle * segment.metrics.descent;
x2 += segment.metrics.italicAngle *
(segment.metrics.ascent + segment.metrics.leading);
float baselineOffset =
layout.getBaselineOffsets()[layout.getBaseline()];
y1 += baselineOffset;
y2 += baselineOffset;
}
}
*/
} else {
y1 = layout.getDescent();
y2 = - layout.getAscent() - layout.getLeading();
x1 = x2 = ((breaker.getBaseLevel() & 0x1) == 0 ^ charIdx < 0) ?
layout.getAdvance() : 0;
}
if (useBounds) {
y1 = (float) bounds.getMaxY();
y2 = (float) bounds.getMinY();
if (x2 > bounds.getMaxX()) {
x1 = x2 = (float) bounds.getMaxX();
}
if (x1 < bounds.getMinX()) {
x1 = x2 = (float) bounds.getMinX();
}
}
return new Line2D.Float(x1, y1, x2, y2);
| public java.awt.geom.Line2D | getCaretShape(java.awt.font.TextHitInfo hitInfo, java.awt.font.TextLayout layout)
return getCaretShape(hitInfo, layout, true, false, null);
| public java.awt.Shape[] | getCaretShapes(int offset, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout$CaretPolicy policy, java.awt.font.TextLayout layout)Creates caret shapes for the specified offset. On the boundaries where
the text is changing its direction this method may return two shapes
for the strong and the weak carets, in other cases it would return one.
TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
TextHitInfo hit2 = getVisualOtherHit(hit1);
Shape caret1 = getCaretShape(hit1, layout);
if (getVisualFromHitInfo(hit1) == getVisualFromHitInfo(hit2)) {
return new Shape[] {caret1, null};
}
Shape caret2 = getCaretShape(hit2, layout);
TextHitInfo strongHit = policy.getStrongCaret(hit1, hit2, layout);
return strongHit.equals(hit1) ?
new Shape[] {caret1, caret2} :
new Shape[] {caret2, caret1};
| private java.awt.font.TextHitInfo | getHitInfoFromVisual(int visual)Calculates text hit info from the visual position
final boolean first = visual == 0;
if (!(first || visual == breaker.getCharCount())) {
int logical = breaker.getLogicalFromVisual(visual);
return (breaker.getLevel(logical) & 0x1) == 0x0 ?
TextHitInfo.leading(logical) : // LTR
TextHitInfo.trailing(logical); // RTL
} else if (first) {
return breaker.isLTR() ?
TextHitInfo.trailing(-1) :
TextHitInfo.leading(breaker.getCharCount());
} else { // Last
return breaker.isLTR() ?
TextHitInfo.leading(breaker.getCharCount()) :
TextHitInfo.trailing(-1);
}
| public java.awt.Shape | getLogicalHighlightShape(int firstEndpoint, int secondEndpoint, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout layout)Creates a highlight shape from given two endpoints in the logical
representation. This shape is not always visually contiguous
GeneralPath res = new GeneralPath();
for (int i=firstEndpoint; i<=secondEndpoint; i++) {
int endRun = breaker.getLevelRunLimit(i, secondEndpoint);
TextHitInfo hit1 = TextHitInfo.leading(i);
TextHitInfo hit2 = TextHitInfo.trailing(endRun-1);
Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);
res.append(connectCarets(caret1, caret2), false);
i = endRun;
}
return res;
| public int[] | getLogicalRangesForVisualSelection(java.awt.font.TextHitInfo hit1, java.awt.font.TextHitInfo hit2)Suppose that the user visually selected a block of text which has
several different levels (mixed RTL and LTR), so, in the logical
representation of the text this selection may be not contigous.
This methods returns a set of logical ranges for the arbitrary
visual selection represented by two hits.
checkHit(hit1);
checkHit(hit2);
int visual1 = getVisualFromHitInfo(hit1);
int visual2 = getVisualFromHitInfo(hit2);
if (visual1 > visual2) {
int tmp = visual2;
visual2 = visual1;
visual1 = tmp;
}
// Max level is 255, so we don't need more than 512 entries
int results[] = new int[512];
int prevLogical, logical, runStart, numRuns = 0;
logical = runStart = prevLogical = breaker.getLogicalFromVisual(visual1);
// Get all the runs. We use the fact that direction is constant in all runs.
for (int i=visual1+1; i<=visual2; i++) {
logical = breaker.getLogicalFromVisual(i);
int diff = logical-prevLogical;
// Start of the next run encountered
if (diff > 1 || diff < -1) {
results[(numRuns)*2] = Math.min(runStart, prevLogical);
results[(numRuns)*2 + 1] = Math.max(runStart, prevLogical);
numRuns++;
runStart = logical;
}
prevLogical = logical;
}
// The last unsaved run
results[(numRuns)*2] = Math.min(runStart, logical);
results[(numRuns)*2 + 1] = Math.max(runStart, logical);
numRuns++;
int retval[] = new int[numRuns*2];
System.arraycopy(results, 0, retval, 0, numRuns*2);
return retval;
| public java.awt.font.TextHitInfo | getNextLeftHit(java.awt.font.TextHitInfo hitInfo)Returns the next position to the left from the current caret position
checkHit(hitInfo);
int visual = getVisualFromHitInfo(hitInfo);
if (visual == 0) {
return null;
}
TextHitInfo newInfo;
while(visual >= 0) {
visual--;
newInfo = getHitInfoFromVisual(visual);
if (newInfo.getCharIndex() < 0) {
return newInfo;
}
// Don't check for rightmost info
if (hitInfo.getCharIndex() < breaker.logical2segment.length) {
if (
breaker.logical2segment[newInfo.getCharIndex()] !=
breaker.logical2segment[hitInfo.getCharIndex()]
) {
return newInfo; // We crossed segment boundary
}
}
TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
.getCharIndex()]);
if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
return newInfo;
}
}
return null;
| public java.awt.font.TextHitInfo | getNextRightHit(java.awt.font.TextHitInfo hitInfo)Returns the next position to the right from the current caret position
checkHit(hitInfo);
int visual = getVisualFromHitInfo(hitInfo);
if (visual == breaker.getCharCount()) {
return null;
}
TextHitInfo newInfo;
while(visual <= breaker.getCharCount()) {
visual++;
newInfo = getHitInfoFromVisual(visual);
if (newInfo.getCharIndex() >= breaker.logical2segment.length) {
return newInfo;
}
if (hitInfo.getCharIndex() >= 0) { // Don't check for leftmost info
if (
breaker.logical2segment[newInfo.getCharIndex()] !=
breaker.logical2segment[hitInfo.getCharIndex()]
) {
return newInfo; // We crossed segment boundary
}
}
TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
.getCharIndex()]);
if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
return newInfo;
}
}
return null;
| private int | getVisualFromHitInfo(java.awt.font.TextHitInfo hitInfo)Calculates and returns visual position from the text hit info.
final int idx = hitInfo.getCharIndex();
if (idx >= 0 && idx < breaker.getCharCount()) {
int visual = breaker.getVisualFromLogical(idx);
// We take next character for (LTR char + TRAILING info) and (RTL + LEADING)
if (hitInfo.isLeadingEdge() ^ ((breaker.getLevel(idx) & 0x1) == 0x0)) {
visual++;
}
return visual;
} else if (idx < 0) {
return breaker.isLTR() ? 0: breaker.getCharCount();
} else {
return breaker.isLTR() ? breaker.getCharCount() : 0;
}
| public java.awt.Shape | getVisualHighlightShape(java.awt.font.TextHitInfo hit1, java.awt.font.TextHitInfo hit2, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout layout)Creates a highlight shape from given two hits. This shape
will always be visually contiguous
checkHit(hit1);
checkHit(hit2);
Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);
return connectCarets(caret1, caret2);
| public java.awt.font.TextHitInfo | getVisualOtherHit(java.awt.font.TextHitInfo hitInfo)For each visual caret position there are two hits. For the simple LTR text one is
a trailing of the previous char and another is the leading of the next char. This
method returns the opposite hit for the given hit.
checkHit(hitInfo);
int idx = hitInfo.getCharIndex();
int resIdx;
boolean resIsLeading;
if (idx >= 0 && idx < breaker.getCharCount()) { // Hit info in the middle
int visual = breaker.getVisualFromLogical(idx);
// Char is LTR + LEADING info
if (((breaker.getLevel(idx) & 0x1) == 0x0) ^ hitInfo.isLeadingEdge()) {
visual++;
if (visual == breaker.getCharCount()) {
if (breaker.isLTR()) {
resIdx = breaker.getCharCount();
resIsLeading = true;
} else {
resIdx = -1;
resIsLeading = false;
}
} else {
resIdx = breaker.getLogicalFromVisual(visual);
if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
resIsLeading = true;
} else {
resIsLeading = false;
}
}
} else {
visual--;
if (visual == -1) {
if (breaker.isLTR()) {
resIdx = -1;
resIsLeading = false;
} else {
resIdx = breaker.getCharCount();
resIsLeading = true;
}
} else {
resIdx = breaker.getLogicalFromVisual(visual);
if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
resIsLeading = false;
} else {
resIsLeading = true;
}
}
}
} else if (idx < 0) { // before "start"
if (breaker.isLTR()) {
resIdx = breaker.getLogicalFromVisual(0);
resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // LTR char?
} else {
resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // RTL char?
}
} else { // idx == breaker.getCharCount()
if (breaker.isLTR()) {
resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // LTR char?
} else {
resIdx = breaker.getLogicalFromVisual(0);
resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // RTL char?
}
}
return resIsLeading ? TextHitInfo.leading(resIdx) : TextHitInfo.trailing(resIdx);
|
|