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