1 /*******************************************************************************
2 * Copyright (c) 2000, 2020 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.graphics;
15
16 import java.util.*;
17
18 import org.eclipse.swt.*;
19 import org.eclipse.swt.internal.*;
20 import org.eclipse.swt.internal.gdip.*;
21 import org.eclipse.swt.internal.ole.win32.*;
22 import org.eclipse.swt.internal.win32.*;
23
24 /**
25 * <code>TextLayout</code> is a graphic object that represents
26 * styled text.
27 * <p>
28 * Instances of this class provide support for drawing, cursor
29 * navigation, hit testing, text wrapping, alignment, tab expansion
30 * line breaking, etc. These are aspects required for rendering internationalized text.
31 * </p><p>
32 * Application code must explicitly invoke the <code>TextLayout#dispose()</code>
33 * method to release the operating system resources managed by each instance
34 * when those instances are no longer required.
35 * </p>
36 *
37 * @see <a href="http://www.eclipse.org/swt/snippets/#textlayout">TextLayout, TextStyle snippets</a>
38 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample, StyledText tab</a>
39 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
40 *
41 * @since 3.0
42 */
43 public final class TextLayout extends Resource {
44 Font font;
45 String text, segmentsText;
46 int lineSpacingInPoints;
47 int ascentInPixels, descentInPixels;
48 int alignment;
49 int wrapWidth;
50 int orientation;
51 int textDirection;
52 int indent;
53 int wrapIndent;
54 boolean justify;
55 int[] tabs;
56 int[] segments;
57 char[] segmentsChars;
58 StyleItem[] styles;
59 int stylesCount;
60
61 StyleItem[] allRuns;
62 StyleItem[][] runs;
63 int[] lineOffset, lineY, lineWidth;
64 IMLangFontLink2 mLangFontLink2;
65 int verticalIndentInPoints;
66
67 static final char LTR_MARK = '\u200E', RTL_MARK = '\u200F';
68 static final int SCRIPT_VISATTR_SIZEOF = 2;
69 static final int GOFFSET_SIZEOF = 8;
70 static final int MERGE_MAX = 512;
71 static final int TOO_MANY_RUNS = 1024;
72
73 /* IME has a copy of these constants */
74 static final int UNDERLINE_IME_DOT = 1 << 16;
75 static final int UNDERLINE_IME_DASH = 2 << 16;
76 static final int UNDERLINE_IME_THICK = 3 << 16;
77
78 class StyleItem {
79 TextStyle style;
80 int start, length;
81 boolean lineBreak, softBreak, tab;
82
83 /*Script cache and analysis */
84 SCRIPT_ANALYSIS analysis;
85 long psc = 0;
86
87 /*Shape info (malloc when the run is shaped) */
88 long glyphs;
89 int glyphCount;
90 long clusters;
91 long visAttrs;
92
93 /*Place info (malloc when the run is placed) */
94 long advances;
95 long goffsets;
96 int width;
97 int ascentInPoints;
98 int descentInPoints;
99 int leadingInPoints;
100 int x;
101 int underlinePos, underlineThickness;
102 int strikeoutPos, strikeoutThickness;
103
104 /* Justify info (malloc during computeRuns) */
105 long justify;
106
107 /* ScriptBreak */
108 long psla;
109
110 long fallbackFont;
111
free()112 void free() {
113 long hHeap = OS.GetProcessHeap();
114 if (psc != 0) {
115 OS.ScriptFreeCache (psc);
116 OS.HeapFree(hHeap, 0, psc);
117 psc = 0;
118 }
119 if (glyphs != 0) {
120 OS.HeapFree(hHeap, 0, glyphs);
121 glyphs = 0;
122 glyphCount = 0;
123 }
124 if (clusters != 0) {
125 OS.HeapFree(hHeap, 0, clusters);
126 clusters = 0;
127 }
128 if (visAttrs != 0) {
129 OS.HeapFree(hHeap, 0, visAttrs);
130 visAttrs = 0;
131 }
132 if (advances != 0) {
133 OS.HeapFree(hHeap, 0, advances);
134 advances = 0;
135 }
136 if (goffsets != 0) {
137 OS.HeapFree(hHeap, 0, goffsets);
138 goffsets = 0;
139 }
140 if (justify != 0) {
141 OS.HeapFree(hHeap, 0, justify);
142 justify = 0;
143 }
144 if (psla != 0) {
145 OS.HeapFree(hHeap, 0, psla);
146 psla = 0;
147 }
148 if (fallbackFont != 0) {
149 OS.DeleteObject(fallbackFont);
150 fallbackFont = 0;
151 }
152 width = ascentInPoints = descentInPoints = x = 0;
153 lineBreak = softBreak = false;
154 }
155 @Override
toString()156 public String toString () {
157 return "StyleItem {" + start + ", " + style + "}";
158 }
159 }
160
161 /**
162 * Constructs a new instance of this class on the given device.
163 * <p>
164 * You must dispose the text layout when it is no longer required.
165 * </p>
166 *
167 * @param device the device on which to allocate the text layout
168 *
169 * @exception IllegalArgumentException <ul>
170 * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
171 * </ul>
172 *
173 * @see #dispose()
174 */
TextLayout(Device device)175 public TextLayout (Device device) {
176 super(device);
177 wrapWidth = ascentInPixels = descentInPixels = -1;
178 lineSpacingInPoints = 0;
179 verticalIndentInPoints = 0;
180 orientation = SWT.LEFT_TO_RIGHT;
181 textDirection = SWT.LEFT_TO_RIGHT;
182 styles = new StyleItem[2];
183 styles[0] = new StyleItem();
184 styles[1] = new StyleItem();
185 stylesCount = 2;
186 text = ""; //$NON-NLS-1$
187 long[] ppv = new long[1];
188 OS.OleInitialize(0);
189 if (COM.CoCreateInstance(COM.CLSID_CMultiLanguage, 0, COM.CLSCTX_INPROC_SERVER, COM.IID_IMLangFontLink2, ppv) == OS.S_OK) {
190 mLangFontLink2 = new IMLangFontLink2(ppv[0]);
191 }
192 init();
193 }
194
addClipRect(StyleItem run, RECT clipRect, RECT rect, int selectionStart, int selectionEnd)195 RECT addClipRect(StyleItem run, RECT clipRect, RECT rect, int selectionStart, int selectionEnd) {
196 if (rect != null) {
197 if (clipRect == null) {
198 clipRect = new RECT ();
199 OS.SetRect(clipRect, -1, rect.top, -1, rect.bottom);
200 }
201 boolean isRTL = (orientation & SWT.RIGHT_TO_LEFT) != 0;
202 if (run.start <= selectionStart && selectionStart <= run.start + run.length) {
203 if (run.analysis.fRTL ^ isRTL) {
204 clipRect.right = rect.left;
205 } else {
206 clipRect.left = rect.left;
207 }
208 }
209 if (run.start <= selectionEnd && selectionEnd <= run.start + run.length) {
210 if (run.analysis.fRTL ^ isRTL) {
211 clipRect.left = rect.right;
212 } else {
213 clipRect.right = rect.right;
214 }
215 }
216 }
217 return clipRect;
218 }
219
breakRun(StyleItem run)220 void breakRun(StyleItem run) {
221 if (run.psla != 0) return;
222 char[] chars = new char[run.length];
223 segmentsText.getChars(run.start, run.start + run.length, chars, 0);
224 long hHeap = OS.GetProcessHeap();
225 run.psla = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, SCRIPT_LOGATTR.sizeof * chars.length);
226 if (run.psla == 0) SWT.error(SWT.ERROR_NO_HANDLES);
227 OS.ScriptBreak(chars, chars.length, run.analysis, run.psla);
228 }
229
checkLayout()230 void checkLayout () {
231 if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
232 }
233
234 /*
235 * Compute the runs: itemize, shape, place, and reorder the runs.
236 * Break paragraphs into lines, wraps the text, and initialize caches.
237 */
computeRuns(GC gc)238 void computeRuns (GC gc) {
239 if (runs != null) return;
240 long hDC = gc != null ? gc.handle : device.internal_new_GC(null);
241 long srcHdc = OS.CreateCompatibleDC(hDC);
242 allRuns = itemize();
243 for (int i=0; i<allRuns.length - 1; i++) {
244 StyleItem run = allRuns[i];
245 OS.SelectObject(srcHdc, getItemFont(run));
246 shape(srcHdc, run);
247 }
248 SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR();
249 SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES();
250 int lineWidth = indent, lineStart = 0, lineCount = 1;
251 for (int i=0; i<allRuns.length - 1; i++) {
252 StyleItem run = allRuns[i];
253 if (tabs != null && run.tab) {
254 int tabsLength = tabs.length, j;
255 for (j = 0; j < tabsLength; j++) {
256 if (tabs[j] > lineWidth) {
257 run.width = tabs[j] - lineWidth;
258 break;
259 }
260 }
261 if (j == tabsLength) {
262 int tabX = tabs[tabsLength-1];
263 int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0];
264 if (lastTabWidth > 0) {
265 while (tabX <= lineWidth) tabX += lastTabWidth;
266 run.width = tabX - lineWidth;
267 }
268 }
269 int length = run.length;
270 if (length > 1) {
271 int stop = j + length - 1;
272 if (stop < tabsLength) {
273 run.width += tabs[stop] - tabs[j];
274 } else {
275 if (j < tabsLength) {
276 run.width += tabs[tabsLength - 1] - tabs[j];
277 length -= (tabsLength - 1) - j;
278 }
279 int lastTabWidth = tabsLength > 1 ? tabs[tabsLength-1] - tabs[tabsLength-2] : tabs[0];
280 run.width += lastTabWidth * (length - 1);
281 }
282 }
283 }
284 if (wrapWidth != -1 && lineWidth + run.width > wrapWidth && !run.tab && !run.lineBreak) {
285 int start = 0;
286 int[] piDx = new int[run.length];
287 if (run.style != null && run.style.metrics != null) {
288 piDx[0] = run.width;
289 } else {
290 OS.ScriptGetLogicalWidths(run.analysis, run.length, run.glyphCount, run.advances, run.clusters, run.visAttrs, piDx);
291 }
292 int width = 0, maxWidth = wrapWidth - lineWidth;
293 while (width + piDx[start] < maxWidth) {
294 width += piDx[start++];
295 }
296 int firstStart = start;
297 int firstIndice = i;
298 while (i >= lineStart) {
299 breakRun(run);
300 while (start >= 0) {
301 OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
302 if (logAttr.fSoftBreak || logAttr.fWhiteSpace) break;
303 start--;
304 }
305
306 /*
307 * Bug in Windows. For some reason Uniscribe sets the fSoftBreak flag for the first letter
308 * after a letter with an accent. This cause a break line to be set in the middle of a word.
309 * The fix is to detect the case and ignore fSoftBreak forcing the algorithm keep searching.
310 */
311 if (start == 0 && i != lineStart && !run.tab) {
312 if (logAttr.fSoftBreak && !logAttr.fWhiteSpace) {
313 OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
314 int langID = properties.langid;
315 StyleItem pRun = allRuns[i - 1];
316 OS.MoveMemory(properties, device.scripts[pRun.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
317 if (properties.langid == langID || langID == OS.LANG_NEUTRAL || properties.langid == OS.LANG_NEUTRAL) {
318 breakRun(pRun);
319 OS.MoveMemory(logAttr, pRun.psla + ((pRun.length - 1) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
320 if (!logAttr.fWhiteSpace) start = -1;
321 }
322 }
323 }
324 if (start >= 0 || i == lineStart) break;
325 run = allRuns[--i];
326 start = run.length - 1;
327 }
328 boolean wrapEntireRun = start == 0 && i != lineStart && !run.tab;
329 if (wrapEntireRun) {
330 breakRun(run);
331 OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
332 wrapEntireRun = !logAttr.fWhiteSpace;
333 }
334 if (wrapEntireRun) {
335 run = allRuns[--i];
336 start = run.length;
337 } else if (start <= 0 && i == lineStart) {
338 /*
339 * No soft-break or line-feed found. Avoid breaking a run at
340 * the first character (firstStart == 0) unless it's the
341 * only run available (firstIndice == lineStart). See bug 408833.
342 */
343 if (firstStart == 0 && firstIndice > lineStart) {
344 i = firstIndice - 1;
345 run = allRuns[i];
346 start = run.length;
347 } else {
348 i = firstIndice;
349 run = allRuns[i];
350 start = Math.max(1, firstStart);
351 }
352 }
353 breakRun(run);
354 while (start < run.length) {
355 OS.MoveMemory(logAttr, run.psla + (start * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
356 if (!logAttr.fWhiteSpace) break;
357 start++;
358 }
359 if (0 < start && start < run.length) {
360 StyleItem newRun = new StyleItem();
361 newRun.start = run.start + start;
362 newRun.length = run.length - start;
363 newRun.style = run.style;
364 newRun.analysis = cloneScriptAnalysis(run.analysis);
365 run.free();
366 run.length = start;
367 OS.SelectObject(srcHdc, getItemFont(run));
368 run.analysis.fNoGlyphIndex = false;
369 shape (srcHdc, run);
370 OS.SelectObject(srcHdc, getItemFont(newRun));
371 newRun.analysis.fNoGlyphIndex = false;
372 shape (srcHdc, newRun);
373 StyleItem[] newAllRuns = new StyleItem[allRuns.length + 1];
374 System.arraycopy(allRuns, 0, newAllRuns, 0, i + 1);
375 System.arraycopy(allRuns, i + 1, newAllRuns, i + 2, allRuns.length - i - 1);
376 allRuns = newAllRuns;
377 allRuns[i + 1] = newRun;
378 }
379 if (i != allRuns.length - 2) {
380 run.softBreak = run.lineBreak = true;
381 }
382 }
383 lineWidth += run.width;
384 if (run.lineBreak) {
385 lineStart = i + 1;
386 lineWidth = run.softBreak ? wrapIndent : indent;
387 lineCount++;
388 }
389 }
390 lineWidth = 0;
391 runs = new StyleItem[lineCount][];
392 lineOffset = new int[lineCount + 1];
393 lineY = new int[lineCount + 1];
394 this.lineWidth = new int[lineCount];
395 int lineRunCount = 0, line = 0;
396 int ascentInPoints = Math.max(0, DPIUtil.autoScaleDown(getDevice(), this.ascentInPixels));
397 int descentInPoints = Math.max(0, DPIUtil.autoScaleDown(getDevice(), this.descentInPixels));
398 StyleItem[] lineRuns = new StyleItem[allRuns.length];
399 for (int i=0; i<allRuns.length; i++) {
400 StyleItem run = allRuns[i];
401 lineRuns[lineRunCount++] = run;
402 lineWidth += run.width;
403 ascentInPoints = Math.max(ascentInPoints, run.ascentInPoints);
404 descentInPoints = Math.max(descentInPoints, run.descentInPoints);
405 if (run.lineBreak || i == allRuns.length - 1) {
406 /* Update the run metrics if the last run is a hard break. */
407 if (lineRunCount == 1 && (i == allRuns.length - 1 || !run.softBreak)) {
408 TEXTMETRIC lptm = new TEXTMETRIC();
409 OS.SelectObject(srcHdc, getItemFont(run));
410 OS.GetTextMetrics(srcHdc, lptm);
411 run.ascentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmAscent);
412 run.descentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmDescent);
413 ascentInPoints = Math.max(ascentInPoints, run.ascentInPoints);
414 descentInPoints = Math.max(descentInPoints, run.descentInPoints);
415 }
416 runs[line] = new StyleItem[lineRunCount];
417 System.arraycopy(lineRuns, 0, runs[line], 0, lineRunCount);
418
419 if (justify && wrapWidth != -1 && run.softBreak && lineWidth > 0) {
420 int lineIndent = wrapIndent;
421 if (line == 0) {
422 lineIndent = indent;
423 } else {
424 StyleItem[] previousLine = runs[line - 1];
425 StyleItem previousRun = previousLine[previousLine.length - 1];
426 if (previousRun.lineBreak && !previousRun.softBreak) {
427 lineIndent = indent;
428 }
429 }
430 lineWidth += lineIndent;
431 long hHeap = OS.GetProcessHeap();
432 int newLineWidth = 0;
433 for (int j = 0; j < runs[line].length; j++) {
434 StyleItem item = runs[line][j];
435 int iDx = item.width * wrapWidth / lineWidth;
436 if (iDx != item.width) {
437 item.justify = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, item.glyphCount * 4);
438 if (item.justify == 0) SWT.error(SWT.ERROR_NO_HANDLES);
439 OS.ScriptJustify(item.visAttrs, item.advances, item.glyphCount, iDx - item.width, 2, item.justify);
440 item.width = iDx;
441 }
442 newLineWidth += item.width;
443 }
444 lineWidth = newLineWidth;
445 }
446 this.lineWidth[line] = lineWidth;
447
448 StyleItem lastRun = runs[line][lineRunCount - 1];
449 int lastOffset = lastRun.start + lastRun.length;
450 runs[line] = reorder(runs[line], i == allRuns.length - 1);
451 lastRun = runs[line][lineRunCount - 1];
452 if (run.softBreak && run != lastRun) {
453 run.softBreak = run.lineBreak = false;
454 lastRun.softBreak = lastRun.lineBreak = true;
455 }
456
457 lineWidth = getLineIndent(line);
458 for (int j = 0; j < runs[line].length; j++) {
459 runs[line][j].x = lineWidth;
460 lineWidth += runs[line][j].width;
461 }
462 line++;
463 lineY[line] = lineY[line - 1] + ascentInPoints + descentInPoints + lineSpacingInPoints;
464 lineOffset[line] = lastOffset;
465 lineRunCount = lineWidth = 0;
466 ascentInPoints = Math.max(0, DPIUtil.autoScaleDown(getDevice(), this.ascentInPixels));
467 descentInPoints = Math.max(0, DPIUtil.autoScaleDown(getDevice(), this.descentInPixels));
468 }
469 }
470 if (srcHdc != 0) OS.DeleteDC(srcHdc);
471 if (gc == null) device.internal_dispose_GC(hDC, null);
472 }
473
474 @Override
destroy()475 void destroy () {
476 freeRuns();
477 font = null;
478 text = null;
479 segmentsText = null;
480 tabs = null;
481 styles = null;
482 runs = null;
483 lineOffset = null;
484 lineY = null;
485 lineWidth = null;
486 segments = null;
487 segmentsChars = null;
488 if (mLangFontLink2 != null) {
489 mLangFontLink2.Release();
490 mLangFontLink2 = null;
491 }
492 OS.OleUninitialize();
493 }
494
cloneScriptAnalysis(SCRIPT_ANALYSIS src)495 SCRIPT_ANALYSIS cloneScriptAnalysis (SCRIPT_ANALYSIS src) {
496 SCRIPT_ANALYSIS dst = new SCRIPT_ANALYSIS();
497 dst.eScript = src.eScript;
498 dst.fRTL = src.fRTL;
499 dst.fLayoutRTL = src.fLayoutRTL;
500 dst.fLinkBefore = src.fLinkBefore;
501 dst.fLinkAfter = src.fLinkAfter;
502 dst.fLogicalOrder = src.fLogicalOrder;
503 dst.fNoGlyphIndex = src.fNoGlyphIndex;
504 dst.s = new SCRIPT_STATE();
505 dst.s.uBidiLevel = src.s.uBidiLevel;
506 dst.s.fOverrideDirection = src.s.fOverrideDirection;
507 dst.s.fInhibitSymSwap = src.s.fInhibitSymSwap;
508 dst.s.fCharShape = src.s.fCharShape;
509 dst.s.fDigitSubstitute = src.s.fDigitSubstitute;
510 dst.s.fInhibitLigate = src.s.fInhibitLigate;
511 dst.s.fDisplayZWG = src.s.fDisplayZWG;
512 dst.s.fArabicNumContext = src.s.fArabicNumContext;
513 dst.s.fGcpClusters = src.s.fGcpClusters;
514 dst.s.fReserved = src.s.fReserved;
515 dst.s.fEngineReserved = src.s.fEngineReserved;
516 return dst;
517 }
518
computePolyline(int left, int top, int right, int bottom)519 int[] computePolyline(int left, int top, int right, int bottom) {
520 int height = bottom - top; // can be any number
521 int width = 2 * height; // must be even
522 int peaks = Compatibility.ceil(right - left, width);
523 if (peaks == 0 && right - left > 2) {
524 peaks = 1;
525 }
526 int length = ((2 * peaks) + 1) * 2;
527 if (length < 0) return new int[0];
528
529 int[] coordinates = new int[length];
530 for (int i = 0; i < peaks; i++) {
531 int index = 4 * i;
532 coordinates[index] = left + (width * i);
533 coordinates[index+1] = bottom;
534 coordinates[index+2] = coordinates[index] + width / 2;
535 coordinates[index+3] = top;
536 }
537 coordinates[length-2] = left + (width * peaks);
538 coordinates[length-1] = bottom;
539 return coordinates;
540 }
541
createGdipBrush(int pixel, int alpha)542 long createGdipBrush(int pixel, int alpha) {
543 int argb = ((alpha & 0xFF) << 24) | ((pixel >> 16) & 0xFF) | (pixel & 0xFF00) | ((pixel & 0xFF) << 16);
544 return Gdip.SolidBrush_new(argb);
545 }
546
createGdipBrush(Color color, int alpha)547 long createGdipBrush(Color color, int alpha) {
548 return createGdipBrush(color.handle, alpha);
549 }
550
551 /**
552 * Draws the receiver's text using the specified GC at the specified
553 * point.
554 *
555 * @param gc the GC to draw
556 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
557 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
558 *
559 * @exception SWTException <ul>
560 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
561 * </ul>
562 * @exception IllegalArgumentException <ul>
563 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
564 * </ul>
565 */
draw(GC gc, int x, int y)566 public void draw (GC gc, int x, int y) {
567 checkLayout();
568 drawInPixels(gc, DPIUtil.autoScaleUp(getDevice(), x), DPIUtil.autoScaleUp(getDevice(), y));
569 }
570
drawInPixels(GC gc, int x, int y)571 void drawInPixels (GC gc, int x, int y) {
572 drawInPixels(gc, x, y, -1, -1, null, null);
573 }
574
575 /**
576 * Draws the receiver's text using the specified GC at the specified
577 * point.
578 *
579 * @param gc the GC to draw
580 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
581 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
582 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
583 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
584 * @param selectionForeground selection foreground, or NULL to use the system default color
585 * @param selectionBackground selection background, or NULL to use the system default color
586 *
587 * @exception SWTException <ul>
588 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
589 * </ul>
590 * @exception IllegalArgumentException <ul>
591 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
592 * </ul>
593 */
draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground)594 public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
595 checkLayout();
596 drawInPixels(gc, DPIUtil.autoScaleUp(getDevice(), x), DPIUtil.autoScaleUp(getDevice(), y), selectionStart, selectionEnd, selectionForeground, selectionBackground);
597 }
598
drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground)599 void drawInPixels (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
600 drawInPixels(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0);
601 }
602
603 /**
604 * Draws the receiver's text using the specified GC at the specified
605 * point.
606 * <p>
607 * The parameter <code>flags</code> can include one of <code>SWT.DELIMITER_SELECTION</code>
608 * or <code>SWT.FULL_SELECTION</code> to specify the selection behavior on all lines except
609 * for the last line, and can also include <code>SWT.LAST_LINE_SELECTION</code> to extend
610 * the specified selection behavior to the last line.
611 * </p>
612 * @param gc the GC to draw
613 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
614 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
615 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
616 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
617 * @param selectionForeground selection foreground, or NULL to use the system default color
618 * @param selectionBackground selection background, or NULL to use the system default color
619 * @param flags drawing options
620 *
621 * @exception SWTException <ul>
622 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
623 * </ul>
624 * @exception IllegalArgumentException <ul>
625 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
626 * </ul>
627 *
628 * @since 3.3
629 */
draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags)630 public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
631 checkLayout();
632 drawInPixels(gc, DPIUtil.autoScaleUp(getDevice(), x), DPIUtil.autoScaleUp(getDevice(), y), selectionStart, selectionEnd, selectionForeground, selectionBackground, flags);
633 }
634
drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags)635 void drawInPixels (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
636 computeRuns(gc);
637 if (gc == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
638 if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
639 if (selectionForeground != null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
640 if (selectionBackground != null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
641 int length = text.length();
642 if (length == 0 && flags == 0) return;
643 y += getScaledVerticalIndent();
644 long hdc = gc.handle;
645 Rectangle clip = gc.getClippingInPixels();
646 GCData data = gc.data;
647 long gdipGraphics = data.gdipGraphics;
648 int foreground = data.foreground;
649 int linkColor = OS.GetSysColor (OS.COLOR_HOTLIGHT);
650 int alpha = data.alpha;
651 boolean gdip = gdipGraphics != 0;
652 long gdipForeground = 0;
653 long gdipLinkColor = 0;
654 int state = 0;
655 if (gdip) {
656 gc.checkGC(GC.FOREGROUND);
657 gdipForeground = gc.getFgBrush();
658 } else {
659 state = OS.SaveDC(hdc);
660 if ((data.style & SWT.MIRRORED) != 0) {
661 OS.SetLayout(hdc, OS.GetLayout(hdc) | OS.LAYOUT_RTL);
662 }
663 }
664 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
665 long gdipSelBackground = 0, gdipSelForeground = 0, gdipFont = 0, lastHFont = 0;
666 long selBackground = 0;
667 int selForeground = 0;
668 if (hasSelection || ((flags & SWT.LAST_LINE_SELECTION) != 0 && (flags & (SWT.FULL_SELECTION | SWT.DELIMITER_SELECTION)) != 0)) {
669 int fgSel = selectionForeground != null ? selectionForeground.handle : OS.GetSysColor (OS.COLOR_HIGHLIGHTTEXT);
670 int bgSel = selectionBackground != null ? selectionBackground.handle : OS.GetSysColor (OS.COLOR_HIGHLIGHT);
671 if (gdip) {
672 gdipSelBackground = createGdipBrush(bgSel, alpha);
673 gdipSelForeground = createGdipBrush(fgSel, alpha);
674 } else {
675 selBackground = OS.CreateSolidBrush(bgSel);
676 selForeground = fgSel;
677 }
678 if (hasSelection) {
679 selectionStart = translateOffset(Math.min(Math.max(0, selectionStart), length - 1));
680 selectionEnd = translateOffset(Math.min(Math.max(0, selectionEnd), length - 1));
681 }
682 }
683 RECT rect = new RECT();
684 OS.SetBkMode(hdc, OS.TRANSPARENT);
685 for (int line=0; line<runs.length; line++) {
686 int drawX = x + getLineIndent(line);
687 int drawY = y + DPIUtil.autoScaleUp(getDevice(), lineY[line]);
688 StyleItem[] lineRuns = runs[line];
689 int lineHeight = DPIUtil.autoScaleUp(getDevice(), lineY[line+1] - lineY[line] - lineSpacingInPoints);
690
691 //Draw last line selection
692 if ((flags & (SWT.FULL_SELECTION | SWT.DELIMITER_SELECTION)) != 0 && (hasSelection || (flags & SWT.LAST_LINE_SELECTION) != 0)) {
693 boolean extents = false;
694 if (line == runs.length - 1 && (flags & SWT.LAST_LINE_SELECTION) != 0) {
695 extents = true;
696 } else {
697 StyleItem run = lineRuns[lineRuns.length - 1];
698 if (run.lineBreak && !run.softBreak) {
699 if (selectionStart <= run.start && run.start <= selectionEnd) extents = true;
700 } else {
701 int endOffset = run.start + run.length - 1;
702 if (selectionStart <= endOffset && endOffset < selectionEnd && (flags & SWT.FULL_SELECTION) != 0) {
703 extents = true;
704 }
705 }
706 }
707 if (extents) {
708 int width;
709 if ((flags & SWT.FULL_SELECTION) != 0) {
710 width = 0x6FFFFFF;
711 } else {
712 width = lineHeight / 3;
713 }
714 if (gdip) {
715 Gdip.Graphics_FillRectangle(gdipGraphics, gdipSelBackground, drawX + lineWidth[line], drawY, width, lineHeight);
716 } else {
717 OS.SelectObject(hdc, selBackground);
718 OS.PatBlt(hdc, drawX + lineWidth[line], drawY, width, lineHeight, OS.PATCOPY);
719 }
720 }
721 }
722 if (drawX > clip.x + clip.width) continue;
723 if (drawX + lineWidth[line] < clip.x) continue;
724
725 //Draw the background of the runs in the line
726 int alignmentX = drawX;
727 for (StyleItem run : lineRuns) {
728 if (run.length == 0) continue;
729 if (drawX > clip.x + clip.width) break;
730 if (drawX + run.width >= clip.x) {
731 if (!run.lineBreak || run.softBreak) {
732 OS.SetRect(rect, drawX, drawY, drawX + run.width, drawY + lineHeight);
733 if (gdip) {
734 drawRunBackgroundGDIP(run, gdipGraphics, rect, selectionStart, selectionEnd, alpha, gdipSelBackground, hasSelection);
735 } else {
736 drawRunBackground(run, hdc, rect, selectionStart, selectionEnd, selBackground, hasSelection);
737 }
738 }
739 }
740 drawX += run.width;
741 }
742
743 //Draw the text, underline, strikeout, and border of the runs in the line
744 int baselineInPixels = Math.max(0, this.ascentInPixels);
745 int lineUnderlinePos = 0;
746 for (StyleItem run : lineRuns) {
747 baselineInPixels = Math.max(baselineInPixels, DPIUtil.autoScaleUp(getDevice(), run.ascentInPoints));
748 lineUnderlinePos = Math.min(lineUnderlinePos, run.underlinePos);
749 }
750 RECT borderClip = null, underlineClip = null, strikeoutClip = null, pRect = null;
751 drawX = alignmentX;
752 for (int i = 0; i < lineRuns.length; i++) {
753 StyleItem run = lineRuns[i];
754 TextStyle style = run.style;
755 boolean hasAdorners = style != null && (style.underline || style.strikeout || style.borderStyle != SWT.NONE);
756 if (run.length == 0) continue;
757 if (drawX > clip.x + clip.width) break;
758 if (drawX + run.width >= clip.x) {
759 boolean skipTab = run.tab && !hasAdorners;
760 if (!skipTab && (!run.lineBreak || run.softBreak) && !(style != null && style.metrics != null)) {
761 OS.SetRect(rect, drawX, drawY, drawX + run.width, drawY + lineHeight);
762 if (gdip) {
763 long hFont = getItemFont(run);
764 if (hFont != lastHFont) {
765 lastHFont = hFont;
766 if (gdipFont != 0) Gdip.Font_delete(gdipFont);
767 long oldFont = OS.SelectObject(hdc, hFont);
768 gdipFont = Gdip.Font_new(hdc, hFont);
769 OS.SelectObject(hdc, oldFont);
770 if (gdipFont == 0) SWT.error(SWT.ERROR_NO_HANDLES);
771 if (!Gdip.Font_IsAvailable(gdipFont)) {
772 Gdip.Font_delete(gdipFont);
773 gdipFont = 0;
774 }
775 }
776 long gdipFg = gdipForeground;
777 if (style != null && style.underline && style.underlineStyle == SWT.UNDERLINE_LINK) {
778 if (gdipLinkColor == 0) gdipLinkColor = createGdipBrush(linkColor, alpha);
779 gdipFg = gdipLinkColor;
780 }
781 if (gdipFont != 0 && !run.analysis.fNoGlyphIndex) {
782 pRect = drawRunTextGDIP(gdipGraphics, run, rect, gdipFont, baselineInPixels, gdipFg, gdipSelForeground, selectionStart, selectionEnd, alpha);
783 } else {
784 int fg = style != null && style.underline && style.underlineStyle == SWT.UNDERLINE_LINK ? linkColor : foreground;
785 pRect = drawRunTextGDIPRaster(gdipGraphics, run, rect, baselineInPixels, fg, selForeground, selectionStart, selectionEnd);
786 }
787 underlineClip = drawUnderlineGDIP(gdipGraphics, x, drawY + baselineInPixels, lineUnderlinePos, drawY + lineHeight, lineRuns, i, gdipFg, gdipSelForeground, underlineClip, pRect, selectionStart, selectionEnd, alpha, clip);
788 strikeoutClip = drawStrikeoutGDIP(gdipGraphics, x, drawY + baselineInPixels, lineRuns, i, gdipFg, gdipSelForeground, strikeoutClip, pRect, selectionStart, selectionEnd, alpha, clip);
789 borderClip = drawBorderGDIP(gdipGraphics, x, drawY, lineHeight, lineRuns, i, gdipFg, gdipSelForeground, borderClip, pRect, selectionStart, selectionEnd, alpha, clip);
790 } else {
791 int fg = style != null && style.underline && style.underlineStyle == SWT.UNDERLINE_LINK ? linkColor : foreground;
792 pRect = drawRunText(hdc, run, rect, baselineInPixels, fg, selForeground, selectionStart, selectionEnd);
793 underlineClip = drawUnderline(hdc, x, drawY + baselineInPixels, lineUnderlinePos, drawY + lineHeight, lineRuns, i, fg, selForeground, underlineClip, pRect, selectionStart, selectionEnd, clip);
794 strikeoutClip = drawStrikeout(hdc, x, drawY + baselineInPixels, lineRuns, i, fg, selForeground, strikeoutClip, pRect, selectionStart, selectionEnd, clip);
795 borderClip = drawBorder(hdc, x, drawY, lineHeight, lineRuns, i, fg, selForeground, borderClip, pRect, selectionStart, selectionEnd, clip);
796 }
797 }
798 }
799 drawX += run.width;
800 }
801 }
802 if (gdipSelBackground != 0) Gdip.SolidBrush_delete(gdipSelBackground);
803 if (gdipSelForeground != 0) Gdip.SolidBrush_delete(gdipSelForeground);
804 if (gdipLinkColor != 0) Gdip.SolidBrush_delete(gdipLinkColor);
805 if (gdipFont != 0) Gdip.Font_delete(gdipFont);
806 if (state != 0) OS.RestoreDC(hdc, state);
807 if (selBackground != 0) OS.DeleteObject (selBackground);
808 }
809
drawBorder(long hdc, int x, int y, int lineHeight, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip)810 RECT drawBorder(long hdc, int x, int y, int lineHeight, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip) {
811 StyleItem run = line[index];
812 TextStyle style = run.style;
813 if (style == null) return null;
814 if (style.borderStyle == SWT.NONE) return null;
815 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
816 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
817 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentBorder(line[index + 1].style)) {
818 int left = run.x;
819 int start = run.start;
820 int end = run.start + run.length - 1;
821 for (int i = index; i > 0 && style.isAdherentBorder(line[i - 1].style); i--) {
822 left = line[i - 1].x;
823 start = Math.min(start, line[i - 1].start);
824 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
825 }
826 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
827 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
828 if (style.borderColor != null) {
829 color = style.borderColor.handle;
830 clipRect = null;
831 } else {
832 if (fullSelection) {
833 color = selectionColor;
834 clipRect = null;
835 } else {
836 if (style.foreground != null) {
837 color = style.foreground.handle;
838 }
839 }
840 }
841 int lineWidth = 1;
842 int pattern = 1;
843 int lineStyle = OS.PS_SOLID;
844 switch (style.borderStyle) {
845 case SWT.BORDER_SOLID: break;
846 case SWT.BORDER_DASH: {
847 lineStyle = OS.PS_DASH;
848 pattern = 4;
849 break;
850 }
851 case SWT.BORDER_DOT: {
852 lineStyle = OS.PS_DOT;
853 pattern = 2;
854 break;
855 }
856 }
857 long oldBrush = OS.SelectObject(hdc, OS.GetStockObject(OS.NULL_BRUSH));
858 LOGBRUSH logBrush = new LOGBRUSH();
859 logBrush.lbStyle = OS.BS_SOLID;
860 logBrush.lbColor = color;
861 long newPen = OS.ExtCreatePen(lineStyle | OS.PS_GEOMETRIC, lineWidth, logBrush, 0, null);
862 long oldPen = OS.SelectObject(hdc, newPen);
863 RECT drawRect = new RECT();
864 OS.SetRect(drawRect, x + left, y, x + run.x + run.width, y + lineHeight);
865 if (drawClip != null) {
866 if (drawRect.left < drawClip.x) {
867 int remainder = drawRect.left % pattern;
868 drawRect.left = drawClip.x / pattern * pattern + remainder - pattern;
869 }
870 if (drawRect.right > drawClip.x + drawClip.width) {
871 int remainder = drawRect.right % pattern;
872 drawRect.right = (drawClip.x + drawClip.width) / pattern * pattern + remainder + pattern;
873 }
874 }
875 OS.Rectangle(hdc, drawRect.left,drawRect.top, drawRect.right, drawRect.bottom);
876 OS.SelectObject(hdc, oldPen);
877 OS.DeleteObject(newPen);
878 if (clipRect != null) {
879 int state = OS.SaveDC(hdc);
880 if (clipRect.left == -1) clipRect.left = 0;
881 if (clipRect.right == -1) clipRect.right = 0x7ffff;
882 OS.IntersectClipRect(hdc, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
883 logBrush.lbColor = selectionColor;
884 long selPen = OS.ExtCreatePen (lineStyle | OS.PS_GEOMETRIC, lineWidth, logBrush, 0, null);
885 oldPen = OS.SelectObject(hdc, selPen);
886 OS.Rectangle(hdc, drawRect.left, drawRect.top, drawRect.right, drawRect.bottom);
887 OS.RestoreDC(hdc, state);
888 OS.SelectObject(hdc, oldPen);
889 OS.DeleteObject(selPen);
890 }
891 OS.SelectObject(hdc, oldBrush);
892 return null;
893 }
894 return clipRect;
895 }
896
drawBorderGDIP(long graphics, int x, int y, int lineHeight, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip)897 RECT drawBorderGDIP(long graphics, int x, int y, int lineHeight, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip) {
898 StyleItem run = line[index];
899 TextStyle style = run.style;
900 if (style == null) return null;
901 if (style.borderStyle == SWT.NONE) return null;
902 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
903 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
904 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentBorder(line[index + 1].style)) {
905 int left = run.x;
906 int start = run.start;
907 int end = run.start + run.length - 1;
908 for (int i = index; i > 0 && style.isAdherentBorder(line[i - 1].style); i--) {
909 left = line[i - 1].x;
910 start = Math.min(start, line[i - 1].start);
911 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
912 }
913 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
914 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
915 long brush = color;
916 if (style.borderColor != null) {
917 brush = createGdipBrush(style.borderColor, alpha);
918 clipRect = null;
919 } else {
920 if (fullSelection) {
921 brush = selectionColor;
922 clipRect = null;
923 } else {
924 if (style.foreground != null) {
925 brush = createGdipBrush(style.foreground, alpha);
926 }
927 }
928 }
929 int lineWidth = 1;
930 int lineStyle = Gdip.DashStyleSolid;
931 switch (style.borderStyle) {
932 case SWT.BORDER_SOLID: break;
933 case SWT.BORDER_DASH: lineStyle = Gdip.DashStyleDash; break;
934 case SWT.BORDER_DOT: lineStyle = Gdip.DashStyleDot; break;
935 }
936 long pen = Gdip.Pen_new(brush, lineWidth);
937 Gdip.Pen_SetDashStyle(pen, lineStyle);
938 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeNone);
939 int smoothingMode = Gdip.Graphics_GetSmoothingMode(graphics);
940 Gdip.Graphics_SetSmoothingMode(graphics, Gdip.SmoothingModeNone);
941 if (clipRect != null) {
942 int gstate = Gdip.Graphics_Save(graphics);
943 if (clipRect.left == -1) clipRect.left = 0;
944 if (clipRect.right == -1) clipRect.right = 0x7ffff;
945 Rect gdipRect = new Rect();
946 gdipRect.X = clipRect.left;
947 gdipRect.Y = clipRect.top;
948 gdipRect.Width = clipRect.right - clipRect.left;
949 gdipRect.Height = clipRect.bottom - clipRect.top;
950 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
951 Gdip.Graphics_DrawRectangle(graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
952 Gdip.Graphics_Restore(graphics, gstate);
953 gstate = Gdip.Graphics_Save(graphics);
954 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
955 long selPen = Gdip.Pen_new(selectionColor, lineWidth);
956 Gdip.Pen_SetDashStyle(selPen, lineStyle);
957 Gdip.Graphics_DrawRectangle(graphics, selPen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
958 Gdip.Pen_delete(selPen);
959 Gdip.Graphics_Restore(graphics, gstate);
960 } else {
961 Gdip.Graphics_DrawRectangle(graphics, pen, x + left, y, run.x + run.width - left - 1, lineHeight - 1);
962 }
963 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeHalf);
964 Gdip.Graphics_SetSmoothingMode(graphics, smoothingMode);
965 Gdip.Pen_delete(pen);
966 if (brush != selectionColor && brush != color) Gdip.SolidBrush_delete(brush);
967 return null;
968 }
969 return clipRect;
970 }
971
drawRunBackground(StyleItem run, long hdc, RECT rect, int selectionStart, int selectionEnd, long selBrush, boolean hasSelection)972 void drawRunBackground(StyleItem run, long hdc, RECT rect, int selectionStart, int selectionEnd, long selBrush, boolean hasSelection) {
973 int end = run.start + run.length - 1;
974 boolean fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
975 if (fullSelection) {
976 OS.SelectObject(hdc, selBrush);
977 OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY);
978 } else {
979 if (run.style != null && run.style.background != null) {
980 int bg = run.style.background.handle;
981 long hBrush = OS.CreateSolidBrush (bg);
982 long oldBrush = OS.SelectObject(hdc, hBrush);
983 OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY);
984 OS.SelectObject(hdc, oldBrush);
985 OS.DeleteObject(hBrush);
986 }
987 boolean partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd);
988 if (partialSelection) {
989 getPartialSelection(run, selectionStart, selectionEnd, rect);
990 OS.SelectObject(hdc, selBrush);
991 OS.PatBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, OS.PATCOPY);
992 }
993 }
994 }
995
drawRunBackgroundGDIP(StyleItem run, long graphics, RECT rect, int selectionStart, int selectionEnd, int alpha, long selBrush, boolean hasSelection)996 void drawRunBackgroundGDIP(StyleItem run, long graphics, RECT rect, int selectionStart, int selectionEnd, int alpha, long selBrush, boolean hasSelection) {
997 int end = run.start + run.length - 1;
998 boolean fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
999 if (fullSelection) {
1000 Gdip.Graphics_FillRectangle(graphics, selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
1001 } else {
1002 if (run.style != null && run.style.background != null) {
1003 long brush = createGdipBrush(run.style.background, alpha);
1004 Gdip.Graphics_FillRectangle(graphics, brush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
1005 Gdip.SolidBrush_delete(brush);
1006 }
1007 boolean partialSelection = hasSelection && !(selectionStart > end || run.start > selectionEnd);
1008 if (partialSelection) {
1009 getPartialSelection(run, selectionStart, selectionEnd, rect);
1010 if (rect.left > rect.right) {
1011 int tmp = rect.left;
1012 rect.left = rect.right;
1013 rect.right = tmp;
1014 }
1015 Gdip.Graphics_FillRectangle(graphics, selBrush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
1016 }
1017 }
1018 }
1019
drawRunText(long hdc, StyleItem run, RECT rect, int baselineInPixels, int color, int selectionColor, int selectionStart, int selectionEnd)1020 RECT drawRunText(long hdc, StyleItem run, RECT rect, int baselineInPixels, int color, int selectionColor, int selectionStart, int selectionEnd) {
1021 int end = run.start + run.length - 1;
1022 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1023 boolean fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
1024 boolean partialSelection = hasSelection && !fullSelection && !(selectionStart > end || run.start > selectionEnd);
1025 int offset = (orientation & SWT.RIGHT_TO_LEFT) != 0 ? -1 : 0;
1026 int x = rect.left + offset;
1027 int y = rect.top + (baselineInPixels - DPIUtil.autoScaleUp(getDevice(), run.ascentInPoints));
1028 long hFont = getItemFont(run);
1029 OS.SelectObject(hdc, hFont);
1030 if (fullSelection) {
1031 color = selectionColor;
1032 } else {
1033 if (run.style != null && run.style.foreground != null) {
1034 color = run.style.foreground.handle;
1035 }
1036 }
1037 OS.SetTextColor(hdc, color);
1038 OS.ScriptTextOut(hdc, run.psc, x, y, 0, null, run.analysis , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
1039 if (partialSelection) {
1040 getPartialSelection(run, selectionStart, selectionEnd, rect);
1041 OS.SetTextColor(hdc, selectionColor);
1042 OS.ScriptTextOut(hdc, run.psc, x, y, OS.ETO_CLIPPED, rect, run.analysis , 0, 0, run.glyphs, run.glyphCount, run.advances, run.justify, run.goffsets);
1043 }
1044 return fullSelection || partialSelection ? rect : null;
1045 }
1046
drawRunTextGDIP(long graphics, StyleItem run, RECT rect, long gdipFont, int baselineInPixels, long color, long selectionColor, int selectionStart, int selectionEnd, int alpha)1047 RECT drawRunTextGDIP(long graphics, StyleItem run, RECT rect, long gdipFont, int baselineInPixels, long color, long selectionColor, int selectionStart, int selectionEnd, int alpha) {
1048 int end = run.start + run.length - 1;
1049 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1050 boolean fullSelection = hasSelection && selectionStart <= run.start && selectionEnd >= end;
1051 boolean partialSelection = hasSelection && !fullSelection && !(selectionStart > end || run.start > selectionEnd);
1052 int drawY = rect.top + baselineInPixels;
1053 if (run.style != null && run.style.rise != 0) drawY -= DPIUtil.autoScaleUp(getDevice(), run.style.rise);
1054 int drawX = rect.left;
1055 long brush = color;
1056 if (fullSelection) {
1057 brush = selectionColor;
1058 } else {
1059 if (run.style != null && run.style.foreground != null) {
1060 brush = createGdipBrush(run.style.foreground, alpha);
1061 }
1062 }
1063 int gstate = 0;
1064 Rect gdipRect = null;
1065 if (partialSelection) {
1066 gdipRect = new Rect();
1067 getPartialSelection(run, selectionStart, selectionEnd, rect);
1068 gdipRect.X = rect.left;
1069 gdipRect.Y = rect.top;
1070 gdipRect.Width = rect.right - rect.left;
1071 gdipRect.Height = rect.bottom - rect.top;
1072 gstate = Gdip.Graphics_Save(graphics);
1073 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
1074 }
1075 int gstateMirrored = 0;
1076 boolean isMirrored = (orientation & SWT.RIGHT_TO_LEFT) != 0;
1077 if (isMirrored) {
1078 switch (Gdip.Brush_GetType(brush)) {
1079 case Gdip.BrushTypeLinearGradient:
1080 Gdip.LinearGradientBrush_ScaleTransform(brush, -1, 1, Gdip.MatrixOrderPrepend);
1081 Gdip.LinearGradientBrush_TranslateTransform(brush, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
1082 break;
1083 case Gdip.BrushTypeTextureFill:
1084 Gdip.TextureBrush_ScaleTransform(brush, -1, 1, Gdip.MatrixOrderPrepend);
1085 Gdip.TextureBrush_TranslateTransform(brush, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
1086 break;
1087 }
1088 gstateMirrored = Gdip.Graphics_Save(graphics);
1089 Gdip.Graphics_ScaleTransform(graphics, -1, 1, Gdip.MatrixOrderPrepend);
1090 Gdip.Graphics_TranslateTransform(graphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
1091 }
1092 int[] advances = new int[run.glyphCount];
1093 float[] points = new float[run.glyphCount * 2];
1094 C.memmove(advances, run.justify != 0 ? run.justify : run.advances, run.glyphCount * 4);
1095 int glyphX = drawX;
1096 for (int h = 0, j = 0; h < advances.length; h++) {
1097 points[j++] = glyphX;
1098 points[j++] = drawY;
1099 glyphX += advances[h];
1100 }
1101 Gdip.Graphics_DrawDriverString(graphics, run.glyphs, run.glyphCount, gdipFont, brush, points, 0, 0);
1102 if (partialSelection) {
1103 if (isMirrored) {
1104 Gdip.Graphics_Restore(graphics, gstateMirrored);
1105 }
1106 Gdip.Graphics_Restore(graphics, gstate);
1107 gstate = Gdip.Graphics_Save(graphics);
1108 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
1109 if (isMirrored) {
1110 gstateMirrored = Gdip.Graphics_Save(graphics);
1111 Gdip.Graphics_ScaleTransform(graphics, -1, 1, Gdip.MatrixOrderPrepend);
1112 Gdip.Graphics_TranslateTransform(graphics, -2 * drawX - run.width, 0, Gdip.MatrixOrderPrepend);
1113 }
1114 Gdip.Graphics_DrawDriverString(graphics, run.glyphs, run.glyphCount, gdipFont, selectionColor, points, 0, 0);
1115 Gdip.Graphics_Restore(graphics, gstate);
1116 }
1117 if (isMirrored) {
1118 switch (Gdip.Brush_GetType(brush)) {
1119 case Gdip.BrushTypeLinearGradient:
1120 Gdip.LinearGradientBrush_ResetTransform(brush);
1121 break;
1122 case Gdip.BrushTypeTextureFill:
1123 Gdip.TextureBrush_ResetTransform(brush);
1124 break;
1125 }
1126 Gdip.Graphics_Restore(graphics, gstateMirrored);
1127 }
1128 if (brush != selectionColor && brush != color) Gdip.SolidBrush_delete(brush);
1129 return fullSelection || partialSelection ? rect : null;
1130 }
1131
drawRunTextGDIPRaster(long graphics, StyleItem run, RECT rect, int baselineInPixels, int color, int selectionColor, int selectionStart, int selectionEnd)1132 RECT drawRunTextGDIPRaster(long graphics, StyleItem run, RECT rect, int baselineInPixels, int color, int selectionColor, int selectionStart, int selectionEnd) {
1133 long clipRgn = 0;
1134 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeNone);
1135 long rgn = Gdip.Region_new();
1136 if (rgn == 0) SWT.error(SWT.ERROR_NO_HANDLES);
1137 Gdip.Graphics_GetClip(graphics, rgn);
1138 if (!Gdip.Region_IsInfinite(rgn, graphics)) {
1139 clipRgn = Gdip.Region_GetHRGN(rgn, graphics);
1140 }
1141 Gdip.Region_delete(rgn);
1142 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeHalf);
1143 float[] lpXform = null;
1144 long matrix = Gdip.Matrix_new(1, 0, 0, 1, 0, 0);
1145 if (matrix == 0) SWT.error(SWT.ERROR_NO_HANDLES);
1146 Gdip.Graphics_GetTransform(graphics, matrix);
1147 if (!Gdip.Matrix_IsIdentity(matrix)) {
1148 lpXform = new float[6];
1149 Gdip.Matrix_GetElements(matrix, lpXform);
1150 }
1151 Gdip.Matrix_delete(matrix);
1152 long hdc = Gdip.Graphics_GetHDC(graphics);
1153 int state = OS.SaveDC(hdc);
1154 if (lpXform != null) {
1155 OS.SetGraphicsMode(hdc, OS.GM_ADVANCED);
1156 OS.SetWorldTransform(hdc, lpXform);
1157 }
1158 if (clipRgn != 0) {
1159 OS.SelectClipRgn(hdc, clipRgn);
1160 OS.DeleteObject(clipRgn);
1161 }
1162 if ((orientation & SWT.RIGHT_TO_LEFT) != 0) {
1163 OS.SetLayout(hdc, OS.GetLayout(hdc) | OS.LAYOUT_RTL);
1164 }
1165 OS.SetBkMode(hdc, OS.TRANSPARENT);
1166 RECT pRect = drawRunText(hdc, run, rect, baselineInPixels, color, selectionColor, selectionStart, selectionEnd);
1167 OS.RestoreDC(hdc, state);
1168 Gdip.Graphics_ReleaseHDC(graphics, hdc);
1169 return pRect;
1170 }
1171
drawStrikeout(long hdc, int x, int baselineInPixels, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip)1172 RECT drawStrikeout(long hdc, int x, int baselineInPixels, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip) {
1173 StyleItem run = line[index];
1174 TextStyle style = run.style;
1175 if (style == null) return null;
1176 if (!style.strikeout) return null;
1177 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
1178 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
1179 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentStrikeout(line[index + 1].style)) {
1180 int left = run.x;
1181 int start = run.start;
1182 int end = run.start + run.length - 1;
1183 for (int i = index; i > 0 && style.isAdherentStrikeout(line[i - 1].style); i--) {
1184 left = line[i - 1].x;
1185 start = Math.min(start, line[i - 1].start);
1186 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
1187 }
1188 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1189 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
1190 if (style.strikeoutColor != null) {
1191 color = style.strikeoutColor.handle;
1192 clipRect = null;
1193 } else {
1194 if (fullSelection) {
1195 color = selectionColor;
1196 clipRect = null;
1197 } else {
1198 if (style.foreground != null) {
1199 color = style.foreground.handle;
1200 }
1201 }
1202 }
1203 RECT rect = new RECT();
1204 int riseInPixels = DPIUtil.autoScaleUp(getDevice(), style.rise);
1205 OS.SetRect(rect, x + left, baselineInPixels - run.strikeoutPos - riseInPixels, x + run.x + run.width, baselineInPixels - run.strikeoutPos + run.strikeoutThickness - riseInPixels);
1206 long brush = OS.CreateSolidBrush(color);
1207 OS.FillRect(hdc, rect, brush);
1208 OS.DeleteObject(brush);
1209 if (clipRect != null) {
1210 long selBrush = OS.CreateSolidBrush(selectionColor);
1211 if (clipRect.left == -1) clipRect.left = 0;
1212 if (clipRect.right == -1) clipRect.right = 0x7ffff;
1213 OS.SetRect(clipRect, Math.max(rect.left, clipRect.left), rect.top, Math.min(rect.right, clipRect.right), rect.bottom);
1214 OS.FillRect(hdc, clipRect, selBrush);
1215 OS.DeleteObject(selBrush);
1216 }
1217 return null;
1218 }
1219 return clipRect;
1220 }
1221
drawStrikeoutGDIP(long graphics, int x, int baselineInPixels, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip)1222 RECT drawStrikeoutGDIP(long graphics, int x, int baselineInPixels, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip) {
1223 StyleItem run = line[index];
1224 TextStyle style = run.style;
1225 if (style == null) return null;
1226 if (!style.strikeout) return null;
1227 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
1228 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
1229 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentStrikeout(line[index + 1].style)) {
1230 int left = run.x;
1231 int start = run.start;
1232 int end = run.start + run.length - 1;
1233 for (int i = index; i > 0 && style.isAdherentStrikeout(line[i - 1].style); i--) {
1234 left = line[i - 1].x;
1235 start = Math.min(start, line[i - 1].start);
1236 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
1237 }
1238 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1239 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
1240 long brush = color;
1241 if (style.strikeoutColor != null) {
1242 brush = createGdipBrush(style.strikeoutColor, alpha);
1243 clipRect = null;
1244 } else {
1245 if (fullSelection) {
1246 brush = selectionColor;
1247 clipRect = null;
1248 } else {
1249 if (style.foreground != null) {
1250 brush = createGdipBrush(style.foreground, alpha);
1251 }
1252 }
1253 }
1254 int riseInPixels = DPIUtil.autoScaleUp(getDevice(), style.rise);
1255 if (clipRect != null) {
1256 int gstate = Gdip.Graphics_Save(graphics);
1257 if (clipRect.left == -1) clipRect.left = 0;
1258 if (clipRect.right == -1) clipRect.right = 0x7ffff;
1259 Rect gdipRect = new Rect();
1260 gdipRect.X = clipRect.left;
1261 gdipRect.Y = clipRect.top;
1262 gdipRect.Width = clipRect.right - clipRect.left;
1263 gdipRect.Height = clipRect.bottom - clipRect.top;
1264 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
1265 Gdip.Graphics_FillRectangle(graphics, brush, x + left, baselineInPixels - run.strikeoutPos - riseInPixels, run.x + run.width - left, run.strikeoutThickness);
1266 Gdip.Graphics_Restore(graphics, gstate);
1267 gstate = Gdip.Graphics_Save(graphics);
1268 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
1269 Gdip.Graphics_FillRectangle(graphics, selectionColor, x + left, baselineInPixels - run.strikeoutPos - riseInPixels, run.x + run.width - left, run.strikeoutThickness);
1270 Gdip.Graphics_Restore(graphics, gstate);
1271 } else {
1272 Gdip.Graphics_FillRectangle(graphics, brush, x + left, baselineInPixels - run.strikeoutPos - riseInPixels, run.x + run.width - left, run.strikeoutThickness);
1273 }
1274 if (brush != selectionColor && brush != color) Gdip.SolidBrush_delete(brush);
1275 return null;
1276 }
1277 return clipRect;
1278 }
1279
drawUnderline(long hdc, int x, int baselineInPixels, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip)1280 RECT drawUnderline(long hdc, int x, int baselineInPixels, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, int color, int selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, Rectangle drawClip) {
1281 StyleItem run = line[index];
1282 TextStyle style = run.style;
1283 if (style == null) return null;
1284 if (!style.underline) return null;
1285 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
1286 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
1287 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentUnderline(line[index + 1].style)) {
1288 int left = run.x;
1289 int start = run.start;
1290 int end = run.start + run.length - 1;
1291 for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
1292 left = line[i - 1].x;
1293 start = Math.min(start, line[i - 1].start);
1294 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
1295 }
1296 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1297 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
1298 if (style.underlineColor != null) {
1299 color = style.underlineColor.handle;
1300 clipRect = null;
1301 } else {
1302 if (fullSelection) {
1303 color = selectionColor;
1304 clipRect = null;
1305 } else {
1306 if (style.foreground != null) {
1307 color = style.foreground.handle;
1308 }
1309 }
1310 }
1311 RECT rect = new RECT();
1312 int riseInPixels = DPIUtil.autoScaleUp(getDevice(), style.rise);
1313 OS.SetRect(rect, x + left, baselineInPixels - lineUnderlinePos - riseInPixels, x + run.x + run.width, baselineInPixels - lineUnderlinePos + run.underlineThickness - riseInPixels);
1314 if (clipRect != null) {
1315 if (clipRect.left == -1) clipRect.left = 0;
1316 if (clipRect.right == -1) clipRect.right = 0x7ffff;
1317 OS.SetRect(clipRect, Math.max(rect.left, clipRect.left), rect.top, Math.min(rect.right, clipRect.right), rect.bottom);
1318 }
1319 switch (style.underlineStyle) {
1320 case SWT.UNDERLINE_SQUIGGLE:
1321 case SWT.UNDERLINE_ERROR: {
1322 int squigglyThickness = 1;
1323 int squigglyHeight = 2 * squigglyThickness;
1324 int squigglyY = Math.min(rect.top - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
1325 int[] points = computePolyline(rect.left, squigglyY, rect.right, squigglyY + squigglyHeight);
1326 long pen = OS.CreatePen(OS.PS_SOLID, squigglyThickness, color);
1327 long oldPen = OS.SelectObject(hdc, pen);
1328 int state = OS.SaveDC(hdc);
1329 OS.IntersectClipRect(hdc, rect.left, squigglyY, rect.right + 1, squigglyY + squigglyHeight + 1);
1330 OS.Polyline(hdc, points, points.length / 2);
1331 int length = points.length;
1332 if (length >= 2 && squigglyThickness <= 1) {
1333 OS.SetPixel (hdc, points[length - 2], points[length - 1], color);
1334 }
1335 OS.SelectObject(hdc, oldPen);
1336 OS.DeleteObject(pen);
1337 OS.RestoreDC(hdc, state);
1338 if (clipRect != null) {
1339 pen = OS.CreatePen(OS.PS_SOLID, squigglyThickness, selectionColor);
1340 oldPen = OS.SelectObject(hdc, pen);
1341 state = OS.SaveDC(hdc);
1342 OS.IntersectClipRect(hdc, clipRect.left, squigglyY, clipRect.right + 1, squigglyY + squigglyHeight + 1);
1343 OS.Polyline(hdc, points, points.length / 2);
1344 if (length >= 2 && squigglyThickness <= 1) {
1345 OS.SetPixel (hdc, points[length - 2], points[length - 1], selectionColor);
1346 }
1347 OS.SelectObject(hdc, oldPen);
1348 OS.DeleteObject(pen);
1349 OS.RestoreDC(hdc, state);
1350 }
1351 break;
1352 }
1353 case SWT.UNDERLINE_SINGLE:
1354 case SWT.UNDERLINE_DOUBLE:
1355 case SWT.UNDERLINE_LINK:
1356 case UNDERLINE_IME_THICK:
1357 if (style.underlineStyle == UNDERLINE_IME_THICK) {
1358 rect.top -= run.underlineThickness;
1359 if (clipRect != null) clipRect.top -= run.underlineThickness;
1360 }
1361 int bottom = style.underlineStyle == SWT.UNDERLINE_DOUBLE ? rect.bottom + run.underlineThickness * 2 : rect.bottom;
1362 if (bottom > lineBottom) {
1363 OS.OffsetRect(rect, 0, lineBottom - bottom);
1364 if (clipRect != null) OS.OffsetRect(clipRect, 0, lineBottom - bottom);
1365 }
1366 long brush = OS.CreateSolidBrush(color);
1367 OS.FillRect(hdc, rect, brush);
1368 if (style.underlineStyle == SWT.UNDERLINE_DOUBLE) {
1369 OS.SetRect(rect, rect.left, rect.top + run.underlineThickness * 2, rect.right, rect.bottom + run.underlineThickness * 2);
1370 OS.FillRect(hdc, rect, brush);
1371 }
1372 OS.DeleteObject(brush);
1373 if (clipRect != null) {
1374 long selBrush = OS.CreateSolidBrush(selectionColor);
1375 OS.FillRect(hdc, clipRect, selBrush);
1376 if (style.underlineStyle == SWT.UNDERLINE_DOUBLE) {
1377 OS.SetRect(clipRect, clipRect.left, rect.top, clipRect.right, rect.bottom);
1378 OS.FillRect(hdc, clipRect, selBrush);
1379 }
1380 OS.DeleteObject(selBrush);
1381 }
1382 break;
1383 case UNDERLINE_IME_DASH:
1384 case UNDERLINE_IME_DOT: {
1385 int penStyle = style.underlineStyle == UNDERLINE_IME_DASH ? OS.PS_DASH : OS.PS_DOT;
1386 long pen = OS.CreatePen(penStyle, 1, color);
1387 long oldPen = OS.SelectObject(hdc, pen);
1388 int descentInPixels = DPIUtil.autoScaleUp(getDevice(), run.descentInPoints);
1389 OS.SetRect(rect, rect.left, baselineInPixels + descentInPixels, rect.right, baselineInPixels + descentInPixels + run.underlineThickness);
1390 OS.MoveToEx(hdc, rect.left, rect.top, 0);
1391 OS.LineTo(hdc, rect.right, rect.top);
1392 OS.SelectObject(hdc, oldPen);
1393 OS.DeleteObject(pen);
1394 if (clipRect != null) {
1395 pen = OS.CreatePen(penStyle, 1, selectionColor);
1396 oldPen = OS.SelectObject(hdc, pen);
1397 OS.SetRect(clipRect, clipRect.left, rect.top, clipRect.right, rect.bottom);
1398 OS.MoveToEx(hdc, clipRect.left, clipRect.top, 0);
1399 OS.LineTo(hdc, clipRect.right, clipRect.top);
1400 OS.SelectObject(hdc, oldPen);
1401 OS.DeleteObject(pen);
1402 }
1403 break;
1404 }
1405 }
1406 return null;
1407 }
1408 return clipRect;
1409 }
1410
drawUnderlineGDIP(long graphics, int x, int baselineInPixels, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip)1411 RECT drawUnderlineGDIP (long graphics, int x, int baselineInPixels, int lineUnderlinePos, int lineBottom, StyleItem[] line, int index, long color, long selectionColor, RECT clipRect, RECT pRect, int selectionStart, int selectionEnd, int alpha, Rectangle drawClip) {
1412 StyleItem run = line[index];
1413 TextStyle style = run.style;
1414 if (style == null) return null;
1415 if (!style.underline) return null;
1416 clipRect = addClipRect(run, clipRect, pRect, selectionStart, selectionEnd);
1417 boolean lastRunVisible = drawClip != null && (x + run.x + run.width) > (drawClip.x + drawClip.width);
1418 if (index + 1 >= line.length || lastRunVisible || !style.isAdherentUnderline(line[index + 1].style)) {
1419 int left = run.x;
1420 int start = run.start;
1421 int end = run.start + run.length - 1;
1422 for (int i = index; i > 0 && style.isAdherentUnderline(line[i - 1].style); i--) {
1423 left = line[i - 1].x;
1424 start = Math.min(start, line[i - 1].start);
1425 end = Math.max(end, line[i - 1].start + line[i - 1].length - 1);
1426 }
1427 boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
1428 boolean fullSelection = hasSelection && selectionStart <= start && end <= selectionEnd;
1429 long brush = color;
1430 if (style.underlineColor != null) {
1431 brush = createGdipBrush(style.underlineColor, alpha);
1432 clipRect = null;
1433 } else {
1434 if (fullSelection) {
1435 brush = selectionColor;
1436 clipRect = null;
1437 } else {
1438 if (style.foreground != null) {
1439 brush = createGdipBrush(style.foreground, alpha);
1440 }
1441 }
1442 }
1443 RECT rect = new RECT();
1444 int riseInPixels = DPIUtil.autoScaleUp(getDevice(), style.rise);
1445 OS.SetRect(rect, x + left, baselineInPixels - lineUnderlinePos - riseInPixels, x + run.x + run.width, baselineInPixels - lineUnderlinePos + run.underlineThickness - riseInPixels);
1446 Rect gdipRect = null;
1447 if (clipRect != null) {
1448 if (clipRect.left == -1) clipRect.left = 0;
1449 if (clipRect.right == -1) clipRect.right = 0x7ffff;
1450 OS.SetRect(clipRect, Math.max(rect.left, clipRect.left), rect.top, Math.min(rect.right, clipRect.right), rect.bottom);
1451 gdipRect = new Rect();
1452 gdipRect.X = clipRect.left;
1453 gdipRect.Y = clipRect.top;
1454 gdipRect.Width = clipRect.right - clipRect.left;
1455 gdipRect.Height = clipRect.bottom - clipRect.top;
1456 }
1457 int gstate = 0;
1458 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeNone);
1459 int smoothingMode = Gdip.Graphics_GetSmoothingMode(graphics);
1460 Gdip.Graphics_SetSmoothingMode(graphics, Gdip.SmoothingModeNone);
1461 switch (style.underlineStyle) {
1462 case SWT.UNDERLINE_SQUIGGLE:
1463 case SWT.UNDERLINE_ERROR: {
1464 int squigglyThickness = 1;
1465 int squigglyHeight = 2 * squigglyThickness;
1466 int squigglyY = Math.min(rect.top - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
1467 int[] points = computePolyline(rect.left, squigglyY, rect.right, squigglyY + squigglyHeight);
1468 long pen = Gdip.Pen_new(brush, squigglyThickness);
1469 gstate = Gdip.Graphics_Save(graphics);
1470 if (gdipRect != null) {
1471 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
1472 } else {
1473 Rect r = new Rect();
1474 r.X = rect.left;
1475 r.Y = squigglyY;
1476 r.Width = rect.right - rect.left;
1477 r.Height = squigglyHeight + 1;
1478 Gdip.Graphics_SetClip(graphics, r, Gdip.CombineModeIntersect);
1479 }
1480 Gdip.Graphics_DrawLines(graphics, pen, points, points.length / 2);
1481 if (gdipRect != null) {
1482 long selPen = Gdip.Pen_new(selectionColor, squigglyThickness);
1483 Gdip.Graphics_Restore(graphics, gstate);
1484 gstate = Gdip.Graphics_Save(graphics);
1485 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
1486 Gdip.Graphics_DrawLines(graphics, selPen, points, points.length / 2);
1487 Gdip.Pen_delete(selPen);
1488 }
1489 Gdip.Graphics_Restore(graphics, gstate);
1490 Gdip.Pen_delete(pen);
1491 if (gstate != 0) Gdip.Graphics_Restore(graphics, gstate);
1492 break;
1493 }
1494 case SWT.UNDERLINE_SINGLE:
1495 case SWT.UNDERLINE_DOUBLE:
1496 case SWT.UNDERLINE_LINK:
1497 case UNDERLINE_IME_THICK:
1498 if (style.underlineStyle == UNDERLINE_IME_THICK) {
1499 rect.top -= run.underlineThickness;
1500 }
1501 int bottom = style.underlineStyle == SWT.UNDERLINE_DOUBLE ? rect.bottom + run.underlineThickness * 2 : rect.bottom;
1502 if (bottom > lineBottom) {
1503 OS.OffsetRect(rect, 0, lineBottom - bottom);
1504 }
1505 if (gdipRect != null) {
1506 gdipRect.Y = rect.top;
1507 if (style.underlineStyle == UNDERLINE_IME_THICK) {
1508 gdipRect.Height = run.underlineThickness * 2;
1509 }
1510 if (style.underlineStyle == SWT.UNDERLINE_DOUBLE) {
1511 gdipRect.Height = run.underlineThickness * 3;
1512 }
1513 gstate = Gdip.Graphics_Save(graphics);
1514 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
1515 }
1516 Gdip.Graphics_FillRectangle(graphics, brush, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
1517 if (style.underlineStyle == SWT.UNDERLINE_DOUBLE) {
1518 Gdip.Graphics_FillRectangle(graphics, brush, rect.left, rect.top + run.underlineThickness * 2, rect.right - rect.left, rect.bottom - rect.top);
1519 }
1520 if (gdipRect != null) {
1521 Gdip.Graphics_Restore(graphics, gstate);
1522 gstate = Gdip.Graphics_Save(graphics);
1523 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
1524 Gdip.Graphics_FillRectangle(graphics, selectionColor, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
1525 if (style.underlineStyle == SWT.UNDERLINE_DOUBLE) {
1526 Gdip.Graphics_FillRectangle(graphics, selectionColor, rect.left, rect.top + run.underlineThickness * 2, rect.right - rect.left, rect.bottom - rect.top);
1527 }
1528 Gdip.Graphics_Restore(graphics, gstate);
1529 }
1530 break;
1531 case UNDERLINE_IME_DOT:
1532 case UNDERLINE_IME_DASH: {
1533 long pen = Gdip.Pen_new(brush, 1);
1534 int dashStyle = style.underlineStyle == UNDERLINE_IME_DOT ? Gdip.DashStyleDot : Gdip.DashStyleDash;
1535 Gdip.Pen_SetDashStyle(pen, dashStyle);
1536 if (gdipRect != null) {
1537 gstate = Gdip.Graphics_Save(graphics);
1538 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeExclude);
1539 }
1540 int descentInPixels = DPIUtil.autoScaleUp(getDevice(), run.descentInPoints);
1541 Gdip.Graphics_DrawLine(graphics, pen, rect.left, baselineInPixels + descentInPixels, run.width - run.length, baselineInPixels + descentInPixels);
1542 if (gdipRect != null) {
1543 Gdip.Graphics_Restore(graphics, gstate);
1544 gstate = Gdip.Graphics_Save(graphics);
1545 Gdip.Graphics_SetClip(graphics, gdipRect, Gdip.CombineModeIntersect);
1546 long selPen = Gdip.Pen_new(brush, 1);
1547 Gdip.Pen_SetDashStyle(selPen, dashStyle);
1548 Gdip.Graphics_DrawLine(graphics, selPen, rect.left, baselineInPixels + descentInPixels, run.width - run.length, baselineInPixels + descentInPixels);
1549 Gdip.Graphics_Restore(graphics, gstate);
1550 Gdip.Pen_delete(selPen);
1551 }
1552 Gdip.Pen_delete(pen);
1553 break;
1554 }
1555 }
1556 if (brush != selectionColor && brush != color) Gdip.SolidBrush_delete(brush);
1557 Gdip.Graphics_SetPixelOffsetMode(graphics, Gdip.PixelOffsetModeHalf);
1558 Gdip.Graphics_SetSmoothingMode(graphics, smoothingMode);
1559 return null;
1560 }
1561 return clipRect;
1562 }
1563
freeRuns()1564 void freeRuns () {
1565 if (allRuns == null) return;
1566 for (StyleItem run : allRuns) {
1567 run.free();
1568 }
1569 allRuns = null;
1570 runs = null;
1571 segmentsText = null;
1572 }
1573
1574 /**
1575 * Returns the receiver's horizontal text alignment, which will be one
1576 * of <code>SWT.LEFT</code>, <code>SWT.CENTER</code> or
1577 * <code>SWT.RIGHT</code>.
1578 *
1579 * @return the alignment used to positioned text horizontally
1580 *
1581 * @exception SWTException <ul>
1582 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1583 * </ul>
1584 */
getAlignment()1585 public int getAlignment () {
1586 checkLayout();
1587 return alignment;
1588 }
1589
1590 /**
1591 * Returns the ascent of the receiver.
1592 *
1593 * @return the ascent
1594 *
1595 * @exception SWTException <ul>
1596 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1597 * </ul>
1598 *
1599 * @see #getDescent()
1600 * @see #setDescent(int)
1601 * @see #setAscent(int)
1602 * @see #getLineMetrics(int)
1603 */
getAscent()1604 public int getAscent () {
1605 checkLayout();
1606 return DPIUtil.autoScaleDown(getDevice(), ascentInPixels);
1607 }
1608
1609 /**
1610 * Returns the bounds of the receiver. The width returned is either the
1611 * width of the longest line or the width set using {@link TextLayout#setWidth(int)}.
1612 * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}.
1613 *
1614 * @return the bounds of the receiver
1615 *
1616 * @exception SWTException <ul>
1617 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1618 * </ul>
1619 *
1620 * @see #setWidth(int)
1621 * @see #getLineBounds(int)
1622 */
getBounds()1623 public Rectangle getBounds () {
1624 checkLayout();
1625 computeRuns(null);
1626 int width = 0;
1627 if (wrapWidth != -1) {
1628 width = wrapWidth;
1629 } else {
1630 for (int line=0; line<runs.length; line++) {
1631 width = Math.max(width, lineWidth[line] + getLineIndent(line));
1632 }
1633 }
1634 return new Rectangle (0, 0, DPIUtil.autoScaleDown(getDevice(), width), lineY[lineY.length - 1] + getScaledVerticalIndent());
1635 }
1636
1637 /**
1638 * Returns the bounds for the specified range of characters. The
1639 * bounds is the smallest rectangle that encompasses all characters
1640 * in the range. The start and end offsets are inclusive and will be
1641 * clamped if out of range.
1642 *
1643 * @param start the start offset
1644 * @param end the end offset
1645 * @return the bounds of the character range
1646 *
1647 * @exception SWTException <ul>
1648 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1649 * </ul>
1650 */
getBounds(int start, int end)1651 public Rectangle getBounds (int start, int end) {
1652 checkLayout();
1653 return DPIUtil.autoScaleDown(getDevice(), getBoundsInPixels(start, end));
1654 }
1655
getBoundsInPixels(int start, int end)1656 Rectangle getBoundsInPixels (int start, int end) {
1657 computeRuns(null);
1658 int length = text.length();
1659 if (length == 0) return new Rectangle(0, 0, 0, 0);
1660 if (start > end) return new Rectangle(0, 0, 0, 0);
1661 start = Math.min(Math.max(0, start), length - 1);
1662 end = Math.min(Math.max(0, end), length - 1);
1663 start = translateOffset(start);
1664 end = translateOffset(end);
1665 /* use the high surrogate for the start offset and the low surrogate for the end offset */
1666 length = segmentsText.length();
1667 char ch = segmentsText.charAt(start);
1668 if (0xDC00 <= ch && ch <= 0xDFFF) {
1669 if (start - 1 >= 0) {
1670 ch = segmentsText.charAt(start - 1);
1671 if (0xD800 <= ch && ch <= 0xDBFF) {
1672 start--;
1673 }
1674 }
1675 }
1676 ch = segmentsText.charAt(end);
1677 if (0xD800 <= ch && ch <= 0xDBFF) {
1678 if (end + 1 < length) {
1679 ch = segmentsText.charAt(end + 1);
1680 if (0xDC00 <= ch && ch <= 0xDFFF) {
1681 end++;
1682 }
1683 }
1684 }
1685 int left = 0x7fffffff, right = 0;
1686 int top = 0x7fffffff, bottom = 0;
1687 boolean isRTL = (orientation & SWT.RIGHT_TO_LEFT) != 0;
1688 for (int i = 0; i < allRuns.length - 1; i++) {
1689 StyleItem run = allRuns[i];
1690 int runEnd = run.start + run.length;
1691 if (runEnd <= start) continue;
1692 if (run.start > end) break;
1693 int runLead = run.x;
1694 int runTrail = run.x + run.width;
1695 if (run.start <= start && start < runEnd) {
1696 int cx = 0;
1697 if (run.style != null && run.style.metrics != null) {
1698 GlyphMetrics metrics = run.style.metrics;
1699 cx = metrics.getWidthInPixels() * (start - run.start);
1700 } else if (!run.tab) {
1701 int[] piX = new int[1];
1702 long advances = run.justify != 0 ? run.justify : run.advances;
1703 OS.ScriptCPtoX(start - run.start, false, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX);
1704 cx = isRTL ? run.width - piX[0] : piX[0];
1705 }
1706 if (run.analysis.fRTL ^ isRTL) {
1707 runTrail = run.x + cx;
1708 } else {
1709 runLead = run.x + cx;
1710 }
1711 }
1712 if (run.start <= end && end < runEnd) {
1713 int cx = run.width;
1714 if (run.style != null && run.style.metrics != null) {
1715 GlyphMetrics metrics = run.style.metrics;
1716 cx = metrics.getWidthInPixels() * (end - run.start + 1);
1717 } else if (!run.tab) {
1718 int[] piX = new int[1];
1719 long advances = run.justify != 0 ? run.justify : run.advances;
1720 OS.ScriptCPtoX(end - run.start, true, run.length, run.glyphCount, run.clusters, run.visAttrs, advances, run.analysis, piX);
1721 cx = isRTL ? run.width - piX[0] : piX[0];
1722 }
1723 if (run.analysis.fRTL ^ isRTL) {
1724 runLead = run.x + cx;
1725 } else {
1726 runTrail = run.x + cx;
1727 }
1728 }
1729 int lineIndex = 0;
1730 while (lineIndex < runs.length && lineOffset[lineIndex + 1] <= run.start) {
1731 lineIndex++;
1732 }
1733 left = Math.min(left, runLead);
1734 right = Math.max(right, runTrail);
1735 top = Math.min(top, DPIUtil.autoScaleUp(getDevice(), lineY[lineIndex]));
1736 bottom = Math.max(bottom, DPIUtil.autoScaleUp(getDevice(), lineY[lineIndex + 1] - lineSpacingInPoints));
1737 }
1738 return new Rectangle(left, top, right - left, bottom - top + getScaledVerticalIndent());
1739 }
1740
1741 /**
1742 * Returns the descent of the receiver.
1743 *
1744 * @return the descent
1745 *
1746 * @exception SWTException <ul>
1747 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1748 * </ul>
1749 *
1750 * @see #getAscent()
1751 * @see #setAscent(int)
1752 * @see #setDescent(int)
1753 * @see #getLineMetrics(int)
1754 */
getDescent()1755 public int getDescent () {
1756 checkLayout();
1757 return DPIUtil.autoScaleDown(getDevice(), descentInPixels);
1758 }
1759
1760 /**
1761 * Returns the default font currently being used by the receiver
1762 * to draw and measure text.
1763 *
1764 * @return the receiver's font
1765 *
1766 * @exception SWTException <ul>
1767 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1768 * </ul>
1769 */
getFont()1770 public Font getFont () {
1771 checkLayout();
1772 return font;
1773 }
1774
1775 /**
1776 * Returns the receiver's indent.
1777 *
1778 * @return the receiver's indent
1779 *
1780 * @exception SWTException <ul>
1781 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1782 * </ul>
1783 *
1784 * @since 3.2
1785 */
getIndent()1786 public int getIndent () {
1787 checkLayout();
1788 return DPIUtil.autoScaleDown(getDevice(), getIndentInPixels());
1789 }
1790
getIndentInPixels()1791 int getIndentInPixels () {
1792 return indent;
1793 }
1794
1795 /**
1796 * Returns the receiver's justification.
1797 *
1798 * @return the receiver's justification
1799 *
1800 * @exception SWTException <ul>
1801 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1802 * </ul>
1803 *
1804 * @since 3.2
1805 */
getJustify()1806 public boolean getJustify () {
1807 checkLayout();
1808 return justify;
1809 }
1810
getItemFont(StyleItem item)1811 long getItemFont (StyleItem item) {
1812 if (item.fallbackFont != 0) return item.fallbackFont;
1813 if (item.style != null && item.style.font != null) {
1814 return item.style.font.handle;
1815 }
1816 if (this.font != null) {
1817 return this.font.handle;
1818 }
1819 return device.systemFont.handle;
1820 }
1821
1822 /**
1823 * Returns the embedding level for the specified character offset. The
1824 * embedding level is usually used to determine the directionality of a
1825 * character in bidirectional text.
1826 *
1827 * @param offset the character offset
1828 * @return the embedding level
1829 *
1830 * @exception IllegalArgumentException <ul>
1831 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
1832 * </ul>
1833 * @exception SWTException <ul>
1834 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li></ul>
1835 */
getLevel(int offset)1836 public int getLevel (int offset) {
1837 checkLayout();
1838 computeRuns(null);
1839 int length = text.length();
1840 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1841 offset = translateOffset(offset);
1842 for (int i=1; i<allRuns.length; i++) {
1843 if (allRuns[i].start > offset) {
1844 return allRuns[i - 1].analysis.s.uBidiLevel;
1845 }
1846 }
1847 return (resolveTextDirection() & SWT.RIGHT_TO_LEFT) != 0 ? 1 : 0;
1848 }
1849
1850 /**
1851 * Returns the bounds of the line for the specified line index.
1852 *
1853 * @param lineIndex the line index
1854 * @return the line bounds
1855 *
1856 * @exception IllegalArgumentException <ul>
1857 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
1858 * </ul>
1859 * @exception SWTException <ul>
1860 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1861 * </ul>
1862 */
getLineBounds(int lineIndex)1863 public Rectangle getLineBounds (int lineIndex) {
1864 checkLayout();
1865 return DPIUtil.autoScaleDown(getDevice(), getLineBoundsInPixels(lineIndex));
1866 }
1867
getLineBoundsInPixels(int lineIndex)1868 Rectangle getLineBoundsInPixels(int lineIndex) {
1869 computeRuns(null);
1870 if (!(0 <= lineIndex && lineIndex < runs.length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1871 int x = getLineIndent(lineIndex);
1872 int y = DPIUtil.autoScaleUp(getDevice(), lineY[lineIndex]);
1873 int width = lineWidth[lineIndex];
1874 int height = DPIUtil.autoScaleUp(getDevice(), lineY[lineIndex + 1] - lineY[lineIndex] - lineSpacingInPoints);
1875 return new Rectangle (x, y, width, height);
1876 }
1877
1878 /**
1879 * Returns the receiver's line count. This includes lines caused
1880 * by wrapping.
1881 *
1882 * @return the line count
1883 *
1884 * @exception SWTException <ul>
1885 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1886 * </ul>
1887 */
getLineCount()1888 public int getLineCount () {
1889 checkLayout();
1890 computeRuns(null);
1891 return runs.length;
1892 }
1893
getLineIndent(int lineIndex)1894 int getLineIndent (int lineIndex) {
1895 int lineIndent = wrapIndent;
1896 if (lineIndex == 0) {
1897 lineIndent = indent;
1898 } else {
1899 StyleItem[] previousLine = runs[lineIndex - 1];
1900 StyleItem previousRun = previousLine[previousLine.length - 1];
1901 if (previousRun.lineBreak && !previousRun.softBreak) {
1902 lineIndent = indent;
1903 }
1904 }
1905 if (wrapWidth != -1) {
1906 boolean partialLine = true;
1907 if (justify) {
1908 StyleItem[] lineRun = runs[lineIndex];
1909 if (lineRun[lineRun.length - 1].softBreak) {
1910 partialLine = false;
1911 }
1912 }
1913 if (partialLine) {
1914 int lineWidth = this.lineWidth[lineIndex] + lineIndent;
1915 switch (alignment) {
1916 case SWT.CENTER: lineIndent += (wrapWidth - lineWidth) / 2; break;
1917 case SWT.RIGHT: lineIndent += wrapWidth - lineWidth; break;
1918 }
1919 }
1920 }
1921 return lineIndent;
1922 }
1923
1924 /**
1925 * Returns the index of the line that contains the specified
1926 * character offset.
1927 *
1928 * @param offset the character offset
1929 * @return the line index
1930 *
1931 * @exception IllegalArgumentException <ul>
1932 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
1933 * </ul>
1934 * @exception SWTException <ul>
1935 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1936 * </ul>
1937 */
getLineIndex(int offset)1938 public int getLineIndex (int offset) {
1939 checkLayout();
1940 computeRuns(null);
1941 int length = text.length();
1942 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1943 offset = translateOffset(offset);
1944 for (int line=0; line<runs.length; line++) {
1945 if (lineOffset[line + 1] > offset) {
1946 return line;
1947 }
1948 }
1949 return runs.length - 1;
1950 }
1951
1952 /**
1953 * Returns the font metrics for the specified line index.
1954 *
1955 * @param lineIndex the line index
1956 * @return the font metrics
1957 *
1958 * @exception IllegalArgumentException <ul>
1959 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
1960 * </ul>
1961 * @exception SWTException <ul>
1962 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1963 * </ul>
1964 */
getLineMetrics(int lineIndex)1965 public FontMetrics getLineMetrics (int lineIndex) {
1966 checkLayout();
1967 computeRuns(null);
1968 if (!(0 <= lineIndex && lineIndex < runs.length)) SWT.error(SWT.ERROR_INVALID_RANGE);
1969 long hDC = device.internal_new_GC(null);
1970 long srcHdc = OS.CreateCompatibleDC(hDC);
1971 TEXTMETRIC lptm = new TEXTMETRIC();
1972 OS.SelectObject(srcHdc, font != null ? font.handle : device.systemFont.handle);
1973 OS.GetTextMetrics(srcHdc, lptm);
1974 OS.DeleteDC(srcHdc);
1975 device.internal_dispose_GC(hDC, null);
1976
1977 int ascentInPoints = DPIUtil.autoScaleDown(getDevice(), Math.max(lptm.tmAscent, this.ascentInPixels));
1978 int descentInPoints = DPIUtil.autoScaleDown(getDevice(), Math.max(lptm.tmDescent, this.descentInPixels));
1979 int leadingInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmInternalLeading);
1980 if (text.length() != 0) {
1981 for (StyleItem run : runs[lineIndex]) {
1982 if (run.ascentInPoints > ascentInPoints) {
1983 ascentInPoints = run.ascentInPoints;
1984 leadingInPoints = run.leadingInPoints;
1985 }
1986 descentInPoints = Math.max(descentInPoints, run.descentInPoints);
1987 }
1988 }
1989 lptm.tmAscent = DPIUtil.autoScaleUp(getDevice(), ascentInPoints);
1990 lptm.tmDescent = DPIUtil.autoScaleUp(getDevice(), descentInPoints);
1991 lptm.tmHeight = DPIUtil.autoScaleUp(getDevice(), ascentInPoints + descentInPoints);
1992 lptm.tmInternalLeading = DPIUtil.autoScaleUp(getDevice(), leadingInPoints);
1993 lptm.tmAveCharWidth = 0;
1994 return FontMetrics.win32_new(lptm);
1995 }
1996
1997 /**
1998 * Returns the line offsets. Each value in the array is the
1999 * offset for the first character in a line except for the last
2000 * value, which contains the length of the text.
2001 *
2002 * @return the line offsets
2003 *
2004 * @exception SWTException <ul>
2005 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2006 * </ul>
2007 */
getLineOffsets()2008 public int[] getLineOffsets () {
2009 checkLayout();
2010 computeRuns(null);
2011 int[] offsets = new int[lineOffset.length];
2012 for (int i = 0; i < offsets.length; i++) {
2013 offsets[i] = untranslateOffset(lineOffset[i]);
2014 }
2015 return offsets;
2016 }
2017
2018 /**
2019 * Returns the location for the specified character offset. The
2020 * <code>trailing</code> argument indicates whether the offset
2021 * corresponds to the leading or trailing edge of the cluster.
2022 *
2023 * @param offset the character offset
2024 * @param trailing the trailing flag
2025 * @return the location of the character offset
2026 *
2027 * @exception SWTException <ul>
2028 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2029 * </ul>
2030 *
2031 * @see #getOffset(Point, int[])
2032 * @see #getOffset(int, int, int[])
2033 */
getLocation(int offset, boolean trailing)2034 public Point getLocation (int offset, boolean trailing) {
2035 checkLayout();
2036 return DPIUtil.autoScaleDown(getDevice(), getLocationInPixels(offset, trailing));
2037 }
2038
getLocationInPixels(int offset, boolean trailing)2039 Point getLocationInPixels (int offset, boolean trailing) {
2040 computeRuns(null);
2041 int length = text.length();
2042 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
2043 length = segmentsText.length();
2044 offset = translateOffset(offset);
2045 int line;
2046 for (line=0; line<runs.length; line++) {
2047 if (lineOffset[line + 1] > offset) break;
2048 }
2049 line = Math.min(line, runs.length - 1);
2050 if (offset == length) {
2051 return new Point(getLineIndent(line) + lineWidth[line], DPIUtil.autoScaleUp(getDevice(), lineY[line]));
2052 }
2053 /* For trailing use the low surrogate and for lead use the high surrogate */
2054 char ch = segmentsText.charAt(offset);
2055 if (trailing) {
2056 if (0xD800 <= ch && ch <= 0xDBFF) {
2057 if (offset + 1 < length) {
2058 ch = segmentsText.charAt(offset + 1);
2059 if (0xDC00 <= ch && ch <= 0xDFFF) {
2060 offset++;
2061 }
2062 }
2063 }
2064 } else {
2065 if (0xDC00 <= ch && ch <= 0xDFFF) {
2066 if (offset - 1 >= 0) {
2067 ch = segmentsText.charAt(offset - 1);
2068 if (0xD800 <= ch && ch <= 0xDBFF) {
2069 offset--;
2070 }
2071 }
2072 }
2073 }
2074 int low = -1;
2075 int high = allRuns.length;
2076 while (high - low > 1) {
2077 int index = ((high + low) / 2);
2078 StyleItem run = allRuns[index];
2079 if (run.start > offset) {
2080 high = index;
2081 } else if (run.start + run.length <= offset) {
2082 low = index;
2083 } else {
2084 int width;
2085 if (run.style != null && run.style.metrics != null) {
2086 GlyphMetrics metrics = run.style.metrics;
2087 width = metrics.getWidthInPixels() * (offset - run.start + (trailing ? 1 : 0));
2088 } else if (run.tab) {
2089 width = (trailing || (offset == length)) ? run.width : 0;
2090 } else {
2091 int runOffset = offset - run.start;
2092 int cChars = run.length;
2093 int gGlyphs = run.glyphCount;
2094 int[] piX = new int[1];
2095 long advances = run.justify != 0 ? run.justify : run.advances;
2096 OS.ScriptCPtoX(runOffset, trailing, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
2097 width = (orientation & SWT.RIGHT_TO_LEFT) != 0 ? run.width - piX[0] : piX[0];
2098 }
2099 return new Point(run.x + width, DPIUtil.autoScaleUp(getDevice(), lineY[line]) + getScaledVerticalIndent());
2100 }
2101 }
2102 return new Point(0, 0);
2103 }
2104
2105 /**
2106 * Returns the next offset for the specified offset and movement
2107 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
2108 * <code>SWT.MOVEMENT_CLUSTER</code>, <code>SWT.MOVEMENT_WORD</code>,
2109 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
2110 *
2111 * @param offset the start offset
2112 * @param movement the movement type
2113 * @return the next offset
2114 *
2115 * @exception IllegalArgumentException <ul>
2116 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
2117 * </ul>
2118 * @exception SWTException <ul>
2119 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2120 * </ul>
2121 *
2122 * @see #getPreviousOffset(int, int)
2123 */
getNextOffset(int offset, int movement)2124 public int getNextOffset (int offset, int movement) {
2125 checkLayout();
2126 return _getOffset (offset, movement, true);
2127 }
2128
_getOffset(int offset, int movement, boolean forward)2129 int _getOffset(int offset, int movement, boolean forward) {
2130 computeRuns(null);
2131 int length = text.length();
2132 if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE, null, " [offset value: " + offset + "]");//$NON-NLS-1$ $NON-NLS-2$
2133 if (forward && offset == length) return length;
2134 if (!forward && offset == 0) return 0;
2135 int step = forward ? 1 : -1;
2136 if ((movement & SWT.MOVEMENT_CHAR) != 0) return offset + step;
2137 length = segmentsText.length();
2138 offset = translateOffset(offset);
2139 SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR();
2140 SCRIPT_PROPERTIES properties = new SCRIPT_PROPERTIES();
2141 int i = forward ? 0 : allRuns.length - 1;
2142 offset = validadeOffset(offset, step);
2143 do {
2144 StyleItem run = allRuns[i];
2145 if (run.start <= offset && offset < run.start + run.length) {
2146 if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
2147 if (run.tab) return untranslateOffset(run.start);
2148 OS.MoveMemory(properties, device.scripts[run.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
2149 boolean isComplex = properties.fNeedsCaretInfo || properties.fNeedsWordBreaking;
2150 if (isComplex) breakRun(run);
2151 while (run.start <= offset && offset < run.start + run.length) {
2152 if (isComplex) {
2153 OS.MoveMemory(logAttr, run.psla + ((offset - run.start) * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
2154 }
2155 switch (movement) {
2156 case SWT.MOVEMENT_CLUSTER: {
2157 if (!properties.fNeedsCaretInfo || (!logAttr.fInvalid && logAttr.fCharStop)) {
2158 char ch = segmentsText.charAt(offset);
2159 if (0xDC00 <= ch && ch <= 0xDFFF) {
2160 if (offset > 0) {
2161 ch = segmentsText.charAt(offset - 1);
2162 if (0xD800 <= ch && ch <= 0xDBFF) {
2163 offset += step;
2164 }
2165 }
2166 }
2167 return untranslateOffset(offset);
2168 }
2169 break;
2170 }
2171 case SWT.MOVEMENT_WORD_START:
2172 case SWT.MOVEMENT_WORD: {
2173 if (properties.fNeedsWordBreaking) {
2174 if (!logAttr.fInvalid && logAttr.fWordStop) return untranslateOffset(offset);
2175 } else {
2176 if (offset > 0) {
2177 boolean letterOrDigit = Character.isLetterOrDigit(segmentsText.charAt(offset));
2178 boolean previousLetterOrDigit = Character.isLetterOrDigit(segmentsText.charAt(offset - 1));
2179 if (letterOrDigit != previousLetterOrDigit || !letterOrDigit) {
2180 if (!Character.isWhitespace(segmentsText.charAt(offset))) {
2181 return untranslateOffset(offset);
2182 }
2183 }
2184 }
2185 }
2186 break;
2187 }
2188 case SWT.MOVEMENT_WORD_END: {
2189 if (offset > 0) {
2190 boolean isLetterOrDigit = Character.isLetterOrDigit(segmentsText.charAt(offset));
2191 boolean previousLetterOrDigit = Character.isLetterOrDigit(segmentsText.charAt(offset - 1));
2192 if (!isLetterOrDigit && previousLetterOrDigit) {
2193 return untranslateOffset(offset);
2194 }
2195 }
2196 break;
2197 }
2198 }
2199 offset = validadeOffset(offset, step);
2200 }
2201 }
2202 i += step;
2203 } while (0 <= i && i < allRuns.length - 1 && 0 <= offset && offset < length);
2204 return forward ? text.length() : 0;
2205 }
2206
2207 /**
2208 * Returns the character offset for the specified point.
2209 * For a typical character, the trailing argument will be filled in to
2210 * indicate whether the point is closer to the leading edge (0) or
2211 * the trailing edge (1). When the point is over a cluster composed
2212 * of multiple characters, the trailing argument will be filled with the
2213 * position of the character in the cluster that is closest to
2214 * the point.
2215 *
2216 * @param point the point
2217 * @param trailing the trailing buffer
2218 * @return the character offset
2219 *
2220 * @exception IllegalArgumentException <ul>
2221 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
2222 * <li>ERROR_NULL_ARGUMENT - if the point is null</li>
2223 * </ul>
2224 * @exception SWTException <ul>
2225 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2226 * </ul>
2227 *
2228 * @see #getLocation(int, boolean)
2229 */
getOffset(Point point, int[] trailing)2230 public int getOffset (Point point, int[] trailing) {
2231 checkLayout();
2232 if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); return getOffsetInPixels(DPIUtil.autoScaleUp(getDevice(), point), trailing);
2233 }
2234
getOffsetInPixels(Point point, int[] trailing)2235 int getOffsetInPixels (Point point, int[] trailing) {
2236 return getOffsetInPixels (point.x, point.y, trailing) ;
2237 }
2238
2239 /**
2240 * Returns the character offset for the specified point.
2241 * For a typical character, the trailing argument will be filled in to
2242 * indicate whether the point is closer to the leading edge (0) or
2243 * the trailing edge (1). When the point is over a cluster composed
2244 * of multiple characters, the trailing argument will be filled with the
2245 * position of the character in the cluster that is closest to
2246 * the point.
2247 *
2248 * @param x the x coordinate of the point
2249 * @param y the y coordinate of the point
2250 * @param trailing the trailing buffer
2251 * @return the character offset
2252 *
2253 * @exception IllegalArgumentException <ul>
2254 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
2255 * </ul>
2256 * @exception SWTException <ul>
2257 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2258 * </ul>
2259 *
2260 * @see #getLocation(int, boolean)
2261 */
getOffset(int x, int y, int[] trailing)2262 public int getOffset (int x, int y, int[] trailing) {
2263 checkLayout();
2264 return getOffsetInPixels(DPIUtil.autoScaleUp(getDevice(), x), DPIUtil.autoScaleUp(getDevice(), y), trailing);
2265 }
2266
getOffsetInPixels(int x, int y, int[] trailing)2267 int getOffsetInPixels (int x, int y, int[] trailing) {
2268 computeRuns(null);
2269 if (trailing != null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2270 int line;
2271 int lineCount = runs.length;
2272 for (line=0; line<lineCount; line++) {
2273 if (DPIUtil.autoScaleUp(getDevice(), lineY[line + 1]) > y) break;
2274 }
2275 line = Math.min(line, runs.length - 1);
2276 StyleItem[] lineRuns = runs[line];
2277 int lineIndent = getLineIndent(line);
2278 if (x >= lineIndent + lineWidth[line]) x = lineIndent + lineWidth[line] - 1;
2279 if (x < lineIndent) x = lineIndent;
2280 int low = -1;
2281 int high = lineRuns.length;
2282 while (high - low > 1) {
2283 int index = ((high + low) / 2);
2284 StyleItem run = lineRuns[index];
2285 if (run.x > x) {
2286 high = index;
2287 } else if (run.x + run.width <= x) {
2288 low = index;
2289 } else {
2290 if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
2291 int xRun = x - run.x;
2292 if (run.style != null && run.style.metrics != null) {
2293 GlyphMetrics metrics = run.style.metrics;
2294 if (metrics.getWidthInPixels() > 0) {
2295 if (trailing != null) {
2296 trailing[0] = (xRun % metrics.getWidthInPixels() < metrics.getWidthInPixels() / 2) ? 0 : 1;
2297 }
2298 return untranslateOffset(run.start + xRun / metrics.getWidthInPixels());
2299 }
2300 }
2301 if (run.tab) {
2302 if (trailing != null) trailing[0] = x < (run.x + run.width / 2) ? 0 : 1;
2303 return untranslateOffset(run.start);
2304 }
2305 int cChars = run.length;
2306 int cGlyphs = run.glyphCount;
2307 int[] piCP = new int[1];
2308 int[] piTrailing = new int[1];
2309 if ((orientation & SWT.RIGHT_TO_LEFT) != 0) {
2310 xRun = run.width - xRun;
2311 }
2312 long advances = run.justify != 0 ? run.justify : run.advances;
2313 OS.ScriptXtoCP(xRun, cChars, cGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piCP, piTrailing);
2314 int offset = run.start + piCP[0];
2315 int length = segmentsText.length();
2316 char ch = offset < length ? segmentsText.charAt(offset) : 0;
2317 if (0xD800 <= ch && ch <= 0xDBFF && piTrailing[0] <= 1) {
2318 if (offset + 1 < length) {
2319 ch = segmentsText.charAt(offset + 1);
2320 if (0xDC00 <= ch && ch <= 0xDFFF) {
2321 if (trailing != null) trailing[0] = 0;
2322 }
2323 }
2324 } else if (0xDC00 <= ch && ch <= 0xDFFF && piTrailing[0] <= 1) {
2325 if (offset - 1 >= 0) {
2326 ch = segmentsText.charAt(offset - 1);
2327 if (0xD800 <= ch && ch <= 0xDBFF) {
2328 offset--;
2329 if (trailing != null) trailing[0] = 2;
2330 }
2331 }
2332 } else {
2333 if (trailing != null) trailing[0] = piTrailing[0];
2334 }
2335 return untranslateOffset(offset);
2336 }
2337 }
2338 if (trailing != null) trailing[0] = 0;
2339 if (lineRuns.length == 1) {
2340 StyleItem run = lineRuns[0];
2341 if (run.lineBreak && !run.softBreak) return untranslateOffset(run.start);
2342 }
2343 return untranslateOffset(lineOffset[line + 1]);
2344 }
2345
2346 /**
2347 * Returns the orientation of the receiver.
2348 *
2349 * @return the orientation style
2350 *
2351 * @exception SWTException <ul>
2352 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2353 * </ul>
2354 */
2355 public int getOrientation () {
2356 checkLayout();
2357 return orientation;
2358 }
2359
2360 void getPartialSelection(StyleItem run, int selectionStart, int selectionEnd, RECT rect) {
2361 int end = run.start + run.length - 1;
2362 int selStart = Math.max(selectionStart, run.start) - run.start;
2363 int selEnd = Math.min(selectionEnd, end) - run.start;
2364 int cChars = run.length;
2365 int gGlyphs = run.glyphCount;
2366 int[] piX = new int[1];
2367 int x = rect.left;
2368 long advances = run.justify != 0 ? run.justify : run.advances;
2369 OS.ScriptCPtoX(selStart, false, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
2370 int runX = (orientation & SWT.RIGHT_TO_LEFT) != 0 ? run.width - piX[0] : piX[0];
2371 rect.left = x + runX;
2372 OS.ScriptCPtoX(selEnd, true, cChars, gGlyphs, run.clusters, run.visAttrs, advances, run.analysis, piX);
2373 runX = (orientation & SWT.RIGHT_TO_LEFT) != 0 ? run.width - piX[0] : piX[0];
2374 rect.right = x + runX;
2375 }
2376
2377 /**
2378 * Returns the previous offset for the specified offset and movement
2379 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>,
2380 * <code>SWT.MOVEMENT_CLUSTER</code> or <code>SWT.MOVEMENT_WORD</code>,
2381 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>.
2382 *
2383 * @param offset the start offset
2384 * @param movement the movement type
2385 * @return the previous offset
2386 *
2387 * @exception IllegalArgumentException <ul>
2388 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
2389 * </ul>
2390 * @exception SWTException <ul>
2391 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2392 * </ul>
2393 *
2394 * @see #getNextOffset(int, int)
2395 */
2396 public int getPreviousOffset (int offset, int movement) {
2397 checkLayout();
2398 return _getOffset (offset, movement, false);
2399 }
2400
2401 /**
2402 * Gets the ranges of text that are associated with a <code>TextStyle</code>.
2403 *
2404 * @return the ranges, an array of offsets representing the start and end of each
2405 * text style.
2406 *
2407 * @exception SWTException <ul>
2408 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2409 * </ul>
2410 *
2411 * @see #getStyles()
2412 *
2413 * @since 3.2
2414 */
2415 public int[] getRanges () {
2416 checkLayout();
2417 int[] result = new int[stylesCount * 2];
2418 int count = 0;
2419 for (int i=0; i<stylesCount - 1; i++) {
2420 if (styles[i].style != null) {
2421 result[count++] = styles[i].start;
2422 result[count++] = styles[i + 1].start - 1;
2423 }
2424 }
2425 if (count != result.length) {
2426 int[] newResult = new int[count];
2427 System.arraycopy(result, 0, newResult, 0, count);
2428 result = newResult;
2429 }
2430 return result;
2431 }
2432
2433 /**
2434 * Returns the text segments offsets of the receiver.
2435 *
2436 * @return the text segments offsets
2437 *
2438 * @exception SWTException <ul>
2439 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2440 * </ul>
2441 */
2442 public int[] getSegments () {
2443 checkLayout();
2444 return segments;
2445 }
2446
2447 /**
2448 * Returns the segments characters of the receiver.
2449 *
2450 * @return the segments characters
2451 *
2452 * @exception SWTException <ul>
2453 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2454 * </ul>
2455 *
2456 * @since 3.6
2457 */
2458 public char[] getSegmentsChars () {
2459 checkLayout();
2460 return segmentsChars;
2461 }
2462
2463 String getSegmentsText() {
2464 int length = text.length();
2465 if (length == 0) return text;
2466 if (segments == null) return text;
2467 int nSegments = segments.length;
2468 if (nSegments == 0) return text;
2469 if (segmentsChars == null) {
2470 if (nSegments == 1) return text;
2471 if (nSegments == 2) {
2472 if (segments[0] == 0 && segments[1] == length) return text;
2473 }
2474 }
2475 char[] oldChars = new char[length];
2476 text.getChars(0, length, oldChars, 0);
2477 char[] newChars = new char[length + nSegments];
2478 int charCount = 0, segmentCount = 0;
2479 char defaultSeparator = (resolveTextDirection() & SWT.RIGHT_TO_LEFT) != 0 ? RTL_MARK : LTR_MARK;
2480 while (charCount < length) {
2481 if (segmentCount < nSegments && charCount == segments[segmentCount]) {
2482 char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
2483 newChars[charCount + segmentCount++] = separator;
2484 } else {
2485 newChars[charCount + segmentCount] = oldChars[charCount++];
2486 }
2487 }
2488 while (segmentCount < nSegments) {
2489 segments[segmentCount] = charCount;
2490 char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
2491 newChars[charCount + segmentCount++] = separator;
2492 }
2493 return new String(newChars, 0, newChars.length);
2494 }
2495
2496 /**
2497 * Returns the line spacing of the receiver.
2498 *
2499 * @return the line spacing
2500 *
2501 * @exception SWTException <ul>
2502 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2503 * </ul>
2504 */
2505 public int getSpacing () {
2506 checkLayout();
2507 return lineSpacingInPoints;
2508 }
2509
2510 /**
2511 * Returns the vertical indent of the receiver.
2512 *
2513 * @return the vertical indent
2514 *
2515 * @exception SWTException <ul>
2516 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2517 * </ul>
2518 * @since 3.109
2519 */
2520 public int getVerticalIndent () {
2521 checkLayout();
2522 return verticalIndentInPoints;
2523 }
2524
2525 /**
2526 * Returns the scaled vertical indent.
2527 *
2528 * @return the scaled vertical indent.
2529 * @since 3.109
2530 */
2531 private int getScaledVerticalIndent() {
2532 if (verticalIndentInPoints == 0) {
2533 return verticalIndentInPoints;
2534 }
2535 return DPIUtil.autoScaleUp(getDevice(), verticalIndentInPoints);
2536 }
2537
2538 /**
2539 * Gets the style of the receiver at the specified character offset.
2540 *
2541 * @param offset the text offset
2542 * @return the style or <code>null</code> if not set
2543 *
2544 * @exception IllegalArgumentException <ul>
2545 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
2546 * </ul>
2547 * @exception SWTException <ul>
2548 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2549 * </ul>
2550 */
2551 public TextStyle getStyle (int offset) {
2552 checkLayout();
2553 int length = text.length();
2554 if (!(0 <= offset && offset < length)) SWT.error(SWT.ERROR_INVALID_RANGE);
2555 for (int i=1; i<stylesCount; i++) {
2556 if (styles[i].start > offset) {
2557 return styles[i - 1].style;
2558 }
2559 }
2560 return null;
2561 }
2562
2563 /**
2564 * Gets all styles of the receiver.
2565 *
2566 * @return the styles
2567 *
2568 * @exception SWTException <ul>
2569 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2570 * </ul>
2571 *
2572 * @see #getRanges()
2573 *
2574 * @since 3.2
2575 */
2576 public TextStyle[] getStyles () {
2577 checkLayout();
2578 TextStyle[] result = new TextStyle[stylesCount];
2579 int count = 0;
2580 for (int i=0; i<stylesCount; i++) {
2581 if (styles[i].style != null) {
2582 result[count++] = styles[i].style;
2583 }
2584 }
2585 if (count != result.length) {
2586 TextStyle[] newResult = new TextStyle[count];
2587 System.arraycopy(result, 0, newResult, 0, count);
2588 result = newResult;
2589 }
2590 return result;
2591 }
2592
2593 /**
2594 * Returns the tab list of the receiver.
2595 *
2596 * @return the tab list
2597 *
2598 * @exception SWTException <ul>
2599 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2600 * </ul>
2601 */
2602 public int[] getTabs () {
2603 checkLayout();
2604 return DPIUtil.autoScaleDown (getDevice(), getTabsInPixels ());
2605 }
2606
2607 int[] getTabsInPixels () {
2608 return tabs;
2609 }
2610
2611 /**
2612 * Gets the receiver's text, which will be an empty
2613 * string if it has never been set.
2614 *
2615 * @return the receiver's text
2616 *
2617 * @exception SWTException <ul>
2618 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2619 * </ul>
2620 */
2621 public String getText () {
2622 checkLayout();
2623 return text;
2624 }
2625
2626 /**
2627 * Returns the text direction of the receiver.
2628 *
2629 * @return the text direction value
2630 *
2631 * @exception SWTException <ul>
2632 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2633 * </ul>
2634 * @since 3.103
2635 */
2636 public int getTextDirection () {
2637 checkLayout();
2638 return resolveTextDirection();
2639 }
2640
2641 /**
2642 * Returns the width of the receiver.
2643 *
2644 * @return the width
2645 *
2646 * @exception SWTException <ul>
2647 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2648 * </ul>
2649 */
2650 public int getWidth () {
2651 checkLayout();
2652 return DPIUtil.autoScaleDown(getDevice(), getWidthInPixels());
2653 }
2654
2655 int getWidthInPixels () {
2656 return wrapWidth;
2657 }
2658
2659 /**
2660 * Returns the receiver's wrap indent.
2661 *
2662 * @return the receiver's wrap indent
2663 *
2664 * @exception SWTException <ul>
2665 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2666 * </ul>
2667 *
2668 * @since 3.6
2669 */
2670 public int getWrapIndent () {
2671 checkLayout();
2672 return DPIUtil.autoScaleDown(getDevice(), getWrapIndentInPixels());
2673 }
2674
2675 int getWrapIndentInPixels () {
2676 return wrapIndent;
2677 }
2678
2679 /**
2680 * Returns <code>true</code> if the text layout has been disposed,
2681 * and <code>false</code> otherwise.
2682 * <p>
2683 * This method gets the dispose state for the text layout.
2684 * When a text layout has been disposed, it is an error to
2685 * invoke any other method (except {@link #dispose()}) using the text layout.
2686 * </p>
2687 *
2688 * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise
2689 */
2690 @Override
2691 public boolean isDisposed () {
2692 return device == null;
2693 }
2694
2695 /*
2696 * Itemize the receiver text
2697 */
2698 StyleItem[] itemize () {
2699 segmentsText = getSegmentsText();
2700 int length = segmentsText.length();
2701 SCRIPT_CONTROL scriptControl = new SCRIPT_CONTROL();
2702 SCRIPT_STATE scriptState = new SCRIPT_STATE();
2703 final int MAX_ITEM = length + 1;
2704
2705 if ((resolveTextDirection() & SWT.RIGHT_TO_LEFT) != 0) {
2706 scriptState.uBidiLevel = 1;
2707 scriptState.fArabicNumContext = true;
2708 }
2709
2710 /*
2711 * In the version of Usp10.h that SWT is compiled the fReserved field is declared
2712 * as a bitfield size 8. In newer versions of the Uniscribe, the first bit of fReserved
2713 * was used to implement the fMergeNeutralItems feature which can be used to increase
2714 * performance by reducing the number of SCRIPT_ITEM returned by ScriptItemize.
2715 *
2716 * Note: This code is wrong on a big endian machine.
2717 *
2718 * Note: This code is intentionally commented because it causes bug#377472.
2719 */
2720 // scriptControl.fReserved = 0x1;
2721
2722 OS.ScriptApplyDigitSubstitution(null, scriptControl, scriptState);
2723
2724 long hHeap = OS.GetProcessHeap();
2725 long pItems = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, MAX_ITEM * SCRIPT_ITEM.sizeof);
2726 if (pItems == 0) SWT.error(SWT.ERROR_NO_HANDLES);
2727 int[] pcItems = new int[1];
2728 char[] chars = new char[length];
2729 segmentsText.getChars(0, length, chars, 0);
2730 // enable font ligatures
2731 scriptControl.fMergeNeutralItems = true;
2732
2733 OS.ScriptItemize(chars, length, MAX_ITEM, scriptControl, scriptState, pItems, pcItems);
2734
2735 // if (hr == E_OUTOFMEMORY) //TODO handle it
2736
2737 StyleItem[] runs = merge(pItems, pcItems[0]);
2738 OS.HeapFree(hHeap, 0, pItems);
2739 return runs;
2740 }
2741
2742 /*
2743 * Merge styles ranges and script items
2744 */
2745 StyleItem[] merge (long items, int itemCount) {
2746 if (styles.length > stylesCount) {
2747 StyleItem[] newStyles = new StyleItem[stylesCount];
2748 System.arraycopy(styles, 0, newStyles, 0, stylesCount);
2749 styles = newStyles;
2750 }
2751 int count = 0, start = 0, end = segmentsText.length(), itemIndex = 0, styleIndex = 0;
2752 StyleItem[] runs = new StyleItem[itemCount + stylesCount];
2753 SCRIPT_ITEM scriptItem = new SCRIPT_ITEM();
2754 int itemLimit = -1;
2755 int nextItemIndex = 0;
2756 boolean linkBefore = false;
2757 boolean merge = itemCount > TOO_MANY_RUNS;
2758 SCRIPT_PROPERTIES sp = new SCRIPT_PROPERTIES();
2759 while (start < end) {
2760 StyleItem item = new StyleItem();
2761 item.start = start;
2762 item.style = styles[styleIndex].style;
2763 runs[count++] = item;
2764 OS.MoveMemory(scriptItem, items + itemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
2765 item.analysis = scriptItem.a;
2766 scriptItem.a = new SCRIPT_ANALYSIS();
2767 if (linkBefore) {
2768 item.analysis.fLinkBefore = true;
2769 linkBefore = false;
2770 }
2771 char ch = segmentsText.charAt(start);
2772 switch (ch) {
2773 case '\r':
2774 case '\n':
2775 item.lineBreak = true;
2776 break;
2777 case '\t':
2778 item.tab = true;
2779 break;
2780 }
2781 if (itemLimit == -1) {
2782 nextItemIndex = itemIndex + 1;
2783 OS.MoveMemory(scriptItem, items + nextItemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
2784 itemLimit = scriptItem.iCharPos;
2785 if (nextItemIndex < itemCount && ch == '\r' && segmentsText.charAt(itemLimit) == '\n') {
2786 nextItemIndex = itemIndex + 2;
2787 OS.MoveMemory(scriptItem, items + nextItemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
2788 itemLimit = scriptItem.iCharPos;
2789 }
2790 if (nextItemIndex < itemCount && merge) {
2791 if (!item.lineBreak) {
2792 OS.MoveMemory(sp, device.scripts[item.analysis.eScript], SCRIPT_PROPERTIES.sizeof);
2793 if (!sp.fComplex || item.tab) {
2794 for (int i = 0; i < MERGE_MAX; i++) {
2795 if (nextItemIndex == itemCount) break;
2796 char c = segmentsText.charAt(itemLimit);
2797 if (c == '\n' || c == '\r') break;
2798 if (c == '\t' != item.tab) break;
2799 OS.MoveMemory(sp, device.scripts[scriptItem.a.eScript], SCRIPT_PROPERTIES.sizeof);
2800 if (!item.tab && sp.fComplex) break;
2801 nextItemIndex++;
2802 OS.MoveMemory(scriptItem, items + nextItemIndex * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
2803 itemLimit = scriptItem.iCharPos;
2804 }
2805 }
2806 }
2807 }
2808 }
2809
2810 int styleLimit = translateOffset(styles[styleIndex + 1].start);
2811 if (styleLimit <= itemLimit) {
2812 styleIndex++;
2813 start = styleLimit;
2814 if (start < itemLimit && 0 < start && start < end) {
2815 char pChar = segmentsText.charAt(start - 1);
2816 char tChar = segmentsText.charAt(start);
2817 if (Character.isLetter(pChar) && Character.isLetter(tChar)) {
2818 item.analysis.fLinkAfter = true;
2819 linkBefore = true;
2820 }
2821 }
2822 }
2823 if (itemLimit <= styleLimit) {
2824 itemIndex = nextItemIndex;
2825 start = itemLimit;
2826 itemLimit = -1;
2827 }
2828 item.length = start - item.start;
2829 }
2830 StyleItem item = new StyleItem();
2831 item.start = end;
2832 OS.MoveMemory(scriptItem, items + itemCount * SCRIPT_ITEM.sizeof, SCRIPT_ITEM.sizeof);
2833 item.analysis = scriptItem.a;
2834 runs[count++] = item;
2835 if (runs.length != count) {
2836 StyleItem[] result = new StyleItem[count];
2837 System.arraycopy(runs, 0, result, 0, count);
2838 return result;
2839 }
2840 return runs;
2841 }
2842
2843 /*
2844 * Resolves text direction. If the nominal direction is LTR or RTL, no
2845 * resolution is needed; if the nominal direction is "auto", have BidiUtil
2846 * resolve it according to the first strong bidi character.
2847 */
2848 int resolveTextDirection () {
2849 return textDirection == SWT.AUTO_TEXT_DIRECTION ? BidiUtil.resolveTextDirection (text) : textDirection;
2850 }
2851
2852 /*
2853 * Reorder the run
2854 */
2855 StyleItem[] reorder (StyleItem[] runs, boolean terminate) {
2856 int length = runs.length;
2857 if (length <= 1) return runs;
2858 byte[] bidiLevels = new byte[length];
2859 for (int i=0; i<length; i++) {
2860 bidiLevels[i] = (byte)(runs[i].analysis.s.uBidiLevel & 0x1F);
2861 }
2862 /*
2863 * Feature in Windows. If the orientation is RTL Uniscribe will
2864 * resolve the level of line breaks to 1, this can cause the line
2865 * break to be reorder to the middle of the line. The fix is to set
2866 * the level to zero to prevent it to be reordered.
2867 */
2868 StyleItem lastRun = runs[length - 1];
2869 if (lastRun.lineBreak && !lastRun.softBreak) {
2870 bidiLevels[length - 1] = 0;
2871 }
2872 int[] log2vis = new int[length];
2873 OS.ScriptLayout(length, bidiLevels, null, log2vis);
2874 StyleItem[] result = new StyleItem[length];
2875 for (int i=0; i<length; i++) {
2876 result[log2vis[i]] = runs[i];
2877 }
2878 if ((orientation & SWT.RIGHT_TO_LEFT) != 0) {
2879 if (terminate) length--;
2880 for (int i = 0; i < length / 2 ; i++) {
2881 StyleItem tmp = result[i];
2882 result[i] = result[length - i - 1];
2883 result[length - i - 1] = tmp;
2884 }
2885 }
2886 return result;
2887 }
2888
2889 /**
2890 * Sets the text alignment for the receiver. The alignment controls
2891 * how a line of text is positioned horizontally. The argument should
2892 * be one of <code>SWT.LEFT</code>, <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>.
2893 * <p>
2894 * The default alignment is <code>SWT.LEFT</code>. Note that the receiver's
2895 * width must be set in order to use <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>
2896 * alignment.
2897 * </p>
2898 *
2899 * @param alignment the new alignment
2900 *
2901 * @exception SWTException <ul>
2902 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2903 * </ul>
2904 *
2905 * @see #setWidth(int)
2906 */
2907 public void setAlignment (int alignment) {
2908 checkLayout();
2909 int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT;
2910 alignment &= mask;
2911 if (alignment == 0) return;
2912 if ((alignment & SWT.LEFT) != 0) alignment = SWT.LEFT;
2913 if ((alignment & SWT.RIGHT) != 0) alignment = SWT.RIGHT;
2914 if (this.alignment == alignment) return;
2915 freeRuns();
2916 this.alignment = alignment;
2917 }
2918
2919 /**
2920 * Sets the ascent of the receiver. The ascent is distance in points
2921 * from the baseline to the top of the line and it is applied to all
2922 * lines. The default value is <code>-1</code> which means that the
2923 * ascent is calculated from the line fonts.
2924 *
2925 * @param ascent the new ascent
2926 *
2927 * @exception IllegalArgumentException <ul>
2928 * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li>
2929 * </ul>
2930 * @exception SWTException <ul>
2931 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2932 * </ul>
2933 *
2934 * @see #setDescent(int)
2935 * @see #getLineMetrics(int)
2936 */
2937 public void setAscent (int ascent) {
2938 checkLayout();
2939 if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2940 ascent = DPIUtil.autoScaleUp(getDevice(), ascent);
2941 if (this.ascentInPixels == ascent) return;
2942 freeRuns();
2943 this.ascentInPixels = ascent;
2944 }
2945
2946 /**
2947 * Sets the descent of the receiver. The descent is distance in points
2948 * from the baseline to the bottom of the line and it is applied to all
2949 * lines. The default value is <code>-1</code> which means that the
2950 * descent is calculated from the line fonts.
2951 *
2952 * @param descent the new descent
2953 *
2954 * @exception IllegalArgumentException <ul>
2955 * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li>
2956 * </ul>
2957 * @exception SWTException <ul>
2958 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2959 * </ul>
2960 *
2961 * @see #setAscent(int)
2962 * @see #getLineMetrics(int)
2963 */
2964 public void setDescent (int descent) {
2965 checkLayout();
2966 if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2967 descent = DPIUtil.autoScaleUp(getDevice(), descent);
2968 if (this.descentInPixels == descent) return;
2969 freeRuns();
2970 this.descentInPixels = descent;
2971 }
2972
2973 /**
2974 * Sets the default font which will be used by the receiver
2975 * to draw and measure text. If the
2976 * argument is null, then a default font appropriate
2977 * for the platform will be used instead. Note that a text
2978 * style can override the default font.
2979 *
2980 * @param font the new font for the receiver, or null to indicate a default font
2981 *
2982 * @exception IllegalArgumentException <ul>
2983 * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li>
2984 * </ul>
2985 * @exception SWTException <ul>
2986 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
2987 * </ul>
2988 */
2989 public void setFont (Font font) {
2990 checkLayout();
2991 if (font != null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2992 Font oldFont = this.font;
2993 if (oldFont == font) return;
2994 this.font = font;
2995 if (oldFont != null && oldFont.equals(font)) return;
2996 freeRuns();
2997 }
2998
2999 /**
3000 * Sets the indent of the receiver. This indent is applied to the first line of
3001 * each paragraph.
3002 *
3003 * @param indent new indent
3004 *
3005 * @exception SWTException <ul>
3006 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3007 * </ul>
3008 *
3009 * @see #setWrapIndent(int)
3010 *
3011 * @since 3.2
3012 */
3013 public void setIndent (int indent) {
3014 checkLayout();
3015 setIndentInPixels(DPIUtil.autoScaleUp(getDevice(), indent));
3016 }
3017
3018 void setIndentInPixels (int indent) {
3019 if (indent < 0) return;
3020 if (this.indent == indent) return;
3021 freeRuns();
3022 this.indent = indent;
3023 }
3024
3025 /**
3026 * Sets the justification of the receiver. Note that the receiver's
3027 * width must be set in order to use justification.
3028 *
3029 * @param justify new justify
3030 *
3031 * @exception SWTException <ul>
3032 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3033 * </ul>
3034 *
3035 * @since 3.2
3036 */
3037 public void setJustify (boolean justify) {
3038 checkLayout();
3039 if (this.justify == justify) return;
3040 freeRuns();
3041 this.justify = justify;
3042 }
3043
3044 /**
3045 * Sets the orientation of the receiver, which must be one
3046 * of <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
3047 *
3048 * @param orientation new orientation style
3049 *
3050 * @exception SWTException <ul>
3051 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3052 * </ul>
3053 */
3054 public void setOrientation (int orientation) {
3055 checkLayout();
3056 int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
3057 orientation &= mask;
3058 if (orientation == 0) return;
3059 if ((orientation & SWT.LEFT_TO_RIGHT) != 0) orientation = SWT.LEFT_TO_RIGHT;
3060 if (this.orientation == orientation) return;
3061 textDirection = this.orientation = orientation;
3062 freeRuns();
3063 }
3064
3065 /**
3066 * Sets the offsets of the receiver's text segments. Text segments are used to
3067 * override the default behavior of the bidirectional algorithm.
3068 * Bidirectional reordering can happen within a text segment but not
3069 * between two adjacent segments.
3070 * <p>
3071 * Each text segment is determined by two consecutive offsets in the
3072 * <code>segments</code> arrays. The first element of the array should
3073 * always be zero and the last one should always be equals to length of
3074 * the text.
3075 * </p>
3076 * <p>
3077 * When segments characters are set, the segments are the offsets where
3078 * the characters are inserted in the text.
3079 * <p>
3080 *
3081 * @param segments the text segments offset
3082 *
3083 * @exception SWTException <ul>
3084 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3085 * </ul>
3086 *
3087 * @see #setSegmentsChars(char[])
3088 */
3089 public void setSegments(int[] segments) {
3090 checkLayout();
3091 if (this.segments == null && segments == null) return;
3092 if (this.segments != null && segments != null) {
3093 if (this.segments.length == segments.length) {
3094 int i;
3095 for (i = 0; i <segments.length; i++) {
3096 if (this.segments[i] != segments[i]) break;
3097 }
3098 if (i == segments.length) return;
3099 }
3100 }
3101 freeRuns();
3102 this.segments = segments;
3103 }
3104
3105 /**
3106 * Sets the characters to be used in the segments boundaries. The segments
3107 * are set by calling <code>setSegments(int[])</code>. The application can
3108 * use this API to insert Unicode Control Characters in the text to control
3109 * the display of the text and bidi reordering. The characters are not
3110 * accessible by any other API in <code>TextLayout</code>.
3111 *
3112 * @param segmentsChars the segments characters
3113 *
3114 * @exception SWTException <ul>
3115 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3116 * </ul>
3117 *
3118 * @see #setSegments(int[])
3119 *
3120 * @since 3.6
3121 */
3122 public void setSegmentsChars(char[] segmentsChars) {
3123 checkLayout();
3124 if (this.segmentsChars == null && segmentsChars == null) return;
3125 if (this.segmentsChars != null && segmentsChars != null) {
3126 if (this.segmentsChars.length == segmentsChars.length) {
3127 int i;
3128 for (i = 0; i <segmentsChars.length; i++) {
3129 if (this.segmentsChars[i] != segmentsChars[i]) break;
3130 }
3131 if (i == segmentsChars.length) return;
3132 }
3133 }
3134 freeRuns();
3135 this.segmentsChars = segmentsChars;
3136 }
3137
3138 /**
3139 * Sets the line spacing of the receiver. The line spacing
3140 * is the space left between lines.
3141 *
3142 * @param spacing the new line spacing
3143 *
3144 * @exception IllegalArgumentException <ul>
3145 * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li>
3146 * </ul>
3147 * @exception SWTException <ul>
3148 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3149 * </ul>
3150 */
3151 public void setSpacing (int spacing) {
3152 checkLayout();
3153 if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3154 if (this.lineSpacingInPoints == spacing) return;
3155 freeRuns();
3156 this.lineSpacingInPoints = spacing;
3157 }
3158
3159 /**
3160 * Sets the vertical indent of the receiver. The vertical indent
3161 * is the space left before the first line.
3162 *
3163 * @param verticalIndent the new vertical indent
3164 *
3165 * @exception IllegalArgumentException <ul>
3166 * <li>ERROR_INVALID_ARGUMENT - if the vertical indent is negative</li>
3167 * </ul>
3168 * @exception SWTException <ul>
3169 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3170 * </ul>
3171 * @since 3.109
3172 */
3173 public void setVerticalIndent (int verticalIndent) {
3174 checkLayout();
3175 if (verticalIndent < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3176 if (this.verticalIndentInPoints == verticalIndent) return;
3177 this.verticalIndentInPoints = verticalIndent;
3178 }
3179
3180 /**
3181 * Sets the style of the receiver for the specified range. Styles previously
3182 * set for that range will be overwritten. The start and end offsets are
3183 * inclusive and will be clamped if out of range.
3184 *
3185 * @param style the style
3186 * @param start the start offset
3187 * @param end the end offset
3188 *
3189 * @exception SWTException <ul>
3190 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3191 * </ul>
3192 */
3193 public void setStyle (TextStyle style, int start, int end) {
3194 checkLayout();
3195 int length = text.length();
3196 if (length == 0) return;
3197 if (start > end) return;
3198 start = Math.min(Math.max(0, start), length - 1);
3199 end = Math.min(Math.max(0, end), length - 1);
3200 int low = -1;
3201 int high = stylesCount;
3202 while (high - low > 1) {
3203 int index = (high + low) / 2;
3204 if (styles[index + 1].start > start) {
3205 high = index;
3206 } else {
3207 low = index;
3208 }
3209 }
3210 if (0 <= high && high < stylesCount) {
3211 StyleItem item = styles[high];
3212 if (item.start == start && styles[high + 1].start - 1 == end) {
3213 if (style == null) {
3214 if (item.style == null) return;
3215 } else {
3216 if (style.equals(item.style)) return;
3217 }
3218 }
3219 }
3220 freeRuns();
3221 int modifyStart = high;
3222 int modifyEnd = modifyStart;
3223 while (modifyEnd < stylesCount) {
3224 if (styles[modifyEnd + 1].start > end) break;
3225 modifyEnd++;
3226 }
3227 if (modifyStart == modifyEnd) {
3228 int styleStart = styles[modifyStart].start;
3229 int styleEnd = styles[modifyEnd + 1].start - 1;
3230 if (styleStart == start && styleEnd == end) {
3231 styles[modifyStart].style = style;
3232 return;
3233 }
3234 if (styleStart != start && styleEnd != end) {
3235 int newLength = stylesCount + 2;
3236 if (newLength > styles.length) {
3237 int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
3238 StyleItem[] newStyles = new StyleItem[newSize];
3239 System.arraycopy(styles, 0, newStyles, 0, stylesCount);
3240 styles = newStyles;
3241 }
3242 System.arraycopy(styles, modifyEnd + 1, styles, modifyEnd + 3, stylesCount - modifyEnd - 1);
3243 StyleItem item = new StyleItem();
3244 item.start = start;
3245 item.style = style;
3246 styles[modifyStart + 1] = item;
3247 item = new StyleItem();
3248 item.start = end + 1;
3249 item.style = styles[modifyStart].style;
3250 styles[modifyStart + 2] = item;
3251 stylesCount = newLength;
3252 return;
3253 }
3254 }
3255 if (start == styles[modifyStart].start) modifyStart--;
3256 if (end == styles[modifyEnd + 1].start - 1) modifyEnd++;
3257 int newLength = stylesCount + 1 - (modifyEnd - modifyStart - 1);
3258 if (newLength > styles.length) {
3259 int newSize = Math.min(newLength + 1024, Math.max(64, newLength * 2));
3260 StyleItem[] newStyles = new StyleItem[newSize];
3261 System.arraycopy(styles, 0, newStyles, 0, stylesCount);
3262 styles = newStyles;
3263 }
3264 System.arraycopy(styles, modifyEnd, styles, modifyStart + 2, stylesCount - modifyEnd);
3265 StyleItem item = new StyleItem();
3266 item.start = start;
3267 item.style = style;
3268 styles[modifyStart + 1] = item;
3269 styles[modifyStart + 2].start = end + 1;
3270 stylesCount = newLength;
3271 }
3272
3273 /**
3274 * Sets the receiver's tab list. Each value in the tab list specifies
3275 * the space in points from the origin of the text layout to the respective
3276 * tab stop. The last tab stop width is repeated continuously.
3277 *
3278 * @param tabs the new tab list
3279 *
3280 * @exception SWTException <ul>
3281 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3282 * </ul>
3283 */
3284 public void setTabs (int[] tabs) {
3285 checkLayout();
3286 if (this.tabs == null && tabs == null) return;
3287 setTabsInPixels (DPIUtil.autoScaleUp (getDevice(), tabs));
3288 }
3289
3290 void setTabsInPixels (int[] tabs) {
3291 if (Arrays.equals (this.tabs, tabs)) return;
3292 freeRuns();
3293 this.tabs = tabs;
3294 }
3295
3296 /**
3297 * Sets the receiver's text.
3298 *<p>
3299 * Note: Setting the text also clears all the styles. This method
3300 * returns without doing anything if the new text is the same as
3301 * the current text.
3302 * </p>
3303 *
3304 * @param text the new text
3305 *
3306 * @exception IllegalArgumentException <ul>
3307 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
3308 * </ul>
3309 * @exception SWTException <ul>
3310 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3311 * </ul>
3312 */
3313 public void setText (String text) {
3314 checkLayout();
3315 if (text == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
3316 if (text.equals(this.text)) return;
3317 freeRuns();
3318 this.text = text;
3319 styles = new StyleItem[2];
3320 styles[0] = new StyleItem();
3321 styles[1] = new StyleItem();
3322 styles[1].start = text.length();
3323 stylesCount = 2;
3324 }
3325
3326 /**
3327 * Sets the text direction of the receiver, which must be one
3328 * of <code>SWT.LEFT_TO_RIGHT</code>, <code>SWT.RIGHT_TO_LEFT</code>
3329 * or <code>SWT.AUTO_TEXT_DIRECTION</code>.
3330 *
3331 * <p>
3332 * <b>Warning</b>: This API is currently only implemented on Windows.
3333 * It doesn't set the base text direction on GTK and Cocoa.
3334 * </p>
3335 *
3336 * @param textDirection the new text direction
3337 *
3338 * @exception SWTException <ul>
3339 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3340 * </ul>
3341 * @since 3.103
3342 */
3343 public void setTextDirection (int textDirection) {
3344 checkLayout();
3345 int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
3346 textDirection &= mask;
3347 if (textDirection == 0) return;
3348 if (textDirection != SWT.AUTO_TEXT_DIRECTION) {
3349 if ((textDirection & SWT.LEFT_TO_RIGHT) != 0) textDirection = SWT.LEFT_TO_RIGHT;
3350 if (this.textDirection == textDirection) return;
3351 }
3352 this.textDirection = textDirection;
3353 freeRuns();
3354 }
3355
3356 /**
3357 * Sets the line width of the receiver, which determines how
3358 * text should be wrapped and aligned. The default value is
3359 * <code>-1</code> which means wrapping is disabled.
3360 *
3361 * @param width the new width
3362 *
3363 * @exception IllegalArgumentException <ul>
3364 * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li>
3365 * </ul>
3366 * @exception SWTException <ul>
3367 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3368 * </ul>
3369 *
3370 * @see #setAlignment(int)
3371 */
3372 public void setWidth (int width) {
3373 checkLayout();
3374 setWidthInPixels(width != SWT.DEFAULT ? DPIUtil.autoScaleUp(getDevice(), width) : width);
3375 }
3376
3377 void setWidthInPixels (int width) {
3378 if (width < -1 || width == 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3379 if (this.wrapWidth == width) return;
3380 freeRuns();
3381 this.wrapWidth = width;
3382 }
3383
3384 /**
3385 * Sets the wrap indent of the receiver. This indent is applied to all lines
3386 * in the paragraph except the first line.
3387 *
3388 * @param wrapIndent new wrap indent
3389 *
3390 * @exception SWTException <ul>
3391 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3392 * </ul>
3393 *
3394 * @see #setIndent(int)
3395 *
3396 * @since 3.6
3397 */
3398 public void setWrapIndent (int wrapIndent) {
3399 checkLayout();
3400 setWrapIndentInPixels(DPIUtil.autoScaleUp(getDevice(), wrapIndent));
3401 }
3402
3403 void setWrapIndentInPixels (int wrapIndent) {
3404 if (wrapIndent < 0) return;
3405 if (this.wrapIndent == wrapIndent) return;
3406 freeRuns();
3407 this.wrapIndent = wrapIndent;
3408 }
3409
3410 boolean shape (long hdc, StyleItem run, char[] chars, int[] glyphCount, int maxGlyphs, SCRIPT_PROPERTIES sp) {
3411 boolean useCMAPcheck = !sp.fComplex && !run.analysis.fNoGlyphIndex;
3412 if (useCMAPcheck) {
3413 short[] glyphs = new short[chars.length];
3414 if (OS.ScriptGetCMap(hdc, run.psc, chars, chars.length, 0, glyphs) != OS.S_OK) {
3415 if (run.psc != 0) {
3416 OS.ScriptFreeCache(run.psc);
3417 glyphCount[0] = 0;
3418 OS.MoveMemory(run.psc, new long [1], C.PTR_SIZEOF);
3419 }
3420 return false;
3421 }
3422 }
3423 int hr = OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, glyphCount);
3424 run.glyphCount = glyphCount[0];
3425 if (useCMAPcheck) return true;
3426
3427 if (hr != OS.USP_E_SCRIPT_NOT_IN_FONT) {
3428 if (run.analysis.fNoGlyphIndex) return true;
3429 SCRIPT_FONTPROPERTIES fp = new SCRIPT_FONTPROPERTIES ();
3430 fp.cBytes = SCRIPT_FONTPROPERTIES.sizeof;
3431 OS.ScriptGetFontProperties(hdc, run.psc, fp);
3432 short[] glyphs = new short[glyphCount[0]];
3433 OS.MoveMemory(glyphs, run.glyphs, glyphs.length * 2);
3434 int i;
3435 for (i = 0; i < glyphs.length; i++) {
3436 if (glyphs[i] == fp.wgDefault) break;
3437 }
3438 if (i == glyphs.length) return true;
3439 }
3440 if (run.psc != 0) {
3441 OS.ScriptFreeCache(run.psc);
3442 glyphCount[0] = 0;
3443 OS.MoveMemory(run.psc, new long [1], C.PTR_SIZEOF);
3444 }
3445 run.glyphCount = 0;
3446 return false;
3447 }
3448
3449 long createMetafileWithChars(long hdc, long hFont, char[] chars, int charCount) {
3450 long hHeap = OS.GetProcessHeap();
3451
3452 /*
3453 * The native string must remain unchanged between ScriptStringAnalyse and ScriptStringOut.
3454 * According to debugging, ScriptStringAnalyse implicitly saves string to SCRIPT_STRING_ANALYSIS.
3455 * Then, ScriptStringOut uses the saved string in internal call to ExtTextOutW.
3456 * I believe this is due to OS.SSA_METAFILE, which is documented as follows:
3457 * Write items with ExtTextOutW calls, not with glyphs.
3458 * Note: passing Java chars to native function is wrong, because JNI will allocate
3459 * temporary native string which will be deallocated upon return from ScriptStringAnalyse.
3460 */
3461 int nativeStringSize = charCount * Character.BYTES;
3462 long nativeString = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, nativeStringSize);
3463 OS.MoveMemory (nativeString, chars, nativeStringSize);
3464
3465 long ssa = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, OS.SCRIPT_STRING_ANALYSIS_sizeof());
3466 long metaFileDc = OS.CreateEnhMetaFile(hdc, null, null, null);
3467 long oldMetaFont = OS.SelectObject(metaFileDc, hFont);
3468 int flags = OS.SSA_METAFILE | OS.SSA_FALLBACK | OS.SSA_GLYPHS | OS.SSA_LINK;
3469 if (OS.ScriptStringAnalyse(metaFileDc, nativeString, charCount, 0, -1, flags, 0, null, null, 0, 0, 0, ssa) == OS.S_OK) {
3470 OS.ScriptStringOut(ssa, 0, 0, 0, null, 0, 0, false);
3471 OS.ScriptStringFree(ssa);
3472 }
3473 OS.HeapFree(hHeap, 0, nativeString);
3474 OS.HeapFree(hHeap, 0, ssa);
3475 OS.SelectObject(metaFileDc, oldMetaFont);
3476 return OS.CloseEnhMetaFile(metaFileDc);
3477 }
3478
3479 /*
3480 * Generate glyphs for one Run.
3481 */
3482 void shape (final long hdc, final StyleItem run) {
3483 if (run.lineBreak) return;
3484 if (run.glyphs != 0) return;
3485 final int[] buffer = new int[1];
3486 final char[] chars = new char[run.length];
3487 segmentsText.getChars(run.start, run.start + run.length, chars, 0);
3488
3489 final int maxGlyphs = (chars.length * 3 / 2) + 16;
3490 long hHeap = OS.GetProcessHeap();
3491 run.glyphs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
3492 if (run.glyphs == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3493 run.clusters = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * 2);
3494 if (run.clusters == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3495 run.visAttrs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, maxGlyphs * SCRIPT_VISATTR_SIZEOF);
3496 if (run.visAttrs == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3497 run.psc = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, C.PTR_SIZEOF);
3498 if (run.psc == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3499 final short script = run.analysis.eScript;
3500 final SCRIPT_PROPERTIES sp = new SCRIPT_PROPERTIES();
3501 OS.MoveMemory(sp, device.scripts[script], SCRIPT_PROPERTIES.sizeof);
3502 boolean shapeSucceed = shape(hdc, run, chars, buffer, maxGlyphs, sp);
3503 if (!shapeSucceed) {
3504 if (sp.fPrivateUseArea) {
3505 run.analysis.fNoGlyphIndex = true;
3506 shapeSucceed = shape(hdc, run, chars, buffer, maxGlyphs, sp);
3507 }
3508 }
3509 if (!shapeSucceed) {
3510 long hFont = OS.GetCurrentObject(hdc, OS.OBJ_FONT);
3511 long newFont = 0;
3512 /*
3513 * Bug in Uniscribe. In some version of Uniscribe, ScriptStringAnalyse crashes
3514 * when the character array is too long. The fix is to limit the size of character
3515 * array to two. Note, limiting the array to only one character would cause surrogate
3516 * pairs to stop working.
3517 */
3518 char[] sampleChars = new char[Math.min(chars.length, 2)];
3519 SCRIPT_LOGATTR logAttr = new SCRIPT_LOGATTR();
3520 breakRun(run);
3521 int count = 0;
3522 for (int i = 0; i < chars.length; i++) {
3523 OS.MoveMemory(logAttr, run.psla + (i * SCRIPT_LOGATTR.sizeof), SCRIPT_LOGATTR.sizeof);
3524 if (!logAttr.fWhiteSpace) {
3525 sampleChars[count++] = chars[i];
3526 if (count == sampleChars.length) break;
3527 }
3528 }
3529 if (count > 0) {
3530 long metaFile = createMetafileWithChars(hdc, hFont, sampleChars, count);
3531 final EMREXTCREATEFONTINDIRECTW emr = new EMREXTCREATEFONTINDIRECTW();
3532 class MetaFileEnumProc {
3533 long metaFileEnumProc (long hDC, long table, long record, long nObj, long lpData) {
3534 OS.MoveMemory(emr.emr, record, EMR.sizeof);
3535 switch (emr.emr.iType) {
3536 case OS.EMR_EXTCREATEFONTINDIRECTW:
3537 OS.MoveMemory(emr, record, EMREXTCREATEFONTINDIRECTW.sizeof);
3538 break;
3539 case OS.EMR_EXTTEXTOUTW:
3540 return 0;
3541 }
3542 return 1;
3543 }
3544 }
3545 MetaFileEnumProc object = new MetaFileEnumProc();
3546 /* Avoid compiler warnings */
3547 boolean compilerWarningWorkaround = false;
3548 if (compilerWarningWorkaround) object.metaFileEnumProc(0, 0, 0, 0, 0);
3549 Callback callback = new Callback(object, "metaFileEnumProc", 5);
3550 OS.EnumEnhMetaFile(0, metaFile, callback.getAddress(), 0, null);
3551 OS.DeleteEnhMetaFile(metaFile);
3552 callback.dispose();
3553 newFont = OS.CreateFontIndirect(emr.elfw.elfLogFont);
3554 } else {
3555 /*
3556 * The run is composed only by white spaces, this happens when a run is split
3557 * by a visual style. The font fallback for the script can not be determined
3558 * using only white spaces. The solution is to use the font of the previous
3559 * or next run of the same script.
3560 */
3561 int index = 0;
3562 while (index < allRuns.length - 1) {
3563 if (allRuns[index] == run) {
3564 if (index > 0) {
3565 StyleItem pRun = allRuns[index - 1];
3566 if (pRun.analysis.eScript == run.analysis.eScript) {
3567 long pFont = getItemFont(pRun);
3568 LOGFONT logFont = new LOGFONT ();
3569 OS.GetObject(pFont, LOGFONT.sizeof, logFont);
3570 newFont = OS.CreateFontIndirect(logFont);
3571 }
3572 }
3573 if (newFont == 0) {
3574 if (index + 1 < allRuns.length - 1) {
3575 StyleItem nRun = allRuns[index + 1];
3576 if (nRun.analysis.eScript == run.analysis.eScript) {
3577 OS.SelectObject(hdc, getItemFont(nRun));
3578 shape(hdc, nRun);
3579 long nFont = getItemFont(nRun);
3580 LOGFONT logFont = new LOGFONT ();
3581 OS.GetObject(nFont, LOGFONT.sizeof, logFont);
3582 newFont = OS.CreateFontIndirect(logFont);
3583 }
3584 }
3585 }
3586 break;
3587 }
3588 index++;
3589 }
3590 }
3591 if (newFont != 0) {
3592 OS.SelectObject(hdc, newFont);
3593 if (shapeSucceed = shape(hdc, run, chars, buffer, maxGlyphs, sp)) {
3594 run.fallbackFont = newFont;
3595 }
3596 }
3597 if (!shapeSucceed) {
3598 if (!sp.fComplex) {
3599 run.analysis.fNoGlyphIndex = true;
3600 if (shapeSucceed = shape(hdc, run, chars, buffer, maxGlyphs, sp)) {
3601 run.fallbackFont = newFont;
3602 } else {
3603 run.analysis.fNoGlyphIndex = false;
3604 }
3605 }
3606 }
3607 if (!shapeSucceed) {
3608 if (mLangFontLink2 != null) {
3609 long [] hNewFont = new long [1];
3610 int[] dwCodePages = new int[1], cchCodePages = new int[1];
3611 mLangFontLink2.GetStrCodePages(chars, chars.length, 0, dwCodePages, cchCodePages);
3612 if (mLangFontLink2.MapFont(hdc, dwCodePages[0], chars[0], hNewFont) == OS.S_OK) {
3613 LOGFONT logFont = new LOGFONT ();
3614 OS.GetObject(hNewFont[0], LOGFONT.sizeof, logFont);
3615 mLangFontLink2.ReleaseFont(hNewFont[0]);
3616 long mLangFont = OS.CreateFontIndirect(logFont);
3617 long oldFont = OS.SelectObject(hdc, mLangFont);
3618 if (shapeSucceed = shape(hdc, run, chars, buffer, maxGlyphs, sp)) {
3619 run.fallbackFont = mLangFont;
3620 } else {
3621 OS.SelectObject(hdc, oldFont);
3622 OS.DeleteObject(mLangFont);
3623 }
3624 }
3625 }
3626 }
3627 if (!shapeSucceed) OS.SelectObject(hdc, hFont);
3628 if (newFont != 0 && newFont != run.fallbackFont) OS.DeleteObject(newFont);
3629 }
3630
3631 if (!shapeSucceed) {
3632 /*
3633 * Shape Failed.
3634 * Give up and shape the run with the default font.
3635 * Missing glyphs typically will be represent as black boxes in the text.
3636 */
3637 OS.ScriptShape(hdc, run.psc, chars, chars.length, maxGlyphs, run.analysis, run.glyphs, run.clusters, run.visAttrs, buffer);
3638 run.glyphCount = buffer[0];
3639 }
3640 int[] abc = new int[3];
3641 run.advances = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * 4);
3642 if (run.advances == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3643 run.goffsets = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, run.glyphCount * GOFFSET_SIZEOF);
3644 if (run.goffsets == 0) SWT.error(SWT.ERROR_NO_HANDLES);
3645 OS.ScriptPlace(hdc, run.psc, run.glyphs, run.glyphCount, run.visAttrs, run.analysis, run.advances, run.goffsets, abc);
3646 run.width = abc[0] + abc[1] + abc[2];
3647 TextStyle style = run.style;
3648 if (style != null) {
3649 OUTLINETEXTMETRIC lotm = null;
3650 if (style.underline || style.strikeout) {
3651 lotm = new OUTLINETEXTMETRIC();
3652 if (OS.GetOutlineTextMetrics(hdc, OUTLINETEXTMETRIC.sizeof, lotm) == 0) {
3653 lotm = null;
3654 }
3655 }
3656 if (style.metrics != null) {
3657 GlyphMetrics metrics = style.metrics;
3658 /*
3659 * Bug in Windows, on a Japanese machine, Uniscribe returns glyphcount
3660 * equals zero for FFFC (possibly other unicode code points), the fix
3661 * is to make sure the glyph is at least one pixel wide.
3662 */
3663 run.width = metrics.getWidthInPixels() * Math.max (1, run.glyphCount);
3664 run.ascentInPoints = metrics.ascent;
3665 run.descentInPoints = metrics.descent;
3666 run.leadingInPoints = 0;
3667 } else {
3668 TEXTMETRIC lptm = null;
3669 if (lotm != null) {
3670 lptm = lotm.otmTextMetrics;
3671 } else {
3672 lptm = new TEXTMETRIC();
3673 OS.GetTextMetrics(hdc, lptm);
3674 }
3675 run.ascentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmAscent);
3676 run.descentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmDescent);
3677 run.leadingInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmInternalLeading);
3678 }
3679 if (lotm != null) {
3680 run.underlinePos = lotm.otmsUnderscorePosition;
3681 run.underlineThickness = Math.max(1, lotm.otmsUnderscoreSize);
3682 run.strikeoutPos = lotm.otmsStrikeoutPosition;
3683 run.strikeoutThickness = Math.max(1, lotm.otmsStrikeoutSize);
3684 } else {
3685 run.underlinePos = 1;
3686 run.underlineThickness = 1;
3687 run.strikeoutPos = DPIUtil.autoScaleUp(getDevice(), run.ascentInPoints) / 2;
3688 run.strikeoutThickness = 1;
3689 }
3690 run.ascentInPoints += style.rise;
3691 run.descentInPoints -= style.rise;
3692 } else {
3693 TEXTMETRIC lptm = new TEXTMETRIC();
3694 OS.GetTextMetrics(hdc, lptm);
3695 run.ascentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmAscent);
3696 run.descentInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmDescent);
3697 run.leadingInPoints = DPIUtil.autoScaleDown(getDevice(), lptm.tmInternalLeading);
3698 }
3699 }
3700
3701 int validadeOffset(int offset, int step) {
3702 offset = untranslateOffset(offset);
3703 return translateOffset(offset + step);
3704 }
3705
3706 /**
3707 * Returns a string containing a concise, human-readable
3708 * description of the receiver.
3709 *
3710 * @return a string representation of the receiver
3711 */
3712 @Override
3713 public String toString () {
3714 if (isDisposed()) return "TextLayout {*DISPOSED*}";
3715 return "TextLayout {}";
3716 }
3717
3718 int translateOffset(int offset) {
3719 int length = text.length();
3720 if (length == 0) return offset;
3721 if (segments == null) return offset;
3722 int nSegments = segments.length;
3723 if (nSegments == 0) return offset;
3724 if (segmentsChars == null) {
3725 if (nSegments == 1) return offset;
3726 if (nSegments == 2) {
3727 if (segments[0] == 0 && segments[1] == length) return offset;
3728 }
3729 }
3730 for (int i = 0; i < nSegments && offset - i >= segments[i]; i++) {
3731 offset++;
3732 }
3733 return offset;
3734 }
3735
3736 int untranslateOffset(int offset) {
3737 int length = text.length();
3738 if (length == 0) return offset;
3739 if (segments == null) return offset;
3740 int nSegments = segments.length;
3741 if (nSegments == 0) return offset;
3742 if (segmentsChars == null) {
3743 if (nSegments == 1) return offset;
3744 if (nSegments == 2) {
3745 if (segments[0] == 0 && segments[1] == length) return offset;
3746 }
3747 }
3748 for (int i = 0; i < nSegments && offset > segments[i]; i++) {
3749 offset--;
3750 }
3751 return offset;
3752 }
3753
3754 /**
3755 * Sets Default Tab Width in terms if number of space characters.
3756 *
3757 * @param tabLength in number of characters
3758 *
3759 * @exception IllegalArgumentException <ul>
3760 * <li>ERROR_INVALID_ARGUMENT - if the tabLength is less than <code>0</code></li>
3761 * </ul>
3762 * @exception SWTException <ul>
3763 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
3764 * </ul>
3765 *
3766 * @noreference This method is not intended to be referenced by clients.
3767 *
3768 * DO NOT USE This might be removed in 4.8
3769 * @since 3.107
3770 */
3771 public void setDefaultTabWidth(int tabLength) {
3772 // unused in win32
3773 }
3774 }
3775