diff --git a/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java b/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java index d95f970ce..0032bc809 100644 --- a/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java +++ b/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java @@ -39,8 +39,8 @@ public final class BulletsDemo { HSLFTextParagraph rt = shape.getTextParagraphs().get(0); rt.getTextRuns().get(0).setFontSize(42); rt.setBullet(true); - rt.setIndent(0); //bullet offset - rt.setLeftMargin(50); //text offset (should be greater than bullet offset) + rt.setIndent(0d); //bullet offset + rt.setLeftMargin(50d); //text offset (should be greater than bullet offset) rt.setBulletChar('\u263A'); //bullet character shape.setText( "January\r" + diff --git a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java index 373f01f33..91366b9d0 100644 --- a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java +++ b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java @@ -48,8 +48,8 @@ public class Tutorial2 { XSLFTextParagraph p2 = shape1.addNewTextParagraph(); // 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 - p2.setSpaceBefore(-20); // 20 pt from the previous paragraph - p2.setSpaceAfter(300); // 3 lines after the paragraph + p2.setSpaceBefore(-20d); // 20 pt from the previous paragraph + p2.setSpaceAfter(300d); // 3 lines after the paragraph XSLFTextRun r2 = p2.addNewTextRun(); r2.setText("Paragraph properties apply to all text residing within the corresponding paragraph."); r2.setFontSize(16); @@ -62,8 +62,8 @@ public class Tutorial2 { r3.setFontColor(new Color(85, 142, 213)); XSLFTextParagraph p4 = shape1.addNewTextParagraph(); - p4.setSpaceBefore(-20); // 20 pt from the previous paragraph - p4.setSpaceAfter(300); // 3 lines after the paragraph + p4.setSpaceBefore(-20d); // 20 pt from the previous paragraph + p4.setSpaceAfter(300d); // 3 lines after the paragraph XSLFTextRun r4 = p4.addNewTextRun(); r4.setFontSize(16); r4.setText( diff --git a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java index a80f23cad..95252d72f 100644 --- a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java +++ b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java @@ -45,9 +45,9 @@ public class Tutorial7 { XSLFTextParagraph p2 = shape.addNewTextParagraph(); // indentation before text - p2.setLeftMargin(60); + p2.setLeftMargin(60d); // the bullet is set 40 pt before the text - p2.setIndent(-40); + p2.setIndent(-40d); p2.setBullet(true); // customize bullets p2.setBulletFontColor(Color.red); diff --git a/src/java/org/apache/poi/util/Units.java b/src/java/org/apache/poi/util/Units.java index bc643fd97..107a9b583 100644 --- a/src/java/org/apache/poi/util/Units.java +++ b/src/java/org/apache/poi/util/Units.java @@ -16,6 +16,8 @@ ==================================================================== */ package org.apache.poi.util; +import org.apache.poi.hslf.usermodel.HSLFShape; + /** * @author Yegor Kozlov */ @@ -23,6 +25,22 @@ public class Units { public static final int EMU_PER_PIXEL = 9525; public static final int EMU_PER_POINT = 12700; + /** + * Master DPI (576 pixels per inch). + * Used by the reference coordinate system in PowerPoint (HSLF) + */ + public static final int MASTER_DPI = 576; + + /** + * Pixels DPI (96 pixels per inch) + */ + public static final int PIXEL_DPI = 96; + + /** + * Points DPI (72 pixels per inch) + */ + public static final int POINT_DPI = 72; + /** * Converts points to EMUs * @param points points @@ -70,4 +88,17 @@ public class Units { int fixedPoint = (i << 16) | (f & 0xFFFF); return fixedPoint; } + + public static double masterToPoints(int masterDPI) { + double points = masterDPI; + points *= HSLFShape.POINT_DPI; + points /= HSLFShape.MASTER_DPI; + return points; + } + + public static int pointsToMaster(double points) { + points *= HSLFShape.MASTER_DPI; + points /= HSLFShape.POINT_DPI; + return (int)points; + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java index aef03197d..f0cf3a78a 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -137,8 +137,10 @@ public class XSLFTextParagraph implements TextParagraph { /** * Returns the alignment that is applied to the paragraph. * - * If this attribute is omitted, then a value of left is implied. - * @return ??? alignment that is applied to the paragraph + * If this attribute is omitted, then null is returned. + * User code can imply the value {@link TextAlign#LEFT} then. + * + * @return alignment that is applied to the paragraph */ public TextAlign getTextAlign(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ @@ -152,7 +154,7 @@ public class XSLFTextParagraph implements TextParagraph { } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? TextAlign.LEFT : fetcher.getValue(); + return fetcher.getValue(); } /** @@ -184,7 +186,7 @@ public class XSLFTextParagraph implements TextParagraph { } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? FontAlign.AUTO : fetcher.getValue(); + return fetcher.getValue(); } /** @@ -294,7 +296,7 @@ public class XSLFTextParagraph implements TextParagraph { * * @return the bullet size */ - public double getBulletFontSize(){ + public Double getBulletFontSize(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetBuSzPct()){ @@ -309,7 +311,7 @@ public class XSLFTextParagraph implements TextParagraph { } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? 100 : fetcher.getValue(); + return fetcher.getValue(); } /** @@ -334,27 +336,19 @@ public class XSLFTextParagraph implements TextParagraph { } } - /** - * Specifies the indent size that will be applied to the first line of text in the paragraph. - * - * @param value the indent in points. - */ @Override - public void setIndent(double value){ + public void setIndent(Double indent){ + if (indent == null && !_p.isSetPPr()) return; CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(value == -1) { + if(indent == -1) { if(pr.isSetIndent()) pr.unsetIndent(); } else { - pr.setIndent(Units.toEMU(value)); + pr.setIndent(Units.toEMU(indent)); } } - /** - * - * @return the indent applied to the first line of text in the paragraph. - */ @Override - public double getIndent(){ + public Double getIndent() { ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ @@ -367,32 +361,26 @@ public class XSLFTextParagraph implements TextParagraph { }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + return fetcher.getValue(); } - /** - * Specifies the left margin of the paragraph. This is specified in addition to the text body - * inset and applies only to this text paragraph. That is the text body Inset and the LeftMargin - * attributes are additive with respect to the text position. - * - * @param value the left margin (in points) of the paragraph - */ @Override - public void setLeftMargin(double value){ + public void setLeftMargin(Double leftMargin){ + if (leftMargin == null && !_p.isSetPPr()) return; CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(value == -1) { + if (leftMargin == null) { if(pr.isSetMarL()) pr.unsetMarL(); } else { - pr.setMarL(Units.toEMU(value)); + pr.setMarL(Units.toEMU(leftMargin)); } } /** - * @return the left margin (in points) of the paragraph + * @return the left margin (in points) of the paragraph, null if unset */ @Override - public double getLeftMargin(){ + public Double getLeftMargin(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetMarL()){ @@ -405,32 +393,26 @@ public class XSLFTextParagraph implements TextParagraph { }; fetchParagraphProperty(fetcher); // if the marL attribute is omitted, then a value of 347663 is implied - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + return fetcher.getValue(); } - /** - * Specifies the right margin of the paragraph. This is specified in addition to the text body - * inset and applies only to this text paragraph. That is the text body Inset and the RightMargin - * attributes are additive with respect to the text position. - * - * @param value the right margin (in points) of the paragraph - */ @Override - public void setRightMargin(double value){ + public void setRightMargin(Double rightMargin){ + if (rightMargin == null && !_p.isSetPPr()) return; CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(value == -1) { + if(rightMargin == -1) { if(pr.isSetMarR()) pr.unsetMarR(); } else { - pr.setMarR(Units.toEMU(value)); + pr.setMarR(Units.toEMU(rightMargin)); } } /** * - * @return the right margin of the paragraph + * @return the right margin of the paragraph, null if unset */ @Override - public double getRightMargin(){ + public Double getRightMargin(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetMarR()){ @@ -443,14 +425,11 @@ public class XSLFTextParagraph implements TextParagraph { }; fetchParagraphProperty(fetcher); // if the marL attribute is omitted, then a value of 347663 is implied - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + return fetcher.getValue(); } - /** - * - * @return the default size for a tab character within this paragraph in points - */ - public double getDefaultTabSize(){ + @Override + public Double getDefaultTabSize(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetDefTabSz()){ @@ -462,7 +441,7 @@ public class XSLFTextParagraph implements TextParagraph { } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + return fetcher.getValue(); } public double getTabStop(final int idx){ @@ -491,16 +470,25 @@ public class XSLFTextParagraph implements TextParagraph { } @Override - public void setLineSpacing(double linespacing){ + public void setLineSpacing(Double lineSpacing){ + if (lineSpacing == null && !_p.isSetPPr()) return; CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); - if(linespacing >= 0) spc.addNewSpcPct().setVal((int)(linespacing*1000)); - else spc.addNewSpcPts().setVal((int)(-linespacing*100)); - pr.setLnSpc(spc); + if(lineSpacing == null) { + if (pr.isSetLnSpc()) pr.unsetLnSpc(); + } else { + CTTextSpacing spc = (pr.isSetLnSpc()) ? pr.getLnSpc() : pr.addNewLnSpc(); + if (lineSpacing >= 0) { + (spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct()).setVal((int)(lineSpacing*1000)); + if (spc.isSetSpcPts()) spc.unsetSpcPts(); + } else { + (spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts()).setVal((int)(-lineSpacing*100)); + if (spc.isSetSpcPct()) spc.unsetSpcPct(); + } + } } @Override - public double getLineSpacing(){ + public Double getLineSpacing(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetLnSpc()){ @@ -515,8 +503,8 @@ public class XSLFTextParagraph implements TextParagraph { }; fetchParagraphProperty(fetcher); - double lnSpc = fetcher.getValue() == null ? 100 : fetcher.getValue(); - if(lnSpc > 0) { + Double lnSpc = fetcher.getValue(); + if (lnSpc != null && lnSpc > 0) { // check if the percentage value is scaled CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit(); if(normAutofit != null) { @@ -528,26 +516,9 @@ public class XSLFTextParagraph implements TextParagraph { return lnSpc; } - /** - * Set the amount of vertical white space that will be present before the paragraph. - * This space is specified in either percentage or points: - *

- * 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 - *

- * Examples: - *

-     *      // The paragraph will be formatted to have a spacing before the paragraph text.
-     *      // The spacing will be 200% of the size of the largest text on each line
-     *      paragraph.setSpaceBefore(200);
-     *
-     *      // The spacing will be a size of 48 points
-     *      paragraph.setSpaceBefore(-48.0);
-     * 
- * - * @param spaceBefore the vertical white space before the paragraph. - */ - public void setSpaceBefore(double spaceBefore){ + @Override + public void setSpaceBefore(Double spaceBefore){ + if (spaceBefore == null && !_p.isSetPPr()) return; CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); if(spaceBefore >= 0) spc.addNewSpcPct().setVal((int)(spaceBefore*1000)); @@ -555,17 +526,8 @@ public class XSLFTextParagraph implements TextParagraph { pr.setSpcBef(spc); } - /** - * The amount of vertical white space before the paragraph - * This may be specified in two different ways, percentage spacing and font point spacing: - *

- * 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 - *

- * - * @return the vertical white space before the paragraph - */ - public double getSpaceBefore(){ + @Override + public Double getSpaceBefore(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetSpcBef()){ @@ -580,30 +542,11 @@ public class XSLFTextParagraph implements TextParagraph { }; fetchParagraphProperty(fetcher); - double spcBef = fetcher.getValue() == null ? 0 : fetcher.getValue(); - return spcBef; + return fetcher.getValue(); } - /** - * Set the amount of vertical white space that will be present after the paragraph. - * This space is specified in either percentage or points: - *

- * If spaceAfter >= 0, then space is a percentage of normal line height. - * If spaceAfter < 0, the absolute value of linespacing is the spacing in points - *

- * Examples: - *

-     *      // The paragraph will be formatted to have a spacing after the paragraph text.
-     *      // The spacing will be 200% of the size of the largest text on each line
-     *      paragraph.setSpaceAfter(200);
-     *
-     *      // The spacing will be a size of 48 points
-     *      paragraph.setSpaceAfter(-48.0);
-     * 
- * - * @param spaceAfter the vertical white space after the paragraph. - */ - public void setSpaceAfter(double spaceAfter){ + @Override + public void setSpaceAfter(Double spaceAfter){ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); if(spaceAfter >= 0) spc.addNewSpcPct().setVal((int)(spaceAfter*1000)); @@ -611,17 +554,8 @@ public class XSLFTextParagraph implements TextParagraph { pr.setSpcAft(spc); } - /** - * The amount of vertical white space after the paragraph - * This may be specified in two different ways, percentage spacing and font point spacing: - *

- * 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 - *

- * - * @return the vertical white space after the paragraph - */ - public double getSpaceAfter(){ + @Override + public Double getSpaceAfter(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ if(props.isSetSpcAft()){ @@ -635,7 +569,7 @@ public class XSLFTextParagraph implements TextParagraph { } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + return fetcher.getValue(); } /** @@ -647,7 +581,6 @@ public class XSLFTextParagraph implements TextParagraph { */ public void setLevel(int level){ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - pr.setLvl(level); } @@ -657,10 +590,7 @@ public class XSLFTextParagraph implements TextParagraph { */ public int getLevel(){ CTTextParagraphProperties pr = _p.getPPr(); - if(pr == null) return 0; - - return pr.getLvl(); - + return (pr == null || !pr.isSetLvl()) ? 0 : pr.getLvl(); } /** @@ -721,53 +651,70 @@ public class XSLFTextParagraph implements TextParagraph { } - CTTextParagraphProperties getDefaultMasterStyle(){ + /* package */ CTTextParagraphProperties getDefaultMasterStyle(){ CTPlaceholder ph = _shape.getCTPlaceholder(); - String defaultStyleSelector; - if(ph == null) defaultStyleSelector = "otherStyle"; // no placeholder means plain text box - else { - switch(ph.getType().intValue()){ - case STPlaceholderType.INT_TITLE: - case STPlaceholderType.INT_CTR_TITLE: - defaultStyleSelector = "titleStyle"; - break; - case STPlaceholderType.INT_FTR: - case STPlaceholderType.INT_SLD_NUM: - case STPlaceholderType.INT_DT: - defaultStyleSelector = "otherStyle"; - break; - default: - defaultStyleSelector = "bodyStyle"; - break; - } + String defaultStyleSelector; + switch(ph == null ? -1 : ph.getType().intValue()) { + case STPlaceholderType.INT_TITLE: + case STPlaceholderType.INT_CTR_TITLE: + defaultStyleSelector = "titleStyle"; + break; + case -1: // no placeholder means plain text box + case STPlaceholderType.INT_FTR: + case STPlaceholderType.INT_SLD_NUM: + case STPlaceholderType.INT_DT: + defaultStyleSelector = "otherStyle"; + break; + default: + defaultStyleSelector = "bodyStyle"; + break; } int level = getLevel(); // wind up and find the root master sheet which must be slide master XSLFSheet masterSheet = _shape.getSheet(); - while (masterSheet.getMasterSheet() != null){ - masterSheet = (XSLFSheet)masterSheet.getMasterSheet(); + for (XSLFSheet m = masterSheet; m != null; m = (XSLFSheet)m.getMasterSheet()) { + masterSheet = m; } - XmlObject[] o = masterSheet.getXmlObject().selectPath( - "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " + - "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " + - ".//p:txStyles/p:" + defaultStyleSelector +"/a:lvl" +(level+1)+ "pPr"); - if (o.length == 1){ - return (CTTextParagraphProperties)o[0]; - } else { - o = masterSheet.getXmlObject().selectPath( - "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " + - "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " + - ".//p:notesStyle/a:lvl" +(level+1)+ "pPr"); - - if (o.length == 1){ + String nsDecl = + "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " + + "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' "; + String xpaths[] = { + nsDecl+".//p:txStyles/p:" + defaultStyleSelector +"/a:lvl" +(level+1)+ "pPr", + nsDecl+".//p:notesStyle/a:lvl" +(level+1)+ "pPr" + }; + XmlObject xo = masterSheet.getXmlObject(); + for (String xpath : xpaths) { + XmlObject[] o = xo.selectPath(xpath); + if (o.length == 1) { return (CTTextParagraphProperties)o[0]; } - - throw new IllegalArgumentException("Failed to fetch default style for " + - defaultStyleSelector + " and level=" + level); } + +// for (CTTextBody txBody : (CTTextBody[])xo.selectPath(nsDecl+".//p:txBody")) { +// CTTextParagraphProperties defaultPr = null, lastPr = null; +// boolean hasLvl = false; +// for (CTTextParagraph p : txBody.getPArray()) { +// CTTextParagraphProperties pr = p.getPPr(); +// if (pr.isSetLvl()) { +// hasLvl |= true; +// lastPr = pr; +// if (pr.getLvl() == level) return pr; +// } else { +// defaultPr = pr; +// } +// } +// if (!hasLvl) continue; +// if (level == 0 && defaultPr != null) return defaultPr; +// if (lastPr != null) return lastPr; +// break; +// } +// +// String err = "Failed to fetch default style for " + defaultStyleSelector + " and level=" + level; +// throw new IllegalArgumentException(err); + + return null; } private boolean fetchParagraphProperty(ParagraphPropertyFetcher visitor){ @@ -860,9 +807,9 @@ public class XSLFTextParagraph implements TextParagraph { } @Override - public double getDefaultFontSize() { + public Double getDefaultFontSize() { CTTextCharacterProperties endPr = _p.getEndParaRPr(); - return (endPr == null || !endPr.isSetSz()) ? 12 : (endPr.getSz() / 100); + return (endPr == null || !endPr.isSetSz()) ? 12 : (endPr.getSz() / 100.); } @Override @@ -871,6 +818,7 @@ public class XSLFTextParagraph implements TextParagraph { } public BulletStyle getBulletStyle() { + if (!isBullet()) return null; return new BulletStyle(){ public String getBulletCharacter() { return XSLFTextParagraph.this.getBulletCharacter(); @@ -880,7 +828,7 @@ public class XSLFTextParagraph implements TextParagraph { return XSLFTextParagraph.this.getBulletFont(); } - public double getBulletFontSize() { + public Double getBulletFontSize() { return XSLFTextParagraph.this.getBulletFontSize(); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index 2b63a5809..d99df7e79 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -17,26 +17,11 @@ package org.apache.poi.xslf.usermodel; import java.awt.Color; -import java.awt.font.FontRenderContext; -import java.awt.font.TextAttribute; -import java.awt.font.TextLayout; -import java.text.AttributedString; import org.apache.poi.sl.usermodel.TextRun; import org.apache.poi.util.Beta; import org.apache.poi.xslf.model.CharacterPropertyFetcher; -import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; -import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; -import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor; -import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeStyle; -import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.STSchemeColorVal; -import org.openxmlformats.schemas.drawingml.x2006.main.STTextStrikeType; -import org.openxmlformats.schemas.drawingml.x2006.main.STTextUnderlineType; +import org.openxmlformats.schemas.drawingml.x2006.main.*; import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; /** @@ -89,28 +74,6 @@ public class XSLFTextRun implements TextRun { return buf.toString(); } - /** - * Replace a tab with the effective number of white spaces. - */ - private String tab2space(){ - AttributedString string = new AttributedString(" "); - // user can pass an object to convert fonts via a rendering hint - string.addAttribute(TextAttribute.FAMILY, getFontFamily()); - - string.addAttribute(TextAttribute.SIZE, (float)getFontSize()); - TextLayout l = new TextLayout(string.getIterator(), new FontRenderContext(null, true, true)); - double wspace = l.getAdvance(); - - double tabSz = _p.getDefaultTabSize(); - - int numSpaces = (int)Math.ceil(tabSz / wspace); - StringBuffer buf = new StringBuffer(); - for(int i = 0; i < numSpaces; i++) { - buf.append(' '); - } - return buf.toString(); - } - public void setText(String text){ _r.setT(text); } @@ -175,9 +138,10 @@ public class XSLFTextRun implements TextRun { } /** - * @return font size in points or -1 if font size is not set. + * @return font size in points or null if font size is not set. */ - public double getFontSize(){ + @Override + public Double getFontSize(){ double scale = 1; CTTextNormalAutofit afit = getParentParagraph().getParentShape().getTextBodyPr().getNormAutofit(); if(afit != null) scale = (double)afit.getFontScale() / 100000; @@ -192,7 +156,7 @@ public class XSLFTextRun implements TextRun { } }; fetchCharacterProperty(fetcher); - return fetcher.getValue() == null ? -1 : fetcher.getValue()*scale; + return fetcher.getValue() == null ? null : fetcher.getValue()*scale; } /** @@ -514,7 +478,7 @@ public class XSLFTextRun implements TextRun { return new XSLFHyperlink(_r.getRPr().getHlinkClick(), this); } - private boolean fetchCharacterProperty(CharacterPropertyFetcher fetcher){ + private boolean fetchCharacterProperty(CharacterPropertyFetcher fetcher){ boolean ok = false; if(_r.isSetRPr()) ok = fetcher.fetch(getRPr()); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java index 6fc5690cc..b3c44f84a 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java @@ -125,7 +125,7 @@ public class TestXSLFAutoShape { p.setIndent(2.0); assertEquals(2.0, p.getIndent(), 0); assertTrue(p.getXmlObject().getPPr().isSetIndent()); - p.setIndent(-1); + p.setIndent(-1d); assertEquals(0.0, p.getIndent(), 0); assertFalse(p.getXmlObject().getPPr().isSetIndent()); p.setIndent(10.0); @@ -149,44 +149,44 @@ public class TestXSLFAutoShape { assertFalse(p.getXmlObject().getPPr().isSetSpcAft()); - p.setSpaceAfter(200); + p.setSpaceAfter(200d); assertEquals(200000, p.getXmlObject().getPPr().getSpcAft().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPts()); - p.setSpaceAfter(100); + p.setSpaceAfter(100d); assertEquals(100000, p.getXmlObject().getPPr().getSpcAft().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPts()); - p.setSpaceAfter(-20); + p.setSpaceAfter(-20d); assertEquals(2000, p.getXmlObject().getPPr().getSpcAft().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPct()); - p.setSpaceAfter(-10); + p.setSpaceAfter(-10d); assertEquals(1000, p.getXmlObject().getPPr().getSpcAft().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPct()); assertFalse(p.getXmlObject().getPPr().isSetSpcBef()); - p.setSpaceBefore(200); + p.setSpaceBefore(200d); assertEquals(200000, p.getXmlObject().getPPr().getSpcBef().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPts()); - p.setSpaceBefore(100); + p.setSpaceBefore(100d); assertEquals(100000, p.getXmlObject().getPPr().getSpcBef().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPts()); - p.setSpaceBefore(-20); + p.setSpaceBefore(-20d); assertEquals(2000, p.getXmlObject().getPPr().getSpcBef().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPct()); - p.setSpaceBefore(-10); + p.setSpaceBefore(-10d); assertEquals(1000, p.getXmlObject().getPPr().getSpcBef().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPct()); assertFalse(p.getXmlObject().getPPr().isSetLnSpc()); - p.setLineSpacing(200); + p.setLineSpacing(200d); assertEquals(200000, p.getXmlObject().getPPr().getLnSpc().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPts()); - p.setLineSpacing(100); + p.setLineSpacing(100d); assertEquals(100000, p.getXmlObject().getPPr().getLnSpc().getSpcPct().getVal()); assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPts()); - p.setLineSpacing(-20); + p.setLineSpacing(-20d); assertEquals(2000, p.getXmlObject().getPPr().getLnSpc().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPct()); - p.setLineSpacing(-10); + p.setLineSpacing(-10d); assertEquals(1000, p.getXmlObject().getPPr().getLnSpc().getSpcPts().getVal()); assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPct()); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java index 0998ee8e7..37a01a73a 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java @@ -92,7 +92,7 @@ public class TestXSLFTextParagraph { assertEquals(expectedWidth, dtp.getWrappingWidth(true, null), 0); assertEquals(expectedWidth, dtp.getWrappingWidth(false, null), 0); - p.setLeftMargin(36); // 0.5" + p.setLeftMargin(36d); // 0.5" leftMargin = p.getLeftMargin(); assertEquals(36.0, leftMargin, 0); expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/FontAlignmentProp.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/FontAlignmentProp.java index c9c911ccf..904feeedf 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/FontAlignmentProp.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/FontAlignmentProp.java @@ -21,12 +21,13 @@ package org.apache.poi.hslf.model.textproperties; * Definition for the font alignment property. */ public class FontAlignmentProp extends TextProp { + public static final String NAME = "fontAlign"; public static final int BASELINE = 0; public static final int TOP = 1; public static final int CENTER = 2; public static final int BOTTOM = 3; public FontAlignmentProp() { - super(2, 0x10000, "fontAlign"); + super(2, 0x10000, NAME); } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java index 17d0c1d2c..84cfe9940 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java @@ -31,47 +31,10 @@ import org.apache.poi.util.LittleEndian; * properties, and the indent level if required. */ public class TextPropCollection { - /* - private static TextProp paragraphSpecialPropTypes[] = { - new ParagraphFlagsTextProp(), - new TextProp(2, 0x80, "bullet.char"), - new TextProp(2, 0x10, "bullet.font"), - new TextProp(2, 0x40, "bullet.size"), - new TextProp(4, 0x20, "bullet.color"), - new TextProp(2, 0xD00, "alignment"), - new TextProp(2, 0x1000, "linespacing"), - new TextProp(2, 0x2000, "spacebefore"), - new TextProp(2, 0x4000, "spaceafter"), - new TextProp(2, 0x8000, "text.offset"), - new TextProp(2, 0x10000, "bullet.offset"), - new TextProp(2, 0x20000, "defaulttab"), - new TextProp(2, 0x40000, "para_unknown_2"), - new TextProp(2, 0x80000, "para_unknown_3"), - new TextProp(2, 0x100000, "para_unknown_4"), - new TextProp(2, 0x200000, "para_unknown_5") - }; - - private static TextProp characterSpecialPropTypes[] = { - new CharFlagsTextProp(), - new TextProp(2, 0x10000, "font.index"), - new TextProp(2, 0x20000, "char_unknown_1"), - new TextProp(4, 0x40000, "char_unknown_2"), - new TextProp(2, 0x80000, "font.size"), - new TextProp(2, 0x100000, "char_unknown_3"), - new TextProp(4, 0x200000, "font.color"), - new TextProp(2, 0x800000, "char_unknown_4") - }; -*/ - - /** All the different kinds of paragraph properties we might handle */ public static final TextProp[] paragraphTextPropTypes = { // TextProp order is according to 2.9.20 TextPFException, // bitmask order can be different -// new TextProp(0, 0x1, "hasBullet"), -// new TextProp(0, 0x2, "hasBulletFont"), -// new TextProp(0, 0x4, "hasBulletColor"), -// new TextProp(0, 0x8, "hasBulletSize"), new ParagraphFlagsTextProp(), new TextProp(2, 0x80, "bullet.char"), new TextProp(2, 0x10, "bullet.font"), @@ -95,24 +58,9 @@ public class TextPropCollection { new TextProp(0, 0x2000000, "hasBulletScheme"), // TODO: check size // 0xFC000000 MUST be zero and MUST be ignored }; + /** All the different kinds of character properties we might handle */ public static final TextProp[] characterTextPropTypes = new TextProp[] { -// new TextProp(0, 0x1, "bold"), -// new TextProp(0, 0x2, "italic"), -// new TextProp(0, 0x4, "underline"), -// new TextProp(0, 0x8, "unused1"), -// new TextProp(0, 0x10, "shadow"), -// new TextProp(0, 0x20, "fehint"), -// new TextProp(0, 0x40, "unused2"), -// new TextProp(0, 0x80, "kumi"), -// new TextProp(0, 0x100, "strikethrough"), -// new TextProp(0, 0x200, "emboss"), -// new TextProp(0, 0x400, "nibble1"), -// new TextProp(0, 0x800, "nibble2"), -// new TextProp(0, 0x1000, "nibble3"), -// new TextProp(0, 0x2000, "nibble4"), -// new TextProp(0, 0x4000, "unused4"), -// new TextProp(0, 0x8000, "unused5"), new TextProp(0, 0x100000, "pp10ext"), new TextProp(0, 0x1000000, "newAsian.font.index"), // A bit that specifies whether the newEAFontRef field of the TextCFException10 structure that contains this CFMasks exists. new TextProp(0, 0x2000000, "cs.font.index"), // A bit that specifies whether the csFontRef field of the TextCFException10 structure that contains this CFMasks exists. @@ -166,6 +114,19 @@ public class TextPropCollection { } return null; } + + public TextProp removeByName(String name) { + Iterator iter = textPropList.iterator(); + TextProp tp = null; + while (iter.hasNext()) { + tp = iter.next(); + if (tp.getName().equals(name)){ + iter.remove(); + break; + } + } + return tp; + } /** Add the TextProp with this name to the list */ public TextProp addWithName(String name) { @@ -192,6 +153,10 @@ public class TextPropCollection { return textProp; } + public TextPropType getTextPropType() { + return textPropType; + } + private TextProp[] getPotentialProperties() { return (textPropType == TextPropType.paragraph) ? paragraphTextPropTypes : characterTextPropTypes; } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/FontCollection.java b/src/scratchpad/src/org/apache/poi/hslf/record/FontCollection.java index 0f7f05148..b90c698dd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/FontCollection.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/FontCollection.java @@ -17,6 +17,7 @@ package org.apache.poi.hslf.record; +import org.apache.poi.hslf.model.PPFont; import org.apache.poi.util.POILogger; import java.io.*; @@ -75,9 +76,9 @@ public final class FontCollection extends RecordContainer { */ public int addFont(String name) { int idx = getFontIndex(name); - if(idx != -1) return idx; + if (idx != -1) return idx; - return addFont(name, 0, 0, 4, 34); + return addFont(name, 0, 0, 4, PPFont.FF_SWISS | PPFont.VARIABLE_PITCH); } public int addFont(String name, int charset, int flags, int type, int pitch) { diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java index a6dfedf07..346dab745 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java @@ -76,45 +76,31 @@ public final class HSLFSlideMaster extends HSLFMasterSheet { * This is the "workhorse" which returns the default style attrubutes. */ public TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) { - + if (_txmaster.length <= txtype) return null; + TxMasterStyleAtom t = _txmaster[txtype]; + List styles = isCharacter ? t.getCharacterStyles() : t.getParagraphStyles(); + TextProp prop = null; - for (int i = level; i >= 0; i--) { - List styles = - isCharacter ? _txmaster[txtype].getCharacterStyles() : _txmaster[txtype].getParagraphStyles(); - if (i < styles.size()) prop = styles.get(i).findByName(name); - if (prop != null) break; + for (int i = Math.min(level, styles.size()-1); prop == null && i >= 0; i--) { + prop = styles.get(i).findByName(name); } - if (prop == null) { - if(isCharacter) { - switch (txtype) { - case TextHeaderAtom.CENTRE_BODY_TYPE: - case TextHeaderAtom.HALF_BODY_TYPE: - case TextHeaderAtom.QUARTER_BODY_TYPE: - txtype = TextHeaderAtom.BODY_TYPE; - break; - case TextHeaderAtom.CENTER_TITLE_TYPE: - txtype = TextHeaderAtom.TITLE_TYPE; - break; - default: - return null; - } - } else { - switch (txtype) { - case TextHeaderAtom.CENTRE_BODY_TYPE: - case TextHeaderAtom.HALF_BODY_TYPE: - case TextHeaderAtom.QUARTER_BODY_TYPE: - txtype = TextHeaderAtom.BODY_TYPE; - break; - case TextHeaderAtom.CENTER_TITLE_TYPE: - txtype = TextHeaderAtom.TITLE_TYPE; - break; - default: - return null; - } - } - prop = getStyleAttribute(txtype, level, name, isCharacter); + + if (prop != null) return prop; + + switch (txtype) { + case TextHeaderAtom.CENTRE_BODY_TYPE: + case TextHeaderAtom.HALF_BODY_TYPE: + case TextHeaderAtom.QUARTER_BODY_TYPE: + txtype = TextHeaderAtom.BODY_TYPE; + break; + case TextHeaderAtom.CENTER_TITLE_TYPE: + txtype = TextHeaderAtom.TITLE_TYPE; + break; + default: + return null; } - return prop; + + return getStyleAttribute(txtype, level, name, isCharacter); } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java index c0d381e91..ee99745b5 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java @@ -34,7 +34,7 @@ import org.apache.poi.util.*; * This class represents a run of text in a powerpoint document. That * run could be text on a sheet, or text in a note. * It is only a very basic class for now - * + * * @author Nick Burch */ @@ -44,50 +44,49 @@ public final class HSLFTextParagraph implements TextParagraph { /** * How to align the text */ - /* package */ static final int AlignLeft = 0; - /* package */ static final int AlignCenter = 1; - /* package */ static final int AlignRight = 2; - /* package */ static final int AlignJustify = 3; - + /* package */static final int AlignLeft = 0; + /* package */static final int AlignCenter = 1; + /* package */static final int AlignRight = 2; + /* package */static final int AlignJustify = 3; // Note: These fields are protected to help with unit testing - // Other classes shouldn't really go playing with them! - private final TextHeaderAtom _headerAtom; - private TextBytesAtom _byteAtom; - private TextCharsAtom _charAtom; - private final TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph); + // Other classes shouldn't really go playing with them! + private final TextHeaderAtom _headerAtom; + private TextBytesAtom _byteAtom; + private TextCharsAtom _charAtom; + private final TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph); protected TextRulerAtom _ruler; - protected List _runs = new ArrayList(); - protected HSLFTextShape _parentShape; + protected final List _runs = new ArrayList(); + protected HSLFTextShape _parentShape; private HSLFSheet _sheet; private int shapeId; - // private StyleTextPropAtom styleTextPropAtom; - private StyleTextProp9Atom styleTextProp9Atom; + // private StyleTextPropAtom styleTextPropAtom; + private StyleTextProp9Atom styleTextProp9Atom; - /** + /** * Constructs a Text Run from a Unicode text block. * Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided. - * + * * @param tha the TextHeaderAtom that defines what's what * @param tba the TextBytesAtom containing the text or null if {@link TextCharsAtom} is provided * @param tca the TextCharsAtom containing the text or null if {@link TextBytesAtom} is provided - */ + */ /* package */ HSLFTextParagraph( TextHeaderAtom tha, TextBytesAtom tba, TextCharsAtom tca ) { - if (tha == null) { - throw new IllegalArgumentException("TextHeaderAtom must be set."); - } - _headerAtom = tha; + if (tha == null) { + throw new IllegalArgumentException("TextHeaderAtom must be set."); + } + _headerAtom = tha; _byteAtom = tba; _charAtom = tca; - } + } - /* package */ HSLFTextParagraph(HSLFTextParagraph other) { + /* package */HSLFTextParagraph(HSLFTextParagraph other) { _headerAtom = other._headerAtom; _byteAtom = other._byteAtom; _charAtom = other._charAtom; @@ -98,67 +97,67 @@ public final class HSLFTextParagraph implements TextParagraph { _paragraphStyle.copy(other._paragraphStyle); } - public void addTextRun(HSLFTextRun run) { - _runs.add(run); - } + public void addTextRun(HSLFTextRun run) { + _runs.add(run); + } - /** + /** * Fetch the rich text runs (runs of text with the same styling) that * are contained within this block of text - */ - public List getTextRuns() { - return _runs; - } + */ + public List getTextRuns() { + return _runs; + } - public TextPropCollection getParagraphStyle() { - return _paragraphStyle; - } + public TextPropCollection getParagraphStyle() { + return _paragraphStyle; + } - public void setParagraphStyle(TextPropCollection paragraphStyle) { - _paragraphStyle.copy(paragraphStyle); - } + public void setParagraphStyle(TextPropCollection paragraphStyle) { + _paragraphStyle.copy(paragraphStyle); + } - /** + /** * Supply the Sheet we belong to, which might have an assigned SlideShow * Also passes it on to our child RichTextRuns */ - public void supplySheet(HSLFSheet sheet){ + public void supplySheet(HSLFSheet sheet) { this._sheet = sheet; if (_runs == null) return; - for(HSLFTextRun rt : _runs) { + for (HSLFTextRun rt : _runs) { rt.updateSheet(); } - } + } - public HSLFSheet getSheet(){ + public HSLFSheet getSheet() { return this._sheet; } /** - * @return Shape ID + * @return Shape ID */ - protected int getShapeId(){ + protected int getShapeId() { return shapeId; } /** - * @param id Shape ID + * @param id Shape ID */ - protected void setShapeId(int id){ + protected void setShapeId(int id) { shapeId = id; } /** - * @return 0-based index of the text run in the SLWT container + * @return 0-based index of the text run in the SLWT container */ - protected int getIndex(){ + protected int getIndex() { return (_headerAtom != null) ? _headerAtom.getIndex() : -1; } /** * Sets the index of the paragraph in the SLWT container - * + * * @param index */ protected void setIndex(int index) { @@ -166,10 +165,10 @@ public final class HSLFTextParagraph implements TextParagraph { } /** - * Returns the type of the text, from the TextHeaderAtom. - * Possible values can be seen from TextHeaderAtom - * @see org.apache.poi.hslf.record.TextHeaderAtom - */ + * Returns the type of the text, from the TextHeaderAtom. + * Possible values can be seen from TextHeaderAtom + * @see org.apache.poi.hslf.record.TextHeaderAtom + */ public int getRunType() { return (_headerAtom != null) ? _headerAtom.getTextType() : -1; } @@ -177,8 +176,7 @@ public final class HSLFTextParagraph implements TextParagraph { public void setRunType(int runType) { if (_headerAtom != null) _headerAtom.setTextType(runType); } - - + /** * Is this Text Run one from a {@link PPDrawing}, or is it * one from the {@link SlideListWithText}? @@ -187,12 +185,12 @@ public final class HSLFTextParagraph implements TextParagraph { return (getIndex() == -1); } - public TextRulerAtom getTextRuler(){ + public TextRulerAtom getTextRuler() { return _ruler; } - public TextRulerAtom createTextRuler(){ + public TextRulerAtom createTextRuler() { _ruler = getTextRuler(); if (_ruler == null) { _ruler = TextRulerAtom.getParagraphInstance(); @@ -207,19 +205,19 @@ public final class HSLFTextParagraph implements TextParagraph { /** * Returns records that make up the list of text paragraphs * (there can be misc InteractiveInfo, TxInteractiveInfo and other records) - * + * * @return text run records */ - public Record[] getRecords(){ + public Record[] getRecords() { Record r[] = _headerAtom.getParentRecord().getChildRecords(); - return getRecords(r, new int[]{0}, _headerAtom); + return getRecords(r, new int[] { 0 }, _headerAtom); } private static Record[] getRecords(Record[] records, int[] startIdx, TextHeaderAtom headerAtom) { if (records == null) { throw new NullPointerException("records need to be set."); } - + for (; startIdx[0] < records.length; startIdx[0]++) { Record r = records[startIdx[0]]; if (r instanceof TextHeaderAtom && (headerAtom == null || r == headerAtom)) break; @@ -229,135 +227,113 @@ public final class HSLFTextParagraph implements TextParagraph { logger.log(POILogger.INFO, "header atom wasn't found - container might contain only an OutlineTextRefAtom"); return new Record[0]; } - + int length; - for (length = 1; startIdx[0]+length < records.length; length++) { + for (length = 1; startIdx[0] + length < records.length; length++) { if (records[startIdx[0]+length] instanceof TextHeaderAtom) break; } - + Record result[] = new Record[length]; System.arraycopy(records, startIdx[0], result, 0, length); startIdx[0] += length; - + return result; } - + /** Numbered List info */ public void setStyleTextProp9Atom(final StyleTextProp9Atom styleTextProp9Atom) { - this.styleTextProp9Atom = styleTextProp9Atom; - } + this.styleTextProp9Atom = styleTextProp9Atom; + } /** Numbered List info */ - public StyleTextProp9Atom getStyleTextProp9Atom() { - return this.styleTextProp9Atom; - } - - /** - * Fetch the value of the given Paragraph related TextProp. - * Returns -1 if that TextProp isn't present. - * If the TextProp isn't present, the value from the appropriate - * Master Sheet will apply. - */ - private int getParaTextPropVal(String propName) { - TextProp prop = _paragraphStyle.findByName(propName); - BitMaskTextProp maskProp = (BitMaskTextProp)_paragraphStyle.findByName(ParagraphFlagsTextProp.NAME); - boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); - if (prop == null && !hardAttribute){ - HSLFSheet sheet = getSheet(); - int txtype = getRunType(); - HSLFMasterSheet master = sheet.getMasterSheet(); - if (master != null) - prop = master.getStyleAttribute(txtype, getIndentLevel(), propName, false); - } - - return prop == null ? -1 : prop.getValue(); + public StyleTextProp9Atom getStyleTextProp9Atom() { + return this.styleTextProp9Atom; } - - /** - * Sets the value of the given Character TextProp, add if required - * @param propName The name of the Character TextProp - * @param val The value to set for the TextProp - */ - public void setParaTextPropVal(String propName, int val) { - // Ensure we have the StyleTextProp atom we're going to need - assert(_paragraphStyle!=null); - TextProp tp = fetchOrAddTextProp(_paragraphStyle, propName); - tp.setValue(val); - } - + @Override public Iterator iterator() { return _runs.iterator(); } @Override - public double getLeftMargin() { - int val = getParaTextPropVal("text.offset"); - return val*HSLFShape.POINT_DPI/((double)HSLFShape.MASTER_DPI); + public Double getLeftMargin() { + TextProp val = getPropVal(_paragraphStyle, "text.offset", this); + return (val == null) ? null : Units.masterToPoints(val.getValue()); } @Override - public void setLeftMargin(double leftMargin) { - int val = (int)(leftMargin*HSLFShape.MASTER_DPI/HSLFShape.POINT_DPI); - setParaTextPropVal("text.offset", val); + public void setLeftMargin(Double leftMargin) { + Integer val = (leftMargin == null) ? null : Units.pointsToMaster(leftMargin); + setPropVal(_paragraphStyle, "text.offset", val); } @Override - public double getRightMargin() { + public Double getRightMargin() { // TODO: find out, how to determine this value - return 0; + return null; } @Override - public void setRightMargin(double rightMargin) { + public void setRightMargin(Double rightMargin) { // TODO: find out, how to set this value } @Override - public double getIndent() { - int val = getParaTextPropVal("bullet.offset"); - return val*HSLFShape.POINT_DPI/((double)HSLFShape.MASTER_DPI); + public Double getIndent() { + TextProp val = getPropVal(_paragraphStyle, "bullet.offset", this); + return (val == null) ? null : Units.masterToPoints(val.getValue()); } @Override - public void setIndent(double intent) { - int val = (int)(intent*HSLFShape.MASTER_DPI/HSLFShape.POINT_DPI); - setParaTextPropVal("bullet.offset", val); + public void setIndent(Double indent) { + Integer val = (indent == null) ? null : Units.pointsToMaster(indent); + setPropVal(_paragraphStyle, "bullet.offset", val); } @Override public String getDefaultFontFamily() { - return (_runs.isEmpty() ? "Arial" : _runs.get(0).getFontFamily()); + String typeface = null; + if (!_runs.isEmpty()) { + typeface = _runs.get(0).getFontFamily(); + } + return (typeface != null) ? typeface : "Arial"; } @Override - public double getDefaultFontSize() { - return (_runs.isEmpty() ? 12 : _runs.get(0).getFontSize()); + public Double getDefaultFontSize() { + Double d = null; + if (!_runs.isEmpty()) { + d = _runs.get(0).getFontSize(); + } + + return (d != null) ? d : 12d; } /** * Sets the type of horizontal alignment for the paragraph. - * + * * @param align - the type of alignment */ public void setAlignment(org.apache.poi.sl.usermodel.TextParagraph.TextAlign align) { - int alignInt; - switch (align) { - default: - case LEFT: alignInt = TextAlignmentProp.LEFT; break; - case CENTER: alignInt = TextAlignmentProp.CENTER; break; - case RIGHT: alignInt = TextAlignmentProp.RIGHT; break; - case DIST: alignInt = TextAlignmentProp.DISTRIBUTED; break; - case JUSTIFY: alignInt = TextAlignmentProp.JUSTIFY; break; - case JUSTIFY_LOW: alignInt = TextAlignmentProp.JUSTIFYLOW; break; - case THAI_DIST: alignInt = TextAlignmentProp.THAIDISTRIBUTED; break; + Integer alignInt = null; + if (align != null) switch (align) { + default: + case LEFT: alignInt = TextAlignmentProp.LEFT;break; + case CENTER: alignInt = TextAlignmentProp.CENTER; break; + case RIGHT: alignInt = TextAlignmentProp.RIGHT; break; + case DIST: alignInt = TextAlignmentProp.DISTRIBUTED; break; + case JUSTIFY: alignInt = TextAlignmentProp.JUSTIFY; break; + case JUSTIFY_LOW: alignInt = TextAlignmentProp.JUSTIFYLOW; break; + case THAI_DIST: alignInt = TextAlignmentProp.THAIDISTRIBUTED; break; } - setParaTextPropVal("alignment", alignInt); + setPropVal(_paragraphStyle, "alignment", alignInt); } @Override public org.apache.poi.sl.usermodel.TextParagraph.TextAlign getTextAlign() { - switch (getParaTextPropVal("alignment")) { + TextProp tp = getPropVal(_paragraphStyle, "alignment", this); + if (tp == null) return null; + switch (tp.getValue()) { default: case TextAlignmentProp.LEFT: return TextAlign.LEFT; case TextAlignmentProp.CENTER: return TextAlign.CENTER; @@ -371,34 +347,33 @@ public final class HSLFTextParagraph implements TextParagraph { @Override public FontAlign getFontAlign() { - switch(getParaTextPropVal("fontAlign")) { - default: - case -1: return FontAlign.AUTO; + TextProp tp = getPropVal(_paragraphStyle, FontAlignmentProp.NAME, this); + if (tp == null) return null; + + switch (tp.getValue()) { case FontAlignmentProp.BASELINE: return FontAlign.BASELINE; case FontAlignmentProp.TOP: return FontAlign.TOP; case FontAlignmentProp.CENTER: return FontAlign.CENTER; case FontAlignmentProp.BOTTOM: return FontAlign.BOTTOM; + default: return FontAlign.AUTO; } } @Override public BulletStyle getBulletStyle() { - if (getBulletChar() == 0) return null; + if (!isBullet()) return null; return new BulletStyle() { public String getBulletCharacter() { - char chr = HSLFTextParagraph.this.getBulletChar(); - return (chr == 0 ? "" : ""+chr); + Character chr = HSLFTextParagraph.this.getBulletChar(); + return (chr == null || chr == 0) ? "" : "" + chr; } public String getBulletFont() { - int fontIdx = HSLFTextParagraph.this.getBulletFont(); - if (fontIdx == -1) return getDefaultFontFamily(); - PPFont ppFont = getSheet().getSlideShow().getFont(fontIdx); - return ppFont.getFontName(); + return HSLFTextParagraph.this.getBulletFont(); } - public double getBulletFontSize() { + public Double getBulletFontSize() { return HSLFTextParagraph.this.getBulletSize(); } @@ -417,666 +392,669 @@ public final class HSLFTextParagraph implements TextParagraph { _parentShape = parentShape; } - /** - * - * @return indentation level - */ - public int getIndentLevel() { - return _paragraphStyle == null ? 0 : _paragraphStyle.getIndentLevel(); - } - - /** - * Sets indentation level - * - * @param level indentation level. Must be in the range [0, 4] - */ - public void setIndentLevel(int level) { - if( _paragraphStyle != null ) _paragraphStyle.setIndentLevel((short)level); - } - - /** - * Sets whether this rich text run has bullets - */ - public void setBullet(boolean flag) { - setFlag(ParagraphFlagsTextProp.BULLET_IDX, flag); - } - - /** - * Returns whether this rich text run has bullets - */ - public boolean isBullet() { - return getFlag(ParagraphFlagsTextProp.BULLET_IDX); - } - - /** - * Returns whether this rich text run has bullets - */ - public boolean isBulletHard() { - return getFlag(ParagraphFlagsTextProp.BULLET_IDX); - } - - /** - * Sets the bullet character - */ - public void setBulletChar(char c) { - setParaTextPropVal("bullet.char", c); - } - - /** - * Returns the bullet character - */ - public char getBulletChar() { - int val = getParaTextPropVal("bullet.char"); - return (char)(val == -1 ? 0 : val); - } - - /** - * Sets the bullet size - */ - public void setBulletSize(int size) { - setParaTextPropVal("bullet.size", size); - } - - /** - * Returns the bullet size - */ - public int getBulletSize() { - return getParaTextPropVal("bullet.size"); - } - - /** - * Sets the bullet color - */ - public void setBulletColor(Color color) { - int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB(); - setParaTextPropVal("bullet.color", rgb); - } - - /** - * Returns the bullet color - */ - public Color getBulletColor() { - int rgb = getParaTextPropVal("bullet.color"); - if (rgb == -1) { - // if bullet color is undefined, return color of first run - if (_runs.isEmpty()) return null; - return _runs.get(0).getFontColor(); - } - - int cidx = rgb >> 24; - if (rgb % 0x1000000 == 0){ - if (_sheet == null) return null; - ColorSchemeAtom ca = _sheet.getColorScheme(); - if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx); - } - Color tmp = new Color(rgb, true); - return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); - } - - /** - * Sets the bullet font - */ - public void setBulletFont(int idx) { - setParaTextPropVal("bullet.font", idx); - setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, true); - } - - /** - * Returns the bullet font - */ - public int getBulletFont() { - return getParaTextPropVal("bullet.font"); - } - - @Override - public void setLineSpacing(double lineSpacing) { - // if lineSpacing < 0, we need to convert points (common interface) to master units (hslf) - if (lineSpacing < 0) { - lineSpacing = (lineSpacing*HSLFShape.MASTER_DPI/HSLFShape.POINT_DPI); - } - setParaTextPropVal("linespacing", (int)lineSpacing); - } - - @Override - public double getLineSpacing() { - double val = getParaTextPropVal("linespacing"); - // 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; - } - - /** - * Sets spacing before a paragraph. - *

- * If spacebefore >= 0, then spacebefore is a percentage of normal line height. - * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates. - *

- */ - public void setSpaceBefore(int val) { - setParaTextPropVal("spacebefore", val); - } - - /** - * Returns spacing before a paragraph - *

- * If spacebefore >= 0, then spacebefore is a percentage of normal line height. - * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates. - *

- * - * @return the spacing before a paragraph - */ - @Override - public double getSpaceBefore() { - int val = getParaTextPropVal("spacebefore"); - return val == -1 ? 0 : val; - } - - /** - * Sets spacing after a paragraph. - *

- * If spaceafter >= 0, then spaceafter is a percentage of normal line height. - * If spaceafter < 0, the absolute value of spaceafter is the spacing in master coordinates. - *

- */ - public void setSpaceAfter(int val) { - setParaTextPropVal("spaceafter", val); - } - - /** - * Returns spacing after a paragraph - *

- * If spaceafter >= 0, then spaceafter is a percentage of normal line height. - * If spaceafter < 0, the absolute value of spaceafter is the spacing in master coordinates. - *

- * - * @return the spacing before a paragraph - */ - @Override - public double getSpaceAfter() { - int val = getParaTextPropVal("spaceafter"); - return val == -1 ? 0 : val; - } - - /** - * Returns the named TextProp, either by fetching it (if it exists) or adding it - * (if it didn't) - * @param textPropCol The TextPropCollection to fetch from / add into - * @param textPropName The name of the TextProp to fetch/add - */ - protected static TextProp fetchOrAddTextProp(TextPropCollection textPropCol, String textPropName) { - // Fetch / Add the TextProp - return textPropCol.addWithName(textPropName); + * + * @return indentation level + */ + public int getIndentLevel() { + return _paragraphStyle == null ? 0 : _paragraphStyle.getIndentLevel(); } - protected boolean getFlag(int index) { - if (_paragraphStyle == null) return false; + /** + * Sets indentation level + * + * @param level indentation level. Must be in the range [0, 4] + */ + public void setIndentLevel(int level) { + if( _paragraphStyle != null ) _paragraphStyle.setIndentLevel((short)level); + } - BitMaskTextProp prop = (BitMaskTextProp) _paragraphStyle.findByName(ParagraphFlagsTextProp.NAME); + /** + * Sets whether this rich text run has bullets + */ + public void setBullet(boolean flag) { + setFlag(ParagraphFlagsTextProp.BULLET_IDX, flag); + } - if (prop == null) { - if (_sheet != null) { - int txtype = getRunType(); - HSLFMasterSheet master = _sheet.getMasterSheet(); - if (master != null) { - prop = (BitMaskTextProp) master.getStyleAttribute(txtype, getIndentLevel(), ParagraphFlagsTextProp.NAME, false); - } - } else { - logger.log(POILogger.WARN, "MasterSheet is not available"); + /** + * Returns whether this rich text run has bullets + */ + public boolean isBullet() { + return getFlag(ParagraphFlagsTextProp.BULLET_IDX); + } + + /** + * Sets the bullet character + */ + public void setBulletChar(Character c) { + Integer val = (c == null) ? null : (int)c.charValue(); + setPropVal(_paragraphStyle, "bullet.char", val); + } + + /** + * Returns the bullet character + */ + public Character getBulletChar() { + TextProp tp = getPropVal(_paragraphStyle, "bullet.char", this); + return (tp == null) ? null : (char)tp.getValue(); + } + + /** + * Sets the bullet size + */ + public void setBulletSize(Double size) { + setPctOrPoints("bullet.size", size); + } + + /** + * Returns the bullet size, null if unset + */ + public Double getBulletSize() { + return getPctOrPoints("bullet.size"); + } + + /** + * Sets the bullet color + */ + public void setBulletColor(Color color) { + Integer val = (color == null) ? null : new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB(); + setPropVal(_paragraphStyle, "bullet.color", val); + } + + /** + * Returns the bullet color + */ + public Color getBulletColor() { + TextProp tp = getPropVal(_paragraphStyle, "bullet.color", this); + if (tp == null) { + // if bullet color is undefined, return color of first run + return (_runs.isEmpty()) ? null : _runs.get(0).getFontColor(); + } + + int rgb = tp.getValue(); + int cidx = rgb >> 24; + if (rgb % 0x1000000 == 0) { + if (_sheet == null) + return null; + ColorSchemeAtom ca = _sheet.getColorScheme(); + if (cidx >= 0 && cidx <= 7) + rgb = ca.getColor(cidx); + } + Color tmp = new Color(rgb, true); + return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); + } + + /** + * Sets the bullet font + */ + public void setBulletFont(String typeface) { + if (typeface == null) { + setPropVal(_paragraphStyle, "bullet.font", null); + setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false); + } + + FontCollection fc = getSheet().getSlideShow().getFontCollection(); + int idx = fc.addFont(typeface); + + setPropVal(_paragraphStyle, "bullet.font", idx); + setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, true); + } + + /** + * Returns the bullet font + */ + public String getBulletFont() { + TextProp tp = getPropVal(_paragraphStyle, "bullet.font", this); + if (tp == null) return getDefaultFontFamily(); + PPFont ppFont = getSheet().getSlideShow().getFont(tp.getValue()); + assert(ppFont != null); + return ppFont.getFontName(); + } + + @Override + public void setLineSpacing(Double lineSpacing) { + setPctOrPoints("linespacing", lineSpacing); + } + + @Override + public Double getLineSpacing() { + return getPctOrPoints("linespacing"); + } + + @Override + public void setSpaceBefore(Double spaceBefore) { + setPctOrPoints("spacebefore", spaceBefore); + } + + @Override + public Double getSpaceBefore() { + return getPctOrPoints("spacebefore"); + } + + @Override + public void setSpaceAfter(Double spaceAfter) { + setPctOrPoints("spaceafter", spaceAfter); + } + + @Override + public Double getSpaceAfter() { + return getPctOrPoints("spaceafter"); + } + + @Override + public Double getDefaultTabSize() { + // TODO: implement + return null; + } + + private Double getPctOrPoints(String propName) { + TextProp tp = getPropVal(_paragraphStyle, propName, this); + if (tp == null) return null; + int val = tp.getValue(); + return (val < 0) ? Units.masterToPoints(val) : val; + } + + private void setPctOrPoints(String propName, Double dval) { + Integer ival = null; + if (dval != null) { + ival = (dval < 0) ? Units.pointsToMaster(dval) : dval.intValue(); + } + setPropVal(_paragraphStyle, propName, ival); + } + + private boolean getFlag(int index) { + BitMaskTextProp tp = (BitMaskTextProp)getPropVal(_paragraphStyle, ParagraphFlagsTextProp.NAME, this); + return (tp == null) ? false : tp.getSubValue(index); + } + + private void setFlag(int index, boolean value) { + BitMaskTextProp tp = (BitMaskTextProp)_paragraphStyle.addWithName(ParagraphFlagsTextProp.NAME); + tp.setSubValue(value, index); + } + + /** + * Fetch the value of the given Paragraph related TextProp. Returns null if + * that TextProp isn't present. If the TextProp isn't present, the value + * from the appropriate Master Sheet will apply. + */ + protected static TextProp getPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) { + TextProp prop = props.findByName(propName); + if (prop != null) return prop; + + BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); + boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); + if (hardAttribute) return null; + + HSLFSheet sheet = paragraph.getSheet(); + int txtype = paragraph.getRunType(); + HSLFMasterSheet master = sheet.getMasterSheet(); + if (master == null) { + logger.log(POILogger.WARN, "MasterSheet is not available"); + return null; + } + + boolean isChar = props.getTextPropType() == TextPropType.character; + return master.getStyleAttribute(txtype, paragraph.getIndentLevel(), propName, isChar); + } + + /** + * Returns the named TextProp, either by fetching it (if it exists) or + * adding it (if it didn't) + * + * @param props the TextPropCollection to fetch from / add into + * @param name the name of the TextProp to fetch/add + * @param val the value, null if unset + */ + protected static void setPropVal(TextPropCollection props, String name, Integer val) { + if (val == null) { + props.removeByName(name); + return; + } + + // Fetch / Add the TextProp + TextProp tp = props.addWithName(name); + tp.setValue(val); + } + + /** + * Check and add linebreaks to text runs leading other paragraphs + * + * @param paragraphs + */ + protected static void fixLineEndings(List paragraphs) { + HSLFTextRun lastRun = null; + for (HSLFTextParagraph p : paragraphs) { + if (lastRun != null && !lastRun.getRawText().endsWith("\r")) { + lastRun.setText(lastRun.getRawText() + "\r"); + } + List ltr = p.getTextRuns(); + if (ltr.isEmpty()) { + throw new RuntimeException("paragraph without textruns found"); + } + lastRun = ltr.get(ltr.size() - 1); + assert (lastRun.getRawText() != null); + } + } + + /** + * Search for a StyleTextPropAtom is for this text header (list of paragraphs) + * + * @param header the header + * @param textLen the length of the rawtext, or -1 if the length is not known + */ + private static StyleTextPropAtom findStyleAtomPresent(TextHeaderAtom header, int textLen) { + boolean afterHeader = false; + StyleTextPropAtom style = null; + for (Record record : header.getParentRecord().getChildRecords()) { + long rt = record.getRecordType(); + if (afterHeader && rt == RecordTypes.TextHeaderAtom.typeID) { + // already on the next header, quit searching + break; + } + afterHeader |= (header == record); + if (afterHeader && rt == RecordTypes.StyleTextPropAtom.typeID) { + // found it + style = (StyleTextPropAtom) record; } } - return prop == null ? false : prop.getSubValue(index); + if (style == null) { + logger.log(POILogger.INFO, "styles atom doesn't exist. Creating dummy record for later saving."); + style = new StyleTextPropAtom((textLen < 0) ? 1 : textLen); + } else { + if (textLen >= 0) { + style.setParentTextSize(textLen); + } + } + + return style; } - protected void setFlag(int index, boolean value) { - // Ensure we have the StyleTextProp atom we're going to need - assert(_paragraphStyle!=null); - BitMaskTextProp prop = (BitMaskTextProp) fetchOrAddTextProp(_paragraphStyle, ParagraphFlagsTextProp.NAME); - prop.setSubValue(value,index); - } + /** + * Saves the modified paragraphs/textrun to the records. + * Also updates the styles to the correct text length. + */ + protected static void storeText(List paragraphs) { + fixLineEndings(paragraphs); - /** - * Check and add linebreaks to text runs leading other paragraphs - * - * @param paragraphs - */ - protected static void fixLineEndings(List paragraphs) { - HSLFTextRun lastRun = null; - for (HSLFTextParagraph p : paragraphs) { - if (lastRun != null && !lastRun.getRawText().endsWith("\r")) { - lastRun.setText(lastRun.getRawText()+"\r"); - } - List ltr = p.getTextRuns(); - if (ltr.isEmpty()) { - throw new RuntimeException("paragraph without textruns found"); - } - lastRun = ltr.get(ltr.size()-1); - assert(lastRun.getRawText() != null); - } - } + String rawText = toInternalString(getRawText(paragraphs)); - /** - * Search for a StyleTextPropAtom is for this text header (list of paragraphs) - * - * @param header the header - * @param textLen the length of the rawtext, or -1 if the length is not known - */ - private static StyleTextPropAtom findStyleAtomPresent(TextHeaderAtom header, int textLen) { - boolean afterHeader = false; - StyleTextPropAtom style = null; - for (Record record : header.getParentRecord().getChildRecords()) { - long rt = record.getRecordType(); - if (afterHeader && rt == RecordTypes.TextHeaderAtom.typeID) { - // already on the next header, quit searching - break; - } - afterHeader |= (header == record); - if (afterHeader && rt == RecordTypes.StyleTextPropAtom.typeID) { - // found it - style = (StyleTextPropAtom)record; - } - } + // Will it fit in a 8 bit atom? + boolean isUnicode = StringUtil.hasMultibyte(rawText); + // isUnicode = true; - if (style == null) { - logger.log(POILogger.INFO, "styles atom doesn't exist. Creating dummy record for later saving."); - style = new StyleTextPropAtom((textLen < 0) ? 1 : textLen); - } else { - if (textLen >= 0) { - style.setParentTextSize(textLen); - } - } - - return style; - } + TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; + TextBytesAtom byteAtom = paragraphs.get(0)._byteAtom; + TextCharsAtom charAtom = paragraphs.get(0)._charAtom; + StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length()); + // Store in the appropriate record + Record oldRecord = null, newRecord = null; + if (isUnicode) { + if (byteAtom != null || charAtom == null) { + oldRecord = byteAtom; + charAtom = new TextCharsAtom(); + } + newRecord = charAtom; + charAtom.setText(rawText); + } else { + if (charAtom != null || byteAtom == null) { + oldRecord = charAtom; + byteAtom = new TextBytesAtom(); + } + newRecord = byteAtom; + byte[] byteText = new byte[rawText.length()]; + StringUtil.putCompressedUnicode(rawText, byteText, 0); + byteAtom.setText(byteText); + } + assert (newRecord != null); - /** - * Saves the modified paragraphs/textrun to the records. - * Also updates the styles to the correct text length. - */ - protected static void storeText(List paragraphs) { - fixLineEndings(paragraphs); + RecordContainer _txtbox = headerAtom.getParentRecord(); + Record[] cr = _txtbox.getChildRecords(); + int headerIdx = -1, textIdx = -1, styleIdx = -1; + for (int i = 0; i < cr.length; i++) { + Record r = cr[i]; + if (r == headerAtom) headerIdx = i; + else if (r == oldRecord || r == newRecord) textIdx = i; + else if (r == styleAtom) styleIdx = i; + } - String rawText = toInternalString(getRawText(paragraphs)); + if (textIdx == -1) { + // the old record was never registered, ignore it + _txtbox.addChildAfter(newRecord, headerAtom); + textIdx = headerIdx + 1; + } else { + // swap not appropriated records - noop if unchanged + cr[textIdx] = newRecord; + } - // Will it fit in a 8 bit atom? - boolean isUnicode = StringUtil.hasMultibyte(rawText); - // isUnicode = true; + if (styleIdx == -1) { + // Add the new StyleTextPropAtom after the TextCharsAtom / TextBytesAtom + _txtbox.addChildAfter(styleAtom, newRecord); + } - TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; - TextBytesAtom byteAtom = paragraphs.get(0)._byteAtom; - TextCharsAtom charAtom = paragraphs.get(0)._charAtom; - StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length()); + for (HSLFTextParagraph p : paragraphs) { + if (newRecord == byteAtom) { + p._byteAtom = byteAtom; + p._charAtom = null; + } else { + p._byteAtom = null; + p._charAtom = charAtom; + } + } - // Store in the appropriate record - Record oldRecord = null, newRecord = null; - if (isUnicode) { - if (byteAtom != null || charAtom == null) { - oldRecord = byteAtom; - charAtom = new TextCharsAtom(); - } - newRecord = charAtom; - charAtom.setText(rawText); - } else { - if (charAtom != null || byteAtom == null) { - oldRecord = charAtom; - byteAtom = new TextBytesAtom(); - } - newRecord = byteAtom; - byte[] byteText = new byte[rawText.length()]; - StringUtil.putCompressedUnicode(rawText,byteText,0); - byteAtom.setText(byteText); - } - assert(newRecord != null); - - RecordContainer _txtbox = headerAtom.getParentRecord(); - Record[] cr = _txtbox.getChildRecords(); - int headerIdx = -1, textIdx = -1, styleIdx = -1; - for (int i=0; i paragraphs, String text, boolean newParagraph) { + text = toInternalString(text); - /** - * Adds the supplied text onto the end of the TextParagraphs, - * creating a new RichTextRun for it to sit in. - * - * @param text the text string used by this object. - */ - protected static HSLFTextRun appendText(List paragraphs, String text, boolean newParagraph) { - text = toInternalString(text); + // check paragraphs + assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); - // check paragraphs - assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); + HSLFTextParagraph htp = paragraphs.get(paragraphs.size() - 1); + HSLFTextRun htr = htp.getTextRuns().get(htp.getTextRuns().size() - 1); - HSLFTextParagraph htp = paragraphs.get(paragraphs.size()-1); - HSLFTextRun htr = htp.getTextRuns().get(htp.getTextRuns().size()-1); + 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.getParagraphStyle().copy(tpc); + htp.setParentShape(prevHtp.getParentShape()); + htp.setShapeId(prevHtp.getShapeId()); + htp.supplySheet(prevHtp.getSheet()); + paragraphs.add(htp); + } + isFirst = false; - 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.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); - } - htr.setText(rawText); - } - - storeText(paragraphs); + 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); + } + htr.setText(rawText); + } - return htr; - } + storeText(paragraphs); - /** - * Sets (overwrites) the current text. - * Uses the properties of the first paragraph / textrun - * - * @param text the text string used by this object. - */ - public static HSLFTextRun setText(List paragraphs, String text) { - // check paragraphs - assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); + return htr; + } - Iterator paraIter = paragraphs.iterator(); - HSLFTextParagraph htp = paraIter.next(); // keep first - assert(htp != null); - while (paraIter.hasNext()) { - paraIter.next(); - paraIter.remove(); - } + /** + * Sets (overwrites) the current text. + * Uses the properties of the first paragraph / textrun + * + * @param text the text string used by this object. + */ + public static HSLFTextRun setText(List paragraphs, String text) { + // check paragraphs + assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); - Iterator runIter = htp.getTextRuns().iterator(); - HSLFTextRun htr = runIter.next(); - htr.setText(""); - assert(htr != null); - while (runIter.hasNext()) { - runIter.next(); - runIter.remove(); - } + Iterator paraIter = paragraphs.iterator(); + HSLFTextParagraph htp = paraIter.next(); // keep first + assert (htp != null); + while (paraIter.hasNext()) { + paraIter.next(); + paraIter.remove(); + } - return appendText(paragraphs, text, false); - } + Iterator runIter = htp.getTextRuns().iterator(); + HSLFTextRun htr = runIter.next(); + htr.setText(""); + assert (htr != null); + while (runIter.hasNext()) { + runIter.next(); + runIter.remove(); + } - public static String getText(List paragraphs) { - assert(!paragraphs.isEmpty()); - String rawText = getRawText(paragraphs); - return toExternalString(rawText, paragraphs.get(0).getRunType()); - } - - public static String getRawText(List paragraphs) { - StringBuilder sb = new StringBuilder(); - for (HSLFTextParagraph p : paragraphs) { - for (HSLFTextRun r : p.getTextRuns()) { - sb.append(r.getRawText()); - } - } - return sb.toString(); - } + return appendText(paragraphs, text, false); + } - /** - * Returns a new string with line breaks converted into internal ppt - * representation - */ - protected static String toInternalString(String s) { - String ns = s.replaceAll("\\r?\\n", "\r"); - return ns; - } + public static String getText(List paragraphs) { + assert (!paragraphs.isEmpty()); + String rawText = getRawText(paragraphs); + return toExternalString(rawText, paragraphs.get(0).getRunType()); + } - /** - * Converts raw text from the text paragraphs to a formatted string, - * i.e. it converts certain control characters used in the raw txt - * - * @param rawText the raw text - * @param runType the run type of the shape, paragraph or headerAtom. - * use -1 if unknown - * @return the formatted string - */ - public static String toExternalString(String rawText, int runType) { - // PowerPoint seems to store files with \r as the line break - // The messes things up on everything but a Mac, so translate - // them to \n - String text = rawText.replace('\r', '\n'); + public static String getRawText(List paragraphs) { + StringBuilder sb = new StringBuilder(); + for (HSLFTextParagraph p : paragraphs) { + for (HSLFTextRun r : p.getTextRuns()) { + sb.append(r.getRawText()); + } + } + return sb.toString(); + } - switch (runType) { - // 0xB acts like cariage return in page titles and like blank in the - // others - case -1: - case org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE: - case org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE: - text = text.replace((char) 0x0B, '\n'); - break; - default: - text = text.replace((char) 0x0B, ' '); - break; - } + /** + * Returns a new string with line breaks converted into internal ppt + * representation + */ + protected static String toInternalString(String s) { + String ns = s.replaceAll("\\r?\\n", "\r"); + return ns; + } - return text; - } + /** + * Converts raw text from the text paragraphs to a formatted string, + * i.e. it converts certain control characters used in the raw txt + * + * @param rawText the raw text + * @param runType the run type of the shape, paragraph or headerAtom. + * use -1 if unknown + * @return the formatted string + */ + public static String toExternalString(String rawText, int runType) { + // PowerPoint seems to store files with \r as the line break + // The messes things up on everything but a Mac, so translate + // them to \n + String text = rawText.replace('\r', '\n'); - /** - * For a given PPDrawing, grab all the TextRuns - */ + switch (runType) { + // 0xB acts like cariage return in page titles and like blank in the + // others + case -1: + case org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE: + case org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE: + text = text.replace((char) 0x0B, '\n'); + break; + default: + text = text.replace((char) 0x0B, ' '); + break; + } + + return text; + } + + /** + * For a given PPDrawing, grab all the TextRuns + */ public static List> findTextParagraphs(PPDrawing ppdrawing, HSLFSheet sheet) { - List> runsV = new ArrayList>(); - for (EscherTextboxWrapper wrapper : ppdrawing.getTextboxWrappers()) { - runsV.add(findTextParagraphs(wrapper, sheet)); - } - return runsV; - } + List> runsV = new ArrayList>(); + for (EscherTextboxWrapper wrapper : ppdrawing.getTextboxWrappers()) { + runsV.add(findTextParagraphs(wrapper, sheet)); + } + return runsV; + } - /** - * Scans through the supplied record array, looking for - * a TextHeaderAtom followed by one of a TextBytesAtom or - * a TextCharsAtom. Builds up TextRuns from these - * - * @param wrapper an EscherTextboxWrapper - */ - protected static List findTextParagraphs(EscherTextboxWrapper wrapper, HSLFSheet sheet) { - // propagate parents to parent-aware records - RecordContainer.handleParentAwareRecords(wrapper); - int shapeId = wrapper.getShapeId(); - List rv = null; - - OutlineTextRefAtom ota = (OutlineTextRefAtom)wrapper.findFirstOfType(OutlineTextRefAtom.typeID); - if (ota != null) { - // if we are based on an outline, there are no further records to be parsed from the wrapper - if (sheet == null) { - throw new RuntimeException("Outline atom reference can't be solved without a sheet record"); - } - - List> sheetRuns = sheet.getTextParagraphs(); - assert(sheetRuns != null); - - int idx = ota.getTextIndex(); - for (List r : sheetRuns) { - if (r.isEmpty()) continue; - int ridx = r.get(0).getIndex(); - if (ridx > idx) break; - if (ridx == idx) { - if (rv == null) { - rv = r; - } else { - // create a new container - // TODO: ... is this case really happening? - rv = new ArrayList(rv); - rv.addAll(r); - } - } - } - if(rv == null || rv.isEmpty()) { - logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx); - } - } else { - if (sheet != null) { - // check sheet runs first, so we get exactly the same paragraph list - List> sheetRuns = sheet.getTextParagraphs(); - assert(sheetRuns != null); + /** + * Scans through the supplied record array, looking for + * a TextHeaderAtom followed by one of a TextBytesAtom or + * a TextCharsAtom. Builds up TextRuns from these + * + * @param wrapper an EscherTextboxWrapper + */ + protected static List findTextParagraphs(EscherTextboxWrapper wrapper, HSLFSheet sheet) { + // propagate parents to parent-aware records + RecordContainer.handleParentAwareRecords(wrapper); + int shapeId = wrapper.getShapeId(); + List rv = null; - for (List paras : sheetRuns) { + OutlineTextRefAtom ota = (OutlineTextRefAtom)wrapper.findFirstOfType(OutlineTextRefAtom.typeID); + if (ota != null) { + // if we are based on an outline, there are no further records to be parsed from the wrapper + if (sheet == null) { + throw new RuntimeException("Outline atom reference can't be solved without a sheet record"); + } + + List> sheetRuns = sheet.getTextParagraphs(); + assert (sheetRuns != null); + + int idx = ota.getTextIndex(); + for (List r : sheetRuns) { + if (r.isEmpty()) continue; + int ridx = r.get(0).getIndex(); + if (ridx > idx) break; + if (ridx == idx) { + if (rv == null) { + rv = r; + } else { + // create a new container + // TODO: ... is this case really happening? + rv = new ArrayList(rv); + rv.addAll(r); + } + } + } + if (rv == null || rv.isEmpty()) { + logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx); + } + } else { + if (sheet != null) { + // check sheet runs first, so we get exactly the same paragraph list + List> sheetRuns = sheet.getTextParagraphs(); + assert (sheetRuns != null); + + for (List paras : sheetRuns) { if (!paras.isEmpty() && paras.get(0)._headerAtom.getParentRecord() == wrapper) { - rv = paras; - break; - } - } - } - - if (rv == null) { - // if we haven't found the wrapper in the sheet runs, create a new paragraph list from its record - List> rvl = findTextParagraphs(wrapper.getChildRecords()); - switch (rvl.size()) { - case 0: break; // nothing found - case 1: rv = rvl.get(0); break; // normal case - default: - throw new RuntimeException("TextBox contains more than one list of paragraphs."); - } - } - } - - if (rv != null) { - StyleTextProp9Atom styleTextProp9Atom = wrapper.getStyleTextProp9Atom(); - - for (HSLFTextParagraph htp : rv) { - htp.setShapeId(shapeId); - htp.setStyleTextProp9Atom(styleTextProp9Atom); - } - } - return rv; - } + rv = paras; + break; + } + } + } - /** - * Scans through the supplied record array, looking for - * a TextHeaderAtom followed by one of a TextBytesAtom or - * a TextCharsAtom. Builds up TextRuns from these - * - * @param records the records to build from - */ + if (rv == null) { + // if we haven't found the wrapper in the sheet runs, create a new paragraph list from its record + List> rvl = findTextParagraphs(wrapper.getChildRecords()); + switch (rvl.size()) { + case 0: break; // nothing found + case 1: rv = rvl.get(0); break; // normal case + default: + throw new RuntimeException("TextBox contains more than one list of paragraphs."); + } + } + } + + if (rv != null) { + StyleTextProp9Atom styleTextProp9Atom = wrapper.getStyleTextProp9Atom(); + + for (HSLFTextParagraph htp : rv) { + htp.setShapeId(shapeId); + htp.setStyleTextProp9Atom(styleTextProp9Atom); + } + } + return rv; + } + + /** + * Scans through the supplied record array, looking for + * a TextHeaderAtom followed by one of a TextBytesAtom or + * a TextCharsAtom. Builds up TextRuns from these + * + * @param records the records to build from + */ protected static List> findTextParagraphs(Record[] records) { List> paragraphCollection = new ArrayList>(); - int[] recordIdx = {0}; - + int[] recordIdx = { 0 }; + for (int slwtIndex = 0; recordIdx[0] < records.length; slwtIndex++) { - TextHeaderAtom header = null; - TextBytesAtom tbytes = null; - TextCharsAtom tchars = null; - TextRulerAtom ruler = null; + TextHeaderAtom header = null; + TextBytesAtom tbytes = null; + TextCharsAtom tchars = null; + TextRulerAtom ruler = null; MasterTextPropAtom indents = null; for (Record r : getRecords(records, recordIdx, null)) { long rt = r.getRecordType(); if (RecordTypes.TextHeaderAtom.typeID == rt) { - header = (TextHeaderAtom)r; + header = (TextHeaderAtom) r; } else if (RecordTypes.TextBytesAtom.typeID == rt) { - tbytes = (TextBytesAtom)r; + tbytes = (TextBytesAtom) r; } else if (RecordTypes.TextCharsAtom.typeID == rt) { - tchars = (TextCharsAtom)r; + tchars = (TextCharsAtom) r; } else if (RecordTypes.TextRulerAtom.typeID == rt) { - ruler = (TextRulerAtom)r; + ruler = (TextRulerAtom) r; } else if (RecordTypes.MasterTextPropAtom.typeID == rt) { - indents = (MasterTextPropAtom)r; + indents = (MasterTextPropAtom) r; } // don't search for RecordTypes.StyleTextPropAtom.typeID here ... see findStyleAtomPresent below } if (header == null) break; - + if (header.getParentRecord() instanceof SlideListWithText) { // runs found in PPDrawing are not linked with SlideListWithTexts header.setIndex(slwtIndex); @@ -1093,7 +1071,7 @@ public final class HSLFTextParagraph implements TextParagraph { List paragraphs = new ArrayList(); paragraphCollection.add(paragraphs); - + // split, but keep delimiter for (String para : rawText.split("(?<=\r)")) { HSLFTextParagraph tpara = new HSLFTextParagraph(header, tbytes, tchars); @@ -1116,7 +1094,7 @@ public final class HSLFTextParagraph implements TextParagraph { if (paragraphCollection.isEmpty()) { logger.log(POILogger.DEBUG, "No text records found."); } - + return paragraphCollection; } @@ -1124,25 +1102,25 @@ public final class HSLFTextParagraph implements TextParagraph { int paraIdx = 0, runIdx = 0; HSLFTextRun trun; - for (int csIdx=0; csIdx runs = para.getTextRuns(); trun = runs.get(runIdx); int len = trun.getLength(); - if (ccRun+len <= ccStyle) { + if (ccRun + len <= ccStyle) { ccRun += len; } else { String text = trun.getRawText(); - trun.setText(text.substring(0,ccStyle-ccRun)); + trun.setText(text.substring(0, ccStyle - ccRun)); HSLFTextRun nextRun = new HSLFTextRun(para); - nextRun.setText(text.substring(ccStyle-ccRun)); - runs.add(runIdx+1, nextRun); + nextRun.setText(text.substring(ccStyle - ccRun)); + runs.add(runIdx + 1, nextRun); - ccRun += ccStyle-ccRun; + ccRun += ccStyle - ccRun; } TextPropCollection pCopy = new TextPropCollection(0, TextPropType.character); @@ -1151,7 +1129,7 @@ public final class HSLFTextParagraph implements TextParagraph { len = trun.getLength(); if (paraIdx == paragraphs.size()-1 && runIdx == runs.size()-1) { - if (csIdx < charStyles.size()-1) { + if (csIdx < charStyles.size() - 1) { // special case, empty trailing text run HSLFTextRun nextRun = new HSLFTextRun(para); nextRun.setText(""); @@ -1204,7 +1182,7 @@ public final class HSLFTextParagraph implements TextParagraph { len += trun.getLength(); } para.setIndentLevel(p.getIndentLevel()); - ccPara += len+1; + ccPara += len + 1; } } } @@ -1237,6 +1215,6 @@ public final class HSLFTextParagraph implements TextParagraph { } public EscherTextboxWrapper getTextboxWrapper() { - return (EscherTextboxWrapper)_headerAtom.getParentRecord(); + return (EscherTextboxWrapper) _headerAtom.getParentRecord(); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java index 1e1dca888..bb4f14041 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java @@ -17,7 +17,8 @@ package org.apache.poi.hslf.usermodel; -import static org.apache.poi.hslf.usermodel.HSLFTextParagraph.fetchOrAddTextProp; +import static org.apache.poi.hslf.usermodel.HSLFTextParagraph.setPropVal; +import static org.apache.poi.hslf.usermodel.HSLFTextParagraph.getPropVal; import java.awt.Color; @@ -132,39 +133,17 @@ public final class HSLFTextRun implements TextRun { * it if required. */ private void setCharFlagsTextPropVal(int index, boolean value) { - if(getFlag(index) != value) setFlag(index, value); + // TODO: check if paragraph/chars can be handled the same ... + if (getFlag(index) != value) setFlag(index, value); } - /** - * Fetch the value of the given Character related TextProp. - * Returns -1 if that TextProp isn't present. - * If the TextProp isn't present, the value from the appropriate - * Master Sheet will apply. - */ - private int getCharTextPropVal(String propName) { - TextProp prop = null; - if (characterStyle != null){ - prop = characterStyle.findByName(propName); - } - - if (prop == null){ - HSLFSheet sheet = parentParagraph.getSheet(); - int txtype = parentParagraph.getRunType(); - HSLFMasterSheet master = sheet.getMasterSheet(); - if (master != null) - prop = master.getStyleAttribute(txtype, parentParagraph.getIndentLevel(), propName, true); - } - return prop == null ? -1 : prop.getValue(); - } - /** * Sets the value of the given Paragraph TextProp, add if required * @param propName The name of the Paragraph TextProp * @param val The value to set for the TextProp */ public void setCharTextPropVal(String propName, int val) { - TextProp tp = fetchOrAddTextProp(characterStyle, propName); - tp.setValue(val); + setPropVal(characterStyle, propName, val); } @@ -260,8 +239,8 @@ public final class HSLFTextRun implements TextRun { * @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript */ public int getSuperscript() { - int val = getCharTextPropVal("superscript"); - return val == -1 ? 0 : val; + TextProp tp = getPropVal(characterStyle, "superscript", parentParagraph); + return tp == null ? 0 : tp.getValue(); } /** @@ -270,14 +249,15 @@ public final class HSLFTextRun implements TextRun { * @param val the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript */ public void setSuperscript(int val) { - setCharTextPropVal("superscript", val); + setPropVal(characterStyle, "superscript", val); } /** * Gets the font size */ - public double getFontSize() { - return getCharTextPropVal("font.size"); + public Double getFontSize() { + TextProp tp = getPropVal(characterStyle, "font.size", parentParagraph); + return tp == null ? null : (double)tp.getValue(); } @@ -292,7 +272,8 @@ public final class HSLFTextRun implements TextRun { * Gets the font index */ public int getFontIndex() { - return getCharTextPropVal("font.index"); + TextProp tp = getPropVal(characterStyle, "font.index", parentParagraph); + return tp == null ? -1 : tp.getValue(); } /** @@ -329,9 +310,9 @@ public final class HSLFTextRun implements TextRun { if (sheet == null || slideShow == null) { return _fontFamily; } - int fontIdx = getCharTextPropVal("font.index"); - if(fontIdx == -1) { return null; } - return slideShow.getFontCollection().getFontWithId(fontIdx); + TextProp tp = getPropVal(characterStyle, "font.index", parentParagraph); + if (tp == null) { return null; } + return slideShow.getFontCollection().getFontWithId(tp.getValue()); } /** @@ -339,7 +320,9 @@ public final class HSLFTextRun implements TextRun { * @see java.awt.Color */ public Color getFontColor() { - int rgb = getCharTextPropVal("font.color"); + TextProp tp = getPropVal(characterStyle, "font.color", parentParagraph); + if (tp == null) return null; + int rgb = tp.getValue(); int cidx = rgb >> 24; if (rgb % 0x1000000 == 0){ @@ -370,7 +353,7 @@ public final class HSLFTextRun implements TextRun { } protected void setFlag(int index, boolean value) { - BitMaskTextProp prop = (BitMaskTextProp) fetchOrAddTextProp(characterStyle, CharFlagsTextProp.NAME); + BitMaskTextProp prop = (BitMaskTextProp)characterStyle.addWithName(CharFlagsTextProp.NAME); prop.setSubValue(value, index); } diff --git a/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextParagraph.java index 3db3c6f6d..d3c606a62 100644 --- a/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextParagraph.java +++ b/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextParagraph.java @@ -55,12 +55,21 @@ public class DrawTextParagraph implements Drawable { double rightInset = insets.right; double penY = y; - double leftMargin = paragraph.getLeftMargin(); boolean firstLine = true; - double indent = paragraph.getIndent(); + Double leftMargin = paragraph.getLeftMargin(); + Double indent = paragraph.getIndent(); + if (leftMargin == null) { + leftMargin = (indent != null) ? -indent : 0; + } + if (indent == null) { + indent = (leftMargin != null) ? -leftMargin : 0; + } + //The vertical line spacing - double spacing = paragraph.getLineSpacing(); + Double spacing = paragraph.getLineSpacing(); + if (spacing == null) spacing = 100d; + for(DrawTextFragment line : lines){ double penX = x + leftMargin; @@ -77,7 +86,7 @@ public class DrawTextParagraph implements Drawable { bullet.setPosition(penX + indent, penY); } else if(indent > 0){ // a positive value means the "First Line" indentation: - // the first line is indented and other lines start at the bullet ofset + // the first line is indented and other lines start at the bullet offset bullet.setPosition(penX, penY); penX += indent; } else { @@ -96,7 +105,9 @@ public class DrawTextParagraph implements Drawable { Rectangle2D anchor = DrawShape.getAnchor(graphics, paragraph.getParentShape()); - switch (paragraph.getTextAlign()) { + TextAlign ta = paragraph.getTextAlign(); + if (ta == null) ta = TextAlign.LEFT; + switch (ta) { case CENTER: penX += (anchor.getWidth() - leftMargin - line.getWidth() - leftInset - rightInset) / 2; break; @@ -219,9 +230,10 @@ public class DrawTextParagraph implements Drawable { if (buColor == null) buColor = (Color)firstLineAttr.getAttribute(TextAttribute.FOREGROUND); float fontSize = (Float)firstLineAttr.getAttribute(TextAttribute.SIZE); - float buSz = (float)bulletStyle.getBulletFontSize(); - if(buSz > 0) fontSize *= buSz* 0.01; - else fontSize = -buSz; + Double buSz = bulletStyle.getBulletFontSize(); + if (buSz == null) buSz = 100d; + if (buSz > 0) fontSize *= buSz* 0.01; + else fontSize = (float)-buSz; AttributedString str = new AttributedString(buCharacter); @@ -237,10 +249,13 @@ public class DrawTextParagraph implements Drawable { protected String getRenderableText(TextRun tr) { StringBuilder buf = new StringBuilder(); TextCap cap = tr.getTextCap(); + String tabs = null; for (char c : tr.getRawText().toCharArray()) { if(c == '\t') { - // TODO: finish support for tabs - buf.append(" "); + if (tabs == null) { + tabs = tab2space(tr); + } + buf.append(tabs); continue; } @@ -255,6 +270,34 @@ public class DrawTextParagraph implements Drawable { return buf.toString(); } + + /** + * Replace a tab with the effective number of white spaces. + */ + private String tab2space(TextRun tr) { + AttributedString string = new AttributedString(" "); + String typeFace = tr.getFontFamily(); + if (typeFace == null) typeFace = "Lucida Sans"; + string.addAttribute(TextAttribute.FAMILY, typeFace); + + Double fs = tr.getFontSize(); + if (fs == null) fs = 12d; + string.addAttribute(TextAttribute.SIZE, fs.floatValue()); + + TextLayout l = new TextLayout(string.getIterator(), new FontRenderContext(null, true, true)); + double wspace = l.getAdvance(); + + Double tabSz = paragraph.getDefaultTabSize(); + if (tabSz == null) tabSz = wspace*4; + + int numSpaces = (int)Math.ceil(tabSz / wspace); + StringBuilder buf = new StringBuilder(); + for(int i = 0; i < numSpaces; i++) { + buf.append(' '); + } + return buf.toString(); + } + /** * Returns wrapping width to break lines in this paragraph @@ -271,8 +314,14 @@ public class DrawTextParagraph implements Drawable { Rectangle2D anchor = DrawShape.getAnchor(graphics, paragraph.getParentShape()); - double leftMargin = paragraph.getLeftMargin(); - double indent = paragraph.getIndent(); + Double leftMargin = paragraph.getLeftMargin(); + Double indent = paragraph.getIndent(); + if (leftMargin == null) { + leftMargin = (indent != null) ? -indent : 0; + } + if (indent == null) { + indent = (leftMargin != null) ? -leftMargin : 0; + } double width; TextShape> ts = paragraph.getParentShape(); @@ -323,7 +372,9 @@ public class DrawTextParagraph implements Drawable { text.append(runText); int endIndex = text.length(); - attList.add(new AttributedStringData(TextAttribute.FOREGROUND, run.getFontColor(), beginIndex, endIndex)); + Color fgColor = run.getFontColor(); + if (fgColor == null) fgColor = Color.BLACK; + attList.add(new AttributedStringData(TextAttribute.FOREGROUND, fgColor, beginIndex, endIndex)); // user can pass an custom object to convert fonts String fontFamily = run.getFontFamily(); @@ -335,10 +386,14 @@ public class DrawTextParagraph implements Drawable { if(fontHandler != null) { fontFamily = fontHandler.getRendererableFont(fontFamily, run.getPitchAndFamily()); } + if (fontFamily == null) { + fontFamily = paragraph.getDefaultFontFamily(); + } attList.add(new AttributedStringData(TextAttribute.FAMILY, fontFamily, beginIndex, endIndex)); - float fontSz = (float)run.getFontSize(); - attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz, beginIndex, endIndex)); + Double fontSz = run.getFontSize(); + if (fontSz == null) fontSz = paragraph.getDefaultFontSize(); + attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), beginIndex, endIndex)); if(run.isBold()) { attList.add(new AttributedStringData(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, beginIndex, endIndex)); @@ -364,9 +419,9 @@ public class DrawTextParagraph implements Drawable { // ensure that the paragraph contains at least one character // We need this trick to correctly measure text if (text.length() == 0) { - float fontSz = (float)paragraph.getDefaultFontSize(); + Double fontSz = paragraph.getDefaultFontSize(); text.append(" "); - attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz, 0, 1)); + attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), 0, 1)); } AttributedString string = new AttributedString(text.toString()); diff --git a/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextShape.java b/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextShape.java index 247c7c184..bb6c6dd29 100644 --- a/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextShape.java +++ b/src/scratchpad/src/org/apache/poi/sl/draw/DrawTextShape.java @@ -95,7 +95,8 @@ public class DrawTextShape 0) { // positive value means percentage spacing of the height of the first line, e.g. // the higher the first line, the bigger the space before the paragraph @@ -112,7 +113,8 @@ public class DrawTextShape 0) { // positive value means percentage spacing of the height of the last line, e.g. // the higher the last line, the bigger the space after the paragraph diff --git a/src/scratchpad/src/org/apache/poi/sl/usermodel/TextParagraph.java b/src/scratchpad/src/org/apache/poi/sl/usermodel/TextParagraph.java index 14eb03c84..e85eee140 100644 --- a/src/scratchpad/src/org/apache/poi/sl/usermodel/TextParagraph.java +++ b/src/scratchpad/src/org/apache/poi/sl/usermodel/TextParagraph.java @@ -104,10 +104,18 @@ public interface TextParagraph extends Iterable { public interface BulletStyle { String getBulletCharacter(); String getBulletFont(); - double getBulletFontSize(); + + /** + * The bullet point font size + * If bulletFontSize >= 0, then space is a percentage of normal line height. + * If bulletFontSize < 0, the absolute value in points + * + * @return the bullet point font size + */ + Double getBulletFontSize(); Color getBulletFontColor(); } - + /** * The amount of vertical white space before the paragraph * This may be specified in two different ways, percentage spacing and font point spacing: @@ -116,9 +124,30 @@ public interface TextParagraph extends Iterable { * If spaceBefore < 0, the absolute value in points *

* - * @return the vertical white space before the paragraph + * @return the vertical white space before the paragraph, or null if unset */ - double getSpaceBefore(); + Double getSpaceBefore(); + + /** + * Set the amount of vertical white space that will be present before the paragraph. + * This space is specified in either percentage or points: + *

+ * 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 + *

+ * Examples: + *

+     *      // The paragraph will be formatted to have a spacing before the paragraph text.
+     *      // The spacing will be 200% of the size of the largest text on each line
+     *      paragraph.setSpaceBefore(200);
+     *
+     *      // The spacing will be a size of 48 points
+     *      paragraph.setSpaceBefore(-48.0);
+     * 
+ * + * @param spaceBefore the vertical white space before the paragraph, null to unset + */ + void setSpaceBefore(Double spaceBefore); /** * The amount of vertical white space after the paragraph @@ -128,40 +157,74 @@ public interface TextParagraph extends Iterable { * If spaceBefore < 0, the absolute value of linespacing is the spacing in points *

* - * @return the vertical white space after the paragraph + * @return the vertical white space after the paragraph or null, if unset */ - double getSpaceAfter(); + Double getSpaceAfter(); /** - * @return the left margin (in points) of the paragraph + * Set the amount of vertical white space that will be present after the paragraph. + * This space is specified in either percentage or points: + *

+ * If spaceAfter >= 0, then space is a percentage of normal line height. + * If spaceAfter < 0, the absolute value of linespacing is the spacing in points + *

+ * Examples: + *

+     *      // The paragraph will be formatted to have a spacing after the paragraph text.
+     *      // The spacing will be 200% of the size of the largest text on each line
+     *      paragraph.setSpaceAfter(200);
+     *
+     *      // The spacing will be a size of 48 points
+     *      paragraph.setSpaceAfter(-48.0);
+     * 
+ * + * @param spaceAfter the vertical white space after the paragraph, null to unset */ - double getLeftMargin(); + public void setSpaceAfter(Double spaceAfter); + + /** + * @return the left margin (in points) of the paragraph or null, if unset + */ + Double getLeftMargin(); /** - * @param leftMargin the left margin (in points) + * Specifies the left margin of the paragraph. This is specified in addition to the text body + * inset and applies only to this text paragraph. That is the text body Inset and the LeftMargin + * attributes are additive with respect to the text position. + * + * @param leftMargin the left margin (in points) or null to unset */ - void setLeftMargin(double leftMargin); + void setLeftMargin(Double leftMargin); /** - * @return the right margin (in points) of the paragraph + * Specifies the right margin of the paragraph. This is specified in addition to the text body + * inset and applies only to this text paragraph. That is the text body Inset and the RightMargin + * attributes are additive with respect to the text position. + * + * The right margin is not support and therefore ignored by the HSLF implementation. + * + * @return the right margin (in points) of the paragraph or null, if unset */ - double getRightMargin(); + Double getRightMargin(); /** * @param rightMargin the right margin (in points) of the paragraph */ - void setRightMargin(double rightMargin); + void setRightMargin(Double rightMargin); /** * @return the indent (in points) applied to the first line of text in the paragraph. + * or null, if unset */ - double getIndent(); + Double getIndent(); /** + * Specifies the indent size that will be applied to the first line of text in the paragraph. + * * @param indent the indent (in points) applied to the first line of text in the paragraph */ - void setIndent(double indent); + void setIndent(Double indent); /** * Returns the vertical line spacing that is to be used within a paragraph. @@ -171,9 +234,9 @@ public interface TextParagraph extends Iterable { * If linespacing < 0, the absolute value of linespacing is the spacing in points *

* - * @return the vertical line spacing. + * @return the vertical line spacing or null, if unset */ - double getLineSpacing(); + Double getLineSpacing(); /** * This element specifies the vertical line spacing that is to be used within a paragraph. @@ -196,14 +259,14 @@ public interface TextParagraph extends Iterable { * * @param linespacing the vertical line spacing */ - void setLineSpacing(double lineSpacing); + void setLineSpacing(Double lineSpacing); String getDefaultFontFamily(); /** - * @return the default font size, in case its not set in the textrun + * @return the default font size, in case its not set in the textrun or null, if unset */ - double getDefaultFontSize(); + Double getDefaultFontSize(); /** * Returns the alignment that is applied to the paragraph. @@ -217,8 +280,10 @@ public interface TextParagraph extends Iterable { /** * Returns the font alignment that is applied to the paragraph. * - * If this attribute is omitted, then a value of auto (~ left) is implied. - * @return ??? alignment that is applied to the paragraph + * If this attribute is omitted, then null is return, + * user code can imply the a value of {@link FontAlign#AUTO} + * + * @return alignment that is applied to the paragraph */ FontAlign getFontAlign(); @@ -227,5 +292,11 @@ public interface TextParagraph extends Iterable { */ BulletStyle getBulletStyle(); + /** + * @return the default size for a tab character within this paragraph in points, null if unset + */ + Double getDefaultTabSize(); + + TextShape> getParentShape(); } diff --git a/src/scratchpad/src/org/apache/poi/sl/usermodel/TextRun.java b/src/scratchpad/src/org/apache/poi/sl/usermodel/TextRun.java index 8ac40c189..bc1652afe 100644 --- a/src/scratchpad/src/org/apache/poi/sl/usermodel/TextRun.java +++ b/src/scratchpad/src/org/apache/poi/sl/usermodel/TextRun.java @@ -21,8 +21,6 @@ import java.awt.Color; /** * Some text. - * - * TODO - decide on how we do rich text stuff */ public interface TextRun { enum TextCap { @@ -31,13 +29,13 @@ public interface TextRun { ALL } - public String getRawText(); - public void setText(String text); + String getRawText(); + void setText(String text); TextCap getTextCap(); Color getFontColor(); - double getFontSize(); + Double getFontSize(); String getFontFamily(); boolean isBold(); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java index 8e5652fb8..4733999d4 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java @@ -22,8 +22,7 @@ import static org.junit.Assert.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import javax.imageio.ImageIO; @@ -35,7 +34,6 @@ import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.SlideShow; import org.apache.poi.util.JvmBugs; import org.apache.poi.xslf.usermodel.XMLSlideShow; -import org.junit.Ignore; import org.junit.Test; /** @@ -142,29 +140,43 @@ public final class TestPicture { @Test // @Ignore("Just for visual validation - antialiasing is different on various systems") public void bug54541() throws Exception { - String file = new String[]{ - "54542_cropped_bitmap.pptx", - "54541_cropped_bitmap.ppt", - "54541_cropped_bitmap2.ppt", - "sample_pptx_grouping_issues.pptx" - }[3]; - InputStream is = _slTests.openResourceAsStream(file); - SlideShow ss = file.endsWith("pptx") ? new XMLSlideShow(is) : new HSLFSlideShow(is); - is.close(); + String files[] = { +// "sample_pptx_grouping_issues.pptx", +// "54542_cropped_bitmap.pptx", +// "54541_cropped_bitmap.ppt", +// "54541_cropped_bitmap2.ppt", +// "alterman_security.ppt", + "alterman_security2.pptx", + }; - boolean debugOut = false; - Dimension pg = ss.getPageSize(); - int i=1; - for(Slide slide : ss.getSlides()) { - if (debugOut) { - DummyGraphics2d graphics = new DummyGraphics2d(); - slide.draw(graphics); - } else { - BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_RGB); - Graphics2D graphics = img.createGraphics(); - fixFonts(graphics); - slide.draw(graphics); - ImageIO.write(img, "PNG", new File("test"+(i++)+"hslf.png")); + BitSet pages = new BitSet(); + pages.set(2); + + for (String file : files) { + InputStream is = _slTests.openResourceAsStream(file); + SlideShow ss = file.endsWith("pptx") ? new XMLSlideShow(is) : new HSLFSlideShow(is); + is.close(); + + boolean debugOut = false; + Dimension pg = ss.getPageSize(); + for (Slide slide : ss.getSlides()) { + int slideNo = slide.getSlideNumber(); + if (!pages.get(slideNo-1)) { + if (pages.nextSetBit(slideNo-1) == -1) break; else continue; + } + if (debugOut) { + DummyGraphics2d graphics = new DummyGraphics2d(); + slide.draw(graphics); + } else { + BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = img.createGraphics(); + fixFonts(graphics); + slide.draw(graphics); + graphics.setColor(Color.BLACK); + graphics.setStroke(new BasicStroke(1)); + graphics.drawRect(0, 0, (int)pg.getWidth()-1, (int)pg.getHeight()-1); + ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png")); + } } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java index 0468955ee..b57e9f503 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java @@ -470,7 +470,7 @@ public final class TestRichTextRun { assertEquals(expected, HSLFTextParagraph.getRawText(txt.get(1))); assertEquals(4, txt.get(1).size()); rt = txt.get(1).get(0); - assertEquals('\u2022', rt.getBulletChar()); + assertEquals('\u2022', (char)rt.getBulletChar()); assertTrue(rt.isBullet()); @@ -486,7 +486,7 @@ public final class TestRichTextRun { assertEquals(4, txt.get(0).size()); rt = txt.get(0).get(0); assertTrue(rt.isBullet()); - assertEquals('\u2022', rt.getBulletChar()); + assertEquals('\u2022', (char)rt.getBulletChar()); expected = "I\u2019m a text box with user-defined\r" + @@ -495,7 +495,7 @@ public final class TestRichTextRun { assertEquals(2, txt.get(1).size()); rt = txt.get(1).get(0); assertTrue(rt.isBullet()); - assertEquals('\u263A', rt.getBulletChar()); + assertEquals('\u263A', (char)rt.getBulletChar()); } @Test @@ -513,8 +513,8 @@ public final class TestRichTextRun { HSLFTextRun tr = rt.getTextRuns().get(0); tr.setFontSize(42); rt.setBullet(true); - rt.setLeftMargin(50); - rt.setIndent(0); + rt.setLeftMargin(50d); + rt.setIndent(0d); rt.setBulletChar('\u263A'); slide.addShape(shape); @@ -522,7 +522,7 @@ public final class TestRichTextRun { assertEquals(true, rt.isBullet()); assertEquals(50.0, rt.getLeftMargin(), 0); assertEquals(0, rt.getIndent(), 0); - assertEquals('\u263A', rt.getBulletChar()); + assertEquals('\u263A', (char)rt.getBulletChar()); shape.setAnchor(new java.awt.Rectangle(50, 50, 500, 300)); slide.addShape(shape); @@ -541,7 +541,7 @@ public final class TestRichTextRun { assertEquals(true, rt.isBullet()); assertEquals(50.0, rt.getLeftMargin(), 0); assertEquals(0, rt.getIndent(), 0); - assertEquals('\u263A', rt.getBulletChar()); + assertEquals('\u263A', (char)rt.getBulletChar()); } @Test diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestTextRun.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestTextRun.java index 9dda9af24..c2f7ac9d0 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestTextRun.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestTextRun.java @@ -556,7 +556,7 @@ public final class TestTextRun { int i=0; for (List textParas : slide.getTextParagraphs()) { assertEquals("Arial", textParas.get(0).getTextRuns().get(0).getFontFamily()); - assertEquals(sizes[i++], (int)textParas.get(0).getTextRuns().get(0).getFontSize()); + assertEquals(sizes[i++], textParas.get(0).getTextRuns().get(0).getFontSize().intValue()); } } diff --git a/test-data/slideshow/alterman_security2.pptx b/test-data/slideshow/alterman_security2.pptx new file mode 100644 index 000000000..132922864 Binary files /dev/null and b/test-data/slideshow/alterman_security2.pptx differ