fixed HSLF text handling

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/common_sl@1677496 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2015-05-03 23:42:42 +00:00
parent f0d95c3f8d
commit 02e70fdbfe
11 changed files with 252 additions and 162 deletions

View File

@ -490,27 +490,7 @@ public class XSLFTextParagraph implements TextParagraph<XSLFTextRun> {
tabStops.addNewTab().setPos(Units.toEMU(value)); tabStops.addNewTab().setPos(Units.toEMU(value));
} }
/** @Override
* This element specifies the vertical line spacing that is to be used within a paragraph.
* This may be specified in two different ways, percentage spacing and font point spacing:
* <p>
* If linespacing >= 0, then linespacing is a percentage of normal line height
* If linespacing < 0, the absolute value of linespacing is the spacing in points
* </p>
* Examples:
* <pre><code>
* // spacing will be 120% of the size of the largest text on each line
* paragraph.setLineSpacing(120);
*
* // spacing will be 200% of the size of the largest text on each line
* paragraph.setLineSpacing(200);
*
* // spacing will be 48 points
* paragraph.setLineSpacing(-48.0);
* </code></pre>
*
* @param linespacing the vertical line spacing
*/
public void setLineSpacing(double linespacing){ public void setLineSpacing(double linespacing){
CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); CTTextSpacing spc = CTTextSpacing.Factory.newInstance();
@ -519,16 +499,7 @@ public class XSLFTextParagraph implements TextParagraph<XSLFTextRun> {
pr.setLnSpc(spc); pr.setLnSpc(spc);
} }
/** @Override
* Returns the vertical line spacing that is to be used within a paragraph.
* This may be specified in two different ways, percentage spacing and font point spacing:
* <p>
* If linespacing >= 0, then linespacing is a percentage of normal line height.
* If linespacing < 0, the absolute value of linespacing is the spacing in points
* </p>
*
* @return the vertical line spacing.
*/
public double getLineSpacing(){ public double getLineSpacing(){
ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
public boolean fetch(CTTextParagraphProperties props){ public boolean fetch(CTTextParagraphProperties props){

View File

@ -82,13 +82,9 @@ public abstract class BitMaskTextProp extends TextProp implements Cloneable {
* Set the true/false status of the subproperty with the given index * Set the true/false status of the subproperty with the given index
*/ */
public void setSubValue(boolean value, int idx) { public void setSubValue(boolean value, int idx) {
if(subPropMatches[idx] == value) { return; } if (subPropMatches[idx] == value) return;
if(value) {
dataValue += subPropMasks[idx];
} else {
dataValue -= subPropMasks[idx];
}
subPropMatches[idx] = value; subPropMatches[idx] = value;
dataValue ^= subPropMasks[idx];
} }
@Override @Override
@ -101,4 +97,8 @@ public abstract class BitMaskTextProp extends TextProp implements Cloneable {
return newObj; return newObj;
} }
public BitMaskTextProp cloneAll(){
return (BitMaskTextProp)super.clone();
}
} }

View File

@ -160,14 +160,9 @@ public class TextPropCollection {
this.reservedField = other.reservedField; this.reservedField = other.reservedField;
this.textPropList.clear(); this.textPropList.clear();
for (TextProp tp : other.textPropList) { for (TextProp tp : other.textPropList) {
TextProp tpCopy = tp.clone(); TextProp tpCopy = (tp instanceof BitMaskTextProp)
if (tpCopy instanceof BitMaskTextProp) { ? ((BitMaskTextProp)tp).cloneAll()
BitMaskTextProp bmt = (BitMaskTextProp)tpCopy; : tp.clone();
boolean matches[] = ((BitMaskTextProp)tp).getSubPropMatches();
for (int i=0; i<matches.length; i++) {
bmt.setSubValue(matches[i], i);
}
}
this.textPropList.add(tpCopy); this.textPropList.add(tpCopy);
} }
} }
@ -183,7 +178,7 @@ public class TextPropCollection {
/** /**
* Writes out to disk the header, and then all the properties * Writes out to disk the header, and then all the properties
*/ */
public void writeOut(OutputStream o) throws IOException { public void writeOut(OutputStream o, TextProp[] potentialProperties) throws IOException {
// First goes the number of characters we affect // First goes the number of characters we affect
StyleTextPropAtom.writeLittleEndian(charactersCovered,o); StyleTextPropAtom.writeLittleEndian(charactersCovered,o);
@ -194,10 +189,8 @@ public class TextPropCollection {
// Then the mask field // Then the mask field
int mask = maskSpecial; int mask = maskSpecial;
for(int i=0; i<textPropList.size(); i++) { for(TextProp textProp : textPropList) {
TextProp textProp = textPropList.get(i);
//sometimes header indicates that the bitmask is present but its value is 0 //sometimes header indicates that the bitmask is present but its value is 0
if (textProp instanceof BitMaskTextProp) { if (textProp instanceof BitMaskTextProp) {
if(mask == 0) mask |= textProp.getWriteMask(); if(mask == 0) mask |= textProp.getWriteMask();
} }
@ -208,16 +201,21 @@ public class TextPropCollection {
StyleTextPropAtom.writeLittleEndian(mask,o); StyleTextPropAtom.writeLittleEndian(mask,o);
// Then the contents of all the properties // Then the contents of all the properties
for(int i=0; i<textPropList.size(); i++) { for (TextProp potProp : potentialProperties) {
TextProp textProp = textPropList.get(i); for(TextProp textProp : textPropList) {
if (!textProp.getName().equals(potProp.getName())) continue;
int val = textProp.getValue(); int val = textProp.getValue();
if(textProp.getSize() == 2) { if (textProp instanceof BitMaskTextProp && val == 0) {
// don't add empty properties, as they can't be recognized while reading
continue;
} else if (textProp.getSize() == 2) {
StyleTextPropAtom.writeLittleEndian((short)val,o); StyleTextPropAtom.writeLittleEndian((short)val,o);
} else if (textProp.getSize() == 4) { } else if (textProp.getSize() == 4) {
StyleTextPropAtom.writeLittleEndian(val,o); StyleTextPropAtom.writeLittleEndian(val,o);
} }
} }
} }
}
public short getReservedField(){ public short getReservedField(){
return reservedField; return reservedField;

View File

@ -17,15 +17,12 @@
package org.apache.poi.hslf.record; package org.apache.poi.hslf.record;
import java.io.ByteArrayOutputStream; import java.io.*;
import java.io.IOException; import java.util.ArrayList;
import java.io.OutputStream; import java.util.List;
import java.util.LinkedList;
import org.apache.poi.hslf.model.textproperties.*; import org.apache.poi.hslf.model.textproperties.*;
import org.apache.poi.util.HexDump; import org.apache.poi.util.*;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogger;
/** /**
* A StyleTextPropAtom (type 4001). Holds basic character properties * A StyleTextPropAtom (type 4001). Holds basic character properties
@ -64,26 +61,26 @@ public final class StyleTextPropAtom extends RecordAtom
* Characters the paragraph covers, and also contains the TextProps * Characters the paragraph covers, and also contains the TextProps
* that actually define the styling of the paragraph. * that actually define the styling of the paragraph.
*/ */
private LinkedList<TextPropCollection> paragraphStyles; private List<TextPropCollection> paragraphStyles;
public LinkedList<TextPropCollection> getParagraphStyles() { return paragraphStyles; } public List<TextPropCollection> getParagraphStyles() { return paragraphStyles; }
/** /**
* Updates the link list of TextPropCollections which make up the * Updates the link list of TextPropCollections which make up the
* paragraph stylings * paragraph stylings
*/ */
public void setParagraphStyles(LinkedList<TextPropCollection> ps) { paragraphStyles = ps; } public void setParagraphStyles(List<TextPropCollection> 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
* Characters the character styling covers, and also contains the * Characters the character styling covers, and also contains the
* TextProps that actually define the styling of the characters. * TextProps that actually define the styling of the characters.
*/ */
private LinkedList<TextPropCollection> charStyles; private List<TextPropCollection> charStyles;
public LinkedList<TextPropCollection> getCharacterStyles() { return charStyles; } public List<TextPropCollection> getCharacterStyles() { return charStyles; }
/** /**
* Updates the link list of TextPropCollections which make up the * Updates the link list of TextPropCollections which make up the
* character stylings * character stylings
*/ */
public void setCharacterStyles(LinkedList<TextPropCollection> cs) { charStyles = cs; } public void setCharacterStyles(List<TextPropCollection> cs) { charStyles = cs; }
/** /**
* Returns how many characters the paragraph's * Returns how many characters the paragraph's
@ -105,7 +102,7 @@ public final class StyleTextPropAtom extends RecordAtom
public int getCharacterTextLengthCovered() { public int getCharacterTextLengthCovered() {
return getCharactersCovered(charStyles); return getCharactersCovered(charStyles);
} }
private int getCharactersCovered(LinkedList<TextPropCollection> styles) { private int getCharactersCovered(List<TextPropCollection> styles) {
int length = 0; int length = 0;
for(TextPropCollection tpc : styles) { for(TextPropCollection tpc : styles) {
length += tpc.getCharactersCovered(); length += tpc.getCharactersCovered();
@ -197,9 +194,9 @@ public final class StyleTextPropAtom extends RecordAtom
System.arraycopy(source,start+8,rawContents,0,rawContents.length); System.arraycopy(source,start+8,rawContents,0,rawContents.length);
reserved = new byte[0]; reserved = new byte[0];
// Set empty linked lists, ready for when they call setParentTextSize // Set empty lists, ready for when they call setParentTextSize
paragraphStyles = new LinkedList<TextPropCollection>(); paragraphStyles = new ArrayList<TextPropCollection>();
charStyles = new LinkedList<TextPropCollection>(); charStyles = new ArrayList<TextPropCollection>();
} }
@ -217,8 +214,8 @@ public final class StyleTextPropAtom extends RecordAtom
LittleEndian.putInt(_header,4,10); LittleEndian.putInt(_header,4,10);
// Set empty paragraph and character styles // Set empty paragraph and character styles
paragraphStyles = new LinkedList<TextPropCollection>(); paragraphStyles = new ArrayList<TextPropCollection>();
charStyles = new LinkedList<TextPropCollection>(); charStyles = new ArrayList<TextPropCollection>();
TextPropCollection defaultParagraphTextProps = TextPropCollection defaultParagraphTextProps =
new TextPropCollection(parentTextSize, (short)0); new TextPropCollection(parentTextSize, (short)0);
@ -377,13 +374,13 @@ public final class StyleTextPropAtom extends RecordAtom
// First up, we need to serialise the paragraph properties // First up, we need to serialise the paragraph properties
for(int i=0; i<paragraphStyles.size(); i++) { for(int i=0; i<paragraphStyles.size(); i++) {
TextPropCollection tpc = paragraphStyles.get(i); TextPropCollection tpc = paragraphStyles.get(i);
tpc.writeOut(baos); tpc.writeOut(baos, paragraphTextPropTypes);
} }
// Now, we do the character ones // Now, we do the character ones
for(int i=0; i<charStyles.size(); i++) { for(int i=0; i<charStyles.size(); i++) {
TextPropCollection tpc = charStyles.get(i); TextPropCollection tpc = charStyles.get(i);
tpc.writeOut(baos); tpc.writeOut(baos, characterTextPropTypes);
} }
rawContents = baos.toByteArray(); rawContents = baos.toByteArray();
@ -454,7 +451,7 @@ public final class StyleTextPropAtom extends RecordAtom
try { try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
pr.writeOut(baos); pr.writeOut(baos, paragraphTextPropTypes);
byte[] b = baos.toByteArray(); byte[] b = baos.toByteArray();
out.append(HexDump.dump(b, 0, 0)); out.append(HexDump.dump(b, 0, 0));
} catch (Exception e ) { } catch (Exception e ) {
@ -475,7 +472,7 @@ public final class StyleTextPropAtom extends RecordAtom
try { try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
pr.writeOut(baos); pr.writeOut(baos, characterTextPropTypes);
byte[] b = baos.toByteArray(); byte[] b = baos.toByteArray();
out.append(HexDump.dump(b, 0, 0)); out.append(HexDump.dump(b, 0, 0));
} catch (Exception e ) { } catch (Exception e ) {

View File

@ -270,7 +270,6 @@ public abstract class HSLFSheet implements Sheet<HSLFShape,HSLFSlideShow> {
* @param shape * @param shape
*/ */
protected void onAddTextShape(HSLFTextShape shape) { protected void onAddTextShape(HSLFTextShape shape) {
} }
/** /**

View File

@ -553,30 +553,22 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
return getParaTextPropVal("bullet.font"); return getParaTextPropVal("bullet.font");
} }
/** @Override
* Sets the line spacing. public void setLineSpacing(double lineSpacing) {
* <p> // if lineSpacing < 0, we need to convert points (common interface) to master units (hslf)
* If linespacing >= 0, then linespacing is a percentage of normal line height. if (lineSpacing < 0) {
* If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates. lineSpacing = (lineSpacing*HSLFShape.MASTER_DPI/HSLFShape.POINT_DPI);
* </p> }
*/ setParaTextPropVal("linespacing", (int)lineSpacing);
public void setLineSpacing(int val) {
setParaTextPropVal("linespacing", val);
} }
/**
* Returns the line spacing
* <p>
* If linespacing >= 0, then linespacing is a percentage of normal line height.
* If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates.
* </p>
*
* @return the spacing between lines
*/
@Override @Override
public double getLineSpacing() { public double getLineSpacing() {
int val = getParaTextPropVal("linespacing"); double val = getParaTextPropVal("linespacing");
return val == -1 ? 0 : val; // if lineSpacing < 0, we need to convert master units (hslf) to points (common interface)
if (val == -1) return 0;
if (val < -1) val *= HSLFShape.POINT_DPI/((double)HSLFShape.MASTER_DPI);
return val;
} }
/** /**
@ -722,6 +714,12 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
throw new RuntimeException("child record not found - malformed container record"); throw new RuntimeException("child record not found - malformed container record");
} }
cr[idx] = newRecord; cr[idx] = newRecord;
if (newRecord == byteAtom) {
charAtom = null;
} else {
byteAtom = null;
}
} }
// Ensure a StyleTextPropAtom is present, adding if required // Ensure a StyleTextPropAtom is present, adding if required
@ -750,6 +748,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
} }
for (HSLFTextRun tr : para.getTextRuns()) { for (HSLFTextRun tr : para.getTextRuns()) {
TextPropCollection rtpc = tr.getCharacterStyle(); TextPropCollection rtpc = tr.getCharacterStyle();
rtpc.updateTextSize(0);
if (!rtpc.equals(lastRTPC)) { if (!rtpc.equals(lastRTPC)) {
lastRTPC = styleAtom.addCharacterTextPropCollection(0); lastRTPC = styleAtom.addCharacterTextPropCollection(0);
lastRTPC.copy(rtpc); lastRTPC.copy(rtpc);
@ -783,20 +782,44 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
* *
* @param text the text string used by this object. * @param text the text string used by this object.
*/ */
protected static void appendText(List<HSLFTextParagraph> paragraphs, String text, boolean newParagraph) { protected static HSLFTextRun appendText(List<HSLFTextParagraph> paragraphs, String text, boolean newParagraph) {
text = toInternalString(text); text = toInternalString(text);
// init paragraphs // check paragraphs
assert(!paragraphs.isEmpty()); assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty());
HSLFTextParagraph lastHTP = paragraphs.get(paragraphs.size()-1); HSLFTextParagraph htp = paragraphs.get(paragraphs.size()-1);
HSLFTextRun lastHTR = lastHTP.getTextRuns().get(lastHTP.getTextRuns().size()-1); HSLFTextRun htr = htp.getTextRuns().get(htp.getTextRuns().size()-1);
HSLFTextParagraph htp = (newParagraph) ? new HSLFTextParagraph(lastHTP) : lastHTP;
HSLFTextRun htr = new HSLFTextRun(htp); if (newParagraph) {
htr.setText(text); htr.setText(htr.getRawText()+"\n");
htr.getCharacterStyle().copy(lastHTR.getCharacterStyle()); }
boolean isFirst = !newParagraph;
for (String rawText : text.split("(?<=\r)")) {
if (!isFirst) {
TextPropCollection tpc = htp.getParagraphStyle();
HSLFTextParagraph prevHtp = htp;
htp = new HSLFTextParagraph(htp._headerAtom, htp._byteAtom, htp._charAtom, htp._styleAtom);
htp.getParagraphStyle().copy(tpc);
htp.setParentShape(prevHtp.getParentShape());
htp.setShapeId(prevHtp.getShapeId());
htp.supplySheet(prevHtp.getSheet());
paragraphs.add(htp);
isFirst = false;
}
TextPropCollection tpc = htr.getCharacterStyle();
// special case, last text run is empty, we will reuse it
if (htr.getLength() > 0) {
htr = new HSLFTextRun(htp);
htr.getCharacterStyle().copy(tpc);
htp.addTextRun(htr); htp.addTextRun(htr);
} }
htr.setText(rawText);
}
return htr;
}
/** /**
* Sets (overwrites) the current text. * Sets (overwrites) the current text.
@ -804,29 +827,30 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
* *
* @param text the text string used by this object. * @param text the text string used by this object.
*/ */
public static void setText(List<HSLFTextParagraph> paragraphs, String text) { public static HSLFTextRun setText(List<HSLFTextParagraph> paragraphs, String text) {
text = HSLFTextParagraph.toInternalString(text); text = HSLFTextParagraph.toInternalString(text);
// init paragraphs // check paragraphs
assert(!paragraphs.isEmpty()); assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty());
Iterator<HSLFTextParagraph> paraIter = paragraphs.iterator(); Iterator<HSLFTextParagraph> paraIter = paragraphs.iterator();
HSLFTextParagraph firstHTP = paraIter.next(); // keep first HSLFTextParagraph htp = paraIter.next(); // keep first
assert(firstHTP != null); assert(htp != null);
while (paraIter.hasNext()) { while (paraIter.hasNext()) {
paraIter.next(); paraIter.next();
paraIter.remove(); paraIter.remove();
} }
Iterator<HSLFTextRun> runIter = firstHTP.getTextRuns().iterator(); Iterator<HSLFTextRun> runIter = htp.getTextRuns().iterator();
HSLFTextRun firstHTR = runIter.next(); HSLFTextRun htr = runIter.next();
assert(firstHTR != null); htr.setText("");
assert(htr != null);
while (runIter.hasNext()) { while (runIter.hasNext()) {
runIter.next(); runIter.next();
runIter.remove(); runIter.remove();
} }
firstHTR.setText(text); return appendText(paragraphs, text, false);
} }
public static String getRawText(List<HSLFTextParagraph> paragraphs) { public static String getRawText(List<HSLFTextParagraph> paragraphs) {
@ -835,9 +859,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
for (HSLFTextRun r : p.getTextRuns()) { for (HSLFTextRun r : p.getTextRuns()) {
sb.append(r.getRawText()); sb.append(r.getRawText());
} }
sb.append("\r");
} }
sb.deleteCharAt(sb.length()-1); // remove last line break
return sb.toString(); return sb.toString();
} }
@ -947,7 +969,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
return paragraphCollection; return paragraphCollection;
} }
for (int slwtIndex = 0; recordIdx < records.length-2; slwtIndex++) { for (int slwtIndex = 0; recordIdx < records.length; slwtIndex++) {
List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>(); List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>();
paragraphCollection.add(paragraphs); paragraphCollection.add(paragraphs);
@ -985,12 +1007,14 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
if (tbytes == null && tchars == null) { if (tbytes == null && tchars == null) {
tbytes = new TextBytesAtom(); tbytes = new TextBytesAtom();
header.getParentRecord().addChildAfter(tbytes, header);
logger.log(POILogger.INFO, "bytes nor chars atom doesn't exist. Creating dummy record for later saving."); logger.log(POILogger.INFO, "bytes nor chars atom doesn't exist. Creating dummy record for later saving.");
} }
String rawText = (tchars != null) ? tchars.getText() : tbytes.getText(); String rawText = (tchars != null) ? tchars.getText() : tbytes.getText();
for (String para : rawText.split("\r")) { // split, but keep delimiter
for (String para : rawText.split("(?<=\r)")) {
HSLFTextParagraph tpara = new HSLFTextParagraph(header, tbytes, tchars, styles); HSLFTextParagraph tpara = new HSLFTextParagraph(header, tbytes, tchars, styles);
paragraphs.add(tpara); paragraphs.add(tpara);
tpara.setStyleTextProp9Atom(styleTextProp9Atom); tpara.setStyleTextProp9Atom(styleTextProp9Atom);
@ -1021,29 +1045,21 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) { protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) {
int paraIdx = 0, runIdx = 0; int paraIdx = 0, runIdx = 0;
for (TextPropCollection p : charStyles) { HSLFTextRun trun;
for (int csIdx=0; csIdx<charStyles.size(); csIdx++) {
TextPropCollection p = charStyles.get(csIdx);
for (int ccRun = 0, ccStyle = p.getCharactersCovered(); ccRun < ccStyle; ) { for (int ccRun = 0, ccStyle = p.getCharactersCovered(); ccRun < ccStyle; ) {
HSLFTextParagraph para = paragraphs.get(paraIdx); HSLFTextParagraph para = paragraphs.get(paraIdx);
List<HSLFTextRun> runs = para.getTextRuns(); List<HSLFTextRun> runs = para.getTextRuns();
HSLFTextRun trun = runs.get(runIdx); trun = runs.get(runIdx);
int len = trun.getLength(); int len = trun.getLength();
if (runIdx+1 >= runs.size()) {
// need to add +1 to the last run of the paragraph
len++;
}
TextPropCollection pCopy = new TextPropCollection(1);
pCopy.copy(p);
if (ccRun+len <= ccStyle) { if (ccRun+len <= ccStyle) {
trun.setCharacterStyle(pCopy);
pCopy.updateTextSize(len);
ccRun += len; ccRun += len;
} else { } else {
String text = trun.getRawText(); String text = trun.getRawText();
trun.setText(text.substring(0,ccStyle-ccRun)); trun.setText(text.substring(0,ccStyle-ccRun));
pCopy.updateTextSize(ccStyle-ccRun);
trun.setCharacterStyle(pCopy);
HSLFTextRun nextRun = new HSLFTextRun(para); HSLFTextRun nextRun = new HSLFTextRun(para);
nextRun.setText(text.substring(ccStyle-ccRun)); nextRun.setText(text.substring(ccStyle-ccRun));
@ -1052,8 +1068,27 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
ccRun += ccStyle-ccRun; ccRun += ccStyle-ccRun;
} }
// need to compare it again, in case a run has been added afer TextPropCollection pCopy = new TextPropCollection(0);
if (++runIdx >= runs.size()) { pCopy.copy(p);
trun.setCharacterStyle(pCopy);
len = trun.getLength();
if (paraIdx == paragraphs.size()-1 && runIdx == runs.size()-1) {
if (csIdx < charStyles.size()-1) {
// special case, empty trailing text run
HSLFTextRun nextRun = new HSLFTextRun(para);
nextRun.setText("");
runs.add(nextRun);
} else {
// need to add +1 to the last run of the last paragraph
len++;
ccRun++;
}
}
pCopy.updateTextSize(len);
// need to compare it again, in case a run has been added after
if (++runIdx == runs.size()) {
paraIdx++; paraIdx++;
runIdx = 0; runIdx = 0;
} }
@ -1065,16 +1100,18 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
int paraIdx = 0; int paraIdx = 0;
for (TextPropCollection p : paraStyles) { for (TextPropCollection p : paraStyles) {
for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) { for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) {
HSLFTextParagraph para = paragraphs.get(paraIdx); if (paraIdx >= paragraphs.size() || ccPara >= ccStyle-1) return;
TextPropCollection pCopy = new TextPropCollection(1); HSLFTextParagraph htp = paragraphs.get(paraIdx);
TextPropCollection pCopy = new TextPropCollection(0);
pCopy.copy(p); pCopy.copy(p);
htp.setParagraphStyle(pCopy);
int len = 0; int len = 0;
for (HSLFTextRun trun : para.getTextRuns()) { for (HSLFTextRun trun : htp.getTextRuns()) {
len += trun.getLength(); len += trun.getLength();
} }
pCopy.updateTextSize(len+1); if (paraIdx == paragraphs.size()-1) len++;
para.setParagraphStyle(pCopy); pCopy.updateTextSize(len);
ccPara += len+1; ccPara += len;
} }
} }
} }
@ -1101,15 +1138,28 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFTextRun> {
tha.setParentRecord(wrapper); tha.setParentRecord(wrapper);
wrapper.appendChildRecord(tha); wrapper.appendChildRecord(tha);
TextCharsAtom tca = new TextCharsAtom(); TextBytesAtom tba = new TextBytesAtom();
wrapper.appendChildRecord(tca); tba.setText("\r".getBytes());
wrapper.appendChildRecord(tba);
StyleTextPropAtom sta = new StyleTextPropAtom(0); StyleTextPropAtom sta = new StyleTextPropAtom(1);
TextPropCollection paraStyle = sta.addParagraphTextPropCollection(1);
TextPropCollection charStyle = sta.addCharacterTextPropCollection(1);
wrapper.appendChildRecord(sta); wrapper.appendChildRecord(sta);
HSLFTextParagraph htp = new HSLFTextParagraph(tha, null, tca, sta); HSLFTextParagraph htp = new HSLFTextParagraph(tha, tba, null, sta);
htp.setParagraphStyle(paraStyle);
htp._records = new Record[0]; htp._records = new Record[0];
htp.setBullet(false);
htp.setLineSpacing(100);
htp.setLeftMargin(0);
htp.setIndent(0);
// set wrap flags
HSLFTextRun htr = new HSLFTextRun(htp); HSLFTextRun htr = new HSLFTextRun(htp);
htr.setCharacterStyle(charStyle);
htr.setText("\r");
htr.setFontColor(Color.black);
htp.addTextRun(htr); htp.addTextRun(htr);
return Arrays.asList(htp); return Arrays.asList(htp);

View File

@ -37,14 +37,14 @@ public final class HSLFTextRun implements TextRun {
/** The TextRun we belong to */ /** The TextRun we belong to */
private HSLFTextParagraph parentParagraph; private HSLFTextParagraph parentParagraph;
private String _runText = "\r"; private String _runText = "";
private String _fontname; private String _fontname;
/** /**
* Our paragraph and character style. * Our paragraph and character style.
* Note - we may share these styles with other RichTextRuns * Note - we may share these styles with other RichTextRuns
*/ */
private TextPropCollection characterStyle = new TextPropCollection(1); private TextPropCollection characterStyle = new TextPropCollection(0);
/** /**
* Create a new wrapper around a rich text string * Create a new wrapper around a rich text string

View File

@ -22,14 +22,11 @@ import static org.apache.poi.hslf.record.RecordTypes.*;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import org.apache.poi.POIXMLException;
import org.apache.poi.ddf.*; import org.apache.poi.ddf.*;
import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.record.*;
import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawTextShape; import org.apache.poi.sl.draw.DrawTextShape;
@ -139,8 +136,12 @@ public abstract class HSLFTextShape extends HSLFSimpleShape implements TextShape
protected void afterInsert(HSLFSheet sh){ protected void afterInsert(HSLFSheet sh){
super.afterInsert(sh); super.afterInsert(sh);
storeText();
EscherTextboxWrapper _txtbox = getEscherTextboxWrapper(); EscherTextboxWrapper _txtbox = getEscherTextboxWrapper();
if(_txtbox != null){ if(_txtbox != null){
_escherContainer.addChildRecord(_txtbox.getEscherRecord());
PPDrawing ppdrawing = sh.getPPDrawing(); PPDrawing ppdrawing = sh.getPPDrawing();
ppdrawing.addTextboxWrapper(_txtbox); ppdrawing.addTextboxWrapper(_txtbox);
// Ensure the escher layer knows about the added records // Ensure the escher layer knows about the added records
@ -712,10 +713,10 @@ public abstract class HSLFTextShape extends HSLFSimpleShape implements TextShape
* *
* @param text the text string used by this object. * @param text the text string used by this object.
*/ */
public void appendText(String text, boolean newParagraph) { public HSLFTextRun appendText(String text, boolean newParagraph) {
// init paragraphs // init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs(); List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextParagraph.appendText(paras, text, newParagraph); return HSLFTextParagraph.appendText(paras, text, newParagraph);
} }
/** /**
@ -723,12 +724,15 @@ public abstract class HSLFTextShape extends HSLFSimpleShape implements TextShape
* Uses the properties of the first paragraph / textrun * Uses the properties of the first paragraph / textrun
* *
* @param text the text string used by this object. * @param text the text string used by this object.
*
* @return the last text run of the splitted text
*/ */
public void setText(String text) { public HSLFTextRun setText(String text) {
// init paragraphs // init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs(); List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextParagraph.setText(paras, text); HSLFTextRun htr = HSLFTextParagraph.setText(paras, text);
setTextId(text.hashCode()); setTextId(text.hashCode());
return htr;
} }
/** /**

View File

@ -104,8 +104,8 @@ public class DrawTextShape<T extends TextShape<? extends TextParagraph<? extends
// negative value means the absolute spacing in points // negative value means the absolute spacing in points
y += -spaceBefore; y += -spaceBefore;
} }
isFirstLine = false;
} }
isFirstLine = false;
dp.setPosition(x, y); dp.setPosition(x, y);
dp.draw(graphics); dp.draw(graphics);

View File

@ -113,7 +113,7 @@ public interface TextParagraph<T extends TextRun> extends Iterable<T> {
* This may be specified in two different ways, percentage spacing and font point spacing: * This may be specified in two different ways, percentage spacing and font point spacing:
* <p> * <p>
* If spaceBefore >= 0, then space is a percentage of normal line height. * If spaceBefore >= 0, then space is a percentage of normal line height.
* If spaceBefore < 0, the absolute value of linespacing is the spacing in points * If spaceBefore < 0, the absolute value in points
* </p> * </p>
* *
* @return the vertical white space before the paragraph * @return the vertical white space before the paragraph
@ -175,6 +175,29 @@ public interface TextParagraph<T extends TextRun> extends Iterable<T> {
*/ */
double getLineSpacing(); double getLineSpacing();
/**
* This element specifies the vertical line spacing that is to be used within a paragraph.
* This may be specified in two different ways, percentage spacing and font point spacing:
* <p>
* If linespacing >= 0, then linespacing is a percentage of normal line height
* If linespacing < 0, the absolute value of linespacing is the spacing in points
* </p>
* Examples:
* <pre><code>
* // spacing will be 120% of the size of the largest text on each line
* paragraph.setLineSpacing(120);
*
* // spacing will be 200% of the size of the largest text on each line
* paragraph.setLineSpacing(200);
*
* // spacing will be 48 points
* paragraph.setLineSpacing(-48.0);
* </code></pre>
*
* @param linespacing the vertical line spacing
*/
void setLineSpacing(double lineSpacing);
String getDefaultFontFamily(); String getDefaultFontFamily();
/** /**

View File

@ -20,6 +20,8 @@ package org.apache.poi.hslf.model;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.*; import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -136,6 +138,48 @@ public final class TestShapes {
} }
} }
@Test
public void testParagraphs() throws Exception {
HSLFSlideShow ppt = new HSLFSlideShow();
HSLFSlide slide = ppt.createSlide();
HSLFTextBox shape = new HSLFTextBox();
HSLFTextRun p1r1 = shape.setText("para 1 run 1. ");
HSLFTextRun p1r2 = shape.appendText("para 1 run 2.", false);
HSLFTextRun p2r1 = shape.appendText("para 2 run 1. ", true);
HSLFTextRun p2r2 = shape.appendText("para 2 run 2. ", false);
p1r1.setFontColor(Color.black);
p1r2.setFontColor(Color.red);
p2r1.setFontColor(Color.yellow);
p2r2.setStrikethrough(true);
// run 3 has same text properties as run 2 and will be merged when saving
HSLFTextRun p2r3 = shape.appendText("para 2 run 3.", false);
shape.setAnchor(new Rectangle2D.Double(100,100,100,10));
slide.addShape(shape);
shape.resizeToFitText();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ppt.write(bos);
ppt = new HSLFSlideShow(new ByteArrayInputStream(bos.toByteArray()));
slide = ppt.getSlides().get(0);
HSLFTextBox tb = (HSLFTextBox)slide.getShapes().get(0);
List<HSLFTextParagraph> para = tb.getTextParagraphs();
HSLFTextRun tr = para.get(0).getTextRuns().get(0);
assertEquals("para 1 run 1. ", tr.getRawText());
assertEquals(Color.black, tr.getFontColor());
tr = para.get(0).getTextRuns().get(1);
assertEquals("para 1 run 2.\r", tr.getRawText());
assertEquals(Color.red, tr.getFontColor());
tr = para.get(1).getTextRuns().get(0);
assertEquals("para 2 run 1. ", tr.getRawText());
assertEquals(Color.yellow, tr.getFontColor());
tr = para.get(1).getTextRuns().get(1);
assertEquals("para 2 run 2. para 2 run 3.", tr.getRawText());
assertEquals(Color.black, tr.getFontColor());
assertTrue(tr.isStrikethrough());
}
/** /**
* Verify that we can add TextBox shapes to a slide * Verify that we can add TextBox shapes to a slide
* and set some of the style attributes * and set some of the style attributes
@ -235,7 +279,11 @@ public final class TestShapes {
for (HSLFShape sh : sld.getShapes()) { for (HSLFShape sh : sld.getShapes()) {
if (sh instanceof HSLFTextShape){ if (sh instanceof HSLFTextShape){
HSLFTextShape tbox = (HSLFTextShape)sh; HSLFTextShape tbox = (HSLFTextShape)sh;
lst2.add(tbox.getText()); for (HSLFTextParagraph p : tbox.getTextParagraphs()) {
for (HSLFTextRun r : p) {
lst2.add(r.getRawText());
}
}
} }
} }
assertTrue(lst1.containsAll(lst2)); assertTrue(lst1.containsAll(lst2));