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.

HemfMisc.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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.hemf.record.emf.HemfDraw.readPointL;
  17. import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
  18. import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
  19. import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
  20. import java.awt.geom.AffineTransform;
  21. import java.awt.geom.NoninvertibleTransformException;
  22. import java.awt.geom.Point2D;
  23. import java.awt.geom.Rectangle2D;
  24. import java.awt.image.BufferedImage;
  25. import java.io.IOException;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.List;
  29. import java.util.function.Function;
  30. import org.apache.poi.hemf.draw.HemfDrawProperties;
  31. import org.apache.poi.hemf.draw.HemfGraphics;
  32. import org.apache.poi.hwmf.draw.HwmfDrawProperties;
  33. import org.apache.poi.hwmf.draw.HwmfGraphics;
  34. import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
  35. import org.apache.poi.hwmf.record.HwmfBitmapDib;
  36. import org.apache.poi.hwmf.record.HwmfBrushStyle;
  37. import org.apache.poi.hwmf.record.HwmfColorRef;
  38. import org.apache.poi.hwmf.record.HwmfFill;
  39. import org.apache.poi.hwmf.record.HwmfHatchStyle;
  40. import org.apache.poi.hwmf.record.HwmfMapMode;
  41. import org.apache.poi.hwmf.record.HwmfMisc;
  42. import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode;
  43. import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
  44. import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
  45. import org.apache.poi.hwmf.record.HwmfPenStyle;
  46. import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
  47. import org.apache.poi.util.LittleEndianConsts;
  48. import org.apache.poi.util.LittleEndianInputStream;
  49. public class HemfMisc {
  50. public enum HemfModifyWorldTransformMode {
  51. /**
  52. * Reset the current transform using the identity matrix.
  53. * In this mode, the specified transform data is ignored.
  54. */
  55. MWT_IDENTITY(1),
  56. /**
  57. * Multiply the current transform. In this mode, the specified transform data is the left multiplicand,
  58. * and the transform that is currently defined in the playback device context is the right multiplicand.
  59. */
  60. MWT_LEFTMULTIPLY(2),
  61. /**
  62. * Multiply the current transform. In this mode, the specified transform data is the right multiplicand,
  63. * and the transform that is currently defined in the playback device context is the left multiplicand.
  64. */
  65. MWT_RIGHTMULTIPLY(3),
  66. /**
  67. * Perform the function of an EMR_SETWORLDTRANSFORM record
  68. */
  69. MWT_SET(4)
  70. ;
  71. public final int id;
  72. HemfModifyWorldTransformMode(int id) {
  73. this.id = id;
  74. }
  75. public static HemfModifyWorldTransformMode valueOf(int id) {
  76. for (HemfModifyWorldTransformMode wrt : values()) {
  77. if (wrt.id == id) return wrt;
  78. }
  79. return null;
  80. }
  81. }
  82. public static class EmfEof implements HemfRecord {
  83. protected final List<PaletteEntry> palette = new ArrayList<>();
  84. @Override
  85. public HemfRecordType getEmfRecordType() {
  86. return HemfRecordType.eof;
  87. }
  88. @Override
  89. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  90. final int startIdx = leis.getReadIndex();
  91. // A 32-bit unsigned integer that specifies the number of palette entries.
  92. final int nPalEntries = (int) leis.readUInt();
  93. // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
  94. final int offPalEntries = (int) leis.readUInt();
  95. int size = 2 * LittleEndianConsts.INT_SIZE;
  96. if (nPalEntries > 0 && offPalEntries > 0) {
  97. int undefinedSpace1 = (int) (offPalEntries - (size + HEADER_SIZE));
  98. assert (undefinedSpace1 >= 0);
  99. leis.skipFully(undefinedSpace1);
  100. size += undefinedSpace1;
  101. for (int i = 0; i < nPalEntries; i++) {
  102. PaletteEntry pe = new PaletteEntry();
  103. size += pe.init(leis);
  104. }
  105. int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE);
  106. assert (undefinedSpace2 >= 0);
  107. leis.skipFully(undefinedSpace2);
  108. size += undefinedSpace2;
  109. }
  110. // A 32-bit unsigned integer that MUST be the same as Size and MUST be the
  111. // last field of the record and hence the metafile.
  112. // LogPaletteEntry objects, if they exist, MUST precede this field.
  113. long sizeLast = leis.readUInt();
  114. size += LittleEndianConsts.INT_SIZE;
  115. // some files store the whole file size in sizeLast, other just the last record size
  116. // assert (sizeLast == size+HEADER_SIZE);
  117. assert (recordSize == size);
  118. return size;
  119. }
  120. }
  121. /**
  122. * The EMF_SAVEDC record saves the playback device context for later retrieval.
  123. */
  124. public static class EmfSaveDc extends HwmfMisc.WmfSaveDc implements HemfRecord {
  125. @Override
  126. public HemfRecordType getEmfRecordType() {
  127. return HemfRecordType.saveDc;
  128. }
  129. @Override
  130. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  131. return 0;
  132. }
  133. }
  134. /**
  135. * The EMF_RESTOREDC record restores the playback device context from a previously saved device
  136. * context.
  137. */
  138. public static class EmfRestoreDc extends HwmfMisc.WmfRestoreDc implements HemfRecord {
  139. @Override
  140. public HemfRecordType getEmfRecordType() {
  141. return HemfRecordType.restoreDc;
  142. }
  143. @Override
  144. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  145. // A 32-bit signed integer that specifies the saved state to restore relative to
  146. // the current state. This value MUST be negative; –1 represents the state that was most
  147. // recently saved on the stack, –2 the one before that, etc.
  148. nSavedDC = leis.readInt();
  149. return LittleEndianConsts.INT_SIZE;
  150. }
  151. }
  152. /**
  153. * The META_SETBKCOLOR record sets the background color in the playback device context to a
  154. * specified color, or to the nearest physical color if the device cannot represent the specified color.
  155. */
  156. public static class EmfSetBkColor extends HwmfMisc.WmfSetBkColor implements HemfRecord {
  157. @Override
  158. public HemfRecordType getEmfRecordType() {
  159. return HemfRecordType.setBkColor;
  160. }
  161. @Override
  162. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  163. return colorRef.init(leis);
  164. }
  165. }
  166. /**
  167. * The EMR_SETBKMODE record specifies the background mix mode of the playback device context.
  168. * The background mix mode is used with text, hatched brushes, and pen styles that are not solid
  169. * lines.
  170. */
  171. public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord {
  172. public HemfRecordType getEmfRecordType() {
  173. return HemfRecordType.setBkMode;
  174. }
  175. @Override
  176. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  177. /*
  178. * A 32-bit unsigned integer that specifies the background mode
  179. * and MUST be in the BackgroundMode (section 2.1.4) enumeration
  180. */
  181. bkMode = HwmfBkMode.valueOf((int) leis.readUInt());
  182. return LittleEndianConsts.INT_SIZE;
  183. }
  184. }
  185. /**
  186. * The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to
  187. * physical fonts, which is performed by the font mapper.
  188. */
  189. public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord {
  190. public HemfRecordType getEmfRecordType() {
  191. return HemfRecordType.setMapperFlags;
  192. }
  193. @Override
  194. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  195. return super.init(leis, recordSize, (int) recordId);
  196. }
  197. }
  198. /**
  199. * The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The
  200. * mapping mode specifies the unit of measure used to transform page space units into device space
  201. * units, and also specifies the orientation of the device's x-axis and y-axis.
  202. */
  203. public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord {
  204. public HemfRecordType getEmfRecordType() {
  205. return HemfRecordType.setMapMode;
  206. }
  207. @Override
  208. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  209. // A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration
  210. mapMode = HwmfMapMode.valueOf((int) leis.readUInt());
  211. return LittleEndianConsts.INT_SIZE;
  212. }
  213. }
  214. /**
  215. * The EMR_SETROP2 record defines a binary raster operation mode.
  216. */
  217. public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord {
  218. @Override
  219. public HemfRecordType getEmfRecordType() {
  220. return HemfRecordType.setRop2;
  221. }
  222. @Override
  223. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  224. // A 32-bit unsigned integer that specifies the raster operation mode and
  225. // MUST be in the WMF Binary Raster Op enumeration
  226. drawMode = HwmfBinaryRasterOp.valueOf((int) leis.readUInt());
  227. return LittleEndianConsts.INT_SIZE;
  228. }
  229. }
  230. /**
  231. * The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode.
  232. */
  233. public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord {
  234. @Override
  235. public HemfRecordType getEmfRecordType() {
  236. return HemfRecordType.setStretchBltMode;
  237. }
  238. @Override
  239. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  240. // A 32-bit unsigned integer that specifies the stretch mode and MAY be
  241. // in the StretchMode enumeration.
  242. stretchBltMode = StretchBltMode.valueOf((int) leis.readUInt());
  243. return LittleEndianConsts.INT_SIZE;
  244. }
  245. }
  246. /**
  247. * The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations.
  248. */
  249. public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord {
  250. /**
  251. * A 32-bit unsigned integer that specifies the index of the logical brush object in the
  252. * EMF Object Table. This index MUST be saved so that this object can be reused or modified.
  253. */
  254. private int brushIdx;
  255. @Override
  256. public HemfRecordType getEmfRecordType() {
  257. return HemfRecordType.createBrushIndirect;
  258. }
  259. @Override
  260. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  261. brushIdx = (int) leis.readUInt();
  262. brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt());
  263. colorRef = new HwmfColorRef();
  264. int size = colorRef.init(leis);
  265. brushHatch = HwmfHatchStyle.valueOf((int) leis.readUInt());
  266. return size + 3 * LittleEndianConsts.INT_SIZE;
  267. }
  268. @Override
  269. public void draw(HemfGraphics ctx) {
  270. ctx.addObjectTableEntry(this, brushIdx);
  271. }
  272. @Override
  273. public String toString() {
  274. return
  275. "{ brushIndex: "+brushIdx+
  276. ", brushStyle: '"+brushStyle+"'"+
  277. ", colorRef: "+colorRef+
  278. ", brushHatch: '"+brushHatch+"' }";
  279. }
  280. }
  281. /**
  282. * The EMR_CREATEDIBPATTERNBRUSHPT record defines a pattern brush for graphics operations.
  283. * The pattern is specified by a DIB.
  284. */
  285. public static class EmfCreateDibPatternBrushPt extends HwmfMisc.WmfDibCreatePatternBrush implements HemfRecord {
  286. protected int brushIdx;
  287. @Override
  288. public HemfRecordType getEmfRecordType() {
  289. return HemfRecordType.createDibPatternBrushPt;
  290. }
  291. @Override
  292. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  293. final int startIdx = leis.getReadIndex();
  294. style = HwmfBrushStyle.BS_DIBPATTERNPT;
  295. // A 32-bit unsigned integer that specifies the index of the pattern brush
  296. // object in the EMF Object Table
  297. brushIdx = (int)leis.readUInt();
  298. // A 32-bit unsigned integer that specifies how to interpret values in the color
  299. // table in the DIB header. This value MUST be in the DIBColors enumeration
  300. colorUsage = HwmfFill.ColorUsage.valueOf((int)leis.readUInt());
  301. // A 32-bit unsigned integer that specifies the offset from the start of this
  302. // record to the DIB header.
  303. final int offBmi = leis.readInt();
  304. // A 32-bit unsigned integer that specifies the size of the DIB header.
  305. final int cbBmi = leis.readInt();
  306. // A 32-bit unsigned integer that specifies the offset from the start of this record to the DIB bits.
  307. final int offBits = leis.readInt();
  308. // A 32-bit unsigned integer that specifies the size of the DIB bits.
  309. final int cbBits = leis.readInt();
  310. int size = 6*LittleEndianConsts.INT_SIZE;
  311. patternDib = new HwmfBitmapDib();
  312. size += readBitmap(leis, patternDib, startIdx, offBmi, cbBmi, offBits, cbBits);
  313. return size;
  314. }
  315. @Override
  316. public void draw(HemfGraphics ctx) {
  317. ctx.addObjectTableEntry(this, brushIdx);
  318. }
  319. }
  320. /**
  321. * The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index
  322. * in the EMF Object Table
  323. */
  324. public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord {
  325. @Override
  326. public HemfRecordType getEmfRecordType() {
  327. return HemfRecordType.deleteobject;
  328. }
  329. @Override
  330. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  331. objectIndex = (int) leis.readUInt();
  332. return LittleEndianConsts.INT_SIZE;
  333. }
  334. }
  335. /**
  336. * The EMR_CREATEPEN record defines a logical pen for graphics operations.
  337. */
  338. public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord {
  339. /**
  340. * A 32-bit unsigned integer that specifies the index of the logical palette object
  341. * in the EMF Object Table. This index MUST be saved so that this object can be
  342. * reused or modified.
  343. */
  344. protected int penIndex;
  345. @Override
  346. public HemfRecordType getEmfRecordType() {
  347. return HemfRecordType.createPen;
  348. }
  349. @Override
  350. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  351. penIndex = (int) leis.readUInt();
  352. // A 32-bit unsigned integer that specifies the PenStyle.
  353. // The value MUST be defined from the PenStyle enumeration table
  354. penStyle = HwmfPenStyle.valueOf((int) leis.readUInt());
  355. int widthX = leis.readInt();
  356. int widthY = leis.readInt();
  357. dimension.setSize(widthX, widthY);
  358. int size = colorRef.init(leis);
  359. return size + 4 * LittleEndianConsts.INT_SIZE;
  360. }
  361. @Override
  362. public void draw(HemfGraphics ctx) {
  363. ctx.addObjectTableEntry(this, penIndex);
  364. }
  365. @Override
  366. public String toString() {
  367. return super.toString().replaceFirst("\\{", "{ penIndex: "+penIndex+", ");
  368. }
  369. }
  370. public static class EmfExtCreatePen extends EmfCreatePen {
  371. protected HwmfBrushStyle brushStyle;
  372. protected HwmfHatchStyle hatchStyle;
  373. protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
  374. @Override
  375. public HemfRecordType getEmfRecordType() {
  376. return HemfRecordType.extCreatePen;
  377. }
  378. @Override
  379. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  380. final int startIdx = leis.getReadIndex();
  381. penIndex = (int) leis.readUInt();
  382. // A 32-bit unsigned integer that specifies the offset from the start of this
  383. // record to the DIB header, if the record contains a DIB.
  384. int offBmi = (int) leis.readUInt();
  385. // A 32-bit unsigned integer that specifies the size of the DIB header, if the
  386. // record contains a DIB.
  387. int cbBmi = (int) leis.readUInt();
  388. // A 32-bit unsigned integer that specifies the offset from the start of this
  389. // record to the DIB bits, if the record contains a DIB.
  390. int offBits = (int) leis.readUInt();
  391. // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
  392. // contains a DIB.
  393. int cbBits = (int) leis.readUInt();
  394. // A 32-bit unsigned integer that specifies the PenStyle.
  395. // The value MUST be defined from the PenStyle enumeration table
  396. final HemfPenStyle emfPS = HemfPenStyle.valueOf((int) leis.readUInt());
  397. penStyle = emfPS;
  398. // A 32-bit unsigned integer that specifies the width of the line drawn by the pen.
  399. // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical
  400. // units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is
  401. // PS_COSMETIC, this value MUST be 0x00000001.
  402. long width = leis.readUInt();
  403. dimension.setSize(width, 0);
  404. int size = 7 * LittleEndianConsts.INT_SIZE;
  405. // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration
  406. //
  407. // If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED.
  408. // The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL.
  409. // The BS_NULL style SHOULD be used to specify a brush that has no effect
  410. brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt());
  411. size += LittleEndianConsts.INT_SIZE;
  412. size += colorRef.init(leis);
  413. hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());
  414. size += LittleEndianConsts.INT_SIZE;
  415. // The number of elements in the array specified in the StyleEntry
  416. // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE.
  417. final int numStyleEntries = (int) leis.readUInt();
  418. size += LittleEndianConsts.INT_SIZE;
  419. assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE);
  420. // An optional array of 32-bit unsigned integers that defines the lengths of
  421. // dashes and gaps in the line drawn by this pen, when the value of PenStyle is
  422. // PS_USERSTYLE line style for the pen. The array contains a number of entries specified by
  423. // NumStyleEntries, but it is used as if it repeated indefinitely.
  424. // The first entry in the array specifies the length of the first dash. The second entry specifies
  425. // the length of the first gap. Thereafter, lengths of dashes and gaps alternate.
  426. // If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical
  427. // units; otherwise, the lengths are specified in device units.
  428. float[] dashPattern = new float[numStyleEntries];
  429. for (int i = 0; i < numStyleEntries; i++) {
  430. dashPattern[i] = (int) leis.readUInt();
  431. }
  432. if (penStyle.getLineDash() == HwmfLineDash.USERSTYLE) {
  433. emfPS.setLineDashes(dashPattern);
  434. }
  435. size += numStyleEntries * LittleEndianConsts.INT_SIZE;
  436. size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
  437. return size;
  438. }
  439. @Override
  440. public String toString() {
  441. // TODO: add style entries + bmp
  442. return
  443. "{ brushStyle: '"+brushStyle+"'"+
  444. ", hatchStyle: '"+hatchStyle+"'"+
  445. ", dashPattern: "+ Arrays.toString(penStyle.getLineDashes())+
  446. ", "+super.toString().substring(1);
  447. }
  448. }
  449. /**
  450. * The EMR_SETMITERLIMIT record specifies the limit for the length of miter joins for the playback
  451. * device context.
  452. */
  453. public static class EmfSetMiterLimit implements HemfRecord {
  454. protected int miterLimit;
  455. @Override
  456. public HemfRecordType getEmfRecordType() {
  457. return HemfRecordType.setMiterLimit;
  458. }
  459. @Override
  460. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  461. miterLimit = (int) leis.readUInt();
  462. return LittleEndianConsts.INT_SIZE;
  463. }
  464. @Override
  465. public void draw(HemfGraphics ctx) {
  466. ctx.getProperties().setPenMiterLimit(miterLimit);
  467. }
  468. @Override
  469. public String toString() {
  470. return "{ miterLimit: "+miterLimit+" }";
  471. }
  472. }
  473. public static class EmfSetBrushOrgEx implements HemfRecord {
  474. protected final Point2D origin = new Point2D.Double();
  475. @Override
  476. public HemfRecordType getEmfRecordType() {
  477. return HemfRecordType.setBrushOrgEx;
  478. }
  479. @Override
  480. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  481. return readPointL(leis, origin);
  482. }
  483. @Override
  484. public String toString() {
  485. return "{ x: "+origin.getX()+", y: "+origin.getY()+" }";
  486. }
  487. }
  488. public static class EmfSetWorldTransform implements HemfRecord {
  489. protected final AffineTransform xForm = new AffineTransform();
  490. @Override
  491. public HemfRecordType getEmfRecordType() {
  492. return HemfRecordType.setWorldTransform;
  493. }
  494. @Override
  495. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  496. return readXForm(leis, xForm);
  497. }
  498. @Override
  499. public void draw(HemfGraphics ctx) {
  500. ctx.updateWindowMapMode();
  501. AffineTransform tx = ctx.getTransform();
  502. tx.concatenate(xForm);
  503. ctx.setTransform(tx);
  504. }
  505. @Override
  506. public String toString() {
  507. return
  508. "{ xForm: " +
  509. "{ scaleX: "+xForm.getScaleX()+
  510. ", shearX: "+xForm.getShearX()+
  511. ", transX: "+xForm.getTranslateX()+
  512. ", scaleY: "+xForm.getScaleY()+
  513. ", shearY: "+xForm.getShearY()+
  514. ", transY: "+xForm.getTranslateY()+" } }";
  515. }
  516. }
  517. public static class EmfModifyWorldTransform implements HemfRecord {
  518. protected final AffineTransform xForm = new AffineTransform();
  519. protected HemfModifyWorldTransformMode modifyWorldTransformMode;
  520. protected HemfHeader header;
  521. @Override
  522. public HemfRecordType getEmfRecordType() {
  523. return HemfRecordType.modifyWorldTransform;
  524. }
  525. @Override
  526. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  527. // An XForm object that defines a two-dimensional linear transform in logical units.
  528. // This transform is used according to the ModifyWorldTransformMode to define a new value for
  529. // the world-space to page-space transform in the playback device context.
  530. int size = readXForm(leis, xForm);
  531. // A 32-bit unsigned integer that specifies how the transform specified in Xform is used.
  532. // This value MUST be in the ModifyWorldTransformMode enumeration
  533. modifyWorldTransformMode = HemfModifyWorldTransformMode.valueOf((int)leis.readUInt());
  534. return size + LittleEndianConsts.INT_SIZE;
  535. }
  536. @Override
  537. public void setHeader(HemfHeader header) {
  538. this.header = header;
  539. }
  540. @Override
  541. public void draw(HemfGraphics ctx) {
  542. if (modifyWorldTransformMode == null) {
  543. return;
  544. }
  545. final HemfDrawProperties prop = ctx.getProperties();
  546. final AffineTransform tx;
  547. switch (modifyWorldTransformMode) {
  548. case MWT_LEFTMULTIPLY:
  549. AffineTransform wsTrans;
  550. final Rectangle2D win = prop.getWindow();
  551. boolean noSetWindowExYet = win.getWidth() == 1 && win.getHeight() == 1;
  552. if (noSetWindowExYet) {
  553. // TODO: understand world-space transformation [MSDN-WRLDPGSPC]
  554. // experimental and horrible solved, because the world-space transformation behind it
  555. // is not understood :(
  556. // only found one example which had landscape bounds and transform of 90 degress
  557. try {
  558. wsTrans = xForm.createInverse();
  559. } catch (NoninvertibleTransformException e) {
  560. wsTrans = new AffineTransform();
  561. }
  562. Rectangle2D emfBounds = header.getBoundsRectangle();
  563. if (xForm.getShearX() == -1.0 && xForm.getShearY() == 1.0) {
  564. // rotate 90 deg
  565. wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
  566. }
  567. } else {
  568. wsTrans = adaptXForm(ctx.getTransform());
  569. }
  570. tx = ctx.getTransform();
  571. tx.concatenate(wsTrans);
  572. break;
  573. case MWT_RIGHTMULTIPLY:
  574. tx = ctx.getTransform();
  575. tx.preConcatenate(adaptXForm(tx));
  576. break;
  577. case MWT_IDENTITY:
  578. ctx.updateWindowMapMode();
  579. tx = ctx.getTransform();
  580. break;
  581. default:
  582. case MWT_SET:
  583. ctx.updateWindowMapMode();
  584. tx = ctx.getTransform();
  585. tx.concatenate(adaptXForm(tx));
  586. break;
  587. }
  588. ctx.setTransform(tx);
  589. }
  590. /**
  591. * adapt xform depending on the base transformation (... experimental ...)
  592. */
  593. private AffineTransform adaptXForm(AffineTransform other) {
  594. // normalize signed zero
  595. Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
  596. double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
  597. double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
  598. return new AffineTransform(
  599. xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
  600. yDiff * xForm.getShearY(),
  601. xDiff * xForm.getShearX(),
  602. xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
  603. xForm.getTranslateX(),
  604. xForm.getTranslateY()
  605. );
  606. }
  607. @Override
  608. public String toString() {
  609. return
  610. "{ xForm: " +
  611. "{ scaleX: "+xForm.getScaleX()+
  612. ", shearX: "+xForm.getShearX()+
  613. ", transX: "+xForm.getTranslateX()+
  614. ", scaleY: "+xForm.getScaleY()+
  615. ", shearY: "+xForm.getShearY()+
  616. ", transY: "+xForm.getTranslateY()+" }"+
  617. ", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
  618. }
  619. }
  620. public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry {
  621. /**
  622. * A 32-bit unsigned integer that specifies the index of the logical palette object
  623. * in the EMF Object Table. This index MUST be saved so that this object can be
  624. * reused or modified.
  625. */
  626. protected int penIndex;
  627. protected HwmfFill.ColorUsage colorUsage;
  628. protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
  629. @Override
  630. public HemfRecordType getEmfRecordType() {
  631. return HemfRecordType.createMonoBrush;
  632. }
  633. @Override
  634. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  635. final int startIdx = leis.getReadIndex();
  636. penIndex = (int) leis.readUInt();
  637. // A 32-bit unsigned integer that specifies how to interpret values in the color
  638. // table in the DIB header. This value MUST be in the DIBColors enumeration
  639. colorUsage = HwmfFill.ColorUsage.valueOf((int) leis.readUInt());
  640. // A 32-bit unsigned integer that specifies the offset from the start of this
  641. // record to the DIB header, if the record contains a DIB.
  642. int offBmi = (int) leis.readUInt();
  643. // A 32-bit unsigned integer that specifies the size of the DIB header, if the
  644. // record contains a DIB.
  645. int cbBmi = (int) leis.readUInt();
  646. // A 32-bit unsigned integer that specifies the offset from the start of this
  647. // record to the DIB bits, if the record contains a DIB.
  648. int offBits = (int) leis.readUInt();
  649. // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
  650. // contains a DIB.
  651. int cbBits = (int) leis.readUInt();
  652. int size = 6 * LittleEndianConsts.INT_SIZE;
  653. size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
  654. return size;
  655. }
  656. @Override
  657. public void draw(HemfGraphics ctx) {
  658. ctx.addObjectTableEntry(this, penIndex);
  659. }
  660. @Override
  661. public void applyObject(HwmfGraphics ctx) {
  662. if (!bitmap.isValid()) {
  663. return;
  664. }
  665. HwmfDrawProperties props = ctx.getProperties();
  666. props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
  667. BufferedImage bmp = bitmap.getImage();
  668. props.setBrushBitmap(bmp);
  669. }
  670. @Override
  671. public String toString() {
  672. return
  673. "{ penIndex: " + penIndex +
  674. ", colorUsage: " + colorUsage +
  675. ", bitmap: " + bitmap +
  676. "}";
  677. }
  678. }
  679. }