X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.eclipse.swt.win32.win32.x86_64%2Fsrc%2Forg%2Feclipse%2Fswt%2Fcustom%2FStyledTextRenderer.java;fp=bundles%2Forg.eclipse.swt.win32.win32.x86_64%2Fsrc%2Forg%2Feclipse%2Fswt%2Fcustom%2FStyledTextRenderer.java;h=d972777663321222c5b78f7d49bbabdd5fcd5aeb;hb=6b98970d0458754dd67f789afbd0a39e1e7ac6eb;hp=0000000000000000000000000000000000000000;hpb=56a61575ce0d27b340cb12438c8a7f303842095e;p=simantics%2Fplatform.git diff --git a/bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/custom/StyledTextRenderer.java b/bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/custom/StyledTextRenderer.java new file mode 100644 index 000000000..d97277766 --- /dev/null +++ b/bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/custom/StyledTextRenderer.java @@ -0,0 +1,1938 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Anton Leherbauer (Wind River Systems) - Bug 439419 + * Angelo Zerr - Customize different line spacing of StyledText - Bug 522020 + *******************************************************************************/ +package org.eclipse.swt.custom; + + +import java.util.*; +import java.util.List; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A StyledTextRenderer renders the content of a StyledText widget. + * This class can be used to render to the display or to a printer. + */ +class StyledTextRenderer { + Device device; + StyledText styledText; + StyledTextContent content; + + /* Custom line spacing */ + StyledTextLineSpacingProvider lineSpacingProvider; + boolean lineSpacingComputing; + + /* Font info */ + Font regularFont, boldFont, italicFont, boldItalicFont; + int tabWidth; + int ascent, descent; + int averageCharWidth; + int tabLength; //tab length in spaces + + /* Line data */ + int topIndex = -1; + TextLayout[] layouts; + int lineCount; + LineSizeInfo[] lineSizes; + LineInfo[] lines; + int maxWidth; + int maxWidthLineIndex; + boolean idleRunning; + + /* Bullet */ + Bullet[] bullets; + int[] bulletsIndices; + int[] redrawLines; + + /* Style data */ + int[] ranges; + int styleCount; + StyleRange[] styles; + StyleRange[] stylesSet; + int stylesSetCount = 0; + boolean hasLinks, fixedPitch; + final static int BULLET_MARGIN = 8; + + final static boolean COMPACT_STYLES = true; + final static boolean MERGE_STYLES = true; + + final static int GROW = 32; + final static int IDLE_TIME = 50; + final static int CACHE_SIZE = 300; + + final static int BACKGROUND = 1 << 0; + final static int ALIGNMENT = 1 << 1; + final static int INDENT = 1 << 2; + final static int JUSTIFY = 1 << 3; + final static int SEGMENTS = 1 << 5; + final static int TABSTOPS = 1 << 6; + final static int WRAP_INDENT = 1 << 7; + final static int SEGMENT_CHARS = 1 << 8; + final static int VERTICAL_INDENT = 1 << 9; + + static class LineSizeInfo { + + private static final int RESETED_SIZE = -1; + + /* Line size */ + int height; + int width; + + public LineSizeInfo() { + resetSize(); + } + + /** + * Reset the line size. + */ + void resetSize() { + height = RESETED_SIZE; + width = RESETED_SIZE; + } + + /** + * Returns true if the TextLayout get from the layout pool can be directly used + * or must be refreshed with styles. + * + * @return true if the TextLayout get from the layout pool can be directly used + * or must be refreshed with styles. + */ + boolean canLayout() { + return !needsRecalculateWidth(); + } + + /** + * Returns true if it needs to recalculate the line size and false + * otherwise. + * + * @return true if it needs to recalculate the line size and false + * otherwise. + */ + boolean needsRecalculateSize() { + return needsRecalculateWidth() || needsRecalculateHeight(); + } + + /** + * Returns true if it needs to recalculate the line width and false + * otherwise. + * + * @return true if it needs to recalculate the line width and false + * otherwise. + */ + boolean needsRecalculateWidth() { + return width == RESETED_SIZE; + } + + /** + * Returns true if it needs to recalculate the line height and false + * otherwise. + * + * @return true if it needs to recalculate the line height and false + * otherwise. + */ + boolean needsRecalculateHeight() { + return height == RESETED_SIZE; + } + } + + static class LineInfo { + int flags; + Color background; + int alignment; + int indent; + int wrapIndent; + boolean justify; + int[] segments; + char[] segmentsChars; + int[] tabStops; + int verticalIndent; + + public LineInfo() { + } + public LineInfo(LineInfo info) { + if (info != null) { + flags = info.flags; + background = info.background; + alignment = info.alignment; + indent = info.indent; + wrapIndent = info.wrapIndent; + justify = info.justify; + segments = info.segments; + segmentsChars = info.segmentsChars; + tabStops = info.tabStops; + verticalIndent = info.verticalIndent; + } + } + } + static int cap (TextLayout layout, int offset) { + if (layout == null) return offset; + return Math.min (layout.getText().length() -1, Math.max (0, offset)); + } + +StyledTextRenderer(Device device, StyledText styledText) { + this.device = device; + this.styledText = styledText; +} +int addMerge(int[] mergeRanges, StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { + int rangeCount = styleCount << 1; + StyleRange endStyle = null; + int endStart = 0, endLength = 0; + if (modifyEnd < rangeCount) { + endStyle = styles[modifyEnd >> 1]; + endStart = ranges[modifyEnd]; + endLength = ranges[modifyEnd + 1]; + } + int grow = mergeCount - (modifyEnd - modifyStart); + if (rangeCount + grow >= ranges.length) { + int[] tmpRanges = new int[ranges.length + grow + (GROW << 1)]; + System.arraycopy(ranges, 0, tmpRanges, 0, modifyStart); + StyleRange[] tmpStyles = new StyleRange[styles.length + (grow >> 1) + GROW]; + System.arraycopy(styles, 0, tmpStyles, 0, modifyStart >> 1); + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyEnd, tmpRanges, modifyStart + mergeCount, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, tmpStyles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1)); + } + ranges = tmpRanges; + styles = tmpStyles; + } else { + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyEnd, ranges, modifyStart + mergeCount, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, styles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1)); + } + } + if (MERGE_STYLES) { + int j = modifyStart; + for (int i = 0; i < mergeCount; i += 2) { + if (j > 0 && ranges[j - 2] + ranges[j - 1] == mergeRanges[i] && mergeStyles[i >> 1].similarTo(styles[(j - 2) >> 1])) { + ranges[j - 1] += mergeRanges[i + 1]; + } else { + styles[j >> 1] = mergeStyles[i >> 1]; + ranges[j++] = mergeRanges[i]; + ranges[j++] = mergeRanges[i + 1]; + } + } + if (endStyle != null && ranges[j - 2] + ranges[j - 1] == endStart && endStyle.similarTo(styles[(j - 2) >> 1])) { + ranges[j - 1] += endLength; + modifyEnd += 2; + mergeCount += 2; + } + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyStart + mergeCount, ranges, j, rangeCount - modifyEnd); + System.arraycopy(styles, (modifyStart + mergeCount) >> 1, styles, j >> 1, styleCount - (modifyEnd >> 1)); + } + grow = (j - modifyStart) - (modifyEnd - modifyStart); + } else { + System.arraycopy(mergeRanges, 0, ranges, modifyStart, mergeCount); + System.arraycopy(mergeStyles, 0, styles, modifyStart >> 1, mergeCount >> 1); + } + styleCount += grow >> 1; + return grow; +} +int addMerge(StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { + int grow = mergeCount - (modifyEnd - modifyStart); + StyleRange endStyle = null; + if (modifyEnd < styleCount) endStyle = styles[modifyEnd]; + if (styleCount + grow >= styles.length) { + StyleRange[] tmpStyles = new StyleRange[styles.length + grow + GROW]; + System.arraycopy(styles, 0, tmpStyles, 0, modifyStart); + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyEnd, tmpStyles, modifyStart + mergeCount, styleCount - modifyEnd); + } + styles = tmpStyles; + } else { + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyEnd, styles, modifyStart + mergeCount, styleCount - modifyEnd); + } + } + if (MERGE_STYLES) { + int j = modifyStart; + for (int i = 0; i < mergeCount; i++) { + StyleRange newStyle = mergeStyles[i], style; + if (j > 0 && (style = styles[j - 1]).start + style.length == newStyle.start && newStyle.similarTo(style)) { + style.length += newStyle.length; + } else { + styles[j++] = newStyle; + } + } + StyleRange style = styles[j - 1]; + if (endStyle != null && style.start + style.length == endStyle.start && endStyle.similarTo(style)) { + style.length += endStyle.length; + modifyEnd++; + mergeCount++; + } + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyStart + mergeCount, styles, j, styleCount - modifyEnd); + } + grow = (j - modifyStart) - (modifyEnd - modifyStart); + } else { + System.arraycopy(mergeStyles, 0, styles, modifyStart, mergeCount); + } + styleCount += grow; + return grow; +} +void calculate(int startLine, int lineCount) { + int endLine = startLine + lineCount; + if (startLine < 0 || endLine > lineSizes.length) { + return; + } + int hTrim = styledText.leftMargin + styledText.rightMargin + styledText.getCaretWidth(); + for (int i = startLine; i < endLine; i++) { + LineSizeInfo line = getLineSize(i); + if (line.needsRecalculateSize()) { + TextLayout layout = getTextLayout(i); + Rectangle rect = layout.getBounds(); + line.width = rect.width + hTrim; + line.height = rect.height; + disposeTextLayout(layout); + } + if (line.width > maxWidth) { + maxWidth = line.width; + maxWidthLineIndex = i; + } + } +} +LineSizeInfo getLineSize(int i) { + if (lineSizes[i] == null) { + lineSizes[i] = new LineSizeInfo(); + } + return lineSizes[i]; +} +void calculateClientArea () { + int index = Math.max (0, styledText.getTopIndex()); + int lineCount = content.getLineCount(); + int height = styledText.getClientArea().height; + int y = 0; + /* + * There exists a possibility of ArrayIndexOutOfBounds Exception in + * below code, exact scenario not known. To avoid this exception added + * check for 'index' value, refer Bug 471192. + */ + while (height > y && lineCount > index && lineSizes.length > index) { + calculate(index, 1); + y += lineSizes[index++].height; + } +} +void calculateIdle () { + if (idleRunning) return; + Runnable runnable = new Runnable() { + @Override + public void run() { + if (styledText == null) return; + int i; + long start = System.currentTimeMillis(); + for (i = 0; i < lineCount; i++) { + LineSizeInfo line = getLineSize(i); + if (line.needsRecalculateSize()) { + calculate(i, 1); + if (System.currentTimeMillis() - start > IDLE_TIME) break; + } + } + if (i < lineCount) { + Display display = styledText.getDisplay(); + display.asyncExec(this); + } else { + idleRunning = false; + styledText.setScrollBars(true); + ScrollBar bar = styledText.getVerticalBar(); + if (bar != null) { + bar.setSelection(styledText.getVerticalScrollOffset()); + } + } + } + }; + Display display = styledText.getDisplay(); + display.asyncExec(runnable); + idleRunning = true; +} +void clearLineBackground(int startLine, int count) { + if (lines == null) return; + for (int i = startLine; i < startLine + count; i++) { + LineInfo info = lines[i]; + if (info != null) { + info.flags &= ~BACKGROUND; + info.background = null; + if (info.flags == 0) lines[i] = null; + } + } +} +void clearLineStyle(int startLine, int count) { + if (lines == null) return; + for (int i = startLine; i < startLine + count; i++) { + LineInfo info = lines[i]; + if (info != null) { + info.flags &= ~(ALIGNMENT | INDENT | VERTICAL_INDENT | WRAP_INDENT | JUSTIFY | TABSTOPS); + if (info.flags == 0) lines[i] = null; + } + } +} +void copyInto(StyledTextRenderer renderer) { + if (ranges != null) { + int[] newRanges = renderer.ranges = new int[styleCount << 1]; + System.arraycopy(ranges, 0, newRanges, 0, newRanges.length); + } + if (styles != null) { + StyleRange[] newStyles = renderer.styles = new StyleRange[styleCount]; + for (int i = 0; i < newStyles.length; i++) { + newStyles[i] = (StyleRange)styles[i].clone(); + } + renderer.styleCount = styleCount; + } + if (lines != null) { + LineInfo[] newLines = renderer.lines = new LineInfo[lineCount]; + for (int i = 0; i < newLines.length; i++) { + newLines[i] = new LineInfo(lines[i]); + } + renderer.lineCount = lineCount; + } +} +void dispose() { + if (boldFont != null) boldFont.dispose(); + if (italicFont != null) italicFont.dispose(); + if (boldItalicFont != null) boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + reset(); + content = null; + device = null; + styledText = null; +} +void disposeTextLayout (TextLayout layout) { + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] == layout) return; + } + } + layout.dispose(); +} +void drawBullet(Bullet bullet, GC gc, int paintX, int paintY, int index, int lineAscent, int lineDescent) { + StyleRange style = bullet.style; + GlyphMetrics metrics = style.metrics; + Color color = style.foreground; + if (color != null) gc.setForeground(color); + Font font = style.font; + if (font != null) gc.setFont(font); + String string = ""; + int type = bullet.type & (ST.BULLET_DOT|ST.BULLET_NUMBER|ST.BULLET_LETTER_LOWER|ST.BULLET_LETTER_UPPER); + switch (type) { + case ST.BULLET_DOT: string = "\u2022"; break; + case ST.BULLET_NUMBER: string = String.valueOf(index + 1); break; + case ST.BULLET_LETTER_LOWER: string = String.valueOf((char) (index % 26 + 97)); break; + case ST.BULLET_LETTER_UPPER: string = String.valueOf((char) (index % 26 + 65)); break; + } + if ((bullet.type & ST.BULLET_TEXT) != 0) string += bullet.text; + Display display = styledText.getDisplay(); + TextLayout layout = new TextLayout(display); + layout.setText(string); + layout.setAscent(lineAscent); + layout.setDescent(lineDescent); + style = (StyleRange)style.clone(); + style.metrics = null; + if (style.font == null) style.font = getFont(style.fontStyle); + layout.setStyle(style, 0, string.length()); + int x = paintX + Math.max(0, metrics.width - layout.getBounds().width - BULLET_MARGIN); + layout.draw(gc, x, paintY); + layout.dispose(); +} +int drawLine(int lineIndex, int paintX, int paintY, GC gc, Color widgetBackground, Color widgetForeground) { + TextLayout layout = getTextLayout(lineIndex); + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int lineLength = line.length(); + Point selection = styledText.getSelection(); + int selectionStart = selection.x - lineOffset; + int selectionEnd = selection.y - lineOffset; + if (styledText.getBlockSelection()) { + selectionStart = selectionEnd = 0; + } + Rectangle client = styledText.getClientArea(); + Color lineBackground = getLineBackground(lineIndex, null); + StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line); + if (event != null && event.lineBackground != null) lineBackground = event.lineBackground; + int height = layout.getBounds().height; + int verticalIndent = layout.getVerticalIndent(); + if (lineBackground != null) { + if (verticalIndent > 0) { + gc.setBackground(widgetBackground); + gc.fillRectangle(client.x, paintY, client.width, verticalIndent); + } + gc.setBackground(lineBackground); + gc.fillRectangle(client.x, paintY + verticalIndent, client.width, height - verticalIndent); + } else { + gc.setBackground(widgetBackground); + styledText.drawBackground(gc, client.x, paintY, client.width, height); + } + gc.setForeground(widgetForeground); + if (selectionStart == selectionEnd || (selectionEnd <= 0 && selectionStart > lineLength - 1)) { + layout.draw(gc, paintX, paintY); + } else { + int start = Math.max(0, selectionStart); + int end = Math.min(lineLength, selectionEnd); + Color selectionFg = styledText.getSelectionForeground(); + Color selectionBg = styledText.getSelectionBackground(); + int flags; + if ((styledText.getStyle() & SWT.FULL_SELECTION) != 0) { + flags = SWT.FULL_SELECTION; + } else { + flags = SWT.DELIMITER_SELECTION; + } + if (selectionStart <= lineLength && lineLength < selectionEnd ) { + flags |= SWT.LAST_LINE_SELECTION; + } + layout.draw(gc, paintX, paintY, start, end - 1, selectionFg, selectionBg, flags); + } + + // draw objects + Bullet bullet = null; + int bulletIndex = -1; + if (bullets != null) { + if (bulletsIndices != null) { + int index = lineIndex - topIndex; + if (0 <= index && index < CACHE_SIZE) { + bullet = bullets[index]; + bulletIndex = bulletsIndices[index]; + } + } else { + for (int i = 0; i < bullets.length; i++) { + bullet = bullets[i]; + bulletIndex = bullet.indexOf(lineIndex); + if (bulletIndex != -1) break; + } + } + } + if (bulletIndex != -1 && bullet != null) { + FontMetrics metrics = layout.getLineMetrics(0); + int lineAscent = metrics.getAscent() + metrics.getLeading(); + if (bullet.type == ST.BULLET_CUSTOM) { + bullet.style.start = lineOffset; + styledText.paintObject(gc, paintX, paintY, lineAscent, metrics.getDescent(), bullet.style, bullet, bulletIndex); + } else { + drawBullet(bullet, gc, paintX, paintY, bulletIndex, lineAscent, metrics.getDescent()); + } + } + TextStyle[] styles = layout.getStyles(); + int[] ranges = null; + for (int i = 0; i < styles.length; i++) { + if (styles[i].metrics != null) { + if (ranges == null) ranges = layout.getRanges(); + int start = ranges[i << 1]; + int length = ranges[(i << 1) + 1] - start + 1; + Point point = layout.getLocation(start, false); + FontMetrics metrics = layout.getLineMetrics(layout.getLineIndex(start)); + StyleRange style = (StyleRange)((StyleRange)styles[i]).clone(); + style.start = start + lineOffset; + style.length = length; + int lineAscent = metrics.getAscent() + metrics.getLeading(); + styledText.paintObject(gc, point.x + paintX, point.y + paintY, lineAscent, metrics.getDescent(), style, null, 0); + } + } + disposeTextLayout(layout); + return height; +} +int getBaseline() { + return ascent; +} +Font getFont(int style) { + switch (style) { + case SWT.BOLD: + if (boldFont != null) return boldFont; + return boldFont = new Font(device, getFontData(style)); + case SWT.ITALIC: + if (italicFont != null) return italicFont; + return italicFont = new Font(device, getFontData(style)); + case SWT.BOLD | SWT.ITALIC: + if (boldItalicFont != null) return boldItalicFont; + return boldItalicFont = new Font(device, getFontData(style)); + default: + return regularFont; + } +} +FontData[] getFontData(int style) { + FontData[] fontDatas = regularFont.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setStyle(style); + } + return fontDatas; +} +int getHeight () { + int defaultLineHeight = getLineHeight(); + if (styledText.isFixedLineHeight()) { + return lineCount * defaultLineHeight + styledText.topMargin + styledText.bottomMargin; + } + int totalHeight = 0; + int width = styledText.getWrapWidth(); + for (int i = 0; i < lineCount; i++) { + LineSizeInfo line = getLineSize(i); + int height = line.height; + if (line.needsRecalculateHeight()) { + if (width > 0) { + int length = content.getLine(i).length(); + height = ((length * averageCharWidth / width) + 1) * defaultLineHeight; + } else { + height = defaultLineHeight; + } + } + totalHeight += height; + } + return totalHeight + styledText.topMargin + styledText.bottomMargin; +} +boolean hasLink(int offset) { + if (offset == -1) return false; + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + String line = content.getLine(lineIndex); + StyledTextEvent event = styledText.getLineStyleData(lineOffset, line); + if (event != null) { + StyleRange[] styles = event.styles; + if (styles != null) { + int[] ranges = event.ranges; + if (ranges != null) { + for (int i = 0; i < ranges.length; i+=2) { + if (ranges[i] <= offset && offset < ranges[i] + ranges[i+1] && styles[i >> 1].underline && styles[i >> 1].underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } else { + for (int i = 0; i < styles.length; i++) { + StyleRange style = styles[i]; + if (style.start <= offset && offset < style.start + style.length && style.underline && style.underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } + } + } else { + if (ranges != null) { + int rangeCount = styleCount << 1; + int index = getRangeIndex(offset, -1, rangeCount); + if (index >= rangeCount) return false; + int rangeStart = ranges[index]; + int rangeLength = ranges[index + 1]; + StyleRange rangeStyle = styles[index >> 1]; + if (rangeStart <= offset && offset < rangeStart + rangeLength && rangeStyle.underline && rangeStyle.underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } + return false; +} +int getLineAlignment(int index, int defaultAlignment) { + if (lines == null) return defaultAlignment; + LineInfo info = lines[index]; + if (info != null && (info.flags & ALIGNMENT) != 0) { + return info.alignment; + } + return defaultAlignment; +} +Color getLineBackground(int index, Color defaultBackground) { + if (lines == null) return defaultBackground; + LineInfo info = lines[index]; + if (info != null && (info.flags & BACKGROUND) != 0) { + return info.background; + } + return defaultBackground; +} +Bullet getLineBullet (int index, Bullet defaultBullet) { + if (bullets == null) return defaultBullet; + if (bulletsIndices != null) return defaultBullet; + for (int i = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + if (bullet.indexOf(index) != -1) return bullet; + } + return defaultBullet; +} +int getLineHeight() { + return ascent + descent; +} +int getLineHeight(int lineIndex) { + LineSizeInfo line = getLineSize(lineIndex); + if (line.needsRecalculateHeight()) { + // here we are in "variable line height", the call of calculate which uses TextLayout can be very slow + // check if line can use the default line height. + if (isVariableHeight(lineIndex)) { + calculate(lineIndex, 1); + } else { + line.height = getLineHeight() + getLineSpacing(lineIndex); + } + } + return line.height; +} +/** + * Returns true if the given line can use the default line height and false + * otherwise. + * + * @param lineIndex + * line index + * @return true if the given line can use the default line height and false + * otherwise. + */ +private boolean isVariableHeight(int lineIndex) { + if (styledText.isWordWrap()) { + // In word wrap mode, the line height must be recomputed with TextLayout + return true; + } + StyleRange[] styles = getStylesForLine(lineIndex); + if (styles != null) { + for (StyleRange style : styles) { + if (style.isVariableHeight()) { + // style is variable height + return true; + } + } + } + return false; +} +/** + * returns true if the given line index defines custom line spacing and false + * otherwise. + * + * @param lineIndex + * the line index. + * @return true if the given line index defines custom line spacing and false + * otherwise. + */ +private int getLineSpacing(int lineIndex) { + if (styledText.lineSpacing > 0) { + return styledText.lineSpacing; + } else if (lineSpacingProvider != null) { + Integer lineSpacing = lineSpacingProvider.getLineSpacing(lineIndex); + if (lineSpacing != null) { + return lineSpacing; + } + } + return 0; +} +/** + * Returns styles range for the given line index and null otherwise. + * + * @param lineIndex + * the line index. + * @return styles range for the given line index and null otherwise. + */ +private StyleRange[] getStylesForLine(int lineIndex) { + int start = styledText.getOffsetAtLine(lineIndex); + int length = styledText.getLine(lineIndex).length(); + return getStyleRanges(start, length, false); +} +int getLineIndent(int index, int defaultIndent) { + if (lines == null) return defaultIndent; + LineInfo info = lines[index]; + if (info != null && (info.flags & INDENT) != 0) { + return info.indent; + } + return defaultIndent; +} +int getLineVerticalIndent(int index) { + if (lines == null) return 0; + LineInfo info = lines[index]; + if (info != null && (info.flags & VERTICAL_INDENT) != 0) { + return info.verticalIndent; + } + return 0; +} +int getLineWrapIndent(int index, int defaultWrapIndent) { + if (lines == null) return defaultWrapIndent; + LineInfo info = lines[index]; + if (info != null && (info.flags & WRAP_INDENT) != 0) { + return info.wrapIndent; + } + return defaultWrapIndent; +} +boolean getLineJustify(int index, boolean defaultJustify) { + if (lines == null) return defaultJustify; + LineInfo info = lines[index]; + if (info != null && (info.flags & JUSTIFY) != 0) { + return info.justify; + } + return defaultJustify; +} +int[] getLineTabStops(int index, int[] defaultTabStops) { + if (lines == null) return defaultTabStops; + LineInfo info = lines[index]; + if (info != null && (info.flags & TABSTOPS) != 0) { + return info.tabStops; + } + return defaultTabStops; +} +StyledTextLineSpacingProvider getLineSpacingProvider() { + return lineSpacingProvider; +} +int getRangeIndex(int offset, int low, int high) { + if (styleCount == 0) return 0; + if (ranges != null) { + while (high - low > 2) { + int index = ((high + low) / 2) / 2 * 2; + int end = ranges[index] + ranges[index + 1]; + if (end > offset) { + high = index; + } else { + low = index; + } + } + } else { + while (high - low > 1) { + int index = ((high + low) / 2); + int end = styles[index].start + styles[index].length; + if (end > offset) { + high = index; + } else { + low = index; + } + } + } + return high; +} +int[] getRanges(int start, int length) { + if (length == 0) return null; + int[] newRanges; + int end = start + length - 1; + if (ranges != null) { + int rangeCount = styleCount << 1; + int rangeStart = getRangeIndex(start, -1, rangeCount); + if (rangeStart >= rangeCount) return null; + if (ranges[rangeStart] > end) return null; + int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); + if (ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); + newRanges = new int[rangeEnd - rangeStart + 2]; + System.arraycopy(ranges, rangeStart, newRanges, 0, newRanges.length); + } else { + int rangeStart = getRangeIndex(start, -1, styleCount); + if (rangeStart >= styleCount) return null; + if (styles[rangeStart].start > end) return null; + int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount)); + if (styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); + newRanges = new int[(rangeEnd - rangeStart + 1) << 1]; + for (int i = rangeStart, j = 0; i <= rangeEnd; i++, j += 2) { + StyleRange style = styles[i]; + newRanges[j] = style.start; + newRanges[j + 1] = style.length; + } + } + if (start > newRanges[0]) { + newRanges[1] = newRanges[0] + newRanges[1] - start; + newRanges[0] = start; + } + if (end < newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1] - 1) { + newRanges[newRanges.length - 1] = end - newRanges[newRanges.length - 2] + 1; + } + return newRanges; +} +StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { + if (length == 0) return null; + StyleRange[] newStyles; + int end = start + length - 1; + if (ranges != null) { + int rangeCount = styleCount << 1; + int rangeStart = getRangeIndex(start, -1, rangeCount); + if (rangeStart >= rangeCount) return null; + if (ranges[rangeStart] > end) return null; + int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); + if (ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); + newStyles = new StyleRange[((rangeEnd - rangeStart) >> 1) + 1]; + if (includeRanges) { + for (int i = rangeStart, j = 0; i <= rangeEnd; i += 2, j++) { + newStyles[j] = (StyleRange)styles[i >> 1].clone(); + newStyles[j].start = ranges[i]; + newStyles[j].length = ranges[i + 1]; + } + } else { + System.arraycopy(styles, rangeStart >> 1, newStyles, 0, newStyles.length); + } + } else { + int rangeStart = getRangeIndex(start, -1, styleCount); + if (rangeStart >= styleCount) return null; + if (styles[rangeStart].start > end) return null; + int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount)); + if (styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); + newStyles = new StyleRange[rangeEnd - rangeStart + 1]; + System.arraycopy(styles, rangeStart, newStyles, 0, newStyles.length); + } + if (includeRanges || ranges == null) { + StyleRange style = newStyles[0]; + if (start > style.start) { + newStyles[0] = style = (StyleRange)style.clone(); + style.length = style.start + style.length - start; + style.start = start; + } + style = newStyles[newStyles.length - 1]; + if (end < style.start + style.length - 1) { + newStyles[newStyles.length - 1] = style = (StyleRange)style.clone(); + style.length = end - style.start + 1; + } + } + return newStyles; +} +StyleRange getStyleRange(StyleRange style) { + if (style.underline && style.underlineStyle == SWT.UNDERLINE_LINK) hasLinks = true; + if (style.start == 0 && style.length == 0 && style.fontStyle == SWT.NORMAL) return style; + StyleRange clone = (StyleRange)style.clone(); + clone.start = clone.length = 0; + clone.fontStyle = SWT.NORMAL; + if (clone.font == null) clone.font = getFont(style.fontStyle); + return clone; +} +TextLayout getTextLayout(int lineIndex) { + if (lineSpacingProvider == null) { + return getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), styledText.lineSpacing); + } + // Compute line spacing for the given line index. + int newLineSpacing = styledText.lineSpacing; + Integer spacing = lineSpacingProvider.getLineSpacing(lineIndex); + if (spacing != null && spacing.intValue() >= 0) { + newLineSpacing = spacing; + } + // Check if line spacing has not changed + if (isSameLineSpacing(lineIndex, newLineSpacing)) { + return getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), newLineSpacing); + } + // Get text layout with original StyledText line spacing. + TextLayout layout = getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), + styledText.lineSpacing); + if (layout.getSpacing() != newLineSpacing) { + layout.setSpacing(newLineSpacing); + if (lineSpacingComputing) { + return layout; + } + try { + /* Call of resetCache, setCaretLocation, redraw call getTextLayout method + * To avoid having stack overflow, lineSpacingComputing flag is used to call + * resetCache, setCaretLocation, redraw methods only at the end of the compute of all lines spacing. + */ + lineSpacingComputing = true; + styledText.resetCache(lineIndex, 1); + styledText.setCaretLocation(); + styledText.redraw(); + } finally { + lineSpacingComputing = false; + } + } + return layout; +} +boolean isSameLineSpacing(int lineIndex, int newLineSpacing) { + if (layouts == null) { + return false; + } + int layoutIndex = lineIndex - topIndex; + if (0 <= layoutIndex && layoutIndex < layouts.length) { + TextLayout layout = layouts[layoutIndex]; + return layout != null && !layout.isDisposed() && layout.getSpacing() == newLineSpacing; + } + return false; +} + +private static final class StyleEntry { + public final int start; + public final int end; + public final TextStyle style; + + public StyleEntry(TextStyle style, int start, int end) { + this.style = style; + this.start = start; + this.end = end; + } +} + +TextLayout getTextLayout(int lineIndex, int orientation, int width, int lineSpacing) { + TextLayout layout = null; + if (styledText != null) { + int topIndex = styledText.topIndex > 0 ? styledText.topIndex - 1 : 0; + if (layouts == null || topIndex != this.topIndex) { + TextLayout[] newLayouts = new TextLayout[CACHE_SIZE]; + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] != null) { + int layoutIndex = (i + this.topIndex) - topIndex; + if (0 <= layoutIndex && layoutIndex < newLayouts.length) { + newLayouts[layoutIndex] = layouts[i]; + } else { + layouts[i].dispose(); + } + } + } + } + if (bullets != null && bulletsIndices != null && topIndex != this.topIndex) { + int delta = topIndex - this.topIndex; + if (delta > 0) { + if (delta < bullets.length) { + System.arraycopy(bullets, delta, bullets, 0, bullets.length - delta); + System.arraycopy(bulletsIndices, delta, bulletsIndices, 0, bulletsIndices.length - delta); + } + int startIndex = Math.max(0, bullets.length - delta); + for (int i = startIndex; i < bullets.length; i++) bullets[i] = null; + } else { + if (-delta < bullets.length) { + System.arraycopy(bullets, 0, bullets, -delta, bullets.length + delta); + System.arraycopy(bulletsIndices, 0, bulletsIndices, -delta, bulletsIndices.length + delta); + } + int endIndex = Math.min(bullets.length, -delta); + for (int i = 0; i < endIndex; i++) bullets[i] = null; + } + } + this.topIndex = topIndex; + layouts = newLayouts; + } + if (layouts != null) { + int layoutIndex = lineIndex - topIndex; + if (0 <= layoutIndex && layoutIndex < layouts.length) { + layout = layouts[layoutIndex]; + if (layout != null) { + // Bug 520374: lineIndex can be >= linesSize.length + if(lineIndex < lineSizes.length && getLineSize(lineIndex).canLayout()) { + return layout; + } + } else { + layout = layouts[layoutIndex] = new TextLayout(device); + } + } + } + } + if (layout == null) layout = new TextLayout(device); + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int[] segments = null; + char[] segmentChars = null; + int indent = 0; + int wrapIndent = 0; + int verticalIndent = 0; + int alignment = SWT.LEFT; + int textDirection = orientation; + boolean justify = false; + int[] tabs = {tabWidth}; + Bullet bullet = null; + int[] ranges = null; + StyleRange[] styles = null; + int rangeStart = 0, styleCount = 0; + StyledTextEvent event = null; + if (styledText != null) { + event = styledText.getBidiSegments(lineOffset, line); + if (event != null) { + segments = event.segments; + segmentChars = event.segmentsChars; + } + event = styledText.getLineStyleData(lineOffset, line); + indent = styledText.indent; + wrapIndent = styledText.wrapIndent; + alignment = styledText.alignment; + if (styledText.isAutoDirection()) { + textDirection = SWT.AUTO_TEXT_DIRECTION; + } else if ((styledText.getStyle() & SWT.FLIP_TEXT_DIRECTION) != 0) { + textDirection = orientation == SWT.RIGHT_TO_LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT; + } + justify = styledText.justify; + if (styledText.tabs != null) tabs = styledText.tabs; + } + if (event != null) { + indent = event.indent; + verticalIndent = event.verticalIndent; + wrapIndent = event.wrapIndent; + alignment = event.alignment; + justify = event.justify; + bullet = event.bullet; + ranges = event.ranges; + styles = event.styles; + if (event.tabStops != null) tabs = event.tabStops; + if (styles != null) { + styleCount = styles.length; + if (styledText.isFixedLineHeight()) { + for (int i = 0; i < styleCount; i++) { + if (styles[i].isVariableHeight()) { + styledText.hasStyleWithVariableHeight = true; + styledText.verticalScrollOffset = -1; + styledText.redraw(); + break; + } + } + } + } + if (bullets == null || bulletsIndices == null) { + bullets = new Bullet[CACHE_SIZE]; + bulletsIndices = new int[CACHE_SIZE]; + } + int index = lineIndex - topIndex; + if (0 <= index && index < CACHE_SIZE) { + bullets[index] = bullet; + bulletsIndices[index] = event.bulletIndex; + } + } else { + if (lines != null) { + LineInfo info = lines[lineIndex]; + if (info != null) { + if ((info.flags & INDENT) != 0) indent = info.indent; + if ((info.flags & VERTICAL_INDENT) != 0) verticalIndent = info.verticalIndent; + if ((info.flags & WRAP_INDENT) != 0) wrapIndent = info.wrapIndent; + if ((info.flags & ALIGNMENT) != 0) alignment = info.alignment; + if ((info.flags & JUSTIFY) != 0) justify = info.justify; + if ((info.flags & SEGMENTS) != 0) segments = info.segments; + if ((info.flags & SEGMENT_CHARS) != 0) segmentChars = info.segmentsChars; + if ((info.flags & TABSTOPS) != 0) tabs = info.tabStops; + } + } + if (bulletsIndices != null) { + bullets = null; + bulletsIndices = null; + } + if (bullets != null) { + for (int i = 0; i < bullets.length; i++) { + if (bullets[i].indexOf(lineIndex) != -1) { + bullet = bullets[i]; + break; + } + } + } + ranges = this.ranges; + styles = this.styles; + styleCount = this.styleCount; + if (ranges != null) { + rangeStart = getRangeIndex(lineOffset, -1, styleCount << 1); + } else { + rangeStart = getRangeIndex(lineOffset, -1, styleCount); + } + } + if (bullet != null) { + StyleRange style = bullet.style; + GlyphMetrics metrics = style.metrics; + indent += metrics.width; + } + + // prepare styles, as it may change the line content, do it before calling layout.setText() + // This needs to happen early to handle the case of GlyphMetrics on \t. + // The root cause is that TextLayout doesn't return the right value for the bounds when + // GlyphMetrics are applied on \t. A better fix could be implemented directly in (all 3) + // TextLayout classes. + List styleEntries = new ArrayList<>(); + int lastOffset = 0; + int length = line.length(); + if (styles != null) { + if (ranges != null) { + int rangeCount = styleCount << 1; + for (int i = rangeStart; i < rangeCount; i += 2) { + int start, end; + if (lineOffset > ranges[i]) { + start = 0; + end = Math.min (length, ranges[i + 1] - lineOffset + ranges[i]); + } else { + start = ranges[i] - lineOffset; + end = Math.min(length, start + ranges[i + 1]); + } + if (start >= length) break; + if (lastOffset < start) { + styleEntries.add(new StyleEntry(null, lastOffset, start - 1)); + } + TextStyle style = getStyleRange(styles[i >> 1]); + int endIndex = Math.max(start, Math.min(length, end + 1)); + if (style.metrics != null && line.substring(start, endIndex).contains("\t")) { + line = + line.substring(0, start) + + line.substring(start, endIndex).replace('\t', ' ') + + (end < line.length() ? line.substring(end + 1, line.length()) : ""); + } + styleEntries.add(new StyleEntry(style, start, end)); + lastOffset = Math.max(lastOffset, end); + } + } else { + for (int i = rangeStart; i < styleCount; i++) { + int start, end; + if (lineOffset > styles[i].start) { + start = 0; + end = Math.min (length, styles[i].length - lineOffset + styles[i].start); + } else { + start = styles[i].start - lineOffset; + end = Math.min(length, start + styles[i].length); + } + if (start >= length) break; + if (lastOffset < start) { + styleEntries.add(new StyleEntry(null, lastOffset, start - 1)); + } + TextStyle style = getStyleRange(styles[i]); + int endIndex = Math.max(start, Math.min(length, end + 1)); + if (style.metrics != null && line.substring(start, endIndex).contains("\t")) { + line = + line.substring(0, start) + + line.substring(start, endIndex).replace('\t', ' ') + + (end < line.length() ? line.substring(end + 1, line.length()) : ""); + } + styleEntries.add(new StyleEntry(style, start, end)); + lastOffset = Math.max(lastOffset, end); + } + } + } + if (lastOffset < length) styleEntries.add(new StyleEntry(null, lastOffset, length)); + + layout.setFont(regularFont); + layout.setAscent(ascent); + layout.setDescent(descent); + layout.setText(line); + layout.setOrientation(orientation); + layout.setSegments(segments); + layout.setSegmentsChars(segmentChars); + layout.setWidth(width); + layout.setSpacing(lineSpacing); + layout.setTabs(tabs); + layout.setDefaultTabWidth(tabLength); + layout.setIndent(indent); + layout.setVerticalIndent(verticalIndent); + layout.setWrapIndent(wrapIndent); + layout.setAlignment(alignment); + layout.setJustify(justify); + layout.setTextDirection(textDirection); + // apply styles, must be done after layout.setText() + for (StyleEntry styleEntry : styleEntries) { + layout.setStyle(styleEntry.style, styleEntry.start, styleEntry.end); + } + + if (styledText != null && styledText.ime != null) { + IME ime = styledText.ime; + int compositionOffset = ime.getCompositionOffset(); + if (compositionOffset != -1) { + int commitCount = ime.getCommitCount(); + int compositionLength = ime.getText().length(); + if (compositionLength != commitCount) { + int compositionLine = content.getLineAtOffset(compositionOffset); + if (compositionLine == lineIndex) { + int[] imeRanges = ime.getRanges(); + TextStyle[] imeStyles = ime.getStyles(); + if (imeRanges.length > 0) { + for (int i = 0; i < imeStyles.length; i++) { + int start = imeRanges[i*2] - lineOffset; + int end = imeRanges[i*2+1] - lineOffset; + TextStyle imeStyle = imeStyles[i], userStyle; + for (int j = start; j <= end; j++) { + if (!(0 <= j && j < length)) break; + userStyle = layout.getStyle(cap(layout, j)); + if (userStyle == null && j > 0) userStyle = layout.getStyle(cap(layout, j - 1)); + if (userStyle == null && j + 1 < length) userStyle = layout.getStyle(cap(layout, j + 1)); + if (userStyle == null) { + layout.setStyle(imeStyle, j, j); + } else { + TextStyle newStyle = new TextStyle(imeStyle); + if (newStyle.font == null) newStyle.font = userStyle.font; + if (newStyle.foreground == null) newStyle.foreground = userStyle.foreground; + if (newStyle.background == null) newStyle.background = userStyle.background; + layout.setStyle(newStyle, j, j); + } + } + } + } else { + int start = compositionOffset - lineOffset; + int end = start + compositionLength - 1; + TextStyle userStyle = layout.getStyle(cap(layout, start)); + if (userStyle == null) { + if (start > 0) userStyle = layout.getStyle(cap(layout, start - 1)); + if (userStyle == null && end + 1 < length) userStyle = layout.getStyle(cap(layout, end + 1)); + if (userStyle != null) { + TextStyle newStyle = new TextStyle(); + newStyle.font = userStyle.font; + newStyle.foreground = userStyle.foreground; + newStyle.background = userStyle.background; + layout.setStyle(newStyle, start, end); + } + } + } + } + } + } + } + + if (styledText != null && styledText.isFixedLineHeight()) { + int index = -1; + int lineCount = layout.getLineCount(); + int height = getLineHeight(); + for (int i = 0; i < lineCount; i++) { + int lineHeight = layout.getLineBounds(i).height; + if (lineHeight > height) { + height = lineHeight; + index = i; + } + } + if (index != -1) { + FontMetrics metrics = layout.getLineMetrics(index); + ascent = metrics.getAscent() + metrics.getLeading(); + descent = metrics.getDescent(); + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] != null && layouts[i] != layout) { + layouts[i].setAscent(ascent); + layouts[i].setDescent(descent); + } + } + } + styledText.calculateScrollBars(); + if (styledText.verticalScrollOffset != 0) { + int topIndex = styledText.topIndex; + int topIndexY = styledText.topIndexY; + int lineHeight = getLineHeight(); + int newVerticalScrollOffset; + if (topIndexY >= 0) { + newVerticalScrollOffset = (topIndex - 1) * lineHeight + lineHeight - topIndexY; + } else { + newVerticalScrollOffset = topIndex * lineHeight - topIndexY; + } + styledText.scrollVertical(newVerticalScrollOffset - styledText.verticalScrollOffset, true); + } + if (styledText.isBidiCaret()) styledText.createCaretBitmaps(); + styledText.caretDirection = SWT.NULL; + styledText.setCaretLocation(); + styledText.redraw(); + } + } + return layout; +} +int getWidth() { + return maxWidth; +} +void reset() { + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + TextLayout layout = layouts[i]; + if (layout != null) layout.dispose(); + } + layouts = null; + } + topIndex = -1; + stylesSetCount = styleCount = lineCount = 0; + ranges = null; + styles = null; + stylesSet = null; + lines = null; + lineSizes = null; + bullets = null; + bulletsIndices = null; + redrawLines = null; + hasLinks = false; +} +void reset(int startLine, int lineCount) { + int endLine = startLine + lineCount; + if (startLine < 0 || endLine > lineSizes.length) return; + SortedSet lines = new TreeSet<>(); + for (int i = startLine; i < endLine; i++) { + lines.add(Integer.valueOf(i)); + } + reset(lines); +} +void reset(Set lines) { + if (lines == null || lines.isEmpty()) return; + int resetLineCount = 0; + for (Integer line : lines) { + if (line >= 0 || line < lineCount) { + resetLineCount++; + getLineSize(line.intValue()).resetSize(); + } + } + if (lines.contains(Integer.valueOf(maxWidthLineIndex))) { + maxWidth = 0; + maxWidthLineIndex = -1; + if (resetLineCount != this.lineCount) { + for (int i = 0; i < this.lineCount; i++) { + LineSizeInfo lineSize = getLineSize(i); + if (lineSize.width > maxWidth) { + maxWidth = lineSize.width; + maxWidthLineIndex = i; + } + } + } + } +} +void setContent(StyledTextContent content) { + reset(); + this.content = content; + lineCount = content.getLineCount(); + lineSizes = new LineSizeInfo[lineCount]; + maxWidth = 0; + maxWidthLineIndex = -1; + reset(0, lineCount); +} +void setFont(Font font, int tabs) { + TextLayout layout = new TextLayout(device); + layout.setFont(regularFont); + tabLength = tabs; + if (font != null) { + if (boldFont != null) boldFont.dispose(); + if (italicFont != null) italicFont.dispose(); + if (boldItalicFont != null) boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + regularFont = font; + layout.setText(" "); + layout.setFont(font); + layout.setStyle(new TextStyle(getFont(SWT.NORMAL), null, null), 0, 0); + layout.setStyle(new TextStyle(getFont(SWT.BOLD), null, null), 1, 1); + layout.setStyle(new TextStyle(getFont(SWT.ITALIC), null, null), 2, 2); + layout.setStyle(new TextStyle(getFont(SWT.BOLD | SWT.ITALIC), null, null), 3, 3); + FontMetrics metrics = layout.getLineMetrics(0); + ascent = metrics.getAscent() + metrics.getLeading(); + descent = metrics.getDescent(); + boldFont.dispose(); + italicFont.dispose(); + boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + } + layout.dispose(); + layout = new TextLayout(device); + layout.setFont(regularFont); + StringBuilder tabBuffer = new StringBuilder(tabs); + for (int i = 0; i < tabs; i++) { + tabBuffer.append(' '); + } + layout.setText(tabBuffer.toString()); + tabWidth = layout.getBounds().width; + layout.dispose(); + if (styledText != null) { + GC gc = new GC(styledText); + averageCharWidth = (int) gc.getFontMetrics().getAverageCharacterWidth(); + fixedPitch = gc.stringExtent("l").x == gc.stringExtent("W").x; //$NON-NLS-1$ //$NON-NLS-2$ + gc.dispose(); + } +} +void setLineAlignment(int startLine, int count, int alignment) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= ALIGNMENT; + lines[i].alignment = alignment; + } +} +void setLineBackground(int startLine, int count, Color background) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= BACKGROUND; + lines[i].background = background; + } +} +void setLineBullet(int startLine, int count, Bullet bullet) { + if (bulletsIndices != null) { + bulletsIndices = null; + bullets = null; + } + if (bullets == null) { + if (bullet == null) return; + bullets = new Bullet[1]; + bullets[0] = bullet; + } + int index = 0; + while (index < bullets.length) { + if (bullet == bullets[index]) break; + index++; + } + if (bullet != null) { + if (index == bullets.length) { + Bullet[] newBulletsList = new Bullet[bullets.length + 1]; + System.arraycopy(bullets, 0, newBulletsList, 0, bullets.length); + newBulletsList[index] = bullet; + bullets = newBulletsList; + } + bullet.addIndices(startLine, count); + } else { + updateBullets(startLine, count, 0, false); + styledText.redrawLinesBullet(redrawLines); + redrawLines = null; + } +} +void setLineIndent(int startLine, int count, int indent) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= INDENT; + lines[i].indent = indent; + } +} +void setLineVerticalIndent(int lineIndex, int verticalLineIndent) { + if (lines == null) + lines = new LineInfo[lineCount]; + if (lines[lineIndex] == null) { + lines[lineIndex] = new LineInfo(); + } + lines[lineIndex].flags |= VERTICAL_INDENT; + lines[lineIndex].verticalIndent = verticalLineIndent; +} +void setLineWrapIndent(int startLine, int count, int wrapIndent) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= WRAP_INDENT; + lines[i].wrapIndent = wrapIndent; + } +} +void setLineJustify(int startLine, int count, boolean justify) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= JUSTIFY; + lines[i].justify = justify; + } +} +void setLineSegments(int startLine, int count, int[] segments) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= SEGMENTS; + lines[i].segments = segments; + } +} +void setLineSegmentChars(int startLine, int count, char[] segmentChars) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= SEGMENT_CHARS; + lines[i].segmentsChars = segmentChars; + } +} +void setLineTabStops(int startLine, int count, int[] tabStops) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= TABSTOPS; + lines[i].tabStops = tabStops; + } +} +void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) { + this.lineSpacingProvider = lineSpacingProvider; +} +void setStyleRanges (int[] newRanges, StyleRange[] newStyles) { + if (newStyles == null) { + stylesSetCount = styleCount = 0; + ranges = null; + styles = null; + stylesSet = null; + hasLinks = false; + return; + } + if (newRanges == null && COMPACT_STYLES) { + newRanges = new int[newStyles.length << 1]; + StyleRange[] tmpStyles = new StyleRange[newStyles.length]; + if (stylesSet == null) stylesSet = new StyleRange[4]; + for (int i = 0, j = 0; i < newStyles.length; i++) { + StyleRange newStyle = newStyles[i]; + newRanges[j++] = newStyle.start; + newRanges[j++] = newStyle.length; + int index = 0; + while (index < stylesSetCount) { + if (stylesSet[index].similarTo(newStyle)) break; + index++; + } + if (index == stylesSetCount) { + if (stylesSetCount == stylesSet.length) { + StyleRange[] tmpStylesSet = new StyleRange[stylesSetCount + 4]; + System.arraycopy(stylesSet, 0, tmpStylesSet, 0, stylesSetCount); + stylesSet = tmpStylesSet; + } + stylesSet[stylesSetCount++] = newStyle; + } + tmpStyles[i] = stylesSet[index]; + } + newStyles = tmpStyles; + } + + if (styleCount == 0) { + if (newRanges != null) { + ranges = new int[newRanges.length]; + System.arraycopy(newRanges, 0, ranges, 0, ranges.length); + } + styles = new StyleRange[newStyles.length]; + System.arraycopy(newStyles, 0, styles, 0, styles.length); + styleCount = newStyles.length; + return; + } + if (newRanges != null && ranges == null) { + ranges = new int[styles.length << 1]; + for (int i = 0, j = 0; i < styleCount; i++) { + ranges[j++] = styles[i].start; + ranges[j++] = styles[i].length; + } + } + if (newRanges == null && ranges != null) { + newRanges = new int[newStyles.length << 1]; + for (int i = 0, j = 0; i < newStyles.length; i++) { + newRanges[j++] = newStyles[i].start; + newRanges[j++] = newStyles[i].length; + } + } + if (ranges != null) { + int rangeCount = styleCount << 1; + int start = newRanges[0]; + int modifyStart = getRangeIndex(start, -1, rangeCount), modifyEnd; + boolean insert = modifyStart == rangeCount; + if (!insert) { + int end = newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1]; + modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); + insert = modifyStart == modifyEnd && ranges[modifyStart] >= end; + } + if (insert) { + addMerge(newRanges, newStyles, newRanges.length, modifyStart, modifyStart); + return; + } + modifyEnd = modifyStart; + int[] mergeRanges = new int[6]; + StyleRange[] mergeStyles = new StyleRange[3]; + for (int i = 0; i < newRanges.length; i += 2) { + int newStart = newRanges[i]; + int newEnd = newStart + newRanges[i + 1]; + if (newStart == newEnd) continue; + int modifyLast = 0, mergeCount = 0; + while (modifyEnd < rangeCount) { + if (newStart >= ranges[modifyStart] + ranges[modifyStart + 1]) modifyStart += 2; + if (ranges[modifyEnd] + ranges[modifyEnd + 1] > newEnd) break; + modifyEnd += 2; + } + if (ranges[modifyStart] < newStart && newStart < ranges[modifyStart] + ranges[modifyStart + 1]) { + mergeStyles[mergeCount >> 1] = styles[modifyStart >> 1]; + mergeRanges[mergeCount] = ranges[modifyStart]; + mergeRanges[mergeCount + 1] = newStart - ranges[modifyStart]; + mergeCount += 2; + } + mergeStyles[mergeCount >> 1] = newStyles[i >> 1]; + mergeRanges[mergeCount] = newStart; + mergeRanges[mergeCount + 1] = newRanges[i + 1]; + mergeCount += 2; + if (modifyEnd < rangeCount && ranges[modifyEnd] < newEnd && newEnd < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + mergeStyles[mergeCount >> 1] = styles[modifyEnd >> 1]; + mergeRanges[mergeCount] = newEnd; + mergeRanges[mergeCount + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - newEnd; + mergeCount += 2; + modifyLast = 2; + } + int grow = addMerge(mergeRanges, mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); + rangeCount += grow; + modifyStart = modifyEnd += grow; + } + } else { + int start = newStyles[0].start; + int modifyStart = getRangeIndex(start, -1, styleCount), modifyEnd; + boolean insert = modifyStart == styleCount; + if (!insert) { + int end = newStyles[newStyles.length - 1].start + newStyles[newStyles.length - 1].length; + modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount); + insert = modifyStart == modifyEnd && styles[modifyStart].start >= end; + } + if (insert) { + addMerge(newStyles, newStyles.length, modifyStart, modifyStart); + return; + } + modifyEnd = modifyStart; + StyleRange[] mergeStyles = new StyleRange[3]; + for (int i = 0; i < newStyles.length; i++) { + StyleRange newStyle = newStyles[i], style; + int newStart = newStyle.start; + int newEnd = newStart + newStyle.length; + if (newStart == newEnd) continue; + int modifyLast = 0, mergeCount = 0; + while (modifyEnd < styleCount) { + if (newStart >= styles[modifyStart].start + styles[modifyStart].length) modifyStart++; + if (styles[modifyEnd].start + styles[modifyEnd].length > newEnd) break; + modifyEnd++; + } + style = styles[modifyStart]; + if (style.start < newStart && newStart < style.start + style.length) { + style = mergeStyles[mergeCount++] = (StyleRange)style.clone(); + style.length = newStart - style.start; + } + mergeStyles[mergeCount++] = newStyle; + if (modifyEnd < styleCount) { + style = styles[modifyEnd]; + if (style.start < newEnd && newEnd < style.start + style.length) { + style = mergeStyles[mergeCount++] = (StyleRange)style.clone(); + style.length += style.start - newEnd; + style.start = newEnd; + modifyLast = 1; + } + } + int grow = addMerge(mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); + modifyStart = modifyEnd += grow; + } + } +} +void textChanging(TextChangingEvent event) { + int start = event.start; + int newCharCount = event.newCharCount, replaceCharCount = event.replaceCharCount; + int newLineCount = event.newLineCount, replaceLineCount = event.replaceLineCount; + + updateRanges(start, replaceCharCount, newCharCount); + + int startLine = content.getLineAtOffset(start); + if (replaceCharCount == content.getCharCount()) lines = null; + if (replaceLineCount == lineCount) { + lineCount = newLineCount; + lineSizes = new LineSizeInfo[lineCount]; + reset(0, lineCount); + } else { + int startIndex = startLine + replaceLineCount + 1; + int endIndex = startLine + newLineCount + 1; + if(lineCount < startLine) { + SWT.error(SWT.ERROR_INVALID_RANGE, null, "bug 478020: lineCount < startLine: " + lineCount + ":" + startLine); + } + if(lineCount < startIndex) { + SWT.error(SWT.ERROR_INVALID_RANGE, null, "bug 478020: lineCount < startIndex: " + lineCount + ":" + startIndex); + } + int delta = newLineCount - replaceLineCount; + if (lineCount + delta > lineSizes.length) { + LineSizeInfo[] newLineSizes = new LineSizeInfo[lineCount + delta + GROW]; + System.arraycopy(lineSizes, 0, newLineSizes, 0, lineCount); + lineSizes = newLineSizes; + } + if (lines != null) { + if (lineCount + delta > lines.length) { + LineInfo[] newLines = new LineInfo[lineCount + delta + GROW]; + System.arraycopy(lines, 0, newLines, 0, lineCount); + lines = newLines; + } + } + System.arraycopy(lineSizes, startIndex, lineSizes, endIndex, lineCount - startIndex); + for (int i = startLine; i < endIndex; i++) { + lineSizes[i] = null; + } + for (int i = lineCount + delta; i < lineCount; i++) { + lineSizes[i] = null; + } + if (layouts != null) { + int layoutStartLine = startLine - topIndex; + int layoutEndLine = layoutStartLine + replaceLineCount + 1; + for (int i = layoutStartLine; i < layoutEndLine; i++) { + if (0 <= i && i < layouts.length) { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + if (delta > 0) { + for (int i = layouts.length - 1; i >= layoutEndLine; i--) { + if (0 <= i && i < layouts.length) { + endIndex = i + delta; + if (0 <= endIndex && endIndex < layouts.length) { + layouts[endIndex] = layouts[i]; + layouts[i] = null; + if (bullets != null && bulletsIndices != null) { + bullets[endIndex] = bullets[i]; + bulletsIndices[endIndex] = bulletsIndices[i]; + bullets[i] = null; + } + } else { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + } + } else if (delta < 0) { + for (int i = layoutEndLine; i < layouts.length; i++) { + if (0 <= i && i < layouts.length) { + endIndex = i + delta; + if (0 <= endIndex && endIndex < layouts.length) { + layouts[endIndex] = layouts[i]; + layouts[i] = null; + if (bullets != null && bulletsIndices != null) { + bullets[endIndex] = bullets[i]; + bulletsIndices[endIndex] = bulletsIndices[i]; + bullets[i] = null; + } + } else { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + } + } + } + if (replaceLineCount != 0 || newLineCount != 0) { + int startLineOffset = content.getOffsetAtLine(startLine); + if (startLineOffset != start) startLine++; + updateBullets(startLine, replaceLineCount, newLineCount, true); + if (lines != null) { + startIndex = startLine + replaceLineCount; + endIndex = startLine + newLineCount; + System.arraycopy(lines, startIndex, lines, endIndex, lineCount - startIndex); + for (int i = startLine; i < endIndex; i++) { + lines[i] = null; + } + for (int i = lineCount + delta; i < lineCount; i++) { + lines[i] = null; + } + } + } + lineCount += delta; + if (maxWidthLineIndex != -1 && startLine <= maxWidthLineIndex && maxWidthLineIndex <= startLine + replaceLineCount) { + maxWidth = 0; + maxWidthLineIndex = -1; + for (int i = 0; i < lineCount; i++) { + LineSizeInfo lineSize = getLineSize(i); + if (lineSize.width > maxWidth) { + maxWidth = lineSize.width; + maxWidthLineIndex = i; + } + } + } + } +} +void updateBullets(int startLine, int replaceLineCount, int newLineCount, boolean update) { + if (bullets == null) return; + if (bulletsIndices != null) return; + for (int i = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + int[] lines = bullet.removeIndices(startLine, replaceLineCount, newLineCount, update); + if (lines != null) { + if (redrawLines == null) { + redrawLines = lines; + } else { + int[] newRedrawBullets = new int[redrawLines.length + lines.length]; + System.arraycopy(redrawLines, 0, newRedrawBullets, 0, redrawLines.length); + System.arraycopy(lines, 0, newRedrawBullets, redrawLines.length, lines.length); + redrawLines = newRedrawBullets; + } + } + } + int removed = 0; + for (int i = 0; i < bullets.length; i++) { + if (bullets[i].size() == 0) removed++; + } + if (removed > 0) { + if (removed == bullets.length) { + bullets = null; + } else { + Bullet[] newBulletsList = new Bullet[bullets.length - removed]; + for (int i = 0, j = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + if (bullet.size() > 0) newBulletsList[j++] = bullet; + } + bullets = newBulletsList; + } + } +} +void updateRanges(int start, int replaceCharCount, int newCharCount) { + if (styleCount == 0 || (replaceCharCount == 0 && newCharCount == 0)) return; + if (ranges != null) { + int rangeCount = styleCount << 1; + int modifyStart = getRangeIndex(start, -1, rangeCount); + if (modifyStart == rangeCount) return; + int end = start + replaceCharCount; + int modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); + int offset = newCharCount - replaceCharCount; + if (modifyStart == modifyEnd && ranges[modifyStart] < start && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + if (newCharCount == 0) { + ranges[modifyStart + 1] -= replaceCharCount; + modifyEnd += 2; + } else { + if (rangeCount + 2 > ranges.length) { + int[] newRanges = new int[ranges.length + (GROW << 1)]; + System.arraycopy(ranges, 0, newRanges, 0, rangeCount); + ranges = newRanges; + StyleRange[] newStyles = new StyleRange[styles.length + GROW]; + System.arraycopy(styles, 0, newStyles, 0, styleCount); + styles = newStyles; + } + System.arraycopy(ranges, modifyStart + 2, ranges, modifyStart + 4, rangeCount - (modifyStart + 2)); + System.arraycopy(styles, (modifyStart + 2) >> 1, styles, (modifyStart + 4) >> 1, styleCount - ((modifyStart + 2) >> 1)); + ranges[modifyStart + 3] = ranges[modifyStart] + ranges[modifyStart + 1] - end; + ranges[modifyStart + 2] = start + newCharCount; + ranges[modifyStart + 1] = start - ranges[modifyStart]; + styles[(modifyStart >> 1) + 1] = styles[modifyStart >> 1]; + rangeCount += 2; + styleCount++; + modifyEnd += 4; + } + if (offset != 0) { + for (int i = modifyEnd; i < rangeCount; i += 2) { + ranges[i] += offset; + } + } + } else { + if (ranges[modifyStart] < start && start < ranges[modifyStart] + ranges[modifyStart + 1]) { + ranges[modifyStart + 1] = start - ranges[modifyStart]; + modifyStart += 2; + } + if (modifyEnd < rangeCount && ranges[modifyEnd] < end && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + ranges[modifyEnd + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - end; + ranges[modifyEnd] = end; + } + if (offset != 0) { + for (int i = modifyEnd; i < rangeCount; i += 2) { + ranges[i] += offset; + } + } + System.arraycopy(ranges, modifyEnd, ranges, modifyStart, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, styles, modifyStart >> 1, styleCount - (modifyEnd >> 1)); + styleCount -= (modifyEnd - modifyStart) >> 1; + } + } else { + int modifyStart = getRangeIndex(start, -1, styleCount); + if (modifyStart == styleCount) return; + int end = start + replaceCharCount; + int modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount); + int offset = newCharCount - replaceCharCount; + if (modifyStart == modifyEnd && styles[modifyStart].start < start && end < styles[modifyEnd].start + styles[modifyEnd].length) { + if (newCharCount == 0) { + styles[modifyStart].length -= replaceCharCount; + modifyEnd++; + } else { + if (styleCount + 1 > styles.length) { + StyleRange[] newStyles = new StyleRange[styles.length + GROW]; + System.arraycopy(styles, 0, newStyles, 0, styleCount); + styles = newStyles; + } + System.arraycopy(styles, modifyStart + 1, styles, modifyStart + 2, styleCount - (modifyStart + 1)); + styles[modifyStart + 1] = (StyleRange)styles[modifyStart].clone(); + styles[modifyStart + 1].length = styles[modifyStart].start + styles[modifyStart].length - end; + styles[modifyStart + 1].start = start + newCharCount; + styles[modifyStart].length = start - styles[modifyStart].start; + styleCount++; + modifyEnd += 2; + } + if (offset != 0) { + for (int i = modifyEnd; i < styleCount; i++) { + styles[i].start += offset; + } + } + } else { + if (styles[modifyStart].start < start && start < styles[modifyStart].start + styles[modifyStart].length) { + styles[modifyStart].length = start - styles[modifyStart].start; + modifyStart++; + } + if (modifyEnd < styleCount && styles[modifyEnd].start < end && end < styles[modifyEnd].start + styles[modifyEnd].length) { + styles[modifyEnd].length = styles[modifyEnd].start + styles[modifyEnd].length - end; + styles[modifyEnd].start = end; + } + if (offset != 0) { + for (int i = modifyEnd; i < styleCount; i++) { + styles[i].start += offset; + } + } + System.arraycopy(styles, modifyEnd, styles, modifyStart, styleCount - modifyEnd); + styleCount -= modifyEnd - modifyStart; + } + } +} + +public boolean hasVerticalIndent() { + return Arrays.stream(lines).filter(Objects::nonNull) // + .mapToInt(line -> line.verticalIndent) // + .anyMatch(n -> n != 0); +} + + +}