Mercurial > projects > dwt-win
annotate dwt/graphics/TextLayout.d @ 48:9a64a7781bab
Added override and alias, first chunk. Thanks torhu for doing this patch.
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 03 Feb 2008 01:14:54 +0100 |
parents | 92c102dd64a3 |
children | 0405e18fec7f |
rev | line source |
---|---|
29 | 1 /******************************************************************************* |
2 * Copyright (c) 2000, 2007 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
31
92c102dd64a3
Added all widgets modules as dummy. Most modules of accessible are equal to the linux version, except Accessible.
Frank Benoit <benoit@tionex.de>
parents:
30
diff
changeset
|
10 * Port to the D programming language: |
92c102dd64a3
Added all widgets modules as dummy. Most modules of accessible are equal to the linux version, except Accessible.
Frank Benoit <benoit@tionex.de>
parents:
30
diff
changeset
|
11 * Frank Benoit <benoit@tionex.de> |
29 | 12 *******************************************************************************/ |
13 module dwt.graphics.TextLayout; | |
14 | |
15 import dwt.DWT; | |
16 import dwt.DWTException; | |
17 import dwt.internal.Compatibility; | |
18 import dwt.internal.gdip.Gdip; | |
19 import dwt.internal.win32.OS; | |
20 | |
21 import dwt.graphics.Color; | |
22 import dwt.graphics.Device; | |
23 import dwt.graphics.Font; | |
24 import dwt.graphics.FontMetrics; | |
25 import dwt.graphics.GC; | |
26 import dwt.graphics.GCData; | |
27 import dwt.graphics.GlyphMetrics; | |
28 import dwt.graphics.Point; | |
29 import dwt.graphics.Rectangle; | |
30 import dwt.graphics.Region; | |
31 import dwt.graphics.Resource; | |
32 import dwt.graphics.TextStyle; | |
33 | |
34 import tango.text.convert.Format; | |
35 import dwt.dwthelper.utils; | |
30 | 36 import dwt.dwthelper.System; |
29 | 37 |
38 /** | |
39 * <code>TextLayout</code> is a graphic object that represents | |
40 * styled text. | |
41 * <p> | |
42 * Instances of this class provide support for drawing, cursor | |
43 * navigation, hit testing, text wrapping, alignment, tab expansion | |
44 * line breaking, etc. These are aspects required for rendering internationalized text. | |
45 * </p><p> | |
46 * Application code must explicitly invoke the <code>TextLayout#dispose()</code> | |
47 * method to release the operating system resources managed by each instance | |
48 * when those instances are no longer required. | |
49 * </p> | |
50 * | |
51 * @since 3.0 | |
52 */ | |
53 public final class TextLayout : Resource { | |
54 Font font; | |
55 char[] text, segmentsText; | |
56 int lineSpacing; | |
57 int ascent, descent; | |
58 int alignment; | |
59 int wrapWidth; | |
60 int orientation; | |
61 int indent; | |
62 bool justify; | |
63 int[] tabs; | |
64 int[] segments; | |
65 StyleItem[] styles; | |
66 | |
67 StyleItem[] allRuns; | |
68 StyleItem[][] runs; | |
69 int[] lineOffset, lineY, lineWidth; | |
30 | 70 void* mLangFontLink2; |
29 | 71 |
72 static const wchar LTR_MARK = '\u200E', RTL_MARK = '\u200F'; | |
73 static const int SCRIPT_VISATTR_SIZEOF = 2; | |
74 static const int GOFFSET_SIZEOF = 8; | |
75 static byte[16] CLSID_CMultiLanguage; | |
76 static byte[16] IID_IMLangFontLink2; | |
77 static this() { | |
78 OS.IIDFromString("{275c23e2-3747-11d0-9fea-00aa003f8646}\0".toCharArray().ptr, CLSID_CMultiLanguage.ptr); | |
79 OS.IIDFromString("{DCCFC162-2B38-11d2-B7EC-00C04F8F5D9A}\0".toCharArray().ptr, IID_IMLangFontLink2.ptr); | |
80 } | |
81 | |
82 class StyleItem { | |
83 TextStyle style; | |
84 int start, length; | |
85 bool lineBreak, softBreak, tab; | |
86 | |
87 /*Script cache and analysis */ | |
88 SCRIPT_ANALYSIS analysis; | |
89 SCRIPT_CACHE* psc; | |
90 | |
91 /*Shape info (malloc when the run is shaped) */ | |
92 WORD* glyphs; | |
93 int glyphCount; | |
94 WORD* clusters; | |
95 SCRIPT_VISATTR* visAttrs; | |
96 | |
97 /*Place info (malloc when the run is placed) */ | |
98 int* advances; | |
30 | 99 GOFFSET* goffsets; |
100 int width; | |
101 int ascent; | |
102 int descent; | |
103 int leading; | |
104 int x; | |
29 | 105 |
106 /* Justify info (malloc during computeRuns) */ | |
107 int* justify; | |
108 | |
109 /* ScriptBreak */ | |
110 SCRIPT_LOGATTR* psla; | |
111 | |
30 | 112 HFONT fallbackFont; |
29 | 113 |
114 void free() { | |
115 auto hHeap = OS.GetProcessHeap(); | |
116 if (psc !is null) { | |
117 OS.ScriptFreeCache (psc); | |
118 OS.HeapFree(hHeap, 0, psc); | |
119 psc = null; | |
120 } | |
121 if (glyphs !is null) { | |
122 OS.HeapFree(hHeap, 0, glyphs); | |
123 glyphs = null; | |
124 glyphCount = 0; | |
125 } | |
126 if (clusters !is null) { | |
127 OS.HeapFree(hHeap, 0, clusters); | |
128 clusters = null; | |
129 } | |
130 if (visAttrs !is null) { | |
131 OS.HeapFree(hHeap, 0, visAttrs); | |
132 visAttrs = null; | |
133 } | |
134 if (advances !is null) { | |
135 OS.HeapFree(hHeap, 0, advances); | |
136 advances = null; | |
137 } | |
138 if (goffsets !is null) { | |
139 OS.HeapFree(hHeap, 0, goffsets); | |
140 goffsets = null; | |
141 } | |
142 if (justify !is null) { | |
143 OS.HeapFree(hHeap, 0, justify); | |
144 justify = null; | |
145 } | |
146 if (psla !is null) { | |
147 OS.HeapFree(hHeap, 0, psla); | |
148 psla = null; | |
149 } | |
150 if (fallbackFont !is null) { | |
30 | 151 if (mLangFontLink2 !is null) { |
29 | 152 /* ReleaseFont() */ |
30 | 153 OS.VtblCall(8, mLangFontLink2, cast(int)fallbackFont); |
29 | 154 } |
155 fallbackFont = null; | |
156 } | |
30 | 157 width = 0; |
158 ascent = 0; | |
159 descent = 0; | |
160 x = 0; | |
29 | 161 lineBreak = softBreak = false; |
162 } | |
48
9a64a7781bab
Added override and alias, first chunk. Thanks torhu for doing this patch.
Frank Benoit <benoit@tionex.de>
parents:
31
diff
changeset
|
163 override public char[] toString () { |
29 | 164 return Format( "StyleItem {{{}, {}}", start, style ); |
165 } | |
166 } | |
167 | |
168 /** | |
169 * Constructs a new instance of this class on the given device. | |
170 * <p> | |
171 * You must dispose the text layout when it is no longer required. | |
172 * </p> | |
173 * | |
174 * @param device the device on which to allocate the text layout | |
175 * | |
176 * @exception IllegalArgumentException <ul> | |
177 * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li> | |
178 * </ul> | |
179 * | |
180 * @see #dispose() | |
181 */ | |
182 public this (Device device) { | |
183 if (device is null) device = Device.getDevice(); | |
184 if (device is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); | |
185 this.device = device; | |
186 wrapWidth = ascent = descent = -1; | |
187 lineSpacing = 0; | |
188 orientation = DWT.LEFT_TO_RIGHT; | |
189 styles = new StyleItem[2]; | |
190 styles[0] = new StyleItem(); | |
191 styles[1] = new StyleItem(); | |
192 text = ""; //$NON-NLS-1$ | |
30 | 193 void* ppv; |
194 OS.OleInitialize(null); | |
195 if (OS.CoCreateInstance(CLSID_CMultiLanguage.ptr, null, OS.CLSCTX_INPROC_SERVER, IID_IMLangFontLink2.ptr, cast(void*)&ppv) is OS.S_OK) { | |
196 mLangFontLink2 = ppv; | |
29 | 197 } |
198 if (device.tracking) device.new_Object(this); | |
199 } | |
200 | |
201 void breakRun(StyleItem run) { | |
30 | 202 if (run.psla !is null) return; |
203 wchar[] chars = StrToWCHARs( segmentsText[ run.start .. run.start + run.length ] ); | |
204 auto hHeap = OS.GetProcessHeap(); | |
205 run.psla = cast(SCRIPT_LOGATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length); | |
206 if (run.psla is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
207 OS.ScriptBreak(chars.ptr, chars.length, &run.analysis, run.psla); | |
29 | 208 } |
209 | |
30 | 210 void checkItem (HDC hDC, StyleItem item) { |
211 if (item.fallbackFont !is null) { | |
29 | 212 /* |
213 * Feature in Windows. The fallback font returned by the MLang service | |
214 * can be disposed by some other client running in the same thread. | |
215 * For example, disposing a Browser widget internally releases all fonts | |
216 * in the MLang cache. The fix is to use GetObject() to detect if the | |
217 * font was disposed and reshape the run. | |
218 */ | |
219 LOGFONT logFont; | |
30 | 220 if (OS.GetObject( item.fallbackFont, LOGFONT.sizeof, &logFont) is 0) { |
29 | 221 item.free(); |
222 OS.SelectObject(hDC, getItemFont(item)); | |
223 shape(hDC, item); | |
224 } | |
225 } | |
226 } | |
227 | |
228 void checkLayout () { | |
229 if (isDisposed()) DWT.error(DWT.ERROR_GRAPHIC_DISPOSED); | |
230 } | |
231 | |
232 /* | |
233 * Compute the runs: itemize, shape, place, and reorder the runs. | |
234 * Break paragraphs into lines, wraps the text, and initialize caches. | |
235 */ | |
236 void computeRuns (GC gc) { | |
237 if (runs !is null) return; | |
30 | 238 auto hDC = gc !is null ? gc.handle : device.internal_new_GC(null); |
239 auto srcHdc = OS.CreateCompatibleDC(hDC); | |
29 | 240 allRuns = itemize(); |
241 for (int i=0; i<allRuns.length - 1; i++) { | |
242 StyleItem run = allRuns[i]; | |
243 OS.SelectObject(srcHdc, getItemFont(run)); | |
244 shape(srcHdc, run); | |
245 } | |
30 | 246 SCRIPT_LOGATTR* logAttr; |
247 SCRIPT_PROPERTIES* properties; | |
29 | 248 int lineWidth = indent, lineStart = 0, lineCount = 1; |
249 for (int i=0; i<allRuns.length - 1; i++) { | |
250 StyleItem run = allRuns[i]; | |
251 if (run.length is 1) { | |
252 char ch = segmentsText.charAt(run.start); | |
253 switch (ch) { | |
254 case '\t': { | |
255 run.tab = true; | |
256 if (tabs is null) break; | |
257 int tabsLength = tabs.length, j; | |
258 for (j = 0; j < tabsLength; j++) { | |
259 if (tabs[j] > lineWidth) { | |
260 run.width = tabs[j] - lineWidth; | |
261 break; | |
262 } | |
263 } | |
264 if (j is tabsLength) { | |
265 int tabX = tabs[tabsLength-1]; | |
266 int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0]; | |
267 if (lastTabWidth > 0) { | |
268 while (tabX <= lineWidth) tabX += lastTabWidth; | |
269 run.width = tabX - lineWidth; | |
270 } | |
271 } | |
272 break; | |
273 } | |
274 case '\n': { | |
275 run.lineBreak = true; | |
276 break; | |
277 } | |
278 case '\r': { | |
279 run.lineBreak = true; | |
280 StyleItem next = allRuns[i + 1]; | |
281 if (next.length !is 0 && segmentsText.charAt(next.start) is '\n') { | |
282 run.length += 1; | |
283 next.free(); | |
284 i++; | |
285 } | |
286 break; | |
287 } | |
288 } | |
289 } | |
290 if (wrapWidth !is -1 && lineWidth + run.width > wrapWidth && !run.tab) { | |
291 int start = 0; | |
292 int[] piDx = new int[run.length]; | |
293 if (run.style !is null && run.style.metrics !is null) { | |
294 piDx[0] = run.width; | |
295 } else { | |
30 | 296 OS.ScriptGetLogicalWidths(&run.analysis, run.length, run.glyphCount, run.advances, run.clusters, run.visAttrs, piDx.ptr); |
29 | 297 } |
298 int width = 0, maxWidth = wrapWidth - lineWidth; | |
299 while (width + piDx[start] < maxWidth) { | |
300 width += piDx[start++]; | |
301 } | |
302 int firstStart = start; | |
303 int firstIndice = i; | |
304 while (i >= lineStart) { | |
305 breakRun(run); | |
306 while (start >= 0) { | |
30 | 307 logAttr = run.psla + start; |
308 //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); | |
29 | 309 if (logAttr.fSoftBreak || logAttr.fWhiteSpace) break; |
310 start--; | |
311 } | |
312 | |
313 /* | |
314 * Bug in Windows. For some reason Uniscribe sets the fSoftBreak flag for the first letter | |
315 * after a letter with an accent. This cause a break line to be set in the middle of a word. | |
316 * The fix is to detect the case and ignore fSoftBreak forcing the algorithm keep searching. | |
317 */ | |
318 if (start is 0 && i !is lineStart && !run.tab) { | |
319 if (logAttr.fSoftBreak && !logAttr.fWhiteSpace) { | |
30 | 320 properties = device.scripts[run.analysis.eScript]; |
321 //OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof); | |
29 | 322 int langID = properties.langid; |
323 StyleItem pRun = allRuns[i - 1]; | |
30 | 324 //OS.MoveMemory(properties, device.scripts[pRun.analysis.eScript], SCRIPT_PROPERTIES.sizeof); |
29 | 325 if (properties.langid is langID || langID is OS.LANG_NEUTRAL || properties.langid is OS.LANG_NEUTRAL) { |
326 breakRun(pRun); | |
30 | 327 logAttr = pRun.psla + (pRun.length - 1); |
328 //OS.MoveMemory(logAttr, pRun.psla + ((pRun.length - 1) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); | |
29 | 329 if (!logAttr.fWhiteSpace) start = -1; |
330 } | |
331 } | |
332 } | |
333 if (start >= 0 || i is lineStart) break; | |
334 run = allRuns[--i]; | |
335 start = run.length - 1; | |
336 } | |
337 if (start is 0 && i !is lineStart && !run.tab) { | |
338 run = allRuns[--i]; | |
339 } else if (start <= 0 && i is lineStart) { | |
340 i = firstIndice; | |
341 run = allRuns[i]; | |
342 start = Math.max(1, firstStart); | |
343 } | |
344 breakRun(run); | |
345 while (start < run.length) { | |
30 | 346 logAttr = run.psla + start; |
347 //OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof); | |
29 | 348 if (!logAttr.fWhiteSpace) break; |
349 start++; | |
350 } | |
351 if (0 < start && start < run.length) { | |
352 StyleItem newRun = new StyleItem(); | |
353 newRun.start = run.start + start; | |
354 newRun.length = run.length - start; | |
355 newRun.style = run.style; | |
356 newRun.analysis = run.analysis; | |
357 run.free(); | |
358 run.length = start; | |
359 OS.SelectObject(srcHdc, getItemFont(run)); | |
360 shape (srcHdc, run); | |
361 OS.SelectObject(srcHdc, getItemFont(newRun)); | |
362 shape (srcHdc, newRun); | |
363 StyleItem[] newAllRuns = new StyleItem[allRuns.length + 1]; | |
364 System.arraycopy(allRuns, 0, newAllRuns, 0, i + 1); | |
365 System.arraycopy(allRuns, i + 1, newAllRuns, i + 2, allRuns.length - i - 1); | |
366 allRuns = newAllRuns; | |
367 allRuns[i + 1] = newRun; | |
368 } | |
369 if (i !is allRuns.length - 2) { | |
370 run.softBreak = run.lineBreak = true; | |
371 } | |
372 } | |
373 lineWidth += run.width; | |
374 if (run.lineBreak) { | |
375 lineStart = i + 1; | |
376 lineWidth = run.softBreak ? 0 : indent; | |
377 lineCount++; | |
378 } | |
379 } | |
380 lineWidth = 0; | |
30 | 381 runs = new StyleItem[][](lineCount); |
29 | 382 lineOffset = new int[lineCount + 1]; |
383 lineY = new int[lineCount + 1]; | |
384 this.lineWidth = new int[lineCount]; | |
385 int lineRunCount = 0, line = 0; | |
386 int ascent = Math.max(0, this.ascent); | |
387 int descent = Math.max(0, this.descent); | |
388 StyleItem[] lineRuns = new StyleItem[allRuns.length]; | |
389 for (int i=0; i<allRuns.length; i++) { | |
390 StyleItem run = allRuns[i]; | |
391 lineRuns[lineRunCount++] = run; | |
392 lineWidth += run.width; | |
393 ascent = Math.max(ascent, run.ascent); | |
394 descent = Math.max(descent, run.descent); | |
395 if (run.lineBreak || i is allRuns.length - 1) { | |
396 /* Update the run metrics if the last run is a hard break. */ | |
397 if (lineRunCount is 1 && i is allRuns.length - 1) { | |
398 TEXTMETRIC lptm; | |
399 OS.SelectObject(srcHdc, getItemFont(run)); | |
400 OS.GetTextMetrics(srcHdc, &lptm); | |
401 run.ascent = lptm.tmAscent; | |
402 run.descent = lptm.tmDescent; | |
403 ascent = Math.max(ascent, run.ascent); | |
404 descent = Math.max(descent, run.descent); | |
405 } | |
406 runs[line] = new StyleItem[lineRunCount]; | |
407 System.arraycopy(lineRuns, 0, runs[line], 0, lineRunCount); | |
408 | |
409 if (justify && wrapWidth !is -1 && run.softBreak && lineWidth > 0) { | |
410 if (line is 0) { | |
411 lineWidth += indent; | |
412 } else { | |
413 StyleItem[] previousLine = runs[line - 1]; | |
414 StyleItem previousRun = previousLine[previousLine.length - 1]; | |
415 if (previousRun.lineBreak && !previousRun.softBreak) { | |
416 lineWidth += indent; | |
417 } | |
418 } | |
30 | 419 auto hHeap = OS.GetProcessHeap(); |
29 | 420 int newLineWidth = 0; |
421 for (int j = 0; j < runs[line].length; j++) { | |
422 StyleItem item = runs[line][j]; | |
423 int iDx = item.width * wrapWidth / lineWidth; | |
424 if (iDx !is item.width) { | |
30 | 425 item.justify = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4); |
426 if (item.justify is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
29 | 427 OS.ScriptJustify(item.visAttrs, item.advances, item.glyphCount, iDx - item.width, 2, item.justify); |
428 item.width = iDx; | |
429 } | |
430 newLineWidth += item.width; | |
431 } | |
432 lineWidth = newLineWidth; | |
433 } | |
434 this.lineWidth[line] = lineWidth; | |
435 | |
436 StyleItem lastRun = runs[line][lineRunCount - 1]; | |
437 int lastOffset = lastRun.start + lastRun.length; | |
438 runs[line] = reorder(runs[line], i is allRuns.length - 1); | |
439 lastRun = runs[line][lineRunCount - 1]; | |
440 if (run.softBreak && run !is lastRun) { | |
441 run.softBreak = run.lineBreak = false; | |
442 lastRun.softBreak = lastRun.lineBreak = true; | |
443 } | |
444 | |
445 lineWidth = getLineIndent(line); | |
446 for (int j = 0; j < runs[line].length; j++) { | |
447 runs[line][j].x = lineWidth; | |
448 lineWidth += runs[line][j].width; | |
449 } | |
450 line++; | |
451 lineY[line] = lineY[line - 1] + ascent + descent + lineSpacing; | |
452 lineOffset[line] = lastOffset; | |
453 lineRunCount = lineWidth = 0; | |
454 ascent = Math.max(0, this.ascent); | |
455 descent = Math.max(0, this.descent); | |
456 } | |
457 } | |
30 | 458 if (srcHdc !is null) OS.DeleteDC(srcHdc); |
29 | 459 if (gc is null) device.internal_dispose_GC(hDC, null); |
460 } | |
461 | |
462 /** | |
463 * Disposes of the operating system resources associated with | |
464 * the text layout. Applications must dispose of all allocated text layouts. | |
465 */ | |
48
9a64a7781bab
Added override and alias, first chunk. Thanks torhu for doing this patch.
Frank Benoit <benoit@tionex.de>
parents:
31
diff
changeset
|
466 override public void dispose () { |
29 | 467 if (device is null) return; |
468 freeRuns(); | |
469 font = null; | |
470 text = null; | |
471 segmentsText = null; | |
472 tabs = null; | |
473 styles = null; | |
474 runs = null; | |
475 lineOffset = null; | |
476 lineY = null; | |
477 lineWidth = null; | |
30 | 478 if (mLangFontLink2 !is null) { |
29 | 479 /* Release() */ |
480 OS.VtblCall(2, mLangFontLink2); | |
30 | 481 mLangFontLink2 = null; |
29 | 482 } |
483 OS.OleUninitialize(); | |
484 if (device.tracking) device.dispose_Object(this); | |
485 device = null; | |
486 } | |
487 | |
488 /** | |
489 * Draws the receiver's text using the specified GC at the specified | |
490 * point. | |
491 * | |
492 * @param gc the GC to draw | |
493 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn | |
494 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn | |
495 * | |
496 * @exception DWTException <ul> | |
497 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
498 * </ul> | |
499 * @exception IllegalArgumentException <ul> | |
500 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> | |
501 * </ul> | |
502 */ | |
503 public void draw (GC gc, int x, int y) { | |
504 draw(gc, x, y, -1, -1, null, null); | |
505 } | |
506 | |
507 /** | |
508 * Draws the receiver's text using the specified GC at the specified | |
509 * point. | |
510 * | |
511 * @param gc the GC to draw | |
512 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn | |
513 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn | |
514 * @param selectionStart the offset where the selections starts, or -1 indicating no selection | |
515 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection | |
516 * @param selectionForeground selection foreground, or NULL to use the system default color | |
517 * @param selectionBackground selection background, or NULL to use the system default color | |
518 * | |
519 * @exception DWTException <ul> | |
520 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
521 * </ul> | |
522 * @exception IllegalArgumentException <ul> | |
523 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> | |
524 * </ul> | |
525 */ | |
526 public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { | |
527 draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0); | |
528 } | |
529 | |
530 /** | |
531 * Draws the receiver's text using the specified GC at the specified | |
532 * point. | |
533 * <p> | |
534 * The parameter <code>flags</code> can include one of <code>DWT.DELIMITER_SELECTION</code> | |
535 * or <code>DWT.FULL_SELECTION</code> to specify the selection behavior on all lines except | |
536 * for the last line, and can also include <code>DWT.LAST_LINE_SELECTION</code> to extend | |
537 * the specified selection behavior to the last line. | |
538 * </p> | |
539 * @param gc the GC to draw | |
540 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn | |
541 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn | |
542 * @param selectionStart the offset where the selections starts, or -1 indicating no selection | |
543 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection | |
544 * @param selectionForeground selection foreground, or NULL to use the system default color | |
545 * @param selectionBackground selection background, or NULL to use the system default color | |
546 * @param flags drawing options | |
547 * | |
548 * @exception DWTException <ul> | |
549 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
550 * </ul> | |
551 * @exception IllegalArgumentException <ul> | |
552 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> | |
553 * </ul> | |
554 * | |
555 * @since 3.3 | |
556 */ | |
557 public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { | |
558 checkLayout(); | |
559 computeRuns(gc); | |
560 if (gc is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); | |
561 if (gc.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
562 if (selectionForeground !is null && selectionForeground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
563 if (selectionBackground !is null && selectionBackground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
30 | 564 int length = text.length; |
29 | 565 if (length is 0 && flags is 0) return; |
30 | 566 auto hdc = gc.handle; |
29 | 567 Rectangle clip = gc.getClipping(); |
568 GCData data = gc.data; | |
30 | 569 auto gdipGraphics = data.gdipGraphics; |
570 auto foreground = data.foreground; | |
571 auto alpha = data.alpha; | |
572 bool gdip = gdipGraphics !is null && (alpha !is 0xFF || data.foregroundPattern !is null); | |
573 HRGN clipRgn; | |
29 | 574 float[] lpXform = null; |
30 | 575 Gdip.Rect gdipRect; |
576 if (gdipGraphics !is null && !gdip) { | |
577 auto matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0); | |
578 if (matrix is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
29 | 579 Gdip.Graphics_GetTransform(gdipGraphics, matrix); |
30 | 580 auto identity_ = gc.identity(); |
581 Gdip.Matrix_Invert(identity_); | |
582 Gdip.Matrix_Multiply(matrix, identity_, Gdip.MatrixOrderAppend); | |
583 Gdip.Matrix_delete(identity_); | |
29 | 584 if (!Gdip.Matrix_IsIdentity(matrix)) { |
585 lpXform = new float[6]; | |
30 | 586 Gdip.Matrix_GetElements(matrix, lpXform.ptr); |
29 | 587 } |
588 Gdip.Matrix_delete(matrix); | |
589 if ((data.style & DWT.MIRRORED) !is 0 && lpXform !is null) { | |
590 gdip = true; | |
591 lpXform = null; | |
592 } else { | |
593 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); | |
30 | 594 auto rgn = Gdip.Region_new(); |
29 | 595 Gdip.Graphics_GetClip(gdipGraphics, rgn); |
596 if (!Gdip.Region_IsInfinite(rgn, gdipGraphics)) { | |
597 clipRgn = Gdip.Region_GetHRGN(rgn, gdipGraphics); | |
598 } | |
599 Gdip.Region_delete(rgn); | |
600 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); | |
601 hdc = Gdip.Graphics_GetHDC(gdipGraphics); | |
602 } | |
603 } | |
30 | 604 Gdip.Brush* foregroundBrush; |
605 int state = 0; | |
29 | 606 if (gdip) { |
607 gc.checkGC(GC.FOREGROUND); | |
608 foregroundBrush = gc.getFgBrush(); | |
609 } else { | |
610 state = OS.SaveDC(hdc); | |
611 if ((data.style & DWT.MIRRORED) !is 0) { | |
612 OS.SetLayout(hdc, OS.GetLayout(hdc) | OS.LAYOUT_RTL); | |
613 } | |
614 if (lpXform !is null) { | |
615 OS.SetGraphicsMode(hdc, OS.GM_ADVANCED); | |
30 | 616 OS.SetWorldTransform(hdc, cast(XFORM*)lpXform.ptr); |
29 | 617 } |
30 | 618 if (clipRgn !is null) { |
29 | 619 OS.SelectClipRgn(hdc, clipRgn); |
620 OS.DeleteObject(clipRgn); | |
621 } | |
622 } | |
623 bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1; | |
624 if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) { | |
625 selectionStart = Math.min(Math.max(0, selectionStart), length - 1); | |
626 selectionEnd = Math.min(Math.max(0, selectionEnd), length - 1); | |
627 if (selectionForeground is null) selectionForeground = device.getSystemColor(DWT.COLOR_LIST_SELECTION_TEXT); | |
628 if (selectionBackground is null) selectionBackground = device.getSystemColor(DWT.COLOR_LIST_SELECTION); | |
629 selectionStart = translateOffset(selectionStart); | |
630 selectionEnd = translateOffset(selectionEnd); | |
631 } | |
30 | 632 RECT rect; |
633 void* selBrush; | |
634 void* selPen; | |
635 void* selBrushFg; | |
636 | |
29 | 637 if (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0) { |
638 if (gdip) { | |
30 | 639 auto bg = selectionBackground.handle; |
640 auto argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16); | |
641 auto color = Gdip.Color_new(argb); | |
29 | 642 selBrush = Gdip.SolidBrush_new(color); |
643 Gdip.Color_delete(color); | |
30 | 644 auto fg = selectionForeground.handle; |
29 | 645 argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16); |
646 color = Gdip.Color_new(argb); | |
647 selBrushFg = Gdip.SolidBrush_new(color); | |
30 | 648 selPen = Gdip.Pen_new( cast(Gdip.Brush*)selBrushFg, 1); |
29 | 649 Gdip.Color_delete(color); |
650 } else { | |
651 selBrush = OS.CreateSolidBrush(selectionBackground.handle); | |
652 selPen = OS.CreatePen(OS.PS_SOLID, 1, selectionForeground.handle); | |
653 } | |
654 } | |
655 int offset = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? -1 : 0; | |
656 OS.SetBkMode(hdc, OS.TRANSPARENT); | |
657 for (int line=0; line<runs.length; line++) { | |
658 int drawX = x + getLineIndent(line); | |
659 int drawY = y + lineY[line]; | |
660 StyleItem[] lineRuns = runs[line]; | |
661 int lineHeight = lineY[line+1] - lineY[line]; | |
662 if (flags !is 0 && (hasSelection || (flags & DWT.LAST_LINE_SELECTION) !is 0)) { | |
663 bool extents = false; | |
664 if (line is runs.length - 1 && (flags & DWT.LAST_LINE_SELECTION) !is 0) { | |
665 extents = true; | |
666 } else { | |
667 StyleItem run = lineRuns[lineRuns.length - 1]; | |
668 if (run.lineBreak && !run.softBreak) { | |
669 if (selectionStart <= run.start && run.start <= selectionEnd) extents = true; | |
670 } else { | |
671 int endOffset = run.start + run.length - 1; | |
672 if (selectionStart <= endOffset && endOffset < selectionEnd && (flags & DWT.FULL_SELECTION) !is 0) { | |
673 extents = true; | |
674 } | |
675 } | |
676 } | |
677 if (extents) { | |
678 int width; | |
679 if ((flags & DWT.FULL_SELECTION) !is 0) { | |
680 width = OS.IsWin95 ? 0x7FFF : 0x6FFFFFF; | |
681 } else { | |
682 width = (lineHeight - lineSpacing) / 3; | |
683 } | |
684 if (gdip) { | |
30 | 685 Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush*)selBrush, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing); |
29 | 686 } else { |
687 OS.SelectObject(hdc, selBrush); | |
688 OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight - lineSpacing, OS.PATCOPY); | |
689 } | |
690 } | |
691 } | |
692 if (drawX > clip.x + clip.width) continue; | |
693 if (drawX + lineWidth[line] < clip.x) continue; | |
694 int baseline = Math.max(0, this.ascent); | |
695 for (int i = 0; i < lineRuns.length; i++) { | |
696 baseline = Math.max(baseline, lineRuns[i].ascent); | |
697 } | |
698 int alignmentX = drawX; | |
699 for (int i = 0; i < lineRuns.length; i++) { | |
700 StyleItem run = lineRuns[i]; | |
701 if (run.length is 0) continue; | |
702 if (drawX > clip.x + clip.width) break; | |
703 if (drawX + run.width >= clip.x) { | |
704 if (!run.lineBreak || run.softBreak) { | |
705 int end = run.start + run.length - 1; | |
706 bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end; | |
707 if (fullSelection) { | |
708 if (gdip) { | |
30 | 709 Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush*)selBrush, drawX, drawY, run.width, lineHeight - lineSpacing); |
29 | 710 } else { |
711 OS.SelectObject(hdc, selBrush); | |
712 OS.PatBlt(hdc, drawX, drawY, run.width, lineHeight - lineSpacing, OS.PATCOPY); | |
713 } | |
714 } else { | |
715 if (run.style !is null && run.style.background !is null) { | |
30 | 716 auto bg = run.style.background.handle; |
29 | 717 int drawRunY = drawY + (baseline - run.ascent); |
718 if (gdip) { | |
719 int argb = ((alpha & 0xFF) << 24) | ((bg >> 16) & 0xFF) | (bg & 0xFF00) | ((bg & 0xFF) << 16); | |
30 | 720 auto color = Gdip.Color_new(argb); |
721 auto brush = Gdip.SolidBrush_new(color); | |
722 Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush*)brush, drawX, drawRunY, run.width, run.ascent + run.descent); | |
29 | 723 Gdip.Color_delete(color); |
724 Gdip.SolidBrush_delete(brush); | |
725 } else { | |
30 | 726 auto hBrush = OS.CreateSolidBrush (bg); |
727 auto oldBrush = OS.SelectObject(hdc, hBrush); | |
29 | 728 OS.PatBlt(hdc, drawX, drawRunY, run.width, run.ascent + run.descent, OS.PATCOPY); |
729 OS.SelectObject(hdc, oldBrush); | |
730 OS.DeleteObject(hBrush); | |
731 } | |
732 } | |
733 bool partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd); | |
734 if (partialSelection) { | |
735 int selStart = Math.max(selectionStart, run.start) - run.start; | |
736 int selEnd = Math.min(selectionEnd, end) - run.start; | |
737 int cChars = run.length; | |
738 int gGlyphs = run.glyphCount; | |
30 | 739 int piX; |
740 int* advances = run.justify !is null ? run.justify : run.advances; | |
741 OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); | |
742 int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; | |
29 | 743 rect.left = drawX + runX; |
744 rect.top = drawY; | |
30 | 745 OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); |
746 runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; | |
29 | 747 rect.right = drawX + runX; |
748 rect.bottom = drawY + lineHeight - lineSpacing; | |
749 if (gdip) { | |
30 | 750 Gdip.Graphics_FillRectangle(gdipGraphics, cast(Gdip.Brush*)selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); |
29 | 751 } else { |
752 OS.SelectObject(hdc, selBrush); | |
753 OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY); | |
754 } | |
755 } | |
756 } | |
757 } | |
758 } | |
759 drawX += run.width; | |
760 } | |
761 drawX = alignmentX; | |
762 for (int i = 0; i < lineRuns.length; i++) { | |
763 StyleItem run = lineRuns[i]; | |
764 if (run.length is 0) continue; | |
765 if (drawX > clip.x + clip.width) break; | |
766 if (drawX + run.width >= clip.x) { | |
767 if (!run.tab && (!run.lineBreak || run.softBreak) && !(run.style !is null && run.style.metrics !is null)) { | |
768 int end = run.start + run.length - 1; | |
769 bool fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end; | |
770 bool partialSelection = hasSelection && !fullSelection && !(selectionStart > end || run.start > selectionEnd); | |
771 checkItem(hdc, run); | |
772 OS.SelectObject(hdc, getItemFont(run)); | |
773 int drawRunY = drawY + (baseline - run.ascent); | |
774 if (partialSelection) { | |
775 int selStart = Math.max(selectionStart, run.start) - run.start; | |
776 int selEnd = Math.min(selectionEnd, end) - run.start; | |
777 int cChars = run.length; | |
778 int gGlyphs = run.glyphCount; | |
30 | 779 int piX; |
780 int* advances = run.justify !is null ? run.justify : run.advances; | |
781 OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); | |
782 int runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; | |
29 | 783 rect.left = drawX + runX; |
784 rect.top = drawY; | |
30 | 785 OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); |
786 runX = (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? run.width - piX : piX; | |
29 | 787 rect.right = drawX + runX; |
788 rect.bottom = drawY + lineHeight; | |
789 } | |
790 if (gdip) { | |
791 OS.BeginPath(hdc); | |
30 | 792 OS.ScriptTextOut(hdc, run.psc, drawX, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); |
29 | 793 OS.EndPath(hdc); |
794 int count = OS.GetPath(hdc, null, null, 0); | |
795 int[] points = new int[count*2]; | |
30 | 796 ubyte[] types = new ubyte[count]; |
797 OS.GetPath(hdc, cast(POINT*)points.ptr, types.ptr, count); | |
29 | 798 for (int typeIndex = 0; typeIndex < types.length; typeIndex++) { |
799 int newType = 0; | |
800 int type = types[typeIndex] & 0xFF; | |
801 switch (type & ~OS.PT_CLOSEFIGURE) { | |
802 case OS.PT_MOVETO: newType = Gdip.PathPointTypeStart; break; | |
803 case OS.PT_LINETO: newType = Gdip.PathPointTypeLine; break; | |
804 case OS.PT_BEZIERTO: newType = Gdip.PathPointTypeBezier; break; | |
805 } | |
806 if ((type & OS.PT_CLOSEFIGURE) !is 0) newType |= Gdip.PathPointTypeCloseSubpath; | |
807 types[typeIndex] = cast(byte)newType; | |
808 } | |
30 | 809 auto path = Gdip.GraphicsPath_new(cast(Gdip.Point*)points.ptr, cast(byte*)types.ptr, count, Gdip.FillModeAlternate); |
810 if (path is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
811 auto brush = foregroundBrush; | |
29 | 812 if (fullSelection) { |
30 | 813 brush = cast(Gdip.Brush*)selBrushFg; |
29 | 814 } else { |
815 if (run.style !is null && run.style.foreground !is null) { | |
30 | 816 auto fg = run.style.foreground.handle; |
29 | 817 int argb = ((alpha & 0xFF) << 24) | ((fg >> 16) & 0xFF) | (fg & 0xFF00) | ((fg & 0xFF) << 16); |
30 | 818 auto color = Gdip.Color_new(argb); |
819 brush = cast(Gdip.Brush*)Gdip.SolidBrush_new(color); | |
29 | 820 Gdip.Color_delete(color); |
821 } | |
822 } | |
823 int gstate = 0; | |
824 if (partialSelection) { | |
825 gdipRect.X = rect.left; | |
826 gdipRect.Y = rect.top; | |
827 gdipRect.Width = rect.right - rect.left; | |
828 gdipRect.Height = rect.bottom - rect.top; | |
829 gstate = Gdip.Graphics_Save(gdipGraphics); | |
30 | 830 Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeExclude); |
29 | 831 } |
832 int antialias = Gdip.Graphics_GetSmoothingMode(gdipGraphics), textAntialias = 0; | |
833 int mode = Gdip.Graphics_GetTextRenderingHint(data.gdipGraphics); | |
834 switch (mode) { | |
835 case Gdip.TextRenderingHintSystemDefault: textAntialias = Gdip.SmoothingModeAntiAlias; break; | |
836 case Gdip.TextRenderingHintSingleBitPerPixel: | |
837 case Gdip.TextRenderingHintSingleBitPerPixelGridFit: textAntialias = Gdip.SmoothingModeNone; break; | |
838 case Gdip.TextRenderingHintAntiAlias: | |
839 case Gdip.TextRenderingHintAntiAliasGridFit: | |
840 case Gdip.TextRenderingHintClearTypeGridFit: textAntialias = Gdip.SmoothingModeAntiAlias; break; | |
841 } | |
842 Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias); | |
843 int gstate2 = 0; | |
844 if ((data.style & DWT.MIRRORED) !is 0) { | |
845 gstate2 = Gdip.Graphics_Save(gdipGraphics); | |
846 Gdip.Graphics_ScaleTransform(gdipGraphics, -1, 1, Gdip.MatrixOrderPrepend); | |
847 Gdip.Graphics_TranslateTransform(gdipGraphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend); | |
848 } | |
849 Gdip.Graphics_FillPath(gdipGraphics, brush, path); | |
850 if ((data.style & DWT.MIRRORED) !is 0) { | |
851 Gdip.Graphics_Restore(gdipGraphics, gstate2); | |
852 } | |
853 Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias); | |
854 if (run.style !is null && (run.style.underline || run.style.strikeout)) { | |
30 | 855 auto newPen = hasSelection ? cast(Gdip.Pen*)selPen : Gdip.Pen_new(brush, 1); |
29 | 856 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); |
857 if (run.style.underline) { | |
858 int underlineY = drawY + baseline + 1 - run.style.rise; | |
859 Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, underlineY, drawX + run.width, underlineY); | |
860 } | |
861 if (run.style.strikeout) { | |
862 int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; | |
863 Gdip.Graphics_DrawLine(gdipGraphics, newPen, drawX, strikeoutY, drawX + run.width, strikeoutY); | |
864 } | |
865 if (newPen !is selPen) Gdip.Pen_delete(newPen); | |
866 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); | |
867 } | |
868 if (partialSelection) { | |
869 Gdip.Graphics_Restore(gdipGraphics, gstate); | |
870 gstate = Gdip.Graphics_Save(gdipGraphics); | |
30 | 871 Gdip.Graphics_SetClip(gdipGraphics, &gdipRect, Gdip.CombineModeIntersect); |
29 | 872 Gdip.Graphics_SetSmoothingMode(gdipGraphics, textAntialias); |
30 | 873 Gdip.Graphics_FillPath(gdipGraphics, cast(Gdip.Brush*)selBrushFg, path); |
29 | 874 Gdip.Graphics_SetSmoothingMode(gdipGraphics, antialias); |
875 if (run.style !is null && (run.style.underline || run.style.strikeout)) { | |
876 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeNone); | |
877 if (run.style.underline) { | |
878 int underlineY = drawY + baseline + 1 - run.style.rise; | |
30 | 879 Gdip.Graphics_DrawLine(gdipGraphics, cast(Gdip.Pen*)selPen, rect.left, underlineY, rect.right, underlineY); |
29 | 880 } |
881 if (run.style.strikeout) { | |
882 int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; | |
30 | 883 Gdip.Graphics_DrawLine(gdipGraphics, cast(Gdip.Pen*)selPen, rect.left, strikeoutY, rect.right, strikeoutY); |
29 | 884 } |
885 Gdip.Graphics_SetPixelOffsetMode(gdipGraphics, Gdip.PixelOffsetModeHalf); | |
886 } | |
887 Gdip.Graphics_Restore(gdipGraphics, gstate); | |
888 } | |
889 Gdip.GraphicsPath_delete(path); | |
30 | 890 if (brush !is selBrushFg && brush !is foregroundBrush) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)brush); |
29 | 891 } else { |
892 int fg = foreground; | |
893 if (fullSelection) { | |
894 fg = selectionForeground.handle; | |
895 } else { | |
896 if (run.style !is null && run.style.foreground !is null) fg = run.style.foreground.handle; | |
897 } | |
898 OS.SetTextColor(hdc, fg); | |
30 | 899 OS.ScriptTextOut(hdc, run.psc, drawX + offset, drawRunY, 0, null, &run.analysis , null, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets); |
29 | 900 if (run.style !is null && (run.style.underline || run.style.strikeout)) { |
30 | 901 auto newPen = hasSelection && fg is selectionForeground.handle ? cast(HPEN)selPen : OS.CreatePen(OS.PS_SOLID, 1, fg); |
902 auto oldPen = OS.SelectObject(hdc, newPen); | |
29 | 903 if (run.style.underline) { |
904 int underlineY = drawY + baseline + 1 - run.style.rise; | |
30 | 905 OS.MoveToEx(hdc, drawX, underlineY, null); |
29 | 906 OS.LineTo(hdc, drawX + run.width, underlineY); |
907 } | |
908 if (run.style.strikeout) { | |
909 int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; | |
30 | 910 OS.MoveToEx(hdc, drawX, strikeoutY, null); |
29 | 911 OS.LineTo(hdc, drawX + run.width, strikeoutY); |
912 } | |
913 OS.SelectObject(hdc, oldPen); | |
914 if (!hasSelection || fg !is selectionForeground.handle) OS.DeleteObject(newPen); | |
915 } | |
916 if (partialSelection && fg !is selectionForeground.handle) { | |
917 OS.SetTextColor(hdc, selectionForeground.handle); | |
30 | 918 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); |
29 | 919 if (run.style !is null && (run.style.underline || run.style.strikeout)) { |
30 | 920 auto oldPen = OS.SelectObject(hdc, selPen); |
29 | 921 if (run.style.underline) { |
922 int underlineY = drawY + baseline + 1 - run.style.rise; | |
30 | 923 OS.MoveToEx(hdc, rect.left, underlineY, null); |
29 | 924 OS.LineTo(hdc, rect.right, underlineY); |
925 } | |
926 if (run.style.strikeout) { | |
927 int strikeoutY = drawRunY + run.leading + (run.ascent - run.style.rise) / 2; | |
30 | 928 OS.MoveToEx(hdc, rect.left, strikeoutY, null); |
29 | 929 OS.LineTo(hdc, rect.right, strikeoutY); |
930 } | |
931 OS.SelectObject(hdc, oldPen); | |
932 } | |
933 } | |
934 } | |
935 } | |
936 } | |
937 drawX += run.width; | |
938 } | |
939 } | |
940 if (gdip) { | |
30 | 941 if (selBrush !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)selBrush); |
942 if (selBrushFg !is null) Gdip.SolidBrush_delete(cast(Gdip.SolidBrush*)selBrushFg); | |
943 if (selPen !is null) Gdip.Pen_delete(cast(Gdip.Pen*)selPen); | |
29 | 944 } else { |
945 OS.RestoreDC(hdc, state); | |
30 | 946 if (gdipGraphics !is null) Gdip.Graphics_ReleaseHDC(gdipGraphics, hdc); |
947 if (selBrush !is null) OS.DeleteObject (selBrush); | |
948 if (selPen !is null) OS.DeleteObject (selPen); | |
29 | 949 } |
950 } | |
951 | |
952 void freeRuns () { | |
953 if (allRuns is null) return; | |
954 for (int i=0; i<allRuns.length; i++) { | |
955 StyleItem run = allRuns[i]; | |
956 run.free(); | |
957 } | |
958 allRuns = null; | |
959 runs = null; | |
960 segmentsText = null; | |
961 } | |
962 | |
963 /** | |
964 * Returns the receiver's horizontal text alignment, which will be one | |
965 * of <code>DWT.LEFT</code>, <code>DWT.CENTER</code> or | |
966 * <code>DWT.RIGHT</code>. | |
967 * | |
968 * @return the alignment used to positioned text horizontally | |
969 * | |
970 * @exception DWTException <ul> | |
971 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
972 * </ul> | |
973 */ | |
974 public int getAlignment () { | |
975 checkLayout(); | |
976 return alignment; | |
977 } | |
978 | |
979 /** | |
980 * Returns the ascent of the receiver. | |
981 * | |
982 * @return the ascent | |
983 * | |
984 * @exception DWTException <ul> | |
985 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
986 * </ul> | |
987 * | |
988 * @see #getDescent() | |
989 * @see #setDescent(int) | |
990 * @see #setAscent(int) | |
991 * @see #getLineMetrics(int) | |
992 */ | |
993 public int getAscent () { | |
994 checkLayout(); | |
995 return ascent; | |
996 } | |
997 | |
998 /** | |
999 * Returns the bounds of the receiver. | |
1000 * | |
1001 * @return the bounds of the receiver | |
1002 * | |
1003 * @exception DWTException <ul> | |
1004 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1005 * </ul> | |
1006 */ | |
1007 public Rectangle getBounds () { | |
1008 checkLayout(); | |
1009 computeRuns(null); | |
1010 int width = 0; | |
1011 if (wrapWidth !is -1) { | |
1012 width = wrapWidth; | |
1013 } else { | |
1014 for (int line=0; line<runs.length; line++) { | |
1015 width = Math.max(width, lineWidth[line] + getLineIndent(line)); | |
1016 } | |
1017 } | |
1018 return new Rectangle (0, 0, width, lineY[lineY.length - 1]); | |
1019 } | |
1020 | |
1021 /** | |
1022 * Returns the bounds for the specified range of characters. The | |
1023 * bounds is the smallest rectangle that encompasses all characters | |
1024 * in the range. The start and end offsets are inclusive and will be | |
1025 * clamped if out of range. | |
1026 * | |
1027 * @param start the start offset | |
1028 * @param end the end offset | |
1029 * @return the bounds of the character range | |
1030 * | |
1031 * @exception DWTException <ul> | |
1032 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1033 * </ul> | |
1034 */ | |
1035 public Rectangle getBounds (int start, int end) { | |
1036 checkLayout(); | |
1037 computeRuns(null); | |
30 | 1038 int length = text.length; |
29 | 1039 if (length is 0) return new Rectangle(0, 0, 0, 0); |
1040 if (start > end) return new Rectangle(0, 0, 0, 0); | |
1041 start = Math.min(Math.max(0, start), length - 1); | |
1042 end = Math.min(Math.max(0, end), length - 1); | |
1043 start = translateOffset(start); | |
1044 end = translateOffset(end); | |
1045 int left = 0x7fffffff, right = 0; | |
1046 int top = 0x7fffffff, bottom = 0; | |
1047 bool isRTL = (orientation & DWT.RIGHT_TO_LEFT) !is 0; | |
1048 for (int i = 0; i < allRuns.length - 1; i++) { | |
1049 StyleItem run = allRuns[i]; | |
1050 int runEnd = run.start + run.length; | |
1051 if (runEnd <= start) continue; | |
1052 if (run.start > end) break; | |
1053 int runLead = run.x; | |
1054 int runTrail = run.x + run.width; | |
1055 if (run.start <= start && start < runEnd) { | |
1056 int cx = 0; | |
1057 if (run.style !is null && run.style.metrics !is null) { | |
1058 GlyphMetrics metrics = run.style.metrics; | |
1059 cx = metrics.width * (start - run.start); | |
1060 } else if (!run.tab) { | |
30 | 1061 int piX; |
1062 int* advances = run.justify !is null ? run.justify : run.advances; | |
1063 OS.ScriptCPtoX(start - run.start, false, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, &run.analysis, &piX); | |
1064 cx = isRTL ? run.width - piX : piX; | |
29 | 1065 } |
1066 if (run.analysis.fRTL ^ isRTL) { | |
1067 runTrail = run.x + cx; | |
1068 } else { | |
1069 runLead = run.x + cx; | |
1070 } | |
1071 } | |
1072 if (run.start <= end && end < runEnd) { | |
1073 int cx = run.width; | |
1074 if (run.style !is null && run.style.metrics !is null) { | |
1075 GlyphMetrics metrics = run.style.metrics; | |
1076 cx = metrics.width * (end - run.start + 1); | |
1077 } else if (!run.tab) { | |
30 | 1078 int piX; |
1079 int* advances = run.justify !is null ? run.justify : run.advances; | |
1080 OS.ScriptCPtoX(end - run.start, true, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, &run.analysis, &piX); | |
1081 cx = isRTL ? run.width - piX : piX; | |
29 | 1082 } |
1083 if (run.analysis.fRTL ^ isRTL) { | |
1084 runLead = run.x + cx; | |
1085 } else { | |
1086 runTrail = run.x + cx; | |
1087 } | |
1088 } | |
1089 int lineIndex = 0; | |
1090 while (lineIndex < runs.length && lineOffset[lineIndex + 1] <= run.start) { | |
1091 lineIndex++; | |
1092 } | |
1093 left = Math.min(left, runLead); | |
1094 right = Math.max(right, runTrail); | |
1095 top = Math.min(top, lineY[lineIndex]); | |
1096 bottom = Math.max(bottom, lineY[lineIndex + 1] - lineSpacing); | |
1097 } | |
1098 return new Rectangle(left, top, right - left, bottom - top); | |
1099 } | |
1100 | |
1101 /** | |
1102 * Returns the descent of the receiver. | |
1103 * | |
1104 * @return the descent | |
1105 * | |
1106 * @exception DWTException <ul> | |
1107 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1108 * </ul> | |
1109 * | |
1110 * @see #getAscent() | |
1111 * @see #setAscent(int) | |
1112 * @see #setDescent(int) | |
1113 * @see #getLineMetrics(int) | |
1114 */ | |
1115 public int getDescent () { | |
1116 checkLayout(); | |
1117 return descent; | |
1118 } | |
1119 | |
1120 /** | |
1121 * Returns the default font currently being used by the receiver | |
1122 * to draw and measure text. | |
1123 * | |
1124 * @return the receiver's font | |
1125 * | |
1126 * @exception DWTException <ul> | |
1127 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1128 * </ul> | |
1129 */ | |
1130 public Font getFont () { | |
1131 checkLayout(); | |
1132 return font; | |
1133 } | |
1134 | |
1135 /** | |
1136 * Returns the receiver's indent. | |
1137 * | |
1138 * @return the receiver's indent | |
1139 * | |
1140 * @exception DWTException <ul> | |
1141 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1142 * </ul> | |
1143 * | |
1144 * @since 3.2 | |
1145 */ | |
1146 public int getIndent () { | |
1147 checkLayout(); | |
1148 return indent; | |
1149 } | |
1150 | |
1151 /** | |
1152 * Returns the receiver's justification. | |
1153 * | |
1154 * @return the receiver's justification | |
1155 * | |
1156 * @exception DWTException <ul> | |
1157 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1158 * </ul> | |
1159 * | |
1160 * @since 3.2 | |
1161 */ | |
1162 public bool getJustify () { | |
1163 checkLayout(); | |
1164 return justify; | |
1165 } | |
1166 | |
30 | 1167 HFONT getItemFont (StyleItem item) { |
1168 if (item.fallbackFont !is null) return cast(HFONT) item.fallbackFont; | |
29 | 1169 if (item.style !is null && item.style.font !is null) { |
1170 return item.style.font.handle; | |
1171 } | |
1172 if (this.font !is null) { | |
1173 return this.font.handle; | |
1174 } | |
1175 return device.systemFont; | |
1176 } | |
1177 | |
1178 /** | |
1179 * Returns the embedding level for the specified character offset. The | |
1180 * embedding level is usually used to determine the directionality of a | |
1181 * character in bidirectional text. | |
1182 * | |
1183 * @param offset the character offset | |
1184 * @return the embedding level | |
1185 * | |
1186 * @exception IllegalArgumentException <ul> | |
1187 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> | |
1188 * </ul> | |
1189 * @exception DWTException <ul> | |
1190 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1191 */ | |
1192 public int getLevel (int offset) { | |
1193 checkLayout(); | |
1194 computeRuns(null); | |
30 | 1195 int length = text.length; |
29 | 1196 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); |
1197 offset = translateOffset(offset); | |
1198 for (int i=1; i<allRuns.length; i++) { | |
1199 if (allRuns[i].start > offset) { | |
1200 return allRuns[i - 1].analysis.s.uBidiLevel; | |
1201 } | |
1202 } | |
1203 return (orientation & DWT.RIGHT_TO_LEFT) !is 0 ? 1 : 0; | |
1204 } | |
1205 | |
1206 /** | |
1207 * Returns the bounds of the line for the specified line index. | |
1208 * | |
1209 * @param lineIndex the line index | |
1210 * @return the line bounds | |
1211 * | |
1212 * @exception IllegalArgumentException <ul> | |
1213 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> | |
1214 * </ul> | |
1215 * @exception DWTException <ul> | |
1216 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1217 * </ul> | |
1218 */ | |
1219 public Rectangle getLineBounds(int lineIndex) { | |
1220 checkLayout(); | |
1221 computeRuns(null); | |
1222 if (!(0 <= lineIndex && lineIndex < runs.length)) DWT.error(DWT.ERROR_INVALID_RANGE); | |
1223 int x = getLineIndent(lineIndex); | |
1224 int y = lineY[lineIndex]; | |
1225 int width = lineWidth[lineIndex]; | |
1226 int height = lineY[lineIndex + 1] - y - lineSpacing; | |
1227 return new Rectangle (x, y, width, height); | |
1228 } | |
1229 | |
1230 /** | |
1231 * Returns the receiver's line count. This includes lines caused | |
1232 * by wrapping. | |
1233 * | |
1234 * @return the line count | |
1235 * | |
1236 * @exception DWTException <ul> | |
1237 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1238 * </ul> | |
1239 */ | |
1240 public int getLineCount () { | |
1241 checkLayout(); | |
1242 computeRuns(null); | |
1243 return runs.length; | |
1244 } | |
1245 | |
1246 int getLineIndent (int lineIndex) { | |
1247 int lineIndent = 0; | |
1248 if (lineIndex is 0) { | |
1249 lineIndent = indent; | |
1250 } else { | |
1251 StyleItem[] previousLine = runs[lineIndex - 1]; | |
1252 StyleItem previousRun = previousLine[previousLine.length - 1]; | |
1253 if (previousRun.lineBreak && !previousRun.softBreak) { | |
1254 lineIndent = indent; | |
1255 } | |
1256 } | |
1257 if (wrapWidth !is -1) { | |
1258 bool partialLine = true; | |
1259 if (justify) { | |
1260 StyleItem[] lineRun = runs[lineIndex]; | |
1261 if (lineRun[lineRun.length - 1].softBreak) { | |
1262 partialLine = false; | |
1263 } | |
1264 } | |
1265 if (partialLine) { | |
1266 int lineWidth = this.lineWidth[lineIndex] + lineIndent; | |
1267 switch (alignment) { | |
1268 case DWT.CENTER: lineIndent += (wrapWidth - lineWidth) / 2; break; | |
1269 case DWT.RIGHT: lineIndent += wrapWidth - lineWidth; break; | |
1270 } | |
1271 } | |
1272 } | |
1273 return lineIndent; | |
1274 } | |
1275 | |
1276 /** | |
1277 * Returns the index of the line that contains the specified | |
1278 * character offset. | |
1279 * | |
1280 * @param offset the character offset | |
1281 * @return the line index | |
1282 * | |
1283 * @exception IllegalArgumentException <ul> | |
1284 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> | |
1285 * </ul> | |
1286 * @exception DWTException <ul> | |
1287 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1288 * </ul> | |
1289 */ | |
1290 public int getLineIndex (int offset) { | |
1291 checkLayout(); | |
1292 computeRuns(null); | |
30 | 1293 int length = text.length; |
29 | 1294 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); |
1295 offset = translateOffset(offset); | |
1296 for (int line=0; line<runs.length; line++) { | |
1297 if (lineOffset[line + 1] > offset) { | |
1298 return line; | |
1299 } | |
1300 } | |
1301 return runs.length - 1; | |
1302 } | |
1303 | |
1304 /** | |
1305 * Returns the font metrics for the specified line index. | |
1306 * | |
1307 * @param lineIndex the line index | |
1308 * @return the font metrics | |
1309 * | |
1310 * @exception IllegalArgumentException <ul> | |
1311 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> | |
1312 * </ul> | |
1313 * @exception DWTException <ul> | |
1314 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1315 * </ul> | |
1316 */ | |
1317 public FontMetrics getLineMetrics (int lineIndex) { | |
1318 checkLayout(); | |
1319 computeRuns(null); | |
1320 if (!(0 <= lineIndex && lineIndex < runs.length)) DWT.error(DWT.ERROR_INVALID_RANGE); | |
30 | 1321 auto hDC = device.internal_new_GC(null); |
1322 auto srcHdc = OS.CreateCompatibleDC(hDC); | |
29 | 1323 TEXTMETRIC lptm; |
1324 OS.SelectObject(srcHdc, font !is null ? font.handle : device.systemFont); | |
1325 OS.GetTextMetrics(srcHdc, &lptm); | |
1326 OS.DeleteDC(srcHdc); | |
1327 device.internal_dispose_GC(hDC, null); | |
1328 | |
1329 int ascent = Math.max(lptm.tmAscent, this.ascent); | |
1330 int descent = Math.max(lptm.tmDescent, this.descent); | |
1331 int leading = lptm.tmInternalLeading; | |
30 | 1332 if (text.length !is 0) { |
29 | 1333 StyleItem[] lineRuns = runs[lineIndex]; |
1334 for (int i = 0; i<lineRuns.length; i++) { | |
1335 StyleItem run = lineRuns[i]; | |
1336 if (run.ascent > ascent) { | |
1337 ascent = run.ascent; | |
1338 leading = run.leading; | |
1339 } | |
1340 descent = Math.max(descent, run.descent); | |
1341 } | |
1342 } | |
1343 lptm.tmAscent = ascent; | |
1344 lptm.tmDescent = descent; | |
1345 lptm.tmHeight = ascent + descent; | |
1346 lptm.tmInternalLeading = leading; | |
1347 lptm.tmAveCharWidth = 0; | |
30 | 1348 return FontMetrics.win32_new(&lptm); |
29 | 1349 } |
1350 | |
1351 /** | |
1352 * Returns the line offsets. Each value in the array is the | |
1353 * offset for the first character in a line except for the last | |
1354 * value, which contains the length of the text. | |
1355 * | |
1356 * @return the line offsets | |
1357 * | |
1358 * @exception DWTException <ul> | |
1359 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1360 * </ul> | |
1361 */ | |
1362 public int[] getLineOffsets () { | |
1363 checkLayout(); | |
1364 computeRuns(null); | |
1365 int[] offsets = new int[lineOffset.length]; | |
1366 for (int i = 0; i < offsets.length; i++) { | |
1367 offsets[i] = untranslateOffset(lineOffset[i]); | |
1368 } | |
1369 return offsets; | |
1370 } | |
1371 | |
1372 /** | |
1373 * Returns the location for the specified character offset. The | |
1374 * <code>trailing</code> argument indicates whether the offset | |
1375 * corresponds to the leading or trailing edge of the cluster. | |
1376 * | |
1377 * @param offset the character offset | |
1378 * @param trailing the trailing flag | |
1379 * @return the location of the character offset | |
1380 * | |
1381 * @exception DWTException <ul> | |
1382 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1383 * </ul> | |
1384 * | |
1385 * @see #getOffset(Point, int[]) | |
1386 * @see #getOffset(int, int, int[]) | |
1387 */ | |
1388 public Point getLocation (int offset, bool trailing) { | |
1389 checkLayout(); | |
1390 computeRuns(null); | |
30 | 1391 int length = text.length; |
29 | 1392 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); |
30 | 1393 length = segmentsText.length; |
29 | 1394 offset = translateOffset(offset); |
1395 int line; | |
1396 for (line=0; line<runs.length; line++) { | |
1397 if (lineOffset[line + 1] > offset) break; | |
1398 } | |
1399 line = Math.min(line, runs.length - 1); | |
1400 StyleItem[] lineRuns = runs[line]; | |
1401 Point result = null; | |
1402 if (offset is length) { | |
1403 result = new Point(lineWidth[line], lineY[line]); | |
1404 } else { | |
1405 int width = 0; | |
1406 for (int i=0; i<lineRuns.length; i++) { | |
1407 StyleItem run = lineRuns[i]; | |
1408 int end = run.start + run.length; | |
1409 if (run.start <= offset && offset < end) { | |
1410 if (run.style !is null && run.style.metrics !is null) { | |
1411 GlyphMetrics metrics = run.style.metrics; | |
1412 width += metrics.width * (offset - run.start + (trailing ? 1 : 0)); | |
1413 result = new Point(width, lineY[line]); | |
1414 } else if (run.tab) { | |
1415 if (trailing || (offset is length)) width += run.width; | |
1416 result = new Point(width, lineY[line]); | |
1417 } else { | |
1418 int runOffset = offset - run.start; | |
1419 int cChars = run.length; | |
1420 int gGlyphs = run.glyphCount; | |
30 | 1421 int piX; |
1422 int* advances = run.justify !is null ? run.justify : run.advances; | |
1423 OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piX); | |
29 | 1424 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { |
30 | 1425 result = new Point(width + (run.width - piX), lineY[line]); |
29 | 1426 } else { |
30 | 1427 result = new Point(width + piX, lineY[line]); |
29 | 1428 } |
1429 } | |
1430 break; | |
1431 } | |
1432 width += run.width; | |
1433 } | |
1434 } | |
1435 if (result is null) result = new Point(0, 0); | |
1436 result.x += getLineIndent(line); | |
1437 return result; | |
1438 } | |
1439 | |
1440 /** | |
1441 * Returns the next offset for the specified offset and movement | |
1442 * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>, | |
1443 * <code>DWT.MOVEMENT_CLUSTER</code>, <code>DWT.MOVEMENT_WORD</code>, | |
1444 * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>. | |
1445 * | |
1446 * @param offset the start offset | |
1447 * @param movement the movement type | |
1448 * @return the next offset | |
1449 * | |
1450 * @exception IllegalArgumentException <ul> | |
1451 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> | |
1452 * </ul> | |
1453 * @exception DWTException <ul> | |
1454 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1455 * </ul> | |
1456 * | |
1457 * @see #getPreviousOffset(int, int) | |
1458 */ | |
1459 public int getNextOffset (int offset, int movement) { | |
1460 checkLayout(); | |
1461 return _getOffset (offset, movement, true); | |
1462 } | |
1463 | |
1464 int _getOffset(int offset, int movement, bool forward) { | |
1465 computeRuns(null); | |
30 | 1466 int length = text.length; |
29 | 1467 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE); |
1468 if (forward && offset is length) return length; | |
1469 if (!forward && offset is 0) return 0; | |
1470 int step = forward ? 1 : -1; | |
1471 if ((movement & DWT.MOVEMENT_CHAR) !is 0) return offset + step; | |
30 | 1472 length = segmentsText.length; |
29 | 1473 offset = translateOffset(offset); |
30 | 1474 SCRIPT_LOGATTR* logAttr; |
1475 SCRIPT_PROPERTIES* properties; | |
29 | 1476 int i = forward ? 0 : allRuns.length - 1; |
1477 offset = validadeOffset(offset, step); | |
1478 do { | |
1479 StyleItem run = allRuns[i]; | |
1480 if (run.start <= offset && offset < run.start + run.length) { | |
1481 if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start); | |
1482 if (run.tab) return untranslateOffset(run.start); | |
30 | 1483 properties = device.scripts[run.analysis.eScript]; |
29 | 1484 bool isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking; |
1485 if (isComplex) breakRun(run); | |
1486 while (run.start <= offset && offset < run.start + run.length) { | |
1487 if (isComplex) { | |
30 | 1488 logAttr = run.psla + (offset - run.start); |
29 | 1489 } |
1490 switch (movement) { | |
1491 case DWT.MOVEMENT_CLUSTER: { | |
1492 if (properties.fNeedsCaretInfo) { | |
1493 if (!logAttr.fInvalid && logAttr.fCharStop) return untranslateOffset(offset); | |
1494 } else { | |
1495 return untranslateOffset(offset); | |
1496 } | |
1497 break; | |
1498 } | |
1499 case DWT.MOVEMENT_WORD_START: | |
1500 case DWT.MOVEMENT_WORD: { | |
1501 if (properties.fNeedsWordBreaking) { | |
1502 if (!logAttr.fInvalid && logAttr.fWordStop) return untranslateOffset(offset); | |
1503 } else { | |
1504 if (offset > 0) { | |
1505 bool letterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset)); | |
1506 bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1)); | |
1507 if (letterOrDigit !is previousLetterOrDigit || !letterOrDigit) { | |
1508 if (!Compatibility.isWhitespace(segmentsText.charAt(offset))) { | |
1509 return untranslateOffset(offset); | |
1510 } | |
1511 } | |
1512 } | |
1513 } | |
1514 break; | |
1515 } | |
1516 case DWT.MOVEMENT_WORD_END: { | |
1517 if (offset > 0) { | |
1518 bool isLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset)); | |
1519 bool previousLetterOrDigit = Compatibility.isLetterOrDigit(segmentsText.charAt(offset - 1)); | |
1520 if (!isLetterOrDigit && previousLetterOrDigit) { | |
1521 return untranslateOffset(offset); | |
1522 } | |
1523 } | |
1524 break; | |
1525 } | |
1526 } | |
1527 offset = validadeOffset(offset, step); | |
1528 } | |
1529 } | |
1530 i += step; | |
1531 } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length); | |
30 | 1532 return forward ? text.length : 0; |
29 | 1533 } |
1534 | |
1535 /** | |
1536 * Returns the character offset for the specified point. | |
1537 * For a typical character, the trailing argument will be filled in to | |
1538 * indicate whether the point is closer to the leading edge (0) or | |
1539 * the trailing edge (1). When the point is over a cluster composed | |
1540 * of multiple characters, the trailing argument will be filled with the | |
1541 * position of the character in the cluster that is closest to | |
1542 * the point. | |
1543 * | |
1544 * @param point the point | |
1545 * @param trailing the trailing buffer | |
1546 * @return the character offset | |
1547 * | |
1548 * @exception IllegalArgumentException <ul> | |
1549 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> | |
1550 * <li>ERROR_NULL_ARGUMENT - if the point is null</li> | |
1551 * </ul> | |
1552 * @exception DWTException <ul> | |
1553 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1554 * </ul> | |
1555 * | |
1556 * @see #getLocation(int, bool) | |
1557 */ | |
1558 public int getOffset (Point point, int[] trailing) { | |
1559 checkLayout(); | |
1560 if (point is null) DWT.error (DWT.ERROR_NULL_ARGUMENT); | |
1561 return getOffset (point.x, point.y, trailing) ; | |
1562 } | |
1563 | |
1564 /** | |
1565 * Returns the character offset for the specified point. | |
1566 * For a typical character, the trailing argument will be filled in to | |
1567 * indicate whether the point is closer to the leading edge (0) or | |
1568 * the trailing edge (1). When the point is over a cluster composed | |
1569 * of multiple characters, the trailing argument will be filled with the | |
1570 * position of the character in the cluster that is closest to | |
1571 * the point. | |
1572 * | |
1573 * @param x the x coordinate of the point | |
1574 * @param y the y coordinate of the point | |
1575 * @param trailing the trailing buffer | |
1576 * @return the character offset | |
1577 * | |
1578 * @exception IllegalArgumentException <ul> | |
1579 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> | |
1580 * </ul> | |
1581 * @exception DWTException <ul> | |
1582 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1583 * </ul> | |
1584 * | |
1585 * @see #getLocation(int, bool) | |
1586 */ | |
1587 public int getOffset (int x, int y, int[] trailing) { | |
1588 checkLayout(); | |
1589 computeRuns(null); | |
1590 if (trailing !is null && trailing.length < 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
1591 int line; | |
1592 int lineCount = runs.length; | |
1593 for (line=0; line<lineCount; line++) { | |
1594 if (lineY[line + 1] > y) break; | |
1595 } | |
1596 line = Math.min(line, runs.length - 1); | |
1597 x -= getLineIndent(line); | |
1598 StyleItem[] lineRuns = runs[line]; | |
1599 if (x >= lineWidth[line]) x = lineWidth[line] - 1; | |
1600 if (x < 0) x = 0; | |
1601 int width = 0; | |
1602 for (int i = 0; i < lineRuns.length; i++) { | |
1603 StyleItem run = lineRuns[i]; | |
1604 if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start); | |
1605 if (width + run.width > x) { | |
1606 int xRun = x - width; | |
1607 if (run.style !is null && run.style.metrics !is null) { | |
1608 GlyphMetrics metrics = run.style.metrics; | |
1609 if (metrics.width > 0) { | |
1610 if (trailing !is null) { | |
1611 trailing[0] = (xRun % metrics.width < metrics.width / 2) ? 0 : 1; | |
1612 } | |
1613 return untranslateOffset(run.start + xRun / metrics.width); | |
1614 } | |
1615 } | |
1616 if (run.tab) { | |
1617 if (trailing !is null) trailing[0] = x < (width + run.width / 2) ? 0 : 1; | |
1618 return untranslateOffset(run.start); | |
1619 } | |
1620 int cChars = run.length; | |
1621 int cGlyphs = run.glyphCount; | |
30 | 1622 int piCP; |
1623 int piTrailing; | |
29 | 1624 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { |
1625 xRun = run.width - xRun; | |
1626 } | |
30 | 1627 int* advances = run.justify !is null ? run.justify : run.advances; |
1628 OS.ScriptXtoCP(xRun, cChars, cGlyphs, run.clusters, run.visAttrs, advances, &run.analysis, &piCP, &piTrailing); | |
1629 if (trailing !is null) trailing[0] = piTrailing; | |
1630 return untranslateOffset(run.start + piCP); | |
29 | 1631 } |
1632 width += run.width; | |
1633 } | |
1634 if (trailing !is null) trailing[0] = 0; | |
1635 return untranslateOffset(lineOffset[line + 1]); | |
1636 } | |
1637 | |
1638 /** | |
1639 * Returns the orientation of the receiver. | |
1640 * | |
1641 * @return the orientation style | |
1642 * | |
1643 * @exception DWTException <ul> | |
1644 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1645 * </ul> | |
1646 */ | |
1647 public int getOrientation () { | |
1648 checkLayout(); | |
1649 return orientation; | |
1650 } | |
1651 | |
1652 /** | |
1653 * Returns the previous offset for the specified offset and movement | |
1654 * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>, | |
1655 * <code>DWT.MOVEMENT_CLUSTER</code> or <code>DWT.MOVEMENT_WORD</code>, | |
1656 * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>. | |
1657 * | |
1658 * @param offset the start offset | |
1659 * @param movement the movement type | |
1660 * @return the previous offset | |
1661 * | |
1662 * @exception IllegalArgumentException <ul> | |
1663 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> | |
1664 * </ul> | |
1665 * @exception DWTException <ul> | |
1666 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1667 * </ul> | |
1668 * | |
1669 * @see #getNextOffset(int, int) | |
1670 */ | |
1671 public int getPreviousOffset (int offset, int movement) { | |
1672 checkLayout(); | |
1673 return _getOffset (offset, movement, false); | |
1674 } | |
1675 | |
1676 /** | |
1677 * Gets the ranges of text that are associated with a <code>TextStyle</code>. | |
1678 * | |
1679 * @return the ranges, an array of offsets representing the start and end of each | |
1680 * text style. | |
1681 * | |
1682 * @exception DWTException <ul> | |
1683 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1684 * </ul> | |
1685 * | |
1686 * @see #getStyles() | |
1687 * | |
1688 * @since 3.2 | |
1689 */ | |
1690 public int[] getRanges () { | |
1691 checkLayout(); | |
1692 int[] result = new int[styles.length * 2]; | |
1693 int count = 0; | |
1694 for (int i=0; i<styles.length - 1; i++) { | |
1695 if (styles[i].style !is null) { | |
1696 result[count++] = styles[i].start; | |
1697 result[count++] = styles[i + 1].start - 1; | |
1698 } | |
1699 } | |
1700 if (count !is result.length) { | |
1701 int[] newResult = new int[count]; | |
1702 System.arraycopy(result, 0, newResult, 0, count); | |
1703 result = newResult; | |
1704 } | |
1705 return result; | |
1706 } | |
1707 | |
1708 /** | |
1709 * Returns the text segments offsets of the receiver. | |
1710 * | |
1711 * @return the text segments offsets | |
1712 * | |
1713 * @exception DWTException <ul> | |
1714 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1715 * </ul> | |
1716 */ | |
1717 public int[] getSegments () { | |
1718 checkLayout(); | |
1719 return segments; | |
1720 } | |
1721 | |
1722 char[] getSegmentsText() { | |
1723 if (segments is null) return text; | |
1724 int nSegments = segments.length; | |
1725 if (nSegments <= 1) return text; | |
30 | 1726 int length = text.length; |
29 | 1727 if (length is 0) return text; |
1728 if (nSegments is 2) { | |
1729 if (segments[0] is 0 && segments[1] is length) return text; | |
1730 } | |
1731 char[] oldChars = new char[length]; | |
1732 text.getChars(0, length, oldChars, 0); | |
1733 char[] newChars = new char[length + nSegments]; | |
1734 int charCount = 0, segmentCount = 0; | |
30 | 1735 wchar separator = orientation is DWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK; |
29 | 1736 while (charCount < length) { |
1737 if (segmentCount < nSegments && charCount is segments[segmentCount]) { | |
1738 newChars[charCount + segmentCount++] = separator; | |
1739 } else { | |
1740 newChars[charCount + segmentCount] = oldChars[charCount++]; | |
1741 } | |
1742 } | |
1743 if (segmentCount < nSegments) { | |
1744 segments[segmentCount] = charCount; | |
1745 newChars[charCount + segmentCount++] = separator; | |
1746 } | |
30 | 1747 return newChars[ 0 .. Math.min(charCount + segmentCount, newChars.length)].dup; |
29 | 1748 } |
1749 | |
1750 /** | |
1751 * Returns the line spacing of the receiver. | |
1752 * | |
1753 * @return the line spacing | |
1754 * | |
1755 * @exception DWTException <ul> | |
1756 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1757 * </ul> | |
1758 */ | |
1759 public int getSpacing () { | |
1760 checkLayout(); | |
1761 return lineSpacing; | |
1762 } | |
1763 | |
1764 /** | |
1765 * Gets the style of the receiver at the specified character offset. | |
1766 * | |
1767 * @param offset the text offset | |
1768 * @return the style or <code>null</code> if not set | |
1769 * | |
1770 * @exception IllegalArgumentException <ul> | |
1771 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> | |
1772 * </ul> | |
1773 * @exception DWTException <ul> | |
1774 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1775 * </ul> | |
1776 */ | |
1777 public TextStyle getStyle (int offset) { | |
1778 checkLayout(); | |
30 | 1779 int length = text.length; |
29 | 1780 if (!(0 <= offset && offset < length)) DWT.error(DWT.ERROR_INVALID_RANGE); |
1781 for (int i=1; i<styles.length; i++) { | |
1782 if (styles[i].start > offset) { | |
1783 return styles[i - 1].style; | |
1784 } | |
1785 } | |
1786 return null; | |
1787 } | |
1788 | |
1789 /** | |
1790 * Gets all styles of the receiver. | |
1791 * | |
1792 * @return the styles | |
1793 * | |
1794 * @exception DWTException <ul> | |
1795 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1796 * </ul> | |
1797 * | |
1798 * @see #getRanges() | |
1799 * | |
1800 * @since 3.2 | |
1801 */ | |
1802 public TextStyle[] getStyles () { | |
1803 checkLayout(); | |
1804 TextStyle[] result = new TextStyle[styles.length]; | |
1805 int count = 0; | |
1806 for (int i=0; i<styles.length; i++) { | |
1807 if (styles[i].style !is null) { | |
1808 result[count++] = styles[i].style; | |
1809 } | |
1810 } | |
1811 if (count !is result.length) { | |
1812 TextStyle[] newResult = new TextStyle[count]; | |
1813 System.arraycopy(result, 0, newResult, 0, count); | |
1814 result = newResult; | |
1815 } | |
1816 return result; | |
1817 } | |
1818 | |
1819 /** | |
1820 * Returns the tab list of the receiver. | |
1821 * | |
1822 * @return the tab list | |
1823 * | |
1824 * @exception DWTException <ul> | |
1825 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1826 * </ul> | |
1827 */ | |
1828 public int[] getTabs () { | |
1829 checkLayout(); | |
1830 return tabs; | |
1831 } | |
1832 | |
1833 /** | |
1834 * Gets the receiver's text, which will be an empty | |
1835 * string if it has never been set. | |
1836 * | |
1837 * @return the receiver's text | |
1838 * | |
1839 * @exception DWTException <ul> | |
1840 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1841 * </ul> | |
1842 */ | |
1843 public char[] getText () { | |
1844 checkLayout(); | |
1845 return text; | |
1846 } | |
1847 | |
1848 /** | |
1849 * Returns the width of the receiver. | |
1850 * | |
1851 * @return the width | |
1852 * | |
1853 * @exception DWTException <ul> | |
1854 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
1855 * </ul> | |
1856 */ | |
1857 public int getWidth () { | |
1858 checkLayout(); | |
1859 return wrapWidth; | |
1860 } | |
1861 | |
1862 /** | |
1863 * Returns <code>true</code> if the text layout has been disposed, | |
1864 * and <code>false</code> otherwise. | |
1865 * <p> | |
1866 * This method gets the dispose state for the text layout. | |
1867 * When a text layout has been disposed, it is an error to | |
1868 * invoke any other method using the text layout. | |
1869 * </p> | |
1870 * | |
1871 * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise | |
1872 */ | |
48
9a64a7781bab
Added override and alias, first chunk. Thanks torhu for doing this patch.
Frank Benoit <benoit@tionex.de>
parents:
31
diff
changeset
|
1873 override public bool isDisposed () { |
29 | 1874 return device is null; |
1875 } | |
1876 | |
1877 /* | |
1878 * Itemize the receiver text | |
1879 */ | |
1880 StyleItem[] itemize () { | |
1881 segmentsText = getSegmentsText(); | |
30 | 1882 int length = segmentsText.length; |
1883 SCRIPT_CONTROL scriptControl; | |
1884 SCRIPT_STATE scriptState; | |
29 | 1885 final int MAX_ITEM = length + 1; |
1886 | |
1887 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { | |
1888 scriptState.uBidiLevel = 1; | |
1889 scriptState.fArabicNumContext = true; | |
30 | 1890 SCRIPT_DIGITSUBSTITUTE psds; |
1891 OS.ScriptRecordDigitSubstitution(OS.LOCALE_USER_DEFAULT, &psds); | |
1892 OS.ScriptApplyDigitSubstitution(&psds, &scriptControl, &scriptState); | |
29 | 1893 } |
1894 | |
30 | 1895 auto hHeap = OS.GetProcessHeap(); |
1896 auto pItems = cast(SCRIPT_ITEM*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof); | |
1897 if (pItems is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
1898 int pcItems; | |
1899 wchar[] chars = StrToWCHARs( segmentsText ); | |
1900 OS.ScriptItemize(chars.ptr, chars.length, MAX_ITEM, &scriptControl, &scriptState, pItems, &pcItems); | |
29 | 1901 // if (hr is E_OUTOFMEMORY) //TODO handle it |
1902 | |
30 | 1903 StyleItem[] runs = merge(pItems, pcItems); |
29 | 1904 OS.HeapFree(hHeap, 0, pItems); |
1905 return runs; | |
1906 } | |
1907 | |
1908 /* | |
1909 * Merge styles ranges and script items | |
1910 */ | |
30 | 1911 StyleItem[] merge (SCRIPT_ITEM* items, int itemCount) { |
1912 int count = 0, start = 0, end = segmentsText.length, itemIndex = 0, styleIndex = 0; | |
29 | 1913 StyleItem[] runs = new StyleItem[itemCount + styles.length]; |
30 | 1914 SCRIPT_ITEM* scriptItem; |
29 | 1915 bool linkBefore = false; |
1916 while (start < end) { | |
1917 StyleItem item = new StyleItem(); | |
1918 item.start = start; | |
1919 item.style = styles[styleIndex].style; | |
1920 runs[count++] = item; | |
30 | 1921 scriptItem = items + itemIndex; |
29 | 1922 item.analysis = scriptItem.a; |
1923 if (linkBefore) { | |
1924 item.analysis.fLinkBefore = true; | |
1925 linkBefore = false; | |
1926 } | |
30 | 1927 //scriptItem.a = new SCRIPT_ANALYSIS(); |
1928 scriptItem = items + (itemIndex + 1); | |
29 | 1929 int itemLimit = scriptItem.iCharPos; |
1930 int styleLimit = translateOffset(styles[styleIndex + 1].start); | |
1931 if (styleLimit <= itemLimit) { | |
1932 styleIndex++; | |
1933 start = styleLimit; | |
1934 if (start < itemLimit && 0 < start && start < end) { | |
1935 char pChar = segmentsText.charAt(start - 1); | |
1936 char tChar = segmentsText.charAt(start); | |
1937 if (!Compatibility.isWhitespace(pChar) && !Compatibility.isWhitespace(tChar)) { | |
1938 item.analysis.fLinkAfter = true; | |
1939 linkBefore = true; | |
1940 } | |
1941 } | |
1942 } | |
1943 if (itemLimit <= styleLimit) { | |
1944 itemIndex++; | |
1945 start = itemLimit; | |
1946 } | |
1947 item.length = start - item.start; | |
1948 } | |
1949 StyleItem item = new StyleItem(); | |
1950 item.start = end; | |
30 | 1951 scriptItem = items + itemCount; |
29 | 1952 item.analysis = scriptItem.a; |
1953 runs[count++] = item; | |
1954 if (runs.length !is count) { | |
1955 StyleItem[] result = new StyleItem[count]; | |
1956 System.arraycopy(runs, 0, result, 0, count); | |
1957 return result; | |
1958 } | |
1959 return runs; | |
1960 } | |
1961 | |
1962 /* | |
1963 * Reorder the run | |
1964 */ | |
1965 StyleItem[] reorder (StyleItem[] runs, bool terminate) { | |
30 | 1966 int length_ = runs.length; |
1967 if (length_ <= 1) return runs; | |
1968 ubyte[] bidiLevels = new ubyte[length_]; | |
1969 for (int i=0; i<length_; i++) { | |
29 | 1970 bidiLevels[i] = cast(byte)(runs[i].analysis.s.uBidiLevel & 0x1F); |
1971 } | |
1972 /* | |
1973 * Feature in Windows. If the orientation is RTL Uniscribe will | |
1974 * resolve the level of line breaks to 1, this can cause the line | |
1975 * break to be reorder to the middle of the line. The fix is to set | |
1976 * the level to zero to prevent it to be reordered. | |
1977 */ | |
30 | 1978 StyleItem lastRun = runs[length_ - 1]; |
29 | 1979 if (lastRun.lineBreak && !lastRun.softBreak) { |
30 | 1980 bidiLevels[length_ - 1] = 0; |
29 | 1981 } |
30 | 1982 int[] log2vis = new int[length_]; |
1983 OS.ScriptLayout(length_, bidiLevels.ptr, null, log2vis.ptr); | |
1984 StyleItem[] result = new StyleItem[length_]; | |
1985 for (int i=0; i<length_; i++) { | |
29 | 1986 result[log2vis[i]] = runs[i]; |
1987 } | |
1988 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { | |
30 | 1989 if (terminate) length_--; |
1990 for (int i = 0; i < length_ / 2 ; i++) { | |
29 | 1991 StyleItem tmp = result[i]; |
30 | 1992 result[i] = result[length_ - i - 1]; |
1993 result[length_ - i - 1] = tmp; | |
29 | 1994 } |
1995 } | |
1996 return result; | |
1997 } | |
1998 | |
1999 /** | |
2000 * Sets the text alignment for the receiver. The alignment controls | |
2001 * how a line of text is positioned horizontally. The argument should | |
2002 * be one of <code>DWT.LEFT</code>, <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>. | |
2003 * <p> | |
2004 * The default alignment is <code>DWT.LEFT</code>. Note that the receiver's | |
2005 * width must be set in order to use <code>DWT.RIGHT</code> or <code>DWT.CENTER</code> | |
2006 * alignment. | |
2007 * </p> | |
2008 * | |
2009 * @param alignment the new alignment | |
2010 * | |
2011 * @exception DWTException <ul> | |
2012 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2013 * </ul> | |
2014 * | |
2015 * @see #setWidth(int) | |
2016 */ | |
2017 public void setAlignment (int alignment) { | |
2018 checkLayout(); | |
2019 int mask = DWT.LEFT | DWT.CENTER | DWT.RIGHT; | |
2020 alignment &= mask; | |
2021 if (alignment is 0) return; | |
2022 if ((alignment & DWT.LEFT) !is 0) alignment = DWT.LEFT; | |
2023 if ((alignment & DWT.RIGHT) !is 0) alignment = DWT.RIGHT; | |
2024 if (this.alignment is alignment) return; | |
2025 freeRuns(); | |
2026 this.alignment = alignment; | |
2027 } | |
2028 | |
2029 /** | |
2030 * Sets the ascent of the receiver. The ascent is distance in pixels | |
2031 * from the baseline to the top of the line and it is applied to all | |
2032 * lines. The default value is <code>-1</code> which means that the | |
2033 * ascent is calculated from the line fonts. | |
2034 * | |
2035 * @param ascent the new ascent | |
2036 * | |
2037 * @exception IllegalArgumentException <ul> | |
2038 * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li> | |
2039 * </ul> | |
2040 * @exception DWTException <ul> | |
2041 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2042 * </ul> | |
2043 * | |
2044 * @see #setDescent(int) | |
2045 * @see #getLineMetrics(int) | |
2046 */ | |
2047 public void setAscent(int ascent) { | |
2048 checkLayout(); | |
2049 if (ascent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
2050 if (this.ascent is ascent) return; | |
2051 freeRuns(); | |
2052 this.ascent = ascent; | |
2053 } | |
2054 | |
2055 /** | |
2056 * Sets the descent of the receiver. The descent is distance in pixels | |
2057 * from the baseline to the bottom of the line and it is applied to all | |
2058 * lines. The default value is <code>-1</code> which means that the | |
2059 * descent is calculated from the line fonts. | |
2060 * | |
2061 * @param descent the new descent | |
2062 * | |
2063 * @exception IllegalArgumentException <ul> | |
2064 * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li> | |
2065 * </ul> | |
2066 * @exception DWTException <ul> | |
2067 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2068 * </ul> | |
2069 * | |
2070 * @see #setAscent(int) | |
2071 * @see #getLineMetrics(int) | |
2072 */ | |
2073 public void setDescent(int descent) { | |
2074 checkLayout(); | |
2075 if (descent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
2076 if (this.descent is descent) return; | |
2077 freeRuns(); | |
2078 this.descent = descent; | |
2079 } | |
2080 | |
2081 /** | |
2082 * Sets the default font which will be used by the receiver | |
2083 * to draw and measure text. If the | |
2084 * argument is null, then a default font appropriate | |
2085 * for the platform will be used instead. Note that a text | |
2086 * style can override the default font. | |
2087 * | |
2088 * @param font the new font for the receiver, or null to indicate a default font | |
2089 * | |
2090 * @exception IllegalArgumentException <ul> | |
2091 * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li> | |
2092 * </ul> | |
2093 * @exception DWTException <ul> | |
2094 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2095 * </ul> | |
2096 */ | |
2097 public void setFont (Font font) { | |
2098 checkLayout(); | |
2099 if (font !is null && font.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
2100 if (this.font is font) return; | |
30 | 2101 if (font !is null && font ==/*eq*/ this.font) return; |
29 | 2102 freeRuns(); |
2103 this.font = font; | |
2104 } | |
2105 | |
2106 /** | |
2107 * Sets the indent of the receiver. This indent it applied of the first line of | |
2108 * each paragraph. | |
2109 * | |
2110 * @param indent new indent | |
2111 * | |
2112 * @exception DWTException <ul> | |
2113 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2114 * </ul> | |
2115 * | |
2116 * @since 3.2 | |
2117 */ | |
2118 public void setIndent (int indent) { | |
2119 checkLayout(); | |
2120 if (indent < 0) return; | |
2121 if (this.indent is indent) return; | |
2122 freeRuns(); | |
2123 this.indent = indent; | |
2124 } | |
2125 | |
2126 /** | |
2127 * Sets the justification of the receiver. Note that the receiver's | |
2128 * width must be set in order to use justification. | |
2129 * | |
2130 * @param justify new justify | |
2131 * | |
2132 * @exception DWTException <ul> | |
2133 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2134 * </ul> | |
2135 * | |
2136 * @since 3.2 | |
2137 */ | |
2138 public void setJustify (bool justify) { | |
2139 checkLayout(); | |
2140 if (this.justify is justify) return; | |
2141 freeRuns(); | |
2142 this.justify = justify; | |
2143 } | |
2144 | |
2145 /** | |
2146 * Sets the orientation of the receiver, which must be one | |
2147 * of <code>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>. | |
2148 * | |
2149 * @param orientation new orientation style | |
2150 * | |
2151 * @exception DWTException <ul> | |
2152 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2153 * </ul> | |
2154 */ | |
2155 public void setOrientation (int orientation) { | |
2156 checkLayout(); | |
2157 int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT; | |
2158 orientation &= mask; | |
2159 if (orientation is 0) return; | |
2160 if ((orientation & DWT.LEFT_TO_RIGHT) !is 0) orientation = DWT.LEFT_TO_RIGHT; | |
2161 if (this.orientation is orientation) return; | |
2162 this.orientation = orientation; | |
2163 freeRuns(); | |
2164 } | |
2165 | |
2166 /** | |
2167 * Sets the offsets of the receiver's text segments. Text segments are used to | |
2168 * override the default behaviour of the bidirectional algorithm. | |
2169 * Bidirectional reordering can happen within a text segment but not | |
2170 * between two adjacent segments. | |
2171 * <p> | |
2172 * Each text segment is determined by two consecutive offsets in the | |
2173 * <code>segments</code> arrays. The first element of the array should | |
2174 * always be zero and the last one should always be equals to length of | |
2175 * the text. | |
2176 * </p> | |
2177 * | |
2178 * @param segments the text segments offset | |
2179 * | |
2180 * @exception DWTException <ul> | |
2181 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2182 * </ul> | |
2183 */ | |
2184 public void setSegments(int[] segments) { | |
2185 checkLayout(); | |
2186 if (this.segments is null && segments is null) return; | |
2187 if (this.segments !is null && segments !is null) { | |
2188 if (this.segments.length is segments.length) { | |
2189 int i; | |
2190 for (i = 0; i <segments.length; i++) { | |
2191 if (this.segments[i] !is segments[i]) break; | |
2192 } | |
2193 if (i is segments.length) return; | |
2194 } | |
2195 } | |
2196 freeRuns(); | |
2197 this.segments = segments; | |
2198 } | |
2199 | |
2200 /** | |
2201 * Sets the line spacing of the receiver. The line spacing | |
2202 * is the space left between lines. | |
2203 * | |
2204 * @param spacing the new line spacing | |
2205 * | |
2206 * @exception IllegalArgumentException <ul> | |
2207 * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li> | |
2208 * </ul> | |
2209 * @exception DWTException <ul> | |
2210 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2211 * </ul> | |
2212 */ | |
2213 public void setSpacing (int spacing) { | |
2214 checkLayout(); | |
2215 if (spacing < 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
2216 if (this.lineSpacing is spacing) return; | |
2217 freeRuns(); | |
2218 this.lineSpacing = spacing; | |
2219 } | |
2220 | |
2221 /** | |
2222 * Sets the style of the receiver for the specified range. Styles previously | |
2223 * set for that range will be overwritten. The start and end offsets are | |
2224 * inclusive and will be clamped if out of range. | |
2225 * | |
2226 * @param style the style | |
2227 * @param start the start offset | |
2228 * @param end the end offset | |
2229 * | |
2230 * @exception DWTException <ul> | |
2231 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2232 * </ul> | |
2233 */ | |
2234 public void setStyle (TextStyle style, int start, int end) { | |
2235 checkLayout(); | |
30 | 2236 int length_ = text.length; |
2237 if (length_ is 0) return; | |
29 | 2238 if (start > end) return; |
30 | 2239 start = Math.min(Math.max(0, start), length_ - 1); |
2240 end = Math.min(Math.max(0, end), length_ - 1); | |
29 | 2241 int low = -1; |
2242 int high = styles.length; | |
2243 while (high - low > 1) { | |
2244 int index = (high + low) / 2; | |
2245 if (styles[index + 1].start > start) { | |
2246 high = index; | |
2247 } else { | |
2248 low = index; | |
2249 } | |
2250 } | |
2251 if (0 <= high && high < styles.length) { | |
2252 StyleItem item = styles[high]; | |
2253 if (item.start is start && styles[high + 1].start - 1 is end) { | |
2254 if (style is null) { | |
2255 if (item.style is null) return; | |
2256 } else { | |
30 | 2257 if (style ==/*eq*/ item.style) return; |
29 | 2258 } |
2259 } | |
2260 } | |
2261 freeRuns(); | |
2262 int modifyStart = high; | |
2263 int modifyEnd = modifyStart; | |
2264 while (modifyEnd < styles.length) { | |
2265 if (styles[modifyEnd + 1].start > end) break; | |
2266 modifyEnd++; | |
2267 } | |
2268 if (modifyStart is modifyEnd) { | |
2269 int styleStart = styles[modifyStart].start; | |
2270 int styleEnd = styles[modifyEnd + 1].start - 1; | |
2271 if (styleStart is start && styleEnd is end) { | |
2272 styles[modifyStart].style = style; | |
2273 return; | |
2274 } | |
2275 if (styleStart !is start && styleEnd !is end) { | |
2276 StyleItem[] newStyles = new StyleItem[styles.length + 2]; | |
2277 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); | |
2278 StyleItem item = new StyleItem(); | |
2279 item.start = start; | |
2280 item.style = style; | |
2281 newStyles[modifyStart + 1] = item; | |
2282 item = new StyleItem(); | |
2283 item.start = end + 1; | |
2284 item.style = styles[modifyStart].style; | |
2285 newStyles[modifyStart + 2] = item; | |
2286 System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1); | |
2287 styles = newStyles; | |
2288 return; | |
2289 } | |
2290 } | |
2291 if (start is styles[modifyStart].start) modifyStart--; | |
2292 if (end is styles[modifyEnd + 1].start - 1) modifyEnd++; | |
2293 int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1); | |
2294 StyleItem[] newStyles = new StyleItem[newLength]; | |
2295 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); | |
2296 StyleItem item = new StyleItem(); | |
2297 item.start = start; | |
2298 item.style = style; | |
2299 newStyles[modifyStart + 1] = item; | |
2300 styles[modifyEnd].start = end + 1; | |
2301 System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd); | |
2302 styles = newStyles; | |
2303 } | |
2304 | |
2305 /** | |
2306 * Sets the receiver's tab list. Each value in the tab list specifies | |
2307 * the space in pixels from the origin of the text layout to the respective | |
2308 * tab stop. The last tab stop width is repeated continuously. | |
2309 * | |
2310 * @param tabs the new tab list | |
2311 * | |
2312 * @exception DWTException <ul> | |
2313 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2314 * </ul> | |
2315 */ | |
2316 public void setTabs (int[] tabs) { | |
2317 checkLayout(); | |
2318 if (this.tabs is null && tabs is null) return; | |
2319 if (this.tabs !is null && tabs !is null) { | |
2320 if (this.tabs.length is tabs.length) { | |
2321 int i; | |
2322 for (i = 0; i <tabs.length; i++) { | |
2323 if (this.tabs[i] !is tabs[i]) break; | |
2324 } | |
2325 if (i is tabs.length) return; | |
2326 } | |
2327 } | |
2328 freeRuns(); | |
2329 this.tabs = tabs; | |
2330 } | |
2331 | |
2332 /** | |
2333 * Sets the receiver's text. | |
2334 * | |
2335 * @param text the new text | |
2336 * | |
2337 * @exception IllegalArgumentException <ul> | |
2338 * <li>ERROR_NULL_ARGUMENT - if the text is null</li> | |
2339 * </ul> | |
2340 * @exception DWTException <ul> | |
2341 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2342 * </ul> | |
2343 */ | |
2344 public void setText (char[] text) { | |
2345 checkLayout(); | |
2346 if (text is null) DWT.error(DWT.ERROR_NULL_ARGUMENT); | |
30 | 2347 if (text==/*eq*/this.text) return; |
29 | 2348 freeRuns(); |
2349 this.text = text; | |
2350 styles = new StyleItem[2]; | |
2351 styles[0] = new StyleItem(); | |
2352 styles[1] = new StyleItem(); | |
30 | 2353 styles[1].start = text.length; |
29 | 2354 } |
2355 | |
2356 /** | |
2357 * Sets the line width of the receiver, which determines how | |
2358 * text should be wrapped and aligned. The default value is | |
2359 * <code>-1</code> which means wrapping is disabled. | |
2360 * | |
2361 * @param width the new width | |
2362 * | |
2363 * @exception IllegalArgumentException <ul> | |
2364 * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li> | |
2365 * </ul> | |
2366 * @exception DWTException <ul> | |
2367 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> | |
2368 * </ul> | |
2369 * | |
2370 * @see #setAlignment(int) | |
2371 */ | |
2372 public void setWidth (int width) { | |
2373 checkLayout(); | |
2374 if (width < -1 || width is 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT); | |
2375 if (this.wrapWidth is width) return; | |
2376 freeRuns(); | |
2377 this.wrapWidth = width; | |
2378 } | |
2379 | |
30 | 2380 bool shape (HDC hdc, StyleItem run, char[] chars, int[] glyphCount, int maxGlyphs) { |
2381 wchar[] wchars = StrToWCHARs( chars ); | |
2382 auto hr = OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount.ptr); | |
29 | 2383 run.glyphCount = glyphCount[0]; |
2384 if (hr !is OS.USP_E_SCRIPT_NOT_IN_FONT) { | |
30 | 2385 SCRIPT_FONTPROPERTIES fp; |
29 | 2386 fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof; |
30 | 2387 OS.ScriptGetFontProperties(hdc, run.psc, &fp); |
2388 ushort[] glyphs = run.glyphs[ 0 .. glyphCount[0] ]; | |
29 | 2389 int i; |
2390 for (i = 0; i < glyphs.length; i++) { | |
2391 if (glyphs[i] is fp.wgDefault) break; | |
2392 } | |
2393 if (i is glyphs.length) return true; | |
2394 } | |
30 | 2395 if (run.psc !is null) { |
29 | 2396 OS.ScriptFreeCache(run.psc); |
2397 glyphCount[0] = 0; | |
30 | 2398 *cast(int*)run.psc = 0; |
29 | 2399 } |
2400 run.glyphCount = 0; | |
2401 return false; | |
2402 } | |
2403 | |
2404 /* | |
2405 * Generate glyphs for one Run. | |
2406 */ | |
2407 void shape (HDC hdc, StyleItem run) { | |
2408 int[] buffer = new int[1]; | |
2409 char[] chars = new char[run.length]; | |
2410 segmentsText.getChars(run.start, run.start + run.length, chars, 0); | |
30 | 2411 wchar[] wchars = StrToWCHARs( chars ); |
29 | 2412 int maxGlyphs = (chars.length * 3 / 2) + 16; |
30 | 2413 auto hHeap = OS.GetProcessHeap(); |
2414 run.glyphs = cast(ushort*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); | |
2415 if (run.glyphs is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
2416 run.clusters = cast(WORD*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2); | |
2417 if (run.clusters is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
2418 run.visAttrs = cast(SCRIPT_VISATTR*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF); | |
2419 if (run.visAttrs is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
2420 run.psc = cast(SCRIPT_CACHE*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, 4); | |
2421 if (run.psc is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
29 | 2422 if (!shape(hdc, run, chars, buffer, maxGlyphs)) { |
30 | 2423 if (mLangFontLink2 !is null) { |
2424 int dwCodePages; | |
2425 int cchCodePages; | |
29 | 2426 /* GetStrCodePages() */ |
30 | 2427 OS.VtblCall(4, mLangFontLink2, cast(int)wchars.ptr, wchars.length, 0, cast(int)&dwCodePages, cast(int)&cchCodePages); |
2428 HFONT hNewFont; | |
29 | 2429 /* MapFont() */ |
30 | 2430 if (OS.VtblCall(10, mLangFontLink2, cast(int)hdc, dwCodePages, cast(int)wchars[0], cast(int)hNewFont) is OS.S_OK) { |
2431 auto hFont = OS.SelectObject(hdc, hNewFont); | |
29 | 2432 if (shape(hdc, run, chars, buffer, maxGlyphs)) { |
30 | 2433 run.fallbackFont = hNewFont; |
29 | 2434 } else { |
2435 /* ReleaseFont() */ | |
30 | 2436 OS.VtblCall(8, mLangFontLink2, cast(int)hNewFont); |
29 | 2437 OS.SelectObject(hdc, hFont); |
30 | 2438 SCRIPT_PROPERTIES* properties; |
2439 properties = device.scripts[run.analysis.eScript]; | |
29 | 2440 if (properties.fPrivateUseArea) { |
2441 run.analysis.fNoGlyphIndex = true; | |
2442 } | |
30 | 2443 OS.ScriptShape(hdc, run.psc, wchars.ptr, wchars.length, maxGlyphs, &run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer.ptr); |
29 | 2444 run.glyphCount = buffer[0]; |
2445 } | |
2446 } | |
2447 } | |
2448 } | |
30 | 2449 ABC abc; |
2450 run.advances = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4); | |
2451 if (run.advances is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
2452 run.goffsets = cast(GOFFSET*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF); | |
2453 if (run.goffsets is null) DWT.error(DWT.ERROR_NO_HANDLES); | |
2454 OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, &run.analysis, run.advances, run.goffsets, &abc); | |
29 | 2455 if (run.style !is null && run.style.metrics !is null) { |
2456 GlyphMetrics metrics = run.style.metrics; | |
2457 /* | |
2458 * Bug in Windows, on a Japanese machine, Uniscribe returns glyphcount | |
2459 * equals zero for FFFC (possibly other unicode code points), the fix | |
2460 * is to make sure the glyph is at least one pixel wide. | |
2461 */ | |
2462 run.width = metrics.width * Math.max (1, run.glyphCount); | |
2463 run.ascent = metrics.ascent; | |
2464 run.descent = metrics.descent; | |
2465 run.leading = 0; | |
2466 } else { | |
30 | 2467 run.width = abc.abcA + abc.abcB + abc.abcC; |
29 | 2468 TEXTMETRIC lptm; |
2469 OS.GetTextMetrics(hdc, &lptm); | |
2470 run.ascent = lptm.tmAscent; | |
2471 run.descent = lptm.tmDescent; | |
2472 run.leading = lptm.tmInternalLeading; | |
2473 } | |
2474 if (run.style !is null) { | |
2475 run.ascent += run.style.rise; | |
2476 run.descent -= +run.style.rise; | |
2477 } | |
2478 } | |
2479 | |
2480 int validadeOffset(int offset, int step) { | |
2481 offset += step; | |
2482 if (segments !is null && segments.length > 2) { | |
2483 for (int i = 0; i < segments.length; i++) { | |
2484 if (translateOffset(segments[i]) - 1 is offset) { | |
2485 offset += step; | |
2486 break; | |
2487 } | |
2488 } | |
2489 } | |
2490 return offset; | |
2491 } | |
2492 | |
2493 /** | |
2494 * Returns a string containing a concise, human-readable | |
2495 * description of the receiver. | |
2496 * | |
2497 * @return a string representation of the receiver | |
2498 */ | |
48
9a64a7781bab
Added override and alias, first chunk. Thanks torhu for doing this patch.
Frank Benoit <benoit@tionex.de>
parents:
31
diff
changeset
|
2499 override public char[] toString () { |
29 | 2500 if (isDisposed()) return "TextLayout {*DISPOSED*}"; |
2501 return "TextLayout {}"; | |
2502 } | |
2503 | |
2504 int translateOffset(int offset) { | |
2505 if (segments is null) return offset; | |
2506 int nSegments = segments.length; | |
2507 if (nSegments <= 1) return offset; | |
30 | 2508 int length_ = text.length; |
2509 if (length_ is 0) return offset; | |
29 | 2510 if (nSegments is 2) { |
30 | 2511 if (segments[0] is 0 && segments[1] is length_) return offset; |
29 | 2512 } |
2513 for (int i = 0; i < nSegments && offset - i >= segments[i]; i++) { | |
2514 offset++; | |
2515 } | |
2516 return offset; | |
2517 } | |
2518 | |
2519 int untranslateOffset(int offset) { | |
2520 if (segments is null) return offset; | |
2521 int nSegments = segments.length; | |
2522 if (nSegments <= 1) return offset; | |
30 | 2523 int length_ = text.length; |
2524 if (length_ is 0) return offset; | |
29 | 2525 if (nSegments is 2) { |
30 | 2526 if (segments[0] is 0 && segments[1] is length_) return offset; |
29 | 2527 } |
2528 for (int i = 0; i < nSegments && offset > segments[i]; i++) { | |
2529 offset--; | |
2530 } | |
2531 return offset; | |
2532 } | |
2533 } |