diff org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/graphics/TextLayout.d @ 0:6dd524f61e62

add dwt win and basic java stuff
author Frank Benoit <benoit@tionex.de>
date Mon, 02 Mar 2009 14:44:16 +0100
parents
children 6bf2837c50fe
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/graphics/TextLayout.d	Mon Mar 02 14:44:16 2009 +0100
@@ -0,0 +1,3170 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module org.eclipse.swt.graphics.TextLayout;
+
+/++
+ + SWT Changes to make the port work.
+ + The USER API is utf8, the Windows API is utf16. In addition to the fields 'text' and 'segmentsText' fields are added to help.
+ + wtext: same as text but utf16
+ + segmentsWText: same as segmentsText but utf16
+ + index8to16: translate indexes from segmentsText to segmentsWText
+ + index16to8: translate indexes from segmentsWText to segmentsText
+ +
+ + 'text' is the original user text, 'segmentsText' is the user text stuffed with
+ + RTL/LTR markers for each line or in addition for User supplied segments. A segment
+ + is a range where Bidi char reordering can happen.
+ + The 'runs' are those ranges with an idiviual style.
+ +/
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.internal.Compatibility;
+import org.eclipse.swt.internal.gdip.Gdip;
+
+import org.eclipse.swt.internal.win32.OS;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.GCData;
+import org.eclipse.swt.graphics.GlyphMetrics;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.graphics.Resource;
+import org.eclipse.swt.graphics.TextStyle;
+
+import tango.text.convert.Format;
+import Utf = tango.text.convert.Utf;
+import java.lang.all;
+import java.lang.System;
+
+
+/**
+ * <code>TextLayout</code> is a graphic object that represents
+ * styled text.
+ * <p>
+ * Instances of this class provide support for drawing, cursor
+ * navigation, hit testing, text wrapping, alignment, tab expansion
+ * line breaking, etc.  These are aspects required for rendering internationalized text.
+ * </p><p>
+ * Application code must explicitly invoke the <code>TextLayout#dispose()</code>
+ * method to release the operating system resources managed by each instance
+ * when those instances are no longer required.
+ * </p>
+ *
+ * @see <a href="http://www.eclipse.org/swt/snippets/#textlayout">TextLayout, TextStyle snippets</a>
+ * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample, StyledText tab</a>
+ * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
+ * 
+ * @since 3.0
+ */
+public final class TextLayout : Resource {
+
+    alias Resource.init_ init_;
+
+/++
+ +  SWT doku
+ +  The styles has at minimum 2 member, each with a start. The last element is the end marker.
+ +
+ +  invariant{
+ +      assert( stylesCount >= 2 );
+ +      assert( stylesCount <= styles.length );
+ +      assert( styles[stylesCount-1] );
+ +      assert( styles[stylesCount-1].start is text.length );
+ +  }
+ +/
+
+
+    Font font;
+    String  text;
+    wchar[] wtext;
+    char[]  segmentsText;
+    wchar[] segmentsWText; // SWT
+    int[]   index8to16; // SWT
+    int[]   index16to8; // SWT
+    int lineSpacing;
+    int ascent, descent;
+    int alignment;
+    int wrapWidth;
+    int orientation;
+    int indent;
+    bool justify;
+    int[] tabs;
+    int[] segments; // indices in 'text'
+    int[] wsegments; // SWT indices in 'wtext'
+    StyleItem[] styles;
+    int stylesCount;
+
+    StyleItem[] allRuns;
+    StyleItem[][] runs;
+    int[] lineOffset, lineY, lineWidth;
+    void* mLangFontLink2;
+
+    static const dchar LTR_MARK = '\u200E', RTL_MARK = '\u200F';
+    static const wchar LTR_MARKw = '\u200E', RTL_MARKw = '\u200F';
+    static const String STR_LTR_MARK = "\u200E", STR_RTL_MARK = "\u200F";
+    static const wchar[] WSTR_LTR_MARK = "\u200E"w, WSTR_RTL_MARK = "\u200F"w;
+    static const int MARK_SIZE = 3;
+    static const int WMARK_SIZE = 1;
+    static const int SCRIPT_VISATTR_SIZEOF = 2;
+    static const int GOFFSET_SIZEOF = 8;
+    private static byte[16] CLSID_CMultiLanguage;
+    private static byte[16] IID_IMLangFontLink2;
+    private static bool static_this_completed = false;
+    private static void static_this() {
+        // in case of allready initialized, we can check and leave without lock
+        if( static_this_completed ){
+            return;
+        }
+        synchronized {
+            if( !static_this_completed ){
+                OS.IIDFromString("{275c23e2-3747-11d0-9fea-00aa003f8646}\0".toWCharArray().ptr, CLSID_CMultiLanguage.ptr);
+                OS.IIDFromString("{DCCFC162-2B38-11d2-B7EC-00C04F8F5D9A}\0".toWCharArray().ptr, IID_IMLangFontLink2.ptr);
+                static_this_completed = true;
+            }
+        }
+    }
+
+    /* IME has a copy of these constants */
+    static const int UNDERLINE_IME_DOT = 1 << 16;
+    static const int UNDERLINE_IME_DASH = 2 << 16;
+    static const int UNDERLINE_IME_THICK = 3 << 16;
+
+    class StyleItem {
+        TextStyle style;
+        // SWT: start, lenght relative to segmentsText
+        int start, length;
+        bool lineBreak, softBreak, tab;
+
+        /*Script cache and analysis */
+        SCRIPT_ANALYSIS analysis;
+        SCRIPT_CACHE* psc;
+
+        /*Shape info (malloc when the run is shaped) */
+        WORD* glyphs;
+        int glyphCount;
+        WORD* clusters;
+        SCRIPT_VISATTR* visAttrs;
+
+        /*Place info (malloc when the run is placed) */
+        int* advances;
+        GOFFSET* goffsets;
+        int width;
+        int ascent;
+        int descent;
+        int leading;
+        int x;
+        int underlinePos, underlineThickness;
+        int strikeoutPos, strikeoutThickness;
+
+        /* Justify info (malloc during computeRuns) */
+        int* justify;
+
+        /* ScriptBreak */
+        SCRIPT_LOGATTR* psla;
+
+        HFONT fallbackFont;
+
+    void free() {
+        auto hHeap = OS.GetProcessHeap();
+        if (psc !is null) {
+            OS.ScriptFreeCache (psc);
+            OS.HeapFree(hHeap, 0, psc);
+            psc = null;
+        }
+        if (glyphs !is null) {
+            OS.HeapFree(hHeap, 0, glyphs);
+            glyphs = null;
+            glyphCount = 0;
+        }
+        if (clusters !is null) {
+            OS.HeapFree(hHeap, 0, clusters);
+            clusters = null;
+        }
+        if (visAttrs !is null) {
+            OS.HeapFree(hHeap, 0, visAttrs);
+            visAttrs = null;
+        }
+        if (advances !is null) {
+            OS.HeapFree(hHeap, 0, advances);
+            advances = null;
+        }
+        if (goffsets !is null) {
+            OS.HeapFree(hHeap, 0, goffsets);
+            goffsets = null;
+        }
+        if (justify !is null) {
+            OS.HeapFree(hHeap, 0, justify);
+            justify = null;
+        }
+        if (psla !is null) {
+            OS.HeapFree(hHeap, 0, psla);
+            psla = null;
+        }
+        if (fallbackFont !is null) {
+            OS.DeleteObject(fallbackFont);
+            fallbackFont = null;
+        }
+        width = 0;
+        ascent = 0;
+        descent = 0;
+        x = 0;
+        lineBreak = softBreak = false;
+    }
+    override public String toString () {
+        return Format( "StyleItem {{{}, {}}", start, style );
+    }
+    }
+
+/**
+ * Constructs a new instance of this class on the given device.
+ * <p>
+ * You must dispose the text layout when it is no longer required.
+ * </p>
+ *
+ * @param device the device on which to allocate the text layout
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
+ * </ul>
+ *
+ * @see #dispose()
+ */
+public this (Device device) {
+    static_this();
+    super(device);
+    wrapWidth = ascent = descent = -1;
+    lineSpacing = 0;
+    orientation = SWT.LEFT_TO_RIGHT;
+    styles = new StyleItem[2];
+    styles[0] = new StyleItem();
+    styles[1] = new StyleItem();
+    stylesCount = 2;
+    text = ""; //$NON-NLS-1$
+    wtext = ""w;
+    void* ppv;
+    OS.OleInitialize(null);
+    if (OS.CoCreateInstance(CLSID_CMultiLanguage.ptr, null, OS.CLSCTX_INPROC_SERVER, IID_IMLangFontLink2.ptr, cast(void*)&ppv) is OS.S_OK) {
+        mLangFontLink2 = ppv;
+    }
+    init_();
+}
+
+void breakRun(StyleItem run) {
+    if (run.psla !is null) return;
+    wchar[] chars = segmentsWText[ index8to16[ run.start ] .. index8to16[ run.start + run.length ] ];
+    auto hHeap = OS.GetProcessHeap();
+    run.psla = cast(SCRIPT_LOGATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length);
+    if (run.psla is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    OS.ScriptBreak(chars.ptr, chars.length, &run.analysis, run.psla);
+}
+
+void checkLayout () {
+    if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
+}
+
+/*
+*  Compute the runs: itemize, shape, place, and reorder the runs.
+*   Break paragraphs into lines, wraps the text, and initialize caches.
+*/
+void computeRuns (GC gc) {
+    if (runs !is null) return;
+    auto hDC = gc !is null ? gc.handle : device.internal_new_GC(null);
+    auto srcHdc = OS.CreateCompatibleDC(hDC);
+    allRuns = itemize();
+    for (int i=0; i<allRuns.length - 1; i++) {
+        StyleItem run = allRuns[i];
+        OS.SelectObject(srcHdc, getItemFont(run));
+        shape(srcHdc, run);
+    }
+    SCRIPT_LOGATTR* logAttr;
+    SCRIPT_PROPERTIES* properties;
+    int lineWidth = indent, lineStart = 0, lineCount = 1;
+    for (int i=0; i<allRuns.length - 1; i++) {
+        StyleItem run = allRuns[i];
+        if (run.length is 1) {
+            char ch = segmentsText.charAt(run.start);
+            switch (ch) {
+                case '\t': {
+                    run.tab = true;
+                    if (tabs is null) break;
+                    int tabsLength = tabs.length, j;
+                    for (j = 0; j < tabsLength; j++) {
+                        if (tabs[j] > lineWidth) {
+                            run.width = tabs[j] - lineWidth;
+                            break;
+                        }
+                    }
+                    if (j is tabsLength) {
+                        int tabX = tabs[tabsLength-1];
+                        int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0];
+                        if (lastTabWidth > 0) {
+                            while (tabX <= lineWidth) tabX += lastTabWidth;
+                            run.width = tabX - lineWidth;
+                        }
+                    }
+                    break;
+                }
+                case '\n': {
+                    run.lineBreak = true;
+                    break;
+                }
+                case '\r': {
+                    run.lineBreak = true;
+                    StyleItem next = allRuns[i + 1];
+                    if (next.length !is 0 && segmentsText.charAt(next.start) is '\n') {
+                        run.length += 1;
+                        next.free();
+                        StyleItem[] newAllRuns = new StyleItem[allRuns.length - 1];
+                        System.arraycopy(allRuns, 0, newAllRuns, 0, i + 1);
+                        System.arraycopy(allRuns, i + 2, newAllRuns, i + 1, allRuns.length - i - 2);
+                        allRuns = newAllRuns;
+                    }
+                    break;
+                }
+                default:
+            }
+        }
+        if (wrapWidth !is -1 && lineWidth + run.width > wrapWidth && !run.tab) {
+            int start = 0;
+            int[] piDx = new int[run.length];
+            if (run.style !is null && run.style.metrics !is null) {
+                piDx[0] = run.width;
+            } else {
+                OS.ScriptGetLogicalWidths(&run.analysis, run.length, run.glyphCount, run.advances, run.clusters, run.visAttrs, piDx.ptr);
+            }
+            int width = 0, maxWidth = wrapWidth - lineWidth;
+            while (width + piDx[start] < maxWidth) {
+                width += piDx[start++];
+            }
+            int firstStart = start;
+            int firstIndice = i;
+            while (i >= lineStart) {
+                breakRun(run);
+                while (start >= 0) {
+                    logAttr = run.psla + start;
+                    //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
+                    if (logAttr.fSoftBreak || logAttr.fWhiteSpace) break;
+                    start--;
+                }
+
+                /*
+                *  Bug in Windows. For some reason Uniscribe sets the fSoftBreak flag for the first letter
+                *  after a letter with an accent. This cause a break line to be set in the middle of a word.
+                *  The fix is to detect the case and ignore fSoftBreak forcing the algorithm keep searching.
+                */
+                if (start is 0 && i !is lineStart && !run.tab) {
+                    if (logAttr.fSoftBreak && !logAttr.fWhiteSpace) {
+                        properties = device.scripts[run.analysis.eScript];
+                        //OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
+                        int langID = properties.langid;
+                        StyleItem pRun = allRuns[i - 1];
+                        //OS.MoveMemory(properties, device.scripts[pRun.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
+                        if (properties.langid is langID || langID is OS.LANG_NEUTRAL || properties.langid is OS.LANG_NEUTRAL) {
+                            breakRun(pRun);
+                            logAttr = pRun.psla + (pRun.length - 1);
+                            //OS.MoveMemory(logAttr, pRun.psla + ((pRun.length - 1) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
+                            if (!logAttr.fWhiteSpace) start = -1;
+                        }
+                    }
+                }
+                if (start >= 0 || i is lineStart) break;
+                run = allRuns[--i];
+                start = run.length - 1;
+            }
+            if (start is 0 && i !is lineStart && !run.tab) {
+                run = allRuns[--i];
+            } else  if (start <= 0 && i is lineStart) {
+                if (lineWidth is wrapWidth && firstIndice > 0) {
+                    i = firstIndice - 1;
+                    run = allRuns[i];
+                    start = run.length;
+                } else {
+                    i = firstIndice;
+                    run = allRuns[i];
+                    start = Math.max(1, firstStart);
+                }
+            }
+            breakRun(run);
+            while (start < run.length) {
+                logAttr = run.psla + start;
+                //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
+                if (!logAttr.fWhiteSpace) break;
+                start++;
+            }
+            if (0 < start && start < run.length) {
+                StyleItem newRun = new StyleItem();
+                newRun.start = run.start + start;
+                newRun.length = run.length - start;
+                newRun.style = run.style;
+                newRun.analysis = cloneScriptAnalysis(run.analysis);
+                run.free();
+                run.length = start;
+                OS.SelectObject(srcHdc, getItemFont(run));
+                run.analysis.fNoGlyphIndex = false;
+                shape (srcHdc, run);
+                OS.SelectObject(srcHdc, getItemFont(newRun));
+                newRun.analysis.fNoGlyphIndex = false;
+                shape (srcHdc, newRun);
+                StyleItem[] newAllRuns = new StyleItem[allRuns.length + 1];
+                System.arraycopy(allRuns, 0, newAllRuns, 0, i + 1);
+                System.arraycopy(allRuns, i + 1, newAllRuns, i + 2, allRuns.length - i - 1);
+                allRuns = newAllRuns;
+                allRuns[i + 1] = newRun;
+            }
+            if (i !is allRuns.length - 2) {
+                run.softBreak = run.lineBreak = true;
+            }
+        }
+        lineWidth += run.width;
+        if (run.lineBreak) {
+            lineStart = i + 1;
+            lineWidth = run.softBreak ?  0 : indent;
+            lineCount++;
+        }
+    }
+    lineWidth = 0;
+    runs = new StyleItem[][](lineCount);
+    lineOffset = new int[lineCount + 1];
+    lineY = new int[lineCount + 1];
+    this.lineWidth = new int[lineCount];
+    int lineRunCount = 0, line = 0;
+    int ascent = Math.max(0, this.ascent);
+    int descent = Math.max(0, this.descent);
+    StyleItem[] lineRuns = new StyleItem[allRuns.length];
+    for (int i=0; i<allRuns.length; i++) {
+        StyleItem run = allRuns[i];
+        lineRuns[lineRunCount++] = run;
+        lineWidth += run.width;
+        ascent = Math.max(ascent, run.ascent);
+        descent = Math.max(descent, run.descent);
+        if (run.lineBreak || i is allRuns.length - 1) {
+            /* Update the run metrics if the last run is a hard break. */
+            if (lineRunCount is 1 && i is allRuns.length - 1) {
+                TEXTMETRIC lptm;
+                OS.SelectObject(srcHdc, getItemFont(run));
+                OS.GetTextMetrics(srcHdc, &lptm);
+                run.ascent = lptm.tmAscent;
+                run.descent = lptm.tmDescent;
+                ascent = Math.max(ascent, run.ascent);
+                descent = Math.max(descent, run.descent);
+            }
+            runs[line] = new StyleItem[lineRunCount];
+            System.arraycopy(lineRuns, 0, runs[line], 0, lineRunCount);
+
+            if (justify && wrapWidth !is -1 && run.softBreak && lineWidth > 0) {
+                if (line is 0) {
+                    lineWidth += indent;
+                } else {
+                    StyleItem[] previousLine = runs[line - 1];
+                    StyleItem previousRun = previousLine[previousLine.length - 1];
+                    if (previousRun.lineBreak && !previousRun.softBreak) {
+                        lineWidth += indent;
+                    }
+                }
+                auto hHeap = OS.GetProcessHeap();
+                int newLineWidth = 0;
+                for (int j = 0; j < runs[line].length; j++) {
+                    StyleItem item = runs[line][j];
+                    int iDx = item.width * wrapWidth / lineWidth;
+                    if (iDx !is item.width) {
+                        item.justify = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4);
+                        if (item.justify is null) SWT.error(SWT.ERROR_NO_HANDLES);
+                        OS.ScriptJustify(item.visAttrs, item.advances, item.glyphCount, iDx - item.width, 2, item.justify);
+                        item.width = iDx;
+                    }
+                    newLineWidth += item.width;
+                }
+                lineWidth = newLineWidth;
+            }
+            this.lineWidth[line] = lineWidth;
+
+            StyleItem lastRun = runs[line][lineRunCount - 1];
+            int lastOffset = lastRun.start + lastRun.length;
+            runs[line] = reorder(runs[line], i is allRuns.length - 1);
+            lastRun = runs[line][lineRunCount - 1];
+            if (run.softBreak && run !is lastRun) {
+                run.softBreak = run.lineBreak = false;
+                lastRun.softBreak = lastRun.lineBreak = true;
+            }
+
+            lineWidth = getLineIndent(line);
+            for (int j = 0; j < runs[line].length; j++) {
+                runs[line][j].x = lineWidth;
+                lineWidth += runs[line][j].width;
+            }
+            line++;
+            lineY[line] = lineY[line - 1] + ascent + descent + lineSpacing;
+            lineOffset[line] = lastOffset;
+            lineRunCount = lineWidth = 0;
+            ascent = Math.max(0, this.ascent);
+            descent = Math.max(0, this.descent);
+        }
+    }
+    if (srcHdc !is null) OS.DeleteDC(srcHdc);
+    if (gc is null) device.internal_dispose_GC(hDC, null);
+}
+
+void destroy () {
+    freeRuns();
+    font = null;
+    text = null;
+    wtext = null;
+    segmentsText = null;
+    segmentsWText = null;
+    tabs = null;
+    styles = null;
+    runs = null;
+    lineOffset = null;
+    lineY = null;
+    lineWidth = null;
+    if (mLangFontLink2 !is null) {
+        /* Release() */
+        OS.VtblCall(2, mLangFontLink2);
+        mLangFontLink2 = null;
+    }
+    OS.OleUninitialize();
+}
+
+SCRIPT_ANALYSIS cloneScriptAnalysis ( inout SCRIPT_ANALYSIS src) {
+    SCRIPT_ANALYSIS dst;
+    dst.eScript = src.eScript;
+    dst.fRTL = src.fRTL;
+    dst.fLayoutRTL = src.fLayoutRTL;
+    dst.fLinkBefore = src.fLinkBefore;
+    dst.fLinkAfter = src.fLinkAfter;
+    dst.fLogicalOrder = src.fLogicalOrder;
+    dst.fNoGlyphIndex = src.fNoGlyphIndex;
+    dst.s.uBidiLevel = src.s.uBidiLevel; 
+    dst.s.fOverrideDirection = src.s.fOverrideDirection;
+    dst.s.fInhibitSymSwap = src.s.fInhibitSymSwap;
+    dst.s.fCharShape = src.s.fCharShape;
+    dst.s.fDigitSubstitute = src.s.fDigitSubstitute;
+    dst.s.fInhibitLigate = src.s.fInhibitLigate;
+    dst.s.fDisplayZWG = src.s.fDisplayZWG;
+    dst.s.fArabicNumContext = src.s.fArabicNumContext;
+    dst.s.fGcpClusters = src.s.fGcpClusters;
+    dst.s.fReserved = src.s.fReserved;
+    dst.s.fEngineReserved = src.s.fEngineReserved;
+    return dst;
+}
+
+/**
+ * Draws the receiver's text using the specified GC at the specified
+ * point.
+ *
+ * @param gc the GC to draw
+ * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
+ * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
+ * </ul>
+ */
+public void draw (GC gc, int x, int y) {
+    draw(gc, x, y, -1, -1, null, null);
+}
+
+/**
+ * Draws the receiver's text using the specified GC at the specified
+ * point.
+ *
+ * @param gc the GC to draw
+ * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
+ * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
+ * @param selectionStart the offset where the selections starts, or -1 indicating no selection
+ * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
+ * @param selectionForeground selection foreground, or NULL to use the system default color
+ * @param selectionBackground selection background, or NULL to use the system default color
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
+ * </ul>
+ */
+public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
+    draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0);
+}
+
+/**
+ * Draws the receiver's text using the specified GC at the specified
+ * point.
+ * <p>
+ * The parameter <code>flags</code> can include one of <code>SWT.DELIMITER_SELECTION</code>
+ * or <code>SWT.FULL_SELECTION</code> to specify the selection behavior on all lines except
+ * for the last line, and can also include <code>SWT.LAST_LINE_SELECTION</code> to extend
+ * the specified selection behavior to the last line.
+ * </p>
+ * @param gc the GC to draw
+ * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
+ * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
+ * @param selectionStart the offset where the selections starts, or -1 indicating no selection
+ * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
+ * @param selectionForeground selection foreground, or NULL to use the system default color
+ * @param selectionBackground selection background, or NULL to use the system default color
+ * @param flags drawing options
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
+ * </ul>
+ *
+ * @since 3.3
+ */
+public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
+    checkLayout();
+    computeRuns(gc);
+    if (gc is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (selectionForeground !is null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (selectionBackground !is null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    int length = text.length;
+    int wlength = wtext.length;
+    if (length is 0 && flags is 0) return;
+    auto hdc = gc.handle;
+    Rectangle clip = gc.getClipping();
+    GCData data = gc.data;
+    auto gdipGraphics = data.gdipGraphics;
+    auto foreground = data.foreground;
+    auto alpha = data.alpha;
+    bool gdip = gdipGraphics !is null && (alpha !is 0xFF || data.foregroundPattern !is null);
+    HRGN clipRgn;
+    float[] lpXform = null;
+    Gdip.Rect gdipRect;
+    if (gdipGraphics !is null && !gdip) {
+        auto matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0);
+        if (matrix is null) SWT.error(SWT.ERROR_NO_HANDLES);
+        Gdip.Graphics_GetTransform(gdipGraphics, matrix);
+        auto identity_ = gc.identity();
+        Gdip.Matrix_Invert(identity_);
+        Gdip.Matrix_Multiply(matrix, identity_, Gdip.MatrixOrderAppend);
+        Gdip.Matrix_delete(identity_);
+        if (!Gdip.Matrix_IsIdentity(matrix)) {
+            lpXform = new float[6];
+            Gdip.Matrix_GetElements(matrix, lpXform.ptr);
+        }
+        Gdip.Matrix_delete(matrix);
+        if ((data.style & SWT.MIRRORED) !is 0 && lpXform !is null) {
+            gdip = true;
+            lpXform = null;
+        } else {
+            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone);
+            auto rgn = Gdip.Region_new();
+            Gdip.Graphics_GetClip(gdipGraphics, rgn);
+            if (!Gdip.Region_IsInfinite(rgn, gdipGraphics)) {
+                clipRgn = Gdip.Region_GetHRGN(rgn, gdipGraphics);
+            }
+            Gdip.Region_delete(rgn);
+            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf);
+            hdc = Gdip.Graphics_GetHDC(gdipGraphics);
+        }
+    }
+    Gdip.Brush foregroundBrush;
+    int state = 0;
+    if (gdip) {
+        gc.checkGC(GC.FOREGROUND);
+        foregroundBrush = gc.getFgBrush();
+    } else {
+        state = OS.SaveDC(hdc);
+        if ((data.style & SWT.MIRRORED) !is 0) {
+            OS.SetLayout(hdc, OS.GetLayout(hdc) | OS.LAYOUT_RTL);
+        }
+        if (lpXform !is null) {
+            OS.SetGraphicsMode(hdc, OS.GM_ADVANCED);
+            OS.SetWorldTransform(hdc, cast(XFORM*)lpXform.ptr);
+        }
+        if (clipRgn !is null) {
+            OS.SelectClipRgn(hdc, clipRgn);
+            OS.DeleteObject(clipRgn);
+        }
+    }
+    bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1;
+    if (hasSelection || (flags & SWT.LAST_LINE_SELECTION) !is 0) {
+        selectionStart = Math.min(Math.max(0, selectionStart), length - 1);
+        selectionEnd = Math.min(Math.max(0, selectionEnd), length - 1);
+        if (selectionForeground is null) selectionForeground = device.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+        if (selectionBackground is null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION);
+        selectionStart = translateOffset(selectionStart);
+        selectionEnd = translateOffset(selectionEnd);
+    }
+    RECT rect;
+    Gdip.Brush selBrush;
+    Gdip.Pen selPen;
+    Gdip.Brush selBrushFg;
+
+    if (hasSelection || (flags & SWT.LAST_LINE_SELECTION) !is 0) {
+        if (gdip) {
+            auto bg = selectionBackground.handle;
+            auto argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
+            auto color = Gdip.Color_new(argb);
+            selBrush = cast(Gdip.Brush)Gdip.SolidBrush_new(color);
+            Gdip.Color_delete(color);
+            auto fg = selectionForeground.handle;
+            argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+            color = Gdip.Color_new(argb);
+            selBrushFg = cast(Gdip.Brush)Gdip.SolidBrush_new(color);
+            selPen = cast(Gdip.Pen) Gdip.Pen_new( cast(Gdip.Brush)selBrushFg, 1);
+            Gdip.Color_delete(color);
+        } else {
+            selBrush = cast(Gdip.Brush)OS.CreateSolidBrush(selectionBackground.handle);
+            selPen = cast(Gdip.Pen)OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle);
+        }
+    }
+    int offset = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? -1 : 0;
+    OS.SetBkMode(hdc, OS.TRANSPARENT);
+    for (int line=0; line<runs.length; line++) {
+        int drawX = x + getLineIndent(line);
+        int drawY = y + lineY[line];
+        StyleItem[] lineRuns = runs[line];
+        int lineHeight = lineY[line+1] - lineY[line] - lineSpacing;
+        if (flags !is 0 && (hasSelection || (flags & SWT.LAST_LINE_SELECTION) !is 0)) {
+            bool extents = false;
+            if (line is runs.length - 1 && (flags & SWT.LAST_LINE_SELECTION) !is 0) {
+                extents = true;
+            } else {
+                StyleItem run = lineRuns[lineRuns.length - 1];
+                if (run.lineBreak && !run.softBreak) {
+                    if (selectionStart <= run.start && run.start <= selectionEnd) extents = true;
+                } else {
+                    int endOffset = segmentsText.getAbsoluteCodePointOffset( run.start + run.length, -1 );
+                    if (selectionStart <= endOffset && endOffset < selectionEnd && (flags & SWT.FULL_SELECTION) !is 0) {
+                        extents = true;
+                    }
+                }
+            }
+            if (extents) {
+                int width;
+                if ((flags & SWT.FULL_SELECTION) !is 0) {
+                    width = OS.IsWin95 ? 0x7FFF : 0x6FFFFFF;
+                } else {
+                    width = lineHeight / 3;
+                }
+                if (gdip) {
+                    Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX + lineWidth[line], drawY, width, lineHeight);
+                } else {
+                    OS.SelectObject(hdc, selBrush);
+                    OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight, OS.PATCOPY);
+                }
+            }
+        }
+        if (drawX > clip.x + clip.width) continue;
+        if (drawX + lineWidth[line] < clip.x) continue;
+        int baseline = Math.max(0, this.ascent);
+        int lineUnderlinePos = 0;
+        for (int i = 0; i < lineRuns.length; i++) {
+            baseline = Math.max(baseline, lineRuns[i].ascent);
+            lineUnderlinePos = Math.min(lineUnderlinePos, lineRuns[i].underlinePos);
+        }
+        int alignmentX = drawX;
+        for (int i = 0; i < lineRuns.length; i++) {
+            StyleItem run = lineRuns[i];
+            if (run.length is 0) continue;
+            if (drawX > clip.x + clip.width) break;
+            if (drawX + run.width >= clip.x) {
+                if (!run.lineBreak || run.softBreak) {
+                    int end = segmentsText.getAbsoluteCodePointOffset( run.start + run.length, -1 );
+                    bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
+                    if (fullSelection) {
+                        if (gdip) {
+                            Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX, drawY, run.width, lineHeight);
+                        } else {
+                            OS.SelectObject(hdc, selBrush);
+                            OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight, OS.PATCOPY);
+                        }
+                    } else {
+                        if (run.style !is null && run.style.background !is null) {
+                            auto bg = run.style.background.handle;
+                            if (gdip) {
+                                int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
+                                auto color = Gdip.Color_new(argb);
+                                auto brush = Gdip.SolidBrush_new(color);
+                                Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)brush, drawX, drawY, run.width, lineHeight);
+                                Gdip.Color_delete(color);
+                                Gdip.SolidBrush_delete(brush);
+                            } else {
+                                auto hBrush = OS.CreateSolidBrush (bg);
+                                auto oldBrush = OS.SelectObject(hdc, hBrush);
+                                OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight, OS.PATCOPY);
+                                OS.SelectObject(hdc, oldBrush);
+                                OS.DeleteObject(hBrush);
+                            }
+                        }
+                        bool partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd);
+                        if (partialSelection) {
+                            int selStart = index8to16[ Math.max(selectionStart, run.start) ] - index8to16[run.start];
+                            int selEnd = index8to16[ Math.min(selectionEnd, end) ] - index8to16[ run.start ];
+                            int cChars = index8to16[run.start+run.length] - index8to16[run.start]; // make it wchar
+                            int gGlyphs = run.glyphCount;
+                            int piX;
+                            int* advances = run.justify !is null ? run.justify : run.advances;
+                            OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                            int runX = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
+                            rect.left = drawX + runX;
+                            rect.top = drawY;
+                            OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                            runX = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
+                            rect.right = drawX + runX;
+                            rect.bottom = drawY + lineHeight;
+                            if (gdip) {
+                                if (rect.left > rect.right) {
+                                    int tmp = rect.left;
+                                    rect.left = rect.right;
+                                    rect.right = tmp;
+                                }
+                                Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+                            } else {
+                                OS.SelectObject(hdc, selBrush);
+                                OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY);
+                            }
+                        }
+                    }
+                }
+            }
+            drawX += run.width;
+        }
+        RECT* borderClip = null;
+        drawX = alignmentX;
+        for (int i = 0; i < lineRuns.length; i++) {
+            StyleItem run = lineRuns[i];
+            if (run.length is 0) continue;
+            if (drawX > clip.x + clip.width) break;
+            if (drawX + run.width >= clip.x) {
+                if (!run.tab && (!run.lineBreak || run.softBreak) && !(run.style !is null && run.style.metrics !is null)) {
+                    int end = run.start + run.length - 1;
+                    bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
+                    bool partialSelection = hasSelection && !fullSelection && !(selectionStart > end || run.start > selectionEnd);
+                    OS.SelectObject(hdc, getItemFont(run));
+                    int drawRunY = drawY + (baseline - run.ascent);
+                    if (partialSelection) {
+                        int selStart = Math.max(index8to16[selectionStart], index8to16[run.start]) - index8to16[run.start];
+                        int selEnd = Math.min(index8to16[selectionEnd], index8to16[end]) - index8to16[run.start];
+                        int cChars = index8to16[run.start+run.length] - index8to16[run.start]; // make it wchar
+                        int gGlyphs = run.glyphCount;
+                        int piX;
+                        int* advances = run.justify !is null ? run.justify : run.advances;
+                        OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                        int runX = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
+                        rect.left = drawX + runX;
+                        rect.top = drawY;
+                        OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                        runX = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
+                        rect.right = drawX + runX;
+                        rect.bottom = drawY + lineHeight;
+                    }
+                    if (gdip) {
+                        OS.BeginPath(hdc);
+                        OS.ScriptTextOut(hdc, run.psc, drawX, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
+                        OS.EndPath(hdc);
+                        int count = OS.GetPath(hdc, null, null, 0);
+                        int[] points = new int[count*2];
+                        ubyte[] types = new ubyte[count];
+                        OS.GetPath(hdc, cast(POINT*)points.ptr, types.ptr, count);
+                        for (int typeIndex = 0; typeIndex < types.length; typeIndex++) {
+                            int newType = 0;
+                            int type = types[typeIndex] & 0xFF;
+                            switch (type & ~OS.PT_CLOSEFIGURE) {
+                                case OS.PT_MOVETO: newType = Gdip.PathPointTypeStart; break;
+                                case OS.PT_LINETO: newType = Gdip.PathPointTypeLine; break;
+                                case OS.PT_BEZIERTO: newType = Gdip.PathPointTypeBezier; break;
+                                default:
+                            }
+                            if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath;
+                            types[typeIndex] = cast(byte)newType;
+                        }
+                        auto path = Gdip.GraphicsPath_new(cast(Gdip.Point*)points.ptr, types.ptr, count, Gdip.FillModeAlternate);
+                        if (path is null) SWT.error(SWT.ERROR_NO_HANDLES);
+                        auto brush = foregroundBrush;
+                        if (fullSelection) {
+                            brush = cast(Gdip.Brush)selBrushFg;
+                        } else {
+                            if (run.style !is null && run.style.foreground !is null) {
+                                auto fg = run.style.foreground.handle;
+                                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                                auto color = Gdip.Color_new(argb);
+                                brush = cast(Gdip.Brush)Gdip.SolidBrush_new(color);
+                                Gdip.Color_delete(color);
+                            }
+                        }
+                        int gstate = 0;
+                        if (partialSelection) {
+                            gdipRect.X = rect.left;
+                            gdipRect.Y = rect.top;
+                            gdipRect.Width = rect.right - rect.left;
+                            gdipRect.Height = rect.bottom - rect.top;
+                            gstate = Gdip.Graphics_Save(gdipGraphics);
+                            Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeExclude);
+                        }
+                        int antialias = Gdip.Graphics_GetSmoothingMode(gdipGraphics), textAntialias = 0;
+                        int mode = Gdip.Graphics_GetTextRenderingHint(data.gdipGraphics);
+                        switch (mode) {
+                            case Gdip.TextRenderingHintSystemDefault: textAntialias = Gdip.SmoothingModeAntiAlias; break;
+                            case Gdip.TextRenderingHintSingleBitPerPixel:
+                            case Gdip.TextRenderingHintSingleBitPerPixelGridFit: textAntialias = Gdip.SmoothingModeNone; break;
+                            case Gdip.TextRenderingHintAntiAlias:
+                            case Gdip.TextRenderingHintAntiAliasGridFit:
+                            case Gdip.TextRenderingHintClearTypeGridFit: textAntialias = Gdip.SmoothingModeAntiAlias; break;
+                            default:
+                        }
+                        Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias);
+                        int gstate2 = 0;
+                        if ((data.style & SWT.MIRRORED) !is 0) {
+                            gstate2 = Gdip.Graphics_Save(gdipGraphics);
+                            Gdip.Graphics_ScaleTransform(gdipGraphics, -1, 1, Gdip.MatrixOrderPrepend);
+                            Gdip.Graphics_TranslateTransform(gdipGraphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
+                        }
+                        Gdip.Graphics_FillPath(gdipGraphics, brush, path);
+                        if ((data.style & SWT.MIRRORED) !is 0) {
+                            Gdip.Graphics_Restore(gdipGraphics, gstate2);
+                        }
+                        Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias);
+                        drawLines(gdip, gdipGraphics, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, brush, null, alpha);
+                        if (partialSelection) {
+                            Gdip.Graphics_Restore(gdipGraphics, gstate);
+                            gstate = Gdip.Graphics_Save(gdipGraphics);
+                            Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeIntersect);
+                            Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias);
+                            if ((data.style & SWT.MIRRORED) !is 0) {
+                                gstate2 = Gdip.Graphics_Save(gdipGraphics);
+                                Gdip.Graphics_ScaleTransform(gdipGraphics, -1, 1, Gdip.MatrixOrderPrepend);
+                                Gdip.Graphics_TranslateTransform(gdipGraphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
+                            }
+                            Gdip.Graphics_FillPath(gdipGraphics, selBrushFg, path);
+                            if ((data.style & SWT.MIRRORED) !is 0) {
+                                Gdip.Graphics_Restore(gdipGraphics, gstate2);
+                            }
+                            Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias);
+                            drawLines(gdip, gdipGraphics, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, selBrushFg, &rect, alpha);
+                            Gdip.Graphics_Restore(gdipGraphics, gstate);
+                        }
+                        borderClip = drawBorder(gdip, gdipGraphics, x, drawY, lineHeight, foregroundBrush, selBrushFg, fullSelection, borderClip, partialSelection ? &rect : null, alpha, lineRuns, i, selectionStart, selectionEnd);
+                        Gdip.GraphicsPath_delete(path);
+                        if ( brush !is cast(Gdip.Brush)selBrushFg && brush !is cast(Gdip.Brush)foregroundBrush)
+                            Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+                    }   else {
+                            auto fg = foreground;
+                            if (fullSelection) {
+                                fg = selectionForeground.handle;
+                            } else {
+                                if (run.style !is null && run.style.foreground !is null) fg = run.style.foreground.handle;
+                        }
+                        OS.SetTextColor(hdc, fg);
+                        OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
+                        drawLines(gdip, hdc, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, cast(void*)fg, null, alpha);
+                        if (partialSelection && fg !is selectionForeground.handle) {
+                            OS.SetTextColor(hdc, selectionForeground.handle);
+                            OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, OS.ETO_CLIPPED, &rect, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
+                            drawLines(gdip, hdc, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, cast(void*)selectionForeground.handle, &rect, alpha);
+                        }
+                        int selForeground = selectionForeground !is null ? selectionForeground.handle : 0;
+                        borderClip = drawBorder(gdip, hdc, x, drawY, lineHeight, cast(void*)foreground, cast(void*)selForeground, fullSelection, borderClip, partialSelection ? &rect : null, alpha, lineRuns, i, selectionStart, selectionEnd);
+                    }
+                }
+            }
+            drawX += run.width;
+        }
+    }
+    if (gdip) {
+        if (selBrush !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)selBrush);
+        if (selBrushFg !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)selBrushFg);
+        if (selPen !is null) Gdip.Pen_delete(selPen);
+    } else {
+        OS.RestoreDC(hdc, state);
+        if (gdipGraphics !is null) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc);
+        if (selBrush !is null) OS.DeleteObject (selBrush);
+        if (selPen !is null) OS.DeleteObject (selPen);
+    }
+}
+
+void drawLines(bool advance, void* graphics, int x, int lineBaseline, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, void* color, RECT* clipRect, int alpha) {
+    StyleItem run = line[index];
+    TextStyle style = run.style;
+    if (style is null) return;
+    if (!style.underline && !style.strikeout) return;
+    int runX = x + run.x;
+    int underlineY = lineBaseline - lineUnderlinePos;
+    int strikeoutY = lineBaseline - run.strikeoutPos;
+    if (advance) {
+        Gdip.Graphics_SetPixelOffsetMode(cast(Gdip.Graphics)graphics, Gdip.PixelOffsetModeNone);
+        auto brush = color;
+        if (style.underline) {
+            if (style.underlineColor !is null) {
+                int fg = style.underlineColor.handle;
+                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            switch (style.underlineStyle) {
+                case SWT.UNDERLINE_SQUIGGLE:
+                case SWT.UNDERLINE_ERROR: {
+                    int squigglyThickness = 1;
+                    int squigglyHeight = 2 * squigglyThickness;
+                    int squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
+                    int squigglyX = runX;
+                    for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
+                        squigglyX = x + line[i - 1].x;
+                    }
+                    int gstate = 0;
+                    if (clipRect is null) {
+                        gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                        Gdip.Rect gdipRect;
+                        gdipRect.X = runX;
+                        gdipRect.Y = squigglyY;
+                        gdipRect.Width = run.width + 1;
+                        gdipRect.Height = squigglyY + squigglyHeight + 1;
+                        Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeIntersect);
+                    }
+                    int[] points = computePolyline(squigglyX, squigglyY, runX + run.width, squigglyY + squigglyHeight);
+                    auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, squigglyThickness);
+                    Gdip.Graphics_DrawLines(cast(Gdip.Graphics)graphics, pen, cast(Gdip.Point*)points.ptr, points.length / 2);
+                    Gdip.Pen_delete(pen);
+                    if (gstate !is 0) Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+                    break;
+                }
+                case SWT.UNDERLINE_SINGLE:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY, run.width, run.underlineThickness);
+                    break;
+                case SWT.UNDERLINE_DOUBLE:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY, run.width, run.underlineThickness);
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY + run.underlineThickness * 2, run.width, run.underlineThickness);
+                    break;
+                case UNDERLINE_IME_THICK:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX - run.underlineThickness, underlineY, run.width, run.underlineThickness * 2);
+                    break;
+                case UNDERLINE_IME_DOT:
+                case UNDERLINE_IME_DASH: {
+                    auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, 1);
+                    int dashStyle = style.underlineStyle is UNDERLINE_IME_DOT ? Gdip.DashStyleDot : Gdip.DashStyleDash;
+                    Gdip.Pen_SetDashStyle(pen, dashStyle);
+                    Gdip.Graphics_DrawLine(cast(Gdip.Graphics)graphics, pen, runX, underlineY, runX + run.width, underlineY);
+                    Gdip.Pen_delete(pen);
+                    break;
+                }
+                default:
+            }
+            if (brush !is color) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        }
+        if (style.strikeout) {
+            if (style.strikeoutColor !is null) {
+                int fg = style.strikeoutColor.handle;
+                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, strikeoutY, run.width, run.strikeoutThickness);
+            if (brush !is color) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        }
+        Gdip.Graphics_SetPixelOffsetMode(cast(Gdip.Graphics)graphics, Gdip.PixelOffsetModeHalf);
+    } else {
+        uint colorRefUnderline = cast(uint)color;
+        uint colorRefStrikeout = cast(uint)color;
+        int /*long*/ brushUnderline = 0;
+        int /*long*/ brushStrikeout = 0;
+        RECT rect;
+        if (style.underline) {
+            if (style.underlineColor !is null) {
+                colorRefUnderline = style.underlineColor.handle;
+            }
+            switch (style.underlineStyle) {
+                case SWT.UNDERLINE_SQUIGGLE:
+                case SWT.UNDERLINE_ERROR: {
+                    int squigglyThickness = 1;
+                    int squigglyHeight = 2 * squigglyThickness;
+                    int squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
+                    int squigglyX = runX;
+                    for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
+                        squigglyX = x + line[i - 1].x;
+                    }
+                    int state = OS.SaveDC(graphics);
+                    if (clipRect !is null) {
+                        OS.IntersectClipRect(graphics, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+                    } else {
+                        OS.IntersectClipRect(graphics, runX, squigglyY, runX + run.width + 1, squigglyY + squigglyHeight + 1);
+                    }
+                    int[] points = computePolyline(squigglyX, squigglyY, runX + run.width, squigglyY + squigglyHeight);
+                    auto pen = OS.CreatePen(OS.PS_SOLID, squigglyThickness, colorRefUnderline);
+                    auto oldPen = OS.SelectObject(graphics, pen);
+                    OS.Polyline(graphics, cast(POINT*)points.ptr, points.length / 2);
+                    int length_ = points.length;
+                    if (length_ >= 2 && squigglyThickness <= 1) {
+                        OS.SetPixel (graphics, points[length_ - 2], points[length_ - 1], colorRefUnderline);
+                    }
+                    OS.RestoreDC(graphics, state);
+                    OS.SelectObject(graphics, oldPen);
+                    OS.DeleteObject(pen);
+                    break;
+                }
+                case SWT.UNDERLINE_SINGLE:
+                    brushUnderline = cast(uint) OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect, cast(void*)brushUnderline);
+                    break;
+                case SWT.UNDERLINE_DOUBLE:
+                    brushUnderline = cast(uint)OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect, cast(void*)brushUnderline);
+                    OS.SetRect(&rect, runX, underlineY + run.underlineThickness * 2, runX + run.width, underlineY + run.underlineThickness * 3);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect,  cast(void*)brushUnderline);
+                    break;
+                case UNDERLINE_IME_THICK:
+                    brushUnderline = cast(uint)OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY - run.underlineThickness, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect,  cast(void*)brushUnderline);
+                    break;
+                case UNDERLINE_IME_DASH:
+                case UNDERLINE_IME_DOT: {
+                    underlineY = lineBaseline + run.descent;
+                    int penStyle = style.underlineStyle is UNDERLINE_IME_DASH ? OS.PS_DASH : OS.PS_DOT;
+                    auto pen = OS.CreatePen(penStyle, 1, colorRefUnderline);
+                    auto oldPen = OS.SelectObject(graphics, pen);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.MoveToEx(graphics, rect.left, rect.top, null);
+                    OS.LineTo(graphics, rect.right, rect.top);
+                    OS.SelectObject(graphics, oldPen);
+                    OS.DeleteObject(pen);
+                    break;
+                }
+                default:
+            }
+        }
+        if (style.strikeout) {
+            if (style.strikeoutColor !is null) {
+                colorRefStrikeout = style.strikeoutColor.handle;
+            }
+            if (brushUnderline !is 0 && colorRefStrikeout is colorRefUnderline) {
+                brushStrikeout = brushUnderline;
+            } else {
+                brushStrikeout = cast(int) OS.CreateSolidBrush(colorRefStrikeout);
+            }
+            OS.SetRect(&rect, runX, strikeoutY, runX + run.width, strikeoutY + run.strikeoutThickness);
+            if (clipRect !is null) {
+                rect.left = Math.max(rect.left, clipRect.left);
+                rect.right = Math.min(rect.right, clipRect.right);
+            }
+            OS.FillRect(graphics, &rect, cast(void*)brushStrikeout);
+        }
+        if (brushUnderline !is 0) OS.DeleteObject(cast(void*)brushUnderline);
+        if (brushStrikeout !is 0 && brushStrikeout !is brushUnderline) OS.DeleteObject(cast(void*)brushStrikeout);
+    }
+}
+
+RECT* drawBorder(bool advance, void* graphics, int x, int y, int lineHeight, void* color, void* selectionColor, bool fullSelection, RECT* clipRect, RECT* rect, int alpha, StyleItem[] line, int index, int selectionStart, int selectionEnd) {
+    StyleItem run = line[index];
+    TextStyle style = run.style;
+    if (style is null) return null;
+    if (style.borderStyle is SWT.NONE) return null;
+    if (rect !is null) {
+        if (clipRect is null) {
+            clipRect = new RECT ();
+            OS.SetRect(clipRect, -1, rect.top, -1, rect.bottom);
+        }
+        bool isRTL = (orientation & SWT.RIGHT_TO_LEFT) !is 0;
+        if (run.start <= selectionStart && selectionStart <= run.start + run.length) {
+            if (run.analysis.fRTL ^ isRTL) {
+                clipRect.right = rect.left;
+            } else {
+                clipRect.left = rect.left;
+            }
+        }
+        if (run.start <= selectionEnd && selectionEnd <= run.start + run.length) {
+            if (run.analysis.fRTL ^ isRTL) {
+                clipRect.left = rect.right;
+            } else {
+                clipRect.right = rect.right;
+            }
+        }
+    }
+    if (index + 1 >= line.length || !style.isAdherentBorder(line[index + 1].style)) {
+        int left = run.x;
+        for (int i = index; i > 0 && style.isAdherentBorder(line[i - 1].style); i--) {
+            left = line[i - 1].x;
+        }
+        if (advance) {
+            auto brush = color;
+            int customColor = -1;
+            if (style.borderColor !is null) {
+                customColor = style.borderColor.handle;
+            } else {
+                if (style.foreground !is null) {
+                    customColor = style.foreground.handle;
+                }
+                if (fullSelection && clipRect is null) {
+                    customColor = -1;
+                    brush = selectionColor;
+                }
+            }
+            if (customColor !is -1) {
+                int argb = ((alpha & 0xFF) << 24) | ((customColor >> 16) & 0xFF) | (customColor & 0xFF00) | ((customColor & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            int lineWidth = 1;
+            int lineStyle = Gdip.DashStyleSolid;
+            switch (style.borderStyle) {
+                case SWT.BORDER_SOLID: break;
+                case SWT.BORDER_DASH: lineStyle = Gdip.DashStyleDash; break;
+                case SWT.BORDER_DOT: lineStyle = Gdip.DashStyleDot; break;
+                default:
+            }
+            auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, lineWidth);
+            Gdip.Pen_SetDashStyle(pen, lineStyle);
+            float gdipXOffset = 0.5f, gdipYOffset = 0.5f;
+            Gdip.Graphics_TranslateTransform(cast(Gdip.Graphics)graphics, gdipXOffset, gdipYOffset, Gdip.MatrixOrderPrepend);
+            if (style.borderColor is null && clipRect !is null) {
+                int gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                if (clipRect.left is -1) clipRect.left = 0;
+                if (clipRect.right is -1) clipRect.right = 0x7ffff;
+                Gdip.Rect gdipRect;
+                gdipRect.X = clipRect.left;
+                gdipRect.Y = clipRect.top;
+                gdipRect.Width = clipRect.right - clipRect.left;
+                gdipRect.Height = clipRect.bottom - clipRect.top;
+                Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeExclude);
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+                Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+                gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeIntersect);
+                auto selPen = Gdip.Pen_new(cast(Gdip.Brush)selectionColor, lineWidth);
+                Gdip.Pen_SetDashStyle(pen, lineStyle);
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, selPen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+                Gdip.Pen_delete(selPen);
+                Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+            } else {
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+            }
+            Gdip.Graphics_TranslateTransform(cast(Gdip.Graphics)graphics, -gdipXOffset, -gdipYOffset, Gdip.MatrixOrderPrepend);
+            Gdip.Pen_delete(pen);
+            if (customColor !is -1) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        } else {
+            if (style.borderColor !is null) {
+                color = cast(void*)style.borderColor.handle;
+            } else {
+                if (style.foreground !is null) {
+                    color = cast(void*)style.foreground.handle;
+                }
+                if (fullSelection && clipRect is null) {
+                    color = selectionColor;
+                }
+            }
+            int lineWidth = 1;
+            int lineStyle = OS.PS_SOLID;
+            switch (style.borderStyle) {
+                case SWT.BORDER_SOLID: break;
+                case SWT.BORDER_DASH: lineStyle = OS.PS_DASH; break;
+                case SWT.BORDER_DOT: lineStyle = OS.PS_DOT; break;
+                default:
+            }
+            LOGBRUSH logBrush;
+            logBrush.lbStyle = OS.BS_SOLID;
+            logBrush.lbColor = cast(uint)color;
+            auto newPen = OS.ExtCreatePen(lineStyle | OS.PS_GEOMETRIC, Math.max(1, lineWidth), &logBrush, 0, null);
+            auto oldPen = OS.SelectObject(graphics, newPen);
+            auto oldBrush = OS.SelectObject(graphics, OS.GetStockObject(OS.NULL_BRUSH));
+            OS.Rectangle(graphics, x + left, y, x + run.x + run.width, y + lineHeight);
+            if (style.borderColor is null && clipRect !is null && color !is selectionColor) {
+                int state = OS.SaveDC(graphics);
+                if (clipRect.left is -1) clipRect.left = 0;
+                if (clipRect.right is -1) clipRect.right = 0x7ffff;
+                OS.IntersectClipRect(graphics, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+                logBrush.lbColor = cast(uint)selectionColor;
+                auto selPen = OS.ExtCreatePen (lineStyle | OS.PS_GEOMETRIC, Math.max(1, lineWidth), &logBrush, 0, null);
+                OS.SelectObject(graphics, selPen);
+                OS.Rectangle(graphics, x + left, y, x + run.x + run.width, y + lineHeight);
+                OS.RestoreDC(graphics, state);
+                OS.SelectObject(graphics, newPen);
+                OS.DeleteObject(selPen);
+            }
+            OS.SelectObject(graphics, oldBrush);
+            OS.SelectObject(graphics, oldPen);
+            OS.DeleteObject(newPen);
+        }
+        return null;
+    }
+    return clipRect;
+}
+
+int[] computePolyline(int left, int top, int right, int bottom) {
+    int height = bottom - top; // can be any number
+    int width = 2 * height; // must be even
+    int peaks = Compatibility.ceil(right - left, width);
+    if (peaks is 0 && right - left > 2) {
+        peaks = 1;
+    }
+    int length_ = ((2 * peaks) + 1) * 2;
+    if (length_ < 0) return new int[0];
+
+    int[] coordinates = new int[length_];
+    for (int i = 0; i < peaks; i++) {
+        int index = 4 * i;
+        coordinates[index] = left + (width * i);
+        coordinates[index+1] = bottom;
+        coordinates[index+2] = coordinates[index] + width / 2;
+        coordinates[index+3] = top;
+    }
+    coordinates[length_-2] = left + (width * peaks);
+    coordinates[length_-1] = bottom;
+    return coordinates;
+}
+
+void freeRuns () {
+    if (allRuns is null) return;
+    for (int i=0; i<allRuns.length; i++) {
+        StyleItem run = allRuns[i];
+        run.free();
+    }
+    allRuns = null;
+    runs = null;
+    segmentsText = null;
+    segmentsWText = null;
+}
+
+/**
+ * Returns the receiver's horizontal text alignment, which will be one
+ * of <code>SWT.LEFT</code>, <code>SWT.CENTER</code> or
+ * <code>SWT.RIGHT</code>.
+ *
+ * @return the alignment used to positioned text horizontally
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getAlignment () {
+    checkLayout();
+    return alignment;
+}
+
+/**
+ * Returns the ascent of the receiver.
+ *
+ * @return the ascent
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getDescent()
+ * @see #setDescent(int)
+ * @see #setAscent(int)
+ * @see #getLineMetrics(int)
+ */
+public int getAscent () {
+    checkLayout();
+    return ascent;
+}
+
+/**
+ * Returns the bounds of the receiver. The width returned is either the
+ * width of the longest line or the width set using {@link TextLayout#setWidth(int)}.
+ * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}.
+ *
+ * @return the bounds of the receiver
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setWidth(int)
+ * @see #getLineBounds(int)
+ */
+public Rectangle getBounds () {
+    checkLayout();
+    computeRuns(null);
+    int width = 0;
+    if (wrapWidth !is -1) {
+        width = wrapWidth;
+    } else {
+        for (int line=0; line<runs.length; line++) {
+            width = Math.max(width, lineWidth[line] + getLineIndent(line));
+        }
+    }
+    return new Rectangle (0, 0, width, lineY[lineY.length - 1]);
+}
+
+/**
+ * Returns the bounds for the specified range of characters. The
+ * bounds is the smallest rectangle that encompasses all characters
+ * in the range. The start and end offsets are inclusive and will be
+ * clamped if out of range.
+ *
+ * @param start the start offset
+ * @param end the end offset
+ * @return the bounds of the character range
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public Rectangle getBounds (int start, int end) {
+    checkLayout();
+    computeRuns(null);
+    int length = text.length;
+    if (length is 0) return new Rectangle(0, 0, 0, 0);
+    if (start > end) return new Rectangle(0, 0, 0, 0);
+    start = Math.min(Math.max(0, start), length - 1);
+    end = Math.min(Math.max(0, end), length - 1);
+    start = translateOffset(start);
+    end = translateOffset(end);
+    int left = 0x7fffffff, right = 0;
+    int top = 0x7fffffff, bottom = 0;
+    bool isRTL = (orientation & SWT.RIGHT_TO_LEFT) !is 0;
+    for (int i = 0; i < allRuns.length - 1; i++) {
+        StyleItem run = allRuns[i];
+        int runEnd = run.start + run.length;
+        if (runEnd <= start) continue;
+        if (run.start > end) break;
+        int runLead = run.x;
+        int runTrail = run.x + run.width;
+        if (run.start <= start && start < runEnd) {
+            int cx = 0;
+            if (run.style !is null && run.style.metrics !is null) {
+                GlyphMetrics metrics = run.style.metrics;
+                cx = metrics.width * (index8to16[start] - index8to16[run.start]);
+            } else if (!run.tab) {
+                int piX;
+                int* advances = run.justify !is null ? run.justify : run.advances;
+                int wlength = index8to16[ run.start+run.length] - index8to16[run.start];
+                OS.ScriptCPtoX(index8to16[start] - index8to16[run.start], false, wlength, run.glyphCount, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                cx = isRTL ? run.width - piX : piX;
+            }
+            if (run.analysis.fRTL ^ isRTL) {
+                runTrail = run.x + cx;
+            } else {
+                runLead = run.x + cx;
+            }
+        }
+        if (run.start <= end && end < runEnd) {
+            int cx = run.width;
+            if (run.style !is null && run.style.metrics !is null) {
+                GlyphMetrics metrics = run.style.metrics;
+                cx = metrics.width * (index8to16[end] - index8to16[run.start] + 1);
+            } else if (!run.tab) {
+                int piX;
+                int* advances = run.justify !is null ? run.justify : run.advances;
+                int wlength = index8to16[ run.start+run.length] - index8to16[run.start];
+                OS.ScriptCPtoX(index8to16[end] - index8to16[run.start], true, wlength, run.glyphCount, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                cx = isRTL ? run.width - piX : piX;
+            }
+            if (run.analysis.fRTL ^ isRTL) {
+                runLead = run.x + cx;
+            } else {
+                runTrail = run.x + cx;
+            }
+        }
+        int lineIndex = 0;
+        while (lineIndex < runs.length && lineOffset[lineIndex + 1] <= run.start) {
+            lineIndex++;
+        }
+        left = Math.min(left, runLead);
+        right = Math.max(right, runTrail);
+        top = Math.min(top, lineY[lineIndex]);
+        bottom = Math.max(bottom, lineY[lineIndex + 1] - lineSpacing);
+    }
+    return new Rectangle(left, top, right - left, bottom - top);
+}
+
+/**
+ * Returns the descent of the receiver.
+ *
+ * @return the descent
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getAscent()
+ * @see #setAscent(int)
+ * @see #setDescent(int)
+ * @see #getLineMetrics(int)
+ */
+public int getDescent () {
+    checkLayout();
+    return descent;
+}
+
+/**
+ * Returns the default font currently being used by the receiver
+ * to draw and measure text.
+ *
+ * @return the receiver's font
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public Font getFont () {
+    checkLayout();
+    return font;
+}
+
+/**
+* Returns the receiver's indent.
+*
+* @return the receiver's indent
+*
+* @exception SWTException <ul>
+*    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+* </ul>
+*
+* @since 3.2
+*/
+public int getIndent () {
+    checkLayout();
+    return indent;
+}
+
+/**
+* Returns the receiver's justification.
+*
+* @return the receiver's justification
+*
+* @exception SWTException <ul>
+*    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+* </ul>
+*
+* @since 3.2
+*/
+public bool getJustify () {
+    checkLayout();
+    return justify;
+}
+
+HFONT getItemFont (StyleItem item) {
+    if (item.fallbackFont !is null) return cast(HFONT) item.fallbackFont;
+    if (item.style !is null && item.style.font !is null) {
+        return item.style.font.handle;
+    }
+    if (this.font !is null) {
+        return this.font.handle;
+    }
+    return device.systemFont.handle;
+}
+
+/**
+ * Returns the embedding level for the specified character offset. The
+ * embedding level is usually used to determine the directionality of a
+ * character in bidirectional text.
+ *
+ * @param offset the character offset
+ * @return the embedding level
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ */
+public int getLevel (int offset) {
+    checkLayout();
+    computeRuns(null);
+    int length = text.length;
+    if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    offset = translateOffset(offset);
+    for (int i=1; i<allRuns.length; i++) {
+        if (allRuns[i].start > offset) {
+            return allRuns[i - 1].analysis.s.uBidiLevel;
+        }
+    }
+    return (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? 1 : 0;
+}
+
+/**
+ * Returns the bounds of the line for the specified line index.
+ *
+ * @param lineIndex the line index
+ * @return the line bounds
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public Rectangle getLineBounds(int lineIndex) {
+    checkLayout();
+    computeRuns(null);
+    if (!(0 <= lineIndex && lineIndex < runs.length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    int x = getLineIndent(lineIndex);
+    int y = lineY[lineIndex];
+    int width = lineWidth[lineIndex];
+    int height = lineY[lineIndex + 1] - y - lineSpacing;
+    return new Rectangle (x, y, width, height);
+}
+
+/**
+ * Returns the receiver's line count. This includes lines caused
+ * by wrapping.
+ *
+ * @return the line count
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getLineCount () {
+    checkLayout();
+    computeRuns(null);
+    return runs.length;
+}
+
+int getLineIndent (int lineIndex) {
+    int lineIndent = 0;
+    if (lineIndex is 0) {
+        lineIndent = indent;
+    } else {
+        StyleItem[] previousLine = runs[lineIndex - 1];
+        StyleItem previousRun = previousLine[previousLine.length - 1];
+        if (previousRun.lineBreak && !previousRun.softBreak) {
+            lineIndent = indent;
+        }
+    }
+    if (wrapWidth !is -1) {
+        bool partialLine = true;
+        if (justify) {
+            StyleItem[] lineRun = runs[lineIndex];
+            if (lineRun[lineRun.length - 1].softBreak) {
+                partialLine = false;
+            }
+        }
+        if (partialLine) {
+            int lineWidth = this.lineWidth[lineIndex] + lineIndent;
+            switch (alignment) {
+                case SWT.CENTER: lineIndent += (wrapWidth - lineWidth) / 2; break;
+                case SWT.RIGHT: lineIndent += wrapWidth - lineWidth; break;
+                default:
+            }
+        }
+    }
+    return lineIndent;
+}
+
+/**
+ * Returns the index of the line that contains the specified
+ * character offset.
+ *
+ * @param offset the character offset
+ * @return the line index
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getLineIndex (int offset) {
+    checkLayout();
+    computeRuns(null);
+    int length = text.length;
+    if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    offset = translateOffset(offset);
+    for (int line=0; line<runs.length; line++) {
+        if (lineOffset[line + 1] > offset) {
+            return line;
+        }
+    }
+    return runs.length - 1;
+}
+
+/**
+ * Returns the font metrics for the specified line index.
+ *
+ * @param lineIndex the line index
+ * @return the font metrics
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public FontMetrics getLineMetrics (int lineIndex) {
+    checkLayout();
+    computeRuns(null);
+    if (!(0 <= lineIndex && lineIndex < runs.length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    auto hDC = device.internal_new_GC(null);
+    auto srcHdc = OS.CreateCompatibleDC(hDC);
+    TEXTMETRIC lptm;
+    OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont.handle);
+    OS.GetTextMetrics(srcHdc, &lptm);
+    OS.DeleteDC(srcHdc);
+    device.internal_dispose_GC(hDC, null);
+
+    int ascent = Math.max(lptm.tmAscent, this.ascent);
+    int descent = Math.max(lptm.tmDescent, this.descent);
+    int leading = lptm.tmInternalLeading;
+    if (text.length !is 0) {
+        StyleItem[] lineRuns = runs[lineIndex];
+        for (int i = 0; i<lineRuns.length; i++) {
+            StyleItem run = lineRuns[i];
+            if (run.ascent > ascent) {
+                ascent = run.ascent;
+                leading = run.leading;
+            }
+            descent = Math.max(descent, run.descent);
+        }
+    }
+    lptm.tmAscent = ascent;
+    lptm.tmDescent = descent;
+    lptm.tmHeight = ascent + descent;
+    lptm.tmInternalLeading = leading;
+    lptm.tmAveCharWidth = 0;
+    return FontMetrics.win32_new(&lptm);
+}
+
+/**
+ * Returns the line offsets.  Each value in the array is the
+ * offset for the first character in a line except for the last
+ * value, which contains the length of the text.
+ *
+ * @return the line offsets
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int[] getLineOffsets () {
+    checkLayout();
+    computeRuns(null);
+    int[] offsets = new int[lineOffset.length];
+    for (int i = 0; i < offsets.length; i++) {
+        offsets[i] = untranslateOffset(lineOffset[i]);
+    }
+    return offsets;
+}
+
+/**
+ * Returns the location for the specified character offset. The
+ * <code>trailing</code> argument indicates whether the offset
+ * corresponds to the leading or trailing edge of the cluster.
+ *
+ * @param offset the character offset
+ * @param trailing the trailing flag
+ * @return the location of the character offset
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getOffset(Point, int[])
+ * @see #getOffset(int, int, int[])
+ */
+public Point getLocation (int offset, bool trailing) {
+    checkLayout();
+    computeRuns(null);
+    int length = text.length;
+    if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    length = segmentsText.length;
+    offset = translateOffset(offset);
+    int line;
+    for (line=0; line<runs.length; line++) {
+        if (lineOffset[line + 1] > offset) break;
+    }
+    line = Math.min(line, runs.length - 1);
+    if (offset is length) {
+        return new Point(getLineIndent(line) + lineWidth[line], lineY[line]);
+    }
+    int low = -1;
+    int high = allRuns.length;
+    while (high - low > 1) {
+        int index = ((high + low) / 2);
+        StyleItem run = allRuns[index];
+        if (run.start > offset) {
+            high = index;
+        } else if (run.start + run.length <= offset) {
+            low = index;
+        } else {
+            int width;
+            if (run.style !is null && run.style.metrics !is null) {
+                GlyphMetrics metrics = run.style.metrics;
+                width = metrics.width * (offset - run.start + (trailing ? 1 : 0));
+            } else if (run.tab) {
+                width = (trailing || (offset is length)) ? run.width : 0;
+            } else {
+                int runOffset = index8to16[offset] - index8to16[run.start];
+                int cChars = index8to16[run.start+run.length] - index8to16[run.start]; // make it wchar
+                int gGlyphs = run.glyphCount;
+                int piX;
+                int* advances = run.justify !is null ? run.justify : run.advances;
+                OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                width = (orientation & SWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
+            }
+            return new Point(run.x + width, lineY[line]);
+        }
+    }
+    return new Point(0, 0);
+}
+
+/**
+ * Returns the next offset for the specified offset and movement
+ * type.  The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
+ * <code>SWT.MOVEMENT_CLUSTER</code>, <code>SWT.MOVEMENT_WORD</code>,
+ * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
+ *
+ * @param offset the start offset
+ * @param movement the movement type
+ * @return the next offset
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getPreviousOffset(int, int)
+ */
+public int getNextOffset (int offset, int movement) {
+    checkLayout();
+    return _getOffset (offset, movement, true);
+}
+
+int _getOffset(int offset, int movement, bool forward) {
+    computeRuns(null);
+    int length = text.length;
+    if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    if (forward && offset is length) return length;
+    if (!forward && offset is 0) return 0;
+    int step = forward ? 1 : -1;
+    if ((movement & SWT.MOVEMENT_CHAR) !is 0) return offset + step;
+    length = segmentsText.length;
+    offset = translateOffset(offset);
+    SCRIPT_LOGATTR* logAttr;
+    SCRIPT_PROPERTIES* properties;
+    int i = forward ? 0 : allRuns.length - 1;
+    offset = validadeOffset(offset, step);
+    do {
+        StyleItem run = allRuns[i];
+        if (run.start <= offset && offset < run.start + run.length) {
+            if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
+            if (run.tab) return untranslateOffset(run.start);
+            properties = device.scripts[run.analysis.eScript];
+            bool isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking;
+            if (isComplex) breakRun(run);
+            while (run.start <= offset && offset < run.start + run.length) {
+                if (isComplex) {
+                    logAttr = run.psla + (index8to16[offset] - index8to16[run.start]);
+                }
+                switch (movement) {
+                    case SWT.MOVEMENT_CLUSTER: {
+                        if (properties.fNeedsCaretInfo) {
+                            if (!logAttr.fInvalid && logAttr.fCharStop) return untranslateOffset(offset);
+                        } else {
+                            return untranslateOffset(offset);
+                        }
+                        break;
+                    }
+                    case SWT.MOVEMENT_WORD_START:
+                    case SWT.MOVEMENT_WORD: {
+                        if (properties.fNeedsWordBreaking) {
+                            if (!logAttr.fInvalid && logAttr.fWordStop) return untranslateOffset(offset);
+                        } else {
+                            if (offset > 0) {
+                                bool letterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset));
+                                bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1));
+                                if (letterOrDigit !is previousLetterOrDigit || !letterOrDigit) {
+                                    if (!Compatibility.isWhitespace(segmentsText[offset..$].firstCodePoint())) {
+                                        return untranslateOffset(offset);
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case SWT.MOVEMENT_WORD_END: {
+                        if (offset > 0) {
+                            bool isLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset));
+                            bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText[offset - 1.. $].firstCodePoint());
+                            if (!isLetterOrDigit && previousLetterOrDigit) {
+                                return untranslateOffset(offset);
+                            }
+                        }
+                        break;
+                    }
+                    default:
+                }
+                offset = validadeOffset(offset, step);
+            }
+        }
+        i += step;
+    } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length);
+    return forward ? text.length : 0;
+}
+
+/**
+ * Returns the character offset for the specified point.
+ * For a typical character, the trailing argument will be filled in to
+ * indicate whether the point is closer to the leading edge (0) or
+ * the trailing edge (1).  When the point is over a cluster composed
+ * of multiple characters, the trailing argument will be filled with the
+ * position of the character in the cluster that is closest to
+ * the point.
+ *
+ * @param point the point
+ * @param trailing the trailing buffer
+ * @return the character offset
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
+ *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getLocation(int, bool)
+ */
+public int getOffset (Point point, int[] trailing) {
+    checkLayout();
+    if (point is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+    return getOffset (point.x, point.y, trailing) ;
+}
+
+/**
+ * Returns the character offset for the specified point.
+ * For a typical character, the trailing argument will be filled in to
+ * indicate whether the point is closer to the leading edge (0) or
+ * the trailing edge (1).  When the point is over a cluster composed
+ * of multiple characters, the trailing argument will be filled with the
+ * position of the character in the cluster that is closest to
+ * the point.
+ *
+ * @param x the x coordinate of the point
+ * @param y the y coordinate of the point
+ * @param trailing the trailing buffer
+ * @return the character offset
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getLocation(int, bool)
+ */
+public int getOffset (int x, int y, int[] trailing) {
+    checkLayout();
+    computeRuns(null);
+    if (trailing !is null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+    int line;
+    int lineCount = runs.length;
+    for (line=0; line<lineCount; line++) {
+        if (lineY[line + 1] > y) break;
+    }
+    line = Math.min(line, runs.length - 1);
+    StyleItem[] lineRuns = runs[line];
+    int lineIndent = getLineIndent(line);
+    if (x >= lineIndent + lineWidth[line]) x = lineIndent + lineWidth[line] - 1;
+    if (x < lineIndent) x = lineIndent;
+    int low = -1;
+    int high = lineRuns.length;
+    while (high - low > 1) {
+        int index = ((high + low) / 2);
+        StyleItem run = lineRuns[index];
+        if (run.x > x) {
+            high = index;
+        } else if (run.x + run.width <= x) {
+            low = index;
+        } else {
+            if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
+            int xRun = x - run.x;
+            if (run.style !is null && run.style.metrics !is null) {
+                GlyphMetrics metrics = run.style.metrics;
+                if (metrics.width > 0) {
+                    if (trailing !is null) {
+                        trailing[0] = (xRun % metrics.width < metrics.width / 2) ? 0 : 1;
+                    }
+                    return untranslateOffset(run.start + xRun / metrics.width);
+                }
+            }
+            if (run.tab) {
+                if (trailing !is null) trailing[0] = x < (run.x + run.width / 2) ? 0 : 1;
+                return untranslateOffset(run.start);
+            }
+            int cChars = index8to16[run.start+run.length] - index8to16[run.start]; // make it wchar
+            int cGlyphs = run.glyphCount;
+            int piCP;
+            int piTrailing;
+            if ((orientation & SWT.RIGHT_TO_LEFT) !is 0) {
+                xRun = run.width - xRun;
+            }
+            int* advances = run.justify !is null ? run.justify : run.advances;
+            OS.ScriptXtoCP(xRun, cChars, cGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piCP, &piTrailing);
+            if (trailing !is null) trailing[0] = piTrailing;
+
+            // SWT: back from codepoints to utf8 index
+            int offsetIndex = segmentsText[ run.start .. $ ].codepointIndexToIndex( piCP );
+            return untranslateOffset(run.start + offsetIndex);
+        }
+    }
+    if (trailing !is null) trailing[0] = 0;
+    return untranslateOffset(lineOffset[line + 1]);
+}
+
+/**
+ * Returns the orientation of the receiver.
+ *
+ * @return the orientation style
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getOrientation () {
+    checkLayout();
+    return orientation;
+}
+
+/**
+ * Returns the previous offset for the specified offset and movement
+ * type.  The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
+ * <code>SWT.MOVEMENT_CLUSTER</code> or <code>SWT.MOVEMENT_WORD</code>,
+ * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
+ *
+ * @param offset the start offset
+ * @param movement the movement type
+ * @return the previous offset
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getNextOffset(int, int)
+ */
+public int getPreviousOffset (int offset, int movement) {
+    checkLayout();
+    return _getOffset (offset, movement, false);
+}
+
+/**
+ * Gets the ranges of text that are associated with a <code>TextStyle</code>.
+ *
+ * @return the ranges, an array of offsets representing the start and end of each
+ * text style.
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getStyles()
+ *
+ * @since 3.2
+ */
+public int[] getRanges () {
+    checkLayout();
+    int[] result = new int[stylesCount * 2];
+    int count = 0;
+    for (int i=0; i<stylesCount - 1; i++) {
+        if (styles[i].style !is null) {
+            result[count++] = styles[i].start;
+            result[count++] = styles[i + 1].start - 1;
+        }
+    }
+    if (count !is result.length) {
+        int[] newResult = new int[count];
+        System.arraycopy(result, 0, newResult, 0, count);
+        result = newResult;
+    }
+    return result;
+}
+
+/**
+ * Returns the text segments offsets of the receiver.
+ *
+ * @return the text segments offsets
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int[] getSegments () {
+    checkLayout();
+    return segments;
+}
+
+void getSegmentsText( out char[] resUtf8, out wchar[] resUtf16 ) {
+
+    void buildIndexTables() { // build the index translation tables.
+        index8to16.length = resUtf8.length + 1;
+        index16to8.length = resUtf16.length + 1;
+
+        int idx8, idx16;
+        while( idx8 < resUtf8.length ){
+            int ate8, ate16;
+            dchar d8 = resUtf8[ idx8 .. $ ].firstCodePoint( ate8 );
+            dchar d16 = resUtf16[ idx16 .. $ ].firstCodePoint( ate16 );
+            assert( d8 is d16 );
+            index16to8[ idx16 .. idx16 +ate16 ] = idx8;
+            index8to16[ idx8  .. idx8  +ate8  ] = idx16;
+            idx8  += ate8;
+            idx16 += ate16;
+        }
+        index16to8[ resUtf16.length ] = resUtf8.length;
+        index8to16[ resUtf8.length  ] = resUtf16.length;
+    }
+
+    if (segments is null) {
+        resUtf8 = text;
+        resUtf16 = wtext;
+        buildIndexTables();
+        return;
+    }
+    int nSegments = segments.length;
+    if (nSegments <= 1) {
+        resUtf8 = text;
+        resUtf16 = wtext;
+        buildIndexTables();
+        return;
+    }
+    int length_ = text.length;
+    int wlength_ = wtext.length;
+    if (length_ is 0) {
+        resUtf8 = text;
+        resUtf16 = wtext;
+        buildIndexTables();
+        return;
+    }
+    if (nSegments is 2) {
+        if (segments[0] is 0 && segments[1] is length_) {
+            resUtf8 = text;
+            resUtf16 = wtext;
+            buildIndexTables();
+            return;
+        }
+    }
+    {
+        char[] oldChars = text;
+        // SWT: MARK is now 3 chars long
+        String separator = orientation is SWT.RIGHT_TO_LEFT ? STR_RTL_MARK : STR_LTR_MARK;
+        assert( separator.length is MARK_SIZE );
+        char[] newChars = new char[length_ + nSegments*MARK_SIZE];
+
+        int charCount = 0, segmentCount = 0;
+        while (charCount < length_) {
+            if (segmentCount < nSegments && charCount is segments[segmentCount]) {
+                int start = charCount + (segmentCount*MARK_SIZE);
+                newChars[ start .. start + MARK_SIZE ] = separator;
+                segmentCount++;
+            } else {
+                newChars[charCount + (segmentCount*MARK_SIZE)] = oldChars[charCount];
+                charCount++;
+            }
+        }
+        if (segmentCount < nSegments) {
+            segments[segmentCount] = charCount;
+            int start = charCount + (segmentCount*MARK_SIZE);
+            newChars[ start .. start + MARK_SIZE ] = separator;
+            segmentCount++;
+        }
+        resUtf8 = newChars[ 0 .. Math.min(charCount + (segmentCount*MARK_SIZE), newChars.length)];
+    }
+    // now for the wide chars
+    {
+        wchar[] oldWChars = wtext;
+        wchar[] wseparator = orientation is SWT.RIGHT_TO_LEFT ? WSTR_RTL_MARK : WSTR_LTR_MARK;
+        assert( wseparator.length is 1 );
+        wchar[] newWChars = new wchar[wlength_ + nSegments];
+
+        int charCount = 0, segmentCount = 0;
+        while (charCount < wlength_) {
+            if (segmentCount < nSegments && charCount is wsegments[segmentCount]) {
+                int start = charCount + (segmentCount*WMARK_SIZE);
+                newWChars[ start .. start + WMARK_SIZE ] = wseparator;
+                segmentCount++;
+            } else {
+                newWChars[charCount + (segmentCount*WMARK_SIZE)] = oldWChars[charCount];
+                charCount++;
+            }
+        }
+        if (segmentCount < nSegments) {
+            wsegments[segmentCount] = charCount;
+            int start = charCount + (segmentCount*WMARK_SIZE);
+            newWChars[ start .. start + WMARK_SIZE ] = wseparator;
+            segmentCount++;
+        }
+        resUtf16 = newWChars[ 0 .. Math.min(charCount + (segmentCount*WMARK_SIZE), newWChars.length)];
+    }
+    buildIndexTables();
+}
+
+/**
+ * Returns the line spacing of the receiver.
+ *
+ * @return the line spacing
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getSpacing () {
+    checkLayout();
+    return lineSpacing;
+}
+
+/**
+ * Gets the style of the receiver at the specified character offset.
+ *
+ * @param offset the text offset
+ * @return the style or <code>null</code> if not set
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public TextStyle getStyle (int offset) {
+    checkLayout();
+    int length = text.length;
+    if (!(0 <= offset && offset < length)) SWT.error(SWT.ERROR_INVALID_RANGE);
+    for (int i=1; i<stylesCount; i++) {
+        if (styles[i].start > offset) {
+            return styles[i - 1].style;
+        }
+    }
+    return null;
+}
+
+/**
+ * Gets all styles of the receiver.
+ *
+ * @return the styles
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #getRanges()
+ *
+ * @since 3.2
+ */
+public TextStyle[] getStyles () {
+    checkLayout();
+    TextStyle[] result = new TextStyle[stylesCount];
+    int count = 0;
+    for (int i=0; i<stylesCount; i++) {
+        if (styles[i].style !is null) {
+            result[count++] = styles[i].style;
+        }
+    }
+    if (count !is result.length) {
+        TextStyle[] newResult = new TextStyle[count];
+        System.arraycopy(result, 0, newResult, 0, count);
+        result = newResult;
+    }
+    return result;
+}
+
+/**
+ * Returns the tab list of the receiver.
+ *
+ * @return the tab list
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int[] getTabs () {
+    checkLayout();
+    return tabs;
+}
+
+/**
+ * Gets the receiver's text, which will be an empty
+ * string if it has never been set.
+ *
+ * @return the receiver's text
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public String getText () {
+    checkLayout();
+    return text;
+}
+
+/**
+ * Returns the width of the receiver.
+ *
+ * @return the width
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int getWidth () {
+    checkLayout();
+    return wrapWidth;
+}
+
+/**
+ * Returns <code>true</code> if the text layout has been disposed,
+ * and <code>false</code> otherwise.
+ * <p>
+ * This method gets the dispose state for the text layout.
+ * When a text layout has been disposed, it is an error to
+ * invoke any other method using the text layout.
+ * </p>
+ *
+ * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise
+ */
+override public bool isDisposed () {
+    return device is null;
+}
+
+/*
+ *  Itemize the receiver text
+ */
+StyleItem[] itemize () {
+    // SWT: itemize is the process of finding changes in direction
+    getSegmentsText(segmentsText, segmentsWText );
+    int length = segmentsText.length;
+    SCRIPT_CONTROL scriptControl;
+    SCRIPT_STATE scriptState;
+    final int MAX_ITEM = length + 1;
+
+    if ((orientation & SWT.RIGHT_TO_LEFT) !is 0) {
+        scriptState.uBidiLevel = 1;
+        scriptState.fArabicNumContext = true;
+        SCRIPT_DIGITSUBSTITUTE psds;
+        OS.ScriptRecordDigitSubstitution(OS.LOCALE_USER_DEFAULT, &psds);
+        OS.ScriptApplyDigitSubstitution(&psds, &scriptControl, &scriptState);
+    }
+
+    auto hHeap = OS.GetProcessHeap();
+    auto pItems = cast(SCRIPT_ITEM*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof);
+    if (pItems is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    int pcItems;
+    wchar[] chars = segmentsWText;
+    OS.ScriptItemize(chars.ptr, chars.length, MAX_ITEM, &scriptControl, &scriptState, pItems, &pcItems);
+//  if (hr is E_OUTOFMEMORY) //TODO handle it
+    // SWT pcItems is not inclusive the trailing item
+
+    StyleItem[] runs = merge(pItems, pcItems);
+    OS.HeapFree(hHeap, 0, pItems);
+    return runs;
+}
+
+/*
+ *  Merge styles ranges and script items
+ */
+StyleItem[] merge (SCRIPT_ITEM* items, int itemCount) {
+    if (styles.length > stylesCount) {
+        StyleItem[] newStyles = new StyleItem[stylesCount];
+        System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+        styles = newStyles;
+    }
+    int count = 0, start = 0, end = segmentsText.length, itemIndex = 0, styleIndex = 0;
+    StyleItem[] runs = new StyleItem[itemCount + stylesCount];
+    SCRIPT_ITEM* scriptItem;
+    bool linkBefore = false;
+    while (start < end) {
+        StyleItem item = new StyleItem();
+        item.start = start;
+        item.style = styles[styleIndex].style;
+        runs[count++] = item;
+        scriptItem = items + itemIndex;
+        item.analysis = scriptItem.a;
+        if (linkBefore) {
+            item.analysis.fLinkBefore = true;
+            linkBefore = false;
+        }
+        //scriptItem.a = new SCRIPT_ANALYSIS();
+        scriptItem = items + (itemIndex + 1);
+        int itemLimit = index16to8[scriptItem.iCharPos];
+        int styleLimit = translateOffset(styles[styleIndex + 1].start);
+        if (styleLimit <= itemLimit) {
+            styleIndex++;
+            start = styleLimit;
+            if (start < itemLimit && 0 < start && start < end) {
+                dchar pChar = segmentsText[ segmentsText.getAbsoluteCodePointOffset(start, -1) ..$].firstCodePoint();
+                dchar tChar = segmentsText[start     ..$].firstCodePoint();
+                if (Compatibility.isLetter(pChar) && Compatibility.isLetter(tChar)) {
+                    item.analysis.fLinkAfter = true;
+                    linkBefore = true;
+                }
+            }
+        }
+        if (itemLimit <= styleLimit) {
+            itemIndex++;
+            start = itemLimit;
+        }
+        item.length = start - item.start;
+    }
+    StyleItem item = new StyleItem();
+    item.start = end;
+    scriptItem = items + itemCount;
+    item.analysis = scriptItem.a;
+    runs[count++] = item;
+    if (runs.length !is count) {
+        StyleItem[] result = new StyleItem[count];
+        System.arraycopy(runs, 0, result, 0, count);
+        return result;
+    }
+    return runs;
+}
+
+/*
+ *  Reorder the run
+ */
+StyleItem[] reorder (StyleItem[] runs, bool terminate) {
+    int length_ = runs.length;
+    if (length_ <= 1) return runs;
+    ubyte[] bidiLevels = new ubyte[length_];
+    for (int i=0; i<length_; i++) {
+        bidiLevels[i] = cast(byte)(runs[i].analysis.s.uBidiLevel & 0x1F);
+    }
+    /*
+    * Feature in Windows.  If the orientation is RTL Uniscribe will
+    * resolve the level of line breaks to 1, this can cause the line
+    * break to be reorder to the middle of the line. The fix is to set
+    * the level to zero to prevent it to be reordered.
+    */
+    StyleItem lastRun = runs[length_ - 1];
+    if (lastRun.lineBreak && !lastRun.softBreak) {
+        bidiLevels[length_ - 1] = 0;
+    }
+    int[] log2vis = new int[length_];
+    OS.ScriptLayout(length_, bidiLevels.ptr, null, log2vis.ptr);
+    StyleItem[] result = new StyleItem[length_];
+    for (int i=0; i<length_; i++) {
+        result[log2vis[i]] = runs[i];
+    }
+    if ((orientation & SWT.RIGHT_TO_LEFT) !is 0) {
+        if (terminate) length_--;
+        for (int i = 0; i < length_ / 2 ; i++) {
+            StyleItem tmp = result[i];
+            result[i] = result[length_ - i - 1];
+            result[length_ - i - 1] = tmp;
+        }
+    }
+    return result;
+}
+
+/**
+ * Sets the text alignment for the receiver. The alignment controls
+ * how a line of text is positioned horizontally. The argument should
+ * be one of <code>SWT.LEFT</code>, <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>.
+ * <p>
+ * The default alignment is <code>SWT.LEFT</code>.  Note that the receiver's
+ * width must be set in order to use <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>
+ * alignment.
+ * </p>
+ *
+ * @param alignment the new alignment
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setWidth(int)
+ */
+public void setAlignment (int alignment) {
+    checkLayout();
+    int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT;
+    alignment &= mask;
+    if (alignment is 0) return;
+    if ((alignment & SWT.LEFT) !is 0) alignment = SWT.LEFT;
+    if ((alignment & SWT.RIGHT) !is 0) alignment = SWT.RIGHT;
+    if (this.alignment is alignment) return;
+    freeRuns();
+    this.alignment = alignment;
+}
+
+/**
+ * Sets the ascent of the receiver. The ascent is distance in pixels
+ * from the baseline to the top of the line and it is applied to all
+ * lines. The default value is <code>-1</code> which means that the
+ * ascent is calculated from the line fonts.
+ *
+ * @param ascent the new ascent
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setDescent(int)
+ * @see #getLineMetrics(int)
+ */
+public void setAscent(int ascent) {
+    checkLayout();
+    if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (this.ascent is ascent) return;
+    freeRuns();
+    this.ascent = ascent;
+}
+
+/**
+ * Sets the descent of the receiver. The descent is distance in pixels
+ * from the baseline to the bottom of the line and it is applied to all
+ * lines. The default value is <code>-1</code> which means that the
+ * descent is calculated from the line fonts.
+ *
+ * @param descent the new descent
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setAscent(int)
+ * @see #getLineMetrics(int)
+ */
+public void setDescent(int descent) {
+    checkLayout();
+    if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (this.descent is descent) return;
+    freeRuns();
+    this.descent = descent;
+}
+
+/**
+ * Sets the default font which will be used by the receiver
+ * to draw and measure text. If the
+ * argument is null, then a default font appropriate
+ * for the platform will be used instead. Note that a text
+ * style can override the default font.
+ *
+ * @param font the new font for the receiver, or null to indicate a default font
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setFont (Font font) {
+    checkLayout();
+    if (font !is null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    Font oldFont = this.font;
+    if (oldFont is font) return;
+    this.font = font;
+    if (oldFont !is null && oldFont.opEquals(font)) return;
+    freeRuns();
+}
+
+/**
+ * Sets the indent of the receiver. This indent it applied of the first line of
+ * each paragraph.
+ *
+ * @param indent new indent
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setIndent (int indent) {
+    checkLayout();
+    if (indent < 0) return;
+    if (this.indent is indent) return;
+    freeRuns();
+    this.indent = indent;
+}
+
+/**
+ * Sets the justification of the receiver. Note that the receiver's
+ * width must be set in order to use justification.
+ *
+ * @param justify new justify
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @since 3.2
+ */
+public void setJustify (bool justify) {
+    checkLayout();
+    if (this.justify is justify) return;
+    freeRuns();
+    this.justify = justify;
+}
+
+/**
+ * Sets the orientation of the receiver, which must be one
+ * of <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation new orientation style
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setOrientation (int orientation) {
+    checkLayout();
+    int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
+    orientation &= mask;
+    if (orientation is 0) return;
+    if ((orientation & SWT.LEFT_TO_RIGHT) !is 0) orientation = SWT.LEFT_TO_RIGHT;
+    if (this.orientation is orientation) return;
+    this.orientation = orientation;
+    freeRuns();
+}
+
+/**
+ * Sets the offsets of the receiver's text segments. Text segments are used to
+ * override the default behaviour of the bidirectional algorithm.
+ * Bidirectional reordering can happen within a text segment but not
+ * between two adjacent segments.
+ * <p>
+ * Each text segment is determined by two consecutive offsets in the
+ * <code>segments</code> arrays. The first element of the array should
+ * always be zero and the last one should always be equals to length of
+ * the text.
+ * </p>
+ *
+ * @param segments the text segments offset
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setSegments(int[] segments) {
+    checkLayout();
+    if (this.segments is null && segments is null) return;
+    if (this.segments !is null && segments !is null) {
+        if (this.segments.length is segments.length) {
+            int i;
+            for (i = 0; i <segments.length; i++) {
+                if (this.segments[i] !is segments[i]) break;
+            }
+            if (i is segments.length) return;
+        }
+    }
+    freeRuns();
+    this.segments  = segments.dup;
+
+    // SWT: create the wsegments ...
+    this.wsegments.length = segments.length;
+    uint index8, index16;
+    uint segIndex = 1;
+    while(index8 < text.length ){
+        int ate8;
+        int ate16;
+        dchar d8 = text[ index8 .. $ ].firstCodePoint( ate8 );
+        dchar d16 = wtext[ index16 .. $ ].firstCodePoint( ate16 );
+        assert( d8 is d16 );
+        assert( ate8 > 0 );
+        assert( ate16 > 0 );
+        index8 += ate8;
+        index16 += ate16;
+        if( segments[segIndex] is index8 ){
+            wsegments[segIndex] = index16;
+        }
+    }
+    assert( index16 is wtext.length );
+    assert( segIndex is segments.length );
+}
+
+/**
+ * Sets the line spacing of the receiver.  The line spacing
+ * is the space left between lines.
+ *
+ * @param spacing the new line spacing
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setSpacing (int spacing) {
+    checkLayout();
+    if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (this.lineSpacing is spacing) return;
+    freeRuns();
+    this.lineSpacing = spacing;
+}
+
+/**
+ * Sets the style of the receiver for the specified range.  Styles previously
+ * set for that range will be overwritten.  The start and end offsets are
+ * inclusive and will be clamped if out of range.
+ *
+ * @param style the style
+ * @param start the start offset
+ * @param end the end offset
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setStyle (TextStyle style, int start, int end) {
+    checkLayout();
+    int length = text.length;
+    if (length is 0) return;
+    if (start > end) return;
+    start = Math.min(Math.max(0, start), length - 1);
+    end = Math.min(Math.max(0, end), length - 1);
+    int low = -1;
+    int high = stylesCount;
+    while (high - low > 1) {
+        int index = (high + low) / 2;
+        if (styles[index + 1].start > start) {
+            high = index;
+        } else {
+            low = index;
+        }
+    }
+    if (0 <= high && high < stylesCount) {
+        StyleItem item = styles[high];
+        if (item.start is start && styles[high + 1].start - 1 is end) {
+            if (style is null) {
+                if (item.style is null) return;
+            } else {
+                if (style.opEquals(item.style)) return;
+            }
+        }
+    }
+    freeRuns();
+    int modifyStart = high;
+    int modifyEnd = modifyStart;
+    while (modifyEnd < stylesCount) {
+        if (styles[modifyEnd + 1].start > end) break;
+        modifyEnd++;
+    }
+    if (modifyStart is modifyEnd) {
+        int styleStart = styles[modifyStart].start;
+        int styleEnd = styles[modifyEnd + 1].start - 1;
+        if (styleStart is start && styleEnd is end) {
+            styles[modifyStart].style = style;
+            return;
+        }
+        if (styleStart !is start && styleEnd !is end) {
+            int newLength = stylesCount + 2;
+            if (newLength > styles.length) {
+                int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
+                StyleItem[] newStyles = new StyleItem[newSize];
+                System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+                styles = newStyles;
+            }
+            System.arraycopy(styles, modifyEnd + 1, styles, modifyEnd + 3, stylesCount - modifyEnd - 1);
+            StyleItem item = new StyleItem();
+            item.start = start;
+            item.style = style;
+            styles[modifyStart + 1] = item;
+            item = new StyleItem();
+            item.start = end + 1;
+            item.style = styles[modifyStart].style;
+            styles[modifyStart + 2] = item;
+            stylesCount = newLength;
+            return;
+        }
+    }
+    if (start is styles[modifyStart].start) modifyStart--;
+    if (end is styles[modifyEnd + 1].start - 1) modifyEnd++;
+    int newLength = stylesCount + 1 - (modifyEnd - modifyStart - 1);
+    if (newLength > styles.length) {
+        int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
+        StyleItem[] newStyles = new StyleItem[newSize];
+        System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+        styles = newStyles;
+    }
+    System.arraycopy(styles, modifyEnd, styles, modifyStart + 2, stylesCount - modifyEnd);
+    StyleItem item = new StyleItem();
+    item.start = start;
+    item.style = style;
+    styles[modifyStart + 1] = item;
+    styles[modifyStart + 2].start = end + 1;
+    stylesCount = newLength;
+}
+
+/**
+ * Sets the receiver's tab list. Each value in the tab list specifies
+ * the space in pixels from the origin of the text layout to the respective
+ * tab stop.  The last tab stop width is repeated continuously.
+ *
+ * @param tabs the new tab list
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setTabs (int[] tabs) {
+    checkLayout();
+    if (this.tabs is null && tabs is null) return;
+    if (this.tabs !is null && tabs !is null) {
+        if (this.tabs.length is tabs.length) {
+            int i;
+            for (i = 0; i <tabs.length; i++) {
+                if (this.tabs[i] !is tabs[i]) break;
+            }
+            if (i is tabs.length) return;
+        }
+    }
+    freeRuns();
+    this.tabs = tabs;
+}
+
+/**
+ * Sets the receiver's text.
+ *
+ * @param text the new text
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_NULL_ARGUMENT - if the text is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setText (String text) {
+    checkLayout();
+
+    //PORTING_CHANGE: allow null argument
+    //if (text is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    if (text.equals(this.text)) return;
+    freeRuns();
+    this.text  = text.dup;
+    this.wtext = StrToWCHARs(text);
+    styles = new StyleItem[2];
+    styles[0] = new StyleItem();
+    styles[1] = new StyleItem();
+    styles[1].start = text.length;
+    stylesCount = 2;
+}
+
+/**
+ * Sets the line width of the receiver, which determines how
+ * text should be wrapped and aligned. The default value is
+ * <code>-1</code> which means wrapping is disabled.
+ *
+ * @param width the new width
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li>
+ * </ul>
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setAlignment(int)
+ */
+public void setWidth (int width) {
+    checkLayout();
+    if (width < -1 || width is 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+    if (this.wrapWidth is width) return;
+    freeRuns();
+    this.wrapWidth = width;
+}
+
+bool shape (HDC hdc, StyleItem run, wchar[] wchars, int[] glyphCount, int maxGlyphs, SCRIPT_PROPERTIES* sp) {
+    bool useCMAPcheck = !sp.fComplex && !run.analysis.fNoGlyphIndex;
+    if (useCMAPcheck) {
+        scope ushort[] glyphs = new ushort[wchars.length];
+        if (OS.ScriptGetCMap(hdc, run.psc, wchars.ptr, wchars.length, 0, glyphs.ptr) !is OS.S_OK) {
+            if (run.psc !is null) {
+                OS.ScriptFreeCache(run.psc);
+                glyphCount[0] = 0;
+                int[1] one = 1;
+                *cast(int*)run.psc = 0;
+            }
+            return false;
+        }
+    }
+    auto hr = OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount.ptr);
+    run.glyphCount = glyphCount[0];
+    if (useCMAPcheck) return true;
+    
+    if (hr !is OS.USP_E_SCRIPT_NOT_IN_FONT) {
+        if (run.analysis.fNoGlyphIndex) return true;
+        SCRIPT_FONTPROPERTIES fp;
+        fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof;
+        OS.ScriptGetFontProperties(hdc, run.psc, &fp);
+        ushort[] glyphs = run.glyphs[ 0 .. glyphCount[0] ];
+        int i;
+        for (i = 0; i < glyphs.length; i++) {
+            if (glyphs[i] is fp.wgDefault) break;
+        }
+        if (i is glyphs.length) return true;
+    }
+    if (run.psc !is null) {
+        OS.ScriptFreeCache(run.psc);
+        glyphCount[0] = 0;
+        *cast(int*)run.psc = 0;
+    }
+    run.glyphCount = 0;
+    return false;
+}
+
+
+/*
+ * Generate glyphs for one Run.
+ */
+void shape (HDC hdc, StyleItem run) {
+    int[1] buffer;
+    wchar[] wchars = segmentsWText[ index8to16[ run.start ] .. index8to16[ run.start + run.length ] ];
+    int maxGlyphs = (wchars.length * 3 / 2) + 16;
+    auto hHeap = OS.GetProcessHeap();
+    run.glyphs = cast(ushort*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
+    if (run.glyphs is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    run.clusters = cast(WORD*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
+    if (run.clusters is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    run.visAttrs = cast(SCRIPT_VISATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF);
+    if (run.visAttrs is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    run.psc = cast(SCRIPT_CACHE*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, (void*).sizeof);
+    if (run.psc is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    short script = run.analysis.eScript;
+    SCRIPT_PROPERTIES sp = *device.scripts[script];
+    bool shapeSucceed = shape(hdc, run, wchars, buffer,  maxGlyphs, &sp);
+int res;
+    if (!shapeSucceed) {
+        auto hFont = OS.GetCurrentObject(hdc, OS.OBJ_FONT);
+        auto ssa = cast(SCRIPT_STRING_ANALYSIS*) OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_STRING_ANALYSIS.sizeof);
+        auto metaFileDc = OS.CreateEnhMetaFile(hdc, null, null, null);
+        auto oldMetaFont = OS.SelectObject(metaFileDc, hFont);
+        int flags = OS.SSA_METAFILE | OS.SSA_FALLBACK | OS.SSA_GLYPHS | OS.SSA_LINK;
+        if (OS.ScriptStringAnalyse(metaFileDc, wchars.ptr, wchars.length, 0, -1, flags, 0, null, null, null, null, null, ssa) is OS.S_OK) {
+            OS.ScriptStringOut(*ssa, 0, 0, 0, null, 0, 0, false);
+            OS.ScriptStringFree(ssa);
+        }
+        OS.HeapFree(hHeap, 0, ssa);
+        OS.SelectObject(metaFileDc, oldMetaFont);
+        auto metaFile = OS.CloseEnhMetaFile(metaFileDc);
+        static extern(Windows) int metaFileEnumProc (HDC hDC, HANDLETABLE* table, ENHMETARECORD* record, int nObj, LPARAM lpData) {
+            EMREXTCREATEFONTINDIRECTW* emr_ = cast(EMREXTCREATEFONTINDIRECTW*)lpData;
+            OS.MoveMemory(&emr_.emr, record, EMR.sizeof);
+            switch (emr_.emr.iType) {
+                case OS.EMR_EXTCREATEFONTINDIRECTW:
+                    OS.MoveMemory(emr_, record, EMREXTCREATEFONTINDIRECTW.sizeof);
+                    break;
+                case OS.EMR_EXTTEXTOUTW:
+                    return 0;
+                default:
+            }
+            return 1;
+        }
+
+        EMREXTCREATEFONTINDIRECTW emr;
+        OS.EnumEnhMetaFile(null, metaFile, &metaFileEnumProc, &emr, null);
+        res = OS.DeleteEnhMetaFile(metaFile);
+        assert( res !is 0 );
+
+        auto newFont = OS.CreateFontIndirectW(&emr.elfw.elfLogFont);
+        assert( newFont !is null );
+
+        OS.SelectObject(hdc, newFont);
+        if ((shapeSucceed = shape(hdc, run, wchars, buffer,  maxGlyphs, &sp)) is true ) {
+            run.fallbackFont = newFont;
+        }
+        if (!shapeSucceed) {
+            if (!sp.fComplex) {
+                run.analysis.fNoGlyphIndex = true;
+                if ((shapeSucceed = shape(hdc, run, wchars, buffer,  maxGlyphs, &sp)) is true ) {
+                    run.fallbackFont = newFont;
+                } else {
+                    run.analysis.fNoGlyphIndex = false;
+                }
+            }
+        }
+        if (!shapeSucceed) {
+            if (mLangFontLink2 !is null) {
+                HANDLE hNewFont;
+                int dwCodePages, cchCodePages;
+                /* GetStrCodePages() */
+                OS.VtblCall(4, mLangFontLink2, cast(int)wchars.ptr, wchars.length, 0, cast(int)&dwCodePages, cast(int)&cchCodePages);
+                /* MapFont() */
+                if (OS.VtblCall(10, mLangFontLink2, cast(int)hdc, dwCodePages, cast(int)wchars[0], cast(int)&hNewFont) is OS.S_OK) {
+                    LOGFONT logFont;
+                    OS.GetObject( hNewFont, LOGFONT.sizeof, &logFont );
+                    /* ReleaseFont() */
+                    OS.VtblCall(8, mLangFontLink2, cast(int)hNewFont);
+                    auto mLangFont = OS.CreateFontIndirect(&logFont);
+                    auto oldFont = OS.SelectObject(hdc, mLangFont);
+                    if ((shapeSucceed = shape(hdc, run, wchars, buffer,  maxGlyphs, &sp)) is true ) {
+                        run.fallbackFont = mLangFont;
+                    } else {
+                        OS.SelectObject(hdc, oldFont);
+                        OS.DeleteObject(mLangFont);
+                    }
+                }
+            }
+        }
+        if (!shapeSucceed) OS.SelectObject(hdc, hFont);
+        if (newFont !is run.fallbackFont) OS.DeleteObject(newFont);
+    }
+
+    if (!shapeSucceed) {
+        /*
+        * Shape Failed.
+        * Give up and shape the run with the default font.
+        * Missing glyphs typically will be represent as black boxes in the text.
+        */
+        OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer.ptr);
+        run.glyphCount = buffer[0];
+    }
+    int[3] abc;
+    run.advances = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4);
+    if (run.advances is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    run.goffsets = cast(GOFFSET*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF);
+    if (run.goffsets is null) SWT.error(SWT.ERROR_NO_HANDLES);
+    OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, &run.analysis, run.advances, run.goffsets, cast(ABC*)abc.ptr);
+    run.width = abc[0] + abc[1] + abc[2];
+    TextStyle style = run.style;
+    if (style !is null) {
+        OUTLINETEXTMETRIC* lotm = null;
+        if (style.underline || style.strikeout) {
+            lotm = new OUTLINETEXTMETRIC();
+            if (OS.GetOutlineTextMetrics(hdc, OUTLINETEXTMETRIC.sizeof, lotm) is 0) {
+                lotm = null;
+            }
+        }
+        if (style.metrics !is null) {
+            GlyphMetrics metrics = style.metrics;
+            /*
+             *  Bug in Windows, on a Japanese machine, Uniscribe returns glyphcount
+             *  equals zero for FFFC (possibly other unicode code points), the fix
+             *  is to make sure the glyph is at least one pixel wide.
+             */
+            run.width = metrics.width * Math.max (1, run.glyphCount);
+            run.ascent = metrics.ascent;
+            run.descent = metrics.descent;
+            run.leading = 0;
+        } else {
+            TEXTMETRIC lptm;
+            if (lotm !is null) {
+                lptm = lotm.otmTextMetrics;
+            } else {
+                lptm = TEXTMETRIC.init;
+                OS.GetTextMetrics(hdc, &lptm);
+            }
+            run.ascent = lptm.tmAscent;
+            run.descent = lptm.tmDescent;
+            run.leading = lptm.tmInternalLeading;
+        }
+        if (lotm !is null) {
+            run.underlinePos = lotm.otmsUnderscorePosition;
+            run.underlineThickness = Math.max(1, lotm.otmsUnderscoreSize);
+            run.strikeoutPos = lotm.otmsStrikeoutPosition;
+            run.strikeoutThickness = Math.max(1, lotm.otmsStrikeoutSize);
+        } else {
+            run.underlinePos = 1;
+            run.underlineThickness = 1;
+            run.strikeoutPos = run.ascent / 2;
+            run.strikeoutThickness = 1;
+        }
+        run.ascent += style.rise;
+        run.descent -= style.rise;
+    } else {
+        TEXTMETRIC lptm;
+        OS.GetTextMetrics(hdc, &lptm);
+        run.ascent = lptm.tmAscent;
+        run.descent = lptm.tmDescent;
+        run.leading = lptm.tmInternalLeading;
+    }
+}
+
+int validadeOffset(int offset, int step) {
+    offset = segmentsText.toAbsoluteCodePointStartOffset( offset );
+    offset += segmentsText.getRelativeCodePointOffset( offset, step );
+    if (segments !is null && segments.length > 2) {
+        for (int i = 0; i < segments.length; i++) {
+            if (translateOffset(segments[i]) - 1 is offset) {
+                offset += step;
+                break;
+            }
+        }
+    }
+    return offset;
+}
+
+/**
+ * Returns a string containing a concise, human-readable
+ * description of the receiver.
+ *
+ * @return a string representation of the receiver
+ */
+override public String toString () {
+    if (isDisposed()) return "TextLayout {*DISPOSED*}";
+    return "TextLayout {}";
+}
+
+int translateOffset(int offset) {
+    if (segments is null) return offset;
+    int nSegments = segments.length;
+    if (nSegments <= 1) return offset;
+    int length = text.length;
+    if (length is 0) return offset;
+    if (nSegments is 2) {
+        if (segments[0] is 0 && segments[1] is length) return offset;
+    }
+    for (int i = 0; i < nSegments && offset - i >= segments[i]; i++) {
+        offset+=MARK_SIZE;
+    }
+    return offset;
+}
+
+int untranslateOffset(int offset) {
+    if (segments is null) return offset;
+    int nSegments = segments.length;
+    if (nSegments <= 1) return offset;
+    int length = text.length;
+    if (length is 0) return offset;
+    if (nSegments is 2) {
+        if (segments[0] is 0 && segments[1] is length) return offset;
+    }
+    for (int i = 0; i < nSegments && offset > segments[i]; i++) {
+        offset-=MARK_SIZE;
+    }
+    return offset;
+}
+}