| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.custom; |
| |
| |
| 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. Must have at least two elements, 0 and the text |
| * length. |
| */ |
| 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 = getSegmentedRangesFor(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) or SWT.DEFAULT if no R2L languages |
| * are installed. |
| */ |
| static int getKeyboardLanguageDirection() { |
| int language = BidiUtil.getKeyboardLanguage(); |
| if (language == BidiUtil.KEYBOARD_BIDI) { |
| return SWT.RIGHT; |
| } |
| if (BidiUtil.isKeyboardBidi()) { |
| return SWT.LEFT; |
| } |
| return SWT.DEFAULT; |
| } |
| /** |
| * 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; |
| } |
| /** |
| * 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 = 0; |
| 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; |
| int endOffset = logicalStart + length; |
| |
| if (logicalStart < 0 || endOffset > getTextLength()) { |
| return; |
| } |
| directionRuns = getDirectionRuns(logicalStart, length).elements(); |
| 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 < 0 || 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 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) { |
| boolean isRightToLeftSegment = isRightToLeft(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 && |
| // If our segment type is RtoL, the order index for the next character should be one less, if there |
| // is no direction change. |
| // If our segment type is LtoR, the order index for the next character will be one more if there is |
| // no direction change. |
| ((isRightToLeftSegment && (order[segmentLogicalEnd + 1]+ 1 == order[segmentLogicalEnd])) || |
| (isRightToLeftSegment == false && (order[segmentLogicalEnd + 1]- 1 == order[segmentLogicalEnd]))) && |
| 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> |
| * |
| * @param offset the logical offset of a character that may be a |
| * ligature. |
| * @return the offset of the last character comprising a ligature. |
| */ |
| int getLigatureEndOffset(int offset) { |
| int newOffset = offset; |
| int i = offset + 1; |
| |
| // assume only bidi languages support ligatures |
| if (offset < 0 || offset >= order.length || isRightToLeft(offset) == false) { |
| return offset; |
| } |
| // 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> |
| * |
| * @param offset the logical offset of a character that may be a |
| * ligature. |
| * @return the offset of the first character comprising a ligature. |
| */ |
| int getLigatureStartOffset(int offset) { |
| int newOffset = offset; |
| int i = offset - 1; |
| |
| // assume only bidi languages support ligatures |
| if (offset < 0 || offset >= order.length || isRightToLeft(offset) == false) { |
| return offset; |
| } |
| // 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 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[] getSegmentedRangesFor(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 x position at the specified offset in the line. |
| * <p> |
| * @param logicalOffset offset of the character in the line. |
| * @return the x position at the specified offset in the line. |
| */ |
| int getTextPosition(int logicalOffset) { |
| return getTextPosition(logicalOffset, ST.COLUMN_NEXT); |
| } |
| /** |
| * Returns the x position at the specified offset in the line. |
| * The direction parameter is used to determine the 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 position in front of the first character of the L2R segment; whereas |
| * pressing cursor right in the R2L segment places the position behind |
| * the last character of the R2L segment. However, both x 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 x position at the specified offset in the line, |
| * taking the direction into account as described above. |
| */ |
| int getTextPosition(int logicalOffset, int direction) { |
| int caretX; |
| |
| if (getTextLength() == 0 || logicalOffset < 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 |
| // always consider local numbers as a direction boundary |
| // because they represent a discontiguous text segment coming from |
| // a R2L segment. |
| // treat user specified direction segments like real direction changes. |
| if (direction == ST.COLUMN_NEXT && |
| (isRightToLeft(logicalOffset) != isRightToLeft(logicalOffset - 1) || |
| isLocalNumber(logicalOffset) != isLocalNumber(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 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 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 if the character at the given offset is a local number. |
| * <p> |
| * |
| * @param logicalIndex the index of the character |
| * @return |
| * true=the character at the specified index is a local number |
| * false=the character at the specified index is not a local number |
| */ |
| boolean isLocalNumber(int logicalIndex) { |
| boolean isLocalNumber = false; |
| |
| if (logicalIndex >= 0 && logicalIndex < classBuffer.length) { |
| isLocalNumber = classBuffer[logicalIndex] == BidiUtil.CLASS_LOCALNUMBER; |
| } |
| return isLocalNumber; |
| } |
| /** |
| * 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 >= 0 && 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 >= 0 && 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]; |
| char[] boldGlyphBuffer; |
| boldGlyphBuffer = BidiUtil.getRenderInfo(gc, text, orderArray, classArray, dxArray, flags, new int[] {0, text.length()}); |
| // update the existing dx/glyph arrays with the new values based on the bold font |
| for (int i = 0; i < dxArray.length; i++) { |
| int index = orderArray[i]; |
| int visualIndex = renderIndexes[i]; |
| dx[visualIndex] = dxArray[index]; |
| glyphBuffer[visualIndex] = boldGlyphBuffer[index]; |
| } |
| } |
| /** |
| * 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; |
| |
| if (logicalStart < 0 || logicalStart + length > getTextLength()) { |
| return; |
| } |
| directionRuns = getDirectionRuns(logicalStart, length).elements(); |
| 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 a bidi or non-bidi language. |
| * <p> |
| * |
| * @param logicalIndex logical offset of the character to use for |
| * determining the new keyboard language. |
| */ |
| void setKeyboardLanguage(int logicalIndex) { |
| int language; |
| int current = BidiUtil.getKeyboardLanguage(); |
| |
| if (logicalIndex < 0 || logicalIndex >= classBuffer.length) { |
| return; |
| } |
| if (isRightToLeftInput(logicalIndex)) { |
| // keyboard already in bidi mode, since we cannot distinguish between |
| // multiple bidi languages, just return |
| if (current == BidiUtil.KEYBOARD_BIDI) return; |
| language = BidiUtil.KEYBOARD_BIDI; |
| } else { |
| // keyboard already in non-bidi mode, since we cannot distinguish between |
| // multiple non-bidi languages, just return |
| if (current == BidiUtil.KEYBOARD_NON_BIDI) return; |
| language = BidiUtil.KEYBOARD_NON_BIDI; |
| } |
| 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(); |
| } |
| } |