blob: e6090812a1a73997ed352ffa19b13b1e720433f9 [file] [log] [blame]
package org.eclipse.swt.custom;
/*
* (c) Copyright IBM Corp. 2001, 2002.
* All Rights Reserved
*/
import java.util.*;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.widgets.*;
/**
* This class provides API for StyledText to implement bidirectional text
* functions.
* Objects of this class are created for a single line of text.
*/
class StyledTextBidi {
private GC gc;
private int[] bidiSegments; // bidi text segments, each segment will be rendered separately
private int[] renderPositions; // x position at which characters of the line are rendered, in visual order
private int[] order; // reordering indices in logical order, iV=order[iL] (iV=visual index, iL=logical index),
// if no character in a line needs reordering all iV and iL are the same.
private int[] dx; // distance between character cells. in visual order. renderPositions[iV + 1] = renderPositions[iV] + dx[iV]
private byte[] classBuffer; // the character types in logical order, see BidiUtil for the possible types
private char[] glyphBuffer; // the glyphs in visual order as they will be rendered on screen.
/**
* This class describes a text segment of a single direction, either
* left-to-right (L2R) or right-to-left (R2L).
* Objects of this class are used by StyledTextBidi rendering methods
* to render logically contiguous text segments that may be visually
* discontiguous if they consist of different directions.
*/
class DirectionRun {
int logicalStart;
int logicalEnd;
DirectionRun(int logicalStart, int logicalEnd) {
this.logicalStart = logicalStart;
this.logicalEnd = logicalEnd;
}
int getVisualStart() {
int visualStart = order[logicalStart];
int visualEnd = order[logicalEnd];
// the visualStart of a R2L direction run is actually
// at the run's logicalEnd, answered as such since rendering
// always occurs from L2R regardless of the text run's
// direction
if (visualEnd < visualStart) {
visualStart = visualEnd;
}
return visualStart;
}
int getVisualEnd() {
int visualStart = order[logicalStart];
int visualEnd = order[logicalEnd];
// the visualEnd of a R2L direction run is actually
// at the run's logicalStart, answered as such since rendering
// always occurs from L2R regardless of the text run's
// direction
if (visualEnd < visualStart) {
visualEnd = visualStart;
}
return visualEnd;
}
int getRenderStartX() {
return renderPositions[getVisualStart()];
}
int getRenderStopX() {
int visualEnd = getVisualEnd();
return renderPositions[visualEnd] + dx[visualEnd];
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("vStart,Stop:" + getVisualStart() + "," + getVisualEnd() + " lStart,Stop:" + logicalStart + "," + logicalEnd + " renderStart,Stop: " + getRenderStartX() + "," + getRenderStopX());
return buf.toString();
}
}
/**
* Constructs an instance of this class for a line of text. The text
* is reordered to reflect how it will be displayed.
* <p>
*
* @param gc the GC to use for rendering and measuring of this line.
* @param tabWidth tab width in number of spaces, used to calculate
* tab stops
* @param text line that bidi data should be calculated for
* @param boldRanges bold text segments in the line, specified as
* i=bold start,i+1=bold length
* @param boldFont font that bold text will be rendered in, needed for
* proper measuring of bold text segments.
* @param offset text segments that should be measured and reordered
* separately, may be needed to preserve the order of separate R2L
* segments to each other
*/
public StyledTextBidi(GC gc, int tabWidth, String text, StyleRange[] ranges, Font boldFont, int[] offsets) {
int length = text.length();
this.gc = gc;
bidiSegments = offsets;
renderPositions = new int[length];
order = new int[length];
dx = new int[length];
classBuffer = new byte[length];
if (length == 0) {
glyphBuffer = new char[0];
}
else {
glyphBuffer = BidiUtil.getRenderInfo(gc, text, order, classBuffer, dx, 0, offsets);
if (ranges != null) {
// If the font supports characters shaping, break up the font style ranges based on
// the specified bidi segments. Each bidi segment will be treated separately
// for font style purposes.
StyleRange[] segmentedRanges;
if (isCharacterShaped(gc)) segmentedRanges = segmentedRangesFor(ranges);
else segmentedRanges = ranges;
Font normalFont = gc.getFont();
gc.setFont(boldFont);
for (int i = 0; i < segmentedRanges.length; i++) {
StyleRange segmentedRange = segmentedRanges[i];
int rangeStart = segmentedRange.start;
int rangeLength = segmentedRange.length;
// Font styled text needs to be processed so that the dx array reflects the styled
// font.
prepareFontStyledText(text, rangeStart, rangeLength);
}
gc.setFont(normalFont);
}
calculateTabStops(text, tabWidth);
calculateRenderPositions();
}
}
/**
* Constructs an instance of this class for a line of text. This constructor
* should be used when only ordering (not rendering) information is needed.
* Only the class and order arrays will be filled during this call.
* <p>
*
* @param gc the GC to use for rendering and measuring of this line.
* @param text line that bidi data should be calculated for
* @param offset text segments that should be measured and reordered
* separately, may be needed to preserve the order of separate R2L
* segments to each other
*/
public StyledTextBidi(GC gc, String text, int[] offsets) {
int length = text.length();
this.gc = gc;
bidiSegments = offsets;
order = new int[length];
classBuffer = new byte[length];
BidiUtil.getOrderInfo(gc, text, order, classBuffer, 0, offsets);
// initialize the unused arrays
dx = new int[0];
renderPositions = new int[0];
glyphBuffer = new char[0];
}
/**
* Adds a listener that should be called when the user changes the
* keyboard layout for the specified window.
* <p>
*
* @param control Control to add the keyboard language listener for.
* Each window has its own keyboard language setting.
* @param runnable the listener that should be called when the user
* changes the keyboard layout.
*/
static void addLanguageListener(Control control, Runnable runnable) {
BidiUtil.addLanguageListener(control.handle, runnable);
}
/**
* Answers the direction of the active keyboard language - either
* L2R or R2L. The active keyboard language determines the direction
* of the caret and can be changed by the user (e.g., via Alt-Shift on
* Win32 platforms).
* <p>
*
* @return the direction of the active keyboard language. SWT.LEFT (for L2R
* language) or SWT.RIGHT (for R2L language).
*/
static int getKeyboardLanguageDirection() {
int language = BidiUtil.getKeyboardLanguage();
if (language == BidiUtil.KEYBOARD_HEBREW) {
return SWT.RIGHT;
}
if (language == BidiUtil.KEYBOARD_ARABIC) {
return SWT.RIGHT;
}
return SWT.LEFT;
}
/**
* Removes the keyboard language listener for the specified window.
* <p>
*
* @param control window to remove the keyboard language listener from.
*/
static void removeLanguageListener(Control control) {
BidiUtil.removeLanguageListener(control.handle);
}
/**
* Calculates render positions using the glyph distance values in the dx array.
*/
private void calculateRenderPositions() {
renderPositions = new int[dx.length];
renderPositions[0] = StyledText.XINSET;
for (int i = 0; i < dx.length - 1; i++) {
renderPositions[i + 1] = renderPositions[i] + dx[i];
}
}
/**
* Calculate the line's tab stops and adjust the dx array to
* reflect the width of tab characters.
* <p>
*
* @param text the original line text (not reordered) containing
* tab characters.
* @param tabWidth number of pixels that one tab character represents
*/
private void calculateTabStops(String text, int tabWidth) {
int tabIndex = text.indexOf('\t', 0);
int logicalIndex = 0;
int x = StyledText.XINSET;
int spaceWidth = gc.stringExtent(" ").x;
while (tabIndex != -1) {
for (; logicalIndex < tabIndex; logicalIndex++) {
x += dx[order[logicalIndex]];
}
int tabStop = x + tabWidth;
// make sure tab stop is at least one space width apart
// from the last character. fixes 4844.
if (tabWidth - tabStop % tabWidth < spaceWidth) {
tabStop += tabWidth;
}
tabStop -= tabStop % tabWidth;
dx[order[tabIndex]] = tabStop - x;
tabIndex = text.indexOf('\t', tabIndex + 1);
}
}
/**
* Renders the specified text segment. All text is rendered L2R
* regardless of the direction of the text. The rendered text may
* be visually discontiguous if the text segment is bidirectional.
* <p>
*
* @param logicalStart start offset in the logical text
* @param length number of logical characters to render
* @param xOffset x location of the line start
* @param yOffset y location of the line start
*/
void drawBidiText(int logicalStart, int length, int xOffset, int yOffset) {
Enumeration directionRuns = getDirectionRuns(logicalStart, length).elements();
int endOffset = logicalStart + length;
if (endOffset > getTextLength()) {
return;
}
while (directionRuns.hasMoreElements()) {
DirectionRun run = (DirectionRun) directionRuns.nextElement();
int visualStart = run.getVisualStart();
int visualEnd = run.getVisualEnd();
int x = xOffset + run.getRenderStartX();
drawGlyphs(visualStart, visualEnd - visualStart + 1, x, yOffset);
}
}
/**
* Renders a segment of glyphs. Glyphs are visual objects so the
* start and length are visual as well. Glyphs are always rendered L2R.
* <p>
*
* @param visualStart start offset of the glyphs to render relative to the
* line start.
* @param length number of glyphs to render
* @param x x location to render at
* @param y y location to render at
*/
private void drawGlyphs(int visualStart, int length, int x, int y) {
char[] renderBuffer = new char[length];
int[] renderDx = new int[length];
if (length == 0) {
return;
}
System.arraycopy(glyphBuffer, visualStart, renderBuffer, 0, length);
// copy the distance values for the desired rendering range
System.arraycopy(dx, visualStart, renderDx, 0, length);
BidiUtil.drawGlyphs(gc, renderBuffer, renderDx, x, y);
}
/**
* Fills a rectangle spanning the given logical range.
* The rectangle may be visually discontiguous if the text segment
* is bidirectional.
* <p>
*
* @param logicalStart logcial start offset of the rectangle
* @param length number of logical characters the rectangle should span
* @param xOffset x location of the line start
* @param yOffset y location of the line start
* @param height height of the rectangle
*/
void fillBackground(int logicalStart, int length, int xOffset, int yOffset, int height) {
Enumeration directionRuns = getDirectionRuns(logicalStart, length).elements();
if (logicalStart + length > getTextLength()) {
return;
}
while (directionRuns.hasMoreElements()) {
DirectionRun run = (DirectionRun) directionRuns.nextElement();
int startX = run.getRenderStartX();
gc.fillRectangle(xOffset + startX, yOffset, run.getRenderStopX() - startX, height);
}
}
/**
* Returns the offset and direction that will be used to position the caret for
* the given x location. The caret will be placed in front of or behind the
* character at location x depending on what type of character (i.e., R2L or L2R)
* is at location x. This method is used for positioning the caret when a mouse
* click occurs within the widget.
* <p>
*
* @param x the x location of the character in the line.
* @return array containing the caret offset and direction for the x location.
* index 0: offset relative to the start of the line
* index 1: direction, either ST.COLUMN_NEXT or ST.COLUMN_PREVIOUS.
* The direction is used to control the caret position at direction
* boundaries. The semantics follow the behavior for keyboard cursor
* navigation.
* Example: RRRLLL
* Pressing cursor left (COLUMN_PREVIOUS) in the L2R segment places the cursor
* in front of the first character of the L2R segment. Pressing cursor right
* (COLUMN_NEXT) in a R2L segment places the cursor behind the last character
* of the R2L segment. However, both are the same logical offset.
*/
int[] getCaretOffsetAndDirectionAtX(int x) {
int lineLength = getTextLength();
int offset;
int direction;
if (lineLength == 0) {
return new int[] {0, 0};
}
int eol = renderPositions[renderPositions.length - 1] + dx[dx.length - 1];
if (x >= eol) {
return new int[] {lineLength, ST.COLUMN_NEXT};
}
// get the visual offset of the clicked character
int visualOffset = getVisualOffsetAtX(x);
// figure out if the character was clicked on the right or left
int halfway = renderPositions[visualOffset] + dx[visualOffset] / 2;
boolean visualLeft = (x <= halfway);
offset = getLogicalOffset(visualOffset);
if (isRightToLeft(offset)) {
if (visualLeft) {
if (isLigated(gc)) {
// the caret should be positioned after the last
// character of the ligature
offset = getLigatureEndOffset(offset);
}
offset++;
// position the caret as if the caret is to the right
// of the character at location x and the NEXT key is
// pressed
direction = ST.COLUMN_NEXT;
}
else {
// position the caret as if the caret is to the left
// of the character at location x and the PREVIOUS key is
// pressed
direction = ST.COLUMN_PREVIOUS;
}
}
else {
if (visualLeft) {
// position the caret as if the caret is to the right
// of the character at location x and the PREVIOUS key is
// pressed
direction = ST.COLUMN_PREVIOUS;
}
else {
// position the caret as if the caret is to the left
// of the character at location x and the NEXT key is
// pressed
offset++;
direction = ST.COLUMN_NEXT;
}
}
return new int[] {offset, direction};
}
/**
* Returns the caret position at the specified offset in the line.
* <p>
* @param logicalOffset offset of the character in the line
* @return the caret position at the specified offset in the line.
*/
int getCaretPosition(int logicalOffset) {
return getCaretPosition(logicalOffset, ST.COLUMN_NEXT);
}
/**
* Returns the caret position at the specified offset in the line.
* The direction parameter is used to determine the caret position
* at direction boundaries. If the logical offset is between a R2L
* and a L2R segment, pressing cursor left in the L2R segment places
* the cursor in front of the first character of the L2R segment; whereas
* pressing cursor right in the R2L segment places the cursor behind
* the last character of the R2L segment. However, both caret positions
* are at the same logical offset.
* <p>
*
* @param logicalOffset offset of the character in the line
* @param direction direction the caret moved to the specified location.
* either ST.COLUMN_NEXT (right cursor key) or ST.COLUMN_PREVIOUS (left cursor key) .
* @return the caret position at the specified offset in the line,
* taking the direction into account as described above.
*/
int getCaretPosition(int logicalOffset, int direction) {
int caretX;
if (getTextLength() == 0) {
return StyledText.XINSET;
}
// at or past end of line?
if (logicalOffset >= order.length) {
logicalOffset = Math.min(logicalOffset, order.length - 1);
int visualOffset = order[logicalOffset];
if (isRightToLeft(logicalOffset)) {
caretX = renderPositions[visualOffset];
}
else {
caretX = renderPositions[visualOffset] + dx[visualOffset];
}
}
else
// at beginning of line?
if (logicalOffset == 0) {
int visualOffset = order[logicalOffset];
if (isRightToLeft(logicalOffset)) {
caretX = renderPositions[visualOffset] + dx[visualOffset];
}
else {
caretX = renderPositions[visualOffset];
}
}
else
// consider local numbers as R2L in determining direction boundaries.
// fixes 1GK9API.
// treat user specified direction segments like real direction changes.
if (direction == ST.COLUMN_NEXT &&
(isRightToLeftInput(logicalOffset) != isRightToLeftInput(logicalOffset - 1) ||
isStartOfBidiSegment(logicalOffset))) {
int visualOffset = order[logicalOffset-1];
// moving between segments.
// do not consider local numbers as R2L here, to determine position,
// because local numbers are navigated L2R and we want the caret to
// be to the right of the number. see 1GK9API
if (isRightToLeft(logicalOffset - 1)) {
// moving from RtoL to LtoR
caretX = renderPositions[visualOffset];
}
else {
// moving from LtoR to RtoL
caretX = renderPositions[visualOffset] + dx[visualOffset];
}
}
else
// consider local numbers as R2L in determining direction boundaries.
// fixes 1GK9API.
if (direction == ST.COLUMN_PREVIOUS &&
isRightToLeftInput(logicalOffset) != isRightToLeftInput(logicalOffset - 1)) {
int visualOffset = order[logicalOffset];
// moving between segments.
// consider local numbers as R2L here, to determine position, because
// we want to stay in L2R segment and place the cursor to the left of
// first L2R character. see 1GK9API
if (isRightToLeftInput(logicalOffset - 1)) {
// moving from LtoR to RtoL
caretX = renderPositions[visualOffset];
}
else {
// moving from RtoL to LtoR
caretX = renderPositions[visualOffset] + dx[visualOffset];
}
}
else
if (isRightToLeft(logicalOffset)) {
int visualOffset = order[logicalOffset];
caretX = renderPositions[visualOffset] + dx[visualOffset];
}
else {
caretX = renderPositions[order[logicalOffset]];
}
return caretX;
}
/**
* Returns the direction segments that are in the specified text
* range. The text range may be visually discontiguous if the
* text is bidirectional. Each returned direction run has a single
* direction and the runs all go from left to right, regardless of
* the direction of the text in the segment. User specified segments
* (via BidiSegmentListener) are taken into account and result in
* separate direction runs.
* <p>
*
* @param logicalStart offset of the logcial start of the first
* direction segment
* @param length length of the text included in the direction
* segments
* @return the direction segments that are in the specified
* text range, each segment has a single direction.
*/
private Vector getDirectionRuns(int logicalStart, int length) {
Vector directionRuns = new Vector();
int logicalEnd = logicalStart + length - 1;
int segmentLogicalStart = logicalStart;
int segmentLogicalEnd = segmentLogicalStart;
if (logicalEnd < getTextLength()) {
int bidiSegmentIndex = 0;
int bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
// Find the bidi segment that the direction runs start in.
// There will always be at least on bidi segment (for the entire line).
while (bidiSegmentIndex < bidiSegments.length - 2 && bidiSegmentEnd <= logicalStart) {
bidiSegmentIndex++;
bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
}
while (segmentLogicalEnd <= logicalEnd) {
int segType = classBuffer[segmentLogicalStart];
// Search for the end of the direction segment. Each segment needs to
// be rendered separately.
// E.g., 11211 (1=R2L, 2=L2R), rendering from logical index 0 to 5
// would be visual 1 to 4 and would thus miss visual 0. Rendering the
// segments separately would render from visual 1 to 0, then 2, then
// 4 to 3.
while (segmentLogicalEnd < logicalEnd &&
segType == classBuffer[segmentLogicalEnd + 1] &&
segmentLogicalEnd + 1 < bidiSegmentEnd) {
segmentLogicalEnd++;
}
directionRuns.addElement(new DirectionRun(segmentLogicalStart, segmentLogicalEnd));
segmentLogicalStart = ++segmentLogicalEnd;
// The current direction run ends at a bidi segment end. Get the next bidi segment.
if (segmentLogicalEnd == bidiSegmentEnd && bidiSegmentIndex < bidiSegments.length - 2) {
bidiSegmentIndex++;
bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
}
}
}
return directionRuns;
}
/**
* Returns the offset of the last character comprising a ligature.
* <p>
*
* @return the offset of the last character comprising a ligature.
*/
int getLigatureEndOffset(int offset) {
// assume only bidi languages support ligatures
if (!isRightToLeft(offset)) return offset;
int newOffset = offset;
int i = offset + 1;
// a ligature is a visual character that is comprised of
// multiple logical characters, thus each logical part of
// a ligature will have the same order value
while (i<order.length && (order[i] == order[offset])) {
newOffset = i;
i++;
}
return newOffset;
}
/**
* Returns the offset of the first character comprising a ligature.
* <p>
*
* @return the offset of the first character comprising a ligature.
*/
int getLigatureStartOffset(int offset) {
// assume only bidi languages support ligatures
if (!isRightToLeft(offset)) return offset;
int newOffset = offset;
int i = offset - 1;
// a ligature is a visual character that is comprised of
// multiple logical characters, thus each logical part of
// a ligature will have the same order value
while (i>=0 && (order[i] == order[offset])) {
newOffset = i;
i--;
}
return newOffset;
}
/**
* Returns the logical offset of the character at the specified
* visual offset.
* <p>
*
* @param visualOffset the visual offset
* @return the logical offset of the character at <code>visualOffset</code>.
*/
private int getLogicalOffset(int visualOffset) {
int logicalOffset = 0;
while (logicalOffset < order.length && order[logicalOffset] != visualOffset) {
logicalOffset++;
}
return logicalOffset;
}
/**
* Returns the offset of the character at the specified x location.
* <p>
*
* @param x the location of the character
* @return the logical offset of the character at the specified x
* location.
*/
int getOffsetAtX(int x) {
int visualOffset;
if (getTextLength() == 0) {
return 0;
}
if (x >= renderPositions[renderPositions.length - 1] + dx[dx.length - 1]) {
// Return when x is past the end of the line. Fixes 1GLADBK.
return -1;
}
visualOffset = getVisualOffsetAtX(x);
return getLogicalOffset(visualOffset);
}
/**
* Returns the visual offset of the character at the specified x
* location.
* <p>
*
* @param x the location of the character
* @return the visual offset of the character at the specified x
* location.
*/
private int getVisualOffsetAtX(int x) {
int lineLength = getTextLength();
int low = -1;
int high = lineLength;
while (high - low > 1) {
int offset = (high + low) / 2;
int visualX = renderPositions[offset];
// visualX + dx is the start of the next character. Restrict right/high
// search boundary only if x is before next character. Fixes 1GL4ZVE.
if (x < visualX + dx[offset]) {
high = offset;
}
else
if (high == lineLength && high - offset == 1) {
// requested x location is past end of line
high = -1;
}
else {
low = offset;
}
}
return high;
}
/**
* Returns the reordering indices that map between logical and
* visual index of characters in the specified range.
* <p>
*
* @param start start offset of the reordering indices
* @param length number of reordering indices to return
* @return the reordering indices that map between logical and
* visual index of characters in the specified range. Relative
* to the start of the range.
*/
private int[] getRenderIndexesFor(int start, int length) {
int[] positions = new int[length];
int end = start + length;
for (int i = start; i < end; i++) {
positions[i-start] = order[i];
}
return positions;
}
/**
* Break up the given ranges such that each range is fully contained within a bidi
* segment.
*/
private StyleRange[] segmentedRangesFor(StyleRange[] ranges) {
if ((bidiSegments == null) || (bidiSegments.length == 0)) return ranges;
Vector newRanges = new Vector();
int j=0;
int startSegment;
int endSegment;
for (int i=0; i<ranges.length; i++) {
int start = ranges[i].start;
int end = start+ranges[i].length;
startSegment=-1;
endSegment=-1;
boolean done = false;
while (j<bidiSegments.length && !done) {
if (bidiSegments[j]<=start) {
startSegment=j;
}
if (bidiSegments[j]>=end) {
endSegment=j-1;
j--;
}
done = (startSegment != -1) && (endSegment != -1);
if (!done) j++;
}
if (startSegment == endSegment) {
// range is within one segment
StyleRange newStyle = new StyleRange(start, end-start, null, null);
newRanges.addElement(newStyle);
} else if (startSegment > endSegment) {
// range is within no segment (i.e., it's empty)
} else {
// range spans multiple segments
StyleRange newStyle = new StyleRange(start, bidiSegments[startSegment+1]-start, null, null);
newRanges.addElement(newStyle);
startSegment++;
for (int k=startSegment; k<endSegment; k++) {
newStyle = new StyleRange(bidiSegments[k], bidiSegments[k+1]-bidiSegments[k], null, null);
newRanges.addElement(newStyle);
}
newStyle = new StyleRange(bidiSegments[endSegment], end-bidiSegments[endSegment], null, null);
newRanges.addElement(newStyle);
}
}
StyleRange[] rangeArray = new StyleRange[newRanges.size()];
for (int i=0; i<newRanges.size(); i++) {
rangeArray[i]=(StyleRange)newRanges.elementAt(i);
}
return rangeArray;
}
/**
* Returns the number of characters in the line.
* <p>
*
* @return the number of characters in the line.
*/
private int getTextLength() {
return dx.length;
}
/**
* Returns the width in pixels of the line.
* <p>
*
* @return the width in pixels of the line.
*/
int getTextWidth() {
int width = 0;
if (getTextLength() > 0) {
width = renderPositions[renderPositions.length - 1] + dx[dx.length - 1];
}
return width;
}
/**
* Returns whether the current platform supports a bidi language.
* <p>
*
* @return true=bidi is supported, false otherwise.
*/
static boolean isBidiPlatform() {
return BidiUtil.isBidiPlatform();
}
/**
* Returns whether the font set in the specified gc supports
* character shaping.
* <p>
*
* @param gc the GC that should be tested for character shaping.
* @return
* true=the font set in the specified gc supports character shaped glyphs
* false=the font set in the specified gc doesn't support character shaped glyphs
*/
static boolean isCharacterShaped(GC gc) {
return (BidiUtil.getFontBidiAttributes(gc) & BidiUtil.GLYPHSHAPE) != 0;
}
/**
* Returns whether the font set in the specified gc contains
* ligatured glyphs.
* <p>
*
* @param gc the GC that should be tested for ligatures.
* @return
* true=the font set in the specified gc contains ligatured glyphs.
* false=the font set in the specified gc doesn't contain ligatured
* glyphs.
*/
static boolean isLigated(GC gc) {
return (BidiUtil.getFontBidiAttributes(gc) & BidiUtil.LIGATE) != 0;
}
/**
* Returns the direction of the character at the specified index.
* Used for rendering and caret positioning where local numbers (e.g.,
* national Arabic, or Hindi, numbers) are considered left-to-right.
* <p>
*
* @param logicalIndex the index of the character
* @return
* true=the character at the specified index is in a right-to-left
* codepage (e.g., Hebrew, Arabic).
* false=the character at the specified index is in a left-to-right/latin
* codepage.
*/
boolean isRightToLeft(int logicalIndex) {
boolean isRightToLeft = false;
if (logicalIndex < classBuffer.length) {
isRightToLeft = (classBuffer[logicalIndex] == BidiUtil.CLASS_ARABIC) ||
(classBuffer[logicalIndex] == BidiUtil.CLASS_HEBREW);
}
return isRightToLeft;
}
/**
* Returns the direction of the character at the specified index.
* Used for setting the keyboard language where local numbers (e.g.,
* national Arabic, or Hindi, numbers) are considered right-to-left.
* <p>
*
* @param logicalIndex the index of the character
* @return
* true=the character at the specified index is in a right-to-left
* codepage (e.g., Hebrew, Arabic).
* false=the character at the specified index is in a left-to-right/latin
* codepage.
*/
boolean isRightToLeftInput(int logicalIndex) {
boolean isRightToLeft = false;
if (logicalIndex < classBuffer.length) {
isRightToLeft = (classBuffer[logicalIndex] == BidiUtil.CLASS_ARABIC) ||
(classBuffer[logicalIndex] == BidiUtil.CLASS_HEBREW) ||
(classBuffer[logicalIndex] == BidiUtil.CLASS_LOCALNUMBER);
}
return isRightToLeft;
}
/**
* Returns whether the specified index is the start of a user
* specified direction segment.
* <p>
*
* @param logicalIndex the index to test
* @return true=the specified index is the start of a user specified
* direction segment, false otherwise
*/
private boolean isStartOfBidiSegment(int logicalIndex) {
for (int i = 0; i < bidiSegments.length; i++) {
if (bidiSegments[i] == logicalIndex) return true;
}
return false;
}
/**
* Reorders and calculates render positions for the specified sub-line
* of text. The results will be merged with the data for the rest of
* the line .
* <p>
*
* @param textline the entire line of text that this object represents.
* @param logicalStart the start offset of the first character to
* reorder.
* @param length the number of characters to reorder
*/
private void prepareFontStyledText(String textline, int logicalStart, int length) {
int byteCount = length;
int flags = 0;
String text = textline.substring(logicalStart, logicalStart + length);
// Figure out what is before and after the substring so that the proper character
// shaping will occur. Character shaping will not occur across bidi segments, so
// if the styled text starts or ends on a bidi segment, do not process the text
// for character shaping.
if (logicalStart != 0
&& isCharacterShaped(gc)
&& !isStartOfBidiSegment(logicalStart)
&& !Compatibility.isWhitespace(textline.charAt(logicalStart - 1))
&& isRightToLeft(logicalStart - 1)) {
// if the start of the substring is not the beginning of the
// text line, check to see what is before the string
flags |= BidiUtil.LINKBEFORE;
}
if ((logicalStart + byteCount) != dx.length
&& isCharacterShaped(gc)
&& !isStartOfBidiSegment(logicalStart + length)
&& !Compatibility.isWhitespace(textline.charAt(logicalStart + byteCount))
&& isRightToLeft(logicalStart + byteCount)) {
// if the end of the substring is not the end of the text line,
// check to see what is after the substring
flags |= BidiUtil.LINKAFTER;
}
// set classification values for the substring
flags |= BidiUtil.CLASSIN;
byte[] classArray = new byte[byteCount];
int[] renderIndexes = getRenderIndexesFor(logicalStart, byteCount);
for (int i = 0; i < byteCount; i++) {
classArray[i] = classBuffer[renderIndexes[i]];
}
int[] dxArray = new int[byteCount];
int[] orderArray = new int[byteCount];
BidiUtil.getRenderInfo(gc, text, orderArray, classArray, dxArray, flags, new int[] {0, text.length()});
// update the existing dx array with the new dx values based on the bold font
for (int i = 0; i < dxArray.length; i++) {
int dxValue = dxArray[orderArray[i]];
int visualIndex = renderIndexes[i];
dx[visualIndex] = dxValue;
}
}
/**
* Redraws a rectangle spanning the given logical range.
* The rectangle may be visually discontiguous if the text segment
* is bidirectional.
* <p>
*
* @param parent window that should be invalidated
* @param logicalStart logcial start offset of the rectangle
* @param length number of logical characters the rectangle should span
* @param xOffset x location of the line start
* @param yOffset y location of the line start
* @param height height of the invalidated rectangle
*/
void redrawRange(Control parent, int logicalStart, int length, int xOffset, int yOffset, int height) {
Enumeration directionRuns = getDirectionRuns(logicalStart, length).elements();
if (logicalStart + length > getTextLength()) {
return;
}
while (directionRuns.hasMoreElements()) {
DirectionRun run = (DirectionRun) directionRuns.nextElement();
int startX = run.getRenderStartX();
parent.redraw(xOffset + startX, yOffset, run.getRenderStopX() - startX, height, true);
}
}
/**
* Sets the keyboard language to match the codepage of the character
* at the specified offset.
* Only distinguishes between left-to-right and right-to-left
* characters and sets the keyboard language to one of Latin, Hebrew
* and Arabic.
* <p>
*
* @param logicalIndex logical offset of the character to use for
* determining the new keyboard language.
*/
void setKeyboardLanguage(int logicalIndex) {
int language = BidiUtil.KEYBOARD_LATIN;
if (logicalIndex >= classBuffer.length) {
return;
}
if (isRightToLeftInput(logicalIndex)) {
String codePage = System.getProperty("file.encoding").toUpperCase();
if ("CP1255".equals(codePage)) {
language = BidiUtil.KEYBOARD_HEBREW;
}
else
if ("CP1256".equals(codePage)) {
language = BidiUtil.KEYBOARD_ARABIC;
}
}
BidiUtil.setKeyboardLanguage(language);
}
/**
* Returns a string representation of the receiver.
* <p>
*
* @return a string representation of the receiver for
* debugging purposes. The output order of the StyledTextbidi values
* is as follows: order, render position, dx, character class, glyphs.
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("StyledTextBidi {{");
// order
for (int i = 0; i < order.length; i++) {
if (i != 0) {
buf.append(",");
}
buf.append(order[i]);
}
buf.append("}, {");
// render positions
for (int i = 0; i < renderPositions.length; i++) {
if (i != 0) {
buf.append(",");
}
buf.append(renderPositions[i]);
}
buf.append("}, {");
// dx
for (int i = 0; i < dx.length; i++) {
if (i != 0) {
buf.append(",");
}
buf.append(dx[i]);
}
buf.append("}, {");
// character class
for (int i = 0; i < classBuffer.length; i++) {
if (i != 0) {
buf.append(",");
}
buf.append(classBuffer[i]);
}
buf.append("}, {");
// glyphs
buf.append(glyphBuffer);
buf.append("}}");
return buf.toString();
}
}