diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java index a7d7a97d8..48acd57e5 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java @@ -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 diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ParentAwareRecord.java b/src/scratchpad/src/org/apache/poi/hslf/record/ParentAwareRecord.java new file mode 100644 index 000000000..8f13e1a1a --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ParentAwareRecord.java @@ -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); +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java index b3cd71324..4290273b6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java @@ -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 diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TextHeaderAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/TextHeaderAtom.java index 4f5ba7b35..7dddc529e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/TextHeaderAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/TextHeaderAtom.java @@ -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 ********************** */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java new file mode 100644 index 000000000..ebe9becd6 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java @@ -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; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 2d8887bd2..de85bf908 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -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