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.

TextPropCollection.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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.hslf.model.textproperties;
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.IOException;
  18. import java.io.OutputStream;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import org.apache.poi.hslf.exceptions.HSLFException;
  24. import org.apache.poi.hslf.record.StyleTextPropAtom;
  25. import org.apache.poi.util.HexDump;
  26. import org.apache.poi.util.LittleEndian;
  27. /**
  28. * For a given run of characters, holds the properties (which could
  29. * be paragraph properties or character properties).
  30. * Used to hold the number of characters affected, the list of active
  31. * properties, and the indent level if required.
  32. */
  33. public class TextPropCollection {
  34. /** All the different kinds of paragraph properties we might handle */
  35. private static final TextProp[] paragraphTextPropTypes = {
  36. // TextProp order is according to 2.9.20 TextPFException,
  37. // bitmask order can be different
  38. new ParagraphFlagsTextProp(),
  39. new TextProp(2, 0x80, "bullet.char"),
  40. new TextProp(2, 0x10, "bullet.font"),
  41. new TextProp(2, 0x40, "bullet.size"),
  42. new TextProp(4, 0x20, "bullet.color"),
  43. new TextAlignmentProp(),
  44. new TextProp(2, 0x1000, "linespacing"),
  45. new TextProp(2, 0x2000, "spacebefore"),
  46. new TextProp(2, 0x4000, "spaceafter"),
  47. new TextProp(2, 0x100, "text.offset"), // left margin
  48. // 0x200 - Undefined and MUST be ignored
  49. new TextProp(2, 0x400, "bullet.offset"), // indent
  50. new TextProp(2, 0x8000, "defaultTabSize"),
  51. new TabStopPropCollection(), // tabstops size is variable!
  52. new FontAlignmentProp(),
  53. new WrapFlagsTextProp(),
  54. new TextProp(2, 0x200000, "textDirection"),
  55. // 0x400000 MUST be zero and MUST be ignored
  56. new TextProp(0, 0x800000, "bullet.blip"), // TODO: check size
  57. new TextProp(0, 0x1000000, "bullet.scheme"), // TODO: check size
  58. new TextProp(0, 0x2000000, "hasBulletScheme"), // TODO: check size
  59. // 0xFC000000 MUST be zero and MUST be ignored
  60. };
  61. /** All the different kinds of character properties we might handle */
  62. private static final TextProp[] characterTextPropTypes = new TextProp[] {
  63. new TextProp(0, 0x100000, "pp10ext"),
  64. new TextProp(0, 0x1000000, "newAsian.font.index"), // A bit that specifies whether the newEAFontRef field of the TextCFException10 structure that contains this CFMasks exists.
  65. new TextProp(0, 0x2000000, "cs.font.index"), // A bit that specifies whether the csFontRef field of the TextCFException10 structure that contains this CFMasks exists.
  66. new TextProp(0, 0x4000000, "pp11ext"), // A bit that specifies whether the pp11ext field of the TextCFException10 structure that contains this CFMasks exists.
  67. new CharFlagsTextProp(),
  68. new TextProp(2, 0x10000, "font.index"),
  69. new TextProp(2, 0x200000, "asian.font.index"),
  70. new TextProp(2, 0x400000, "ansi.font.index"),
  71. new TextProp(2, 0x800000, "symbol.font.index"),
  72. new TextProp(2, 0x20000, "font.size"),
  73. new TextProp(4, 0x40000, "font.color"),
  74. new TextProp(2, 0x80000, "superscript")
  75. };
  76. public enum TextPropType {
  77. paragraph, character;
  78. }
  79. private int charactersCovered;
  80. // indentLevel is only valid for paragraph collection
  81. // if it's set to -1, it must be omitted - see 2.9.36 TextMasterStyleLevel
  82. private short indentLevel = 0;
  83. private final Map<String,TextProp> textProps = new HashMap<String,TextProp>();
  84. private int maskSpecial = 0;
  85. private final TextPropType textPropType;
  86. /**
  87. * Create a new collection of text properties (be they paragraph
  88. * or character) which will be groked via a subsequent call to
  89. * buildTextPropList().
  90. */
  91. public TextPropCollection(int charactersCovered, TextPropType textPropType) {
  92. this.charactersCovered = charactersCovered;
  93. this.textPropType = textPropType;
  94. }
  95. public int getSpecialMask() {
  96. return maskSpecial;
  97. }
  98. /** Fetch the number of characters this styling applies to */
  99. public int getCharactersCovered() {
  100. return charactersCovered;
  101. }
  102. /** Fetch the TextProps that define this styling in the record order */
  103. public List<TextProp> getTextPropList() {
  104. List<TextProp> orderedList = new ArrayList<TextProp>();
  105. for (TextProp potProp : getPotentialProperties()) {
  106. TextProp textProp = textProps.get(potProp.getName());
  107. if (textProp != null) {
  108. orderedList.add(textProp);
  109. }
  110. }
  111. return orderedList;
  112. }
  113. /** Fetch the TextProp with this name, or null if it isn't present */
  114. public final TextProp findByName(String textPropName) {
  115. return textProps.get(textPropName);
  116. }
  117. public final TextProp removeByName(String name) {
  118. return textProps.remove(name);
  119. }
  120. public final TextPropType getTextPropType() {
  121. return textPropType;
  122. }
  123. private TextProp[] getPotentialProperties() {
  124. return (textPropType == TextPropType.paragraph) ? paragraphTextPropTypes : characterTextPropTypes;
  125. }
  126. /**
  127. * Checks the paragraph or character properties for the given property name.
  128. * Throws a HSLFException, if the name doesn't belong into this set of properties
  129. *
  130. * @param name the property name
  131. * @return if found, the property template to copy from
  132. */
  133. private TextProp validatePropName(String name) {
  134. for (TextProp tp : getPotentialProperties()) {
  135. if (tp.getName().equals(name)) {
  136. return tp;
  137. }
  138. }
  139. String errStr =
  140. "No TextProp with name " + name + " is defined to add from. " +
  141. "Character and paragraphs have their own properties/names.";
  142. throw new HSLFException(errStr);
  143. }
  144. /** Add the TextProp with this name to the list */
  145. public final TextProp addWithName(String name) {
  146. // Find the base TextProp to base on
  147. TextProp existing = findByName(name);
  148. if (existing != null) return existing;
  149. // Add a copy of this property
  150. TextProp textProp = validatePropName(name).clone();
  151. textProps.put(name,textProp);
  152. return textProp;
  153. }
  154. /**
  155. * Add the property at the correct position. Replaces an existing property with the same name.
  156. *
  157. * @param textProp the property to be added
  158. */
  159. public final void addProp(TextProp textProp) {
  160. if (textProp == null) {
  161. throw new HSLFException("TextProp must not be null");
  162. }
  163. String propName = textProp.getName();
  164. validatePropName(propName);
  165. textProps.put(propName, textProp);
  166. }
  167. /**
  168. * For an existing set of text properties, build the list of
  169. * properties coded for in a given run of properties.
  170. * @return the number of bytes that were used encoding the properties list
  171. */
  172. public int buildTextPropList(int containsField, byte[] data, int dataOffset) {
  173. int bytesPassed = 0;
  174. // For each possible entry, see if we match the mask
  175. // If we do, decode that, save it, and shuffle on
  176. for(TextProp tp : getPotentialProperties()) {
  177. // Check there's still data left to read
  178. // Check if this property is found in the mask
  179. if((containsField & tp.getMask()) != 0) {
  180. if(dataOffset+bytesPassed >= data.length) {
  181. // Out of data, can't be any more properties to go
  182. // remember the mask and return
  183. maskSpecial |= tp.getMask();
  184. return bytesPassed;
  185. }
  186. // Bingo, data contains this property
  187. TextProp prop = tp.clone();
  188. int val = 0;
  189. if (prop.getSize() == 2) {
  190. val = LittleEndian.getShort(data,dataOffset+bytesPassed);
  191. } else if(prop.getSize() == 4) {
  192. val = LittleEndian.getInt(data,dataOffset+bytesPassed);
  193. } else if (prop.getSize() == 0 && !(prop instanceof TabStopPropCollection)) {
  194. //remember "special" bits.
  195. maskSpecial |= tp.getMask();
  196. continue;
  197. }
  198. if (prop instanceof BitMaskTextProp) {
  199. ((BitMaskTextProp)prop).setValueWithMask(val, containsField);
  200. } else if (prop instanceof TabStopPropCollection) {
  201. ((TabStopPropCollection)prop).parseProperty(data, dataOffset+bytesPassed);
  202. } else {
  203. prop.setValue(val);
  204. }
  205. bytesPassed += prop.getSize();
  206. addProp(prop);
  207. }
  208. }
  209. // Return how many bytes were used
  210. return bytesPassed;
  211. }
  212. /**
  213. * Clones the given text properties
  214. */
  215. public void copy(TextPropCollection other) {
  216. if (other == null) {
  217. throw new HSLFException("trying to copy null TextPropCollection");
  218. }
  219. if (this == other) return;
  220. this.charactersCovered = other.charactersCovered;
  221. this.indentLevel = other.indentLevel;
  222. this.maskSpecial = other.maskSpecial;
  223. this.textProps.clear();
  224. for (TextProp tp : other.textProps.values()) {
  225. TextProp tpCopy = (tp instanceof BitMaskTextProp)
  226. ? ((BitMaskTextProp)tp).cloneAll()
  227. : tp.clone();
  228. addProp(tpCopy);
  229. }
  230. }
  231. /**
  232. * Update the size of the text that this set of properties
  233. * applies to
  234. */
  235. public void updateTextSize(int textSize) {
  236. charactersCovered = textSize;
  237. }
  238. /**
  239. * Writes out to disk the header, and then all the properties
  240. */
  241. public void writeOut(OutputStream o) throws IOException {
  242. writeOut(o, false);
  243. }
  244. /**
  245. * Writes out to disk the header, and then all the properties
  246. */
  247. public void writeOut(OutputStream o, boolean isMasterStyle) throws IOException {
  248. if (!isMasterStyle) {
  249. // First goes the number of characters we affect
  250. // MasterStyles don't have this field
  251. StyleTextPropAtom.writeLittleEndian(charactersCovered,o);
  252. }
  253. // Then we have the indentLevel field if it's a paragraph collection
  254. if (textPropType == TextPropType.paragraph && indentLevel > -1) {
  255. StyleTextPropAtom.writeLittleEndian(indentLevel, o);
  256. }
  257. // Then the mask field
  258. int mask = maskSpecial;
  259. for (TextProp textProp : textProps.values()) {
  260. mask |= textProp.getWriteMask();
  261. }
  262. StyleTextPropAtom.writeLittleEndian(mask,o);
  263. // Then the contents of all the properties
  264. for (TextProp textProp : getTextPropList()) {
  265. int val = textProp.getValue();
  266. if (textProp instanceof BitMaskTextProp && textProp.getWriteMask() == 0) {
  267. // don't add empty properties, as they can't be recognized while reading
  268. continue;
  269. } else if (textProp.getSize() == 2) {
  270. StyleTextPropAtom.writeLittleEndian((short)val,o);
  271. } else if (textProp.getSize() == 4) {
  272. StyleTextPropAtom.writeLittleEndian(val,o);
  273. }
  274. }
  275. }
  276. public short getIndentLevel(){
  277. return indentLevel;
  278. }
  279. public void setIndentLevel(short indentLevel) {
  280. if (textPropType == TextPropType.character) {
  281. throw new RuntimeException("trying to set an indent on a character collection.");
  282. }
  283. this.indentLevel = indentLevel;
  284. }
  285. public int hashCode() {
  286. final int prime = 31;
  287. int result = 1;
  288. result = prime * result + charactersCovered;
  289. result = prime * result + maskSpecial;
  290. result = prime * result + indentLevel;
  291. result = prime * result + ((textProps == null) ? 0 : textProps.hashCode());
  292. return result;
  293. }
  294. /**
  295. * compares most properties apart of the covered characters length
  296. */
  297. public boolean equals(Object other) {
  298. if (this == other) return true;
  299. if (other == null) return false;
  300. if (getClass() != other.getClass()) return false;
  301. TextPropCollection o = (TextPropCollection)other;
  302. if (o.maskSpecial != this.maskSpecial || o.indentLevel != this.indentLevel) {
  303. return false;
  304. }
  305. return textProps.equals(o.textProps);
  306. }
  307. public String toString() {
  308. StringBuilder out = new StringBuilder();
  309. out.append(" chars covered: " + getCharactersCovered());
  310. out.append(" special mask flags: 0x" + HexDump.toHex(getSpecialMask()) + "\n");
  311. if (textPropType == TextPropType.paragraph) {
  312. out.append(" indent level: "+getIndentLevel()+"\n");
  313. }
  314. for(TextProp p : getTextPropList()) {
  315. out.append(" " + p.getName() + " = " + p.getValue() );
  316. out.append(" (0x" + HexDump.toHex(p.getValue()) + ")\n");
  317. if (p instanceof BitMaskTextProp) {
  318. BitMaskTextProp bm = (BitMaskTextProp)p;
  319. int i = 0;
  320. for (String s : bm.getSubPropNames()) {
  321. if (bm.getSubPropMatches()[i]) {
  322. out.append(" " + s + " = " + bm.getSubValue(i) + "\n");
  323. }
  324. i++;
  325. }
  326. }
  327. }
  328. out.append(" bytes that would be written: \n");
  329. try {
  330. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  331. writeOut(baos);
  332. byte[] b = baos.toByteArray();
  333. out.append(HexDump.dump(b, 0, 0));
  334. } catch (Exception e ) {
  335. e.printStackTrace();
  336. }
  337. return out.toString();
  338. }
  339. }