Lay some of the groundwork for model/usermodel support for rich text

(note - RichTextRun is not fixed, and method signatures might well change)


git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353788 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2005-11-07 22:24:15 +00:00
parent aa0705ea2e
commit 3b6297c63f
6 changed files with 293 additions and 34 deletions

View File

@ -19,7 +19,11 @@
package org.apache.poi.hslf.model;
import java.util.LinkedList;
import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.record.StyleTextPropAtom.TextPropCollection;
import org.apache.poi.hslf.usermodel.RichTextRun;
import org.apache.poi.util.StringUtil;
/**
@ -37,18 +41,17 @@ public class TextRun
private TextCharsAtom _charAtom;
private StyleTextPropAtom _styleAtom;
private boolean _isUnicode;
private RichTextRun[] _rtRuns;
/**
* Constructs a Text Run from a Unicode text block
*
* @param tha the TextHeaderAtom that defines what's what
* @param tca the TextCharsAtom containing the text
* @param sta the StyleTextPropAtom which defines the character stylings
*/
public TextRun(TextHeaderAtom tha, TextCharsAtom tca, StyleTextPropAtom sta) {
_headerAtom = tha;
_charAtom = tca;
_styleAtom = sta;
_isUnicode = true;
this(tha,null,tca,sta);
}
/**
@ -56,12 +59,136 @@ public class TextRun
*
* @param tha the TextHeaderAtom that defines what's what
* @param tba the TextBytesAtom containing the text
* @param sta the StyleTextPropAtom which defines the character stylings
*/
public TextRun(TextHeaderAtom tha, TextBytesAtom tba, StyleTextPropAtom sta) {
this(tha,tba,null,sta);
}
/**
* Internal constructor and initializer
*/
private TextRun(TextHeaderAtom tha, TextBytesAtom tba, TextCharsAtom tca, StyleTextPropAtom sta) {
_headerAtom = tha;
_byteAtom = tba;
_styleAtom = sta;
_isUnicode = false;
if(tba != null) {
_byteAtom = tba;
_isUnicode = false;
} else {
_charAtom = tca;
_isUnicode = true;
}
// Figure out the rich text runs
// TODO: Handle when paragraph style and character styles don't match up
LinkedList pStyles = new LinkedList();
LinkedList cStyles = new LinkedList();
if(_styleAtom != null) {
pStyles = _styleAtom.getParagraphStyles();
cStyles = _styleAtom.getCharacterStyles();
}
if(pStyles.size() != cStyles.size()) {
throw new RuntimeException("Don't currently handle case of overlapping styles");
}
_rtRuns = new RichTextRun[pStyles.size()];
//for(int i=0; i<)
}
// Update methods follow
/**
* Saves the given string to the records. Doesn't touch the stylings.
*/
private void storeText(String s) {
if(_isUnicode) {
// The atom can safely convert to unicode
_charAtom.setText(s);
} else {
// Will it fit in a 8 bit atom?
boolean hasMultibyte = StringUtil.hasMultibyte(s);
if(! hasMultibyte) {
// Fine to go into 8 bit atom
byte[] text = new byte[s.length()];
StringUtil.putCompressedUnicode(s,text,0);
_byteAtom.setText(text);
} else {
throw new RuntimeException("Setting of unicode text is currently only possible for Text Runs that are Unicode in the file, sorry. For now, please convert that text to us-ascii and re-try it");
}
}
}
/**
* Handles an update to the text stored in one of the Rich Text Runs
* @param run
* @param s
*/
public synchronized void changeTextInRichTextRun(RichTextRun run, String s) {
// Figure out which run it is
int runID = -1;
for(int i=0; i<_rtRuns.length; i++) {
if(run.equals(_rtRuns[i])) {
runID = i;
}
}
if(runID == -1) {
throw new IllegalArgumentException("Supplied RichTextRun wasn't from this TextRun");
}
// Update the text length for its Paragraph and Character stylings
LinkedList pStyles = _styleAtom.getParagraphStyles();
LinkedList cStyles = _styleAtom.getCharacterStyles();
TextPropCollection pCol = (TextPropCollection)pStyles.get(runID);
TextPropCollection cCol = (TextPropCollection)cStyles.get(runID);
pCol.updateTextSize(s.length());
cCol.updateTextSize(s.length());
// Build up the new text
// As we go through, update the start position for all subsequent runs
// The building relies on the old text still being present
StringBuffer newText = new StringBuffer();
for(int i=0; i<_rtRuns.length; i++) {
// Update start position
if(i > runID) {
_rtRuns[i].updateStartPosition(newText.length());
}
// Grab new text
if(i != runID) {
newText.append(_rtRuns[i].getRawText());
} else {
newText.append(s);
}
}
// Save the new text
storeText(newText.toString());
}
/**
* Changes the text, and sets it all to have the same styling
* as the the first character has.
* If you care about styling, do setText on a RichTextRun instead
*/
public synchronized void setText(String s) {
// Save the new text to the atoms
storeText(s);
// Now handle record stylings:
// everthing gets the same style that the first block has
LinkedList pStyles = _styleAtom.getParagraphStyles();
while(pStyles.size() > 1) { pStyles.removeLast(); }
LinkedList cStyles = _styleAtom.getCharacterStyles();
while(cStyles.size() > 1) { cStyles.removeLast(); }
TextPropCollection pCol = (TextPropCollection)pStyles.getFirst();
TextPropCollection cCol = (TextPropCollection)cStyles.getFirst();
pCol.updateTextSize(s.length());
cCol.updateTextSize(s.length());
// Finally, zap and re-do the RichTextRuns
_rtRuns = new RichTextRun[1];
_rtRuns[0] = new RichTextRun(this,0,s.length());
}
@ -95,32 +222,12 @@ public class TextRun
}
/**
* Changes the text. Chance are, this won't work just yet, because
* we also need to update some other bits of the powerpoint file
* to match the change in the Text Atom, especially byte offsets
* Fetch the rich text runs (runs of text with the same styling) that
* are contained within this block of text
* @return
*/
public void setText(String s) {
// If size changed, warn
if(s.length() != getText().length()) {
System.err.println("Warning: Your powerpoint file may no longer readable by powerpoint, as the text run has changed size!");
}
if(_isUnicode) {
// The atom can safely convert to unicode
_charAtom.setText(s);
} else {
// Will it fit in a 8 bit atom?
boolean hasMultibyte = StringUtil.hasMultibyte(s);
if(! hasMultibyte) {
// Fine to go into 8 bit atom
byte[] text = new byte[s.length()];
StringUtil.putCompressedUnicode(s,text,0);
_byteAtom.setText(text);
} else {
throw new RuntimeException("Setting of unicode text is currently only possible for Text Runs that are Unicode in the file, sorry. For now, please convert that text to us-ascii and re-try it");
}
}
public RichTextRun[] getRichTextRuns() {
return _rtRuns;
}
/**

View File

@ -0,0 +1,12 @@
package org.apache.poi.hslf.record;
/**
* Interface to define how a record can indicate it cares about what its
* parent is, and how it wants to be told which record is its parent.
*
* @author Nick Burch (nick at torchbox dot com)
*/
public interface ParentAwareRecord {
public RecordContainer getParentRecord();
public void setParentRecord(RecordContainer parentRecord);
}

View File

@ -64,6 +64,11 @@ public class StyleTextPropAtom extends RecordAtom
*/
private LinkedList paragraphStyles;
public LinkedList getParagraphStyles() { return paragraphStyles; }
/**
* Updates the link list of TextPropCollections which make up the
* paragraph stylings
*/
public void setParagraphStyles(LinkedList ps) { paragraphStyles = ps; }
/**
* The list of all the different character stylings we code for.
* Each entry is a TextPropCollection, which tells you how many
@ -72,6 +77,11 @@ public class StyleTextPropAtom extends RecordAtom
*/
private LinkedList charStyles;
public LinkedList getCharacterStyles() { return charStyles; }
/**
* Updates the link list of TextPropCollections which make up the
* character stylings
*/
public void setCharacterStyles(LinkedList cs) { charStyles = cs; }
/** All the different kinds of paragraph properties we might handle */
public TextProp[] paragraphTextPropTypes = new TextProp[] {
@ -371,6 +381,14 @@ public class StyleTextPropAtom extends RecordAtom
textPropList = new LinkedList();
}
/**
* Update the size of the text that this set of properties
* applies to
*/
public void updateTextSize(int textSize) {
charactersCovered = textSize;
}
/**
* Writes out to disk the header, and then all the properties
*/

View File

@ -30,10 +30,11 @@ import java.io.OutputStream;
* @author Nick Burch
*/
public class TextHeaderAtom extends RecordAtom
public class TextHeaderAtom extends RecordAtom implements ParentAwareRecord
{
private byte[] _header;
private static long _type = 3999l;
private RecordContainer parentRecord;
public static final int TITLE_TYPE = 0;
public static final int BODY_TYPE = 1;
@ -50,6 +51,9 @@ public class TextHeaderAtom extends RecordAtom
public int getTextType() { return textType; }
public void setTextType(int type) { textType = type; }
public RecordContainer getParentRecord() { return parentRecord; }
public void setParentRecord(RecordContainer record) { this.parentRecord = record; }
/* *************** record code follows ********************** */
/**

View File

@ -0,0 +1,85 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hslf.usermodel;
import org.apache.poi.hslf.model.TextRun;
/**
* Represents a run of text, all with the same style
*
* TODO: get access to the font/character properties
*
* @author Nick Burch
*/
public class RichTextRun
{
/** The TextRun we belong to */
private TextRun parentRun;
/** Where in the parent TextRun we start from */
private int startPos;
/** How long a string (in the parent TextRun) we represent */
private int length;
/** Our paragraph and character style */
/**
* Create a new wrapper around a rich text string
* @param parent
* @param startAt
* @param len
*/
public RichTextRun(TextRun parent, int startAt, int len) {
parentRun = parent;
startPos = startAt;
length = len;
}
/**
* Fetch the text, in output suitable form
*/
public String getText() {
return parentRun.getText().substring(startPos, startPos+length);
}
/**
* Fetch the text, in raw storage form
*/
public String getRawText() {
return parentRun.getRawText().substring(startPos, startPos+length);
}
/**
* Change the text
*/
public void setText(String text) {
length = text.length();
parentRun.changeTextInRichTextRun(this,text);
}
/**
* Tells the RichTextRun its new position in the parent TextRun
* @param startAt
*/
public void updateStartPosition(int startAt) {
startPos = startAt;
}
}

View File

@ -27,7 +27,9 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.hslf.*;
import org.apache.poi.hslf.model.*;
import org.apache.poi.hslf.record.FontCollection;
import org.apache.poi.hslf.record.ParentAwareRecord;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.RecordContainer;
import org.apache.poi.hslf.record.RecordTypes;
import org.apache.poi.hslf.record.SlideAtom;
import org.apache.poi.hslf.record.SlideListWithText;
@ -79,6 +81,11 @@ public class SlideShow
_records = _hslfSlideShow.getRecords();
byte[] _docstream = _hslfSlideShow.getUnderlyingBytes();
// Handle Parent-aware Reocrds
for(int i=0; i<_records.length; i++) {
handleParentAwareRecords(_records[i]);
}
// Find the versions of the core records we'll want to use
findMostRecentCoreRecords();
@ -87,6 +94,32 @@ public class SlideShow
}
/**
* Find the records that are parent-aware, and tell them
* who their parent is
*/
private void handleParentAwareRecords(Record baseRecord) {
// Only need to do something if this is a container record
if(baseRecord instanceof RecordContainer) {
RecordContainer br = (RecordContainer)baseRecord;
Record[] childRecords = br.getChildRecords();
// Loop over child records, looking for interesting ones
for(int i=0; i<childRecords.length; i++) {
Record record = childRecords[i];
// Tell parent aware records of their parent
if(record instanceof ParentAwareRecord) {
((ParentAwareRecord)record).setParentRecord(br);
}
// Walk on down for the case of container records
if(record instanceof RecordContainer) {
handleParentAwareRecords(record);
}
}
}
}
/**
* Use the PersistPtrHolder entries to figure out what is
* the "most recent" version of all the core records