1 /*******************************************************************************
2 * Copyright (c) 2000, 2020 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.graphics;
15
16 import java.util.*;
17
18 import org.eclipse.swt.*;
19 import org.eclipse.swt.internal.*;
20 import org.eclipse.swt.internal.cairo.*;
21 import org.eclipse.swt.internal.gtk.*;
22
23 /**
24 * <code>TextLayout</code> is a graphic object that represents
25 * styled text.
26 * <p>
27 * Instances of this class provide support for drawing, cursor
28 * navigation, hit testing, text wrapping, alignment, tab expansion
29 * line breaking, etc. These are aspects required for rendering internationalized text.
30 * </p><p>
31 * Application code must explicitly invoke the <code>TextLayout#dispose()</code>
32 * method to release the operating system resources managed by each instance
33 * when those instances are no longer required.
34 * </p>
35 *
36 * @see <a href="http://www.eclipse.org/swt/snippets/#textlayout">TextLayout, TextStyle snippets</a>
37 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample, StyledText tab</a>
38 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
39 *
40 * @since 3.0
41 */
42 public final class TextLayout extends Resource {
43
44 static class StyleItem {
45 TextStyle style;
46 int start;
47
48 @Override
toString()49 public String toString () {
50 return "StyleItem {" + start + ", " + style + "}";
51 }
52 }
53
54 Font font;
55 String text;
56 int ascentInPoints, descentInPoints;
57 int indent, wrapIndent, wrapWidth;
58 int[] segments;
59 char[] segmentsChars;
60 int[] tabs;
61 StyleItem[] styles;
62 int stylesCount;
63 long layout, context, attrList, selAttrList;
64 int[] invalidOffsets;
65 int verticalIndentInPoints;
66 static final char LTR_MARK = '\u200E', RTL_MARK = '\u200F', ZWS = '\u200B', ZWNBS = '\uFEFF';
67
68 /**
69 * Constructs a new instance of this class on the given device.
70 * <p>
71 * You must dispose the text layout when it is no longer required.
72 * </p>
73 *
74 * @param device the device on which to allocate the text layout
75 *
76 * @exception IllegalArgumentException <ul>
77 * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
78 * </ul>
79 *
80 * @see #dispose()
81 */
TextLayout(Device device)82 public TextLayout (Device device) {
83 super(device);
84 device = this.device;
85 if (GTK.GTK4) {
86 long fontMap = OS.pango_cairo_font_map_get_default ();
87 context = OS.pango_font_map_create_context (fontMap);
88 } else {
89 context = GDK.gdk_pango_context_get();
90 }
91 if (context == 0) SWT.error(SWT.ERROR_NO_HANDLES);
92 OS.pango_context_set_language(context, GTK.gtk_get_default_language());
93 OS.pango_context_set_base_dir(context, OS.PANGO_DIRECTION_LTR);
94 layout = OS.pango_layout_new(context);
95 if (layout == 0) SWT.error(SWT.ERROR_NO_HANDLES);
96 OS.pango_layout_set_font_description(layout, device.systemFont.handle);
97 OS.pango_layout_set_wrap(layout, OS.PANGO_WRAP_WORD_CHAR);
98 OS.pango_layout_set_tabs(layout, device.emptyTab);
99 OS.pango_layout_set_auto_dir(layout, false);
100 text = "";
101 wrapWidth = ascentInPoints = descentInPoints = -1;
102 styles = new StyleItem[2];
103 styles[0] = new StyleItem();
104 styles[1] = new StyleItem();
105 stylesCount = 2;
106 init();
107 }
108
checkLayout()109 void checkLayout() {
110 if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
111 }
112
computeRuns()113 void computeRuns () {
114 if (attrList != 0) return;
115 String segmentsText = getSegmentsText();
116 byte[] buffer = Converter.wcsToMbcs(segmentsText, false);
117 OS.pango_layout_set_text (layout, buffer, buffer.length);
118 if (stylesCount == 2 && styles[0].style == null && ascentInPoints == -1 && descentInPoints == -1 && segments == null) return;
119 long ptr = OS.pango_layout_get_text(layout);
120 attrList = OS.pango_attr_list_new();
121 selAttrList = OS.pango_attr_list_new();
122 PangoAttribute attribute = new PangoAttribute();
123 char[] chars = null;
124 int segementsLength = segmentsText.length();
125 int nSegments = segementsLength - text.length();
126 int offsetCount = nSegments;
127 int[] lineOffsets = null;
128 if ((ascentInPoints != -1 || descentInPoints != -1) && segementsLength > 0) {
129 PangoRectangle rect = new PangoRectangle();
130 if (ascentInPoints != -1) rect.y = -(DPIUtil.autoScaleUp(getDevice(), ascentInPoints) * OS.PANGO_SCALE);
131 rect.height = DPIUtil.autoScaleUp(getDevice(), (Math.max(0, ascentInPoints) + Math.max(0, descentInPoints))) * OS.PANGO_SCALE;
132 int lineCount = OS.pango_layout_get_line_count(layout);
133 chars = new char[segementsLength + lineCount * 2];
134 lineOffsets = new int [lineCount];
135 int oldPos = 0, lineIndex = 0;
136 PangoLayoutLine line = new PangoLayoutLine();
137 while (lineIndex < lineCount) {
138 long linePtr = OS.pango_layout_get_line(layout, lineIndex);
139 OS.memmove(line, linePtr, PangoLayoutLine.sizeof);
140 int bytePos = line.start_index;
141 /* Note: The length in bytes of ZWS and ZWNBS are both equals to 3 */
142 int offset = lineIndex * 6;
143 long attr = OS.pango_attr_shape_new (rect, rect);
144 OS.memmove (attribute, attr, PangoAttribute.sizeof);
145 attribute.start_index = bytePos + offset;
146 attribute.end_index = bytePos + offset + 3;
147 OS.memmove (attr, attribute, PangoAttribute.sizeof);
148 OS.pango_attr_list_insert(attrList, attr);
149 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
150 attr = OS.pango_attr_shape_new (rect, rect);
151 OS.memmove (attribute, attr, PangoAttribute.sizeof);
152 attribute.start_index = bytePos + offset + 3;
153 attribute.end_index = bytePos + offset + 6;
154 OS.memmove (attr, attribute, PangoAttribute.sizeof);
155 OS.pango_attr_list_insert(attrList, attr);
156 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
157 int pos = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + bytePos);
158 chars[pos + lineIndex * 2] = ZWS;
159 chars[pos + lineIndex * 2 + 1] = ZWNBS;
160 segmentsText.getChars(oldPos, pos, chars, oldPos + lineIndex * 2);
161 lineOffsets[lineIndex] = pos + lineIndex * 2;
162 oldPos = pos;
163 lineIndex++;
164 }
165 segmentsText.getChars(oldPos, segementsLength, chars, oldPos + lineIndex * 2);
166 buffer = Converter.wcsToMbcs(chars, false);
167 OS.pango_layout_set_text (layout, buffer, buffer.length);
168 ptr = OS.pango_layout_get_text(layout);
169 offsetCount += 2 * lineCount;
170 } else {
171 chars = new char[segementsLength];
172 segmentsText.getChars(0, segementsLength, chars, 0);
173 }
174 invalidOffsets = new int[offsetCount];
175 if (offsetCount > 0) {
176 offsetCount = 0;
177 int lineIndex = 0;
178 int segmentCount = 0;
179 for (int i = 0; i < chars.length; i++) {
180 char c = chars[i];
181 if (c == ZWS && lineOffsets != null && lineIndex < lineOffsets.length && i == lineOffsets[lineIndex]) {
182 invalidOffsets[offsetCount++] = i; //ZWS
183 invalidOffsets[offsetCount++] = ++i; //ZWNBS
184 lineIndex++;
185 } else if (segmentCount < nSegments && i - offsetCount == segments[segmentCount]) {
186 invalidOffsets[offsetCount++] = i;
187 segmentCount++;
188 }
189 }
190 }
191 int strlen = C.strlen(ptr);
192 Font defaultFont = font != null ? font : device.systemFont;
193 for (int i = 0; i < stylesCount - 1; i++) {
194 StyleItem styleItem = styles[i];
195 TextStyle style = styleItem.style;
196 if (style == null) continue;
197 int start = translateOffset(styleItem.start);
198 int end = translateOffset(styles[i+1].start - 1);
199 int byteStart = (int)(OS.g_utf16_offset_to_pointer(ptr, start) - ptr);
200 int byteEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, end + 1) - ptr);
201 byteStart = Math.min(byteStart, strlen);
202 byteEnd = Math.min(byteEnd, strlen);
203 Font font = style.font;
204 if (font != null && !font.isDisposed() && !defaultFont.equals(font)) {
205 long attr = OS.pango_attr_font_desc_new (font.handle);
206 OS.memmove (attribute, attr, PangoAttribute.sizeof);
207 attribute.start_index = byteStart;
208 attribute.end_index = byteEnd;
209 OS.memmove (attr, attribute, PangoAttribute.sizeof);
210 OS.pango_attr_list_insert(attrList, attr);
211 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
212 }
213 if (style.underline) {
214 int underlineStyle = OS.PANGO_UNDERLINE_NONE;
215 switch (style.underlineStyle) {
216 case SWT.UNDERLINE_SINGLE:
217 underlineStyle = OS.PANGO_UNDERLINE_SINGLE;
218 break;
219 case SWT.UNDERLINE_DOUBLE:
220 underlineStyle = OS.PANGO_UNDERLINE_DOUBLE;
221 break;
222 case SWT.UNDERLINE_SQUIGGLE:
223 case SWT.UNDERLINE_ERROR:
224 underlineStyle = OS.PANGO_UNDERLINE_ERROR;
225 break;
226 case SWT.UNDERLINE_LINK: {
227 if (style.foreground == null) {
228 // Bug 497071: use COLOR_LINK_FOREGROUND for StyledText links
229 long attr;
230 GdkRGBA linkRGBA = device.getSystemColor(SWT.COLOR_LINK_FOREGROUND).handle;
231 // Manual conversion since PangoAttrColor is a special case.
232 // It uses GdkColor style colors but is supported on GTK3.
233 attr = OS.pango_attr_foreground_new((short)(linkRGBA.red * 0xFFFF),
234 (short)(linkRGBA.green * 0xFFFF), (short)(linkRGBA.blue * 0xFFFF));
235 OS.memmove (attribute, attr, PangoAttribute.sizeof);
236 attribute.start_index = byteStart;
237 attribute.end_index = byteEnd;
238 OS.memmove (attr, attribute, PangoAttribute.sizeof);
239 OS.pango_attr_list_insert(attrList, attr);
240 }
241 underlineStyle = OS.PANGO_UNDERLINE_SINGLE;
242 break;
243 }
244 }
245 long attr = OS.pango_attr_underline_new(underlineStyle);
246 OS.memmove(attribute, attr, PangoAttribute.sizeof);
247 attribute.start_index = byteStart;
248 attribute.end_index = byteEnd;
249 OS.memmove(attr, attribute, PangoAttribute.sizeof);
250 OS.pango_attr_list_insert(attrList, attr);
251 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
252 if (style.underlineColor != null) {
253 GdkRGBA rgba = style.underlineColor.handle;
254 attr = OS.pango_attr_underline_color_new((short)(rgba.red * 0xFFFF),
255 (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF));
256 if (attr != 0) {
257 OS.memmove(attribute, attr, PangoAttribute.sizeof);
258 attribute.start_index = byteStart;
259 attribute.end_index = byteEnd;
260 OS.memmove(attr, attribute, PangoAttribute.sizeof);
261 OS.pango_attr_list_insert(attrList, attr);
262 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
263 }
264 }
265 }
266 if (style.strikeout) {
267 long attr = OS.pango_attr_strikethrough_new(true);
268 OS.memmove(attribute, attr, PangoAttribute.sizeof);
269 attribute.start_index = byteStart;
270 attribute.end_index = byteEnd;
271 OS.memmove(attr, attribute, PangoAttribute.sizeof);
272 OS.pango_attr_list_insert(attrList, attr);
273 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
274 if (style.strikeoutColor != null) {
275 GdkRGBA rgba = style.strikeoutColor.handle;
276 attr = OS.pango_attr_strikethrough_color_new((short)(rgba.red * 0xFFFF),
277 (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF));
278 if (attr != 0) {
279 OS.memmove(attribute, attr, PangoAttribute.sizeof);
280 attribute.start_index = byteStart;
281 attribute.end_index = byteEnd;
282 OS.memmove(attr, attribute, PangoAttribute.sizeof);
283 OS.pango_attr_list_insert(attrList, attr);
284 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
285 }
286 }
287 }
288 Color foreground = style.foreground;
289 if (foreground != null && !foreground.isDisposed()) {
290 long attr;
291 GdkRGBA rgba = foreground.handle;
292 attr = OS.pango_attr_foreground_new((short)(rgba.red * 0xFFFF),
293 (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF));
294 OS.memmove (attribute, attr, PangoAttribute.sizeof);
295 attribute.start_index = byteStart;
296 attribute.end_index = byteEnd;
297 OS.memmove (attr, attribute, PangoAttribute.sizeof);
298 OS.pango_attr_list_insert(attrList, attr);
299 }
300 Color background = style.background;
301 if (background != null && !background.isDisposed()) {
302 long attr;
303 GdkRGBA rgba = background.handle;
304 attr = OS.pango_attr_background_new((short)(rgba.red * 0xFFFF),
305 (short)(rgba.green * 0xFFFF), (short)(rgba.blue * 0xFFFF));
306 OS.memmove (attribute, attr, PangoAttribute.sizeof);
307 attribute.start_index = byteStart;
308 attribute.end_index = byteEnd;
309 OS.memmove (attr, attribute, PangoAttribute.sizeof);
310 OS.pango_attr_list_insert(attrList, attr);
311 }
312 GlyphMetrics metrics = style.metrics;
313 if (metrics != null) {
314 PangoRectangle rect = new PangoRectangle();
315 rect.y = -(DPIUtil.autoScaleUp(getDevice(), metrics.ascent) * OS.PANGO_SCALE);
316 rect.height = DPIUtil.autoScaleUp(getDevice(), (metrics.ascent + metrics.descent)) * OS.PANGO_SCALE;
317 rect.width = DPIUtil.autoScaleUp(getDevice(), metrics.width) * OS.PANGO_SCALE;
318 long attr = OS.pango_attr_shape_new (rect, rect);
319 OS.memmove (attribute, attr, PangoAttribute.sizeof);
320 attribute.start_index = byteStart;
321 attribute.end_index = byteEnd;
322 OS.memmove (attr, attribute, PangoAttribute.sizeof);
323 OS.pango_attr_list_insert(attrList, attr);
324 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
325 }
326 int rise = style.rise;
327 if (rise != 0) {
328 long attr = OS.pango_attr_rise_new (DPIUtil.autoScaleUp(getDevice(), rise) * OS.PANGO_SCALE);
329 OS.memmove (attribute, attr, PangoAttribute.sizeof);
330 attribute.start_index = byteStart;
331 attribute.end_index = byteEnd;
332 OS.memmove (attr, attribute, PangoAttribute.sizeof);
333 OS.pango_attr_list_insert(attrList, attr);
334 OS.pango_attr_list_insert(selAttrList, OS.pango_attribute_copy(attr));
335 }
336 }
337 OS.pango_layout_set_attributes(layout, attrList);
338 }
339
computePolyline(int left, int top, int right, int bottom)340 int[] computePolyline(int left, int top, int right, int bottom) {
341 int height = bottom - top; // can be any number
342 int width = 2 * height; // must be even
343 int peaks = Compatibility.ceil(right - left, width);
344 if (peaks == 0 && right - left > 2) {
345 peaks = 1;
346 }
347 int length = ((2 * peaks) + 1) * 2;
348 if (length < 0) return new int[0];
349
350 int[] coordinates = new int[length];
351 for (int i = 0; i < peaks; i++) {
352 int index = 4 * i;
353 coordinates[index] = left + (width * i);
354 coordinates[index+1] = bottom;
355 coordinates[index+2] = coordinates[index] + width / 2;
356 coordinates[index+3] = top;
357 }
358 coordinates[length-2] = left + (width * peaks);
359 coordinates[length-1] = bottom;
360 return coordinates;
361 }
362
363 @Override
destroy()364 void destroy() {
365 font = null;
366 text = null;
367 styles = null;
368 freeRuns();
369 segments = null;
370 segmentsChars = null;
371 if (layout != 0) OS.g_object_unref(layout);
372 layout = 0;
373 if (context != 0) OS.g_object_unref(context);
374 context = 0;
375 }
376
377 /**
378 * Draws the receiver's text using the specified GC at the specified
379 * point.
380 *
381 * @param gc the GC to draw
382 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
383 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
384 *
385 * @exception SWTException <ul>
386 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
387 * </ul>
388 * @exception IllegalArgumentException <ul>
389 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
390 * </ul>
391 */
draw(GC gc, int x, int y)392 public void draw(GC gc, int x, int y) {
393 x = DPIUtil.autoScaleUp(getDevice(), x);
394 y = DPIUtil.autoScaleUp(getDevice(), y);
395 drawInPixels(gc, x, y);
396 }
397
drawInPixels(GC gc, int x, int y)398 void drawInPixels(GC gc, int x, int y) {
399 drawInPixels(gc, x, y, -1, -1, null, null);
400 }
401
402 /**
403 * Draws the receiver's text using the specified GC at the specified
404 * point.
405 *
406 * @param gc the GC to draw
407 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
408 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
409 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
410 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
411 * @param selectionForeground selection foreground, or NULL to use the system default color
412 * @param selectionBackground selection background, or NULL to use the system default color
413 *
414 * @exception SWTException <ul>
415 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
416 * </ul>
417 * @exception IllegalArgumentException <ul>
418 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
419 * </ul>
420 */
draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground)421 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
422 checkLayout ();
423 x = DPIUtil.autoScaleUp(getDevice(), x);
424 y = DPIUtil.autoScaleUp(getDevice(), y);
425 drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground);
426 }
drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground)427 void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
428 drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0);
429 }
430
431 /**
432 * Draws the receiver's text using the specified GC at the specified
433 * point.
434 * <p>
435 * The parameter <code>flags</code> can include one of <code>SWT.DELIMITER_SELECTION</code>
436 * or <code>SWT.FULL_SELECTION</code> to specify the selection behavior on all lines except
437 * for the last line, and can also include <code>SWT.LAST_LINE_SELECTION</code> to extend
438 * the specified selection behavior to the last line.
439 * </p>
440 * @param gc the GC to draw
441 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
442 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
443 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
444 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
445 * @param selectionForeground selection foreground, or NULL to use the system default color
446 * @param selectionBackground selection background, or NULL to use the system default color
447 * @param flags drawing options
448 *
449 * @exception SWTException <ul>
450 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
451 * </ul>
452 * @exception IllegalArgumentException <ul>
453 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
454 * </ul>
455 *
456 * @since 3.3
457 */
draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags)458 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
459 checkLayout ();
460 x = DPIUtil.autoScaleUp(getDevice(), x);
461 y = DPIUtil.autoScaleUp(getDevice(), y);
462 drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, flags);
463 }
drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags)464 void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
465 checkLayout ();
466 computeRuns();
467 if (gc == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
468 if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
469 if (selectionForeground != null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
470 if (selectionBackground != null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
471 gc.checkGC(GC.FOREGROUND);
472 int length = text.length();
473 x += Math.min (indent, wrapIndent);
474 y += getScaledVerticalIndent();
475 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
476 GCData data = gc.data;
477 long cairo = data.cairo;
478 if ((flags & (SWT.FULL_SELECTION | SWT.DELIMITER_SELECTION)) != 0 && (hasSelection || (flags & SWT.LAST_LINE_SELECTION) != 0)) {
479 long [] attrs = new long [1];
480 int[] nAttrs = new int[1];
481 PangoLogAttr logAttr = new PangoLogAttr();
482 PangoRectangle rect = new PangoRectangle();
483 int lineCount = OS.pango_layout_get_line_count(layout);
484 long ptr = OS.pango_layout_get_text(layout);
485 long iter = OS.pango_layout_get_iter(layout);
486 if (selectionBackground == null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION);
487 Cairo.cairo_save(cairo);
488 GdkRGBA rgba = selectionBackground.handle;
489 Cairo.cairo_set_source_rgba(cairo, rgba.red, rgba.green, rgba.blue, rgba.alpha);
490 int lineIndex = 0;
491 do {
492 int lineEnd;
493 OS.pango_layout_iter_get_line_extents(iter, null, rect);
494 if (OS.pango_layout_iter_next_line(iter)) {
495 int bytePos = OS.pango_layout_iter_get_index(iter);
496 lineEnd = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + bytePos);
497 } else {
498 lineEnd = (int)OS.g_utf16_strlen(ptr, -1);
499 }
500 boolean extent = false;
501 if (lineIndex == lineCount - 1 && (flags & SWT.LAST_LINE_SELECTION) != 0) {
502 extent = true;
503 } else {
504 if (attrs[0] == 0) OS.pango_layout_get_log_attrs(layout, attrs, nAttrs);
505 OS.memmove(logAttr, attrs[0] + lineEnd * PangoLogAttr.sizeof, PangoLogAttr.sizeof);
506 if (!logAttr.is_line_break) {
507 if (selectionStart <= lineEnd && lineEnd <= selectionEnd) extent = true;
508 } else {
509 if (selectionStart <= lineEnd && lineEnd < selectionEnd && (flags & SWT.FULL_SELECTION) != 0) {
510 extent = true;
511 }
512 }
513 }
514 if (extent) {
515 int lineX = x + OS.PANGO_PIXELS(rect.x) + OS.PANGO_PIXELS(rect.width);
516 int lineY = y + OS.PANGO_PIXELS(rect.y);
517 int height = OS.PANGO_PIXELS(rect.height);
518 if (ascentInPoints != -1 && descentInPoints != -1) {
519 height = Math.max (height, DPIUtil.autoScaleUp(getDevice(), ascentInPoints + descentInPoints));
520 }
521 int width = (flags & SWT.FULL_SELECTION) != 0 ? 0x7fff : height / 3;
522 Cairo.cairo_rectangle(cairo, lineX, lineY, width, height);
523 Cairo.cairo_fill(cairo);
524 }
525 lineIndex++;
526 } while (lineIndex < lineCount);
527 OS.pango_layout_iter_free(iter);
528 if (attrs[0] != 0) OS.g_free(attrs[0]);
529 Cairo.cairo_restore(cairo);
530 }
531 if (length == 0) return;
532 if (!hasSelection) {
533 if ((data.style & SWT.MIRRORED) != 0) {
534 Cairo.cairo_save(cairo);
535 Cairo.cairo_scale(cairo, -1, 1);
536 Cairo.cairo_translate(cairo, -2 * x - width(), 0);
537 }
538 Cairo.cairo_move_to(cairo, x, y);
539 OS.pango_cairo_show_layout(cairo, layout);
540 drawBorder(gc, x, y, null);
541 if ((data.style & SWT.MIRRORED) != 0) {
542 Cairo.cairo_restore(cairo);
543 }
544 } else {
545 selectionStart = Math.min(Math.max(0, selectionStart), length - 1);
546 selectionEnd = Math.min(Math.max(0, selectionEnd), length - 1);
547 length = (int)OS.g_utf16_strlen(OS.pango_layout_get_text(layout), -1);
548 selectionStart = translateOffset(selectionStart);
549 selectionEnd = translateOffset(selectionEnd);
550 if (selectionForeground == null) selectionForeground = device.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
551 if (selectionBackground == null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION);
552 boolean fullSelection = selectionStart == 0 && selectionEnd == length - 1;
553 if (fullSelection) {
554 long ptr = OS.pango_layout_get_text(layout);
555 if ((data.style & SWT.MIRRORED) != 0) {
556 Cairo.cairo_save(cairo);
557 Cairo.cairo_scale(cairo, -1, 1);
558 Cairo.cairo_translate(cairo, -2 * x - width(), 0);
559 }
560 drawWithCairo(gc, x, y, 0, C.strlen(ptr), fullSelection, selectionForeground.handle,
561 selectionBackground.handle);
562 if ((data.style & SWT.MIRRORED) != 0) {
563 Cairo.cairo_restore(cairo);
564 }
565 } else {
566 long ptr = OS.pango_layout_get_text(layout);
567 int byteSelStart = (int)(OS.g_utf16_offset_to_pointer(ptr, selectionStart) - ptr);
568 int byteSelEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, selectionEnd + 1) - ptr);
569 int strlen = C.strlen(ptr);
570 byteSelStart = Math.min(byteSelStart, strlen);
571 byteSelEnd = Math.min(byteSelEnd, strlen);
572 if ((data.style & SWT.MIRRORED) != 0) {
573 Cairo.cairo_save(cairo);
574 Cairo.cairo_scale(cairo, -1, 1);
575 Cairo.cairo_translate(cairo, -2 * x - width(), 0);
576 }
577 drawWithCairo(gc, x, y, byteSelStart, byteSelEnd, fullSelection, selectionForeground.handle,
578 selectionBackground.handle);
579 if ((data.style & SWT.MIRRORED) != 0) {
580 Cairo.cairo_restore(cairo);
581 }
582 }
583 }
584 Cairo.cairo_new_path(cairo);
585 }
586
587 // Bug 477950: In order to support GTK2 and GTK3 colors simultaneously, this method's parameters
588 // were modified to accept SWT Color objects instead of GdkColor structs.
drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelection, GdkRGBA fg, GdkRGBA bg)589 void drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelection, GdkRGBA fg, GdkRGBA bg) {
590 GCData data = gc.data;
591 long cairo = data.cairo;
592 Cairo.cairo_save(cairo);
593 if (!fullSelection) {
594 Cairo.cairo_move_to(cairo, x, y);
595 OS.pango_cairo_show_layout(cairo, layout);
596 drawBorder(gc, x, y, null);
597 }
598 int[] ranges = new int[]{start, end};
599 long rgn = GDK.gdk_pango_layout_get_clip_region(layout, x, y, ranges, ranges.length / 2);
600 if (rgn != 0) {
601 GDK.gdk_cairo_region(cairo, rgn);
602 Cairo.cairo_clip(cairo);
603 Cairo.cairo_set_source_rgba(cairo, bg.red, bg.green, bg.blue, bg.alpha);
604 Cairo.cairo_paint(cairo);
605 Cairo.cairo_region_destroy(rgn);
606 }
607 Cairo.cairo_set_source_rgba(cairo, fg.red, fg.green, fg.blue, fg.alpha);
608 Cairo.cairo_move_to(cairo, x, y);
609 OS.pango_layout_set_attributes(layout, selAttrList);
610 OS.pango_cairo_show_layout(cairo, layout);
611 OS.pango_layout_set_attributes(layout, attrList);
612 drawBorder(gc, x, y, fg);
613 Cairo.cairo_restore(cairo);
614 }
615
drawBorder(GC gc, int x, int y, GdkRGBA selectionColor)616 void drawBorder(GC gc, int x, int y, GdkRGBA selectionColor) {
617 GCData data = gc.data;
618 long cairo = data.cairo;
619 long ptr = OS.pango_layout_get_text(layout);
620 Cairo.cairo_save(cairo);
621 for (int i = 0; i < stylesCount - 1; i++) {
622 TextStyle style = styles[i].style;
623 if (style == null) continue;
624
625 boolean drawBorder = style.borderStyle != SWT.NONE;
626 if (drawBorder && !style.isAdherentBorder(styles[i+1].style)) {
627 int start = styles[i].start;
628 for (int j = i; j > 0 && style.isAdherentBorder(styles[j-1].style); j--) {
629 start = styles[j - 1].start;
630 }
631 start = translateOffset(start);
632 int end = translateOffset(styles[i+1].start - 1);
633 int byteStart = (int)(OS.g_utf16_offset_to_pointer(ptr, start) - ptr);
634 int byteEnd = (int)(OS.g_utf16_offset_to_pointer(ptr, end + 1) - ptr);
635 int[] ranges = new int[]{byteStart, byteEnd};
636 long rgn = GDK.gdk_pango_layout_get_clip_region(layout, x, y, ranges, ranges.length / 2);
637 if (rgn != 0) {
638 int[] nRects = new int[1];
639 long [] rects = new long [1];
640 Region.cairo_region_get_rectangles(rgn, rects, nRects);
641 cairo_rectangle_int_t rect = new cairo_rectangle_int_t();
642 GdkRGBA colorRGBA = null;
643 if (colorRGBA == null && style.borderColor != null) colorRGBA = style.borderColor.handle;
644 if (colorRGBA == null && selectionColor != null) colorRGBA = selectionColor;
645 if (colorRGBA == null && style.foreground != null) colorRGBA = style.foreground.handle;
646 if (colorRGBA == null) colorRGBA = data.foregroundRGBA;
647 int width = 1;
648 float[] dashes = null;
649 switch (style.borderStyle) {
650 case SWT.BORDER_SOLID: break;
651 case SWT.BORDER_DASH: dashes = width != 0 ? GC.LINE_DASH : GC.LINE_DASH_ZERO; break;
652 case SWT.BORDER_DOT: dashes = width != 0 ? GC.LINE_DOT : GC.LINE_DOT_ZERO; break;
653 }
654 Cairo.cairo_set_source_rgba(cairo, colorRGBA.red, colorRGBA.green, colorRGBA.blue, colorRGBA.alpha);
655 Cairo.cairo_set_line_width(cairo, width);
656 if (dashes != null) {
657 double[] cairoDashes = new double[dashes.length];
658 for (int j = 0; j < cairoDashes.length; j++) {
659 cairoDashes[j] = width == 0 || data.lineStyle == SWT.LINE_CUSTOM ? dashes[j] : dashes[j] * width;
660 }
661 Cairo.cairo_set_dash(cairo, cairoDashes, cairoDashes.length, 0);
662 } else {
663 Cairo.cairo_set_dash(cairo, null, 0, 0);
664 }
665 for (int j=0; j<nRects[0]; j++) {
666 Cairo.memmove(rect, rects[0] + (j * cairo_rectangle_int_t.sizeof), cairo_rectangle_int_t.sizeof);
667 Cairo.cairo_rectangle(cairo, rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.height - 1);
668 }
669 Cairo.cairo_stroke(cairo);
670 if (rects[0] != 0) OS.g_free(rects[0]);
671 Cairo.cairo_region_destroy(rgn);
672 }
673 }
674 }
675 Cairo.cairo_restore(cairo);
676 }
677
freeRuns()678 void freeRuns() {
679 if (attrList == 0) return;
680 OS.pango_layout_set_attributes(layout, 0);
681 OS.pango_attr_list_unref(attrList);
682 attrList = 0;
683 if (selAttrList != 0) {
684 OS.pango_attr_list_unref(selAttrList);
685 selAttrList = 0;
686 }
687 invalidOffsets = null;
688 }
689
690 /**
691 * Returns the receiver's horizontal text alignment, which will be one
692 * of <code>SWT.LEFT</code>, <code>SWT.CENTER</code> or
693 * <code>SWT.RIGHT</code>.
694 *
695 * @return the alignment used to positioned text horizontally
696 *
697 * @exception SWTException <ul>
698 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
699 * </ul>
700 */
getAlignment()701 public int getAlignment() {
702 checkLayout();
703 int align = OS.pango_layout_get_alignment(layout);
704 boolean rtl = OS.pango_context_get_base_dir(context) == OS.PANGO_DIRECTION_RTL;
705 switch (align) {
706 case OS.PANGO_ALIGN_LEFT: return rtl ? SWT.RIGHT : SWT.LEFT;
707 case OS.PANGO_ALIGN_RIGHT: return rtl ? SWT.LEFT : SWT.RIGHT;
708 }
709 return SWT.CENTER;
710 }
711
712 /**
713 * Returns the ascent of the receiver.
714 *
715 * @return the ascent
716 *
717 * @exception SWTException <ul>
718 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
719 * </ul>
720 *
721 * @see #getDescent()
722 * @see #setDescent(int)
723 * @see #setAscent(int)
724 * @see #getLineMetrics(int)
725 */
getAscent()726 public int getAscent () {
727 checkLayout();
728 return ascentInPoints;
729 }
730
731 /**
732 * Returns the bounds of the receiver. The width returned is either the
733 * width of the longest line or the width set using {@link TextLayout#setWidth(int)}.
734 * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}.
735 *
736 * @return the bounds of the receiver
737 *
738 * @exception SWTException <ul>
739 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
740 * </ul>
741 *
742 * @see #setWidth(int)
743 * @see #getLineBounds(int)
744 */
getBounds()745 public Rectangle getBounds() {
746 int spacingInPixels = getSpacingInPixels();
747 return DPIUtil.autoScaleDown(getDevice(), getBoundsInPixels(spacingInPixels));
748 }
749
getBoundsInPixels(int spacingInPixels)750 Rectangle getBoundsInPixels(int spacingInPixels) {
751 checkLayout();
752 computeRuns();
753 int[] w = new int[1], h = new int[1];
754 OS.pango_layout_get_size(layout, w, h);
755 int wrapWidth = OS.pango_layout_get_width(layout);
756 w[0] = wrapWidth != -1 ? wrapWidth : w[0] + OS.pango_layout_get_indent(layout);
757 int width = OS.PANGO_PIXELS(w[0]);
758 int height = OS.PANGO_PIXELS(h[0]);
759 if (ascentInPoints != -1 && descentInPoints != -1) {
760 height = Math.max (height, DPIUtil.autoScaleUp(getDevice(), ascentInPoints + descentInPoints));
761 }
762 height += spacingInPixels;
763 return new Rectangle(0, 0, width, height + getScaledVerticalIndent());
764 }
765
766 /**
767 * Returns the bounds for the specified range of characters. The
768 * bounds is the smallest rectangle that encompasses all characters
769 * in the range. The start and end offsets are inclusive and will be
770 * clamped if out of range.
771 *
772 * @param start the start offset
773 * @param end the end offset
774 * @return the bounds of the character range
775 *
776 * @exception SWTException <ul>
777 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
778 * </ul>
779 */
getBounds(int start, int end)780 public Rectangle getBounds(int start, int end) {
781 checkLayout();
782 return DPIUtil.autoScaleDown(getDevice(), getBoundsInPixels(start, end));
783 }
784
getBoundsInPixels(int start, int end)785 Rectangle getBoundsInPixels(int start, int end) {
786 checkLayout();
787 computeRuns();
788 int length = text.length();
789 if (length == 0) return new Rectangle(0, 0, 0, 0);
790 if (start > end) return new Rectangle(0, 0, 0, 0);
791 start = Math.min(Math.max(0, start), length - 1);
792 end = Math.min(Math.max(0, end), length - 1);
793 start = translateOffset(start);
794 end = translateOffset(end);
795 long ptr = OS.pango_layout_get_text(layout);
796 int byteStart = (int)(OS.g_utf16_offset_to_pointer (ptr, start) - ptr);
797 int byteEnd = (int)(OS.g_utf16_offset_to_pointer (ptr, end + 1) - ptr);
798 int strlen = C.strlen(ptr);
799 byteStart = Math.min(byteStart, strlen);
800 byteEnd = Math.min(byteEnd, strlen);
801 int[] ranges = new int[]{byteStart, byteEnd};
802 long clipRegion = GDK.gdk_pango_layout_get_clip_region(layout, 0, 0, ranges, 1);
803 if (clipRegion == 0) return new Rectangle(0, 0, 0, 0);
804 cairo_rectangle_int_t rect = new cairo_rectangle_int_t();
805
806 /*
807 * Bug in Pango. The region returned by gdk_pango_layout_get_clip_region()
808 * includes areas from lines outside of the requested range. The fix
809 * is to subtract these areas from the clip region.
810 */
811 PangoRectangle pangoRect = new PangoRectangle();
812 long iter = OS.pango_layout_get_iter(layout);
813 if (iter == 0) SWT.error(SWT.ERROR_NO_HANDLES);
814 long linesRegion = Cairo.cairo_region_create();
815 if (linesRegion == 0) SWT.error(SWT.ERROR_NO_HANDLES);
816 int lineEnd = 0;
817 do {
818 OS.pango_layout_iter_get_line_extents(iter, null, pangoRect);
819 if (OS.pango_layout_iter_next_line(iter)) {
820 lineEnd = OS.pango_layout_iter_get_index(iter) - 1;
821 } else {
822 lineEnd = strlen;
823 }
824 if (byteStart > lineEnd) continue;
825 rect.x = OS.PANGO_PIXELS(pangoRect.x);
826 rect.y = OS.PANGO_PIXELS(pangoRect.y);
827 rect.width = OS.PANGO_PIXELS(pangoRect.width);
828 rect.height = OS.PANGO_PIXELS(pangoRect.height);
829 Cairo.cairo_region_union_rectangle(linesRegion, rect);
830 } while (lineEnd + 1 <= byteEnd);
831 Cairo.cairo_region_intersect(clipRegion, linesRegion);
832 Cairo.cairo_region_destroy(linesRegion);
833 OS.pango_layout_iter_free(iter);
834
835 Cairo.cairo_region_get_extents(clipRegion, rect);
836 Cairo.cairo_region_destroy(clipRegion);
837 rect.x += Math.min (indent, wrapIndent);
838 return new Rectangle(rect.x, rect.y, rect.width, rect.height + getScaledVerticalIndent());
839 }
840
841 /**
842 * Returns the descent of the receiver.
843 *
844 * @return the descent
845 *
846 * @exception SWTException <ul>
847 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
848 * </ul>
849 *
850 * @see #getAscent()
851 * @see #setAscent(int)
852 * @see #setDescent(int)
853 * @see #getLineMetrics(int)
854 */
getDescent()855 public int getDescent () {
856 checkLayout();
857 return descentInPoints;
858 }
859
860 /**
861 * Returns the default font currently being used by the receiver
862 * to draw and measure text.
863 *
864 * @return the receiver's font
865 *
866 * @exception SWTException <ul>
867 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
868 * </ul>
869 */
getFont()870 public Font getFont () {
871 checkLayout();
872 return font;
873 }
874
875 /**
876 * Returns the receiver's indent.
877 *
878 * @return the receiver's indent
879 *
880 * @exception SWTException <ul>
881 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
882 * </ul>
883 *
884 * @since 3.2
885 */
getIndent()886 public int getIndent () {
887 checkLayout();
888 return DPIUtil.autoScaleDown(getDevice(), getIndentInPixels());
889 }
890
getIndentInPixels()891 int getIndentInPixels () {
892 return indent;
893 }
894
895 /**
896 * Returns the receiver's justification.
897 *
898 * @return the receiver's justification
899 *
900 * @exception SWTException <ul>
901 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
902 * </ul>
903 *
904 * @since 3.2
905 */
getJustify()906 public boolean getJustify () {
907 checkLayout();
908 return OS.pango_layout_get_justify(layout);
909 }
910
911 /**
912 * Returns the embedding level for the specified character offset. The
913 * embedding level is usually used to determine the directionality of a
914 * character in bidirectional text.
915 *
916 * @param offset the character offset
917 * @return the embedding level
918 *
919 * @exception IllegalArgumentException <ul>
920 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
921 * </ul>
922 * @exception SWTException <ul>
923 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li></ul>
924 */
getLevel(int offset)925 public int getLevel(int offset) {
926 checkLayout();
927 computeRuns();
928 int length = text.length();
929 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
930 offset = translateOffset(offset);
931 long iter = OS.pango_layout_get_iter(layout);
932 if (iter == 0) SWT.error(SWT.ERROR_NO_HANDLES);
933 int level = 0;
934 PangoItem item = new PangoItem();
935 PangoLayoutRun run = new PangoLayoutRun();
936 long ptr = OS.pango_layout_get_text(layout);
937 long byteOffset = OS.g_utf16_offset_to_pointer(ptr, offset) - ptr;
938 int strlen = C.strlen(ptr);
939 byteOffset = Math.min(byteOffset, strlen);
940 do {
941 long runPtr = OS.pango_layout_iter_get_run(iter);
942 if (runPtr != 0) {
943 OS.memmove(run, runPtr, PangoLayoutRun.sizeof);
944 OS.memmove(item, run.item, PangoItem.sizeof);
945 if (item.offset <= byteOffset && byteOffset < item.offset + item.length) {
946 level = item.analysis_level;
947 break;
948 }
949 }
950 } while (OS.pango_layout_iter_next_run(iter));
951 OS.pango_layout_iter_free(iter);
952 return level;
953 }
954
955 /**
956 * Returns the bounds of the line for the specified line index.
957 *
958 * @param lineIndex the line index
959 * @return the line bounds
960 *
961 * @exception IllegalArgumentException <ul>
962 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
963 * </ul>
964 * @exception SWTException <ul>
965 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
966 * </ul>
967 */
getLineBounds(int lineIndex)968 public Rectangle getLineBounds(int lineIndex) {
969 checkLayout();
970 return DPIUtil.autoScaleDown(getDevice(), getLineBoundsInPixels(lineIndex));
971 }
972
getLineBoundsInPixels(int lineIndex)973 Rectangle getLineBoundsInPixels(int lineIndex) {
974 computeRuns();
975 int lineCount = OS.pango_layout_get_line_count(layout);
976 if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE);
977 long iter = OS.pango_layout_get_iter(layout);
978 for (int i = 0; i < lineIndex; i++) {
979 OS.pango_layout_iter_next_line(iter);
980 }
981 Rectangle lineBoundsInPixels = getLineBoundsInPixels(lineIndex, iter);
982 OS.pango_layout_iter_free(iter);
983 return lineBoundsInPixels;
984 }
985
getLineBoundsInPixels(int lineIndex, long iter)986 private Rectangle getLineBoundsInPixels(int lineIndex, long iter) {
987 if (iter == 0) SWT.error(SWT.ERROR_NO_HANDLES);
988 PangoRectangle rect = new PangoRectangle();
989 OS.pango_layout_iter_get_line_extents(iter, null, rect);
990 int x = OS.PANGO_PIXELS(rect.x);
991 int y = OS.PANGO_PIXELS(rect.y);
992 int width = OS.PANGO_PIXELS(rect.width);
993 int height = OS.PANGO_PIXELS(rect.height);
994 if (ascentInPoints != -1 && descentInPoints != -1) {
995 height = Math.max (height, DPIUtil.autoScaleUp(getDevice(), ascentInPoints + descentInPoints));
996 }
997 x += Math.min (indent, wrapIndent);
998 return new Rectangle(x, y, width, height);
999 }
1000
1001 /**
1002 * Returns the receiver's line count. This includes lines caused
1003 * by wrapping.
1004 *
1005 * @return the line count
1006 *
1007 * @exception SWTException <ul>
1008 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1009 * </ul>
1010 */
getLineCount()1011 public int getLineCount() {
1012 checkLayout ();
1013 computeRuns();
1014 return OS.pango_layout_get_line_count(layout);
1015 }
1016
1017 /**
1018 * Returns the index of the line that contains the specified
1019 * character offset.
1020 *
1021 * @param offset the character offset
1022 * @return the line index
1023 *
1024 * @exception IllegalArgumentException <ul>
1025 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
1026 * </ul>
1027 * @exception SWTException <ul>
1028 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1029 * </ul>
1030 */
getLineIndex(int offset)1031 public int getLineIndex(int offset) {
1032 checkLayout ();
1033 computeRuns();
1034 int length = text.length();
1035 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1036 offset = translateOffset(offset);
1037 int line = 0;
1038 long ptr = OS.pango_layout_get_text(layout);
1039 long byteOffset = OS.g_utf16_offset_to_pointer(ptr,offset) - ptr;
1040 int strlen = C.strlen(ptr);
1041 byteOffset = Math.min(byteOffset, strlen);
1042 long iter = OS.pango_layout_get_iter(layout);
1043 if (iter == 0) SWT.error(SWT.ERROR_NO_HANDLES);
1044 while (OS.pango_layout_iter_next_line(iter)) {
1045 if (OS.pango_layout_iter_get_index(iter) > byteOffset) break;
1046 line++;
1047 }
1048 OS.pango_layout_iter_free(iter);
1049 return line;
1050 }
1051
1052 /**
1053 * Returns the font metrics for the specified line index.
1054 *
1055 * @param lineIndex the line index
1056 * @return the font metrics
1057 *
1058 * @exception IllegalArgumentException <ul>
1059 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
1060 * </ul>
1061 * @exception SWTException <ul>
1062 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1063 * </ul>
1064 */
getLineMetrics(int lineIndex)1065 public FontMetrics getLineMetrics (int lineIndex) {
1066 checkLayout ();
1067 computeRuns();
1068 int lineCount = OS.pango_layout_get_line_count(layout);
1069 if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE);
1070 PangoLayoutLine line = new PangoLayoutLine();
1071 OS.memmove(line, OS.pango_layout_get_line(layout, lineIndex), PangoLayoutLine.sizeof);
1072 int heightInPoints;
1073 int ascentInPoints;
1074 if (line.runs == 0) {
1075 long font = this.font != null ? this.font.handle : device.systemFont.handle;
1076 long lang = OS.pango_context_get_language(context);
1077 long metrics = OS.pango_context_get_metrics(context, font, lang);
1078 int ascent = OS.pango_font_metrics_get_ascent(metrics);
1079 int descent = OS.pango_font_metrics_get_descent(metrics);
1080 ascentInPoints = DPIUtil.autoScaleDown(getDevice(), OS.PANGO_PIXELS(ascent));
1081 heightInPoints = DPIUtil.autoScaleDown(getDevice(), OS.PANGO_PIXELS(ascent + descent));
1082 OS.pango_font_metrics_unref(metrics);
1083 } else {
1084 PangoRectangle rect = new PangoRectangle();
1085 OS.pango_layout_line_get_extents(OS.pango_layout_get_line(layout, lineIndex), null, rect);
1086 ascentInPoints = DPIUtil.autoScaleDown(getDevice(), OS.PANGO_PIXELS(-rect.y));
1087 heightInPoints = DPIUtil.autoScaleDown(getDevice(), OS.PANGO_PIXELS(rect.height));
1088 }
1089 heightInPoints = Math.max(this.ascentInPoints + this.descentInPoints, heightInPoints);
1090 ascentInPoints = Math.max(this.ascentInPoints, ascentInPoints);
1091 int descentInPoints = heightInPoints - ascentInPoints;
1092 FontMetrics fm = new FontMetrics();
1093 fm.ascentInPoints = ascentInPoints;
1094 fm.descentInPoints = descentInPoints;
1095 fm.averageCharWidthInPoints = 0;
1096 return fm;
1097 }
1098
1099 /**
1100 * Returns the line offsets. Each value in the array is the
1101 * offset for the first character in a line except for the last
1102 * value, which contains the length of the text.
1103 *
1104 * @return the line offsets
1105 *
1106 * @exception SWTException <ul>
1107 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1108 * </ul>
1109 */
getLineOffsets()1110 public int[] getLineOffsets() {
1111 checkLayout();
1112 computeRuns();
1113 int lineCount = OS.pango_layout_get_line_count(layout);
1114 int[] offsets = new int [lineCount + 1];
1115 long ptr = OS.pango_layout_get_text(layout);
1116 PangoLayoutLine line = new PangoLayoutLine();
1117 for (int i = 0; i < lineCount; i++) {
1118 long linePtr = OS.pango_layout_get_line(layout, i);
1119 OS.memmove(line, linePtr, PangoLayoutLine.sizeof);
1120 int pos = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + line.start_index);
1121 offsets[i] = untranslateOffset(pos);
1122 }
1123 offsets[lineCount] = text.length();
1124 return offsets;
1125 }
1126
1127 /**
1128 * Returns the location for the specified character offset. The
1129 * <code>trailing</code> argument indicates whether the offset
1130 * corresponds to the leading or trailing edge of the cluster.
1131 *
1132 * @param offset the character offset
1133 * @param trailing the trailing flag
1134 * @return the location of the character offset
1135 *
1136 * @exception SWTException <ul>
1137 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1138 * </ul>
1139 *
1140 * @see #getOffset(Point, int[])
1141 * @see #getOffset(int, int, int[])
1142 */
getLocation(int offset, boolean trailing)1143 public Point getLocation(int offset, boolean trailing) {
1144 checkLayout();
1145 return DPIUtil.autoScaleDown(getDevice(), getLocationInPixels(offset, trailing));
1146 }
1147
getLocationInPixels(int offset, boolean trailing)1148 Point getLocationInPixels(int offset, boolean trailing) {
1149 computeRuns();
1150 int length = text.length();
1151 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1152 offset = translateOffset(offset);
1153 long ptr = OS.pango_layout_get_text(layout);
1154 int byteOffset = (int)(OS.g_utf16_offset_to_pointer(ptr, offset) - ptr);
1155 int strlen = C.strlen(ptr);
1156 byteOffset = Math.min(byteOffset, strlen);
1157 PangoRectangle pos = new PangoRectangle();
1158 OS.pango_layout_index_to_pos(layout, byteOffset, pos);
1159 int x = trailing ? pos.x + pos.width : pos.x;
1160 int y = pos.y;
1161 x = OS.PANGO_PIXELS(x);
1162 if (OS.pango_context_get_base_dir(context) == OS.PANGO_DIRECTION_RTL) {
1163 x = width() - x;
1164 }
1165 x += Math.min (indent, wrapIndent);
1166 return new Point(x, OS.PANGO_PIXELS(y) + getScaledVerticalIndent());
1167 }
1168
1169 /**
1170 * Returns the next offset for the specified offset and movement
1171 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
1172 * <code>SWT.MOVEMENT_CLUSTER</code>, <code>SWT.MOVEMENT_WORD</code>,
1173 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
1174 *
1175 * @param offset the start offset
1176 * @param movement the movement type
1177 * @return the next offset
1178 *
1179 * @exception IllegalArgumentException <ul>
1180 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
1181 * </ul>
1182 * @exception SWTException <ul>
1183 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1184 * </ul>
1185 *
1186 * @see #getPreviousOffset(int, int)
1187 */
getNextOffset(int offset, int movement)1188 public int getNextOffset (int offset, int movement) {
1189 return _getOffset(offset, movement, true);
1190 }
1191
_getOffset(int offset, int movement, boolean forward)1192 int _getOffset (int offset, int movement, boolean forward) {
1193 checkLayout();
1194 computeRuns();
1195 int length = text.length();
1196 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1197 if (forward) {
1198 if (offset == length) return length;
1199 } else {
1200 if (offset == 0) return 0;
1201 }
1202 int step = forward ? 1 : -1;
1203 if ((movement & SWT.MOVEMENT_CHAR) != 0) return offset + step;
1204 long [] attrs = new long [1];
1205 int[] nAttrs = new int[1];
1206 OS.pango_layout_get_log_attrs(layout, attrs, nAttrs);
1207 if (attrs[0] == 0) return offset + step;
1208
1209 long ptr = OS.pango_layout_get_text(layout);
1210 int utf8Offset = (int)OS.g_utf16_offset_to_utf8_offset (ptr, translateOffset(offset));
1211 int utf8Length = (int)OS.g_utf8_strlen(ptr, -1);
1212 utf8Offset += step;
1213 PangoLogAttr logAttr = new PangoLogAttr();
1214 while (0 <= utf8Offset && utf8Offset <= utf8Length) {
1215 OS.memmove(logAttr, attrs[0] + utf8Offset * PangoLogAttr.sizeof, PangoLogAttr.sizeof);
1216 boolean found = false, limit = false;
1217 if (((movement & SWT.MOVEMENT_CLUSTER) != 0) && logAttr.is_cursor_position) found = true;
1218 if ((movement & SWT.MOVEMENT_WORD) != 0) {
1219 if (forward) {
1220 if (logAttr.is_word_end) found = true;
1221 } else {
1222 if (logAttr.is_word_start) found = true;
1223 }
1224 }
1225 if ((movement & SWT.MOVEMENT_WORD_START) != 0) {
1226 if (logAttr.is_word_start) found = true;
1227 if (logAttr.is_sentence_end) found = true;
1228 }
1229 if ((movement & SWT.MOVEMENT_WORD_END) != 0) {
1230 if (logAttr.is_word_end) found = true;
1231 if (logAttr.is_sentence_start) found = true;
1232 }
1233 if (forward) {
1234 if (utf8Offset == utf8Length) limit = true;
1235 } else {
1236 if (utf8Offset == 0) limit = true;
1237 }
1238 if (found || limit) {
1239 int testOffset = (int)OS.g_utf8_offset_to_utf16_offset (ptr, utf8Offset);
1240 if (found && invalidOffsets != null) {
1241 for (int i = 0; i < invalidOffsets.length; i++) {
1242 if (testOffset == invalidOffsets[i]) {
1243 found = false;
1244 break;
1245 }
1246 }
1247 }
1248 if (found || limit) {
1249 offset = untranslateOffset(testOffset);
1250 break;
1251 }
1252 }
1253 utf8Offset += step;
1254 }
1255 OS.g_free(attrs[0]);
1256 return Math.min(Math.max(0, offset), length);
1257 }
1258
1259 /**
1260 * Returns the character offset for the specified point.
1261 * For a typical character, the trailing argument will be filled in to
1262 * indicate whether the point is closer to the leading edge (0) or
1263 * the trailing edge (1). When the point is over a cluster composed
1264 * of multiple characters, the trailing argument will be filled with the
1265 * position of the character in the cluster that is closest to
1266 * the point.
1267 *
1268 * @param point the point
1269 * @param trailing the trailing buffer
1270 * @return the character offset
1271 *
1272 * @exception IllegalArgumentException <ul>
1273 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
1274 * <li>ERROR_NULL_ARGUMENT - if the point is null</li>
1275 * </ul>
1276 * @exception SWTException <ul>
1277 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1278 * </ul>
1279 *
1280 * @see #getLocation(int, boolean)
1281 */
getOffset(Point point, int[] trailing)1282 public int getOffset(Point point, int[] trailing) {
1283 checkLayout();
1284 return getOffsetInPixels(DPIUtil.autoScaleUp(getDevice(), point), trailing);
1285 }
1286
getOffsetInPixels(Point point, int[] trailing)1287 int getOffsetInPixels(Point point, int[] trailing) {
1288 if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1289 return getOffsetInPixels(point.x, point.y, trailing);
1290 }
1291
1292 /**
1293 * Returns the character offset for the specified point.
1294 * For a typical character, the trailing argument will be filled in to
1295 * indicate whether the point is closer to the leading edge (0) or
1296 * the trailing edge (1). When the point is over a cluster composed
1297 * of multiple characters, the trailing argument will be filled with the
1298 * position of the character in the cluster that is closest to
1299 * the point.
1300 *
1301 * @param x the x coordinate of the point
1302 * @param y the y coordinate of the point
1303 * @param trailing the trailing buffer
1304 * @return the character offset
1305 *
1306 * @exception IllegalArgumentException <ul>
1307 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
1308 * </ul>
1309 * @exception SWTException <ul>
1310 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1311 * </ul>
1312 *
1313 * @see #getLocation(int, boolean)
1314 */
getOffset(int x, int y, int[] trailing)1315 public int getOffset(int x, int y, int[] trailing) {
1316 checkLayout();
1317 return getOffset(new Point(x, y), trailing);
1318 }
1319
getOffsetInPixels(int x, int y, int[] trailing)1320 int getOffsetInPixels(int x, int y, int[] trailing) {
1321 computeRuns();
1322 if (trailing != null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1323 x -= Math.min (indent, wrapIndent);
1324 if (OS.pango_context_get_base_dir(context) == OS.PANGO_DIRECTION_RTL) {
1325 x = width() - x;
1326 }
1327
1328 /*
1329 * Feature in GTK. pango_layout_xy_to_index() returns the
1330 * logical end/start offset of a line when the coordinates are outside
1331 * the line bounds. In SWT the correct behavior is to return the closest
1332 * visual offset. The fix is to clamp the coordinates inside the
1333 * line bounds.
1334 */
1335 long iter = OS.pango_layout_get_iter(layout);
1336 if (iter == 0) SWT.error(SWT.ERROR_NO_HANDLES);
1337 PangoRectangle rect = new PangoRectangle();
1338 do {
1339 OS.pango_layout_iter_get_line_extents(iter, null, rect);
1340 rect.y = OS.PANGO_PIXELS(rect.y);
1341 rect.height = OS.PANGO_PIXELS(rect.height);
1342 if (rect.y <= y && y < rect.y + rect.height) {
1343 rect.x = OS.PANGO_PIXELS(rect.x);
1344 rect.width = OS.PANGO_PIXELS(rect.width);
1345 if (x >= rect.x + rect.width) x = rect.x + rect.width - 1;
1346 if (x < rect.x) x = rect.x;
1347 break;
1348 }
1349 } while (OS.pango_layout_iter_next_line(iter));
1350 OS.pango_layout_iter_free(iter);
1351
1352 int[] index = new int[1];
1353 int[] piTrailing = new int[1];
1354 OS.pango_layout_xy_to_index(layout, x * OS.PANGO_SCALE, y * OS.PANGO_SCALE, index, piTrailing);
1355 long ptr = OS.pango_layout_get_text(layout);
1356 int offset = (int)OS.g_utf16_pointer_to_offset(ptr, ptr + index[0]);
1357 if (trailing != null) {
1358 trailing[0] = piTrailing[0];
1359 if (piTrailing[0] != 0) {
1360 trailing[0] = (int)OS.g_utf8_offset_to_utf16_offset(ptr, OS.g_utf8_pointer_to_offset(ptr, ptr + index[0]) + piTrailing[0]) - offset;
1361 }
1362 }
1363 return untranslateOffset(offset);
1364 }
1365
1366 /**
1367 * Returns the orientation of the receiver.
1368 *
1369 * @return the orientation style
1370 *
1371 * @exception SWTException <ul>
1372 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1373 * </ul>
1374 */
getOrientation()1375 public int getOrientation() {
1376 checkLayout();
1377 int baseDir = OS.pango_context_get_base_dir(context);
1378 return baseDir == OS.PANGO_DIRECTION_RTL ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
1379 }
1380
1381 /**
1382 * Returns the previous offset for the specified offset and movement
1383 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
1384 * <code>SWT.MOVEMENT_CLUSTER</code> or <code>SWT.MOVEMENT_WORD</code>,
1385 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
1386 *
1387 * @param offset the start offset
1388 * @param movement the movement type
1389 * @return the previous offset
1390 *
1391 * @exception IllegalArgumentException <ul>
1392 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
1393 * </ul>
1394 * @exception SWTException <ul>
1395 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1396 * </ul>
1397 *
1398 * @see #getNextOffset(int, int)
1399 */
getPreviousOffset(int offset, int movement)1400 public int getPreviousOffset (int offset, int movement) {
1401 return _getOffset(offset, movement, false);
1402 }
1403
1404 /**
1405 * Gets the ranges of text that are associated with a <code>TextStyle</code>.
1406 *
1407 * @return the ranges, an array of offsets representing the start and end of each
1408 * text style.
1409 *
1410 * @exception SWTException <ul>
1411 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1412 * </ul>
1413 *
1414 * @see #getStyles()
1415 *
1416 * @since 3.2
1417 */
getRanges()1418 public int[] getRanges () {
1419 checkLayout();
1420 int[] result = new int[stylesCount * 2];
1421 int count = 0;
1422 for (int i=0; i<stylesCount - 1; i++) {
1423 if (styles[i].style != null) {
1424 result[count++] = styles[i].start;
1425 result[count++] = styles[i + 1].start - 1;
1426 }
1427 }
1428 if (count != result.length) {
1429 int[] newResult = new int[count];
1430 System.arraycopy(result, 0, newResult, 0, count);
1431 result = newResult;
1432 }
1433 return result;
1434 }
1435
1436 /**
1437 * Returns the text segments offsets of the receiver.
1438 *
1439 * @return the text segments offsets
1440 *
1441 * @exception SWTException <ul>
1442 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1443 * </ul>
1444 */
getSegments()1445 public int[] getSegments() {
1446 checkLayout();
1447 return segments;
1448 }
1449
1450 /**
1451 * Returns the segments characters of the receiver.
1452 *
1453 * @return the segments characters
1454 *
1455 * @exception SWTException <ul>
1456 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1457 * </ul>
1458 *
1459 * @since 3.6
1460 */
getSegmentsChars()1461 public char[] getSegmentsChars () {
1462 checkLayout();
1463 return segmentsChars;
1464 }
1465
getSegmentsText()1466 String getSegmentsText() {
1467 int length = text.length();
1468 if (length == 0) return text;
1469 if (segments == null) return text;
1470 int nSegments = segments.length;
1471 if (nSegments == 0) return text;
1472 if (segmentsChars == null) {
1473 if (nSegments == 1) return text;
1474 if (nSegments == 2) {
1475 if (segments[0] == 0 && segments[1] == length) return text;
1476 }
1477 }
1478 char[] oldChars = new char[length];
1479 text.getChars(0, length, oldChars, 0);
1480 char[] newChars = new char[length + nSegments];
1481 int charCount = 0, segmentCount = 0;
1482 char defaultSeparator = getOrientation() == SWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK;
1483 while (charCount < length) {
1484 if (segmentCount < nSegments && charCount == segments[segmentCount]) {
1485 char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
1486 newChars[charCount + segmentCount++] = separator;
1487 } else {
1488 newChars[charCount + segmentCount] = oldChars[charCount++];
1489 }
1490 }
1491 while (segmentCount < nSegments) {
1492 segments[segmentCount] = charCount;
1493 char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
1494 newChars[charCount + segmentCount++] = separator;
1495 }
1496 return new String(newChars, 0, newChars.length);
1497 }
1498
1499 /**
1500 * Returns the line spacing of the receiver.
1501 *
1502 * @return the line spacing
1503 *
1504 * @exception SWTException <ul>
1505 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1506 * </ul>
1507 */
getSpacing()1508 public int getSpacing () {
1509 checkLayout();
1510 return DPIUtil.autoScaleDown(getDevice(), getSpacingInPixels());
1511 }
1512
getSpacingInPixels()1513 int getSpacingInPixels () {
1514 return OS.PANGO_PIXELS(OS.pango_layout_get_spacing(layout));
1515 }
1516
1517 /**
1518 * Returns the vertical indent of the receiver.
1519 *
1520 * @return the vertical indent
1521 *
1522 * @exception SWTException <ul>
1523 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1524 * </ul>
1525 * @since 3.109
1526 */
getVerticalIndent()1527 public int getVerticalIndent () {
1528 checkLayout();
1529 return verticalIndentInPoints;
1530 }
1531
1532 /**
1533 * Returns the scaled vertical indent.
1534 *
1535 * @return the scaled vertical indent.
1536 * @since 3.109
1537 */
getScaledVerticalIndent()1538 private int getScaledVerticalIndent() {
1539 if (verticalIndentInPoints == 0) {
1540 return verticalIndentInPoints;
1541 }
1542 return DPIUtil.autoScaleUp(getDevice(), verticalIndentInPoints);
1543 }
1544
1545 /**
1546 * Gets the style of the receiver at the specified character offset.
1547 *
1548 * @param offset the text offset
1549 * @return the style or <code>null</code> if not set
1550 *
1551 * @exception IllegalArgumentException <ul>
1552 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
1553 * </ul>
1554 * @exception SWTException <ul>
1555 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1556 * </ul>
1557 */
getStyle(int offset)1558 public TextStyle getStyle (int offset) {
1559 checkLayout();
1560 int length = text.length();
1561 if (!(0 <= offset && offset < length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1562 for (int i=1; i<stylesCount; i++) {
1563 StyleItem item = styles[i];
1564 if (item.start > offset) {
1565 return styles[i - 1].style;
1566 }
1567 }
1568 return null;
1569 }
1570
1571 /**
1572 * Gets all styles of the receiver.
1573 *
1574 * @return the styles
1575 *
1576 * @exception SWTException <ul>
1577 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1578 * </ul>
1579 *
1580 * @see #getRanges()
1581 *
1582 * @since 3.2
1583 */
getStyles()1584 public TextStyle[] getStyles () {
1585 checkLayout();
1586 TextStyle[] result = new TextStyle[stylesCount];
1587 int count = 0;
1588 for (int i=0; i<stylesCount; i++) {
1589 if (styles[i].style != null) {
1590 result[count++] = styles[i].style;
1591 }
1592 }
1593 if (count != result.length) {
1594 TextStyle[] newResult = new TextStyle[count];
1595 System.arraycopy(result, 0, newResult, 0, count);
1596 result = newResult;
1597 }
1598 return result;
1599 }
1600
1601 /**
1602 * Returns the tab list of the receiver.
1603 *
1604 * @return the tab list
1605 *
1606 * @exception SWTException <ul>
1607 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1608 * </ul>
1609 */
getTabs()1610 public int[] getTabs() {
1611 checkLayout();
1612 return DPIUtil.autoScaleDown (getDevice(), getTabsInPixels ());
1613 }
1614
getTabsInPixels()1615 int[] getTabsInPixels () {
1616 return tabs;
1617 }
1618
1619 /**
1620 * Gets the receiver's text, which will be an empty
1621 * string if it has never been set.
1622 *
1623 * @return the receiver's text
1624 *
1625 * @exception SWTException <ul>
1626 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1627 * </ul>
1628 */
getText()1629 public String getText () {
1630 checkLayout ();
1631 return text;
1632 }
1633
1634 /**
1635 * Returns the text direction of the receiver.
1636 *
1637 * @return the text direction value
1638 *
1639 * @exception SWTException <ul>
1640 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1641 * </ul>
1642 * @since 3.103
1643 */
getTextDirection()1644 public int getTextDirection () {
1645 return getOrientation ();
1646 }
1647
1648 /**
1649 * Returns the width of the receiver.
1650 *
1651 * @return the width
1652 *
1653 * @exception SWTException <ul>
1654 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1655 * </ul>
1656 */
getWidth()1657 public int getWidth () {
1658 checkLayout ();
1659 return DPIUtil.autoScaleDown(getDevice(), getWidthInPixels());
1660 }
1661
getWidthInPixels()1662 int getWidthInPixels () {
1663 return wrapWidth;
1664 }
1665
1666 /**
1667 * Returns the receiver's wrap indent.
1668 *
1669 * @return the receiver's wrap indent
1670 *
1671 * @exception SWTException <ul>
1672 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1673 * </ul>
1674 *
1675 * @since 3.6
1676 */
getWrapIndent()1677 public int getWrapIndent () {
1678 checkLayout ();
1679 return DPIUtil.autoScaleDown(getDevice(), getWrapIndentInPixels());
1680 }
getWrapIndentInPixels()1681 int getWrapIndentInPixels () {
1682 return wrapIndent;
1683 }
1684
1685 /**
1686 * Returns <code>true</code> if the text layout has been disposed,
1687 * and <code>false</code> otherwise.
1688 * <p>
1689 * This method gets the dispose state for the text layout.
1690 * When a text layout has been disposed, it is an error to
1691 * invoke any other method (except {@link #dispose()}) using the text layout.
1692 * </p>
1693 *
1694 * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise
1695 */
1696 @Override
isDisposed()1697 public boolean isDisposed () {
1698 return layout == 0;
1699 }
1700
1701 /**
1702 * Sets the text alignment for the receiver. The alignment controls
1703 * how a line of text is positioned horizontally. The argument should
1704 * be one of <code>SWT.LEFT</code>, <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>.
1705 * <p>
1706 * The default alignment is <code>SWT.LEFT</code>. Note that the receiver's
1707 * width must be set in order to use <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>
1708 * alignment.
1709 * </p>
1710 *
1711 * @param alignment the new alignment
1712 *
1713 * @exception SWTException <ul>
1714 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1715 * </ul>
1716 *
1717 * @see #setWidth(int)
1718 */
setAlignment(int alignment)1719 public void setAlignment (int alignment) {
1720 checkLayout();
1721 int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT;
1722 alignment &= mask;
1723 if (alignment == 0) return;
1724 if ((alignment & SWT.LEFT) != 0) alignment = SWT.LEFT;
1725 if ((alignment & SWT.RIGHT) != 0) alignment = SWT.RIGHT;
1726 boolean rtl = OS.pango_context_get_base_dir(context) == OS.PANGO_DIRECTION_RTL;
1727 int align = OS.PANGO_ALIGN_CENTER;
1728 switch (alignment) {
1729 case SWT.LEFT:
1730 align = rtl ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT;
1731 break;
1732 case SWT.RIGHT:
1733 align = rtl ? OS.PANGO_ALIGN_LEFT : OS.PANGO_ALIGN_RIGHT;
1734 break;
1735 }
1736 OS.pango_layout_set_alignment(layout, align);
1737 }
1738
1739 /**
1740 * Sets the ascent of the receiver. The ascent is distance in points
1741 * from the baseline to the top of the line and it is applied to all
1742 * lines. The default value is <code>-1</code> which means that the
1743 * ascent is calculated from the line fonts.
1744 *
1745 * @param ascent the new ascent
1746 *
1747 * @exception IllegalArgumentException <ul>
1748 * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li>
1749 * </ul>
1750 * @exception SWTException <ul>
1751 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1752 * </ul>
1753 *
1754 * @see #setDescent(int)
1755 * @see #getLineMetrics(int)
1756 */
setAscent(int ascent)1757 public void setAscent (int ascent) {
1758 checkLayout();
1759 if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1760 if (this.ascentInPoints == ascent) return;
1761 freeRuns();
1762 this.ascentInPoints = ascent;
1763 }
1764
1765 /**
1766 * Sets the descent of the receiver. The descent is distance in points
1767 * from the baseline to the bottom of the line and it is applied to all
1768 * lines. The default value is <code>-1</code> which means that the
1769 * descent is calculated from the line fonts.
1770 *
1771 * @param descent the new descent
1772 *
1773 * @exception IllegalArgumentException <ul>
1774 * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li>
1775 * </ul>
1776 * @exception SWTException <ul>
1777 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1778 * </ul>
1779 *
1780 * @see #setAscent(int)
1781 * @see #getLineMetrics(int)
1782 */
setDescent(int descent)1783 public void setDescent (int descent) {
1784 checkLayout();
1785 if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1786 if (this.descentInPoints == descent) return;
1787 freeRuns();
1788 this.descentInPoints = descent;
1789 }
1790
1791 /**
1792 * Sets the default font which will be used by the receiver
1793 * to draw and measure text. If the
1794 * argument is null, then a default font appropriate
1795 * for the platform will be used instead. Note that a text
1796 * style can override the default font.
1797 *
1798 * @param font the new font for the receiver, or null to indicate a default font
1799 *
1800 * @exception IllegalArgumentException <ul>
1801 * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li>
1802 * </ul>
1803 * @exception SWTException <ul>
1804 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1805 * </ul>
1806 */
setFont(Font font)1807 public void setFont (Font font) {
1808 checkLayout ();
1809 if (font != null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1810 Font oldFont = this.font;
1811 if (oldFont == font) return;
1812 freeRuns();
1813 this.font = font;
1814 if (oldFont != null && oldFont.equals(font)) return;
1815 OS.pango_layout_set_font_description(layout, font != null ? font.handle : device.systemFont.handle);
1816 }
1817
1818 /**
1819 * Sets the indent of the receiver. This indent is applied to the first line of
1820 * each paragraph.
1821 *
1822 * @param indent new indent
1823 *
1824 * @exception SWTException <ul>
1825 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1826 * </ul>
1827 *
1828 * @see #setWrapIndent(int)
1829 *
1830 * @since 3.2
1831 */
setIndent(int indent)1832 public void setIndent (int indent) {
1833 checkLayout ();
1834 setIndentInPixels(DPIUtil.autoScaleUp(getDevice(), indent));
1835 }
1836
setIndentInPixels(int indent)1837 void setIndentInPixels (int indent) {
1838 checkLayout();
1839 if (indent < 0) return;
1840 if (this.indent == indent) return;
1841 this.indent = indent;
1842 OS.pango_layout_set_indent(layout, (indent - wrapIndent) * OS.PANGO_SCALE);
1843 if (wrapWidth != -1) setWidth();
1844 }
1845
1846 /**
1847 * Sets the justification of the receiver. Note that the receiver's
1848 * width must be set in order to use justification.
1849 *
1850 * @param justify new justify
1851 *
1852 * @exception SWTException <ul>
1853 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1854 * </ul>
1855 *
1856 * @since 3.2
1857 */
setJustify(boolean justify)1858 public void setJustify (boolean justify) {
1859 checkLayout();
1860 OS.pango_layout_set_justify(layout, justify);
1861 }
1862
1863 /**
1864 * Sets the orientation of the receiver, which must be one
1865 * of <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
1866 *
1867 * @param orientation new orientation style
1868 *
1869 * @exception SWTException <ul>
1870 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1871 * </ul>
1872 */
setOrientation(int orientation)1873 public void setOrientation(int orientation) {
1874 checkLayout();
1875 int mask = SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT;
1876 orientation &= mask;
1877 if (orientation == 0) return;
1878 if ((orientation & SWT.LEFT_TO_RIGHT) != 0) orientation = SWT.LEFT_TO_RIGHT;
1879 int baseDir = orientation == SWT.RIGHT_TO_LEFT ? OS.PANGO_DIRECTION_RTL : OS.PANGO_DIRECTION_LTR;
1880 if (OS.pango_context_get_base_dir(context) == baseDir) return;
1881 freeRuns();
1882 OS.pango_context_set_base_dir(context, baseDir);
1883 OS.pango_layout_context_changed(layout);
1884 int align = OS.pango_layout_get_alignment(layout);
1885 if (align != OS.PANGO_ALIGN_CENTER) {
1886 align = align == OS.PANGO_ALIGN_LEFT ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT;
1887 OS.pango_layout_set_alignment(layout, align);
1888 }
1889 }
1890
1891 /**
1892 * Sets the line spacing of the receiver. The line spacing
1893 * is the space left between lines.
1894 *
1895 * @param spacing the new line spacing
1896 *
1897 * @exception IllegalArgumentException <ul>
1898 * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li>
1899 * </ul>
1900 * @exception SWTException <ul>
1901 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1902 * </ul>
1903 */
setSpacing(int spacing)1904 public void setSpacing (int spacing) {
1905 checkLayout();
1906 if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1907 setSpacingInPixels(DPIUtil.autoScaleUp(getDevice(), spacing));
1908 }
1909
setSpacingInPixels(int spacing)1910 void setSpacingInPixels (int spacing) {
1911 OS.pango_layout_set_spacing(layout, spacing * OS.PANGO_SCALE);
1912 }
1913
1914 /**
1915 * Sets the vertical indent of the receiver. The vertical indent
1916 * is the space left before the first line.
1917 *
1918 * @param verticalIndent the new vertical indent
1919 *
1920 * @exception IllegalArgumentException <ul>
1921 * <li>ERROR_INVALID_ARGUMENT - if the vertical indent is negative</li>
1922 * </ul>
1923 * @exception SWTException <ul>
1924 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1925 * </ul>
1926 * @since 3.109
1927 */
setVerticalIndent(int verticalIndent)1928 public void setVerticalIndent (int verticalIndent) {
1929 checkLayout();
1930 if (verticalIndent < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1931 if (this.verticalIndentInPoints == verticalIndent) return;
1932 this.verticalIndentInPoints = verticalIndent;
1933 }
1934
1935 /**
1936 * Sets the offsets of the receiver's text segments. Text segments are used to
1937 * override the default behavior of the bidirectional algorithm.
1938 * Bidirectional reordering can happen within a text segment but not
1939 * between two adjacent segments.
1940 * <p>
1941 * Each text segment is determined by two consecutive offsets in the
1942 * <code>segments</code> arrays. The first element of the array should
1943 * always be zero and the last one should always be equals to length of
1944 * the text.
1945 * </p>
1946 * <p>
1947 * When segments characters are set, the segments are the offsets where
1948 * the characters are inserted in the text.
1949 * <p>
1950 *
1951 * @param segments the text segments offset
1952 *
1953 * @exception SWTException <ul>
1954 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1955 * </ul>
1956 *
1957 * @see #setSegmentsChars(char[])
1958 */
setSegments(int[] segments)1959 public void setSegments(int[] segments) {
1960 checkLayout();
1961 if (this.segments == null && segments == null) return;
1962 if (this.segments != null && segments !=null) {
1963 if (this.segments.length == segments.length) {
1964 int i;
1965 for (i = 0; i <segments.length; i++) {
1966 if (this.segments[i] != segments[i]) break;
1967 }
1968 if (i == segments.length) return;
1969 }
1970 }
1971 freeRuns();
1972 this.segments = segments;
1973 }
1974
1975 /**
1976 * Sets the characters to be used in the segments boundaries. The segments
1977 * are set by calling <code>setSegments(int[])</code>. The application can
1978 * use this API to insert Unicode Control Characters in the text to control
1979 * the display of the text and bidi reordering. The characters are not
1980 * accessible by any other API in <code>TextLayout</code>.
1981 *
1982 * @param segmentsChars the segments characters
1983 *
1984 * @exception SWTException <ul>
1985 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1986 * </ul>
1987 *
1988 * @see #setSegments(int[])
1989 *
1990 * @since 3.6
1991 */
setSegmentsChars(char[] segmentsChars)1992 public void setSegmentsChars(char[] segmentsChars) {
1993 checkLayout();
1994 if (this.segmentsChars == null && segmentsChars == null) return;
1995 if (this.segmentsChars != null && segmentsChars != null) {
1996 if (this.segmentsChars.length == segmentsChars.length) {
1997 int i;
1998 for (i = 0; i <segmentsChars.length; i++) {
1999 if (this.segmentsChars[i] != segmentsChars[i]) break;
2000 }
2001 if (i == segmentsChars.length) return;
2002 }
2003 }
2004 freeRuns();
2005 this.segmentsChars = segmentsChars;
2006 }
2007
2008 /**
2009 * Sets the style of the receiver for the specified range. Styles previously
2010 * set for that range will be overwritten. The start and end offsets are
2011 * inclusive and will be clamped if out of range.
2012 *
2013 * @param style the style
2014 * @param start the start offset
2015 * @param end the end offset
2016 *
2017 * @exception SWTException <ul>
2018 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2019 * </ul>
2020 */
setStyle(TextStyle style, int start, int end)2021 public void setStyle (TextStyle style, int start, int end) {
2022 checkLayout();
2023 int length = text.length();
2024 if (length == 0) return;
2025 if (start > end) return;
2026 start = Math.min(Math.max(0, start), length - 1);
2027 end = Math.min(Math.max(0, end), length - 1);
2028
2029 /*
2030 * Bug in Pango. Pango 1.2.2 will cause a segmentation fault if a style
2031 * is not applied for a whole ligature. The fix is to applied the
2032 * style for the whole ligature.
2033 *
2034 * NOTE that fix only LamAlef ligatures.
2035 */
2036 if (start > 0 && isAlef(text.charAt(start)) && isLam(text.charAt(start - 1))) {
2037 start--;
2038 }
2039 if (end < length - 1 && isLam(text.charAt(end)) && isAlef(text.charAt(end + 1))) {
2040 end++;
2041 }
2042
2043 int low = -1;
2044 int high = stylesCount;
2045 while (high - low > 1) {
2046 int index = (high + low) / 2;
2047 if (styles[index + 1].start > start) {
2048 high = index;
2049 } else {
2050 low = index;
2051 }
2052 }
2053 if (0 <= high && high < stylesCount) {
2054 StyleItem item = styles[high];
2055 if (item.start == start && styles[high + 1].start - 1 == end) {
2056 if (style == null) {
2057 if (item.style == null) return;
2058 } else {
2059 if (style.equals(item.style)) return;
2060 }
2061 }
2062 }
2063 freeRuns();
2064 int modifyStart = high;
2065 int modifyEnd = modifyStart;
2066 while (modifyEnd < stylesCount) {
2067 if (styles[modifyEnd + 1].start > end) break;
2068 modifyEnd++;
2069 }
2070 if (modifyStart == modifyEnd) {
2071 int styleStart = styles[modifyStart].start;
2072 int styleEnd = styles[modifyEnd + 1].start - 1;
2073 if (styleStart == start && styleEnd == end) {
2074 styles[modifyStart].style = style;
2075 return;
2076 }
2077 if (styleStart != start && styleEnd != end) {
2078 int newLength = stylesCount + 2;
2079 if (newLength > styles.length) {
2080 int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
2081 StyleItem[] newStyles = new StyleItem[newSize];
2082 System.arraycopy(styles, 0, newStyles, 0, stylesCount);
2083 styles = newStyles;
2084 }
2085 System.arraycopy(styles, modifyEnd + 1, styles, modifyEnd + 3, stylesCount - modifyEnd - 1);
2086 StyleItem item = new StyleItem();
2087 item.start = start;
2088 item.style = style;
2089 styles[modifyStart + 1] = item;
2090 item = new StyleItem();
2091 item.start = end + 1;
2092 item.style = styles[modifyStart].style;
2093 styles[modifyStart + 2] = item;
2094 stylesCount = newLength;
2095 return;
2096 }
2097 }
2098 if (start == styles[modifyStart].start) modifyStart--;
2099 if (end == styles[modifyEnd + 1].start - 1) modifyEnd++;
2100 int newLength = stylesCount + 1 - (modifyEnd - modifyStart - 1);
2101 if (newLength > styles.length) {
2102 int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
2103 StyleItem[] newStyles = new StyleItem[newSize];
2104 System.arraycopy(styles, 0, newStyles, 0, stylesCount);
2105 styles = newStyles;
2106 }
2107 System.arraycopy(styles, modifyEnd, styles, modifyStart + 2, stylesCount - modifyEnd);
2108 StyleItem item = new StyleItem();
2109 item.start = start;
2110 item.style = style;
2111 styles[modifyStart + 1] = item;
2112 styles[modifyStart + 2].start = end + 1;
2113 stylesCount = newLength;
2114 }
2115
2116 /**
2117 * Sets the receiver's tab list. Each value in the tab list specifies
2118 * the space in points from the origin of the text layout to the respective
2119 * tab stop. The last tab stop width is repeated continuously.
2120 *
2121 * @param tabs the new tab list
2122 *
2123 * @exception SWTException <ul>
2124 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2125 * </ul>
2126 */
setTabs(int[] tabs)2127 public void setTabs(int[] tabs) {
2128 checkLayout();
2129 if (this.tabs == null && tabs == null) return;
2130 setTabsInPixels (DPIUtil.autoScaleUp (getDevice(), tabs));
2131 }
2132
setTabsInPixels(int[] tabs)2133 void setTabsInPixels (int[] tabs) {
2134 if (Arrays.equals (this.tabs, tabs)) return;
2135 this.tabs = tabs;
2136 if (tabs == null) {
2137 OS.pango_layout_set_tabs(layout, device.emptyTab);
2138 } else {
2139 long tabArray = OS.pango_tab_array_new(tabs.length, true);
2140 if (tabArray != 0) {
2141 for (int i = 0; i < tabs.length; i++) {
2142 OS.pango_tab_array_set_tab(tabArray, i, OS.PANGO_TAB_LEFT, tabs[i]);
2143 }
2144 OS.pango_layout_set_tabs(layout, tabArray);
2145 OS.pango_tab_array_free(tabArray);
2146 }
2147 }
2148 /*
2149 * Bug in Pango. A change in the tab stop array is not automatically reflected in the
2150 * pango layout object because the call pango_layout_set_tabs() does not free the
2151 * lines cache. The fix to use pango_layout_context_changed() to free the lines cache.
2152 */
2153 OS.pango_layout_context_changed(layout);
2154 }
2155
2156 /**
2157 * Sets the receiver's text.
2158 *<p>
2159 * Note: Setting the text also clears all the styles. This method
2160 * returns without doing anything if the new text is the same as
2161 * the current text.
2162 * </p>
2163 *
2164 * @param text the new text
2165 *
2166 * @exception IllegalArgumentException <ul>
2167 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
2168 * </ul>
2169 * @exception SWTException <ul>
2170 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2171 * </ul>
2172 */
setText(String text)2173 public void setText (String text) {
2174 checkLayout ();
2175 if (text == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
2176 if (text.equals(this.text)) return;
2177 freeRuns();
2178 this.text = text;
2179 styles = new StyleItem[2];
2180 styles[0] = new StyleItem();
2181 styles[1] = new StyleItem();
2182 styles[1].start = text.length();
2183 stylesCount = 2;
2184 }
2185
2186 /**
2187 * Sets the text direction of the receiver, which must be one
2188 * of <code>SWT.LEFT_TO_RIGHT</code>, <code>SWT.RIGHT_TO_LEFT</code>
2189 * or <code>SWT.AUTO_TEXT_DIRECTION</code>.
2190 *
2191 * <p>
2192 * <b>Warning</b>: This API is currently only implemented on Windows.
2193 * It doesn't set the base text direction on GTK and Cocoa.
2194 * </p>
2195 *
2196 * @param textDirection the new text direction
2197 *
2198 * @exception SWTException <ul>
2199 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2200 * </ul>
2201 * @since 3.103
2202 */
setTextDirection(int textDirection)2203 public void setTextDirection (int textDirection) {
2204 checkLayout();
2205 }
2206
2207 /**
2208 * Sets the line width of the receiver, which determines how
2209 * text should be wrapped and aligned. The default value is
2210 * <code>-1</code> which means wrapping is disabled.
2211 *
2212 * @param width the new width
2213 *
2214 * @exception IllegalArgumentException <ul>
2215 * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li>
2216 * </ul>
2217 * @exception SWTException <ul>
2218 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2219 * </ul>
2220 *
2221 * @see #setAlignment(int)
2222 */
setWidth(int width)2223 public void setWidth (int width) {
2224 checkLayout ();
2225 if (width < -1 || width == 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2226 setWidthInPixels(DPIUtil.autoScaleUp(getDevice(), width));
2227 }
2228
setWidthInPixels(int width)2229 void setWidthInPixels (int width) {
2230 if (wrapWidth == width) return;
2231 freeRuns();
2232 wrapWidth = width;
2233 setWidth();
2234 }
2235
setWidth()2236 void setWidth () {
2237 if (wrapWidth == -1) {
2238 OS.pango_layout_set_width(layout, -1);
2239 boolean rtl = OS.pango_context_get_base_dir(context) == OS.PANGO_DIRECTION_RTL;
2240 OS.pango_layout_set_alignment(layout, rtl ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT);
2241 } else {
2242 int margin = Math.min (indent, wrapIndent);
2243 OS.pango_layout_set_width(layout, (wrapWidth - margin) * OS.PANGO_SCALE);
2244 }
2245 }
2246
2247 /**
2248 * Sets the wrap indent of the receiver. This indent is applied to all lines
2249 * in the paragraph except the first line.
2250 *
2251 * @param wrapIndent new wrap indent
2252 *
2253 * @exception SWTException <ul>
2254 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2255 * </ul>
2256 *
2257 * @see #setIndent(int)
2258 *
2259 * @since 3.6
2260 */
setWrapIndent(int wrapIndent)2261 public void setWrapIndent (int wrapIndent) {
2262 checkLayout();
2263 if (wrapIndent < 0) return;
2264 setWrapIndentInPixels(DPIUtil.autoScaleUp(getDevice(), wrapIndent));
2265 }
2266
setWrapIndentInPixels(int wrapIndent)2267 void setWrapIndentInPixels (int wrapIndent) {
2268 if (this.wrapIndent == wrapIndent) return;
2269 this.wrapIndent = wrapIndent;
2270 OS.pango_layout_set_indent(layout, (indent - wrapIndent) * OS.PANGO_SCALE);
2271 if (wrapWidth != -1) setWidth();
2272 }
2273
isLam(int ch)2274 static final boolean isLam(int ch) {
2275 return ch == 0x0644;
2276 }
2277
isAlef(int ch)2278 static final boolean isAlef(int ch) {
2279 switch (ch) {
2280 case 0x0622:
2281 case 0x0623:
2282 case 0x0625:
2283 case 0x0627:
2284 case 0x0649:
2285 case 0x0670:
2286 case 0x0671:
2287 case 0x0672:
2288 case 0x0673:
2289 case 0x0675:
2290 return true;
2291 }
2292 return false;
2293 }
2294
2295 /**
2296 * Returns a string containing a concise, human-readable
2297 * description of the receiver.
2298 *
2299 * @return a string representation of the receiver
2300 */
2301 @Override
toString()2302 public String toString () {
2303 if (isDisposed()) return "TextLayout {*DISPOSED*}";
2304 return "TextLayout {" + layout + "}";
2305 }
2306
2307 /*
2308 * Translate a client offset to an internal offset
2309 */
translateOffset(int offset)2310 int translateOffset(int offset) {
2311 int length = text.length();
2312 if (length == 0) return offset;
2313 if (invalidOffsets == null) return offset;
2314 for (int i = 0; i < invalidOffsets.length; i++) {
2315 if (offset < invalidOffsets[i]) break;
2316 offset++;
2317 }
2318 return offset;
2319 }
2320
2321 /*
2322 * Translate an internal offset to a client offset
2323 */
untranslateOffset(int offset)2324 int untranslateOffset(int offset) {
2325 int length = text.length();
2326 if (length == 0) return offset;
2327 if (invalidOffsets == null) return offset;
2328 int i = 0;
2329 while (i < invalidOffsets.length && offset > invalidOffsets[i]) {
2330 i++;
2331 }
2332 return offset - i;
2333 }
2334
width()2335 int width () {
2336 int wrapWidth = OS.pango_layout_get_width(layout);
2337 if (wrapWidth != -1) return OS.PANGO_PIXELS(wrapWidth);
2338 int[] w = new int[1], h = new int[1];
2339 OS.pango_layout_get_pixel_size(layout, w, h);
2340 return w[0];
2341 }
2342
2343 /**
2344 * Sets Default Tab Width in terms if number of space characters.
2345 *
2346 * @param tabLength in number of characters
2347 *
2348 * @exception IllegalArgumentException <ul>
2349 * <li>ERROR_INVALID_ARGUMENT - if the tabLength is less than <code>0</code></li>
2350 * </ul>
2351 * @exception SWTException <ul>
2352 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2353 * </ul>
2354 *
2355 * @noreference This method is not intended to be referenced by clients.
2356 *
2357 * DO NOT USE This might be removed in 4.8
2358 * @since 3.107
2359 */
setDefaultTabWidth(int tabLength)2360 public void setDefaultTabWidth(int tabLength) {
2361 //unused in GTK
2362 }
2363
2364 }
2365