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 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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;
  16. import static java.nio.charset.StandardCharsets.UTF_16LE;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.EOFException;
  19. import java.io.IOException;
  20. import java.io.InputStreamReader;
  21. import java.io.Reader;
  22. import java.nio.charset.Charset;
  23. import org.apache.poi.util.IOUtils;
  24. import org.apache.poi.util.Internal;
  25. import org.apache.poi.util.LittleEndian;
  26. import org.apache.poi.util.LittleEndianInputStream;
  27. import org.apache.poi.util.RecordFormatException;
  28. /**
  29. * Container class to gather all text-related commands
  30. * This is starting out as read only, and very little is actually
  31. * implemented at this point!
  32. */
  33. @Internal
  34. public class HemfText {
  35. private static final int MAX_RECORD_LENGTH = 1_000_000;
  36. public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord {
  37. }
  38. public static class ExtTextOutA implements HemfRecord {
  39. private long left,top,right,bottom;
  40. //TODO: translate this to a graphicsmode enum
  41. private long graphicsMode;
  42. private long exScale;
  43. private long eyScale;
  44. EmrTextObject textObject;
  45. @Override
  46. public HemfRecordType getRecordType() {
  47. return HemfRecordType.exttextouta;
  48. }
  49. @Override
  50. public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
  51. //note that the first 2 uInts have been read off and the recordsize has
  52. //been decreased by 8
  53. left = leis.readInt();
  54. top = leis.readInt();
  55. right = leis.readInt();
  56. bottom = leis.readInt();
  57. graphicsMode = leis.readUInt();
  58. exScale = leis.readUInt();
  59. eyScale = leis.readUInt();
  60. int recordSizeInt = -1;
  61. if (recordSize < Integer.MAX_VALUE) {
  62. recordSizeInt = (int)recordSize;
  63. } else {
  64. throw new RecordFormatException("can't have text length > Integer.MAX_VALUE");
  65. }
  66. //guarantee to read the rest of the EMRTextObjectRecord
  67. //emrtextbytes start after 7*4 bytes read above
  68. byte[] emrTextBytes = IOUtils.safelyAllocate(recordSizeInt-(7*LittleEndian.INT_SIZE), MAX_RECORD_LENGTH);
  69. IOUtils.readFully(leis, emrTextBytes);
  70. textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8
  71. return recordSize;
  72. }
  73. protected Charset getEncodingHint() {
  74. return null;
  75. }
  76. /**
  77. *
  78. * To be implemented! We need to get the current character set
  79. * from the current font for {@link ExtTextOutA},
  80. * which has to be tracked in the playback device.
  81. *
  82. * For {@link ExtTextOutW}, the charset is "UTF-16LE"
  83. *
  84. * @param charset the charset to be used to decode the character bytes
  85. * @return text from this text element
  86. * @throws IOException
  87. */
  88. public String getText(Charset charset) throws IOException {
  89. return textObject.getText(charset);
  90. }
  91. /**
  92. *
  93. * @return the x offset for the EmrTextObject
  94. */
  95. public long getX() {
  96. return textObject.x;
  97. }
  98. /**
  99. *
  100. * @return the y offset for the EmrTextObject
  101. */
  102. public long getY() {
  103. return textObject.y;
  104. }
  105. public long getLeft() {
  106. return left;
  107. }
  108. public long getTop() {
  109. return top;
  110. }
  111. public long getRight() {
  112. return right;
  113. }
  114. public long getBottom() {
  115. return bottom;
  116. }
  117. public long getGraphicsMode() {
  118. return graphicsMode;
  119. }
  120. public long getExScale() {
  121. return exScale;
  122. }
  123. public long getEyScale() {
  124. return eyScale;
  125. }
  126. }
  127. public static class ExtTextOutW extends ExtTextOutA {
  128. @Override
  129. public HemfRecordType getRecordType() {
  130. return HemfRecordType.exttextoutw;
  131. }
  132. @Override
  133. protected Charset getEncodingHint() {
  134. return UTF_16LE;
  135. }
  136. public String getText() throws IOException {
  137. return getText(UTF_16LE);
  138. }
  139. }
  140. /**
  141. * Needs to be implemented. Couldn't find example.
  142. */
  143. public static class PolyTextOutA extends UnimplementedHemfRecord {
  144. }
  145. /**
  146. * Needs to be implemented. Couldn't find example.
  147. */
  148. public static class PolyTextOutW extends UnimplementedHemfRecord {
  149. }
  150. public static class SetTextAlign extends UnimplementedHemfRecord {
  151. }
  152. public static class SetTextColor extends UnimplementedHemfRecord {
  153. }
  154. public static class SetTextJustification extends UnimplementedHemfRecord {
  155. }
  156. private static class EmrTextObject {
  157. long x;
  158. long y;
  159. int numChars;
  160. byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record.
  161. //Because of potential variable length encodings, must
  162. //carefully read only the numChars from this byte array.
  163. EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException {
  164. int offset = 0;
  165. x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
  166. y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
  167. long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
  168. long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
  169. int start = (int)offString-offset-readSoFar;
  170. if (numCharsLong == 0) {
  171. rawTextBytes = new byte[0];
  172. numChars = 0;
  173. return;
  174. }
  175. if (numCharsLong > Integer.MAX_VALUE) {
  176. throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE");
  177. } else if (numCharsLong < 0) {
  178. throw new RecordFormatException("Number of characters can't be < 0");
  179. }
  180. numChars = (int)numCharsLong;
  181. rawTextBytes = IOUtils.safelyAllocate(emrTextObjBytes.length-start, MAX_RECORD_LENGTH);
  182. System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start);
  183. }
  184. String getText(Charset charset) throws IOException {
  185. StringBuilder sb = new StringBuilder();
  186. try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) {
  187. for (int i = 0; i < numChars; i++) {
  188. sb.appendCodePoint(readCodePoint(r));
  189. }
  190. }
  191. return sb.toString();
  192. }
  193. //TODO: move this to IOUtils?
  194. private int readCodePoint(Reader r) throws IOException {
  195. int c1 = r.read();
  196. if (c1 == -1) {
  197. throw new EOFException("Tried to read beyond byte array");
  198. }
  199. if (!Character.isHighSurrogate((char)c1)) {
  200. return c1;
  201. }
  202. int c2 = r.read();
  203. if (c2 == -1) {
  204. throw new EOFException("Tried to read beyond byte array");
  205. }
  206. if (!Character.isLowSurrogate((char)c2)) {
  207. throw new RecordFormatException("Expected low surrogate after high surrogate");
  208. }
  209. return Character.toCodePoint((char)c1, (char)c2);
  210. }
  211. }
  212. }