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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@ -93,36 +220,16 @@ public class TextRun
|
||||
return _byteAtom.getText();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of the text, from the 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;
|
||||
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[] {
|
||||
@ -370,6 +380,14 @@ public class StyleTextPropAtom extends RecordAtom
|
||||
reservedField = -1;
|
||||
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
|
||||
|
@ -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;
|
||||
@ -49,6 +50,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 ********************** */
|
||||
|
||||
|
@ -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.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;
|
||||
@ -78,6 +80,11 @@ public class SlideShow
|
||||
_hslfSlideShow = hslfSlideShow;
|
||||
_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();
|
||||
@ -85,6 +92,32 @@ public class SlideShow
|
||||
// Build up the model level Slides and Notes
|
||||
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