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