diff --git a/icu4j/src/com/ibm/richtext/styledtext/CharBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/CharBuffer.java new file mode 100755 index 00000000000..7108f7a9901 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/CharBuffer.java @@ -0,0 +1,379 @@ +/* + * @(#)$RCSfile: CharBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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. + */ +/** An implementation of MCharBuffer that stores chars in an array with an insertion gap. */ +/* + Change history + 072396 jf - fixed a bug in replace(int, int, char[], int, int) so that it correctly + inserted into the middle of the buffer. + 080296 jf - added timestamp. This is strictly a debugging device to help catch + stale iterators. + + 082296 jbr added check for 0-length iterator in replace +*/ + +package com.ibm.richtext.styledtext; + +import java.io.Externalizable; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +import java.text.CharacterIterator; + +final class CharBuffer + extends MCharBuffer implements Externalizable +{ + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private static final int kGrowSize = 0x80; // small size for testing + private static final int CURRENT_VERSION = 1; // version code for streaming + private static final long serialVersionUID = 563174; + + transient Validation fValidation = null; + private char[] fArray; + transient private int fArraySize; + transient private int fGap; + + /** Create an empty char buffer. */ + public CharBuffer() + { + } + + /** Create a char buffer that can hold at least capacity chars. */ + + public CharBuffer(int capacity) + { + fArray = allocate(capacity); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + if (in.readInt() != CURRENT_VERSION) { + throw new IOException("Invalid version of CharBuffer"); + } + + fArray = (char[]) in.readObject(); + if (fArray != null) { + fArraySize = fArray.length; + fGap = fArraySize; + } + else { + fArraySize = 0; + fGap = 0; + } + } + + public void writeExternal(ObjectOutput out) throws IOException { + + compress(); + out.writeInt(CURRENT_VERSION); + out.writeObject(fArray); + } + + private void invalidate() { + + if (fValidation != null) { + fValidation.invalidate(); + fValidation = null; + } + } + + // not ThreadSafe - could end up with two Validations + // being generated + private Validation getValidation() { + + if (fValidation == null) { + fValidation = new Validation(); + } + return fValidation; + } + + /** Replace the chars from start to limit with the chars from srcStart to srcLimit in srcBuffer. */ + + /** Replace the chars from start to limit with the chars from srcStart to srcLimit in srcChars. + * This is the core routine for manipulating the buffer. + */ + public void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit) + { + invalidate(); + int dstLength = limit - start; + int srcLength = srcLimit - srcStart; + + if (dstLength < 0 || srcLength < 0) { + throw new IllegalArgumentException("replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit)"); + } + + int gapAlloc = 0; + if (srcChars == null) { + gapAlloc = srcLength; + srcLength = 0; + } + + int newSize = fArraySize - dstLength + srcLength; + + if (fArray == null) { + if (start != 0 || limit != 0) { + throw new IllegalArgumentException("replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit)"); + } + if (newSize + gapAlloc > 0) { + fArray = allocate(newSize + gapAlloc); + if (srcLength > 0) { + System.arraycopy(srcChars, srcStart, fArray, 0, srcLength); + fArraySize = srcLength; + fGap = srcLength; + } + } + } else { + int newGap = start + srcLength; + int gapLimit = fArray.length - fArraySize + fGap; + + if (newSize + gapAlloc > fArray.length) { + char[] temp = allocate(newSize + gapAlloc); + + //move stuff at beginning that we aren't writing over + if (start > 0) { + at(0, start, temp, 0); + } + //move stuff from src array that we are copying + if (srcLength > 0) { + System.arraycopy(srcChars, srcStart, temp, start, srcLength); + } + //move stuff at end that we aren't copying over + if (limit < fArraySize) { + at(limit, fArraySize, temp, temp.length - newSize + newGap); + //change 7-23-96 + // at(limit, fArraySize - limit, temp, temp.length - newSize + newGap); + } + + fArray = temp; + } else { + if (start > fGap) { + System.arraycopy(fArray, gapLimit, fArray, fGap, start - fGap); + } + if (limit < fGap) { + System.arraycopy(fArray, limit, fArray, fArray.length - newSize + newGap, fGap - limit); + } + if (srcLength > 0) { + System.arraycopy(srcChars, srcStart, fArray, start, srcLength); + } + } + + fArraySize = newSize; + fGap = newGap; + } + } + + /** Replace the chars from start to limit with the chars from srcStart to srcLimit in srcString. */ + + /* This implements optimizations for null text or inserting text that fits at the gap, + and defaults to call the core replace routine if these optimizations fail. */ + + public void replace(int start, int limit, String srcString, int srcStart, int srcLimit) + { + invalidate(); + int length = limit - start; + int srcLength = srcLimit - srcStart; + + if (fArray == null) { + if (start != 0 || limit != 0) { + throw new IllegalArgumentException("replace(int start, int limit, String srcString, int srcStart, int srcLimit)"); + } + if (srcLength > 0) { + fArray = allocate(srcLength); + srcString.getChars(srcStart, srcLimit, fArray, 0); + fArraySize = srcLength; + fGap = srcLength; + } + } else { + if (start == fGap && fArray.length >= fArraySize - length + srcLength) { + if (srcLimit > 0) { + srcString.getChars(srcStart, srcLimit, fArray, fGap); + fGap += srcLength; + } + fArraySize += srcLength - length; + } else { + replace(start, limit, srcString != null ? srcString.toCharArray() : null, srcStart, srcLimit); + } + } + } + + public void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit) + { + invalidate(); + int length = limit - start; + int srcLength = srcLimit - srcStart; + + if (fArray == null) { + if (start != 0 || limit != 0) { + throw new IllegalArgumentException("replace(int start, int limit, String srcString, int srcStart, int srcLimit)"); + } + if (srcLength > 0) { + fArray = allocate(srcLength); + srcText.extractChars(srcStart, srcLimit, fArray, 0); + fArraySize = srcLength; + fGap = srcLength; + } + } else { + if (start == fGap && fArray.length >= fArraySize - length + srcLength) { + if (srcLimit > 0) { + srcText.extractChars(srcStart, srcLimit, fArray, fGap); + fGap += srcLength; + } + fArraySize += srcLength - length; + } else { + char[] temp = srcLength == 0? null : new char[srcLength]; + if (temp != null) { + srcText.extractChars(srcStart, srcLimit, temp, 0); + } + replace(start, limit, temp, 0, srcLimit - srcStart); + } + } + } + + /** Replace the chars from start to limit with srcChar. */ + + /* This implements optimizations for null text or replacing a character that fits into the gap, + and defaults to call the core replace routine if these optimizations fail. */ + + public void replace(int start, int limit, char srcChar) + { + invalidate(); + if (fArray == null) { + if (start != 0 || limit != 0) { + throw new IllegalArgumentException("replace(int start, int limit, char srcChar)"); + } + fArray = allocate(1); + fArray[0] = srcChar; + fArraySize = 1; + fGap = 1; + } else { + int length = limit - start; + if (start == fGap && fArray.length > fArraySize - length) { + fArray[fGap] = srcChar; + fGap += 1; + fArraySize += 1 - length; + } else { + replace(start, limit, new char[] { srcChar} , 0, 1); + } + } + } + + /** Return the char at pos. */ + + public char at(int pos) + { + if (pos < 0 || pos >= fArraySize) { + throw new IllegalArgumentException(); + } + return pos < fGap ? fArray[pos] : fArray[fArray.length - fArraySize + pos]; + } + + /** Copy the chars from start to limit to dst starting at dstStart. */ + + public void at(int start, int limit, char[] dst, int dstStart) + { + int length = limit - start; + + if (start < 0 || limit < start || limit > fArraySize) { + throw new IllegalArgumentException(); + } + + if (limit <= fGap) { + System.arraycopy(fArray, start, dst, dstStart, length); + } else if (start >= fGap) { + System.arraycopy(fArray, fArray.length - fArraySize + start, dst, dstStart, length); + } else { + System.arraycopy(fArray, start, dst, dstStart, fGap - start); + System.arraycopy(fArray, fArray.length - fArraySize + fGap, dst, dstStart + fGap - start, limit - fGap); + } + } + + /** Return the number of chars in the buffer. */ + + public final int length() + { + return fArraySize; + } + + /** Return the number of chars the buffer can hold before it must reallocate. */ + + public final int capacity() + { + return fArray != null ? fArray.length : 0; + } + + /** Reserve capacity chars at start. Utility to optimize a sequence of operations at start. */ + + public void reserveCapacity(int start, int capacity) + { + replace(start, start, (char[])null, 0, capacity); + } + + /** Minimize the storage used by the buffer. */ + + public void compress() + { + invalidate(); + if (fArraySize == 0) { + fArray = null; + fGap = 0; + } else if (fArraySize != fArray.length) { + char[] temp = new char[fArraySize]; + at(0, fArraySize, temp, 0); + fArray = temp; + fGap = fArraySize; + } + } + + /** Display the buffer. */ + + public String toString() + { + if (fArray != null) { + return new StringBuffer() + .append("limit: ").append(fArray.length) + .append(", size: ").append(fArraySize) + .append(", gap: ").append(fGap) + .append(", ").append(fArray, 0, fGap) + .append(fArray, fArray.length - fArraySize + fGap, fArraySize - fGap) + .toString(); + } else { + return new String("The buffer is empty."); + } + } + + public CharacterIterator createCharacterIterator(int start, int limit) { + + Validation val = getValidation(); + return new CharBufferIterator(start, limit, fArray, fArraySize, fGap, val); + } + + /** The resizing algorithm. Return a value >= minSize. */ + + protected int allocation(int minSize) + { + // return (minSize + kGrowSize) & ~(kGrowSize - 1); + return minSize < kGrowSize ? kGrowSize : (minSize * 2 + kGrowSize) & ~(kGrowSize - 1); + } + + /** Allocate a new character array of limit >= minSize. */ + + protected char[] allocate(int minSize) + { + return new char[allocation(minSize)]; + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/CharBufferIterator.java b/icu4j/src/com/ibm/richtext/styledtext/CharBufferIterator.java new file mode 100755 index 00000000000..6a0ac16f793 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/CharBufferIterator.java @@ -0,0 +1,131 @@ +/* + * @(#)$RCSfile: CharBufferIterator.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.text.CharacterIterator; + +final class CharBufferIterator implements CharacterIterator, + Cloneable +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private int fRangeStart; + private int fRangeLimit; + private int fCurrentIndex; + private char fStorage[]; + private int fGap; + private int fGapLength; + private Validation fValidation; + + CharBufferIterator(int start, + int limit, + char[] storage, + int length, + int gap, + Validation validation) { + + if (start > limit) { + throw new IllegalArgumentException("start > limit"); + } + fRangeStart = start; + fRangeLimit = limit; + fCurrentIndex = fRangeStart; + fStorage = storage; + fGap = gap; + fGapLength = (storage==null? 0 : storage.length) - length; + fValidation = validation; + } + + private void checkValidation() { + + if (!fValidation.isValid()) { + throw new Error("Iterator is no longer valid"); + } + } + + public char first() + { + return setIndex(fRangeStart); + } + + public char last() + { + return setIndex(fRangeLimit - 1); + } + + public char current() + { + checkValidation(); + if (fCurrentIndex < fRangeStart || fCurrentIndex >= fRangeLimit) + return DONE; + int i = (fCurrentIndex < fGap) ? fCurrentIndex : (fCurrentIndex + fGapLength); + return fStorage[i]; + } + + public char next() + { + checkValidation(); + fCurrentIndex++; + if (fCurrentIndex >= fRangeLimit) + { + fCurrentIndex = fRangeLimit; + return DONE; + } + int i = (fCurrentIndex < fGap) ? fCurrentIndex : (fCurrentIndex + fGapLength); + return fStorage[i]; + } + + public char previous() + { + fCurrentIndex--; + if (fCurrentIndex >= fRangeStart) + return current(); + fCurrentIndex = fRangeStart; + return DONE; + } + + public char setIndex(int i) + { + if (i < fRangeStart || i > fRangeLimit) + throw new IllegalArgumentException("Invalid position"); + fCurrentIndex = i; + return current(); + } + + public int getBeginIndex() + { + return fRangeStart; + } + + public int getEndIndex() + { + return fRangeLimit; + } + + public int getIndex() + { + return fCurrentIndex; + } + + public Object clone() + { + try { + return super.clone(); + } + catch (CloneNotSupportedException e) { + return null; + } + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/FastIntBinarySearch.java b/icu4j/src/com/ibm/richtext/styledtext/FastIntBinarySearch.java new file mode 100755 index 00000000000..e297a7b71e7 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/FastIntBinarySearch.java @@ -0,0 +1,136 @@ +/* + * @(#)$RCSfile: FastIntBinarySearch.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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. + */ +/* +(C) Copyright Taligent, Inc. 1996 - All Rights Reserved +(C) Copyright IBM Corp. 1996 - All Rights Reserved + + The original version of this source code and documentation is copyrighted and +owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These materials are +provided under terms of a License Agreement between Taligent and Sun. This +technology is protected by multiple US and International patents. This notice and +attribution to Taligent may not be removed. + Taligent is a registered trademark of Taligent, Inc. +*/ + +/* + 7/29/96 + Modified to search portions of an integer array. Should be retested. +*/ + +package com.ibm.richtext.styledtext; + +/** + * This class searches a segment of an array of integers. The segment + * must be sorted in ascending order (but this class does not verify this). + * Also, this class aliases the array; if the array is modified later the + * search results are undefined. + */ +final class FastIntBinarySearch +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private int dataArray[]; + private int auxStart; + private int power; + + private int fFirstIndex; + + private static final int exp2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072 }; + + public FastIntBinarySearch(int data[]) + { + this(data, 0, data.length); + } + + public FastIntBinarySearch(int data[], int firstValidIndex, int validLength) + { + setData(data, firstValidIndex, validLength); + } + + public void setData(int data[]) { + + setData(data, 0, data.length); + } + + public void setData(int data[], int firstValidIndex, int validLength) { + + if (data.length < 1) throw new IllegalArgumentException(); + if (data.length >= exp2[exp2.length-1]) throw new IllegalArgumentException(); + + dataArray = data; + fFirstIndex = firstValidIndex; + + for (power = exp2.length-1; power > 0 && validLength < exp2[power]; power--) {} + + // at this point, array.length >= 2^power + + auxStart = validLength - exp2[power]; + } + + /** + * Return the index in the array of the first element which is at least + * as large as value. If value is larger than the largest + * element in the array the last valid index in the array is returned. + */ + public int findIndex(int value) + { + int index = exp2[power]-1 + fFirstIndex; + if (value >= dataArray[auxStart + fFirstIndex]) { + index += auxStart; + } + + // at this point, index is the "upper limit" of the search + + switch (power) { + case 17: + if (value < dataArray[index-65536]) index -= 65536; + case 16: + if (value < dataArray[index-32768]) index -= 32768; + case 15: + if (value < dataArray[index-16384]) index -= 16384; + case 14: + if (value < dataArray[index-8192]) index -= 8192; + case 13: + if (value < dataArray[index-4096]) index -= 4096; + case 12: + if (value < dataArray[index-2048]) index -= 2048; + case 11: + if (value < dataArray[index-1024]) index -= 1024; + case 10: + if (value < dataArray[index-512]) index -= 512; + case 9: + if (value < dataArray[index-256]) index -= 256; + case 8: + if (value < dataArray[index-128]) index -= 128; + case 7: + if (value < dataArray[index-64]) index -= 64; + case 6: + if (value < dataArray[index-32]) index -= 32; + case 5: + if (value < dataArray[index-16]) index -= 16; + case 4: + if (value < dataArray[index-8]) index -= 8; + case 3: + if (value < dataArray[index-4]) index -= 4; + case 2: + if (value < dataArray[index-2]) index -= 2; + case 1: + if (value < dataArray[index-1]) index -= 1; + case 0: + if (value < dataArray[index]) index -= 1; + } + return index; + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/MCharBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/MCharBuffer.java new file mode 100755 index 00000000000..0eebff17c85 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MCharBuffer.java @@ -0,0 +1,38 @@ +/* + * @(#)$RCSfile: MCharBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.text.CharacterIterator; + +/** A dynamic character array optimized for sequences of insert + or delete operations in a local region. */ + +abstract class MCharBuffer +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + abstract void replace(int start, int limit, MConstText text, int srcStart, int srcLimit); + abstract void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit); + abstract void replace(int start, int limit, String srcString, int srcStart, int srcLimit); + abstract void replace(int start, int limit, char srcChar); + abstract CharacterIterator createCharacterIterator(int start, int limit); + abstract char at(int pos); + abstract void at(int start, int limit, char[] dst, int dstStart); + + abstract int length(); + + abstract int capacity(); + abstract void reserveCapacity(int pos, int length); + abstract void compress(); +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/MConstText.java b/icu4j/src/com/ibm/richtext/styledtext/MConstText.java new file mode 100755 index 00000000000..ea6e41956b2 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MConstText.java @@ -0,0 +1,325 @@ +/* + * @(#)$RCSfile: MConstText.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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 com.ibm.textlayout.attributes.AttributeMap; +import java.text.CharacterIterator; +import java.awt.datatransfer.DataFlavor; + +/** + * MConstText is a base class for text with multiple character and + * paragraph styles. The text is a sequence of Unicode characters, + * represented by char. Character and paragraph + * styles are represented by the AttributeMap class. + *

+ * Characters in the text are accessed with an integer index using the + * at method. + * Valid indices are between 0 and (length-1), where length is the number + * of characters in the text. Additionally, the + * characters in the text may be accessed through a + * java.text.CharacterIterator. + *

+ * Every character in the text has a character style associated with it, + * represented by the AttributeMap class. The character + * style for a particular character can be obtained using the + * characterStyleAt method. + *

+ * Each character in the text is contained in a paragraph. A paragraph + * is a range of text including and terminated by a + * paragraph separator (either \n or U+2029). + * Every + * paragraph has a paragraph style associated with it, represented + * by the AttributeMap class. Paragraph boundaries and + * styles can be obtained from the MConstText. + *

+ * This class does not have methods for modifying the text or styles. + * However, subclasses may add this capability, so it is not safe to + * assume that an MConstText instance is immutable. In particular, + * the MText class adds modification protocol to this class. Clients + * can detect whether an MConstText has changed by keeping track of its + * timestamp. + *

+ * A DataFlavor for clipboard content is defined in this class. Using + * this DataFlavor insures that all clients will recognize MConstText + * content on the clipboard. + * @see MText + * @see AttributeMap + * @see java.text.CharacterIterator + * @see java.awt.datatransfer.DataFlavor + */ +public abstract class MConstText { + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + /** + * The DataFlavor for MConstText clipboard content. Used to + * indicate that clipboard data has an MConstText representation. + */ + public static final DataFlavor styledTextFlavor = + new DataFlavor(MConstText.class, "Styled Text"); + + protected MConstText() { + } + +//======================================================== +// CHARACTER ACCESS +//======================================================== +/** +* Return the character at offset pos. +* @param pos a valid offset into the text +* @returns the character at offset pos +*/ + public abstract char at(int pos); + +/** +* Copy the characters in the range [start, limit) +* into the array dst, beginning at dstStart. +* @param start offset of first character which will be copied into the array +* @param limit offset immediately after the last character which will be copied into the array +* @param dst array in which to copy characters. The length of dst must be at least +* (dstStart + limit - start). +*/ + public abstract void extractChars(int start, int limit, char[] dst, int dstStart); + +/** +* Create an MConstText containing the characters and styles in the range +* [start, limit). +* @param start offset of first character in the new text +* @param limit offset immediately after the last character in the new text +* @return an MConstText object containing the characters and styles in the given range +*/ + public abstract MConstText extract(int start, int limit); + +/** +* Create a java.text.CharacterIterator over all +* of the characters in the text. Default implementation calls +* createCharacterIterator(0, length()) +* @return a java.text.CharacterIterator over all +* of the characters in the text +*/ + public CharacterIterator createCharacterIterator() { + + return createCharacterIterator(0, length()); + } + +/** +* Create a java.text.CharacterIterator over the +* given range of characters in the text. +* @param start the first index in the iteration range +* @param limit the index after the last character in the iteration range +* @return a java.text.CharacterIterator over the +* given range +*/ + public abstract CharacterIterator createCharacterIterator(int start, + int limit); + + +//======================================================== +// SIZE/CAPACITY +//======================================================== +/** +* Return the length of the MConstText object. The length is the number of characters in the text. +* @return the length of the MConstText object +*/ + public abstract int length(); + +//======================================================== +// Character styles +//======================================================== + +/** +* Return the index of the first character in the character style run +* containing pos. All characters in a style run have the same character +* style. +* @returns the style at offset pos +*/ + public abstract int characterStyleStart(int pos); + +/** +* Return the index after the last character in the character style run +* containing pos. All characters in a style run have the same character +* style. +* @returns the style at offset pos +*/ + public abstract int characterStyleLimit(int pos); + +/** +* Return the style applied to the character at offset pos. +* @param pos a valid offset into the text +* @returns the style at offset pos +*/ + public abstract AttributeMap characterStyleAt(int pos); + +//======================================================== +// PARAGRAPH BOUNDARIES +//======================================================== +/** +* Return the start of the paragraph containing the character at offset pos. +* @param pos a valid offset into the text +* @returns the start of the paragraph containing the character at offset pos +*/ + public abstract int paragraphStart(int pos); + +/** +* Return the limit of the paragraph containing the character at offset pos. +* @param pos a valid offset into the text +* @returns the limit of the paragraph containing the character at offset pos +*/ + public abstract int paragraphLimit(int pos); + +/** +* Return the paragraph style applied to the paragraph containing offset pos. +* @param pos a valid offset into the text +* @returns the paragraph style in effect at pos +*/ + public abstract AttributeMap paragraphStyleAt(int pos); + +/** +* Return the current time stamp. The time stamp is +* incremented whenever the contents of the MConstText changes. +* @return the current paragraph style time stamp +*/ + public abstract int getTimeStamp(); + +/** +* Return the start of the damaged range. If the start is not less +* than the the limit of the damaged range, then the damaged range +* is empty. +* @return the start of the damaged range +* @see #damagedRangeLimit +* @see MText#resetDamagedRange +*/ + public abstract int damagedRangeStart(); + +/** +* Return the limit of the damaged range. If the start is not less +* than the the limit of the damaged range, then the damaged range +* is empty. +* @return the start of the damaged range +* @see #damagedRangeStart +* @see MText#resetDamagedRange +*/ + public abstract int damagedRangeLimit(); + +//======================================================== +// Equality and hashCode +//======================================================== +/** +* Compare this to another Object for equality. This is +* equal to rhs if rhs is an MConstText which is equal +* to this. +* @param rhs Object to compare to +* @return true if this equals rhs +*/ + public final boolean equals(Object rhs) { + + MConstText otherText; + + try { + otherText = (MConstText) rhs; + } + catch(ClassCastException e) { + return false; + } + + return equals(otherText); + } + +/** +* Compare this to another MConstText for equality. This is +* equal to rhs if the characters and styles in rhs are the +* same as this. Subclasses may override this implementation +* for efficiency, but they should preserve these semantics. +* Determining that two MConstText instances are equal may be +* an expensive operation, since every character and style must +* be compared. +* @param rhs Object to compare to +* @return true if this equals rhs +*/ + public boolean equals(MConstText rhs) { + + if (rhs == null) { + return false; + } + + if (rhs == this) { + return true; + } + + if (hashCode() != rhs.hashCode()) { + return false; + } + + int length = length(); + if (length != rhs.length()) { + return false; + } + + for (int i=0; i < length; i++) { + if (i < length && at(i) != rhs.at(i)) { + return false; + } + } + + for (int start = 0; start < length;) { + if (!characterStyleAt(start).equals(rhs.characterStyleAt(start))) { + return false; + } + int limit = characterStyleLimit(start); + if (limit != rhs.characterStyleLimit(start)) { + return false; + } + start = limit; + } + + for (int start = 0; start < length;) { + + if (!paragraphStyleAt(start).equals(rhs.paragraphStyleAt(start))) { + return false; + } + start = paragraphLimit(start); + } + + return paragraphStyleAt(length).equals(rhs.paragraphStyleAt(length)); + } + + /** + * Return the hashCode for this MConstText. An empty MConstText + * has hashCode 0; a nonempty MConstText's hashCode is + *

+     *       at(0) +
+     *       at(length/2)*31^1 +
+     *       at(length-1)*31^2 +
+     *       characterStyleAt(0).hashCode()*31^3 +
+     *       paragraphStyleAt(length-1).hashCode()*31^4
+     * 
+ * where ^ is exponentiation (not bitwise XOR). + */ + public final int hashCode() { + + int hashCode = 0; + int length = length(); + + if (length > 0) { + hashCode = paragraphStyleAt(length-1).hashCode(); + hashCode = hashCode*31 + characterStyleAt(0).hashCode(); + hashCode = hashCode*31 + at(length-1); + hashCode = hashCode*31 + at(length/2); + hashCode = hashCode*31 + at(0); + } + + return hashCode; + } +} \ No newline at end of file diff --git a/icu4j/src/com/ibm/richtext/styledtext/MParagraphBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/MParagraphBuffer.java new file mode 100755 index 00000000000..6fb4f311b8e --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MParagraphBuffer.java @@ -0,0 +1,94 @@ +/* + * @(#)$RCSfile: MParagraphBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.Serializable; +import com.ibm.textlayout.attributes.AttributeMap; + +abstract class MParagraphBuffer +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + +/** +* Returns the start of the paragraph containing offset pos. +*/ + abstract int paragraphStart(int pos); + +/** +* Returns the limit of the paragraph containing offset pos. +*/ + abstract int paragraphLimit(int pos); + +/** +* Returns the style of the paragraph containing offset pos. +*/ + abstract AttributeMap paragraphStyleAt(int offset); + +/** +* Process a character insertion at offset start. +* If a paragraph break was inserted, propogate paragraph style at +* start to new paragraph. +*/ + abstract void insertText(int start, char insertedChar); + +/** +* Process character insertion at offset start. +* Each new paragraph gets paragraph style at +* start. +*/ + abstract void insertText(int start, + char[] srcChars, + int srcStart, + int srcLimit); + +/** +* Process deletion by removing paragraph breaks contained in +* deleted range. Propogate paragraph styles backward, if necessary. +*/ + abstract void deleteText(int start, + int limit, + int[] damagedRange); + +/* +* Replace paragraph breaks/styles between start and limit with paragraph breaks/styles +* from srcText. +* @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 srcText to copy +* @param srcLimit the first index after the range in srcText to copy +*/ + abstract void replace(int start, + int limit, + MConstText srcText, + int srcStart, + int srcLimit, + int[] damagedRange); + +/** +* Set the style of all paragraphs containing offsets in the range [start, limit) to +* style. +*/ + abstract boolean modifyParagraphStyles(int start, + int limit, + StyleModifier modifier, + int[] damagedRange); + +/** +* Minimize the amount of memory used by this object. +*/ + abstract void compress(); +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/MStyleBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/MStyleBuffer.java new file mode 100755 index 00000000000..133fa30a83c --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MStyleBuffer.java @@ -0,0 +1,112 @@ +/* + * @(#)$RCSfile: MStyleBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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 com.ibm.textlayout.attributes.AttributeMap; + +/* + 8/1/96 + Style -> ResolvedStyle + 8/7/96 jf + added countStyles and getStyles protocol + 8/13/96 + ResolvedStyle->Style + 8/22/96 jf + Removed the setIterator methods. +*/ +/* +* MStyleBuffer is the abstract interface for a class which maintains +* style runs in an MText. A "style run" consists of a +* style and the interval on which the style applies. +*

+* MStyleBuffer includes methods to call when text is inserted into +* or deleted from the MText. These methods update the +* style runs in accordance with the commonly accepted behavior for +* style runs. +*

+* Additionally, MStyleBuffer provides methods for replacing the style runs on a +* text range with another set of style runs. MStyleBuffer does not do style "combining" (for +* example, adding the bold attribute to text which is italicized); clients are +* responsible for computing the combined styles, and passing these styles into +* MStyleBuffer. +*

+* MStyleBuffer supplies a method for replacing the style runs on a text range with the runs +* represented in an MStyleRunIterator. This is useful for implementing paste +* operations, in which the style runs on a range of text are replaced by style runs +* from an external source. +*

+* +* @author John Raley +* +* @see AttributeMap +* @see MText +*/ +abstract class MStyleBuffer +{ + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; +/** +* Respond to an insertion in the text. The length of the last style run which +* begins before start is increased by limit-start. +* @param start the offset where the insertion began +* @param limit the offset where the insertion ended +*/ + abstract void insertText(int start, int limit); + +/** +* Respond to a deletion in the text. The last style run before +* start is truncated to end at start. The +* style run containing (start+length) is set to begin +* at (start+length). Runs in between are deleted. +* If the deletion occurs entirely within one style run, the length of the style +* run is reduced by length. +* @param start the offset where the deletion began +* @param length the offset where the deletion ended +*/ + abstract void deleteText(int start, int limit); + +/* +* Replace style runs between offsets start and limit with styles in +* iter. This method can be used to perform a "paste" operation. +* @param start the offset where the replacement begins +* @param limit the offset where the replacement ends +* @param iter an MStyleRunIterator containing style runs which will replace old +* style runs. +*/ + abstract void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit); + + abstract int styleStart(int pos); + abstract int styleLimit(int pos); + +/** +* Return style at location pos. +* @param pos an offset into the text +* @returns the style of the character at offset +*/ + abstract AttributeMap styleAt(int pos); + +/** + * Return true if styles were modified. + */ + abstract boolean modifyStyles(int start, + int limit, + StyleModifier modifier, + int[] damagedRange); + +/** +* Minimize the amount of memory used by this object. +*/ + abstract void compress(); +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/MTabRuler.java b/icu4j/src/com/ibm/richtext/styledtext/MTabRuler.java new file mode 100755 index 00000000000..40fa4e1e053 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MTabRuler.java @@ -0,0 +1,113 @@ +/* + * @(#)$RCSfile: MTabRuler.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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; + +/** + * This interface represents a sequence of TabStops, ordered by position. + * The first + * TabStop in the ruler can be obtained with the firstTab + * method; subsequent TabStops are obtained with the nextTab + * method. + *

+ * If a TabStop with type TabStop.kAuto is returned, all tabs + * after that TabStop will also have type TabStop.kAuto, and + * their positions will be multiples of autoSpacing. + * @see TabStop + */ +public abstract class MTabRuler +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + /** + * Return first tab in the ruler. If an autoTab, it is at position zero, and + * all subsequent tabs will be autotabs at autoSpacing intervals. + */ + public abstract TabStop firstTab(); + + /** + * Return the first tab in the ruler with fPosition > position. If it is an + * autotab, it is at an increment of autoSpacing, and all subsequent tabs will be + * autotabs at autoSpacing intervals. + */ + public abstract TabStop nextTab(int position); + + /** + * Return the interval for autotabs. + */ + public abstract int autoSpacing(); + + /** + * Compute the hashCode for this ruler. The hashCode is the + * hashCode of the first tab multiplied by the autoSpacing + * interval. + */ + public final int hashCode() { + + return firstTab().hashCode() * autoSpacing(); + } + + /** + * Return true if this tab ruler contains the given tab. + * @param tabToTest the tab to search for + * @return true if this tab ruler contains tabToTest + */ + public boolean containsTab(TabStop tabToTest) { + + for (TabStop tab = firstTab(); + tab.getType() != TabStop.kAuto; + tab = nextTab(tab.getPosition())) { + if (tab.getPosition() >= tabToTest.getPosition()) { + return tabToTest.equals(tab); + } + } + + return false; + } + + /** + * Return a tab ruler identical to this ruler, except with the + * given tab added. This ruler is not modified. + * @param tabToAdd the tab to add to the new tab ruler + * @return an MTabRuler resulting from this operation + */ + public MTabRuler addTab(TabStop tabToAdd) { + + return StandardTabRuler.addTabToRuler(this, tabToAdd); + } + + /** + * Return a tab ruler identical to the given ruler, except with the + * tab at the given position removed. This ruler is not modified. + * @param position the position of the tab to remove from the new tab ruler + * @return an MTabRuler resulting from this operation + */ + public MTabRuler removeTab(int position) { + + return StandardTabRuler.removeTabFromRuler(this, position); + } + + /** + * Return a tab ruler identical to this ruler, except with the + * tab at position fromPosition moved to position + * toPosition. This ruler is not modified. + * @param fromPosition the position of the tab to move + * @param toPosition the new position of the tab + * @return an MTabRuler resulting from this operation + */ + public MTabRuler moveTab(int fromPosition, int toPosition) { + + return StandardTabRuler.moveTabOnRuler(this, fromPosition, toPosition); + } +} \ No newline at end of file diff --git a/icu4j/src/com/ibm/richtext/styledtext/MText.java b/icu4j/src/com/ibm/richtext/styledtext/MText.java new file mode 100755 index 00000000000..54be5967559 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/MText.java @@ -0,0 +1,243 @@ +/* + * @(#)$RCSfile: MText.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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 com.ibm.textlayout.attributes.AttributeMap; + +/* + Change history: + + 10/29/96 jef split the character and paragraph style access functions + 8/14/96 sfb eliminated StyleSheetIterator + 8/21/96 jef completed abstract interface (changed iterator classes etc.) + 1/30/97 rtg cleaned up interface, brought in functions from SimpleTextView + 7/31/98 jbr switched from Style to AttributeMap + +*/ + +/** + * This class is a mutable extension of MConstText. It has methods for + * inserting, appending, replacing, and removing styled text. Additionally, + * it has methods for modifying paragraph and character styles. + *

+ * Styled characters (from another MConstText instance) added + * to the text retain their original character styles. The style of plain characters + * (specified as a char or char[]) is always + * specified explicitly when they are added to the text. MText does not do + * character style "propagation", where unstyled characters take on the + * style of previous characters. Clients can implement this behavior by + * specifying the styles to propagate. + *

+ * When unstyled characters are added to the text, their paragraph style + * is the paragraph style in effect immediately after the last new character. + * If the characters contain paragraph separators, then every new paragraph + * will have the same paragraph style. When styled characters are added + * to the text, their resulting paragraph style is determined by the + * following rule: + *

+ * The paragraph styles in the new text + * become the paragraph styles in the target text, with the exception of the + * last paragraph in the new text, which takes on the paragraph style in + * effect immediately after the inserted text. + * If the new text is added at the end of the target text, the new text's + * paragraph styles take effect in any paragraph affected by the addition. + *
+ * For example, suppose there is a single paragraph of text with style 'A', + * delimited with a paragraph separator 'P': + *
+ * AAAAAAP + *
+ * Suppose the following styled paragraphs are inserted into the above text + * after the fourth character: + *
+ * BBBBPCCCPDDD + *
+ * Then the original paragraph style of each character is: + *
+ * AAAABBBBPCCCPDDDAAP + *
+ * The resulting paragraph styles are: + *
+ * BBBBBBBBPCCCPAAAAAP + *
+ * Similarly, if characters are deleted, the paragraph style immediately + * after the deletion takes effect on the paragraph containing the deletion. + * So, if characters 4-16 were deleted in the example above, the paragraph + * styles would be: + *
+ * AAAAAAP + *
+ * This paragraph-style propagation policy is sometimes referred to as + * following styles win, since styles at the end of the paragraph + * become the style for the entire paragraph. + *

+ * This class can accumulate a damaged range - an interval in + * which characters, character styles, or paragraph styles have changed. This is + * useful for clients such as text editors which reformat and draw text after + * changes. Usually the damaged range is exactly the range of characters + * operated upon; however, larger ranges may be damaged if paragraph styles + * change. + * @see StyleModifier + */ + +public abstract class MText extends MConstText +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + protected MText() { + } + +//================================================== +// MAIN CHARACTER MODIFICATION FUNCTIONS +//================================================== +/** +* Replace the characters and styles in the range [start, limit) with the characters +* and styles in srcText in the range [srcStart, srcLimit). srcText is not +* modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character and style at +* limit is not modified. +* @param srcText the source for the new characters and styles +* @param srcStart the offset into srcText where new characters and styles will be obtained +* @param srcLimit the offset into srcText where the new characters and styles end +*/ + public abstract void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit); + +/** +* Replace the characters and styles in the range [start, limit) with the characters +* and styles in srcText. srcText is not +* modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character and style at +* limit is not modified. +* @param srcText the source for the new characters and styles +*/ + public abstract void replace(int start, int limit, MConstText text); + +/** +* Replace the characters in the range [start, limit) with the characters +* in srcChars in the range [srcStart, srcLimit). New characters take on the style +* charsStyle. +* srcChars is not modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character at +* limit is not modified. +* @param srcChars the source for the new characters +* @param srcStart the offset into srcChars where new characters will be obtained +* @param srcLimit the offset into srcChars where the new characters end +* @param charsStyle the style of the new characters +*/ + public abstract void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit, AttributeMap charsStyle); + +/** +* Replace the characters in the range [start, limit) with the character srcChar. +* The new character takes on the style charStyle +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character at +* limit is not modified. +* @param srcChar the new character +* @param charsStyle the style of the new character +*/ + public abstract void replace(int start, int limit, char srcChar, AttributeMap charStyle); + +/** +* Replace the entire contents of this MText (both characters and styles) with +* the contents of srcText. +* @param srcText the source for the new characters and styles +*/ + public abstract void replaceAll(MConstText srcText); + +/** +* Insert the contents of srcText (both characters and styles) into this +* MText at the position specified by pos. +* @param pos The character offset where the new text is to be inserted. +* @param srcText The text to insert. */ + public abstract void insert(int pos, MConstText srcText); + +/** +* Append the contents of srcText (both characters and styles) to the +* end of this MText. +* @param srcText The text to append. */ + public abstract void append(MConstText srcText); + +/** +* Delete the specified range of characters (and styles). +* @param start Offset of the first character to delete. +* @param limit Offset of the first character after the range to delete. */ + public abstract void remove(int start, int limit); + +/** +* Delete all characters and styles. +*/ + public abstract void remove(); + +/** +* Create an MText containing the characters and styles in the range +* [start, limit). +* @param start offset of first character in the new text +* @param limit offset immediately after the last character in the new text +* @return an MConstText object containing the characters and styles in the given range +*/ + public abstract MText extractWritable(int start, int limit); + + +//================================================== +// STORAGE MANAGEMENT +//================================================== + +/** +* Minimize the amount of memory used by the MText object. +*/ + public abstract void compress(); + +//================================================== +// STYLE MODIFICATION +//================================================== + +/** +* Set the character style of all characters in the MText object to +* AttributeMap.EMPTY_ATTRIBUTE_MAP. +*/ + public abstract void removeCharacterStyles(); + +/** +* Invoke the given modifier on all character styles from start to limit. +* @param modifier the modifier to apply to the range. +* @param start the start of the range of text to modify. +* @param limit the limit of the range of text to modify. +*/ + public abstract void modifyCharacterStyles(int start, int limit, StyleModifier modifier); + +/** +* Invoke the given modifier on all paragraph styles in paragraphs +* containing characters in the range [start, limit). +* @param modifier the modifier to apply to the range. +* @param start the start of the range of text to modify. +* @param limit the limit of the range of text to modify. +*/ + public abstract void modifyParagraphStyles(int start, int limit, StyleModifier modifier); + +//================================================== +// DAMAGED RANGE +//================================================== +/** +* Reset the damaged range to an empty interval, and begin accumulating the damaged +* range. The damaged range includes every index where a character, character style, +* or paragraph style has changed. +* @see #damagedRangeStart +* @see #damagedRangeLimit +*/ + public abstract void resetDamagedRange(); +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/ParagraphBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/ParagraphBuffer.java new file mode 100755 index 00000000000..555737975e7 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/ParagraphBuffer.java @@ -0,0 +1,716 @@ +/* + * @(#)$RCSfile: ParagraphBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.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 charBuffer. +*/ + 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 start. +* If a paragraph break was inserted, propogate paragraph style at +* start 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 start. +* Each new paragraph gets paragraph style at +* start. +*/ + 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 pos. +*/ + 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 pos. +*/ + 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 pos. +*/ + 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 srcText. +* @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 srcText to copy +* @param srcLimit the first index after the range in srcText 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 +* style. +*/ + 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 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; + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/RunArray.java b/icu4j/src/com/ibm/richtext/styledtext/RunArray.java new file mode 100755 index 00000000000..7e18f038062 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/RunArray.java @@ -0,0 +1,282 @@ +/* + * @(#)$RCSfile: RunArray.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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. + */ +/** +* This class maintains intervals within a piece of text. Interval boundaries +* are stored in the fRunStart array. Interval boundaries may have a +* positive or negative representation. A positive boundary is given as an offset +* from 0. A negative boundary is given as a negative offset from the ned of the text. +* The RunArray stores positive boundaries in the entries [0, fPosEnd], and negative +* boundaries in the entries [fNegStart, fLength). New boundaries may be inserted into +* the undefined middle of the RunArray. If fPosEnd < 0, there are no positive entries. +* If fNegStart >= fRunArray.length, there are no negative netries. It's possible to have +* a runarray with neither positive or negative entries. +* +* As an example of how the RunArray works, consider a piece of text with 5 intervals, +* where each interval is 3 characters in length. The RunArray for this text could +* look like: +* fCurTextLength = 15, fPosEnd = 5, fNegStart = 10, +* fRunStart = { 0, 3, 6, 9, 12, U, U, U, U, U }; +* where U is an undefined array element. + +* An equivalent representation would be: +* fCurTextLength = 15, fPosEnd = 3, fNegStart = 8, +* fRunStart = { 0, 3, 6, U, U, U, U, U, -6, -3 }; +* +* The RunArray class is used in the StyleBuffer and the ParagraphBuffer. In the StyleBuffer, +* the entries in fRunStart give the offsets where style runs begin. In the +* ParagraphBuffer, the fRunStart entries store offsets of paragraph breaks. +* +* This class provides methods for shifting the run table to a particular position, expanding the +* run table, and returning the index of the run containing a particular offset in the text. All +* other functionality is implemented in the RunArray clients. +* +* RunArray uses FastIntBinarySearch for searches. The searches are constructed on demand in +* the findRunContaining method. The searches are invalidated when the run array is shifted; +* however, the RunArray can be modified by other classes. Thus, if another class modifies +* the entries in fRunArray, or modifies fPosEnd or fNegStart, it is responsible for +* calling runStartsChanged. +*/ + +package com.ibm.richtext.styledtext; + +import java.io.Externalizable; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +final class RunArray implements Externalizable { + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private static final long serialVersionUID = 22356934; + + int[] fRunStart; + private int fCurTextLength; + int fPosEnd, fNegStart; + + transient private FastIntBinarySearch fPosSearch; + transient private boolean fPosSearchValid; + transient private FastIntBinarySearch fNegSearch; + transient private boolean fNegSearchValid; + + private static final int CURRENT_VERSION = 1; + + RunArray(int initialSize, int curTextLength) { + + fRunStart = new int[initialSize]; + fCurTextLength = curTextLength; + fPosEnd = -1; + fNegStart = initialSize; + + fPosSearch = new FastIntBinarySearch(fRunStart, 0, 1); + fNegSearch = new FastIntBinarySearch(fRunStart, 0, 1); + fPosSearchValid = fNegSearchValid = false; + } + + /** + * Note: this constructor is ONLY for use by the Serialization + * mechanism. It does not leave this object in a valid state! + */ + public RunArray() { + } + + public void writeExternal(ObjectOutput out) throws IOException { + + out.writeInt(CURRENT_VERSION); + out.writeObject(fRunStart); + out.writeInt(fCurTextLength); + out.writeInt(fPosEnd); + out.writeInt(fNegStart); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + if (in.readInt() != CURRENT_VERSION) { + throw new IOException("Invalid version of RunArray"); + } + fRunStart = (int[]) in.readObject(); + fCurTextLength = in.readInt(); + fPosEnd = in.readInt(); + fNegStart = in.readInt(); + + fPosSearch = new FastIntBinarySearch(fRunStart, 0, 1); + fNegSearch = new FastIntBinarySearch(fRunStart, 0, 1); + fPosSearchValid = fNegSearchValid = false; + } + + public int getCurTextLength() { + + return fCurTextLength; + } + + public void setCurTextLength(int curTextLength) { + + fCurTextLength = curTextLength; + } + + public void addToCurTextLength(int delta) { + + fCurTextLength += delta; + } + + public void runStartsChanged() { + + fPosSearchValid = fNegSearchValid = false; + } + +/** +* Returns the index of the last valid run. +*/ + int lastRun() { + + return (fNegStart == fRunStart.length)? fPosEnd : fRunStart.length-1; + } + +/** +* Returns the length of the run array. Replaces old fLength member. +*/ + int getArrayLength() { + + return fRunStart.length; + } + +/** +* Shifts style table such that the last positive run +* starts before pos. +*/ + void shiftTableTo(int pos) { + + int oldPosEnd = fPosEnd; + + while (fPosEnd >= 0 && fRunStart[fPosEnd] >= pos) { + + fNegStart--; + fRunStart[fNegStart] = fRunStart[fPosEnd] - fCurTextLength; + fPosEnd--; + + } + + pos -= fCurTextLength; + + while (fNegStart= fRunStart[fNegStart])) { + + pos -= fCurTextLength; + + if (!fNegSearchValid) { + fNegSearch.setData(fRunStart, fNegStart, length-fNegStart); + } + search = fNegSearch; + } + else if (fPosEnd >= 0) { + + if (!fPosSearchValid) { + fPosSearch.setData(fRunStart, 0, fPosEnd+1); + } + search = fPosSearch; + } + else + return -1; + + int run = search.findIndex(pos); + + return run; + } + + int getLogicalRunStart(int run) { + + if (run == -1) { + return 0; + } + else if (run == fRunStart.length) { + return fCurTextLength; + } + else { + if (run <= fPosEnd) { + return fRunStart[run]; + } + else if (run >= fNegStart) { + return fRunStart[run] + fCurTextLength; + } + else { + throw new IllegalArgumentException("Illegal run"); + } + } + } + +/** +* Increases size of run table. Current implementation doubles the run table's size. +*/ + void expandRunTable() { + + resizeRunTable(fRunStart.length * 2); + } + +/** +* Return the minimum number of elements possible in fRunStart. +*/ + private int getMinSize() { + + return Math.max(fPosEnd + (fRunStart.length-fNegStart) + 1, 1); + } + + void compress() { + + int minSize = getMinSize(); + if (fRunStart.length > minSize) { + resizeRunTable(minSize); + } + } + + private void resizeRunTable(int newSize) { + + if (newSize < getMinSize()) { + throw new IllegalArgumentException("Attempt to make RunArray too small."); + } + + final int oldLength = fRunStart.length; + + int newRunStart[] = new int[newSize]; + System.arraycopy(fRunStart, 0, newRunStart, 0, fPosEnd+1); + int newNegStart = newRunStart.length - (oldLength-fNegStart); + System.arraycopy(fRunStart, fNegStart, newRunStart, newNegStart, (oldLength-fNegStart)); + + fNegStart = newNegStart; + fRunStart = newRunStart; + + fPosSearchValid = fNegSearchValid = false; + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/StandardTabRuler.java b/icu4j/src/com/ibm/richtext/styledtext/StandardTabRuler.java new file mode 100755 index 00000000000..e0b583f1230 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/StandardTabRuler.java @@ -0,0 +1,374 @@ +/* + * @(#)$RCSfile: StandardTabRuler.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.util.Vector; + +import java.io.Externalizable; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +/** + * This class is a standard implementation of MTabRuler. + * It can have a finite number of client-specified TabStops. After + * the client-specified TabStops, all TabStops have type + * TabStop.kAuto and are at the autospace intervals. + * @see TabStop + */ +public final class StandardTabRuler extends MTabRuler + implements Externalizable +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private static final int CURRENT_VERSION = 1; + private static final long serialVersionUID = 22356934; + + private static final TabStop AUTO_ZERO = new TabStop(0, TabStop.kAuto); + + private TabStop[] fTabs = null; + private int fAutoSpacing = 36; // every 1/2 inch. + + /** + * Create a StandardTabRuler with only auto tabs, with spacing of 36. + */ + public StandardTabRuler() + { + } + + /** + * Create a StandardTabRuler with only auto tabs, with the + * given autoSpacing. + * @param autoSpacing the autoSpacing for this tab ruler + */ + public StandardTabRuler(int autoSpacing) + { + fAutoSpacing = autoSpacing; + } + + /** + * Create a StandardTabRuler. The first TabStops on the ruler will be + * the TabStops in the tabs array. After these tabs all + * tabs are auto tabs. + * @param tabs an array of TabStops. The TabStops in the array must + * be in strictly increasing order (of positions), and cannot have + * type TabStop.kAuto. + * @param autoSpacing the autoSpacing interval to use after the last + * client-specified tab. + */ + public StandardTabRuler(TabStop[] tabs, int autoSpacing) + { + if (tabs.length > 0) { + validateTabArray(tabs); + fTabs = (TabStop[]) tabs.clone(); + } + else { + fTabs = null; + } + fAutoSpacing = autoSpacing; + } + + /** Tabs as provided, then autoSpacing after the last tab to eternity. Use this constructor when + munging a ruler, it does no validation on the tabs in the vector. Vector may not be null. */ + + /*public*/ StandardTabRuler(Vector v, int autoSpacing) + { + fTabs = tabArrayFromVector(v); + fAutoSpacing = autoSpacing; + } + + /** Construct from another ruler. No validation. Ruler may not be null. */ + + /*public*/ StandardTabRuler(MTabRuler ruler) + { + if (ruler == null) { + throw new IllegalArgumentException("ruler may not be null"); + } + + fTabs = tabArrayFromVector(vectorFromTabRuler(ruler)); + fAutoSpacing = ruler.autoSpacing(); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + int version = in.readInt(); + if (version != CURRENT_VERSION) { + throw new IOException("Invalid version of StyledText: " + version); + } + fTabs = (TabStop[]) in.readObject(); + fAutoSpacing = in.readInt(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + + out.writeInt(CURRENT_VERSION); + out.writeObject(fTabs); + out.writeInt(fAutoSpacing); + } + + /** + * Return first tab in the ruler. If an autoTab, it is at position zero, and + * all subsequent tabs will be autotabs at autoSpacing intervals. + */ + public TabStop firstTab() + { + if (fTabs != null && fTabs.length > 0) { + return fTabs[0]; + } + + return AUTO_ZERO; + } + + /** + * Return the first tab in the ruler with fPosition > position. If it is an + * autotab, it is at an increment of autoSpacing, and all subsequent tabs will be + * autotabs at autoSpacing intervals. + * @param position the position of the TabStop returned will be greater than this parameter + */ + public TabStop nextTab(int position) + { + if (fTabs != null) { + for (int i = 0; i < fTabs.length; ++i) { + if (position < fTabs[i].getPosition()) + return fTabs[i]; + } + } + + if (position >= 4000) { // debug: sanity check + System.out.println("auto tab past 4000"); + } + + return new TabStop(((position / fAutoSpacing) + 1) * fAutoSpacing, TabStop.kAuto); + } + + /** + * Return the interval for autotabs. + */ + public int autoSpacing() + { + return fAutoSpacing; + } + + /** + * Compare this to another Object. Returns true if the object + * is an MTabRuler with the same autoSpacing and tabs. + */ + public boolean equals(Object o) + { + if (o == this) { + return true; + } + else if (o == null) { + return false; + } + + MTabRuler rhs; + try { + rhs = (MTabRuler)o; + } + catch(ClassCastException e) { + return false; + } + + if (fAutoSpacing != rhs.autoSpacing()) + return false; + + TabStop rhsTab = rhs.firstTab(); + + if (fTabs != null) { + for (int i = 0; i < fTabs.length; ++i) { + if (!fTabs[i].equals(rhsTab)) + return false; + + rhsTab = rhs.nextTab(rhsTab.getPosition()); + } + } + + return rhsTab.getType() == TabStop.kAuto; + } + + /** + * Return debug information about this tab ruler. + */ + public String toString() + { + StringBuffer buffer = new StringBuffer(super.toString()); + buffer.append(" auto: "); + buffer.append(Integer.toString(fAutoSpacing)); + + if (fTabs != null) { + for (int i = 0; i < fTabs.length; ++i) { + buffer.append(fTabs[i].toString()); + } + } + + return buffer.toString(); + } + + /** Utility to convert a vector of tabs to an array. */ + + private static TabStop[] tabArrayFromVector(Vector v) + { + int count = v.size(); + TabStop[] tabs = new TabStop[count]; + for (int i = 0; i < count; ++i) { + tabs[i] = (TabStop)v.elementAt(i); + } + + return tabs; + } + + /** Utility to convert a ruler to a vector of tabs, for munging. */ + + private static Vector vectorFromTabRuler(MTabRuler ruler) + { + Vector v = new Vector(); + for (TabStop tab = ruler.firstTab(); tab != null && tab.getType() != TabStop.kAuto; tab = ruler.nextTab(tab.getPosition())) { + v.addElement(tab); + } + + return v; + } + + /** Utility to validate an array of tabs. The array must not be null, must not contain null + entries, must not be kAuto, and positions must in increasing order. */ + + private static void validateTabArray(TabStop[] tabs) + { + int pos = Integer.MIN_VALUE; + for (int i = 0; i < tabs.length; ++i) { + if (tabs[i].getType() == TabStop.kAuto) { + throw new IllegalArgumentException("can't explicitly specify an auto tab."); + } + int nextpos = tabs[i].getPosition(); + if (nextpos <= pos) { + throw new IllegalArgumentException("tab positions must be in increasing order."); + } + pos = nextpos; + } + } + + /** + * Return a tab ruler identical to the given ruler, except with the + * given tab added. + * @param ruler the original ruler. The MTabRuler will be the same as + * this except for the additional tab. ruler is not modified. + * @param tabToAdd the tab to add to the new tab ruler + * @return an MTabRuler resulting from this operation + */ + /*public*/ static MTabRuler addTabToRuler(MTabRuler ruler, TabStop tabToAdd) + { + if (ruler == null || tabToAdd == null) + throw new IllegalArgumentException("ruler and tabToAdd may not be null"); + + Vector vector = new Vector(); + + int pos = 0; + boolean added = false; + for (TabStop tab = ruler.firstTab(); tab.getType() != TabStop.kAuto; tab = ruler.nextTab(pos)) { + pos = tab.getPosition(); + + if (!added && pos >= tabToAdd.getPosition()) { + if (pos == tabToAdd.getPosition()) + tab = null; + vector.addElement(tabToAdd); + added = true; + } + + if (tab != null) + vector.addElement(tab); + } + if (!added) + vector.addElement(tabToAdd); + + return new StandardTabRuler(vector, ruler.autoSpacing()); + } + + /** + * Return a tab ruler identical to the given ruler, except with the + * given tab removed. + * @param ruler the original ruler. The MTabRuler will be the same as + * this except for the removed tab. ruler is not modified. + * @param position the position of the tab to remove from the new tab ruler + * @return an MTabRuler resulting from this operation + */ + /*public*/ static MTabRuler removeTabFromRuler(MTabRuler ruler, int position) + { + if (ruler == null) + throw new IllegalArgumentException("ruler may not be null"); + + Vector vector = new Vector(); + + int pos = 0; + boolean removed = false; + for (TabStop tab = ruler.firstTab(); tab.getType() != TabStop.kAuto; tab = ruler.nextTab(pos)) { + pos = tab.getPosition(); + + if (!removed && pos >= position) { + if (pos == position) { + removed = true; + continue; // skip this tab and continue with the remainder + } + break; // we didn't remove a tab, but skipped position, so don't bother with the rest + } + + vector.addElement(tab); + } + if (!removed) // no change + return ruler; + + if (vector.size() == 0) + return new StandardTabRuler(ruler.autoSpacing()); + + return new StandardTabRuler(vector, ruler.autoSpacing()); + } + + /** + * Return a tab ruler identical to the given ruler, except with the + * tab at position fromPosition moved to position + * toPosition. + * @param ruler the original ruler. The MTabRuler will be the same as + * this except for the moved tab. ruler is not modified. + * @param fromPosition the position of the tab to move + * @param toPosition the new position of the tab + * @return an MTabRuler resulting from this operation + */ + /*public*/ static MTabRuler moveTabOnRuler(MTabRuler ruler, int fromPosition, int toPosition) + { + if (ruler == null) + throw new IllegalArgumentException("ruler may not be null"); + + Vector vector = new Vector(); + + int pos = 0; + boolean moved = false; + for (TabStop tab = ruler.firstTab(); tab.getType() != TabStop.kAuto; tab = ruler.nextTab(pos)) { + pos = tab.getPosition(); + + if (!moved && pos == fromPosition) { + moved = true; + tab = new TabStop(toPosition, tab.getType()); // copy it + } + + vector.addElement(tab); + } + if (!moved) // no change + return ruler; + + return new StandardTabRuler(vector, ruler.autoSpacing()); + } + +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/StyleBuffer.java b/icu4j/src/com/ibm/richtext/styledtext/StyleBuffer.java new file mode 100755 index 00000000000..23cdb139a4a --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/StyleBuffer.java @@ -0,0 +1,671 @@ +/* + * @(#)$RCSfile: StyleBuffer.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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.textlayout.attributes.AttributeMap; + +/* + 8/2/96 + Added setIteratorUsingRun method + + 8/5/96 + No longer has to be constructed with an MText. + + 8/8/96 + Added replace method, which reads styles from a StyleRunIterator. + Also, added a constructor which takes a StyleRunIterator. + These methods are for copy/paste support. + 8/16/96 + StyleBuffer now takes MConstText instead of MText where possible. + + 10/23/96 + Some old commented-out code removed for aesthetic reasons. + + 7/31/98 Switched to AttributeMap +*/ + +/** +* StyleBuffer implements MStyleBuffer. It maintains +* AttributeMap objects to apply to the text in an MText object, +* and the +* intervals on which those styles apply. +*

+* StyleBuffer stores the intervals on which styles apply in a RunArray +* object (see RunArray for more information). The styles are stored in +* an array of AttributeMap objects. +*

+* RunArray maintains an array of integers which represent offsets into text. +* The array has a "positive" region in which offsets are given as positive distances +* from the start of the text, and a "negative" region in which offsets are given as +* negative distances from the end of the text. Between the positive and negative regions +* is a gap, into which new offsets may be inserted. This storage scheme allows for +* efficient response to a series of editing operations which occur in the same area of the +* text. +*

+* StyleBuffer uses the offsets in RunArray as the boundaries of style runs. +* A style run begins at each offset in RunArray, and each style run continues to +* the next offset. The style array is kept in sync with the array of offsets in RunArray; +* that is, the style which begins at RunArray[i] is stored in StyleArray[i]. +*

+* The first entry in the RunArray is always 0. +* +* @author John Raley +* +* @see AttributeMap +* @see MText +* @see RunArray +*/ + +final class StyleBuffer extends MStyleBuffer implements Externalizable { + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + /** + * Creates a new style buffer with length equal to the length of text, + * and with a single run of defaultStyle. + */ + private static final long serialVersionUID = 22356934; + + private static final int CURRENT_VERSION = 1; + private static final int kInitialSize = 10; + private RunArray fRunArray; + + private AttributeMap fStyleTable[]; + + StyleBuffer(MConstText text, AttributeMap initialStyle) { + + this(text.length(), initialStyle); + } + + /** + * Creates a new style buffer with length initialLength and with a + * single run of defaultStyle. + */ + + StyleBuffer(int initialLength, AttributeMap initialStyle) { + + fRunArray = new RunArray(kInitialSize, initialLength); + fRunArray.fPosEnd = 0; + fRunArray.fRunStart[0] = 0; + + fStyleTable = new AttributeMap[kInitialSize]; // do I really want to do this??? + + fStyleTable[0] = initialStyle; + } + + /** + * Note: this constructor is ONLY for use by the Serialization + * mechanism. It does not leave this object in a valid state! + */ + public StyleBuffer() { + } + + public void writeExternal(ObjectOutput out) throws IOException { + + compress(); + out.writeInt(CURRENT_VERSION); + out.writeObject(fRunArray); + out.writeObject(fStyleTable); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + if (in.readInt() != CURRENT_VERSION) { + throw new IOException("Invalid version of StyleBuffer"); + } + fRunArray = (RunArray) in.readObject(); + fStyleTable = (AttributeMap[]) in.readObject(); + } + +/** +* Shift style and run tables such that the last positive run begins before the given position. +* Since there is always a run start at 0, this method ensures that the first run will not be shifted. +* This is called by: insertText and deleteText. +* @param pos a position in the text. +*/ + + private void shiftTableTo(int pos) { + + if (pos == 0) + pos = 1; + + 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; + } + +/** +* Minimize the amount of storage used by this object. +*/ + void compress() { + + int oldNegStart = fRunArray.fNegStart; + fRunArray.compress(); + if (fRunArray.fNegStart != oldNegStart) { + handleArrayResize(oldNegStart); + } + } + +/** +* Increase the storage capacity of the style and run tables if no room remains. +*/ + private void expandStyleTableIfFull() { + + if (fRunArray.fPosEnd + 1 == fRunArray.fNegStart) { + + int oldNegStart = fRunArray.fNegStart; + fRunArray.expandRunTable(); + handleArrayResize(oldNegStart); + } + } + +/* + public MStyleRunIterator createStyleRunIterator(int start, int limit) { + + return new StyleRunIterator(start, limit); + } +*/ +/** +* Respond to an insertion in the text. The length of the last style run which +* begins before start is increased by length. The run table +* is shifted such that the run into which text was inserted is the last positive run. +* This implementation assumes that all styles propogate. +* @param start the offset where the insertion began +* @param length the number of characters inserted +*/ + public void insertText(int start, int limit) { + + shiftTableTo(start); + fRunArray.addToCurTextLength(limit - start); + } + +/** +* Respond to a deletion in the text. The last style run before +* start is truncated to end at start. The +* style run containing (start+length) is set to begin +* at (start+length). Runs in between are deleted. +* If the deletion occurs entirely within one style run, the length of the style +* run is reduced by length. +* This implementation assumes that all styles propogate. +* This method shifts the run table such that the run in which the delete began +* is the last positive run. Other methods depend on this "side effect". +* @param start the offset where the deletion began +* @param length the offset where the deletion stopped +*/ + public void deleteText(int start, int limit) { + + int length = limit - start; + + // An optimization - if a whole run wasn't deleted we don't + // need to check for run merging, which could be expensive. + boolean wholeRunDeleted = false; + + shiftTableTo(start); + + int firstRunLimit = fRunArray.getCurTextLength(); + if (fRunArray.fNegStart < fRunArray.getArrayLength()) + firstRunLimit += fRunArray.fRunStart[fRunArray.fNegStart]; + + if (limit == fRunArray.getCurTextLength()) { + fRunArray.fNegStart = fRunArray.getArrayLength(); + } + else if (limit >= firstRunLimit) { + + int end = fRunArray.findRunContaining(limit); + if (end != fRunArray.fPosEnd) { + fRunArray.fRunStart[end] = limit - fRunArray.getCurTextLength(); + fRunArray.fNegStart = end; + wholeRunDeleted = true; + } + } + + if (fRunArray.fNegStart != fRunArray.getArrayLength()) { + if (start == 0 && limit >= firstRunLimit) { + // the first style run was deleted; move first "negative" run into + // first position + fStyleTable[0] = fStyleTable[fRunArray.fNegStart++]; + } + else if (wholeRunDeleted) { + if (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd])) { + // merge style runs + fRunArray.fNegStart++; + } + } + } + + fRunArray.addToCurTextLength(-length); + + fRunArray.runStartsChanged(); + //System.out.println("In deleteText: number of style runs = " + numRuns(this)); + } + +/** +* Arrange style table so that old styles in the provided range are removed, and +* new styles can be inserted into the insertion gap. +* After calling this method, new style starts and styles may be placed +* in the insertion gaps of fRunArray.fStyleStart and fStyleTable. +* @param start offset in the text where insertion operation begins +* @param limit offset in the text where previous styles resume +*/ + private void prepareStyleInsert(int start) { + + if (start == 0) { + + // fRunArray.fPosEnd should be 0 if we're in this branch. + + if (fRunArray.getCurTextLength() > 0) { + + /* Move first existing style run to negative end of buffer. + Don't do this if length==0; that is, if there is no real + style run at 0. + */ + + fRunArray.fNegStart--; + fStyleTable[fRunArray.fNegStart] = fStyleTable[0]; + fRunArray.fRunStart[fRunArray.fNegStart] = -fRunArray.getCurTextLength(); + } + + fRunArray.fPosEnd = -1; + } + else { + + // consistency check: start should be in current gap + if (fRunArray.fRunStart[fRunArray.fPosEnd] >= start) { + throw new Error("Inconsistent state! Start should be within insertion gap."); + } + + int endOfInsertionGap = fRunArray.getCurTextLength(); + if (fRunArray.fNegStart < fRunArray.getArrayLength()) { + endOfInsertionGap += fRunArray.fRunStart[fRunArray.fNegStart]; + } + + if (endOfInsertionGap < start) { + throw new Error("Inconsistent state! Start should be within insertion gap."); + } + + // if no break at start (on negative end of buffer) make one + + if (endOfInsertionGap != start) { + + // split style run in insertion gap + + expandStyleTableIfFull(); + + fRunArray.fNegStart--; + fStyleTable[fRunArray.fNegStart] = fStyleTable[fRunArray.fPosEnd]; + fRunArray.fRunStart[fRunArray.fNegStart] = start - fRunArray.getCurTextLength(); + + //System.out.println("splitting run."); + } + } + } + + public boolean modifyStyles(int start, + int limit, + StyleModifier modifier, + int[] damagedRange) { + + if (limit == start) { + return false; + } + + shiftTableTo(start); + + int currentRunStart = start; + AttributeMap oldStyle; + AttributeMap mergeStyle = fStyleTable[fRunArray.fPosEnd]; + + if (fRunArray.fNegStart < fRunArray.getArrayLength() && + fRunArray.fRunStart[fRunArray.fNegStart]+fRunArray.getCurTextLength() == start) { + + oldStyle = fStyleTable[fRunArray.fNegStart]; + ++fRunArray.fNegStart; + } + else { + oldStyle = mergeStyle; + } + + boolean modifiedAnywhere = false; + for(;;) { + + boolean modified = false; + + // push new style into gap on positive side + AttributeMap newStyle = modifier.modifyStyle(oldStyle); + if (damagedRange != null && !newStyle.equals(oldStyle)) { + modified = modifiedAnywhere = true; + damagedRange[0] = Math.min(currentRunStart, damagedRange[0]); + } + + if (!newStyle.equals(mergeStyle)) { + + if (currentRunStart != 0) { + expandStyleTableIfFull(); + ++fRunArray.fPosEnd; + } + + fStyleTable[fRunArray.fPosEnd] = newStyle; + fRunArray.fRunStart[fRunArray.fPosEnd] = currentRunStart; + } + + mergeStyle = newStyle; + + int nextRunStart = fRunArray.getLogicalRunStart(fRunArray.fNegStart); + + if (limit > nextRunStart) { + oldStyle = fStyleTable[fRunArray.fNegStart]; + currentRunStart = nextRunStart; + if (modified) { + damagedRange[1] = Math.max(currentRunStart, damagedRange[1]); + } + ++fRunArray.fNegStart; + } + else { + if (limit < nextRunStart && !oldStyle.equals(mergeStyle)) { + expandStyleTableIfFull(); + ++fRunArray.fPosEnd; + fStyleTable[fRunArray.fPosEnd] = oldStyle; + fRunArray.fRunStart[fRunArray.fPosEnd] = limit; + } + if (modified) { + damagedRange[1] = Math.max(limit, damagedRange[1]); + } + break; + } + } + + // merge last run if needed + if ((fRunArray.fNegStart < fRunArray.getArrayLength()) && + (fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd]))) { + fRunArray.fNegStart++; + } + + fRunArray.runStartsChanged(); + + return modifiedAnywhere; + } + + public int styleStart(int pos) { + + if (pos == fRunArray.getCurTextLength()) { + return pos; + } + + return fRunArray.getLogicalRunStart(fRunArray.findRunContaining(pos)); + } + + public int styleLimit(int pos) { + + if (pos == fRunArray.getCurTextLength()) { + return pos; + } + + int run = fRunArray.findRunContaining(pos); + + if (run == fRunArray.fPosEnd) { + run = fRunArray.fNegStart; + } + else { + ++run; + } + + return fRunArray.getLogicalRunStart(run); + } + +/** +* Return style at location pos. +* @param pos an offset into the text +* @returns the style of the character at offset +*/ + public AttributeMap styleAt(int pos) { + + return fStyleTable[ fRunArray.findRunContaining(pos) ]; + } + +/* +* Set run start, run length, and run value in an iterator. This method is +* only called by a StyleRunIterator. +* @param pos an offset into the text. The iterator's run start and run limit are +* set to the run containing pos. +* @param iter the iterator to set +*/ + void setIterator(int pos, StyleRunIterator iter) { + + if ((pos < 0) || (pos > fRunArray.getCurTextLength())) { + + iter.set(null, 0, 0, kNoRun); + return; + } + + int run = fRunArray.findRunContaining(pos); + + setIteratorUsingRun(run, iter); + } + +/** +* Set run start, run length, and run value in an iterator. This method is +* only called by a StyleRunIterator. +* @param run the index of the run to which the iterator should be set +* @param iter the iterator to set +*/ + private void setIteratorUsingRun(int run, StyleRunIterator iter) { + + int lastValidRun = fRunArray.lastRun(); + + if (run < 0 || run > lastValidRun) { + + iter.set(null, 0, 0, kNoRun); + return; + } + + if (run == fRunArray.fPosEnd+1) + run = fRunArray.fNegStart; + else if (run == fRunArray.fNegStart-1) + run = fRunArray.fPosEnd; + + int runStart = fRunArray.fRunStart[run]; + if (runStart < 0) + runStart += fRunArray.getCurTextLength(); + + AttributeMap style = fStyleTable[run]; + + 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(); + } + + //System.out.println("setIterator: pos="+pos+", runStart="+runStart+", runLimit="+runLimit+ + // ", run="+run+", fPosEnd="+fPosEnd); + + iter.set(style, runStart, runLimit, run); + } + + public void replace(int start, int limit, MConstText srcText, int srcStart, int srcLimit) + { + deleteText(start, limit); + if (srcStart == srcLimit) + return; + prepareStyleInsert(start); + for (int j2 = srcStart; j2 < srcLimit; j2 = srcText.characterStyleLimit(j2)) + { + AttributeMap attributeMap = srcText.characterStyleAt(j2); + if (fRunArray.fPosEnd < 0 || !fStyleTable[fRunArray.fPosEnd].equals(attributeMap)) + { + expandStyleTableIfFull(); + fRunArray.fPosEnd++; + fRunArray.fRunStart[fRunArray.fPosEnd] = j2 - srcStart + start; + fStyleTable[fRunArray.fPosEnd] = attributeMap; + } + } + fRunArray.addToCurTextLength(srcLimit - srcStart); + if (fRunArray.fNegStart < fRunArray.getArrayLength() && fStyleTable[fRunArray.fNegStart].equals(fStyleTable[fRunArray.fPosEnd])) + fRunArray.fNegStart++; + } + + private static final int kNoRun = -42; // iterator use + + private final class StyleRunIterator /*implements MStyleRunIterator*/ { + + StyleRunIterator(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 fStyle != null; + } + + public void next() + { + if (fRunLimit < fLimit) { + fCurrentRun++; + setIteratorUsingRun(fCurrentRun, this); + } + else + set(null, 0, 0, kNoRun); + } + + public void prev() + { + if (fRunStart > fStart) { + fCurrentRun--; + setIteratorUsingRun(fCurrentRun, this); + } + else + set(null, 0, 0, kNoRun); + } + + public void set(int pos) + { + if (pos >= fStart && pos < fLimit) { + setIterator(pos, this); + } else { + set(null, 0, 0, kNoRun); + } + } + + void set(AttributeMap style, int start, int limit, int currentRun) + { + fStyle = style; + fCurrentRun = currentRun; + fRunStart = start < fStart ? fStart : start; + fRunLimit = limit > fLimit ? fLimit : limit; + } + + 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 AttributeMap style() + { + return fStyle; + } + + public int runStart() + { + return fRunStart; + } + + public int runLimit() + { + return fRunLimit; + } + + public int runLength() + { + return fRunLimit - fRunStart; + } + + private int fStart; + private int fLimit; + private AttributeMap fStyle; + private int fRunStart; + private int fRunLimit; + private int fCurrentRun; + } +} \ No newline at end of file diff --git a/icu4j/src/com/ibm/richtext/styledtext/StyleModifier.java b/icu4j/src/com/ibm/richtext/styledtext/StyleModifier.java new file mode 100755 index 00000000000..90453c1136d --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/StyleModifier.java @@ -0,0 +1,192 @@ +/* + * @(#)$RCSfile: StyleModifier.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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 com.ibm.textlayout.attributes.AttributeMap; +import com.ibm.textlayout.attributes.AttributeSet; + +/** + * StyleModifier is the base class for operations on AttributeMap. To implement + * an operation on AttributeMap, subclass StyleModifier and override + * modifyStyle. StyleModifiers are used by MText. + *

+ * For convenience, this class contains factory methods which will create a + * StyleModifier for + * certain common operations: attribute union, attribute removal, and AttributeMap + * replacement. + * @see AttributeMap + * @see AttributeSet + * @see MText + */ +/* + * {jbr} StyleModifier is not the best name for this class - styles are immutable and never + * really modified. + */ +public class StyleModifier +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + /** + * Create a StyleModifier. + */ + protected StyleModifier() { + } + + /** + * Return the result of this StyleModifier's operation on the given style. + * Default implementation just returns the given style. + * @param style the AttributeMap to perform the operation on + */ + public AttributeMap modifyStyle(AttributeMap style) + { + return style; + } + + /** + * A StyleModifier which simply returns the given style. + */ + public static final StyleModifier IDENTITY = new StyleModifier(); + + /** + * Create a StyleModifier whose operation is + * style.addAttributes(s), + * where style is the AttributeMap passed to + * modifyStyle. + * @param s the AttributeMap to union with + * @return a StyleModifier for this operation + */ + public static StyleModifier createAddModifier(AttributeMap s) { + + return new StyleAddModifier(s); + } + + /** + * Create a StyleModifier whose operation is + * style.addAttribute(key, value), + * where style is the AttributeMap passed to + * modifyStyle. + * @param key the key to add + * @param value the value to add + * @return a StyleModifier for this operation + */ + public static StyleModifier createAddModifier(Object key, + Object value) { + + return new AttributeAddModifier(key, value); + } + + /** + * Create a StyleModifier whose operation returns s, + * ignoring the parameter to modifyStyle. + * @param s the AttributeMap which will replace any other AttributeMap + * @return a StyleModifier for this operation + */ + public static StyleModifier createReplaceModifier(AttributeMap s) { + + return new StyleReplaceModifier(s); + } + + /** + * Create a StyleModifier whose operation is + * style.removeAttributes(s), + * where style is the AttributeMap passed to + * modifyStyle. + * @param s the AttributeSet of attributes to remove + * @return a StyleModifier for this operation + */ + public static StyleModifier createRemoveModifier(AttributeSet s) { + + return new StyleRemoveModifier(s); + } + + static final class AttributeAddModifier extends StyleModifier { + + private Object fKey; + private Object fValue; + + public AttributeAddModifier(Object key, Object value) { + + fKey = key; + fValue = value; + } + + public AttributeMap modifyStyle(AttributeMap style) { + + return style.addAttribute(fKey, fValue); + } + } + + /** + * Create this with the styles to add. These styles will add to and override any already + * present in the style passed to modifyStyle. + */ + static final class StyleAddModifier extends StyleModifier + { + private AttributeMap fStyle; + + public StyleAddModifier(AttributeMap style) + { + if (style == null) { + throw new IllegalArgumentException("style is null"); + } + fStyle = style; + } + + public AttributeMap modifyStyle(AttributeMap style) + { + return style.addAttributes(fStyle); + } + } + + /** + * Create this with the styles to replace. All style runs will have only these + * styles. + */ + static final class StyleReplaceModifier extends StyleModifier + { + private AttributeMap fStyle; + + public StyleReplaceModifier(AttributeMap style) + { + if (style == null) { + throw new IllegalArgumentException("style is null"); + } + fStyle = style; + } + + public AttributeMap modifyStyle(AttributeMap style) + { + return fStyle; + } + } + + static final class StyleRemoveModifier extends StyleModifier { + + private AttributeSet fRemoveSet; + + public StyleRemoveModifier(AttributeSet removeSet) { + + if (removeSet == null) { + throw new IllegalArgumentException("set is null"); + } + fRemoveSet = removeSet; + } + + public AttributeMap modifyStyle(AttributeMap style) { + + return style.removeAttributes(fRemoveSet); + } + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/StyledText.java b/icu4j/src/com/ibm/richtext/styledtext/StyledText.java new file mode 100755 index 00000000000..55206461278 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/StyledText.java @@ -0,0 +1,686 @@ +/* + * @(#)$RCSfile: StyledText.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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 com.ibm.textlayout.attributes.AttributeMap; + +import java.io.Externalizable; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; +import java.text.CharacterIterator; + +/** + * This class is an implementation of MText, a modifyable, styled text + * storage model. Additionally, it supports persistance through the + * Externalizable interface. + * @see MText + */ + +/* + 10/28/96 {jf} - split the character and paragraph style access and setter function around... + just to keep things interesting. + 8/7/96 {jf} - moved paragraph break implementation from AbstractText into Style text. + - added countStyles, getStyles, and ReplaceStyles implementation. + + 8/14/96 sfb eliminated StyleSheetIterator + + 8/29/96 {jbr} changed iter-based replace method - doesn't call at() unless it is safe to do so + Also, added checkStartAndLimit for debugging + + 7/31/98 Switched from Style to AttributeMap + +*/ + +public final class StyledText extends MText implements Externalizable +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private static final int CURRENT_VERSION = 1; + private static final long serialVersionUID = 22356934; + + /* unicode storage */ + private MCharBuffer fCharBuffer; + /* character style storage */ + private MStyleBuffer fStyleBuffer; + /* paragraph style storage */ + private MParagraphBuffer fParagraphBuffer; + + private transient int fTimeStamp = 0; + private transient int[] fDamagedRange = { Integer.MAX_VALUE, + Integer.MIN_VALUE }; + + private static class ForceModifier extends StyleModifier { + + private AttributeMap fStyle = AttributeMap.EMPTY_ATTRIBUTE_MAP; + + void setStyle(AttributeMap style) { + + fStyle = style; + } + + public AttributeMap modifyStyle(AttributeMap style) { + + return fStyle; + } + } + + // Keep this around foruse in replaceCharStylesWith. OK since + // this class isn't threadsafe anyway. + private transient ForceModifier forceModifier = null; + + //====================================================== + // CONSTRUCTORS + //====================================================== + /** + * Create an empty text object. + */ + public StyledText() + { + this(0); + } + + /** + * Create an empty text object ready to hold at least capacity chars. + * @param capacity the minimum capacity of the internal text buffer + */ + public StyledText(int capacity) + { + fCharBuffer = capacity>0? new CharBuffer(capacity) : new CharBuffer(); + fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP); + fParagraphBuffer = new ParagraphBuffer(fCharBuffer); + } + + /** + * Create a text object with the characters in the string, + * in the given style. + * @param string the initial contents + * @param initialStyle the style of the initial text + */ + public StyledText(String string, AttributeMap initialStyle) + { + fCharBuffer = new CharBuffer(string.length()); + fCharBuffer.replace(0, 0, string, 0, string.length()); + + fStyleBuffer = new StyleBuffer(this, initialStyle); + fParagraphBuffer = new ParagraphBuffer(fCharBuffer); + } + + /** + * Create a text object from the given source. + * @param source the text to copy + */ + public StyledText(MConstText source) { + this(); + append(source); + } + + /** + * Create a text object from a subrange of the given source. + * @param source the text to copy from + * @param srcStart the index of the first character to copy + * @param srcLimit the index after the last character to copy + */ + public StyledText(MConstText source, int srcStart, int srcLimit) { + this(); + replace(0, 0, source, srcStart, srcLimit); + } + + public void writeExternal(ObjectOutput out) throws IOException { + + out.writeInt(CURRENT_VERSION); + out.writeObject(fCharBuffer); + out.writeObject(fStyleBuffer); + out.writeObject(fParagraphBuffer); + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + int version = in.readInt(); + if (version != CURRENT_VERSION) { + throw new IOException("Invalid version of StyledText: " + version); + } + fCharBuffer = (MCharBuffer) in.readObject(); + fStyleBuffer = (MStyleBuffer) in.readObject(); + fParagraphBuffer = (MParagraphBuffer) in.readObject(); + + resetDamagedRange(); + } + + //====================================================== + // MConstText INTERFACES + //====================================================== + + //-------------------------------------------------------- + // character access + //-------------------------------------------------------- +/** +* Return the character at offset pos. +* @param pos a valid offset into the text +* @returns the character at offset pos +*/ + public char at(int pos) + { + return fCharBuffer.at(pos); + } + +/** +* Copy the characters in the range [start, limit) +* into the array dst, beginning at dstStart. +* @param start offset of first character which will be copied into the array +* @param limit offset immediately after the last character which will be copied into the array +* @param dst array in which to copy characters. The length of dst must be at least +* (dstStart + limit - start). +*/ + public void extractChars(int start, int limit, char[] dst, int dstStart) + { + fCharBuffer.at(start, limit, dst, dstStart); + } + + //------------------------------------------------------- + // text model creation + //------------------------------------------------------- +/** +* Create an MConstText containing the characters and styles in the range +* [start, limit). +* @param start offset of first character in the new text +* @param limit offset immediately after the last character in the new text +* @return an MConstText object containing the characters and styles in the given range +*/ + public MConstText extract(int start, int limit) + { + return extractWritable(start, limit); + } + +/** +* Create an MText containing the characters and styles in the range +* [start, limit). +* @param start offset of first character in the new text +* @param limit offset immediately after the last character in the new text +* @return an MConstText object containing the characters and styles in the given range +*/ + public MText extractWritable(int start, int limit) + { + MText text = new StyledText(); + text.replace(0, 0, this, start, limit); + text.resetDamagedRange(); + return text; + } + + //-------------------------------------------------------- + // size/capacity + //-------------------------------------------------------- +/** +* Return the length of the MConstText object. The length is the number of characters in the text. +* @return the length of the MConstText object +*/ + public int length() + { + return fCharBuffer.length(); + } + +/** +* Create a CharacterIterator over the range [start, limit). +* @param start the beginning of the iterator's range +* @param limit the limit of the iterator's range +* @returns a valid CharacterIterator over the specified range +* @see java.text.CharacterIterator +*/ + public CharacterIterator createCharacterIterator(int start, int limit) + { + return fCharBuffer.createCharacterIterator(start, limit); + } + + //-------------------------------------------------------- + // character styles + //-------------------------------------------------------- + +/** +* Return the index of the first character in the character style run +* containing pos. All characters in a style run have the same character +* style. +* @returns the style at offset pos +*/ + public int characterStyleStart(int pos) { + + checkPos(pos, LESS_THAN_LENGTH); + return fStyleBuffer.styleStart(pos); + } + +/** +* Return the index after the last character in the character style run +* containing pos. All characters in a style run have the same character +* style. +* @returns the style at offset pos +*/ + public int characterStyleLimit(int pos) { + + checkPos(pos, NOT_GREATER_THAN_LENGTH); + return fStyleBuffer.styleLimit(pos); + } + +/** +* Return the style applied to the character at offset pos. +* @param pos a valid offset into the text +* @returns the style at offset pos +*/ + public AttributeMap characterStyleAt(int pos) + { + checkPos(pos, NOT_GREATER_THAN_LENGTH); + return fStyleBuffer.styleAt(pos); + } + + //-------------------------------------------------------- + // paragraph boundaries and styles + //-------------------------------------------------------- +/** +* Return the start of the paragraph containing the character at offset pos. +* @param pos a valid offset into the text +* @returns the start of the paragraph containing the character at offset pos +*/ + public int paragraphStart(int pos) + { + checkPos(pos, NOT_GREATER_THAN_LENGTH); + return fParagraphBuffer.paragraphStart(pos); + } + +/** +* Return the limit of the paragraph containing the character at offset pos. +* @param pos a valid offset into the text +* @returns the limit of the paragraph containing the character at offset pos +*/ + public int paragraphLimit(int pos) + { + checkPos(pos, NOT_GREATER_THAN_LENGTH); + return fParagraphBuffer.paragraphLimit(pos); + } + +/** +* Return the paragraph style applied to the paragraph containing offset pos. +* @param pos a valid offset into the text +* @returns the paragraph style in effect at pos +*/ + public AttributeMap paragraphStyleAt(int pos) + { + checkPos(pos, NOT_GREATER_THAN_LENGTH); + return fParagraphBuffer.paragraphStyleAt(pos); + } + +/** +* Return the current time stamp. The time stamp is +* incremented whenever the contents of the MConstText changes. +* @return the current paragraph style time stamp +*/ + public int getTimeStamp() { + + return fTimeStamp; + } + + //====================================================== + // MText INTERFACES + //====================================================== + //-------------------------------------------------------- + // character modfication functions + //-------------------------------------------------------- + + private void updateDamagedRange(int deleteStart, + int deleteLimit, + int insertLength) { + + fDamagedRange[0] = Math.min(fDamagedRange[0], deleteStart); + + if (fDamagedRange[1] >= deleteLimit) { + int lengthChange = insertLength - (deleteLimit-deleteStart); + fDamagedRange[1] += lengthChange; + } + else { + fDamagedRange[1] = deleteStart + insertLength; + } + } + +/** +* Replace the characters and styles in the range [start, limit) with the characters +* and styles in srcText in the range [srcStart, srcLimit). srcText is not +* modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character and style at +* limit is not modified. +* @param srcText the source for the new characters and styles +* @param srcStart the offset into srcText where new characters and styles will be obtained +* @param srcLimit the offset into srcText where the new characters and styles end +*/ + public void replace(int start, int limit, MConstText text, int srcStart, int srcLimit) + { + if (text == this) { + text = new StyledText(text); + } + + if (start == limit && srcStart == srcLimit) { + return; + } + + checkStartLimit(start, limit); + + updateDamagedRange(start, limit, srcLimit-srcStart); + + fCharBuffer.replace(start, limit, text, srcStart, srcLimit); + fStyleBuffer.replace(start, limit, text, srcStart, srcLimit); + fParagraphBuffer.replace(start, limit, text, srcStart, srcLimit, fDamagedRange); + fTimeStamp += 1; + } + +/** +* Replace the characters and styles in the range [start, limit) with the characters +* and styles in srcText. srcText is not +* modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character and style at +* limit is not modified. +* @param srcText the source for the new characters and styles +*/ + public void replace(int start, int limit, MConstText text) { + + replace(start, limit, text, 0, text.length()); + } + +/** +* Replace the characters in the range [start, limit) with the characters +* in srcChars in the range [srcStart, srcLimit). New characters take on the style +* charsStyle. +* srcChars is not modified. +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character at +* limit is not modified. +* @param srcChars the source for the new characters +* @param srcStart the offset into srcChars where new characters will be obtained +* @param srcLimit the offset into srcChars where the new characters end +* @param charsStyle the style of the new characters +*/ + public void replace(int start, int limit, char[] srcChars, int srcStart, int srcLimit, AttributeMap charsStyle) + { + checkStartLimit(start, limit); + + if (start == limit && srcStart == srcLimit) { + return; + } + + updateDamagedRange(start, limit, srcLimit-srcStart); + + fCharBuffer.replace(start, limit, srcChars, srcStart, srcLimit); + + replaceCharStylesWith(start, limit, start + (srcLimit-srcStart), charsStyle); + + fParagraphBuffer.deleteText(start, limit, fDamagedRange); + fParagraphBuffer.insertText(start, srcChars, srcStart, srcLimit); + + fTimeStamp += 1; + } + + private void replaceCharStylesWith(int start, int oldLimit, int newLimit, AttributeMap style) { + + if (start < oldLimit) { + fStyleBuffer.deleteText(start, oldLimit); + } + if (start < newLimit) { + if (forceModifier == null) { + forceModifier = new ForceModifier(); + } + forceModifier.setStyle(style); + fStyleBuffer.insertText(start, newLimit); + fStyleBuffer.modifyStyles(start, newLimit, forceModifier, null); + } + } + +/** +* Replace the characters in the range [start, limit) with the character srcChar. +* The new character takes on the style charStyle +* @param start the offset at which the replace operation begins +* @param limit the offset at which the replace operation ends. The character at +* limit is not modified. +* @param srcChar the new character +* @param charsStyle the style of the new character +*/ + public void replace(int start, int limit, char srcChar, AttributeMap charStyle) + { + checkStartLimit(start, limit); + + updateDamagedRange(start, limit, 1); + + fCharBuffer.replace(start, limit, srcChar); + + replaceCharStylesWith(start, limit, start + 1, charStyle); + + if (start < limit) { + fParagraphBuffer.deleteText(start, limit, fDamagedRange); + } + + fParagraphBuffer.insertText(start, srcChar); + + fTimeStamp += 1; + } + +/** +* Replace the entire contents of this MText (both characters and styles) with +* the contents of srcText. +* @param srcText the source for the new characters and styles +*/ + public void replaceAll(MConstText srcText) + { + replace(0, length(), srcText, 0, srcText.length()); + } + +/** +* Insert the contents of srcText (both characters and styles) into this +* MText at the position specified by pos. +* @param pos The character offset where the new text is to be inserted. +* @param srcText The text to insert. +*/ + public void insert(int pos, MConstText srcText) + { + replace(pos, pos, srcText, 0, srcText.length()); + } + +/** +* Append the contents of srcText (both characters and styles) to the +* end of this MText. +* @param srcText The text to append. +*/ + public void append(MConstText srcText) + { + replace(length(), length(), srcText, 0, srcText.length()); + } + +/** +* Delete the specified range of characters (and styles). +* @param start Offset of the first character to delete. +* @param limit Offset of the first character after the range to delete. +*/ + public void remove(int start, int limit) + { + replace(start, limit, (char[])null, 0, 0, AttributeMap.EMPTY_ATTRIBUTE_MAP); + } + +/** +* Delete all characters and styles. Always increments time stamp. +*/ + public void remove() + { + // rather than going through replace(), just reinitialize the StyledText, + // letting the old data structures fall on the floor + fCharBuffer = new CharBuffer(); + fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP); + fParagraphBuffer = new ParagraphBuffer(fCharBuffer); + fTimeStamp += 1; + fDamagedRange[0] = fDamagedRange[1] = 0; + } + + //-------------------------------------------------------- + // storage management + //-------------------------------------------------------- + +/** +* Minimize the amount of memory used by the MText object. +*/ + public void compress() { + + fCharBuffer.compress(); + fStyleBuffer.compress(); + fParagraphBuffer.compress(); + } + + //-------------------------------------------------------- + // style modification + //-------------------------------------------------------- + +/** +* Set the style of all characters in the MText object to +* AttributeMap.EMPTY_ATTRIBUTE_MAP. +*/ + public void removeCharacterStyles() { + + fStyleBuffer = new StyleBuffer(this, AttributeMap.EMPTY_ATTRIBUTE_MAP); + fTimeStamp += 1; + fDamagedRange[0] = 0; + fDamagedRange[1] = length(); + } + +/** +* Invoke the given modifier on all character styles from start to limit. +* @param modifier the modifier to apply to the range. +* @param start the start of the range of text to modify. +* @param limit the limit of the range of text to modify. +*/ + public void modifyCharacterStyles(int start, int limit, StyleModifier modifier) { + + checkStartLimit(start, limit); + boolean modified = fStyleBuffer.modifyStyles(start, + limit, + modifier, + fDamagedRange); + if (modified) { + fTimeStamp += 1; + } + } + +/** +* Invoke the given modifier on all paragraph styles in paragraphs +* containing characters in the range [start, limit). +* @param modifier the modifier to apply to the range. +* @param start the start of the range of text to modify. +* @param limit the limit of the range of text to modify. +*/ + public void modifyParagraphStyles(int start, int limit, StyleModifier modifier) { + + checkStartLimit(start, limit); + boolean modified = fParagraphBuffer.modifyParagraphStyles(start, + limit, + modifier, + fDamagedRange); + if (modified) { + fTimeStamp += 1; + } + } + +/** +* Reset the damaged range to an empty interval, and begin accumulating the damaged +* range. The damaged range includes every index where a character, character style, +* or paragraph style has changed. +* @see #damagedRangeStart +* @see #damagedRangeLimit +*/ + public void resetDamagedRange() { + + fDamagedRange[0] = Integer.MAX_VALUE; + fDamagedRange[1] = Integer.MIN_VALUE; + } + +/** +* Return the start of the damaged range. +* If the start is +* Integer.MAX_VALUE and the limit is +* Integer.MIN_VALUE, then the damaged range +* is empty. +* @return the start of the damaged range +* @see #damagedRangeLimit +* @see #resetDamagedRange +*/ + public int damagedRangeStart() { + + return fDamagedRange[0]; + } + +/** +* Return the limit of the damaged range. +* If the start is +* Integer.MAX_VALUE and the limit is +* Integer.MIN_VALUE, then the damaged range +* is empty. +* @return the limit of the damaged range +* @see #damagedRangeStart +* @see #resetDamagedRange +*/ + public int damagedRangeLimit() { + + return fDamagedRange[1]; + } + + public String toString() + { + String result =""; + for (int i = 0; i < length(); i++) { + result += at(i); + } + return result; + } + + //====================================================== + // IMPLEMENTATION + //====================================================== + + /* check a range to see if it is well formed and within the bounds of the text */ + private void checkStartLimit(int start, int limit) + { + if (start > limit) { + //System.out.println("Start is less than limit. start:"+start+"; limit:"+limit); + throw new IllegalArgumentException("Start is greater than limit. start:"+start+"; limit:"+limit); + } + + if (start < 0) { + //System.out.println("Start is negative. start:"+start); + throw new IllegalArgumentException("Start is negative. start:"+start); + } + + if (limit > length()) { + //System.out.println("Limit is greater than length. limit:"+limit); + throw new IllegalArgumentException("Limit is greater than length. limit:"+limit); + } + } + + private static final boolean LESS_THAN_LENGTH = false; + private static final boolean NOT_GREATER_THAN_LENGTH = true; + + private void checkPos(int pos, boolean endAllowed) { + + int lastValidPos = length(); + if (endAllowed == LESS_THAN_LENGTH) { + --lastValidPos; + } + + if (pos < 0 || pos > lastValidPos) { + throw new IllegalArgumentException("Position is out of range."); + } + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/TabStop.java b/icu4j/src/com/ibm/richtext/styledtext/TabStop.java new file mode 100755 index 00000000000..b57889cf96c --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/TabStop.java @@ -0,0 +1,185 @@ +/* + * @(#)$RCSfile: TabStop.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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; + +/** + * TabStop represents a position on a tab ruler. Each tab stop has a + * position, giving its location on the ruler, and one of several + * types. The type determines how a segment controled by this TabStop + * is positioned on a line: + *

+ * @see MTabRuler + */ +public final class TabStop implements Externalizable +{ + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private static final int CURRENT_VERSION = 1; + private static final long serialVersionUID = 22356934; + + private byte fType; // left, center, right, decimal + private int fPosition; // tab stop position from line origin. + + /** + * A TabStop with this type aligns its segment's leading edge + * to the TabStop's position. + */ + public static final byte kLeading = 0; + + /** + * A TabStop with this type aligns its segment's center + * to the TabStop's position. + */ + public static final byte kCenter = 1; + + /** + * A TabStop with this type aligns its segment's trailing edge + * to the TabStop's position. + */ + public static final byte kTrailing = 2; + + /** + * A TabStop with this type aligns its segment's first decimal + * to the TabStop's position. + */ + public static final byte kDecimal = 3; + + /** + * A TabStop with this type aligns its segment's leading edge + * to the TabStop's position. After a TabStop of this type, + * all tabs are at autospace intervals. Usually, clients will + * not construct TabStops with this type. + */ + public static final byte kAuto = 4; + + /** + * Create a TabStop with position 0 and type kLeading. + */ + public TabStop() { + + this(0, kLeading); + } + + /** + * Create a TabStop with the given position and type. + * @param position the TabStop's position + * @param type the TabStop's type. Must be one of constants + * in this class. + */ + public TabStop(int position, byte type) { + + if (type < kLeading || type > kAuto) { + throw new IllegalArgumentException("Invalid tab type"); + } + + fPosition = position; + fType = type; + } + + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + + int version = in.readInt(); + if (version != CURRENT_VERSION) { + throw new IOException("Invalid version of TabStop."); + } + fPosition = in.readInt(); + fType = in.readByte(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + + out.writeInt(CURRENT_VERSION); + out.writeInt(fPosition); + out.writeByte(fType); + } + + /** + * Compare this to another Object. TabStops are equal if + * their position and type are the same. + */ + public boolean equals(Object rhs) + { + if (rhs == null) { + return false; + } + + TabStop rhsTab; + try { + rhsTab = (TabStop) rhs; + } + catch(ClassCastException e) { + return false; + } + + return fType == rhsTab.fType && fPosition == rhsTab.fPosition; + } + + /** + * Return the hash code for this TabStop. The hash code is + * position << type. + */ + public int hashCode() { + + return fPosition << fType; + } + + public String toString() + { + char typeChar; + switch (fType) { + case kLeading: typeChar = 'L'; break; + case kCenter: typeChar = 'C'; break; + case kTrailing: typeChar = 'R'; break; + case kDecimal: typeChar = 'D'; break; + case kAuto: typeChar = 'A'; break; + default: typeChar = '?'; break; + } + return "TabStop[" + Integer.toString(fPosition) + typeChar + ']'; + } + + /** + * Return the type of this TabStop. Will be one of the constants + * in this class. + */ + public byte getType() { + return fType; + } + + /** + * Return the position of this TabStop. + */ + public int getPosition() { + return fPosition; + } +} + diff --git a/icu4j/src/com/ibm/richtext/styledtext/TestFastIntBinarySearch.java b/icu4j/src/com/ibm/richtext/styledtext/TestFastIntBinarySearch.java new file mode 100755 index 00000000000..04cbf0ce813 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/TestFastIntBinarySearch.java @@ -0,0 +1,60 @@ +/* + * @(#)$RCSfile: TestFastIntBinarySearch.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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; + +final class TestFastIntBinarySearch { + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + public static void main(String[] args) { + + boolean result = new TestFastIntBinarySearch().test(); + System.out.println(result? "PASSED" : "FAILED"); + } + + public boolean test() { + + boolean result = true; + int[] test = {-5, -3, 0, 2, 5}; + FastIntBinarySearch fibs = new FastIntBinarySearch(test); + + for (int i=0; i < 2; i++) { + int beforeAny = fibs.findIndex(-6); + if (beforeAny != -1) { + result = false; + } + + int atEnd = fibs.findIndex(5); + if (atEnd != test.length-1) { + result = false; + } + + int afterAny = fibs.findIndex(6); + if (afterAny != test.length-1) { + result = false; + } + + int exactly = fibs.findIndex(-3); + if (exactly != 1) { + result = false; + } + + fibs = new FastIntBinarySearch(new int[] {20, 40}); + fibs.setData(test); + } + + return result; + } +} \ No newline at end of file diff --git a/icu4j/src/com/ibm/richtext/styledtext/Validation.java b/icu4j/src/com/ibm/richtext/styledtext/Validation.java new file mode 100755 index 00000000000..2ef7b556825 --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/Validation.java @@ -0,0 +1,39 @@ +/* + * @(#)$RCSfile: Validation.java,v $ $Revision: 1.1 $ $Date: 2000/04/20 17:45:10 $ + * + * (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; + +/** + * Iterators use this class to keep from getting out of sync with + * their underlying data. When created, the iterator gets a + * Validation instance. If the underlying data changes, the Validation + * becomes invalid. Usually iterators will throw exceptions if accessed + * after becoming invalid. + */ +final class Validation { + + static final String COPYRIGHT = + "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; + private boolean fIsValid = true; + + boolean isValid() { + + return fIsValid; + } + + void invalidate() { + + fIsValid = false; + } +} diff --git a/icu4j/src/com/ibm/richtext/styledtext/package.html b/icu4j/src/com/ibm/richtext/styledtext/package.html new file mode 100755 index 00000000000..dd580d30dbf --- /dev/null +++ b/icu4j/src/com/ibm/richtext/styledtext/package.html @@ -0,0 +1,5 @@ + + +Provides styled text storage and related classes. + +