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.

HwmfDraw.java 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hwmf.record;
  16. import java.awt.Shape;
  17. import java.awt.geom.Arc2D;
  18. import java.awt.geom.Area;
  19. import java.awt.geom.Dimension2D;
  20. import java.awt.geom.Ellipse2D;
  21. import java.awt.geom.Line2D;
  22. import java.awt.geom.Path2D;
  23. import java.awt.geom.Point2D;
  24. import java.awt.geom.Rectangle2D;
  25. import java.awt.geom.RoundRectangle2D;
  26. import java.io.IOException;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.function.Supplier;
  31. import org.apache.poi.hwmf.draw.HwmfGraphics;
  32. import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
  33. import org.apache.poi.util.Dimension2DDouble;
  34. import org.apache.poi.util.GenericRecordJsonWriter;
  35. import org.apache.poi.util.GenericRecordUtil;
  36. import org.apache.poi.util.Internal;
  37. import org.apache.poi.util.LittleEndianConsts;
  38. import org.apache.poi.util.LittleEndianInputStream;
  39. @SuppressWarnings("WeakerAccess")
  40. public final class HwmfDraw {
  41. private HwmfDraw() {}
  42. /**
  43. * The META_MOVETO record sets the output position in the playback device context to a specified
  44. * point.
  45. */
  46. public static class WmfMoveTo implements HwmfRecord {
  47. protected final Point2D point = new Point2D.Double();
  48. @Override
  49. public HwmfRecordType getWmfRecordType() {
  50. return HwmfRecordType.moveTo;
  51. }
  52. @Override
  53. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  54. return readPointS(leis, point);
  55. }
  56. @Override
  57. public void draw(HwmfGraphics ctx) {
  58. ctx.getProperties().setLocation(point);
  59. }
  60. @Override
  61. public String toString() {
  62. return GenericRecordJsonWriter.marshal(this);
  63. }
  64. public Point2D getPoint() {
  65. return point;
  66. }
  67. @Override
  68. public Map<String, Supplier<?>> getGenericProperties() {
  69. return GenericRecordUtil.getGenericProperties("point", this::getPoint);
  70. }
  71. }
  72. /**
  73. * The META_LINETO record draws a line from the drawing position that is defined in the playback
  74. * device context up to, but not including, the specified point.
  75. */
  76. public static class WmfLineTo implements HwmfRecord {
  77. protected final Point2D point = new Point2D.Double();
  78. @Override
  79. public HwmfRecordType getWmfRecordType() {
  80. return HwmfRecordType.lineTo;
  81. }
  82. @Override
  83. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  84. return readPointS(leis, point);
  85. }
  86. @Override
  87. public void draw(HwmfGraphics ctx) {
  88. Point2D start = ctx.getProperties().getLocation();
  89. Line2D line = new Line2D.Double(start, point);
  90. ctx.draw(line);
  91. ctx.getProperties().setLocation(point);
  92. }
  93. @Override
  94. public String toString() {
  95. return GenericRecordJsonWriter.marshal(this);
  96. }
  97. public Point2D getPoint() {
  98. return point;
  99. }
  100. @Override
  101. public Map<String, Supplier<?>> getGenericProperties() {
  102. return GenericRecordUtil.getGenericProperties("point", this::getPoint);
  103. }
  104. }
  105. /**
  106. * The META_POLYGON record paints a polygon consisting of two or more vertices connected by
  107. * straight lines. The polygon is outlined by using the pen and filled by using the brush and polygon fill
  108. * mode that are defined in the playback device context.
  109. */
  110. public static class WmfPolygon implements HwmfRecord {
  111. protected Path2D poly;
  112. @Override
  113. public HwmfRecordType getWmfRecordType() {
  114. return HwmfRecordType.polygon;
  115. }
  116. @Override
  117. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  118. //A 16-bit signed integer that defines the number of points in the array.
  119. int numberOfPoints = leis.readShort();
  120. poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberOfPoints);
  121. for (int i=0; i<numberOfPoints; i++) {
  122. // A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
  123. int x = leis.readShort();
  124. // A 16-bit signed integer that defines the vertical (y) coordinate of the point.
  125. int y = leis.readShort();
  126. if (i==0) {
  127. poly.moveTo(x, y);
  128. } else {
  129. poly.lineTo(x, y);
  130. }
  131. }
  132. if (numberOfPoints > 0 && addClose()) {
  133. // polygons are closed / polylines not
  134. poly.closePath();
  135. }
  136. return LittleEndianConsts.SHORT_SIZE+numberOfPoints*LittleEndianConsts.INT_SIZE;
  137. }
  138. @Override
  139. public void draw(HwmfGraphics ctx) {
  140. Path2D p = (Path2D)poly.clone();
  141. // don't close the path
  142. p.setWindingRule(ctx.getProperties().getWindingRule());
  143. getFillDrawStyle().handler.accept(ctx, p);
  144. }
  145. @Override
  146. public String toString() {
  147. return GenericRecordJsonWriter.marshal(this);
  148. }
  149. /**
  150. * @return true, if the shape should be filled
  151. */
  152. protected FillDrawStyle getFillDrawStyle() {
  153. return FillDrawStyle.FILL;
  154. }
  155. public Path2D getPoly() {
  156. return poly;
  157. }
  158. /**
  159. * @return {@code true} if the path needs to be closed
  160. */
  161. protected boolean addClose() {
  162. return true;
  163. }
  164. @Override
  165. public Map<String, Supplier<?>> getGenericProperties() {
  166. return GenericRecordUtil.getGenericProperties("poly", this::getPoly);
  167. }
  168. }
  169. /**
  170. * The META_POLYLINE record draws a series of line segments by connecting the points in the
  171. * specified array.
  172. */
  173. public static class WmfPolyline extends WmfPolygon {
  174. @Override
  175. public HwmfRecordType getWmfRecordType() {
  176. return HwmfRecordType.polyline;
  177. }
  178. @Override
  179. protected FillDrawStyle getFillDrawStyle() {
  180. return FillDrawStyle.DRAW;
  181. }
  182. @Override
  183. protected boolean addClose() {
  184. return false;
  185. }
  186. }
  187. /**
  188. * The META_ELLIPSE record draws an ellipse. The center of the ellipse is the center of the specified
  189. * bounding rectangle. The ellipse is outlined by using the pen and is filled by using the brush; these
  190. * are defined in the playback device context.
  191. */
  192. public static class WmfEllipse implements HwmfRecord {
  193. protected final Rectangle2D bounds = new Rectangle2D.Double();
  194. @Override
  195. public HwmfRecordType getWmfRecordType() {
  196. return HwmfRecordType.ellipse;
  197. }
  198. @Override
  199. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  200. return readBounds(leis, bounds);
  201. }
  202. @Override
  203. public void draw(HwmfGraphics ctx) {
  204. ctx.fill(getShape());
  205. }
  206. protected Ellipse2D getShape() {
  207. return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
  208. }
  209. @Override
  210. public String toString() {
  211. return GenericRecordJsonWriter.marshal(this);
  212. }
  213. public Rectangle2D getBounds() {
  214. return bounds;
  215. }
  216. @Override
  217. public Map<String, Supplier<?>> getGenericProperties() {
  218. return GenericRecordUtil.getGenericProperties("bounds", this::getBounds);
  219. }
  220. }
  221. /**
  222. * The META_FRAMEREGION record draws a border around a specified region using a specified brush.
  223. */
  224. public static class WmfFrameRegion implements HwmfRecord {
  225. /**
  226. * A 16-bit unsigned integer used to index into the WMF Object Table to get
  227. * the region to be framed.
  228. */
  229. protected int regionIndex;
  230. /**
  231. * A 16-bit unsigned integer used to index into the WMF Object Table to get the
  232. * Brush to use for filling the region.
  233. */
  234. protected int brushIndex;
  235. /**
  236. * The region frame, in logical units
  237. */
  238. protected final Dimension2D frame = new Dimension2DDouble();
  239. @Override
  240. public HwmfRecordType getWmfRecordType() {
  241. return HwmfRecordType.frameRegion;
  242. }
  243. @Override
  244. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  245. regionIndex = leis.readUShort();
  246. brushIndex = leis.readUShort();
  247. // A 16-bit signed integer that defines the height/width, in logical units, of the region frame.
  248. int height = leis.readShort();
  249. int width = leis.readShort();
  250. frame.setSize(width, height);
  251. return 4*LittleEndianConsts.SHORT_SIZE;
  252. }
  253. @Override
  254. public void draw(HwmfGraphics ctx) {
  255. ctx.applyObjectTableEntry(brushIndex);
  256. ctx.applyObjectTableEntry(regionIndex);
  257. Rectangle2D inner = ctx.getProperties().getRegion().getBounds();
  258. double x = inner.getX()-frame.getWidth();
  259. double y = inner.getY()-frame.getHeight();
  260. double w = inner.getWidth()+2.0*frame.getWidth();
  261. double h = inner.getHeight()+2.0*frame.getHeight();
  262. Rectangle2D outer = new Rectangle2D.Double(x,y,w,h);
  263. Area frame = new Area(outer);
  264. frame.subtract(new Area(inner));
  265. ctx.fill(frame);
  266. }
  267. public int getRegionIndex() {
  268. return regionIndex;
  269. }
  270. public int getBrushIndex() {
  271. return brushIndex;
  272. }
  273. public Dimension2D getFrame() {
  274. return frame;
  275. }
  276. @Override
  277. public Map<String, Supplier<?>> getGenericProperties() {
  278. return GenericRecordUtil.getGenericProperties(
  279. "regionIndex", this::getRegionIndex,
  280. "brushIndex", this::getBrushIndex,
  281. "frame", this::getFrame);
  282. }
  283. }
  284. /**
  285. * The META_POLYPOLYGON record paints a series of closed polygons. Each polygon is outlined by
  286. * using the pen and filled by using the brush and polygon fill mode; these are defined in the playback
  287. * device context. The polygons drawn by this function can overlap.
  288. */
  289. public static class WmfPolyPolygon implements HwmfRecord {
  290. protected final List<Path2D> polyList = new ArrayList<>();
  291. @Override
  292. public HwmfRecordType getWmfRecordType() {
  293. return HwmfRecordType.polyPolygon;
  294. }
  295. @Override
  296. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  297. // see also CVE-2008-3014 - https://dl.packetstormsecurity.net/papers/attack/CVE-2008-3014.pdf ;)
  298. // A 16-bit unsigned integer that defines the number of polygons in the object.
  299. int numberOfPolygons = leis.readUShort();
  300. // A NumberOfPolygons array of 16-bit unsigned integers that define the number of points for
  301. // each polygon in the object.
  302. int[] pointsPerPolygon = new int[numberOfPolygons];
  303. int size = LittleEndianConsts.SHORT_SIZE;
  304. for (int i=0; i<numberOfPolygons; i++) {
  305. pointsPerPolygon[i] = leis.readUShort();
  306. size += LittleEndianConsts.SHORT_SIZE;
  307. }
  308. for (int nPoints : pointsPerPolygon) {
  309. // An array of 16-bit signed integers that define the coordinates of the polygons.
  310. // (Note: MS-WMF wrongly says unsigned integers ...)
  311. Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints);
  312. for (int i=0; i<nPoints; i++) {
  313. int x = leis.readShort();
  314. int y = leis.readShort();
  315. size += 2*LittleEndianConsts.SHORT_SIZE;
  316. if (i == 0) {
  317. poly.moveTo(x, y);
  318. } else {
  319. poly.lineTo(x, y);
  320. }
  321. }
  322. poly.closePath();
  323. polyList.add(poly);
  324. }
  325. return size;
  326. }
  327. @Override
  328. public void draw(HwmfGraphics ctx) {
  329. Shape shape = getShape(ctx);
  330. if (shape == null) {
  331. return;
  332. }
  333. switch (getFillDrawStyle()) {
  334. case DRAW:
  335. ctx.draw(shape);
  336. break;
  337. case FILL:
  338. ctx.fill(shape);
  339. break;
  340. case FILL_DRAW:
  341. ctx.fill(shape);
  342. ctx.draw(shape);
  343. break;
  344. }
  345. }
  346. protected FillDrawStyle getFillDrawStyle() {
  347. // Each polygon SHOULD be outlined using the current pen, and filled using the current brush and
  348. // polygon fill mode that are defined in the playback device context. The polygons defined by this
  349. // record can overlap.
  350. return FillDrawStyle.FILL_DRAW;
  351. }
  352. /**
  353. * @return true, if a polyline should be closed, i.e. is a polygon
  354. */
  355. protected boolean isClosed() {
  356. return true;
  357. }
  358. protected Shape getShape(HwmfGraphics ctx) {
  359. int windingRule = ctx.getProperties().getWindingRule();
  360. if (isClosed()) {
  361. Area area = null;
  362. for (Path2D poly : polyList) {
  363. Path2D p = (Path2D)poly.clone();
  364. p.setWindingRule(windingRule);
  365. Area newArea = new Area(p);
  366. if (area == null) {
  367. area = newArea;
  368. } else {
  369. area.exclusiveOr(newArea);
  370. }
  371. }
  372. return area;
  373. } else {
  374. Path2D path = new Path2D.Double();
  375. path.setWindingRule(windingRule);
  376. for (Path2D poly : polyList) {
  377. path.append(poly, false);
  378. }
  379. return path;
  380. }
  381. }
  382. @Override
  383. public String toString() {
  384. return GenericRecordJsonWriter.marshal(this);
  385. }
  386. public List<Path2D> getPolyList() {
  387. return polyList;
  388. }
  389. @Override
  390. public Map<String, Supplier<?>> getGenericProperties() {
  391. return GenericRecordUtil.getGenericProperties("polyList", this::getPolyList);
  392. }
  393. }
  394. /**
  395. * The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and
  396. * filled by using the brush that are defined in the playback device context.
  397. */
  398. public static class WmfRectangle implements HwmfRecord {
  399. protected final Rectangle2D bounds = new Rectangle2D.Double();
  400. @Override
  401. public HwmfRecordType getWmfRecordType() {
  402. return HwmfRecordType.rectangle;
  403. }
  404. @Override
  405. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  406. return readBounds(leis, bounds);
  407. }
  408. @Override
  409. public void draw(HwmfGraphics ctx) {
  410. ctx.fill(bounds);
  411. }
  412. @Override
  413. public String toString() {
  414. return GenericRecordJsonWriter.marshal(this);
  415. }
  416. public Rectangle2D getBounds() {
  417. return bounds;
  418. }
  419. @Override
  420. public Map<String, Supplier<?>> getGenericProperties() {
  421. return GenericRecordUtil.getGenericProperties("bounds", this::getBounds);
  422. }
  423. }
  424. /**
  425. * The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color.
  426. */
  427. public static class WmfSetPixel implements HwmfRecord {
  428. /**
  429. * A ColorRef Object that defines the color value.
  430. */
  431. protected final HwmfColorRef colorRef = new HwmfColorRef();
  432. protected final Point2D point = new Point2D.Double();
  433. @Override
  434. public HwmfRecordType getWmfRecordType() {
  435. return HwmfRecordType.setPixel;
  436. }
  437. @Override
  438. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  439. int size = colorRef.init(leis);
  440. return size+ readPointS(leis, point);
  441. }
  442. @Override
  443. public void draw(HwmfGraphics ctx) {
  444. Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1);
  445. ctx.fill(s);
  446. }
  447. public HwmfColorRef getColorRef() {
  448. return colorRef;
  449. }
  450. public Point2D getPoint() {
  451. return point;
  452. }
  453. @Override
  454. public Map<String, Supplier<?>> getGenericProperties() {
  455. return GenericRecordUtil.getGenericProperties(
  456. "colorRef", this::getColorRef,
  457. "point", this::getPoint
  458. );
  459. }
  460. }
  461. /**
  462. * The META_ROUNDRECT record paints a rectangle with rounded corners. The rectangle is outlined
  463. * using the pen and filled using the brush, as defined in the playback device context.
  464. */
  465. public static class WmfRoundRect implements HwmfRecord {
  466. protected final Dimension2D corners = new Dimension2DDouble();
  467. protected final Rectangle2D bounds = new Rectangle2D.Double();
  468. @Override
  469. public HwmfRecordType getWmfRecordType() {
  470. return HwmfRecordType.roundRect;
  471. }
  472. @Override
  473. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  474. // A 16-bit signed integer that defines the height/width, in logical coordinates,
  475. // of the ellipse used to draw the rounded corners.
  476. int height = leis.readShort();
  477. int width = leis.readShort();
  478. corners.setSize(width, height);
  479. return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds);
  480. }
  481. @Override
  482. public void draw(HwmfGraphics ctx) {
  483. ctx.fill(getShape());
  484. }
  485. protected RoundRectangle2D getShape() {
  486. return new RoundRectangle2D.Double(
  487. bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(),
  488. corners.getWidth(), corners.getHeight()
  489. );
  490. }
  491. public Dimension2D getCorners() {
  492. return corners;
  493. }
  494. public Rectangle2D getBounds() {
  495. return bounds;
  496. }
  497. @Override
  498. public Map<String, Supplier<?>> getGenericProperties() {
  499. return GenericRecordUtil.getGenericProperties(
  500. "bounds", this::getBounds,
  501. "corners", this::getCorners
  502. );
  503. }
  504. }
  505. /**
  506. * The META_ARC record draws an elliptical arc.
  507. */
  508. public static class WmfArc implements HwmfRecord {
  509. public enum WmfArcClosure {
  510. ARC(HwmfRecordType.arc, Arc2D.OPEN, FillDrawStyle.DRAW),
  511. CHORD(HwmfRecordType.chord, Arc2D.CHORD, FillDrawStyle.FILL_DRAW),
  512. PIE(HwmfRecordType.pie, Arc2D.PIE, FillDrawStyle.FILL_DRAW);
  513. public final HwmfRecordType recordType;
  514. public final int awtType;
  515. public final FillDrawStyle drawStyle;
  516. WmfArcClosure(HwmfRecordType recordType, int awtType, FillDrawStyle drawStyle) {
  517. this.recordType = recordType;
  518. this.awtType = awtType;
  519. this.drawStyle = drawStyle;
  520. }
  521. }
  522. /** starting point of the arc */
  523. protected final Point2D startPoint = new Point2D.Double();
  524. /** ending point of the arc */
  525. protected final Point2D endPoint = new Point2D.Double();
  526. /** the bounding rectangle */
  527. protected final Rectangle2D bounds = new Rectangle2D.Double();
  528. @Override
  529. public HwmfRecordType getWmfRecordType() {
  530. return HwmfRecordType.arc;
  531. }
  532. @Override
  533. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  534. readPointS(leis, endPoint);
  535. readPointS(leis, startPoint);
  536. readBounds(leis, bounds);
  537. return 8*LittleEndianConsts.SHORT_SIZE;
  538. }
  539. @Override
  540. public void draw(HwmfGraphics ctx) {
  541. getFillDrawStyle().handler.accept(ctx, getShape());
  542. }
  543. public WmfArcClosure getArcClosure() {
  544. switch (getWmfRecordType()) {
  545. default:
  546. case arc:
  547. return WmfArcClosure.ARC;
  548. case chord:
  549. return WmfArcClosure.CHORD;
  550. case pie:
  551. return WmfArcClosure.PIE;
  552. }
  553. }
  554. protected FillDrawStyle getFillDrawStyle() {
  555. return getArcClosure().drawStyle;
  556. }
  557. protected Arc2D getShape() {
  558. double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX()));
  559. double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX()));
  560. double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
  561. if (startAngle < 0) {
  562. startAngle += 360;
  563. }
  564. return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(),
  565. startAngle, arcAngle, getArcClosure().awtType);
  566. }
  567. @Override
  568. public String toString() {
  569. return GenericRecordJsonWriter.marshal(this);
  570. }
  571. public Point2D getStartPoint() {
  572. return startPoint;
  573. }
  574. public Point2D getEndPoint() {
  575. return endPoint;
  576. }
  577. public Rectangle2D getBounds() {
  578. return bounds;
  579. }
  580. @Override
  581. public Map<String, Supplier<?>> getGenericProperties() {
  582. final Arc2D arc = getShape();
  583. return GenericRecordUtil.getGenericProperties(
  584. "startPoint", this::getStartPoint,
  585. "endPoint", this::getEndPoint,
  586. "startAngle", arc::getAngleStart,
  587. "extentAngle", arc::getAngleExtent,
  588. "bounds", this::getBounds
  589. );
  590. }
  591. }
  592. /**
  593. * The META_PIE record draws a pie-shaped wedge bounded by the intersection of an ellipse and two
  594. * radials. The pie is outlined by using the pen and filled by using the brush that are defined in the
  595. * playback device context.
  596. */
  597. public static class WmfPie extends WmfArc {
  598. @Override
  599. public HwmfRecordType getWmfRecordType() {
  600. return HwmfRecordType.pie;
  601. }
  602. }
  603. /**
  604. * The META_CHORD record draws a chord, which is defined by a region bounded by the intersection of
  605. * an ellipse with a line segment. The chord is outlined using the pen and filled using the brush
  606. * that are defined in the playback device context.
  607. */
  608. public static class WmfChord extends WmfArc {
  609. @Override
  610. public HwmfRecordType getWmfRecordType() {
  611. return HwmfRecordType.chord;
  612. }
  613. }
  614. /**
  615. * The META_SELECTOBJECT record specifies a graphics object for the playback device context. The
  616. * new object replaces the previous object of the same type, unless if the previous object is a palette
  617. * object. If the previous object is a palette object, then the META_SELECTPALETTE record must be
  618. * used instead of the META_SELECTOBJECT record, as the META_SELECTOBJECT record does not
  619. * support replacing the palette object type.
  620. */
  621. public static class WmfSelectObject implements HwmfRecord {
  622. /**
  623. * A 16-bit unsigned integer used to index into the WMF Object Table to
  624. * get the object to be selected.
  625. */
  626. protected int objectIndex;
  627. @Override
  628. public HwmfRecordType getWmfRecordType() {
  629. return HwmfRecordType.selectObject;
  630. }
  631. @Override
  632. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  633. objectIndex = leis.readUShort();
  634. return LittleEndianConsts.SHORT_SIZE;
  635. }
  636. @Override
  637. public void draw(HwmfGraphics ctx) {
  638. ctx.applyObjectTableEntry(objectIndex);
  639. }
  640. @Override
  641. public String toString() {
  642. return GenericRecordJsonWriter.marshal(this);
  643. }
  644. public int getObjectIndex() {
  645. return objectIndex;
  646. }
  647. @Override
  648. public Map<String, Supplier<?>> getGenericProperties() {
  649. return GenericRecordUtil.getGenericProperties("objectIndex", this::getObjectIndex);
  650. }
  651. }
  652. @SuppressWarnings("DuplicatedCode")
  653. static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) {
  654. // The 16-bit signed integers that defines the corners of the bounding rectangle.
  655. int bottom = leis.readShort();
  656. int right = leis.readShort();
  657. int top = leis.readShort();
  658. int left = leis.readShort();
  659. int x = Math.min(left, right);
  660. int y = Math.min(top, bottom);
  661. int w = Math.abs(left - right - 1);
  662. int h = Math.abs(top - bottom - 1);
  663. bounds.setRect(x, y, w, h);
  664. return 4 * LittleEndianConsts.SHORT_SIZE;
  665. }
  666. @SuppressWarnings("DuplicatedCode")
  667. static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
  668. // The 16-bit signed integers that defines the corners of the bounding rectangle.
  669. int left = leis.readShort();
  670. int top = leis.readShort();
  671. int right = leis.readShort();
  672. int bottom = leis.readShort();
  673. int x = Math.min(left, right);
  674. int y = Math.min(top, bottom);
  675. int w = Math.abs(left - right - 1);
  676. int h = Math.abs(top - bottom - 1);
  677. bounds.setRect(x, y, w, h);
  678. return 4 * LittleEndianConsts.SHORT_SIZE;
  679. }
  680. static int readPointS(LittleEndianInputStream leis, Point2D point) {
  681. // a signed integer that defines the x/y-coordinate, in logical units.
  682. int y = leis.readShort();
  683. int x = leis.readShort();
  684. point.setLocation(x, y);
  685. return 2*LittleEndianConsts.SHORT_SIZE;
  686. }
  687. @Internal
  688. public static Rectangle2D normalizeBounds(Rectangle2D bounds) {
  689. return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds
  690. : new Rectangle2D.Double(
  691. bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(),
  692. bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(),
  693. Math.abs(bounds.getWidth()),
  694. Math.abs(bounds.getHeight())
  695. );
  696. }
  697. }