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(); | |
} | |
} |