Mercurial > projects > dwt-win
changeset 29:16332d261df7
TextLayout not yet compiling
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 28 Jan 2008 00:27:56 +0100 |
parents | b868bfa989cd |
children | 1e14cb29290a |
files | dwt/graphics/TextLayout.d |
diffstat | 1 files changed, 2520 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwt/graphics/TextLayout.d Mon Jan 28 00:27:56 2008 +0100 @@ -0,0 +1,2520 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +module dwt.graphics.TextLayout; + +import dwt.DWT; +import dwt.DWTException; +import dwt.internal.Compatibility; +import dwt.internal.gdip.Gdip; +import dwt.internal.win32.OS; + +import dwt.graphics.Color; +import dwt.graphics.Device; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.GCData; +import dwt.graphics.GlyphMetrics; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.graphics.Region; +import dwt.graphics.Resource; +import dwt.graphics.TextStyle; + +import tango.text.convert.Format; +import dwt.dwthelper.utils; + +/** + * <code>TextLayout</code> is a graphic object that represents + * styled text. + * <p> + * Instances of this class provide support for drawing, cursor + * navigation, hit testing, text wrapping, alignment, tab expansion + * line breaking, etc. These are aspects required for rendering internationalized text. + * </p><p> + * Application code must explicitly invoke the <code>TextLayout#dispose()</code> + * method to release the operating system resources managed by each instance + * when those instances are no longer required. + * </p> + * + * @since 3.0 + */ +public final class TextLayout : Resource { + Font font; + char[] text, segmentsText; + int lineSpacing; + int ascent, descent; + int alignment; + int wrapWidth; + int orientation; + int indent; + bool justify; + int[] tabs; + int[] segments; + StyleItem[] styles; + + StyleItem[] allRuns; + StyleItem[][] runs; + int[] lineOffset, lineY, lineWidth; + int mLangFontLink2; + + static const wchar LTR_MARK = '\u200E', RTL_MARK = '\u200F'; + static const int SCRIPT_VISATTR_SIZEOF = 2; + static const int GOFFSET_SIZEOF = 8; + static byte[16] CLSID_CMultiLanguage; + static byte[16] IID_IMLangFontLink2; + static this() { + OS.IIDFromString("{275c23e2-3747-11d0-9fea-00aa003f8646}\0".toCharArray().ptr, CLSID_CMultiLanguage.ptr); + OS.IIDFromString("{DCCFC162-2B38-11d2-B7EC-00C04F8F5D9A}\0".toCharArray().ptr, IID_IMLangFontLink2.ptr); + } + + class StyleItem { + TextStyle style; + int start, length; + bool lineBreak, softBreak, tab; + + /*Script cache and analysis */ + SCRIPT_ANALYSIS analysis; + SCRIPT_CACHE* psc; + + /*Shape info (malloc when the run is shaped) */ + WORD* glyphs; + int glyphCount; + WORD* clusters; + SCRIPT_VISATTR* visAttrs; + + /*Place info (malloc when the run is placed) */ + int* advances; + int* goffsets; + int* width; + int* ascent; + int* descent; + int* leading; + int* x; + + /* Justify info (malloc during computeRuns) */ + int* justify; + + /* ScriptBreak */ + SCRIPT_LOGATTR* psla; + + int* fallbackFont; + + void free() { + auto hHeap = OS.GetProcessHeap(); + if (psc !is null) { + OS.ScriptFreeCache (psc); + OS.HeapFree(hHeap, 0, psc); + psc = null; + } + if (glyphs !is null) { + OS.HeapFree(hHeap, 0, glyphs); + glyphs = null; + glyphCount = 0; + } + if (clusters !is null) { + OS.HeapFree(hHeap, 0, clusters); + clusters = null; + } + if (visAttrs !is null) { + OS.HeapFree(hHeap, 0, visAttrs); + visAttrs = null; + } + if (advances !is null) { + OS.HeapFree(hHeap, 0, advances); + advances = null; + } + if (goffsets !is null) { + OS.HeapFree(hHeap, 0, goffsets); + goffsets = null; + } + if (justify !is null) { + OS.HeapFree(hHeap, 0, justify); + justify = null; + } + if (psla !is null) { + OS.HeapFree(hHeap, 0, psla); + psla = null; + } + if (fallbackFont !is null) { + if (mLangFontLink2 !is 0) { + /* ReleaseFont() */ + OS.VtblCall(8, mLangFontLink2, fallbackFont); + } + fallbackFont = null; + } + width = ascent = descent = x = 0; + lineBreak = softBreak = false; + } + public char[] toString () { + return Format( "StyleItem {{{}, {}}", start, style ); + } + } + +/** + * Constructs a new instance of this class on the given device. + * <p> + * You must dispose the text layout when it is no longer required. + * </p> + * + * @param device the device on which to allocate the text layout + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li> + * </ul> + * + * @see #dispose() + */ +public this (Device device) { + if (device is null) device = Device.getDevice(); + if (device is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); + this.device = device; + wrapWidth = ascent = descent = -1; + lineSpacing = 0; + orientation = DWT.LEFT_TO_RIGHT; + styles = new StyleItem[2]; + styles[0] = new StyleItem(); + styles[1] = new StyleItem(); + text = ""; //$NON-NLS-1$ + int[] ppv = new int[1]; + OS.OleInitialize(0); + if (OS.CoCreateInstance(CLSID_CMultiLanguage, 0, OS.CLSCTX_INPROC_SERVER, IID_IMLangFontLink2, ppv) is OS.S_OK) { + mLangFontLink2 = ppv[0]; + } + if (device.tracking) device.new_Object(this); +} + +void breakRun(StyleItem run) { + if (run.psla !is 0) return; + char[] chars = new char[run.length]; + segmentsText.getChars(run.start, run.start + run.length, chars, 0); + int hHeap = OS.GetProcessHeap(); + run.psla = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length); + if (run.psla is 0) DWT.error(DWT.ERROR_NO_HANDLES); + OS.ScriptBreak(chars, chars.length, run.analysis, run.psla); +} + +void checkItem (int hDC, StyleItem item) { + if (item.fallbackFont !is 0) { + /* + * Feature in Windows. The fallback font returned by the MLang service + * can be disposed by some other client running in the same thread. + * For example, disposing a Browser widget internally releases all fonts + * in the MLang cache. The fix is to use GetObject() to detect if the + * font was disposed and reshape the run. + */ + LOGFONT logFont; + if (OS.GetObject(item.fallbackFont, LOGFONT.sizeof, &logFont) is 0) { + item.free(); + OS.SelectObject(hDC, getItemFont(item)); + shape(hDC, item); + } + } +} + +void checkLayout () { + if (isDisposed()) DWT.error(DWT.ERROR_GRAPHIC_DISPOSED); +} + +/* +* Compute the runs: itemize, shape, place, and reorder the runs. +* Break paragraphs into lines, wraps the text, and initialize caches. +*/ +void computeRuns (GC gc) { + if (runs !is null) return; + int hDC = gc !is null ? gc.handle : device.internal_new_GC(null); + int srcHdc = OS.CreateCompatibleDC(hDC); + allRuns = itemize(); + for (int i=0; i<allRuns.length - 1; i++) { + StyleItem run = allRuns[i]; + OS.SelectObject(srcHdc, getItemFont(run)); + shape(srcHdc, run); + } + SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR(); + SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES(); + int lineWidth = indent, lineStart = 0, lineCount = 1; + for (int i=0; i<allRuns.length - 1; i++) { + StyleItem run = allRuns[i]; + if (run.length is 1) { + char ch = segmentsText.charAt(run.start); + switch (ch) { + case '\t': { + run.tab = true; + if (tabs is null) break; + int tabsLength = tabs.length, j; + for (j = 0; j < tabsLength; j++) { + if (tabs[j] > lineWidth) { + run.width = tabs[j] - lineWidth; + break; + } + } + if (j is tabsLength) { + int tabX = tabs[tabsLength-1]; + int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0]; + if (lastTabWidth > 0) { + while (tabX <= lineWidth) tabX += lastTabWidth; + run.width = tabX - lineWidth; + } + } + break; + } + case '\n': { + run.lineBreak = true; + break; + } + case '\r': { + run.lineBreak = true; + StyleItem next = allRuns[i + 1]; + if (next.length !is 0 && segmentsText.charAt(next.start) is '\n') { + run.length += 1; + next.free(); + i++; + } + break; + } + } + } + if (wrapWidth !is -1 && lineWidth + run.width > wrapWidth && !run.tab) { + int start = 0; + int[] piDx = new int[run.length]; + if (run.style !is null && run.style.metrics !is null) { + piDx[0] = run.width; + } else { + OS.ScriptGetLogicalWidths(run.analysis, run.length, run.glyphCount, run.advances, run.clusters, run.visAttrs, piDx); + } + int width = 0, maxWidth = wrapWidth - lineWidth; + while (width + piDx[start] < maxWidth) { + width += piDx[start++]; + } + int firstStart = start; + int firstIndice = i; + while (i >= lineStart) { + breakRun(run); + while (start >= 0) { + OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); + if (logAttr.fSoftBreak || logAttr.fWhiteSpace) break; + start--; + } + + /* + * Bug in Windows. For some reason Uniscribe sets the fSoftBreak flag for the first letter + * after a letter with an accent. This cause a break line to be set in the middle of a word. + * The fix is to detect the case and ignore fSoftBreak forcing the algorithm keep searching. + */ + if (start is 0 && i !is lineStart && !run.tab) { + if (logAttr.fSoftBreak && !logAttr.fWhiteSpace) { + OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof); + int langID = properties.langid; + StyleItem pRun = allRuns[i - 1]; + OS.MoveMemory(properties, device.scripts[pRun.analysis.eScript], SCRIPT_PROPERTIES.sizeof); + if (properties.langid is langID || langID is OS.LANG_NEUTRAL || properties.langid is OS.LANG_NEUTRAL) { + breakRun(pRun); + OS.MoveMemory(logAttr, pRun.psla + ((pRun.length - 1) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); + if (!logAttr.fWhiteSpace) start = -1; + } + } + } + if (start >= 0 || i is lineStart) break; + run = allRuns[--i]; + start = run.length - 1; + } + if (start is 0 && i !is lineStart && !run.tab) { + run = allRuns[--i]; + } else if (start <= 0 && i is lineStart) { + i = firstIndice; + run = allRuns[i]; + start = Math.max(1, firstStart); + } + breakRun(run); + while (start < run.length) { + OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); + if (!logAttr.fWhiteSpace) break; + start++; + } + if (0 < start && start < run.length) { + StyleItem newRun = new StyleItem(); + newRun.start = run.start + start; + newRun.length = run.length - start; + newRun.style = run.style; + newRun.analysis = run.analysis; + run.free(); + run.length = start; + OS.SelectObject(srcHdc, getItemFont(run)); + shape (srcHdc, run); + OS.SelectObject(srcHdc, getItemFont(newRun)); + shape (srcHdc, newRun); + StyleItem[] newAllRuns = new StyleItem[allRuns.length + 1]; + System.arraycopy(allRuns, 0, newAllRuns, 0, i + 1); + System.arraycopy(allRuns, i + 1, newAllRuns, i + 2, allRuns.length - i - 1); + allRuns = newAllRuns; + allRuns[i + 1] = newRun; + } + if (i !is allRuns.length - 2) { + run.softBreak = run.lineBreak = true; + } + } + lineWidth += run.width; + if (run.lineBreak) { + lineStart = i + 1; + lineWidth = run.softBreak ? 0 : indent; + lineCount++; + } + } + lineWidth = 0; + runs = new StyleItem[lineCount][]; + lineOffset = new int[lineCount + 1]; + lineY = new int[lineCount + 1]; + this.lineWidth = new int[lineCount]; + int lineRunCount = 0, line = 0; + int ascent = Math.max(0, this.ascent); + int descent = Math.max(0, this.descent); + StyleItem[] lineRuns = new StyleItem[allRuns.length]; + for (int i=0; i<allRuns.length; i++) { + StyleItem run = allRuns[i]; + lineRuns[lineRunCount++] = run; + lineWidth += run.width; + ascent = Math.max(ascent, run.ascent); + descent = Math.max(descent, run.descent); + if (run.lineBreak || i is allRuns.length - 1) { + /* Update the run metrics if the last run is a hard break. */ + if (lineRunCount is 1 && i is allRuns.length - 1) { + TEXTMETRIC lptm; + OS.SelectObject(srcHdc, getItemFont(run)); + OS.GetTextMetrics(srcHdc, &lptm); + run.ascent = lptm.tmAscent; + run.descent = lptm.tmDescent; + ascent = Math.max(ascent, run.ascent); + descent = Math.max(descent, run.descent); + } + runs[line] = new StyleItem[lineRunCount]; + System.arraycopy(lineRuns, 0, runs[line], 0, lineRunCount); + + if (justify && wrapWidth !is -1 && run.softBreak && lineWidth > 0) { + if (line is 0) { + lineWidth += indent; + } else { + StyleItem[] previousLine = runs[line - 1]; + StyleItem previousRun = previousLine[previousLine.length - 1]; + if (previousRun.lineBreak && !previousRun.softBreak) { + lineWidth += indent; + } + } + int hHeap = OS.GetProcessHeap(); + int newLineWidth = 0; + for (int j = 0; j < runs[line].length; j++) { + StyleItem item = runs[line][j]; + int iDx = item.width * wrapWidth / lineWidth; + if (iDx !is item.width) { + item.justify = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4); + if (item.justify is 0) DWT.error(DWT.ERROR_NO_HANDLES); + OS.ScriptJustify(item.visAttrs, item.advances, item.glyphCount, iDx - item.width, 2, item.justify); + item.width = iDx; + } + newLineWidth += item.width; + } + lineWidth = newLineWidth; + } + this.lineWidth[line] = lineWidth; + + StyleItem lastRun = runs[line][lineRunCount - 1]; + int lastOffset = lastRun.start + lastRun.length; + runs[line] = reorder(runs[line], i is allRuns.length - 1); + lastRun = runs[line][lineRunCount - 1]; + if (run.softBreak && run !is lastRun) { + run.softBreak = run.lineBreak = false; + lastRun.softBreak = lastRun.lineBreak = true; + } + + lineWidth = getLineIndent(line); + for (int j = 0; j < runs[line].length; j++) { + runs[line][j].x = lineWidth; + lineWidth += runs[line][j].width; + } + line++; + lineY[line] = lineY[line - 1] + ascent + descent + lineSpacing; + lineOffset[line] = lastOffset; + lineRunCount = lineWidth = 0; + ascent = Math.max(0, this.ascent); + descent = Math.max(0, this.descent); + } + } + if (srcHdc !is 0) OS.DeleteDC(srcHdc); + if (gc is null) device.internal_dispose_GC(hDC, null); +} + +/** + * Disposes of the operating system resources associated with + * the text layout. Applications must dispose of all allocated text layouts. + */ +public void dispose () { + if (device is null) return; + freeRuns(); + font = null; + text = null; + segmentsText = null; + tabs = null; + styles = null; + runs = null; + lineOffset = null; + lineY = null; + lineWidth = null; + if (mLangFontLink2 !is 0) { + /* Release() */ + OS.VtblCall(2, mLangFontLink2); + mLangFontLink2 = 0; + } + OS.OleUninitialize(); + if (device.tracking) device.dispose_Object(this); + device = null; +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + */ +public void draw (GC gc, int x, int y) { + draw(gc, x, y, -1, -1, null, null); +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param selectionStart the offset where the selections starts, or -1 indicating no selection + * @param selectionEnd the offset where the selections ends, or -1 indicating no selection + * @param selectionForeground selection foreground, or NULL to use the system default color + * @param selectionBackground selection background, or NULL to use the system default color + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + */ +public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { + draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0); +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * <p> + * The parameter <code>flags</code> can include one of <code>DWT.DELIMITER_SELECTION</code> + * or <code>DWT.FULL_SELECTION</code> to specify the selection behavior on all lines except + * for the last line, and can also include <code>DWT.LAST_LINE_SELECTION</code> to extend + * the specified selection behavior to the last line. + * </p> + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param selectionStart the offset where the selections starts, or -1 indicating no selection + * @param selectionEnd the offset where the selections ends, or -1 indicating no selection + * @param selectionForeground selection foreground, or NULL to use the system default color + * @param selectionBackground selection background, or NULL to use the system default color + * @param flags drawing options + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + * + * @since 3.3 + */ +public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { + checkLayout(); + computeRuns(gc); + if (gc is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); + if (gc.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (selectionForeground !is null && selectionForeground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (selectionBackground !is null && selectionBackground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + int length = text.length(); + if (length is 0 && flags is 0) return; + int hdc = gc.handle; + Rectangle clip = gc.getClipping(); + GCData data = gc.data; + int gdipGraphics = data.gdipGraphics; + int foreground = data.foreground; + int alpha = data.alpha; + bool gdip = gdipGraphics !is 0 && (alpha !is 0xFF || data.foregroundPattern !is null); + int clipRgn = 0; + float[] lpXform = null; + Rect gdipRect = new Rect(); + if (gdipGraphics !is 0 && !gdip) { + int matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0); + if (matrix is 0) DWT.error(DWT.ERROR_NO_HANDLES); + Gdip.Graphics_GetTransform(gdipGraphics, matrix); + int identity = gc.identity(); + Gdip.Matrix_Invert(identity); + Gdip.Matrix_Multiply(matrix, identity, Gdip.MatrixOrderAppend); + Gdip.Matrix_delete(identity); + if (!Gdip.Matrix_IsIdentity(matrix)) { + lpXform = new float[6]; + Gdip.Matrix_GetElements(matrix, lpXform); + } + Gdip.Matrix_delete(matrix); + if ((data.style & DWT.MIRRORED) !is 0 && lpXform !is null) { + gdip = true; + lpXform = null; + } else { + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); + int rgn = Gdip.Region_new(); + Gdip.Graphics_GetClip(gdipGraphics, rgn); + if (!Gdip.Region_IsInfinite(rgn, gdipGraphics)) { + clipRgn = Gdip.Region_GetHRGN(rgn, gdipGraphics); + } + Gdip.Region_delete(rgn); + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); + hdc = Gdip.Graphics_GetHDC(gdipGraphics); + } + } + int foregroundBrush = 0, state = 0; + if (gdip) { + gc.checkGC(GC.FOREGROUND); + foregroundBrush = gc.getFgBrush(); + } else { + state = OS.SaveDC(hdc); + if ((data.style & DWT.MIRRORED) !is 0) { + OS.SetLayout(hdc, OS.GetLayout(hdc) | OS.LAYOUT_RTL); + } + if (lpXform !is null) { + OS.SetGraphicsMode(hdc, OS.GM_ADVANCED); + OS.SetWorldTransform(hdc, lpXform); + } + if (clipRgn !is 0) { + OS.SelectClipRgn(hdc, clipRgn); + OS.DeleteObject(clipRgn); + } + } + bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1; + if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) { + selectionStart = Math.min(Math.max(0, selectionStart), length - 1); + selectionEnd = Math.min(Math.max(0, selectionEnd), length - 1); + if (selectionForeground is null) selectionForeground = device.getSystemColor(DWT.COLOR_LIST_SELECTION_TEXT); + if (selectionBackground is null) selectionBackground = device.getSystemColor(DWT.COLOR_LIST_SELECTION); + selectionStart = translateOffset(selectionStart); + selectionEnd = translateOffset(selectionEnd); + } + RECT rect = new RECT(); + int selBrush = 0, selPen = 0, selBrushFg = 0; + if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) { + if (gdip) { + int bg = selectionBackground.handle; + int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16); + int color = Gdip.Color_new(argb); + selBrush = Gdip.SolidBrush_new(color); + Gdip.Color_delete(color); + int fg = selectionForeground.handle; + argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16); + color = Gdip.Color_new(argb); + selBrushFg = Gdip.SolidBrush_new(color); + selPen = Gdip.Pen_new(selBrushFg, 1); + Gdip.Color_delete(color); + } else { + selBrush = OS.CreateSolidBrush(selectionBackground.handle); + selPen = OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle); + } + } + int offset = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? -1 : 0; + OS.SetBkMode(hdc, OS.TRANSPARENT); + for (int line=0; line<runs.length; line++) { + int drawX = x + getLineIndent(line); + int drawY = y + lineY[line]; + StyleItem[] lineRuns = runs[line]; + int lineHeight = lineY[line+1] - lineY[line]; + if (flags !is 0 && (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0)) { + bool extents = false; + if (line is runs.length - 1 && (flags & DWT.LAST_LINE_SELECTION) !is 0) { + extents = true; + } else { + StyleItem run = lineRuns[lineRuns.length - 1]; + if (run.lineBreak && !run.softBreak) { + if (selectionStart <= run.start && run.start <= selectionEnd) extents = true; + } else { + int endOffset = run.start + run.length - 1; + if (selectionStart <= endOffset && endOffset < selectionEnd && (flags & DWT.FULL_SELECTION) !is 0) { + extents = true; + } + } + } + if (extents) { + int width; + if ((flags & DWT.FULL_SELECTION) !is 0) { + width = OS.IsWin95 ? 0x7FFF : 0x6FFFFFF; + } else { + width = (lineHeight - lineSpacing) / 3; + } + if (gdip) { + Gdip.Graphics_FillRectangle(gdipGraphics, selBrush, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing); + } else { + OS.SelectObject(hdc, selBrush); + OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing, OS.PATCOPY); + } + } + } + if (drawX > clip.x + clip.width) continue; + if (drawX + lineWidth[line] < clip.x) continue; + int baseline = Math.max(0, this.ascent); + for (int i = 0; i < lineRuns.length; i++) { + baseline = Math.max(baseline, lineRuns[i].ascent); + } + int alignmentX = drawX; + for (int i = 0; i < lineRuns.length; i++) { + StyleItem run = lineRuns[i]; + if (run.length is 0) continue; + if (drawX > clip.x + clip.width) break; + if (drawX + run.width >= clip.x) { + if (!run.lineBreak || run.softBreak) { + int end = run.start + run.length - 1; + bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end; + if (fullSelection) { + if (gdip) { + Gdip.Graphics_FillRectangle(gdipGraphics, selBrush, drawX, drawY, run.width, lineHeight - lineSpacing); + } else { + OS.SelectObject(hdc, selBrush); + OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight - lineSpacing, OS.PATCOPY); + } + } else { + if (run.style !is null && run.style.background !is null) { + int bg = run.style.background.handle; + int drawRunY = drawY + (baseline - run.ascent); + if (gdip) { + int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16); + int color = Gdip.Color_new(argb); + int brush = Gdip.SolidBrush_new(color); + Gdip.Graphics_FillRectangle(gdipGraphics, brush, drawX, drawRunY, run.width, run.ascent + run.descent); + Gdip.Color_delete(color); + Gdip.SolidBrush_delete(brush); + } else { + int hBrush = OS.CreateSolidBrush (bg); + int oldBrush = OS.SelectObject(hdc, hBrush); + OS.PatBlt(hdc, drawX, drawRunY, run.width, run.ascent + run.descent, OS.PATCOPY); + OS.SelectObject(hdc, oldBrush); + OS.DeleteObject(hBrush); + } + } + bool partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd); + if (partialSelection) { + int selStart = Math.max(selectionStart, run.start) - run.start; + int selEnd = Math.min(selectionEnd, end) - run.start; + int cChars = run.length; + int gGlyphs = run.glyphCount; + int[] piX = new int[1]; + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX); + int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0]; + rect.left = drawX + runX; + rect.top = drawY; + OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX); + runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0]; + rect.right = drawX + runX; + rect.bottom = drawY + lineHeight - lineSpacing; + if (gdip) { + Gdip.Graphics_FillRectangle(gdipGraphics, selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } else { + OS.SelectObject(hdc, selBrush); + OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY); + } + } + } + } + } + drawX += run.width; + } + drawX = alignmentX; + for (int i = 0; i < lineRuns.length; i++) { + StyleItem run = lineRuns[i]; + if (run.length is 0) continue; + if (drawX > clip.x + clip.width) break; + if (drawX + run.width >= clip.x) { + if (!run.tab && (!run.lineBreak || run.softBreak) && !(run.style !is null && run.style.metrics !is null)) { + int end = run.start + run.length - 1; + bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end; + bool partialSelection = hasSelection && !fullSelection && !(selectionStart > end || run.start > selectionEnd); + checkItem(hdc, run); + OS.SelectObject(hdc, getItemFont(run)); + int drawRunY = drawY + (baseline - run.ascent); + if (partialSelection) { + int selStart = Math.max(selectionStart, run.start) - run.start; + int selEnd = Math.min(selectionEnd, end) - run.start; + int cChars = run.length; + int gGlyphs = run.glyphCount; + int[] piX = new int[1]; + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX); + int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0]; + rect.left = drawX + runX; + rect.top = drawY; + OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX); + runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX[0] : piX[0]; + rect.right = drawX + runX; + rect.bottom = drawY + lineHeight; + } + if (gdip) { + OS.BeginPath(hdc); + OS.ScriptTextOut(hdc, run.psc, drawX, drawRunY, 0, null, run.analysis , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); + OS.EndPath(hdc); + int count = OS.GetPath(hdc, null, null, 0); + int[] points = new int[count*2]; + byte[] types = new byte[count]; + OS.GetPath(hdc, points, types, count); + for (int typeIndex = 0; typeIndex < types.length; typeIndex++) { + int newType = 0; + int type = types[typeIndex] & 0xFF; + switch (type & ~OS.PT_CLOSEFIGURE) { + case OS.PT_MOVETO: newType = Gdip.PathPointTypeStart; break; + case OS.PT_LINETO: newType = Gdip.PathPointTypeLine; break; + case OS.PT_BEZIERTO: newType = Gdip.PathPointTypeBezier; break; + } + if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath; + types[typeIndex] = cast(byte)newType; + } + int path = Gdip.GraphicsPath_new(points, types, count, Gdip.FillModeAlternate); + if (path is 0) DWT.error(DWT.ERROR_NO_HANDLES); + int brush = foregroundBrush; + if (fullSelection) { + brush = selBrushFg; + } else { + if (run.style !is null && run.style.foreground !is null) { + int fg = run.style.foreground.handle; + int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16); + int color = Gdip.Color_new(argb); + brush = Gdip.SolidBrush_new(color); + Gdip.Color_delete(color); + } + } + int gstate = 0; + if (partialSelection) { + gdipRect.X = rect.left; + gdipRect.Y = rect.top; + gdipRect.Width = rect.right - rect.left; + gdipRect.Height = rect.bottom - rect.top; + gstate = Gdip.Graphics_Save(gdipGraphics); + Gdip.Graphics_SetClip(gdipGraphics, gdipRect, Gdip.CombineModeExclude); + } + int antialias = Gdip.Graphics_GetSmoothingMode(gdipGraphics), textAntialias = 0; + int mode = Gdip.Graphics_GetTextRenderingHint(data.gdipGraphics); + switch (mode) { + case Gdip.TextRenderingHintSystemDefault: textAntialias = Gdip.SmoothingModeAntiAlias; break; + case Gdip.TextRenderingHintSingleBitPerPixel: + case Gdip.TextRenderingHintSingleBitPerPixelGridFit: textAntialias = Gdip.SmoothingModeNone; break; + case Gdip.TextRenderingHintAntiAlias: + case Gdip.TextRenderingHintAntiAliasGridFit: + case Gdip.TextRenderingHintClearTypeGridFit: textAntialias = Gdip.SmoothingModeAntiAlias; break; + } + Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias); + int gstate2 = 0; + if ((data.style & DWT.MIRRORED) !is 0) { + gstate2 = Gdip.Graphics_Save(gdipGraphics); + Gdip.Graphics_ScaleTransform(gdipGraphics, -1, 1, Gdip.MatrixOrderPrepend); + Gdip.Graphics_TranslateTransform(gdipGraphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend); + } + Gdip.Graphics_FillPath(gdipGraphics, brush, path); + if ((data.style & DWT.MIRRORED) !is 0) { + Gdip.Graphics_Restore(gdipGraphics, gstate2); + } + Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias); + if (run.style !is null && (run.style.underline || run.style.strikeout)) { + int newPen = hasSelection ? selPen : Gdip.Pen_new(brush, 1); + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); + if (run.style.underline) { + int underlineY = drawY + baseline + 1 - run.style.rise; + Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, underlineY, drawX + run.width, underlineY); + } + if (run.style.strikeout) { + int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; + Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, strikeoutY, drawX + run.width, strikeoutY); + } + if (newPen !is selPen) Gdip.Pen_delete(newPen); + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); + } + if (partialSelection) { + Gdip.Graphics_Restore(gdipGraphics, gstate); + gstate = Gdip.Graphics_Save(gdipGraphics); + Gdip.Graphics_SetClip(gdipGraphics, gdipRect, Gdip.CombineModeIntersect); + Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias); + Gdip.Graphics_FillPath(gdipGraphics, selBrushFg, path); + Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias); + if (run.style !is null && (run.style.underline || run.style.strikeout)) { + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); + if (run.style.underline) { + int underlineY = drawY + baseline + 1 - run.style.rise; + Gdip.Graphics_DrawLine(gdipGraphics, selPen, rect.left, underlineY, rect.right, underlineY); + } + if (run.style.strikeout) { + int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; + Gdip.Graphics_DrawLine(gdipGraphics, selPen, rect.left, strikeoutY, rect.right, strikeoutY); + } + Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); + } + Gdip.Graphics_Restore(gdipGraphics, gstate); + } + Gdip.GraphicsPath_delete(path); + if (brush !is selBrushFg && brush !is foregroundBrush) Gdip.SolidBrush_delete(brush); + } else { + int fg = foreground; + if (fullSelection) { + fg = selectionForeground.handle; + } else { + if (run.style !is null && run.style.foreground !is null) fg = run.style.foreground.handle; + } + OS.SetTextColor(hdc, fg); + OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, 0, null, run.analysis , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); + if (run.style !is null && (run.style.underline || run.style.strikeout)) { + int newPen = hasSelection && fg is selectionForeground.handle ? selPen : OS.CreatePen(OS.PS_SOLID, 1, fg); + int oldPen = OS.SelectObject(hdc, newPen); + if (run.style.underline) { + int underlineY = drawY + baseline + 1 - run.style.rise; + OS.MoveToEx(hdc, drawX, underlineY, 0); + OS.LineTo(hdc, drawX + run.width, underlineY); + } + if (run.style.strikeout) { + int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; + OS.MoveToEx(hdc, drawX, strikeoutY, 0); + OS.LineTo(hdc, drawX + run.width, strikeoutY); + } + OS.SelectObject(hdc, oldPen); + if (!hasSelection || fg !is selectionForeground.handle) OS.DeleteObject(newPen); + } + if (partialSelection && fg !is selectionForeground.handle) { + OS.SetTextColor(hdc, selectionForeground.handle); + OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, OS.ETO_CLIPPED, rect, run.analysis , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); + if (run.style !is null && (run.style.underline || run.style.strikeout)) { + int oldPen = OS.SelectObject(hdc, selPen); + if (run.style.underline) { + int underlineY = drawY + baseline + 1 - run.style.rise; + OS.MoveToEx(hdc, rect.left, underlineY, 0); + OS.LineTo(hdc, rect.right, underlineY); + } + if (run.style.strikeout) { + int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; + OS.MoveToEx(hdc, rect.left, strikeoutY, 0); + OS.LineTo(hdc, rect.right, strikeoutY); + } + OS.SelectObject(hdc, oldPen); + } + } + } + } + } + drawX += run.width; + } + } + if (gdip) { + if (selBrush !is 0) Gdip.SolidBrush_delete(selBrush); + if (selBrushFg !is 0) Gdip.SolidBrush_delete(selBrushFg); + if (selPen !is 0) Gdip.Pen_delete(selPen); + } else { + OS.RestoreDC(hdc, state); + if (gdipGraphics !is 0) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc); + if (selBrush !is 0) OS.DeleteObject (selBrush); + if (selPen !is 0) OS.DeleteObject (selPen); + } +} + +void freeRuns () { + if (allRuns is null) return; + for (int i=0; i<allRuns.length; i++) { + StyleItem run = allRuns[i]; + run.free(); + } + allRuns = null; + runs = null; + segmentsText = null; +} + +/** + * Returns the receiver's horizontal text alignment, which will be one + * of <code>DWT.LEFT</code>, <code>DWT.CENTER</code> or + * <code>DWT.RIGHT</code>. + * + * @return the alignment used to positioned text horizontally + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getAlignment () { + checkLayout(); + return alignment; +} + +/** + * Returns the ascent of the receiver. + * + * @return the ascent + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getDescent() + * @see #setDescent(int) + * @see #setAscent(int) + * @see #getLineMetrics(int) + */ +public int getAscent () { + checkLayout(); + return ascent; +} + +/** + * Returns the bounds of the receiver. + * + * @return the bounds of the receiver + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Rectangle getBounds () { + checkLayout(); + computeRuns(null); + int width = 0; + if (wrapWidth !is -1) { + width = wrapWidth; + } else { + for (int line=0; line<runs.length; line++) { + width = Math.max(width, lineWidth[line] + getLineIndent(line)); + } + } + return new Rectangle (0, 0, width, lineY[lineY.length - 1]); +} + +/** + * Returns the bounds for the specified range of characters. The + * bounds is the smallest rectangle that encompasses all characters + * in the range. The start and end offsets are inclusive and will be + * clamped if out of range. + * + * @param start the start offset + * @param end the end offset + * @return the bounds of the character range + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Rectangle getBounds (int start, int end) { + checkLayout(); + computeRuns(null); + int length = text.length(); + if (length is 0) return new Rectangle(0, 0, 0, 0); + if (start > end) return new Rectangle(0, 0, 0, 0); + start = Math.min(Math.max(0, start), length - 1); + end = Math.min(Math.max(0, end), length - 1); + start = translateOffset(start); + end = translateOffset(end); + int left = 0x7fffffff, right = 0; + int top = 0x7fffffff, bottom = 0; + bool isRTL = (orientation & DWT.RIGHT_TO_LEFT) !is 0; + for (int i = 0; i < allRuns.length - 1; i++) { + StyleItem run = allRuns[i]; + int runEnd = run.start + run.length; + if (runEnd <= start) continue; + if (run.start > end) break; + int runLead = run.x; + int runTrail = run.x + run.width; + if (run.start <= start && start < runEnd) { + int cx = 0; + if (run.style !is null && run.style.metrics !is null) { + GlyphMetrics metrics = run.style.metrics; + cx = metrics.width * (start - run.start); + } else if (!run.tab) { + int[] piX = new int[1]; + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptCPtoX(start - run.start, false, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX); + cx = isRTL ? run.width - piX[0] : piX[0]; + } + if (run.analysis.fRTL ^ isRTL) { + runTrail = run.x + cx; + } else { + runLead = run.x + cx; + } + } + if (run.start <= end && end < runEnd) { + int cx = run.width; + if (run.style !is null && run.style.metrics !is null) { + GlyphMetrics metrics = run.style.metrics; + cx = metrics.width * (end - run.start + 1); + } else if (!run.tab) { + int[] piX = new int[1]; + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptCPtoX(end - run.start, true, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX); + cx = isRTL ? run.width - piX[0] : piX[0]; + } + if (run.analysis.fRTL ^ isRTL) { + runLead = run.x + cx; + } else { + runTrail = run.x + cx; + } + } + int lineIndex = 0; + while (lineIndex < runs.length && lineOffset[lineIndex + 1] <= run.start) { + lineIndex++; + } + left = Math.min(left, runLead); + right = Math.max(right, runTrail); + top = Math.min(top, lineY[lineIndex]); + bottom = Math.max(bottom, lineY[lineIndex + 1] - lineSpacing); + } + return new Rectangle(left, top, right - left, bottom - top); +} + +/** + * Returns the descent of the receiver. + * + * @return the descent + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getAscent() + * @see #setAscent(int) + * @see #setDescent(int) + * @see #getLineMetrics(int) + */ +public int getDescent () { + checkLayout(); + return descent; +} + +/** + * Returns the default font currently being used by the receiver + * to draw and measure text. + * + * @return the receiver's font + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Font getFont () { + checkLayout(); + return font; +} + +/** +* Returns the receiver's indent. +* +* @return the receiver's indent +* +* @exception DWTException <ul> +* <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> +* </ul> +* +* @since 3.2 +*/ +public int getIndent () { + checkLayout(); + return indent; +} + +/** +* Returns the receiver's justification. +* +* @return the receiver's justification +* +* @exception DWTException <ul> +* <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> +* </ul> +* +* @since 3.2 +*/ +public bool getJustify () { + checkLayout(); + return justify; +} + +int getItemFont (StyleItem item) { + if (item.fallbackFont !is 0) return item.fallbackFont; + if (item.style !is null && item.style.font !is null) { + return item.style.font.handle; + } + if (this.font !is null) { + return this.font.handle; + } + return device.systemFont; +} + +/** + * Returns the embedding level for the specified character offset. The + * embedding level is usually used to determine the directionality of a + * character in bidirectional text. + * + * @param offset the character offset + * @return the embedding level + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + */ +public int getLevel (int offset) { + checkLayout(); + computeRuns(null); + int length = text.length(); + if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); + offset = translateOffset(offset); + for (int i=1; i<allRuns.length; i++) { + if (allRuns[i].start > offset) { + return allRuns[i - 1].analysis.s.uBidiLevel; + } + } + return (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? 1 : 0; +} + +/** + * Returns the bounds of the line for the specified line index. + * + * @param lineIndex the line index + * @return the line bounds + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Rectangle getLineBounds(int lineIndex) { + checkLayout(); + computeRuns(null); + if (!(0 <= lineIndex && lineIndex < runs.length)) DWT.error(DWT.ERROR_INVALID_RANGE); + int x = getLineIndent(lineIndex); + int y = lineY[lineIndex]; + int width = lineWidth[lineIndex]; + int height = lineY[lineIndex + 1] - y - lineSpacing; + return new Rectangle (x, y, width, height); +} + +/** + * Returns the receiver's line count. This includes lines caused + * by wrapping. + * + * @return the line count + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getLineCount () { + checkLayout(); + computeRuns(null); + return runs.length; +} + +int getLineIndent (int lineIndex) { + int lineIndent = 0; + if (lineIndex is 0) { + lineIndent = indent; + } else { + StyleItem[] previousLine = runs[lineIndex - 1]; + StyleItem previousRun = previousLine[previousLine.length - 1]; + if (previousRun.lineBreak && !previousRun.softBreak) { + lineIndent = indent; + } + } + if (wrapWidth !is -1) { + bool partialLine = true; + if (justify) { + StyleItem[] lineRun = runs[lineIndex]; + if (lineRun[lineRun.length - 1].softBreak) { + partialLine = false; + } + } + if (partialLine) { + int lineWidth = this.lineWidth[lineIndex] + lineIndent; + switch (alignment) { + case DWT.CENTER: lineIndent += (wrapWidth - lineWidth) / 2; break; + case DWT.RIGHT: lineIndent += wrapWidth - lineWidth; break; + } + } + } + return lineIndent; +} + +/** + * Returns the index of the line that contains the specified + * character offset. + * + * @param offset the character offset + * @return the line index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getLineIndex (int offset) { + checkLayout(); + computeRuns(null); + int length = text.length(); + if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); + offset = translateOffset(offset); + for (int line=0; line<runs.length; line++) { + if (lineOffset[line + 1] > offset) { + return line; + } + } + return runs.length - 1; +} + +/** + * Returns the font metrics for the specified line index. + * + * @param lineIndex the line index + * @return the font metrics + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public FontMetrics getLineMetrics (int lineIndex) { + checkLayout(); + computeRuns(null); + if (!(0 <= lineIndex && lineIndex < runs.length)) DWT.error(DWT.ERROR_INVALID_RANGE); + int hDC = device.internal_new_GC(null); + int srcHdc = OS.CreateCompatibleDC(hDC); + TEXTMETRIC lptm; + OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont); + OS.GetTextMetrics(srcHdc, &lptm); + OS.DeleteDC(srcHdc); + device.internal_dispose_GC(hDC, null); + + int ascent = Math.max(lptm.tmAscent, this.ascent); + int descent = Math.max(lptm.tmDescent, this.descent); + int leading = lptm.tmInternalLeading; + if (text.length() !is 0) { + StyleItem[] lineRuns = runs[lineIndex]; + for (int i = 0; i<lineRuns.length; i++) { + StyleItem run = lineRuns[i]; + if (run.ascent > ascent) { + ascent = run.ascent; + leading = run.leading; + } + descent = Math.max(descent, run.descent); + } + } + lptm.tmAscent = ascent; + lptm.tmDescent = descent; + lptm.tmHeight = ascent + descent; + lptm.tmInternalLeading = leading; + lptm.tmAveCharWidth = 0; + return FontMetrics.win32_new(lptm); +} + +/** + * Returns the line offsets. Each value in the array is the + * offset for the first character in a line except for the last + * value, which contains the length of the text. + * + * @return the line offsets + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getLineOffsets () { + checkLayout(); + computeRuns(null); + int[] offsets = new int[lineOffset.length]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = untranslateOffset(lineOffset[i]); + } + return offsets; +} + +/** + * Returns the location for the specified character offset. The + * <code>trailing</code> argument indicates whether the offset + * corresponds to the leading or trailing edge of the cluster. + * + * @param offset the character offset + * @param trailing the trailing flag + * @return the location of the character offset + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getOffset(Point, int[]) + * @see #getOffset(int, int, int[]) + */ +public Point getLocation (int offset, bool trailing) { + checkLayout(); + computeRuns(null); + int length = text.length(); + if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); + length = segmentsText.length(); + offset = translateOffset(offset); + int line; + for (line=0; line<runs.length; line++) { + if (lineOffset[line + 1] > offset) break; + } + line = Math.min(line, runs.length - 1); + StyleItem[] lineRuns = runs[line]; + Point result = null; + if (offset is length) { + result = new Point(lineWidth[line], lineY[line]); + } else { + int width = 0; + for (int i=0; i<lineRuns.length; i++) { + StyleItem run = lineRuns[i]; + int end = run.start + run.length; + if (run.start <= offset && offset < end) { + if (run.style !is null && run.style.metrics !is null) { + GlyphMetrics metrics = run.style.metrics; + width += metrics.width * (offset - run.start + (trailing ? 1 : 0)); + result = new Point(width, lineY[line]); + } else if (run.tab) { + if (trailing || (offset is length)) width += run.width; + result = new Point(width, lineY[line]); + } else { + int runOffset = offset - run.start; + int cChars = run.length; + int gGlyphs = run.glyphCount; + int[] piX = new int[1]; + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX); + if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { + result = new Point(width + (run.width - piX[0]), lineY[line]); + } else { + result = new Point(width + piX[0], lineY[line]); + } + } + break; + } + width += run.width; + } + } + if (result is null) result = new Point(0, 0); + result.x += getLineIndent(line); + return result; +} + +/** + * Returns the next offset for the specified offset and movement + * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>, + * <code>DWT.MOVEMENT_CLUSTER</code>, <code>DWT.MOVEMENT_WORD</code>, + * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>. + * + * @param offset the start offset + * @param movement the movement type + * @return the next offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getPreviousOffset(int, int) + */ +public int getNextOffset (int offset, int movement) { + checkLayout(); + return _getOffset (offset, movement, true); +} + +int _getOffset(int offset, int movement, bool forward) { + computeRuns(null); + int length = text.length(); + if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); + if (forward && offset is length) return length; + if (!forward && offset is 0) return 0; + int step = forward ? 1 : -1; + if ((movement & DWT.MOVEMENT_CHAR) !is 0) return offset + step; + length = segmentsText.length(); + offset = translateOffset(offset); + SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR(); + SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES(); + int i = forward ? 0 : allRuns.length - 1; + offset = validadeOffset(offset, step); + do { + StyleItem run = allRuns[i]; + if (run.start <= offset && offset < run.start + run.length) { + if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start); + if (run.tab) return untranslateOffset(run.start); + OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof); + bool isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking; + if (isComplex) breakRun(run); + while (run.start <= offset && offset < run.start + run.length) { + if (isComplex) { + OS.MoveMemory(logAttr, run.psla + ((offset - run.start) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); + } + switch (movement) { + case DWT.MOVEMENT_CLUSTER: { + if (properties.fNeedsCaretInfo) { + if (!logAttr.fInvalid && logAttr.fCharStop) return untranslateOffset(offset); + } else { + return untranslateOffset(offset); + } + break; + } + case DWT.MOVEMENT_WORD_START: + case DWT.MOVEMENT_WORD: { + if (properties.fNeedsWordBreaking) { + if (!logAttr.fInvalid && logAttr.fWordStop) return untranslateOffset(offset); + } else { + if (offset > 0) { + bool letterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset)); + bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1)); + if (letterOrDigit !is previousLetterOrDigit || !letterOrDigit) { + if (!Compatibility.isWhitespace(segmentsText.charAt(offset))) { + return untranslateOffset(offset); + } + } + } + } + break; + } + case DWT.MOVEMENT_WORD_END: { + if (offset > 0) { + bool isLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset)); + bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1)); + if (!isLetterOrDigit && previousLetterOrDigit) { + return untranslateOffset(offset); + } + } + break; + } + } + offset = validadeOffset(offset, step); + } + } + i += step; + } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length); + return forward ? text.length() : 0; +} + +/** + * Returns the character offset for the specified point. + * For a typical character, the trailing argument will be filled in to + * indicate whether the point is closer to the leading edge (0) or + * the trailing edge (1). When the point is over a cluster composed + * of multiple characters, the trailing argument will be filled with the + * position of the character in the cluster that is closest to + * the point. + * + * @param point the point + * @param trailing the trailing buffer + * @return the character offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> + * <li>ERROR_NULL_ARGUMENT - if the point is null</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getLocation(int, bool) + */ +public int getOffset (Point point, int[] trailing) { + checkLayout(); + if (point is null) DWT.error (DWT.ERROR_NULL_ARGUMENT); + return getOffset (point.x, point.y, trailing) ; +} + +/** + * Returns the character offset for the specified point. + * For a typical character, the trailing argument will be filled in to + * indicate whether the point is closer to the leading edge (0) or + * the trailing edge (1). When the point is over a cluster composed + * of multiple characters, the trailing argument will be filled with the + * position of the character in the cluster that is closest to + * the point. + * + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @param trailing the trailing buffer + * @return the character offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getLocation(int, bool) + */ +public int getOffset (int x, int y, int[] trailing) { + checkLayout(); + computeRuns(null); + if (trailing !is null && trailing.length < 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + int line; + int lineCount = runs.length; + for (line=0; line<lineCount; line++) { + if (lineY[line + 1] > y) break; + } + line = Math.min(line, runs.length - 1); + x -= getLineIndent(line); + StyleItem[] lineRuns = runs[line]; + if (x >= lineWidth[line]) x = lineWidth[line] - 1; + if (x < 0) x = 0; + int width = 0; + for (int i = 0; i < lineRuns.length; i++) { + StyleItem run = lineRuns[i]; + if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start); + if (width + run.width > x) { + int xRun = x - width; + if (run.style !is null && run.style.metrics !is null) { + GlyphMetrics metrics = run.style.metrics; + if (metrics.width > 0) { + if (trailing !is null) { + trailing[0] = (xRun % metrics.width < metrics.width / 2) ? 0 : 1; + } + return untranslateOffset(run.start + xRun / metrics.width); + } + } + if (run.tab) { + if (trailing !is null) trailing[0] = x < (width + run.width / 2) ? 0 : 1; + return untranslateOffset(run.start); + } + int cChars = run.length; + int cGlyphs = run.glyphCount; + int[] piCP = new int[1]; + int[] piTrailing = new int[1]; + if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { + xRun = run.width - xRun; + } + int advances = run.justify !is 0 ? run.justify : run.advances; + OS.ScriptXtoCP(xRun, cChars, cGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piCP, piTrailing); + if (trailing !is null) trailing[0] = piTrailing[0]; + return untranslateOffset(run.start + piCP[0]); + } + width += run.width; + } + if (trailing !is null) trailing[0] = 0; + return untranslateOffset(lineOffset[line + 1]); +} + +/** + * Returns the orientation of the receiver. + * + * @return the orientation style + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getOrientation () { + checkLayout(); + return orientation; +} + +/** + * Returns the previous offset for the specified offset and movement + * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>, + * <code>DWT.MOVEMENT_CLUSTER</code> or <code>DWT.MOVEMENT_WORD</code>, + * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>. + * + * @param offset the start offset + * @param movement the movement type + * @return the previous offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getNextOffset(int, int) + */ +public int getPreviousOffset (int offset, int movement) { + checkLayout(); + return _getOffset (offset, movement, false); +} + +/** + * Gets the ranges of text that are associated with a <code>TextStyle</code>. + * + * @return the ranges, an array of offsets representing the start and end of each + * text style. + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getStyles() + * + * @since 3.2 + */ +public int[] getRanges () { + checkLayout(); + int[] result = new int[styles.length * 2]; + int count = 0; + for (int i=0; i<styles.length - 1; i++) { + if (styles[i].style !is null) { + result[count++] = styles[i].start; + result[count++] = styles[i + 1].start - 1; + } + } + if (count !is result.length) { + int[] newResult = new int[count]; + System.arraycopy(result, 0, newResult, 0, count); + result = newResult; + } + return result; +} + +/** + * Returns the text segments offsets of the receiver. + * + * @return the text segments offsets + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getSegments () { + checkLayout(); + return segments; +} + +char[] getSegmentsText() { + if (segments is null) return text; + int nSegments = segments.length; + if (nSegments <= 1) return text; + int length = text.length(); + if (length is 0) return text; + if (nSegments is 2) { + if (segments[0] is 0 && segments[1] is length) return text; + } + char[] oldChars = new char[length]; + text.getChars(0, length, oldChars, 0); + char[] newChars = new char[length + nSegments]; + int charCount = 0, segmentCount = 0; + char separator = orientation is DWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK; + while (charCount < length) { + if (segmentCount < nSegments && charCount is segments[segmentCount]) { + newChars[charCount + segmentCount++] = separator; + } else { + newChars[charCount + segmentCount] = oldChars[charCount++]; + } + } + if (segmentCount < nSegments) { + segments[segmentCount] = charCount; + newChars[charCount + segmentCount++] = separator; + } + return new char[](newChars, 0, Math.min(charCount + segmentCount, newChars.length)); +} + +/** + * Returns the line spacing of the receiver. + * + * @return the line spacing + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getSpacing () { + checkLayout(); + return lineSpacing; +} + +/** + * Gets the style of the receiver at the specified character offset. + * + * @param offset the text offset + * @return the style or <code>null</code> if not set + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public TextStyle getStyle (int offset) { + checkLayout(); + int length = text.length(); + if (!(0 <= offset && offset < length)) DWT.error(DWT.ERROR_INVALID_RANGE); + for (int i=1; i<styles.length; i++) { + if (styles[i].start > offset) { + return styles[i - 1].style; + } + } + return null; +} + +/** + * Gets all styles of the receiver. + * + * @return the styles + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getRanges() + * + * @since 3.2 + */ +public TextStyle[] getStyles () { + checkLayout(); + TextStyle[] result = new TextStyle[styles.length]; + int count = 0; + for (int i=0; i<styles.length; i++) { + if (styles[i].style !is null) { + result[count++] = styles[i].style; + } + } + if (count !is result.length) { + TextStyle[] newResult = new TextStyle[count]; + System.arraycopy(result, 0, newResult, 0, count); + result = newResult; + } + return result; +} + +/** + * Returns the tab list of the receiver. + * + * @return the tab list + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getTabs () { + checkLayout(); + return tabs; +} + +/** + * Gets the receiver's text, which will be an empty + * string if it has never been set. + * + * @return the receiver's text + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public char[] getText () { + checkLayout(); + return text; +} + +/** + * Returns the width of the receiver. + * + * @return the width + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getWidth () { + checkLayout(); + return wrapWidth; +} + +/** + * Returns <code>true</code> if the text layout has been disposed, + * and <code>false</code> otherwise. + * <p> + * This method gets the dispose state for the text layout. + * When a text layout has been disposed, it is an error to + * invoke any other method using the text layout. + * </p> + * + * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise + */ +public bool isDisposed () { + return device is null; +} + +/* + * Itemize the receiver text + */ +StyleItem[] itemize () { + segmentsText = getSegmentsText(); + int length = segmentsText.length(); + SCRIPT_CONTROL scriptControl = new SCRIPT_CONTROL(); + SCRIPT_STATE scriptState = new SCRIPT_STATE(); + final int MAX_ITEM = length + 1; + + if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { + scriptState.uBidiLevel = 1; + scriptState.fArabicNumContext = true; + SCRIPT_DIGITSUBSTITUTE psds = new SCRIPT_DIGITSUBSTITUTE(); + OS.ScriptRecordDigitSubstitution(OS.LOCALE_USER_DEFAULT, psds); + OS.ScriptApplyDigitSubstitution(psds, scriptControl, scriptState); + } + + int hHeap = OS.GetProcessHeap(); + int pItems = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof); + if (pItems is 0) DWT.error(DWT.ERROR_NO_HANDLES); + int[] pcItems = new int[1]; + char[] chars = new char[length]; + segmentsText.getChars(0, length, chars, 0); + OS.ScriptItemize(chars, length, MAX_ITEM, scriptControl, scriptState, pItems, pcItems); +// if (hr is E_OUTOFMEMORY) //TODO handle it + + StyleItem[] runs = merge(pItems, pcItems[0]); + OS.HeapFree(hHeap, 0, pItems); + return runs; +} + +/* + * Merge styles ranges and script items + */ +StyleItem[] merge (int items, int itemCount) { + int count = 0, start = 0, end = segmentsText.length(), itemIndex = 0, styleIndex = 0; + StyleItem[] runs = new StyleItem[itemCount + styles.length]; + SCRIPT_ITEM scriptItem = new SCRIPT_ITEM(); + bool linkBefore = false; + while (start < end) { + StyleItem item = new StyleItem(); + item.start = start; + item.style = styles[styleIndex].style; + runs[count++] = item; + OS.MoveMemory(scriptItem, items + itemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof); + item.analysis = scriptItem.a; + if (linkBefore) { + item.analysis.fLinkBefore = true; + linkBefore = false; + } + scriptItem.a = new SCRIPT_ANALYSIS(); + OS.MoveMemory(scriptItem, items + (itemIndex + 1) * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof); + int itemLimit = scriptItem.iCharPos; + int styleLimit = translateOffset(styles[styleIndex + 1].start); + if (styleLimit <= itemLimit) { + styleIndex++; + start = styleLimit; + if (start < itemLimit && 0 < start && start < end) { + char pChar = segmentsText.charAt(start - 1); + char tChar = segmentsText.charAt(start); + if (!Compatibility.isWhitespace(pChar) && !Compatibility.isWhitespace(tChar)) { + item.analysis.fLinkAfter = true; + linkBefore = true; + } + } + } + if (itemLimit <= styleLimit) { + itemIndex++; + start = itemLimit; + } + item.length = start - item.start; + } + StyleItem item = new StyleItem(); + item.start = end; + OS.MoveMemory(scriptItem, items + itemCount * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof); + item.analysis = scriptItem.a; + runs[count++] = item; + if (runs.length !is count) { + StyleItem[] result = new StyleItem[count]; + System.arraycopy(runs, 0, result, 0, count); + return result; + } + return runs; +} + +/* + * Reorder the run + */ +StyleItem[] reorder (StyleItem[] runs, bool terminate) { + int length = runs.length; + if (length <= 1) return runs; + byte[] bidiLevels = new byte[length]; + for (int i=0; i<length; i++) { + bidiLevels[i] = cast(byte)(runs[i].analysis.s.uBidiLevel & 0x1F); + } + /* + * Feature in Windows. If the orientation is RTL Uniscribe will + * resolve the level of line breaks to 1, this can cause the line + * break to be reorder to the middle of the line. The fix is to set + * the level to zero to prevent it to be reordered. + */ + StyleItem lastRun = runs[length - 1]; + if (lastRun.lineBreak && !lastRun.softBreak) { + bidiLevels[length - 1] = 0; + } + int[] log2vis = new int[length]; + OS.ScriptLayout(length, bidiLevels, null, log2vis); + StyleItem[] result = new StyleItem[length]; + for (int i=0; i<length; i++) { + result[log2vis[i]] = runs[i]; + } + if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { + if (terminate) length--; + for (int i = 0; i < length / 2 ; i++) { + StyleItem tmp = result[i]; + result[i] = result[length - i - 1]; + result[length - i - 1] = tmp; + } + } + return result; +} + +/** + * Sets the text alignment for the receiver. The alignment controls + * how a line of text is positioned horizontally. The argument should + * be one of <code>DWT.LEFT</code>, <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>. + * <p> + * The default alignment is <code>DWT.LEFT</code>. Note that the receiver's + * width must be set in order to use <code>DWT.RIGHT</code> or <code>DWT.CENTER</code> + * alignment. + * </p> + * + * @param alignment the new alignment + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setWidth(int) + */ +public void setAlignment (int alignment) { + checkLayout(); + int mask = DWT.LEFT | DWT.CENTER | DWT.RIGHT; + alignment &= mask; + if (alignment is 0) return; + if ((alignment & DWT.LEFT) !is 0) alignment = DWT.LEFT; + if ((alignment & DWT.RIGHT) !is 0) alignment = DWT.RIGHT; + if (this.alignment is alignment) return; + freeRuns(); + this.alignment = alignment; +} + +/** + * Sets the ascent of the receiver. The ascent is distance in pixels + * from the baseline to the top of the line and it is applied to all + * lines. The default value is <code>-1</code> which means that the + * ascent is calculated from the line fonts. + * + * @param ascent the new ascent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setDescent(int) + * @see #getLineMetrics(int) + */ +public void setAscent(int ascent) { + checkLayout(); + if (ascent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (this.ascent is ascent) return; + freeRuns(); + this.ascent = ascent; +} + +/** + * Sets the descent of the receiver. The descent is distance in pixels + * from the baseline to the bottom of the line and it is applied to all + * lines. The default value is <code>-1</code> which means that the + * descent is calculated from the line fonts. + * + * @param descent the new descent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setAscent(int) + * @see #getLineMetrics(int) + */ +public void setDescent(int descent) { + checkLayout(); + if (descent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (this.descent is descent) return; + freeRuns(); + this.descent = descent; +} + +/** + * Sets the default font which will be used by the receiver + * to draw and measure text. If the + * argument is null, then a default font appropriate + * for the platform will be used instead. Note that a text + * style can override the default font. + * + * @param font the new font for the receiver, or null to indicate a default font + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setFont (Font font) { + checkLayout(); + if (font !is null && font.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (this.font is font) return; + if (font !is null && font.equals(this.font)) return; + freeRuns(); + this.font = font; +} + +/** + * Sets the indent of the receiver. This indent it applied of the first line of + * each paragraph. + * + * @param indent new indent + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @since 3.2 + */ +public void setIndent (int indent) { + checkLayout(); + if (indent < 0) return; + if (this.indent is indent) return; + freeRuns(); + this.indent = indent; +} + +/** + * Sets the justification of the receiver. Note that the receiver's + * width must be set in order to use justification. + * + * @param justify new justify + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @since 3.2 + */ +public void setJustify (bool justify) { + checkLayout(); + if (this.justify is justify) return; + freeRuns(); + this.justify = justify; +} + +/** + * Sets the orientation of the receiver, which must be one + * of <code>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>. + * + * @param orientation new orientation style + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setOrientation (int orientation) { + checkLayout(); + int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT; + orientation &= mask; + if (orientation is 0) return; + if ((orientation & DWT.LEFT_TO_RIGHT) !is 0) orientation = DWT.LEFT_TO_RIGHT; + if (this.orientation is orientation) return; + this.orientation = orientation; + freeRuns(); +} + +/** + * Sets the offsets of the receiver's text segments. Text segments are used to + * override the default behaviour of the bidirectional algorithm. + * Bidirectional reordering can happen within a text segment but not + * between two adjacent segments. + * <p> + * Each text segment is determined by two consecutive offsets in the + * <code>segments</code> arrays. The first element of the array should + * always be zero and the last one should always be equals to length of + * the text. + * </p> + * + * @param segments the text segments offset + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setSegments(int[] segments) { + checkLayout(); + if (this.segments is null && segments is null) return; + if (this.segments !is null && segments !is null) { + if (this.segments.length is segments.length) { + int i; + for (i = 0; i <segments.length; i++) { + if (this.segments[i] !is segments[i]) break; + } + if (i is segments.length) return; + } + } + freeRuns(); + this.segments = segments; +} + +/** + * Sets the line spacing of the receiver. The line spacing + * is the space left between lines. + * + * @param spacing the new line spacing + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setSpacing (int spacing) { + checkLayout(); + if (spacing < 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (this.lineSpacing is spacing) return; + freeRuns(); + this.lineSpacing = spacing; +} + +/** + * Sets the style of the receiver for the specified range. Styles previously + * set for that range will be overwritten. The start and end offsets are + * inclusive and will be clamped if out of range. + * + * @param style the style + * @param start the start offset + * @param end the end offset + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setStyle (TextStyle style, int start, int end) { + checkLayout(); + int length = text.length(); + if (length is 0) return; + if (start > end) return; + start = Math.min(Math.max(0, start), length - 1); + end = Math.min(Math.max(0, end), length - 1); + int low = -1; + int high = styles.length; + while (high - low > 1) { + int index = (high + low) / 2; + if (styles[index + 1].start > start) { + high = index; + } else { + low = index; + } + } + if (0 <= high && high < styles.length) { + StyleItem item = styles[high]; + if (item.start is start && styles[high + 1].start - 1 is end) { + if (style is null) { + if (item.style is null) return; + } else { + if (style.equals(item.style)) return; + } + } + } + freeRuns(); + int modifyStart = high; + int modifyEnd = modifyStart; + while (modifyEnd < styles.length) { + if (styles[modifyEnd + 1].start > end) break; + modifyEnd++; + } + if (modifyStart is modifyEnd) { + int styleStart = styles[modifyStart].start; + int styleEnd = styles[modifyEnd + 1].start - 1; + if (styleStart is start && styleEnd is end) { + styles[modifyStart].style = style; + return; + } + if (styleStart !is start && styleEnd !is end) { + StyleItem[] newStyles = new StyleItem[styles.length + 2]; + System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); + StyleItem item = new StyleItem(); + item.start = start; + item.style = style; + newStyles[modifyStart + 1] = item; + item = new StyleItem(); + item.start = end + 1; + item.style = styles[modifyStart].style; + newStyles[modifyStart + 2] = item; + System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1); + styles = newStyles; + return; + } + } + if (start is styles[modifyStart].start) modifyStart--; + if (end is styles[modifyEnd + 1].start - 1) modifyEnd++; + int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1); + StyleItem[] newStyles = new StyleItem[newLength]; + System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); + StyleItem item = new StyleItem(); + item.start = start; + item.style = style; + newStyles[modifyStart + 1] = item; + styles[modifyEnd].start = end + 1; + System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd); + styles = newStyles; +} + +/** + * Sets the receiver's tab list. Each value in the tab list specifies + * the space in pixels from the origin of the text layout to the respective + * tab stop. The last tab stop width is repeated continuously. + * + * @param tabs the new tab list + * + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setTabs (int[] tabs) { + checkLayout(); + if (this.tabs is null && tabs is null) return; + if (this.tabs !is null && tabs !is null) { + if (this.tabs.length is tabs.length) { + int i; + for (i = 0; i <tabs.length; i++) { + if (this.tabs[i] !is tabs[i]) break; + } + if (i is tabs.length) return; + } + } + freeRuns(); + this.tabs = tabs; +} + +/** + * Sets the receiver's text. + * + * @param text the new text + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the text is null</li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setText (char[] text) { + checkLayout(); + if (text is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); + if (text.equals(this.text)) return; + freeRuns(); + this.text = text; + styles = new StyleItem[2]; + styles[0] = new StyleItem(); + styles[1] = new StyleItem(); + styles[1].start = text.length(); +} + +/** + * Sets the line width of the receiver, which determines how + * text should be wrapped and aligned. The default value is + * <code>-1</code> which means wrapping is disabled. + * + * @param width the new width + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li> + * </ul> + * @exception DWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setAlignment(int) + */ +public void setWidth (int width) { + checkLayout(); + if (width < -1 || width is 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT); + if (this.wrapWidth is width) return; + freeRuns(); + this.wrapWidth = width; +} + +bool shape (int hdc, StyleItem run, char[] chars, int[] glyphCount, int maxGlyphs) { + int hr = OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount); + run.glyphCount = glyphCount[0]; + if (hr !is OS.USP_E_SCRIPT_NOT_IN_FONT) { + SCRIPT_FONTPROPERTIES fp = new SCRIPT_FONTPROPERTIES (); + fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof; + OS.ScriptGetFontProperties(hdc, run.psc, fp); + short[] glyphs = new short[glyphCount[0]]; + OS.MoveMemory(glyphs, run.glyphs, glyphs.length * 2); + int i; + for (i = 0; i < glyphs.length; i++) { + if (glyphs[i] is fp.wgDefault) break; + } + if (i is glyphs.length) return true; + } + if (run.psc !is 0) { + OS.ScriptFreeCache(run.psc); + glyphCount[0] = 0; + OS.MoveMemory(run.psc, glyphCount, 4); + } + run.glyphCount = 0; + return false; +} + +/* + * Generate glyphs for one Run. + */ +void shape (HDC hdc, StyleItem run) { + int[] buffer = new int[1]; + char[] chars = new char[run.length]; + segmentsText.getChars(run.start, run.start + run.length, chars, 0); + int maxGlyphs = (chars.length * 3 / 2) + 16; + int hHeap = OS.GetProcessHeap(); + run.glyphs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); + if (run.glyphs is 0) DWT.error(DWT.ERROR_NO_HANDLES); + run.clusters = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); + if (run.clusters is 0) DWT.error(DWT.ERROR_NO_HANDLES); + run.visAttrs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF); + if (run.visAttrs is 0) DWT.error(DWT.ERROR_NO_HANDLES); + run.psc = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, 4); + if (run.psc is 0) DWT.error(DWT.ERROR_NO_HANDLES); + if (!shape(hdc, run, chars, buffer, maxGlyphs)) { + if (mLangFontLink2 !is 0) { + int[] dwCodePages = new int[1]; + int[] cchCodePages = new int[1]; + /* GetStrCodePages() */ + OS.VtblCall(4, mLangFontLink2, chars, chars.length, 0, dwCodePages, cchCodePages); + int[] hNewFont = new int[1]; + /* MapFont() */ + if (OS.VtblCall(10, mLangFontLink2, hdc, dwCodePages[0], chars[0], hNewFont) is OS.S_OK) { + int hFont = OS.SelectObject(hdc, hNewFont[0]); + if (shape(hdc, run, chars, buffer, maxGlyphs)) { + run.fallbackFont = hNewFont[0]; + } else { + /* ReleaseFont() */ + OS.VtblCall(8, mLangFontLink2, hNewFont[0]); + OS.SelectObject(hdc, hFont); + SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES(); + OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof); + if (properties.fPrivateUseArea) { + run.analysis.fNoGlyphIndex = true; + } + OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer); + run.glyphCount = buffer[0]; + } + } + } + } + int[] abc = new int[3]; + run.advances = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4); + if (run.advances is 0) DWT.error(DWT.ERROR_NO_HANDLES); + run.goffsets = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF); + if (run.goffsets is 0) DWT.error(DWT.ERROR_NO_HANDLES); + OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, run.analysis, run.advances, run.goffsets, abc); + if (run.style !is null && run.style.metrics !is null) { + GlyphMetrics metrics = run.style.metrics; + /* + * Bug in Windows, on a Japanese machine, Uniscribe returns glyphcount + * equals zero for FFFC (possibly other unicode code points), the fix + * is to make sure the glyph is at least one pixel wide. + */ + run.width = metrics.width * Math.max (1, run.glyphCount); + run.ascent = metrics.ascent; + run.descent = metrics.descent; + run.leading = 0; + } else { + run.width = abc[0] + abc[1] + abc[2]; + TEXTMETRIC lptm; + OS.GetTextMetrics(hdc, &lptm); + run.ascent = lptm.tmAscent; + run.descent = lptm.tmDescent; + run.leading = lptm.tmInternalLeading; + } + if (run.style !is null) { + run.ascent += run.style.rise; + run.descent -= +run.style.rise; + } +} + +int validadeOffset(int offset, int step) { + offset += step; + if (segments !is null && segments.length > 2) { + for (int i = 0; i < segments.length; i++) { + if (translateOffset(segments[i]) - 1 is offset) { + offset += step; + break; + } + } + } + return offset; +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the receiver + */ +public char[] toString () { + if (isDisposed()) return "TextLayout {*DISPOSED*}"; + return "TextLayout {}"; +} + +int translateOffset(int offset) { + if (segments is null) return offset; + int nSegments = segments.length; + if (nSegments <= 1) return offset; + int length = text.length(); + if (length is 0) return offset; + if (nSegments is 2) { + if (segments[0] is 0 && segments[1] is length) return offset; + } + for (int i = 0; i < nSegments && offset - i >= segments[i]; i++) { + offset++; + } + return offset; +} + +int untranslateOffset(int offset) { + if (segments is null) return offset; + int nSegments = segments.length; + if (nSegments <= 1) return offset; + int length = text.length(); + if (length is 0) return offset; + if (nSegments is 2) { + if (segments[0] is 0 && segments[1] is length) return offset; + } + for (int i = 0; i < nSegments && offset > segments[i]; i++) { + offset--; + } + return offset; +} +}