diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java index f2bfba99f..55b4c8492 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java @@ -17,11 +17,13 @@ package org.apache.poi.xslf.usermodel; /** + * + * * @author Yegor Kozlov */ public enum LineCap { /** - * Rounded ends - the default + * Rounded ends */ ROUND, /** diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/RenderableShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/RenderableShape.java index bbd0de9ea..82a29756a 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/RenderableShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/RenderableShape.java @@ -469,10 +469,19 @@ class RenderableShape { // first fill Paint fill = getFillPaint(graphics); + Paint line = getLinePaint(graphics); + applyStroke(graphics); // the stroke applies both to the shadow and the shape + + // first paint the shadow + if(shadow != null) for(Outline o : elems){ + if(o.getPath().isFilled()){ + if(fill != null) shadow.fill(graphics, o.getOutline()); + if(line != null) shadow.draw(graphics, o.getOutline()); + } + } + // then fill the shape interior if(fill != null) for(Outline o : elems){ if(o.getPath().isFilled()){ - if(shadow != null) shadow.fill(graphics, o.getOutline()); - graphics.setPaint(fill); graphics.fill(o.getOutline()); } @@ -482,13 +491,8 @@ class RenderableShape { _shape.drawContent(graphics); // then stroke the shape outline - Paint line = getLinePaint(graphics); if(line != null) for(Outline o : elems){ if(o.getPath().isStroked()){ - applyStroke(graphics); // the stroke applies both to the shadow and the shape - - if(shadow != null) shadow.draw(graphics, o.getOutline()); - graphics.setPaint(line); graphics.draw(o.getOutline()); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java index 647b414c5..b7f1f3d38 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java @@ -331,4 +331,12 @@ public class XSLFConnectorShape extends XSLFSimpleShape { return lst; } + /** + * YK: dashing of lines is suppressed for now. + * @return + */ + @Override + public XSLFShadow getShadow() { + return null; + } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java index 445c16e9b..1b5bbea2e 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java @@ -22,6 +22,7 @@ package org.apache.poi.xslf.usermodel; import org.apache.poi.POIXMLException; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.util.Beta; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; @@ -97,8 +98,7 @@ public class XSLFPictureShape extends XSLFSimpleShape { public XSLFPictureData getPictureData() { if(_data == null){ - CTPicture ct = (CTPicture)getXmlObject(); - String blipId = ct.getBlipFill().getBlip().getEmbed(); + String blipId = getBlipId(); PackagePart p = getSheet().getPackagePart(); PackageRelationship rel = p.getRelationship(blipId); @@ -115,6 +115,11 @@ public class XSLFPictureShape extends XSLFSimpleShape { return _data; } + private String getBlipId(){ + CTPicture ct = (CTPicture)getXmlObject(); + return ct.getBlipFill().getBlip().getEmbed(); + } + @Override public void drawContent(Graphics2D graphics) { @@ -126,4 +131,19 @@ public class XSLFPictureShape extends XSLFSimpleShape { renderer.drawImage(graphics, data, getAnchor()); } + + + @Override + void copy(XSLFShape sh){ + super.copy(sh); + + XSLFPictureShape p = (XSLFPictureShape)sh; + String blipId = p.getBlipId(); + String relId = getSheet().importBlip(blipId, p.getSheet().getPackagePart()); + + CTPicture ct = (CTPicture)getXmlObject(); + CTBlip blip = ct.getBlipFill().getBlip(); + blip.setEmbed(relId); + + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShadow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShadow.java index 70d42ce5c..7c2128e8f 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShadow.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShadow.java @@ -32,6 +32,7 @@ import java.awt.geom.Rectangle2D; * @author Yegor Kozlov */ public class XSLFShadow extends XSLFSimpleShape { + private XSLFSimpleShape _parent; /* package */XSLFShadow(CTOuterShadowEffect shape, XSLFSimpleShape parentShape) { diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java index 9ff619257..00191df6f 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java @@ -20,6 +20,7 @@ package org.apache.poi.xslf.usermodel; import org.apache.poi.util.Beta; +import org.apache.poi.util.Internal; import org.apache.xmlbeans.XmlObject; import java.awt.Graphics2D; @@ -34,27 +35,23 @@ import java.awt.geom.Rectangle2D; public abstract class XSLFShape { /** - * * @return the position of this shape within the drawing canvas. - * The coordinates are expressed in points + * The coordinates are expressed in points */ public abstract Rectangle2D getAnchor(); /** - * * @param anchor the position of this shape within the drawing canvas. - * The coordinates are expressed in points + * The coordinates are expressed in points */ public abstract void setAnchor(Rectangle2D anchor); /** - * * @return the xml bean holding this shape's data */ public abstract XmlObject getXmlObject(); /** - * * @return human-readable name of this shape, e.g. "Rectange 3" */ public abstract String getShapeName(); @@ -64,8 +61,8 @@ public abstract class XSLFShape { * This ID may be used to assist in uniquely identifying this object so that it can * be referred to by other parts of the document. *

- * If multiple objects within the same document share the same id attribute value, - * then the document shall be considered non-conformant. + * If multiple objects within the same document share the same id attribute value, + * then the document shall be considered non-conformant. *

* * @return unique id of this shape @@ -82,7 +79,7 @@ public abstract class XSLFShape { * @param theta the rotation angle in degrees. */ public abstract void setRotation(double theta); - + /** * Rotation angle in degrees *

@@ -105,7 +102,7 @@ public abstract class XSLFShape { * @param flip whether the shape is vertically flipped */ public abstract void setFlipVertical(boolean flip); - + /** * Whether the shape is horizontally flipped * @@ -132,15 +129,15 @@ public abstract class XSLFShape { * * @param graphics the graphics whos transform matrix will be modified */ - protected void applyTransform(Graphics2D graphics){ + protected void applyTransform(Graphics2D graphics) { Rectangle2D anchor = getAnchor(); // rotation double rotation = getRotation(); - if(rotation != 0.) { - // PowerPoint rotates shapes relative to the geometric center - double centerX = anchor.getX() + anchor.getWidth()/2; - double centerY = anchor.getY() + anchor.getHeight()/2; + if (rotation != 0.) { + // PowerPoint rotates shapes relative to the geometric center + double centerX = anchor.getX() + anchor.getWidth() / 2; + double centerY = anchor.getY() + anchor.getHeight() / 2; graphics.translate(centerX, centerY); graphics.rotate(Math.toRadians(rotation)); @@ -148,18 +145,34 @@ public abstract class XSLFShape { } //flip horizontal - if(getFlipHorizontal()){ + if (getFlipHorizontal()) { graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); graphics.scale(-1, 1); - graphics.translate(-anchor.getX() , -anchor.getY()); + graphics.translate(-anchor.getX(), -anchor.getY()); } //flip vertical - if(getFlipVertical()){ + if (getFlipVertical()) { graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); graphics.scale(1, -1); graphics.translate(-anchor.getX(), -anchor.getY()); } } + /** + * Set the contents of this shape to be a copy of the source shape. + * This method is called recursively for each shape when merging slides + * + * @param sh the source shape + * @see org.apache.poi.xslf.usermodel.XSLFSlide#importContent(XSLFSheet) + */ + @Internal + void copy(XSLFShape sh) { + if (!getClass().isInstance(sh)) { + throw new IllegalArgumentException( + "Can't copy " + sh.getClass().getSimpleName() + " into " + getClass().getSimpleName()); + } + + setAnchor(sh.getAnchor()); + } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java index 964df2df9..a5baed531 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -17,9 +17,11 @@ package org.apache.poi.xslf.usermodel; import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.util.Beta; import org.apache.poi.util.Internal; import org.apache.xmlbeans.XmlObject; @@ -270,14 +272,29 @@ public abstract class XSLFSheet extends POIXMLDocumentPart implements Iterable tgtShapes = getShapeList(); + List srcShapes = src.getShapeList(); + for(int i = 0; i < tgtShapes.size(); i++){ + XSLFShape s1 = srcShapes.get(i); + XSLFShape s2 = tgtShapes.get(i); + + s2.copy(s1); + } + return this; } /** @@ -423,4 +440,32 @@ public abstract class XSLFSheet extends POIXMLDocumentPart implements Iterable{ private final XSLFTextShape _shape; private List _lines; private TextFragment _bullet; + /** + * the highest line in this paragraph. Used for line spacing. + */ + private double _maxLineHeight; XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape){ _p = p; _runs = new ArrayList(); _shape = shape; - for (CTRegularTextRun r : _p.getRList()) { - _runs.add(new XSLFTextRun(r, this)); - } - - for (CTTextField f : _p.getFldList()) { - CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); - r.setT(f.getT()); - _runs.add(new XSLFTextRun(r, this)); + for(XmlObject ch : _p.selectPath("*")){ + if(ch instanceof CTRegularTextRun){ + CTRegularTextRun r = (CTRegularTextRun)ch; + _runs.add(new XSLFTextRun(r, this)); + } else if (ch instanceof CTTextLineBreak){ + CTTextLineBreak br = (CTTextLineBreak)ch; + CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); + r.setRPr(br.getRPr()); + r.setT("\n"); + _runs.add(new XSLFTextRun(r, this)); + } else if (ch instanceof CTTextField){ + CTTextField f = (CTTextField)ch; + CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); + r.setRPr(f.getRPr()); + r.setT(f.getT()); + _runs.add(new XSLFTextRun(r, this)); + } } } @@ -81,19 +100,10 @@ public class XSLFTextParagraph implements Iterable{ return out.toString(); } - private String getVisibleText(){ + String getRenderableText(){ StringBuilder out = new StringBuilder(); for (XSLFTextRun r : _runs) { - String txt = r.getText(); - switch (r.getTextCap()){ - case ALL: - txt = txt.toUpperCase(); - break; - case SMALL: - txt = txt.toLowerCase(); - break; - } - out.append(txt); + out.append(r.getRenderableText()); } return out.toString(); } @@ -183,6 +193,12 @@ public class XSLFTextParagraph implements Iterable{ return fetcher.getValue(); } + public void setBulletFont(String typeface){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextFont font = pr.isSetBuFont() ? pr.getBuFont() : pr.addNewBuFont(); + font.setTypeface(typeface); + } + /** * @return the character to be used in place of the standard bullet point */ @@ -200,6 +216,12 @@ public class XSLFTextParagraph implements Iterable{ return fetcher.getValue(); } + public void setBulletCharacter(String str){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextCharBullet c = pr.isSetBuChar() ? pr.getBuChar() : pr.addNewBuChar(); + c.setChar(str); + } + public Color getBulletFontColor(){ final XSLFTheme theme = getParentShape().getSheet().getTheme(); ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ @@ -216,6 +238,13 @@ public class XSLFTextParagraph implements Iterable{ return fetcher.getValue(); } + public void setBulletFontColor(Color color){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTColor c = pr.isSetBuClr() ? pr.getBuClr() : pr.addNewBuClr(); + CTSRgbColor clr = c.isSetSrgbClr() ? c.getSrgbClr() : c.addNewSrgbClr(); + clr.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + } + public double getBulletFontSize(){ ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getLevel()){ public boolean fetch(CTTextParagraphProperties props){ @@ -234,11 +263,17 @@ public class XSLFTextParagraph implements Iterable{ return fetcher.getValue() == null ? 100 : fetcher.getValue(); } + public void setBulletFontSize(double size){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextBulletSizePoint pt = pr.isSetBuSzPts() ? pr.getBuSzPts() : pr.addNewBuSzPts(); + pt.setVal((int)(size*1000)); + if(pr.isSetBuSzPct()) pr.unsetBuSzPct(); + } + /** * Specifies the indent size that will be applied to the first line of text in the paragraph. * - * @param value the indent in points. The value of -1 unsets the indent attribute - * from the underlying xml bean. + * @param value the indent in points. */ public void setIndent(double value){ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); @@ -297,7 +332,8 @@ public class XSLFTextParagraph implements Iterable{ } }; fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + // if the marL attribute is omitted, then a value of 347663 is implied + return fetcher.getValue() == null ? Units.toPoints(347663) : fetcher.getValue(); } /** @@ -512,7 +548,7 @@ public class XSLFTextParagraph implements Iterable{ /** * - * @param isBullet whether text in this paragraph has bullets + * @param flag whether text in this paragraph has bullets */ public void setBullet(boolean flag) { if(isBullet() == flag) return; @@ -535,38 +571,117 @@ public class XSLFTextParagraph implements Iterable{ return _lines; } + /** + * Returns wrapping width to break lines in this paragraph + * + * @param firstLine whether the first line is breaking + * + * @return wrapping width in points + */ + double getWrappingWidth(boolean firstLine){ + // internal margins for the text box + double leftInset = _shape.getLeftInset(); + double rightInset = _shape.getRightInset(); + + Rectangle2D anchor = _shape.getAnchor(); + + double leftMargin = getLeftMargin(); + double indent = getIndent(); + + double width; + if(!_shape.getWordWrap()) { + // if wordWrap == false then we return the advance to the right border of the sheet + width = _shape.getSheet().getSlideShow().getPageSize().getWidth() - anchor.getX(); + } else { + width = anchor.getWidth() - leftInset - rightInset - leftMargin; + if(firstLine) { + if(isBullet()){ + width -= Math.abs(indent); + } else { + if(indent > 0) width -= indent; // first line indentation + else if (indent < 0) { // hanging indentation: the first line start at the left margin + width += leftMargin; + } + } + } + } + + return width; + } + public double draw(Graphics2D graphics, double x, double y){ - double marginLeft = _shape.getLeftInset(); - double marginRight = _shape.getRightInset(); + double leftInset = _shape.getLeftInset(); + double rightInset = _shape.getRightInset(); Rectangle2D anchor = _shape.getAnchor(); double penY = y; - double textOffset = getLeftMargin(); + double leftMargin = getLeftMargin(); boolean firstLine = true; + double indent = getIndent(); for(TextFragment line : _lines){ double penX = x; + + if(firstLine) { + + if(_bullet != null){ + if(indent < 0) { + // a negative value means "Hanging" indentation and + // indicates the position of the actual bullet character. + // (the bullet is shifted to right relative to the text) + _bullet.draw(graphics, penX, penY); + penX -= indent; + } else if(indent > 0){ + penX += leftMargin; + // a positive value means the "First Line" indentation: + // the first line is indented and other lines start at the bullet ofset + _bullet.draw(graphics, penX, penY); + penX += indent; + } else { + // no special indent. The first line behaves like all others + penX += leftMargin; + + // a zero indent means that the bullet and text have the same offset + _bullet.draw(graphics, penX, penY); + + // don't let text overlay the bullet and advance by the bullet width + penX += _bullet._layout.getAdvance() + 1; + } + } else { + if(indent < 0) { + // if bullet=false and indentation=hanging then the first line + // starts at the left offset (penX is not incremented) + } else if(indent > 0) { + // first line indent shifts penX + penX += indent + leftMargin; + } else { + // no special indent. The first line behaves like all others + penX += leftMargin; + } + } + } else { + penX += leftMargin; + } + + switch (getTextAlign()) { case CENTER: - penX += textOffset + (anchor.getWidth() - textOffset - line.getWidth() - marginLeft - marginRight) / 2; + penX += (anchor.getWidth() - leftMargin - line.getWidth() - leftInset - rightInset) / 2; break; case RIGHT: - penX += (anchor.getWidth() - line.getWidth() - marginLeft - marginRight); + penX += (anchor.getWidth() - line.getWidth() - leftInset - rightInset); break; default: - penX = x + textOffset; + //penX += leftInset; break; } - if(_bullet != null && firstLine){ - _bullet.draw(graphics, penX + getIndent(), penY); - } line.draw(graphics, penX, penY); //The vertical line spacing double spacing = getLineSpacing(); if(spacing > 0) { // If linespacing >= 0, then linespacing is a percentage of normal line height. - penY += spacing*0.01*line.getHeight(); + penY += spacing*0.01* _maxLineHeight; } else { // positive value means absolute spacing in points penY += -spacing; @@ -607,13 +722,13 @@ public class XSLFTextParagraph implements Iterable{ } AttributedString getAttributedString(Graphics2D graphics){ - String text = getVisibleText(); + String text = getRenderableText(); AttributedString string = new AttributedString(text); int startIndex = 0; for (XSLFTextRun run : _runs){ - int length = run.getText().length(); + int length = run.getRenderableText().length(); if(length == 0) { // skip empty runs continue; @@ -656,6 +771,7 @@ public class XSLFTextParagraph implements Iterable{ void breakText(Graphics2D graphics){ _lines = new ArrayList(); + String text = getRenderableText(); AttributedString at = getAttributedString(graphics); AttributedCharacterIterator it = at.getIterator(); if(it.getBeginIndex() == it.getEndIndex()) { @@ -664,12 +780,17 @@ public class XSLFTextParagraph implements Iterable{ LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext()); for (;;) { int startIndex = measurer.getPosition(); - double wrappingWidth = getWrappingWidth() + 1; // add a pixel to compensate rounding errors - TextLayout layout = measurer.nextLayout((float)wrappingWidth, it.getEndIndex(), true); + double wrappingWidth = getWrappingWidth(_lines.size() == 0) + 1; // add a pixel to compensate rounding errors + + + int nextBreak = text.indexOf('\n', startIndex + 1); + if(nextBreak == -1) nextBreak = it.getEndIndex(); + + TextLayout layout = measurer.nextLayout((float)wrappingWidth, nextBreak, true); if (layout == null) { // layout can be null if the entire word at the current position // does not fit within the wrapping width. Try with requireNextWord=false. - layout = measurer.nextLayout((float)wrappingWidth, it.getEndIndex(), false); + layout = measurer.nextLayout((float)wrappingWidth, nextBreak, false); } int endIndex = measurer.getPosition(); @@ -683,6 +804,8 @@ public class XSLFTextParagraph implements Iterable{ TextFragment line = new TextFragment(layout, str); _lines.add(line); + _maxLineHeight = Math.max(_maxLineHeight, line.getHeight()); + if(endIndex == it.getEndIndex()) break; } @@ -714,17 +837,6 @@ public class XSLFTextParagraph implements Iterable{ } - double getWrappingWidth(){ - double width; - if(!_shape.getWordWrap()) { - width = _shape.getSheet().getSlideShow().getPageSize().getWidth(); - } else { - width = _shape.getAnchor().getWidth() - - _shape.getLeftInset() - _shape.getRightInset() - getLeftMargin(); - } - return width; - } - CTTextParagraphProperties getDefaultStyle(){ CTPlaceholder ph = _shape.getCTPlaceholder(); String defaultStyleSelector; @@ -782,4 +894,64 @@ public class XSLFTextParagraph implements Iterable{ return ok; } + void copy(XSLFTextParagraph p){ + TextAlign srcAlign = p.getTextAlign(); + if(srcAlign != getTextAlign()){ + setTextAlign(srcAlign); + } + + boolean isBullet = p.isBullet(); + if(isBullet != isBullet()){ + setBullet(isBullet); + if(isBullet) { + String buFont = p.getBulletFont(); + if(buFont != null && !buFont.equals(getBulletFont())){ + setBulletFont(buFont); + } + String buChar = p.getBulletCharacter(); + if(buChar != null && !buChar.equals(getBulletCharacter())){ + setBulletCharacter(buChar); + } + Color buColor = p.getBulletFontColor(); + if(buColor != null && !buColor.equals(getBulletFontColor())){ + setBulletFontColor(buColor); + } + double buSize = p.getBulletFontSize(); + if(buSize != getBulletFontSize()){ + setBulletFontSize(buSize); + } + } + } + + double leftMargin = p.getLeftMargin(); + if(leftMargin != getLeftMargin()){ + setLeftMargin(leftMargin); + } + + double indent = p.getIndent(); + if(indent != getIndent()){ + setIndent(indent); + } + + double spaceAfter = p.getSpaceAfter(); + if(spaceAfter != getSpaceAfter()){ + setSpaceAfter(spaceAfter); + } + double spaceBefore = p.getSpaceBefore(); + if(spaceBefore != getSpaceBefore()){ + setSpaceBefore(spaceBefore); + } + double lineSpacing = p.getLineSpacing(); + if(lineSpacing != getLineSpacing()){ + setLineSpacing(lineSpacing); + } + + List srcR = p.getTextRuns(); + List tgtR = getTextRuns(); + for(int i = 0; i < srcR.size(); i++){ + XSLFTextRun r1 = srcR.get(i); + XSLFTextRun r2 = tgtR.get(i); + r2.copy(r1); + } + } } 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 b4aa3ca8b..dc79e73c6 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -56,6 +56,23 @@ public class XSLFTextRun { return _r.getT(); } + String getRenderableText(){ + String txt = _r.getT(); + switch (getTextCap()){ + case ALL: + txt = txt.toUpperCase(); + break; + case SMALL: + txt = txt.toLowerCase(); + break; + } + // TODO-1 is is the place to convert wingdings to unicode + + // TODO-2 this is a temporary hack. Rendering text with tabs is not yet supported. + // for now tabs are replaced with some number of spaces. + return txt.replace("\t", " "); + } + public void setText(String text){ _r.setT(text); } @@ -69,6 +86,13 @@ public class XSLFTextRun { CTSolidColorFillProperties fill = rPr.isSetSolidFill() ? rPr.getSolidFill() : rPr.addNewSolidFill(); CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr(); clr.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + + if(fill.isSetHslClr()) fill.unsetHslClr(); + if(fill.isSetPrstClr()) fill.unsetPrstClr(); + if(fill.isSetSchemeClr()) fill.unsetSchemeClr(); + if(fill.isSetScrgbClr()) fill.unsetScrgbClr(); + if(fill.isSetSysClr()) fill.unsetSysClr(); + } public Color getFontColor(){ @@ -393,4 +417,32 @@ public class XSLFTextRun { return ok; } + void copy(XSLFTextRun r){ + String srcFontFamily = r.getFontFamily(); + if(srcFontFamily != null && !srcFontFamily.equals(getFontFamily())){ + setFontFamily(srcFontFamily); + } + + Color srcFontColor = r.getFontColor(); + if(srcFontColor != null && !srcFontColor.equals(getFontColor())){ + setFontColor(srcFontColor); + } + + double srcFontSize = r.getFontSize(); + if(srcFontSize != getFontSize()){ + setFontSize(srcFontSize); + } + + boolean bold = r.isBold(); + if(bold != isBold()) setBold(bold); + + boolean italic = r.isItalic(); + if(italic != isItalic()) setItalic(italic); + + boolean underline = r.isUnderline(); + if(underline != isUnderline()) setUnderline(underline); + + boolean strike = r.isStrikethrough(); + if(strike != isStrikethrough()) setStrikethrough(strike); + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java index dd49c316a..cce5a61e8 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java @@ -202,7 +202,8 @@ public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable< } }; fetchShapeProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + // If this attribute is omitted, then a value of 0.05 inches is implied + return fetcher.getValue() == null ? 3.6 : fetcher.getValue(); } /** @@ -224,7 +225,8 @@ public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable< } }; fetchShapeProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + // If this attribute is omitted, then a value of 0.1 inches is implied + return fetcher.getValue() == null ? 7.2 : fetcher.getValue(); } /** @@ -246,7 +248,8 @@ public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable< } }; fetchShapeProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + // If this attribute is omitted, then a value of 0.1 inches is implied + return fetcher.getValue() == null ? 7.2 : fetcher.getValue(); } /** @@ -267,7 +270,8 @@ public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable< } }; fetchShapeProperty(fetcher); - return fetcher.getValue() == null ? 0 : fetcher.getValue(); + // If this attribute is omitted, then a value of 0.05 inches is implied + return fetcher.getValue() == null ? 3.6 : fetcher.getValue(); } /** @@ -521,4 +525,46 @@ public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable< return y - y0; } + @Override + void copy(XSLFShape sh){ + super.copy(sh); + + XSLFTextShape tsh = (XSLFTextShape)sh; + + boolean srcWordWrap = tsh.getWordWrap(); + if(srcWordWrap != getWordWrap()){ + setWordWrap(srcWordWrap); + } + + double leftInset = tsh.getLeftInset(); + if(leftInset != getLeftInset()) { + setLeftInset(leftInset); + } + double rightInset = tsh.getRightInset(); + if(rightInset != getRightInset()) { + setRightInset(rightInset); + } + double topInset = tsh.getTopInset(); + if(topInset != getTopInset()) { + setTopInset(topInset); + } + double bottomInset = tsh.getBottomInset(); + if(bottomInset != getBottomInset()) { + setBottomInset(bottomInset); + } + + VerticalAlignment vAlign = tsh.getVerticalAlignment(); + if(vAlign != getVerticalAlignment()) { + setVerticalAlignment(vAlign); + } + + List srcP = tsh.getTextParagraphs(); + List tgtP = getTextParagraphs(); + for(int i = 0; i < srcP.size(); i++){ + XSLFTextParagraph p1 = srcP.get(i); + XSLFTextParagraph p2 = tgtP.get(i); + p2.copy(p1); + } + + } } \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java index d8cb5783e..d5e7c61c2 100755 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java @@ -19,6 +19,12 @@ package org.apache.poi.xslf.usermodel; import junit.framework.TestCase; import org.apache.poi.xslf.XSLFTestDataSamples; +import org.apache.poi.openxml4j.opc.PackagePart; + +import java.awt.Color; +import java.util.List; +import java.util.Arrays; +import java.util.regex.Pattern; /** * @author Yegor Kozlov @@ -103,4 +109,58 @@ public class TestXSLFSlide extends TestCase { assertTrue(slide.getFollowMasterGraphics()); } + public void testImportContent(){ + XMLSlideShow ppt = new XMLSlideShow(); + + XMLSlideShow src = XSLFTestDataSamples.openSampleDocument("themes.pptx"); + + // create a blank slide and import content from the 4th slide of themes.pptx + XSLFSlide slide1 = ppt.createSlide().importContent(src.getSlides()[3]); + XSLFShape[] shapes1 = slide1.getShapes(); + assertEquals(2, shapes1.length); + + XSLFTextShape sh1 = (XSLFTextShape)shapes1[0]; + assertEquals("Austin Theme", sh1.getText()); + XSLFTextRun r1 = sh1.getTextParagraphs().get(0).getTextRuns().get(0); + assertEquals("Century Gothic", r1.getFontFamily()); + assertEquals(40.0, r1.getFontSize()); + assertTrue(r1.isBold()); + assertTrue(r1.isItalic()); + assertEquals(new Color(148, 198, 0), r1.getFontColor()); + assertNull(sh1.getFillColor()); + assertNull(sh1.getLineColor()); + + XSLFTextShape sh2 = (XSLFTextShape)shapes1[1]; + assertEquals( + "Text in a autoshape is white\n" + + "Fill: RGB(148, 198,0)", sh2.getText()); + XSLFTextRun r2 = sh2.getTextParagraphs().get(0).getTextRuns().get(0); + assertEquals("Century Gothic", r2.getFontFamily()); + assertEquals(18.0, r2.getFontSize()); + assertFalse(r2.isBold()); + assertFalse(r2.isItalic()); + assertEquals(Color.white, r2.getFontColor()); + assertEquals(new Color(148, 198, 0), sh2.getFillColor()); + assertEquals(new Color(74, 99, 0), sh2.getLineColor()); // slightly different from PowerPoint! + + // the 5th slide has a picture and a texture fill + XSLFSlide slide2 = ppt.createSlide().importContent(src.getSlides()[4]); + XSLFShape[] shapes2 = slide2.getShapes(); + assertEquals(2, shapes2.length); + + XSLFTextShape sh3 = (XSLFTextShape)shapes2[0]; + assertEquals("This slide overrides master background with a texture fill", sh3.getText()); + XSLFTextRun r3 = sh3.getTextParagraphs().get(0).getTextRuns().get(0); + assertEquals("Century Gothic", r3.getFontFamily()); + //assertEquals(32.4.0, r3.getFontSize()); + assertTrue(r3.isBold()); + assertTrue(r3.isItalic()); + assertEquals(new Color(148, 198, 0), r3.getFontColor()); + assertNull(sh3.getFillColor()); + assertNull(sh3.getLineColor()); + + XSLFPictureShape sh4 = (XSLFPictureShape)shapes2[1]; + XSLFPictureShape srcPic = (XSLFPictureShape)src.getSlides()[4].getShapes()[1]; + assertTrue(Arrays.equals(sh4.getPictureData().getData(), srcPic.getPictureData().getData())); + } } \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java new file mode 100755 index 000000000..c426fd15c --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java @@ -0,0 +1,105 @@ +package org.apache.poi.xslf.usermodel; + +import junit.framework.TestCase; + +import java.awt.Rectangle; +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.io.FileOutputStream; + +import org.apache.poi.xssf.dev.XSSFDump; +import org.apache.poi.xslf.util.PPTX2PNG; + +/** + * Created by IntelliJ IDEA. + * User: yegor + * Date: Nov 10, 2011 + * Time: 1:43:25 PM + * To change this template use File | Settings | File Templates. + */ +public class TestXSLFTextParagraph extends TestCase { + + public void testWrappingWidth() throws Exception { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + XSLFTextShape sh = slide.createAutoShape(); + sh.setLineColor(Color.black); + + XSLFTextParagraph p = sh.addNewTextParagraph(); + p.addNewTextRun().setText( + "Paragraph formatting allows for more granular control " + + "of text within a shape. Properties here apply to all text " + + "residing within the corresponding paragraph."); + + Rectangle2D anchor = new Rectangle(50, 50, 300, 200); + sh.setAnchor(anchor); + + double leftInset = sh.getLeftInset(); + double rightInset = sh.getRightInset(); + assertEquals(7.2, leftInset); + assertEquals(7.2, rightInset); + + double leftMargin = p.getLeftMargin(); + assertEquals(0.0, leftMargin); + + double indent = p.getIndent(); + assertEquals(0.0, indent); // default + + double expectedWidth; + + // Case 1: bullet=false, leftMargin=0, indent=0. + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; + assertEquals(285.6, expectedWidth); // 300 - 7.2 - 7.2 - 0 + assertEquals(expectedWidth, p.getWrappingWidth(true)); + assertEquals(expectedWidth, p.getWrappingWidth(false)); + + p.setLeftMargin(36); // 0.5" + leftMargin = p.getLeftMargin(); + assertEquals(36.0, leftMargin); + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; + assertEquals(249.6, expectedWidth, 1E-5); // 300 - 7.2 - 7.2 - 36 + assertEquals(expectedWidth, p.getWrappingWidth(true)); + assertEquals(expectedWidth, p.getWrappingWidth(false)); + + // increase insets, the wrapping width should get smaller + sh.setLeftInset(10); + sh.setRightInset(10); + leftInset = sh.getLeftInset(); + rightInset = sh.getRightInset(); + assertEquals(10.0, leftInset); + assertEquals(10.0, rightInset); + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; + assertEquals(244.0, expectedWidth); // 300 - 10 - 10 - 36 + assertEquals(expectedWidth, p.getWrappingWidth(true)); + assertEquals(expectedWidth, p.getWrappingWidth(false)); + + // set a positive indent of a 0.5 inch. This means "First Line" indentation: + // |<--- indent -->|Here goes first line of the text + // Here go other lines (second and subsequent) + + p.setIndent(36.0); // 0.5" + indent = p.getIndent(); + assertEquals(36.0, indent); + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin - indent; + assertEquals(208.0, expectedWidth); // 300 - 10 - 10 - 36 - 6.4 + assertEquals(expectedWidth, p.getWrappingWidth(true)); // first line is indented + // other lines are not indented + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; + assertEquals(244.0, expectedWidth); // 300 - 10 - 10 - 36 + assertEquals(expectedWidth, p.getWrappingWidth(false)); + + // set a negative indent of a 1 inch. This means "Hanging" indentation: + // Here goes first line of the text + // |<--- indent -->|Here go other lines (second and subsequent) + p.setIndent(-72.0); // 1" + indent = p.getIndent(); + assertEquals(-72.0, indent); + expectedWidth = anchor.getWidth() - leftInset - rightInset; + assertEquals(280.0, expectedWidth); // 300 - 10 - 10 + assertEquals(expectedWidth, p.getWrappingWidth(true)); // first line is NOT indented + // other lines are indented by leftMargin (the value of indent is not used) + expectedWidth = anchor.getWidth() - leftInset - rightInset - leftMargin; + assertEquals(244.0, expectedWidth); // 300 - 10 - 10 - 36 + assertEquals(expectedWidth, p.getWrappingWidth(false)); + } +} diff --git a/test-data/slideshow/themes.pptx b/test-data/slideshow/themes.pptx index ff39de3ed..5f2e751a7 100755 Binary files a/test-data/slideshow/themes.pptx and b/test-data/slideshow/themes.pptx differ