You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RenderableShape.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.xslf.usermodel;
  20. import org.apache.poi.openxml4j.opc.PackagePart;
  21. import org.apache.poi.openxml4j.opc.PackageRelationship;
  22. import org.apache.poi.util.Internal;
  23. import org.apache.poi.util.Units;
  24. import org.apache.poi.xslf.model.PropertyFetcher;
  25. import org.apache.poi.xslf.model.geom.Context;
  26. import org.apache.poi.xslf.model.geom.CustomGeometry;
  27. import org.apache.poi.xslf.model.geom.Guide;
  28. import org.apache.poi.xslf.model.geom.IAdjustableShape;
  29. import org.apache.poi.xslf.model.geom.Outline;
  30. import org.apache.poi.xslf.model.geom.Path;
  31. import org.apache.xmlbeans.XmlObject;
  32. import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip;
  33. import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
  34. import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomGuide;
  35. import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
  36. import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientStop;
  37. import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
  38. import org.openxmlformats.schemas.drawingml.x2006.main.CTNoFillProperties;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.CTPathShadeProperties;
  40. import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D;
  41. import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor;
  42. import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
  43. import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeStyle;
  44. import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
  45. import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference;
  46. import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType;
  47. import java.awt.AlphaComposite;
  48. import java.awt.BasicStroke;
  49. import java.awt.Color;
  50. import java.awt.GradientPaint;
  51. import java.awt.Graphics2D;
  52. import java.awt.Paint;
  53. import java.awt.Shape;
  54. import java.awt.Stroke;
  55. import java.awt.TexturePaint;
  56. import java.awt.geom.AffineTransform;
  57. import java.awt.geom.Point2D;
  58. import java.awt.geom.Rectangle2D;
  59. import java.awt.image.BufferedImage;
  60. import java.lang.reflect.Constructor;
  61. import java.util.ArrayList;
  62. import java.util.Arrays;
  63. import java.util.Collection;
  64. import java.util.Comparator;
  65. /**
  66. * Encapsulates logic to translate DrawingML objects to Java2D
  67. */
  68. @Internal
  69. class RenderableShape {
  70. public final static Color NO_PAINT = new Color(0xFF, 0xFF, 0xFF, 0);
  71. private XSLFSimpleShape _shape;
  72. public RenderableShape(XSLFSimpleShape shape){
  73. _shape = shape;
  74. }
  75. /**
  76. * Convert shape fill into java.awt.Paint. The result is either Color or
  77. * TexturePaint or GradientPaint or null
  78. *
  79. * @param graphics the target graphics
  80. * @param obj the xml to read. Must contain elements from the EG_ColorChoice group:
  81. * <code>
  82. * a:scrgbClr RGB Color Model - Percentage Variant
  83. * a:srgbClr RGB Color Model - Hex Variant
  84. * a:hslClr Hue, Saturation, Luminance Color Model
  85. * a:sysClr System Color
  86. * a:schemeClr Scheme Color
  87. * a:prstClr Preset Color
  88. * </code>
  89. *
  90. * @param phClr context color
  91. * @param parentPart the parent package part. Any external references (images, etc.) are resolved relative to it.
  92. *
  93. * @return the applied Paint or null if none was applied
  94. */
  95. public Paint selectPaint(Graphics2D graphics, XmlObject obj, CTSchemeColor phClr, PackagePart parentPart) {
  96. XSLFTheme theme = _shape.getSheet().getTheme();
  97. Rectangle2D anchor = _shape.getAnchor();
  98. Paint paint = null;
  99. if (obj instanceof CTNoFillProperties) {
  100. paint = NO_PAINT;
  101. }
  102. else if (obj instanceof CTSolidColorFillProperties) {
  103. CTSolidColorFillProperties solidFill = (CTSolidColorFillProperties) obj;
  104. XSLFColor c = new XSLFColor(solidFill, theme, phClr);
  105. paint = c.getColor();
  106. }
  107. else if (obj instanceof CTBlipFillProperties) {
  108. CTBlipFillProperties blipFill = (CTBlipFillProperties)obj;
  109. paint = createTexturePaint(blipFill, graphics, parentPart);
  110. }
  111. else if (obj instanceof CTGradientFillProperties) {
  112. CTGradientFillProperties gradFill = (CTGradientFillProperties) obj;
  113. if (gradFill.isSetLin()) {
  114. paint = createLinearGradientPaint(gradFill, anchor, theme, phClr);
  115. } else if (gradFill.isSetPath()){
  116. CTPathShadeProperties ps = gradFill.getPath();
  117. if(ps.getPath() == STPathShadeType.CIRCLE){
  118. paint = createRadialGradientPaint(gradFill, anchor, theme, phClr);
  119. }
  120. }
  121. }
  122. return paint;
  123. }
  124. private Paint createTexturePaint(CTBlipFillProperties blipFill, Graphics2D graphics,
  125. PackagePart parentPart){
  126. Paint paint = null;
  127. CTBlip blip = blipFill.getBlip();
  128. String blipId = blip.getEmbed();
  129. PackageRelationship rel = parentPart.getRelationship(blipId);
  130. if (rel != null) {
  131. XSLFImageRendener renderer = null;
  132. if (graphics != null)
  133. renderer = (XSLFImageRendener) graphics.getRenderingHint(XSLFRenderingHint.IMAGE_RENDERER);
  134. if (renderer == null) renderer = new XSLFImageRendener();
  135. try {
  136. BufferedImage img = renderer.readImage(parentPart.getRelatedPart(rel));
  137. if (blip.sizeOfAlphaModFixArray() > 0) {
  138. float alpha = blip.getAlphaModFixArray(0).getAmt() / 100000.f;
  139. AlphaComposite ac = AlphaComposite.getInstance(
  140. AlphaComposite.SRC_OVER, alpha);
  141. if (graphics != null) graphics.setComposite(ac);
  142. }
  143. if(img != null) {
  144. paint = new TexturePaint(
  145. img, new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight()));
  146. }
  147. }
  148. catch (Exception e) {
  149. e.printStackTrace();
  150. }
  151. }
  152. return paint;
  153. }
  154. private static Paint createLinearGradientPaint(
  155. CTGradientFillProperties gradFill, Rectangle2D anchor,
  156. XSLFTheme theme, CTSchemeColor phClr) {
  157. double angle = gradFill.getLin().getAng() / 60000;
  158. CTGradientStop[] gs = gradFill.getGsLst().getGsArray();
  159. Arrays.sort(gs, new Comparator<CTGradientStop>() {
  160. public int compare(CTGradientStop o1, CTGradientStop o2) {
  161. Integer pos1 = o1.getPos();
  162. Integer pos2 = o2.getPos();
  163. return pos1.compareTo(pos2);
  164. }
  165. });
  166. Color[] colors = new Color[gs.length];
  167. float[] fractions = new float[gs.length];
  168. AffineTransform at = AffineTransform.getRotateInstance(
  169. Math.toRadians(angle),
  170. anchor.getX() + anchor.getWidth() / 2,
  171. anchor.getY() + anchor.getHeight() / 2);
  172. double diagonal = Math.sqrt(anchor.getHeight() * anchor.getHeight() + anchor.getWidth() * anchor.getWidth());
  173. Point2D p1 = new Point2D.Double(anchor.getX() + anchor.getWidth() / 2 - diagonal / 2,
  174. anchor.getY() + anchor.getHeight() / 2);
  175. p1 = at.transform(p1, null);
  176. Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2);
  177. p2 = at.transform(p2, null);
  178. snapToAnchor(p1, anchor);
  179. snapToAnchor(p2, anchor);
  180. for (int i = 0; i < gs.length; i++) {
  181. CTGradientStop stop = gs[i];
  182. colors[i] = new XSLFColor(stop, theme, phClr).getColor();
  183. fractions[i] = stop.getPos() / 100000.f;
  184. }
  185. // Trick to return GradientPaint on JDK 1.5 and LinearGradientPaint on JDK 1.6+
  186. Paint paint;
  187. try {
  188. Class clz = Class.forName("java.awt.LinearGradientPaint");
  189. Constructor c =
  190. clz.getConstructor(Point2D.class, Point2D.class, float[].class, Color[].class);
  191. paint = (Paint) c.newInstance(p1, p2, fractions, colors);
  192. } catch (ClassNotFoundException e) {
  193. paint = new GradientPaint(p1, colors[0], p2, colors[colors.length - 1]);
  194. } catch (Exception e) {
  195. throw new RuntimeException(e);
  196. }
  197. return paint;
  198. }
  199. private static Paint createRadialGradientPaint(
  200. CTGradientFillProperties gradFill, Rectangle2D anchor,
  201. XSLFTheme theme, CTSchemeColor phClr) {
  202. CTGradientStop[] gs = gradFill.getGsLst().getGsArray();
  203. Point2D pCenter = new Point2D.Double(anchor.getX() + anchor.getWidth()/2,
  204. anchor.getY() + anchor.getHeight()/2);
  205. float radius = (float)Math.max(anchor.getWidth(), anchor.getHeight());
  206. Arrays.sort(gs, new Comparator<CTGradientStop>() {
  207. public int compare(CTGradientStop o1, CTGradientStop o2) {
  208. Integer pos1 = o1.getPos();
  209. Integer pos2 = o2.getPos();
  210. return pos1.compareTo(pos2);
  211. }
  212. });
  213. Color[] colors = new Color[gs.length];
  214. float[] fractions = new float[gs.length];
  215. for (int i = 0; i < gs.length; i++) {
  216. CTGradientStop stop = gs[i];
  217. colors[i] = new XSLFColor(stop, theme, phClr).getColor();
  218. fractions[i] = stop.getPos() / 100000.f;
  219. }
  220. // Trick to return GradientPaint on JDK 1.5 and RadialGradientPaint on JDK 1.6+
  221. Paint paint;
  222. try {
  223. Class clz = Class.forName("java.awt.RadialGradientPaint");
  224. Constructor c =
  225. clz.getConstructor(Point2D.class, float.class,
  226. float[].class, Color[].class);
  227. paint = (Paint) c.newInstance(pCenter, radius, fractions, colors);
  228. } catch (ClassNotFoundException e) {
  229. // the result on JDK 1.5 is incorrect, but it is better than nothing
  230. paint = new GradientPaint(
  231. new Point2D.Double(anchor.getX(), anchor.getY()),
  232. colors[0], pCenter, colors[colors.length - 1]);
  233. } catch (Exception e) {
  234. throw new RuntimeException(e);
  235. }
  236. return paint;
  237. }
  238. private static void snapToAnchor(Point2D p, Rectangle2D anchor) {
  239. if (p.getX() < anchor.getX()) {
  240. p.setLocation(anchor.getX(), p.getY());
  241. } else if (p.getX() > (anchor.getX() + anchor.getWidth())) {
  242. p.setLocation(anchor.getX() + anchor.getWidth(), p.getY());
  243. }
  244. if (p.getY() < anchor.getY()) {
  245. p.setLocation(p.getX(), anchor.getY());
  246. } else if (p.getY() > (anchor.getY() + anchor.getHeight())) {
  247. p.setLocation(p.getX(), anchor.getY() + anchor.getHeight());
  248. }
  249. }
  250. @SuppressWarnings("deprecation") // getXYZArray() array accessors are deprecated
  251. Paint getPaint(Graphics2D graphics, XmlObject spPr, CTSchemeColor phClr) {
  252. Paint paint = null;
  253. for (XmlObject obj : spPr.selectPath("*")) {
  254. paint = selectPaint(graphics, obj, phClr, _shape.getSheet().getPackagePart());
  255. if(paint != null) break;
  256. }
  257. return paint == NO_PAINT ? null : paint;
  258. }
  259. /**
  260. * fetch shape fill as a java.awt.Paint
  261. *
  262. * @return either Color or GradientPaint or TexturePaint or null
  263. */
  264. Paint getFillPaint(final Graphics2D graphics) {
  265. PropertyFetcher<Paint> fetcher = new PropertyFetcher<Paint>() {
  266. public boolean fetch(XSLFSimpleShape shape) {
  267. CTShapeProperties spPr = shape.getSpPr();
  268. if (spPr.isSetNoFill()) {
  269. setValue(RenderableShape.NO_PAINT); // use it as 'nofill' value
  270. return true;
  271. }
  272. Paint paint = getPaint(graphics, spPr, null);
  273. if (paint != null) {
  274. setValue(paint);
  275. return true;
  276. }
  277. return false;
  278. }
  279. };
  280. _shape.fetchShapeProperty(fetcher);
  281. Paint paint = fetcher.getValue();
  282. if (paint == null) {
  283. // fill color was not found, check if it is defined in the theme
  284. CTShapeStyle style = _shape.getSpStyle();
  285. if (style != null) {
  286. // get a reference to a fill style within the style matrix.
  287. CTStyleMatrixReference fillRef = style.getFillRef();
  288. // The idx attribute refers to the index of a fill style or
  289. // background fill style within the presentation's style matrix, defined by the fmtScheme element.
  290. // value of 0 or 1000 indicates no background,
  291. // values 1-999 refer to the index of a fill style within the fillStyleLst element
  292. // values 1001 and above refer to the index of a background fill style within the bgFillStyleLst element.
  293. int idx = (int)fillRef.getIdx();
  294. CTSchemeColor phClr = fillRef.getSchemeClr();
  295. XSLFSheet sheet = _shape.getSheet();
  296. XSLFTheme theme = sheet.getTheme();
  297. XmlObject fillProps = null;
  298. if(idx >= 1 && idx <= 999){
  299. fillProps = theme.getXmlObject().
  300. getThemeElements().getFmtScheme().getFillStyleLst().selectPath("*")[idx - 1];
  301. } else if (idx >= 1001 ){
  302. fillProps = theme.getXmlObject().
  303. getThemeElements().getFmtScheme().getBgFillStyleLst().selectPath("*")[idx - 1001];
  304. }
  305. if(fillProps != null) {
  306. paint = selectPaint(graphics, fillProps, phClr, sheet.getPackagePart());
  307. }
  308. }
  309. }
  310. return paint == RenderableShape.NO_PAINT ? null : paint;
  311. }
  312. public Paint getLinePaint(final Graphics2D graphics) {
  313. PropertyFetcher<Paint> fetcher = new PropertyFetcher<Paint>() {
  314. public boolean fetch(XSLFSimpleShape shape) {
  315. CTLineProperties spPr = shape.getSpPr().getLn();
  316. if (spPr != null) {
  317. if (spPr.isSetNoFill()) {
  318. setValue(NO_PAINT); // use it as 'nofill' value
  319. return true;
  320. }
  321. Paint paint = getPaint(graphics, spPr, null);
  322. if (paint != null) {
  323. setValue(paint);
  324. return true;
  325. }
  326. }
  327. return false;
  328. }
  329. };
  330. _shape.fetchShapeProperty(fetcher);
  331. Paint paint = fetcher.getValue();
  332. if (paint == null) {
  333. // line color was not found, check if it is defined in the theme
  334. CTShapeStyle style = _shape.getSpStyle();
  335. if (style != null) {
  336. // get a reference to a line style within the style matrix.
  337. CTStyleMatrixReference lnRef = style.getLnRef();
  338. int idx = (int)lnRef.getIdx();
  339. CTSchemeColor phClr = lnRef.getSchemeClr();
  340. if(idx > 0){
  341. XSLFTheme theme = _shape.getSheet().getTheme();
  342. XmlObject lnProps = theme.getXmlObject().
  343. getThemeElements().getFmtScheme().getLnStyleLst().selectPath("*")[idx - 1];
  344. paint = getPaint(graphics, lnProps, phClr);
  345. }
  346. }
  347. }
  348. return paint == NO_PAINT ? null : paint;
  349. }
  350. /**
  351. * convert PPT dash into java.awt.BasicStroke
  352. *
  353. * The mapping is derived empirically on PowerPoint 2010
  354. */
  355. private static float[] getDashPattern(LineDash lineDash, float lineWidth) {
  356. float[] dash = null;
  357. switch (lineDash) {
  358. case SYS_DOT:
  359. dash = new float[]{lineWidth, lineWidth};
  360. break;
  361. case SYS_DASH:
  362. dash = new float[]{2 * lineWidth, 2 * lineWidth};
  363. break;
  364. case DASH:
  365. dash = new float[]{3 * lineWidth, 4 * lineWidth};
  366. break;
  367. case DASH_DOT:
  368. dash = new float[]{4 * lineWidth, 3 * lineWidth, lineWidth,
  369. 3 * lineWidth};
  370. break;
  371. case LG_DASH:
  372. dash = new float[]{8 * lineWidth, 3 * lineWidth};
  373. break;
  374. case LG_DASH_DOT:
  375. dash = new float[]{8 * lineWidth, 3 * lineWidth, lineWidth,
  376. 3 * lineWidth};
  377. break;
  378. case LG_DASH_DOT_DOT:
  379. dash = new float[]{8 * lineWidth, 3 * lineWidth, lineWidth,
  380. 3 * lineWidth, lineWidth, 3 * lineWidth};
  381. break;
  382. }
  383. return dash;
  384. }
  385. public Stroke applyStroke(Graphics2D graphics) {
  386. float lineWidth = (float) _shape.getLineWidth();
  387. LineDash lineDash = _shape.getLineDash();
  388. float[] dash = null;
  389. float dash_phase = 0;
  390. if (lineDash != null) {
  391. dash = getDashPattern(lineDash, lineWidth);
  392. }
  393. int cap = BasicStroke.CAP_BUTT;
  394. LineCap lineCap = _shape.getLineCap();
  395. if (lineCap != null) {
  396. switch (lineCap) {
  397. case ROUND:
  398. cap = BasicStroke.CAP_ROUND;
  399. break;
  400. case SQUARE:
  401. cap = BasicStroke.CAP_SQUARE;
  402. break;
  403. default:
  404. cap = BasicStroke.CAP_BUTT;
  405. break;
  406. }
  407. }
  408. int meter = BasicStroke.JOIN_ROUND;
  409. Stroke stroke = new BasicStroke(lineWidth, cap, meter, Math.max(1, lineWidth), dash,
  410. dash_phase);
  411. graphics.setStroke(stroke);
  412. return stroke;
  413. }
  414. public void render(Graphics2D graphics){
  415. Collection<Outline> elems = computeOutlines();
  416. // shadow
  417. XSLFShadow shadow = _shape.getShadow();
  418. // first fill
  419. Paint fill = getFillPaint(graphics);
  420. if(fill != null) for(Outline o : elems){
  421. if(o.getPath().isFilled()){
  422. if(shadow != null) shadow.fill(graphics, o.getOutline());
  423. graphics.setPaint(fill);
  424. graphics.fill(o.getOutline());
  425. }
  426. }
  427. // then draw any content within this shape (text, image, etc.)
  428. _shape.drawContent(graphics);
  429. // then stroke the shape outline
  430. Paint line = getLinePaint(graphics);
  431. if(line != null) for(Outline o : elems){
  432. if(o.getPath().isStroked()){
  433. applyStroke(graphics); // the stroke applies both to the shadow and the shape
  434. if(shadow != null) shadow.draw(graphics, o.getOutline());
  435. graphics.setPaint(line);
  436. graphics.draw(o.getOutline());
  437. }
  438. }
  439. }
  440. private Collection<Outline> computeOutlines() {
  441. CustomGeometry geom = _shape.getGeometry();
  442. Collection<Outline> lst = new ArrayList<Outline>();
  443. Rectangle2D anchor = _shape.getAnchor();
  444. for (Path p : geom) {
  445. double w = p.getW() == -1 ? anchor.getWidth() * Units.EMU_PER_POINT : p.getW();
  446. double h = p.getH() == -1 ? anchor.getHeight() * Units.EMU_PER_POINT : p.getH();
  447. // the guides in the shape definitions are all defined relative to each other,
  448. // so we build the path starting from (0,0).
  449. final Rectangle2D pathAnchor = new Rectangle2D.Double(
  450. 0,
  451. 0,
  452. w,
  453. h
  454. );
  455. Context ctx = new Context(geom, pathAnchor, new IAdjustableShape() {
  456. public Guide getAdjustValue(String name) {
  457. CTPresetGeometry2D prst = _shape.getSpPr().getPrstGeom();
  458. if (prst.isSetAvLst()) {
  459. for (CTGeomGuide g : prst.getAvLst().getGdList()) {
  460. if (g.getName().equals(name)) {
  461. return new Guide(g);
  462. }
  463. }
  464. }
  465. return null;
  466. }
  467. }) ;
  468. Shape gp = p.getPath(ctx);
  469. // translate the result to the canvas coordinates in points
  470. AffineTransform at = new AffineTransform();
  471. at.translate(anchor.getX(), anchor.getY());
  472. double scaleX, scaleY;
  473. if (p.getW() != -1) {
  474. scaleX = anchor.getWidth() / p.getW();
  475. } else {
  476. scaleX = 1.0 / Units.EMU_PER_POINT;
  477. }
  478. if (p.getH() != -1) {
  479. scaleY = anchor.getHeight() / p.getH();
  480. } else {
  481. scaleY = 1.0 / Units.EMU_PER_POINT;
  482. }
  483. at.scale(scaleX, scaleY);
  484. Shape canvasShape = at.createTransformedShape(gp);
  485. lst.add(new Outline(canvasShape, p));
  486. }
  487. // add any shape-specific stuff here (line decorations, etc.)
  488. lst.addAll(_shape.getCustomOutlines());
  489. return lst;
  490. }
  491. }