1 /******************************************************************************* 2 * Copyright (c) 2000, 2008 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 * Port to the D programming language: 11 * Frank Benoit <benoit@tionex.de> 12 *******************************************************************************/ 13 module org.eclipse.swt.graphics.TextLayout; 14 15 import org.eclipse.swt.internal.Compatibility; 16 import org.eclipse.swt.internal.cairo.Cairo : Cairo; 17 import org.eclipse.swt.internal.gtk.OS; 18 import org.eclipse.swt.internal.Converter; 19 import org.eclipse.swt.SWT; 20 import org.eclipse.swt.graphics.Color; 21 import org.eclipse.swt.graphics.Device; 22 import org.eclipse.swt.graphics.Font; 23 import org.eclipse.swt.graphics.FontMetrics; 24 import org.eclipse.swt.graphics.GC; 25 import org.eclipse.swt.graphics.GCData; 26 import org.eclipse.swt.graphics.GlyphMetrics; 27 import org.eclipse.swt.graphics.Point; 28 import org.eclipse.swt.graphics.Rectangle; 29 import org.eclipse.swt.graphics.Region; 30 import org.eclipse.swt.graphics.Resource; 31 import org.eclipse.swt.graphics.TextStyle; 32 import java.lang.all; 33 import java.nonstandard.UnsafeUtf; 34 35 version(Tango){ 36 import tango.stdc..string : memmove; 37 import tango.text.convert.Utf; 38 } else { // Phobos 39 import core.stdc..string : memmove; 40 } 41 42 /** 43 * <code>TextLayout</code> is a graphic object that represents 44 * styled text. 45 * <p> 46 * Instances of this class provide support for drawing, cursor 47 * navigation, hit testing, text wrapping, alignment, tab expansion 48 * line breaking, etc. These are aspects required for rendering internationalized text. 49 * </p><p> 50 * Application code must explicitly invoke the <code>TextLayout#dispose()</code> 51 * method to release the operating system resources managed by each instance 52 * when those instances are no longer required. 53 * </p> 54 * 55 * @see <a href="http://www.eclipse.org/swt/snippets/#textlayout">TextLayout, TextStyle snippets</a> 56 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample, StyledText tab</a> 57 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> 58 * 59 * @since 3.0 60 */ 61 public final class TextLayout : Resource { 62 63 static class StyleItem { 64 TextStyle style; 65 int start; 66 67 public override String toString () { 68 return Format( "StyleItem {{{}, {}}", start, style ); 69 } 70 } 71 72 Font font; 73 String text; 74 int ascent, descent; 75 int[] segments; 76 int[] tabs; 77 StyleItem[] styles; 78 PangoLayout* layout; 79 PangoContext* context; 80 PangoAttrList* attrList; 81 int[] invalidOffsets; 82 // LTR_MARK LEFT-TO-RIGHT MARK 83 // RTL_MARK RIGHT-TO-LEFT MARK 84 // ZWS ZERO WIDTH SPACE 85 // ZWNBS ZERO WIDTH NO-BREAK SPACE 86 static const dchar LTR_MARK = '\u200E'; // x"E2 80 8E" LEFT-TO-RIGHT MARK 87 static const dchar RTL_MARK = '\u200F'; // x"E2 80 8F" RIGHT-TO-LEFT MARK 88 static const dchar ZWS = '\u200B'; // x"E2 80 8B" ZERO WIDTH SPACE 89 static const dchar ZWNBS = '\uFEFF'; // x"EF BB BF" ZERO WIDTH NO-BREAK SPACE 90 static const String STR_LTR_MARK = "\u200E"; 91 static const String STR_RTL_MARK = "\u200F"; 92 static const String STR_ZWS = "\u200B"; 93 static const String STR_ZWNBS = "\uFEFF"; 94 95 /** 96 * Constructs a new instance of this class on the given device. 97 * <p> 98 * You must dispose the text layout when it is no longer required. 99 * </p> 100 * 101 * @param device the device on which to allocate the text layout 102 * 103 * @exception IllegalArgumentException <ul> 104 * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li> 105 * </ul> 106 * 107 * @see #dispose() 108 */ 109 public this (Device device) { 110 super(device); 111 device = this.device; 112 context = OS.gdk_pango_context_get(); 113 if (context is null) SWT.error(SWT.ERROR_NO_HANDLES); 114 OS.pango_context_set_language(context, OS.gtk_get_default_language()); 115 OS.pango_context_set_base_dir(context, OS.PANGO_DIRECTION_LTR); 116 OS.gdk_pango_context_set_colormap(context, OS.gdk_colormap_get_system()); 117 layout = OS.pango_layout_new(context); 118 if (layout is null) SWT.error(SWT.ERROR_NO_HANDLES); 119 OS.pango_layout_set_font_description(layout, device.systemFont.handle); 120 OS.pango_layout_set_wrap(layout, OS.PANGO_WRAP_WORD_CHAR); 121 OS.pango_layout_set_tabs(layout, device.emptyTab); 122 if (OS.GTK_VERSION >= OS.buildVERSION(2, 4, 0)) { 123 OS.pango_layout_set_auto_dir(layout, false); 124 } 125 text = ""; 126 ascent = descent = -1; 127 styles = new StyleItem[2]; 128 styles[0] = new StyleItem(); 129 styles[1] = new StyleItem(); 130 init_(); 131 } 132 133 void checkLayout() { 134 if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); 135 } 136 137 void computeRuns () { 138 if (attrList !is null) return; 139 String segmentsText = getSegmentsText(); 140 OS.pango_layout_set_text (layout, segmentsText.ptr, 141 cast(int)/*64bit*/segmentsText.length); 142 if (styles.length is 2 && styles[0].style is null && ascent is -1 && descent is -1 && segments is null) return; 143 auto ptr = OS.pango_layout_get_text(layout); 144 attrList = OS.pango_attr_list_new(); 145 PangoAttribute* attribute; 146 char[] chars = null; 147 auto segementsLength = segmentsText.length; 148 if ((ascent !is -1 || descent !is -1) && segementsLength > 0) { 149 PangoRectangle rect; 150 if (ascent !is -1) rect.y = -(ascent * OS.PANGO_SCALE); 151 rect.height = (Math.max(0, ascent) + Math.max(0, descent)) * OS.PANGO_SCALE; 152 int lineCount = OS.pango_layout_get_line_count(layout); 153 chars = new char[segementsLength + lineCount * 6/*2*/]; 154 int oldPos = 0, lineIndex = 0; 155 while (lineIndex < lineCount) { 156 auto line = OS.pango_layout_get_line(layout, lineIndex); 157 int bytePos = line.start_index; 158 /* Note: The length in bytes of ZWS and ZWNBS are both equals to 3 */ 159 int offset = lineIndex * 6; 160 PangoAttribute* attr = OS.pango_attr_shape_new (&rect, &rect); 161 attribute = attr; 162 attribute.start_index = bytePos + offset; 163 attribute.end_index = bytePos + offset + 3; 164 OS.pango_attr_list_insert(attrList, attr); 165 attr = OS.pango_attr_shape_new (&rect, &rect); 166 attribute = attr; 167 attribute.start_index = bytePos + offset + 3; 168 attribute.end_index = bytePos + offset + 6; 169 OS.pango_attr_list_insert(attrList, attr); 170 int pos = bytePos;//OS.g_utf8_pointer_to_offset(ptr, ptr + bytePos); 171 chars[pos + lineIndex * 6 +0 .. pos + lineIndex * 6 + 3] = STR_ZWS; 172 chars[pos + lineIndex * 6 +3 .. pos + lineIndex * 6 + 6] = STR_ZWNBS; 173 chars[ oldPos + lineIndex*6 .. oldPos + lineIndex*6 + pos - oldPos ] = 174 segmentsText[ oldPos .. pos ]; 175 oldPos = pos; 176 lineIndex++; 177 } 178 segmentsText.getChars(oldPos, cast(int)/*64bit*/segementsLength, chars, oldPos + lineIndex * 6); 179 auto buffer = chars;// Converter.wcsToMbcs(null, chars, false); 180 181 OS.pango_layout_set_text (layout, buffer.ptr, cast(int)/*64bit*/buffer.length); 182 ptr = OS.pango_layout_get_text(layout); 183 } else { 184 chars = segmentsText.dup; 185 } 186 int offsetCount = 0; 187 { 188 int i = 0; 189 while( i < chars.length ){ 190 ptrdiff_t incr; 191 dchar c = chars.dcharAt(i, incr); 192 if (c is LTR_MARK || c is RTL_MARK || c is ZWNBS || c is ZWS) { 193 offsetCount+=3; 194 } 195 i += incr; 196 } 197 } 198 invalidOffsets = new int[offsetCount]; 199 offsetCount = 0; 200 { 201 int i = 0; 202 while( i < chars.length ){ 203 ptrdiff_t incr; 204 dchar c = chars.dcharAt(i, incr); 205 if (c is LTR_MARK || c is RTL_MARK || c is ZWNBS || c is ZWS) { 206 invalidOffsets[offsetCount++] = i; 207 invalidOffsets[offsetCount++] = i+1; 208 invalidOffsets[offsetCount++] = i+2; 209 } 210 i += incr; 211 } 212 } 213 int slen = OS.strlen(ptr); 214 Font defaultFont = font !is null ? font : device.systemFont; 215 for (int i = 0; i < styles.length - 1; i++) { 216 StyleItem styleItem = styles[i]; 217 TextStyle style = styleItem.style; 218 if (style is null) continue; 219 int start = translateOffset(styleItem.start); 220 int end = translateOffset(styles[i+1].start - 1); 221 int byteStart = start;//(OS.g_utf8_offset_to_pointer(ptr, start) - ptr); 222 int byteEnd = end+1;//(OS.g_utf8_offset_to_pointer(ptr, end + 1) - ptr); 223 byteStart = Math.min(byteStart, slen); 224 byteEnd = Math.min(byteEnd, slen); 225 Font font = style.font; 226 if (font !is null && !font.isDisposed() && !defaultFont.opEquals(font)) { 227 auto attr = OS.pango_attr_font_desc_new (font.handle); 228 attr.start_index = byteStart; 229 attr.end_index = byteEnd; 230 OS.pango_attr_list_insert(attrList, attr); 231 } 232 if (style.underline) { 233 int underlineStyle = OS.PANGO_UNDERLINE_NONE; 234 switch (style.underlineStyle) { 235 case SWT.UNDERLINE_SINGLE: 236 underlineStyle = OS.PANGO_UNDERLINE_SINGLE; 237 break; 238 case SWT.UNDERLINE_DOUBLE: 239 underlineStyle = OS.PANGO_UNDERLINE_DOUBLE; 240 break; 241 case SWT.UNDERLINE_SQUIGGLE: 242 case SWT.UNDERLINE_ERROR: 243 if (OS.GTK_VERSION >= OS.buildVERSION(2, 4, 0)) { 244 underlineStyle = OS.PANGO_UNDERLINE_ERROR; 245 } 246 break; 247 default: break; 248 } 249 if (underlineStyle !is OS.PANGO_UNDERLINE_NONE && style.underlineColor is null) { 250 auto attr = OS.pango_attr_underline_new(underlineStyle); 251 attr.start_index = byteStart; 252 attr.end_index = byteEnd; 253 OS.pango_attr_list_insert(attrList, attr); 254 } 255 } 256 if (style.strikeout && style.strikeoutColor is null) { 257 auto attr = OS.pango_attr_strikethrough_new(true); 258 attr.start_index = byteStart; 259 attr.end_index = byteEnd; 260 OS.pango_attr_list_insert(attrList, attr); 261 } 262 Color foreground = style.foreground; 263 if (foreground !is null && !foreground.isDisposed()) { 264 GdkColor* fg = foreground.handle; 265 auto attr = OS.pango_attr_foreground_new(fg.red, fg.green, fg.blue); 266 attr.start_index = byteStart; 267 attr.end_index = byteEnd; 268 OS.pango_attr_list_insert(attrList, attr); 269 } 270 Color background = style.background; 271 if (background !is null && !background.isDisposed()) { 272 GdkColor* bg = background.handle; 273 auto attr = OS.pango_attr_background_new(bg.red, bg.green, bg.blue); 274 attr.start_index = byteStart; 275 attr.end_index = byteEnd; 276 OS.pango_attr_list_insert(attrList, attr); 277 } 278 GlyphMetrics metrics = style.metrics; 279 if (metrics !is null) { 280 PangoRectangle rect; 281 rect.y = -(metrics.ascent * OS.PANGO_SCALE); 282 rect.height = (metrics.ascent + metrics.descent) * OS.PANGO_SCALE; 283 rect.width = metrics.width * OS.PANGO_SCALE; 284 auto attr = OS.pango_attr_shape_new (&rect, &rect); 285 attr.start_index = byteStart; 286 attr.end_index = byteEnd; 287 OS.pango_attr_list_insert(attrList, attr); 288 } 289 int rise = style.rise; 290 if (rise !is 0) { 291 auto attr = OS.pango_attr_rise_new (rise * OS.PANGO_SCALE); 292 attr.start_index = byteStart; 293 attr.end_index = byteEnd; 294 OS.pango_attr_list_insert(attrList, attr); 295 } 296 } 297 OS.pango_layout_set_attributes(layout, attrList); 298 } 299 300 int[] computePolyline(int left, int top, int right, int bottom) { 301 int height = bottom - top; // can be any number 302 int width = 2 * height; // must be even 303 int peaks = Compatibility.ceil(right - left, width); 304 if (peaks is 0 && right - left > 2) { 305 peaks = 1; 306 } 307 int length_ = ((2 * peaks) + 1) * 2; 308 if (length_ < 0) return new int[0]; 309 310 int[] coordinates = new int[length_]; 311 for (int i = 0; i < peaks; i++) { 312 int index = 4 * i; 313 coordinates[index] = left + (width * i); 314 coordinates[index+1] = bottom; 315 coordinates[index+2] = coordinates[index] + width / 2; 316 coordinates[index+3] = top; 317 } 318 coordinates[length_-2] = left + (width * peaks); 319 coordinates[length_-1] = bottom; 320 return coordinates; 321 } 322 323 override 324 void destroy() { 325 font = null; 326 text = null; 327 styles = null; 328 freeRuns(); 329 if (layout !is null) OS.g_object_unref(layout); 330 layout = null; 331 if (context !is null) OS.g_object_unref(context); 332 context = null; 333 } 334 335 /** 336 * Draws the receiver's text using the specified GC at the specified 337 * point. 338 * 339 * @param gc the GC to draw 340 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn 341 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn 342 * 343 * @exception SWTException <ul> 344 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 345 * </ul> 346 * @exception IllegalArgumentException <ul> 347 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> 348 * </ul> 349 */ 350 public void draw(GC gc, int x, int y) { 351 draw(gc, x, y, -1, -1, null, null); 352 } 353 354 /** 355 * Draws the receiver's text using the specified GC at the specified 356 * point. 357 * 358 * @param gc the GC to draw 359 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn 360 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn 361 * @param selectionStart the offset where the selections starts, or -1 indicating no selection 362 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection 363 * @param selectionForeground selection foreground, or NULL to use the system default color 364 * @param selectionBackground selection background, or NULL to use the system default color 365 * 366 * @exception SWTException <ul> 367 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 368 * </ul> 369 * @exception IllegalArgumentException <ul> 370 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> 371 * </ul> 372 */ 373 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { 374 draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0); 375 } 376 377 /** 378 * Draws the receiver's text using the specified GC at the specified 379 * point. 380 * <p> 381 * The parameter <code>flags</code> can include one of <code>SWT.DELIMITER_SELECTION</code> 382 * or <code>SWT.FULL_SELECTION</code> to specify the selection behavior on all lines except 383 * for the last line, and can also include <code>SWT.LAST_LINE_SELECTION</code> to extend 384 * the specified selection behavior to the last line. 385 * </p> 386 * @param gc the GC to draw 387 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn 388 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn 389 * @param selectionStart the offset where the selections starts, or -1 indicating no selection 390 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection 391 * @param selectionForeground selection foreground, or NULL to use the system default color 392 * @param selectionBackground selection background, or NULL to use the system default color 393 * @param flags drawing options 394 * 395 * @exception SWTException <ul> 396 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 397 * </ul> 398 * @exception IllegalArgumentException <ul> 399 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> 400 * </ul> 401 * 402 * @since 3.3 403 */ 404 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { 405 checkLayout (); 406 computeRuns(); 407 if (gc is null) SWT.error(SWT.ERROR_NULL_ARGUMENT); 408 if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 409 if (selectionForeground !is null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 410 if (selectionBackground !is null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 411 gc.checkGC(GC.FOREGROUND); 412 auto length_ = text.length; 413 bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1; 414 GCData data = gc.data; 415 auto cairo = data.cairo; 416 if (flags !is 0 && (hasSelection || (flags & SWT.LAST_LINE_SELECTION) !is 0)) { 417 PangoLogAttr* attrs; 418 int nAttrs; 419 PangoLogAttr* logAttr = new PangoLogAttr(); 420 PangoRectangle rect; 421 int lineCount = OS.pango_layout_get_line_count(layout); 422 auto ptr = OS.pango_layout_get_text(layout); 423 auto iter = OS.pango_layout_get_iter(layout); 424 if (selectionBackground is null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION); 425 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 426 Cairo.cairo_save(cairo); 427 GdkColor* color = selectionBackground.handle; 428 Cairo.cairo_set_source_rgba(cairo, (color.red & 0xFFFF) / cast(float)0xFFFF, (color.green & 0xFFFF) / cast(float)0xFFFF, (color.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 429 } else { 430 OS.gdk_gc_set_foreground(gc.handle, selectionBackground.handle); 431 } 432 int lineIndex = 0; 433 do { 434 int lineEnd; 435 OS.pango_layout_iter_get_line_extents(iter, null, &rect); 436 if (OS.pango_layout_iter_next_line(iter)) { 437 int bytePos = OS.pango_layout_iter_get_index(iter); 438 lineEnd = bytePos;//OS.g_utf8_pointer_to_offset(ptr, ptr + bytePos); 439 } else { 440 lineEnd = cast(int)/*64bit*/OS.g_utf8_strlen(ptr, -1); 441 } 442 bool extent = false; 443 if (lineIndex is lineCount - 1 && (flags & SWT.LAST_LINE_SELECTION) !is 0) { 444 extent = true; 445 } else { 446 if (attrs is null) OS.pango_layout_get_log_attrs(layout, &attrs, &nAttrs); 447 *logAttr = attrs[lineEnd]; 448 if (!( logAttr.bitfield0 & 0x01 /* PangoLogAttr.is_line_break is Bit0 */)) { 449 if (selectionStart <= lineEnd && lineEnd <= selectionEnd) extent = true; 450 } else { 451 if (selectionStart <= lineEnd && lineEnd < selectionEnd && (flags & SWT.FULL_SELECTION) !is 0) { 452 extent = true; 453 } 454 } 455 } 456 if (extent) { 457 int lineX = x + OS.PANGO_PIXELS(rect.x) + OS.PANGO_PIXELS(rect.width); 458 int lineY = y + OS.PANGO_PIXELS(rect.y); 459 int height = OS.PANGO_PIXELS(rect.height); 460 if (ascent !is -1 && descent !is -1) { 461 height = Math.max (height, ascent + descent); 462 } 463 int width = (flags & SWT.FULL_SELECTION) !is 0 ? 0x7fffffff : height / 3; 464 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 465 Cairo.cairo_rectangle(cairo, lineX, lineY, width, height); 466 Cairo.cairo_fill(cairo); 467 } else { 468 OS.gdk_draw_rectangle(data.drawable, gc.handle, 1, lineX, lineY, width, height); 469 } 470 } 471 lineIndex++; 472 } while (lineIndex < lineCount); 473 OS.pango_layout_iter_free(iter); 474 if (attrs !is null) OS.g_free(attrs); 475 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 476 Cairo.cairo_restore(cairo); 477 } else { 478 OS.gdk_gc_set_foreground(gc.handle, data.foreground); 479 } 480 } 481 if (length_ is 0) return; 482 if (!hasSelection) { 483 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 484 if ((data.style & SWT.MIRRORED) !is 0) { 485 Cairo.cairo_save(cairo); 486 Cairo.cairo_scale(cairo, -1, 1); 487 Cairo.cairo_translate(cairo, -2 * x - width(), 0); 488 } 489 Cairo.cairo_move_to(cairo, x, y); 490 OS.pango_cairo_show_layout(cairo, layout); 491 drawBorder(gc, x, y, null); 492 if ((data.style & SWT.MIRRORED) !is 0) { 493 Cairo.cairo_restore(cairo); 494 } 495 } else { 496 OS.gdk_draw_layout(data.drawable, gc.handle, x, y, layout); 497 drawBorder(gc, x, y, null); 498 } 499 } else { 500 selectionStart = cast(int)/*64bit*/(Math.min(Math.max 501 (0, selectionStart), length_ - 1)); 502 selectionEnd = cast(int)/*64bit*/(Math.min(Math.max 503 (0, selectionEnd), length_ - 1)); 504 length_ = OS.g_utf8_strlen(OS.pango_layout_get_text(layout), -1); 505 selectionStart = translateOffset(selectionStart); 506 selectionEnd = translateOffset(selectionEnd); 507 if (selectionForeground is null) selectionForeground = device.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); 508 if (selectionBackground is null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION); 509 bool fullSelection = selectionStart is 0 && selectionEnd is length_ - 1; 510 if (fullSelection) { 511 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 512 auto ptr = OS.pango_layout_get_text(layout); 513 if ((data.style & SWT.MIRRORED) !is 0) { 514 Cairo.cairo_save(cairo); 515 Cairo.cairo_scale(cairo, -1, 1); 516 Cairo.cairo_translate(cairo, -2 * x - width(), 0); 517 } 518 drawWithCairo(gc, x, y, 0, OS.strlen(ptr), fullSelection, selectionForeground.handle, selectionBackground.handle); 519 if ((data.style & SWT.MIRRORED) !is 0) { 520 Cairo.cairo_restore(cairo); 521 } 522 } else { 523 OS.gdk_draw_layout_with_colors(data.drawable, gc.handle, x, y, layout, selectionForeground.handle, selectionBackground.handle); 524 drawBorder(gc, x, y, selectionForeground.handle); 525 } 526 } else { 527 auto ptr = OS.pango_layout_get_text(layout); 528 int byteSelStart = selectionStart;//(OS.g_utf8_offset_to_pointer(ptr, selectionStart) - ptr); 529 int byteSelEnd = selectionEnd + 1;//(OS.g_utf8_offset_to_pointer(ptr, selectionEnd + 1) - ptr); 530 int slen = OS.strlen(ptr); 531 byteSelStart = Math.min(byteSelStart, slen); 532 byteSelEnd = Math.min(byteSelEnd, slen); 533 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 534 if ((data.style & SWT.MIRRORED) !is 0) { 535 Cairo.cairo_save(cairo); 536 Cairo.cairo_scale(cairo, -1, 1); 537 Cairo.cairo_translate(cairo, -2 * x - width(), 0); 538 } 539 drawWithCairo(gc, x, y, byteSelStart, byteSelEnd, fullSelection, selectionForeground.handle, selectionBackground.handle); 540 if ((data.style & SWT.MIRRORED) !is 0) { 541 Cairo.cairo_restore(cairo); 542 } 543 } else { 544 Region clipping = new Region(); 545 gc.getClipping(clipping); 546 OS.gdk_draw_layout(data.drawable, gc.handle, x, y, layout); 547 drawBorder(gc, x, y, null); 548 int[] ranges = [byteSelStart, byteSelEnd]; 549 auto rgn = OS.gdk_pango_layout_get_clip_region(layout, x, y, 550 ranges.ptr, cast(int)/*64bit*/ranges.length / 2); 551 if (rgn !is null) { 552 OS.gdk_gc_set_clip_region(gc.handle, rgn); 553 OS.gdk_region_destroy(rgn); 554 } 555 OS.gdk_draw_layout_with_colors(data.drawable, gc.handle, x, y, layout, selectionForeground.handle, selectionBackground.handle); 556 drawBorder(gc, x, y, selectionForeground.handle); 557 gc.setClipping(clipping); 558 clipping.dispose(); 559 } 560 } 561 } 562 } 563 564 void drawWithCairo(GC gc, int x, int y, int start, int end, bool fullSelection, GdkColor* fg, GdkColor* bg) { 565 GCData data = gc.data; 566 cairo_t* cairo = data.cairo; 567 Cairo.cairo_save(cairo); 568 if (!fullSelection) { 569 Cairo.cairo_move_to(cairo, x, y); 570 OS.pango_cairo_show_layout(cairo, layout); 571 drawBorder(gc, x, y, null); 572 } 573 int[] ranges = [start, end]; 574 auto rgn = OS.gdk_pango_layout_get_clip_region(layout, x, y, ranges.ptr, 575 cast(int)/*64bit*/ranges.length / 2); 576 if (rgn !is null) { 577 OS.gdk_cairo_region(cairo, rgn); 578 Cairo.cairo_clip(cairo); 579 Cairo.cairo_set_source_rgba(cairo, (bg.red & 0xFFFF) / cast(float)0xFFFF, (bg.green & 0xFFFF) / cast(float)0xFFFF, (bg.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 580 Cairo.cairo_paint(cairo); 581 OS.gdk_region_destroy(rgn); 582 } 583 Cairo.cairo_set_source_rgba(cairo, (fg.red & 0xFFFF) / cast(float)0xFFFF, (fg.green & 0xFFFF) / cast(float)0xFFFF, (fg.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 584 Cairo.cairo_move_to(cairo, x, y); 585 OS.pango_cairo_show_layout(cairo, layout); 586 drawBorder(gc, x, y, fg); 587 Cairo.cairo_restore(cairo); 588 } 589 590 void drawBorder(GC gc, int x, int y, GdkColor* selectionColor) { 591 GCData data = gc.data; 592 auto cairo = data.cairo; 593 auto gdkGC = gc.handle; 594 auto ptr = OS.pango_layout_get_text(layout); 595 GdkGCValues* gcValues = null; 596 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 597 Cairo.cairo_save(cairo); 598 } 599 for (int i = 0; i < styles.length - 1; i++) { 600 TextStyle style = styles[i].style; 601 if (style is null) continue; 602 603 bool drawBorder = style.borderStyle !is SWT.NONE; 604 if (drawBorder && !style.isAdherentBorder(styles[i+1].style)) { 605 int start = styles[i].start; 606 for (int j = i; j > 0 && style.isAdherentBorder(styles[j-1].style); j--) { 607 start = styles[j - 1].start; 608 } 609 start = translateOffset(start); 610 int end = translateOffset(styles[i+1].start - 1); 611 int byteStart = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, start) - ptr); 612 int byteEnd = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, end + 1) - ptr); 613 int[] ranges = [byteStart, byteEnd]; 614 auto rgn = OS.gdk_pango_layout_get_clip_region(layout, x, y, 615 ranges.ptr, cast(int)/*64bit*/ranges.length / 2); 616 if (rgn !is null) { 617 int nRects; 618 GdkRectangle* rects; 619 OS.gdk_region_get_rectangles(rgn, &rects, &nRects); 620 GdkRectangle rect; 621 GdkColor* color = null; 622 if (color is null && style.borderColor !is null) color = style.borderColor.handle; 623 if (color is null && selectionColor !is null) color = selectionColor; 624 if (color is null && style.foreground !is null) color = style.foreground.handle; 625 if (color is null) color = data.foreground; 626 int width = 1; 627 TryConst!(float)[] dashes = null; 628 switch (style.borderStyle) { 629 case SWT.BORDER_SOLID: break; 630 case SWT.BORDER_DASH: dashes = width !is 0 ? GC.LINE_DASH : GC.LINE_DASH_ZERO; break; 631 case SWT.BORDER_DOT: dashes = width !is 0 ? GC.LINE_DOT : GC.LINE_DOT_ZERO; break; 632 default: break; 633 } 634 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 635 Cairo.cairo_set_source_rgba(cairo, (color.red & 0xFFFF) / cast(float)0xFFFF, (color.green & 0xFFFF) / cast(float)0xFFFF, (color.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 636 Cairo.cairo_set_line_width(cairo, width); 637 if (dashes !is null) { 638 double[] cairoDashes = new double[dashes.length]; 639 for (int j = 0; j < cairoDashes.length; j++) { 640 cairoDashes[j] = width is 0 || data.lineStyle is SWT.LINE_CUSTOM ? dashes[j] : dashes[j] * width; 641 } 642 Cairo.cairo_set_dash(cairo, cairoDashes.ptr, 643 cast(int)/*64bit*/cairoDashes.length, 0); 644 } else { 645 Cairo.cairo_set_dash(cairo, null, 0, 0); 646 } 647 for (int j=0; j<nRects; j++) { 648 rect = rects[j]; 649 Cairo.cairo_rectangle(cairo, rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.height - 1); 650 } 651 Cairo.cairo_stroke(cairo); 652 } else { 653 if (gcValues is null) { 654 gcValues = new GdkGCValues(); 655 OS.gdk_gc_get_values(gdkGC, gcValues); 656 } 657 OS.gdk_gc_set_foreground(gdkGC, color); 658 int cap_style = OS.GDK_CAP_BUTT; 659 int join_style = OS.GDK_JOIN_MITER; 660 int line_style = 0; 661 if (dashes !is null) { 662 byte[] dash_list = new byte[dashes.length]; 663 for (int j = 0; j < dash_list.length; j++) { 664 dash_list[j] = cast(byte)(width is 0 || data.lineStyle is SWT.LINE_CUSTOM ? dashes[j] : dashes[j] * width); 665 } 666 OS.gdk_gc_set_dashes(gdkGC, 0, cast(char*)dash_list.ptr, cast(int)/*64bit*/dash_list.length); 667 line_style = OS.GDK_LINE_ON_OFF_DASH; 668 } else { 669 line_style = OS.GDK_LINE_SOLID; 670 } 671 OS.gdk_gc_set_line_attributes(gdkGC, width, line_style, cap_style, join_style); 672 for (int j=0; j<nRects; j++) { 673 rect = rects[j]; 674 OS.gdk_draw_rectangle(data.drawable, gdkGC, 0, rect.x, rect.y, rect.width - 1, rect.height - 1); 675 } 676 } 677 if (rects !is null) OS.g_free(rects); 678 OS.gdk_region_destroy(rgn); 679 } 680 } 681 682 bool drawUnderline = false; 683 if (style.underline && style.underlineColor !is null) drawUnderline = true; 684 if (style.underline && (style.underlineStyle is SWT.UNDERLINE_ERROR || style.underlineStyle is SWT.UNDERLINE_SQUIGGLE)&& OS.GTK_VERSION < OS.buildVERSION(2, 4, 0)) drawUnderline = true; 685 if (drawUnderline && !style.isAdherentUnderline(styles[i+1].style)) { 686 int start = styles[i].start; 687 for (int j = i; j > 0 && style.isAdherentUnderline(styles[j-1].style); j--) { 688 start = styles[j - 1].start; 689 } 690 start = translateOffset(start); 691 int end = translateOffset(styles[i+1].start - 1); 692 int byteStart = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, start) - ptr); 693 int byteEnd = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, end + 1) - ptr); 694 int[] ranges = [byteStart, byteEnd]; 695 auto rgn = OS.gdk_pango_layout_get_clip_region(layout, x, y, ranges.ptr, cast(int)/*64bit*/ranges.length / 2); 696 if (rgn !is null) { 697 int nRects; 698 GdkRectangle* rects; 699 OS.gdk_region_get_rectangles(rgn, &rects, &nRects); 700 GdkRectangle rect; 701 GdkColor* color = null; 702 if (color is null && style.underlineColor !is null) color = style.underlineColor.handle; 703 if (color is null && selectionColor !is null) color = selectionColor; 704 if (color is null && style.foreground !is null) color = style.foreground.handle; 705 if (color is null) color = data.foreground; 706 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 707 Cairo.cairo_set_source_rgba(cairo, (color.red & 0xFFFF) / cast(float)0xFFFF, (color.green & 0xFFFF) / cast(float)0xFFFF, (color.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 708 } else { 709 if (gcValues is null) { 710 gcValues = new GdkGCValues(); 711 OS.gdk_gc_get_values(gdkGC, gcValues); 712 } 713 OS.gdk_gc_set_foreground(gdkGC, color); 714 } 715 int underlinePosition = -1; 716 int underlineThickness = 1; 717 if (OS.GTK_VERSION >= OS.buildVERSION(2, 6, 0)) { 718 Font font = style.font; 719 if (font is null) font = this.font; 720 if (font is null) font = device.systemFont; 721 auto lang = OS.pango_context_get_language(context); 722 auto metrics = OS.pango_context_get_metrics(context, font.handle, lang); 723 underlinePosition = OS.PANGO_PIXELS(OS.pango_font_metrics_get_underline_position(metrics)); 724 underlineThickness = OS.PANGO_PIXELS(OS.pango_font_metrics_get_underline_thickness(metrics)); 725 OS.pango_font_metrics_unref(metrics); 726 } 727 for (int j=0; j<nRects; j++) { 728 rect = rects[j]; 729 int offset = getOffset(rect.x - x, rect.y - y, null); 730 int lineIndex = getLineIndex(offset); 731 FontMetrics metrics = getLineMetrics(lineIndex); 732 int underlineY = rect.y + metrics.ascent - underlinePosition - style.rise; 733 switch (style.underlineStyle) { 734 case SWT.UNDERLINE_SQUIGGLE: 735 case SWT.UNDERLINE_ERROR: { 736 int squigglyThickness = underlineThickness; 737 int squigglyHeight = 2 * squigglyThickness; 738 int squigglyY = Math.min(underlineY, rect.y + rect.height - squigglyHeight - 1); 739 int[] points = computePolyline(rect.x, squigglyY, (rect.x + rect.width), squigglyY + squigglyHeight); 740 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 741 Cairo.cairo_set_line_width(cairo, squigglyThickness); 742 Cairo.cairo_set_line_cap(cairo, Cairo.CAIRO_LINE_CAP_BUTT); 743 Cairo.cairo_set_line_join(cairo, Cairo.CAIRO_LINE_JOIN_MITER); 744 if (points.length > 0) { 745 double xOffset = 0.5, yOffset = 0.5; 746 Cairo.cairo_move_to(cairo, points[0] + xOffset, points[1] + yOffset); 747 for (int k = 2; k < points.length; k += 2) { 748 Cairo.cairo_line_to(cairo, points[k] + xOffset, points[k + 1] + yOffset); 749 } 750 Cairo.cairo_stroke(cairo); 751 } 752 } else { 753 OS.gdk_gc_set_line_attributes(gdkGC, squigglyThickness, OS.GDK_LINE_SOLID, OS.GDK_CAP_BUTT, OS.GDK_JOIN_MITER); 754 OS.gdk_draw_lines(data.drawable, gdkGC, cast(GdkPoint*)points.ptr, cast(int)/*64bit*/points.length / 2); 755 } 756 break; 757 } 758 case SWT.UNDERLINE_DOUBLE: 759 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 760 Cairo.cairo_rectangle(cairo, rect.x, underlineY + underlineThickness * 2, rect.width, underlineThickness); 761 Cairo.cairo_fill(cairo); 762 } else { 763 OS.gdk_draw_rectangle(data.drawable, gdkGC, 1, rect.x, underlineY + underlineThickness * 2, rect.width, underlineThickness); 764 } 765 goto case SWT.UNDERLINE_SINGLE; 766 case SWT.UNDERLINE_SINGLE: 767 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 768 Cairo.cairo_rectangle(cairo, rect.x, underlineY, rect.width, underlineThickness); 769 Cairo.cairo_fill(cairo); 770 } else { 771 OS.gdk_draw_rectangle(data.drawable, gdkGC, 1, rect.x, underlineY, rect.width, underlineThickness); 772 } 773 break; 774 default: break; 775 } 776 } 777 if (rects !is null) OS.g_free(rects); 778 OS.gdk_region_destroy(rgn); 779 } 780 } 781 782 bool drawStrikeout = false; 783 if (style.strikeout && style.strikeoutColor !is null) drawStrikeout = true; 784 if (drawStrikeout && !style.isAdherentStrikeout(styles[i+1].style)) { 785 int start = styles[i].start; 786 for (int j = i; j > 0 && style.isAdherentStrikeout(styles[j-1].style); j--) { 787 start = styles[j - 1].start; 788 } 789 start = translateOffset(start); 790 int end = translateOffset(styles[i+1].start - 1); 791 int byteStart = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, start) - ptr); 792 int byteEnd = cast(int)/*64bit*/(OS.g_utf8_offset_to_pointer(ptr, end + 1) - ptr); 793 int[] ranges = [byteStart, byteEnd]; 794 auto rgn = OS.gdk_pango_layout_get_clip_region(layout, x, y, ranges.ptr, cast(int)/*64bit*/ranges.length / 2); 795 if (rgn !is null) { 796 int nRects; 797 GdkRectangle* rects; 798 OS.gdk_region_get_rectangles(rgn, &rects, &nRects); 799 GdkRectangle rect; 800 GdkColor* color = null; 801 if (color is null && style.strikeoutColor !is null) color = style.strikeoutColor.handle; 802 if (color is null && selectionColor !is null) color = selectionColor; 803 if (color is null && style.foreground !is null) color = style.foreground.handle; 804 if (color is null) color = data.foreground; 805 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 806 Cairo.cairo_set_source_rgba(cairo, (color.red & 0xFFFF) / cast(float)0xFFFF, (color.green & 0xFFFF) / cast(float)0xFFFF, (color.blue & 0xFFFF) / cast(float)0xFFFF, data.alpha / cast(float)0xFF); 807 } else { 808 if (gcValues is null) { 809 gcValues = new GdkGCValues(); 810 OS.gdk_gc_get_values(gdkGC, gcValues); 811 } 812 OS.gdk_gc_set_foreground(gdkGC, color); 813 } 814 int strikeoutPosition = -1; 815 int strikeoutThickness = 1; 816 if (OS.GTK_VERSION >= OS.buildVERSION(2, 6, 0)) { 817 Font font = style.font; 818 if (font is null) font = this.font; 819 if (font is null) font = device.systemFont; 820 auto lang = OS.pango_context_get_language(context); 821 auto metrics = OS.pango_context_get_metrics(context, font.handle, lang); 822 strikeoutPosition = OS.PANGO_PIXELS(OS.pango_font_metrics_get_strikethrough_position(metrics)); 823 strikeoutThickness = OS.PANGO_PIXELS(OS.pango_font_metrics_get_strikethrough_thickness(metrics)); 824 OS.pango_font_metrics_unref(metrics); 825 } 826 for (int j=0; j<nRects; j++) { 827 rect = rects[j]; 828 int strikeoutY = rect.y + rect.height / 2 - style.rise; 829 if (OS.GTK_VERSION >= OS.buildVERSION(2, 6, 0)) { 830 int offset = getOffset(rect.x - x, rect.y - y, null); 831 int lineIndex = getLineIndex(offset); 832 FontMetrics metrics = getLineMetrics(lineIndex); 833 strikeoutY = rect.y + metrics.ascent - strikeoutPosition - style.rise; 834 } 835 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 836 Cairo.cairo_rectangle(cairo, rect.x, strikeoutY, rect.width, strikeoutThickness); 837 Cairo.cairo_fill(cairo); 838 } else { 839 OS.gdk_draw_rectangle(data.drawable, gdkGC, 1, rect.x, strikeoutY, rect.width, strikeoutThickness); 840 } 841 } 842 if (rects !is null) OS.g_free(rects); 843 OS.gdk_region_destroy(rgn); 844 } 845 } 846 } 847 if (gcValues !is null) { 848 int mask = OS.GDK_GC_FOREGROUND | OS.GDK_GC_LINE_WIDTH | OS.GDK_GC_LINE_STYLE | OS.GDK_GC_CAP_STYLE | OS.GDK_GC_JOIN_STYLE; 849 OS.gdk_gc_set_values(gdkGC, gcValues, mask); 850 data.state &= ~GC.LINE_STYLE; 851 } 852 if (cairo !is null && OS.GTK_VERSION >= OS.buildVERSION(2, 8, 0)) { 853 Cairo.cairo_restore(cairo); 854 } 855 } 856 857 void freeRuns() { 858 if (attrList is null) return; 859 OS.pango_layout_set_attributes(layout, null ); 860 OS.pango_attr_list_unref(attrList); 861 attrList = null; 862 invalidOffsets = null; 863 } 864 865 /** 866 * Returns the receiver's horizontal text alignment, which will be one 867 * of <code>SWT.LEFT</code>, <code>SWT.CENTER</code> or 868 * <code>SWT.RIGHT</code>. 869 * 870 * @return the alignment used to positioned text horizontally 871 * 872 * @exception SWTException <ul> 873 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 874 * </ul> 875 */ 876 public int getAlignment() { 877 checkLayout(); 878 auto alignment = OS.pango_layout_get_alignment(layout); 879 bool rtl = OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL; 880 switch ( cast(int)alignment) { 881 case OS.PANGO_ALIGN_LEFT: return rtl ? SWT.RIGHT : SWT.LEFT; 882 case OS.PANGO_ALIGN_RIGHT: return rtl ? SWT.LEFT : SWT.RIGHT; 883 default: 884 } 885 return SWT.CENTER; 886 } 887 888 /** 889 * Returns the ascent of the receiver. 890 * 891 * @return the ascent 892 * 893 * @exception SWTException <ul> 894 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 895 * </ul> 896 * 897 * @see #getDescent() 898 * @see #setDescent(int) 899 * @see #setAscent(int) 900 * @see #getLineMetrics(int) 901 */ 902 public int getAscent () { 903 checkLayout(); 904 return ascent; 905 } 906 907 /** 908 * Returns the bounds of the receiver. The width returned is either the 909 * width of the longest line or the width set using {@link TextLayout#setWidth(int)}. 910 * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}. 911 * 912 * @return the bounds of the receiver 913 * 914 * @exception SWTException <ul> 915 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 916 * </ul> 917 * 918 * @see #setWidth(int) 919 * @see #getLineBounds(int) 920 */ 921 public Rectangle getBounds() { 922 checkLayout(); 923 computeRuns(); 924 int w, h; 925 OS.pango_layout_get_size(layout, &w, &h); 926 int wrapWidth = OS.pango_layout_get_width(layout); 927 w = wrapWidth !is -1 ? wrapWidth : w + OS.pango_layout_get_indent(layout); 928 int width = OS.PANGO_PIXELS(w); 929 int height = OS.PANGO_PIXELS(h); 930 if (ascent !is -1 && descent !is -1) { 931 height = Math.max (height, ascent + descent); 932 } 933 return new Rectangle(0, 0, width, height); 934 } 935 936 /** 937 * Returns the bounds for the specified range of characters. The 938 * bounds is the smallest rectangle that encompasses all characters 939 * in the range. The start and end offsets are inclusive and will be 940 * clamped if out of range. 941 * 942 * @param start the start offset 943 * @param end the end offset 944 * @return the bounds of the character range 945 * 946 * @exception SWTException <ul> 947 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 948 * </ul> 949 */ 950 public Rectangle getBounds(int start, int end) { 951 checkLayout(); 952 computeRuns(); 953 auto length_ = text.length; 954 if (length_ is 0) return new Rectangle(0, 0, 0, 0); 955 if (start > end) return new Rectangle(0, 0, 0, 0); 956 start = cast(int)/*64bit*/Math.min(Math.max(0, start), length_ - 1); 957 end = cast(int)/*64bit*/Math.min(Math.max(0, end), length_ - 1); 958 start = translateOffset(start); 959 end = translateOffset(end); 960 auto ptr = OS.pango_layout_get_text(layout); 961 auto cont = fromStringz(ptr); 962 UTF8index longStart = start; 963 UTF8index longEnd = end; 964 cont.adjustUTF8index( longStart ); 965 cont.adjustUTF8index( longEnd ); 966 start = cast(int)/*64bit*/longStart; 967 end = cast(int)/*64bit*/longEnd; 968 int incr = 1; 969 if( end < cont.length ){ 970 incr = cast(int)cont.UTF8strideAt(end); 971 } 972 auto byteStart = start;//(OS.g_utf8_offset_to_pointer (ptr, start) - ptr); 973 auto byteEnd = end + incr;//(OS.g_utf8_offset_to_pointer (ptr, end + 1) - ptr); 974 auto slen = OS.strlen(ptr); 975 byteStart = Math.min(byteStart, slen); 976 byteEnd = Math.min(byteEnd, slen); 977 int[] ranges = [byteStart, byteEnd]; 978 auto clipRegion = OS.gdk_pango_layout_get_clip_region(layout, 0, 0, ranges.ptr, 1); 979 if (clipRegion is null) return new Rectangle(0, 0, 0, 0); 980 GdkRectangle rect; 981 982 /* 983 * Bug in Pango. The region returned by gdk_pango_layout_get_clip_region() 984 * includes areas from lines outside of the requested range. The fix 985 * is to subtract these areas from the clip region. 986 */ 987 PangoRectangle* pangoRect = new PangoRectangle(); 988 auto iter = OS.pango_layout_get_iter(layout); 989 if (iter is null) SWT.error(SWT.ERROR_NO_HANDLES); 990 auto linesRegion = OS.gdk_region_new(); 991 if (linesRegion is null) SWT.error(SWT.ERROR_NO_HANDLES); 992 int lineEnd = 0; 993 do { 994 OS.pango_layout_iter_get_line_extents(iter, null, pangoRect); 995 if (OS.pango_layout_iter_next_line(iter)) { 996 lineEnd = OS.pango_layout_iter_get_index(iter) - 1; 997 } else { 998 lineEnd = slen; 999 } 1000 if (byteStart > lineEnd) continue; 1001 rect.x = OS.PANGO_PIXELS(pangoRect.x); 1002 rect.y = OS.PANGO_PIXELS(pangoRect.y); 1003 rect.width = OS.PANGO_PIXELS(pangoRect.width); 1004 rect.height = OS.PANGO_PIXELS(pangoRect.height); 1005 OS.gdk_region_union_with_rect(linesRegion, &rect); 1006 } while (lineEnd + 1 <= byteEnd); 1007 OS.gdk_region_intersect(clipRegion, linesRegion); 1008 OS.gdk_region_destroy(linesRegion); 1009 OS.pango_layout_iter_free(iter); 1010 1011 OS.gdk_region_get_clipbox(clipRegion, &rect); 1012 OS.gdk_region_destroy(clipRegion); 1013 if (OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL) { 1014 rect.x = width() - rect.x - rect.width; 1015 } 1016 return new Rectangle(rect.x, rect.y, rect.width, rect.height); 1017 } 1018 1019 /** 1020 * Returns the descent of the receiver. 1021 * 1022 * @return the descent 1023 * 1024 * @exception SWTException <ul> 1025 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1026 * </ul> 1027 * 1028 * @see #getAscent() 1029 * @see #setAscent(int) 1030 * @see #setDescent(int) 1031 * @see #getLineMetrics(int) 1032 */ 1033 public int getDescent () { 1034 checkLayout(); 1035 return descent; 1036 } 1037 1038 /** 1039 * Returns the default font currently being used by the receiver 1040 * to draw and measure text. 1041 * 1042 * @return the receiver's font 1043 * 1044 * @exception SWTException <ul> 1045 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1046 * </ul> 1047 */ 1048 public Font getFont () { 1049 checkLayout(); 1050 return font; 1051 } 1052 1053 /** 1054 * Returns the receiver's indent. 1055 * 1056 * @return the receiver's indent 1057 * 1058 * @exception SWTException <ul> 1059 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1060 * </ul> 1061 * 1062 * @since 3.2 1063 */ 1064 public int getIndent () { 1065 checkLayout(); 1066 return OS.PANGO_PIXELS(OS.pango_layout_get_indent(layout)); 1067 } 1068 1069 /** 1070 * Returns the receiver's justification. 1071 * 1072 * @return the receiver's justification 1073 * 1074 * @exception SWTException <ul> 1075 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1076 * </ul> 1077 * 1078 * @since 3.2 1079 */ 1080 public bool getJustify () { 1081 checkLayout(); 1082 return cast(bool) OS.pango_layout_get_justify(layout); 1083 } 1084 1085 /** 1086 * Returns the embedding level for the specified character offset. The 1087 * embedding level is usually used to determine the directionality of a 1088 * character in bidirectional text. 1089 * 1090 * @param offset the character offset 1091 * @return the embedding level 1092 * 1093 * @exception IllegalArgumentException <ul> 1094 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> 1095 * </ul> 1096 * @exception SWTException <ul> 1097 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1098 */ 1099 public int getLevel(int offset) { 1100 checkLayout(); 1101 computeRuns(); 1102 auto length_ = text.length; 1103 if (!(0 <= offset && offset <= length_)) SWT.error(SWT.ERROR_INVALID_RANGE); 1104 offset = translateOffset(offset); 1105 auto iter = OS.pango_layout_get_iter(layout); 1106 if (iter is null) SWT.error(SWT.ERROR_NO_HANDLES); 1107 int level = 0; 1108 PangoItem* item = new PangoItem(); 1109 PangoLayoutRun* run = new PangoLayoutRun(); 1110 auto ptr = OS.pango_layout_get_text(layout); 1111 auto byteOffset = offset;//OS.g_utf8_offset_to_pointer(ptr, offset) - ptr; 1112 auto slen = OS.strlen(ptr); 1113 byteOffset = Math.min(byteOffset, slen); 1114 do { 1115 auto runPtr = OS.pango_layout_iter_get_run(iter); 1116 if (runPtr !is null) { 1117 memmove(run, runPtr, PangoLayoutRun.sizeof); 1118 memmove(item, run.item, PangoItem.sizeof); 1119 if (item.offset <= byteOffset && byteOffset < item.offset + item.length) { 1120 level = item.analysis.level; 1121 break; 1122 } 1123 } 1124 } while (OS.pango_layout_iter_next_run(iter)); 1125 OS.pango_layout_iter_free(iter); 1126 return level; 1127 } 1128 1129 /** 1130 * Returns the bounds of the line for the specified line index. 1131 * 1132 * @param lineIndex the line index 1133 * @return the line bounds 1134 * 1135 * @exception IllegalArgumentException <ul> 1136 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> 1137 * </ul> 1138 * @exception SWTException <ul> 1139 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1140 * </ul> 1141 */ 1142 public Rectangle getLineBounds(int lineIndex) { 1143 checkLayout(); 1144 computeRuns(); 1145 int lineCount = OS.pango_layout_get_line_count(layout); 1146 if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE); 1147 auto iter = OS.pango_layout_get_iter(layout); 1148 if (iter is null) SWT.error(SWT.ERROR_NO_HANDLES); 1149 for (int i = 0; i < lineIndex; i++) OS.pango_layout_iter_next_line(iter); 1150 PangoRectangle rect; 1151 OS.pango_layout_iter_get_line_extents(iter, null, &rect); 1152 OS.pango_layout_iter_free(iter); 1153 int x = OS.PANGO_PIXELS(rect.x); 1154 int y = OS.PANGO_PIXELS(rect.y); 1155 int width_ = OS.PANGO_PIXELS(rect.width); 1156 int height = OS.PANGO_PIXELS(rect.height); 1157 if (ascent !is -1 && descent !is -1) { 1158 height = Math.max (height, ascent + descent); 1159 } 1160 if (OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL) { 1161 x = width() - x - width_; 1162 } 1163 return new Rectangle(x, y, width_, height); 1164 } 1165 1166 /** 1167 * Returns the receiver's line count. This includes lines caused 1168 * by wrapping. 1169 * 1170 * @return the line count 1171 * 1172 * @exception SWTException <ul> 1173 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1174 * </ul> 1175 */ 1176 public int getLineCount() { 1177 checkLayout (); 1178 computeRuns(); 1179 return OS.pango_layout_get_line_count(layout); 1180 } 1181 1182 /** 1183 * Returns the index of the line that contains the specified 1184 * character offset. 1185 * 1186 * @param offset the character offset 1187 * @return the line index 1188 * 1189 * @exception IllegalArgumentException <ul> 1190 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> 1191 * </ul> 1192 * @exception SWTException <ul> 1193 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1194 * </ul> 1195 */ 1196 public int getLineIndex(int offset) { 1197 checkLayout (); 1198 computeRuns(); 1199 auto length_ = text.length; 1200 if (!(0 <= offset && offset <= length_)) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1201 offset = translateOffset(offset); 1202 int line = 0; 1203 auto ptr = OS.pango_layout_get_text(layout); 1204 auto byteOffset = offset;//OS.g_utf8_offset_to_pointer(ptr,offset) - ptr; 1205 int slen = OS.strlen(ptr); 1206 byteOffset = Math.min(byteOffset, slen); 1207 auto iter = OS.pango_layout_get_iter(layout); 1208 if (iter is null) SWT.error(SWT.ERROR_NO_HANDLES); 1209 while (OS.pango_layout_iter_next_line(iter)) { 1210 if (OS.pango_layout_iter_get_index(iter) > byteOffset) break; 1211 line++; 1212 } 1213 OS.pango_layout_iter_free(iter); 1214 return line; 1215 } 1216 1217 /** 1218 * Returns the font metrics for the specified line index. 1219 * 1220 * @param lineIndex the line index 1221 * @return the font metrics 1222 * 1223 * @exception IllegalArgumentException <ul> 1224 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> 1225 * </ul> 1226 * @exception SWTException <ul> 1227 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1228 * </ul> 1229 */ 1230 public FontMetrics getLineMetrics (int lineIndex) { 1231 checkLayout (); 1232 computeRuns(); 1233 int lineCount = OS.pango_layout_get_line_count(layout); 1234 if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE); 1235 int ascent = 0, descent = 0; 1236 PangoLayoutLine* line = new PangoLayoutLine(); 1237 memmove(line, OS.pango_layout_get_line(layout, lineIndex), PangoLayoutLine.sizeof); 1238 if (line.runs is null) { 1239 auto font = this.font !is null ? this.font.handle : device.systemFont.handle; 1240 auto lang = OS.pango_context_get_language(context); 1241 auto metrics = OS.pango_context_get_metrics(context, font, lang); 1242 ascent = OS.pango_font_metrics_get_ascent(metrics); 1243 descent = OS.pango_font_metrics_get_descent(metrics); 1244 OS.pango_font_metrics_unref(metrics); 1245 } else { 1246 PangoRectangle rect; 1247 OS.pango_layout_line_get_extents(OS.pango_layout_get_line(layout, lineIndex), null, &rect); 1248 ascent = -rect.y; 1249 descent = rect.height - ascent; 1250 } 1251 ascent = Math.max(this.ascent, OS.PANGO_PIXELS(ascent)); 1252 descent = Math.max(this.descent, OS.PANGO_PIXELS(descent)); 1253 return FontMetrics.gtk_new(ascent, descent, 0, 0, ascent + descent); 1254 } 1255 1256 /** 1257 * Returns the line offsets. Each value in the array is the 1258 * offset for the first character in a line except for the last 1259 * value, which contains the length of the text. 1260 * 1261 * @return the line offsets 1262 * 1263 * @exception SWTException <ul> 1264 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1265 * </ul> 1266 */ 1267 public int[] getLineOffsets() { 1268 checkLayout(); 1269 computeRuns(); 1270 int lineCount = OS.pango_layout_get_line_count(layout); 1271 int[] offsets = new int [lineCount + 1]; 1272 auto ptr = OS.pango_layout_get_text(layout); 1273 for (int i = 0; i < lineCount; i++) { 1274 auto line = OS.pango_layout_get_line(layout, i); 1275 int pos = cast(int)/*64bit*/OS.g_utf8_pointer_to_offset(ptr, ptr + line.start_index); 1276 offsets[i] = untranslateOffset(pos); 1277 } 1278 offsets[lineCount] = cast(int)/*64bit*/text.length; 1279 return offsets; 1280 } 1281 1282 /** 1283 * Returns the location for the specified character offset. The 1284 * <code>trailing</code> argument indicates whether the offset 1285 * corresponds to the leading or trailing edge of the cluster. 1286 * 1287 * @param offset the character offset 1288 * @param trailing the trailing flag 1289 * @return the location of the character offset 1290 * 1291 * @exception SWTException <ul> 1292 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1293 * </ul> 1294 * 1295 * @see #getOffset(Point, int[]) 1296 * @see #getOffset(int, int, int[]) 1297 */ 1298 public Point getLocation(int offset, bool trailing) { 1299 checkLayout(); 1300 computeRuns(); 1301 auto length_ = text.length; 1302 if (!(0 <= offset && offset <= length_)) SWT.error(SWT.ERROR_INVALID_RANGE); 1303 offset = translateOffset(offset); 1304 auto ptr = OS.pango_layout_get_text(layout); 1305 auto cont = fromStringz(ptr); 1306 ptrdiff_t longOffset = offset; 1307 cont.adjustUTF8index(longOffset); 1308 // leading ZWS+ZWNBS are 2 codepoints in 6 bytes, so we miss 4 bytes here 1309 int byteOffset = cast(int)/*64bit*/longOffset;//(OS.g_utf8_offset_to_pointer(ptr, offset) - ptr); 1310 int slen = cast(int)/*64bit*/cont.length; 1311 byteOffset = Math.min(byteOffset, slen); 1312 PangoRectangle* pos = new PangoRectangle(); 1313 OS.pango_layout_index_to_pos(layout, byteOffset, pos); 1314 int x = trailing ? pos.x + pos.width : pos.x; 1315 int y = pos.y; 1316 x = OS.PANGO_PIXELS(x); 1317 if (OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL) { 1318 x = width() - x; 1319 } 1320 return new Point(x, OS.PANGO_PIXELS(y)); 1321 } 1322 1323 /** 1324 * Returns the next offset for the specified offset and movement 1325 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>, 1326 * <code>SWT.MOVEMENT_CLUSTER</code>, <code>SWT.MOVEMENT_WORD</code>, 1327 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>. 1328 * 1329 * @param offset the start offset 1330 * @param movement the movement type 1331 * @return the next offset 1332 * 1333 * @exception IllegalArgumentException <ul> 1334 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> 1335 * </ul> 1336 * @exception SWTException <ul> 1337 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1338 * </ul> 1339 * 1340 * @see #getPreviousOffset(int, int) 1341 */ 1342 public int getNextOffset (int offset, int movement) { 1343 return _getOffset(offset, movement, true); 1344 } 1345 1346 int _getOffset (int offset, int movement, bool forward) { 1347 checkLayout(); 1348 computeRuns(); 1349 auto length_ = text.length; 1350 if (!(0 <= offset && offset <= length_)) SWT.error(SWT.ERROR_INVALID_RANGE); 1351 if (forward) { 1352 if (offset is length_) return cast(int)/*64bit*/length_; 1353 } else { 1354 if (offset is 0) return 0; 1355 } 1356 auto cont = OS.pango_layout_get_text(layout); 1357 assert( cont ); 1358 auto dcont = fromStringz(cont); 1359 int step = forward ? 1 : -1; 1360 if ((movement & SWT.MOVEMENT_CHAR) !is 0){ 1361 //PORTING take care of utf8 1362 ptrdiff_t toffset = translateOffset(offset); 1363 dcont.adjustUTF8index( toffset ); 1364 int incr = cast(int)/*64bit*/dcont.toUTF8shift(toffset, step); 1365 return offset + incr; 1366 } 1367 PangoLogAttr* attrs; 1368 int nAttrs; 1369 // return one attr per codepoint (=char in pango) 1370 OS.pango_layout_get_log_attrs(layout, &attrs, &nAttrs); 1371 if (attrs is null) return offset + step; 1372 length_ = dcont.length;//OS.g_utf8_strlen(cont, -1); 1373 offset = translateOffset(offset); 1374 ptrdiff_t longOffset = offset; 1375 dcont.adjustUTF8index( longOffset ); 1376 offset = cast(int)/*64bit*/longOffset; 1377 1378 PangoLogAttr* logAttr; 1379 offset = validateOffset( dcont, offset, step); 1380 // the loop is byte oriented 1381 while (0 < offset && offset < length_) { 1382 logAttr = & attrs[ OS.g_utf8_pointer_to_offset( cont, cont+offset) ]; 1383 if (((movement & SWT.MOVEMENT_CLUSTER) !is 0) && logAttr.is_cursor_position ) break; 1384 if ((movement & SWT.MOVEMENT_WORD) !is 0) { 1385 if (forward) { 1386 if (logAttr.is_word_end ) break; 1387 } else { 1388 if (logAttr.is_word_start ) break; 1389 } 1390 } 1391 if ((movement & SWT.MOVEMENT_WORD_START) !is 0) { 1392 if (logAttr.is_word_start ) break; 1393 } 1394 if ((movement & SWT.MOVEMENT_WORD_END) !is 0) { 1395 if (logAttr.is_word_end ) break; 1396 } 1397 offset = validateOffset( dcont, offset, step); 1398 } 1399 OS.g_free(attrs); 1400 return cast(int)/*64bit*/Math.min(Math.max 1401 (0, untranslateOffset(offset)), text.length); 1402 } 1403 1404 /** 1405 * Returns the character offset for the specified point. 1406 * For a typical character, the trailing argument will be filled in to 1407 * indicate whether the point is closer to the leading edge (0) or 1408 * the trailing edge (1). When the point is over a cluster composed 1409 * of multiple characters, the trailing argument will be filled with the 1410 * position of the character in the cluster that is closest to 1411 * the point. 1412 * 1413 * @param point the point 1414 * @param trailing the trailing buffer 1415 * @return the character offset 1416 * 1417 * @exception IllegalArgumentException <ul> 1418 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> 1419 * <li>ERROR_NULL_ARGUMENT - if the point is null</li> 1420 * </ul> 1421 * @exception SWTException <ul> 1422 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1423 * </ul> 1424 * 1425 * @see #getLocation(int, bool) 1426 */ 1427 public int getOffset(Point point, int[] trailing) { 1428 checkLayout(); 1429 if (point is null) SWT.error(SWT.ERROR_NULL_ARGUMENT); 1430 return getOffset(point.x, point.y, trailing); 1431 } 1432 1433 /** 1434 * Returns the character offset for the specified point. 1435 * For a typical character, the trailing argument will be filled in to 1436 * indicate whether the point is closer to the leading edge (0) or 1437 * the trailing edge (1). When the point is over a cluster composed 1438 * of multiple characters, the trailing argument will be filled with the 1439 * position of the character in the cluster that is closest to 1440 * the point. 1441 * 1442 * @param x the x coordinate of the point 1443 * @param y the y coordinate of the point 1444 * @param trailing the trailing buffer 1445 * @return the character offset 1446 * 1447 * @exception IllegalArgumentException <ul> 1448 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> 1449 * </ul> 1450 * @exception SWTException <ul> 1451 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1452 * </ul> 1453 * 1454 * @see #getLocation(int, bool) 1455 */ 1456 public int getOffset(int x, int y, int[] trailing) { 1457 checkLayout(); 1458 computeRuns(); 1459 if (trailing !is null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1460 if (OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL) { 1461 x = width() - x; 1462 } 1463 1464 /* 1465 * Feature in GTK. pango_layout_xy_to_index() returns the 1466 * logical end/start offset of a line when the coordinates are outside 1467 * the line bounds. In SWT the correct behavior is to return the closest 1468 * visual offset. The fix is to clamp the coordinates inside the 1469 * line bounds. 1470 */ 1471 auto iter = OS.pango_layout_get_iter(layout); 1472 if (iter is null) SWT.error(SWT.ERROR_NO_HANDLES); 1473 PangoRectangle rect; 1474 do { 1475 OS.pango_layout_iter_get_line_extents(iter, null, &rect); 1476 rect.y = OS.PANGO_PIXELS(rect.y); 1477 rect.height = OS.PANGO_PIXELS(rect.height); 1478 if (rect.y <= y && y < rect.y + rect.height) { 1479 rect.x = OS.PANGO_PIXELS(rect.x); 1480 rect.width = OS.PANGO_PIXELS(rect.width); 1481 if (x >= rect.x + rect.width) x = rect.x + rect.width - 1; 1482 if (x < rect.x) x = rect.x; 1483 break; 1484 } 1485 } while (OS.pango_layout_iter_next_line(iter)); 1486 OS.pango_layout_iter_free(iter); 1487 1488 int index; 1489 int piTrailing; 1490 OS.pango_layout_xy_to_index(layout, x * OS.PANGO_SCALE, y * OS.PANGO_SCALE, &index, &piTrailing); 1491 auto ptr = OS.pango_layout_get_text(layout); 1492 int offset = index;//OS.g_utf8_pointer_to_offset(ptr, ptr + index); 1493 if (trailing !is null) trailing[0] = piTrailing; 1494 return untranslateOffset(offset); 1495 } 1496 1497 /** 1498 * Returns the orientation of the receiver. 1499 * 1500 * @return the orientation style 1501 * 1502 * @exception SWTException <ul> 1503 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1504 * </ul> 1505 */ 1506 public int getOrientation() { 1507 checkLayout(); 1508 ptrdiff_t baseDir = OS.pango_context_get_base_dir(context); 1509 return baseDir is OS.PANGO_DIRECTION_RTL ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT; 1510 } 1511 1512 /** 1513 * Returns the previous offset for the specified offset and movement 1514 * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>, 1515 * <code>SWT.MOVEMENT_CLUSTER</code> or <code>SWT.MOVEMENT_WORD</code>, 1516 * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>. 1517 * 1518 * @param offset the start offset 1519 * @param movement the movement type 1520 * @return the previous offset 1521 * 1522 * @exception IllegalArgumentException <ul> 1523 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> 1524 * </ul> 1525 * @exception SWTException <ul> 1526 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1527 * </ul> 1528 * 1529 * @see #getNextOffset(int, int) 1530 */ 1531 public int getPreviousOffset (int index, int movement) { 1532 return _getOffset(index, movement, false); 1533 } 1534 1535 /** 1536 * Gets the ranges of text that are associated with a <code>TextStyle</code>. 1537 * 1538 * @return the ranges, an array of offsets representing the start and end of each 1539 * text style. 1540 * 1541 * @exception SWTException <ul> 1542 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1543 * </ul> 1544 * 1545 * @see #getStyles() 1546 * 1547 * @since 3.2 1548 */ 1549 public int[] getRanges () { 1550 checkLayout(); 1551 int[] result = new int[styles.length * 2]; 1552 int count = 0; 1553 for (int i=0; i<styles.length - 1; i++) { 1554 if (styles[i].style !is null) { 1555 result[count++] = styles[i].start; 1556 result[count++] = styles[i + 1].start - 1; 1557 } 1558 } 1559 if (count !is result.length) { 1560 int[] newResult = new int[count]; 1561 System.arraycopy(result, 0, newResult, 0, count); 1562 result = newResult; 1563 } 1564 return result; 1565 } 1566 1567 /** 1568 * Returns the text segments offsets of the receiver. 1569 * 1570 * @return the text segments offsets 1571 * 1572 * @exception SWTException <ul> 1573 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1574 * </ul> 1575 */ 1576 public int[] getSegments() { 1577 checkLayout(); 1578 return segments; 1579 } 1580 1581 String getSegmentsText() { 1582 if (segments is null) return text; 1583 auto nSegments = segments.length; 1584 if (nSegments <= 1) return text; 1585 auto len = text.length; 1586 if (len is 0) return text; 1587 if (nSegments is 2) { 1588 if (segments[0] is 0 && segments[1] is len) return text; 1589 } 1590 char[] oldChars = text[0..len].dup; 1591 char[] newChars = new char[len + nSegments*3]; 1592 int charCount = 0, segmentCount = 0; 1593 String separator = getOrientation() is SWT.RIGHT_TO_LEFT ? STR_RTL_MARK : STR_LTR_MARK; 1594 while (charCount < len) { 1595 if (segmentCount < nSegments && charCount is segments[segmentCount]) { 1596 newChars[charCount + segmentCount .. charCount + segmentCount + separator.length ] = separator; 1597 segmentCount+=separator.length; 1598 } else { 1599 newChars[charCount + segmentCount] = oldChars[charCount]; 1600 charCount++; 1601 } 1602 } 1603 if (segmentCount < nSegments) { 1604 segments[segmentCount] = charCount; 1605 newChars[charCount + segmentCount .. charCount + segmentCount + separator.length ] = separator; 1606 segmentCount+=separator.length; 1607 } 1608 return cast(String)newChars[ 0 .. Math.min(charCount + segmentCount, newChars.length) ]; 1609 } 1610 1611 /** 1612 * Returns the line spacing of the receiver. 1613 * 1614 * @return the line spacing 1615 * 1616 * @exception SWTException <ul> 1617 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1618 * </ul> 1619 */ 1620 public int getSpacing () { 1621 checkLayout(); 1622 return OS.PANGO_PIXELS(OS.pango_layout_get_spacing(layout)); 1623 } 1624 1625 /** 1626 * Gets the style of the receiver at the specified character offset. 1627 * 1628 * @param offset the text offset 1629 * @return the style or <code>null</code> if not set 1630 * 1631 * @exception IllegalArgumentException <ul> 1632 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> 1633 * </ul> 1634 * @exception SWTException <ul> 1635 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1636 * </ul> 1637 */ 1638 public TextStyle getStyle (int offset) { 1639 checkLayout(); 1640 auto length_ = text.length; 1641 if (!(0 <= offset && offset < length_)) SWT.error(SWT.ERROR_INVALID_RANGE); 1642 for (int i=1; i<styles.length; i++) { 1643 StyleItem item = styles[i]; 1644 if (item.start > offset) { 1645 return styles[i - 1].style; 1646 } 1647 } 1648 return null; 1649 } 1650 1651 /** 1652 * Gets all styles of the receiver. 1653 * 1654 * @return the styles 1655 * 1656 * @exception SWTException <ul> 1657 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1658 * </ul> 1659 * 1660 * @see #getRanges() 1661 * 1662 * @since 3.2 1663 */ 1664 public TextStyle[] getStyles () { 1665 checkLayout(); 1666 TextStyle[] result = new TextStyle[styles.length]; 1667 int count = 0; 1668 for (int i=0; i<styles.length; i++) { 1669 if (styles[i].style !is null) { 1670 result[count++] = styles[i].style; 1671 } 1672 } 1673 if (count !is result.length) { 1674 TextStyle[] newResult = new TextStyle[count]; 1675 System.arraycopy(result, 0, newResult, 0, count); 1676 result = newResult; 1677 } 1678 return result; 1679 } 1680 1681 /** 1682 * Returns the tab list of the receiver. 1683 * 1684 * @return the tab list 1685 * 1686 * @exception SWTException <ul> 1687 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1688 * </ul> 1689 */ 1690 public int[] getTabs() { 1691 checkLayout(); 1692 return tabs; 1693 } 1694 1695 /** 1696 * Gets the receiver's text, which will be an empty 1697 * string if it has never been set. 1698 * 1699 * @return the receiver's text 1700 * 1701 * @exception SWTException <ul> 1702 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1703 * </ul> 1704 */ 1705 public String getText () { 1706 checkLayout (); 1707 return text; 1708 } 1709 1710 /** 1711 * Returns the width of the receiver. 1712 * 1713 * @return the width 1714 * 1715 * @exception SWTException <ul> 1716 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1717 * </ul> 1718 */ 1719 public int getWidth () { 1720 checkLayout (); 1721 int width = OS.pango_layout_get_width(layout); 1722 return width !is -1 ? OS.PANGO_PIXELS(width) : -1; 1723 } 1724 1725 /** 1726 * Returns <code>true</code> if the text layout has been disposed, 1727 * and <code>false</code> otherwise. 1728 * <p> 1729 * This method gets the dispose state for the text layout. 1730 * When a text layout has been disposed, it is an error to 1731 * invoke any other method using the text layout. 1732 * </p> 1733 * 1734 * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise 1735 */ 1736 public override bool isDisposed () { 1737 return layout is null; 1738 } 1739 1740 /** 1741 * Sets the text alignment for the receiver. The alignment controls 1742 * how a line of text is positioned horizontally. The argument should 1743 * be one of <code>SWT.LEFT</code>, <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>. 1744 * <p> 1745 * The default alignment is <code>SWT.LEFT</code>. Note that the receiver's 1746 * width must be set in order to use <code>SWT.RIGHT</code> or <code>SWT.CENTER</code> 1747 * alignment. 1748 * </p> 1749 * 1750 * @param alignment the new alignment 1751 * 1752 * @exception SWTException <ul> 1753 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1754 * </ul> 1755 * 1756 * @see #setWidth(int) 1757 */ 1758 public void setAlignment (int alignment) { 1759 checkLayout(); 1760 int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT; 1761 alignment &= mask; 1762 if (alignment is 0) return; 1763 if ((alignment & SWT.LEFT) !is 0) alignment = SWT.LEFT; 1764 if ((alignment & SWT.RIGHT) !is 0) alignment = SWT.RIGHT; 1765 bool rtl = OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL; 1766 int align_ = OS.PANGO_ALIGN_CENTER; 1767 switch (alignment) { 1768 case SWT.LEFT: 1769 align_ = rtl ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT; 1770 break; 1771 case SWT.RIGHT: 1772 align_ = rtl ? OS.PANGO_ALIGN_LEFT : OS.PANGO_ALIGN_RIGHT; 1773 break; 1774 default: break; 1775 } 1776 OS.pango_layout_set_alignment(layout, align_); 1777 } 1778 1779 /** 1780 * Sets the ascent of the receiver. The ascent is distance in pixels 1781 * from the baseline to the top of the line and it is applied to all 1782 * lines. The default value is <code>-1</code> which means that the 1783 * ascent is calculated from the line fonts. 1784 * 1785 * @param ascent the new ascent 1786 * 1787 * @exception IllegalArgumentException <ul> 1788 * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li> 1789 * </ul> 1790 * @exception SWTException <ul> 1791 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1792 * </ul> 1793 * 1794 * @see #setDescent(int) 1795 * @see #getLineMetrics(int) 1796 */ 1797 public void setAscent (int ascent) { 1798 checkLayout(); 1799 if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1800 if (this.ascent is ascent) return; 1801 freeRuns(); 1802 this.ascent = ascent; 1803 } 1804 1805 /** 1806 * Sets the descent of the receiver. The descent is distance in pixels 1807 * from the baseline to the bottom of the line and it is applied to all 1808 * lines. The default value is <code>-1</code> which means that the 1809 * descent is calculated from the line fonts. 1810 * 1811 * @param descent the new descent 1812 * 1813 * @exception IllegalArgumentException <ul> 1814 * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li> 1815 * </ul> 1816 * @exception SWTException <ul> 1817 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1818 * </ul> 1819 * 1820 * @see #setAscent(int) 1821 * @see #getLineMetrics(int) 1822 */ 1823 public void setDescent (int descent) { 1824 checkLayout(); 1825 if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1826 if (this.descent is descent) return; 1827 freeRuns(); 1828 this.descent = descent; 1829 } 1830 1831 /** 1832 * Sets the default font which will be used by the receiver 1833 * to draw and measure text. If the 1834 * argument is null, then a default font appropriate 1835 * for the platform will be used instead. Note that a text 1836 * style can override the default font. 1837 * 1838 * @param font the new font for the receiver, or null to indicate a default font 1839 * 1840 * @exception IllegalArgumentException <ul> 1841 * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li> 1842 * </ul> 1843 * @exception SWTException <ul> 1844 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1845 * </ul> 1846 */ 1847 public void setFont (Font font) { 1848 checkLayout (); 1849 if (font !is null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1850 Font oldFont = this.font; 1851 if (oldFont is font) return; 1852 freeRuns(); 1853 this.font = font; 1854 if (oldFont !is null && oldFont.opEquals(font)) return; 1855 OS.pango_layout_set_font_description(layout, font !is null ? font.handle : device.systemFont.handle); 1856 } 1857 1858 /** 1859 * Sets the indent of the receiver. This indent it applied of the first line of 1860 * each paragraph. 1861 * 1862 * @param indent new indent 1863 * 1864 * @exception SWTException <ul> 1865 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1866 * </ul> 1867 * 1868 * @since 3.2 1869 */ 1870 public void setIndent (int indent) { 1871 checkLayout(); 1872 if (indent < 0) return; 1873 OS.pango_layout_set_indent(layout, indent * OS.PANGO_SCALE); 1874 } 1875 1876 /** 1877 * Sets the justification of the receiver. Note that the receiver's 1878 * width must be set in order to use justification. 1879 * 1880 * @param justify new justify 1881 * 1882 * @exception SWTException <ul> 1883 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1884 * </ul> 1885 * 1886 * @since 3.2 1887 */ 1888 public void setJustify (bool justify) { 1889 checkLayout(); 1890 OS.pango_layout_set_justify(layout, justify); 1891 } 1892 1893 /** 1894 * Sets the orientation of the receiver, which must be one 1895 * of <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>. 1896 * 1897 * @param orientation new orientation style 1898 * 1899 * @exception SWTException <ul> 1900 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1901 * </ul> 1902 */ 1903 public void setOrientation(int orientation) { 1904 checkLayout(); 1905 int mask = SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT; 1906 orientation &= mask; 1907 if (orientation is 0) return; 1908 if ((orientation & SWT.LEFT_TO_RIGHT) !is 0) orientation = SWT.LEFT_TO_RIGHT; 1909 int baseDir = orientation is SWT.RIGHT_TO_LEFT ? OS.PANGO_DIRECTION_RTL : OS.PANGO_DIRECTION_LTR; 1910 if (OS.pango_context_get_base_dir(context) is baseDir) return; 1911 OS.pango_context_set_base_dir(context, baseDir); 1912 OS.pango_layout_context_changed(layout); 1913 int align_ = OS.pango_layout_get_alignment(layout); 1914 if (align_ !is OS.PANGO_ALIGN_CENTER) { 1915 align_ = align_ is OS.PANGO_ALIGN_LEFT ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT; 1916 OS.pango_layout_set_alignment(layout, align_); 1917 } 1918 } 1919 1920 /** 1921 * Sets the line spacing of the receiver. The line spacing 1922 * is the space left between lines. 1923 * 1924 * @param spacing the new line spacing 1925 * 1926 * @exception IllegalArgumentException <ul> 1927 * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li> 1928 * </ul> 1929 * @exception SWTException <ul> 1930 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1931 * </ul> 1932 */ 1933 public void setSpacing (int spacing) { 1934 checkLayout(); 1935 if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 1936 OS.pango_layout_set_spacing(layout, spacing * OS.PANGO_SCALE); 1937 } 1938 1939 /** 1940 * Sets the offsets of the receiver's text segments. Text segments are used to 1941 * override the default behaviour of the bidirectional algorithm. 1942 * Bidirectional reordering can happen within a text segment but not 1943 * between two adjacent segments. 1944 * <p> 1945 * Each text segment is determined by two consecutive offsets in the 1946 * <code>segments</code> arrays. The first element of the array should 1947 * always be zero and the last one should always be equals to length of 1948 * the text. 1949 * </p> 1950 * 1951 * @param segments the text segments offset 1952 * 1953 * @exception SWTException <ul> 1954 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1955 * </ul> 1956 */ 1957 public void setSegments(int[] segments) { 1958 checkLayout(); 1959 if (this.segments is null && segments is null) return; 1960 if (this.segments !is null && segments !is null) { 1961 if (this.segments.length is segments.length) { 1962 int i; 1963 for (i = 0; i <segments.length; i++) { 1964 if (this.segments[i] !is segments[i]) break; 1965 } 1966 if (i is segments.length) return; 1967 } 1968 } 1969 freeRuns(); 1970 this.segments = segments; 1971 } 1972 1973 /** 1974 * Sets the style of the receiver for the specified range. Styles previously 1975 * set for that range will be overwritten. The start and end offsets are 1976 * inclusive and will be clamped if out of range. 1977 * 1978 * @param style the style 1979 * @param start the start offset 1980 * @param end the end offset 1981 * 1982 * @exception SWTException <ul> 1983 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 1984 * </ul> 1985 */ 1986 public void setStyle (TextStyle style, int start, int end) { 1987 checkLayout(); 1988 auto length_ = text.length; 1989 if (length_ is 0) return; 1990 if (start > end) return; 1991 start = cast(int)/*64bit*/Math.min(Math.max(0, start), length_ - 1); 1992 end = cast(int)/*64bit*/Math.min(Math.max(0, end), length_ - 1); 1993 ptrdiff_t longStart = start; 1994 ptrdiff_t longEnd = end; 1995 text.adjustUTF8index( longStart ); 1996 text.adjustUTF8index( longEnd ); 1997 start = cast(int)/*64bit*/longStart; 1998 end = cast(int)/*64bit*/longEnd; 1999 2000 2001 /* 2002 * Bug in Pango. Pango 1.2.2 will cause a segmentation fault if a style 2003 * is not applied for a whole ligature. The fix is to applied the 2004 * style for the whole ligature. 2005 * 2006 * NOTE that fix only LamAlef ligatures. 2007 */ 2008 if ((start > 0 ) && isAlef(text.dcharAt(start)) && isLam(text.dcharBefore(start))) { 2009 start += text.offsetBefore(start); 2010 } 2011 if ((end < length_ - 1) && isLam(text.dcharAt(end)) && isAlef(text.dcharAfter(end))) { 2012 end = cast(int)/*64bit*/text.offsetAfter(end); 2013 } 2014 2015 int low = -1; 2016 int high = cast(int)/*64bit*/styles.length; 2017 while (high - low > 1) { 2018 auto index = (high + low) / 2; 2019 if (styles[index + 1].start > start) { 2020 high = index; 2021 } else { 2022 low = index; 2023 } 2024 } 2025 if (0 <= high && high < styles.length) { 2026 StyleItem item = styles[high]; 2027 if (item.start is start && styles[high + 1].start - 1 is end) { 2028 if (style is null) { 2029 if (item.style is null) return; 2030 } else { 2031 if (style.opEquals(item.style)) return; 2032 } 2033 } 2034 } 2035 freeRuns(); 2036 auto modifyStart = high; 2037 auto modifyEnd = modifyStart; 2038 while (modifyEnd < styles.length) { 2039 if (styles[modifyEnd + 1].start > end) break; 2040 modifyEnd++; 2041 } 2042 if (modifyStart is modifyEnd) { 2043 auto styleStart = styles[modifyStart].start; 2044 auto styleEnd = styles[modifyEnd + 1].start - 1; 2045 if (styleStart is start && styleEnd is end) { 2046 styles[modifyStart].style = style; 2047 return; 2048 } 2049 if (styleStart !is start && styleEnd !is end) { 2050 StyleItem[] newStyles = new StyleItem[styles.length + 2]; 2051 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); 2052 StyleItem item = new StyleItem(); 2053 item.start = start; 2054 item.style = style; 2055 newStyles[modifyStart + 1] = item; 2056 item = new StyleItem(); 2057 item.start = end + 1; 2058 item.style = styles[modifyStart].style; 2059 newStyles[modifyStart + 2] = item; 2060 System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1); 2061 styles = newStyles; 2062 return; 2063 } 2064 } 2065 if (start is styles[modifyStart].start) modifyStart--; 2066 if (end is styles[modifyEnd + 1].start - 1) modifyEnd++; 2067 auto newLength = styles.length + 1 - (modifyEnd - modifyStart - 1); 2068 StyleItem[] newStyles = new StyleItem[newLength]; 2069 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); 2070 StyleItem item = new StyleItem(); 2071 item.start = start; 2072 item.style = style; 2073 newStyles[modifyStart + 1] = item; 2074 styles[modifyEnd].start = end + 1; 2075 System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd); 2076 styles = newStyles; 2077 } 2078 2079 /** 2080 * Sets the receiver's tab list. Each value in the tab list specifies 2081 * the space in pixels from the origin of the text layout to the respective 2082 * tab stop. The last tab stop width is repeated continuously. 2083 * 2084 * @param tabs the new tab list 2085 * 2086 * @exception SWTException <ul> 2087 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 2088 * </ul> 2089 */ 2090 public void setTabs(int[] tabs) { 2091 checkLayout(); 2092 if (this.tabs is null && tabs is null) return; 2093 if (this.tabs!is null && tabs !is null) { 2094 if (this.tabs.length is tabs.length) { 2095 int i; 2096 for (i = 0; i <tabs.length; i++) { 2097 if (this.tabs[i] !is tabs[i]) break; 2098 } 2099 if (i is tabs.length) return; 2100 } 2101 } 2102 this.tabs = tabs; 2103 if (tabs is null) { 2104 OS.pango_layout_set_tabs(layout, device.emptyTab); 2105 } else { 2106 auto tabArray = OS.pango_tab_array_new(cast(int)/*64bit*/tabs.length, true); 2107 if (tabArray !is null) { 2108 for (int i = 0; i < tabs.length; i++) { 2109 OS.pango_tab_array_set_tab(tabArray, i, OS.PANGO_TAB_LEFT, tabs[i]); 2110 } 2111 OS.pango_layout_set_tabs(layout, tabArray); 2112 OS.pango_tab_array_free(tabArray); 2113 } 2114 } 2115 /* 2116 * Bug in Pango. A change in the tab stop array is not automatically reflected in the 2117 * pango layout object because the call pango_layout_set_tabs() does not free the 2118 * lines cache. The fix to use pango_layout_context_changed() to free the lines cache. 2119 */ 2120 OS.pango_layout_context_changed(layout); 2121 } 2122 2123 /** 2124 * Sets the receiver's text. 2125 * 2126 * @param text the new text 2127 * 2128 * @exception SWTException <ul> 2129 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 2130 * </ul> 2131 */ 2132 public void setText (String text) { 2133 checkLayout (); 2134 if (text.equals(this.text)) return; 2135 freeRuns(); 2136 this.text = text; 2137 styles = new StyleItem[2]; 2138 styles[0] = new StyleItem(); 2139 styles[1] = new StyleItem(); 2140 styles[styles.length - 1].start = cast(int)/*64bit*/text.length; 2141 } 2142 2143 /** 2144 * Sets the line width of the receiver, which determines how 2145 * text should be wrapped and aligned. The default value is 2146 * <code>-1</code> which means wrapping is disabled. 2147 * 2148 * @param width the new width 2149 * 2150 * @exception IllegalArgumentException <ul> 2151 * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li> 2152 * </ul> 2153 * @exception SWTException <ul> 2154 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> 2155 * </ul> 2156 * 2157 * @see #setAlignment(int) 2158 */ 2159 public void setWidth (int width) { 2160 checkLayout (); 2161 if (width < -1 || width is 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); 2162 freeRuns(); 2163 if (width is -1) { 2164 OS.pango_layout_set_width(layout, -1); 2165 bool rtl = OS.pango_context_get_base_dir(context) is OS.PANGO_DIRECTION_RTL; 2166 OS.pango_layout_set_alignment(layout, rtl ? OS.PANGO_ALIGN_RIGHT : OS.PANGO_ALIGN_LEFT); 2167 } else { 2168 OS.pango_layout_set_width(layout, width * OS.PANGO_SCALE); 2169 } 2170 } 2171 2172 static final bool isLam(int ch) { 2173 return ch is 0x0644; 2174 } 2175 2176 static final bool isAlef(int ch) { 2177 switch (ch) { 2178 case 0x0622: 2179 case 0x0623: 2180 case 0x0625: 2181 case 0x0627: 2182 case 0x0649: 2183 case 0x0670: 2184 case 0x0671: 2185 case 0x0672: 2186 case 0x0673: 2187 case 0x0675: 2188 return true; 2189 default: 2190 } 2191 return false; 2192 } 2193 2194 /** 2195 * Returns a string containing a concise, human-readable 2196 * description of the receiver. 2197 * 2198 * @return a string representation of the receiver 2199 */ 2200 public override String toString () { 2201 if (isDisposed()) return "TextLayout {*DISPOSED*}"; 2202 return Format( "TextLayout {{{}}", layout ); 2203 } 2204 2205 /* 2206 * Translate a client offset to an internal offset 2207 */ 2208 int translateOffset(int offset) { 2209 auto length_ = text.length; 2210 if (length_ is 0) return offset; 2211 if (invalidOffsets is null) return offset; 2212 for (int i = 0; i < invalidOffsets.length; i++) { 2213 if (offset < invalidOffsets[i]) break; 2214 offset++; 2215 } 2216 return offset; 2217 } 2218 2219 /* 2220 * Translate an internal offset to a client offset 2221 */ 2222 int untranslateOffset(int offset) { 2223 auto length_ = text.length; 2224 if (length_ is 0) return offset; 2225 if (invalidOffsets is null) return offset; 2226 for (int i = 0; i < invalidOffsets.length; i++) { 2227 if (offset is invalidOffsets[i]) { 2228 offset++; 2229 continue; 2230 } 2231 if (offset < invalidOffsets[i]) { 2232 return offset - i; 2233 } 2234 } 2235 return cast(int)/*64bit*/(offset - invalidOffsets.length); 2236 } 2237 2238 int validateOffset( in char[] cont, int offset, int step) { 2239 if (invalidOffsets is null) return offset + step; 2240 size_t i = step > 0 ? 0 : invalidOffsets.length - 1; 2241 do { 2242 if( offset is 0 && step < 0 ){ 2243 offset += step; 2244 } 2245 else{ 2246 offset += cont.toUTF8shift( offset, step ); 2247 } 2248 while (0 <= i && i < invalidOffsets.length) { 2249 if (invalidOffsets[i] is offset) break; 2250 i += step; 2251 } 2252 } while (0 <= i && i < invalidOffsets.length); 2253 return offset; 2254 } 2255 2256 int width () { 2257 int wrapWidth = OS.pango_layout_get_width(layout); 2258 if (wrapWidth !is -1) return OS.PANGO_PIXELS(wrapWidth); 2259 int w, h; 2260 OS.pango_layout_get_size(layout, &w, &h); 2261 return OS.PANGO_PIXELS(w + OS.pango_layout_get_indent(layout)); 2262 } 2263 2264 }