diff dwt/graphics/TextLayout.d @ 213:36f5cb12e1a2

Update to SWT 3.4M7
author Frank Benoit <benoit@tionex.de>
date Sat, 17 May 2008 17:34:28 +0200
parents ab60f3309436
children a8fed3e56433
line wrap: on
line diff
--- a/dwt/graphics/TextLayout.d	Mon May 05 00:12:38 2008 +0200
+++ b/dwt/graphics/TextLayout.d	Sat May 17 17:34:28 2008 +0200
@@ -16,6 +16,7 @@
 import dwt.DWTException;
 import dwt.internal.Compatibility;
 import dwt.internal.gdip.Gdip;
+
 import dwt.internal.win32.OS;
 
 import dwt.graphics.Color;
@@ -51,6 +52,8 @@
  *  @since 3.0
  */
 public final class TextLayout : Resource {
+    alias Resource.init_ init_;
+
     Font font;
     String text, segmentsText;
     int lineSpacing;
@@ -63,6 +66,7 @@
     int[] tabs;
     int[] segments;
     StyleItem[] styles;
+    int stylesCount;
 
     StyleItem[] allRuns;
     StyleItem[][] runs;
@@ -89,6 +93,11 @@
         }
     }
 
+    /* IME has a copy of these constants */
+    static const int UNDERLINE_IME_DOT = 1 << 16;
+    static const int UNDERLINE_IME_DASH = 2 << 16;
+    static const int UNDERLINE_IME_THICK = 3 << 16;
+
     class StyleItem {
         TextStyle style;
         int start, length;
@@ -112,6 +121,8 @@
         int descent;
         int leading;
         int x;
+        int underlinePos, underlineThickness;
+        int strikeoutPos, strikeoutThickness;
 
         /* Justify info (malloc during computeRuns) */
         int* justify;
@@ -191,22 +202,21 @@
  */
 public this (Device device) {
     static_this();
-    if (device is null) device = Device.getDevice();
-    if (device is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
-    this.device = device;
+    super(device);
     wrapWidth = ascent = descent = -1;
     lineSpacing = 0;
     orientation = DWT.LEFT_TO_RIGHT;
     styles = new StyleItem[2];
     styles[0] = new StyleItem();
     styles[1] = new StyleItem();
+    stylesCount = 2;
     text = ""; //$NON-NLS-1$
     void* ppv;
     OS.OleInitialize(null);
     if (OS.CoCreateInstance(CLSID_CMultiLanguage.ptr, null, OS.CLSCTX_INPROC_SERVER, IID_IMLangFontLink2.ptr, cast(void*)&ppv) is OS.S_OK) {
         mLangFontLink2 = ppv;
     }
-    if (device.tracking) device.new_Object(this);
+    init_();
 }
 
 void breakRun(StyleItem run) {
@@ -349,9 +359,15 @@
             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);
+                if (lineWidth is wrapWidth && firstIndice > 0) {
+                    i = firstIndice - 1;
+                    run = allRuns[i];
+                    start = run.length;
+                } else {
+                    i = firstIndice;
+                    run = allRuns[i];
+                    start = Math.max(1, firstStart);
+                }
             }
             breakRun(run);
             while (start < run.length) {
@@ -471,12 +487,7 @@
     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.
- */
-override public void dispose () {
-    if (device is null) return;
+void destroy () {
     freeRuns();
     font = null;
     text = null;
@@ -493,8 +504,6 @@
         mLangFontLink2 = null;
     }
     OS.OleUninitialize();
-    if (device.tracking) device.dispose_Object(this);
-    device = null;
 }
 
 /**
@@ -595,7 +604,7 @@
         Gdip.Matrix_delete(identity_);
         if (!Gdip.Matrix_IsIdentity(matrix)) {
             lpXform = new float[6];
-            Gdip.Matrix_GetElements(matrix, lpXform);
+            Gdip.Matrix_GetElements(matrix, lpXform.ptr);
         }
         Gdip.Matrix_delete(matrix);
         if ((data.style & DWT.MIRRORED) !is 0 && lpXform !is null) {
@@ -642,26 +651,26 @@
         selectionEnd = translateOffset(selectionEnd);
     }
     RECT rect;
-    void* selBrush;
-    void* selPen;
-    void* selBrushFg;
+    Gdip.Brush selBrush;
+    Gdip.Pen selPen;
+    Gdip.Brush selBrushFg;
 
     if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) {
         if (gdip) {
             auto bg = selectionBackground.handle;
             auto argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
             auto color = Gdip.Color_new(argb);
-            selBrush = Gdip.SolidBrush_new(color);
+            selBrush = cast(Gdip.Brush)Gdip.SolidBrush_new(color);
             Gdip.Color_delete(color);
             auto fg = selectionForeground.handle;
             argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
             color = Gdip.Color_new(argb);
-            selBrushFg = Gdip.SolidBrush_new(color);
-            selPen = Gdip.Pen_new( cast(Gdip.Brush)selBrushFg, 1);
+            selBrushFg = cast(Gdip.Brush)Gdip.SolidBrush_new(color);
+            selPen = cast(Gdip.Pen) Gdip.Pen_new( cast(Gdip.Brush)selBrushFg, 1);
             Gdip.Color_delete(color);
         } else {
-            selBrush = OS.CreateSolidBrush(selectionBackground.handle);
-            selPen = OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle);
+            selBrush = cast(Gdip.Brush)OS.CreateSolidBrush(selectionBackground.handle);
+            selPen = cast(Gdip.Pen)OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle);
         }
     }
     int offset = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? -1 : 0;
@@ -670,7 +679,7 @@
         int drawX = x + getLineIndent(line);
         int drawY = y + lineY[line];
         StyleItem[] lineRuns = runs[line];
-        int lineHeight = lineY[line+1] - lineY[line];
+        int lineHeight = lineY[line+1] - lineY[line] - lineSpacing;
         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) {
@@ -691,21 +700,23 @@
                 if ((flags & DWT.FULL_SELECTION) !is 0) {
                     width = OS.IsWin95 ? 0x7FFF : 0x6FFFFFF;
                 } else {
-                    width = (lineHeight - lineSpacing) / 3;
+                    width = lineHeight / 3;
                 }
                 if (gdip) {
-                    Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing);
+                    Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX + lineWidth[line], drawY, width, lineHeight);
                 } else {
                     OS.SelectObject(hdc, selBrush);
-                    OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing, OS.PATCOPY);
+                    OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight, OS.PATCOPY);
                 }
             }
         }
         if (drawX > clip.x + clip.width) continue;
         if (drawX + lineWidth[line] < clip.x) continue;
         int baseline = Math.max(0, this.ascent);
+        int lineUnderlinePos = 0;
         for (int i = 0; i < lineRuns.length; i++) {
             baseline = Math.max(baseline, lineRuns[i].ascent);
+            lineUnderlinePos = Math.min(lineUnderlinePos, lineRuns[i].underlinePos);
         }
         int alignmentX = drawX;
         for (int i = 0; i < lineRuns.length; i++) {
@@ -718,26 +729,25 @@
                     bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
                     if (fullSelection) {
                         if (gdip) {
-                            Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX, drawY, run.width, lineHeight - lineSpacing);
+                            Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, drawX, drawY, run.width, lineHeight);
                         } else {
                             OS.SelectObject(hdc, selBrush);
-                            OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight - lineSpacing, OS.PATCOPY);
+                            OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight, OS.PATCOPY);
                         }
                     } else {
                         if (run.style !is null && run.style.background !is null) {
                             auto bg = run.style.background.handle;
-                            int drawRunY = drawY + (baseline - run.ascent);
                             if (gdip) {
                                 int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16);
                                 auto color = Gdip.Color_new(argb);
                                 auto brush = Gdip.SolidBrush_new(color);
-                                Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)brush, drawX, drawRunY, run.width, run.ascent + run.descent);
+                                Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)brush, drawX, drawY, run.width, lineHeight);
                                 Gdip.Color_delete(color);
                                 Gdip.SolidBrush_delete(brush);
                             } else {
                                 auto hBrush = OS.CreateSolidBrush (bg);
                                 auto oldBrush = OS.SelectObject(hdc, hBrush);
-                                OS.PatBlt(hdc, drawX, drawRunY, run.width, run.ascent + run.descent, OS.PATCOPY);
+                                OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight, OS.PATCOPY);
                                 OS.SelectObject(hdc, oldBrush);
                                 OS.DeleteObject(hBrush);
                             }
@@ -757,8 +767,13 @@
                             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 : piX;
                             rect.right = drawX + runX;
-                            rect.bottom = drawY + lineHeight - lineSpacing;
+                            rect.bottom = drawY + lineHeight;
                             if (gdip) {
+                                if (rect.left > rect.right) {
+                                    int tmp = rect.left;
+                                    rect.left = rect.right;
+                                    rect.right = tmp;
+                                }
                                 Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush)selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
                             } else {
                                 OS.SelectObject(hdc, selBrush);
@@ -770,6 +785,7 @@
             }
             drawX += run.width;
         }
+        RECT* borderClip = null;
         drawX = alignmentX;
         for (int i = 0; i < lineRuns.length; i++) {
             StyleItem run = lineRuns[i];
@@ -819,7 +835,7 @@
                             if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath;
                             types[typeIndex] = cast(byte)newType;
                         }
-                        auto path = Gdip.GraphicsPath_new(cast(Gdip.Point[])points, types, count, Gdip.FillModeAlternate);
+                        auto path = Gdip.GraphicsPath_new(cast(Gdip.Point*)points.ptr, types.ptr, count, Gdip.FillModeAlternate);
                         if (path is null) DWT.error(DWT.ERROR_NO_HANDLES);
                         auto brush = foregroundBrush;
                         if (fullSelection) {
@@ -865,46 +881,31 @@
                             Gdip.Graphics_Restore(gdipGraphics, gstate2);
                         }
                         Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias);
-                        if (run.style !is null && (run.style.underline || run.style.strikeout)) {
-                            auto newPen = hasSelection ? cast(Gdip.Pen)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 (cast(void*)newPen !is selPen) Gdip.Pen_delete(newPen);
-                            Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf);
-                        }
+                        drawLines(gdip, gdipGraphics, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, brush, null, alpha);
                         if (partialSelection) {
                             Gdip.Graphics_Restore(gdipGraphics, gstate);
                             gstate = Gdip.Graphics_Save(gdipGraphics);
                             Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeIntersect);
                             Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias);
-                            Gdip.Graphics_FillPath(gdipGraphics, cast(Gdip.Brush)selBrushFg, path);
+                            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, selBrushFg, 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)) {
-                                Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone);
-                                if (run.style.underline) {
-                                    int underlineY = drawY + baseline + 1 - run.style.rise;
-                                    Gdip.Graphics_DrawLine(gdipGraphics, cast(Gdip.Pen)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, cast(Gdip.Pen)selPen, rect.left, strikeoutY, rect.right, strikeoutY);
-                                }
-                                Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf);
-                            }
+                            drawLines(gdip, gdipGraphics, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, selBrushFg, &rect, alpha);
                             Gdip.Graphics_Restore(gdipGraphics, gstate);
                         }
+                        borderClip = drawBorder(gdip, gdipGraphics, x, drawY, lineHeight, foregroundBrush, selBrushFg, fullSelection, borderClip, partialSelection ? &rect : null, alpha, lineRuns, i, selectionStart, selectionEnd);
                         Gdip.GraphicsPath_delete(path);
                         if ( brush !is cast(Gdip.Brush)selBrushFg && brush !is cast(Gdip.Brush)foregroundBrush)
                             Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
                     }   else {
-                            int fg = foreground;
+                            auto fg = foreground;
                             if (fullSelection) {
                                 fg = selectionForeground.handle;
                             } else {
@@ -912,40 +913,14 @@
                         }
                         OS.SetTextColor(hdc, fg);
                         OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
-                        if (run.style !is null && (run.style.underline || run.style.strikeout)) {
-                            auto newPen = hasSelection && fg is selectionForeground.handle ? cast(HPEN)selPen : OS.CreatePen(OS.PS_SOLID, 1, fg);
-                            auto oldPen = OS.SelectObject(hdc, newPen);
-                            if (run.style.underline) {
-                                int underlineY = drawY + baseline + 1 - run.style.rise;
-                                OS.MoveToEx(hdc, drawX, underlineY, null);
-                                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, null);
-                                OS.LineTo(hdc, drawX + run.width, strikeoutY);
-                            }
-                            OS.SelectObject(hdc, oldPen);
-                            if (!hasSelection || fg !is selectionForeground.handle) OS.DeleteObject(newPen);
-                        }
+                        drawLines(gdip, hdc, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, cast(void*)fg, null, alpha);
                         if (partialSelection && fg !is selectionForeground.handle) {
                             OS.SetTextColor(hdc, selectionForeground.handle);
                             OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, OS.ETO_CLIPPED, &rect, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
-                            if (run.style !is null && (run.style.underline || run.style.strikeout)) {
-                                auto oldPen = OS.SelectObject(hdc, selPen);
-                                if (run.style.underline) {
-                                    int underlineY = drawY + baseline + 1 - run.style.rise;
-                                    OS.MoveToEx(hdc, rect.left, underlineY, null);
-                                    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, null);
-                                    OS.LineTo(hdc, rect.right, strikeoutY);
-                                }
-                                OS.SelectObject(hdc, oldPen);
-                            }
+                            drawLines(gdip, hdc, x, drawY + baseline, lineUnderlinePos, drawY + lineHeight, lineRuns, i, cast(void*)selectionForeground.handle, &rect, alpha);
                         }
+                        int selForeground = selectionForeground !is null ? selectionForeground.handle : 0;
+                        borderClip = drawBorder(gdip, hdc, x, drawY, lineHeight, cast(void*)foreground, cast(void*)selForeground, fullSelection, borderClip, partialSelection ? &rect : null, alpha, lineRuns, i, selectionStart, selectionEnd);
                     }
                 }
             }
@@ -955,7 +930,7 @@
     if (gdip) {
         if (selBrush !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)selBrush);
         if (selBrushFg !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)selBrushFg);
-        if (selPen !is null) Gdip.Pen_delete(cast(Gdip.Pen)selPen);
+        if (selPen !is null) Gdip.Pen_delete(selPen);
     } else {
         OS.RestoreDC(hdc, state);
         if (gdipGraphics !is null) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc);
@@ -964,6 +939,355 @@
     }
 }
 
+void drawLines(bool advance, void* graphics, int x, int lineBaseline, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, void* color, RECT* clipRect, int alpha) {
+    StyleItem run = line[index];
+    TextStyle style = run.style;
+    if (style is null) return;
+    if (!style.underline && !style.strikeout) return;
+    int runX = x + run.x;
+    int underlineY = lineBaseline - lineUnderlinePos;
+    int strikeoutY = lineBaseline - run.strikeoutPos;
+    if (advance) {
+        Gdip.Graphics_SetPixelOffsetMode(cast(Gdip.Graphics)graphics, Gdip.PixelOffsetModeNone);
+        auto brush = color;
+        if (style.underline) {
+            if (style.underlineColor !is null) {
+                int fg = style.underlineColor.handle;
+                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            switch (style.underlineStyle) {
+                case DWT.UNDERLINE_SQUIGGLE:
+                case DWT.UNDERLINE_ERROR: {
+                    int squigglyThickness = 1;
+                    int squigglyHeight = 2 * squigglyThickness;
+                    int squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
+                    int squigglyX = runX;
+                    for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
+                        squigglyX = x + line[i - 1].x;
+                    }
+                    int gstate = 0;
+                    if (clipRect is null) {
+                        gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                        Gdip.Rect gdipRect;
+                        gdipRect.X = runX;
+                        gdipRect.Y = squigglyY;
+                        gdipRect.Width = run.width + 1;
+                        gdipRect.Height = squigglyY + squigglyHeight + 1;
+                        Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeIntersect);
+                    }
+                    int[] points = computePolyline(squigglyX, squigglyY, runX + run.width, squigglyY + squigglyHeight);
+                    auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, squigglyThickness);
+                    Gdip.Graphics_DrawLines(cast(Gdip.Graphics)graphics, pen, cast(Gdip.Point*)points.ptr, points.length / 2);
+                    Gdip.Pen_delete(pen);
+                    if (gstate !is 0) Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+                    break;
+                }
+                case DWT.UNDERLINE_SINGLE:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY, run.width, run.underlineThickness);
+                    break;
+                case DWT.UNDERLINE_DOUBLE:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY, run.width, run.underlineThickness);
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, underlineY + run.underlineThickness * 2, run.width, run.underlineThickness);
+                    break;
+                case UNDERLINE_IME_THICK:
+                    Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX - run.underlineThickness, underlineY, run.width, run.underlineThickness * 2);
+                    break;
+                case UNDERLINE_IME_DOT:
+                case UNDERLINE_IME_DASH: {
+                    auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, 1);
+                    int dashStyle = style.underlineStyle is UNDERLINE_IME_DOT ? Gdip.DashStyleDot : Gdip.DashStyleDash;
+                    Gdip.Pen_SetDashStyle(pen, dashStyle);
+                    Gdip.Graphics_DrawLine(cast(Gdip.Graphics)graphics, pen, runX, underlineY, runX + run.width, underlineY);
+                    Gdip.Pen_delete(pen);
+                    break;
+                }
+            }
+            if (brush !is color) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        }
+        if (style.strikeout) {
+            if (style.strikeoutColor !is null) {
+                int fg = style.strikeoutColor.handle;
+                int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            Gdip.Graphics_FillRectangle(cast(Gdip.Graphics)graphics, cast(Gdip.Brush)brush, runX, strikeoutY, run.width, run.strikeoutThickness);
+            if (brush !is color) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        }
+        Gdip.Graphics_SetPixelOffsetMode(cast(Gdip.Graphics)graphics, Gdip.PixelOffsetModeHalf);
+    } else {
+        uint colorRefUnderline = cast(uint)color;
+        uint colorRefStrikeout = cast(uint)color;
+        int /*long*/ brushUnderline = 0;
+        int /*long*/ brushStrikeout = 0;
+        RECT rect;
+        if (style.underline) {
+            if (style.underlineColor !is null) {
+                colorRefUnderline = style.underlineColor.handle;
+            }
+            switch (style.underlineStyle) {
+                case DWT.UNDERLINE_SQUIGGLE:
+                case DWT.UNDERLINE_ERROR: {
+                    int squigglyThickness = 1;
+                    int squigglyHeight = 2 * squigglyThickness;
+                    int squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
+                    int squigglyX = runX;
+                    for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
+                        squigglyX = x + line[i - 1].x;
+                    }
+                    int state = OS.SaveDC(graphics);
+                    if (clipRect !is null) {
+                        OS.IntersectClipRect(graphics, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+                    } else {
+                        OS.IntersectClipRect(graphics, runX, squigglyY, runX + run.width + 1, squigglyY + squigglyHeight + 1);
+                    }
+                    int[] points = computePolyline(squigglyX, squigglyY, runX + run.width, squigglyY + squigglyHeight);
+                    auto pen = OS.CreatePen(OS.PS_SOLID, squigglyThickness, colorRefUnderline);
+                    auto oldPen = OS.SelectObject(graphics, pen);
+                    OS.Polyline(graphics, cast(POINT*)points.ptr, points.length / 2);
+                    int length_ = points.length;
+                    if (length_ >= 2 && squigglyThickness <= 1) {
+                        OS.SetPixel (graphics, points[length_ - 2], points[length_ - 1], colorRefUnderline);
+                    }
+                    OS.RestoreDC(graphics, state);
+                    OS.SelectObject(graphics, oldPen);
+                    OS.DeleteObject(pen);
+                    break;
+                }
+                case DWT.UNDERLINE_SINGLE:
+                    brushUnderline = cast(uint) OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect, cast(void*)brushUnderline);
+                    break;
+                case DWT.UNDERLINE_DOUBLE:
+                    brushUnderline = cast(uint)OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect, cast(void*)brushUnderline);
+                    OS.SetRect(&rect, runX, underlineY + run.underlineThickness * 2, runX + run.width, underlineY + run.underlineThickness * 3);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect,  cast(void*)brushUnderline);
+                    break;
+                case UNDERLINE_IME_THICK:
+                    brushUnderline = cast(uint)OS.CreateSolidBrush(colorRefUnderline);
+                    OS.SetRect(&rect, runX, underlineY - run.underlineThickness, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.FillRect(graphics, &rect,  cast(void*)brushUnderline);
+                    break;
+                case UNDERLINE_IME_DASH:
+                case UNDERLINE_IME_DOT: {
+                    underlineY = lineBaseline + run.descent;
+                    int penStyle = style.underlineStyle is UNDERLINE_IME_DASH ? OS.PS_DASH : OS.PS_DOT;
+                    auto pen = OS.CreatePen(penStyle, 1, colorRefUnderline);
+                    auto oldPen = OS.SelectObject(graphics, pen);
+                    OS.SetRect(&rect, runX, underlineY, runX + run.width, underlineY + run.underlineThickness);
+                    if (clipRect !is null) {
+                        rect.left = Math.max(rect.left, clipRect.left);
+                        rect.right = Math.min(rect.right, clipRect.right);
+                    }
+                    OS.MoveToEx(graphics, rect.left, rect.top, null);
+                    OS.LineTo(graphics, rect.right, rect.top);
+                    OS.SelectObject(graphics, oldPen);
+                    OS.DeleteObject(pen);
+                    break;
+                }
+            }
+        }
+        if (style.strikeout) {
+            if (style.strikeoutColor !is null) {
+                colorRefStrikeout = style.strikeoutColor.handle;
+            }
+            if (brushUnderline !is 0 && colorRefStrikeout is colorRefUnderline) {
+                brushStrikeout = brushUnderline;
+            } else {
+                brushStrikeout = cast(int) OS.CreateSolidBrush(colorRefStrikeout);
+            }
+            OS.SetRect(&rect, runX, strikeoutY, runX + run.width, strikeoutY + run.strikeoutThickness);
+            if (clipRect !is null) {
+                rect.left = Math.max(rect.left, clipRect.left);
+                rect.right = Math.min(rect.right, clipRect.right);
+            }
+            OS.FillRect(graphics, &rect, cast(void*)brushStrikeout);
+        }
+        if (brushUnderline !is 0) OS.DeleteObject(cast(void*)brushUnderline);
+        if (brushStrikeout !is 0 && brushStrikeout !is brushUnderline) OS.DeleteObject(cast(void*)brushStrikeout);
+    }
+}
+
+RECT* drawBorder(bool advance, void* graphics, int x, int y, int lineHeight, void* color, void* selectionColor, bool fullSelection, RECT* clipRect, RECT* rect, int alpha, StyleItem[] line, int index, int selectionStart, int selectionEnd) {
+    StyleItem run = line[index];
+    TextStyle style = run.style;
+    if (style is null) return null;
+    if (style.borderStyle is DWT.NONE) return null;
+    if (rect !is null) {
+        if (clipRect is null) {
+            clipRect = new RECT ();
+            OS.SetRect(clipRect, -1, rect.top, -1, rect.bottom);
+        }
+        bool isRTL = (orientation & DWT.RIGHT_TO_LEFT) !is 0;
+        if (run.start <= selectionStart && selectionStart <= run.start + run.length) {
+            if (run.analysis.fRTL ^ isRTL) {
+                clipRect.right = rect.left;
+            } else {
+                clipRect.left = rect.left;
+            }
+        }
+        if (run.start <= selectionEnd && selectionEnd <= run.start + run.length) {
+            if (run.analysis.fRTL ^ isRTL) {
+                clipRect.left = rect.right;
+            } else {
+                clipRect.right = rect.right;
+            }
+        }
+    }
+    if (index + 1 >= line.length || !style.isAdherentBorder(line[index + 1].style)) {
+        int left = run.x;
+        for (int i = index; i > 0 && style.isAdherentBorder(line[i - 1].style); i--) {
+            left = line[i - 1].x;
+        }
+        if (advance) {
+            auto brush = color;
+            int customColor = -1;
+            if (style.borderColor !is null) {
+                customColor = style.borderColor.handle;
+            } else {
+                if (style.foreground !is null) {
+                    customColor = style.foreground.handle;
+                }
+                if (fullSelection && clipRect is null) {
+                    customColor = -1;
+                    brush = selectionColor;
+                }
+            }
+            if (customColor !is -1) {
+                int argb = ((alpha & 0xFF) << 24) | ((customColor >> 16) & 0xFF) | (customColor & 0xFF00) | ((customColor & 0xFF) << 16);
+                auto gdiColor = Gdip.Color_new(argb);
+                brush = Gdip.SolidBrush_new(gdiColor);
+                Gdip.Color_delete(gdiColor);
+            }
+            int lineWidth = 1;
+            int lineStyle = Gdip.DashStyleSolid;
+            switch (style.borderStyle) {
+                case DWT.BORDER_SOLID: break;
+                case DWT.BORDER_DASH: lineStyle = Gdip.DashStyleDash; break;
+                case DWT.BORDER_DOT: lineStyle = Gdip.DashStyleDot; break;
+            }
+            auto pen = Gdip.Pen_new(cast(Gdip.Brush)brush, lineWidth);
+            Gdip.Pen_SetDashStyle(pen, lineStyle);
+            float gdipXOffset = 0.5f, gdipYOffset = 0.5f;
+            Gdip.Graphics_TranslateTransform(cast(Gdip.Graphics)graphics, gdipXOffset, gdipYOffset, Gdip.MatrixOrderPrepend);
+            if (style.borderColor is null && clipRect !is null) {
+                int gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                if (clipRect.left is -1) clipRect.left = 0;
+                if (clipRect.right is -1) clipRect.right = 0x7ffff;
+                Gdip.Rect gdipRect;
+                gdipRect.X = clipRect.left;
+                gdipRect.Y = clipRect.top;
+                gdipRect.Width = clipRect.right - clipRect.left;
+                gdipRect.Height = clipRect.bottom - clipRect.top;
+                Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeExclude);
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+                Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+                gstate = Gdip.Graphics_Save(cast(Gdip.Graphics)graphics);
+                Gdip.Graphics_SetClip(cast(Gdip.Graphics)graphics, &gdipRect, Gdip.CombineModeIntersect);
+                auto selPen = Gdip.Pen_new(cast(Gdip.Brush)selectionColor, lineWidth);
+                Gdip.Pen_SetDashStyle(pen, lineStyle);
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, selPen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+                Gdip.Pen_delete(selPen);
+                Gdip.Graphics_Restore(cast(Gdip.Graphics)graphics, gstate);
+            } else {
+                Gdip.Graphics_DrawRectangle(cast(Gdip.Graphics)graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
+            }
+            Gdip.Graphics_TranslateTransform(cast(Gdip.Graphics)graphics, -gdipXOffset, -gdipYOffset, Gdip.MatrixOrderPrepend);
+            Gdip.Pen_delete(pen);
+            if (customColor !is -1) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush)brush);
+        } else {
+            if (style.borderColor !is null) {
+                color = cast(void*)style.borderColor.handle;
+            } else {
+                if (style.foreground !is null) {
+                    color = cast(void*)style.foreground.handle;
+                }
+                if (fullSelection && clipRect is null) {
+                    color = selectionColor;
+                }
+            }
+            int lineWidth = 1;
+            int lineStyle = OS.PS_SOLID;
+            switch (style.borderStyle) {
+                case DWT.BORDER_SOLID: break;
+                case DWT.BORDER_DASH: lineStyle = OS.PS_DASH; break;
+                case DWT.BORDER_DOT: lineStyle = OS.PS_DOT; break;
+            }
+            LOGBRUSH logBrush;
+            logBrush.lbStyle = OS.BS_SOLID;
+            logBrush.lbColor = cast(uint)color;
+            auto newPen = OS.ExtCreatePen(lineStyle | OS.PS_GEOMETRIC, Math.max(1, lineWidth), &logBrush, 0, null);
+            auto oldPen = OS.SelectObject(graphics, newPen);
+            auto oldBrush = OS.SelectObject(graphics, OS.GetStockObject(OS.NULL_BRUSH));
+            OS.Rectangle(graphics, x + left, y, x + run.x + run.width, y + lineHeight);
+            if (style.borderColor is null && clipRect !is null && color !is selectionColor) {
+                int state = OS.SaveDC(graphics);
+                if (clipRect.left is -1) clipRect.left = 0;
+                if (clipRect.right is -1) clipRect.right = 0x7ffff;
+                OS.IntersectClipRect(graphics, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+                logBrush.lbColor = cast(uint)selectionColor;
+                auto selPen = OS.ExtCreatePen (lineStyle | OS.PS_GEOMETRIC, Math.max(1, lineWidth), &logBrush, 0, null);
+                OS.SelectObject(graphics, selPen);
+                OS.Rectangle(graphics, x + left, y, x + run.x + run.width, y + lineHeight);
+                OS.RestoreDC(graphics, state);
+                OS.SelectObject(graphics, newPen);
+                OS.DeleteObject(selPen);
+            }
+            OS.SelectObject(graphics, oldBrush);
+            OS.SelectObject(graphics, oldPen);
+            OS.DeleteObject(newPen);
+        }
+        return null;
+    }
+    return clipRect;
+}
+
+int[] computePolyline(int left, int top, int right, int bottom) {
+    int height = bottom - top; // can be any number
+    int width = 2 * height; // must be even
+    int peaks = Compatibility.ceil(right - left, width);
+    if (peaks is 0 && right - left > 2) {
+        peaks = 1;
+    }
+    int length_ = ((2 * peaks) + 1) * 2;
+    if (length_ < 0) return new int[0];
+
+    int[] coordinates = new int[length_];
+    for (int i = 0; i < peaks; i++) {
+        int index = 4 * i;
+        coordinates[index] = left + (width * i);
+        coordinates[index+1] = bottom;
+        coordinates[index+2] = coordinates[index] + width / 2;
+        coordinates[index+3] = top;
+    }
+    coordinates[length_-2] = left + (width * peaks);
+    coordinates[length_-1] = bottom;
+    return coordinates;
+}
+
 void freeRuns () {
     if (allRuns is null) return;
     for (int i=0; i<allRuns.length; i++) {
@@ -1011,13 +1335,18 @@
 }
 
 /**
- * Returns the bounds of the receiver.
+ * Returns the bounds of the receiver. The width returned is either the
+ * width of the longest line or the width set using {@link TextLayout#setWidth(int)}.
+ * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}.
  *
  * @return the bounds of the receiver
  *
  * @exception DWTException <ul>
  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
  * </ul>
+ *
+ * @see #setWidth(int)
+ * @see #getLineBounds(int)
  */
 public Rectangle getBounds () {
     checkLayout();
@@ -1050,11 +1379,11 @@
 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);
+    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 = 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;
@@ -1187,7 +1516,7 @@
     if (this.font !is null) {
         return this.font.handle;
     }
-    return device.systemFont;
+    return device.systemFont.handle;
 }
 
 /**
@@ -1207,8 +1536,8 @@
 public int getLevel (int offset) {
     checkLayout();
     computeRuns(null);
-    int length = text.length;
-    if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    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) {
@@ -1306,8 +1635,8 @@
 public int getLineIndex (int offset) {
     checkLayout();
     computeRuns(null);
-    int length = text.length;
-    if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    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) {
@@ -1337,7 +1666,7 @@
     auto hDC = device.internal_new_GC(null);
     auto srcHdc = OS.CreateCompatibleDC(hDC);
     TEXTMETRIC lptm;
-    OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont);
+    OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont.handle);
     OS.GetTextMetrics(srcHdc, &lptm);
     OS.DeleteDC(srcHdc);
     device.internal_dispose_GC(hDC, null);
@@ -1404,53 +1733,47 @@
 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;
+    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;
-                    int* advances = run.justify !is null ? 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), lineY[line]);
-                    } else {
-                        result = new Point(width + piX, lineY[line]);
-                    }
-                }
-                break;
+    if (offset is length_) {
+        return new Point(getLineIndent(line) + lineWidth[line], lineY[line]);
+    }
+    int low = -1;
+    int high = allRuns.length;
+    while (high - low > 1) {
+        int index = ((high + low) / 2);
+        StyleItem run = allRuns[index];
+        if (run.start > offset) {
+            high = index;
+        } else if (run.start + run.length <= offset) {
+            low = index;
+        } else {
+            int width;
+            if (run.style !is null && run.style.metrics !is null) {
+                GlyphMetrics metrics = run.style.metrics;
+                width = metrics.width * (offset - run.start + (trailing ? 1 : 0));
+            } else if (run.tab) {
+                width = (trailing || (offset is length_)) ? run.width : 0;
+            } else {
+                int runOffset = offset - run.start;
+                int cChars = run.length;
+                int gGlyphs = run.glyphCount;
+                int piX;
+                int* advances = run.justify !is null ? run.justify : run.advances;
+                OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX);
+                width = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX;
             }
-            width += run.width;
+            return new Point(run.x + width, lineY[line]);
         }
     }
-    if (result is null) result = new Point(0, 0);
-    result.x += getLineIndent(line);
-    return result;
+    return new Point(0, 0);
 }
 
 /**
@@ -1479,13 +1802,13 @@
 
 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;
+    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;
+    length_ = segmentsText.length;
     offset = translateOffset(offset);
     SCRIPT_LOGATTR* logAttr;
     SCRIPT_PROPERTIES* properties;
@@ -1545,7 +1868,7 @@
             }
         }
         i += step;
-    } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length);
+    } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length_);
     return forward ? text.length : 0;
 }
 
@@ -1611,16 +1934,22 @@
         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;
+    int lineIndent = getLineIndent(line);
+    if (x >= lineIndent + lineWidth[line]) x = lineIndent + lineWidth[line] - 1;
+    if (x < lineIndent) x = lineIndent;
+    int low = -1;
+    int high = lineRuns.length;
+    while (high - low > 1) {
+        int index = ((high + low) / 2);
+        StyleItem run = lineRuns[index];
+        if (run.x > x) {
+            high = index;
+        } else if (run.x + run.width <= x) {
+            low = index;
+        } else {
+            if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
+            int xRun = x - run.x;
             if (run.style !is null && run.style.metrics !is null) {
                 GlyphMetrics metrics = run.style.metrics;
                 if (metrics.width > 0) {
@@ -1631,7 +1960,7 @@
                 }
             }
             if (run.tab) {
-                if (trailing !is null) trailing[0] = x < (width + run.width / 2) ? 0 : 1;
+                if (trailing !is null) trailing[0] = x < (run.x + run.width / 2) ? 0 : 1;
                 return untranslateOffset(run.start);
             }
             int cChars = run.length;
@@ -1646,7 +1975,6 @@
             if (trailing !is null) trailing[0] = piTrailing;
             return untranslateOffset(run.start + piCP);
         }
-        width += run.width;
     }
     if (trailing !is null) trailing[0] = 0;
     return untranslateOffset(lineOffset[line + 1]);
@@ -1706,9 +2034,9 @@
  */
 public int[] getRanges () {
     checkLayout();
-    int[] result = new int[styles.length * 2];
+    int[] result = new int[stylesCount * 2];
     int count = 0;
-    for (int i=0; i<styles.length - 1; i++) {
+    for (int i=0; i<stylesCount - 1; i++) {
         if (styles[i].style !is null) {
             result[count++] = styles[i].start;
             result[count++] = styles[i + 1].start - 1;
@@ -1740,17 +2068,17 @@
     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;
+    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;
+        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];
+    char[] oldChars = new char[length_];
+    text.getChars(0, length_, oldChars, 0);
+    char[] newChars = new char[length_ + nSegments];
     int charCount = 0, segmentCount = 0;
     wchar separator = orientation is DWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK;
-    while (charCount < length) {
+    while (charCount < length_) {
         if (segmentCount < nSegments && charCount is segments[segmentCount]) {
             newChars[charCount + segmentCount++] = separator;
         } else {
@@ -1793,9 +2121,9 @@
  */
 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++) {
+    int length_ = text.length;
+    if (!(0 <= offset && offset < length_)) DWT.error(DWT.ERROR_INVALID_RANGE);
+    for (int i=1; i<stylesCount; i++) {
         if (styles[i].start > offset) {
             return styles[i - 1].style;
         }
@@ -1818,9 +2146,9 @@
  */
 public TextStyle[] getStyles () {
     checkLayout();
-    TextStyle[] result = new TextStyle[styles.length];
+    TextStyle[] result = new TextStyle[stylesCount];
     int count = 0;
-    for (int i=0; i<styles.length; i++) {
+    for (int i=0; i<stylesCount; i++) {
         if (styles[i].style !is null) {
             result[count++] = styles[i].style;
         }
@@ -1896,10 +2224,10 @@
  */
 StyleItem[] itemize () {
     segmentsText = getSegmentsText();
-    int length = segmentsText.length;
+    int length_ = segmentsText.length;
     SCRIPT_CONTROL scriptControl;
     SCRIPT_STATE scriptState;
-    final int MAX_ITEM = length + 1;
+    final int MAX_ITEM = length_ + 1;
 
     if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) {
         scriptState.uBidiLevel = 1;
@@ -1926,23 +2254,28 @@
  *  Merge styles ranges and script items
  */
 StyleItem[] merge (SCRIPT_ITEM* items, int itemCount) {
+    if (styles.length > stylesCount) {
+        StyleItem[] newStyles = new StyleItem[stylesCount];
+        System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+        styles = newStyles;
+    }
     int count = 0, start = 0, end = segmentsText.length, itemIndex = 0, styleIndex = 0;
-    StyleItem[] runs = new StyleItem[itemCount + styles.length];
-    SCRIPT_ITEM* scriptItem;
+    StyleItem[] runs = new StyleItem[itemCount + stylesCount];
+    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;
-        scriptItem = items + itemIndex;
+        *scriptItem = items[itemIndex];
         item.analysis = scriptItem.a;
         if (linkBefore) {
             item.analysis.fLinkBefore = true;
             linkBefore = false;
         }
         //scriptItem.a = new SCRIPT_ANALYSIS();
-        scriptItem = items + (itemIndex + 1);
+        *scriptItem = items[ itemIndex + 1];
         int itemLimit = scriptItem.iCharPos;
         int styleLimit = translateOffset(styles[styleIndex + 1].start);
         if (styleLimit <= itemLimit) {
@@ -1951,7 +2284,7 @@
             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)) {
+                if (Compatibility.isLetter(pChar) && Compatibility.isLetter(tChar)) {
                     item.analysis.fLinkAfter = true;
                     linkBefore = true;
                 }
@@ -1965,7 +2298,7 @@
     }
     StyleItem item = new StyleItem();
     item.start = end;
-    scriptItem = items + itemCount;
+    *scriptItem = items[ itemCount ];
     item.analysis = scriptItem.a;
     runs[count++] = item;
     if (runs.length !is count) {
@@ -2114,10 +2447,11 @@
 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 ==/*eq*/ this.font) return;
+    Font oldFont = this.font;
+    if (oldFont is font) return;
+    this.font = font;
+    if (oldFont !is null && oldFont.opEquals(font)) return;
     freeRuns();
-    this.font = font;
 }
 
 /**
@@ -2256,7 +2590,7 @@
     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;
+    int high = stylesCount;
     while (high - low > 1) {
         int index = (high + low) / 2;
         if (styles[index + 1].start > start) {
@@ -2265,7 +2599,7 @@
             low = index;
         }
     }
-    if (0 <= high && high < styles.length) {
+    if (0 <= high && high < stylesCount) {
         StyleItem item = styles[high];
         if (item.start is start && styles[high + 1].start - 1 is end) {
             if (style is null) {
@@ -2278,7 +2612,7 @@
     freeRuns();
     int modifyStart = high;
     int modifyEnd = modifyStart;
-    while (modifyEnd < styles.length) {
+    while (modifyEnd < stylesCount) {
         if (styles[modifyEnd + 1].start > end) break;
         modifyEnd++;
     }
@@ -2290,33 +2624,42 @@
             return;
         }
         if (styleStart !is start && styleEnd !is end) {
-            StyleItem[] newStyles = new StyleItem[styles.length + 2];
-            System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
+            int newLength = stylesCount + 2;
+            if (newLength > styles.length) {
+                int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
+                StyleItem[] newStyles = new StyleItem[newSize];
+                System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+                styles = newStyles;
+            }
+            System.arraycopy(styles, modifyEnd + 1, styles, modifyEnd + 3, stylesCount - modifyEnd - 1);
             StyleItem item = new StyleItem();
             item.start = start;
             item.style = style;
-            newStyles[modifyStart + 1] = item;
+            styles[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;
+            styles[modifyStart + 2] = item;
+            stylesCount = newLength;
             return;
         }
     }
     if (start is styles[modifyStart].start) modifyStart--;
     if (end is styles[modifyEnd + 1].start - 1) modifyEnd++;
-    int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1);
-    StyleItem[] newStyles = new StyleItem[newLength];
-    System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
+    int newLength = stylesCount + 1 - (modifyEnd - modifyStart - 1);
+    if (newLength > styles.length) {
+        int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
+        StyleItem[] newStyles = new StyleItem[newSize];
+        System.arraycopy(styles, 0, newStyles, 0, stylesCount);
+        styles = newStyles;
+    }
+    System.arraycopy(styles, modifyEnd, styles, modifyStart + 2, stylesCount - modifyEnd);
     StyleItem item = new StyleItem();
     item.start = start;
     item.style = style;
-    newStyles[modifyStart + 1] = item;
-    styles[modifyEnd].start = end + 1;
-    System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd);
-    styles = newStyles;
+    styles[modifyStart + 1] = item;
+    styles[modifyStart + 2].start = end + 1;
+    stylesCount = newLength;
 }
 
 /**
@@ -2369,6 +2712,7 @@
     styles[0] = new StyleItem();
     styles[1] = new StyleItem();
     styles[1].start = text.length;
+    stylesCount = 2;
 }
 
 /**
@@ -2419,12 +2763,21 @@
     return false;
 }
 
+struct CallbackDataEnumFontFamExProc {
+    int delegate ( ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, int lParam) EnumFontFamExProc;
+    int lParam;
+}
+extern(Windows) private static int EnumFontFamExProcFunc( ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, int lParam) {
+    auto cb = cast(CallbackDataEnumFontFamExProc*)cast(void*)lParam;
+    return cb.EnumFontFamExProc( lpelfe, lpntme, FontType, cb.lParam );
+}
+
 /*
  * Generate glyphs for one Run.
  */
 void shape (HDC hdc, StyleItem run) {
-    int[] buffer = new int[1];
-    char[] chars = new char[run.length];
+    final int[] buffer = new int[1];
+    final char[] chars = new char[run.length];
     segmentsText.getChars(run.start, run.start + run.length, chars, 0);
     wchar[] wchars = StrToWCHARs( chars );
     int maxGlyphs = (chars.length * 3 / 2) + 16;
@@ -2435,9 +2788,29 @@
     if (run.clusters is null) DWT.error(DWT.ERROR_NO_HANDLES);
     run.visAttrs = cast(SCRIPT_VISATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF);
     if (run.visAttrs is null) DWT.error(DWT.ERROR_NO_HANDLES);
-    run.psc = cast(SCRIPT_CACHE*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, 4);
+    run.psc = cast(SCRIPT_CACHE*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, (void*).sizeof);
     if (run.psc is null) DWT.error(DWT.ERROR_NO_HANDLES);
-    if (!shape(hdc, run, chars, buffer,  maxGlyphs)) {
+    bool shapeSucceed = shape(hdc, run, chars, buffer,  maxGlyphs);
+    final short script = run.analysis.eScript;
+
+    if (!shapeSucceed) {
+        /*
+         * Shape failed.
+         * Try to shape with fNoGlyphIndex when the run is in the
+         * Private Use Area. This allows for end-user-defined character (EUDC).
+         */
+        auto properties = device.scripts[script];
+        if (properties.fPrivateUseArea) {
+            run.analysis.fNoGlyphIndex = true;
+            shapeSucceed = shape(hdc, run, chars, buffer,  maxGlyphs);
+        }
+    }
+
+    if (!shapeSucceed) {
+        /*
+        * Shape Failed.
+        * Try to use MLANG to find a suitable font to shape the run.
+        */
         if (mLangFontLink2 !is null) {
             int dwCodePages;
             int cchCodePages;
@@ -2447,52 +2820,152 @@
             /* MapFont() */
             if (OS.VtblCall(10, mLangFontLink2, cast(int)hdc, dwCodePages, cast(int)wchars[0], cast(int)hNewFont) is OS.S_OK) {
                 auto hFont = OS.SelectObject(hdc, hNewFont);
-                if (shape(hdc, run, chars, buffer, maxGlyphs)) {
+                shapeSucceed = shape(hdc, run, chars, buffer,  maxGlyphs);
+                if (shapeSucceed) {
                     run.fallbackFont = hNewFont;
                 } else {
                     /* ReleaseFont() */
                     OS.VtblCall(8, mLangFontLink2, cast(int)hNewFont);
                     OS.SelectObject(hdc, hFont);
-                    SCRIPT_PROPERTIES* properties;
-                    properties = device.scripts[run.analysis.eScript];
-                    if (properties.fPrivateUseArea) {
-                        run.analysis.fNoGlyphIndex = true;
-                    }
-                    OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer.ptr);
-                    run.glyphCount = buffer[0];
                 }
             }
         }
     }
-    ABC abc;
+
+    if (!shapeSucceed) {
+        /*
+        * Shape Failed.
+        * Try to shape the run using the LOGFONT in the cache.
+        */
+        auto hFont = OS.GetCurrentObject(hdc, OS.OBJ_FONT);
+        LOGFONT logFont;
+        OS.GetObject(hFont, LOGFONT.sizeof, &logFont);
+
+        LOGFONT* cachedLogFont = device.logFontsCache !is null ? device.logFontsCache[script] : null;
+        if (cachedLogFont !is null) {
+            cachedLogFont.lfHeight = logFont.lfHeight;
+            cachedLogFont.lfWeight = logFont.lfWeight;
+            cachedLogFont.lfItalic = logFont.lfItalic;
+            cachedLogFont.lfWidth = logFont.lfWidth;
+            auto newFont = OS.CreateFontIndirect(cachedLogFont);
+            OS.SelectObject(hdc, newFont);
+            shapeSucceed = shape(hdc, run, chars, buffer,  maxGlyphs);
+            if (shapeSucceed) {
+                run.fallbackFont = newFont;
+            } else {
+                OS.SelectObject(hdc, hFont);
+                OS.DeleteObject(newFont);
+            }
+        }
+        if (!shapeSucceed) {
+            /*
+            * Shape Failed.
+            * Use EnumFontFamExProc to iterate over every font in the system that supports
+            * the charset of the run and try to shape it.
+            */
+            if (device.logFontsCache is null) device.logFontsCache = new LOGFONT*[device.scripts.length];
+            LOGFONT newLogFont;
+
+            int EnumFontFamExProc( ENUMLOGFONTEX* lpelfe, NEWTEXTMETRICEX* lpntme, int FontType, int lParam) {
+                OS.MoveMemory(&newLogFont, cast(void*)lpelfe, LOGFONT.sizeof);
+                if (FontType is OS.RASTER_FONTTYPE) return 1;
+                newLogFont.lfHeight = logFont.lfHeight;
+                newLogFont.lfWeight = logFont.lfWeight;
+                newLogFont.lfItalic = logFont.lfItalic;
+                newLogFont.lfWidth = logFont.lfWidth;
+                auto newFont = OS.CreateFontIndirect(&newLogFont);
+                OS.SelectObject(hdc, newFont);
+                if (shape(hdc, run, chars, buffer, maxGlyphs)) {
+                    run.fallbackFont = newFont;
+                    LOGFONT* cacheLogFont = new LOGFONT();
+                    OS.MoveMemory(cacheLogFont, lpelfe, LOGFONT.sizeof);
+                    device.logFontsCache[script] = cacheLogFont;
+                    return 0;
+                }
+                OS.SelectObject(hdc, hFont);
+                OS.DeleteObject(newFont);
+                return 1;
+            }
+            CallbackDataEnumFontFamExProc cb;
+            cb.EnumFontFamExProc = &EnumFontFamExProc;
+            cb.lParam = 0;
+            auto properties = device.scripts[script];
+            int charSet = properties.fAmbiguousCharSet ? OS.DEFAULT_CHARSET : properties.bCharSet;
+            newLogFont.lfCharSet = cast(byte)charSet;
+            OS.EnumFontFamiliesEx(hdc, &newLogFont, &EnumFontFamExProcFunc, cast(int) &cb, 0);
+            shapeSucceed = run.fallbackFont !is null;
+        }
+    }
+
+    if (!shapeSucceed) {
+        /*
+        * Shape Failed.
+        * Give up and shape the run with the default font.
+        * Missing glyphs typically will be represent as black boxes in the text.
+        */
+        auto wchars_ = StrToWCHARs(chars);
+        OS.ScriptShape(hdc, run.psc, wchars_.ptr, wchars_.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer.ptr);
+        run.glyphCount = buffer[0];
+    }
+    int[3] abc;
     run.advances = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4);
     if (run.advances is null) DWT.error(DWT.ERROR_NO_HANDLES);
     run.goffsets = cast(GOFFSET*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF);
     if (run.goffsets is null) 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;
+    OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, &run.analysis, run.advances, run.goffsets, cast(ABC*)abc.ptr);
+    run.width = abc[0] + abc[1] + abc[2];
+    TextStyle style = run.style;
+    if (style !is null) {
+        OUTLINETEXTMETRIC* lotm = null;
+        if (style.underline || style.strikeout) {
+            lotm = new OUTLINETEXTMETRIC();
+            if (OS.GetOutlineTextMetrics(hdc, OUTLINETEXTMETRIC.sizeof, lotm) is 0) {
+                lotm = null;
+            }
+        }
+        if (style.metrics !is null) {
+            GlyphMetrics metrics = style.metrics;
+            /*
+             *  Bug in Windows, on a Japanese machine, Uniscribe returns glyphcount
+             *  equals zero for FFFC (possibly other unicode code points), the fix
+             *  is to make sure the glyph is at least one pixel wide.
+             */
+            run.width = metrics.width * Math.max (1, run.glyphCount);
+            run.ascent = metrics.ascent;
+            run.descent = metrics.descent;
+            run.leading = 0;
+        } else {
+            TEXTMETRIC lptm;
+            if (lotm !is null) {
+                lptm = lotm.otmTextMetrics;
+            } else {
+                lptm = TEXTMETRIC.init;
+                OS.GetTextMetrics(hdc, &lptm);
+            }
+            run.ascent = lptm.tmAscent;
+            run.descent = lptm.tmDescent;
+            run.leading = lptm.tmInternalLeading;
+        }
+        if (lotm !is null) {
+            run.underlinePos = lotm.otmsUnderscorePosition;
+            run.underlineThickness = Math.max(1, lotm.otmsUnderscoreSize);
+            run.strikeoutPos = lotm.otmsStrikeoutPosition;
+            run.strikeoutThickness = Math.max(1, lotm.otmsStrikeoutSize);
+        } else {
+            run.underlinePos = 1;
+            run.underlineThickness = 1;
+            run.strikeoutPos = run.ascent / 2;
+            run.strikeoutThickness = 1;
+        }
+        run.ascent += style.rise;
+        run.descent -= style.rise;
     } else {
-        run.width = abc.abcA + abc.abcB + abc.abcC;
         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) {