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.

HemfText.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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 java.nio.charset.StandardCharsets.UTF_16LE;
  17. import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
  18. import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
  19. import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
  20. import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
  21. import java.awt.geom.Dimension2D;
  22. import java.awt.geom.Rectangle2D;
  23. import java.io.IOException;
  24. import java.nio.charset.Charset;
  25. import java.util.Map;
  26. import java.util.function.Supplier;
  27. import org.apache.poi.hemf.draw.HemfGraphics;
  28. import org.apache.poi.hwmf.draw.HwmfGraphics;
  29. import org.apache.poi.hwmf.record.HwmfText;
  30. import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign;
  31. import org.apache.poi.util.Dimension2DDouble;
  32. import org.apache.poi.util.GenericRecordJsonWriter;
  33. import org.apache.poi.util.GenericRecordUtil;
  34. import org.apache.poi.util.IOUtils;
  35. import org.apache.poi.util.Internal;
  36. import org.apache.poi.util.LittleEndianConsts;
  37. import org.apache.poi.util.LittleEndianInputStream;
  38. import org.apache.poi.util.RecordFormatException;
  39. /**
  40. * Container class to gather all text-related commands
  41. * This is starting out as read only, and very little is actually
  42. * implemented at this point!
  43. */
  44. @Internal
  45. @SuppressWarnings("WeakerAccess")
  46. public class HemfText {
  47. private static final int MAX_RECORD_LENGTH = 1_000_000;
  48. public enum EmfGraphicsMode {
  49. GM_COMPATIBLE, GM_ADVANCED
  50. }
  51. public static class EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord {
  52. protected Rectangle2D boundsIgnored = new Rectangle2D.Double();
  53. protected EmfGraphicsMode graphicsMode;
  54. /**
  55. * The scale factor to apply along the X/Y axis to convert from page space units to .01mm units.
  56. * This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
  57. */
  58. protected final Dimension2D scale = new Dimension2DDouble();
  59. public EmfExtTextOutA() {
  60. super(new EmfExtTextOutOptions());
  61. }
  62. @Override
  63. public HemfRecordType getEmfRecordType() {
  64. return HemfRecordType.extTextOutA;
  65. }
  66. @Override
  67. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  68. if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) {
  69. throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)");
  70. }
  71. // A WMF RectL object. It is not used and MUST be ignored on receipt.
  72. long size = readRectL(leis, boundsIgnored);
  73. // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration
  74. graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1];
  75. size += LittleEndianConsts.INT_SIZE;
  76. size += readDimensionFloat(leis, scale);
  77. // A WMF PointL object that specifies the coordinates of the reference point used to position the string.
  78. // The reference point is defined by the last EMR_SETTEXTALIGN record.
  79. // If no such record has been set, the default alignment is TA_LEFT,TA_TOP.
  80. size += readPointL(leis, reference);
  81. // A 32-bit unsigned integer that specifies the number of characters in the string.
  82. stringLength = (int)leis.readUInt();
  83. // A 32-bit unsigned integer that specifies the offset to the output string, in bytes,
  84. // from the start of the record in which this object is contained.
  85. // This value MUST be 8- or 16-bit aligned, according to the character format.
  86. int offString = (int)leis.readUInt();
  87. size += 2*LittleEndianConsts.INT_SIZE;
  88. size += options.init(leis);
  89. // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units.
  90. // This rectangle is applied to the text output performed by the containing record.
  91. if (options.isClipped() || options.isOpaque()) {
  92. size += readRectL(leis, bounds);
  93. }
  94. // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes,
  95. // from the start of the record in which this object is contained. This value MUST be 32-bit aligned.
  96. int offDx = (int)leis.readUInt();
  97. size += LittleEndianConsts.INT_SIZE;
  98. // handle dx before string and other way round
  99. final String order = (offDx < offString) ? "ds" : "sd";
  100. // the next byte index after the string ends
  101. int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE);
  102. for (char op : order.toCharArray()) {
  103. switch (op) {
  104. case 'd': {
  105. dx.clear();
  106. int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
  107. if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) {
  108. leis.skipFully(undefinedSpace2);
  109. size += undefinedSpace2;
  110. // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent
  111. // character cells in logical units. The location of this field is specified by the value of offDx
  112. // in bytes from the start of this record. If spacing is defined, this field contains the same number
  113. // of values as characters in the output string.
  114. //
  115. // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer
  116. // contains twice as many values as there are characters in the output string, one
  117. // horizontal and one vertical offset for each, in that order.
  118. //
  119. // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right.
  120. // No other options affect the interpretation of this field.
  121. final int maxSize = (int)Math.min((offDx < offString) ? (offString-HEADER_SIZE) : recordSize, recordSize);
  122. while (size <= maxSize-LittleEndianConsts.INT_SIZE) {
  123. dx.add((int) leis.readUInt());
  124. size += LittleEndianConsts.INT_SIZE;
  125. }
  126. }
  127. if (dx.size() < stringLength) {
  128. // invalid dx array
  129. dx.clear();
  130. }
  131. strEnd = (int)recordSize;
  132. break;
  133. }
  134. default:
  135. case 's': {
  136. int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
  137. if (offString > 0 && undefinedSpace1 >= 0 && offString-HEADER_SIZE < recordSize) {
  138. leis.skipFully(undefinedSpace1);
  139. size += undefinedSpace1;
  140. // read all available bytes and not just "stringLength * 1(ansi)/2(unicode)"
  141. // in case we need to deal with surrogate pairs
  142. final int maxSize = (int)(Math.min(recordSize, strEnd)-size);
  143. rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH);
  144. leis.readFully(rawTextBytes);
  145. size += maxSize;
  146. break;
  147. }
  148. }
  149. }
  150. }
  151. return size;
  152. }
  153. /**
  154. *
  155. * To be implemented! We need to get the current character set
  156. * from the current font for {@link EmfExtTextOutA},
  157. * which has to be tracked in the playback device.
  158. *
  159. * For {@link EmfExtTextOutW}, the charset is "UTF-16LE"
  160. *
  161. * @param charset the charset to be used to decode the character bytes
  162. * @return text from this text element
  163. * @throws IOException if the charset is not compatible to the underlying bytes
  164. */
  165. public String getText(Charset charset) throws IOException {
  166. return super.getText(charset);
  167. }
  168. public EmfGraphicsMode getGraphicsMode() {
  169. return graphicsMode;
  170. }
  171. public Dimension2D getScale() {
  172. return scale;
  173. }
  174. @Override
  175. public void draw(HwmfGraphics ctx) {
  176. // A 32-bit floating-point value that specifies the scale factor to apply along
  177. // the axis to convert from page space units to .01mm units.
  178. // This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
  179. Dimension2D scl = graphicsMode == EmfGraphicsMode.GM_COMPATIBLE ? scale : null;
  180. ctx.setCharsetProvider(charsetProvider);
  181. ctx.drawString(rawTextBytes, stringLength, reference, scl, bounds, options, dx, isUnicode());
  182. }
  183. @Override
  184. public String toString() {
  185. return GenericRecordJsonWriter.marshal(this);
  186. }
  187. @Override
  188. public Map<String, Supplier<?>> getGenericProperties() {
  189. return GenericRecordUtil.getGenericProperties(
  190. "base", super::getGenericProperties,
  191. "boundsIgnored", () -> boundsIgnored,
  192. "graphicsMode", this::getGraphicsMode,
  193. "scale", this::getScale
  194. );
  195. }
  196. @Override
  197. public HemfRecordType getGenericRecordType() {
  198. return getEmfRecordType();
  199. }
  200. }
  201. public static class EmfExtTextOutW extends EmfExtTextOutA {
  202. @Override
  203. public HemfRecordType getEmfRecordType() {
  204. return HemfRecordType.extTextOutW;
  205. }
  206. public String getText() throws IOException {
  207. return getText(UTF_16LE);
  208. }
  209. protected boolean isUnicode() {
  210. return true;
  211. }
  212. }
  213. /**
  214. * The EMR_SETTEXTALIGN record specifies text alignment.
  215. */
  216. public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord {
  217. @Override
  218. public HemfRecordType getEmfRecordType() {
  219. return HemfRecordType.setTextAlign;
  220. }
  221. @Override
  222. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  223. // A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags.
  224. // These are either WMF TextAlignmentMode Flags for text with a horizontal baseline,
  225. // or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline.
  226. // Only one value can be chosen from those that affect horizontal and vertical alignment.
  227. textAlignmentMode = (int)leis.readUInt();
  228. return LittleEndianConsts.INT_SIZE;
  229. }
  230. @Override
  231. public HemfRecordType getGenericRecordType() {
  232. return getEmfRecordType();
  233. }
  234. }
  235. /**
  236. * The EMR_SETTEXTCOLOR record defines the current text color.
  237. */
  238. public static class EmfSetTextColor extends HwmfText.WmfSetTextColor implements HemfRecord {
  239. @Override
  240. public HemfRecordType getEmfRecordType() {
  241. return HemfRecordType.setTextColor;
  242. }
  243. @Override
  244. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  245. return colorRef.init(leis);
  246. }
  247. @Override
  248. public HemfRecordType getGenericRecordType() {
  249. return getEmfRecordType();
  250. }
  251. }
  252. public static class EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
  253. implements HemfRecord {
  254. int fontIdx;
  255. public EmfExtCreateFontIndirectW() {
  256. super(new HemfFont());
  257. }
  258. @Override
  259. public HemfRecordType getEmfRecordType() {
  260. return HemfRecordType.extCreateFontIndirectW;
  261. }
  262. @Override
  263. public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
  264. // A 32-bit unsigned integer that specifies the index of the logical font object
  265. // in the EMF Object Table
  266. fontIdx = (int)leis.readUInt();
  267. long size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE));
  268. return size+LittleEndianConsts.INT_SIZE;
  269. }
  270. @Override
  271. public void draw(HemfGraphics ctx) {
  272. ctx.addObjectTableEntry(this, fontIdx);
  273. }
  274. @Override
  275. public String toString() {
  276. return GenericRecordJsonWriter.marshal(this);
  277. }
  278. public int getFontIdx() {
  279. return fontIdx;
  280. }
  281. @Override
  282. public Map<String, Supplier<?>> getGenericProperties() {
  283. return GenericRecordUtil.getGenericProperties(
  284. "base", super::getGenericProperties,
  285. "fontIdx", this::getFontIdx
  286. );
  287. }
  288. @Override
  289. public HemfRecordType getGenericRecordType() {
  290. return getEmfRecordType();
  291. }
  292. }
  293. public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions {
  294. @Override
  295. public int init(LittleEndianInputStream leis) {
  296. // A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field.
  297. // This field can be a combination of more than one ExtTextOutOptions enumeration
  298. flags = (int)leis.readUInt();
  299. return LittleEndianConsts.INT_SIZE;
  300. }
  301. }
  302. public static class SetTextJustification extends UnimplementedHemfRecord {
  303. }
  304. /**
  305. * Needs to be implemented. Couldn't find example.
  306. */
  307. public static class PolyTextOutA extends UnimplementedHemfRecord {
  308. }
  309. /**
  310. * Needs to be implemented. Couldn't find example.
  311. */
  312. public static class PolyTextOutW extends UnimplementedHemfRecord {
  313. }
  314. }