Skip to main content
summaryrefslogtreecommitdiffstats
blob: ccd01231c269942b926de1eaf9a60cf2a31a8aaf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
package org.eclipse.swt.custom;
/*
 * (c) Copyright IBM Corp. 2002.
 * All Rights Reserved
 */

import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.custom.StyledText.*;
import org.eclipse.swt.graphics.*;

/**
 * A StyledTextRenderer renders the content of a StyledText widget.
 * Subclasses can provide a different device (e.g., Display, Printer) 
 * to render on and implement abstract methods to return resources 
 * created on that device.
 */
abstract class StyledTextRenderer {
	private Device device;					// device to render on
	private Font regularFont;
	private Font boldFont;
	private boolean isBidi;
	private int leftMargin;
	private int tabLength;					// length in space characters of a tab character
	private int tabWidth;					// width in pixels of a tab character
	private int lineHeight;					// height in pixels of a line
	private int lineEndSpaceWidth;			// width in pixels of the space used to represent line delimiters
	
/**
 * Creates an instance of <class>StyledTextRenderer</class>.
 * </p>
 * @param device Device to render on
 * @param regularFont Font to use for regular (non-bold) text
 * @param isBidi true=bidi platform, false=no bidi platform
 * @param leftMargin margin to the left of the text
 */
StyledTextRenderer(Device device, Font regularFont, boolean isBidi, int leftMargin) {
	FontData fontData = regularFont.getFontData()[0];

	fontData.setStyle(fontData.getStyle() | SWT.BOLD);
	boldFont = new Font(device, fontData);
	this.device = device;
	this.regularFont = regularFont;	
	this.isBidi = isBidi;
	this.leftMargin = leftMargin;
}
/**
 * Returns the width of the specified text. 
 * </p>
 *
 * @param text text to be measured.
 * @param startOffset offset of the character to start measuring.
 * @param length number of characters to measure. Tabs are counted 
 * 	as one character in this parameter.
 * @param startXOffset x position of "startOffset" in "text". Used for
 * 	calculating tab stops
 * @param bidi the bidi object to use for measuring text in bidi locales. 
 * @return width of the text with tabs expanded to tab stops or 0 if the 
 * 	startOffset or length is outside the specified text.
 */
int bidiTextWidth(String text, int startOffset, int length, int startXOffset, StyledTextBidi bidi) {
	int endOffset = startOffset + length;
	int textLength = text.length();
	
	if (startOffset < 0 || startOffset >= textLength || endOffset > textLength) {
		return 0;
	}
	// Use lastCaretDirection in order to get same results as during
	// caret positioning (setBidiCaretLocation). Fixes 1GKU4C5.
	return bidi.getCaretPosition(endOffset, getLastCaretDirection()) - startXOffset;
}
/**
 * Calculates the line height and space width.
 */
void calculateLineHeight() {
	GC gc = getGC();
	
	lineHeight = gc.getFontMetrics().getHeight();	
	lineEndSpaceWidth = gc.stringExtent(" ").x;
	disposeGC(gc);
}
/**
 * Disposes the resource created by the receiver.
 */
void dispose() {
	if (boldFont != null) {
		boldFont.dispose();
		boldFont = null;
	}
}
/**
 * Dispose the specified GC.
 * Allows subclasses to reuse GCs.
 * </p>
 * @param gc GC to dispose.
 */
protected abstract void disposeGC(GC gc);
/** 
 * Draws a line of text at the specified location.
 * </p>
 *
 * @param line the line to draw
 * @param lineIndex	index of the line to draw
 * @param paintY y location to draw at
 * @param gc GC to draw on
 * @param widgetBackground the widget background color. 
 * 	Used as the default rendering color.
 * @param widgetForeground the widget foreground color. 
 * 	Used as the default rendering color. 
 * @param currentFont the font currently set in gc. Cached for better 
 * 	performance.
 * @param clearBackground true if the line background should be drawn 
 * 	explicitly.
 */
void drawLine(String line, int lineIndex, int paintY, GC gc, Color widgetBackground, Color widgetForeground, FontData currentFont, boolean clearBackground) {
	int lineOffset = getContent().getOffsetAtLine(lineIndex);
	int lineLength = line.length();
	Point selection = getSelection();
	int selectionStart = selection.x;
	int selectionEnd = selection.y;
	StyleRange[] styles = new StyleRange[0];
	Color lineBackground = null;
	StyledTextEvent event = getLineStyleData(lineOffset, line);
	StyledTextBidi bidi = null;
	
	if (event != null) {
		styles = event.styles;
	}
	if (isBidi()) {
		setLineFont(gc, currentFont, SWT.NORMAL);
		bidi = getStyledTextBidi(line, lineOffset, gc, styles);
	}
	event = getLineBackgroundData(lineOffset, line);
	if (event != null) {
		lineBackground = event.lineBackground;
	}
	if (lineBackground == null) {
		lineBackground = widgetBackground;
	}
	if (clearBackground &&
		(isFullLineSelection() == false || 
		 selectionStart > lineOffset || 
		 selectionEnd <= lineOffset + lineLength)) {
		// draw background if full selection is off or if line is not 
		// completely selected
		gc.setBackground(lineBackground);
		gc.setForeground(lineBackground);
		gc.fillRectangle(leftMargin, paintY, getClientArea().width, lineHeight);
	}
	if (selectionStart != selectionEnd) {
		drawLineSelectionBackground(line, lineOffset, styles, paintY, gc, currentFont, bidi);
	}
	if (selectionStart != selectionEnd && 
		((selectionStart >= lineOffset && selectionStart < lineOffset + lineLength) || 
		 (selectionStart < lineOffset && selectionEnd > lineOffset))) {
		styles = getSelectionLineStyles(styles);
	}
	if (isBidi()) {
		int paintX = bidiTextWidth(line, 0, 0, 0, bidi);
		drawStyledLine(line, lineOffset, 0, styles, paintX, paintY, gc, lineBackground, widgetForeground, currentFont, bidi);
	}
	else {
		drawStyledLine(line, lineOffset, 0, styles, 0, paintY, gc, lineBackground, widgetForeground, currentFont, bidi);
	}
}
/** 
 * Draws the background of the line selection.
 * </p>
 *
 * @param line the line to draw
 * @param lineOffset offset of the first character in the line.
 * 	Relative to the start of the document.
 * @param styles line styles
 * @param paintY y location to draw at
 * @param gc GC to draw on
 * @param currentFont the font currently set in gc. Cached for 
 * 	better performance.
 * @param bidi the bidi object to use for measuring and rendering 
 * 	text in bidi locales. null when not in bidi mode.
 */
protected abstract void drawLineSelectionBackground(String line, int lineOffset, StyleRange[] styles, int paintY, GC gc, FontData currentFont, StyledTextBidi bidi);
/** 
 * Draws the line at the specified location.
 * </p>
 *
 * @param line the line to draw
 * @param lineOffset offset of the first character in the line.
 * 	Relative to the start of the document.
 * @param renderOffset offset of the first character that should 
 * 	be rendered. Relative to the start of the line.
 * @param styles the styles to use for rendering line segments. 
 * 	May be empty but not null.
 * @param paintX x location to draw at, not used in bidi mode
 * @param paintY y location to draw at
 * @param gc GC to draw on
 * @param lineBackground line background color, used when no style 
 * 	is specified for a line segment.
 * @param lineForeground line foreground color, used when no style 
 * 	is specified for a line segment.
 * @param currentFont the font currently set in gc. Cached for better 
 * 	performance.
 * @param bidi the bidi object to use for measuring and rendering 
 * 	text in bidi locales. null when not in bidi mode.
 */
private void drawStyledLine(String line, int lineOffset, int renderOffset, StyleRange[] styles, int paintX, int paintY, GC gc, Color lineBackground, Color lineForeground, FontData currentFont, StyledTextBidi bidi) {
	int lineLength = line.length();
	int horizontalScrollOffset = getHorizontalPixel();
	Color background = gc.getBackground();
	Color foreground = gc.getForeground();	
	StyleRange style = null;
	StyleRange[] filteredStyles = filterLineStyles(styles);	
	int renderStopX = getClientArea().width + horizontalScrollOffset;
		
	// Always render the entire line when in a bidi locale.
	// Since we render the line in logical order we may start past the end
	// of the visual right border of the client area and work towards the
	// left.
	for (int i = 0; i < styles.length && (paintX < renderStopX || bidi != null); i++) {
		int styleLineLength;
		int styleLineStart;
		int styleLineEnd;
		style = styles[i];
		styleLineEnd = style.start + style.length - lineOffset;
		styleLineStart = Math.max(style.start - lineOffset, 0);
		// render unstyled text between the start of the current 
		// style range and the end of the previously rendered 
		// style range
		if (styleLineStart > renderOffset) {
			background = setLineBackground(gc, background, lineBackground);
			foreground = setLineForeground(gc, foreground, lineForeground);
			setLineFont(gc, currentFont, SWT.NORMAL);			
			// don't try to render more text than requested
			styleLineStart = Math.min(lineLength, styleLineStart);
			paintX = drawText(line, renderOffset, styleLineStart - renderOffset, paintX, paintY, gc, bidi);
			renderOffset = styleLineStart;
		}
		else
		if (styleLineEnd <= renderOffset) {
			// style ends before render start offset
			// skip to the next style
			continue;
		}
		if (styleLineStart >= lineLength) {
			// there are line styles but no text for those styles
			// possible when called with partial line text
			break;
		}		
		styleLineLength = Math.min(styleLineEnd, lineLength) - renderOffset;
		// set style background color if specified
		if (style.background != null) {
			background = setLineBackground(gc, background, style.background);
			foreground = setLineForeground(gc, foreground, style.background);
			if (bidi != null) {
				bidi.fillBackground(renderOffset, styleLineLength, leftMargin - horizontalScrollOffset, paintY, lineHeight);
			}
			else {
				int fillWidth = textWidth(line, lineOffset, renderOffset, styleLineLength, filteredStyles, paintX, gc, currentFont);
				gc.fillRectangle(paintX - horizontalScrollOffset + leftMargin, paintY, fillWidth, lineHeight);
			}
		}
		else {
			background = setLineBackground(gc, background, lineBackground);
		}
		// set style foreground color if specified
		if (style.foreground != null) {
			foreground = setLineForeground(gc, foreground, style.foreground);
		}
		else {
			foreground = setLineForeground(gc, foreground, lineForeground);
		}
		setLineFont(gc, currentFont, style.fontStyle);
		paintX = drawText(line, renderOffset, styleLineLength, paintX, paintY, gc, bidi);
		renderOffset += styleLineLength;
	}
	// render unstyled text at the end of the line
	if ((style == null || renderOffset < lineLength) && 
		(paintX < renderStopX || bidi != null)) {
		setLineBackground(gc, background, lineBackground);
		setLineForeground(gc, foreground, lineForeground);
		setLineFont(gc, currentFont, SWT.NORMAL);
		drawText(line, renderOffset, lineLength - renderOffset, paintX, paintY, gc, bidi);
	}	
}
/**
 * Draws the text at the specified location. Expands tabs to tab 
 * stops using the widget tab width.
 * </p>
 *
 * @param text text to draw 
 * @param startOffset offset of the first character in text to draw 
 * @param length number of characters to draw
 * @param paintX x location to start drawing at, not used in bidi mode
 * @param paintY y location to draw at. Unused when draw is false
 * @param gc GC to draw on
 * 	location where drawing would stop
 * @param bidi the bidi object to use for measuring and rendering 
 * 	text in bidi locales. null when not in bidi mode.
 * @return x location where drawing stopped or 0 if the startOffset or 
 * 	length is outside the specified text. In bidi mode this value is 
 * 	the same as the paintX input parameter.
 */
private int drawText(String text, int startOffset, int length, int paintX, int paintY, GC gc, StyledTextBidi bidi) {
	int endOffset = startOffset + length;
	int textLength = text.length();
	int horizontalScrollOffset = getHorizontalPixel();
	
	if (startOffset < 0 || startOffset >= textLength || startOffset + length > textLength) {
		return paintX;
	}
	for (int i = startOffset; i < endOffset; i++) {
		int tabIndex = text.indexOf(StyledText.TAB, i);
		// is tab not present or past the rendering range?
		if (tabIndex == -1 || tabIndex > endOffset) {
			tabIndex = endOffset;
		}
		if (tabIndex != i) {
			String tabSegment = text.substring(i, tabIndex);
			if (bidi != null) {
				bidi.drawBidiText(i, tabIndex - i, leftMargin - horizontalScrollOffset, paintY);
			}
			else {
				gc.drawString(tabSegment, paintX - horizontalScrollOffset + leftMargin, paintY, true);
				paintX += gc.stringExtent(tabSegment).x;
				if (tabIndex != endOffset && tabWidth > 0) {
					paintX = getTabStop(paintX);
				}
			}
			i = tabIndex;
		}
		else 		// is tab at current rendering offset?
		if (tabWidth > 0 && isBidi() == false) {
			paintX = getTabStop(paintX);
		}
	}
	return paintX;
}
/** 
 * Filter the given style ranges based on the font style and 
 * return the unchanged styles only if there is at least one 
 * non-regular (e.g., bold) font.
 * </p>
 * 
 * @param styles styles that may contain font styles.
 * @return null if the styles contain only regular font styles, the 
 * 	unchanged styles otherwise.
 */
StyleRange[] filterLineStyles(StyleRange[] styles) {
	if (styles != null) {
		int styleIndex = 0;
		while (styleIndex < styles.length && styles[styleIndex].fontStyle == SWT.NORMAL) {
			styleIndex++;
		}
		if (styleIndex == styles.length) {
			styles = null;
		}
	}
	return styles;
}
/**
 * Returns the visible client area that can be used for rendering.
 * </p>
 * @return the visible client area that can be used for rendering.
 */
protected abstract Rectangle getClientArea();
/**
 * Returns the <class>StyledTextContent</class> to use for line offset
 * calculations.
 * </p>
 * @return the <class>StyledTextContent</class> to use for line offset
 * calculations.
 */
protected abstract StyledTextContent getContent();
/**
 * Returns the Device that is being rendered on.
 * </p>
 * @return the Device that is being rendered on.
 */
Device getDevice() {
	return device;
}
/**
 * Returns an array of text ranges that have a font style specified (e.g., SWT.BOLD).
 * </p>
 * @param styles style ranges in the line
 * @param lineOffset start index of the line, relative to the start of the document
 * @param length of the line
 * @return StyleRange[], array of ranges with a font style specified, 
 *	null if styles parameter is null
 */
private StyleRange[] getFontStyleRanges(StyleRange[] styles, int lineOffset, int lineLength) {
	int count = 0;
	StyleRange[] ranges = null;
	
	if (styles == null) {
		return null;
	}
	// figure out the number of ranges with font styles
	for (int i = 0; i < styles.length; i++) {
		StyleRange style = styles[i];
		if (style.start - lineOffset < lineLength) {
			if (style.fontStyle == SWT.BOLD) {
				count++;
			}
		}
	}
	// get the style information
	if (count > 0) {
		ranges = new StyleRange[count];
		count = 0;
		for (int i = 0; i < styles.length; i++) {
			StyleRange style = styles[i];
			int styleLineStart = style.start - lineOffset;
			if (styleLineStart < lineLength) {			
				if (style.fontStyle == SWT.BOLD) {
					StyleRange newStyle = new StyleRange();
					newStyle.start = Math.max(0, styleLineStart);
					newStyle.length = (Math.min(styleLineStart + style.length, lineLength)) - newStyle.start;
					ranges[count] = newStyle;
					count++;
				}
			}		
		}
	}
	return ranges;
}
/**
 * Returns the text segments that should be treated as if they 
 * had a different direction than the surrounding text.
 * </p>
 *
 * @param lineOffset offset of the first character in the line. 
 * 	0 based from the beginning of the document.
 * @param line text of the line to specify bidi segments for
 * @return text segments that should be treated as if they had a
 * 	different direction than the surrounding text. Only the start 
 * 	index of a segment is specified, relative to the start of the 
 * 	line. Always starts with 0 and ends with the line length. 
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the segment indices returned 
 * 		by the listener do not start with 0, are not in ascending order,
 * 		exceed the line length or have duplicates</li>
 * </ul>
 */
protected abstract int[] getBidiSegments(int lineOffset, String lineText);
/**
 * Returns the GC to use for rendering and measuring.
 * </p>
 * @return the GC to use for rendering and measuring.
 */
protected abstract GC getGC();
/**
 * Returns the horizontal scroll position.
 * </p>
 * @return the horizontal scroll position.
 */
protected abstract int getHorizontalPixel();
/**
 * Returns the most recent caret direction.
 * Used for measuring caret positions.
 * </p>
 * @return the most recent caret direction.
 */
protected abstract int getLastCaretDirection();
/**
 * Method getLeftMargin.
 * @return int
 */
int getLeftMargin() {
	return leftMargin;
}
/**
 * Returns the width in pixels of the space used to represent line delimiters.
 * @return the width in pixels of the space used to represent line delimiters.
 */
int getLineEndSpaceWidth() {
	return lineEndSpaceWidth;
}
/**
 * Returns the line background data for the given line or null if 
 * there is none.
 * </p>
 * @param lineOffset offset of the line start relative to the start
 * 	of the content.
 * @param line line to get line background data for
 * @return line background data for the given line.
 */
protected abstract StyledTextEvent getLineBackgroundData(int lineOffset, String line);
/**
 * Returns the height in pixels of a line.
 * </p>
 * @return the height in pixels of a line.
 */
int getLineHeight() {
	return lineHeight;
}
/**
 * Returns the line style data for the specified line.
 * The lineOffset and line may specify a segment of a logical line stored
 * in the <class>StyledTextContent</class> of the widget.
 * The returned styles are guaranteed to be at least partially on the
 * segment.
 * </p>
 * @param event the styles for the logical line
 * @param lineOffset offset of the line start relative to the start of 
 * 	the content.
 * @param line line to get line styles for
 * @return line style data for the given line segment. Styles may start 
 * 	before line start and end after line end but are guaranteed to be at 
 * 	least partially on the line.
 */
StyledTextEvent getLineStyleData(StyledTextEvent event, int lineOffset, String line) {
	int lineLength = line.length();
	
	if (event.styles != null && getWordWrap()) {
		event.styles = getVisualLineStyleData(event.styles, lineOffset, lineLength);
	}
	if (event.styles == null) {
		event.styles = new StyleRange[0];
	}
	else
	if (isBidi()) {
		GC gc = getGC();
		if (StyledTextBidi.isLigated(gc)) {
			// Check for ligatures that are partially styled, if one is found
			// automatically apply the style to the entire ligature.
			// Since ligatures can't extend over multiple lines (they aren't 
			// ligatures if they are separated by a line delimiter) we can ignore
			// style starts or ends that are not on the current line.
			// Note that there is no need to deal with segments when checking for
			// the ligatures.
			StyledTextBidi bidi = new StyledTextBidi(gc, line, new int[] {0, lineLength});
			for (int i=0; i<event.styles.length; i++) {
				StyleRange range = event.styles[i];
				StyleRange newRange = null;
				int relativeStart = range.start - lineOffset;
				if (relativeStart >= 0) {
					int startLigature = bidi.getLigatureStartOffset(relativeStart);
					if (startLigature != relativeStart) {
						newRange = (StyleRange) range.clone();
						range = event.styles[i] = newRange;
						range.start = range.start - (relativeStart - startLigature);
						range.length = range.length + (relativeStart - startLigature);
					}
				}
				int rangeEnd = range.start + range.length;
				int relativeEnd = rangeEnd - lineOffset - 1;
				if (relativeEnd < lineLength) {
					int endLigature = bidi.getLigatureEndOffset(relativeEnd);
					if (endLigature != relativeEnd) {
						if (newRange == null) {
							newRange = (StyleRange) range.clone();
							range = event.styles[i] = newRange;
						}
						range.length = range.length + (endLigature - relativeEnd);
					}
				}
	        }
	    }
	    disposeGC(gc);
	}
	return event;
}
/**
 * Returns the line style data for the given line or null if there is 
 * none. If there is a LineStyleListener but it does not set any styles, 
 * the StyledTextEvent.styles field will be initialized to an empty 
 * array.
 * </p>
 * 
 * @param lineOffset offset of the line start relative to the start of 
 * 	the content.
 * @param line line to get line styles for
 * @return line style data for the given line. Styles may start before 
 * 	line start and end after line end
 */
protected abstract StyledTextEvent getLineStyleData(int lineOffset, String line);
/**
 * Returns the widget selection.
 * </p>
 * @return the widget selection.
 */
protected abstract Point getSelection();
/**
 * Merges the selection into the styles that are passed in.
 * The font style of existing style ranges is preserved in the selection.
 * </p>
 * @param styles the existing styles that the selection should be 
 * 	applied to.
 * @return the selection style range merged with the existing styles
 */
protected abstract StyleRange[] getSelectionLineStyles(StyleRange[] styles);
/**
 * Returns a StyledTextBidi object for the specified line.
 * </p>
 * 
 * @param lineText the line that the StyledTextBidi object should 
 * 	work on.
 * @param lineOffset offset of the beginning of the line, relative 
 * 	to the beginning of the document
 * @param gc GC to use when creating a new StyledTextBidi object.
 * @param styles StyleRanges to use when creating a new StyledTextBidi 
 * 	object.
 * @return a StyledTextBidi object for the specified line.
 */
StyledTextBidi getStyledTextBidi(String lineText, int lineOffset, GC gc, StyleRange[] styles) {
	StyleRange[] fontStyles = null;
	
	if (styles == null) {
		StyledTextEvent event = getLineStyleData(lineOffset, lineText);
		if (event != null) {
			fontStyles = getFontStyleRanges(event.styles, lineOffset, lineText.length());
		}
	}
	else {
		fontStyles = getFontStyleRanges(styles, lineOffset, lineText.length());
	}
	return new StyledTextBidi(gc, tabWidth, lineText, fontStyles, boldFont, getBidiSegments(lineOffset, lineText));
}	
/**
 * Returns the next tab stop for the specified x location.
 * </p>
 *
 * @param x the x location in front of a tab
 * @return the next tab stop for the specified x location.
 */
private int getTabStop(int x) {
	int spaceWidth = tabWidth / tabLength;

	// make sure tab stop is at least one space width apart 
	// from the last character. fixes 4844.
	if (tabWidth - x % tabWidth < spaceWidth) {
		x += tabWidth;
	}
	x += tabWidth;
	x -= x % tabWidth;
	return x;
}
/**
 * Returns styles for the specified visual (wrapped) line.
 * </p>
 * 
 * @param logicalStyles the styles for a logical (unwrapped) line
 * @param lineOffset offset of the visual line
 * @param lineLength length of the visual line
 * @return styles in the logicalStyles array that are at least 
 * 	partially on the specified visual line.
 */
StyleRange[] getVisualLineStyleData(StyleRange[] logicalStyles, int lineOffset, int lineLength) {
	int lineEnd = lineOffset + lineLength;
	int oldStyleCount = logicalStyles.length;
	int newStyleCount = 0;
	
	for (int i = 0; i < oldStyleCount; i++) {
		StyleRange style = logicalStyles[i];
		if (style.start < lineEnd && style.start + style.length > lineOffset) {
			newStyleCount++;
		}
	}
	if (newStyleCount != oldStyleCount) {
		StyleRange[] newStyles = new StyleRange[newStyleCount];
		for (int i = 0, j = 0; i < oldStyleCount; i++) {
			StyleRange style = logicalStyles[i];
			if (style.start < lineEnd && style.start + style.length > lineOffset) {
				newStyles[j++] = logicalStyles[i];						
			}
		}
		logicalStyles = newStyles;
	}
	return logicalStyles;
}
/**
 * Returns the word wrap state.
 * </p>
 * @return true=word wrap is on. false=no word wrap, lines may extend 
 * 	beyond the right side of the client area.
 */
protected abstract boolean getWordWrap();
/**
 * Returns whether the widget is running on a bidi platform.
 * </p>
 * @return true=the widget is running on a bidi platform, false=otherwise.
 */
boolean isBidi() {
	return isBidi;
}
/**
 * Returns whether the widget was created with the SWT.FULL_SELECTION style.
 * </p>
 * @return true=the widget is running in full line selection mode, 
 * 	false=otherwise
 */
protected abstract boolean isFullLineSelection();
/** 
 * Sets the background of the specified GC for a line rendering operation,
 * if it is not already set.
 * </p>
 *
 * @param gc GC to set the background color in
 * @param currentBackground background color currently set in gc
 * @param newBackground new background color of gc
 */
private Color setLineBackground(GC gc, Color currentBackground, Color newBackground) {
	if (currentBackground.equals(newBackground) == false) {
		gc.setBackground(newBackground);
	}
	return newBackground;	
}
/** 
 * Sets the font of the specified GC if it is not already set.
 * </p>
 *
 * @param gc GC to set the font in
 * @param currentFont font data of font currently set in gc
 * @param style desired style of the font in gc. Can be one of 
 * 	SWT.NORMAL, SWT.BOLD
 */
private void setLineFont(GC gc, FontData currentFont, int style) {
	if (currentFont.getStyle() != style) {
		if (style == SWT.BOLD) {
			currentFont.setStyle(style);
			gc.setFont(boldFont);
		}
		else
		if (style == SWT.NORMAL) {
			currentFont.setStyle(style);
			gc.setFont(regularFont);
		}
	}
}
/** 
 * Sets the foreground of the specified GC for a line rendering operation,
 * if it is not already set.
 * </p>
 *
 * @param gc GC to set the foreground color in
 * @param currentForeground foreground color currently set in gc
 * @param newForeground new foreground color of gc
 */
private Color setLineForeground(GC gc, Color currentForeground, Color newForeground) {
	if (currentForeground.equals(newForeground) == false) {
		gc.setForeground(newForeground);
	}
	return newForeground;
}
/**
 * Calculates the width in pixel of a tab character
 * </p>
 * @param tabLength number of space characters represented by a tab character.
 */
void setTabLength(int tabLength) {
	GC gc = getGC();
	StringBuffer tabBuffer = new StringBuffer(tabLength);
	
	this.tabLength = tabLength;
	for (int i = 0; i < tabLength; i++) {
		tabBuffer.append(' ');
	}
	tabWidth = gc.stringExtent(tabBuffer.toString()).x;
	disposeGC(gc);
}
/**
 * Measures the text as rendered at the specified location. Expand tabs to tab stops using
 * the widget tab width.
 * </p>
 *
 * @param text text to draw 
 * @param textStartOffset offset of the first character in text relative 
 * 	to the first character in the document
 * @param lineStyles styles of the line
 * @param paintX x location to start drawing at
 * @param gc GC to draw on
 * @param fontData the font data of the font currently set in gc
 * @return x location where drawing stopped or 0 if the startOffset or 
 * 	length is outside the specified text.
 */
private int styledTextWidth(String text, int textStartOffset, StyleRange[] lineStyles, int paintX, GC gc, FontData fontData) {
	String textSegment;
	int textLength = text.length();
	int textIndex = 0;

	for (int styleIndex = 0; styleIndex < lineStyles.length; styleIndex++) {
		StyleRange style = lineStyles[styleIndex];
		int textEnd;
		int styleSegmentStart = style.start - textStartOffset;
		if (styleSegmentStart + style.length < 0) {
			continue;
		}
		if (styleSegmentStart >= textLength) {
			break;
		}
		// is there a style for the current string position?
		if (textIndex < styleSegmentStart) {
			setLineFont(gc, fontData, SWT.NORMAL);
			textSegment = text.substring(textIndex, styleSegmentStart);
			paintX += gc.stringExtent(textSegment).x;
			textIndex = styleSegmentStart;
		}
		textEnd = Math.min(textLength, styleSegmentStart + style.length);
		setLineFont(gc, fontData, style.fontStyle);
		textSegment = text.substring(textIndex, textEnd);
		paintX += gc.stringExtent(textSegment).x;
		textIndex = textEnd;
	}
	// is there unmeasured and unstyled text?
	if (textIndex < textLength) {
		setLineFont(gc, fontData, SWT.NORMAL);
		textSegment = text.substring(textIndex, textLength);
		paintX += gc.stringExtent(textSegment).x;
	}
	return paintX;
}
/**
 * Returns the width of the specified text. Expand tabs to tab stops using
 * the widget tab width.
 * </p>
 *
 * @param text text to be measured.
 * @param lineOffset offset of the first character in the line. 
 * @param startOffset offset of the character to start measuring and 
 * 	expand tabs.
 * @param length number of characters to measure. Tabs are counted 
 * 	as one character in this parameter.
 * @param styles line styles
 * @param startXOffset x position of "startOffset" in "text". Used for
 * 	calculating tab stops
 * @param gc GC to use for measuring text
 * @param fontData the font currently set in gc. Cached for better performance.
 * @return width of the text with tabs expanded to tab stops or 0 if the 
 * 	startOffset or length is outside the specified text.
 */
int textWidth(String text, int lineOffset, int startOffset, int length, StyleRange[] lineStyles, int startXOffset, GC gc, FontData fontData) {
	int paintX = 0;
	int endOffset = startOffset + length;
	int textLength = text.length();
	
	if (startOffset < 0 || startOffset >= textLength || endOffset > textLength) {
		return paintX;
	}
	for (int i = startOffset; i < endOffset; i++) {
		int tabIndex = text.indexOf(StyledText.TAB, i);
		// is tab not present or past the rendering range?
		if (tabIndex == -1 || tabIndex > endOffset) {
			tabIndex = endOffset;
		}
		if (tabIndex != i) {
			String tabSegment = text.substring(i, tabIndex);
			if (lineStyles != null) {
				paintX = styledTextWidth(tabSegment, lineOffset + i, lineStyles, paintX, gc, fontData);
			}
			else {
				setLineFont(gc, fontData, SWT.NORMAL);
				paintX += gc.stringExtent(tabSegment).x;
			}
			if (tabIndex != endOffset && tabWidth > 0) {
				paintX = getTabStop(startXOffset + paintX) - startXOffset;
			}
			i = tabIndex;
		}
		else 		
		if (tabWidth > 0) {
			paintX = getTabStop(startXOffset + paintX) - startXOffset;
		}
	}
	return paintX;
}
}

Back to the top