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

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