From 58f41001658d81ae0d04a86f0743d68c97f9f121 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sat, 14 Nov 2015 02:44:07 +0000 Subject: [PATCH] #54210 When saving PPT to PNG, some text is rendered backwards #53189 Shapes drawn wrongly when ppt file converted to image fix line decoration (HSLF) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1714290 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/sl/draw/DrawSimpleShape.java | 118 +++++---- .../org/apache/poi/sl/draw/DrawTextShape.java | 47 ++-- .../poi/sl/usermodel/LineDecoration.java | 66 +++-- .../apache/poi/sl/usermodel/TextShape.java | 22 ++ .../poi/xslf/usermodel/XSLFTextShape.java | 61 +++-- .../poi/hslf/usermodel/HSLFSimpleShape.java | 81 +++++- .../poi/hslf/usermodel/HSLFTextShape.java | 238 +++++++----------- 7 files changed, 376 insertions(+), 257 deletions(-) diff --git a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java index 941e5c72d9..cdc665a617 100644 --- a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java @@ -48,6 +48,7 @@ import org.apache.poi.sl.draw.geom.CustomGeometry; import org.apache.poi.sl.draw.geom.Outline; import org.apache.poi.sl.draw.geom.Path; import org.apache.poi.sl.usermodel.LineDecoration; +import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape; import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.Shadow; @@ -59,6 +60,8 @@ import org.apache.poi.util.Units; public class DrawSimpleShape extends DrawShape { + + private static final double DECO_SIZE_POW = 1.5d; public DrawSimpleShape(SimpleShape shape) { super(shape); @@ -73,10 +76,10 @@ public class DrawSimpleShape extends DrawShape { graphics.setStroke(stroke); Collection elems = computeOutlines(graphics); - + // first paint the shadow drawShadow(graphics, elems, fill, line); - + // then fill the shape interior if (fill != null) { graphics.setPaint(fill); @@ -85,10 +88,10 @@ public class DrawSimpleShape extends DrawShape { java.awt.Shape s = o.getOutline(); graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s); graphics.fill(s); - } + } } } - + // then draw any content within this shape (text, image, etc.) drawContent(graphics); @@ -103,7 +106,7 @@ public class DrawSimpleShape extends DrawShape { } } } - + // draw line decorations drawDecoration(graphics, line, stroke); } @@ -111,44 +114,60 @@ public class DrawSimpleShape extends DrawShape { protected void drawDecoration(Graphics2D graphics, Paint line, BasicStroke stroke) { if(line == null) return; graphics.setPaint(line); - + List lst = new ArrayList(); LineDecoration deco = getShape().getLineDecoration(); Outline head = getHeadDecoration(graphics, deco, stroke); if (head != null) lst.add(head); Outline tail = getTailDecoration(graphics, deco, stroke); if (tail != null) lst.add(tail); - - + + for(Outline o : lst){ java.awt.Shape s = o.getOutline(); Path p = o.getPath(); graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s); - + if(p.isFilled()) graphics.fill(s); if(p.isStroked()) graphics.draw(s); } } protected Outline getTailDecoration(Graphics2D graphics, LineDecoration deco, BasicStroke stroke) { + if (deco == null || stroke == null) { + return null; + } DecorationSize tailLength = deco.getTailLength(); + if (tailLength == null) { + tailLength = DecorationSize.MEDIUM; + } DecorationSize tailWidth = deco.getTailWidth(); - + if (tailWidth == null) { + tailWidth = DecorationSize.MEDIUM; + } + double lineWidth = Math.max(2.5, stroke.getLineWidth()); - + Rectangle2D anchor = getAnchor(graphics, getShape()); double x2 = anchor.getX() + anchor.getWidth(), y2 = anchor.getY() + anchor.getHeight(); - + double alpha = Math.atan(anchor.getHeight() / anchor.getWidth()); - + AffineTransform at = new AffineTransform(); java.awt.Shape tailShape = null; Path p = null; Rectangle2D bounds; - final double scaleY = Math.pow(2, tailWidth.ordinal()+1); - final double scaleX = Math.pow(2, tailLength.ordinal()+1); - switch (deco.getTailShape()) { + final double scaleY = Math.pow(DECO_SIZE_POW, tailWidth.ordinal()+1); + final double scaleX = Math.pow(DECO_SIZE_POW, tailLength.ordinal()+1); + + DecorationShape tailShapeEnum = deco.getTailShape(); + + if (tailShapeEnum == null) { + return null; + } + + switch (tailShapeEnum) { case OVAL: p = new Path(); tailShape = new Ellipse2D.Double(0, 0, lineWidth * scaleX, lineWidth * scaleY); @@ -181,32 +200,47 @@ public class DrawSimpleShape extends DrawShape { default: break; } - + if (tailShape != null) { tailShape = at.createTransformedShape(tailShape); } return tailShape == null ? null : new Outline(tailShape, p); } - + protected Outline getHeadDecoration(Graphics2D graphics, LineDecoration deco, BasicStroke stroke) { + if (deco == null || stroke == null) { + return null; + } DecorationSize headLength = deco.getHeadLength(); + if (headLength == null) { + headLength = DecorationSize.MEDIUM; + } DecorationSize headWidth = deco.getHeadWidth(); - + if (headWidth == null) { + headWidth = DecorationSize.MEDIUM; + } + double lineWidth = Math.max(2.5, stroke.getLineWidth()); - + Rectangle2D anchor = getAnchor(graphics, getShape()); double x1 = anchor.getX(), y1 = anchor.getY(); - + double alpha = Math.atan(anchor.getHeight() / anchor.getWidth()); - + AffineTransform at = new AffineTransform(); java.awt.Shape headShape = null; Path p = null; Rectangle2D bounds; - final double scaleY = Math.pow(2, headWidth.ordinal()+1); - final double scaleX = Math.pow(2, headLength.ordinal()+1); - switch (deco.getHeadShape()) { + final double scaleY = Math.pow(DECO_SIZE_POW, headWidth.ordinal()+1); + final double scaleX = Math.pow(DECO_SIZE_POW, headLength.ordinal()+1); + DecorationShape headShapeEnum = deco.getHeadShape(); + + if (headShapeEnum == null) { + return null; + } + + switch (headShapeEnum) { case OVAL: p = new Path(); headShape = new Ellipse2D.Double(0, 0, lineWidth * scaleX, lineWidth * scaleY); @@ -239,16 +273,16 @@ public class DrawSimpleShape extends DrawShape { default: break; } - + if (headShape != null) { headShape = at.createTransformedShape(headShape); } return headShape == null ? null : new Outline(headShape, p); } - + public BasicStroke getStroke() { StrokeStyle strokeStyle = getShape().getStrokeStyle(); - + float lineWidth = (float) strokeStyle.getLineWidth(); if (lineWidth == 0.0f) lineWidth = 0.25f; // Both PowerPoint and OOo draw zero-length lines as 0.25pt @@ -299,7 +333,7 @@ public class DrawSimpleShape extends DrawShape { SolidPaint shadowPaint = shadow.getFillStyle(); Color shadowColor = DrawPaint.applyColorTransform(shadowPaint.getSolidColor()); - + double shapeRotation = getShape().getRotation(); if(getShape().getFlipVertical()) { shapeRotation += 180; @@ -308,15 +342,15 @@ public class DrawSimpleShape extends DrawShape { double dist = shadow.getDistance(); double dx = dist * Math.cos(Math.toRadians(angle)); double dy = dist * Math.sin(Math.toRadians(angle)); - + graphics.translate(dx, dy); - + for(Outline o : outlines){ java.awt.Shape s = o.getOutline(); Path p = o.getPath(); graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s); graphics.setPaint(shadowColor); - + if(fill != null && p.isFilled()){ graphics.fill(s); } else if (line != null && p.isStroked()) { @@ -326,26 +360,26 @@ public class DrawSimpleShape extends DrawShape { graphics.translate(-dx, -dy); } - + protected static CustomGeometry getCustomGeometry(String name) { return getCustomGeometry(name, null); } - + protected static CustomGeometry getCustomGeometry(String name, Graphics2D graphics) { @SuppressWarnings("unchecked") Map presets = (graphics == null) ? null : (Map)graphics.getRenderingHint(Drawable.PRESET_GEOMETRY_CACHE); - + if (presets == null) { presets = new HashMap(); if (graphics != null) { graphics.setRenderingHint(Drawable.PRESET_GEOMETRY_CACHE, presets); } - + String packageName = "org.apache.poi.sl.draw.binding"; InputStream presetIS = Drawable.class.getResourceAsStream("presetShapeDefinitions.xml"); - + // StAX: EventFilter startElementFilter = new EventFilter() { @Override @@ -353,7 +387,7 @@ public class DrawSimpleShape extends DrawShape { return event.isStartElement(); } }; - + try { XMLInputFactory staxFactory = XMLInputFactory.newInstance(); XMLEventReader staxReader = staxFactory.createXMLEventReader(presetIS); @@ -363,14 +397,14 @@ public class DrawSimpleShape extends DrawShape { // JAXB: JAXBContext jaxbContext = JAXBContext.newInstance(packageName); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - + while (staxFiltRd.peek() != null) { StartElement evRoot = (StartElement)staxFiltRd.peek(); String cusName = evRoot.getName().getLocalPart(); // XMLEvent ev = staxReader.nextEvent(); JAXBElement el = unmarshaller.unmarshal(staxReader, CTCustomGeometry2D.class); CTCustomGeometry2D cusGeom = el.getValue(); - + presets.put(cusName, new CustomGeometry(cusGeom)); } } catch (Exception e) { @@ -383,10 +417,10 @@ public class DrawSimpleShape extends DrawShape { } } } - + return presets.get(name); } - + protected Collection computeOutlines(Graphics2D graphics) { List lst = new ArrayList(); diff --git a/src/java/org/apache/poi/sl/draw/DrawTextShape.java b/src/java/org/apache/poi/sl/draw/DrawTextShape.java index b1ec2d9436..77af128d29 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextShape.java @@ -37,8 +37,10 @@ public class DrawTextShape extends DrawSimpleShape { public void drawContent(Graphics2D graphics) { fixFonts(graphics); - Rectangle2D anchor = DrawShape.getAnchor(graphics, getShape()); - Insets2D insets = getShape().getInsets(); + TextShape s = getShape(); + + Rectangle2D anchor = DrawShape.getAnchor(graphics, s); + Insets2D insets = s.getInsets(); double x = anchor.getX() + insets.left; double y = anchor.getY(); @@ -50,39 +52,42 @@ public class DrawTextShape extends DrawSimpleShape { // (see DrawShape#applyTransform ), but we need to restore it to avoid painting "upside down". // See Bugzilla 54210. - if(getShape().getFlipVertical()){ - graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); - graphics.scale(1, -1); - graphics.translate(-anchor.getX(), -anchor.getY()); - - // text in vertically flipped shapes is rotated by 180 degrees - double centerX = anchor.getX() + anchor.getWidth()/2; - double centerY = anchor.getY() + anchor.getHeight()/2; - graphics.translate(centerX, centerY); - graphics.rotate(Math.toRadians(180)); - graphics.translate(-centerX, -centerY); - } - + boolean vertFlip = s.getFlipVertical(); + boolean horzFlip = s.getFlipHorizontal(); + ShapeContainer sc = s.getParent(); + while (sc instanceof PlaceableShape) { + PlaceableShape ps = (PlaceableShape)sc; + vertFlip ^= ps.getFlipVertical(); + horzFlip ^= ps.getFlipHorizontal(); + sc = ps.getParent(); + }; + // Horizontal flipping applies only to shape outline and not to the text in the shape. // Applying flip second time restores the original not-flipped transform - if(getShape().getFlipHorizontal()){ + if (horzFlip ^ vertFlip) { graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); graphics.scale(-1, 1); - graphics.translate(-anchor.getX() , -anchor.getY()); + graphics.translate(-anchor.getX(), -anchor.getY()); + } + + Double textRot = s.getTextRotation(); + if (textRot != null) { + graphics.translate(anchor.getCenterX(), anchor.getCenterY()); + graphics.rotate(Math.toRadians(textRot)); + graphics.translate(-anchor.getCenterX(), -anchor.getCenterY()); } - // first dry-run to calculate the total height of the text - double textHeight = getShape().getTextHeight(); + double textHeight = s.getTextHeight(); - switch (getShape().getVerticalAlignment()){ + switch (s.getVerticalAlignment()){ + default: case TOP: y += insets.top; break; case BOTTOM: y += anchor.getHeight() - textHeight - insets.bottom; break; - default: case MIDDLE: double delta = anchor.getHeight() - textHeight - insets.top - insets.bottom; y += insets.top + delta/2; diff --git a/src/java/org/apache/poi/sl/usermodel/LineDecoration.java b/src/java/org/apache/poi/sl/usermodel/LineDecoration.java index e95551c9e7..ad61e413c9 100644 --- a/src/java/org/apache/poi/sl/usermodel/LineDecoration.java +++ b/src/java/org/apache/poi/sl/usermodel/LineDecoration.java @@ -22,19 +22,28 @@ public interface LineDecoration { * Represents the shape decoration that appears at the ends of lines. */ enum DecorationShape { - NONE(1), - TRIANGLE(2), - STEALTH(3), - DIAMOND(4), - OVAL(5), - ARROW(6); - + NONE(0,1), + TRIANGLE(1,2), + STEALTH(2,3), + DIAMOND(3,4), + OVAL(4,5), + ARROW(5,6); + + public final int nativeId; public final int ooxmlId; - - DecorationShape(int ooxmlId) { + + DecorationShape(int nativeId, int ooxmlId) { + this.nativeId = nativeId; this.ooxmlId = ooxmlId; } - + + public static DecorationShape fromNativeId(int nativeId) { + for (DecorationShape ld : values()) { + if (ld.nativeId == nativeId) return ld; + } + return null; + } + public static DecorationShape fromOoxmlId(int ooxmlId) { for (DecorationShape ds : values()) { if (ds.ooxmlId == ooxmlId) return ds; @@ -42,18 +51,27 @@ public interface LineDecoration { return null; } } - + enum DecorationSize { - SMALL(1), - MEDIUM(2), - LARGE(3); - + SMALL(0, 1), + MEDIUM(1, 2), + LARGE(2, 3); + + public final int nativeId; public final int ooxmlId; - - DecorationSize(int ooxmlId) { + + DecorationSize(int nativeId, int ooxmlId) { + this.nativeId = nativeId; this.ooxmlId = ooxmlId; } - + + public static DecorationSize fromNativeId(int nativeId) { + for (DecorationSize ld : values()) { + if (ld.nativeId == nativeId) return ld; + } + return null; + } + public static DecorationSize fromOoxmlId(int ooxmlId) { for (DecorationSize ds : values()) { if (ds.ooxmlId == ooxmlId) return ds; @@ -61,32 +79,32 @@ public interface LineDecoration { return null; } } - + /** * @return the line start shape */ DecorationShape getHeadShape(); - + /** * @return the width of the start shape */ DecorationSize getHeadWidth(); - + /** * @return the length of the start shape */ DecorationSize getHeadLength(); - + /** * @return the line end shape */ DecorationShape getTailShape(); - + /** * @return the width of the end shape */ DecorationSize getTailWidth(); - + /** * @return the length of the end shape */ diff --git a/src/java/org/apache/poi/sl/usermodel/TextShape.java b/src/java/org/apache/poi/sl/usermodel/TextShape.java index 0ec2ddfb91..df302ff62e 100644 --- a/src/java/org/apache/poi/sl/usermodel/TextShape.java +++ b/src/java/org/apache/poi/sl/usermodel/TextShape.java @@ -199,6 +199,28 @@ public interface TextShape< * @return vertical orientation of the text */ TextDirection getTextDirection(); + + /** + * sets the vertical orientation + * @param orientation vertical orientation of the text + */ + void setTextDirection(TextDirection orientation); + + /** + * The text rotation can be independent specified from the shape rotation. + * For XSLF this can be an arbitrary degree, for HSLF the degree is given in steps of 90 degrees + * + * @return text rotation in degrees, returns null if no rotation is given + */ + Double getTextRotation(); + + /** + * Sets the text rotation. + * For XSLF this can ben an arbitrary degree, for HSLF the rotation is rounded to next 90 degree step + * + * @param rotation the text rotation, or null to unset the rotation + */ + void setTextRotation(Double rotation); /** * Sets the text placeholder 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 8a4a935209..9c0eb12cc0 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java @@ -161,7 +161,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape @Override public void setVerticalAlignment(VerticalAlignment anchor){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(anchor == null) { if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor(); @@ -189,7 +189,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape @Override public void setHorizontalCentered(Boolean isCentered){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (isCentered == null) { if (bodyPr.isSetAnchorCtr()) bodyPr.unsetAnchorCtr(); @@ -214,12 +214,9 @@ public abstract class XSLFTextShape extends XSLFSimpleShape return fetcher.getValue() == null ? false : fetcher.getValue(); } - /** - * - * @param orientation vertical orientation of the text - */ + @Override public void setTextDirection(TextDirection orientation){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(orientation == null) { if(bodyPr.isSetVert()) bodyPr.unsetVert(); @@ -229,9 +226,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape } } - /** - * @return vertical orientation of the text - */ + @Override public TextDirection getTextDirection(){ CTTextBodyProperties bodyPr = getTextBodyPr(); if (bodyPr != null) { @@ -243,7 +238,24 @@ public abstract class XSLFTextShape extends XSLFSimpleShape return TextDirection.HORIZONTAL; } - + @Override + public Double getTextRotation() { + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null && bodyPr.isSetRot()) { + return bodyPr.getRot() / 60000.; + } + return null; + } + + @Override + public void setTextRotation(Double rotation) { + CTTextBodyProperties bodyPr = getTextBodyPr(true); + if (bodyPr != null) { + bodyPr.setRot((int)(rotation * 60000.)); + } + } + + /** * Returns the distance (in points) between the bottom of the text frame * and the bottom of the inscribed rectangle of the shape that contains the text. @@ -341,7 +353,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape * @param margin the bottom margin */ public void setBottomInset(double margin){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(margin == -1) bodyPr.unsetBIns(); else bodyPr.setBIns(Units.toEMU(margin)); @@ -355,7 +367,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape * @param margin the left margin */ public void setLeftInset(double margin){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(margin == -1) bodyPr.unsetLIns(); else bodyPr.setLIns(Units.toEMU(margin)); @@ -369,7 +381,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape * @param margin the right margin */ public void setRightInset(double margin){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(margin == -1) bodyPr.unsetRIns(); else bodyPr.setRIns(Units.toEMU(margin)); @@ -383,7 +395,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape * @param margin the top margin */ public void setTopInset(double margin){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(margin == -1) bodyPr.unsetTIns(); else bodyPr.setTIns(Units.toEMU(margin)); @@ -421,7 +433,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape @Override public void setWordWrap(boolean wrap){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { bodyPr.setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE); } @@ -435,7 +447,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape * @param value type of autofit */ public void setTextAutofit(TextAutofit value){ - CTTextBodyProperties bodyPr = getTextBodyPr(); + CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit(); if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit(); @@ -464,10 +476,21 @@ public abstract class XSLFTextShape extends XSLFSimpleShape } protected CTTextBodyProperties getTextBodyPr(){ - CTTextBody textBody = getTextBody(false); - return textBody == null ? null : textBody.getBodyPr(); + return getTextBodyPr(false); } + protected CTTextBodyProperties getTextBodyPr(boolean create) { + CTTextBody textBody = getTextBody(create); + if (textBody == null) { + return null; + } + CTTextBodyProperties textBodyPr = textBody.getBodyPr(); + if (textBodyPr == null && create) { + textBodyPr = textBody.addNewBodyPr(); + } + return textBodyPr; + } + protected abstract CTTextBody getTextBody(boolean create); @Override diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java index 74761e49ca..47ab16b167 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java @@ -26,6 +26,7 @@ import org.apache.poi.hslf.record.*; import org.apache.poi.sl.draw.DrawPaint; import org.apache.poi.sl.draw.geom.*; import org.apache.poi.sl.usermodel.*; +import org.apache.poi.sl.usermodel.LineDecoration.*; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.StrokeStyle.LineCap; import org.apache.poi.sl.usermodel.StrokeStyle.LineCompound; @@ -462,32 +463,100 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape { @@ -63,16 +61,37 @@ implements TextShape { /** * How to anchor the text */ - /* package */ static final int AnchorTop = 0; - /* package */ static final int AnchorMiddle = 1; - /* package */ static final int AnchorBottom = 2; - /* package */ static final int AnchorTopCentered = 3; - /* package */ static final int AnchorMiddleCentered = 4; - /* package */ static final int AnchorBottomCentered = 5; - /* package */ static final int AnchorTopBaseline = 6; - /* package */ static final int AnchorBottomBaseline = 7; - /* package */ static final int AnchorTopCenteredBaseline = 8; - /* package */ static final int AnchorBottomCenteredBaseline = 9; + private enum HSLFTextAnchor { + TOP (0, VerticalAlignment.TOP, false, false), + MIDDLE (1, VerticalAlignment.MIDDLE, false, false), + BOTTOM (2, VerticalAlignment.BOTTOM, false, false), + TOP_CENTER (3, VerticalAlignment.TOP, true, false), + MIDDLE_CENTER (4, VerticalAlignment.MIDDLE, true, null), + BOTTOM_CENTER (5, VerticalAlignment.BOTTOM, true, false), + TOP_BASELINE (6, VerticalAlignment.TOP, false, true), + BOTTOM_BASELINE (7, VerticalAlignment.BOTTOM, false, true), + TOP_CENTER_BASELINE (8, VerticalAlignment.TOP, true, true), + BOTTOM_CENTER_BASELINE(9, VerticalAlignment.BOTTOM, true, true); + + public final int nativeId; + public final VerticalAlignment vAlign; + public final boolean centered; + public final Boolean baseline; + + HSLFTextAnchor(int nativeId, VerticalAlignment vAlign, boolean centered, Boolean baseline) { + this.nativeId = nativeId; + this.vAlign = vAlign; + this.centered = centered; + this.baseline = baseline; + } + + static HSLFTextAnchor fromNativeId(int nativeId) { + for (HSLFTextAnchor ta : values()) { + if (ta.nativeId == nativeId) return ta; + } + return null; + } + } /** * Specifies that a line of text will continue on subsequent lines instead @@ -120,7 +139,7 @@ implements TextShape { * * @see */ - boolean alignToBaseline = false; +// boolean alignToBaseline = false; /** * Used to calculate text bounds @@ -279,38 +298,40 @@ implements TextShape { * * @return the type of alignment */ - /* package */ int getAlignment(){ + /* package */ HSLFTextAnchor getAlignment(){ AbstractEscherOptRecord opt = getEscherOptRecord(); EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT); - int align = HSLFTextShape.AnchorTop; + HSLFTextAnchor align = HSLFTextAnchor.TOP; if (prop == null){ /** * If vertical alignment was not found in the shape properties then try to * fetch the master shape and search for the align property there. */ int type = getRunType(); - if(getSheet() != null && getSheet().getMasterSheet() != null){ - HSLFMasterSheet master = getSheet().getMasterSheet(); - HSLFTextShape masterShape = master.getPlaceholderByTextType(type); - if(masterShape != null) align = masterShape.getAlignment(); + HSLFSheet sh = getSheet(); + HSLFMasterSheet master = (sh != null) ? sh.getMasterSheet() : null; + HSLFTextShape masterShape = (master != null) ? master.getPlaceholderByTextType(type) : null; + if (masterShape != null && type != TextHeaderAtom.OTHER_TYPE) { + align = masterShape.getAlignment(); } else { //not found in the master sheet. Use the hardcoded defaults. switch (type){ - case org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE: - case org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE: - align = HSLFTextShape.AnchorMiddle; + case TextHeaderAtom.TITLE_TYPE: + case TextHeaderAtom.CENTER_TITLE_TYPE: + align = HSLFTextAnchor.MIDDLE; break; default: - align = HSLFTextShape.AnchorTop; + align = HSLFTextAnchor.TOP; break; } } } else { - align = prop.getPropertyValue(); + align = HSLFTextAnchor.fromNativeId(prop.getPropertyValue()); } - alignToBaseline = (align == AnchorBottomBaseline || align == AnchorBottomCenteredBaseline - || align == AnchorTopBaseline || align == AnchorTopCenteredBaseline); + if (align == null) { + align = HSLFTextAnchor.TOP; + } return align; } @@ -319,26 +340,21 @@ implements TextShape { * Sets the type of alignment for the text. * One of the Anchor* constants defined in this class. * - * @param align - the type of alignment - */ - /* package */ void setAlignment(Boolean isCentered, VerticalAlignment vAlign) { - int align[]; - switch (vAlign) { - case TOP: - align = new int[]{AnchorTop, AnchorTopCentered, AnchorTopBaseline, AnchorTopCenteredBaseline}; - break; - default: - case MIDDLE: - align = new int[]{AnchorMiddle, AnchorMiddleCentered, AnchorMiddle, AnchorMiddleCentered}; - break; - case BOTTOM: - align = new int[]{AnchorBottom, AnchorBottomCentered, AnchorBottomBaseline, AnchorBottomCenteredBaseline}; - break; + * @param isCentered horizontal centered? + * @param vAlign vertical alignment + * @param baseline aligned to baseline? + */ + /* package */ void setAlignment(Boolean isCentered, VerticalAlignment vAlign, boolean baseline) { + for (HSLFTextAnchor hta : HSLFTextAnchor.values()) { + if ( + (hta.centered == (isCentered != null && isCentered)) && + (hta.vAlign == vAlign) && + (hta.baseline == null || hta.baseline == baseline) + ) { + setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, hta.nativeId); + break; + } } - - int align2 = align[(isCentered ? 1 : 0)+(alignToBaseline ? 2 : 0)]; - - setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, align2); } /** @@ -346,8 +362,7 @@ implements TextShape { * this is only used for older versions less equals Office 2003 */ public boolean isAlignToBaseline() { - getAlignment(); - return alignToBaseline; + return getAlignment().baseline; } /** @@ -356,51 +371,27 @@ implements TextShape { * @param alignToBaseline if true, vertical alignment is relative to baseline */ public void setAlignToBaseline(boolean alignToBaseline) { - this.alignToBaseline = alignToBaseline; - setAlignment(isHorizontalCentered(), getVerticalAlignment()); + setAlignment(isHorizontalCentered(), getVerticalAlignment(), alignToBaseline); } @Override public boolean isHorizontalCentered() { - int va = getAlignment(); - switch (va) { - case AnchorTopCentered: - case AnchorTopCenteredBaseline: - case AnchorBottomCentered: - case AnchorBottomCenteredBaseline: - case AnchorMiddleCentered: - return true; - default: - return false; - } + return getAlignment().centered; } @Override public void setHorizontalCentered(Boolean isCentered) { - setAlignment(isCentered, getVerticalAlignment()); + setAlignment(isCentered, getVerticalAlignment(), getAlignment().baseline); } @Override public VerticalAlignment getVerticalAlignment() { - int va = getAlignment(); - switch (va) { - case AnchorTop: - case AnchorTopCentered: - case AnchorTopBaseline: - case AnchorTopCenteredBaseline: return VerticalAlignment.TOP; - case AnchorBottom: - case AnchorBottomCentered: - case AnchorBottomBaseline: - case AnchorBottomCenteredBaseline: return VerticalAlignment.BOTTOM; - default: - case AnchorMiddle: - case AnchorMiddleCentered: return VerticalAlignment.MIDDLE; - } + return getAlignment().vAlign; } @Override public void setVerticalAlignment(VerticalAlignment vAlign) { - setAlignment(isHorizontalCentered(), vAlign); + setAlignment(isHorizontalCentered(), vAlign, getAlignment().baseline); } /** @@ -583,13 +574,6 @@ implements TextShape { if (_paragraphs.isEmpty()) { logger.log(POILogger.WARN, "TextRecord didn't contained any text lines"); } -// initParagraphsFromSheetRecords(); -// if (_paragraphs.isEmpty()) { -// List> llhtp = HSLFTextParagraph.findTextParagraphs(_txtbox); -// if (!llhtp.isEmpty()) { -// _paragraphs.addAll(llhtp.get(0)); -// } -// } } for (HSLFTextParagraph p : _paragraphs) { @@ -615,68 +599,6 @@ implements TextShape { } } -// protected void initParagraphsFromSheetRecords(){ -// EscherTextboxWrapper txtbox = getEscherTextboxWrapper(); -// HSLFSheet sheet = getSheet(); -// -// if (sheet == null || txtbox == null) return; -// List> sheetRuns = _sheet.getTextParagraphs(); -// if (sheetRuns == null) return; -// -// _paragraphs.clear(); -// OutlineTextRefAtom ota = (OutlineTextRefAtom)txtbox.findFirstOfType(OutlineTextRefAtom.typeID); -// -// if (ota != 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) _paragraphs.addAll(r); -// } -// if(_paragraphs.isEmpty()) { -// logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx); -// } -// } else { -// int shapeId = getShapeId(); -// for (List r : sheetRuns) { -// if (r.isEmpty()) continue; -// if (r.get(0).getShapeId() == shapeId) _paragraphs.addAll(r); -// } -// } -// -// // ensure the same references child records of TextRun - see #48916 -//// if(_txtrun != null) { -//// for (int i = 0; i < child.length; i++) { -//// for (Record r : _txtrun.getRecords()) { -//// if (child[i].getRecordType() == r.getRecordType()) { -//// child[i] = r; -//// } -//// } -//// } -//// } -// } - - /* - // 0xB acts like cariage return in page titles and like blank in the others - char replChr; - switch(tha == null ? -1 : tha.getTextType()) { - case -1: - case TextHeaderAtom.TITLE_TYPE: - case TextHeaderAtom.CENTER_TITLE_TYPE: - replChr = '\n'; - break; - default: - replChr = ' '; - break; - } - - // 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').replace('\u000b', replChr); - */ - /** * Return OEPlaceholderAtom, the atom that describes a placeholder. * @@ -755,9 +677,35 @@ implements TextShape { @Override public TextDirection getTextDirection() { // TODO: determine vertical text setting + // see 2.3.22.10 Geometry Text Boolean Properties return TextDirection.HORIZONTAL; } + @Override + public void setTextDirection(TextDirection orientation) { + // TODO: determine vertical text setting + // see 2.3.22.10 Geometry Text Boolean Properties / gtextFVertical [MS-ODRAW] + } + + @Override + public Double getTextRotation() { + // see 2.4.6 MSOCDIR + AbstractEscherOptRecord opt = getEscherOptRecord(); + EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__FONTROTATION); + return (prop == null) ? null : (90. * prop.getPropertyValue()); + } + + @Override + public void setTextRotation(Double rotation) { + AbstractEscherOptRecord opt = getEscherOptRecord(); + if (rotation == null) { + opt.removeEscherProperty(EscherProperties.TEXT__FONTROTATION); + } else { + int rot = (int)(Math.round(rotation / 90.) % 4L); + setEscherProperty(EscherProperties.TEXT__FONTROTATION, rot); + } + } + /** * Returns the raw text content of the shape. This hasn't had any * changes applied to it, and so is probably unlikely to print -- 2.39.5