/pptx/src/org/apache/poi/xslf/usermodel/RenderableShape.java
Java | 633 lines | 462 code | 79 blank | 92 comment | 105 complexity | 6e68dd3c8ec32b1c5501783941fcea98 MD5 | raw file
- /*
- * ====================================================================
- * 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.lang.reflect.Constructor;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Comparator;
-
- import net.pbdavey.awt.Graphics2D;
-
- import org.apache.poi.openxml4j.opc.PackagePart;
- import org.apache.poi.openxml4j.opc.PackageRelationship;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.Units;
- import org.apache.poi.xslf.model.PropertyFetcher;
- import org.apache.poi.xslf.model.geom.Context;
- import org.apache.poi.xslf.model.geom.CustomGeometry;
- import org.apache.poi.xslf.model.geom.Guide;
- import org.apache.poi.xslf.model.geom.IAdjustableShape;
- import org.apache.poi.xslf.model.geom.Outline;
- import org.apache.poi.xslf.model.geom.Path;
- 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.CTGeomGuide;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientStop;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTNoFillProperties;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTPathShadeProperties;
- import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType;
- //import org.openxmlformats.schemas.drawingml.x2006.main.CTPathShadeProperties;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D;
- 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.CTStyleMatrixReference;
- //import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType;
-
- import and.awt.BasicStroke;
- import and.awt.BufferedImage;
- import and.awt.Color;
- import and.awt.Paint;
- import and.awt.Shape;
- import and.awt.Stroke;
- import and.awt.TexturePaint;
- import and.awt.geom.AffineTransform;
- import and.awt.geom.Point2D;
- import and.awt.geom.Rectangle2D;
-
- /**
- * Encapsulates logic to translate DrawingML objects to Java2D
- */
- @Internal
- class RenderableShape {
- public final static Color NO_PAINT = new Color(0xFF, 0xFF, 0xFF, 0);
-
- private XSLFSimpleShape _shape;
-
- public RenderableShape(XSLFSimpleShape shape){
- _shape = shape;
- }
-
- /**
- * Convert shape fill into and.awt.Paint. The result is either Color or
- * TexturePaint or GradientPaint or null
- *
- * @param graphics the target graphics
- * @param obj the xml to read. Must contain elements from the EG_ColorChoice group:
- * <code>
- * a:scrgbClr RGB Color Model - Percentage Variant
- * a:srgbClr RGB Color Model - Hex Variant
- * a:hslClr Hue, Saturation, Luminance Color Model
- * a:sysClr System Color
- * a:schemeClr Scheme Color
- * a:prstClr Preset Color
- * </code>
- *
- * @param phClr context color
- * @param parentPart the parent package part. Any external references (images, etc.) are resolved relative to it.
- *
- * @return the applied Paint or null if none was applied
- */
- public Paint selectPaint(Graphics2D graphics, XmlObject obj, CTSchemeColor phClr, PackagePart parentPart) {
- XSLFTheme theme = _shape.getSheet().getTheme();
-
- Paint paint = null;
- if (obj instanceof CTNoFillProperties) {
- paint = NO_PAINT;
-
- }
- else if (obj instanceof CTSolidColorFillProperties) {
- CTSolidColorFillProperties solidFill = (CTSolidColorFillProperties) obj;
- XSLFColor c = new XSLFColor(solidFill, theme, phClr);
- paint = c.getColor();
- }
- else if (obj instanceof CTBlipFillProperties) {
- CTBlipFillProperties blipFill = (CTBlipFillProperties)obj;
- paint = createTexturePaint(blipFill, graphics, parentPart);
- }
- else if (obj instanceof CTGradientFillProperties) {
- Rectangle2D anchor = getAnchor(graphics);
- CTGradientFillProperties gradFill = (CTGradientFillProperties) obj;
- if (gradFill.isSetLin()) {
- paint = createLinearGradientPaint(graphics, gradFill, anchor, theme, phClr);
- } else if (gradFill.isSetPath()){
- CTPathShadeProperties ps = gradFill.getPath();
- if(ps.getPath() == STPathShadeType.CIRCLE){
- paint = createRadialGradientPaint(gradFill, anchor, theme, phClr);
- } else if (ps.getPath() == STPathShadeType.SHAPE){
- paint = toRadialGradientPaint(gradFill, anchor, theme, phClr);
- }
- }
- }
-
- return paint;
- }
-
- private Paint createTexturePaint(CTBlipFillProperties blipFill, Graphics2D graphics,
- PackagePart parentPart){
- Paint paint = null;
- CTBlip blip = blipFill.getBlip();
- String blipId = blip.getEmbed();
- PackageRelationship rel = parentPart.getRelationship(blipId);
- if (rel != null) {
- XSLFImageRenderer renderer = null;
- // if (graphics != null)
- // renderer = (XSLFImageRenderer) graphics.getRenderingHint(XSLFRenderingHint.IMAGE_RENDERER);
- if (renderer == null) renderer = new XSLFImageRenderer();
-
- try {
- BufferedImage img = renderer.readImage(parentPart.getRelatedPart(rel));
- // XXX: DD
- // if (blip.sizeOfAlphaModFixArray() > 0) {
- // float alpha = blip.getAlphaModFixArray(0).getAmt() / 100000.f;
- // AlphaComposite ac = AlphaComposite.getInstance(
- // AlphaComposite.SRC_OVER, alpha);
- // if (graphics != null) graphics.setComposite(ac);
- // }
-
- if(img != null) {
- paint = new TexturePaint(
- img, new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight()));
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- }
- }
- return paint;
- }
-
- private Paint createLinearGradientPaint(
- Graphics2D graphics,
- CTGradientFillProperties gradFill, Rectangle2D anchor,
- XSLFTheme theme, CTSchemeColor phClr) {
- double angle = gradFill.getLin().getAng() / 60000;
- @SuppressWarnings("deprecation")
- CTGradientStop[] gs = gradFill.getGsLst().getGsArray();
-
- Arrays.sort(gs, new Comparator<CTGradientStop>() {
- public int compare(CTGradientStop o1, CTGradientStop o2) {
- Integer pos1 = o1.getPos();
- Integer pos2 = o2.getPos();
- return pos1.compareTo(pos2);
- }
- });
-
- Color[] colors = new Color[gs.length];
- float[] fractions = new float[gs.length];
-
- AffineTransform at = AffineTransform.getRotateInstance(
- Math.toRadians(angle),
- anchor.getX() + anchor.getWidth() / 2,
- anchor.getY() + anchor.getHeight() / 2);
-
- double diagonal = Math.sqrt(anchor.getHeight() * anchor.getHeight() + anchor.getWidth() * anchor.getWidth());
- Point2D p1 = new Point2D.Double(anchor.getX() + anchor.getWidth() / 2 - diagonal / 2,
- anchor.getY() + anchor.getHeight() / 2);
- p1 = at.transform(p1, null);
-
- Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2);
- p2 = at.transform(p2, null);
-
- snapToAnchor(p1, anchor);
- snapToAnchor(p2, anchor);
-
- for (int i = 0; i < gs.length; i++) {
- CTGradientStop stop = gs[i];
- colors[i] = new XSLFColor(stop, theme, phClr).getColor();
- fractions[i] = stop.getPos() / 100000.f;
- }
-
- AffineTransform grAt = new AffineTransform();
- if(gradFill.isSetRotWithShape() || !gradFill.getRotWithShape()) {
- double rotation = _shape.getRotation();
- if (rotation != 0.) {
- double centerX = anchor.getX() + anchor.getWidth() / 2;
- double centerY = anchor.getY() + anchor.getHeight() / 2;
-
- grAt.translate(centerX, centerY);
- grAt.rotate(Math.toRadians(-rotation));
- grAt.translate(-centerX, -centerY);
- }
- }
-
- // Trick to return GradientPaint on JDK 1.5 and LinearGradientPaint on JDK 1.6+
- Paint paint = null;
- try {
- Class clz = Class.forName("and.awt.LinearGradientPaint");
- Class clzCycleMethod = Class.forName("and.awt.MultipleGradientPaint$CycleMethod");
- Class clzColorSpaceType = Class.forName("and.awt.MultipleGradientPaint$ColorSpaceType");
- Constructor c =
- clz.getConstructor(Point2D.class, Point2D.class, float[].class, Color[].class,
- clzCycleMethod, clzColorSpaceType, AffineTransform.class);
- paint = (Paint) c.newInstance(p1, p2, fractions, colors,
- Enum.valueOf(clzCycleMethod, "NO_CYCLE"),
- Enum.valueOf(clzColorSpaceType, "SRGB"), grAt);
- } catch (ClassNotFoundException e) {
- // XXX: DD
- // paint = new GradientPaint(p1, colors[0], p2, colors[colors.length - 1]);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- return paint;
- }
-
- /**
- * gradients with type=shape are enot supported by Java graphics.
- * We approximate it with a radial gradient.
- */
- private static Paint toRadialGradientPaint(
- CTGradientFillProperties gradFill, Rectangle2D anchor,
- XSLFTheme theme, CTSchemeColor phClr) {
-
- @SuppressWarnings("deprecation")
- CTGradientStop[] gs = gradFill.getGsLst().getGsArray();
- Arrays.sort(gs, new Comparator<CTGradientStop>() {
- public int compare(CTGradientStop o1, CTGradientStop o2) {
- Integer pos1 = o1.getPos();
- Integer pos2 = o2.getPos();
- return pos1.compareTo(pos2);
- }
- });
- gs[1].setPos(50000);
-
- CTGradientFillProperties g = CTGradientFillProperties.Factory.newInstance();
- g.set(gradFill);
- g.getGsLst().setGsArray(new CTGradientStop[]{gs[0], gs[1]});
- return createRadialGradientPaint(g, anchor, theme, phClr);
- }
-
- private static Paint createRadialGradientPaint(
- CTGradientFillProperties gradFill, Rectangle2D anchor,
- XSLFTheme theme, CTSchemeColor phClr) {
- @SuppressWarnings("deprecation")
- CTGradientStop[] gs = gradFill.getGsLst().getGsArray();
-
- Point2D pCenter = new Point2D.Double(anchor.getX() + anchor.getWidth()/2,
- anchor.getY() + anchor.getHeight()/2);
-
- float radius = (float)Math.max(anchor.getWidth(), anchor.getHeight());
-
- Arrays.sort(gs, new Comparator<CTGradientStop>() {
- public int compare(CTGradientStop o1, CTGradientStop o2) {
- Integer pos1 = o1.getPos();
- Integer pos2 = o2.getPos();
- return pos1.compareTo(pos2);
- }
- });
-
- Color[] colors = new Color[gs.length];
- float[] fractions = new float[gs.length];
-
-
- for (int i = 0; i < gs.length; i++) {
- CTGradientStop stop = gs[i];
- colors[i] = new XSLFColor(stop, theme, phClr).getColor();
- fractions[i] = stop.getPos() / 100000.f;
- }
-
- // Trick to return GradientPaint on JDK 1.5 and RadialGradientPaint on JDK 1.6+
- Paint paint = null;
- try {
- Class clz = Class.forName("and.awt.RadialGradientPaint");
- Constructor c =
- clz.getConstructor(Point2D.class, float.class,
- float[].class, Color[].class);
- paint = (Paint) c.newInstance(pCenter, radius, fractions, colors);
- } catch (ClassNotFoundException e) {
- // the result on JDK 1.5 is incorrect, but it is better than nothing
- // paint = new GradientPaint(
- // new Point2D.Double(anchor.getX(), anchor.getY()),
- // colors[0], pCenter, colors[colors.length - 1]);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- return paint;
- }
-
- private static void snapToAnchor(Point2D p, Rectangle2D anchor) {
- if (p.getX() < anchor.getX()) {
- p.setLocation(anchor.getX(), p.getY());
- } else if (p.getX() > (anchor.getX() + anchor.getWidth())) {
- p.setLocation(anchor.getX() + anchor.getWidth(), p.getY());
- }
-
- if (p.getY() < anchor.getY()) {
- p.setLocation(p.getX(), anchor.getY());
- } else if (p.getY() > (anchor.getY() + anchor.getHeight())) {
- p.setLocation(p.getX(), anchor.getY() + anchor.getHeight());
- }
- }
-
-
- Paint getPaint(Graphics2D graphics, XmlObject spPr, CTSchemeColor phClr) {
-
- Paint paint = null;
- for (XmlObject obj : spPr.selectPath("*")) {
- paint = selectPaint(graphics, obj, phClr, _shape.getSheet().getPackagePart());
- if(paint != null) break;
- }
- return paint == NO_PAINT ? null : paint;
- }
-
-
- /**
- * fetch shape fill as a and.awt.Paint
- *
- * @return either Color or GradientPaint or TexturePaint or null
- */
- Paint getFillPaint(final Graphics2D graphics) {
- PropertyFetcher<Paint> fetcher = new PropertyFetcher<Paint>() {
- public boolean fetch(XSLFSimpleShape shape) {
- CTShapeProperties spPr = shape.getSpPr();
- if (spPr.isSetNoFill()) {
- setValue(RenderableShape.NO_PAINT); // use it as 'nofill' value
- return true;
- }
- Paint paint = getPaint(graphics, spPr, null);
- if (paint != null) {
- setValue(paint);
- return true;
- }
- return false;
- }
- };
- _shape.fetchShapeProperty(fetcher);
-
- Paint paint = fetcher.getValue();
- if (paint == null) {
- // fill color was not found, check if it is defined in the theme
- CTShapeStyle style = _shape.getSpStyle();
- if (style != null) {
- // get a reference to a fill style within the style matrix.
- CTStyleMatrixReference fillRef = style.getFillRef();
- // 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();
- CTSchemeColor phClr = fillRef.getSchemeClr();
- XSLFSheet sheet = _shape.getSheet();
- XSLFTheme theme = sheet.getTheme();
- XmlObject fillProps = null;
- if(idx >= 1 && idx <= 999){
- fillProps = theme.getXmlObject().
- getThemeElements().getFmtScheme().getFillStyleLst().selectPath("*")[idx - 1];
- } else if (idx >= 1001 ){
- fillProps = theme.getXmlObject().
- getThemeElements().getFmtScheme().getBgFillStyleLst().selectPath("*")[idx - 1001];
- }
- if(fillProps != null) {
- paint = selectPaint(graphics, fillProps, phClr, sheet.getPackagePart());
- }
- }
- }
- return paint == RenderableShape.NO_PAINT ? null : paint;
- }
-
- public Paint getLinePaint(final Graphics2D graphics) {
- PropertyFetcher<Paint> fetcher = new PropertyFetcher<Paint>() {
- public boolean fetch(XSLFSimpleShape shape) {
- CTLineProperties spPr = shape.getSpPr().getLn();
- if (spPr != null) {
- if (spPr.isSetNoFill()) {
- setValue(NO_PAINT); // use it as 'nofill' value
- return true;
- }
- Paint paint = getPaint(graphics, spPr, null);
- if (paint != null) {
- setValue(paint);
- return true;
- }
- }
- return false;
-
- }
- };
- _shape.fetchShapeProperty(fetcher);
-
- Paint paint = fetcher.getValue();
- if (paint == null) {
- // line color was not found, check if it is defined in the theme
- CTShapeStyle style = _shape.getSpStyle();
- if (style != null) {
- // get a reference to a line style within the style matrix.
- CTStyleMatrixReference lnRef = style.getLnRef();
- int idx = (int)lnRef.getIdx();
- CTSchemeColor phClr = lnRef.getSchemeClr();
- if(idx > 0){
- XSLFTheme theme = _shape.getSheet().getTheme();
- XmlObject lnProps = theme.getXmlObject().
- getThemeElements().getFmtScheme().getLnStyleLst().selectPath("*")[idx - 1];
- paint = getPaint(graphics, lnProps, phClr);
- }
- }
- }
-
- return paint == NO_PAINT ? null : paint;
- }
-
- /**
- * convert PPT dash into and.awt.BasicStroke
- *
- * The mapping is derived empirically on PowerPoint 2010
- */
- private static float[] getDashPattern(LineDash lineDash, float lineWidth) {
- float[] dash = null;
- switch (lineDash) {
- case SYS_DOT:
- dash = new float[]{lineWidth, lineWidth};
- break;
- case SYS_DASH:
- dash = new float[]{2 * lineWidth, 2 * lineWidth};
- break;
- case DASH:
- dash = new float[]{3 * lineWidth, 4 * lineWidth};
- break;
- case DASH_DOT:
- dash = new float[]{4 * lineWidth, 3 * lineWidth, lineWidth,
- 3 * lineWidth};
- break;
- case LG_DASH:
- dash = new float[]{8 * lineWidth, 3 * lineWidth};
- break;
- case LG_DASH_DOT:
- dash = new float[]{8 * lineWidth, 3 * lineWidth, lineWidth,
- 3 * lineWidth};
- break;
- case LG_DASH_DOT_DOT:
- dash = new float[]{8 * lineWidth, 3 * lineWidth, lineWidth,
- 3 * lineWidth, lineWidth, 3 * lineWidth};
- break;
- }
- return dash;
- }
-
-
- public Stroke applyStroke(Graphics2D graphics) {
-
- float lineWidth = (float) _shape.getLineWidth();
- if(lineWidth == 0.0f) lineWidth = 0.25f; // Both PowerPoint and OOo draw zero-length lines as 0.25pt
-
- LineDash lineDash = _shape.getLineDash();
- float[] dash = null;
- float dash_phase = 0;
- if (lineDash != null) {
- dash = getDashPattern(lineDash, lineWidth);
- }
-
- int cap = BasicStroke.CAP_BUTT;
- LineCap lineCap = _shape.getLineCap();
- if (lineCap != null) {
- switch (lineCap) {
- case ROUND:
- cap = BasicStroke.CAP_ROUND;
- break;
- case SQUARE:
- cap = BasicStroke.CAP_SQUARE;
- break;
- default:
- cap = BasicStroke.CAP_BUTT;
- break;
- }
- }
-
- int meter = BasicStroke.JOIN_ROUND;
-
- Stroke stroke = new BasicStroke(lineWidth, cap, meter, Math.max(1, lineWidth), dash,
- dash_phase);
- graphics.setStroke(stroke);
- return stroke;
- }
-
- public void render(Graphics2D graphics){
- Collection<Outline> elems = computeOutlines(graphics);
-
- // shadow
- XSLFShadow shadow = _shape.getShadow();
-
- // 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());
- else if(line != null) shadow.draw(graphics, o.getOutline());
- }
- }
- // then fill the shape interior
- if(fill != null) for(Outline o : elems){
- if(o.getPath().isFilled()){
- graphics.setPaint(fill);
- graphics.fill(o.getOutline());
- }
- }
-
- // then draw any content within this shape (text, image, etc.)
- _shape.drawContent(graphics);
-
- // then stroke the shape outline
- if(line != null) for(Outline o : elems){
- if(o.getPath().isStroked()){
- graphics.setPaint(line);
- graphics.draw(o.getOutline());
- }
- }
- }
-
- private Collection<Outline> computeOutlines(Graphics2D graphics) {
-
- Collection<Outline> lst = new ArrayList<Outline>();
- CustomGeometry geom = _shape.getGeometry();
- if(geom == null) {
- return lst;
- }
-
- Rectangle2D anchor = getAnchor(graphics);
- for (Path p : geom) {
-
- double w = p.getW() == -1 ? anchor.getWidth() * Units.EMU_PER_POINT : p.getW();
- double h = p.getH() == -1 ? anchor.getHeight() * Units.EMU_PER_POINT : p.getH();
-
- // the guides in the shape definitions are all defined relative to each other,
- // so we build the path starting from (0,0).
- final Rectangle2D pathAnchor = new Rectangle2D.Double(
- 0,
- 0,
- w,
- h
- );
-
- Context ctx = new Context(geom, pathAnchor, new IAdjustableShape() {
-
- public Guide getAdjustValue(String name) {
- CTPresetGeometry2D prst = _shape.getSpPr().getPrstGeom();
- if (prst.isSetAvLst()) {
- for (CTGeomGuide g : prst.getAvLst().getGdList()) {
- if (g.getName().equals(name)) {
- return new Guide(g);
- }
- }
- }
- return null;
- }
- }) ;
-
- Shape gp = p.getPath(ctx);
-
- // translate the result to the canvas coordinates in points
- AffineTransform at = new AffineTransform();
- at.translate(anchor.getX(), anchor.getY());
-
- double scaleX, scaleY;
- if (p.getW() != -1) {
- scaleX = anchor.getWidth() / p.getW();
- } else {
- scaleX = 1.0 / Units.EMU_PER_POINT;
- }
- if (p.getH() != -1) {
- scaleY = anchor.getHeight() / p.getH();
- } else {
- scaleY = 1.0 / Units.EMU_PER_POINT;
- }
-
- at.scale(scaleX, scaleY);
-
- Shape canvasShape = at.createTransformedShape(gp);
-
- lst.add(new Outline(canvasShape, p));
- }
-
- return lst;
- }
-
- public Rectangle2D getAnchor(Graphics2D graphics) {
- Rectangle2D anchor = _shape.getAnchor();
- if(graphics == null) {
- return anchor;
- }
-
- AffineTransform tx = (AffineTransform)graphics.getRenderingHint(XSLFRenderingHint.GROUP_TRANSFORM);
- if(tx != null) {
- anchor = tx.createTransformedShape(anchor).getBounds2D();
- }
- return anchor;
- }
- }