blob: 0ec2d05dae46f0073bb0c00c9587548cd165ead3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Oleg V. Khaschansky
* @version $Revision$
*
*/
package org.apache.harmony.awt.gl.font;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
// XXX - TODO - bidi not implemented yet
//import java.text.Bidi;
import java.util.Arrays;
import org.apache.harmony.awt.internal.nls.Messages;
/**
* Date: Apr 25, 2005
* Time: 4:33:18 PM
*
* This class contains the implementation of the behavior of the
* text run segment with constant text attributes and direction.
*/
public class TextRunSegmentImpl {
/**
* This class contains basic information required for creation
* of the glyph-based text run segment.
*/
public static class TextSegmentInfo {
// XXX - TODO - bidi not implemented yet
//Bidi bidi;
Font font;
FontRenderContext frc;
char text[];
int start;
int end;
int length;
int flags = 0;
byte level = 0;
TextSegmentInfo(
byte level,
Font font, FontRenderContext frc,
char text[], int start, int end
) {
this.font = font;
this.frc = frc;
this.text = text;
this.start = start;
this.end = end;
this.level = level;
length = end - start;
}
}
/**
* This class represents a simple text segment backed by the glyph vector
*/
public static class TextRunSegmentCommon extends TextRunSegment {
TextSegmentInfo info;
private GlyphVector gv;
private float advanceIncrements[];
private int char2glyph[];
private GlyphJustificationInfo gjis[]; // Glyph justification info
TextRunSegmentCommon(TextSegmentInfo i, TextDecorator.Decoration d) {
// XXX - todo - check support bidi
i.flags &= ~0x09; // Clear bidi flags
if ((i.level & 0x1) != 0) {
i.flags |= Font.LAYOUT_RIGHT_TO_LEFT;
}
info = i;
this.decoration = d;
LineMetrics lm = i.font.getLineMetrics(i.text, i.start, i.end, i.frc);
this.metrics = new BasicMetrics(lm, i.font);
if (lm.getNumChars() != i.length) { // XXX todo - This should be handled
// awt.41=Font returned unsupported type of line metrics. This case is known, but not supported yet.
throw new UnsupportedOperationException(
Messages.getString("awt.41")); //$NON-NLS-1$
}
}
@Override
public Object clone() {
return new TextRunSegmentCommon(info, decoration);
}
/**
* Creates glyph vector from the managed text if needed
* @return glyph vector
*/
private GlyphVector getGlyphVector() {
if (gv==null) {
gv = info.font.layoutGlyphVector(
info.frc,
info.text,
info.start,
info.end - info.start, // NOTE: This parameter violates
// spec, it is count,
// not limit as spec states
info.flags
);
}
return gv;
}
/**
* Renders this text run segment
* @param g2d - graphics to render to
* @param xOffset - X offset from the graphics origin to the
* origin of the text layout
* @param yOffset - Y offset from the graphics origin to the
* origin of the text layout
*/
@Override
void draw(Graphics2D g2d, float xOffset, float yOffset) {
if (decoration == null) {
g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y);
} else {
TextDecorator.prepareGraphics(this, g2d, xOffset, yOffset);
g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y);
TextDecorator.drawTextDecorations(this, g2d, xOffset, yOffset);
TextDecorator.restoreGraphics(decoration, g2d);
}
}
/**
* Returns visual bounds of this segment
* @return visual bounds
*/
@Override
Rectangle2D getVisualBounds() {
if (visualBounds == null) {
visualBounds =
TextDecorator.extendVisualBounds(
this,
getGlyphVector().getVisualBounds(),
decoration
);
visualBounds.setRect(
x + visualBounds.getX(),
y + visualBounds.getY(),
visualBounds.getWidth(),
visualBounds.getHeight()
);
}
return (Rectangle2D) visualBounds.clone();
}
/**
* Returns logical bounds of this segment
* @return logical bounds
*/
@Override
Rectangle2D getLogicalBounds() {
if (logicalBounds == null) {
logicalBounds = getGlyphVector().getLogicalBounds();
logicalBounds.setRect(
x + logicalBounds.getX(),
y + logicalBounds.getY(),
logicalBounds.getWidth(),
logicalBounds.getHeight()
);
}
return (Rectangle2D) logicalBounds.clone();
}
@Override
float getAdvance() {
return (float) getLogicalBounds().getWidth();
}
/**
* Attemts to map each character to the corresponding advance increment
*/
void initAdvanceMapping() {
GlyphVector gv = getGlyphVector();
int charIndicies[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), null);
advanceIncrements = new float[info.length];
for (int i=0; i<charIndicies.length; i++) {
advanceIncrements[charIndicies[i]] = gv.getGlyphMetrics(i).getAdvance();
}
}
/**
* Calculates advance delta between two characters
* @param start - 1st position
* @param end - 2nd position
* @return advance increment between specified positions
*/
@Override
float getAdvanceDelta(int start, int end) {
// Get coordinates in the segment context
start -= info.start;
end -= info.start;
if (advanceIncrements == null) {
initAdvanceMapping();
}
if (start < 0) {
start = 0;
}
if (end > info.length) {
end = info.length;
}
float sum = 0;
for (int i=start; i<end; i++) {
sum += advanceIncrements[i];
}
return sum;
}
/**
* Calculates index of the character which advance is equal to
* the given. If the given advance is greater then the segment
* advance it returns the position after the last character.
* @param advance - given advance
* @param start - character, from which to start measuring advance
* @return character index
*/
@Override
int getCharIndexFromAdvance(float advance, int start) {
// XXX - todo - probably, possible to optimize
// Add check if the given advance is greater then
// the segment advance in the beginning. In this case
// we don't need to run through all increments
if (advanceIncrements == null) {
initAdvanceMapping();
}
start -= info.start;
if (start < 0) {
start = 0;
}
int i = start;
for (; i<info.length; i++) {
advance -= advanceIncrements[i];
if (advance < 0) {
break;
}
}
return i + info.start;
}
@Override
int getStart() {
return info.start;
}
@Override
int getEnd() {
return info.end;
}
@Override
int getLength() {
return info.length;
}
/**
* Attemts to create mapping of the characters to glyphs in the glyph vector.
* @return array where for each character index stored corresponding glyph index
*/
private int[] getChar2Glyph() {
if (char2glyph == null) {
GlyphVector gv = getGlyphVector();
char2glyph = new int[info.length];
Arrays.fill(char2glyph, -1);
// Fill glyph indicies for first characters corresponding to each glyph
int charIndicies[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), null);
for (int i=0; i<charIndicies.length; i++) {
char2glyph[charIndicies[i]] = i;
}
// If several characters corresponds to one glyph, create mapping for them
// Suppose that these characters are going all together
int currIndex = 0;
for (int i=0; i<char2glyph.length; i++) {
if (char2glyph[i] < 0) {
char2glyph[i] = currIndex;
} else {
currIndex = char2glyph[i];
}
}
}
return char2glyph;
}
/**
* Creates black box bounds shape for the specified range
* @param start - range sart
* @param limit - range end
* @return black box bounds shape
*/
@Override
Shape getCharsBlackBoxBounds(int start, int limit) {
start -= info.start;
limit -= info.start;
if (limit > info.length) {
limit = info.length;
}
GeneralPath result = new GeneralPath();
int glyphIndex = 0;
for (int i=start; i<limit; i++) {
glyphIndex = getChar2Glyph()[i];
result.append(getGlyphVector().getGlyphVisualBounds(glyphIndex), false);
}
// Shift to the segment's coordinates
result.transform(AffineTransform.getTranslateInstance(x, y));
return result;
}
/**
* Calculates position of the character on the screen
* @param index - character index
* @return X coordinate of the character position
*/
@Override
float getCharPosition(int index) {
index -= info.start;
if (index > info.length) {
index = info.length;
}
float result = 0;
int glyphIndex = getChar2Glyph()[index];
result = (float) getGlyphVector().getGlyphPosition(glyphIndex).getX();
// Shift to the segment's coordinates
result += x;
return result;
}
/**
* Returns the advance of the individual character
* @param index - character index
* @return character advance
*/
@Override
float getCharAdvance(int index) {
if (advanceIncrements == null) {
initAdvanceMapping();
}
return advanceIncrements[index - this.getStart()];
}
/**
* Returns the outline shape
* @return outline
*/
@Override
Shape getOutline() {
AffineTransform t = AffineTransform.getTranslateInstance(x, y);
return t.createTransformedShape(
TextDecorator.extendOutline(
this,
getGlyphVector().getOutline(),
decoration
)
);
}
/**
* Checks if the character doesn't contribute to the text advance
* @param index - character index
* @return true if the character has zero advance
*/
@Override
boolean charHasZeroAdvance(int index) {
if (advanceIncrements == null) {
initAdvanceMapping();
}
return advanceIncrements[index - this.getStart()] == 0;
}
/**
* Creates text hit info from the hit position
* @param hitX - X coordinate relative to the origin of the layout
* @param hitY - Y coordinate relative to the origin of the layout
* @return hit info
*/
@Override
TextHitInfo hitTest(float hitX, float hitY) {
hitX -= x;
float glyphPositions[] =
getGlyphVector().getGlyphPositions(0, info.length+1, null);
int glyphIdx;
boolean leading = false;
for (glyphIdx = 1; glyphIdx <= info.length; glyphIdx++) {
if (glyphPositions[(glyphIdx)*2] >= hitX) {
float advance =
glyphPositions[(glyphIdx)*2] - glyphPositions[(glyphIdx-1)*2];
leading = glyphPositions[(glyphIdx-1)*2] + advance/2 > hitX ? true : false;
glyphIdx--;
break;
}
}
if (glyphIdx == info.length) {
glyphIdx--;
}
int charIdx = getGlyphVector().getGlyphCharIndex(glyphIdx);
return (leading) ^ ((info.level & 0x1) == 0x1)?
TextHitInfo.leading(charIdx + info.start) :
TextHitInfo.trailing(charIdx + info.start);
}
/**
* Collects GlyphJustificationInfo objects from the glyph vector
* @return array of all GlyphJustificationInfo objects
*/
private GlyphJustificationInfo[] getGlyphJustificationInfos() {
if (gjis == null) {
GlyphVector gv = getGlyphVector();
int nGlyphs = gv.getNumGlyphs();
int charIndicies[] = gv.getGlyphCharIndices(0, nGlyphs, null);
gjis = new GlyphJustificationInfo[nGlyphs];
// Patch: temporary patch, getGlyphJustificationInfo is not implemented
float fontSize = info.font.getSize2D();
GlyphJustificationInfo defaultInfo =
new GlyphJustificationInfo(
0, // weight
false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0, // grow
false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0); // shrink
GlyphJustificationInfo spaceInfo = new GlyphJustificationInfo(
fontSize, // weight
true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize, // grow
true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize); // shrink
////////
// Temporary patch, getGlyphJustificationInfo is not implemented
for (int i = 0; i < nGlyphs; i++) {
//gjis[i] = getGlyphVector().getGlyphJustificationInfo(i);
char c = info.text[charIndicies[i] + info.start];
if (Character.isWhitespace(c)) {
gjis[i] = spaceInfo;
} else {
gjis[i] = defaultInfo;
}
// End patch
}
}
return gjis;
}
/**
* Collects justification information into JustificationInfo object
* @param jInfo - JustificationInfo object
*/
@Override
void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) {
int lastChar = Math.min(jInfo.lastIdx, info.end) - info.start;
boolean haveFirst = info.start <= jInfo.firstIdx;
boolean haveLast = info.end >= (jInfo.lastIdx + 1);
int prevGlyphIdx = -1;
int currGlyphIdx;
if (jInfo.grow) { // Check how much we can grow/shrink on current priority level
for (int i=0; i<lastChar; i++) {
currGlyphIdx = getChar2Glyph()[i];
if (currGlyphIdx == prevGlyphIdx) {
// Several chars could be represented by one glyph,
// suppose they are contiguous
continue;
}
prevGlyphIdx = currGlyphIdx;
GlyphJustificationInfo gji = getGlyphJustificationInfos()[currGlyphIdx];
if (gji.growPriority == jInfo.priority) {
jInfo.weight += gji.weight * 2;
jInfo.growLimit += gji.growLeftLimit;
jInfo.growLimit += gji.growRightLimit;
if (gji.growAbsorb) {
jInfo.absorbedWeight += gji.weight * 2;
}
}
}
} else {
for (int i=0; i<lastChar; i++) {
currGlyphIdx = getChar2Glyph()[i];
if (currGlyphIdx == prevGlyphIdx) {
continue;
}
prevGlyphIdx = currGlyphIdx;
GlyphJustificationInfo gji = getGlyphJustificationInfos()[currGlyphIdx];
if (gji.shrinkPriority == jInfo.priority) {
jInfo.weight += gji.weight * 2;
jInfo.growLimit -= gji.shrinkLeftLimit;
jInfo.growLimit -= gji.shrinkRightLimit;
if (gji.shrinkAbsorb) {
jInfo.absorbedWeight += gji.weight * 2;
}
}
}
}
if (haveFirst) { // Don't add padding before first char
GlyphJustificationInfo gji = getGlyphJustificationInfos()[getChar2Glyph()[0]];
jInfo.weight -= gji.weight;
if (jInfo.grow) {
jInfo.growLimit -= gji.growLeftLimit;
if (gji.growAbsorb) {
jInfo.absorbedWeight -= gji.weight;
}
} else {
jInfo.growLimit += gji.shrinkLeftLimit;
if (gji.shrinkAbsorb) {
jInfo.absorbedWeight -= gji.weight;
}
}
}
if (haveLast) { // Don't add padding after last char
GlyphJustificationInfo gji =
getGlyphJustificationInfos()[getChar2Glyph()[lastChar]];
jInfo.weight -= gji.weight;
if (jInfo.grow) {
jInfo.growLimit -= gji.growRightLimit;
if (gji.growAbsorb) {
jInfo.absorbedWeight -= gji.weight;
}
} else {
jInfo.growLimit += gji.shrinkRightLimit;
if (gji.shrinkAbsorb) {
jInfo.absorbedWeight -= gji.weight;
}
}
}
}
/**
* Performs justification of the segment.
* Updates positions of individual characters.
* @param jInfos - justification information, gathered by the previous passes
* @return amount of growth or shrink of the segment
*/
@Override
float doJustification(TextRunBreaker.JustificationInfo jInfos[]) {
int lastPriority =
jInfos[jInfos.length-1] == null ?
-1 : jInfos[jInfos.length-1].priority;
// Get the highest priority
int highestPriority = 0;
for (; highestPriority<jInfos.length; highestPriority++) {
if (jInfos[highestPriority] != null) {
break;
}
}
if (highestPriority == jInfos.length) {
return 0;
}
TextRunBreaker.JustificationInfo firstInfo = jInfos[highestPriority];
TextRunBreaker.JustificationInfo lastInfo =
lastPriority > 0 ? jInfos[lastPriority] : null;
boolean haveFirst = info.start <= firstInfo.firstIdx;
boolean haveLast = info.end >= (firstInfo.lastIdx + 1);
// Here we suppose that GLYPHS are ordered LEFT TO RIGHT
int firstGlyph = haveFirst ?
getChar2Glyph()[firstInfo.firstIdx - info.start] :
getChar2Glyph()[0];
int lastGlyph = haveLast ?
getChar2Glyph()[firstInfo.lastIdx - info.start] :
getChar2Glyph()[info.length - 1];
if (haveLast) {
lastGlyph--;
}
TextRunBreaker.JustificationInfo currInfo;
float glyphOffset = 0;
float positionIncrement = 0;
float sideIncrement = 0;
if (haveFirst) { // Don't add padding before first char
GlyphJustificationInfo gji = getGlyphJustificationInfos()[firstGlyph];
currInfo = jInfos[gji.growPriority];
if (currInfo != null) {
if (currInfo.useLimits) {
if (currInfo.absorb) {
glyphOffset += gji.weight * currInfo.absorbedGapPerUnit;
} else if (
lastInfo != null &&
lastInfo.priority == currInfo.priority
) {
glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit;
}
glyphOffset +=
firstInfo.grow ?
gji.growRightLimit :
-gji.shrinkRightLimit;
} else {
glyphOffset += gji.weight * currInfo.gapPerUnit;
}
}
firstGlyph++;
}
if (firstInfo.grow) {
for (int i=firstGlyph; i<=lastGlyph; i++) {
GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
currInfo = jInfos[gji.growPriority];
if (currInfo == null) {
// We still have to increment glyph position
Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
getGlyphVector().setGlyphPosition(i, glyphPos);
continue;
}
if (currInfo.useLimits) {
glyphOffset += gji.growLeftLimit;
if (currInfo.absorb) {
sideIncrement = gji.weight * currInfo.absorbedGapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
} else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
} else {
positionIncrement = glyphOffset;
}
glyphOffset += gji.growRightLimit;
} else {
sideIncrement = gji.weight * currInfo.gapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
}
Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY());
getGlyphVector().setGlyphPosition(i, glyphPos);
}
} else {
for (int i=firstGlyph; i<=lastGlyph; i++) {
GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
currInfo = jInfos[gji.shrinkPriority];
if (currInfo == null) {
// We still have to increment glyph position
Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
getGlyphVector().setGlyphPosition(i, glyphPos);
continue;
}
if (currInfo.useLimits) {
glyphOffset -= gji.shrinkLeftLimit;
if (currInfo.absorb) {
sideIncrement = gji.weight * currInfo.absorbedGapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
} else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
} else {
positionIncrement = glyphOffset;
}
glyphOffset -= gji.shrinkRightLimit;
} else {
sideIncrement = gji.weight * currInfo.gapPerUnit;
glyphOffset += sideIncrement;
positionIncrement = glyphOffset;
glyphOffset += sideIncrement;
}
Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY());
getGlyphVector().setGlyphPosition(i, glyphPos);
}
}
if (haveLast) { // Don't add padding after last char
lastGlyph++;
GlyphJustificationInfo gji = getGlyphJustificationInfos()[lastGlyph];
currInfo = jInfos[gji.growPriority];
if (currInfo != null) {
if (currInfo.useLimits) {
glyphOffset += firstInfo.grow ? gji.growLeftLimit : -gji.shrinkLeftLimit;
if (currInfo.absorb) {
glyphOffset += gji.weight * currInfo.absorbedGapPerUnit;
} else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit;
}
} else {
glyphOffset += gji.weight * currInfo.gapPerUnit;
}
}
// Ajust positions of all glyphs after last glyph
for (int i=lastGlyph; i<getGlyphVector().getNumGlyphs()+1; i++) {
Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
getGlyphVector().setGlyphPosition(i, glyphPos);
}
} else { // Update position after last glyph in glyph vector -
// to get correct advance for it
Point2D glyphPos = getGlyphVector().getGlyphPosition(lastGlyph+1);
glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
getGlyphVector().setGlyphPosition(lastGlyph+1, glyphPos);
}
gjis = null; // We don't need justification infos any more
// Also we have to reset cached bounds and metrics
this.visualBounds = null;
this.logicalBounds = null;
return glyphOffset; // How much our segment grown or shrunk
}
}
public static class TextRunSegmentGraphic extends TextRunSegment {
GraphicAttribute ga;
int start;
int length;
float fullAdvance;
TextRunSegmentGraphic(GraphicAttribute attr, int len, int start) {
this.start = start;
length = len;
ga = attr;
metrics = new BasicMetrics(ga);
fullAdvance = ga.getAdvance() * length;
}
@Override
public Object clone() {
return new TextRunSegmentGraphic(ga, length, start);
}
// Renders this text run segment
@Override
void draw(Graphics2D g2d, float xOffset, float yOffset) {
if (decoration != null) {
TextDecorator.prepareGraphics(this, g2d, xOffset, yOffset);
}
float xPos = x + xOffset;
float yPos = y + yOffset;
for (int i=0; i < length; i++) {
ga.draw(g2d, xPos, yPos);
xPos += ga.getAdvance();
}
if (decoration != null) {
TextDecorator.drawTextDecorations(this, g2d, xOffset, yOffset);
TextDecorator.restoreGraphics(decoration, g2d);
}
}
// Returns visual bounds of this segment
@Override
Rectangle2D getVisualBounds() {
if (visualBounds == null) {
Rectangle2D bounds = ga.getBounds();
// First and last chars can be out of logical bounds, so we calculate
// (bounds.getWidth() - ga.getAdvance()) which is exactly the difference
bounds.setRect(
bounds.getMinX() + x,
bounds.getMinY() + y,
bounds.getWidth() - ga.getAdvance() + getAdvance(),
bounds.getHeight()
);
visualBounds = TextDecorator.extendVisualBounds(this, bounds, decoration);
}
return (Rectangle2D) visualBounds.clone();
}
@Override
Rectangle2D getLogicalBounds() {
if (logicalBounds == null) {
logicalBounds =
new Rectangle2D.Float(
x, y - metrics.ascent,
getAdvance(), metrics.ascent + metrics.descent
);
}
return (Rectangle2D) logicalBounds.clone();
}
@Override
float getAdvance() {
return fullAdvance;
}
@Override
float getAdvanceDelta(int start, int end) {
return ga.getAdvance() * (end - start);
}
@Override
int getCharIndexFromAdvance(float advance, int start) {
start -= this.start;
if (start < 0) {
start = 0;
}
int charOffset = (int) (advance/ga.getAdvance());
if (charOffset + start > length) {
return length + this.start;
}
return charOffset + start + this.start;
}
@Override
int getStart() {
return start;
}
@Override
int getEnd() {
return start + length;
}
@Override
int getLength() {
return length;
}
@Override
Shape getCharsBlackBoxBounds(int start, int limit) {
start -= this.start;
limit -= this.start;
if (limit > length) {
limit = length;
}
Rectangle2D charBounds = ga.getBounds();
charBounds.setRect(
charBounds.getX() + ga.getAdvance() * start + x,
charBounds.getY() + y,
charBounds.getWidth() + ga.getAdvance() * (limit - start),
charBounds.getHeight()
);
return charBounds;
}
@Override
float getCharPosition(int index) {
index -= start;
if (index > length) {
index = length;
}
return ga.getAdvance() * index + x;
}
@Override
float getCharAdvance(int index) {
return ga.getAdvance();
}
@Override
Shape getOutline() {
AffineTransform t = AffineTransform.getTranslateInstance(x, y);
return t.createTransformedShape(
TextDecorator.extendOutline(this, getVisualBounds(), decoration)
);
}
@Override
boolean charHasZeroAdvance(int index) {
return false;
}
@Override
TextHitInfo hitTest(float hitX, float hitY) {
hitX -= x;
float tmp = hitX / ga.getAdvance();
int hitIndex = Math.round(tmp);
if (tmp > hitIndex) {
return TextHitInfo.leading(hitIndex + this.start);
}
return TextHitInfo.trailing(hitIndex + this.start);
}
@Override
void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) {
// Do nothing
}
@Override
float doJustification(TextRunBreaker.JustificationInfo jInfos[]) {
// Do nothing
return 0;
}
}
}