| /* |
| * ==================================================================== |
| * 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.awt.Graphics2D; |
| import java.awt.geom.Dimension2D; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Arrays; |
| |
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
| import org.apache.poi.openxml4j.opc.PackagePart; |
| import org.apache.poi.openxml4j.opc.PackageRelationship; |
| import org.apache.poi.sl.draw.DrawFactory; |
| import org.apache.poi.sl.draw.DrawPaint; |
| import org.apache.poi.sl.usermodel.ColorStyle; |
| import org.apache.poi.sl.usermodel.MasterSheet; |
| import org.apache.poi.sl.usermodel.PaintStyle; |
| import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; |
| import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; |
| import org.apache.poi.sl.usermodel.PlaceableShape; |
| import org.apache.poi.sl.usermodel.Placeholder; |
| import org.apache.poi.sl.usermodel.PlaceholderDetails; |
| import org.apache.poi.sl.usermodel.Shape; |
| import org.apache.poi.sl.usermodel.SimpleShape; |
| import org.apache.poi.util.Beta; |
| import org.apache.poi.util.Dimension2DDouble; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.Units; |
| import org.apache.poi.xslf.model.PropertyFetcher; |
| import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties; |
| import org.apache.xmlbeans.XmlCursor; |
| import org.apache.xmlbeans.XmlObject; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientStop; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeStyle; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTTileInfoProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType; |
| import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; |
| import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; |
| |
| /** |
| * Base super-class class for all shapes in PresentationML |
| */ |
| @Beta |
| public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> { |
| static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main"; |
| |
| private final XmlObject _shape; |
| private final XSLFSheet _sheet; |
| private XSLFShapeContainer _parent; |
| |
| private CTShapeStyle _spStyle; |
| private CTNonVisualDrawingProps _nvPr; |
| |
| protected XSLFShape(XmlObject shape, XSLFSheet sheet) { |
| _shape = shape; |
| _sheet = sheet; |
| } |
| |
| /** |
| * @return the xml bean holding this shape's data |
| */ |
| public final XmlObject getXmlObject() { |
| // it's final because the xslf inheritance hierarchy is not necessary the same as |
| // the (not existing) xmlbeans hierarchy and subclasses shouldn't narrow it's return value |
| return _shape; |
| } |
| |
| @Override |
| public XSLFSheet getSheet() { |
| return _sheet; |
| } |
| |
| @Override |
| public String getShapeName() { |
| CTNonVisualDrawingProps nonVisualDrawingProps = getCNvPr(); |
| return nonVisualDrawingProps == null ? null : nonVisualDrawingProps.getName(); |
| } |
| |
| @Override |
| public int getShapeId() { |
| CTNonVisualDrawingProps nonVisualDrawingProps = getCNvPr(); |
| if (nonVisualDrawingProps == null) { |
| throw new IllegalStateException("no underlying shape exists"); |
| } |
| return Math.toIntExact(nonVisualDrawingProps.getId()); |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| if (this instanceof PlaceableShape) { |
| PlaceableShape<?,?> ps = (PlaceableShape<?,?>)this; |
| ps.setAnchor(sh.getAnchor()); |
| } |
| |
| |
| } |
| |
| public void setParent(XSLFShapeContainer parent) { |
| this._parent = parent; |
| } |
| |
| @Override |
| public XSLFShapeContainer getParent() { |
| return this._parent; |
| } |
| |
| protected PaintStyle getFillPaint() { |
| final XSLFTheme theme = getSheet().getTheme(); |
| final boolean hasPlaceholder = getPlaceholder() != null; |
| PropertyFetcher<PaintStyle> fetcher = new PropertyFetcher<PaintStyle>() { |
| @Override |
| public boolean fetch(XSLFShape shape) { |
| PackagePart pp = shape.getSheet().getPackagePart(); |
| if (shape instanceof XSLFPictureShape) { |
| CTPicture pic = (CTPicture)shape.getXmlObject(); |
| if (pic.getBlipFill() != null) { |
| setValue(selectPaint(pic.getBlipFill(), pp)); |
| return true; |
| } |
| } |
| |
| XSLFFillProperties fp = XSLFPropertiesDelegate.getFillDelegate(shape.getShapeProperties()); |
| if (fp == null) { |
| return false; |
| } |
| |
| if (fp.isSetNoFill()) { |
| setValue(null); |
| return true; |
| } |
| |
| PaintStyle paint = selectPaint(fp, null, pp, theme, hasPlaceholder); |
| if (paint != null) { |
| setValue(paint); |
| return true; |
| } |
| |
| CTShapeStyle style = shape.getSpStyle(); |
| if (style != null) { |
| fp = XSLFPropertiesDelegate.getFillDelegate(style.getFillRef()); |
| paint = selectPaint(fp, null, pp, theme, hasPlaceholder); |
| } |
| if (paint != null) { |
| setValue(paint); |
| return true; |
| } |
| |
| |
| return false; |
| } |
| }; |
| fetchShapeProperty(fetcher); |
| |
| return fetcher.getValue(); |
| } |
| |
| @SuppressWarnings("unused") |
| protected CTBackgroundProperties getBgPr() { |
| return getChild(CTBackgroundProperties.class, PML_NS, "bgPr"); |
| } |
| |
| @SuppressWarnings("unused") |
| protected CTStyleMatrixReference getBgRef() { |
| return getChild(CTStyleMatrixReference.class, PML_NS, "bgRef"); |
| } |
| |
| protected CTGroupShapeProperties getGrpSpPr() { |
| return getChild(CTGroupShapeProperties.class, PML_NS, "grpSpPr"); |
| } |
| |
| protected CTNonVisualDrawingProps getCNvPr() { |
| if (_nvPr == null) { |
| String xquery = "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:cNvPr"; |
| _nvPr = selectProperty(CTNonVisualDrawingProps.class, xquery); |
| } |
| return _nvPr; |
| } |
| |
| @SuppressWarnings("WeakerAccess") |
| protected CTShapeStyle getSpStyle() { |
| if (_spStyle == null) { |
| _spStyle = getChild(CTShapeStyle.class, PML_NS, "style"); |
| } |
| return _spStyle; |
| } |
| |
| /** |
| * Return direct child objects of this shape |
| * |
| * @param childClass the class to cast the properties to |
| * @param namespace the namespace - usually it is {@code "http://schemas.openxmlformats.org/presentationml/2006/main"} |
| * @param nodename the node name, without prefix |
| * @return the properties object or null if it can't be found |
| */ |
| @SuppressWarnings({"unchecked", "WeakerAccess", "unused", "SameParameterValue"}) |
| protected <T extends XmlObject> T getChild(Class<T> childClass, String namespace, String nodename) { |
| XmlCursor cur = getXmlObject().newCursor(); |
| T child = null; |
| if (cur.toChild(namespace, nodename)) { |
| child = (T)cur.getObject(); |
| } |
| if (cur.toChild(XSLFRelation.NS_DRAWINGML, nodename)) { |
| child = (T)cur.getObject(); |
| } |
| cur.dispose(); |
| return child; |
| } |
| |
| public boolean isPlaceholder() { |
| return getPlaceholderDetails().getCTPlaceholder(false) != null; |
| } |
| |
| /** |
| * @see PlaceholderDetails#getPlaceholder() |
| */ |
| public Placeholder getPlaceholder() { |
| return getPlaceholderDetails().getPlaceholder(); |
| } |
| |
| /** |
| * @see PlaceholderDetails#setPlaceholder(Placeholder) |
| */ |
| public void setPlaceholder(final Placeholder placeholder) { |
| getPlaceholderDetails().setPlaceholder(placeholder); |
| } |
| |
| /** |
| * @see SimpleShape#getPlaceholderDetails() |
| */ |
| @SuppressWarnings("WeakerAccess") |
| public XSLFPlaceholderDetails getPlaceholderDetails() { |
| return new XSLFPlaceholderDetails(this); |
| } |
| |
| /** |
| * As there's no xmlbeans hierarchy, but XSLF works with subclassing, not all |
| * child classes work with a {@link CTShape} object, but often contain the same |
| * properties. This method is the generalized form of selecting and casting those |
| * properties. |
| * |
| * @param resultClass the requested result class |
| * @param xquery the simple (xmlbean) xpath expression to the property |
| * @return the xml object at the xpath location, or null if not found |
| */ |
| @SuppressWarnings({"unchecked", "WeakerAccess"}) |
| protected <T extends XmlObject> T selectProperty(Class<T> resultClass, String xquery) { |
| XmlObject[] rs = getXmlObject().selectPath(xquery); |
| if (rs.length == 0) { |
| return null; |
| } |
| return (resultClass.isInstance(rs[0])) ? (T)rs[0] : null; |
| } |
| |
| /** |
| * Walk up the inheritance tree and fetch shape properties.<p> |
| * |
| * The following order of inheritance is assumed:<p> |
| * <ol> |
| * <li>slide |
| * <li>slideLayout |
| * <li>slideMaster |
| * </ol> |
| * |
| * Currently themes and their defaults aren't correctly handled |
| * |
| * @param visitor the object that collects the desired property |
| * @return true if the property was fetched |
| */ |
| @SuppressWarnings("WeakerAccess") |
| protected boolean fetchShapeProperty(PropertyFetcher<?> visitor) { |
| // try shape properties in slide |
| if (visitor.fetch(this)) { |
| return true; |
| } |
| |
| final CTPlaceholder ph = getPlaceholderDetails().getCTPlaceholder(false); |
| if (ph == null) { |
| return false; |
| } |
| MasterSheet<XSLFShape,XSLFTextParagraph> sm = getSheet().getMasterSheet(); |
| |
| // try slide layout |
| if (sm instanceof XSLFSlideLayout) { |
| XSLFSlideLayout slideLayout = (XSLFSlideLayout)sm; |
| XSLFSimpleShape placeholderShape = slideLayout.getPlaceholder(ph); |
| if (placeholderShape != null && visitor.fetch(placeholderShape)) { |
| return true; |
| } |
| sm = slideLayout.getMasterSheet(); |
| } |
| |
| // try slide master |
| if (sm instanceof XSLFSlideMaster) { |
| XSLFSlideMaster master = (XSLFSlideMaster)sm; |
| int textType = getPlaceholderType(ph); |
| XSLFSimpleShape masterShape = master.getPlaceholderByType(textType); |
| return masterShape != null && visitor.fetch(masterShape); |
| } |
| |
| return false; |
| } |
| |
| private static int getPlaceholderType(CTPlaceholder ph) { |
| if ( !ph.isSetType()) { |
| return STPlaceholderType.INT_BODY; |
| } |
| |
| switch (ph.getType().intValue()) { |
| case STPlaceholderType.INT_TITLE: |
| case STPlaceholderType.INT_CTR_TITLE: |
| return STPlaceholderType.INT_TITLE; |
| case STPlaceholderType.INT_FTR: |
| case STPlaceholderType.INT_SLD_NUM: |
| case STPlaceholderType.INT_DT: |
| return ph.getType().intValue(); |
| default: |
| return STPlaceholderType.INT_BODY; |
| } |
| } |
| |
| /** |
| * Convert shape fill into java.awt.Paint. The result is either Color or |
| * TexturePaint or GradientPaint or null |
| * |
| * @param fp a properties handler specific to the underlying shape properties |
| * @param phClr context color |
| * @param parentPart the parent package part. Any external references (images, etc.) are resolved relative to it. |
| * @param theme the theme for the shape/sheet |
| * |
| * @return the applied Paint or null if none was applied |
| */ |
| @SuppressWarnings("WeakerAccess") |
| protected static PaintStyle selectPaint(XSLFFillProperties fp, final CTSchemeColor phClr, final PackagePart parentPart, final XSLFTheme theme, boolean hasPlaceholder) { |
| if (fp == null || fp.isSetNoFill()) { |
| return null; |
| } else if (fp.isSetSolidFill()) { |
| return selectPaint(fp.getSolidFill(), phClr, theme); |
| } else if (fp.isSetBlipFill()) { |
| return selectPaint(fp.getBlipFill(), parentPart); |
| } else if (fp.isSetGradFill()) { |
| return selectPaint(fp.getGradFill(), phClr, theme); |
| } else if (fp.isSetMatrixStyle()) { |
| return selectPaint(fp.getMatrixStyle(), theme, fp.isLineStyle(), hasPlaceholder); |
| } else { |
| return null; |
| } |
| } |
| |
| @SuppressWarnings("WeakerAccess") |
| protected static PaintStyle selectPaint(CTSolidColorFillProperties solidFill, CTSchemeColor phClr, final XSLFTheme theme) { |
| if (solidFill.isSetSchemeClr()) { |
| // if there's a reference to the placeholder color, |
| // stop evaluating further and let the caller select |
| // the next style inheritance level |
| // if (STSchemeColorVal.PH_CLR.equals(solidFill.getSchemeClr().getVal())) { |
| // return null; |
| // } |
| if (phClr == null) { |
| phClr = solidFill.getSchemeClr(); |
| } |
| } |
| final XSLFColor c = new XSLFColor(solidFill, theme, phClr); |
| return DrawPaint.createSolidPaint(c.getColorStyle()); |
| } |
| |
| @SuppressWarnings("WeakerAccess") |
| protected static PaintStyle selectPaint(final CTBlipFillProperties blipFill, final PackagePart parentPart) { |
| final CTBlip blip = blipFill.getBlip(); |
| return new TexturePaint() { |
| private PackagePart getPart() { |
| try { |
| String blipId = blip.getEmbed(); |
| PackageRelationship rel = parentPart.getRelationship(blipId); |
| return parentPart.getRelatedPart(rel); |
| } catch (InvalidFormatException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public InputStream getImageData() { |
| try { |
| return getPart().getInputStream(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public String getContentType() { |
| if (blip == null || !blip.isSetEmbed() || blip.getEmbed().isEmpty()) { |
| return null; |
| } |
| /* TOOD: map content-type */ |
| return getPart().getContentType(); |
| } |
| |
| @Override |
| public int getAlpha() { |
| return (blip.sizeOfAlphaModFixArray() > 0) |
| ? blip.getAlphaModFixArray(0).getAmt() |
| : 100000; |
| } |
| |
| @Override |
| public boolean isRotatedWithShape() { |
| return blipFill.isSetRotWithShape() && blipFill.getRotWithShape(); |
| } |
| |
| @Override |
| public Dimension2D getScale() { |
| CTTileInfoProperties tile = blipFill.getTile(); |
| return (tile == null) ? null : new Dimension2DDouble( |
| tile.isSetSx() ? tile.getSx()/100_000. : 1, |
| tile.isSetSy() ? tile.getSy()/100_000. : 1); |
| } |
| |
| @Override |
| public Point2D getOffset() { |
| CTTileInfoProperties tile = blipFill.getTile(); |
| return (tile == null) ? null : new Point2D.Double( |
| tile.isSetTx() ? Units.toPoints(tile.getTx()) : 0, |
| tile.isSetTy() ? Units.toPoints(tile.getTy()) : 0); |
| } |
| |
| @Override |
| public FlipMode getFlipMode() { |
| CTTileInfoProperties tile = blipFill.getTile(); |
| switch (tile == null ? STTileFlipMode.INT_NONE : tile.getFlip().intValue()) { |
| default: |
| case STTileFlipMode.INT_NONE: |
| return FlipMode.NONE; |
| case STTileFlipMode.INT_X: |
| return FlipMode.X; |
| case STTileFlipMode.INT_Y: |
| return FlipMode.Y; |
| case STTileFlipMode.INT_XY: |
| return FlipMode.XY; |
| } |
| } |
| |
| @Override |
| public TextureAlignment getAlignment() { |
| CTTileInfoProperties tile = blipFill.getTile(); |
| return (tile == null || !tile.isSetAlgn()) ? null |
| : TextureAlignment.fromOoxmlId(tile.getAlgn().toString()); |
| } |
| }; |
| } |
| |
| @SuppressWarnings("WeakerAccess") |
| protected static PaintStyle selectPaint(final CTGradientFillProperties gradFill, CTSchemeColor phClr, final XSLFTheme theme) { |
| |
| @SuppressWarnings("deprecation") |
| final CTGradientStop[] gs = gradFill.getGsLst() == null ? |
| new CTGradientStop[0] : gradFill.getGsLst().getGsArray(); |
| |
| Arrays.sort(gs, (o1, o2) -> { |
| int pos1 = o1.getPos(); |
| int pos2 = o2.getPos(); |
| return Integer.compare(pos1, pos2); |
| }); |
| |
| final ColorStyle[] cs = new ColorStyle[gs.length]; |
| final float[] fractions = new float[gs.length]; |
| |
| int i=0; |
| for (CTGradientStop cgs : gs) { |
| CTSchemeColor phClrCgs = phClr; |
| if (phClrCgs == null && cgs.isSetSchemeClr()) { |
| phClrCgs = cgs.getSchemeClr(); |
| } |
| cs[i] = new XSLFColor(cgs, theme, phClrCgs).getColorStyle(); |
| fractions[i] = cgs.getPos() / 100000.f; |
| i++; |
| } |
| |
| return new GradientPaint() { |
| |
| @Override |
| public double getGradientAngle() { |
| return (gradFill.isSetLin()) |
| ? gradFill.getLin().getAng() / 60000.d |
| : 0; |
| } |
| |
| @Override |
| public ColorStyle[] getGradientColors() { |
| return cs; |
| } |
| |
| @Override |
| public float[] getGradientFractions() { |
| return fractions; |
| } |
| |
| @Override |
| public boolean isRotatedWithShape() { |
| return gradFill.getRotWithShape(); |
| } |
| |
| @Override |
| public GradientType getGradientType() { |
| if (gradFill.isSetLin()) { |
| return GradientType.linear; |
| } |
| |
| if (gradFill.isSetPath()) { |
| /* TODO: handle rect path */ |
| STPathShadeType.Enum ps = gradFill.getPath().getPath(); |
| if (ps == STPathShadeType.CIRCLE) { |
| return GradientType.circular; |
| } else if (ps == STPathShadeType.SHAPE) { |
| return GradientType.shape; |
| } |
| } |
| |
| return GradientType.linear; |
| } |
| }; |
| } |
| |
| @SuppressWarnings("WeakerAccess") |
| protected static PaintStyle selectPaint(CTStyleMatrixReference fillRef, final XSLFTheme theme, boolean isLineStyle, boolean hasPlaceholder) { |
| if (fillRef == null) { |
| return null; |
| } |
| |
| // The idx attribute refers to the index of a fill style or |
| // background fill style within the presentation's style matrix, defined by the fmtScheme element. |
| // value of 0 or 1000 indicates no background, |
| // values 1-999 refer to the index of a fill style within the fillStyleLst element |
| // values 1001 and above refer to the index of a background fill style within the bgFillStyleLst element. |
| int idx = (int)fillRef.getIdx(); |
| CTStyleMatrix matrix = theme.getXmlObject().getThemeElements().getFmtScheme(); |
| final XmlObject styleLst; |
| int childIdx; |
| if (idx >= 1 && idx <= 999) { |
| childIdx = idx-1; |
| styleLst = (isLineStyle) ? matrix.getLnStyleLst() : matrix.getFillStyleLst(); |
| } else if (idx >= 1001 ){ |
| childIdx = idx - 1001; |
| styleLst = matrix.getBgFillStyleLst(); |
| } else { |
| return null; |
| } |
| XmlCursor cur = styleLst.newCursor(); |
| XSLFFillProperties fp = null; |
| if (cur.toChild(childIdx)) { |
| fp = XSLFPropertiesDelegate.getFillDelegate(cur.getObject()); |
| } |
| cur.dispose(); |
| |
| CTSchemeColor phClr = fillRef.getSchemeClr(); |
| PaintStyle res = selectPaint(fp, phClr, theme.getPackagePart(), theme, hasPlaceholder); |
| // check for empty placeholder value |
| // see http://officeopenxml.com/prSlide-color.php - "Color Placeholders within Themes" |
| if (res != null || hasPlaceholder) { |
| return res; |
| } |
| XSLFColor col = new XSLFColor(fillRef, theme, phClr); |
| return DrawPaint.createSolidPaint(col.getColorStyle()); |
| } |
| |
| @Override |
| public void draw(Graphics2D graphics, Rectangle2D bounds) { |
| DrawFactory.getInstance(graphics).drawShape(graphics, this, bounds); |
| } |
| |
| /** |
| * Return the shape specific (visual) properties |
| * |
| * @return the shape specific properties |
| */ |
| protected XmlObject getShapeProperties() { |
| return getChild(CTShapeProperties.class, PML_NS, "spPr"); |
| } |
| } |