0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2003 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.swt.custom;
0011:
0012: import java.util.*;
0013: import org.eclipse.swt.*;
0014: import org.eclipse.swt.graphics.*;
0015: import org.eclipse.swt.internal.*;
0016: import org.eclipse.swt.widgets.*;
0017:
0018: /**
0019: * This class provides API for StyledText to implement bidirectional text
0020: * functions.
0021: * Objects of this class are created for a single line of text.
0022: */
0023: class StyledTextBidi {
0024: private GC gc;
0025: private int[] bidiSegments; // bidi text segments, each segment will be rendered separately
0026: private int[] renderPositions; // x position at which characters of the line are rendered, in visual order
0027: private int[] order; // reordering indices in logical order, iV=order[iL] (iV=visual index, iL=logical index),
0028: // if no character in a line needs reordering all iV and iL are the same.
0029: private int[] dx; // distance between character cells. in visual order. renderPositions[iV + 1] = renderPositions[iV] + dx[iV]
0030: private byte[] classBuffer; // the character types in logical order, see BidiUtil for the possible types
0031: private char[] glyphBuffer; // the glyphs in visual order as they will be rendered on screen.
0032: private boolean isRightOriented;// writing orientation
0033:
0034: /**
0035: * This class describes a text segment of a single direction, either
0036: * left-to-right (L2R) or right-to-left (R2L).
0037: * Objects of this class are used by StyledTextBidi rendering methods
0038: * to render logically contiguous text segments that may be visually
0039: * discontiguous if they consist of different directions.
0040: */
0041: class DirectionRun {
0042: int logicalStart;
0043: int logicalEnd;
0044:
0045: DirectionRun(int logicalStart, int logicalEnd) {
0046: this .logicalStart = logicalStart;
0047: this .logicalEnd = logicalEnd;
0048: }
0049:
0050: int getVisualStart() {
0051: int visualStart = order[logicalStart];
0052: int visualEnd = order[logicalEnd];
0053: // the visualStart of a R2L direction run is actually
0054: // at the run's logicalEnd, answered as such since rendering
0055: // always occurs from L2R regardless of the text run's
0056: // direction
0057: if (visualEnd < visualStart) {
0058: visualStart = visualEnd;
0059: }
0060: return visualStart;
0061: }
0062:
0063: int getVisualEnd() {
0064: int visualStart = order[logicalStart];
0065: int visualEnd = order[logicalEnd];
0066: // the visualEnd of a R2L direction run is actually
0067: // at the run's logicalStart, answered as such since rendering
0068: // always occurs from L2R regardless of the text run's
0069: // direction
0070: if (visualEnd < visualStart) {
0071: visualEnd = visualStart;
0072: }
0073: return visualEnd;
0074: }
0075:
0076: int getRenderStartX() {
0077: return renderPositions[getVisualStart()];
0078: }
0079:
0080: int getRenderStopX() {
0081: int visualEnd = getVisualEnd();
0082:
0083: return renderPositions[visualEnd] + dx[visualEnd];
0084: }
0085:
0086: public String toString() {
0087: StringBuffer buf = new StringBuffer();
0088: buf.append("vStart,Stop:" + getVisualStart() + ","
0089: + getVisualEnd() + " lStart,Stop:" + logicalStart
0090: + "," + logicalEnd + " renderStart,Stop: "
0091: + getRenderStartX() + "," + getRenderStopX());
0092: return buf.toString();
0093: }
0094: }
0095:
0096: /**
0097: * Constructs an instance of this class for a line of text. The text
0098: * is reordered to reflect how it will be displayed.
0099: * <p>
0100: *
0101: * @param gc the GC to use for rendering and measuring of this line.
0102: * @param tabWidth tab width in number of spaces, used to calculate
0103: * tab stops
0104: * @param text line that bidi data should be calculated for
0105: * @param boldRanges bold text segments in the line, specified as
0106: * i=bold start,i+1=bold length
0107: * @param boldFont font that bold text will be rendered in, needed for
0108: * proper measuring of bold text segments.
0109: * @param offset text segments that should be measured and reordered
0110: * separately, may be needed to preserve the order of separate R2L
0111: * segments to each other. Must have at least two elements, 0 and the text
0112: * length.
0113: */
0114: public StyledTextBidi(GC gc, int tabWidth, String text,
0115: StyleRange[] ranges, Font boldFont, int[] offsets) {
0116: int length = text.length();
0117: isRightOriented = (gc.getStyle() & SWT.MIRRORED) != 0;
0118:
0119: this .gc = gc;
0120: bidiSegments = offsets;
0121: renderPositions = new int[length];
0122: order = new int[length];
0123: dx = new int[length];
0124: classBuffer = new byte[length];
0125: if (length == 0) {
0126: glyphBuffer = new char[0];
0127: } else {
0128: glyphBuffer = BidiUtil.getRenderInfo(gc, text, order,
0129: classBuffer, dx, 0, offsets);
0130: if (ranges != null) {
0131: // If the font supports characters shaping, break up the font style ranges based on
0132: // the specified bidi segments. Each bidi segment will be treated separately
0133: // for font style purposes.
0134: StyleRange[] segmentedRanges;
0135: if (isCharacterShaped(gc))
0136: segmentedRanges = getSegmentedRangesFor(ranges);
0137: else
0138: segmentedRanges = ranges;
0139: Font normalFont = gc.getFont();
0140: gc.setFont(boldFont);
0141: for (int i = 0; i < segmentedRanges.length; i++) {
0142: StyleRange segmentedRange = segmentedRanges[i];
0143: int rangeStart = segmentedRange.start;
0144: int rangeLength = segmentedRange.length;
0145: // Font styled text needs to be processed so that the dx array reflects the styled
0146: // font.
0147: prepareFontStyledText(text, rangeStart, rangeLength);
0148: }
0149: gc.setFont(normalFont);
0150: }
0151: calculateTabStops(text, tabWidth);
0152: calculateRenderPositions();
0153: }
0154: }
0155:
0156: /**
0157: * Constructs an instance of this class for a line of text. This constructor
0158: * should be used when only ordering (not rendering) information is needed.
0159: * Only the class and order arrays will be filled during this call.
0160: * <p>
0161: *
0162: * @param gc the GC to use for rendering and measuring of this line.
0163: * @param text line that bidi data should be calculated for
0164: * @param offset text segments that should be measured and reordered
0165: * separately, may be needed to preserve the order of separate R2L
0166: * segments to each other
0167: */
0168: public StyledTextBidi(GC gc, String text, int[] offsets) {
0169: int length = text.length();
0170: isRightOriented = (gc.getStyle() & SWT.MIRRORED) != 0;
0171: this .gc = gc;
0172: bidiSegments = offsets;
0173: order = new int[length];
0174: classBuffer = new byte[length];
0175: BidiUtil.getOrderInfo(gc, text, order, classBuffer, 0, offsets);
0176: // initialize the unused arrays
0177: dx = new int[0];
0178: renderPositions = new int[0];
0179: glyphBuffer = new char[0];
0180:
0181: }
0182:
0183: /**
0184: * Adds a listener that should be called when the user changes the
0185: * keyboard layout for the specified window.
0186: * <p>
0187: *
0188: * @param control Control to add the keyboard language listener for.
0189: * Each window has its own keyboard language setting.
0190: * @param runnable the listener that should be called when the user
0191: * changes the keyboard layout.
0192: */
0193: static void addLanguageListener(Control control, Runnable runnable) {
0194: BidiUtil.addLanguageListener(control.handle, runnable);
0195: }
0196:
0197: /**
0198: * Answers the direction of the active keyboard language - either
0199: * L2R or R2L. The active keyboard language determines the direction
0200: * of the caret and can be changed by the user (e.g., via Alt-Shift on
0201: * Win32 platforms).
0202: * <p>
0203: *
0204: * @return the direction of the active keyboard language. SWT.LEFT (for L2R
0205: * language) or SWT.RIGHT (for R2L language) or SWT.DEFAULT if no R2L languages
0206: * are installed.
0207: */
0208: static int getKeyboardLanguageDirection() {
0209: int language = BidiUtil.getKeyboardLanguage();
0210: if (language == BidiUtil.KEYBOARD_BIDI) {
0211: return SWT.RIGHT;
0212: }
0213: if (BidiUtil.isKeyboardBidi()) {
0214: return SWT.LEFT;
0215: }
0216: return SWT.DEFAULT;
0217: }
0218:
0219: /**
0220: * Returns whether the current platform supports a bidi language.
0221: * <p>
0222: *
0223: * @return true=bidi is supported, false otherwise.
0224: */
0225: static boolean isBidiPlatform() {
0226: return BidiUtil.isBidiPlatform();
0227: }
0228:
0229: /**
0230: * Returns whether the font set in the specified gc supports
0231: * character shaping.
0232: * <p>
0233: *
0234: * @param gc the GC that should be tested for character shaping.
0235: * @return
0236: * true=the font set in the specified gc supports character shaped glyphs
0237: * false=the font set in the specified gc doesn't support character shaped glyphs
0238: */
0239: static boolean isCharacterShaped(GC gc) {
0240: return (BidiUtil.getFontBidiAttributes(gc) & BidiUtil.GLYPHSHAPE) != 0;
0241: }
0242:
0243: /**
0244: * Returns whether the font set in the specified gc contains
0245: * ligatured glyphs.
0246: * <p>
0247: *
0248: * @param gc the GC that should be tested for ligatures.
0249: * @return
0250: * true=the font set in the specified gc contains ligatured glyphs.
0251: * false=the font set in the specified gc doesn't contain ligatured
0252: * glyphs.
0253: */
0254: static boolean isLigated(GC gc) {
0255: return (BidiUtil.getFontBidiAttributes(gc) & BidiUtil.LIGATE) != 0;
0256: }
0257:
0258: /**
0259: * Removes the keyboard language listener for the specified window.
0260: * <p>
0261: *
0262: * @param control window to remove the keyboard language listener from.
0263: */
0264: static void removeLanguageListener(Control control) {
0265: BidiUtil.removeLanguageListener(control.handle);
0266: }
0267:
0268: /**
0269: * Calculates render positions using the glyph distance values in the dx array.
0270: */
0271: private void calculateRenderPositions() {
0272: renderPositions = new int[dx.length];
0273: renderPositions[0] = StyledText.XINSET;
0274: for (int i = 0; i < dx.length - 1; i++) {
0275: renderPositions[i + 1] = renderPositions[i] + dx[i];
0276: }
0277: }
0278:
0279: /**
0280: * Calculate the line's tab stops and adjust the dx array to
0281: * reflect the width of tab characters.
0282: * <p>
0283: *
0284: * @param text the original line text (not reordered) containing
0285: * tab characters.
0286: * @param tabWidth number of pixels that one tab character represents
0287: */
0288: private void calculateTabStops(String text, int tabWidth) {
0289: int tabIndex = text.indexOf('\t', 0);
0290: int logicalIndex = 0;
0291: int x = 0;
0292: int spaceWidth = gc.stringExtent(" ").x;
0293:
0294: while (tabIndex != -1) {
0295: for (; logicalIndex < tabIndex; logicalIndex++) {
0296: x += dx[order[logicalIndex]];
0297: }
0298: int tabStop = x + tabWidth;
0299: // make sure tab stop is at least one space width apart
0300: // from the last character. fixes 4844.
0301: if (tabWidth - tabStop % tabWidth < spaceWidth) {
0302: tabStop += tabWidth;
0303: }
0304: tabStop -= tabStop % tabWidth;
0305: dx[order[tabIndex]] = tabStop - x;
0306: tabIndex = text.indexOf('\t', tabIndex + 1);
0307: }
0308: }
0309:
0310: /**
0311: * Renders the specified text segment. All text is rendered L2R
0312: * regardless of the direction of the text. The rendered text may
0313: * be visually discontiguous if the text segment is bidirectional.
0314: * <p>
0315: *
0316: * @param logicalStart start offset in the logical text
0317: * @param length number of logical characters to render
0318: * @param xOffset x location of the line start
0319: * @param yOffset y location of the line start
0320: */
0321: void drawBidiText(String text, int logicalStart, int length,
0322: int xOffset, int yOffset) {
0323: Enumeration directionRuns;
0324: int endOffset = logicalStart + length;
0325:
0326: if (logicalStart < 0 || endOffset > getTextLength()) {
0327: return;
0328: }
0329: directionRuns = getDirectionRuns(logicalStart, length)
0330: .elements();
0331: while (directionRuns.hasMoreElements()) {
0332: DirectionRun run = (DirectionRun) directionRuns
0333: .nextElement();
0334: int x = xOffset + run.getRenderStartX();
0335: if (glyphBuffer != null) {
0336: int visualStart = run.getVisualStart();
0337: int visualEnd = run.getVisualEnd();
0338: drawGlyphs(visualStart, visualEnd - visualStart + 1, x,
0339: yOffset);
0340: } else {
0341: drawChars(text, run.logicalStart, run.logicalEnd, x,
0342: yOffset, classBuffer[run.logicalStart]);
0343: }
0344: }
0345: }
0346:
0347: /**
0348: * Renders a segment of glyphs. Glyphs are visual objects so the
0349: * start and length are visual as well. Glyphs are always rendered L2R.
0350: * <p>
0351: *
0352: * @param visualStart start offset of the glyphs to render relative to the
0353: * line start.
0354: * @param length number of glyphs to render
0355: * @param x x location to render at
0356: * @param y y location to render at
0357: */
0358: private void drawGlyphs(int visualStart, int length, int x, int y) {
0359: char[] renderBuffer = new char[length];
0360: int[] renderDx = new int[length];
0361: if (length == 0) {
0362: return;
0363: }
0364: System.arraycopy(glyphBuffer, visualStart, renderBuffer, 0,
0365: length);
0366: // copy the distance values for the desired rendering range
0367: System.arraycopy(dx, visualStart, renderDx, 0, length);
0368: BidiUtil.drawGlyphs(gc, renderBuffer, renderDx, x, y);
0369: }
0370:
0371: private void drawChars(String text, int startOffset, int endOffset,
0372: int x, int y, byte bidiClass) {
0373: BidiUtil.drawChars(gc, text, startOffset, endOffset, x, y,
0374: bidiClass, order, renderPositions, dx);
0375: }
0376:
0377: /**
0378: * Fills a rectangle spanning the given logical range.
0379: * The rectangle may be visually discontiguous if the text segment
0380: * is bidirectional.
0381: * <p>
0382: *
0383: * @param logicalStart logcial start offset of the rectangle
0384: * @param length number of logical characters the rectangle should span
0385: * @param xOffset x location of the line start
0386: * @param yOffset y location of the line start
0387: * @param height height of the rectangle
0388: */
0389: void fillBackground(int logicalStart, int length, int xOffset,
0390: int yOffset, int height) {
0391: Enumeration directionRuns = getDirectionRuns(logicalStart,
0392: length).elements();
0393:
0394: if (logicalStart < 0 || logicalStart + length > getTextLength()) {
0395: return;
0396: }
0397: while (directionRuns.hasMoreElements()) {
0398: DirectionRun run = (DirectionRun) directionRuns
0399: .nextElement();
0400: int startX = run.getRenderStartX();
0401: gc.fillRectangle(xOffset + startX, yOffset, run
0402: .getRenderStopX()
0403: - startX, height);
0404: }
0405: }
0406:
0407: /**
0408: * Returns the offset and direction that will be used to position the caret for
0409: * the given x location. The caret will be placed in front of or behind the
0410: * character at location x depending on what type of character (i.e., R2L or L2R)
0411: * is at location x. This method is used for positioning the caret when a mouse
0412: * click occurs within the widget.
0413: * <p>
0414: *
0415: * @param x the x location of the character in the line.
0416: * @return array containing the caret offset and direction for the x location.
0417: * index 0: offset relative to the start of the line
0418: * index 1: direction, either ST.COLUMN_NEXT or ST.COLUMN_PREVIOUS.
0419: * The direction is used to control the caret position at direction
0420: * boundaries. The semantics follow the behavior for keyboard cursor
0421: * navigation.
0422: * Example: RRRLLL
0423: * Pressing cursor left (COLUMN_PREVIOUS) in the L2R segment places the cursor
0424: * in front of the first character of the L2R segment. Pressing cursor right
0425: * (COLUMN_NEXT) in a R2L segment places the cursor behind the last character
0426: * of the R2L segment. However, both are the same logical offset.
0427: */
0428: int[] getCaretOffsetAndDirectionAtX(int x) {
0429: int lineLength = getTextLength();
0430:
0431: if (lineLength == 0) {
0432: return new int[] { 0, 0 };
0433: }
0434: int eol = renderPositions[renderPositions.length - 1]
0435: + dx[dx.length - 1];
0436: if (x >= eol) {
0437: return new int[] { lineLength, ST.COLUMN_NEXT };
0438: }
0439: // get the visual offset of the clicked character
0440: int visualOffset = getVisualOffsetAtX(x);
0441: // figure out if the character was clicked on the right or left
0442: int halfway = renderPositions[visualOffset] + dx[visualOffset]
0443: / 2;
0444: boolean visualLeft;
0445: int offset = getLogicalOffset(visualOffset);
0446: int direction;
0447:
0448: if (isRightOriented) {
0449: visualLeft = (x > halfway);
0450: } else {
0451: visualLeft = (x <= halfway);
0452: }
0453: if (isRightToLeft(offset)) {
0454: if (visualLeft) {
0455: if (isLigated(gc)) {
0456: // the caret should be positioned after the last
0457: // character of the ligature
0458: offset = getLigatureEndOffset(offset);
0459: }
0460: offset++;
0461: // position the caret as if the caret is to the right
0462: // of the character at location x and the NEXT key is
0463: // pressed
0464: direction = ST.COLUMN_NEXT;
0465: } else {
0466: // position the caret as if the caret is to the left
0467: // of the character at location x and the PREVIOUS
0468: // key is pressed
0469: direction = ST.COLUMN_PREVIOUS;
0470: }
0471: } else {
0472: if (visualLeft) {
0473: // position the caret as if the caret is to the left
0474: // of the character at location x and the PREVIOUS
0475: // key is pressed
0476: direction = ST.COLUMN_PREVIOUS;
0477: } else {
0478: offset++;
0479: // position the caret as if the caret is to the right
0480: // of the character at location x and the NEXT key is
0481: // pressed
0482: direction = ST.COLUMN_NEXT;
0483: }
0484: }
0485: return new int[] { offset, direction };
0486: }
0487:
0488: /**
0489: * Returns the direction segments that are in the specified text
0490: * range. The text range may be visually discontiguous if the
0491: * text is bidirectional. Each returned direction run has a single
0492: * direction and the runs all go from left to right, regardless of
0493: * the direction of the text in the segment. User specified segments
0494: * (via BidiSegmentListener) are taken into account and result in
0495: * separate direction runs.
0496: * <p>
0497: *
0498: * @param logicalStart offset of the logcial start of the first
0499: * direction segment
0500: * @param length length of the text included in the direction
0501: * segments
0502: * @return the direction segments that are in the specified
0503: * text range, each segment has a single direction.
0504: */
0505: private Vector getDirectionRuns(int logicalStart, int length) {
0506: Vector directionRuns = new Vector();
0507: int logicalEnd = logicalStart + length - 1;
0508: int segmentLogicalStart = logicalStart;
0509: int segmentLogicalEnd = segmentLogicalStart;
0510: int checkSide = isRightOriented ? -1 : 1;
0511:
0512: if (logicalEnd < getTextLength()) {
0513: int bidiSegmentIndex = 0;
0514: int bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
0515:
0516: // Find the bidi segment that the direction runs start in.
0517: // There will always be at least on bidi segment (for the entire line).
0518: while (bidiSegmentIndex < bidiSegments.length - 2
0519: && bidiSegmentEnd <= logicalStart) {
0520: bidiSegmentIndex++;
0521: bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
0522: }
0523: while (segmentLogicalEnd <= logicalEnd) {
0524: boolean isRightToLeftSegment = isRightToLeft(segmentLogicalStart);
0525: // Search for the end of the direction segment. Each segment needs to
0526: // be rendered separately.
0527: // E.g., 11211 (1=R2L, 2=L2R), rendering from logical index 0 to 5
0528: // would be visual 1 to 4 and would thus miss visual 0. Rendering the
0529: // segments separately would render from visual 1 to 0, then 2, then
0530: // 4 to 3.
0531: while (segmentLogicalEnd < logicalEnd
0532: &&
0533: // If our segment type is RtoL, the order index for the next character should be one less, if there
0534: // is no direction change.
0535: // If our segment type is LtoR, the order index for the next character will be one more if there is
0536: // no direction change.
0537: (order[segmentLogicalEnd + 1] == order[segmentLogicalEnd]
0538: || // treat ligatures as part of the direction segment
0539: (isRightToLeftSegment && (order[segmentLogicalEnd + 1]
0540: + checkSide == order[segmentLogicalEnd])) || (isRightToLeftSegment == false && (order[segmentLogicalEnd + 1]
0541: - checkSide == order[segmentLogicalEnd])))
0542: &&
0543: // For correct numeric shaping, don't mix segments with different bidi classes.
0544: // E.g., logical ABC3 (capitalized=R2L, 3=local number) would be visually completely reversed - 3CBA, however,
0545: // it's not homogeneous and consists of 2 different directional runs.
0546: // Compare: logical ABC34 (4=another local number) look in visual form as 34CBA, i.e. not completely reversed.
0547: classBuffer[segmentLogicalEnd + 1] == classBuffer[segmentLogicalEnd]
0548: && segmentLogicalEnd + 1 < bidiSegmentEnd) {
0549: segmentLogicalEnd++;
0550: }
0551: directionRuns.addElement(new DirectionRun(
0552: segmentLogicalStart, segmentLogicalEnd));
0553: segmentLogicalStart = ++segmentLogicalEnd;
0554: // The current direction run ends at a bidi segment end. Get the next bidi segment.
0555: if (segmentLogicalEnd == bidiSegmentEnd
0556: && bidiSegmentIndex < bidiSegments.length - 2) {
0557: bidiSegmentIndex++;
0558: bidiSegmentEnd = bidiSegments[bidiSegmentIndex + 1];
0559: }
0560: }
0561: }
0562: return directionRuns;
0563: }
0564:
0565: /**
0566: * Returns the offset of the last character comprising a ligature.
0567: * <p>
0568: *
0569: * @param offset the logical offset of a character that may be a
0570: * ligature.
0571: * @return the offset of the last character comprising a ligature.
0572: */
0573: int getLigatureEndOffset(int offset) {
0574: int newOffset = offset;
0575: int i = offset + 1;
0576:
0577: // assume only bidi languages support ligatures
0578: if (offset < 0 || offset >= order.length
0579: || isRightToLeft(offset) == false) {
0580: return offset;
0581: }
0582: // a ligature is a visual character that is comprised of
0583: // multiple logical characters, thus each logical part of
0584: // a ligature will have the same order value
0585: while (i < order.length && (order[i] == order[offset])) {
0586: newOffset = i;
0587: i++;
0588: }
0589: return newOffset;
0590: }
0591:
0592: /**
0593: * Returns the offset of the first character comprising a ligature.
0594: * <p>
0595: *
0596: * @param offset the logical offset of a character that may be a
0597: * ligature.
0598: * @return the offset of the first character comprising a ligature.
0599: */
0600: int getLigatureStartOffset(int offset) {
0601: int newOffset = offset;
0602: int i = offset - 1;
0603:
0604: // assume only bidi languages support ligatures
0605: if (offset < 0 || offset >= order.length
0606: || isRightToLeft(offset) == false) {
0607: return offset;
0608: }
0609: // a ligature is a visual character that is comprised of
0610: // multiple logical characters, thus each logical part of
0611: // a ligature will have the same order value
0612: while (i >= 0 && (order[i] == order[offset])) {
0613: newOffset = i;
0614: i--;
0615: }
0616: return newOffset;
0617: }
0618:
0619: /**
0620: * Returns the logical offset of the character at the specified
0621: * visual offset.
0622: * <p>
0623: *
0624: * @param visualOffset the visual offset
0625: * @return the logical offset of the character at <code>visualOffset</code>.
0626: */
0627: int getLogicalOffset(int visualOffset) {
0628: int logicalOffset = 0;
0629:
0630: while (logicalOffset < order.length
0631: && order[logicalOffset] != visualOffset) {
0632: logicalOffset++;
0633: }
0634: return logicalOffset;
0635: }
0636:
0637: /**
0638: * Returns the offset of the character at the specified x location.
0639: * <p>
0640: *
0641: * @param x the location of the character
0642: * @return the logical offset of the character at the specified x
0643: * location.
0644: */
0645: int getOffsetAtX(int x) {
0646: int visualOffset;
0647:
0648: if (getTextLength() == 0) {
0649: return 0;
0650: }
0651: if (x >= renderPositions[renderPositions.length - 1]
0652: + dx[dx.length - 1]) {
0653: // Return when x is past the end of the line. Fixes 1GLADBK.
0654: return -1;
0655: }
0656: visualOffset = getVisualOffsetAtX(x);
0657: return getLogicalOffset(visualOffset);
0658: }
0659:
0660: /**
0661: * Returns the reordering indices that map between logical and
0662: * visual index of characters in the specified range.
0663: * <p>
0664: *
0665: * @param start start offset of the reordering indices
0666: * @param length number of reordering indices to return
0667: * @return the reordering indices that map between logical and
0668: * visual index of characters in the specified range. Relative
0669: * to the start of the range.
0670: */
0671: private int[] getRenderIndexesFor(int start, int length) {
0672: int[] positions = new int[length];
0673: int end = start + length;
0674:
0675: for (int i = start; i < end; i++) {
0676: positions[i - start] = order[i];
0677: }
0678: return positions;
0679: }
0680:
0681: /**
0682: * Break up the given ranges such that each range is fully contained within a bidi
0683: * segment.
0684: */
0685: private StyleRange[] getSegmentedRangesFor(StyleRange[] ranges) {
0686: if ((bidiSegments == null) || (bidiSegments.length == 0))
0687: return ranges;
0688: Vector newRanges = new Vector();
0689: int j = 0;
0690: int startSegment;
0691: int endSegment;
0692: for (int i = 0; i < ranges.length; i++) {
0693: int start = ranges[i].start;
0694: int end = start + ranges[i].length;
0695: startSegment = -1;
0696: endSegment = -1;
0697: boolean done = false;
0698: while (j < bidiSegments.length && !done) {
0699: if (bidiSegments[j] <= start) {
0700: startSegment = j;
0701: }
0702: if (bidiSegments[j] >= end) {
0703: endSegment = j - 1;
0704: j--;
0705: }
0706: done = (startSegment != -1) && (endSegment != -1);
0707: if (!done)
0708: j++;
0709: }
0710: if (startSegment == endSegment) {
0711: // range is within one segment
0712: StyleRange newStyle = new StyleRange(start,
0713: end - start, null, null);
0714: newRanges.addElement(newStyle);
0715: } else if (startSegment > endSegment) {
0716: // range is within no segment (i.e., it's empty)
0717: } else {
0718: // range spans multiple segments
0719: StyleRange newStyle = new StyleRange(start,
0720: bidiSegments[startSegment + 1] - start, null,
0721: null);
0722: newRanges.addElement(newStyle);
0723: startSegment++;
0724: for (int k = startSegment; k < endSegment; k++) {
0725: newStyle = new StyleRange(bidiSegments[k],
0726: bidiSegments[k + 1] - bidiSegments[k],
0727: null, null);
0728: newRanges.addElement(newStyle);
0729: }
0730: newStyle = new StyleRange(bidiSegments[endSegment], end
0731: - bidiSegments[endSegment], null, null);
0732: newRanges.addElement(newStyle);
0733: }
0734: }
0735: StyleRange[] rangeArray = new StyleRange[newRanges.size()];
0736: for (int i = 0; i < newRanges.size(); i++) {
0737: rangeArray[i] = (StyleRange) newRanges.elementAt(i);
0738: }
0739: return rangeArray;
0740: }
0741:
0742: /**
0743: * Returns the number of characters in the line.
0744: * <p>
0745: *
0746: * @return the number of characters in the line.
0747: */
0748: private int getTextLength() {
0749: return dx.length;
0750: }
0751:
0752: /**
0753: * Returns the x position at the specified offset in the line.
0754: * <p>
0755: * @param logicalOffset offset of the character in the line.
0756: * @return the x position at the specified offset in the line.
0757: */
0758: int getTextPosition(int logicalOffset) {
0759: return getTextPosition(logicalOffset, ST.COLUMN_NEXT);
0760: }
0761:
0762: /**
0763: * Returns the x position at the specified offset in the line.
0764: * The direction parameter is used to determine the position
0765: * at direction boundaries. If the logical offset is between a R2L
0766: * and a L2R segment, pressing cursor left in the L2R segment places
0767: * the position in front of the first character of the L2R segment; whereas
0768: * pressing cursor right in the R2L segment places the position behind
0769: * the last character of the R2L segment. However, both x positions
0770: * are at the same logical offset.
0771: * <p>
0772: *
0773: * @param logicalOffset offset of the character in the line
0774: * @param direction direction the caret moved to the specified location.
0775: * either ST.COLUMN_NEXT (right cursor key) or ST.COLUMN_PREVIOUS (left cursor key) .
0776: * @return the x position at the specified offset in the line,
0777: * taking the direction into account as described above.
0778: */
0779: int getTextPosition(int logicalOffset, int direction) {
0780: int caretX;
0781:
0782: if (getTextLength() == 0 || logicalOffset < 0) {
0783: return StyledText.XINSET;
0784: }
0785:
0786: boolean isRightToLeft = isRightToLeft(logicalOffset);
0787: // at or past end of line?
0788: if (logicalOffset >= order.length) {
0789: logicalOffset = Math.min(logicalOffset, order.length - 1);
0790: isRightToLeft = isRightToLeft(logicalOffset);
0791: int visualOffset = order[logicalOffset];
0792: if ((!isRightOriented && !isRightToLeft)
0793: || (isRightOriented && isRightToLeft)) {
0794: caretX = renderPositions[visualOffset]
0795: + dx[visualOffset];
0796: } else {
0797: caretX = renderPositions[visualOffset];
0798: }
0799: } else
0800: // at beginning of line?
0801: if (logicalOffset == 0) {
0802: int visualOffset = order[logicalOffset];
0803: if ((!isRightOriented && !isRightToLeft)
0804: || (isRightOriented && isRightToLeft)) {
0805: caretX = renderPositions[visualOffset];
0806: } else {
0807: caretX = renderPositions[visualOffset]
0808: + dx[visualOffset];
0809: }
0810: } else
0811: // always consider local numbers as a direction boundary
0812: // because they represent a discontiguous text segment coming from
0813: // a R2L segment.
0814: // treat user specified direction segments like real direction changes.
0815: if (direction == ST.COLUMN_NEXT
0816: && (isRightToLeft(logicalOffset) != isRightToLeft(logicalOffset - 1)
0817: || isLocalNumber(logicalOffset) != isLocalNumber(logicalOffset - 1) || isStartOfBidiSegment(logicalOffset))) {
0818: int visualOffset = order[logicalOffset - 1];
0819: // moving between segments.
0820: // do not consider local numbers as R2L here, to determine position,
0821: // because local numbers are navigated L2R and we want the caret to
0822: // be to the right of the number. see 1GK9API
0823: isRightToLeft = isRightToLeft(logicalOffset - 1);
0824: if ((!isRightOriented && !isRightToLeft)
0825: || (isRightOriented && isRightToLeft)) {
0826: caretX = renderPositions[visualOffset]
0827: + dx[visualOffset];
0828: } else {
0829: caretX = renderPositions[visualOffset];
0830: }
0831: } else
0832: // consider local numbers as R2L in determining direction boundaries.
0833: // fixes 1GK9API.
0834: if (direction == ST.COLUMN_PREVIOUS
0835: && isRightToLeftInput(logicalOffset) != isRightToLeftInput(logicalOffset - 1)) {
0836: int visualOffset = order[logicalOffset];
0837: // moving between segments.
0838: // consider local numbers as R2L here, to determine position, because
0839: // we want to stay in L2R segment and place the cursor to the left of
0840: // first L2R character. see 1GK9API
0841: isRightToLeft = isRightToLeft(logicalOffset - 1);
0842: if ((!isRightOriented && !isRightToLeft)
0843: || (isRightOriented && isRightToLeft)) {
0844: caretX = renderPositions[visualOffset]
0845: + dx[visualOffset];
0846: } else {
0847: caretX = renderPositions[visualOffset];
0848: }
0849: } else {
0850: int visualOffset = order[logicalOffset];
0851: if ((!isRightOriented && !isRightToLeft)
0852: || (isRightOriented && isRightToLeft)) {
0853: caretX = renderPositions[visualOffset];
0854: } else {
0855: caretX = renderPositions[visualOffset]
0856: + dx[visualOffset];
0857: }
0858: }
0859: return caretX;
0860: }
0861:
0862: /**
0863: * Returns the width in pixels of the line.
0864: * <p>
0865: *
0866: * @return the width in pixels of the line.
0867: */
0868: int getTextWidth() {
0869: int width = 0;
0870:
0871: if (getTextLength() > 0) {
0872: width = renderPositions[renderPositions.length - 1]
0873: + dx[dx.length - 1];
0874: }
0875: return width;
0876: }
0877:
0878: /**
0879: * Returns the visual offset of the character at the specified
0880: * logical offset.
0881: * <p>
0882: *
0883: * @param logicalOffset the logical offset
0884: * @return the visual offset of the character at <code>logicalOffset</code>.
0885: */
0886: int getVisualOffset(int logicalOffset) {
0887: return order[logicalOffset];
0888: }
0889:
0890: /**
0891: * Returns the visual offset of the character at the specified x
0892: * location.
0893: * <p>
0894: *
0895: * @param x the location of the character
0896: * @return the visual offset of the character at the specified x
0897: * location.
0898: */
0899: private int getVisualOffsetAtX(int x) {
0900: int lineLength = getTextLength();
0901: int low = -1;
0902: int high = lineLength;
0903:
0904: while (high - low > 1) {
0905: int offset = (high + low) / 2;
0906: int visualX = renderPositions[offset];
0907:
0908: // visualX + dx is the start of the next character. Restrict right/high
0909: // search boundary only if x is before next character. Fixes 1GL4ZVE.
0910: if (x < visualX + dx[offset]) {
0911: high = offset;
0912: } else if (high == lineLength && high - offset == 1) {
0913: // requested x location is past end of line
0914: high = -1;
0915: } else {
0916: low = offset;
0917: }
0918: }
0919: return high;
0920: }
0921:
0922: /**
0923: * Returns if the character at the given offset is a latin number.
0924: * <p>
0925: *
0926: * @param logicalIndex the index of the character
0927: * @return
0928: * true=the character at the specified index is a latin number
0929: * false=the character at the specified index is not a latin number
0930: */
0931: boolean isLatinNumber(int logicalIndex) {
0932: boolean isLatinNumber = false;
0933:
0934: if (logicalIndex >= 0 && logicalIndex < classBuffer.length) {
0935: isLatinNumber = classBuffer[logicalIndex] == BidiUtil.CLASS_EUROPEAN_NUMBER;
0936: }
0937: return isLatinNumber;
0938: }
0939:
0940: /**
0941: * Returns if the character at the given offset is a local number.
0942: * <p>
0943: *
0944: * @param logicalIndex the index of the character
0945: * @return
0946: * true=the character at the specified index is a local number
0947: * false=the character at the specified index is not a local number
0948: */
0949: boolean isLocalNumber(int logicalIndex) {
0950: boolean isLocalNumber = false;
0951:
0952: if (logicalIndex >= 0 && logicalIndex < classBuffer.length) {
0953: isLocalNumber = classBuffer[logicalIndex] == BidiUtil.CLASS_ARABIC_NUMBER;
0954: }
0955: return isLocalNumber;
0956: }
0957:
0958: /**
0959: * Returns the direction of the character at the specified index.
0960: * Used for rendering and caret positioning where local numbers (e.g.,
0961: * national Arabic, or Hindi, numbers) are considered left-to-right.
0962: * <p>
0963: *
0964: * @param logicalIndex the index of the character
0965: * @return
0966: * true=the character at the specified index is in a right-to-left
0967: * codepage (e.g., Hebrew, Arabic).
0968: * false=the character at the specified index is in a left-to-right/latin
0969: * codepage.
0970: */
0971: boolean isRightToLeft(int logicalIndex) {
0972: boolean isRightToLeft = false;
0973:
0974: if (logicalIndex >= 0 && logicalIndex < classBuffer.length) {
0975: isRightToLeft = (classBuffer[logicalIndex] == BidiUtil.CLASS_RTL_ARABIC)
0976: || (classBuffer[logicalIndex] == BidiUtil.CLASS_RTL);
0977: }
0978: return isRightToLeft;
0979: }
0980:
0981: /**
0982: * Returns the direction of the character at the specified index.
0983: * Used for setting the keyboard language where local numbers (e.g.,
0984: * national Arabic, or Hindi, numbers) are considered right-to-left.
0985: * <p>
0986: *
0987: * @param logicalIndex the index of the character
0988: * @return
0989: * true=the character at the specified index is in a right-to-left
0990: * codepage (e.g., Hebrew, Arabic).
0991: * false=the character at the specified index is in a left-to-right/latin
0992: * codepage.
0993: */
0994: boolean isRightToLeftInput(int logicalIndex) {
0995: boolean isRightToLeft = false;
0996:
0997: if (logicalIndex >= 0 && logicalIndex < classBuffer.length) {
0998: isRightToLeft = (classBuffer[logicalIndex] == BidiUtil.CLASS_RTL_ARABIC)
0999: || (classBuffer[logicalIndex] == BidiUtil.CLASS_RTL)
1000: || (classBuffer[logicalIndex] == BidiUtil.CLASS_ARABIC_NUMBER);
1001: }
1002: return isRightToLeft;
1003: }
1004:
1005: /**
1006: * Returns whether the specified index is the start of a user
1007: * specified direction segment.
1008: * <p>
1009: *
1010: * @param logicalIndex the index to test
1011: * @return true=the specified index is the start of a user specified
1012: * direction segment, false otherwise
1013: */
1014: private boolean isStartOfBidiSegment(int logicalIndex) {
1015: for (int i = 0; i < bidiSegments.length; i++) {
1016: if (bidiSegments[i] == logicalIndex)
1017: return true;
1018: }
1019: return false;
1020: }
1021:
1022: /**
1023: * Reorders and calculates render positions for the specified sub-line
1024: * of text. The results will be merged with the data for the rest of
1025: * the line .
1026: * <p>
1027: *
1028: * @param textline the entire line of text that this object represents.
1029: * @param logicalStart the start offset of the first character to
1030: * reorder.
1031: * @param length the number of characters to reorder
1032: */
1033: private void prepareFontStyledText(String textline,
1034: int logicalStart, int length) {
1035: int byteCount = length;
1036: int flags = 0;
1037: String text = textline.substring(logicalStart, logicalStart
1038: + length);
1039:
1040: // Figure out what is before and after the substring so that the proper character
1041: // shaping will occur. Character shaping will not occur across bidi segments, so
1042: // if the styled text starts or ends on a bidi segment, do not process the text
1043: // for character shaping.
1044: if (logicalStart != 0
1045: && isCharacterShaped(gc)
1046: && !isStartOfBidiSegment(logicalStart)
1047: && !Compatibility.isWhitespace(textline
1048: .charAt(logicalStart - 1))
1049: && isRightToLeft(logicalStart - 1)) {
1050: // if the start of the substring is not the beginning of the
1051: // text line, check to see what is before the string
1052: flags |= BidiUtil.LINKBEFORE;
1053: }
1054: if ((logicalStart + byteCount) != dx.length
1055: && isCharacterShaped(gc)
1056: && !isStartOfBidiSegment(logicalStart + length)
1057: && !Compatibility.isWhitespace(textline
1058: .charAt(logicalStart + byteCount))
1059: && isRightToLeft(logicalStart + byteCount)) {
1060: // if the end of the substring is not the end of the text line,
1061: // check to see what is after the substring
1062: flags |= BidiUtil.LINKAFTER;
1063: }
1064: // set classification values for the substring
1065: flags |= BidiUtil.CLASSIN;
1066: byte[] classArray = new byte[byteCount];
1067: int[] renderIndexes = getRenderIndexesFor(logicalStart,
1068: byteCount);
1069: for (int i = 0; i < byteCount; i++) {
1070: classArray[i] = classBuffer[renderIndexes[i]];
1071: }
1072: int[] dxArray = new int[byteCount];
1073: int[] orderArray = new int[byteCount];
1074: char[] boldGlyphBuffer;
1075: boldGlyphBuffer = BidiUtil.getRenderInfo(gc, text, orderArray,
1076: classArray, dxArray, flags, new int[] { 0,
1077: text.length() });
1078: // update the existing dx/glyph arrays with the new values based on the bold font
1079: for (int i = 0; i < dxArray.length; i++) {
1080: int index = orderArray[i];
1081: int visualIndex = renderIndexes[i];
1082: dx[visualIndex] = dxArray[index];
1083: glyphBuffer[visualIndex] = boldGlyphBuffer[index];
1084: }
1085: }
1086:
1087: /**
1088: * Redraws a rectangle spanning the given logical range.
1089: * The rectangle may be visually discontiguous if the text segment
1090: * is bidirectional.
1091: * <p>
1092: *
1093: * @param parent window that should be invalidated
1094: * @param logicalStart logcial start offset of the rectangle
1095: * @param length number of logical characters the rectangle should span
1096: * @param xOffset x location of the line start
1097: * @param yOffset y location of the line start
1098: * @param height height of the invalidated rectangle
1099: */
1100: void redrawRange(Control parent, int logicalStart, int length,
1101: int xOffset, int yOffset, int height) {
1102: Enumeration directionRuns;
1103: if (logicalStart < 0 || logicalStart + length > getTextLength()) {
1104: return;
1105: }
1106: directionRuns = getDirectionRuns(logicalStart, length)
1107: .elements();
1108: while (directionRuns.hasMoreElements()) {
1109: DirectionRun run = (DirectionRun) directionRuns
1110: .nextElement();
1111: int startX = run.getRenderStartX();
1112: int endX = run.getRenderStopX();
1113: int runStartOffset = run.getVisualStart();
1114: int runEndOffset = run.getVisualEnd();
1115:
1116: // expand the redraw area by one character in both directions.
1117: // fixes bug 40019
1118: if (runStartOffset > 0) {
1119: startX = renderPositions[runStartOffset - 1];
1120: } else {
1121: startX = 0;
1122: }
1123: if (runEndOffset < renderPositions.length - 1) {
1124: endX = renderPositions[runEndOffset + 1]
1125: + dx[runEndOffset + 1];
1126: }
1127: parent.redraw(xOffset + startX, yOffset, endX - startX,
1128: height, true);
1129: }
1130: }
1131:
1132: /**
1133: * Sets the keyboard language to match the codepage of the character
1134: * at the specified offset.
1135: * Only distinguishes between left-to-right and right-to-left characters and
1136: * sets the keyboard language to a bidi or non-bidi language.
1137: * <p>
1138: *
1139: * @param logicalIndex logical offset of the character to use for
1140: * determining the new keyboard language.
1141: */
1142: void setKeyboardLanguage(int logicalIndex) {
1143: int language;
1144: int current = BidiUtil.getKeyboardLanguage();
1145:
1146: if (logicalIndex < 0 || logicalIndex >= classBuffer.length) {
1147: return;
1148: }
1149: if (isRightToLeftInput(logicalIndex)) {
1150: // keyboard already in bidi mode, since we cannot distinguish between
1151: // multiple bidi languages, just return
1152: if (current == BidiUtil.KEYBOARD_BIDI)
1153: return;
1154: language = BidiUtil.KEYBOARD_BIDI;
1155: } else {
1156: // keyboard already in non-bidi mode, since we cannot distinguish between
1157: // multiple non-bidi languages, just return
1158: if (current == BidiUtil.KEYBOARD_NON_BIDI)
1159: return;
1160: language = BidiUtil.KEYBOARD_NON_BIDI;
1161: }
1162: BidiUtil.setKeyboardLanguage(language);
1163: }
1164:
1165: /**
1166: * Sets the orientation (writing order) of the specified control. Text will
1167: * be right aligned for right to left writing order.
1168: * <p>
1169: *
1170: * @param orientation one of SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT
1171: * @return true if the orientation was changed, false if the orientation
1172: * could not be changed
1173: */
1174: static boolean setOrientation(Control control, int orientation) {
1175: return BidiUtil.setOrientation(control.handle, orientation);
1176: }
1177:
1178: /**
1179: * Returns a string representation of the receiver.
1180: * <p>
1181: *
1182: * @return a string representation of the receiver for
1183: * debugging purposes. The output order of the StyledTextbidi values
1184: * is as follows: order, render position, dx, character class, glyphs.
1185: */
1186: public String toString() {
1187: StringBuffer buf = new StringBuffer();
1188:
1189: buf.append("StyledTextBidi {{");
1190: // order
1191: for (int i = 0; i < order.length; i++) {
1192: if (i != 0) {
1193: buf.append(",");
1194: }
1195: buf.append(order[i]);
1196: }
1197: buf.append("}, {");
1198: // render positions
1199: for (int i = 0; i < renderPositions.length; i++) {
1200: if (i != 0) {
1201: buf.append(",");
1202: }
1203: buf.append(renderPositions[i]);
1204: }
1205: buf.append("}, {");
1206: // dx
1207: for (int i = 0; i < dx.length; i++) {
1208: if (i != 0) {
1209: buf.append(",");
1210: }
1211: buf.append(dx[i]);
1212: }
1213: buf.append("}, {");
1214: // character class
1215: for (int i = 0; i < classBuffer.length; i++) {
1216: if (i != 0) {
1217: buf.append(",");
1218: }
1219: buf.append(classBuffer[i]);
1220: }
1221: buf.append("}, {");
1222: // glyphs
1223: buf.append(glyphBuffer);
1224: buf.append("}}");
1225: return buf.toString();
1226: }
1227: }
|