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.custom.StyledTextRenderer;
14 
15 
16 
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.graphics.Color;
19 import org.eclipse.swt.graphics.Device;
20 import org.eclipse.swt.graphics.Font;
21 import org.eclipse.swt.graphics.FontData;
22 import org.eclipse.swt.graphics.FontMetrics;
23 import org.eclipse.swt.graphics.GC;
24 import org.eclipse.swt.graphics.GlyphMetrics;
25 import org.eclipse.swt.graphics.Point;
26 import org.eclipse.swt.graphics.Rectangle;
27 import org.eclipse.swt.graphics.TextLayout;
28 import org.eclipse.swt.graphics.TextStyle;
29 import org.eclipse.swt.widgets.Display;
30 import org.eclipse.swt.widgets.IME;
31 import org.eclipse.swt.widgets.ScrollBar;
32 import org.eclipse.swt.custom.StyledText;
33 import org.eclipse.swt.custom.Bullet;
34 import org.eclipse.swt.custom.StyleRange;
35 import org.eclipse.swt.custom.StyledText;
36 import org.eclipse.swt.custom.StyledTextContent;
37 import org.eclipse.swt.custom.TextChangingEvent;
38 import org.eclipse.swt.custom.ST;
39 import org.eclipse.swt.custom.StyledTextEvent;
40 
41 import java.lang.all;
42 import java.nonstandard.UnsafeUtf;
43 
44 /**
45  * A StyledTextRenderer renders the content of a StyledText widget.
46  * This class can be used to render to the display or to a printer.
47  */
48 class StyledTextRenderer {
49     Device device;
50     StyledText styledText;
51     StyledTextContent content;
52 
53     /* Font info */
54     Font regularFont, boldFont, italicFont, boldItalicFont;
55     int tabWidth;
56     int ascent, descent;
57     int averageCharWidth;
58 
59     /* Line data */
60     int topIndex = -1;
61     TextLayout[] layouts;
62     int lineCount;
63     int[] lineWidth;
64     int[] lineHeight;
65     LineInfo[] lines;
66     int maxWidth;
67     int maxWidthLineIndex;
68     bool idleRunning;
69 
70     /* Bullet */
71     Bullet[] bullets;
72     int[] bulletsIndices;
73     int[] redrawLines;
74 
75     /* Style data */
76     int[] ranges;
77     int styleCount;
78     StyleRange[] styles;
79     StyleRange[] stylesSet;
80     int stylesSetCount = 0;
81     const static int BULLET_MARGIN = 8;
82 
83     const static bool COMPACT_STYLES = true;
84     const static bool MERGE_STYLES = true;
85 
86     const static int GROW = 32;
87     const static int IDLE_TIME = 50;
88     const static int CACHE_SIZE = 128;
89 
90     const static int BACKGROUND = 1 << 0;
91     const static int ALIGNMENT = 1 << 1;
92     const static int INDENT = 1 << 2;
93     const static int JUSTIFY = 1 << 3;
94     const static int SEGMENTS = 1 << 5;
95 
96     static class LineInfo {
97         int flags;
98         Color background;
99         int alignment;
100         int indent;
101         bool justify;
102         int[] segments;
103 
104         public this() {
105         }
106         public this(LineInfo info) {
107             if (info !is null) {
108                 flags = info.flags;
109                 background = info.background;
110                 alignment = info.alignment;
111                 indent = info.indent;
112                 justify = info.justify;
113                 segments = info.segments;
114             }
115         }
116     }
117 
118 this(Device device, StyledText styledText) {
119     this.device = device;
120     this.styledText = styledText;
121 }
122 int addMerge(int[] mergeRanges, StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) {
123     int rangeCount = styleCount << 1;
124     StyleRange endStyle = null;
125     int endStart = 0, endLength = 0;
126     if (modifyEnd < rangeCount) {
127         endStyle = styles[modifyEnd >> 1];
128         endStart = ranges[modifyEnd];
129         endLength = ranges[modifyEnd + 1];
130     }
131     int grow = mergeCount - (modifyEnd - modifyStart);
132     if (rangeCount + grow >= ranges.length) {
133         int[] tmpRanges = new int[ranges.length + grow + (GROW << 1)];
134         System.arraycopy(ranges, 0, tmpRanges, 0, modifyStart);
135         StyleRange[] tmpStyles = new StyleRange[styles.length + (grow >> 1) + GROW];
136         System.arraycopy(styles, 0, tmpStyles, 0, modifyStart >> 1);
137         if (rangeCount > modifyEnd) {
138             System.arraycopy(ranges, modifyEnd, tmpRanges, modifyStart + mergeCount, rangeCount - modifyEnd);
139             System.arraycopy(styles, modifyEnd >> 1, tmpStyles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1));
140         }
141         ranges = tmpRanges;
142         styles = tmpStyles;
143     } else {
144         if (rangeCount > modifyEnd) {
145             System.arraycopy(ranges, modifyEnd, ranges, modifyStart + mergeCount, rangeCount - modifyEnd);
146             System.arraycopy(styles, modifyEnd >> 1, styles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1));
147         }
148     }
149     if (MERGE_STYLES) {
150         int j = modifyStart;
151         for (int i = 0; i < mergeCount; i += 2) {
152             if (j > 0 && ranges[j - 2] + ranges[j - 1] is mergeRanges[i] && mergeStyles[i >> 1].similarTo(styles[(j - 2) >> 1])) {
153                 ranges[j - 1] += mergeRanges[i + 1];
154             } else {
155                 styles[j >> 1] = mergeStyles[i >> 1];
156                 ranges[j++] = mergeRanges[i];
157                 ranges[j++] = mergeRanges[i + 1];
158             }
159         }
160         if (endStyle !is null && ranges[j - 2] + ranges[j - 1] is endStart && endStyle.similarTo(styles[(j - 2) >> 1])) {
161             ranges[j - 1] += endLength;
162             modifyEnd += 2;
163             mergeCount += 2;
164         }
165         if (rangeCount > modifyEnd) {
166             System.arraycopy(ranges, modifyStart + mergeCount, ranges, j, rangeCount - modifyEnd);
167             System.arraycopy(styles, (modifyStart + mergeCount) >> 1, styles, j >> 1, styleCount - (modifyEnd >> 1));
168         }
169         grow = (j - modifyStart) - (modifyEnd - modifyStart);
170     } else {
171         System.arraycopy(mergeRanges, 0, ranges, modifyStart, mergeCount);
172         System.arraycopy(mergeStyles, 0, styles, modifyStart >> 1, mergeCount >> 1);
173     }
174     styleCount += grow >> 1;
175     return grow;
176 }
177 int addMerge(StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) {
178     int grow = mergeCount - (modifyEnd - modifyStart);
179     StyleRange endStyle = null;
180     if (modifyEnd < styleCount) endStyle = styles[modifyEnd];
181     if (styleCount + grow >= styles.length) {
182         StyleRange[] tmpStyles = new StyleRange[styles.length + grow + GROW];
183         System.arraycopy(styles, 0, tmpStyles, 0, modifyStart);
184         if (styleCount > modifyEnd) {
185             System.arraycopy(styles, modifyEnd, tmpStyles, modifyStart + mergeCount, styleCount - modifyEnd);
186         }
187         styles = tmpStyles;
188     } else {
189         if (styleCount > modifyEnd) {
190             System.arraycopy(styles, modifyEnd, styles, modifyStart + mergeCount, styleCount - modifyEnd);
191         }
192     }
193     if (MERGE_STYLES) {
194         int j = modifyStart;
195         for (int i = 0; i < mergeCount; i++) {
196             StyleRange newStyle = mergeStyles[i], style;
197             if (j > 0 && (style = styles[j - 1]).start + style.length is newStyle.start && newStyle.similarTo(style)) {
198                 style.length += newStyle.length;
199             } else {
200                 styles[j++] = newStyle;
201             }
202         }
203         StyleRange style = styles[j - 1];
204         if (endStyle !is null && style.start + style.length is endStyle.start && endStyle.similarTo(style)) {
205             style.length += endStyle.length;
206             modifyEnd++;
207             mergeCount++;
208         }
209         if (styleCount > modifyEnd) {
210             System.arraycopy(styles, modifyStart + mergeCount, styles, j, styleCount - modifyEnd);
211         }
212         grow = (j - modifyStart) - (modifyEnd - modifyStart);
213     } else {
214         System.arraycopy(mergeStyles, 0, styles, modifyStart, mergeCount);
215     }
216     styleCount += grow;
217     return grow;
218 }
219 void calculate(int startLine, int lineCount) {
220     int endLine = startLine + lineCount;
221     if (startLine < 0 || endLine > lineWidth.length) {
222         return;
223     }
224     int hTrim = styledText.leftMargin + styledText.rightMargin + styledText.getCaretWidth();
225     for (int i = startLine; i < endLine; i++) {
226         if (lineWidth[i] is -1 || lineHeight[i] is -1) {
227             TextLayout layout = getTextLayout(i);
228             Rectangle rect = layout.getBounds();
229             lineWidth[i] = rect.width + hTrim;
230             lineHeight[i] = rect.height;
231             disposeTextLayout(layout);
232         }
233         if (lineWidth[i] > maxWidth) {
234             maxWidth = lineWidth[i];
235             maxWidthLineIndex = i;
236         }
237     }
238 }
239 void calculateClientArea () {
240     int index = styledText.getTopIndex();
241     int lineCount = content.getLineCount();
242     int height = styledText.getClientArea().height;
243     int y = 0;
244     while (height > y && lineCount > index) {
245         calculate(index, 1);
246         y += lineHeight[index++];
247     }
248 }
249 void calculateIdle () {
250     if (idleRunning) return;
251     Runnable runnable = new class() Runnable {
252         public void run() {
253             if (styledText is null) return;
254             int i;
255             long start = System.currentTimeMillis();
256             for (i = 0; i < lineCount; i++) {
257                 if (lineHeight[i] is -1 || lineWidth[i] is -1) {
258                     calculate(i, 1);
259                     if (System.currentTimeMillis() - start > IDLE_TIME) break;
260                 }
261             }
262             if (i < lineCount) {
263                 Display display = styledText.getDisplay();
264                 display.asyncExec(this);
265             } else {
266                 idleRunning = false;
267                 styledText.setScrollBars(true);
268                 ScrollBar bar = styledText.getVerticalBar();
269                 if (bar !is null) {
270                     bar.setSelection(styledText.getVerticalScrollOffset());
271                 }
272             }
273         }
274     };
275     Display display = styledText.getDisplay();
276     display.asyncExec(runnable);
277     idleRunning = true;
278 }
279 void clearLineBackground(int startLine, int count) {
280     if (lines is null) return;
281     for (int i = startLine; i < startLine + count; i++) {
282         LineInfo info = lines[i];
283         if (info !is null) {
284             info.flags &= ~BACKGROUND;
285             info.background = null;
286             if (info.flags is 0) lines[i] = null;
287         }
288     }
289 }
290 void clearLineStyle(int startLine, int count) {
291     if (lines is null) return;
292     for (int i = startLine; i < startLine + count; i++) {
293         LineInfo info = lines[i];
294         if (info !is null) {
295             info.flags &= ~(ALIGNMENT | INDENT | JUSTIFY);
296             if (info.flags is 0) lines[i] = null;
297         }
298     }
299 }
300 void copyInto(StyledTextRenderer renderer) {
301     if (ranges !is null) {
302         int[] newRanges = renderer.ranges = new int[styleCount << 1];
303         System.arraycopy(ranges, 0, newRanges, 0, newRanges.length);
304     }
305     if (styles !is null) {
306         StyleRange[] newStyles = renderer.styles = new StyleRange[styleCount];
307         for (int i = 0; i < newStyles.length; i++) {
308             newStyles[i] = cast(StyleRange)styles[i].clone();
309         }
310         renderer.styleCount = styleCount;
311     }
312     if (lines !is null) {
313         LineInfo[] newLines = renderer.lines = new LineInfo[lineCount];
314         for (int i = 0; i < newLines.length; i++) {
315             newLines[i] = new LineInfo(lines[i]);
316         }
317         renderer.lineCount = lineCount;
318     }
319 }
320 void dispose() {
321     if (boldFont !is null) boldFont.dispose();
322     if (italicFont !is null) italicFont.dispose();
323     if (boldItalicFont !is null) boldItalicFont.dispose();
324     boldFont = italicFont = boldItalicFont = null;
325     reset();
326     content = null;
327     device = null;
328     styledText = null;
329 }
330 void disposeTextLayout (TextLayout layout) {
331     if (layouts !is null) {
332         for (int i = 0; i < layouts.length; i++) {
333             if (layouts[i] is layout) return;
334         }
335     }
336     layout.dispose();
337 }
338 void drawBullet(Bullet bullet, GC gc, int paintX, int paintY, int index, int lineAscent, int lineDescent) {
339     StyleRange style = bullet.style;
340     GlyphMetrics metrics = style.metrics;
341     Color color = style.foreground;
342     if (color !is null) gc.setForeground(color);
343     if ((bullet.type & ST.BULLET_DOT) !is 0 && StyledText.IS_MOTIF) {
344         int size = Math.max(4, (lineAscent + lineDescent) / 4);
345         if ((size & 1) is 0) size++;
346         if (color is null) {
347             Display display = styledText.getDisplay();
348             color = display.getSystemColor(SWT.COLOR_BLACK);
349         }
350         gc.setBackground(color);
351         int x = paintX + Math.max(0, metrics.width - size - BULLET_MARGIN);
352         gc.fillArc(x, paintY + size, size + 1, size + 1, 0, 360);
353         return;
354     }
355     Font font = style.font;
356     if (font !is null) gc.setFont(font);
357     String string = "";
358     int type = bullet.type & (ST.BULLET_DOT|ST.BULLET_NUMBER|ST.BULLET_LETTER_LOWER|ST.BULLET_LETTER_UPPER);
359     switch (type) {
360         case ST.BULLET_DOT: string = "\u2022"; break;
361         case ST.BULLET_NUMBER: string = String_valueOf(index); break;
362         case ST.BULLET_LETTER_LOWER: string = [cast(char) (index % 26 + 97)]; break;
363         case ST.BULLET_LETTER_UPPER: string = [cast(char) (index % 26 + 65)]; break;
364         default:
365     }
366     if ((bullet.type & ST.BULLET_TEXT) !is 0) string ~= bullet.text;
367     Display display = styledText.getDisplay();
368     TextLayout layout = new TextLayout(display);
369     layout.setText(string);
370     layout.setAscent(lineAscent);
371     layout.setDescent(lineDescent);
372     style = cast(StyleRange)style.clone();
373     style.metrics = null;
374     if (style.font is null) style.font = getFont(style.fontStyle);
375     layout.setStyle(style, 0, cast(int)/*64bit*/string.length);
376     int x = paintX + Math.max(0, metrics.width - layout.getBounds().width - BULLET_MARGIN);
377     layout.draw(gc, x, paintY);
378     layout.dispose();
379 }
380 int drawLine(int lineIndex, int paintX, int paintY, GC gc, Color widgetBackground, Color widgetForeground) {
381     TextLayout layout = getTextLayout(lineIndex);
382     String line = content.getLine(lineIndex);
383     int lineOffset = content.getOffsetAtLine(lineIndex);
384     int lineLength = cast(int)/*64bit*/line.length;
385     Point selection = styledText.getSelection();
386     int selectionStart = selection.x - lineOffset;
387     int selectionEnd = selection.y - lineOffset;
388     Rectangle client = styledText.getClientArea();
389     Color lineBackground = getLineBackground(lineIndex, null);
390     StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
391     if (event !is null && event.lineBackground !is null) lineBackground = event.lineBackground;
392 
393     int height = layout.getBounds().height;
394     if (lineBackground !is null) {
395         gc.setBackground(lineBackground);
396         gc.fillRectangle(client.x, paintY, client.width, height);
397     } else {
398         gc.setBackground(widgetBackground);
399         styledText.drawBackground(gc, client.x, paintY, client.width, height);
400     }
401     gc.setForeground(widgetForeground);
402     if (selectionStart is selectionEnd || (selectionEnd <= 0 && selectionStart >= lineLength)) {
403         layout.draw(gc, paintX, paintY);
404     } else {
405         int start = Math.max(0, selectionStart);
406         int end = Math.min(lineLength, selectionEnd);
407         Color selectionFg = styledText.getSelectionForeground();
408         Color selectionBg = styledText.getSelectionBackground();
409         int flags;
410         if ((styledText.getStyle() & SWT.FULL_SELECTION) !is 0) {
411             flags = SWT.FULL_SELECTION;
412         } else {
413             flags = SWT.DELIMITER_SELECTION;
414         }
415         if (selectionStart <= lineLength && lineLength < selectionEnd ) {
416             flags |= SWT.LAST_LINE_SELECTION;
417         }
418         layout.draw(gc, paintX, paintY, start, end > 0 ? cast(int)/*64bit*/line.offsetBefore(end) : end - 1, selectionFg, selectionBg, flags);
419     }
420 
421     // draw objects
422     Bullet bullet = null;
423     int bulletIndex = -1;
424     if (bullets !is null) {
425         if (bulletsIndices !is null) {
426             int index = lineIndex - topIndex;
427             if (0 <= index && index < CACHE_SIZE) {
428                 bullet = bullets[index];
429                 bulletIndex = bulletsIndices[index];
430             }
431         } else {
432             for (int i = 0; i < bullets.length; i++) {
433                 bullet = bullets[i];
434                 bulletIndex = bullet.indexOf(lineIndex);
435                 if (bulletIndex !is -1) break;
436             }
437         }
438     }
439     if (bulletIndex !is -1 && bullet !is null) {
440         FontMetrics metrics = layout.getLineMetrics(0);
441         int lineAscent = metrics.getAscent() + metrics.getLeading();
442         if (bullet.type is ST.BULLET_CUSTOM) {
443             bullet.style.start = lineOffset;
444             styledText.paintObject(gc, paintX, paintY, lineAscent, metrics.getDescent(), bullet.style, bullet, bulletIndex);
445         } else {
446             drawBullet(bullet, gc, paintX, paintY, bulletIndex, lineAscent, metrics.getDescent());
447         }
448     }
449     TextStyle[] styles = layout.getStyles();
450     int[] ranges = null;
451     for (int i = 0; i < styles.length; i++) {
452         if (styles[i].metrics !is null) {
453             if (ranges is null) ranges = layout.getRanges();
454             int start = ranges[i << 1];
455             int length = ranges[(i << 1) + 1] - start;
456             Point point = layout.getLocation(start, false);
457             FontMetrics metrics = layout.getLineMetrics(layout.getLineIndex(start));
458             StyleRange style = cast(StyleRange)(cast(StyleRange)styles[i]).clone();
459             style.start = start + lineOffset;
460             style.length = length;
461             int lineAscent = metrics.getAscent() + metrics.getLeading();
462             styledText.paintObject(gc, point.x + paintX, point.y + paintY, lineAscent, metrics.getDescent(), style, null, 0);
463         }
464     }
465     disposeTextLayout(layout);
466     return height;
467 }
468 int getBaseline() {
469     return ascent;
470 }
471 Font getFont(int style) {
472     switch (style) {
473         case SWT.BOLD:
474             if (boldFont !is null) return boldFont;
475             return boldFont = new Font(device, getFontData(style));
476         case SWT.ITALIC:
477             if (italicFont !is null) return italicFont;
478             return italicFont = new Font(device, getFontData(style));
479         case SWT.BOLD | SWT.ITALIC:
480             if (boldItalicFont !is null) return boldItalicFont;
481             return boldItalicFont = new Font(device, getFontData(style));
482         default:
483             return regularFont;
484     }
485 }
486 FontData[] getFontData(int style) {
487     FontData[] fontDatas = regularFont.getFontData();
488     for (int i = 0; i < fontDatas.length; i++) {
489         fontDatas[i].setStyle(style);
490     }
491     return fontDatas;
492 }
493 int getHeight () {
494     int defaultLineHeight = getLineHeight();
495     if (styledText.isFixedLineHeight()) {
496         return lineCount * defaultLineHeight;
497     }
498     int totalHeight = 0;
499     int width = styledText.getWrapWidth();
500     for (int i = 0; i < lineCount; i++) {
501         int height = lineHeight[i];
502         if (height is -1) {
503             if (width > 0) {
504                 auto length = content.getLine(i).length;
505                 height = cast(int)/*64bit*/((length * averageCharWidth / width) + 1) * defaultLineHeight;
506             } else {
507                 height = defaultLineHeight;
508             }
509         }
510         totalHeight += height;
511     }
512     return totalHeight + styledText.topMargin + styledText.bottomMargin;
513 }
514 int getLineAlignment(int index, int defaultAlignment) {
515     if (lines is null) return defaultAlignment;
516     LineInfo info = lines[index];
517     if (info !is null && (info.flags & ALIGNMENT) !is 0) {
518         return info.alignment;
519     }
520     return defaultAlignment;
521 }
522 Color getLineBackground(int index, Color defaultBackground) {
523     if (lines is null) return defaultBackground;
524     LineInfo info = lines[index];
525     if (info !is null && (info.flags & BACKGROUND) !is 0) {
526         return info.background;
527     }
528     return defaultBackground;
529 }
530 Bullet getLineBullet (int index, Bullet defaultBullet) {
531     if (bullets is null) return defaultBullet;
532     if (bulletsIndices !is null) return defaultBullet;
533     for (int i = 0; i < bullets.length; i++) {
534         Bullet bullet = bullets[i];
535         if (bullet.indexOf(index) !is -1) return bullet;
536     }
537     return defaultBullet;
538 }
539 int getLineHeight() {
540     return ascent + descent;
541 }
542 int getLineHeight(int lineIndex) {
543     if (lineHeight[lineIndex] is -1) {
544         calculate(lineIndex, 1);
545     }
546     return lineHeight[lineIndex];
547 }
548 int getLineIndent(int index, int defaultIndent) {
549     if (lines is null) return defaultIndent;
550     LineInfo info = lines[index];
551     if (info !is null && (info.flags & INDENT) !is 0) {
552         return info.indent;
553     }
554     return defaultIndent;
555 }
556 bool getLineJustify(int index, bool defaultJustify) {
557     if (lines is null) return defaultJustify;
558     LineInfo info = lines[index];
559     if (info !is null && (info.flags & JUSTIFY) !is 0) {
560         return info.justify;
561     }
562     return defaultJustify;
563 }
564 int[] getLineSegments(int index, int[] defaultSegments) {
565     if (lines is null) return defaultSegments;
566     LineInfo info = lines[index];
567     if (info !is null && (info.flags & SEGMENTS) !is 0) {
568         return info.segments;
569     }
570     return defaultSegments;
571 }
572 int getRangeIndex(int offset, int low, int high) {
573     if (styleCount is 0) return 0;
574     if (ranges !is null)  {
575         while (high - low > 2) {
576             int index = ((high + low) / 2) / 2 * 2;
577             int end = ranges[index] + ranges[index + 1];
578             if (end > offset) {
579                 high = index;
580             } else {
581                 low = index;
582             }
583         }
584     } else {
585         while (high - low > 1) {
586             int index = ((high + low) / 2);
587             int end = styles[index].start + styles[index].length;
588             if (end > offset) {
589                 high = index;
590             } else {
591                 low = index;
592             }
593         }
594     }
595     return high;
596 }
597 int[] getRanges(int start, int length) {
598     int[] newRanges;
599     int end = start + length - 1; //not a valid index, but OK
600     if (ranges !is null) {
601         int rangeCount = styleCount << 1;
602         int rangeStart = getRangeIndex(start, -1, rangeCount);
603         if (rangeStart >= rangeCount) return null;
604         if (ranges[rangeStart] > end) return null;
605         int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount) + 1);
606         newRanges = new int[rangeEnd - rangeStart + 2];
607         System.arraycopy(ranges, rangeStart, newRanges, 0, newRanges.length);
608     } else {
609         int rangeStart = getRangeIndex(start, -1, styleCount);
610         if (rangeStart >= styleCount) return null;
611         if (styles[rangeStart].start > end) return null;
612         int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount));
613         newRanges = new int[(rangeEnd - rangeStart + 1) << 1];
614         for (int i = rangeStart, j = 0; i <= rangeEnd; i++, j += 2) {
615             StyleRange style = styles[i];
616             newRanges[j] = style.start;
617             newRanges[j + 1] = style.length;
618         }
619     }
620     if (start > newRanges[0]) {
621         newRanges[1] = newRanges[0] + newRanges[1] - start;
622         newRanges[0] = start;
623     }
624     if (end < newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1] - 1) {
625         newRanges[newRanges.length - 1] = end - newRanges[newRanges.length - 2] + 1;
626     }
627     return newRanges;
628 }
629 StyleRange[] getStyleRanges(int start, int length, bool includeRanges) {
630     StyleRange[] newStyles;
631     int end = start + length - 1; //not a valid index, but OK
632     if (ranges !is null) {
633         int rangeCount = styleCount << 1;
634         int rangeStart = getRangeIndex(start, -1, rangeCount);
635         if (rangeStart >= rangeCount) return null;
636         if (ranges[rangeStart] > end) return null;
637         int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount) + 1);
638         newStyles = new StyleRange[((rangeEnd - rangeStart) >> 1) + 1];
639         if (includeRanges) {
640             for (int i = rangeStart, j = 0; i <= rangeEnd; i += 2, j++) {
641                 newStyles[j] = cast(StyleRange)styles[i >> 1].clone();
642                 newStyles[j].start = ranges[i];
643                 newStyles[j].length = ranges[i + 1];
644             }
645         } else {
646             System.arraycopy(styles, rangeStart >> 1, newStyles, 0, newStyles.length);
647         }
648     } else {
649         int rangeStart = getRangeIndex(start, -1, styleCount);
650         if (rangeStart >= styleCount) return null;
651         if (styles[rangeStart].start > end) return null;
652         int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount));
653         newStyles = new StyleRange[rangeEnd - rangeStart + 1];
654         System.arraycopy(styles, rangeStart, newStyles, 0, newStyles.length);
655     }
656     StyleRange style = newStyles[0];
657     if (start > style.start) {
658         if (!includeRanges || ranges is null) newStyles[0] = style = cast(StyleRange)style.clone();
659         style.length = style.start + style.length - start;
660         style.start = start;
661     }
662     style = newStyles[newStyles.length - 1];
663     if (end < style.start + style.length - 1) {
664         if (end < style.start) {
665             StyleRange[] tmp = new StyleRange[newStyles.length - 1];
666             System.arraycopy(newStyles, 0, tmp, 0, newStyles.length - 1);
667             newStyles = tmp;
668         } else {
669             if (!includeRanges || ranges is null) newStyles[newStyles.length - 1] = style = cast(StyleRange)style.clone();
670             style.length = end - style.start + 1;
671         }
672     }
673     return newStyles;
674 }
675 StyleRange getStyleRange(StyleRange style) {
676     if (style.start is 0 && style.length is 0 && style.fontStyle is SWT.NORMAL) return style;
677     StyleRange clone = cast(StyleRange)style.clone();
678     clone.start = clone.length = 0;
679     clone.fontStyle = SWT.NORMAL;
680     if (clone.font is null) clone.font = getFont(style.fontStyle);
681     return clone;
682 }
683 TextLayout getTextLayout(int lineIndex) {
684     return getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), styledText.lineSpacing);
685 }
686 TextLayout getTextLayout(int lineIndex, int orientation, int width, int lineSpacing) {
687     TextLayout layout = null;
688     if (styledText !is null) {
689         int topIndex = styledText.topIndex > 0 ? styledText.topIndex - 1 : 0;
690         if (layouts is null || topIndex !is this.topIndex) {
691             TextLayout[] newLayouts = new TextLayout[CACHE_SIZE];
692             if (layouts !is null) {
693                 for (int i = 0; i < layouts.length; i++) {
694                     if (layouts[i] !is null) {
695                         int layoutIndex = (i + this.topIndex) - topIndex;
696                         if (0 <= layoutIndex && layoutIndex < newLayouts.length) {
697                             newLayouts[layoutIndex] = layouts[i];
698                         } else {
699                             layouts[i].dispose();
700                         }
701                     }
702                 }
703             }
704             if (bullets !is null && bulletsIndices !is null && topIndex !is this.topIndex) {
705                 int delta = topIndex - this.topIndex;
706                 if (delta > 0) {
707                     if (delta < bullets.length) {
708                         System.arraycopy(bullets, delta, bullets, 0, bullets.length - delta);
709                         System.arraycopy(bulletsIndices, delta, bulletsIndices, 0, bulletsIndices.length - delta);
710                     }
711                     auto startIndex = Math.max(0, bullets.length - delta);
712                     for (auto i = startIndex; i < bullets.length; i++) bullets[i] = null;
713                 } else {
714                     if (-delta < bullets.length) {
715                         System.arraycopy(bullets, 0, bullets, -delta, bullets.length + delta);
716                         System.arraycopy(bulletsIndices, 0, bulletsIndices, -delta, bulletsIndices.length + delta);
717                     }
718                     auto endIndex = Math.min(bullets.length, -delta);
719                     for (size_t i = 0; i < endIndex; i++) bullets[i] = null;
720                 }
721             }
722             this.topIndex = topIndex;
723             layouts = newLayouts;
724         }
725         if (layouts !is null) {
726             int layoutIndex = lineIndex - topIndex;
727             if (0 <= layoutIndex && layoutIndex < layouts.length) {
728                 layout = layouts[layoutIndex];
729                 if (layout !is null) {
730                     if (lineWidth[lineIndex] !is -1) return layout;
731                 } else {
732                     layout = layouts[layoutIndex] = new TextLayout(device);
733                 }
734             }
735         }
736     }
737     if (layout is null) layout = new TextLayout(device);
738     String line = content.getLine(lineIndex);
739     int lineOffset = content.getOffsetAtLine(lineIndex);
740     int[] segments = null;
741     int indent = 0;
742     int alignment = SWT.LEFT;
743     bool justify = false;
744     Bullet bullet = null;
745     int[] ranges = null;
746     StyleRange[] styles = null;
747     int rangeStart = 0, styleCount = 0;
748     StyledTextEvent event = null;
749     if (styledText !is null) {
750         event = styledText.getLineStyleData(lineOffset, line);
751         segments = styledText.getBidiSegments(lineOffset, line);
752         indent = styledText.indent;
753         alignment = styledText.alignment;
754         justify = styledText.justify;
755     }
756     if (event !is null) {
757         indent = event.indent;
758         alignment = event.alignment;
759         justify = event.justify;
760         bullet = event.bullet;
761         ranges = event.ranges;
762         styles = event.styles;
763         if (styles !is null) {
764             styleCount = cast(int)/*64bit*/styles.length;
765             if (styledText.isFixedLineHeight()) {
766                 for (int i = 0; i < styleCount; i++) {
767                     if (styles[i].isVariableHeight()) {
768                         styledText.verticalScrollOffset = -1;
769                         styledText.setVariableLineHeight();
770                         styledText.redraw();
771                         break;
772                     }
773                 }
774             }
775         }
776         if (bullets is null || bulletsIndices is null) {
777             bullets = new Bullet[CACHE_SIZE];
778             bulletsIndices = new int[CACHE_SIZE];
779         }
780         int index = lineIndex - topIndex;
781         if (0 <= index && index < CACHE_SIZE) {
782             bullets[index] = bullet;
783             bulletsIndices[index] = event.bulletIndex;
784         }
785     } else {
786         if (lines !is null) {
787             LineInfo info = lines[lineIndex];
788             if (info !is null) {
789                 if ((info.flags & INDENT) !is 0) indent = info.indent;
790                 if ((info.flags & ALIGNMENT) !is 0) alignment = info.alignment;
791                 if ((info.flags & JUSTIFY) !is 0) justify = info.justify;
792                 if ((info.flags & SEGMENTS) !is 0) segments = info.segments;
793             }
794         }
795         if (bulletsIndices !is null) {
796             bullets = null;
797             bulletsIndices = null;
798         }
799         if (bullets !is null) {
800             for (int i = 0; i < bullets.length; i++) {
801                 if (bullets[i].indexOf(lineIndex) !is -1) {
802                     bullet = bullets[i];
803                     break;
804                 }
805             }
806         }
807         ranges = this.ranges;
808         styles = this.styles;
809         styleCount = this.styleCount;
810         if (ranges !is null) {
811             rangeStart = getRangeIndex(lineOffset, -1, styleCount << 1);
812         } else {
813             rangeStart = getRangeIndex(lineOffset, -1, styleCount);
814         }
815     }
816     if (bullet !is null) {
817         StyleRange style = bullet.style;
818         GlyphMetrics metrics = style.metrics;
819         indent += metrics.width;
820     }
821     layout.setFont(regularFont);
822     layout.setAscent(ascent);
823     layout.setDescent(descent);
824     layout.setText(line);
825     layout.setOrientation(orientation);
826     layout.setSegments(segments);
827     layout.setWidth(width);
828     layout.setSpacing(lineSpacing);
829     layout.setTabs([tabWidth]);
830     layout.setIndent(indent);
831     layout.setAlignment(alignment);
832     layout.setJustify(justify);
833 
834     int lastOffset = 0;
835     int length = cast(int)/*64bit*/line.length;
836     if (styles !is null) {
837         if (ranges !is null) {
838             int rangeCount = styleCount << 1;
839             for (int i = rangeStart; i < rangeCount; i += 2) {
840                 int start, end;
841                 if (lineOffset > ranges[i]) {
842                     start = 0;
843                     end = Math.min (length, ranges[i + 1] - lineOffset + ranges[i]);
844                 } else {
845                     start = ranges[i] - lineOffset;
846                     end = Math.min(length, start + ranges[i + 1]);
847                 }
848                 if (start >= length) break;
849                 if (lastOffset < start) {
850                     layout.setStyle(null, lastOffset, cast(int)/*64bit*/line.offsetBefore(start));
851                 }
852                 layout.setStyle(getStyleRange(styles[i >> 1]), start, end);
853                 lastOffset = Math.max(lastOffset, end);
854             }
855         } else {
856             for (int i = rangeStart; i < styleCount; i++) {
857                 int start, end;
858                 if (lineOffset > styles[i].start) {
859                     start = 0;
860                     end = Math.min (length, styles[i].length - lineOffset + styles[i].start);
861                 } else {
862                     start = styles[i].start - lineOffset;
863                     end = Math.min(length, start + styles[i].length);
864                 }
865                 if (start >= length) break;
866                 if (lastOffset < start) {
867                     layout.setStyle(null, lastOffset, cast(int)/*64bit*/line.offsetBefore(start));
868                 }
869                 layout.setStyle(getStyleRange(styles[i]), start, end);
870                 lastOffset = Math.max(lastOffset, end);
871             }
872         }
873     }
874     if (lastOffset < length) layout.setStyle(null, lastOffset, length);
875     if (styledText !is null && styledText.ime !is null) {
876         IME ime = styledText.ime;
877         int compositionOffset = ime.getCompositionOffset();
878         if (compositionOffset !is -1) {
879             int commitCount = ime.getCommitCount();
880             int compositionLength = cast(int)/*64bit*/ime.getText().length;
881             if (compositionLength !is commitCount) {
882                 int compositionLine = content.getLineAtOffset(compositionOffset);
883                 if (compositionLine is lineIndex) {
884                     int[] imeRanges = ime.getRanges();
885                     TextStyle[] imeStyles = ime.getStyles();
886                     if (imeRanges.length > 0) {
887                         for (int i = 0; i < imeStyles.length; i++) {
888                             int start = imeRanges[i*2] - lineOffset;
889                             int end = imeRanges[i*2+1] - lineOffset;
890                             TextStyle imeStyle = imeStyles[i], userStyle;
891                             for (int j = start; j <= end; j++) {
892                                 userStyle = layout.getStyle(j);
893                                 if (userStyle is null && j > 0) userStyle = layout.getStyle(j - 1);
894                                 if (userStyle is null && j + 1 < length) userStyle = layout.getStyle(j + 1);
895                                 if (userStyle is null) {
896                                     layout.setStyle(imeStyle, j, j);
897                                 } else {
898                                     TextStyle newStyle = new TextStyle(imeStyle);
899                                     if (newStyle.font is null) newStyle.font = userStyle.font;
900                                     if (newStyle.foreground is null) newStyle.foreground = userStyle.foreground;
901                                     if (newStyle.background is null) newStyle.background = userStyle.background;
902                                     layout.setStyle(newStyle, j, j);
903                                 }
904                             }
905                         }
906                     } else {
907                         int start = compositionOffset - lineOffset;
908                         int end = cast(int)/*64bit*/line.offsetBefore(start + compositionLength);
909                         TextStyle userStyle = layout.getStyle(start);
910                         if (userStyle is null) {
911                             if (start > 0) userStyle = layout.getStyle(cast(int)/*64bit*/line.offsetBefore(start));
912                             if (userStyle is null && line.offsetAfter(end) < length) userStyle = layout.getStyle(cast(int)/*64bit*/line.offsetAfter(end));
913                             if (userStyle !is null) {
914                                 TextStyle newStyle = new TextStyle();
915                                 newStyle.font = userStyle.font;
916                                 newStyle.foreground = userStyle.foreground;
917                                 newStyle.background = userStyle.background;
918                                 layout.setStyle(newStyle, start, end);
919                             }
920                         }
921                     }
922                 }
923             }
924         }
925     }
926 
927     if (styledText !is null && styledText.isFixedLineHeight()) {
928         int index = -1;
929         int lineCount = layout.getLineCount();
930         int height = getLineHeight();
931         for (int i = 0; i < lineCount; i++) {
932             int lineHeight = layout.getLineBounds(i).height;
933             if (lineHeight > height) {
934                 height = lineHeight;
935                 index = i;
936             }
937         }
938         if (index !is -1) {
939             FontMetrics metrics = layout.getLineMetrics(index);
940             ascent = metrics.getAscent() + metrics.getLeading();
941             descent = metrics.getDescent();
942             if (layouts !is null) {
943                 for (int i = 0; i < layouts.length; i++) {
944                     if (layouts[i] !is null && layouts[i] !is layout) {
945                         layouts[i].setAscent(ascent);
946                         layouts[i].setDescent(descent);
947                     }
948                 }
949             }
950             if (styledText.verticalScrollOffset !is 0) {
951                 int topIndex = styledText.topIndex;
952                 int topIndexY = styledText.topIndexY;
953                 int lineHeight = getLineHeight();
954                 if (topIndexY >= 0) {
955                     styledText.verticalScrollOffset = (topIndex - 1) * lineHeight + lineHeight - topIndexY;
956                 } else {
957                     styledText.verticalScrollOffset = topIndex * lineHeight - topIndexY;
958                 }
959             }
960             styledText.calculateScrollBars();
961             if (styledText.isBidiCaret()) styledText.createCaretBitmaps();
962             styledText.caretDirection = SWT.NULL;
963             styledText.setCaretLocation();
964             styledText.redraw();
965         }
966     }
967     return layout;
968 }
969 int getWidth() {
970     return maxWidth;
971 }
972 void reset() {
973     if (layouts !is null) {
974         for (int i = 0; i < layouts.length; i++) {
975             TextLayout layout = layouts[i];
976             if (layout !is null) layout.dispose();
977         }
978         layouts = null;
979     }
980     topIndex = -1;
981     stylesSetCount = styleCount = lineCount = 0;
982     ranges = null;
983     styles = null;
984     stylesSet = null;
985     lines = null;
986     lineWidth = null;
987     lineHeight = null;
988     bullets = null;
989     bulletsIndices = null;
990     redrawLines = null;
991 }
992 void reset(int startLine, int lineCount) {
993     int endLine = startLine + lineCount;
994     if (startLine < 0 || endLine > lineWidth.length) return;
995     for (int i = startLine; i < endLine; i++) {
996         lineWidth[i] = -1;
997         lineHeight[i] = -1;
998     }
999     if (startLine <= maxWidthLineIndex && maxWidthLineIndex < endLine) {
1000         maxWidth = 0;
1001         maxWidthLineIndex = -1;
1002         if (lineCount !is this.lineCount) {
1003             for (int i = 0; i < this.lineCount; i++) {
1004                 if (lineWidth[i] > maxWidth) {
1005                     maxWidth = lineWidth[i];
1006                     maxWidthLineIndex = i;
1007                 }
1008             }
1009         }
1010     }
1011 }
1012 void setContent(StyledTextContent content) {
1013     reset();
1014     this.content = content;
1015     lineCount = content.getLineCount();
1016     lineWidth = new int[lineCount];
1017     lineHeight = new int[lineCount];
1018     reset(0, lineCount);
1019 }
1020 void setFont(Font font, int tabs) {
1021     TextLayout layout = new TextLayout(device);
1022     layout.setFont(regularFont);
1023     if (font !is null) {
1024         if (boldFont !is null) boldFont.dispose();
1025         if (italicFont !is null) italicFont.dispose();
1026         if (boldItalicFont !is null) boldItalicFont.dispose();
1027         boldFont = italicFont = boldItalicFont = null;
1028         regularFont = font;
1029         layout.setText("    ");
1030         layout.setFont(font);
1031         layout.setStyle(new TextStyle(getFont(SWT.NORMAL), null, null), 0, 0);
1032         layout.setStyle(new TextStyle(getFont(SWT.BOLD), null, null), 1, 1);
1033         layout.setStyle(new TextStyle(getFont(SWT.ITALIC), null, null), 2, 2);
1034         layout.setStyle(new TextStyle(getFont(SWT.BOLD | SWT.ITALIC), null, null), 3, 3);
1035         FontMetrics metrics = layout.getLineMetrics(0);
1036         ascent = metrics.getAscent() + metrics.getLeading();
1037         descent = metrics.getDescent();
1038         boldFont.dispose();
1039         italicFont.dispose();
1040         boldItalicFont.dispose();
1041         boldFont = italicFont = boldItalicFont = null;
1042     }
1043     layout.dispose();
1044     layout = new TextLayout(device);
1045     layout.setFont(regularFont);
1046     StringBuffer tabBuffer = new StringBuffer(tabs);
1047     for (int i = 0; i < tabs; i++) {
1048         tabBuffer.append(' ');
1049     }
1050     layout.setText(tabBuffer.toString());
1051     tabWidth = layout.getBounds().width;
1052     layout.dispose();
1053     if (styledText !is null) {
1054         GC gc = new GC(styledText);
1055         averageCharWidth = gc.getFontMetrics().getAverageCharWidth();
1056         gc.dispose();
1057     }
1058 }
1059 void setLineAlignment(int startLine, int count, int alignment) {
1060     if (lines is null) lines = new LineInfo[lineCount];
1061     for (int i = startLine; i < startLine + count; i++) {
1062         if (lines[i] is null) {
1063             lines[i] = new LineInfo();
1064         }
1065         lines[i].flags |= ALIGNMENT;
1066         lines[i].alignment = alignment;
1067     }
1068 }
1069 void setLineBackground(int startLine, int count, Color background) {
1070     if (lines is null) lines = new LineInfo[lineCount];
1071     for (int i = startLine; i < startLine + count; i++) {
1072         if (lines[i] is null) {
1073             lines[i] = new LineInfo();
1074         }
1075         lines[i].flags |= BACKGROUND;
1076         lines[i].background = background;
1077     }
1078 }
1079 void setLineBullet(int startLine, int count, Bullet bullet) {
1080     if (bulletsIndices !is null) {
1081         bulletsIndices = null;
1082         bullets = null;
1083     }
1084     if (bullets is null) {
1085         if (bullet is null) return;
1086         bullets = new Bullet[1];
1087         bullets[0] = bullet;
1088     }
1089     int index = 0;
1090     while (index < bullets.length) {
1091         if (bullet is bullets[index]) break;
1092         index++;
1093     }
1094     if (bullet !is null) {
1095         if (index is bullets.length) {
1096             Bullet[] newBulletsList = new Bullet[bullets.length + 1];
1097             System.arraycopy(bullets, 0, newBulletsList, 0, bullets.length);
1098             newBulletsList[index] = bullet;
1099             bullets = newBulletsList;
1100         }
1101         bullet.addIndices(startLine, count);
1102     } else {
1103         updateBullets(startLine, count, 0, false);
1104         styledText.redrawLinesBullet(redrawLines);
1105         redrawLines = null;
1106     }
1107 }
1108 void setLineIndent(int startLine, int count, int indent) {
1109     if (lines is null) lines = new LineInfo[lineCount];
1110     for (int i = startLine; i < startLine + count; i++) {
1111         if (lines[i] is null) {
1112             lines[i] = new LineInfo();
1113         }
1114         lines[i].flags |= INDENT;
1115         lines[i].indent = indent;
1116     }
1117 }
1118 void setLineJustify(int startLine, int count, bool justify) {
1119     if (lines is null) lines = new LineInfo[lineCount];
1120     for (int i = startLine; i < startLine + count; i++) {
1121         if (lines[i] is null) {
1122             lines[i] = new LineInfo();
1123         }
1124         lines[i].flags |= JUSTIFY;
1125         lines[i].justify = justify;
1126     }
1127 }
1128 void setLineSegments(int startLine, int count, int[] segments) {
1129     if (lines is null) lines = new LineInfo[lineCount];
1130     for (int i = startLine; i < startLine + count; i++) {
1131         if (lines[i] is null) {
1132             lines[i] = new LineInfo();
1133         }
1134         lines[i].flags |= SEGMENTS;
1135         lines[i].segments = segments;
1136     }
1137 }
1138 void setStyleRanges (int[] newRanges, StyleRange[] newStyles) {
1139     if (newStyles is null) {
1140         stylesSetCount = styleCount = 0;
1141         ranges = null;
1142         styles = null;
1143         stylesSet = null;
1144         return;
1145     }
1146     if (newRanges is null && COMPACT_STYLES) {
1147         newRanges = new int[newStyles.length << 1];
1148         StyleRange[] tmpStyles = new StyleRange[newStyles.length];
1149         if (stylesSet is null) stylesSet = new StyleRange[4];
1150         for (int i = 0, j = 0; i < newStyles.length; i++) {
1151             StyleRange newStyle = newStyles[i];
1152             newRanges[j++] = newStyle.start;
1153             newRanges[j++] = newStyle.length;
1154             int index = 0;
1155             while (index < stylesSetCount) {
1156                 if (stylesSet[index].similarTo(newStyle)) break;
1157                 index++;
1158             }
1159             if (index is stylesSetCount) {
1160                 if (stylesSetCount is stylesSet.length) {
1161                     StyleRange[] tmpStylesSet = new StyleRange[stylesSetCount + 4];
1162                     System.arraycopy(stylesSet, 0, tmpStylesSet, 0, stylesSetCount);
1163                     stylesSet = tmpStylesSet;
1164                 }
1165                 stylesSet[stylesSetCount++] = newStyle;
1166             }
1167             tmpStyles[i] = stylesSet[index];
1168         }
1169         newStyles = tmpStyles;
1170     }
1171 
1172     if (styleCount is 0) {
1173         if (newRanges !is null) {
1174             ranges = new int[newRanges.length];
1175             System.arraycopy(newRanges, 0, ranges, 0, ranges.length);
1176         }
1177         styles = new StyleRange[newStyles.length];
1178         System.arraycopy(newStyles, 0, styles, 0, styles.length);
1179         styleCount = cast(int)/*64bit*/newStyles.length;
1180         return;
1181     }
1182     if (newRanges !is null && ranges is null) {
1183         ranges = new int[styles.length << 1];
1184         for (int i = 0, j = 0; i < styleCount; i++) {
1185             ranges[j++] = styles[i].start;
1186             ranges[j++] = styles[i].length;
1187         }
1188     }
1189     if (newRanges is null && ranges !is null) {
1190         newRanges = new int[newStyles.length << 1];
1191         for (int i = 0, j = 0; i < newStyles.length; i++) {
1192             newRanges[j++] = newStyles[i].start;
1193             newRanges[j++] = newStyles[i].length;
1194         }
1195     }
1196     if (ranges !is null) {
1197         int rangeCount = styleCount << 1;
1198         int start = newRanges[0];
1199         int modifyStart = getRangeIndex(start, -1, rangeCount), modifyEnd;
1200         bool insert = modifyStart is rangeCount;
1201         if (!insert) {
1202             int end = newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1];
1203             modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount);
1204             insert = modifyStart is modifyEnd && ranges[modifyStart] >= end;
1205         }
1206         if (insert) {
1207             addMerge(newRanges, newStyles, cast(int)/*64bit*/newRanges.length, modifyStart, modifyStart);
1208             return;
1209         }
1210         modifyEnd = modifyStart;
1211         int[] mergeRanges = new int[6];
1212         StyleRange[] mergeStyles = new StyleRange[3];
1213         for (int i = 0; i < newRanges.length; i += 2) {
1214             int newStart = newRanges[i];
1215             int newEnd = newStart + newRanges[i + 1];
1216             if (newStart is newEnd) continue;
1217             int modifyLast = 0, mergeCount = 0;
1218             while (modifyEnd < rangeCount) {
1219                 if (newStart >= ranges[modifyStart] + ranges[modifyStart + 1]) modifyStart += 2;
1220                 if (ranges[modifyEnd] + ranges[modifyEnd + 1] > newEnd) break;
1221                 modifyEnd += 2;
1222             }
1223             if (ranges[modifyStart] < newStart && newStart < ranges[modifyStart] + ranges[modifyStart + 1]) {
1224                 mergeStyles[mergeCount >> 1] = styles[modifyStart >> 1];
1225                 mergeRanges[mergeCount] = ranges[modifyStart];
1226                 mergeRanges[mergeCount + 1] = newStart - ranges[modifyStart];
1227                 mergeCount += 2;
1228             }
1229             mergeStyles[mergeCount >> 1] = newStyles[i >> 1];
1230             mergeRanges[mergeCount] = newStart;
1231             mergeRanges[mergeCount + 1] = newRanges[i + 1];
1232             mergeCount += 2;
1233             if (modifyEnd < rangeCount && ranges[modifyEnd] < newEnd && newEnd < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
1234                 mergeStyles[mergeCount >> 1] = styles[modifyEnd >> 1];
1235                 mergeRanges[mergeCount] = newEnd;
1236                 mergeRanges[mergeCount + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - newEnd;
1237                 mergeCount += 2;
1238                 modifyLast = 2;
1239             }
1240             int grow = addMerge(mergeRanges, mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast);
1241             rangeCount += grow;
1242             modifyStart = modifyEnd += grow;
1243         }
1244     } else {
1245         int start = newStyles[0].start;
1246         int modifyStart = getRangeIndex(start, -1, styleCount), modifyEnd;
1247         bool insert = modifyStart is styleCount;
1248         if (!insert) {
1249             int end = newStyles[newStyles.length - 1].start + newStyles[newStyles.length - 1].length;
1250             modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount);
1251             insert = modifyStart is modifyEnd && styles[modifyStart].start >= end;
1252         }
1253         if (insert) {
1254             addMerge(newStyles, cast(int)/*64bit*/newStyles.length, modifyStart, modifyStart);
1255             return;
1256         }
1257         modifyEnd = modifyStart;
1258         StyleRange[] mergeStyles = new StyleRange[3];
1259         for (int i = 0; i < newStyles.length; i++) {
1260             StyleRange newStyle = newStyles[i], style;
1261             int newStart = newStyle.start;
1262             int newEnd = newStart + newStyle.length;
1263             if (newStart is newEnd) continue;
1264             int modifyLast = 0, mergeCount = 0;
1265             while (modifyEnd < styleCount) {
1266                 if (newStart >= styles[modifyStart].start + styles[modifyStart].length) modifyStart++;
1267                 if (styles[modifyEnd].start + styles[modifyEnd].length > newEnd) break;
1268                 modifyEnd++;
1269             }
1270             style = styles[modifyStart];
1271             if (style.start < newStart && newStart < style.start + style.length) {
1272                 style = mergeStyles[mergeCount++] = cast(StyleRange)style.clone();
1273                 style.length = newStart - style.start;
1274             }
1275             mergeStyles[mergeCount++] = newStyle;
1276             if (modifyEnd < styleCount) {
1277                 style = styles[modifyEnd];
1278                 if (style.start < newEnd && newEnd < style.start + style.length) {
1279                     style = mergeStyles[mergeCount++] = cast(StyleRange)style.clone();
1280                     style.length += style.start - newEnd;
1281                     style.start = newEnd;
1282                     modifyLast = 1;
1283                 }
1284             }
1285             int grow = addMerge(mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast);
1286             modifyStart = modifyEnd += grow;
1287         }
1288     }
1289 }
1290 void textChanging(TextChangingEvent event) {
1291     int start = event.start;
1292     int newCharCount = event.newCharCount, replaceCharCount = event.replaceCharCount;
1293     int newLineCount = event.newLineCount, replaceLineCount = event.replaceLineCount;
1294 
1295     updateRanges(start, replaceCharCount, newCharCount);
1296 
1297     int startLine = content.getLineAtOffset(start);
1298     if (replaceCharCount is content.getCharCount()) lines = null;
1299     if (replaceLineCount is lineCount) {
1300         lineCount = newLineCount;
1301         lineWidth = new int[lineCount];
1302         lineHeight = new int[lineCount];
1303         reset(0, lineCount);
1304     } else {
1305         int delta = newLineCount - replaceLineCount;
1306         if (lineCount + delta > lineWidth.length) {
1307             int[] newWidths = new int[lineCount + delta + GROW];
1308             System.arraycopy(lineWidth, 0, newWidths, 0, lineCount);
1309             lineWidth = newWidths;
1310             int[] newHeights = new int[lineCount + delta + GROW];
1311             System.arraycopy(lineHeight, 0, newHeights, 0, lineCount);
1312             lineHeight = newHeights;
1313         }
1314         if (lines !is null) {
1315             if (lineCount + delta > lines.length) {
1316                 LineInfo[] newLines = new LineInfo[lineCount + delta + GROW];
1317                 System.arraycopy(lines, 0, newLines, 0, lineCount);
1318                 lines = newLines;
1319             }
1320         }
1321         int startIndex = startLine + replaceLineCount + 1;
1322         int endIndex = startLine + newLineCount + 1;
1323         System.arraycopy(lineWidth, startIndex, lineWidth, endIndex, lineCount - startIndex);
1324         System.arraycopy(lineHeight, startIndex, lineHeight, endIndex, lineCount - startIndex);
1325         for (int i = startLine; i < endIndex; i++) {
1326             lineWidth[i] = lineHeight[i] = -1;
1327         }
1328         for (int i = lineCount + delta; i < lineCount; i++) {
1329             lineWidth[i] = lineHeight[i] = -1;
1330         }
1331         if (layouts !is null) {
1332             int layoutStartLine = startLine - topIndex;
1333             int layoutEndLine = layoutStartLine + replaceLineCount + 1;
1334             for (int i = layoutStartLine; i < layoutEndLine; i++) {
1335                 if (0 <= i && i < layouts.length) {
1336                     if (layouts[i] !is null) layouts[i].dispose();
1337                     layouts[i] = null;
1338                     if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
1339                 }
1340             }
1341             if (delta > 0) {
1342                 for (ptrdiff_t i = cast(ptrdiff_t) (layouts.length) - 1; i >= layoutEndLine; i--) {
1343                     if (0 <= i && i < layouts.length) {
1344                         endIndex = cast(int)/*64bit*/(i + delta);
1345                         if (0 <= endIndex && endIndex < layouts.length) {
1346                             layouts[endIndex] = layouts[i];
1347                             layouts[i] = null;
1348                             if (bullets !is null && bulletsIndices !is null) {
1349                                 bullets[endIndex] = bullets[i];
1350                                 bulletsIndices[endIndex] = bulletsIndices[i];
1351                                 bullets[i] = null;
1352                             }
1353                         } else {
1354                             if (layouts[i] !is null) layouts[i].dispose();
1355                             layouts[i] = null;
1356                             if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
1357                         }
1358                     }
1359                 }
1360             } else if (delta < 0) {
1361                 for (int i = layoutEndLine; i < layouts.length; i++) {
1362                     if (0 <= i && i < layouts.length) {
1363                         endIndex = i + delta;
1364                         if (0 <= endIndex && endIndex < layouts.length) {
1365                             layouts[endIndex] = layouts[i];
1366                             layouts[i] = null;
1367                             if (bullets !is null && bulletsIndices !is null) {
1368                                 bullets[endIndex] = bullets[i];
1369                                 bulletsIndices[endIndex] = bulletsIndices[i];
1370                                 bullets[i] = null;
1371                             }
1372                         } else {
1373                             if (layouts[i] !is null) layouts[i].dispose();
1374                             layouts[i] = null;
1375                             if (bullets !is null && bulletsIndices !is null) bullets[i] = null;
1376                         }
1377                     }
1378                 }
1379             }
1380         }
1381         if (replaceLineCount !is 0 || newLineCount !is 0) {
1382             int startLineOffset = content.getOffsetAtLine(startLine);
1383             if (startLineOffset !is start) startLine++;
1384             updateBullets(startLine, replaceLineCount, newLineCount, true);
1385             if (lines !is null) {
1386                 startIndex = startLine + replaceLineCount;
1387                 endIndex = startLine + newLineCount;
1388                 System.arraycopy(lines, startIndex, lines, endIndex, lineCount - startIndex);
1389                 for (int i = startLine; i < endIndex; i++) {
1390                     lines[i] = null;
1391                 }
1392                 for (int i = lineCount + delta; i < lineCount; i++) {
1393                     lines[i] = null;
1394                 }
1395             }
1396         }
1397         lineCount += delta;
1398         if (maxWidthLineIndex !is -1 && startLine <= maxWidthLineIndex && maxWidthLineIndex <= startLine + replaceLineCount) {
1399             maxWidth = 0;
1400             maxWidthLineIndex = -1;
1401             for (int i = 0; i < lineCount; i++) {
1402                 if (lineWidth[i] > maxWidth) {
1403                     maxWidth = lineWidth[i];
1404                     maxWidthLineIndex = i;
1405                 }
1406             }
1407         }
1408     }
1409 }
1410 void updateBullets(int startLine, int replaceLineCount, int newLineCount, bool update) {
1411     if (bullets is null) return;
1412     if (bulletsIndices !is null) return;
1413     for (int i = 0; i < bullets.length; i++) {
1414         Bullet bullet = bullets[i];
1415         int[] lines = bullet.removeIndices(startLine, replaceLineCount, newLineCount, update);
1416         if (lines !is null) {
1417             if (redrawLines is null) {
1418                 redrawLines = lines;
1419             } else {
1420                 int[] newRedrawBullets = new int[redrawLines.length + lines.length];
1421                 System.arraycopy(redrawLines, 0, newRedrawBullets, 0, redrawLines.length);
1422                 System.arraycopy(lines, 0, newRedrawBullets, redrawLines.length, lines.length);
1423                 redrawLines = newRedrawBullets;
1424             }
1425         }
1426     }
1427     int removed = 0;
1428     for (int i = 0; i < bullets.length; i++) {
1429         if (bullets[i].size() is 0) removed++;
1430     }
1431     if (removed > 0) {
1432         if (removed is bullets.length) {
1433             bullets = null;
1434         } else {
1435             Bullet[] newBulletsList = new Bullet[bullets.length - removed];
1436             for (int i = 0, j = 0; i < bullets.length; i++) {
1437                 Bullet bullet = bullets[i];
1438                 if (bullet.size() > 0) newBulletsList[j++] = bullet;
1439             }
1440             bullets = newBulletsList;
1441         }
1442     }
1443 }
1444 void updateRanges(int start, int replaceCharCount, int newCharCount) {
1445     if (styleCount is 0 || (replaceCharCount is 0 && newCharCount is 0)) return;
1446     if (ranges !is null) {
1447         int rangeCount = styleCount << 1;
1448         int modifyStart = getRangeIndex(start, -1, rangeCount);
1449         if (modifyStart is rangeCount) return;
1450         int end = start + replaceCharCount;
1451         int modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount);
1452         int offset = newCharCount - replaceCharCount;
1453         if (modifyStart is modifyEnd && ranges[modifyStart] < start && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
1454             if (newCharCount is 0) {
1455                 ranges[modifyStart + 1] -= replaceCharCount;
1456                 modifyEnd += 2;
1457             } else {
1458                 if (rangeCount + 2 > ranges.length) {
1459                     int[] newRanges = new int[ranges.length + (GROW << 1)];
1460                     System.arraycopy(ranges, 0, newRanges, 0, rangeCount);
1461                     ranges = newRanges;
1462                     StyleRange[] newStyles = new StyleRange[styles.length + GROW];
1463                     System.arraycopy(styles, 0, newStyles, 0, styleCount);
1464                     styles = newStyles;
1465                 }
1466                 System.arraycopy(ranges, modifyStart + 2, ranges, modifyStart + 4, rangeCount - (modifyStart + 2));
1467                 System.arraycopy(styles, (modifyStart + 2) >> 1, styles, (modifyStart + 4) >> 1, styleCount - ((modifyStart + 2) >> 1));
1468                 ranges[modifyStart + 3] = ranges[modifyStart] + ranges[modifyStart + 1] - end;
1469                 ranges[modifyStart + 2] = start + newCharCount;
1470                 ranges[modifyStart + 1] = start - ranges[modifyStart];
1471                 styles[(modifyStart >> 1) + 1] = styles[modifyStart >> 1];
1472                 rangeCount += 2;
1473                 styleCount++;
1474                 modifyEnd += 4;
1475             }
1476             if (offset !is 0) {
1477                 for (int i = modifyEnd; i < rangeCount; i += 2) {
1478                     ranges[i] += offset;
1479                 }
1480             }
1481         } else {
1482             if (ranges[modifyStart] < start && start < ranges[modifyStart] + ranges[modifyStart + 1]) {
1483                 ranges[modifyStart + 1] = start - ranges[modifyStart];
1484                 modifyStart += 2;
1485             }
1486             if (modifyEnd < rangeCount && ranges[modifyEnd] < end && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) {
1487                 ranges[modifyEnd + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - end;
1488                 ranges[modifyEnd] = end;
1489             }
1490             if (offset !is 0) {
1491                 for (int i = modifyEnd; i < rangeCount; i += 2) {
1492                     ranges[i] += offset;
1493                 }
1494             }
1495             System.arraycopy(ranges, modifyEnd, ranges, modifyStart, rangeCount - modifyEnd);
1496             System.arraycopy(styles, modifyEnd >> 1, styles, modifyStart >> 1, styleCount - (modifyEnd >> 1));
1497             styleCount -= (modifyEnd - modifyStart) >> 1;
1498         }
1499     } else {
1500         int modifyStart = getRangeIndex(start, -1, styleCount);
1501         if (modifyStart is styleCount) return;
1502         int end = start + replaceCharCount;
1503         int modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount);
1504         int offset = newCharCount - replaceCharCount;
1505         if (modifyStart is modifyEnd && styles[modifyStart].start < start && end < styles[modifyEnd].start + styles[modifyEnd].length) {
1506             if (newCharCount is 0) {
1507                 styles[modifyStart].length -= replaceCharCount;
1508                 modifyEnd++;
1509             } else {
1510                 if (styleCount + 1 > styles.length) {
1511                     StyleRange[] newStyles = new StyleRange[styles.length + GROW];
1512                     System.arraycopy(styles, 0, newStyles, 0, styleCount);
1513                     styles = newStyles;
1514                 }
1515                 System.arraycopy(styles, modifyStart + 1, styles, modifyStart + 2, styleCount - (modifyStart + 1));
1516                 styles[modifyStart + 1] = cast(StyleRange)styles[modifyStart].clone();
1517                 styles[modifyStart + 1].length = styles[modifyStart].start + styles[modifyStart].length - end;
1518                 styles[modifyStart + 1].start = start + newCharCount;
1519                 styles[modifyStart].length = start - styles[modifyStart].start;
1520                 styleCount++;
1521                 modifyEnd += 2;
1522             }
1523             if (offset !is 0) {
1524                 for (int i = modifyEnd; i < styleCount; i++) {
1525                     styles[i].start += offset;
1526                 }
1527             }
1528         } else {
1529             if (styles[modifyStart].start < start && start < styles[modifyStart].start + styles[modifyStart].length) {
1530                 styles[modifyStart].length = start - styles[modifyStart].start;
1531                 modifyStart++;
1532             }
1533             if (modifyEnd < styleCount && styles[modifyEnd].start < end && end < styles[modifyEnd].start + styles[modifyEnd].length) {
1534                 styles[modifyEnd].length = styles[modifyEnd].start + styles[modifyEnd].length - end;
1535                 styles[modifyEnd].start = end;
1536             }
1537             if (offset !is 0) {
1538                 for (int i = modifyEnd; i < styleCount; i++) {
1539                     styles[i].start += offset;
1540                 }
1541             }
1542             System.arraycopy(styles, modifyEnd, styles, modifyStart, styleCount - modifyEnd);
1543             styleCount -= modifyEnd - modifyStart;
1544         }
1545     }
1546 }
1547 }