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.

HemfDraw.java 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  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.hemf.record.emf;
  16. import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
  17. import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
  18. import java.awt.Shape;
  19. import java.awt.geom.Arc2D;
  20. import java.awt.geom.Dimension2D;
  21. import java.awt.geom.Path2D;
  22. import java.awt.geom.PathIterator;
  23. import java.awt.geom.Point2D;
  24. import java.awt.geom.Rectangle2D;
  25. import java.io.IOException;
  26. import org.apache.poi.hemf.draw.HemfDrawProperties;
  27. import org.apache.poi.hemf.draw.HemfGraphics;
  28. import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
  29. import org.apache.poi.hwmf.record.HwmfDraw;
  30. import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
  31. import org.apache.poi.util.LittleEndianConsts;
  32. import org.apache.poi.util.LittleEndianInputStream;
  33. public class HemfDraw {
  34. /**
  35. * The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device
  36. * context. The object is specified either by its index in the EMF Object Table or by its
  37. * value from the StockObject enumeration.
  38. */
  39. public static class EmfSelectObject extends WmfSelectObject implements HemfRecord {
  40. private static final String[] STOCK_IDS = {
  41. "0x80000000 /* WHITE_BRUSH */",
  42. "0x80000001 /* LTGRAY_BRUSH */",
  43. "0x80000002 /* GRAY_BRUSH */",
  44. "0x80000003 /* DKGRAY_BRUSH */",
  45. "0x80000004 /* BLACK_BRUSH */",
  46. "0x80000005 /* NULL_BRUSH */",
  47. "0x80000006 /* WHITE_PEN */",
  48. "0x80000007 /* BLACK_PEN */",
  49. "0x80000008 /* NULL_PEN */",
  50. "0x8000000A /* OEM_FIXED_FONT */",
  51. "0x8000000B /* ANSI_FIXED_FONT */",
  52. "0x8000000C /* ANSI_VAR_FONT */",
  53. "0x8000000D /* SYSTEM_FONT */",
  54. "0x8000000E /* DEVICE_DEFAULT_FONT */",
  55. "0x8000000F /* DEFAULT_PALETTE */",
  56. "0x80000010 /* SYSTEM_FIXED_FONT */",
  57. "0x80000011 /* DEFAULT_GUI_FONT */",
  58. "0x80000012 /* DC_BRUSH */",
  59. "0x80000013 /* DC_PEN */"
  60. };
  61. @Override
  62. public HemfRecordType getEmfRecordType() {
  63. return HemfRecordType.selectObject;
  64. }
  65. @Override
  66. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  67. // A 32-bit unsigned integer that specifies either the index of a graphics object in the
  68. // EMF Object Table or the index of a stock object from the StockObject enumeration.
  69. objectIndex = leis.readInt();
  70. return LittleEndianConsts.INT_SIZE;
  71. }
  72. @Override
  73. public String toString() {
  74. return "{ index: "+
  75. (((objectIndex & 0x80000000) != 0 && (objectIndex & 0x3FFFFFFF) <= 13 )
  76. ? STOCK_IDS[objectIndex & 0x3FFFFFFF]
  77. : objectIndex)+" }";
  78. }
  79. }
  80. /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */
  81. public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord {
  82. private final Rectangle2D bounds = new Rectangle2D.Double();
  83. @Override
  84. public HemfRecordType getEmfRecordType() {
  85. return HemfRecordType.polyBezier;
  86. }
  87. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  88. return readPointL(leis, point);
  89. }
  90. @Override
  91. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  92. long size = readRectL(leis, bounds);
  93. /* A 32-bit unsigned integer that specifies the number of points in the points
  94. * array. This value MUST be one more than three times the number of curves to
  95. * be drawn, because each Bezier curve requires two control points and an
  96. * endpoint, and the initial curve requires an additional starting point.
  97. *
  98. * Line width | Device supports wideline | Maximum points allowed
  99. * 1 | n/a | 16K
  100. * > 1 | yes | 16K
  101. * > 1 | no | 1360
  102. *
  103. * Any extra points MUST be ignored.
  104. */
  105. final int count = (int)leis.readUInt();
  106. final int points = Math.min(count, 16384);
  107. size += LittleEndianConsts.INT_SIZE;
  108. poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points+2);
  109. /* Cubic Bezier curves are defined using the endpoints and control points
  110. * specified by the points field. The first curve is drawn from the first
  111. * point to the fourth point, using the second and third points as control
  112. * points. Each subsequent curve in the sequence needs exactly three more points:
  113. * the ending point of the previous curve is used as the starting point,
  114. * the next two points in the sequence are control points,
  115. * and the third is the ending point.
  116. * The cubic Bezier curves SHOULD be drawn using the current pen.
  117. */
  118. Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() };
  119. int i=0;
  120. if (hasStartPoint()) {
  121. if (i < points) {
  122. size += readPoint(leis, pnt[0]);
  123. poly.moveTo(pnt[0].getX(), pnt[0].getY());
  124. i++;
  125. }
  126. } else {
  127. poly.moveTo(0, 0);
  128. }
  129. for (; i+2<points; i+=3) {
  130. size += readPoint(leis, pnt[0]);
  131. size += readPoint(leis, pnt[1]);
  132. size += readPoint(leis, pnt[2]);
  133. poly.curveTo(
  134. pnt[0].getX(),pnt[0].getY(),
  135. pnt[1].getX(),pnt[1].getY(),
  136. pnt[2].getX(),pnt[2].getY()
  137. );
  138. }
  139. return size;
  140. }
  141. /**
  142. * @return true, if start point is in the list of points. false, if start point is taken from the context
  143. */
  144. protected boolean hasStartPoint() {
  145. return true;
  146. }
  147. @Override
  148. protected FillDrawStyle getFillDrawStyle() {
  149. // The cubic Bezier curves SHOULD be drawn using the current pen.
  150. return FillDrawStyle.DRAW;
  151. }
  152. @Override
  153. public void draw(HemfGraphics ctx) {
  154. ctx.draw(path -> path.append(poly, !hasStartPoint()), getFillDrawStyle());
  155. }
  156. }
  157. /**
  158. * The EMR_POLYBEZIER16 record specifies one or more Bezier curves.
  159. * The curves are drawn using the current pen.
  160. */
  161. public static class EmfPolyBezier16 extends EmfPolyBezier {
  162. @Override
  163. public HemfRecordType getEmfRecordType() {
  164. return HemfRecordType.polyBezier16;
  165. }
  166. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  167. return readPointS(leis, point);
  168. }
  169. }
  170. /**
  171. * The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
  172. * straight lines.
  173. */
  174. public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
  175. private final Rectangle2D bounds = new Rectangle2D.Double();
  176. @Override
  177. public HemfRecordType getEmfRecordType() {
  178. return HemfRecordType.polygon;
  179. }
  180. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  181. return readPointL(leis, point);
  182. }
  183. @Override
  184. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  185. long size = readRectL(leis, bounds);
  186. // see PolyBezier about limits
  187. final int count = (int)leis.readUInt();
  188. final int points = Math.min(count, 16384);
  189. size += LittleEndianConsts.INT_SIZE;
  190. poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points);
  191. Point2D pnt = new Point2D.Double();
  192. for (int i=0; i<points; i++) {
  193. size += readPoint(leis, pnt);
  194. if (i==0) {
  195. if (hasStartPoint()) {
  196. poly.moveTo(pnt.getX(), pnt.getY());
  197. } else {
  198. // if this path is connected to the current position (= has no start point)
  199. // the first entry is a dummy entry and will be skipped later
  200. poly.moveTo(0,0);
  201. poly.lineTo(pnt.getX(), pnt.getY());
  202. }
  203. } else {
  204. poly.lineTo(pnt.getX(), pnt.getY());
  205. }
  206. }
  207. return size;
  208. }
  209. /**
  210. * @return true, if start point is in the list of points. false, if start point is taken from the context
  211. */
  212. protected boolean hasStartPoint() {
  213. return true;
  214. }
  215. @Override
  216. protected FillDrawStyle getFillDrawStyle() {
  217. // The polygon SHOULD be outlined using the current pen and filled using the current brush and
  218. // polygon fill mode. The polygon SHOULD be closed automatically by drawing a line from the last
  219. // vertex to the first.
  220. return FillDrawStyle.FILL_DRAW;
  221. }
  222. @Override
  223. public void draw(HemfGraphics ctx) {
  224. ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
  225. }
  226. }
  227. /**
  228. * The EMR_POLYGON16 record specifies a polygon consisting of two or more vertexes connected by straight lines.
  229. * The polygon is outlined by using the current pen and filled by using the current brush and polygon fill mode.
  230. * The polygon is closed automatically by drawing a line from the last vertex to the first
  231. */
  232. public static class EmfPolygon16 extends EmfPolygon {
  233. @Override
  234. public HemfRecordType getEmfRecordType() {
  235. return HemfRecordType.polygon16;
  236. }
  237. @Override
  238. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  239. return readPointS(leis, point);
  240. }
  241. }
  242. /**
  243. * The EMR_POLYLINE record specifies a series of line segments by connecting the points in the
  244. * specified array.
  245. */
  246. public static class EmfPolyline extends EmfPolygon {
  247. @Override
  248. public HemfRecordType getEmfRecordType() {
  249. return HemfRecordType.polyline;
  250. }
  251. @Override
  252. protected FillDrawStyle getFillDrawStyle() {
  253. // The line segments SHOULD be drawn using the current pen.
  254. return FillDrawStyle.DRAW;
  255. }
  256. }
  257. /**
  258. * The EMR_POLYLINE16 record specifies a series of line segments by connecting the points in the
  259. * specified array.
  260. */
  261. public static class EmfPolyline16 extends EmfPolyline {
  262. @Override
  263. public HemfRecordType getEmfRecordType() {
  264. return HemfRecordType.polyline16;
  265. }
  266. @Override
  267. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  268. return readPointS(leis, point);
  269. }
  270. }
  271. /**
  272. * The EMR_POLYBEZIERTO record specifies one or more Bezier curves based upon the current
  273. * position.
  274. */
  275. public static class EmfPolyBezierTo extends EmfPolyBezier {
  276. @Override
  277. public HemfRecordType getEmfRecordType() {
  278. return HemfRecordType.polyBezierTo;
  279. }
  280. @Override
  281. protected boolean hasStartPoint() {
  282. return false;
  283. }
  284. @Override
  285. public void draw(HemfGraphics ctx) {
  286. polyTo(ctx, poly, getFillDrawStyle());
  287. }
  288. }
  289. /**
  290. * The EMR_POLYBEZIERTO16 record specifies one or more Bezier curves based on the current
  291. * position.
  292. */
  293. public static class EmfPolyBezierTo16 extends EmfPolyBezierTo {
  294. @Override
  295. public HemfRecordType getEmfRecordType() {
  296. return HemfRecordType.polyBezierTo16;
  297. }
  298. @Override
  299. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  300. return readPointS(leis, point);
  301. }
  302. }
  303. /** The EMR_POLYLINETO record specifies one or more straight lines based upon the current position. */
  304. public static class EmfPolylineTo extends EmfPolyline {
  305. @Override
  306. public HemfRecordType getEmfRecordType() {
  307. return HemfRecordType.polylineTo;
  308. }
  309. @Override
  310. protected boolean hasStartPoint() {
  311. return false;
  312. }
  313. @Override
  314. public void draw(HemfGraphics ctx) {
  315. polyTo(ctx, poly, getFillDrawStyle());
  316. }
  317. }
  318. /**
  319. * The EMR_POLYLINETO16 record specifies one or more straight lines based upon the current position.
  320. * A line is drawn from the current position to the first point specified by the points field by using the
  321. * current pen. For each additional line, drawing is performed from the ending point of the previous
  322. * line to the next point specified by points.
  323. */
  324. public static class EmfPolylineTo16 extends EmfPolylineTo {
  325. @Override
  326. public HemfRecordType getEmfRecordType() {
  327. return HemfRecordType.polylineTo16;
  328. }
  329. @Override
  330. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  331. return readPointS(leis, point);
  332. }
  333. }
  334. /**
  335. * The EMR_POLYPOLYGON record specifies a series of closed polygons.
  336. */
  337. public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
  338. private final Rectangle2D bounds = new Rectangle2D.Double();
  339. @Override
  340. public HemfRecordType getEmfRecordType() {
  341. return HemfRecordType.polyPolygon;
  342. }
  343. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  344. return readPointL(leis, point);
  345. }
  346. @Override
  347. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  348. long size = readRectL(leis, bounds);
  349. // A 32-bit unsigned integer that specifies the number of polygons.
  350. long numberOfPolygons = leis.readUInt();
  351. // A 32-bit unsigned integer that specifies the total number of points in all polygons.
  352. long count = Math.min(16384, leis.readUInt());
  353. size += 2 * LittleEndianConsts.INT_SIZE;
  354. // An array of 32-bit unsigned integers that specifies the point count for each polygon.
  355. long[] polygonPointCount = new long[(int)numberOfPolygons];
  356. size += numberOfPolygons * LittleEndianConsts.INT_SIZE;
  357. for (int i=0; i<numberOfPolygons; i++) {
  358. polygonPointCount[i] = leis.readUInt();
  359. }
  360. Point2D pnt = new Point2D.Double();
  361. for (long nPoints : polygonPointCount) {
  362. /**
  363. * An array of WMF PointL objects that specifies the points for all polygons in logical units.
  364. * The number of points is specified by the Count field value.
  365. */
  366. Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, (int)nPoints);
  367. for (int i=0; i<nPoints; i++) {
  368. size += readPoint(leis, pnt);
  369. if (i == 0) {
  370. poly.moveTo(pnt.getX(), pnt.getY());
  371. } else {
  372. poly.lineTo(pnt.getX(), pnt.getY());
  373. }
  374. }
  375. if (isClosed()) {
  376. poly.closePath();
  377. }
  378. polyList.add(poly);
  379. }
  380. return size;
  381. }
  382. @Override
  383. public void draw(HemfGraphics ctx) {
  384. Shape shape = getShape(ctx);
  385. if (shape == null) {
  386. return;
  387. }
  388. ctx.draw(path -> path.append(shape, false), getFillDrawStyle());
  389. }
  390. }
  391. /**
  392. * The EMR_POLYPOLYGON16 record specifies a series of closed polygons. Each polygon is outlined
  393. * using the current pen, and filled using the current brush and polygon fill mode.
  394. * The polygons drawn by this record can overlap.
  395. */
  396. public static class EmfPolyPolygon16 extends EmfPolyPolygon {
  397. @Override
  398. public HemfRecordType getEmfRecordType() {
  399. return HemfRecordType.polyPolygon16;
  400. }
  401. @Override
  402. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  403. return readPointS(leis, point);
  404. }
  405. }
  406. /**
  407. * The EMR_POLYPOLYLINE record specifies multiple series of connected line segments.
  408. */
  409. public static class EmfPolyPolyline extends EmfPolyPolygon {
  410. @Override
  411. public HemfRecordType getEmfRecordType() {
  412. return HemfRecordType.polyPolyline;
  413. }
  414. @Override
  415. protected boolean isClosed() {
  416. return false;
  417. }
  418. @Override
  419. protected FillDrawStyle getFillDrawStyle() {
  420. return FillDrawStyle.DRAW;
  421. }
  422. }
  423. /** The EMR_POLYPOLYLINE16 record specifies multiple series of connected line segments. */
  424. public static class EmfPolyPolyline16 extends EmfPolyPolyline {
  425. @Override
  426. public HemfRecordType getEmfRecordType() {
  427. return HemfRecordType.polyPolyline16;
  428. }
  429. @Override
  430. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  431. return readPointS(leis, point);
  432. }
  433. }
  434. /**
  435. * The EMR_SETPIXELV record defines the color of the pixel at the specified logical coordinates.
  436. */
  437. public static class EmfSetPixelV extends HwmfDraw.WmfSetPixel implements HemfRecord {
  438. @Override
  439. public HemfRecordType getEmfRecordType() {
  440. return HemfRecordType.setPixelV;
  441. }
  442. @Override
  443. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  444. long size = readPointL(leis, point);
  445. size += colorRef.init(leis);
  446. return size;
  447. }
  448. }
  449. /**
  450. * The EMR_MOVETOEX record specifies coordinates of the new current position, in logical units.
  451. */
  452. public static class EmfSetMoveToEx extends HwmfDraw.WmfMoveTo implements HemfRecord {
  453. @Override
  454. public HemfRecordType getEmfRecordType() {
  455. return HemfRecordType.setMoveToEx;
  456. }
  457. @Override
  458. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  459. return readPointL(leis, point);
  460. }
  461. @Override
  462. public void draw(final HemfGraphics ctx) {
  463. ctx.draw((path) -> path.moveTo(point.getX(), point.getY()), FillDrawStyle.NONE);
  464. }
  465. }
  466. /**
  467. * The EMR_ARC record specifies an elliptical arc.
  468. * It resets the current position to the end point of the arc.
  469. */
  470. public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord {
  471. @Override
  472. public HemfRecordType getEmfRecordType() {
  473. return HemfRecordType.arc;
  474. }
  475. @Override
  476. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  477. long size = readRectL(leis, bounds);
  478. size += readPointL(leis, startPoint);
  479. size += readPointL(leis, endPoint);
  480. return size;
  481. }
  482. @Override
  483. public void draw(HemfGraphics ctx) {
  484. ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
  485. }
  486. }
  487. /**
  488. * The EMR_CHORD record specifies a chord, which is a region bounded by the intersection of an
  489. * ellipse and a line segment, called a secant. The chord is outlined by using the current pen
  490. * and filled by using the current brush.
  491. */
  492. public static class EmfChord extends HwmfDraw.WmfChord implements HemfRecord {
  493. @Override
  494. public HemfRecordType getEmfRecordType() {
  495. return HemfRecordType.chord;
  496. }
  497. @Override
  498. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  499. long size = readRectL(leis, bounds);
  500. size += readPointL(leis, startPoint);
  501. size += readPointL(leis, endPoint);
  502. return size;
  503. }
  504. @Override
  505. public void draw(HemfGraphics ctx) {
  506. ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
  507. }
  508. }
  509. /**
  510. * The EMR_PIE record specifies a pie-shaped wedge bounded by the intersection of an ellipse and two
  511. * radials. The pie is outlined by using the current pen and filled by using the current brush.
  512. */
  513. public static class EmfPie extends HwmfDraw.WmfPie implements HemfRecord {
  514. @Override
  515. public HemfRecordType getEmfRecordType() {
  516. return HemfRecordType.pie;
  517. }
  518. @Override
  519. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  520. long size = readRectL(leis, bounds);
  521. size += readPointL(leis, startPoint);
  522. size += readPointL(leis, endPoint);
  523. return size;
  524. }
  525. @Override
  526. public void draw(HemfGraphics ctx) {
  527. ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
  528. }
  529. }
  530. /**
  531. * The EMR_ELLIPSE record specifies an ellipse. The center of the ellipse is the center of the specified
  532. * bounding rectangle. The ellipse is outlined by using the current pen and is filled by using the current
  533. * brush.
  534. */
  535. public static class EmfEllipse extends HwmfDraw.WmfEllipse implements HemfRecord {
  536. @Override
  537. public HemfRecordType getEmfRecordType() {
  538. return HemfRecordType.ellipse;
  539. }
  540. @Override
  541. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  542. return readRectL(leis, bounds);
  543. }
  544. @Override
  545. public void draw(HemfGraphics ctx) {
  546. ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
  547. }
  548. }
  549. /**
  550. * The EMR_RECTANGLE record draws a rectangle. The rectangle is outlined by using the current pen
  551. * and filled by using the current brush.
  552. */
  553. public static class EmfRectangle extends HwmfDraw.WmfRectangle implements HemfRecord {
  554. @Override
  555. public HemfRecordType getEmfRecordType() {
  556. return HemfRecordType.rectangle;
  557. }
  558. @Override
  559. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  560. return readRectL(leis, bounds);
  561. }
  562. @Override
  563. public void draw(HemfGraphics ctx) {
  564. ctx.draw(path -> path.append(normalizeBounds(bounds), false), FillDrawStyle.FILL_DRAW);
  565. }
  566. }
  567. /**
  568. * The EMR_ROUNDRECT record specifies a rectangle with rounded corners. The rectangle is outlined
  569. * by using the current pen and filled by using the current brush.
  570. */
  571. public static class EmfRoundRect extends HwmfDraw.WmfRoundRect implements HemfRecord {
  572. @Override
  573. public HemfRecordType getEmfRecordType() {
  574. return HemfRecordType.roundRect;
  575. }
  576. @Override
  577. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  578. long size = readRectL(leis, bounds);
  579. // A 32-bit unsigned integer that defines the x-coordinate of the point.
  580. width = (int)leis.readUInt();
  581. height = (int)leis.readUInt();
  582. return size + 2*LittleEndianConsts.INT_SIZE;
  583. }
  584. @Override
  585. public void draw(HemfGraphics ctx) {
  586. ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
  587. }
  588. }
  589. /**
  590. * The EMR_LINETO record specifies a line from the current position up to, but not including, the
  591. * specified point. It resets the current position to the specified point.
  592. */
  593. public static class EmfLineTo extends HwmfDraw.WmfLineTo implements HemfRecord {
  594. @Override
  595. public HemfRecordType getEmfRecordType() {
  596. return HemfRecordType.lineTo;
  597. }
  598. @Override
  599. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  600. return readPointL(leis, point);
  601. }
  602. @Override
  603. public void draw(final HemfGraphics ctx) {
  604. ctx.draw((path) -> path.lineTo(point.getX(), point.getY()), FillDrawStyle.DRAW);
  605. }
  606. }
  607. /**
  608. * The EMR_ARCTO record specifies an elliptical arc.
  609. * It resets the current position to the end point of the arc.
  610. */
  611. public static class EmfArcTo extends HwmfDraw.WmfArc implements HemfRecord {
  612. @Override
  613. public HemfRecordType getEmfRecordType() {
  614. return HemfRecordType.arcTo;
  615. }
  616. @Override
  617. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  618. long size = readRectL(leis, bounds);
  619. size += readPointL(leis, startPoint);
  620. size += readPointL(leis, endPoint);
  621. return size;
  622. }
  623. @Override
  624. public void draw(final HemfGraphics ctx) {
  625. final Arc2D arc = getShape();
  626. ctx.draw((path) -> path.append(arc, true), getFillDrawStyle());
  627. }
  628. }
  629. /** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
  630. public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
  631. private final Rectangle2D bounds = new Rectangle2D.Double();
  632. @Override
  633. public HemfRecordType getEmfRecordType() {
  634. return HemfRecordType.polyDraw;
  635. }
  636. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  637. return readPointL(leis, point);
  638. }
  639. @Override
  640. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  641. long size = readRectL(leis, bounds);
  642. int count = (int)leis.readUInt();
  643. size += LittleEndianConsts.INT_SIZE;
  644. Point2D points[] = new Point2D[count];
  645. for (int i=0; i<count; i++) {
  646. points[i] = new Point2D.Double();
  647. size += readPoint(leis, points[i]);
  648. }
  649. poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, count);
  650. for (int i=0; i<count; i++) {
  651. int mode = leis.readUByte();
  652. switch (mode & 0x06) {
  653. // PT_LINETO
  654. // Specifies that a line is to be drawn from the current position to this point, which
  655. // then becomes the new current position.
  656. case 0x02:
  657. poly.lineTo(points[i].getX(), points[i].getY());
  658. break;
  659. // PT_BEZIERTO
  660. // Specifies that this point is a control point or ending point for a Bezier curve.
  661. // PT_BEZIERTO types always occur in sets of three.
  662. // The current position defines the starting point for the Bezier curve.
  663. // The first two PT_BEZIERTO points are the control points,
  664. // and the third PT_BEZIERTO point is the ending point.
  665. // The ending point becomes the new current position.
  666. // If there are not three consecutive PT_BEZIERTO points, an error results.
  667. case 0x04:
  668. int mode2 = leis.readUByte();
  669. int mode3 = leis.readUByte();
  670. assert(mode2 == 0x04 && (mode3 == 0x04 || mode3 == 0x05));
  671. poly.curveTo(
  672. points[i].getX(), points[i].getY(),
  673. points[i+1].getX(), points[i+1].getY(),
  674. points[i+2].getX(), points[i+2].getY()
  675. );
  676. // update mode for closePath handling below
  677. mode = mode3;
  678. i+=2;
  679. break;
  680. // PT_MOVETO
  681. // Specifies that this point starts a disjoint figure. This point becomes the new current position.
  682. case 0x06:
  683. poly.moveTo(points[i].getX(), points[i].getY());
  684. break;
  685. default:
  686. // TODO: log error
  687. break;
  688. }
  689. // PT_CLOSEFIGURE
  690. // A PT_LINETO or PT_BEZIERTO type can be combined with this value by using the bitwise operator OR
  691. // to indicate that the corresponding point is the last point in a figure and the figure is closed.
  692. // The current position is set to the ending point of the closing line.
  693. if ((mode & 0x01) == 0x01) {
  694. this.poly.closePath();
  695. }
  696. }
  697. size += count;
  698. return size;
  699. }
  700. @Override
  701. protected FillDrawStyle getFillDrawStyle() {
  702. // Draws a set of line segments and Bezier curves.
  703. return FillDrawStyle.DRAW;
  704. }
  705. @Override
  706. public void draw(HemfGraphics ctx) {
  707. ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
  708. }
  709. }
  710. public static class EmfPolyDraw16 extends EmfPolyDraw {
  711. @Override
  712. public HemfRecordType getEmfRecordType() {
  713. return HemfRecordType.polyDraw16;
  714. }
  715. protected long readPoint(LittleEndianInputStream leis, Point2D point) {
  716. return readPointS(leis, point);
  717. }
  718. }
  719. /**
  720. * This record opens a path bracket in the current playback device context.
  721. *
  722. * After a path bracket is open, an application can begin processing records to define
  723. * the points that lie in the path. An application MUST close an open path bracket by
  724. * processing the EMR_ENDPATH record.
  725. *
  726. * When an application processes the EMR_BEGINPATH record, all previous paths
  727. * MUST be discarded from the playback device context.
  728. */
  729. public static class EmfBeginPath implements HemfRecord {
  730. @Override
  731. public HemfRecordType getEmfRecordType() {
  732. return HemfRecordType.beginPath;
  733. }
  734. @Override
  735. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  736. return 0;
  737. }
  738. @Override
  739. public void draw(HemfGraphics ctx) {
  740. final HemfDrawProperties prop = ctx.getProperties();
  741. prop.setPath(new Path2D.Double());
  742. prop.setUsePathBracket(true);
  743. }
  744. @Override
  745. public String toString() {
  746. return "{}";
  747. }
  748. }
  749. /**
  750. * This record closes a path bracket and selects the path defined by the bracket into
  751. * the playback device context.
  752. */
  753. public static class EmfEndPath implements HemfRecord {
  754. @Override
  755. public HemfRecordType getEmfRecordType() {
  756. return HemfRecordType.endPath;
  757. }
  758. @Override
  759. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  760. return 0;
  761. }
  762. @Override
  763. public void draw(HemfGraphics ctx) {
  764. final HemfDrawProperties prop = ctx.getProperties();
  765. prop.setUsePathBracket(false);
  766. }
  767. @Override
  768. public String toString() {
  769. return "{}";
  770. }
  771. }
  772. /**
  773. * This record aborts a path bracket or discards the path from a closed path bracket.
  774. */
  775. public static class EmfAbortPath implements HemfRecord {
  776. @Override
  777. public HemfRecordType getEmfRecordType() {
  778. return HemfRecordType.abortPath;
  779. }
  780. @Override
  781. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  782. return 0;
  783. }
  784. @Override
  785. public void draw(HemfGraphics ctx) {
  786. final HemfDrawProperties prop = ctx.getProperties();
  787. prop.setPath(null);
  788. prop.setUsePathBracket(false);
  789. }
  790. @Override
  791. public String toString() {
  792. return "{}";
  793. }
  794. }
  795. /**
  796. * This record closes an open figure in a path.
  797. *
  798. * Processing the EMR_CLOSEFIGURE record MUST close the figure by drawing a line
  799. * from the current position to the first point of the figure, and then it MUST connect
  800. * the lines by using the line join style. If a figure is closed by processing the
  801. * EMR_LINETO record instead of the EMR_CLOSEFIGURE record, end caps are
  802. * used to create the corner instead of a join.
  803. *
  804. * The EMR_CLOSEFIGURE record SHOULD only be used if there is an open path
  805. * bracket in the playback device context.
  806. *
  807. * A figure in a path is open unless it is explicitly closed by processing this record.
  808. * Note: A figure can be open even if the current point and the starting point of the
  809. * figure are the same.
  810. *
  811. * After processing the EMR_CLOSEFIGURE record, adding a line or curve to the path
  812. * MUST start a new figure.
  813. */
  814. public static class EmfCloseFigure implements HemfRecord {
  815. @Override
  816. public HemfRecordType getEmfRecordType() {
  817. return HemfRecordType.closeFigure;
  818. }
  819. @Override
  820. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  821. return 0;
  822. }
  823. @Override
  824. public void draw(HemfGraphics ctx) {
  825. final HemfDrawProperties prop = ctx.getProperties();
  826. final Path2D path = prop.getPath();
  827. if (path != null && path.getCurrentPoint() != null) {
  828. path.closePath();
  829. prop.setLocation(path.getCurrentPoint());
  830. }
  831. }
  832. @Override
  833. public String toString() {
  834. return "{}";
  835. }
  836. }
  837. /**
  838. * This record transforms any curves in the selected path into the playback device
  839. * context; each curve MUST be turned into a sequence of lines.
  840. */
  841. public static class EmfFlattenPath implements HemfRecord {
  842. @Override
  843. public HemfRecordType getEmfRecordType() {
  844. return HemfRecordType.flattenPath;
  845. }
  846. @Override
  847. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  848. return 0;
  849. }
  850. }
  851. /**
  852. * This record redefines the current path as the area that would be painted if the path
  853. * were drawn using the pen currently selected into the playback device context.
  854. */
  855. public static class EmfWidenPath implements HemfRecord {
  856. @Override
  857. public HemfRecordType getEmfRecordType() {
  858. return HemfRecordType.widenPath;
  859. }
  860. @Override
  861. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  862. return 0;
  863. }
  864. @Override
  865. public String toString() {
  866. return "{}";
  867. }
  868. }
  869. /**
  870. * The EMR_STROKEPATH record renders the specified path by using the current pen.
  871. */
  872. public static class EmfStrokePath implements HemfRecord {
  873. protected final Rectangle2D bounds = new Rectangle2D.Double();
  874. @Override
  875. public HemfRecordType getEmfRecordType() {
  876. return HemfRecordType.strokePath;
  877. }
  878. @Override
  879. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  880. // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
  881. return (recordSize == 0) ? 0 : readRectL(leis, bounds);
  882. }
  883. @Override
  884. public void draw(HemfGraphics ctx) {
  885. HemfDrawProperties props = ctx.getProperties();
  886. Path2D path = props.getPath();
  887. path.setWindingRule(ctx.getProperties().getWindingRule());
  888. ctx.draw(path);
  889. }
  890. @Override
  891. public String toString() {
  892. return boundsToString(bounds);
  893. }
  894. }
  895. /**
  896. * The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by
  897. * using the current brush and polygon-filling mode.
  898. */
  899. public static class EmfFillPath extends EmfStrokePath {
  900. @Override
  901. public HemfRecordType getEmfRecordType() {
  902. return HemfRecordType.fillPath;
  903. }
  904. @Override
  905. public void draw(HemfGraphics ctx) {
  906. final HemfDrawProperties prop = ctx.getProperties();
  907. final Path2D origPath = prop.getPath();
  908. if (origPath.getCurrentPoint() == null) {
  909. return;
  910. }
  911. final Path2D path = (Path2D)origPath.clone();
  912. path.closePath();
  913. path.setWindingRule(ctx.getProperties().getWindingRule());
  914. ctx.fill(path);
  915. }
  916. }
  917. /**
  918. * The EMR_STROKEANDFILLPATH record closes any open figures in a path, strokes the outline of the
  919. * path by using the current pen, and fills its interior by using the current brush.
  920. */
  921. public static class EmfStrokeAndFillPath extends EmfStrokePath {
  922. protected final Rectangle2D bounds = new Rectangle2D.Double();
  923. @Override
  924. public HemfRecordType getEmfRecordType() {
  925. return HemfRecordType.strokeAndFillPath;
  926. }
  927. @Override
  928. public void draw(HemfGraphics ctx) {
  929. HemfDrawProperties props = ctx.getProperties();
  930. Path2D path = props.getPath();
  931. path.closePath();
  932. path.setWindingRule(ctx.getProperties().getWindingRule());
  933. ctx.fill(path);
  934. ctx.draw(path);
  935. }
  936. }
  937. static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) {
  938. /* A 32-bit signed integer that defines the x coordinate, in logical coordinates,
  939. * of the ... corner of the rectangle.
  940. */
  941. final int left = leis.readInt();
  942. final int top = leis.readInt();
  943. final int right = leis.readInt();
  944. final int bottom = leis.readInt();
  945. bounds.setRect(left, top, right-left, bottom-top);
  946. return 4 * LittleEndianConsts.INT_SIZE;
  947. }
  948. static long readPointS(LittleEndianInputStream leis, Point2D point) {
  949. // x (2 bytes): A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
  950. final int x = leis.readShort();
  951. // y (2 bytes): A 16-bit signed integer that defines the vertical (y) coordinate of the point.
  952. final int y = leis.readShort();
  953. point.setLocation(x, y);
  954. return 2*LittleEndianConsts.SHORT_SIZE;
  955. }
  956. static long readPointL(LittleEndianInputStream leis, Point2D point) {
  957. // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
  958. final int x = leis.readInt();
  959. // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
  960. final int y = leis.readInt();
  961. point.setLocation(x, y);
  962. return 2*LittleEndianConsts.INT_SIZE;
  963. }
  964. static long readDimensionFloat(LittleEndianInputStream leis, Dimension2D dimension) {
  965. final double width = leis.readFloat();
  966. final double height = leis.readFloat();
  967. dimension.setSize(width, height);
  968. return 2*LittleEndianConsts.INT_SIZE;
  969. }
  970. static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) {
  971. // although the spec says "use unsigned ints", there are examples out there using signed ints
  972. final double width = leis.readInt();
  973. final double height = leis.readInt();
  974. dimension.setSize(width, height);
  975. return 2*LittleEndianConsts.INT_SIZE;
  976. }
  977. private static void polyTo(final HemfGraphics ctx, final Path2D poly, FillDrawStyle fillDrawStyle) {
  978. if (poly.getCurrentPoint() == null) {
  979. return;
  980. }
  981. final PathIterator pi = poly.getPathIterator(null);
  982. // ignore empty polys and dummy start point (moveTo)
  983. pi.next();
  984. if (pi.isDone()) {
  985. return;
  986. }
  987. ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
  988. }
  989. }