Add support for HSLF metro blobs

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1713318 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2015-11-08 23:13:28 +00:00
parent 1a32b63fd3
commit e8d108ff22
10 changed files with 235 additions and 50 deletions

View File

@ -61,7 +61,7 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> {
protected XSLFGroupShape(CTGroupShape shape, XSLFSheet sheet){
super(shape,sheet);
_shapes = sheet.buildShapes(shape);
_shapes = XSLFSheet.buildShapes(shape, sheet);
_grpSpPr = shape.getGrpSpPr();
}

View File

@ -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();
}
}
}
}

View File

@ -90,20 +90,20 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
throw new IllegalStateException("SlideShow was not found");
}
protected List<XSLFShape> buildShapes(CTGroupShape spTree){
protected static List<XSLFShape> buildShapes(CTGroupShape spTree, XSLFSheet sheet){
List<XSLFShape> shapes = new ArrayList<XSLFShape>();
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<XSLFShape,XSLFTextParagraph> {
_drawing = new XSLFDrawing(this, cgs);
}
if (_shapes == null) {
_shapes = buildShapes(cgs);
_shapes = buildShapes(cgs, this);
}
}

View File

@ -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);
}
}

View File

@ -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<T extends Shape<?,?>> {
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;
}
}
}

View File

@ -499,4 +499,9 @@ public final class HSLFSlide extends HSLFSheet implements Slide<HSLFShape,HSLFTe
// TODO Auto-generated method stub
}
@Override
public boolean getFollowMasterGraphics() {
return getFollowMasterObjects();
}
}

View File

@ -17,8 +17,12 @@
package org.apache.poi.hslf.usermodel;
import org.apache.poi.ddf.*;
import org.apache.poi.sl.usermodel.*;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.sl.usermodel.ShapeContainer;
import org.apache.poi.sl.usermodel.ShapeType;
import org.apache.poi.sl.usermodel.TextBox;
import org.apache.poi.sl.usermodel.VerticalAlignment;
/**
* Represents a TextFrame shape in PowerPoint.
@ -88,5 +92,4 @@ public class HSLFTextBox extends HSLFTextShape implements TextBox<HSLFShape,HSLF
setVerticalAlignment(VerticalAlignment.TOP);
setEscherProperty(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x20002);
}
}

View File

@ -993,6 +993,15 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (HSLFTextRun r : getTextRuns()) {
sb.append(r.getRawText());
}
return toExternalString(sb.toString(), getRunType());
}
/**
* Returns a new string with line breaks converted into internal ppt

View File

@ -34,6 +34,7 @@ import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.ddf.EscherSimpleProperty;
import org.apache.poi.ddf.EscherTextboxRecord;
import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.model.HSLFMetroShape;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.InteractiveInfoAtom;
@ -102,7 +103,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
*/
public static final int WrapThrough = 4;
/**
* TextRun object which holds actual text and format data
*/
@ -116,11 +117,11 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
/**
* This setting is used for supporting a deprecated alignment
*
*
* @see <a href=""></a>
*/
boolean alignToBaseline = false;
/**
* Used to calculate text bounds
*/
@ -176,11 +177,11 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
}
}
}
_txtbox = new EscherTextboxWrapper(textRecord);
return _txtbox;
}
@ -236,15 +237,15 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
paras.get(0).setRunType(type);
}
}
/**
* Returns the type of vertical alignment for the text.
* One of the <code>Anchor*</code> constants defined in this class.
@ -310,7 +311,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
alignToBaseline = (align == AnchorBottomBaseline || align == AnchorBottomCenteredBaseline
|| align == AnchorTopBaseline || align == AnchorTopCenteredBaseline);
return align;
}
@ -334,15 +335,15 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
this.alignToBaseline = alignToBaseline;
setAlignment(isHorizontalCentered(), getVerticalAlignment());
}
@Override
public boolean isHorizontalCentered() {
int va = getAlignment();
@ -378,7 +379,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
public void setHorizontalCentered(Boolean isCentered) {
setAlignment(isCentered, getVerticalAlignment());
}
@Override
public VerticalAlignment getVerticalAlignment() {
int va = getAlignment();
@ -492,7 +493,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
* 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<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
*/
private void setInset(short propId, double margin){
setEscherProperty(propId, Units.toEMU(margin));
}
}
/**
* Returns the value indicating word wrap.
*
@ -524,7 +525,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
public void setWordWrap(boolean wrap) {
setWordWrapEx(wrap ? WrapSquare : WrapNone);
}
/**
* @return id for the text.
*/
@ -567,7 +568,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
@Override
public List<HSLFTextParagraph> getTextParagraphs(){
if (!_paragraphs.isEmpty()) return _paragraphs;
_txtbox = getEscherTextboxWrapper();
if (_txtbox == null) {
_paragraphs.addAll(HSLFTextParagraph.createEmptyParagraph());
@ -578,7 +579,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
// 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<HSLFShape,HSLFTextParagraph> {
for (HSLFTextParagraph p : _paragraphs) {
p.setParentShape(this);
}
return _paragraphs;
}
@ -675,7 +676,7 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
// them to \n
String text = rawText.replace('\r','\n').replace('\u000b', replChr);
*/
/**
* Return <code>OEPlaceholderAtom</code>, the atom that describes a placeholder.
*
@ -777,13 +778,13 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
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<HSLFShape,HSLFTextParagraph> {
/**
* Returns the array of all hyperlinks in this text run
*
*
* @return the array of all hyperlinks in this text run or <code>null</code>
* if not found.
*/
@ -880,5 +881,15 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
}
}
/**
* 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<TextShape<?,?>> mbs = new HSLFMetroShape<TextShape<?,?>>(this);
return mbs.getShape();
}
}

Binary file not shown.