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.

HemfPlusDraw.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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.emfplus;
  16. import static java.util.stream.Collectors.joining;
  17. import static org.apache.poi.hemf.record.emf.HemfDraw.xformToString;
  18. import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
  19. import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
  20. import java.awt.Color;
  21. import java.awt.geom.AffineTransform;
  22. import java.awt.geom.Area;
  23. import java.awt.geom.Path2D;
  24. import java.awt.geom.Point2D;
  25. import java.awt.geom.Rectangle2D;
  26. import java.awt.image.BufferedImage;
  27. import java.io.IOException;
  28. import java.math.BigDecimal;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import java.util.function.BiFunction;
  32. import org.apache.commons.math3.linear.LUDecomposition;
  33. import org.apache.commons.math3.linear.MatrixUtils;
  34. import org.apache.commons.math3.linear.RealMatrix;
  35. import org.apache.poi.hemf.draw.HemfDrawProperties;
  36. import org.apache.poi.hemf.draw.HemfGraphics;
  37. import org.apache.poi.hemf.record.emf.HemfFill;
  38. import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
  39. import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
  40. import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
  41. import org.apache.poi.hwmf.record.HwmfBrushStyle;
  42. import org.apache.poi.hwmf.record.HwmfColorRef;
  43. import org.apache.poi.hwmf.record.HwmfDraw;
  44. import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
  45. import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
  46. import org.apache.poi.util.BitField;
  47. import org.apache.poi.util.BitFieldFactory;
  48. import org.apache.poi.util.IOUtils;
  49. import org.apache.poi.util.LittleEndianConsts;
  50. import org.apache.poi.util.LittleEndianInputStream;
  51. import org.apache.poi.util.StringUtil;
  52. public class HemfPlusDraw {
  53. private static final int MAX_OBJECT_SIZE = 1_000_000;
  54. public enum EmfPlusUnitType {
  55. /** Specifies a unit of logical distance within the world space. */
  56. World(0x00),
  57. /** Specifies a unit of distance based on the characteristics of the physical display. */
  58. Display(0x01),
  59. /** Specifies a unit of 1 pixel. */
  60. Pixel(0x02),
  61. /** Specifies a unit of 1 printer's point, or 1/72 inch. */
  62. Point(0x03),
  63. /** Specifies a unit of 1 inch. */
  64. Inch(0x04),
  65. /** Specifies a unit of 1/300 inch. */
  66. Document(0x05),
  67. /** Specifies a unit of 1 millimeter. */
  68. Millimeter(0x06)
  69. ;
  70. public final int id;
  71. EmfPlusUnitType(int id) {
  72. this.id = id;
  73. }
  74. public static EmfPlusUnitType valueOf(int id) {
  75. for (EmfPlusUnitType wrt : values()) {
  76. if (wrt.id == id) return wrt;
  77. }
  78. return null;
  79. }
  80. }
  81. public interface EmfPlusCompressed {
  82. /**
  83. * This bit indicates whether the data in the RectData field is compressed.
  84. * If set, RectData contains an EmfPlusRect object.
  85. * If clear, RectData contains an EmfPlusRectF object object.
  86. */
  87. BitField COMPRESSED = BitFieldFactory.getInstance(0x4000);
  88. int getFlags();
  89. /**
  90. * The index in the EMF+ Object Table to associate with the object
  91. * created by this record. The value MUST be zero to 63, inclusive.
  92. */
  93. default boolean isCompressed() {
  94. return COMPRESSED.isSet(getFlags());
  95. }
  96. default BiFunction<LittleEndianInputStream, Rectangle2D, Integer> getReadRect() {
  97. return isCompressed() ? HemfPlusDraw::readRectS : HemfPlusDraw::readRectF;
  98. }
  99. }
  100. public interface EmfPlusRelativePosition {
  101. /**
  102. * This bit indicates whether the PointData field specifies relative or absolute locations.
  103. * If set, each element in PointData specifies a location in the coordinate space that is relative to the
  104. * location specified by the previous element in the array. In the case of the first element in PointData,
  105. * a previous location at coordinates (0,0) is assumed.
  106. * If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
  107. *
  108. * Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
  109. */
  110. BitField POSITION = BitFieldFactory.getInstance(0x0800);
  111. int getFlags();
  112. default boolean isRelativePosition() {
  113. return POSITION.isSet(getFlags());
  114. }
  115. }
  116. public interface EmfPlusSolidColor {
  117. /**
  118. * If set, brushId specifies a color as an EmfPlusARGB object.
  119. * If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
  120. */
  121. BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
  122. int getFlags();
  123. int getBrushIdValue();
  124. default boolean isSolidColor() {
  125. return SOLID_COLOR.isSet(getFlags());
  126. }
  127. default int getBrushId() {
  128. return (isSolidColor()) ? -1 : getBrushIdValue();
  129. }
  130. default Color getSolidColor() {
  131. return (isSolidColor()) ? readARGB(getBrushIdValue()) : null;
  132. }
  133. default void applyColor(HemfGraphics ctx) {
  134. HemfDrawProperties prop = ctx.getProperties();
  135. if (isSolidColor()) {
  136. prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
  137. prop.setBrushColor(new HwmfColorRef(getSolidColor()));
  138. } else {
  139. ctx.applyObjectTableEntry(getBrushId());
  140. }
  141. }
  142. }
  143. /**
  144. * The EmfPlusDrawPath record specifies drawing a graphics path
  145. */
  146. public static class EmfPlusDrawPath implements HemfPlusRecord, EmfPlusObjectId {
  147. private int flags;
  148. private int penId;
  149. @Override
  150. public HemfPlusRecordType getEmfPlusRecordType() {
  151. return HemfPlusRecordType.drawPath;
  152. }
  153. @Override
  154. public int getFlags() {
  155. return flags;
  156. }
  157. @Override
  158. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  159. this.flags = flags;
  160. // A 32-bit unsigned integer that specifies an index in the EMF+ Object Table for an EmfPlusPen object
  161. // to use for drawing the EmfPlusPath. The value MUST be zero to 63, inclusive.
  162. penId = leis.readInt();
  163. assert (0 <= penId && penId <= 63);
  164. assert (dataSize == LittleEndianConsts.INT_SIZE);
  165. return LittleEndianConsts.INT_SIZE;
  166. }
  167. @Override
  168. public void draw(HemfGraphics ctx) {
  169. ctx.applyObjectTableEntry(penId);
  170. ctx.applyObjectTableEntry(getObjectId());
  171. HemfDrawProperties prop = ctx.getProperties();
  172. final Path2D path = prop.getPath();
  173. if (path != null) {
  174. ctx.draw(path);
  175. }
  176. }
  177. @Override
  178. public String toString() {
  179. return
  180. "{ flags: "+flags+
  181. ", penId: "+penId+" }";
  182. }
  183. }
  184. /**
  185. * The EmfPlusFillRects record specifies filling the interiors of a series of rectangles.
  186. */
  187. public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed, EmfPlusSolidColor {
  188. private int flags;
  189. private int brushId;
  190. private final ArrayList<Rectangle2D> rectData = new ArrayList<>();
  191. @Override
  192. public HemfPlusRecordType getEmfPlusRecordType() {
  193. return HemfPlusRecordType.fillRects;
  194. }
  195. @Override
  196. public int getFlags() {
  197. return flags;
  198. }
  199. @Override
  200. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  201. this.flags = flags;
  202. // A 32-bit unsigned integer that defines the brush, the content of which is
  203. // determined by the S bit in the Flags field.
  204. brushId = leis.readInt();
  205. // A 32-bit unsigned integer that specifies the number of rectangles in the RectData field.
  206. int count = leis.readInt();
  207. BiFunction<LittleEndianInputStream, Rectangle2D, Integer> readRect = getReadRect();
  208. rectData.ensureCapacity(count);
  209. int size = 2 * LittleEndianConsts.INT_SIZE;
  210. for (int i = 0; i<count; i++) {
  211. Rectangle2D rect = new Rectangle2D.Double();
  212. size += readRect.apply(leis, rect);
  213. rectData.add(rect);
  214. }
  215. return size;
  216. }
  217. @Override
  218. public void draw(HemfGraphics ctx) {
  219. applyColor(ctx);
  220. Area area = new Area();
  221. rectData.stream().map(Area::new).forEach(area::add);
  222. ctx.fill(area);
  223. }
  224. @Override
  225. public int getBrushIdValue() {
  226. return brushId;
  227. }
  228. @Override
  229. public String toString() {
  230. return
  231. "{ flags: "+flags+
  232. ", brushId: "+brushId+
  233. ", rectData: "+rectData.stream().map(HwmfDraw::boundsToString).collect(joining(",", "{", "}"))+
  234. "}";
  235. }
  236. }
  237. public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
  238. /**
  239. * This bit indicates that the rendering of the image includes applying an effect.
  240. * If set, an object of the Effect class MUST have been specified in an earlier EmfPlusSerializableObject record.
  241. */
  242. private static final BitField EFFECT = BitFieldFactory.getInstance(0x2000);
  243. private int flags;
  244. private int imageAttributesID;
  245. private EmfPlusUnitType srcUnit;
  246. private final Rectangle2D srcRect = new Rectangle2D.Double();
  247. private final Point2D upperLeft = new Point2D.Double();
  248. private final Point2D lowerRight = new Point2D.Double();
  249. private final Point2D lowerLeft = new Point2D.Double();
  250. private final AffineTransform trans = new AffineTransform();
  251. @Override
  252. public HemfPlusRecordType getEmfPlusRecordType() {
  253. return HemfPlusRecordType.drawImagePoints;
  254. }
  255. @Override
  256. public int getFlags() {
  257. return flags;
  258. }
  259. @Override
  260. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  261. this.flags = flags;
  262. // A 32-bit unsigned integer that contains the index of the
  263. // optional EmfPlusImageAttributes object in the EMF+ Object Table.
  264. imageAttributesID = leis.readInt();
  265. // A 32-bit signed integer that defines the units of the SrcRect field.
  266. // It MUST be the UnitPixel value of the UnitType enumeration
  267. srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
  268. assert(srcUnit == EmfPlusUnitType.Pixel);
  269. int size = 2 * LittleEndianConsts.INT_SIZE;
  270. // An EmfPlusRectF object that defines a portion of the image to be rendered.
  271. size += readRectF(leis, srcRect);
  272. // A 32-bit unsigned integer that specifies the number of points in the PointData array.
  273. // Exactly 3 points MUST be specified.
  274. int count = leis.readInt();
  275. assert(count == 3);
  276. size += LittleEndianConsts.INT_SIZE;
  277. BiFunction<LittleEndianInputStream, Point2D, Integer> readPoint;
  278. if (isRelativePosition()) {
  279. // If the POSITION flag is set in the Flags, the points specify relative locations.
  280. readPoint = HemfPlusDraw::readPointR;
  281. } else if (isCompressed()) {
  282. // If the POSITION bit is clear and the COMPRESSED bit is set in the Flags field, the points
  283. // specify absolute locations with integer values.
  284. readPoint = HemfPlusDraw::readPointS;
  285. } else {
  286. // If the POSITION bit is clear and the COMPRESSED bit is clear in the Flags field, the points
  287. // specify absolute locations with floating-point values.
  288. readPoint = HemfPlusDraw::readPointF;
  289. }
  290. // TODO: handle relative coordinates
  291. // An array of Count points that specify three points of a parallelogram.
  292. // The three points represent the upper-left, upper-right, and lower-left corners of the parallelogram.
  293. // The fourth point of the parallelogram is extrapolated from the first three.
  294. // The portion of the image specified by the SrcRect field SHOULD have scaling and shearing transforms
  295. // applied if necessary to fit inside the parallelogram.
  296. // size += readPoint.apply(leis, upperLeft);
  297. // size += readPoint.apply(leis, upperRight);
  298. // size += readPoint.apply(leis, lowerLeft);
  299. size += readPoint.apply(leis, lowerLeft);
  300. size += readPoint.apply(leis, lowerRight);
  301. size += readPoint.apply(leis, upperLeft);
  302. // https://math.stackexchange.com/questions/2772737/how-to-transform-arbitrary-rectangle-into-specific-parallelogram
  303. RealMatrix para2normal = MatrixUtils.createRealMatrix(new double[][] {
  304. { lowerLeft.getX(), lowerRight.getX(), upperLeft.getX() },
  305. { lowerLeft.getY(), lowerRight.getY(), upperLeft.getY() },
  306. { 1, 1, 1 }
  307. });
  308. RealMatrix rect2normal = MatrixUtils.createRealMatrix(new double[][]{
  309. { srcRect.getMinX(), srcRect.getMaxX(), srcRect.getMinX() },
  310. { srcRect.getMinY(), srcRect.getMinY(), srcRect.getMaxY() },
  311. { 1, 1, 1 }
  312. });
  313. RealMatrix normal2rect = new LUDecomposition(rect2normal).getSolver().getInverse();
  314. double[][] m = para2normal.multiply(normal2rect).getData();
  315. trans.setTransform(round10(m[0][0]), round10(m[1][0]), round10(m[0][1]), round10(m[1][1]), round10(m[0][2]), round10(m[1][2]));
  316. return size;
  317. }
  318. @Override
  319. public void draw(HemfGraphics ctx) {
  320. HemfDrawProperties prop = ctx.getProperties();
  321. ctx.applyObjectTableEntry(imageAttributesID);
  322. ctx.applyObjectTableEntry(getObjectId());
  323. AffineTransform txSaved = ctx.getTransform(), tx = new AffineTransform(txSaved);
  324. try {
  325. tx.concatenate(trans);
  326. ctx.setTransform(tx);
  327. EmfPlusObject imgObj = (EmfPlusObject)ctx.getObjectTableEntry(getObjectId());
  328. EmfPlusImage img = imgObj.getObjectData();
  329. Rectangle2D srcBounds = img.getBounds(imgObj.getContinuedObject());
  330. BufferedImage bi = prop.getEmfPlusImage();
  331. prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
  332. prop.setBkMode(HwmfBkMode.TRANSPARENT);
  333. // the buffered image might be rescaled, so we need to calculate a new src rect to take
  334. // the image data from
  335. AffineTransform srcTx = new AffineTransform();
  336. srcTx.translate(-srcBounds.getX(), srcBounds.getY());
  337. srcTx.scale(bi.getWidth()/srcBounds.getWidth(), bi.getHeight()/srcBounds.getHeight());
  338. srcTx.translate(bi.getMinX(), bi.getMinY());
  339. Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
  340. // TODO: handle srcUnit
  341. Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
  342. ctx.drawImage(bi, srcRect, destRect);
  343. } finally {
  344. ctx.setTransform(txSaved);
  345. }
  346. }
  347. @Override
  348. public String toString() {
  349. return
  350. "{ flags: "+flags+
  351. ", imageAttributesID: "+imageAttributesID+
  352. ", srcUnit: '"+srcUnit+"'"+
  353. ", srcRect: "+boundsToString(srcRect)+
  354. ", upperLeft: "+pointToString(upperLeft)+
  355. ", lowerLeft: "+pointToString(lowerLeft)+
  356. ", lowerRight: "+pointToString(lowerRight)+
  357. ", transform: "+xformToString(trans)+
  358. "}"
  359. ;
  360. }
  361. }
  362. /** The EmfPlusDrawImage record specifies drawing a scaled image. */
  363. public static class EmfPlusDrawImage implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
  364. private int flags;
  365. private int imageAttributesID;
  366. private EmfPlusUnitType srcUnit;
  367. private final Rectangle2D srcRect = new Rectangle2D.Double();
  368. private final Rectangle2D rectData = new Rectangle2D.Double();
  369. @Override
  370. public HemfPlusRecordType getEmfPlusRecordType() {
  371. return HemfPlusRecordType.drawImage;
  372. }
  373. @Override
  374. public int getFlags() {
  375. return flags;
  376. }
  377. @Override
  378. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  379. this.flags = flags;
  380. // A 32-bit unsigned integer that contains the index of the
  381. // optional EmfPlusImageAttributes object in the EMF+ Object Table.
  382. imageAttributesID = leis.readInt();
  383. // A 32-bit signed integer that defines the units of the SrcRect field.
  384. // It MUST be the UnitPixel value of the UnitType enumeration
  385. srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
  386. assert(srcUnit == EmfPlusUnitType.Pixel);
  387. int size = 2 * LittleEndianConsts.INT_SIZE;
  388. // An EmfPlusRectF object that specifies a portion of the image to be rendered. The portion of the image
  389. // specified by this rectangle is scaled to fit the destination rectangle specified by the RectData field.
  390. size += readRectF(leis, srcRect);
  391. // Either an EmfPlusRect or EmfPlusRectF object that defines the bounding box of the image. The portion of
  392. // the image specified by the SrcRect field is scaled to fit this rectangle.
  393. size += getReadRect().apply(leis, rectData);
  394. return size;
  395. }
  396. @Override
  397. public void draw(HemfGraphics ctx) {
  398. ctx.applyObjectTableEntry(imageAttributesID);
  399. ctx.applyObjectTableEntry(getObjectId());
  400. HemfDrawProperties prop = ctx.getProperties();
  401. prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
  402. prop.setBkMode(HwmfBkMode.TRANSPARENT);
  403. ctx.drawImage(prop.getEmfPlusImage(), srcRect, rectData);
  404. }
  405. @Override
  406. public String toString() {
  407. return
  408. "{ flags: "+flags+
  409. ", imageAttributesID: "+imageAttributesID+
  410. ", srcUnit: '"+srcUnit+"'"+
  411. ", srcRect: "+boundsToString(srcRect)+
  412. ", rectData: "+boundsToString(rectData)+
  413. "}"
  414. ;
  415. }
  416. }
  417. /** The EmfPlusFillRegion record specifies filling the interior of a graphics region. */
  418. public static class EmfPlusFillRegion implements HemfPlusRecord, EmfPlusSolidColor, EmfPlusObjectId {
  419. private int flags;
  420. private int brushId;
  421. @Override
  422. public HemfPlusRecordType getEmfPlusRecordType() {
  423. return HemfPlusRecordType.fillRegion;
  424. }
  425. @Override
  426. public int getFlags() {
  427. return flags;
  428. }
  429. @Override
  430. public int getBrushIdValue() {
  431. return brushId;
  432. }
  433. @Override
  434. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  435. this.flags = flags;
  436. // A 32-bit unsigned integer that defines the brush, the content of which is determined by
  437. // the SOLID_COLOR bit in the Flags field.
  438. // If SOLID_COLOR is set, BrushId specifies a color as an EmfPlusARGB object.
  439. // If clear, BrushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
  440. brushId = leis.readInt();
  441. return LittleEndianConsts.INT_SIZE;
  442. }
  443. @Override
  444. public void draw(HemfGraphics ctx) {
  445. applyColor(ctx);
  446. ctx.applyObjectTableEntry(getObjectId());
  447. HemfDrawProperties prop = ctx.getProperties();
  448. ctx.fill(prop.getPath());
  449. }
  450. @Override
  451. public String toString() {
  452. return
  453. "{ flags: "+flags+
  454. ", brushId: "+brushId+" }";
  455. }
  456. }
  457. /** The EmfPlusFillPath record specifies filling the interior of a graphics path. */
  458. public static class EmfPlusFillPath extends EmfPlusFillRegion {
  459. @Override
  460. public HemfPlusRecordType getEmfPlusRecordType() {
  461. return HemfPlusRecordType.fillPath;
  462. }
  463. }
  464. /** The EmfPlusDrawDriverString record specifies text output with character positions. */
  465. public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId, EmfPlusSolidColor {
  466. /**
  467. * If set, the positions of character glyphs SHOULD be specified in a character map lookup table.
  468. * If clear, the glyph positions SHOULD be obtained from an array of coordinates.
  469. */
  470. private static final BitField CMAP_LOOKUP = BitFieldFactory.getInstance(0x0001);
  471. /**
  472. * If set, the string SHOULD be rendered vertically.
  473. * If clear, the string SHOULD be rendered horizontally.
  474. */
  475. private static final BitField VERTICAL = BitFieldFactory.getInstance(0x0002);
  476. /**
  477. * If set, character glyph positions SHOULD be calculated relative to the position of the first glyph.
  478. * If clear, the glyph positions SHOULD be obtained from an array of coordinates.
  479. */
  480. private static final BitField REALIZED_ADVANCE = BitFieldFactory.getInstance(0x0004);
  481. /**
  482. * If set, less memory SHOULD be used to cache anti-aliased glyphs, which produces lower quality text rendering.
  483. * If clear, more memory SHOULD be used, which produces higher quality text rendering.
  484. */
  485. private static final BitField LIMIT_SUBPIXEL = BitFieldFactory.getInstance(0x0008);
  486. private int flags;
  487. private int brushId;
  488. private int optionsFlags;
  489. private String glyphs;
  490. private final List<Point2D> glpyhPos = new ArrayList<>();
  491. private final AffineTransform transformMatrix = new AffineTransform();
  492. @Override
  493. public HemfPlusRecordType getEmfPlusRecordType() {
  494. return HemfPlusRecordType.drawDriverstring;
  495. }
  496. @Override
  497. public int getFlags() {
  498. return flags;
  499. }
  500. @Override
  501. public int getBrushIdValue() {
  502. return brushId;
  503. }
  504. @Override
  505. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  506. this.flags = flags;
  507. // A 32-bit unsigned integer that specifies either the foreground color of the text or a graphics brush,
  508. // depending on the value of the SOLID_COLOR flag in the Flags.
  509. brushId = leis.readInt();
  510. // A 32-bit unsigned integer that specifies the spacing, orientation, and quality of rendering for the
  511. // string. This value MUST be composed of DriverStringOptions flags
  512. optionsFlags = leis.readInt();
  513. // A 32-bit unsigned integer that specifies whether a transform matrix is present in the
  514. // TransformMatrix field.
  515. boolean hasMatrix = leis.readInt() == 1;
  516. // A 32-bit unsigned integer that specifies number of glyphs in the string.
  517. int glyphCount = leis.readInt();
  518. int size = 4 * LittleEndianConsts.INT_SIZE;
  519. // TOOD: implement Non-Cmap-Lookup correctly
  520. // If the CMAP_LOOKUP flag in the optionsFlags field is set, each value in this array specifies a
  521. // Unicode character. Otherwise, each value specifies an index to a character glyph in the EmfPlusFont
  522. // object specified by the ObjectId value in Flags field.
  523. byte[] glyphBuf = IOUtils.toByteArray(leis, glyphCount*2, MAX_OBJECT_SIZE);
  524. glyphs = StringUtil.getFromUnicodeLE(glyphBuf);
  525. size += glyphBuf.length;
  526. // An array of EmfPlusPointF objects that specify the output position of each character glyph.
  527. // There MUST be GlyphCount elements, which have a one-to-one correspondence with the elements
  528. // in the Glyphs array.
  529. //
  530. // Glyph positions are calculated from the position of the first glyph if the REALIZED_ADVANCE flag in
  531. // Options flags is set. In this case, GlyphPos specifies the position of the first glyph only.
  532. int glyphPosCnt = REALIZED_ADVANCE.isSet(optionsFlags) ? 1 : glyphCount;
  533. for (int i=0; i<glyphCount; i++) {
  534. Point2D p = new Point2D.Double();
  535. size += readPointF(leis, p);
  536. glpyhPos.add(p);
  537. }
  538. if (hasMatrix) {
  539. size += HemfFill.readXForm(leis, transformMatrix);
  540. }
  541. return size;
  542. }
  543. }
  544. /** The EmfPlusDrawRects record specifies drawing a series of rectangles. */
  545. public static class EmfPlusDrawRects implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
  546. private int flags;
  547. private final List<Rectangle2D> rectData = new ArrayList<>();
  548. @Override
  549. public HemfPlusRecordType getEmfPlusRecordType() {
  550. return HemfPlusRecordType.drawRects;
  551. }
  552. @Override
  553. public int getFlags() {
  554. return flags;
  555. }
  556. @Override
  557. public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
  558. this.flags = flags;
  559. // A 32-bit unsigned integer that specifies the number of rectangles in the RectData member.
  560. int count = leis.readInt();
  561. int size = LittleEndianConsts.INT_SIZE;
  562. BiFunction<LittleEndianInputStream, Rectangle2D, Integer> readRect = getReadRect();
  563. for (int i=0; i<count; i++) {
  564. Rectangle2D rect = new Rectangle2D.Double();
  565. size += readRect.apply(leis, rect);
  566. }
  567. return size;
  568. }
  569. }
  570. static double round10(double d) {
  571. return new BigDecimal(d).setScale(10, BigDecimal.ROUND_HALF_UP).doubleValue();
  572. }
  573. static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
  574. // A 16-bit signed integer that defines the ... coordinate
  575. final int x = leis.readShort();
  576. final int y = leis.readShort();
  577. final int width = leis.readShort();
  578. final int height = leis.readShort();
  579. bounds.setRect(x, y, width, height);
  580. return 4 * LittleEndianConsts.SHORT_SIZE;
  581. }
  582. static int readRectF(LittleEndianInputStream leis, Rectangle2D bounds) {
  583. // A 32-bit floating-point that defines the ... coordinate
  584. final double x = leis.readFloat();
  585. final double y = leis.readFloat();
  586. final double width = leis.readFloat();
  587. final double height = leis.readFloat();
  588. bounds.setRect(x, y, width, height);
  589. return 4 * LittleEndianConsts.INT_SIZE;
  590. }
  591. /**
  592. * The EmfPlusPoint object specifies an ordered pair of integer (X,Y) values that define an absolute
  593. * location in a coordinate space.
  594. */
  595. static int readPointS(LittleEndianInputStream leis, Point2D point) {
  596. double x = leis.readShort();
  597. double y = leis.readShort();
  598. point.setLocation(x,y);
  599. return 2*LittleEndianConsts.SHORT_SIZE;
  600. }
  601. /**
  602. * The EmfPlusPointF object specifies an ordered pair of floating-point (X,Y) values that define an
  603. * absolute location in a coordinate space.
  604. */
  605. static int readPointF(LittleEndianInputStream leis, Point2D point) {
  606. double x = leis.readFloat();
  607. double y = leis.readFloat();
  608. point.setLocation(x,y);
  609. return 2*LittleEndianConsts.INT_SIZE;
  610. }
  611. /**
  612. * The EmfPlusPointR object specifies an ordered pair of integer (X,Y) values that define a relative
  613. * location in a coordinate space.
  614. */
  615. static int readPointR(LittleEndianInputStream leis, Point2D point) {
  616. int[] p = { 0 };
  617. int size = readEmfPlusInteger(leis, p);
  618. double x = p[0];
  619. size += readEmfPlusInteger(leis, p);
  620. double y = p[0];
  621. point.setLocation(x,y);
  622. return size;
  623. }
  624. private static int readEmfPlusInteger(LittleEndianInputStream leis, int[] value) {
  625. value[0] = leis.readByte();
  626. // check for EmfPlusInteger7 value
  627. if ((value[0] & 0x80) == 0) {
  628. return LittleEndianConsts.BYTE_SIZE;
  629. }
  630. // ok we've read a EmfPlusInteger15
  631. value[0] = ((value[0] << 8) | leis.readByte()) & 0x7FFF;
  632. return LittleEndianConsts.SHORT_SIZE;
  633. }
  634. static Color readARGB(int argb) {
  635. return new Color((argb >>> 16) & 0xFF, (argb >>> 8) & 0xFF, argb & 0xFF, (argb >>> 24) & 0xFF);
  636. }
  637. }