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