123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- /* ====================================================================
- 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.sl.draw;
-
- import java.awt.BasicStroke;
- import java.awt.Color;
- import java.awt.Graphics2D;
- import java.awt.Paint;
- import java.awt.geom.AffineTransform;
- import java.awt.geom.Ellipse2D;
- import java.awt.geom.Path2D;
- import java.awt.geom.Rectangle2D;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import javax.xml.bind.JAXBContext;
- import javax.xml.bind.JAXBElement;
- import javax.xml.bind.Unmarshaller;
- import javax.xml.stream.EventFilter;
- import javax.xml.stream.XMLEventReader;
- import javax.xml.stream.XMLInputFactory;
- import javax.xml.stream.events.StartElement;
- import javax.xml.stream.events.XMLEvent;
-
- import org.apache.poi.sl.draw.binding.CTCustomGeometry2D;
- import org.apache.poi.sl.draw.geom.Context;
- 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;
- import org.apache.poi.sl.usermodel.SimpleShape;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.StaxHelper;
- 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);
- }
-
- @Override
- public void draw(Graphics2D graphics) {
- DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
- Paint fill = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint());
- Paint line = drawPaint.getPaint(graphics, getShape().getStrokeStyle().getPaint());
- BasicStroke stroke = getStroke(); // the stroke applies both to the shadow and the shape
- graphics.setStroke(stroke);
-
- Collection<Outline> elems = computeOutlines(graphics);
-
- // first paint the shadow
- drawShadow(graphics, elems, fill, line);
-
- // then fill the shape interior
- if (fill != null) {
- for (Outline o : elems) {
- if (o.getPath().isFilled()){
- Paint fillMod = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint(), o.getPath().getFill());
- if (fillMod != null) {
- graphics.setPaint(fillMod);
- 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);
-
- // then stroke the shape outline
- if(line != null) {
- graphics.setPaint(line);
- graphics.setStroke(stroke);
- for(Outline o : elems){
- if(o.getPath().isStroked()){
- java.awt.Shape s = o.getOutline();
- graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s);
- graphics.draw(s);
- }
- }
- }
-
- // draw line decorations
- drawDecoration(graphics, line, stroke);
- }
-
- protected void drawDecoration(Graphics2D graphics, Paint line, BasicStroke stroke) {
- if(line == null) {
- return;
- }
- graphics.setPaint(line);
-
- List<Outline> 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(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);
- bounds = tailShape.getBounds2D();
- at.translate(x2 - bounds.getWidth() / 2, y2 - bounds.getHeight() / 2);
- at.rotate(alpha, bounds.getX() + bounds.getWidth() / 2, bounds.getY() + bounds.getHeight() / 2);
- break;
- case STEALTH:
- case ARROW:
- p = new Path(false, true);
- Path2D.Double arrow = new Path2D.Double();
- arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));
- arrow.lineTo(0, 0);
- arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));
- tailShape = arrow;
- at.translate(x2, y2);
- at.rotate(alpha);
- break;
- case TRIANGLE:
- p = new Path();
- Path2D.Double triangle = new Path2D.Double();
- triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));
- triangle.lineTo(0, 0);
- triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));
- triangle.closePath();
- tailShape = triangle;
- at.translate(x2, y2);
- at.rotate(alpha);
- break;
- 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(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);
- bounds = headShape.getBounds2D();
- at.translate(x1 - bounds.getWidth() / 2, y1 - bounds.getHeight() / 2);
- at.rotate(alpha, bounds.getX() + bounds.getWidth() / 2, bounds.getY() + bounds.getHeight() / 2);
- break;
- case STEALTH:
- case ARROW:
- p = new Path(false, true);
- Path2D.Double arrow = new Path2D.Double();
- arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));
- arrow.lineTo(0, 0);
- arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));
- headShape = arrow;
- at.translate(x1, y1);
- at.rotate(alpha);
- break;
- case TRIANGLE:
- p = new Path();
- Path2D.Double triangle = new Path2D.Double();
- triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));
- triangle.lineTo(0, 0);
- triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));
- triangle.closePath();
- headShape = triangle;
- at.translate(x1, y1);
- at.rotate(alpha);
- break;
- default:
- break;
- }
-
- if (headShape != null) {
- headShape = at.createTransformedShape(headShape);
- }
- return headShape == null ? null : new Outline(headShape, p);
- }
-
- public BasicStroke getStroke() {
- return getStroke(getShape().getStrokeStyle());
- }
-
- protected void drawShadow(
- Graphics2D graphics
- , Collection<Outline> outlines
- , Paint fill
- , Paint line
- ) {
- Shadow<?,?> shadow = getShape().getShadow();
- if (shadow == null || (fill == null && line == null)) {
- return;
- }
-
- SolidPaint shadowPaint = shadow.getFillStyle();
- Color shadowColor = DrawPaint.applyColorTransform(shadowPaint.getSolidColor());
-
- double shapeRotation = getShape().getRotation();
- if(getShape().getFlipVertical()) {
- shapeRotation += 180;
- }
- double angle = shadow.getAngle() - shapeRotation;
- 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()) {
- graphics.draw(s);
- }
- }
-
- 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<String, CustomGeometry> presets = (graphics == null)
- ? null
- : (Map<String, CustomGeometry>)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
- public boolean accept(XMLEvent event) {
- return event.isStartElement();
- }
- };
-
- try {
- XMLInputFactory staxFactory = StaxHelper.newXMLInputFactory();
- XMLEventReader staxReader = staxFactory.createXMLEventReader(presetIS);
- XMLEventReader staxFiltRd = staxFactory.createFilteredReader(staxReader, startElementFilter);
- // Ignore StartElement:
- staxFiltRd.nextEvent();
- // 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<org.apache.poi.sl.draw.binding.CTCustomGeometry2D> el = unmarshaller.unmarshal(staxReader, CTCustomGeometry2D.class);
- CTCustomGeometry2D cusGeom = el.getValue();
-
- presets.put(cusName, new CustomGeometry(cusGeom));
- }
-
- staxFiltRd.close();
- staxReader.close();
- } catch (Exception e) {
- throw new RuntimeException("Unable to load preset geometries.", e);
- } finally {
- IOUtils.closeQuietly(presetIS);
- }
- }
-
- return presets.get(name);
- }
-
- protected Collection<Outline> computeOutlines(Graphics2D graphics) {
- final SimpleShape<?,?> sh = getShape();
-
- List<Outline> lst = new ArrayList<>();
- CustomGeometry geom = sh.getGeometry();
- if(geom == null) {
- return lst;
- }
-
- Rectangle2D anchor = getAnchor(graphics, sh);
- if(anchor == null) {
- return lst;
- }
- for (Path p : geom) {
-
- double w = p.getW(), h = p.getH(), scaleX, scaleY;
- if (w == -1) {
- w = Units.toEMU(anchor.getWidth());
- scaleX = Units.toPoints(1);
- } else if (anchor.getWidth() == 0) {
- scaleX = 1;
- } else {
- scaleX = anchor.getWidth() / w;
- }
- if (h == -1) {
- h = Units.toEMU(anchor.getHeight());
- scaleY = Units.toPoints(1);
- } else if (anchor.getHeight() == 0) {
- scaleY = 1;
- } else {
- scaleY = anchor.getHeight() / h;
- }
-
- // 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, sh);
-
- java.awt.Shape gp = p.getPath(ctx);
-
- // translate the result to the canvas coordinates in points
- AffineTransform at = new AffineTransform();
- at.translate(anchor.getX(), anchor.getY());
- at.scale(scaleX, scaleY);
-
- java.awt.Shape canvasShape = at.createTransformedShape(gp);
-
- lst.add(new Outline(canvasShape, p));
- }
-
- return lst;
- }
-
- @Override
- protected SimpleShape<?,?> getShape() {
- return (SimpleShape<?,?>)shape;
- }
- }
|