changeset 29:16332d261df7

TextLayout not yet compiling
author Frank Benoit <benoit@tionex.de>
date Mon, 28 Jan 2008 00:27:56 +0100
parents b868bfa989cd
children 1e14cb29290a
files dwt/graphics/TextLayout.d
diffstat 1 files changed, 2520 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/graphics/TextLayout.d	Mon Jan 28 00:27:56 2008 +0100
@@ -0,0 +1,2520 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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
+ *******************************************************************************/
+module dwt.graphics.TextLayout;
+
+import dwt.DWT;
+import dwt.DWTException;
+import dwt.internal.Compatibility;
+import dwt.internal.gdip.Gdip;
+import dwt.internal.win32.OS;
+
+import dwt.graphics.Color;
+import dwt.graphics.Device;
+import dwt.graphics.Font;
+import dwt.graphics.FontMetrics;
+import dwt.graphics.GC;
+import dwt.graphics.GCData;
+import dwt.graphics.GlyphMetrics;
+import dwt.graphics.Point;
+import dwt.graphics.Rectangle;
+import dwt.graphics.Region;
+import dwt.graphics.Resource;
+import dwt.graphics.TextStyle;
+
+import tango.text.convert.Format;
+import dwt.dwthelper.utils;
+
+/**
+ * <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>
+ *
+ *  @since 3.0
+ */
+public final class TextLayout : Resource {
+    Font font;
+    char[] text, segmentsText;
+    int lineSpacing;
+    int ascent, descent;
+    int alignment;
+    int wrapWidth;
+    int orientation;
+    int indent;
+    bool justify;
+    int[] tabs;
+    int[] segments;
+    StyleItem[] styles;
+
+    StyleItem[] allRuns;
+    StyleItem[][] runs;
+    int[] lineOffset, lineY, lineWidth;
+    int mLangFontLink2;
+
+    static const wchar LTR_MARK = '\u200E', RTL_MARK = '\u200F';
+    static const int SCRIPT_VISATTR_SIZEOF = 2;
+    static const int GOFFSET_SIZEOF = 8;
+    static byte[16] CLSID_CMultiLanguage;
+    static byte[16] IID_IMLangFontLink2;
+    static this() {
+        OS.IIDFromString("{275c23e2-3747-11d0-9fea-00aa003f8646}\0".toCharArray().ptr, CLSID_CMultiLanguage.ptr);
+        OS.IIDFromString("{DCCFC162-2B38-11d2-B7EC-00C04F8F5D9A}\0".toCharArray().ptr, IID_IMLangFontLink2.ptr);
+    }
+
+    class StyleItem {
+        TextStyle style;
+        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;
+        int* goffsets;
+        int* width;
+        int* ascent;
+        int* descent;
+        int* leading;
+        int* x;
+
+        /* Justify info (malloc during computeRuns) */
+        int* justify;
+
+        /* ScriptBreak */
+        SCRIPT_LOGATTR* psla;
+
+        int* 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) {
+            if (mLangFontLink2 !is 0) {
+                /* ReleaseFont() */
+                OS.VtblCall(8, mLangFontLink2, fallbackFont);
+            }
+            fallbackFont = null;
+        }
+        width = ascent = descent = x = 0;
+        lineBreak = softBreak = false;
+    }
+    public char[] 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) {
+    if (device is null) device = Device.getDevice();
+    if (device is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    this.device = device;
+    wrapWidth = ascent = descent = -1;
+    lineSpacing = 0;
+    orientation = DWT.LEFT_TO_RIGHT;
+    styles = new StyleItem[2];
+    styles[0] = new StyleItem();
+    styles[1] = new StyleItem();
+    text = ""; //$NON-NLS-1$
+    int[] ppv = new int[1];
+    OS.OleInitialize(0);
+    if (OS.CoCreateInstance(CLSID_CMultiLanguage, 0, OS.CLSCTX_INPROC_SERVER, IID_IMLangFontLink2, ppv) is OS.S_OK) {
+        mLangFontLink2 = ppv[0];
+    }
+    if (device.tracking) device.new_Object(this);
+}
+
+void breakRun(StyleItem run) {
+    if (run.psla !is 0) return;
+    char[] chars = new char[run.length];
+    segmentsText.getChars(run.start, run.start + run.length, chars, 0);
+    int hHeap = OS.GetProcessHeap();
+    run.psla = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length);
+    if (run.psla is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    OS.ScriptBreak(chars, chars.length, run.analysis, run.psla);
+}
+
+void checkItem (int hDC, StyleItem item) {
+    if (item.fallbackFont !is 0) {
+        /*
+        * Feature in Windows. The fallback font returned by the MLang service
+        * can be disposed by some other client running in the same thread.
+        * For example, disposing a Browser widget internally releases all fonts
+        * in the MLang cache. The fix is to use GetObject() to detect if the
+        * font was disposed and reshape the run.
+        */
+        LOGFONT logFont;
+        if (OS.GetObject(item.fallbackFont, LOGFONT.sizeof, &logFont) is 0) {
+            item.free();
+            OS.SelectObject(hDC, getItemFont(item));
+            shape(hDC, item);
+        }
+    }
+}
+
+void checkLayout () {
+    if (isDisposed()) DWT.error(DWT.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;
+    int hDC = gc !is null ? gc.handle : device.internal_new_GC(null);
+    int 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 = new SCRIPT_LOGATTR();
+    SCRIPT_PROPERTIES properties = new SCRIPT_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();
+                        i++;
+                    }
+                    break;
+                }
+            }
+        }
+        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);
+            }
+            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) {
+                    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) {
+                        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);
+                            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) {
+                i = firstIndice;
+                run = allRuns[i];
+                start = Math.max(1, firstStart);
+            }
+            breakRun(run);
+            while (start < run.length) {
+                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 = run.analysis;
+                run.free();
+                run.length = start;
+                OS.SelectObject(srcHdc, getItemFont(run));
+                shape (srcHdc, run);
+                OS.SelectObject(srcHdc, getItemFont(newRun));
+                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;
+                    }
+                }
+                int 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 = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4);
+                        if (item.justify is 0) DWT.error(DWT.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 0) OS.DeleteDC(srcHdc);
+    if (gc is null) device.internal_dispose_GC(hDC, null);
+}
+
+/**
+ * Disposes of the operating system resources associated with
+ * the text layout. Applications must dispose of all allocated text layouts.
+ */
+public void dispose () {
+    if (device is null) return;
+    freeRuns();
+    font = null;
+    text = null;
+    segmentsText = null;
+    tabs = null;
+    styles = null;
+    runs = null;
+    lineOffset = null;
+    lineY = null;
+    lineWidth = null;
+    if (mLangFontLink2 !is 0) {
+        /* Release() */
+        OS.VtblCall(2, mLangFontLink2);
+        mLangFontLink2 = 0;
+    }
+    OS.OleUninitialize();
+    if (device.tracking) device.dispose_Object(this);
+    device = 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
+ *
+ * @exception DWTException <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 DWTException <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>DWT.DELIMITER_SELECTION</code>
+ * or <code>DWT.FULL_SELECTION</code> to specify the selection behavior on all lines except
+ * for the last line, and can also include <code>DWT.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 DWTException <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) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    if (gc.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    if (selectionForeground !is null && selectionForeground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    if (selectionBackground !is null && selectionBackground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    int length = text.length();
+    if (length is 0 && flags is 0) return;
+    int hdc = gc.handle;
+    Rectangle clip = gc.getClipping();
+    GCData data = gc.data;
+    int gdipGraphics = data.gdipGraphics;
+    int foreground = data.foreground;
+    int alpha = data.alpha;
+    bool gdip = gdipGraphics !is 0 && (alpha !is 0xFF || data.foregroundPattern !is null);
+    int clipRgn = 0;
+    float[] lpXform = null;
+    Rect gdipRect = new Rect();
+    if (gdipGraphics !is 0 && !gdip) {
+        int matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0);
+        if (matrix is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+        Gdip.Graphics_GetTransform(gdipGraphics, matrix);
+        int 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);
+        }
+        Gdip.Matrix_delete(matrix);
+        if ((data.style & DWT.MIRRORED) !is 0 && lpXform !is null) {
+            gdip = true;
+            lpXform = null;
+        } else {
+            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone);
+            int 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);
+        }
+    }
+    int foregroundBrush = 0, state = 0;
+    if (gdip) {
+        gc.checkGC(GC.FOREGROUND);
+        foregroundBrush = gc.getFgBrush();
+    } else {
+        state = OS.SaveDC(hdc);
+        if ((data.style & DWT.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, lpXform);
+        }
+        if (clipRgn !is 0) {
+            OS.SelectClipRgn(hdc, clipRgn);
+            OS.DeleteObject(clipRgn);
+        }
+    }
+    bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1;
+    if (hasSelection || (flags & DWT.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(DWT.COLOR_LIST_SELECTION_TEXT);
+        if (selectionBackground is null) selectionBackground = device.getSystemColor(DWT.COLOR_LIST_SELECTION);
+        selectionStart = translateOffset(selectionStart);
+        selectionEnd = translateOffset(selectionEnd);
+    }
+    RECT rect = new RECT();
+    int selBrush = 0, selPen = 0, selBrushFg = 0;
+    if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) {
+        if (gdip) {
+            int bg = selectionBackground.handle;
+            int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
+            int color = Gdip.Color_new(argb);
+            selBrush = Gdip.SolidBrush_new(color);
+            Gdip.Color_delete(color);
+            int fg = selectionForeground.handle;
+            argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+            color = Gdip.Color_new(argb);
+            selBrushFg = Gdip.SolidBrush_new(color);
+            selPen = Gdip.Pen_new(selBrushFg, 1);
+            Gdip.Color_delete(color);
+        } else {
+            selBrush = OS.CreateSolidBrush(selectionBackground.handle);
+            selPen = OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle);
+        }
+    }
+    int offset = (orientation & DWT.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];
+        if (flags !is 0 && (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0)) {
+            bool extents = false;
+            if (line is runs.length - 1 && (flags & DWT.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 = run.start + run.length - 1;
+                    if (selectionStart <= endOffset && endOffset < selectionEnd && (flags & DWT.FULL_SELECTION) !is 0) {
+                        extents = true;
+                    }
+                }
+            }
+            if (extents) {
+                int width;
+                if ((flags & DWT.FULL_SELECTION) !is 0) {
+                    width = OS.IsWin95 ? 0x7FFF : 0x6FFFFFF;
+                } else {
+                    width = (lineHeight - lineSpacing) / 3;
+                }
+                if (gdip) {
+                    Gdip.Graphics_FillRectangle(gdipGraphics, selBrush, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing);
+                } else {
+                    OS.SelectObject(hdc, selBrush);
+                    OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing, OS.PATCOPY);
+                }
+            }
+        }
+        if (drawX > clip.x + clip.width) continue;
+        if (drawX + lineWidth[line] < clip.x) continue;
+        int baseline = Math.max(0, this.ascent);
+        for (int i = 0; i < lineRuns.length; i++) {
+            baseline = Math.max(baseline, lineRuns[i].ascent);
+        }
+        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 = run.start + run.length - 1;
+                    bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
+                    if (fullSelection) {
+                        if (gdip) {
+                            Gdip.Graphics_FillRectangle(gdipGraphics, selBrush, drawX, drawY, run.width, lineHeight - lineSpacing);
+                        } else {
+                            OS.SelectObject(hdc, selBrush);
+                            OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight - lineSpacing, OS.PATCOPY);
+                        }
+                    } else {
+                        if (run.style !is null && run.style.background !is null) {
+                            int bg = run.style.background.handle;
+                            int drawRunY = drawY + (baseline - run.ascent);
+                            if (gdip) {
+                                int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
+                                int color = Gdip.Color_new(argb);
+                                int brush = Gdip.SolidBrush_new(color);
+                                Gdip.Graphics_FillRectangle(gdipGraphics, brush, drawX, drawRunY, run.width, run.ascent + run.descent);
+                                Gdip.Color_delete(color);
+                                Gdip.SolidBrush_delete(brush);
+                            } else {
+                                int hBrush = OS.CreateSolidBrush (bg);
+                                int oldBrush = OS.SelectObject(hdc, hBrush);
+                                OS.PatBlt(hdc, drawX, drawRunY, run.width, run.ascent + run.descent, OS.PATCOPY);
+                                OS.SelectObject(hdc, oldBrush);
+                                OS.DeleteObject(hBrush);
+                            }
+                        }
+                        bool partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd);
+                        if (partialSelection) {
+                            int selStart = Math.max(selectionStart, run.start) - run.start;
+                            int selEnd = Math.min(selectionEnd, end) - run.start;
+                            int cChars = run.length;
+                            int gGlyphs = run.glyphCount;
+                            int[] piX = new int[1];
+                            int advances = run.justify !is 0 ? run.justify : run.advances;
+                            OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                            int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0];
+                            rect.left = drawX + runX;
+                            rect.top = drawY;
+                            OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                            runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0];
+                            rect.right = drawX + runX;
+                            rect.bottom = drawY + lineHeight - lineSpacing;
+                            if (gdip) {
+                                Gdip.Graphics_FillRectangle(gdipGraphics, 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;
+        }
+        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);
+                    checkItem(hdc, run);
+                    OS.SelectObject(hdc, getItemFont(run));
+                    int drawRunY = drawY + (baseline - run.ascent);
+                    if (partialSelection) {
+                        int selStart = Math.max(selectionStart, run.start) - run.start;
+                        int selEnd = Math.min(selectionEnd, end) - run.start;
+                        int cChars = run.length;
+                        int gGlyphs = run.glyphCount;
+                        int[] piX = new int[1];
+                        int advances = run.justify !is 0 ? run.justify : run.advances;
+                        OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                        int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0];
+                        rect.left = drawX + runX;
+                        rect.top = drawY;
+                        OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                        runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0];
+                        rect.right = drawX + runX;
+                        rect.bottom = drawY + lineHeight;
+                    }
+                    if (gdip) {
+                        OS.BeginPath(hdc);
+                        OS.ScriptTextOut(hdc, run.psc, drawX, drawRunY, 0, null, run.analysis , 0, 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];
+                        byte[] types = new byte[count];
+                        OS.GetPath(hdc, points, types, 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;
+                            }
+                            if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath;
+                            types[typeIndex] = cast(byte)newType;
+                        }
+                        int path = Gdip.GraphicsPath_new(points, types, count, Gdip.FillModeAlternate);
+                        if (path is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+                        int brush = foregroundBrush;
+                        if (fullSelection) {
+                            brush = selBrushFg;
+                        } else {
+                            if (run.style !is null && run.style.foreground !is null) {
+                                int fg = run.style.foreground.handle;
+                                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                                int color = Gdip.Color_new(argb);
+                                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;
+                        }
+                        Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias);
+                        int gstate2 = 0;
+                        if ((data.style & DWT.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 & DWT.MIRRORED) !is 0) {
+                            Gdip.Graphics_Restore(gdipGraphics, gstate2);
+                        }
+                        Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias);
+                        if (run.style !is null && (run.style.underline || run.style.strikeout)) {
+                            int newPen = hasSelection ? selPen : Gdip.Pen_new(brush, 1);
+                            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone);
+                            if (run.style.underline) {
+                                int underlineY = drawY + baseline + 1 - run.style.rise;
+                                Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, underlineY, drawX + run.width, underlineY);
+                            }
+                            if (run.style.strikeout) {
+                                int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2;
+                                Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, strikeoutY, drawX + run.width, strikeoutY);
+                            }
+                            if (newPen !is selPen) Gdip.Pen_delete(newPen);
+                            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf);
+                        }
+                        if (partialSelection) {
+                            Gdip.Graphics_Restore(gdipGraphics, gstate);
+                            gstate = Gdip.Graphics_Save(gdipGraphics);
+                            Gdip.Graphics_SetClip(gdipGraphics, gdipRect, Gdip.CombineModeIntersect);
+                            Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias);
+                            Gdip.Graphics_FillPath(gdipGraphics, selBrushFg, path);
+                            Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias);
+                            if (run.style !is null && (run.style.underline || run.style.strikeout)) {
+                                Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone);
+                                if (run.style.underline) {
+                                    int underlineY = drawY + baseline + 1 - run.style.rise;
+                                    Gdip.Graphics_DrawLine(gdipGraphics, selPen, rect.left, underlineY, rect.right, underlineY);
+                                }
+                                if (run.style.strikeout) {
+                                    int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2;
+                                    Gdip.Graphics_DrawLine(gdipGraphics, selPen, rect.left, strikeoutY, rect.right, strikeoutY);
+                                }
+                                Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf);
+                            }
+                            Gdip.Graphics_Restore(gdipGraphics, gstate);
+                        }
+                        Gdip.GraphicsPath_delete(path);
+                        if (brush !is selBrushFg && brush !is foregroundBrush) Gdip.SolidBrush_delete(brush);
+                    }  else {
+                        int 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 , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
+                        if (run.style !is null && (run.style.underline || run.style.strikeout)) {
+                            int newPen = hasSelection && fg is selectionForeground.handle ? selPen : OS.CreatePen(OS.PS_SOLID, 1, fg);
+                            int oldPen = OS.SelectObject(hdc, newPen);
+                            if (run.style.underline) {
+                                int underlineY = drawY + baseline + 1 - run.style.rise;
+                                OS.MoveToEx(hdc, drawX, underlineY, 0);
+                                OS.LineTo(hdc, drawX + run.width, underlineY);
+                            }
+                            if (run.style.strikeout) {
+                                int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2;
+                                OS.MoveToEx(hdc, drawX, strikeoutY, 0);
+                                OS.LineTo(hdc, drawX + run.width, strikeoutY);
+                            }
+                            OS.SelectObject(hdc, oldPen);
+                            if (!hasSelection || fg !is selectionForeground.handle) OS.DeleteObject(newPen);
+                        }
+                        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 , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
+                            if (run.style !is null && (run.style.underline || run.style.strikeout)) {
+                                int oldPen = OS.SelectObject(hdc, selPen);
+                                if (run.style.underline) {
+                                    int underlineY = drawY + baseline + 1 - run.style.rise;
+                                    OS.MoveToEx(hdc, rect.left, underlineY, 0);
+                                    OS.LineTo(hdc, rect.right, underlineY);
+                                }
+                                if (run.style.strikeout) {
+                                    int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2;
+                                    OS.MoveToEx(hdc, rect.left, strikeoutY, 0);
+                                    OS.LineTo(hdc, rect.right, strikeoutY);
+                                }
+                                OS.SelectObject(hdc, oldPen);
+                            }
+                        }
+                    }
+                }
+            }
+            drawX += run.width;
+        }
+    }
+    if (gdip) {
+        if (selBrush !is 0) Gdip.SolidBrush_delete(selBrush);
+        if (selBrushFg !is 0) Gdip.SolidBrush_delete(selBrushFg);
+        if (selPen !is 0) Gdip.Pen_delete(selPen);
+    } else {
+        OS.RestoreDC(hdc, state);
+        if (gdipGraphics !is 0) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc);
+        if (selBrush !is 0) OS.DeleteObject (selBrush);
+        if (selPen !is 0) OS.DeleteObject (selPen);
+    }
+}
+
+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;
+}
+
+/**
+ * Returns the receiver's horizontal text alignment, which will be one
+ * of <code>DWT.LEFT</code>, <code>DWT.CENTER</code> or
+ * <code>DWT.RIGHT</code>.
+ *
+ * @return the alignment used to positioned text horizontally
+ *
+ * @exception DWTException <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 DWTException <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.
+ *
+ * @return the bounds of the receiver
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+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 DWTException <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 & DWT.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 * (start - run.start);
+            } else if (!run.tab) {
+                int[] piX = new int[1];
+                int advances = run.justify !is 0 ? run.justify : run.advances;
+                OS.ScriptCPtoX(start - run.start, false, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                cx = isRTL ? run.width - piX[0] : piX[0];
+            }
+            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 * (end - run.start + 1);
+            } else if (!run.tab) {
+                int[] piX = new int[1];
+                int advances = run.justify !is 0 ? run.justify : run.advances;
+                OS.ScriptCPtoX(end - run.start, true, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                cx = isRTL ? run.width - piX[0] : piX[0];
+            }
+            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 DWTException <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 DWTException <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 DWTException <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 DWTException <ul>
+*    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+* </ul>
+*
+* @since 3.2
+*/
+public bool getJustify () {
+    checkLayout();
+    return justify;
+}
+
+int getItemFont (StyleItem item) {
+    if (item.fallbackFont !is 0) return 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;
+}
+
+/**
+ * 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 DWTException <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)) DWT.error(DWT.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 & DWT.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 DWTException <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)) DWT.error(DWT.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 DWTException <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 DWT.CENTER: lineIndent += (wrapWidth - lineWidth) / 2; break;
+                case DWT.RIGHT: lineIndent += wrapWidth - lineWidth; break;
+            }
+        }
+    }
+    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 DWTException <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)) DWT.error(DWT.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 DWTException <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)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    int hDC = device.internal_new_GC(null);
+    int srcHdc = OS.CreateCompatibleDC(hDC);
+    TEXTMETRIC lptm;
+    OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont);
+    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 DWTException <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 DWTException <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)) DWT.error(DWT.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);
+    StyleItem[] lineRuns = runs[line];
+    Point result = null;
+    if (offset is length) {
+        result = new Point(lineWidth[line], lineY[line]);
+    } else {
+        int width = 0;
+        for (int i=0; i<lineRuns.length; i++) {
+            StyleItem run = lineRuns[i];
+            int end = run.start + run.length;
+            if (run.start <= offset && offset < end) {
+                if (run.style !is null && run.style.metrics !is null) {
+                    GlyphMetrics metrics = run.style.metrics;
+                    width += metrics.width * (offset - run.start + (trailing ? 1 : 0));
+                    result = new Point(width, lineY[line]);
+                } else if (run.tab) {
+                    if (trailing || (offset is length)) width += run.width;
+                    result = new Point(width, lineY[line]);
+                } else {
+                    int runOffset = offset - run.start;
+                    int cChars = run.length;
+                    int gGlyphs = run.glyphCount;
+                    int[] piX = new int[1];
+                    int advances = run.justify !is 0 ? run.justify : run.advances;
+                    OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
+                    if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) {
+                        result = new Point(width + (run.width - piX[0]), lineY[line]);
+                    } else {
+                        result = new Point(width + piX[0], lineY[line]);
+                    }
+                }
+                break;
+            }
+            width += run.width;
+        }
+    }
+    if (result is null) result = new Point(0, 0);
+    result.x += getLineIndent(line);
+    return result;
+}
+
+/**
+ * Returns the next offset for the specified offset and movement
+ * type.  The movement is one of <code>DWT.MOVEMENT_CHAR</code>,
+ * <code>DWT.MOVEMENT_CLUSTER</code>, <code>DWT.MOVEMENT_WORD</code>,
+ * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.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 DWTException <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)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    if (forward && offset is length) return length;
+    if (!forward && offset is 0) return 0;
+    int step = forward ? 1 : -1;
+    if ((movement & DWT.MOVEMENT_CHAR) !is 0) return offset + step;
+    length = segmentsText.length();
+    offset = translateOffset(offset);
+    SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR();
+    SCRIPT_PROPERTIES properties = new  SCRIPT_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);
+            OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
+            bool isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking;
+            if (isComplex) breakRun(run);
+            while (run.start <= offset && offset < run.start + run.length) {
+                if (isComplex) {
+                    OS.MoveMemory(logAttr, run.psla + ((offset - run.start) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
+                }
+                switch (movement) {
+                    case DWT.MOVEMENT_CLUSTER: {
+                        if (properties.fNeedsCaretInfo) {
+                            if (!logAttr.fInvalid && logAttr.fCharStop) return untranslateOffset(offset);
+                        } else {
+                            return untranslateOffset(offset);
+                        }
+                        break;
+                    }
+                    case DWT.MOVEMENT_WORD_START:
+                    case DWT.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.charAt(offset))) {
+                                        return untranslateOffset(offset);
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case DWT.MOVEMENT_WORD_END: {
+                        if (offset > 0) {
+                            bool isLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset));
+                            bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1));
+                            if (!isLetterOrDigit && previousLetterOrDigit) {
+                                return untranslateOffset(offset);
+                            }
+                        }
+                        break;
+                    }
+                }
+                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 DWTException <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) DWT.error (DWT.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 DWTException <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) DWT.error(DWT.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);
+    x -= getLineIndent(line);
+    StyleItem[] lineRuns = runs[line];
+    if (x >= lineWidth[line]) x = lineWidth[line] - 1;
+    if (x < 0) x = 0;
+    int width = 0;
+    for (int i = 0; i < lineRuns.length; i++) {
+        StyleItem run = lineRuns[i];
+        if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
+        if (width + run.width > x) {
+            int xRun = x - width;
+            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 < (width + run.width / 2) ? 0 : 1;
+                return untranslateOffset(run.start);
+            }
+            int cChars = run.length;
+            int cGlyphs = run.glyphCount;
+            int[] piCP = new int[1];
+            int[] piTrailing = new int[1];
+            if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) {
+                xRun = run.width - xRun;
+            }
+            int advances = run.justify !is 0 ? 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[0];
+            return untranslateOffset(run.start + piCP[0]);
+        }
+        width += run.width;
+    }
+    if (trailing !is null) trailing[0] = 0;
+    return untranslateOffset(lineOffset[line + 1]);
+}
+
+/**
+ * Returns the orientation of the receiver.
+ *
+ * @return the orientation style
+ *
+ * @exception DWTException <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>DWT.MOVEMENT_CHAR</code>,
+ * <code>DWT.MOVEMENT_CLUSTER</code> or <code>DWT.MOVEMENT_WORD</code>,
+ * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.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 DWTException <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 DWTException <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[styles.length * 2];
+    int count = 0;
+    for (int i=0; i<styles.length - 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 DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public int[] getSegments () {
+    checkLayout();
+    return segments;
+}
+
+char[] getSegmentsText() {
+    if (segments is null) return text;
+    int nSegments = segments.length;
+    if (nSegments <= 1) return text;
+    int length = text.length();
+    if (length is 0) return text;
+    if (nSegments is 2) {
+        if (segments[0] is 0 && segments[1] is length) return text;
+    }
+    char[] oldChars = new char[length];
+    text.getChars(0, length, oldChars, 0);
+    char[] newChars = new char[length + nSegments];
+    int charCount = 0, segmentCount = 0;
+    char separator = orientation is DWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK;
+    while (charCount < length) {
+        if (segmentCount < nSegments && charCount is segments[segmentCount]) {
+            newChars[charCount + segmentCount++] = separator;
+        } else {
+            newChars[charCount + segmentCount] = oldChars[charCount++];
+        }
+    }
+    if (segmentCount < nSegments) {
+        segments[segmentCount] = charCount;
+        newChars[charCount + segmentCount++] = separator;
+    }
+    return new char[](newChars, 0, Math.min(charCount + segmentCount, newChars.length));
+}
+
+/**
+ * Returns the line spacing of the receiver.
+ *
+ * @return the line spacing
+ *
+ * @exception DWTException <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 DWTException <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)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    for (int i=1; i<styles.length; i++) {
+        if (styles[i].start > offset) {
+            return styles[i - 1].style;
+        }
+    }
+    return null;
+}
+
+/**
+ * Gets all styles of the receiver.
+ *
+ * @return the styles
+ *
+ * @exception DWTException <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[styles.length];
+    int count = 0;
+    for (int i=0; i<styles.length; 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 DWTException <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 DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public char[] getText () {
+    checkLayout();
+    return text;
+}
+
+/**
+ * Returns the width of the receiver.
+ *
+ * @return the width
+ *
+ * @exception DWTException <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
+ */
+public bool isDisposed () {
+    return device is null;
+}
+
+/*
+ *  Itemize the receiver text
+ */
+StyleItem[] itemize () {
+    segmentsText = getSegmentsText();
+    int length = segmentsText.length();
+    SCRIPT_CONTROL scriptControl = new SCRIPT_CONTROL();
+    SCRIPT_STATE scriptState = new SCRIPT_STATE();
+    final int MAX_ITEM = length + 1;
+
+    if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) {
+        scriptState.uBidiLevel = 1;
+        scriptState.fArabicNumContext = true;
+        SCRIPT_DIGITSUBSTITUTE psds = new SCRIPT_DIGITSUBSTITUTE();
+        OS.ScriptRecordDigitSubstitution(OS.LOCALE_USER_DEFAULT, psds);
+        OS.ScriptApplyDigitSubstitution(psds, scriptControl, scriptState);
+    }
+
+    int hHeap = OS.GetProcessHeap();
+    int pItems = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof);
+    if (pItems is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    int[] pcItems = new int[1];
+    char[] chars = new char[length];
+    segmentsText.getChars(0, length, chars, 0);
+    OS.ScriptItemize(chars, length, MAX_ITEM, scriptControl, scriptState, pItems, pcItems);
+//  if (hr is E_OUTOFMEMORY) //TODO handle it
+
+    StyleItem[] runs = merge(pItems, pcItems[0]);
+    OS.HeapFree(hHeap, 0, pItems);
+    return runs;
+}
+
+/*
+ *  Merge styles ranges and script items
+ */
+StyleItem[] merge (int items, int itemCount) {
+    int count = 0, start = 0, end = segmentsText.length(), itemIndex = 0, styleIndex = 0;
+    StyleItem[] runs = new StyleItem[itemCount + styles.length];
+    SCRIPT_ITEM scriptItem = new SCRIPT_ITEM();
+    bool linkBefore = false;
+    while (start < end) {
+        StyleItem item = new StyleItem();
+        item.start = start;
+        item.style = styles[styleIndex].style;
+        runs[count++] = item;
+        OS.MoveMemory(scriptItem, items + itemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
+        item.analysis = scriptItem.a;
+        if (linkBefore) {
+            item.analysis.fLinkBefore = true;
+            linkBefore = false;
+        }
+        scriptItem.a = new SCRIPT_ANALYSIS();
+        OS.MoveMemory(scriptItem, items + (itemIndex + 1) * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
+        int itemLimit = scriptItem.iCharPos;
+        int styleLimit = translateOffset(styles[styleIndex + 1].start);
+        if (styleLimit <= itemLimit) {
+            styleIndex++;
+            start = styleLimit;
+            if (start < itemLimit && 0 < start && start < end) {
+                char pChar = segmentsText.charAt(start - 1);
+                char tChar = segmentsText.charAt(start);
+                if (!Compatibility.isWhitespace(pChar) && !Compatibility.isWhitespace(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;
+    OS.MoveMemory(scriptItem, items + itemCount * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
+    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;
+    byte[] bidiLevels = new byte[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, null, log2vis);
+    StyleItem[] result = new StyleItem[length];
+    for (int i=0; i<length; i++) {
+        result[log2vis[i]] = runs[i];
+    }
+    if ((orientation & DWT.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>DWT.LEFT</code>, <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>.
+ * <p>
+ * The default alignment is <code>DWT.LEFT</code>.  Note that the receiver's
+ * width must be set in order to use <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>
+ * alignment.
+ * </p>
+ *
+ * @param alignment the new alignment
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ *
+ * @see #setWidth(int)
+ */
+public void setAlignment (int alignment) {
+    checkLayout();
+    int mask = DWT.LEFT | DWT.CENTER | DWT.RIGHT;
+    alignment &= mask;
+    if (alignment is 0) return;
+    if ((alignment & DWT.LEFT) !is 0) alignment = DWT.LEFT;
+    if ((alignment & DWT.RIGHT) !is 0) alignment = DWT.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 DWTException <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) DWT.error(DWT.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 DWTException <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) DWT.error(DWT.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 DWTException <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()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    if (this.font is font) return;
+    if (font !is null && font.equals(this.font)) return;
+    freeRuns();
+    this.font = font;
+}
+
+/**
+ * Sets the indent of the receiver. This indent it applied of the first line of
+ * each paragraph.
+ *
+ * @param indent new indent
+ *
+ * @exception DWTException <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 DWTException <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>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation new orientation style
+ *
+ * @exception DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setOrientation (int orientation) {
+    checkLayout();
+    int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
+    orientation &= mask;
+    if (orientation is 0) return;
+    if ((orientation & DWT.LEFT_TO_RIGHT) !is 0) orientation = DWT.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 DWTException <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;
+}
+
+/**
+ * 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 DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setSpacing (int spacing) {
+    checkLayout();
+    if (spacing < 0) DWT.error(DWT.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 DWTException <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 = styles.length;
+    while (high - low > 1) {
+        int index = (high + low) / 2;
+        if (styles[index + 1].start > start) {
+            high = index;
+        } else {
+            low = index;
+        }
+    }
+    if (0 <= high && high < styles.length) {
+        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.equals(item.style)) return;
+            }
+        }
+    }
+    freeRuns();
+    int modifyStart = high;
+    int modifyEnd = modifyStart;
+    while (modifyEnd < styles.length) {
+        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) {
+            StyleItem[] newStyles = new StyleItem[styles.length + 2];
+            System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
+            StyleItem item = new StyleItem();
+            item.start = start;
+            item.style = style;
+            newStyles[modifyStart + 1] = item;
+            item = new StyleItem();
+            item.start = end + 1;
+            item.style = styles[modifyStart].style;
+            newStyles[modifyStart + 2] = item;
+            System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1);
+            styles = newStyles;
+            return;
+        }
+    }
+    if (start is styles[modifyStart].start) modifyStart--;
+    if (end is styles[modifyEnd + 1].start - 1) modifyEnd++;
+    int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1);
+    StyleItem[] newStyles = new StyleItem[newLength];
+    System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
+    StyleItem item = new StyleItem();
+    item.start = start;
+    item.style = style;
+    newStyles[modifyStart + 1] = item;
+    styles[modifyEnd].start = end + 1;
+    System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd);
+    styles = newStyles;
+}
+
+/**
+ * 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 DWTException <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 DWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ * </ul>
+ */
+public void setText (char[] text) {
+    checkLayout();
+    if (text is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
+    if (text.equals(this.text)) return;
+    freeRuns();
+    this.text = text;
+    styles = new StyleItem[2];
+    styles[0] = new StyleItem();
+    styles[1] = new StyleItem();
+    styles[1].start = text.length();
+}
+
+/**
+ * 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 DWTException <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) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+    if (this.wrapWidth is width) return;
+    freeRuns();
+    this.wrapWidth = width;
+}
+
+bool shape (int hdc, StyleItem run, char[] chars, int[] glyphCount, int maxGlyphs) {
+    int hr = OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount);
+    run.glyphCount = glyphCount[0];
+    if (hr !is OS.USP_E_SCRIPT_NOT_IN_FONT) {
+        SCRIPT_FONTPROPERTIES fp = new SCRIPT_FONTPROPERTIES ();
+        fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof;
+        OS.ScriptGetFontProperties(hdc, run.psc, fp);
+        short[] glyphs = new short[glyphCount[0]];
+        OS.MoveMemory(glyphs, run.glyphs, glyphs.length * 2);
+        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 0) {
+        OS.ScriptFreeCache(run.psc);
+        glyphCount[0] = 0;
+        OS.MoveMemory(run.psc, glyphCount, 4);
+    }
+    run.glyphCount = 0;
+    return false;
+}
+
+/*
+ * Generate glyphs for one Run.
+ */
+void shape (HDC hdc, StyleItem run) {
+    int[] buffer = new int[1];
+    char[] chars = new char[run.length];
+    segmentsText.getChars(run.start, run.start + run.length, chars, 0);
+    int maxGlyphs = (chars.length * 3 / 2) + 16;
+    int hHeap = OS.GetProcessHeap();
+    run.glyphs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
+    if (run.glyphs is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    run.clusters = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
+    if (run.clusters is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    run.visAttrs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF);
+    if (run.visAttrs is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    run.psc = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, 4);
+    if (run.psc is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    if (!shape(hdc, run, chars, buffer,  maxGlyphs)) {
+        if (mLangFontLink2 !is 0) {
+            int[] dwCodePages = new int[1];
+            int[] cchCodePages = new int[1];
+            /* GetStrCodePages() */
+            OS.VtblCall(4, mLangFontLink2, chars, chars.length, 0, dwCodePages, cchCodePages);
+            int[] hNewFont = new int[1];
+            /* MapFont() */
+            if (OS.VtblCall(10, mLangFontLink2, hdc, dwCodePages[0], chars[0], hNewFont) is OS.S_OK) {
+                int hFont = OS.SelectObject(hdc, hNewFont[0]);
+                if (shape(hdc, run, chars, buffer, maxGlyphs)) {
+                    run.fallbackFont = hNewFont[0];
+                } else {
+                    /* ReleaseFont() */
+                    OS.VtblCall(8, mLangFontLink2, hNewFont[0]);
+                    OS.SelectObject(hdc, hFont);
+                    SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES();
+                    OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
+                    if (properties.fPrivateUseArea) {
+                        run.analysis.fNoGlyphIndex = true;
+                    }
+                    OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer);
+                    run.glyphCount = buffer[0];
+                }
+            }
+        }
+    }
+    int[] abc = new int[3];
+    run.advances = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4);
+    if (run.advances is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    run.goffsets = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF);
+    if (run.goffsets is 0) DWT.error(DWT.ERROR_NO_HANDLES);
+    OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, run.analysis, run.advances, run.goffsets, abc);
+    if (run.style !is null && run.style.metrics !is null) {
+        GlyphMetrics metrics = run.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 {
+        run.width = abc[0] + abc[1] + abc[2];
+        TEXTMETRIC lptm;
+        OS.GetTextMetrics(hdc, &lptm);
+        run.ascent = lptm.tmAscent;
+        run.descent = lptm.tmDescent;
+        run.leading = lptm.tmInternalLeading;
+    }
+    if (run.style !is null) {
+        run.ascent += run.style.rise;
+        run.descent -= +run.style.rise;
+    }
+}
+
+int validadeOffset(int offset, int step) {
+    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
+ */
+public char[] 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++;
+    }
+    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--;
+    }
+    return offset;
+}
+}