From f9c8f0826d4e576ecc237c6c01c61214d882439d Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 8 Nov 2015 23:13:28 +0000 Subject: Add support for HSLF metro blobs git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1713318 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/xslf/usermodel/XSLFGroupShape.java | 2 +- .../apache/poi/xslf/usermodel/XSLFMetroShape.java | 60 +++++++++++++++ .../org/apache/poi/xslf/usermodel/XSLFSheet.java | 14 ++-- .../poi/xslf/usermodel/TestXSLFTextShape.java | 20 ++++- .../org/apache/poi/hslf/model/HSLFMetroShape.java | 83 +++++++++++++++++++++ .../org/apache/poi/hslf/usermodel/HSLFSlide.java | 5 ++ .../org/apache/poi/hslf/usermodel/HSLFTextBox.java | 9 ++- .../poi/hslf/usermodel/HSLFTextParagraph.java | 9 +++ .../apache/poi/hslf/usermodel/HSLFTextShape.java | 83 ++++++++++++--------- test-data/slideshow/bug52297.ppt | Bin 0 -> 227840 bytes 10 files changed, 235 insertions(+), 50 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFMetroShape.java create mode 100644 src/scratchpad/src/org/apache/poi/hslf/model/HSLFMetroShape.java create mode 100644 test-data/slideshow/bug52297.ppt diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java index ae9c8bac66..5f66e9fca1 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java @@ -61,7 +61,7 @@ implements XSLFShapeContainer, GroupShape { protected XSLFGroupShape(CTGroupShape shape, XSLFSheet sheet){ super(shape,sheet); - _shapes = sheet.buildShapes(shape); + _shapes = XSLFSheet.buildShapes(shape, sheet); _grpSpPr = shape.getGrpSpPr(); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFMetroShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFMetroShape.java new file mode 100644 index 0000000000..87278f701a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFMetroShape.java @@ -0,0 +1,60 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.xslf.usermodel; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.sl.usermodel.Shape; +import org.apache.poi.util.Internal; +import org.apache.xmlbeans.XmlException; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; + +/** + * Experimental class for metro blobs, i.e. an alternative escher property + * containing an ooxml representation of the shape. + * This is the helper class for HSLFMetroShape to dive into OOXML classes + */ +@Internal +public class XSLFMetroShape { + /* + * parses the metro bytes to a XSLF shape + */ + public static Shape parseShape(byte metroBytes[]) + throws InvalidFormatException, IOException, XmlException { + PackagePartName shapePN = PackagingURIHelper.createPartName("/drs/shapexml.xml"); + OPCPackage pkg = null; + try { + pkg = OPCPackage.open(new ByteArrayInputStream(metroBytes)); + PackagePart shapePart = pkg.getPart(shapePN); + CTGroupShape gs = CTGroupShape.Factory.parse(shapePart.getInputStream()); + XSLFGroupShape xgs = new XSLFGroupShape(gs, null); + return xgs.getShapes().get(0); + } finally { + if (pkg != null) { + pkg.close(); + } + } + } +} 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 daca97131d..547e01fe8a 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -90,20 +90,20 @@ implements XSLFShapeContainer, Sheet { throw new IllegalStateException("SlideShow was not found"); } - protected List buildShapes(CTGroupShape spTree){ + protected static List buildShapes(CTGroupShape spTree, XSLFSheet sheet){ List shapes = new ArrayList(); for(XmlObject ch : spTree.selectPath("*")){ if(ch instanceof CTShape){ // simple shape - XSLFAutoShape shape = XSLFAutoShape.create((CTShape)ch, this); + XSLFAutoShape shape = XSLFAutoShape.create((CTShape)ch, sheet); shapes.add(shape); } else if (ch instanceof CTGroupShape){ - shapes.add(new XSLFGroupShape((CTGroupShape)ch, this)); + shapes.add(new XSLFGroupShape((CTGroupShape)ch, sheet)); } else if (ch instanceof CTConnector){ - shapes.add(new XSLFConnectorShape((CTConnector)ch, this)); + shapes.add(new XSLFConnectorShape((CTConnector)ch, sheet)); } else if (ch instanceof CTPicture){ - shapes.add(new XSLFPictureShape((CTPicture)ch, this)); + shapes.add(new XSLFPictureShape((CTPicture)ch, sheet)); } else if (ch instanceof CTGraphicalObjectFrame){ - XSLFGraphicFrame shape = XSLFGraphicFrame.create((CTGraphicalObjectFrame)ch, this); + XSLFGraphicFrame shape = XSLFGraphicFrame.create((CTGraphicalObjectFrame)ch, sheet); shapes.add(shape); } } @@ -156,7 +156,7 @@ implements XSLFShapeContainer, Sheet { _drawing = new XSLFDrawing(this, cgs); } if (_shapes == null) { - _shapes = buildShapes(cgs); + _shapes = buildShapes(cgs, this); } } diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java index 18632ffbf0..c8a1aeebd5 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java @@ -25,10 +25,15 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.awt.Color; +import java.io.File; import java.io.IOException; import java.util.List; +import org.apache.poi.POIDataSamples; +import org.apache.poi.hslf.usermodel.HSLFTextShape; import org.apache.poi.sl.usermodel.SimpleShape.Placeholder; +import org.apache.poi.sl.usermodel.SlideShow; +import org.apache.poi.sl.usermodel.SlideShowFactory; import org.apache.poi.sl.usermodel.TextParagraph.TextAlign; import org.apache.poi.sl.usermodel.VerticalAlignment; import org.apache.poi.xslf.XSLFTestDataSamples; @@ -40,9 +45,6 @@ import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType; import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; -/** - * @author Yegor Kozlov - */ public class TestXSLFTextShape { @Test @@ -913,4 +915,16 @@ public class TestXSLFTextShape { ppt.close(); } + + @Test + public void metroBlob() throws IOException { + File f = POIDataSamples.getSlideShowInstance().getFile("bug52297.ppt"); + SlideShow ppt = SlideShowFactory.create(f); + HSLFTextShape sh = (HSLFTextShape)ppt.getSlides().get(1).getShapes().get(3); + XSLFAutoShape xsh = (XSLFAutoShape)sh.getMetroShape(); + String textExp = " ___ ___ ___ ________ __ _______ ___ ___________ __________ __ _____ ___ ___ ___ _______ ____ ______ ___________ _____________ ___ _______ ______ ____ ______ __ ___________ __________ ___ _________ _____ ________ __________ ___ _______ __________ "; + String textAct = xsh.getText(); + ppt.close(); + assertEquals(textExp, textAct); + } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/HSLFMetroShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/HSLFMetroShape.java new file mode 100644 index 0000000000..e5d9f93a95 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/HSLFMetroShape.java @@ -0,0 +1,83 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hslf.model; + +import java.lang.reflect.Method; + +import org.apache.poi.ddf.AbstractEscherOptRecord; +import org.apache.poi.ddf.EscherComplexProperty; +import org.apache.poi.ddf.EscherProperties; +import org.apache.poi.ddf.EscherTertiaryOptRecord; +import org.apache.poi.hslf.usermodel.HSLFShape; +import org.apache.poi.sl.usermodel.Shape; +import org.apache.poi.util.Internal; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * Experimental class for metro blobs, i.e. an alternative escher property + * containing an ooxml representation of the shape + */ +@Internal +public class HSLFMetroShape> { + private static final POILogger LOGGER = POILogFactory.getLogger(HSLFMetroShape.class); + + private final HSLFShape shape; + + public HSLFMetroShape(HSLFShape shape) { + this.shape = shape; + } + + /** + * @return the bytes of the metro blob, which are bytes of an OPCPackage, i.e. a zip stream + */ + public byte[] getMetroBytes() { + AbstractEscherOptRecord opt = shape.getEscherChild(EscherTertiaryOptRecord.RECORD_ID); + if (opt != null) { + EscherComplexProperty ep = (EscherComplexProperty)opt.lookup(EscherProperties.GROUPSHAPE__METROBLOB); + if (ep != null) { + return ep.getComplexData(); + } + } + return null; + } + + /** + * @return the metro blob shape or null if either there's no metro blob or the ooxml classes + * aren't in the classpath + */ + @SuppressWarnings("unchecked") + public T getShape() { + byte metroBytes[] = getMetroBytes(); + if (metroBytes == null) { + return null; + } + + // org.apache.poi.xslf.usermodel.XSLFMetroShape + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + try { + Class ms = cl.loadClass("org.apache.poi.xslf.usermodel.XSLFMetroShape"); + Method m = ms.getMethod("parseShape", byte[].class); + return (T)m.invoke(null, new Object[]{metroBytes}); + } catch (Exception e) { + LOGGER.log(POILogger.ERROR, "can't process metro blob, check if all dependencies for POI OOXML are in the classpath.", e); + return null; + } + } +} + diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlide.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlide.java index 8e94421c5d..1e485de9ff 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlide.java @@ -499,4 +499,9 @@ public final class HSLFSlide extends HSLFSheet implements Slide { */ public static final int WrapThrough = 4; - + /** * TextRun object which holds actual text and format data */ @@ -116,11 +117,11 @@ implements TextShape { /** * This setting is used for supporting a deprecated alignment - * + * * @see */ boolean alignToBaseline = false; - + /** * Used to calculate text bounds */ @@ -176,11 +177,11 @@ implements TextShape { super.afterInsert(sh); storeText(); - + EscherTextboxWrapper thisTxtbox = getEscherTextboxWrapper(); if(thisTxtbox != null){ _escherContainer.addChildRecord(thisTxtbox.getEscherRecord()); - + PPDrawing ppdrawing = sh.getPPDrawing(); ppdrawing.addTextboxWrapper(thisTxtbox); // Ensure the escher layer knows about the added records @@ -199,10 +200,10 @@ implements TextShape { protected EscherTextboxWrapper getEscherTextboxWrapper(){ if(_txtbox != null) return _txtbox; - + EscherTextboxRecord textRecord = getEscherChild(EscherTextboxRecord.RECORD_ID); if (textRecord == null) return null; - + HSLFSheet sheet = getSheet(); if (sheet != null) { PPDrawing drawing = sheet.getPPDrawing(); @@ -219,7 +220,7 @@ implements TextShape { } } } - + _txtbox = new EscherTextboxWrapper(textRecord); return _txtbox; } @@ -236,15 +237,15 @@ implements TextShape { anchor.setSize(200, (int)anchor.getHeight()); setAnchor(anchor); } - double height = getTextHeight(); + double height = getTextHeight(); height += 1; // add a pixel to compensate rounding errors - + anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height); setAnchor(anchor); - + return anchor; - } - + } + /** * Returns the type of the text, from the TextHeaderAtom. * Possible values can be seen from TextHeaderAtom @@ -271,7 +272,7 @@ implements TextShape { paras.get(0).setRunType(type); } } - + /** * Returns the type of vertical alignment for the text. * One of the Anchor* constants defined in this class. @@ -310,7 +311,7 @@ implements TextShape { alignToBaseline = (align == AnchorBottomBaseline || align == AnchorBottomCenteredBaseline || align == AnchorTopBaseline || align == AnchorTopCenteredBaseline); - + return align; } @@ -334,15 +335,15 @@ implements TextShape { align = new int[]{AnchorBottom, AnchorBottomCentered, AnchorBottomBaseline, AnchorBottomCenteredBaseline}; break; } - + int align2 = align[(isCentered ? 1 : 0)+(alignToBaseline ? 2 : 0)]; - + setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, align2); } - + /** * @return true, if vertical alignment is relative to baseline - * this is only used for older versions less equals Office 2003 + * this is only used for older versions less equals Office 2003 */ public boolean isAlignToBaseline() { getAlignment(); @@ -358,7 +359,7 @@ implements TextShape { this.alignToBaseline = alignToBaseline; setAlignment(isHorizontalCentered(), getVerticalAlignment()); } - + @Override public boolean isHorizontalCentered() { int va = getAlignment(); @@ -378,7 +379,7 @@ implements TextShape { public void setHorizontalCentered(Boolean isCentered) { setAlignment(isCentered, getVerticalAlignment()); } - + @Override public VerticalAlignment getVerticalAlignment() { int va = getAlignment(); @@ -492,7 +493,7 @@ implements TextShape { * Returns the distance (in points) between the edge of the text frame * and the edge of the inscribed rectangle of the shape that contains the text. * Default value is 1/20 inch. - * + * * @param propId the id of the inset edge * @return the inset in points */ @@ -500,7 +501,7 @@ implements TextShape { AbstractEscherOptRecord opt = getEscherOptRecord(); EscherSimpleProperty prop = getEscherProperty(opt, propId); int val = prop == null ? (int)(Units.toEMU(Units.POINT_DPI)*defaultInch) : prop.getPropertyValue(); - return Units.toPoints(val); + return Units.toPoints(val); } /** @@ -509,8 +510,8 @@ implements TextShape { */ private void setInset(short propId, double margin){ setEscherProperty(propId, Units.toEMU(margin)); - } - + } + /** * Returns the value indicating word wrap. * @@ -524,7 +525,7 @@ implements TextShape { EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT); return prop == null ? WrapSquare : prop.getPropertyValue(); } - + /** * Specifies how the text should be wrapped * @@ -545,7 +546,7 @@ implements TextShape { public void setWordWrap(boolean wrap) { setWordWrapEx(wrap ? WrapSquare : WrapNone); } - + /** * @return id for the text. */ @@ -567,7 +568,7 @@ implements TextShape { @Override public List getTextParagraphs(){ if (!_paragraphs.isEmpty()) return _paragraphs; - + _txtbox = getEscherTextboxWrapper(); if (_txtbox == null) { _paragraphs.addAll(HSLFTextParagraph.createEmptyParagraph()); @@ -578,7 +579,7 @@ implements TextShape { // there are actually TextBoxRecords without extra data - see #54722 _paragraphs = HSLFTextParagraph.createEmptyParagraph(_txtbox); } - + if (_paragraphs.isEmpty()) { logger.log(POILogger.WARN, "TextRecord didn't contained any text lines"); } @@ -594,7 +595,7 @@ implements TextShape { for (HSLFTextParagraph p : _paragraphs) { p.setParentShape(this); } - + return _paragraphs; } @@ -675,7 +676,7 @@ implements TextShape { // them to \n String text = rawText.replace('\r','\n').replace('\u000b', replChr); */ - + /** * Return OEPlaceholderAtom, the atom that describes a placeholder. * @@ -777,13 +778,13 @@ implements TextShape { return HSLFTextParagraph.toExternalString(rawText, getRunType()); } - + // Update methods follow /** * 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. */ public HSLFTextRun appendText(String text, boolean newParagraph) { @@ -800,7 +801,7 @@ implements TextShape { setTextId(text.hashCode()); return htr; } - + /** * Saves the modified paragraphs/textrun to the records. * Also updates the styles to the correct text length. @@ -813,7 +814,7 @@ implements TextShape { /** * Returns the array of all hyperlinks in this text run - * + * * @return the array of all hyperlinks in this text run or null * if not found. */ @@ -880,5 +881,15 @@ implements TextShape { } } - + + /** + * Get alternative representation of text shape stored as metro blob escher property. + * The returned shape is the first shape in stored group shape of the metro blob + * + * @return null, if there's no alternative representation, otherwise the text shape + */ + public TextShape getMetroShape() { + HSLFMetroShape> mbs = new HSLFMetroShape>(this); + return mbs.getShape(); + } } \ No newline at end of file diff --git a/test-data/slideshow/bug52297.ppt b/test-data/slideshow/bug52297.ppt new file mode 100644 index 0000000000..79fd609b80 Binary files /dev/null and b/test-data/slideshow/bug52297.ppt differ -- cgit v1.2.3