001: /*
002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: package com.ibm.richtext.styledtext;
014:
015: import java.io.Externalizable;
016: import java.io.ObjectInput;
017: import java.io.ObjectOutput;
018: import java.io.IOException;
019:
020: import com.ibm.richtext.textlayout.attributes.AttributeMap;
021:
022: /*
023: Right now, you have to construct this class with a charBuffer. That's pretty ugly... */
024:
025: /*
026: 8/8/96
027: Added replace method, which reads styles from a ParagraphIterator.
028: Also, added a constructor which takes a ParagraphIterator.
029: These methods are for copy/paste support.
030:
031: 8/22/96
032: Replace method (which takes an iterator as an argument) tests for a
033: 0-length iterator.
034:
035: 9/30/96
036: {jbr} modified paragraphLimit();
037:
038: 10/23/96
039: This class now maintains paragraph styles. Also has a timestamp.
040:
041: 10/25/96
042: Holds on to Style instead of Style.
043:
044: 7/31/98 Switched to AttributeMap
045:
046: */
047:
048: /**
049: * This class stores offsets where paragraph breaks occur, and the style applied to
050: * each paragraph.
051: *
052: * The offsets where paragraph breaks occur are stored in a RunArray object. This is
053: * not strictly necessary, but it makes scanning the text for paragraph breaks unnecessary.
054: * However, it makes determining where paragraphs start a little confusing. If there is a
055: * paragraph break at offset p, then there will be a paragraph start at offset p+1.
056: * If the last character in the text is a paragraph break, there will be a run array entry
057: * for that character (and also a paragraph style for that paragraph, even though the
058: * style does not apply to any text).
059: *
060: * The style of the first paragraph in the text is in the fFirstStyle member. Other
061: * paragraph styles are stored in the fStyleTable array, in the following manner: the
062: * paragraph with begins at offset fRunArray.fRunStart[i]+1 has style fStyleTable[i].
063: * The style table's "gap range" is kept in sync with the RunArray.
064: *
065: * This class propogates paragraph styles in the "Microsoft Word" fashion: styles
066: * propogate backward from paragraph breaks.
067: *
068: * This class maintains a time stamp, which changes every time extra formatting (formatting
069: * on a range other than the current selection) is needed; for example, when a paragraph
070: * break is removed.
071: */
072:
073: final class ParagraphBuffer extends MParagraphBuffer implements
074: Externalizable {
075:
076: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
077: private static final int kInitialSize = 10;
078: private static final int CURRENT_VERSION = 1;
079: private static final long serialVersionUID = 22356934;
080:
081: private RunArray fRunArray;
082: private AttributeMap[] fStyleTable;
083: private AttributeMap fFirstStyle;
084:
085: private static final boolean isParagraphBreak(char c) {
086:
087: return c == '\u2029' || c == '\n';
088: }
089:
090: /**
091: * Construct a new paragraph buffer from the characters in <tt>charBuffer</tt>.
092: */
093: ParagraphBuffer(MCharBuffer charBuffer) {
094:
095: this (charBuffer.length());
096:
097: // scan text for paragraph boundaries
098:
099: int textLength = fRunArray.getCurTextLength();
100:
101: for (int pos = 0; pos < textLength; pos++) {
102:
103: if (isParagraphBreak(charBuffer.at(pos))) {
104: if (fRunArray.fPosEnd + 1 >= fRunArray.fNegStart)
105: expandStyleTable();
106: fRunArray.fRunStart[++fRunArray.fPosEnd] = pos;
107: fStyleTable[fRunArray.fPosEnd] = fFirstStyle;
108: }
109: }
110:
111: }
112:
113: /**
114: * Private constructor.
115: */
116: private ParagraphBuffer(int initialLength) {
117:
118: fRunArray = new RunArray(kInitialSize, initialLength);
119: fStyleTable = new AttributeMap[fRunArray.getArrayLength()];
120:
121: fFirstStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP;
122: }
123:
124: /**
125: * Note: this constructor is ONLY for use by the Serialization
126: * mechanism. It does not leave this object in a valid state!
127: */
128: public ParagraphBuffer() {
129: }
130:
131: public void writeExternal(ObjectOutput out) throws IOException {
132:
133: compress();
134: out.writeInt(CURRENT_VERSION);
135: out.writeObject(fRunArray);
136: out.writeObject(fStyleTable);
137: out.writeObject(fFirstStyle);
138: }
139:
140: public void readExternal(ObjectInput in) throws IOException,
141: ClassNotFoundException {
142:
143: if (in.readInt() != CURRENT_VERSION) {
144: throw new IOException("Invalid version of ParagraphBuffer");
145: }
146: fRunArray = (RunArray) in.readObject();
147: fStyleTable = (AttributeMap[]) in.readObject();
148: fFirstStyle = (AttributeMap) in.readObject();
149: }
150:
151: /**
152: * Shift table such that the last positive run starts before pos.
153: */
154: private void shiftTableTo(int pos) {
155:
156: int oldNegStart = fRunArray.fNegStart;
157: int oldPosEnd = fRunArray.fPosEnd;
158:
159: fRunArray.shiftTableTo(pos);
160:
161: if (oldPosEnd > fRunArray.fPosEnd)
162: System.arraycopy(fStyleTable, fRunArray.fPosEnd + 1,
163: fStyleTable, fRunArray.fNegStart, oldPosEnd
164: - fRunArray.fPosEnd);
165: else if (oldNegStart < fRunArray.fNegStart)
166: System.arraycopy(fStyleTable, oldNegStart, fStyleTable,
167: oldPosEnd + 1, fRunArray.fNegStart - oldNegStart);
168: }
169:
170: /**
171: * Update the style table to reflect a change in the RunArray's size.
172: */
173: private void handleArrayResize(int oldNegStart) {
174:
175: AttributeMap newStyleTable[] = new AttributeMap[fRunArray
176: .getArrayLength()];
177: System.arraycopy(fStyleTable, 0, newStyleTable, 0,
178: fRunArray.fPosEnd + 1);
179: System.arraycopy(fStyleTable, oldNegStart, newStyleTable,
180: fRunArray.fNegStart,
181: (fRunArray.getArrayLength() - fRunArray.fNegStart));
182: fStyleTable = newStyleTable;
183: }
184:
185: void compress() {
186:
187: int oldNegStart = fRunArray.fNegStart;
188: fRunArray.compress();
189: if (fRunArray.fNegStart != oldNegStart) {
190: handleArrayResize(oldNegStart);
191: }
192: }
193:
194: /**
195: * Make more room in run/style tables.
196: */
197: private void expandStyleTable() {
198:
199: int oldNegStart = fRunArray.fNegStart;
200: fRunArray.expandRunTable();
201: handleArrayResize(oldNegStart);
202: }
203:
204: /**
205: * Process a character insertion at offset <tt>start</tt>.
206: * If a paragraph break was inserted, propogate paragraph style at
207: * <tt>start</tt> to new paragraph.
208: */
209: public void insertText(int start, char insertedChar) {
210:
211: shiftTableTo(start);
212: if (isParagraphBreak(insertedChar)) {
213: if (fRunArray.fPosEnd + 1 >= fRunArray.fNegStart)
214: expandStyleTable();
215: fRunArray.fRunStart[++fRunArray.fPosEnd] = start;
216: fStyleTable[fRunArray.fPosEnd] = (fRunArray.fPosEnd == 0) ? fFirstStyle
217: : fStyleTable[fRunArray.fPosEnd - 1];
218: fRunArray.runStartsChanged();
219: }
220:
221: //fRunArray.fCurTextLength++;
222: fRunArray.addToCurTextLength(1);
223: }
224:
225: /**
226: * Process character insertion at offset <tt>start</tt>.
227: * Each new paragraph gets paragraph style at
228: * <tt>start</tt>.
229: */
230: public void insertText(int start, char srcChars[], int srcStart,
231: int srcLimit) {
232:
233: shiftTableTo(start);
234:
235: int adjust = start - srcStart;
236:
237: for (int i = srcStart; i < srcLimit; i++)
238: if (isParagraphBreak(srcChars[i])) {
239: if (fRunArray.fPosEnd + 1 >= fRunArray.fNegStart)
240: expandStyleTable();
241: fRunArray.fRunStart[++fRunArray.fPosEnd] = adjust + i;
242: fStyleTable[fRunArray.fPosEnd] = (fRunArray.fPosEnd == 0) ? fFirstStyle
243: : fStyleTable[fRunArray.fPosEnd - 1];
244: fRunArray.runStartsChanged();
245: }
246:
247: //fRunArray.fCurTextLength += (srcLimit-srcStart);
248: fRunArray.addToCurTextLength(srcLimit - srcStart);
249: }
250:
251: /**
252: * Process deletion by removing paragraph breaks contained in
253: * deleted range. Propogate paragraph styles backward, if necessary.
254: */
255: public void deleteText(int start, int limit, int[] damagedRange) {
256:
257: int length = limit - start;
258: if (length < 0) {
259: throw new IllegalArgumentException("Invalid range");
260: }
261:
262: shiftTableTo(limit);
263:
264: int newEnd = fRunArray.findRunContaining(start - 1);
265:
266: if (newEnd != fRunArray.fPosEnd) {
267:
268: AttributeMap propStyle = fStyleTable[fRunArray.fPosEnd];
269: boolean propogated;
270:
271: if (newEnd == -1) {
272: propogated = !propStyle.equals(fFirstStyle);
273: fFirstStyle = propStyle;
274: } else {
275: propogated = !propStyle.equals(fStyleTable[newEnd]);
276: fStyleTable[newEnd] = propStyle;
277: }
278:
279: if (propogated) {
280: int pStart = (newEnd == -1) ? 0
281: : fRunArray.fRunStart[newEnd] + 1;
282: damagedRange[0] = Math.min(damagedRange[0], pStart);
283: }
284:
285: fRunArray.fPosEnd = newEnd;
286: }
287:
288: fRunArray.addToCurTextLength(-length);
289:
290: fRunArray.runStartsChanged();
291: }
292:
293: /**
294: * Returns the start of the paragraph containing offset <tt>pos</tt>.
295: */
296: public int paragraphStart(int pos) {
297:
298: int run = fRunArray.findRunContaining(pos - 1);
299: if (run == -1) {
300: return 0;
301: } else {
302: return fRunArray.getLogicalRunStart(run) + 1;
303: }
304: }
305:
306: /**
307: * Returns the limit of the paragraph containing offset <tt>pos</tt>.
308: */
309: public int paragraphLimit(int pos) {
310:
311: int run = fRunArray.findRunContaining(pos - 1);
312:
313: if (run == fRunArray.fPosEnd)
314: run = fRunArray.fNegStart;
315: else
316: run++;
317:
318: if (run == fRunArray.getArrayLength()) {
319: return fRunArray.getCurTextLength();
320: }
321:
322: int start = fRunArray.getLogicalRunStart(run);
323:
324: return start + 1;
325: }
326:
327: /**
328: * Returns the style of the paragraph containing offset <tt>pos</tt>.
329: */
330: public AttributeMap paragraphStyleAt(int offset) {
331:
332: int run = fRunArray.findRunContaining(offset - 1);
333: if (run < 0)
334: return fFirstStyle;
335: else
336: return fStyleTable[run];
337: }
338:
339: /**
340: * Create paragraph iterator.
341: */
342: /*
343: public MParagraphIterator createParagraphIterator(int start, int limit) {
344:
345: return new ParagraphIterator(start, limit);
346: }
347: */
348:
349: /**
350: * Called by iterator to get run info.
351: */
352: private void setIterator(int pos, ParagraphIterator iter) {
353:
354: if ((pos < 0) || (pos >= fRunArray.getCurTextLength())) {
355: iter.set(0, 0, kNoRun, null);
356: return;
357: }
358:
359: int run;
360:
361: if (pos > 0)
362: run = fRunArray.findRunContaining(pos - 1);
363: else
364: run = -1;
365:
366: setIteratorUsingRun(run, iter);
367: }
368:
369: /**
370: * Called by iterator to get run info.
371: */
372: private void setIteratorUsingRun(int run, ParagraphIterator iter) {
373:
374: int lastValidRun = fRunArray.lastRun();
375:
376: if (run < -1 || run > lastValidRun) {
377: iter.set(0, 0, kNoRun, null);
378: return;
379: }
380:
381: if (run == fRunArray.fPosEnd + 1)
382: run = fRunArray.fNegStart;
383: else if (run == fRunArray.fNegStart - 1)
384: run = fRunArray.fPosEnd;
385:
386: int runStart;
387: AttributeMap style;
388:
389: if (run < 0) {
390: runStart = 0;
391: style = fFirstStyle;
392: } else {
393: runStart = fRunArray.fRunStart[run];
394: style = fStyleTable[run];
395: if (runStart < 0)
396: runStart += fRunArray.getCurTextLength();
397: runStart++;
398: }
399:
400: int nextRun;
401:
402: if (run == fRunArray.fPosEnd)
403: nextRun = fRunArray.fNegStart;
404: else
405: nextRun = run + 1;
406:
407: int runLimit;
408:
409: if (nextRun >= fRunArray.getArrayLength())
410: runLimit = fRunArray.getCurTextLength();
411: else {
412: runLimit = fRunArray.fRunStart[nextRun];
413: if (runLimit < 0)
414: runLimit += fRunArray.getCurTextLength();
415: runLimit++;
416: }
417:
418: iter.set(runStart, runLimit, run, style);
419: }
420:
421: /**
422: * Replace paragraph breaks/styles between start and length with paragraph breaks/styles
423: * from <tt>srcText</tt>.
424: * @param start an offset into the text
425: * @param limit the index after the last character to replace
426: * @param srcText the text from which new paragraphs are taken
427: * @param srcStart the start of the range in <code>srcText</code> to copy
428: * @param srcLimit the first index after the range in <code>srcText</code> to copy
429: */
430: public void replace(int start, int limit, MConstText srcText,
431: int srcStart, int srcLimit, int[] damagedRange) {
432:
433: final int insLength = srcLimit - srcStart;
434: if (insLength < 0) {
435: throw new Error("invalid range");
436: }
437: final int origLength = fRunArray.getCurTextLength();
438: deleteText(start, limit, damagedRange);
439:
440: if (insLength == 0)
441: return;
442:
443: final int oldPosEnd = fRunArray.fPosEnd;
444: AttributeMap origStyle;
445: if (limit < origLength) {
446: origStyle = (fRunArray.fPosEnd >= 0) ? fStyleTable[fRunArray.fPosEnd]
447: : fFirstStyle;
448: } else {
449: origStyle = srcText.paragraphStyleAt(srcLimit);
450: }
451:
452: int paragraphStart = srcStart;
453: int lastPLimit = srcText.paragraphStart(srcLimit);
454: boolean separatorAtEnd = lastPLimit > srcStart
455: && isParagraphBreak(srcText.at(lastPLimit - 1));
456:
457: if (limit == origLength && lastPLimit == paragraphStart) {
458: if (fRunArray.fPosEnd > 0) {
459: fStyleTable[fRunArray.fPosEnd] = origStyle;
460: } else {
461: fFirstStyle = origStyle;
462: }
463: } else {
464: boolean firstPass = true;
465: while (paragraphStart < lastPLimit) {
466:
467: AttributeMap style = srcText
468: .paragraphStyleAt(paragraphStart);
469: int paragraphLimit = srcText
470: .paragraphLimit(paragraphStart);
471:
472: if (fRunArray.fPosEnd + 1 >= fRunArray.fNegStart)
473: expandStyleTable();
474:
475: if (fRunArray.fPosEnd >= 0) {
476: if (!style.equals(fStyleTable[fRunArray.fPosEnd])) {
477: fStyleTable[fRunArray.fPosEnd] = style;
478: if (firstPass) {
479: int pStart = fRunArray.fRunStart[fRunArray.fPosEnd] + 1;
480: damagedRange[0] = Math.min(damagedRange[0],
481: pStart);
482: }
483: }
484: } else if (!style.equals(fFirstStyle)) {
485: fFirstStyle = style;
486: damagedRange[0] = 0;
487: }
488:
489: firstPass = false;
490:
491: if (paragraphLimit < lastPLimit || separatorAtEnd) {
492: fRunArray.fRunStart[++fRunArray.fPosEnd] = paragraphLimit
493: - 1 + start - srcStart;
494: }
495: paragraphStart = paragraphLimit;
496: }
497: if (fRunArray.fPosEnd != oldPosEnd) {
498: fStyleTable[fRunArray.fPosEnd] = origStyle;
499: }
500: }
501:
502: fRunArray.addToCurTextLength(insLength);
503: }
504:
505: /**
506: * Modify the style of all paragraphs containing offsets in the range [start, limit) to
507: * <tt>style</tt>.
508: */
509: public boolean modifyParagraphStyles(int start, int limit,
510: StyleModifier modifier, int[] damagedRange) {
511:
512: int run = fRunArray.findRunContaining(start - 1);
513: int currentPStart;
514: if (run == -1) {
515: currentPStart = 0;
516: } else {
517: currentPStart = fRunArray.getLogicalRunStart(run) + 1;
518: }
519:
520: boolean modifiedAnywhere = false;
521:
522: for (;;) {
523:
524: boolean modified = false;
525:
526: if (run < 0) {
527:
528: AttributeMap newStyle = modifier
529: .modifyStyle(fFirstStyle);
530:
531: if (!newStyle.equals(fFirstStyle)) {
532: fFirstStyle = newStyle;
533: modified = true;
534: }
535: } else {
536:
537: AttributeMap newStyle = modifier
538: .modifyStyle(fStyleTable[run]);
539:
540: if (!fStyleTable[run].equals(newStyle)) {
541: fStyleTable[run] = newStyle;
542: modified = true;
543: }
544: }
545:
546: if (run == fRunArray.fPosEnd) {
547: run = fRunArray.fNegStart;
548: } else {
549: run++;
550: }
551:
552: int nextPStart;
553: if (run == fRunArray.getArrayLength()) {
554: nextPStart = fRunArray.getCurTextLength();
555: } else {
556: nextPStart = fRunArray.getLogicalRunStart(run) + 1;
557: }
558:
559: if (modified) {
560: modifiedAnywhere = true;
561: damagedRange[0] = Math.min(damagedRange[0],
562: currentPStart);
563: damagedRange[1] = Math.max(damagedRange[1], nextPStart);
564: }
565:
566: if (limit <= nextPStart) {
567: break;
568: } else {
569: currentPStart = nextPStart;
570: }
571: }
572:
573: return modifiedAnywhere;
574: }
575:
576: private static void dumpParagraphStarts(ParagraphBuffer st) {
577:
578: System.out.println("fRunArray.fPosEnd=" + st.fRunArray.fPosEnd
579: + ", fRunArray.fNegStart=" + st.fRunArray.fNegStart
580: + ", fRunArray.getArrayLength()="
581: + st.fRunArray.getArrayLength()
582: + ", fRunArray.getCurTextLength()="
583: + st.fRunArray.getCurTextLength());
584:
585: int i;
586: System.out.print("Positives: ");
587: for (i = 0; i <= st.fRunArray.fPosEnd; i++)
588: System.out.print(st.fRunArray.fRunStart[i] + " ");
589:
590: System.out.print(" Negatives: ");
591: for (i = st.fRunArray.fNegStart; i < st.fRunArray
592: .getArrayLength(); i++)
593: System.out.print(st.fRunArray.fRunStart[i] + " ");
594:
595: System.out.println(" ");
596: }
597:
598: private static final int kNoRun = -42; // iterator use
599:
600: private final class ParagraphIterator /*implements MParagraphIterator*/
601: {
602: ParagraphIterator(int start, int limit) {
603: reset(start, limit, start);
604: }
605:
606: public void reset(int start, int limit, int pos) {
607: fStart = start;
608: fLimit = limit;
609: setIterator(fStart, this );
610: }
611:
612: public boolean isValid() {
613: return fCurrentRun != kNoRun;
614: }
615:
616: public void next() {
617: if (fRunLimit < fLimit) {
618: fCurrentRun++;
619: setIteratorUsingRun(fCurrentRun, this );
620: } else
621: set(0, 0, kNoRun, null);
622: }
623:
624: public void prev() {
625: if (fRunStart > fStart) {
626: fCurrentRun--;
627: setIteratorUsingRun(fCurrentRun, this );
628: } else
629: set(0, 0, kNoRun, null);
630: }
631:
632: public void set(int pos) {
633: if (pos >= fStart && pos < fLimit) {
634: setIterator(pos, this );
635: } else {
636: set(0, 0, kNoRun, null);
637: }
638: }
639:
640: // ParagraphBuffer calls back on this to set iterators
641: void set(int start, int limit, int currentRun,
642: AttributeMap style) {
643: fRunStart = start < fStart ? fStart : start;
644: fRunLimit = limit > fLimit ? fLimit : limit;
645: fCurrentRun = currentRun;
646: fStyle = style;
647: }
648:
649: public void reset(int start, int limit) {
650: reset(start, limit, start);
651: }
652:
653: public void first() {
654: set(fStart);
655: }
656:
657: public void last() {
658: set(fLimit - 1);
659: }
660:
661: public int rangeStart() {
662: return fStart;
663: }
664:
665: public int rangeLimit() {
666: return fLimit;
667: }
668:
669: public int rangeLength() {
670: return fLimit - fStart;
671: }
672:
673: public int runStart() {
674: return fRunStart;
675: }
676:
677: public int runLimit() {
678: return fRunLimit;
679: }
680:
681: public int runLength() {
682: return fRunLimit - fRunStart;
683: }
684:
685: public AttributeMap style() {
686:
687: return fStyle;
688: }
689:
690: private int fStart;
691: private int fLimit;
692: private int fRunStart;
693: private int fRunLimit;
694: private int fCurrentRun;
695: private AttributeMap fStyle;
696: }
697: }
|