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:
parent
aa0705ea2e
commit
3b6297c63f
@ -19,7 +19,11 @@
|
|||||||
|
|
||||||
package org.apache.poi.hslf.model;
|
package org.apache.poi.hslf.model;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import org.apache.poi.hslf.record.*;
|
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;
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,18 +41,17 @@ public class TextRun
|
|||||||
private TextCharsAtom _charAtom;
|
private TextCharsAtom _charAtom;
|
||||||
private StyleTextPropAtom _styleAtom;
|
private StyleTextPropAtom _styleAtom;
|
||||||
private boolean _isUnicode;
|
private boolean _isUnicode;
|
||||||
|
private RichTextRun[] _rtRuns;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a Text Run from a Unicode text block
|
* Constructs a Text Run from a Unicode text block
|
||||||
*
|
*
|
||||||
* @param tha the TextHeaderAtom that defines what's what
|
* @param tha the TextHeaderAtom that defines what's what
|
||||||
* @param tca the TextCharsAtom containing the text
|
* @param tca the TextCharsAtom containing the text
|
||||||
|
* @param sta the StyleTextPropAtom which defines the character stylings
|
||||||
*/
|
*/
|
||||||
public TextRun(TextHeaderAtom tha, TextCharsAtom tca, StyleTextPropAtom sta) {
|
public TextRun(TextHeaderAtom tha, TextCharsAtom tca, StyleTextPropAtom sta) {
|
||||||
_headerAtom = tha;
|
this(tha,null,tca,sta);
|
||||||
_charAtom = tca;
|
|
||||||
_styleAtom = sta;
|
|
||||||
_isUnicode = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,12 +59,136 @@ public class TextRun
|
|||||||
*
|
*
|
||||||
* @param tha the TextHeaderAtom that defines what's what
|
* @param tha the TextHeaderAtom that defines what's what
|
||||||
* @param tba the TextBytesAtom containing the text
|
* @param tba the TextBytesAtom containing the text
|
||||||
|
* @param sta the StyleTextPropAtom which defines the character stylings
|
||||||
*/
|
*/
|
||||||
public TextRun(TextHeaderAtom tha, TextBytesAtom tba, StyleTextPropAtom sta) {
|
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;
|
_headerAtom = tha;
|
||||||
_byteAtom = tba;
|
|
||||||
_styleAtom = sta;
|
_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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,36 +220,16 @@ public class TextRun
|
|||||||
return _byteAtom.getText();
|
return _byteAtom.getText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the text. Chance are, this won't work just yet, because
|
* Fetch the rich text runs (runs of text with the same styling) that
|
||||||
* we also need to update some other bits of the powerpoint file
|
* are contained within this block of text
|
||||||
* to match the change in the Text Atom, especially byte offsets
|
* @return
|
||||||
*/
|
*/
|
||||||
public void setText(String s) {
|
public RichTextRun[] getRichTextRuns() {
|
||||||
// If size changed, warn
|
return _rtRuns;
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the text, from the TextHeaderAtom.
|
* Returns the type of the text, from the TextHeaderAtom.
|
||||||
* Possible values can be seen from TextHeaderAtom
|
* Possible values can be seen from TextHeaderAtom
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -64,6 +64,11 @@ public class StyleTextPropAtom extends RecordAtom
|
|||||||
*/
|
*/
|
||||||
private LinkedList paragraphStyles;
|
private LinkedList paragraphStyles;
|
||||||
public LinkedList getParagraphStyles() { return 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.
|
* The list of all the different character stylings we code for.
|
||||||
* Each entry is a TextPropCollection, which tells you how many
|
* Each entry is a TextPropCollection, which tells you how many
|
||||||
@ -72,6 +77,11 @@ public class StyleTextPropAtom extends RecordAtom
|
|||||||
*/
|
*/
|
||||||
private LinkedList charStyles;
|
private LinkedList charStyles;
|
||||||
public LinkedList getCharacterStyles() { return 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 */
|
/** All the different kinds of paragraph properties we might handle */
|
||||||
public TextProp[] paragraphTextPropTypes = new TextProp[] {
|
public TextProp[] paragraphTextPropTypes = new TextProp[] {
|
||||||
@ -370,6 +380,14 @@ public class StyleTextPropAtom extends RecordAtom
|
|||||||
reservedField = -1;
|
reservedField = -1;
|
||||||
textPropList = new LinkedList();
|
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
|
* Writes out to disk the header, and then all the properties
|
||||||
|
@ -30,10 +30,11 @@ import java.io.OutputStream;
|
|||||||
* @author Nick Burch
|
* @author Nick Burch
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class TextHeaderAtom extends RecordAtom
|
public class TextHeaderAtom extends RecordAtom implements ParentAwareRecord
|
||||||
{
|
{
|
||||||
private byte[] _header;
|
private byte[] _header;
|
||||||
private static long _type = 3999l;
|
private static long _type = 3999l;
|
||||||
|
private RecordContainer parentRecord;
|
||||||
|
|
||||||
public static final int TITLE_TYPE = 0;
|
public static final int TITLE_TYPE = 0;
|
||||||
public static final int BODY_TYPE = 1;
|
public static final int BODY_TYPE = 1;
|
||||||
@ -49,6 +50,9 @@ public class TextHeaderAtom extends RecordAtom
|
|||||||
|
|
||||||
public int getTextType() { return textType; }
|
public int getTextType() { return textType; }
|
||||||
public void setTextType(int type) { textType = type; }
|
public void setTextType(int type) { textType = type; }
|
||||||
|
|
||||||
|
public RecordContainer getParentRecord() { return parentRecord; }
|
||||||
|
public void setParentRecord(RecordContainer record) { this.parentRecord = record; }
|
||||||
|
|
||||||
/* *************** record code follows ********************** */
|
/* *************** record code follows ********************** */
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,9 @@ import org.apache.poi.util.LittleEndian;
|
|||||||
import org.apache.poi.hslf.*;
|
import org.apache.poi.hslf.*;
|
||||||
import org.apache.poi.hslf.model.*;
|
import org.apache.poi.hslf.model.*;
|
||||||
import org.apache.poi.hslf.record.FontCollection;
|
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.Record;
|
||||||
|
import org.apache.poi.hslf.record.RecordContainer;
|
||||||
import org.apache.poi.hslf.record.RecordTypes;
|
import org.apache.poi.hslf.record.RecordTypes;
|
||||||
import org.apache.poi.hslf.record.SlideAtom;
|
import org.apache.poi.hslf.record.SlideAtom;
|
||||||
import org.apache.poi.hslf.record.SlideListWithText;
|
import org.apache.poi.hslf.record.SlideListWithText;
|
||||||
@ -78,6 +80,11 @@ public class SlideShow
|
|||||||
_hslfSlideShow = hslfSlideShow;
|
_hslfSlideShow = hslfSlideShow;
|
||||||
_records = _hslfSlideShow.getRecords();
|
_records = _hslfSlideShow.getRecords();
|
||||||
byte[] _docstream = _hslfSlideShow.getUnderlyingBytes();
|
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
|
// Find the versions of the core records we'll want to use
|
||||||
findMostRecentCoreRecords();
|
findMostRecentCoreRecords();
|
||||||
@ -85,6 +92,32 @@ public class SlideShow
|
|||||||
// Build up the model level Slides and Notes
|
// Build up the model level Slides and Notes
|
||||||
buildSlidesAndNotes();
|
buildSlidesAndNotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user