Mercurial > projects > dwt-win
view dwt/graphics/TextLayout.d @ 30:1e14cb29290a
TextLayout
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 28 Jan 2008 02:37:23 +0100 |
parents | 16332d261df7 |
children | 92c102dd64a3 |
line wrap: on
line source
/******************************************************************************* * 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; import dwt.dwthelper.System; /** * <code>TextLayout</code> is a graphic object that represents * styled text. * <p> * Instances of this class provide support for drawing, cursor * navigation, hit testing, text wrapping, alignment, tab expansion * line breaking, etc. These are aspects required for rendering internationalized text. * </p><p> * Application code must explicitly invoke the <code>TextLayout#dispose()</code> * method to release the operating system resources managed by each instance * when those instances are no longer required. * </p> * * @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; void* 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; GOFFSET* goffsets; int width; int ascent; int descent; int leading; int x; /* Justify info (malloc during computeRuns) */ int* justify; /* ScriptBreak */ SCRIPT_LOGATTR* psla; HFONT fallbackFont; void free() { auto hHeap = OS.GetProcessHeap(); if (psc !is null) { OS.ScriptFreeCache (psc); OS.HeapFree(hHeap, 0, psc); psc = null; } if (glyphs !is null) { OS.HeapFree(hHeap, 0, glyphs); glyphs = null; glyphCount = 0; } if (clusters !is null) { OS.HeapFree(hHeap, 0, clusters); clusters = null; } if (visAttrs !is null) { OS.HeapFree(hHeap, 0, visAttrs); visAttrs = null; } if (advances !is null) { OS.HeapFree(hHeap, 0, advances); advances = null; } if (goffsets !is null) { OS.HeapFree(hHeap, 0, goffsets); goffsets = null; } if (justify !is null) { OS.HeapFree(hHeap, 0, justify); justify = null; } if (psla !is null) { OS.HeapFree(hHeap, 0, psla); psla = null; } if (fallbackFont !is null) { if (mLangFontLink2 !is null) { /* ReleaseFont() */ OS.VtblCall(8, mLangFontLink2, cast(int)fallbackFont); } fallbackFont = null; } width = 0; ascent = 0; descent = 0; 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$ 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); } void breakRun(StyleItem run) { if (run.psla !is null) return; wchar[] chars = StrToWCHARs( segmentsText[ run.start .. run.start + run.length ] ); auto hHeap = OS.GetProcessHeap(); run.psla = cast(SCRIPT_LOGATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length); if (run.psla is null) DWT.error(DWT.ERROR_NO_HANDLES); OS.ScriptBreak(chars.ptr, chars.length, &run.analysis, run.psla); } void checkItem (HDC hDC, StyleItem item) { if (item.fallbackFont !is null) { /* * 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; auto hDC = gc !is null ? gc.handle : device.internal_new_GC(null); auto srcHdc = OS.CreateCompatibleDC(hDC); allRuns = itemize(); for (int i=0; i<allRuns.length - 1; i++) { StyleItem run = allRuns[i]; OS.SelectObject(srcHdc, getItemFont(run)); shape(srcHdc, run); } SCRIPT_LOGATTR* logAttr; SCRIPT_PROPERTIES* properties; int lineWidth = indent, lineStart = 0, lineCount = 1; for (int i=0; i<allRuns.length - 1; i++) { StyleItem run = allRuns[i]; if (run.length is 1) { char ch = segmentsText.charAt(run.start); switch (ch) { case '\t': { run.tab = true; if (tabs is null) break; int tabsLength = tabs.length, j; for (j = 0; j < tabsLength; j++) { if (tabs[j] > lineWidth) { run.width = tabs[j] - lineWidth; break; } } if (j is tabsLength) { int tabX = tabs[tabsLength-1]; int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0]; if (lastTabWidth > 0) { while (tabX <= lineWidth) tabX += lastTabWidth; run.width = tabX - lineWidth; } } break; } case '\n': { run.lineBreak = true; break; } case '\r': { run.lineBreak = true; StyleItem next = allRuns[i + 1]; if (next.length !is 0 && segmentsText.charAt(next.start) is '\n') { run.length += 1; next.free(); 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.ptr); } int width = 0, maxWidth = wrapWidth - lineWidth; while (width + piDx[start] < maxWidth) { width += piDx[start++]; } int firstStart = start; int firstIndice = i; while (i >= lineStart) { breakRun(run); while (start >= 0) { logAttr = run.psla + start; //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); if (logAttr.fSoftBreak || logAttr.fWhiteSpace) break; start--; } /* * Bug in Windows. For some reason Uniscribe sets the fSoftBreak flag for the first letter * after a letter with an accent. This cause a break line to be set in the middle of a word. * The fix is to detect the case and ignore fSoftBreak forcing the algorithm keep searching. */ if (start is 0 && i !is lineStart && !run.tab) { if (logAttr.fSoftBreak && !logAttr.fWhiteSpace) { properties = device.scripts[run.analysis.eScript]; //OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof); int langID = properties.langid; StyleItem pRun = allRuns[i - 1]; //OS.MoveMemory(properties, device.scripts[pRun.analysis.eScript], SCRIPT_PROPERTIES.sizeof); if (properties.langid is langID || langID is OS.LANG_NEUTRAL || properties.langid is OS.LANG_NEUTRAL) { breakRun(pRun); logAttr = pRun.psla + (pRun.length - 1); //OS.MoveMemory(logAttr, pRun.psla + ((pRun.length - 1) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); if (!logAttr.fWhiteSpace) start = -1; } } } if (start >= 0 || i is lineStart) break; run = allRuns[--i]; start = run.length - 1; } if (start is 0 && i !is lineStart && !run.tab) { run = allRuns[--i]; } else if (start <= 0 && i is lineStart) { i = firstIndice; run = allRuns[i]; start = Math.max(1, firstStart); } breakRun(run); while (start < run.length) { logAttr = run.psla + start; //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); if (!logAttr.fWhiteSpace) break; start++; } if (0 < start && start < run.length) { StyleItem newRun = new StyleItem(); newRun.start = run.start + start; newRun.length = run.length - start; newRun.style = run.style; newRun.analysis = 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; } } auto hHeap = OS.GetProcessHeap(); int newLineWidth = 0; for (int j = 0; j < runs[line].length; j++) { StyleItem item = runs[line][j]; int iDx = item.width * wrapWidth / lineWidth; if (iDx !is item.width) { item.justify = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4); if (item.justify is null) 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 null) 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 null) { /* Release() */ OS.VtblCall(2, mLangFontLink2); mLangFontLink2 = null; } 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; auto hdc = gc.handle; Rectangle clip = gc.getClipping(); GCData data = gc.data; auto gdipGraphics = data.gdipGraphics; auto foreground = data.foreground; auto alpha = data.alpha; bool gdip = gdipGraphics !is null && (alpha !is 0xFF || data.foregroundPattern !is null); HRGN clipRgn; float[] lpXform = null; Gdip.Rect gdipRect; if (gdipGraphics !is null && !gdip) { auto matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0); if (matrix is null) DWT.error(DWT.ERROR_NO_HANDLES); Gdip.Graphics_GetTransform(gdipGraphics, matrix); auto identity_ = gc.identity(); Gdip.Matrix_Invert(identity_); Gdip.Matrix_Multiply(matrix, identity_, Gdip.MatrixOrderAppend); Gdip.Matrix_delete(identity_); if (!Gdip.Matrix_IsIdentity(matrix)) { lpXform = new float[6]; Gdip.Matrix_GetElements(matrix, lpXform.ptr); } Gdip.Matrix_delete(matrix); if ((data.style & DWT.MIRRORED) !is 0 && lpXform !is null) { gdip = true; lpXform = null; } else { Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); auto rgn = Gdip.Region_new(); Gdip.Graphics_GetClip(gdipGraphics, rgn); if (!Gdip.Region_IsInfinite(rgn, gdipGraphics)) { clipRgn = Gdip.Region_GetHRGN(rgn, gdipGraphics); } Gdip.Region_delete(rgn); Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); hdc = Gdip.Graphics_GetHDC(gdipGraphics); } } Gdip.Brush* foregroundBrush; int state = 0; if (gdip) { gc.checkGC(GC.FOREGROUND); foregroundBrush = gc.getFgBrush(); } else { state = OS.SaveDC(hdc); if ((data.style & 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, cast(XFORM*)lpXform.ptr); } if (clipRgn !is null) { OS.SelectClipRgn(hdc, clipRgn); OS.DeleteObject(clipRgn); } } bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1; if (hasSelection || (flags & 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; void* selBrush; void* selPen; void* 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); 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); 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, cast(Gdip.Brush*)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, cast(Gdip.Brush*)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) { 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.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.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; int* advances = run.justify !is null ? run.justify : run.advances; OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; rect.left = drawX + runX; rect.top = drawY; OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; rect.right = drawX + runX; rect.bottom = drawY + lineHeight - lineSpacing; if (gdip) { Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush*)selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } else { OS.SelectObject(hdc, selBrush); OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY); } } } } } drawX += run.width; } 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; int* advances = run.justify !is null ? run.justify : run.advances; OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; rect.left = drawX + runX; rect.top = drawY; OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; rect.right = drawX + runX; rect.bottom = drawY + lineHeight; } if (gdip) { OS.BeginPath(hdc); OS.ScriptTextOut(hdc, run.psc, drawX, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); OS.EndPath(hdc); int count = OS.GetPath(hdc, null, null, 0); int[] points = new int[count*2]; ubyte[] types = new ubyte[count]; OS.GetPath(hdc, cast(POINT*)points.ptr, types.ptr, count); for (int typeIndex = 0; typeIndex < types.length; typeIndex++) { int newType = 0; int type = types[typeIndex] & 0xFF; switch (type & ~OS.PT_CLOSEFIGURE) { case OS.PT_MOVETO: newType = Gdip.PathPointTypeStart; break; case OS.PT_LINETO: newType = Gdip.PathPointTypeLine; break; case OS.PT_BEZIERTO: newType = Gdip.PathPointTypeBezier; break; } if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath; types[typeIndex] = cast(byte)newType; } auto path = Gdip.GraphicsPath_new(cast(Gdip.Point*)points.ptr, cast(byte*)types.ptr, count, Gdip.FillModeAlternate); if (path is null) DWT.error(DWT.ERROR_NO_HANDLES); auto brush = foregroundBrush; if (fullSelection) { brush = cast(Gdip.Brush*)selBrushFg; } else { if (run.style !is null && run.style.foreground !is null) { auto fg = run.style.foreground.handle; int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16); auto color = Gdip.Color_new(argb); brush = cast(Gdip.Brush*)Gdip.SolidBrush_new(color); Gdip.Color_delete(color); } } int gstate = 0; if (partialSelection) { gdipRect.X = rect.left; gdipRect.Y = rect.top; gdipRect.Width = rect.right - rect.left; gdipRect.Height = rect.bottom - rect.top; gstate = Gdip.Graphics_Save(gdipGraphics); Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeExclude); } int antialias = Gdip.Graphics_GetSmoothingMode(gdipGraphics), textAntialias = 0; int mode = Gdip.Graphics_GetTextRenderingHint(data.gdipGraphics); switch (mode) { case Gdip.TextRenderingHintSystemDefault: textAntialias = Gdip.SmoothingModeAntiAlias; break; case Gdip.TextRenderingHintSingleBitPerPixel: case Gdip.TextRenderingHintSingleBitPerPixelGridFit: textAntialias = Gdip.SmoothingModeNone; break; case Gdip.TextRenderingHintAntiAlias: case Gdip.TextRenderingHintAntiAliasGridFit: case Gdip.TextRenderingHintClearTypeGridFit: textAntialias = Gdip.SmoothingModeAntiAlias; break; } 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)) { 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 (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, cast(Gdip.Brush*)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, 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); } Gdip.Graphics_Restore(gdipGraphics, gstate); } Gdip.GraphicsPath_delete(path); if (brush !is selBrushFg && brush !is foregroundBrush) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)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 , 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); } 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); } } } } } drawX += run.width; } } if (gdip) { if (selBrush !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)selBrush); if (selBrushFg !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)selBrushFg); if (selPen !is null) Gdip.Pen_delete(cast(Gdip.Pen*)selPen); } else { OS.RestoreDC(hdc, state); if (gdipGraphics !is null) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc); if (selBrush !is null) OS.DeleteObject (selBrush); if (selPen !is null) OS.DeleteObject (selPen); } } void 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; int* advances = run.justify !is null ? 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 : piX; } if (run.analysis.fRTL ^ isRTL) { runTrail = run.x + cx; } else { runLead = run.x + cx; } } if (run.start <= end && end < runEnd) { int cx = run.width; if (run.style !is null && run.style.metrics !is null) { GlyphMetrics metrics = run.style.metrics; cx = metrics.width * (end - run.start + 1); } else if (!run.tab) { int piX; int* advances = run.justify !is null ? 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 : piX; } if (run.analysis.fRTL ^ isRTL) { runLead = run.x + cx; } else { runTrail = run.x + cx; } } int lineIndex = 0; while (lineIndex < runs.length && lineOffset[lineIndex + 1] <= run.start) { lineIndex++; } left = Math.min(left, runLead); right = Math.max(right, runTrail); top = Math.min(top, lineY[lineIndex]); bottom = Math.max(bottom, lineY[lineIndex + 1] - lineSpacing); } return new Rectangle(left, top, right - left, bottom - top); } /** * Returns the descent of the receiver. * * @return the descent * * @exception 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; } HFONT getItemFont (StyleItem item) { if (item.fallbackFont !is null) return cast(HFONT) item.fallbackFont; if (item.style !is null && item.style.font !is null) { return item.style.font.handle; } if (this.font !is null) { return this.font.handle; } return device.systemFont; } /** * 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); 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.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; 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; } 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; SCRIPT_PROPERTIES* properties; int i = forward ? 0 : allRuns.length - 1; offset = validadeOffset(offset, step); do { StyleItem run = allRuns[i]; if (run.start <= offset && offset < run.start + run.length) { if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start); if (run.tab) return untranslateOffset(run.start); properties = device.scripts[run.analysis.eScript]; bool isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking; if (isComplex) breakRun(run); while (run.start <= offset && offset < run.start + run.length) { if (isComplex) { logAttr = run.psla + (offset - run.start); } 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; int piTrailing; if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { xRun = run.width - xRun; } int* advances = run.justify !is null ? run.justify : run.advances; OS.ScriptXtoCP(xRun, cChars, cGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piCP, &piTrailing); if (trailing !is null) trailing[0] = piTrailing; return untranslateOffset(run.start + piCP); } 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; wchar 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 newChars[ 0 .. Math.min(charCount + segmentCount, newChars.length)].dup; } /** * 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; SCRIPT_STATE scriptState; final int MAX_ITEM = length + 1; if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { scriptState.uBidiLevel = 1; scriptState.fArabicNumContext = true; SCRIPT_DIGITSUBSTITUTE psds; OS.ScriptRecordDigitSubstitution(OS.LOCALE_USER_DEFAULT, &psds); OS.ScriptApplyDigitSubstitution(&psds, &scriptControl, &scriptState); } auto hHeap = OS.GetProcessHeap(); auto pItems = cast(SCRIPT_ITEM*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof); if (pItems is null) DWT.error(DWT.ERROR_NO_HANDLES); int pcItems; wchar[] chars = StrToWCHARs( segmentsText ); OS.ScriptItemize(chars.ptr, chars.length, MAX_ITEM, &scriptControl, &scriptState, pItems, &pcItems); // if (hr is E_OUTOFMEMORY) //TODO handle it StyleItem[] runs = merge(pItems, pcItems); OS.HeapFree(hHeap, 0, pItems); return runs; } /* * Merge styles ranges and script items */ StyleItem[] merge (SCRIPT_ITEM* items, int itemCount) { int count = 0, start = 0, end = segmentsText.length, itemIndex = 0, styleIndex = 0; StyleItem[] runs = new StyleItem[itemCount + styles.length]; SCRIPT_ITEM* scriptItem; bool linkBefore = false; while (start < end) { StyleItem item = new StyleItem(); item.start = start; item.style = styles[styleIndex].style; runs[count++] = item; scriptItem = items + itemIndex; item.analysis = scriptItem.a; if (linkBefore) { item.analysis.fLinkBefore = true; linkBefore = false; } //scriptItem.a = new SCRIPT_ANALYSIS(); scriptItem = items + (itemIndex + 1); int itemLimit = 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; scriptItem = items + itemCount; item.analysis = scriptItem.a; runs[count++] = item; if (runs.length !is count) { StyleItem[] result = new StyleItem[count]; System.arraycopy(runs, 0, result, 0, count); return result; } return runs; } /* * Reorder the run */ StyleItem[] reorder (StyleItem[] runs, bool terminate) { int length_ = runs.length; if (length_ <= 1) return runs; ubyte[] bidiLevels = new ubyte[length_]; for (int i=0; i<length_; i++) { bidiLevels[i] = cast(byte)(runs[i].analysis.s.uBidiLevel & 0x1F); } /* * Feature in Windows. If the orientation is RTL Uniscribe will * resolve the level of line breaks to 1, this can cause the line * break to be reorder to the middle of the line. The fix is to set * the level to zero to prevent it to be reordered. */ StyleItem lastRun = runs[length_ - 1]; if (lastRun.lineBreak && !lastRun.softBreak) { bidiLevels[length_ - 1] = 0; } int[] log2vis = new int[length_]; OS.ScriptLayout(length_, bidiLevels.ptr, null, log2vis.ptr); StyleItem[] result = new StyleItem[length_]; for (int i=0; i<length_; i++) { result[log2vis[i]] = runs[i]; } if ((orientation & 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 ==/*eq*/ 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 ==/*eq*/ 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==/*eq*/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 (HDC hdc, StyleItem run, char[] chars, int[] glyphCount, int maxGlyphs) { wchar[] wchars = StrToWCHARs( chars ); auto hr = OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount.ptr); run.glyphCount = glyphCount[0]; if (hr !is OS.USP_E_SCRIPT_NOT_IN_FONT) { SCRIPT_FONTPROPERTIES fp; fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof; OS.ScriptGetFontProperties(hdc, run.psc, &fp); ushort[] glyphs = run.glyphs[ 0 .. glyphCount[0] ]; int i; for (i = 0; i < glyphs.length; i++) { if (glyphs[i] is fp.wgDefault) break; } if (i is glyphs.length) return true; } if (run.psc !is null) { OS.ScriptFreeCache(run.psc); glyphCount[0] = 0; *cast(int*)run.psc = 0; } run.glyphCount = 0; return false; } /* * Generate glyphs for one Run. */ void shape (HDC hdc, StyleItem run) { int[] buffer = new int[1]; 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; auto hHeap = OS.GetProcessHeap(); run.glyphs = cast(ushort*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); if (run.glyphs is null) DWT.error(DWT.ERROR_NO_HANDLES); run.clusters = cast(WORD*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); 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); if (run.psc is null) DWT.error(DWT.ERROR_NO_HANDLES); if (!shape(hdc, run, chars, buffer, maxGlyphs)) { if (mLangFontLink2 !is null) { int dwCodePages; int cchCodePages; /* GetStrCodePages() */ OS.VtblCall(4, mLangFontLink2, cast(int)wchars.ptr, wchars.length, 0, cast(int)&dwCodePages, cast(int)&cchCodePages); HFONT hNewFont; /* 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)) { 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; 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; } 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) { 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; } }