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.

HemfPlusBrush.java 41KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  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 org.apache.poi.hemf.record.emf.HemfFill.readXForm;
  17. import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
  18. import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
  19. import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
  20. import java.awt.Color;
  21. import java.awt.geom.AffineTransform;
  22. import java.awt.geom.Point2D;
  23. import java.awt.geom.Rectangle2D;
  24. import java.io.ByteArrayInputStream;
  25. import java.io.IOException;
  26. import java.util.AbstractMap;
  27. import java.util.Arrays;
  28. import java.util.Collections;
  29. import java.util.LinkedHashMap;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.function.Consumer;
  33. import java.util.function.Function;
  34. import java.util.function.Supplier;
  35. import java.util.stream.Collectors;
  36. import java.util.stream.IntStream;
  37. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  38. import org.apache.poi.common.usermodel.GenericRecord;
  39. import org.apache.poi.hemf.draw.HemfDrawProperties;
  40. import org.apache.poi.hemf.draw.HemfGraphics;
  41. import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
  42. import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
  43. import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
  44. import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
  45. import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
  46. import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
  47. import org.apache.poi.hwmf.record.HwmfBrushStyle;
  48. import org.apache.poi.hwmf.record.HwmfColorRef;
  49. import org.apache.poi.sl.draw.DrawPaint;
  50. import org.apache.poi.util.BitField;
  51. import org.apache.poi.util.BitFieldFactory;
  52. import org.apache.poi.util.GenericRecordJsonWriter;
  53. import org.apache.poi.util.GenericRecordUtil;
  54. import org.apache.poi.util.IOUtils;
  55. import org.apache.poi.util.LittleEndianConsts;
  56. import org.apache.poi.util.LittleEndianInputStream;
  57. public class HemfPlusBrush {
  58. /** The BrushType enumeration defines types of graphics brushes, which are used to fill graphics regions. */
  59. public enum EmfPlusBrushType {
  60. SOLID_COLOR(0X00000000, EmfPlusSolidBrushData::new),
  61. HATCH_FILL(0X00000001, EmfPlusHatchBrushData::new),
  62. TEXTURE_FILL(0X00000002, EmfPlusTextureBrushData::new),
  63. PATH_GRADIENT(0X00000003, EmfPlusPathGradientBrushData::new),
  64. LINEAR_GRADIENT(0X00000004, EmfPlusLinearGradientBrushData::new)
  65. ;
  66. public final int id;
  67. public final Supplier<? extends EmfPlusBrushData> constructor;
  68. EmfPlusBrushType(int id, Supplier<? extends EmfPlusBrushData> constructor) {
  69. this.id = id;
  70. this.constructor = constructor;
  71. }
  72. public static EmfPlusBrushType valueOf(int id) {
  73. for (EmfPlusBrushType wrt : values()) {
  74. if (wrt.id == id) return wrt;
  75. }
  76. return null;
  77. }
  78. }
  79. public enum EmfPlusHatchStyle {
  80. /** Specifies equally spaced horizontal lines. */
  81. HORIZONTAL(0X00000000),
  82. /** Specifies equally spaced vertical lines. */
  83. VERTICAL(0X00000001),
  84. /** Specifies lines on a diagonal from upper left to lower right. */
  85. FORWARD_DIAGONAL(0X00000002),
  86. /** Specifies lines on a diagonal from upper right to lower left. */
  87. BACKWARD_DIAGONAL(0X00000003),
  88. /** Specifies crossing horizontal and vertical lines. */
  89. LARGE_GRID(0X00000004),
  90. /** Specifies crossing forward diagonal and backward diagonal lines with anti-aliasing. */
  91. DIAGONAL_CROSS(0X00000005),
  92. /** Specifies a 5-percent hatch, which is the ratio of foreground color to background color equal to 5:100. */
  93. PERCENT_05(0X00000006),
  94. /** Specifies a 10-percent hatch, which is the ratio of foreground color to background color equal to 10:100. */
  95. PERCENT_10(0X00000007),
  96. /** Specifies a 20-percent hatch, which is the ratio of foreground color to background color equal to 20:100. */
  97. PERCENT_20(0X00000008),
  98. /** Specifies a 25-percent hatch, which is the ratio of foreground color to background color equal to 25:100. */
  99. PERCENT_25(0X00000009),
  100. /** Specifies a 30-percent hatch, which is the ratio of foreground color to background color equal to 30:100. */
  101. PERCENT_30(0X0000000A),
  102. /** Specifies a 40-percent hatch, which is the ratio of foreground color to background color equal to 40:100. */
  103. PERCENT_40(0X0000000B),
  104. /** Specifies a 50-percent hatch, which is the ratio of foreground color to background color equal to 50:100. */
  105. PERCENT_50(0X0000000C),
  106. /** Specifies a 60-percent hatch, which is the ratio of foreground color to background color equal to 60:100. */
  107. PERCENT_60(0X0000000D),
  108. /** Specifies a 70-percent hatch, which is the ratio of foreground color to background color equal to 70:100. */
  109. PERCENT_70(0X0000000E),
  110. /** Specifies a 75-percent hatch, which is the ratio of foreground color to background color equal to 75:100. */
  111. PERCENT_75(0X0000000F),
  112. /** Specifies an 80-percent hatch, which is the ratio of foreground color to background color equal to 80:100. */
  113. PERCENT_80(0X00000010),
  114. /** Specifies a 90-percent hatch, which is the ratio of foreground color to background color equal to 90:100. */
  115. PERCENT_90(0X00000011),
  116. /**
  117. * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
  118. * They are spaced 50 percent further apart than lines in the FORWARD_DIAGONAL pattern
  119. */
  120. LIGHT_DOWNWARD_DIAGONAL(0X00000012),
  121. /**
  122. * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
  123. * They are spaced 50 percent further apart than lines in the BACKWARD_DIAGONAL pattern.
  124. */
  125. LIGHT_UPWARD_DIAGONAL(0X00000013),
  126. /**
  127. * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
  128. * They are spaced 50 percent closer and are twice the width of lines in the FORWARD_DIAGONAL pattern.
  129. */
  130. DARK_DOWNWARD_DIAGONAL(0X00000014),
  131. /**
  132. * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
  133. * They are spaced 50 percent closer and are twice the width of lines in the BACKWARD_DIAGONAL pattern.
  134. */
  135. DARK_UPWARD_DIAGONAL(0X00000015),
  136. /**
  137. * Specifies diagonal lines that slant to the right from top to bottom points with no anti-aliasing.
  138. * They have the same spacing between lines in WIDE_DOWNWARD_DIAGONAL pattern and FORWARD_DIAGONAL pattern,
  139. * but WIDE_DOWNWARD_DIAGONAL has the triple line width of FORWARD_DIAGONAL.
  140. */
  141. WIDE_DOWNWARD_DIAGONAL(0X00000016),
  142. /**
  143. * Specifies diagonal lines that slant to the left from top to bottom points with no anti-aliasing.
  144. * They have the same spacing between lines in WIDE_UPWARD_DIAGONAL pattern and BACKWARD_DIAGONAL pattern,
  145. * but WIDE_UPWARD_DIAGONAL has the triple line width of WIDE_UPWARD_DIAGONAL.
  146. */
  147. WIDE_UPWARD_DIAGONAL(0X00000017),
  148. /** Specifies vertical lines that are spaced 50 percent closer together than lines in the VERTICAL pattern. */
  149. LIGHT_VERTICAL(0X00000018),
  150. /** Specifies horizontal lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
  151. LIGHT_HORIZONTAL(0X00000019),
  152. /**
  153. * Specifies vertical lines that are spaced 75 percent closer than lines in the VERTICAL pattern;
  154. * or 25 percent closer than lines in the LIGHT_VERTICAL pattern.
  155. */
  156. NARROW_VERTICAL(0X0000001A),
  157. /**
  158. * Specifies horizontal lines that are spaced 75 percent closer than lines in the HORIZONTAL pattern;
  159. * or 25 percent closer than lines in the LIGHT_HORIZONTAL pattern.
  160. */
  161. NARROW_HORIZONTAL(0X0000001B),
  162. /** Specifies lines that are spaced 50 percent closer than lines in the VERTICAL pattern. */
  163. DARK_VERTICAL(0X0000001C),
  164. /** Specifies lines that are spaced 50 percent closer than lines in the HORIZONTAL pattern. */
  165. DARK_HORIZONTAL(0X0000001D),
  166. /** Specifies dashed diagonal lines that slant to the right from top to bottom points. */
  167. DASHED_DOWNWARD_DIAGONAL(0X0000001E),
  168. /** Specifies dashed diagonal lines that slant to the left from top to bottom points. */
  169. DASHED_UPWARD_DIAGONAL(0X0000001F),
  170. /** Specifies dashed horizontal lines. */
  171. DASHED_HORIZONTAL(0X00000020),
  172. /** Specifies dashed vertical lines. */
  173. DASHED_VERTICAL(0X00000021),
  174. /** Specifies a pattern of lines that has the appearance of confetti. */
  175. SMALL_CONFETTI(0X00000022),
  176. /**
  177. * Specifies a pattern of lines that has the appearance of confetti, and is composed of larger pieces
  178. * than the SMALL_CONFETTI pattern.
  179. */
  180. LARGE_CONFETTI(0X00000023),
  181. /** Specifies horizontal lines that are composed of zigzags. */
  182. ZIGZAG(0X00000024),
  183. /** Specifies horizontal lines that are composed of tildes. */
  184. WAVE(0X00000025),
  185. /**
  186. * Specifies a pattern of lines that has the appearance of layered bricks that slant to the left from
  187. * top to bottom points.
  188. */
  189. DIAGONAL_BRICK(0X00000026),
  190. /** Specifies a pattern of lines that has the appearance of horizontally layered bricks. */
  191. HORIZONTAL_BRICK(0X00000027),
  192. /** Specifies a pattern of lines that has the appearance of a woven material. */
  193. WEAVE(0X00000028),
  194. /** Specifies a pattern of lines that has the appearance of a plaid material. */
  195. PLAID(0X00000029),
  196. /** Specifies a pattern of lines that has the appearance of divots. */
  197. DIVOT(0X0000002A),
  198. /** Specifies crossing horizontal and vertical lines, each of which is composed of dots. */
  199. DOTTED_GRID(0X0000002B),
  200. /** Specifies crossing forward and backward diagonal lines, each of which is composed of dots. */
  201. DOTTED_DIAMOND(0X0000002C),
  202. /**
  203. * Specifies a pattern of lines that has the appearance of diagonally layered
  204. * shingles that slant to the right from top to bottom points.
  205. */
  206. SHINGLE(0X0000002D),
  207. /** Specifies a pattern of lines that has the appearance of a trellis. */
  208. TRELLIS(0X0000002E),
  209. /** Specifies a pattern of lines that has the appearance of spheres laid adjacent to each other. */
  210. SPHERE(0X0000002F),
  211. /** Specifies crossing horizontal and vertical lines that are spaced 50 percent closer together than LARGE_GRID. */
  212. SMALL_GRID(0X00000030),
  213. /** Specifies a pattern of lines that has the appearance of a checkerboard. */
  214. SMALL_CHECKER_BOARD(0X00000031),
  215. /**
  216. * Specifies a pattern of lines that has the appearance of a checkerboard, with squares that are twice the
  217. * size of the squares in the SMALL_CHECKER_BOARD pattern.
  218. */
  219. LARGE_CHECKER_BOARD(0X00000032),
  220. /** Specifies crossing forward and backward diagonal lines; the lines are not anti-aliased. */
  221. OUTLINED_DIAMOND(0X00000033),
  222. /** Specifies a pattern of lines that has the appearance of a checkerboard placed diagonally. */
  223. SOLID_DIAMOND(0X00000034)
  224. ;
  225. public final int id;
  226. EmfPlusHatchStyle(int id) {
  227. this.id = id;
  228. }
  229. public static EmfPlusHatchStyle valueOf(int id) {
  230. for (EmfPlusHatchStyle wrt : values()) {
  231. if (wrt.id == id) return wrt;
  232. }
  233. return null;
  234. }
  235. }
  236. @SuppressWarnings("unused")
  237. public interface EmfPlusBrushData extends GenericRecord {
  238. /**
  239. * This flag is meaningful in EmfPlusPathGradientBrushData objects.
  240. *
  241. * If set, an EmfPlusBoundaryPathData object MUST be specified in the BoundaryData field of the brush data object.
  242. * If clear, an EmfPlusBoundaryPointData object MUST be specified in the BoundaryData field of the brush data object.
  243. */
  244. BitField PATH = BitFieldFactory.getInstance(0x00000001);
  245. /**
  246. * This flag is meaningful in EmfPlusLinearGradientBrushData objects , EmfPlusPathGradientBrushData objects,
  247. * and EmfPlusTextureBrushData objects.
  248. *
  249. * If set, a 2x3 world space to device space transform matrix MUST be specified in the OptionalData field of
  250. * the brush data object.
  251. */
  252. BitField TRANSFORM = BitFieldFactory.getInstance(0x00000002);
  253. /**
  254. * This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
  255. *
  256. * If set, an EmfPlusBlendColors object MUST be specified in the OptionalData field of the brush data object.
  257. */
  258. BitField PRESET_COLORS = BitFieldFactory.getInstance(0x00000004);
  259. /**
  260. * This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
  261. *
  262. * If set, an EmfPlusBlendFactors object that specifies a blend pattern along a horizontal gradient MUST be
  263. * specified in the OptionalData field of the brush data object.
  264. */
  265. BitField BLEND_FACTORS_H = BitFieldFactory.getInstance(0x00000008);
  266. /**
  267. * This flag is meaningful in EmfPlusLinearGradientBrushData objects.
  268. *
  269. * If set, an EmfPlusBlendFactors object that specifies a blend pattern along a vertical gradient MUST be
  270. * specified in the OptionalData field of the brush data object.
  271. */
  272. BitField BLEND_FACTORS_V = BitFieldFactory.getInstance(0x00000010);
  273. /**
  274. * This flag is meaningful in EmfPlusPathGradientBrushData objects.
  275. *
  276. * If set, an EmfPlusFocusScaleData object MUST be specified in the OptionalData field of the brush data object.
  277. */
  278. BitField FOCUS_SCALES = BitFieldFactory.getInstance(0x00000040);
  279. /**
  280. * This flag is meaningful in EmfPlusLinearGradientBrushData, EmfPlusPathGradientBrushData, and
  281. * EmfPlusTextureBrushData objects.
  282. *
  283. * If set, the brush MUST already be gamma corrected; that is, output brightness and intensity have been
  284. * corrected to match the input image.
  285. */
  286. BitField IS_GAMMA_CORRECTED = BitFieldFactory.getInstance(0x00000080);
  287. /**
  288. * This flag is meaningful in EmfPlusTextureBrushData objects.
  289. *
  290. * If set, a world space to device space transform SHOULD NOT be applied to the texture brush.
  291. */
  292. BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
  293. long init(LittleEndianInputStream leis, long dataSize) throws IOException;
  294. /**
  295. * Apply brush data to graphics properties
  296. * @param ctx the graphics context
  297. * @param continuedObjectData the list continued object data
  298. */
  299. void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
  300. /**
  301. * Apply brush data to pen properties
  302. * @param ctx the graphics context
  303. * @param continuedObjectData the list continued object data
  304. */
  305. void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData);
  306. }
  307. /** The EmfPlusBrush object specifies a graphics brush for filling regions. */
  308. public static class EmfPlusBrush implements EmfPlusObjectData {
  309. private static final int MAX_OBJECT_SIZE = 1_000_000;
  310. private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
  311. private EmfPlusBrushType brushType;
  312. private byte[] brushBytes;
  313. @Override
  314. public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
  315. leis.mark(LittleEndianConsts.INT_SIZE);
  316. long size = graphicsVersion.init(leis);
  317. if (isContinuedRecord()) {
  318. leis.reset();
  319. size = 0;
  320. } else {
  321. int brushInt = leis.readInt();
  322. brushType = EmfPlusBrushType.valueOf(brushInt);
  323. assert(brushType != null);
  324. size += LittleEndianConsts.INT_SIZE;
  325. }
  326. brushBytes = IOUtils.toByteArray(leis, (int)(dataSize-size), MAX_OBJECT_SIZE);
  327. return dataSize;
  328. }
  329. @Override
  330. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  331. EmfPlusBrushData brushData = getBrushData(continuedObjectData);
  332. brushData.applyObject(ctx, continuedObjectData);
  333. }
  334. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  335. EmfPlusBrushData brushData = getBrushData(continuedObjectData);
  336. brushData.applyPen(ctx, continuedObjectData);
  337. }
  338. @Override
  339. public EmfPlusGraphicsVersion getGraphicsVersion() {
  340. return graphicsVersion;
  341. }
  342. @Override
  343. public String toString() {
  344. return GenericRecordJsonWriter.marshal(this);
  345. }
  346. public byte[] getBrushBytes() {
  347. return brushBytes;
  348. }
  349. public EmfPlusBrushData getBrushData(List<? extends EmfPlusObjectData> continuedObjectData) {
  350. EmfPlusBrushData brushData = brushType.constructor.get();
  351. byte[] buf = getRawData(continuedObjectData);
  352. try {
  353. brushData.init(new LittleEndianInputStream(new ByteArrayInputStream(buf)), buf.length);
  354. } catch (IOException e) {
  355. throw new RuntimeException(e);
  356. }
  357. return brushData;
  358. }
  359. public byte[] getRawData(List<? extends EmfPlusObjectData> continuedObjectData) {
  360. try (UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream()) {
  361. bos.write(getBrushBytes());
  362. if (continuedObjectData != null) {
  363. for (EmfPlusObjectData od : continuedObjectData) {
  364. bos.write(((EmfPlusBrush)od).getBrushBytes());
  365. }
  366. }
  367. return bos.toByteArray();
  368. } catch (IOException e) {
  369. throw new RuntimeException(e);
  370. }
  371. }
  372. @Override
  373. public EmfPlusBrushType getGenericRecordType() {
  374. return brushType;
  375. }
  376. @Override
  377. public Map<String, Supplier<?>> getGenericProperties() {
  378. return GenericRecordUtil.getGenericProperties(
  379. "graphicsVersion", this::getGraphicsVersion,
  380. /* only return the first object data ... enough for now */
  381. "brushData", () -> getBrushData(null)
  382. );
  383. }
  384. }
  385. /** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
  386. public static class EmfPlusSolidBrushData implements EmfPlusBrushData {
  387. private Color solidColor;
  388. @Override
  389. public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
  390. solidColor = readARGB(leis.readInt());
  391. return LittleEndianConsts.INT_SIZE;
  392. }
  393. @Override
  394. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  395. HemfDrawProperties prop = ctx.getProperties();
  396. prop.setBrushColor(new HwmfColorRef(solidColor));
  397. prop.setBrushTransform(null);
  398. prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
  399. }
  400. @Override
  401. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  402. HemfDrawProperties prop = ctx.getProperties();
  403. prop.setPenColor(new HwmfColorRef(solidColor));
  404. }
  405. @Override
  406. public String toString() {
  407. return GenericRecordJsonWriter.marshal(this);
  408. }
  409. @Override
  410. public EmfPlusBrushType getGenericRecordType() {
  411. return EmfPlusBrushType.SOLID_COLOR;
  412. }
  413. @Override
  414. public Map<String, Supplier<?>> getGenericProperties() {
  415. return GenericRecordUtil.getGenericProperties("solidColor", () -> solidColor);
  416. }
  417. }
  418. /** The EmfPlusHatchBrushData object specifies a hatch pattern for a graphics brush. */
  419. public static class EmfPlusHatchBrushData implements EmfPlusBrushData {
  420. private EmfPlusHatchStyle style;
  421. private Color foreColor, backColor;
  422. @Override
  423. public long init(LittleEndianInputStream leis, long dataSize) {
  424. style = EmfPlusHatchStyle.valueOf(leis.readInt());
  425. foreColor = readARGB(leis.readInt());
  426. backColor = readARGB(leis.readInt());
  427. return 3L*LittleEndianConsts.INT_SIZE;
  428. }
  429. @Override
  430. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  431. HemfDrawProperties prop = ctx.getProperties();
  432. prop.setBrushColor(new HwmfColorRef(foreColor));
  433. prop.setBackgroundColor(new HwmfColorRef(backColor));
  434. prop.setEmfPlusBrushHatch(style);
  435. }
  436. @Override
  437. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  438. HemfDrawProperties prop = ctx.getProperties();
  439. prop.setPenColor(new HwmfColorRef(foreColor));
  440. }
  441. @Override
  442. public String toString() {
  443. return GenericRecordJsonWriter.marshal(this);
  444. }
  445. @Override
  446. public EmfPlusBrushType getGenericRecordType() {
  447. return EmfPlusBrushType.HATCH_FILL;
  448. }
  449. @Override
  450. public Map<String, Supplier<?>> getGenericProperties() {
  451. return GenericRecordUtil.getGenericProperties(
  452. "style", () -> style,
  453. "foreColor", () -> foreColor,
  454. "backColor", () -> backColor
  455. );
  456. }
  457. }
  458. /** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
  459. public static class EmfPlusLinearGradientBrushData implements EmfPlusBrushData {
  460. private int dataFlags;
  461. private EmfPlusWrapMode wrapMode;
  462. private final Rectangle2D rect = new Rectangle2D.Double();
  463. private Color startColor, endColor;
  464. private AffineTransform blendTransform;
  465. private float[] positions;
  466. private Color[] blendColors;
  467. private float[] positionsV;
  468. private float[] blendFactorsV;
  469. private float[] positionsH;
  470. private float[] blendFactorsH;
  471. private static final int[] FLAG_MASKS = {0x02, 0x04, 0x08, 0x10, 0x80};
  472. private static final String[] FLAG_NAMES = {"TRANSFORM", "PRESET_COLORS", "BLEND_FACTORS_H", "BLEND_FACTORS_V", "BRUSH_DATA_IS_GAMMA_CORRECTED"};
  473. @Override
  474. public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
  475. // A 32-bit unsigned integer that specifies the data in the OptionalData field.
  476. // This value MUST be composed of BrushData flags
  477. dataFlags = leis.readInt();
  478. // A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
  479. // the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
  480. // gradient is repeated.
  481. wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
  482. int size = 2 * LittleEndianConsts.INT_SIZE;
  483. size += readRectF(leis, rect);
  484. // An EmfPlusARGB object that specifies the color at the starting/ending boundary point of the linear gradient brush.
  485. startColor = readARGB(leis.readInt());
  486. endColor = readARGB(leis.readInt());
  487. // skip reserved1/2 fields
  488. leis.skipFully(2 * LittleEndianConsts.INT_SIZE);
  489. size += 4 * LittleEndianConsts.INT_SIZE;
  490. if (TRANSFORM.isSet(dataFlags)) {
  491. size += readXForm(leis, (blendTransform = new AffineTransform()));
  492. }
  493. if (isPreset() && (isBlendH() || isBlendV())) {
  494. throw new RuntimeException("invalid combination of preset colors and blend factors v/h");
  495. }
  496. size += (isPreset()) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
  497. size += (isBlendV()) ? readFactors(leis, d -> positionsV = d, f -> blendFactorsV = f) : 0;
  498. size += (isBlendH()) ? readFactors(leis, d -> positionsH = d, f -> blendFactorsH = f) : 0;
  499. return size;
  500. }
  501. @Override
  502. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  503. HemfDrawProperties prop = ctx.getProperties();
  504. prop.setBrushStyle(HwmfBrushStyle.BS_LINEAR_GRADIENT);
  505. prop.setBrushRect(rect);
  506. prop.setBrushTransform(blendTransform);
  507. // Preset colors and BlendH/V are mutual exclusive
  508. if (isPreset()) {
  509. setColorProps(prop::setBrushColorsH, positions, this::getBlendColorAt);
  510. } else {
  511. setColorProps(prop::setBrushColorsH, positionsH, this::getBlendHColorAt);
  512. }
  513. setColorProps(prop::setBrushColorsV, positionsV, this::getBlendVColorAt);
  514. if (!(isPreset() || isBlendH() || isBlendV())) {
  515. prop.setBrushColorsH(Arrays.asList(kv(0f, startColor), kv(1f, endColor)));
  516. }
  517. }
  518. @Override
  519. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  520. }
  521. @Override
  522. public String toString() {
  523. return GenericRecordJsonWriter.marshal(this);
  524. }
  525. @Override
  526. public EmfPlusBrushType getGenericRecordType() {
  527. return EmfPlusBrushType.LINEAR_GRADIENT;
  528. }
  529. @Override
  530. public Map<String, Supplier<?>> getGenericProperties() {
  531. final Map<String, Supplier<?>> m = new LinkedHashMap<>();
  532. m.put("flags", GenericRecordUtil.getBitsAsString(() -> dataFlags, FLAG_MASKS, FLAG_NAMES));
  533. m.put("wrapMode", () -> wrapMode);
  534. m.put("rect", () -> rect);
  535. m.put("startColor", () -> startColor);
  536. m.put("endColor", () -> endColor);
  537. m.put("blendTransform", () -> blendTransform);
  538. m.put("positions", () -> positions);
  539. m.put("blendColors", () -> blendColors);
  540. m.put("positionsV", () -> positionsV);
  541. m.put("blendFactorsV", () -> blendFactorsV);
  542. m.put("positionsH", () -> positionsH);
  543. m.put("blendFactorsH", () -> blendFactorsH);
  544. return Collections.unmodifiableMap(m);
  545. }
  546. private boolean isPreset() {
  547. return PRESET_COLORS.isSet(dataFlags);
  548. }
  549. private boolean isBlendH() {
  550. return BLEND_FACTORS_H.isSet(dataFlags);
  551. }
  552. private boolean isBlendV() {
  553. return BLEND_FACTORS_V.isSet(dataFlags);
  554. }
  555. private Map.Entry<Float, Color> getBlendColorAt(int index) {
  556. return kv(positions[index], blendColors[index]);
  557. }
  558. private Map.Entry<Float, Color> getBlendHColorAt(int index) {
  559. return kv(positionsH[index], interpolateColors(blendFactorsH[index]));
  560. }
  561. private Map.Entry<Float, Color> getBlendVColorAt(int index) {
  562. return kv(positionsV[index], interpolateColors(blendFactorsV[index]));
  563. }
  564. private static Map.Entry<Float, Color> kv(Float position, Color color) {
  565. return new AbstractMap.SimpleEntry<>(position, color);
  566. }
  567. private static void setColorProps(
  568. Consumer<List<? extends Map.Entry<Float, Color>>> setter, float[] positions, Function<Integer, ? extends Map.Entry<Float, Color>> sup) {
  569. if (positions == null) {
  570. setter.accept(null);
  571. } else {
  572. setter.accept(IntStream.range(0, positions.length).boxed().map(sup).collect(Collectors.toList()));
  573. }
  574. }
  575. private Color interpolateColors(final double factor) {
  576. return interpolateColorsRGB(factor);
  577. }
  578. private Color interpolateColorsRGB(final double factor) {
  579. // TODO: check IS_GAMMA_CORRECTED flag and maybe don't convert into scRGB
  580. double[] start = DrawPaint.RGB2SCRGB(startColor);
  581. double[] end = DrawPaint.RGB2SCRGB(endColor);
  582. // compute the interpolated color in linear space
  583. int a = (int)Math.round(startColor.getAlpha() + factor * (endColor.getAlpha() - startColor.getAlpha()));
  584. double r = start[0] + factor * (end[0] - start[0]);
  585. double g = start[1] + factor * (end[1] - start[1]);
  586. double b = start[2] + factor * (end[2] - start[2]);
  587. Color inter = DrawPaint.SCRGB2RGB(r,g,b);
  588. return new Color(inter.getRed(), inter.getGreen(), inter.getBlue(), a);
  589. }
  590. }
  591. /** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
  592. public static class EmfPlusPathGradientBrushData implements EmfPlusBrushData {
  593. private int dataFlags;
  594. private EmfPlusWrapMode wrapMode;
  595. private Color centerColor;
  596. private final Point2D centerPoint = new Point2D.Double();
  597. private Color[] surroundingColor;
  598. private EmfPlusPath boundaryPath;
  599. private Point2D[] boundaryPoints;
  600. private AffineTransform blendTransform;
  601. private float[] positions;
  602. private Color[] blendColors;
  603. private float[] blendFactorsH;
  604. private Double focusScaleX, focusScaleY;
  605. @Override
  606. public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
  607. // A 32-bit unsigned integer that specifies the data in the OptionalData field.
  608. // This value MUST be composed of BrushData flags
  609. dataFlags = leis.readInt();
  610. // A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
  611. // the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
  612. // gradient is repeated.
  613. wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
  614. // An EmfPlusARGB object that specifies the center color of the path gradient brush, which is the color
  615. // that appears at the center point of the brush. The color of the brush changes gradually from the
  616. // boundary color to the center color as it moves from the boundary to the center point.
  617. centerColor = readARGB(leis.readInt());
  618. int size = 3*LittleEndianConsts.INT_SIZE;
  619. if (wrapMode == null) {
  620. return size;
  621. }
  622. size += readPointF(leis, centerPoint);
  623. // An unsigned 32-bit integer that specifies the number of colors specified in the SurroundingColor field.
  624. // The surrounding colors are colors specified for discrete points on the boundary of the brush.
  625. final int colorCount = leis.readInt();
  626. // An array of SurroundingColorCount EmfPlusARGB objects that specify the colors for discrete points on the
  627. // boundary of the brush.
  628. surroundingColor = new Color[colorCount];
  629. for (int i = 0; i < colorCount; i++) {
  630. surroundingColor[i] = readARGB(leis.readInt());
  631. }
  632. size += (colorCount + 1) * LittleEndianConsts.INT_SIZE;
  633. // The boundary of the path gradient brush, which is specified by either a path or a closed cardinal spline.
  634. // If the BrushDataPath flag is set in the BrushDataFlags field, this field MUST contain an
  635. // EmfPlusBoundaryPathData object; otherwise, this field MUST contain an EmfPlusBoundaryPointData object.
  636. if (PATH.isSet(dataFlags)) {
  637. // A 32-bit signed integer that specifies the size in bytes of the BoundaryPathData field.
  638. int pathDataSize = leis.readInt();
  639. size += LittleEndianConsts.INT_SIZE;
  640. // An EmfPlusPath object that specifies the boundary of the brush.
  641. size += (boundaryPath = new EmfPlusPath()).init(leis, pathDataSize, EmfPlusObjectType.PATH, 0);
  642. } else {
  643. // A 32-bit signed integer that specifies the number of points in the BoundaryPointData field.
  644. int pointCount = leis.readInt();
  645. size += LittleEndianConsts.INT_SIZE;
  646. // An array of BoundaryPointCount EmfPlusPointF objects that specify the boundary of the brush.
  647. boundaryPoints = new Point2D[pointCount];
  648. for (int i=0; i<pointCount; i++) {
  649. size += readPointF(leis, boundaryPoints[i] = new Point2D.Double());
  650. }
  651. }
  652. // An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
  653. // the path gradient brush. This field MUST be present if the BrushDataTransform flag is set in the
  654. // BrushDataFlags field of the EmfPlusPathGradientBrushData object.
  655. if (TRANSFORM.isSet(dataFlags)) {
  656. size += readXForm(leis, (blendTransform = new AffineTransform()));
  657. }
  658. // An optional blend pattern for the path gradient brush. If this field is present, it MUST contain either
  659. // an EmfPlusBlendColors object, or an EmfPlusBlendFactors object, but it MUST NOT contain both.
  660. final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
  661. final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
  662. if (isPreset && blendH) {
  663. throw new RuntimeException("invalid combination of preset colors and blend factors h");
  664. }
  665. size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
  666. size += (blendH) ? readFactors(leis, d -> positions = d, f -> blendFactorsH = f) : 0;
  667. // An optional EmfPlusFocusScaleData object that specifies focus scales for the path gradient brush.
  668. // This field MUST be present if the BrushDataFocusScales flag is set in the BrushDataFlags field of the
  669. // EmfPlusPathGradientBrushData object.
  670. if (FOCUS_SCALES.isSet(dataFlags)) {
  671. // A 32-bit unsigned integer that specifies the number of focus scales. This value MUST be 2.
  672. int focusScaleCount = leis.readInt();
  673. if (focusScaleCount != 2) {
  674. throw new RuntimeException("invalid focus scale count");
  675. }
  676. // A floating-point value that defines the horizontal/vertical focus scale.
  677. // The focus scale MUST be a value between 0.0 and 1.0, exclusive.
  678. focusScaleX = (double)leis.readFloat();
  679. focusScaleY = (double)leis.readFloat();
  680. size += 3*LittleEndianConsts.INT_SIZE;
  681. }
  682. return size;
  683. }
  684. @Override
  685. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  686. }
  687. @Override
  688. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  689. }
  690. @Override
  691. public String toString() {
  692. return GenericRecordJsonWriter.marshal(this);
  693. }
  694. @Override
  695. public EmfPlusBrushType getGenericRecordType() {
  696. return EmfPlusBrushType.PATH_GRADIENT;
  697. }
  698. @Override
  699. public Map<String, Supplier<?>> getGenericProperties() {
  700. final Map<String,Supplier<?>> m = new LinkedHashMap<>();
  701. m.put("flags", () -> dataFlags);
  702. m.put("wrapMode", () -> wrapMode);
  703. m.put("centerColor", () -> centerColor);
  704. m.put("centerPoint", () -> centerPoint);
  705. m.put("surroundingColor", () -> surroundingColor);
  706. m.put("boundaryPath", () -> boundaryPath);
  707. m.put("boundaryPoints", () -> boundaryPoints);
  708. m.put("blendTransform", () -> blendTransform);
  709. m.put("positions", () -> positions);
  710. m.put("blendColors", () -> blendColors);
  711. m.put("blendFactorsH", () -> blendFactorsH);
  712. m.put("focusScaleX", () -> focusScaleX);
  713. m.put("focusScaleY", () -> focusScaleY);
  714. return Collections.unmodifiableMap(m);
  715. }
  716. }
  717. /** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
  718. public static class EmfPlusTextureBrushData implements EmfPlusBrushData {
  719. private int dataFlags;
  720. private EmfPlusWrapMode wrapMode;
  721. private AffineTransform brushTransform;
  722. private EmfPlusImage image;
  723. @Override
  724. public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
  725. // A 32-bit unsigned integer that specifies the data in the OptionalData field.
  726. // This value MUST be composed of BrushData flags.
  727. dataFlags = leis.readInt();
  728. // A 32-bit signed integer from the WrapMode enumeration that specifies how to repeat the texture image
  729. // across a shape, when the image is smaller than the area being filled.
  730. wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
  731. int size = 2*LittleEndianConsts.INT_SIZE;
  732. if (TRANSFORM.isSet(dataFlags)) {
  733. size += readXForm(leis, (brushTransform = new AffineTransform()));
  734. }
  735. if (dataSize > size) {
  736. size += (image = new EmfPlusImage()).init(leis, dataSize-size, EmfPlusObjectType.IMAGE, 0);
  737. }
  738. return size;
  739. }
  740. @Override
  741. public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  742. HemfDrawProperties prop = ctx.getProperties();
  743. image.applyObject(ctx, null);
  744. prop.setBrushBitmap(prop.getEmfPlusImage());
  745. prop.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
  746. prop.setBrushTransform(brushTransform);
  747. }
  748. @Override
  749. public void applyPen(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
  750. }
  751. @Override
  752. public String toString() {
  753. return GenericRecordJsonWriter.marshal(this);
  754. }
  755. @Override
  756. public EmfPlusBrushType getGenericRecordType() {
  757. return EmfPlusBrushType.TEXTURE_FILL;
  758. }
  759. @Override
  760. public Map<String, Supplier<?>> getGenericProperties() {
  761. return GenericRecordUtil.getGenericProperties(
  762. "dataFlags", () -> dataFlags,
  763. "wrapMode", () -> wrapMode,
  764. "brushTransform", () -> brushTransform,
  765. "image", () -> image
  766. );
  767. }
  768. }
  769. private static int readPositions(LittleEndianInputStream leis, Consumer<float[]> pos) {
  770. final int count = leis.readInt();
  771. int size = LittleEndianConsts.INT_SIZE;
  772. float[] positions = new float[count];
  773. for (int i=0; i<count; i++) {
  774. positions[i] = leis.readFloat();
  775. size += LittleEndianConsts.INT_SIZE;
  776. }
  777. pos.accept(positions);
  778. return size;
  779. }
  780. private static int readColors(LittleEndianInputStream leis, Consumer<float[]> pos, Consumer<Color[]> cols) {
  781. int[] count = { 0 };
  782. int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
  783. Color[] colors = new Color[count[0]];
  784. for (int i=0; i<colors.length; i++) {
  785. colors[i] = readARGB(leis.readInt());
  786. }
  787. cols.accept(colors);
  788. return size + colors.length * LittleEndianConsts.INT_SIZE;
  789. }
  790. private static int readFactors(LittleEndianInputStream leis, Consumer<float[]> pos, Consumer<float[]> facs) {
  791. int[] count = { 0 };
  792. int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
  793. float[] factors = new float[count[0]];
  794. for (int i=0; i<factors.length; i++) {
  795. factors[i] = leis.readFloat();
  796. }
  797. facs.accept(factors);
  798. return size + factors.length * LittleEndianConsts.INT_SIZE;
  799. }
  800. }