org.eclipse.swt.graphics.TextLayout Maven / Gradle / Ivy
Show all versions of org.eclipse.swt.gtk.linux.aarch64 Show documentation
/******************************************************************************* * Copyright (c) 2000, 2020 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 *******************************************************************************/ package org.eclipse.swt.graphics; import java.util.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.cairo.*; import org.eclipse.swt.internal.gtk.*; /** *
,TextLayout
is a graphic object that represents * styled text. ** Instances of this class provide support for drawing, cursor * navigation, hit testing, text wrapping, alignment, tab expansion * line breaking, etc. These are aspects required for rendering internationalized text. *
* Application code must explicitly invoke the
* * @see TextLayout, TextStyle snippets * @see SWT Example: CustomControlExample, StyledText tab * @see Sample code and further information * * @since 3.0 */ public final class TextLayout extends Resource { static class StyleItem { TextStyle style; int start; @Override public String toString () { return "StyleItem {" + start + ", " + style + "}"; } } Font font; String text; int ascentInPoints, descentInPoints; int indent, wrapIndent, wrapWidth; int[] segments; char[] segmentsChars; int[] tabs; StyleItem[] styles; int stylesCount; long layout, context, attrList, selAttrList; int[] invalidOffsets; int verticalIndentInPoints; MetricsAdapter metricsAdapter = new MetricsAdapter(); static final char LTR_MARK = '\u200E', RTL_MARK = '\u200F', ZWS = '\u200B', ZWNBS = '\uFEFF'; /** * Adapts necessary Pango APIs to enforce fixed line metrics (when set) */ private static class MetricsAdapter { private FontMetrics lineMetricsInPixels; /** * Calculates Y offset from line metrics configured in * {@link #lineMetricsInPixels} to real text position for painting. */ private int wantToRealInPango(PangoRectangle realMetrics) { int wantHeightInPixels = lineMetricsInPixels.getHeight(); int realHeightInPixels = OS.PANGO_PIXELS(realMetrics.height); if (realHeightInPixels == wantHeightInPixels) { return 0; } // The idea is to preserve baseline location, this looks best. // This is the behavior documented in `TextLayout#setFixedLineMetrics()`. int wantAboveInPango = OS.PANGO_SCALE * lineMetricsInPixels.getAscent(); int realAboveInPango = -realMetrics.y; return wantAboveInPango - realAboveInPango; } private int wantToRealInPango(long line) { PangoRectangle rect = new PangoRectangle(); // Pango caches result, so the API is very cheap to call multiple times OS.pango_layout_line_get_extents(line, null, rect); return wantToRealInPango(rect); } public boolean isFixedMetrics() { return (lineMetricsInPixels != null); } public FontMetrics getFixedLineMetrics(Device device) { if (lineMetricsInPixels == null) { return null; } FontMetrics result = new FontMetrics(); result.ascentInPoints = DPIUtil.autoScaleDown(device, lineMetricsInPixels.ascentInPoints); result.descentInPoints = DPIUtil.autoScaleDown(device, lineMetricsInPixels.descentInPoints); result.averageCharWidthInPoints = DPIUtil.autoScaleDown(device, lineMetricsInPixels.averageCharWidthInPoints); return result; } public void setFixedLineMetrics(Device device, FontMetrics metrics) { if (metrics == null) { lineMetricsInPixels = null; return; } FontMetrics result = new FontMetrics(); result.ascentInPoints = DPIUtil.autoScaleUp(device, metrics.ascentInPoints); result.descentInPoints = DPIUtil.autoScaleUp(device, metrics.descentInPoints); result.averageCharWidthInPoints = DPIUtil.autoScaleUp(device, metrics.averageCharWidthInPoints); lineMetricsInPixels = result; } private void validateLayout (long layout) { // Pango caches result, so the API is very cheap to call multiple times if (OS.pango_layout_get_line_count(layout) > 1) { // Multi-line layouts (including word wrapping) are not yet supported. // Note that `StyledText` uses separate `TextLayout` for every line. SWT.error (SWT.ERROR_INVALID_ARGUMENT); } } public long gdk_pango_layout_get_clip_region (long layout, int x_origin, int y_origin, int[] index_ranges, int n_ranges) { // In order to get proper text clip, adjust Y in the same way // as in pango_cairo_show_layout() int yAdjustInPixels = 0; if (isFixedMetrics()) { validateLayout(layout); long line0 = OS.pango_layout_get_line(layout, 0); yAdjustInPixels = OS.PANGO_PIXELS(wantToRealInPango(line0)); } long rgn = GDK.gdk_pango_layout_get_clip_region(layout, x_origin, y_origin + yAdjustInPixels, index_ranges, n_ranges); // Adjust region via intersecting with desired region if (isFixedMetrics()) { cairo_rectangle_int_t wantRect = new cairo_rectangle_int_t(); // Use real x,width with desired y,height Cairo.cairo_region_get_extents(rgn, wantRect); wantRect.y = y_origin; wantRect.height = lineMetricsInPixels.getHeight(); long limitRgn = Cairo.cairo_region_create_rectangle(wantRect); Cairo.cairo_region_intersect(rgn, limitRgn); Cairo.cairo_region_destroy(limitRgn); } return rgn; } public void pango_cairo_show_layout (long cairo, long layout, double x, double y) { int yAdjustInPixels = 0; if (isFixedMetrics()) { validateLayout(layout); long line0 = OS.pango_layout_get_line(layout, 0); yAdjustInPixels = OS.PANGO_PIXELS(wantToRealInPango(line0)); } Cairo.cairo_move_to(cairo, x, y + yAdjustInPixels); OS.pango_cairo_show_layout (cairo, layout); } public void pango_layout_get_size (long layout, int[] width, int[] height) { OS.pango_layout_get_size(layout, width, height); if (isFixedMetrics()) { validateLayout(layout); height[0] = lineMetricsInPixels.getHeight(); } } public void pango_layout_iter_get_line_extents (long iter, PangoRectangle ink_rect, PangoRectangle logical_rect) { OS.pango_layout_iter_get_line_extents(iter, ink_rect, logical_rect); if (isFixedMetrics()) { if (ink_rect != null) { // SWT doesn't use that, so I didn't implement SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (logical_rect != null) { logical_rect.height = OS.PANGO_SCALE * lineMetricsInPixels.getHeight(); } } } public void pango_layout_line_get_extents (long line, PangoRectangle ink_rect, PangoRectangle logical_rect) { OS.pango_layout_line_get_extents(line, ink_rect, logical_rect); if (isFixedMetrics()) { if (ink_rect != null) { // SWT doesn't use that, so I didn't implement SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (logical_rect != null) { logical_rect.height = OS.PANGO_SCALE * lineMetricsInPixels.getHeight(); } } } } /** * Constructs a new instance of this class on the given device. *TextLayout#dispose()
* method to release the operating system resources managed by each instance * when those instances are no longer required. ** You must dispose the text layout when it is no longer required. *
* * @param device the device on which to allocate the text layout * * @exception IllegalArgumentException*
* * @see #dispose() */ public TextLayout (Device device) { super(device); device = this.device; if (GTK.GTK4) { long fontMap = OS.pango_cairo_font_map_get_default (); context = OS.pango_font_map_create_context (fontMap); } else { context = GDK.gdk_pango_context_get(); } if (context == 0) SWT.error(SWT.ERROR_NO_HANDLES); OS.pango_context_set_language(context, GTK.gtk_get_default_language()); OS.pango_context_set_base_dir(context, OS.PANGO_DIRECTION_LTR); layout = OS.pango_layout_new(context); if (layout == 0) SWT.error(SWT.ERROR_NO_HANDLES); OS.pango_layout_set_font_description(layout, device.systemFont.handle); OS.pango_layout_set_wrap(layout, OS.PANGO_WRAP_WORD_CHAR); OS.pango_layout_set_tabs(layout, device.emptyTab); OS.pango_layout_set_auto_dir(layout, false); text = ""; wrapWidth = ascentInPoints = descentInPoints = -1; styles = new StyleItem[2]; styles[0] = new StyleItem(); styles[1] = new StyleItem(); stylesCount = 2; init(); } void checkLayout() { if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); } void computeRuns () { if (attrList != 0) return; String segmentsText = getSegmentsText(); byte[] buffer = Converter.wcsToMbcs(segmentsText, false); OS.pango_layout_set_text (layout, buffer, buffer.length); attrList = OS.pango_attr_list_new(); selAttrList = OS.pango_attr_list_new(); // pango_attr_insert_hyphens_new function is available only on Pango 1.44.0+ // pango_version (used for version check) encodes the version in an integer via // the following formula: // MAJOR*10000+MINOR*100+MICRO if ((1 * 10000 + 44 * 100 + 0) <= OS.pango_version()) { long hyphenAttr = OS.pango_attr_insert_hyphens_new(false); OS.pango_attr_list_insert(attrList, hyphenAttr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(hyphenAttr)); } if (stylesCount == 2 && styles[0].style == null && ascentInPoints == -1 && descentInPoints == -1 && segments == null) { OS.pango_layout_set_attributes(layout, attrList); return; } long ptr = OS.pango_layout_get_text(layout); PangoAttribute attribute = new PangoAttribute(); char[] chars = null; int segementsLength = segmentsText.length(); int nSegments = segementsLength - text.length(); int offsetCount = nSegments; int[] lineOffsets = null; // Set minimum line ascent/descent. Feature in Pango: glyphs affected // by `pango_attr_shape_new()` become invisible. Workaround: insert // additional control characters and shape these instead. boolean useMinAscentDescent = !metricsAdapter.isFixedMetrics() && (ascentInPoints != -1 || descentInPoints != -1); if (useMinAscentDescent && segementsLength > 0) { PangoRectangle rect = new PangoRectangle(); if (ascentInPoints != -1) rect.y = -(DPIUtil.autoScaleUp(getDevice(), ascentInPoints) * OS.PANGO_SCALE); rect.height = DPIUtil.autoScaleUp(getDevice(), (Math.max(0, ascentInPoints) + Math.max(0, descentInPoints))) * OS.PANGO_SCALE; int lineCount = OS.pango_layout_get_line_count(layout); chars = new char[segementsLength + lineCount * 2]; lineOffsets = new int [lineCount]; int oldPos = 0, lineIndex = 0; PangoLayoutLine line = new PangoLayoutLine(); while (lineIndex < lineCount) { long linePtr = OS.pango_layout_get_line(layout, lineIndex); OS.memmove(line, linePtr, PangoLayoutLine.sizeof); int bytePos = line.start_index; /* Note: The length in bytes of ZWS and ZWNBS are both equals to 3 */ int offset = lineIndex * 6; long attr = OS.pango_attr_shape_new (rect, rect); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = bytePos + offset; attribute.end_index = bytePos + offset + 3; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); attr = OS.pango_attr_shape_new (rect, rect); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = bytePos + offset + 3; attribute.end_index = bytePos + offset + 6; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); int pos = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + bytePos); chars[pos + lineIndex * 2] = ZWS; chars[pos + lineIndex * 2 + 1] = ZWNBS; segmentsText.getChars(oldPos, pos, chars, oldPos + lineIndex * 2); lineOffsets[lineIndex] = pos + lineIndex * 2; oldPos = pos; lineIndex++; } segmentsText.getChars(oldPos, segementsLength, chars, oldPos + lineIndex * 2); buffer = Converter.wcsToMbcs(chars, false); OS.pango_layout_set_text (layout, buffer, buffer.length); ptr = OS.pango_layout_get_text(layout); offsetCount += 2 * lineCount; } else { chars = new char[segementsLength]; segmentsText.getChars(0, segementsLength, chars, 0); } invalidOffsets = new int[offsetCount]; if (offsetCount > 0) { offsetCount = 0; int lineIndex = 0; int segmentCount = 0; for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (c == ZWS && lineOffsets != null && lineIndex < lineOffsets.length && i == lineOffsets[lineIndex]) { invalidOffsets[offsetCount++] = i; //ZWS invalidOffsets[offsetCount++] = ++i; //ZWNBS lineIndex++; } else if (segmentCount < nSegments && i - offsetCount == segments[segmentCount]) { invalidOffsets[offsetCount++] = i; segmentCount++; } } } int strlen = C.strlen(ptr); Font defaultFont = font != null ? font : device.systemFont; for (int i = 0; i < stylesCount - 1; i++) { StyleItem styleItem = styles[i]; TextStyle style = styleItem.style; if (style == null) continue; int start = translateOffset(styleItem.start); int end = translateOffset(styles[i+1].start - 1); int byteStart = (int)(OS.g_utf16_offset_to_pointer(ptr, start) - ptr); int byteEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, end + 1) - ptr); byteStart = Math.min(byteStart, strlen); byteEnd = Math.min(byteEnd, strlen); Font font = style.font; if (font != null && !font.isDisposed() && !defaultFont.equals(font)) { long attr = OS.pango_attr_font_desc_new (font.handle); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); } if (style.underline) { int underlineStyle = OS.PANGO_UNDERLINE_NONE; switch (style.underlineStyle) { case SWT.UNDERLINE_SINGLE: underlineStyle = OS.PANGO_UNDERLINE_SINGLE; break; case SWT.UNDERLINE_DOUBLE: underlineStyle = OS.PANGO_UNDERLINE_DOUBLE; break; case SWT.UNDERLINE_SQUIGGLE: case SWT.UNDERLINE_ERROR: underlineStyle = OS.PANGO_UNDERLINE_ERROR; break; case SWT.UNDERLINE_LINK: { if (style.foreground == null) { // Bug 497071: use COLOR_LINK_FOREGROUND for StyledText links long attr; GdkRGBA linkRGBA = device.getSystemColor(SWT.COLOR_LINK_FOREGROUND).handle; // Manual conversion since PangoAttrColor is a special case. // It uses GdkColor style colors but is supported on GTK3. attr = OS.pango_attr_foreground_new((short)(linkRGBA.red * 0xFFFF), (short)(linkRGBA.green * 0xFFFF), (short)(linkRGBA.blue * 0xFFFF)); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); } underlineStyle = OS.PANGO_UNDERLINE_SINGLE; break; } } long attr = OS.pango_attr_underline_new(underlineStyle); OS.memmove(attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove(attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); if (style.underlineColor != null) { GdkRGBA rgba = style.underlineColor.handle; attr = OS.pango_attr_underline_color_new((short)(rgba.red * 0xFFFF), (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF)); if (attr != 0) { OS.memmove(attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove(attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); } } } if (style.strikeout) { long attr = OS.pango_attr_strikethrough_new(true); OS.memmove(attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove(attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); if (style.strikeoutColor != null) { GdkRGBA rgba = style.strikeoutColor.handle; attr = OS.pango_attr_strikethrough_color_new((short)(rgba.red * 0xFFFF), (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF)); if (attr != 0) { OS.memmove(attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove(attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); } } } Color foreground = style.foreground; if (foreground != null && !foreground.isDisposed()) { long attr; GdkRGBA rgba = foreground.handle; attr = OS.pango_attr_foreground_new((short)(rgba.red * 0xFFFF), (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF)); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); } Color background = style.background; if (background != null && !background.isDisposed()) { long attr; GdkRGBA rgba = background.handle; attr = OS.pango_attr_background_new((short)(rgba.red * 0xFFFF), (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF)); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); } GlyphMetrics metrics = style.metrics; if (metrics != null) { PangoRectangle rect = new PangoRectangle(); rect.y = -(DPIUtil.autoScaleUp(getDevice(), metrics.ascent) * OS.PANGO_SCALE); rect.height = DPIUtil.autoScaleUp(getDevice(), (metrics.ascent + metrics.descent)) * OS.PANGO_SCALE; rect.width = DPIUtil.autoScaleUp(getDevice(), metrics.width) * OS.PANGO_SCALE; long attr = OS.pango_attr_shape_new (rect, rect); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); } int rise = style.rise; if (rise != 0) { long attr = OS.pango_attr_rise_new (DPIUtil.autoScaleUp(getDevice(), rise) * OS.PANGO_SCALE); OS.memmove (attribute, attr, PangoAttribute.sizeof); attribute.start_index = byteStart; attribute.end_index = byteEnd; OS.memmove (attr, attribute, PangoAttribute.sizeof); OS.pango_attr_list_insert(attrList, attr); OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr)); } } OS.pango_layout_set_attributes(layout, attrList); } int[] computePolyline(int left, int top, int right, int bottom) { int height = bottom - top; // can be any number int width = 2 * height; // must be even int peaks = Compatibility.ceil(right - left, width); if (peaks == 0 && right - left > 2) { peaks = 1; } int length = ((2 * peaks) + 1) * 2; if (length < 0) return new int[0]; int[] coordinates = new int[length]; for (int i = 0; i < peaks; i++) { int index = 4 * i; coordinates[index] = left + (width * i); coordinates[index+1] = bottom; coordinates[index+2] = coordinates[index] + width / 2; coordinates[index+3] = top; } coordinates[length-2] = left + (width * peaks); coordinates[length-1] = bottom; return coordinates; } @Override void destroy() { font = null; text = null; styles = null; freeRuns(); segments = null; segmentsChars = null; if (layout != 0) OS.g_object_unref(layout); layout = 0; if (context != 0) OS.g_object_unref(context); context = 0; } /** * Draws the receiver's text using the specified GC at the specified * point. * * @param gc the GC to draw * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn * * @exception SWTException- ERROR_NULL_ARGUMENT - if device is null and there is no current device
**
* @exception IllegalArgumentException- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed
**
*/ public void draw(GC gc, int x, int y) { x = DPIUtil.autoScaleUp(getDevice(), x); y = DPIUtil.autoScaleUp(getDevice(), y); drawInPixels(gc, x, y); } void drawInPixels(GC gc, int x, int y) { drawInPixels(gc, x, y, -1, -1, null, null); } /** * Draws the receiver's text using the specified GC at the specified * point. * * @param gc the GC to draw * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn * @param selectionStart the offset where the selections starts, or -1 indicating no selection * @param selectionEnd the offset where the selections ends, or -1 indicating no selection * @param selectionForeground selection foreground, or NULL to use the system default color * @param selectionBackground selection background, or NULL to use the system default color * * @exception SWTException- ERROR_NULL_ARGUMENT - if the gc is null
**
* @exception IllegalArgumentException- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed
**
*/ public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { checkLayout (); x = DPIUtil.autoScaleUp(getDevice(), x); y = DPIUtil.autoScaleUp(getDevice(), y); drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground); } void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0); } /** * Draws the receiver's text using the specified GC at the specified * point. *- ERROR_NULL_ARGUMENT - if the gc is null
** The parameter
* @param gc the GC to draw * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn * @param selectionStart the offset where the selections starts, or -1 indicating no selection * @param selectionEnd the offset where the selections ends, or -1 indicating no selection * @param selectionForeground selection foreground, or NULL to use the system default color * @param selectionBackground selection background, or NULL to use the system default color * @param flags drawing options * * @exception SWTExceptionflags
can include one ofSWT.DELIMITER_SELECTION
* orSWT.FULL_SELECTION
to specify the selection behavior on all lines except * for the last line, and can also includeSWT.LAST_LINE_SELECTION
to extend * the specified selection behavior to the last line. **
* @exception IllegalArgumentException- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed
**
* * @since 3.3 */ public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { checkLayout (); x = DPIUtil.autoScaleUp(getDevice(), x); y = DPIUtil.autoScaleUp(getDevice(), y); drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, flags); } void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { checkLayout (); computeRuns(); if (gc == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); if (selectionForeground != null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); if (selectionBackground != null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); gc.checkGC(GC.FOREGROUND); int length = text.length(); x += Math.min (indent, wrapIndent); y += getScaledVerticalIndent(); boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1; GCData data = gc.data; long cairo = data.cairo; boolean extent = false; if ((flags & (SWT.FULL_SELECTION | SWT.DELIMITER_SELECTION)) != 0 && (hasSelection || (flags & SWT.LAST_LINE_SELECTION) != 0)) { long [] attrs = new long [1]; int[] nAttrs = new int[1]; PangoLogAttr logAttr = new PangoLogAttr(); PangoRectangle rect = new PangoRectangle(); int lineCount = OS.pango_layout_get_line_count(layout); long ptr = OS.pango_layout_get_text(layout); long iter = OS.pango_layout_get_iter(layout); if (selectionBackground == null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION); Cairo.cairo_save(cairo); GdkRGBA rgba = selectionBackground.handle; Cairo.cairo_set_source_rgba(cairo, rgba.red, rgba.green, rgba.blue, rgba.alpha); int lineIndex = 0; do { int lineEnd; metricsAdapter.pango_layout_iter_get_line_extents(iter, null, rect); if (OS.pango_layout_iter_next_line(iter)) { int bytePos = OS.pango_layout_iter_get_index(iter); lineEnd = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + bytePos); } else { lineEnd = (int)OS.g_utf16_strlen(ptr, -1); } if (lineIndex == lineCount - 1 && (flags & SWT.LAST_LINE_SELECTION) != 0) { extent = true; } else { if (attrs[0] == 0) OS.pango_layout_get_log_attrs(layout, attrs, nAttrs); OS.memmove(logAttr, attrs[0] + lineEnd * PangoLogAttr.sizeof, PangoLogAttr.sizeof); if (!logAttr.is_line_break) { if (selectionStart <= lineEnd && lineEnd <= selectionEnd) extent = true; } else { if (selectionStart <= lineEnd && lineEnd < selectionEnd && (flags & SWT.FULL_SELECTION) != 0) { extent = true; } } } if (extent) { int lineX = x + OS.PANGO_PIXELS(rect.x) + OS.PANGO_PIXELS(rect.width); int lineY = y + OS.PANGO_PIXELS(rect.y); int height = OS.PANGO_PIXELS(rect.height); if (ascentInPoints != -1 && descentInPoints != -1) { height = Math.max (height, DPIUtil.autoScaleUp(getDevice(), ascentInPoints + descentInPoints)); } height += getSpacingInPixels(); int width = (flags & SWT.FULL_SELECTION) != 0 ? 0x7fff : height / 3; Cairo.cairo_rectangle(cairo, lineX, lineY, width, height); Cairo.cairo_fill(cairo); } lineIndex++; } while (lineIndex < lineCount); OS.pango_layout_iter_free(iter); if (attrs[0] != 0) OS.g_free(attrs[0]); Cairo.cairo_restore(cairo); } if (length == 0) return; if (!hasSelection) { if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_save(cairo); Cairo.cairo_scale(cairo, -1, 1); Cairo.cairo_translate(cairo, -2 * x - width(), 0); } metricsAdapter.pango_cairo_show_layout(cairo, layout, x, y); drawBorder(gc, x, y, null); if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_restore(cairo); } } else { selectionStart = Math.min(Math.max(0, selectionStart), length - 1); selectionEnd = Math.min(Math.max(0, selectionEnd), length - 1); length = (int)OS.g_utf16_strlen(OS.pango_layout_get_text(layout), -1); selectionStart = translateOffset(selectionStart); selectionEnd = translateOffset(selectionEnd); if (selectionForeground == null) selectionForeground = device.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); if (selectionBackground == null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION); int yExtent = extent ? getSpacingInPixels() : 0; boolean fullSelection = selectionStart == 0 && selectionEnd == length - 1; if (fullSelection) { long ptr = OS.pango_layout_get_text(layout); if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_save(cairo); Cairo.cairo_scale(cairo, -1, 1); Cairo.cairo_translate(cairo, -2 * x - width(), 0); } drawWithCairo(gc, x, y, 0, C.strlen(ptr), yExtent, fullSelection, selectionForeground.handle, selectionBackground.handle); if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_restore(cairo); } } else { long ptr = OS.pango_layout_get_text(layout); int byteSelStart = (int)(OS.g_utf16_offset_to_pointer(ptr, selectionStart) - ptr); int byteSelEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, selectionEnd + 1) - ptr); int strlen = C.strlen(ptr); byteSelStart = Math.min(byteSelStart, strlen); byteSelEnd = Math.min(byteSelEnd, strlen); if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_save(cairo); Cairo.cairo_scale(cairo, -1, 1); Cairo.cairo_translate(cairo, -2 * x - width(), 0); } drawWithCairo(gc, x, y, byteSelStart, byteSelEnd, yExtent, fullSelection, selectionForeground.handle, selectionBackground.handle); if ((data.style & SWT.MIRRORED) != 0) { Cairo.cairo_restore(cairo); } } } Cairo.cairo_new_path(cairo); } void drawWithCairo(GC gc, int x, int y, int start, int end, int yExtent, boolean fullSelection, GdkRGBA fg, GdkRGBA bg) { GCData data = gc.data; long cairo = data.cairo; Cairo.cairo_save(cairo); if (!fullSelection) { metricsAdapter.pango_cairo_show_layout(cairo, layout, x, y); drawBorder(gc, x, y, null); } int[] ranges = new int[]{start, end}; long rgn = metricsAdapter.gdk_pango_layout_get_clip_region(layout, x, y, ranges, ranges.length / 2); if (rgn != 0) { if (yExtent > 0) { cairo_rectangle_int_t rect = new cairo_rectangle_int_t(); Cairo.cairo_region_get_extents(rgn, rect); rect.height += yExtent; long extendRgn = Cairo.cairo_region_create_rectangle(rect); Cairo.cairo_region_union(rgn, extendRgn); Cairo.cairo_region_destroy(extendRgn); } GDK.gdk_cairo_region(cairo, rgn); Cairo.cairo_clip(cairo); Cairo.cairo_set_source_rgba(cairo, bg.red, bg.green, bg.blue, bg.alpha); Cairo.cairo_paint(cairo); Cairo.cairo_region_destroy(rgn); } Cairo.cairo_set_source_rgba(cairo, fg.red, fg.green, fg.blue, fg.alpha); OS.pango_layout_set_attributes(layout, selAttrList); metricsAdapter.pango_cairo_show_layout(cairo, layout, x, y); OS.pango_layout_set_attributes(layout, attrList); drawBorder(gc, x, y, fg); Cairo.cairo_restore(cairo); } void drawBorder(GC gc, int x, int y, GdkRGBA selectionColor) { GCData data = gc.data; long cairo = data.cairo; long ptr = OS.pango_layout_get_text(layout); Cairo.cairo_save(cairo); for (int i = 0; i < stylesCount - 1; i++) { TextStyle style = styles[i].style; if (style == null) continue; boolean drawBorder = style.borderStyle != SWT.NONE; if (drawBorder && !style.isAdherentBorder(styles[i+1].style)) { int start = styles[i].start; for (int j = i; j > 0 && style.isAdherentBorder(styles[j-1].style); j--) { start = styles[j - 1].start; } start = translateOffset(start); int end = translateOffset(styles[i+1].start - 1); int byteStart = (int)(OS.g_utf16_offset_to_pointer(ptr, start) - ptr); int byteEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, end + 1) - ptr); int[] ranges = new int[]{byteStart, byteEnd}; long rgn = metricsAdapter.gdk_pango_layout_get_clip_region(layout, x, y, ranges, ranges.length / 2); if (rgn != 0) { int[] nRects = new int[1]; long [] rects = new long [1]; Region.cairo_region_get_rectangles(rgn, rects, nRects); cairo_rectangle_int_t rect = new cairo_rectangle_int_t(); GdkRGBA colorRGBA = null; if (colorRGBA == null && style.borderColor != null) colorRGBA = style.borderColor.handle; if (colorRGBA == null && selectionColor != null) colorRGBA = selectionColor; if (colorRGBA == null && style.foreground != null) colorRGBA = style.foreground.handle; if (colorRGBA == null) colorRGBA = data.foregroundRGBA; int width = 1; float[] dashes = null; switch (style.borderStyle) { case SWT.BORDER_SOLID: break; case SWT.BORDER_DASH: dashes = width != 0 ? GC.LINE_DASH : GC.LINE_DASH_ZERO; break; case SWT.BORDER_DOT: dashes = width != 0 ? GC.LINE_DOT : GC.LINE_DOT_ZERO; break; } Cairo.cairo_set_source_rgba(cairo, colorRGBA.red, colorRGBA.green, colorRGBA.blue, colorRGBA.alpha); Cairo.cairo_set_line_width(cairo, width); if (dashes != null) { double[] cairoDashes = new double[dashes.length]; for (int j = 0; j < cairoDashes.length; j++) { cairoDashes[j] = width == 0 || data.lineStyle == SWT.LINE_CUSTOM ? dashes[j] : dashes[j] * width; } Cairo.cairo_set_dash(cairo, cairoDashes, cairoDashes.length, 0); } else { Cairo.cairo_set_dash(cairo, null, 0, 0); } for (int j=0; j- ERROR_NULL_ARGUMENT - if the gc is null
*SWT.LEFT SWT.CENTER
or *SWT.RIGHT
. * * @return the alignment used to positioned text horizontally * * @exception SWTException
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the character offset is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed
-
*
- ERROR_INVALID_ARGUMENT - if the line index is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the character offset is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the line index is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
trailing
argument indicates whether the offset
* corresponds to the leading or trailing edge of the cluster.
*
* @param offset the character offset
* @param trailing the trailing flag
* @return the location of the character offset
*
* @exception SWTException -
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
SWT.MOVEMENT_CHAR
,
* SWT.MOVEMENT_CLUSTER
, SWT.MOVEMENT_WORD
,
* SWT.MOVEMENT_WORD_END
or SWT.MOVEMENT_WORD_START
.
*
* @param offset the start offset
* @param movement the movement type
* @return the next offset
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the offset is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the trailing length is less than
1
* - ERROR_NULL_ARGUMENT - if the point is null *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the trailing length is less than
1
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
SWT.MOVEMENT_CHAR
,
* SWT.MOVEMENT_CLUSTER
or SWT.MOVEMENT_WORD
,
* SWT.MOVEMENT_WORD_END
or SWT.MOVEMENT_WORD_START
.
*
* @param offset the start offset
* @param movement the movement type
* @return the previous offset
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the offset is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
TextStyle
.
*
* @return the ranges, an array of offsets representing the start and end of each
* text style.
*
* @exception SWTException -
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
null
if not set
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the character offset is out of range *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
true
if the text layout has been disposed,
* and false
otherwise.
* * This method gets the dispose state for the text layout. * When a text layout has been disposed, it is an error to * invoke any other method (except {@link #dispose()}) using the text layout. *
* * @returntrue
when the text layout is disposed and false
otherwise
*/
@Override
public boolean isDisposed () {
return layout == 0;
}
/**
* Sets the text alignment for the receiver. The alignment controls
* how a line of text is positioned horizontally. The argument should
* be one of SWT.LEFT
, SWT.RIGHT
or SWT.CENTER
.
*
* The default alignment is SWT.LEFT
. Note that the receiver's
* width must be set in order to use SWT.RIGHT
or SWT.CENTER
* alignment.
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-1
which means that the
* ascent is calculated from the line fonts.
*
* @param ascent the new ascent
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the ascent is less than
-1
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-1
which means that the
* descent is calculated from the line fonts.
*
* @param descent the new descent
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the descent is less than
-1
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
* Text lines with different metrics will be forced to fit. This means * painting text in such a way that its baseline is where specified by * given 'metrics'. This can sometimes introduce small visual artifacs, * such as taller lines overpainting or being clipped by content above * and below. *
* The possible ways to set FontMetrics include: *-
*
- Obtaining 'FontMetrics' via {@link GC#getFontMetrics}. Note that * this will only obtain metrics for currently selected font and will not * account for font fallbacks (for example, with a latin font selected, * painting hieroglyphs usually involves a fallback font). *
- Obtaining 'FontMetrics' via a temporary 'TextLayout'. This would * involve setting a desired text sample to 'TextLayout', then measuring * it with {@link TextLayout#getLineMetrics(int)}. This approach will also * take fallback fonts into account. *
-
*
- ERROR_INVALID_ARGUMENT - if the font has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
SWT.LEFT_TO_RIGHT
or SWT.RIGHT_TO_LEFT
.
*
* @param orientation new orientation style
*
* @exception SWTException -
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the spacing is negative *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the vertical indent is negative *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
* Each text segment is determined by two consecutive offsets in the
* segments
arrays. The first element of the array should
* always be zero and the last one should always be equals to length of
* the text.
*
* When segments characters are set, the segments are the offsets where * the characters are inserted in the text. *
* * @param segments the text segments offset * * @exception SWTException-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
TextLayout
.
*
* @param segmentsChars the segments characters
*
* @exception SWTException -
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
* Note: Setting the text also clears all the styles. This method * returns without doing anything if the new text is the same as * the current text. *
* * @param text the new text * * @exception IllegalArgumentException-
*
- ERROR_NULL_ARGUMENT - if the text is null *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
SWT.LEFT_TO_RIGHT
, SWT.RIGHT_TO_LEFT
* or SWT.AUTO_TEXT_DIRECTION
.
*
* * Warning: This API is currently only implemented on Windows. * It doesn't set the base text direction on GTK and Cocoa. *
* * @param textDirection the new text direction * * @exception SWTException-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-1
which means wrapping is disabled.
*
* @param width the new width
*
* @exception IllegalArgumentException -
*
- ERROR_INVALID_ARGUMENT - if the width is
0
or less than-1
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *
-
*
- ERROR_INVALID_ARGUMENT - if the tabLength is less than
0
*
-
*
- ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed *