]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/richtext/styledtext/StyleBuffer.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / richtext / styledtext / StyleBuffer.java
1 /*\r
2  * (C) Copyright IBM Corp. 1998-2004.  All Rights Reserved.\r
3  *\r
4  * The program is provided "as is" without any warranty express or\r
5  * implied, including the warranty of non-infringement and the implied\r
6  * warranties of merchantibility and fitness for a particular purpose.\r
7  * IBM will not be liable for any damages suffered by you as a result\r
8  * of using the Program. In no event will IBM be liable for any\r
9  * special, indirect or consequential damages or lost profits even if\r
10  * IBM has been advised of the possibility of their occurrence. IBM\r
11  * will not be liable for any third party claims against you.\r
12  */\r
13 package com.ibm.richtext.styledtext;\r
14 \r
15 import java.io.Externalizable;\r
16 import java.io.ObjectInput;\r
17 import java.io.ObjectOutput;\r
18 import java.io.IOException;\r
19 \r
20 import com.ibm.richtext.textlayout.attributes.AttributeMap;\r
21 \r
22 /*\r
23     8/2/96\r
24         Added setIteratorUsingRun method\r
25 \r
26     8/5/96\r
27         No longer has to be constructed with an MText.\r
28 \r
29     8/8/96\r
30         Added replace method, which reads styles from a StyleRunIterator.\r
31         Also, added a constructor which takes a StyleRunIterator.\r
32         These methods are for copy/paste support.\r
33     8/16/96\r
34         StyleBuffer now takes MConstText instead of MText where possible.\r
35 \r
36     10/23/96\r
37         Some old commented-out code removed for aesthetic reasons.\r
38 \r
39     7/31/98 Switched to AttributeMap\r
40 */\r
41 \r
42 /**\r
43 * StyleBuffer implements <tt>MStyleBuffer</tt>.  It maintains\r
44 * <tt>AttributeMap</tt> objects to apply to the text in an <tt>MText</tt> object,\r
45 * and the\r
46 * intervals on which those styles apply.\r
47 * <p>\r
48 * StyleBuffer stores the intervals on which styles apply in a <tt>RunArray</tt>\r
49 * object (see <tt>RunArray</tt> for more information).  The styles are stored in\r
50 * an array of <tt>AttributeMap</tt> objects.\r
51 * <p>\r
52 * <tt>RunArray</tt> maintains an array of integers which represent offsets into text.\r
53 * The array has a "positive" region in which offsets are given as positive distances\r
54 * from the start of the text, and a "negative" region in which offsets are given as\r
55 * negative distances from the end of the text.  Between the positive and negative regions\r
56 * is a gap, into which new offsets may be inserted.  This storage scheme allows for\r
57 * efficient response to a series of editing operations which occur in the same area of the\r
58 * text.\r
59 * <p>\r
60 * StyleBuffer uses the offsets in <tt>RunArray</tt> as the boundaries of style runs.\r
61 * A style run begins at each offset in <tt>RunArray</tt>, and each style run continues to\r
62 * the next offset.  The style array is kept in sync with the array of offsets in <tt>RunArray</tt>;\r
63 * that is, the style which begins at RunArray[i] is stored in StyleArray[i].\r
64 * <p>\r
65 * The first entry in the <tt>RunArray</tt> is always 0.\r
66 *\r
67 * @author John Raley\r
68 *\r
69 * @see AttributeMap\r
70 * @see MText\r
71 * @see RunArray\r
72 */\r
73 \r
74 final class StyleBuffer extends MStyleBuffer implements Externalizable {\r
75 \r
76     static final String COPYRIGHT =\r
77                 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
78     /**\r
79     * Creates a new style buffer with length equal to the length of <tt>text</tt>,\r
80     * and with a single run of <tt>defaultStyle</tt>.\r
81     */\r
82     private static final long serialVersionUID = 22356934;\r
83 \r
84     private static final int CURRENT_VERSION = 1;\r
85     private static final int kInitialSize = 10;\r
86     private RunArray fRunArray;\r
87 \r
88     private AttributeMap fStyleTable[];\r
89 \r
90     StyleBuffer(MConstText text, AttributeMap initialStyle) {\r
91 \r
92         this(text.length(), initialStyle);\r
93     }\r
94 \r
95     /**\r
96     * Creates a new style buffer with length <tt>initialLength</tt> and with a\r
97     * single run of <tt>defaultStyle</tt>.\r
98     */\r
99 \r
100     StyleBuffer(int initialLength, AttributeMap initialStyle) {\r
101 \r
102         fRunArray = new RunArray(kInitialSize, initialLength);\r
103         fRunArray.fPosEnd = 0;\r
104         fRunArray.fRunStart[0] = 0;\r
105 \r
106         fStyleTable = new AttributeMap[kInitialSize]; // do I really want to do this???\r
107 \r
108         fStyleTable[0] = initialStyle;\r
109     }\r
110 \r
111     /**\r
112      * Note: this constructor is ONLY for use by the Serialization\r
113      * mechanism.  It does not leave this object in a valid state!\r
114      */\r
115     public StyleBuffer() {\r
116     }\r
117 \r
118     public void writeExternal(ObjectOutput out) throws IOException {\r
119 \r
120         compress();\r
121         out.writeInt(CURRENT_VERSION);\r
122         out.writeObject(fRunArray);\r
123         out.writeObject(fStyleTable);\r
124     }\r
125 \r
126     public void readExternal(ObjectInput in) throws IOException,\r
127                                                 ClassNotFoundException {\r
128 \r
129         if (in.readInt() != CURRENT_VERSION) {\r
130             throw new IOException("Invalid version of StyleBuffer");\r
131         }\r
132         fRunArray = (RunArray) in.readObject();\r
133         fStyleTable = (AttributeMap[]) in.readObject();\r
134     }\r
135 \r
136 /**\r
137 * Shift style and run tables such that the last positive run begins before the given position.\r
138 * Since there is always a run start at 0, this method ensures that the first run will not be shifted.\r
139 * This is called by: <tt>insertText</tt> and <tt>deleteText</tt>.\r
140 * @param pos a position in the text.\r
141 */\r
142 \r
143     private void shiftTableTo(int pos) {\r
144 \r
145         if (pos == 0)\r
146             pos = 1;\r
147 \r
148         int oldNegStart = fRunArray.fNegStart;\r
149         int oldPosEnd = fRunArray.fPosEnd;\r
150 \r
151         fRunArray.shiftTableTo(pos);\r
152 \r
153         if (oldPosEnd > fRunArray.fPosEnd)\r
154             System.arraycopy(fStyleTable, fRunArray.fPosEnd+1,\r
155                              fStyleTable, fRunArray.fNegStart,\r
156                              oldPosEnd-fRunArray.fPosEnd);\r
157         else if (oldNegStart < fRunArray.fNegStart)\r
158             System.arraycopy(fStyleTable, oldNegStart,\r
159                              fStyleTable, oldPosEnd+1,\r
160                              fRunArray.fNegStart-oldNegStart);\r
161     }\r
162 \r
163 /**\r
164 * Update the style table to reflect a change in the RunArray's size.\r
165 */\r
166     private void handleArrayResize(int oldNegStart) {\r
167 \r
168         AttributeMap newStyleTable[] = new AttributeMap[fRunArray.getArrayLength()];\r
169         System.arraycopy(fStyleTable, 0, newStyleTable, 0, fRunArray.fPosEnd+1);\r
170         System.arraycopy(fStyleTable, oldNegStart, newStyleTable, fRunArray.fNegStart, (fRunArray.getArrayLength()-fRunArray.fNegStart));\r
171         fStyleTable = newStyleTable;\r
172     }\r
173 \r
174 /**\r
175 * Minimize the amount of storage used by this object.\r
176 */\r
177     void compress() {\r
178 \r
179         int oldNegStart = fRunArray.fNegStart;\r
180         fRunArray.compress();\r
181         if (fRunArray.fNegStart != oldNegStart) {\r
182             handleArrayResize(oldNegStart);\r
183         }\r
184     }\r
185 \r
186 /**\r
187 * Increase the storage capacity of the style and run tables if no room remains.\r
188 */\r
189     private void expandStyleTableIfFull() {\r
190 \r
191         if (fRunArray.fPosEnd + 1 == fRunArray.fNegStart) {\r
192 \r
193             int oldNegStart = fRunArray.fNegStart;\r
194             fRunArray.expandRunTable();\r
195             handleArrayResize(oldNegStart);\r
196         }\r
197     }\r
198 \r
199 /*\r
200     public MStyleRunIterator createStyleRunIterator(int start, int limit) {\r
201 \r
202         return new StyleRunIterator(start, limit);\r
203     }\r
204 */\r
205 /**\r
206 * Respond to an insertion in the text.  The length of the last style run which\r
207 * begins before <tt>start</tt> is increased by <tt>length</tt>.  The run table\r
208 * is shifted such that the run into which text was inserted is the last positive run.\r
209 * This implementation assumes that all styles propogate.\r
210 * @param start the offset where the insertion began\r
211 * @param length the number of characters inserted\r
212 */\r
213     public void insertText(int start, int limit) {\r
214 \r
215         shiftTableTo(start);\r
216         fRunArray.addToCurTextLength(limit - start);\r
217     }\r
218 \r
219 /**\r
220 * Respond to a deletion in the text.  The last style run before\r
221 * <tt>start</tt> is truncated to end at <tt>start</tt>.  The\r
222 * style run containing (<tt>start</tt>+<tt>length</tt>) is set to begin\r
223 * at (<tt>start</tt>+<tt>length</tt>).  Runs in between are deleted.\r
224 * If the deletion occurs entirely within one style run, the length of the style\r
225 * run is reduced by <tt>length</tt>.\r
226 * This implementation assumes that all styles propogate.\r
227 * This method shifts the run table such that the run in which the delete began\r
228 * is the last positive run.  Other methods depend on this "side effect".\r
229 * @param start the offset where the deletion began\r
230 * @param length the offset where the deletion stopped\r
231 */\r
232     public void deleteText(int start, int limit) {\r
233 \r
234         int length = limit - start;\r
235 \r
236         // An optimization - if a whole run wasn't deleted we don't\r
237         // need to check for run merging, which could be expensive.\r
238         boolean wholeRunDeleted = false;\r
239 \r
240         shiftTableTo(start);\r
241 \r
242         int firstRunLimit = fRunArray.getCurTextLength();\r
243         if (fRunArray.fNegStart < fRunArray.getArrayLength())\r
244             firstRunLimit += fRunArray.fRunStart[fRunArray.fNegStart];\r
245 \r
246         if (limit == fRunArray.getCurTextLength()) {\r
247             fRunArray.fNegStart = fRunArray.getArrayLength();\r
248         }\r
249         else if (limit >= firstRunLimit) {\r
250 \r
251             int end = fRunArray.findRunContaining(limit);\r
252             if (end != fRunArray.fPosEnd) {\r
253                 fRunArray.fRunStart[end] = limit - fRunArray.getCurTextLength();\r
254                 fRunArray.fNegStart = end;\r
255                 wholeRunDeleted = true;\r
256             }\r
257         }\r
258 \r
259         if (fRunArray.fNegStart != fRunArray.getArrayLength()) {\r
260             if (start == 0 && limit >= firstRunLimit) {\r
261                 // the first style run was deleted;  move first "negative" run into\r
262                 // first position\r
263                 fStyleTable[0] = fStyleTable[fRunArray.fNegStart++];\r
264             }\r
265             else if (wholeRunDeleted) {\r
266                 if (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd])) {\r
267                     // merge style runs\r
268                     fRunArray.fNegStart++;\r
269                 }\r
270             }\r
271         }\r
272 \r
273         fRunArray.addToCurTextLength(-length);\r
274 \r
275         fRunArray.runStartsChanged();\r
276         //System.out.println("In deleteText:  number of style runs = " + numRuns(this));\r
277     }\r
278 \r
279 /**\r
280 * Arrange style table so that old styles in the provided range are removed, and\r
281 * new styles can be inserted into the insertion gap.\r
282 * After calling this method, new style starts and styles may be placed\r
283 * in the insertion gaps of fRunArray.fStyleStart and fStyleTable.\r
284 * @param start offset in the text where insertion operation begins\r
285 * @param limit offset in the text where previous styles resume\r
286 */\r
287     private void prepareStyleInsert(int start) {\r
288 \r
289         if (start == 0) {\r
290 \r
291             // fRunArray.fPosEnd should be 0 if we're in this branch.\r
292 \r
293             if (fRunArray.getCurTextLength() > 0) {\r
294 \r
295                 /* Move first existing style run to negative end of buffer.\r
296                    Don't do this if length==0;  that is, if there is no real\r
297                    style run at 0.\r
298                  */\r
299 \r
300                 fRunArray.fNegStart--;\r
301                 fStyleTable[fRunArray.fNegStart] = fStyleTable[0];\r
302                 fRunArray.fRunStart[fRunArray.fNegStart] = -fRunArray.getCurTextLength();\r
303             }\r
304 \r
305             fRunArray.fPosEnd = -1;\r
306         }\r
307         else {\r
308 \r
309             // consistency check: start should be in current gap\r
310             if (fRunArray.fRunStart[fRunArray.fPosEnd] >= start) {\r
311                 throw new Error("Inconsistent state!  Start should be within insertion gap.");\r
312             }\r
313 \r
314             int endOfInsertionGap = fRunArray.getCurTextLength();\r
315             if (fRunArray.fNegStart < fRunArray.getArrayLength()) {\r
316                 endOfInsertionGap += fRunArray.fRunStart[fRunArray.fNegStart];\r
317             }\r
318 \r
319             if (endOfInsertionGap < start) {\r
320                 throw new Error("Inconsistent state!  Start should be within insertion gap.");\r
321             }\r
322 \r
323             // if no break at start (on negative end of buffer) make one\r
324 \r
325             if (endOfInsertionGap != start) {\r
326 \r
327                 // split style run in insertion gap\r
328 \r
329                 expandStyleTableIfFull();\r
330 \r
331                 fRunArray.fNegStart--;\r
332                 fStyleTable[fRunArray.fNegStart] = fStyleTable[fRunArray.fPosEnd];\r
333                 fRunArray.fRunStart[fRunArray.fNegStart] = start - fRunArray.getCurTextLength();\r
334 \r
335                 //System.out.println("splitting run.");\r
336             }\r
337         }\r
338     }\r
339 \r
340     public boolean modifyStyles(int start,\r
341                                 int limit,\r
342                                 StyleModifier modifier,\r
343                                 int[] damagedRange) {\r
344 \r
345         if (limit == start) {\r
346             return false;\r
347         }\r
348 \r
349         shiftTableTo(start);\r
350 \r
351         int currentRunStart = start;\r
352         AttributeMap oldStyle;\r
353         AttributeMap mergeStyle = fStyleTable[fRunArray.fPosEnd];\r
354 \r
355         if (fRunArray.fNegStart < fRunArray.getArrayLength() &&\r
356                 fRunArray.fRunStart[fRunArray.fNegStart]+fRunArray.getCurTextLength() == start) {\r
357 \r
358             oldStyle = fStyleTable[fRunArray.fNegStart];\r
359             ++fRunArray.fNegStart;\r
360         }\r
361         else {\r
362             oldStyle = mergeStyle;\r
363         }\r
364 \r
365         boolean modifiedAnywhere = false;\r
366         for(;;) {\r
367 \r
368             boolean modified = false;\r
369 \r
370             // push new style into gap on positive side\r
371             AttributeMap newStyle = modifier.modifyStyle(oldStyle);\r
372             if (damagedRange != null && !newStyle.equals(oldStyle)) {\r
373                 modified = modifiedAnywhere = true;\r
374                 damagedRange[0] = Math.min(currentRunStart, damagedRange[0]);\r
375             }\r
376 \r
377             if (!newStyle.equals(mergeStyle)) {\r
378 \r
379                 if (currentRunStart != 0) {\r
380                     expandStyleTableIfFull();\r
381                     ++fRunArray.fPosEnd;\r
382                 }\r
383 \r
384                 fStyleTable[fRunArray.fPosEnd] = newStyle;\r
385                 fRunArray.fRunStart[fRunArray.fPosEnd] = currentRunStart;\r
386             }\r
387 \r
388             mergeStyle = newStyle;\r
389 \r
390             int nextRunStart = fRunArray.getLogicalRunStart(fRunArray.fNegStart);\r
391 \r
392             if (limit > nextRunStart) {\r
393                 oldStyle = fStyleTable[fRunArray.fNegStart];\r
394                 currentRunStart = nextRunStart;\r
395                 if (modified) {\r
396                     damagedRange[1] = Math.max(currentRunStart, damagedRange[1]);\r
397                 }\r
398                 ++fRunArray.fNegStart;\r
399             }\r
400             else {\r
401                 if (limit < nextRunStart && !oldStyle.equals(mergeStyle)) {\r
402                     expandStyleTableIfFull();\r
403                     ++fRunArray.fPosEnd;\r
404                     fStyleTable[fRunArray.fPosEnd] = oldStyle;\r
405                     fRunArray.fRunStart[fRunArray.fPosEnd] = limit;\r
406                 }\r
407                 if (modified) {\r
408                     damagedRange[1] = Math.max(limit, damagedRange[1]);\r
409                 }\r
410                 break;\r
411             }\r
412         }\r
413 \r
414         // merge last run if needed\r
415         if ((fRunArray.fNegStart < fRunArray.getArrayLength()) &&\r
416                     (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))) {\r
417             fRunArray.fNegStart++;\r
418         }\r
419 \r
420         fRunArray.runStartsChanged();\r
421 \r
422         return modifiedAnywhere;\r
423     }\r
424 \r
425     public int styleStart(int pos) {\r
426 \r
427         if (pos == fRunArray.getCurTextLength()) {\r
428             return pos;\r
429         }\r
430 \r
431         return fRunArray.getLogicalRunStart(fRunArray.findRunContaining(pos));\r
432     }\r
433 \r
434     public int styleLimit(int pos) {\r
435 \r
436         if (pos == fRunArray.getCurTextLength()) {\r
437             return pos;\r
438         }\r
439 \r
440         int run = fRunArray.findRunContaining(pos);\r
441 \r
442         if (run == fRunArray.fPosEnd) {\r
443             run = fRunArray.fNegStart;\r
444         }\r
445         else {\r
446             ++run;\r
447         }\r
448 \r
449         return fRunArray.getLogicalRunStart(run);\r
450     }\r
451 \r
452 /**\r
453 * Return style at location <tt>pos</tt>.\r
454 * @param pos an offset into the text\r
455 * @returns the style of the character at <tt>offset</tt>\r
456 */\r
457     public AttributeMap styleAt(int pos) {\r
458 \r
459         return fStyleTable[ fRunArray.findRunContaining(pos) ];\r
460     }\r
461 \r
462 /*\r
463 * Set run start, run length, and run value in an iterator.  This method is\r
464 * only called by a <tt>StyleRunIterator</tt>.\r
465 * @param pos an offset into the text.  The iterator's run start and run limit are\r
466 * set to the run containing <tt>pos</tt>.\r
467 * @param iter the iterator to set\r
468 */\r
469     void setIterator(int pos, StyleRunIterator iter) {\r
470 \r
471         if ((pos < 0) || (pos > fRunArray.getCurTextLength())) {\r
472 \r
473             iter.set(null, 0, 0, kNoRun);\r
474             return;\r
475         }\r
476 \r
477         int run = fRunArray.findRunContaining(pos);\r
478 \r
479         setIteratorUsingRun(run, iter);\r
480     }\r
481 \r
482 /**\r
483 * Set run start, run length, and run value in an iterator.  This method is\r
484 * only called by a <tt>StyleRunIterator</tt>.\r
485 * @param run the index of the run to which the iterator should be set\r
486 * @param iter the iterator to set\r
487 */\r
488     private void setIteratorUsingRun(int run, StyleRunIterator iter) {\r
489 \r
490         int lastValidRun = fRunArray.lastRun();\r
491 \r
492         if (run < 0 || run > lastValidRun) {\r
493 \r
494             iter.set(null, 0, 0, kNoRun);\r
495             return;\r
496         }\r
497 \r
498         if (run == fRunArray.fPosEnd+1)\r
499             run = fRunArray.fNegStart;\r
500         else if (run == fRunArray.fNegStart-1)\r
501             run = fRunArray.fPosEnd;\r
502 \r
503         int runStart = fRunArray.fRunStart[run];\r
504         if (runStart < 0)\r
505             runStart += fRunArray.getCurTextLength();\r
506 \r
507         AttributeMap style = fStyleTable[run];\r
508 \r
509         int nextRun;\r
510 \r
511         if (run == fRunArray.fPosEnd)\r
512             nextRun = fRunArray.fNegStart;\r
513         else\r
514             nextRun = run + 1;\r
515 \r
516         int runLimit;\r
517 \r
518         if (nextRun >= fRunArray.getArrayLength())\r
519             runLimit = fRunArray.getCurTextLength();\r
520         else {\r
521             runLimit = fRunArray.fRunStart[nextRun];\r
522             if (runLimit < 0)\r
523                 runLimit += fRunArray.getCurTextLength();\r
524         }\r
525 \r
526         //System.out.println("setIterator: pos="+pos+", runStart="+runStart+", runLimit="+runLimit+\r
527         //                  ", run="+run+", fPosEnd="+fPosEnd);\r
528 \r
529         iter.set(style, runStart, runLimit, run);\r
530     }\r
531 \r
532     public void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit)\r
533     {\r
534         deleteText(start, limit);\r
535         if (srcStart == srcLimit)\r
536             return;\r
537         prepareStyleInsert(start);\r
538         for (int j2 = srcStart; j2 < srcLimit; j2 = srcText.characterStyleLimit(j2))\r
539         {\r
540             AttributeMap attributeMap = srcText.characterStyleAt(j2);\r
541             if (fRunArray.fPosEnd < 0 || !fStyleTable[fRunArray.fPosEnd].equals(attributeMap))\r
542             {\r
543                 expandStyleTableIfFull();\r
544                 fRunArray.fPosEnd++;\r
545                 fRunArray.fRunStart[fRunArray.fPosEnd] = j2 - srcStart + start;\r
546                 fStyleTable[fRunArray.fPosEnd] = attributeMap;\r
547             }\r
548         }\r
549         fRunArray.addToCurTextLength(srcLimit - srcStart);\r
550         if (fRunArray.fNegStart < fRunArray.getArrayLength() && fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))\r
551             fRunArray.fNegStart++;\r
552     }\r
553 \r
554     private  static final int kNoRun = -42; // iterator use\r
555 \r
556     private final class StyleRunIterator /*implements MStyleRunIterator*/ {\r
557 \r
558         StyleRunIterator(int start, int limit)\r
559         {\r
560             reset(start, limit, start);\r
561         }\r
562 \r
563         public void reset(int start, int limit, int pos)\r
564         {\r
565             fStart = start;\r
566             fLimit = limit;\r
567             setIterator(fStart, this);\r
568         }\r
569 \r
570         public boolean isValid()\r
571         {\r
572             return fStyle != null;\r
573         }\r
574 \r
575         public void next()\r
576         {\r
577             if (fRunLimit < fLimit) {\r
578                 fCurrentRun++;\r
579                 setIteratorUsingRun(fCurrentRun, this);\r
580             }\r
581             else\r
582                 set(null, 0, 0, kNoRun);\r
583         }\r
584 \r
585         public void prev()\r
586         {\r
587             if (fRunStart > fStart) {\r
588                 fCurrentRun--;\r
589                 setIteratorUsingRun(fCurrentRun, this);\r
590             }\r
591             else\r
592                 set(null, 0, 0, kNoRun);\r
593         }\r
594 \r
595         public void set(int pos)\r
596         {\r
597             if (pos >= fStart && pos < fLimit) {\r
598                 setIterator(pos, this);\r
599             } else {\r
600                 set(null, 0, 0, kNoRun);\r
601             }\r
602         }\r
603 \r
604         void set(AttributeMap style, int start, int limit, int currentRun)\r
605         {\r
606             fStyle = style;\r
607             fCurrentRun = currentRun;\r
608             fRunStart = start < fStart ? fStart : start;\r
609             fRunLimit = limit > fLimit ? fLimit : limit;\r
610         }\r
611 \r
612         public void reset(int start, int limit)\r
613         {\r
614             reset(start, limit, start);\r
615         }\r
616 \r
617         public void first()\r
618         {\r
619             set(fStart);\r
620         }\r
621 \r
622         public void last()\r
623         {\r
624             set(fLimit - 1);\r
625         }\r
626 \r
627         public int rangeStart()\r
628         {\r
629             return fStart;\r
630         }\r
631 \r
632         public int rangeLimit()\r
633         {\r
634             return fLimit;\r
635         }\r
636 \r
637         public int rangeLength()\r
638         {\r
639             return fLimit - fStart;\r
640         }\r
641 \r
642         public AttributeMap style()\r
643         {\r
644             return fStyle;\r
645         }\r
646 \r
647         public int runStart()\r
648         {\r
649             return fRunStart;\r
650         }\r
651 \r
652         public int runLimit()\r
653         {\r
654             return fRunLimit;\r
655         }\r
656 \r
657         public int runLength()\r
658         {\r
659             return fRunLimit - fRunStart;\r
660         }\r
661 \r
662         private int fStart;\r
663         private int fLimit;\r
664         private AttributeMap fStyle;\r
665         private int fRunStart;\r
666         private int fRunLimit;\r
667         private int fCurrentRun;\r
668     }\r
669 }