| /* |
| * @(#)$RCSfile: ParagraphBuffer.java,v $ $Revision: 1.2 $ $Date: 2002/02/16 03:06:36 $ |
| * |
| * (C) Copyright IBM Corp. 1998-1999. All Rights Reserved. |
| * |
| * The program is provided "as is" without any warranty express or |
| * implied, including the warranty of non-infringement and the implied |
| * warranties of merchantibility and fitness for a particular purpose. |
| * IBM will not be liable for any damages suffered by you as a result |
| * of using the Program. In no event will IBM be liable for any |
| * special, indirect or consequential damages or lost profits even if |
| * IBM has been advised of the possibility of their occurrence. IBM |
| * will not be liable for any third party claims against you. |
| */ |
| package com.ibm.richtext.styledtext; |
| |
| import java.io.Externalizable; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.IOException; |
| |
| import com.ibm.richtext.textlayout.attributes.AttributeMap; |
| |
| /* |
| Right now, you have to construct this class with a charBuffer. That's pretty ugly... */ |
| |
| /* |
| 8/8/96 |
| Added replace method, which reads styles from a ParagraphIterator. |
| Also, added a constructor which takes a ParagraphIterator. |
| These methods are for copy/paste support. |
| |
| 8/22/96 |
| Replace method (which takes an iterator as an argument) tests for a |
| 0-length iterator. |
| |
| 9/30/96 |
| {jbr} modified paragraphLimit(); |
| |
| 10/23/96 |
| This class now maintains paragraph styles. Also has a timestamp. |
| |
| 10/25/96 |
| Holds on to Style instead of Style. |
| |
| 7/31/98 Switched to AttributeMap |
| |
| */ |
| |
| /** |
| * This class stores offsets where paragraph breaks occur, and the style applied to |
| * each paragraph. |
| * |
| * The offsets where paragraph breaks occur are stored in a RunArray object. This is |
| * not strictly necessary, but it makes scanning the text for paragraph breaks unnecessary. |
| * However, it makes determining where paragraphs start a little confusing. If there is a |
| * paragraph break at offset p, then there will be a paragraph start at offset p+1. |
| * If the last character in the text is a paragraph break, there will be a run array entry |
| * for that character (and also a paragraph style for that paragraph, even though the |
| * style does not apply to any text). |
| * |
| * The style of the first paragraph in the text is in the fFirstStyle member. Other |
| * paragraph styles are stored in the fStyleTable array, in the following manner: the |
| * paragraph with begins at offset fRunArray.fRunStart[i]+1 has style fStyleTable[i]. |
| * The style table's "gap range" is kept in sync with the RunArray. |
| * |
| * This class propogates paragraph styles in the "Microsoft Word" fashion: styles |
| * propogate backward from paragraph breaks. |
| * |
| * This class maintains a time stamp, which changes every time extra formatting (formatting |
| * on a range other than the current selection) is needed; for example, when a paragraph |
| * break is removed. |
| */ |
| |
| |
| final class ParagraphBuffer extends MParagraphBuffer implements Externalizable { |
| |
| static final String COPYRIGHT = |
| "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; |
| private static final int kInitialSize = 10; |
| private static final int CURRENT_VERSION = 1; |
| private static final long serialVersionUID = 22356934; |
| |
| private RunArray fRunArray; |
| private AttributeMap[] fStyleTable; |
| private AttributeMap fFirstStyle; |
| |
| private static final boolean isParagraphBreak(char c) { |
| |
| return c =='\u2029' || c == '\n'; |
| } |
| |
| /** |
| * Construct a new paragraph buffer from the characters in <tt>charBuffer</tt>. |
| */ |
| ParagraphBuffer(MCharBuffer charBuffer) { |
| |
| this(charBuffer.length()); |
| |
| // scan text for paragraph boundaries |
| |
| int textLength = fRunArray.getCurTextLength(); |
| |
| for (int pos=0; pos < textLength; pos++) { |
| |
| if (isParagraphBreak(charBuffer.at(pos))) { |
| if (fRunArray.fPosEnd+1 >= fRunArray.fNegStart) |
| expandStyleTable(); |
| fRunArray.fRunStart[++fRunArray.fPosEnd] = pos; |
| fStyleTable[fRunArray.fPosEnd] = fFirstStyle; |
| } |
| } |
| |
| } |
| |
| /** |
| * Private constructor. |
| */ |
| private ParagraphBuffer(int initialLength) { |
| |
| fRunArray = new RunArray(kInitialSize, initialLength); |
| fStyleTable = new AttributeMap[fRunArray.getArrayLength()]; |
| |
| fFirstStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP; |
| } |
| |
| /** |
| * Note: this constructor is ONLY for use by the Serialization |
| * mechanism. It does not leave this object in a valid state! |
| */ |
| public ParagraphBuffer() { |
| } |
| |
| public void writeExternal(ObjectOutput out) throws IOException { |
| |
| compress(); |
| out.writeInt(CURRENT_VERSION); |
| out.writeObject(fRunArray); |
| out.writeObject(fStyleTable); |
| out.writeObject(fFirstStyle); |
| } |
| |
| public void readExternal(ObjectInput in) throws IOException, |
| ClassNotFoundException { |
| |
| if (in.readInt() != CURRENT_VERSION) { |
| throw new IOException("Invalid version of ParagraphBuffer"); |
| } |
| fRunArray = (RunArray) in.readObject(); |
| fStyleTable = (AttributeMap[]) in.readObject(); |
| fFirstStyle = (AttributeMap) in.readObject(); |
| } |
| |
| /** |
| * Shift table such that the last positive run starts before pos. |
| */ |
| private void shiftTableTo(int pos) { |
| |
| int oldNegStart = fRunArray.fNegStart; |
| int oldPosEnd = fRunArray.fPosEnd; |
| |
| fRunArray.shiftTableTo(pos); |
| |
| if (oldPosEnd > fRunArray.fPosEnd) |
| System.arraycopy(fStyleTable, fRunArray.fPosEnd+1, |
| fStyleTable, fRunArray.fNegStart, |
| oldPosEnd-fRunArray.fPosEnd); |
| else if (oldNegStart < fRunArray.fNegStart) |
| System.arraycopy(fStyleTable, oldNegStart, |
| fStyleTable, oldPosEnd+1, |
| fRunArray.fNegStart-oldNegStart); |
| } |
| |
| /** |
| * Update the style table to reflect a change in the RunArray's size. |
| */ |
| private void handleArrayResize(int oldNegStart) { |
| |
| AttributeMap newStyleTable[] = new AttributeMap[fRunArray.getArrayLength()]; |
| System.arraycopy(fStyleTable, 0, newStyleTable, 0, fRunArray.fPosEnd+1); |
| System.arraycopy(fStyleTable, oldNegStart, newStyleTable, fRunArray.fNegStart, (fRunArray.getArrayLength()-fRunArray.fNegStart)); |
| fStyleTable = newStyleTable; |
| } |
| |
| void compress() { |
| |
| int oldNegStart = fRunArray.fNegStart; |
| fRunArray.compress(); |
| if (fRunArray.fNegStart != oldNegStart) { |
| handleArrayResize(oldNegStart); |
| } |
| } |
| |
| /** |
| * Make more room in run/style tables. |
| */ |
| private void expandStyleTable() { |
| |
| int oldNegStart = fRunArray.fNegStart; |
| fRunArray.expandRunTable(); |
| handleArrayResize(oldNegStart); |
| } |
| |
| /** |
| * Process a character insertion at offset <tt>start</tt>. |
| * If a paragraph break was inserted, propogate paragraph style at |
| * <tt>start</tt> to new paragraph. |
| */ |
| public void insertText(int start, char insertedChar) { |
| |
| shiftTableTo(start); |
| if (isParagraphBreak(insertedChar)) { |
| if (fRunArray.fPosEnd+1 >= fRunArray.fNegStart) |
| expandStyleTable(); |
| fRunArray.fRunStart[++fRunArray.fPosEnd] = start; |
| fStyleTable[fRunArray.fPosEnd] = |
| (fRunArray.fPosEnd == 0)? fFirstStyle : fStyleTable[fRunArray.fPosEnd-1]; |
| fRunArray.runStartsChanged(); |
| } |
| |
| //fRunArray.fCurTextLength++; |
| fRunArray.addToCurTextLength(1); |
| } |
| |
| /** |
| * Process character insertion at offset <tt>start</tt>. |
| * Each new paragraph gets paragraph style at |
| * <tt>start</tt>. |
| */ |
| public void insertText(int start, char srcChars[], int srcStart, int srcLimit) { |
| |
| shiftTableTo(start); |
| |
| int adjust = start - srcStart; |
| |
| for (int i=srcStart; i < srcLimit; i++) |
| if (isParagraphBreak(srcChars[i])) { |
| if (fRunArray.fPosEnd+1 >= fRunArray.fNegStart) |
| expandStyleTable(); |
| fRunArray.fRunStart[++fRunArray.fPosEnd] = adjust + i; |
| fStyleTable[fRunArray.fPosEnd] = |
| (fRunArray.fPosEnd == 0)? fFirstStyle : fStyleTable[fRunArray.fPosEnd-1]; |
| fRunArray.runStartsChanged(); |
| } |
| |
| //fRunArray.fCurTextLength += (srcLimit-srcStart); |
| fRunArray.addToCurTextLength(srcLimit-srcStart); |
| } |
| |
| /** |
| * Process deletion by removing paragraph breaks contained in |
| * deleted range. Propogate paragraph styles backward, if necessary. |
| */ |
| public void deleteText(int start, int limit, int[] damagedRange) { |
| |
| int length = limit - start; |
| if (length < 0) { |
| throw new IllegalArgumentException("Invalid range"); |
| } |
| |
| shiftTableTo(limit); |
| |
| int newEnd = fRunArray.findRunContaining(start-1); |
| |
| if (newEnd != fRunArray.fPosEnd) { |
| |
| AttributeMap propStyle = fStyleTable[fRunArray.fPosEnd]; |
| boolean propogated; |
| |
| if (newEnd == -1) { |
| propogated = !propStyle.equals(fFirstStyle); |
| fFirstStyle = propStyle; |
| } |
| else { |
| propogated = !propStyle.equals(fStyleTable[newEnd]); |
| fStyleTable[newEnd] = propStyle; |
| } |
| |
| if (propogated) { |
| int pStart = (newEnd==-1)? 0 : fRunArray.fRunStart[newEnd] + 1; |
| damagedRange[0] = Math.min(damagedRange[0], pStart); |
| } |
| |
| fRunArray.fPosEnd = newEnd; |
| } |
| |
| fRunArray.addToCurTextLength(-length); |
| |
| fRunArray.runStartsChanged(); |
| } |
| |
| /** |
| * Returns the start of the paragraph containing offset <tt>pos</tt>. |
| */ |
| public int paragraphStart(int pos) { |
| |
| int run = fRunArray.findRunContaining(pos-1); |
| if (run == -1) { |
| return 0; |
| } |
| else { |
| return fRunArray.getLogicalRunStart(run) + 1; |
| } |
| } |
| |
| /** |
| * Returns the limit of the paragraph containing offset <tt>pos</tt>. |
| */ |
| public int paragraphLimit(int pos) { |
| |
| int run = fRunArray.findRunContaining(pos-1); |
| |
| if (run == fRunArray.fPosEnd) |
| run = fRunArray.fNegStart; |
| else |
| run++; |
| |
| if (run == fRunArray.getArrayLength()) { |
| return fRunArray.getCurTextLength(); |
| } |
| |
| int start = fRunArray.getLogicalRunStart(run); |
| |
| return start+1; |
| } |
| |
| /** |
| * Returns the style of the paragraph containing offset <tt>pos</tt>. |
| */ |
| public AttributeMap paragraphStyleAt(int offset) { |
| |
| int run = fRunArray.findRunContaining(offset-1); |
| if (run < 0) |
| return fFirstStyle; |
| else |
| return fStyleTable[run]; |
| } |
| |
| /** |
| * Create paragraph iterator. |
| */ |
| /* |
| public MParagraphIterator createParagraphIterator(int start, int limit) { |
| |
| return new ParagraphIterator(start, limit); |
| } |
| */ |
| |
| /** |
| * Called by iterator to get run info. |
| */ |
| private void setIterator(int pos, ParagraphIterator iter) { |
| |
| if ((pos < 0) || (pos >= fRunArray.getCurTextLength())) { |
| iter.set(0, 0, kNoRun, null); |
| return; |
| } |
| |
| int run; |
| |
| if (pos > 0) |
| run = fRunArray.findRunContaining(pos-1); |
| else |
| run = -1; |
| |
| setIteratorUsingRun(run, iter); |
| } |
| |
| /** |
| * Called by iterator to get run info. |
| */ |
| private void setIteratorUsingRun(int run, ParagraphIterator iter) { |
| |
| int lastValidRun = fRunArray.lastRun(); |
| |
| if (run < -1 || run > lastValidRun) { |
| iter.set(0, 0, kNoRun, null); |
| return; |
| } |
| |
| if (run == fRunArray.fPosEnd+1) |
| run = fRunArray.fNegStart; |
| else if (run == fRunArray.fNegStart-1) |
| run = fRunArray.fPosEnd; |
| |
| int runStart; |
| AttributeMap style; |
| |
| if (run < 0) { |
| runStart = 0; |
| style = fFirstStyle; |
| } |
| else { |
| runStart = fRunArray.fRunStart[run]; |
| style = fStyleTable[run]; |
| if (runStart < 0) |
| runStart += fRunArray.getCurTextLength(); |
| runStart++; |
| } |
| |
| int nextRun; |
| |
| if (run == fRunArray.fPosEnd) |
| nextRun = fRunArray.fNegStart; |
| else |
| nextRun = run + 1; |
| |
| int runLimit; |
| |
| if (nextRun >= fRunArray.getArrayLength()) |
| runLimit = fRunArray.getCurTextLength(); |
| else { |
| runLimit = fRunArray.fRunStart[nextRun]; |
| if (runLimit < 0) |
| runLimit += fRunArray.getCurTextLength(); |
| runLimit++; |
| } |
| |
| iter.set(runStart, runLimit, run, style); |
| } |
| |
| /** |
| * Replace paragraph breaks/styles between start and length with paragraph breaks/styles |
| * from <tt>srcText</tt>. |
| * @param start an offset into the text |
| * @param limit the index after the last character to replace |
| * @param srcText the text from which new paragraphs are taken |
| * @param srcStart the start of the range in <code>srcText</code> to copy |
| * @param srcLimit the first index after the range in <code>srcText</code> to copy |
| */ |
| public void replace(int start, |
| int limit, |
| MConstText srcText, |
| int srcStart, |
| int srcLimit, |
| int[] damagedRange) { |
| |
| final int insLength = srcLimit - srcStart; |
| if (insLength < 0) { |
| throw new Error("invalid range"); |
| } |
| final int origLength = fRunArray.getCurTextLength(); |
| deleteText(start, limit, damagedRange); |
| |
| if (insLength == 0) |
| return; |
| |
| final int oldPosEnd = fRunArray.fPosEnd; |
| AttributeMap origStyle; |
| if (limit < origLength) { |
| origStyle = (fRunArray.fPosEnd>=0)? fStyleTable[fRunArray.fPosEnd] : fFirstStyle; |
| } |
| else { |
| origStyle = srcText.paragraphStyleAt(srcLimit); |
| } |
| |
| int paragraphStart = srcStart; |
| int lastPLimit = srcText.paragraphStart(srcLimit); |
| boolean separatorAtEnd = lastPLimit > srcStart && isParagraphBreak(srcText.at(lastPLimit-1)); |
| |
| if (limit == origLength && lastPLimit == paragraphStart) { |
| if (fRunArray.fPosEnd > 0) { |
| fStyleTable[fRunArray.fPosEnd] = origStyle; |
| } |
| else { |
| fFirstStyle = origStyle; |
| } |
| } |
| else { |
| boolean firstPass = true; |
| while (paragraphStart < lastPLimit) { |
| |
| AttributeMap style = srcText.paragraphStyleAt(paragraphStart); |
| int paragraphLimit = srcText.paragraphLimit(paragraphStart); |
| |
| if (fRunArray.fPosEnd+1 >= fRunArray.fNegStart) |
| expandStyleTable(); |
| |
| if (fRunArray.fPosEnd >= 0) { |
| if (!style.equals(fStyleTable[fRunArray.fPosEnd])) { |
| fStyleTable[fRunArray.fPosEnd] = style; |
| if (firstPass) { |
| int pStart = fRunArray.fRunStart[fRunArray.fPosEnd]+1; |
| damagedRange[0] = Math.min(damagedRange[0], pStart); |
| } |
| } |
| } |
| else if (!style.equals(fFirstStyle)) { |
| fFirstStyle = style; |
| damagedRange[0] = 0; |
| } |
| |
| firstPass = false; |
| |
| if (paragraphLimit < lastPLimit || separatorAtEnd) { |
| fRunArray.fRunStart[++fRunArray.fPosEnd] = paragraphLimit - 1 + start - srcStart; |
| } |
| paragraphStart = paragraphLimit; |
| } |
| if (fRunArray.fPosEnd != oldPosEnd) { |
| fStyleTable[fRunArray.fPosEnd] = origStyle; |
| } |
| } |
| |
| fRunArray.addToCurTextLength(insLength); |
| } |
| |
| /** |
| * Modify the style of all paragraphs containing offsets in the range [start, limit) to |
| * <tt>style</tt>. |
| */ |
| public boolean modifyParagraphStyles(int start, |
| int limit, |
| StyleModifier modifier, |
| int[] damagedRange) { |
| |
| int run = fRunArray.findRunContaining(start-1); |
| int currentPStart; |
| if (run == -1) { |
| currentPStart = 0; |
| } |
| else { |
| currentPStart = fRunArray.getLogicalRunStart(run) + 1; |
| } |
| |
| boolean modifiedAnywhere = false; |
| |
| for (;;) { |
| |
| boolean modified = false; |
| |
| if (run < 0) { |
| |
| AttributeMap newStyle = modifier.modifyStyle(fFirstStyle); |
| |
| if (!newStyle.equals(fFirstStyle)) { |
| fFirstStyle = newStyle; |
| modified = true; |
| } |
| } |
| else { |
| |
| AttributeMap newStyle = modifier.modifyStyle(fStyleTable[run]); |
| |
| if (!fStyleTable[run].equals(newStyle)) { |
| fStyleTable[run] = newStyle; |
| modified = true; |
| } |
| } |
| |
| if (run == fRunArray.fPosEnd) { |
| run = fRunArray.fNegStart; |
| } |
| else { |
| run++; |
| } |
| |
| int nextPStart; |
| if (run == fRunArray.getArrayLength()) { |
| nextPStart = fRunArray.getCurTextLength(); |
| } |
| else { |
| nextPStart = fRunArray.getLogicalRunStart(run) + 1; |
| } |
| |
| if (modified) { |
| modifiedAnywhere = true; |
| damagedRange[0] = Math.min(damagedRange[0], currentPStart); |
| damagedRange[1] = Math.max(damagedRange[1], nextPStart); |
| } |
| |
| if (limit <= nextPStart) { |
| break; |
| } |
| else { |
| currentPStart = nextPStart; |
| } |
| } |
| |
| return modifiedAnywhere; |
| } |
| |
| private static void dumpParagraphStarts(ParagraphBuffer st) { |
| |
| System.out.println("fRunArray.fPosEnd="+st.fRunArray.fPosEnd+", fRunArray.fNegStart="+st.fRunArray.fNegStart+ |
| ", fRunArray.getArrayLength()="+st.fRunArray.getArrayLength()+", fRunArray.getCurTextLength()="+st.fRunArray.getCurTextLength()); |
| |
| int i; |
| System.out.print("Positives: "); |
| for (i=0; i<=st.fRunArray.fPosEnd; i++) |
| System.out.print(st.fRunArray.fRunStart[i]+" "); |
| |
| System.out.print(" Negatives: "); |
| for (i=st.fRunArray.fNegStart; i<st.fRunArray.getArrayLength(); i++) |
| System.out.print(st.fRunArray.fRunStart[i]+" "); |
| |
| System.out.println(" "); |
| } |
| |
| private static final int kNoRun = -42; // iterator use |
| |
| private final class ParagraphIterator /*implements MParagraphIterator*/ |
| { |
| ParagraphIterator(int start, int limit) |
| { |
| reset(start, limit, start); |
| } |
| |
| public void reset(int start, int limit, int pos) |
| { |
| fStart = start; |
| fLimit = limit; |
| setIterator(fStart, this); |
| } |
| |
| public boolean isValid() |
| { |
| return fCurrentRun != kNoRun; |
| } |
| |
| public void next() |
| { |
| if (fRunLimit < fLimit) { |
| fCurrentRun++; |
| setIteratorUsingRun(fCurrentRun, this); |
| } |
| else |
| set(0, 0, kNoRun, null); |
| } |
| |
| public void prev() |
| { |
| if (fRunStart > fStart) { |
| fCurrentRun--; |
| setIteratorUsingRun(fCurrentRun, this); |
| } |
| else |
| set(0, 0, kNoRun, null); |
| } |
| |
| public void set(int pos) |
| { |
| if (pos >= fStart && pos < fLimit) { |
| setIterator(pos, this); |
| } else { |
| set(0, 0, kNoRun, null); |
| } |
| } |
| |
| // ParagraphBuffer calls back on this to set iterators |
| void set(int start, int limit, int currentRun, AttributeMap style) |
| { |
| fRunStart = start < fStart ? fStart : start; |
| fRunLimit = limit > fLimit ? fLimit : limit; |
| fCurrentRun = currentRun; |
| fStyle = style; |
| } |
| |
| public void reset(int start, int limit) |
| { |
| reset(start, limit, start); |
| } |
| |
| public void first() |
| { |
| set(fStart); |
| } |
| |
| public void last() |
| { |
| set(fLimit - 1); |
| } |
| |
| public int rangeStart() |
| { |
| return fStart; |
| } |
| |
| public int rangeLimit() |
| { |
| return fLimit; |
| } |
| |
| public int rangeLength() |
| { |
| return fLimit - fStart; |
| } |
| |
| public int runStart() |
| { |
| return fRunStart; |
| } |
| |
| public int runLimit() |
| { |
| return fRunLimit; |
| } |
| |
| public int runLength() |
| { |
| return fRunLimit - fRunStart; |
| } |
| |
| public AttributeMap style() { |
| |
| return fStyle; |
| } |
| |
| private int fStart; |
| private int fLimit; |
| private int fRunStart; |
| private int fRunLimit; |
| private int fCurrentRun; |
| private AttributeMap fStyle; |
| } |
| } |