1
2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
18
19
20 package org.apache.poi.hslf.record;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27
28 import org.apache.poi.hslf.model.textproperties.AlignmentTextProp;
29 import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
30 import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp;
31 import org.apache.poi.hslf.model.textproperties.TextProp;
32 import org.apache.poi.hslf.model.textproperties.TextPropCollection;
33 import org.apache.poi.util.LittleEndian;
34 import org.apache.poi.util.POILogger;
35
36 /**
37 * A StyleTextPropAtom (type 4001). Holds basic character properties
38 * (bold, italic, underline, font size etc) and paragraph properties
39 * (alignment, line spacing etc) for the block of text (TextBytesAtom
40 * or TextCharsAtom) that this record follows.
41 * You will find two lists within this class.
42 * 1 - Paragraph style list (paragraphStyles)
43 * 2 - Character style list (charStyles)
44 * Both are lists of TextPropCollections. These define how many characters
45 * the style applies to, and what style elements make up the style (another
46 * list, this time of TextProps). Each TextProp has a value, which somehow
47 * encapsulates a property of the style
48 *
49 * @author Nick Burch
50 * @author Yegor Kozlov
51 */
52
53 public class StyleTextPropAtom extends RecordAtom
54 {
55 private byte[] _header;
56 private static long _type = 4001l;
57 private byte[] reserved;
58
59 private byte[] rawContents; // Holds the contents between write-outs
60
61 /**
62 * Only set to true once setParentTextSize(int) is called.
63 * Until then, no stylings will have been decoded
64 */
65 private boolean initialised = false;
66
67 /**
68 * The list of all the different paragraph stylings we code for.
69 * Each entry is a TextPropCollection, which tells you how many
70 * Characters the paragraph covers, and also contains the TextProps
71 * that actually define the styling of the paragraph.
72 */
73 private LinkedList paragraphStyles;
74 public LinkedList getParagraphStyles() { return paragraphStyles; }
75 /**
76 * Updates the link list of TextPropCollections which make up the
77 * paragraph stylings
78 */
79 public void setParagraphStyles(LinkedList ps) { paragraphStyles = ps; }
80 /**
81 * The list of all the different character stylings we code for.
82 * Each entry is a TextPropCollection, which tells you how many
83 * Characters the character styling covers, and also contains the
84 * TextProps that actually define the styling of the characters.
85 */
86 private LinkedList charStyles;
87 public LinkedList getCharacterStyles() { return charStyles; }
88 /**
89 * Updates the link list of TextPropCollections which make up the
90 * character stylings
91 */
92 public void setCharacterStyles(LinkedList cs) { charStyles = cs; }
93
94 /**
95 * Returns how many characters the paragraph's
96 * TextPropCollections cover.
97 * (May be one or two more than the underlying text does,
98 * due to having extra characters meaning something
99 * special to powerpoint)
100 */
101 public int getParagraphTextLengthCovered() {
102 return getCharactersCovered(paragraphStyles);
103 }
104 /**
105 * Returns how many characters the character's
106 * TextPropCollections cover.
107 * (May be one or two more than the underlying text does,
108 * due to having extra characters meaning something
109 * special to powerpoint)
110 */
111 public int getCharacterTextLengthCovered() {
112 return getCharactersCovered(charStyles);
113 }
114 private int getCharactersCovered(LinkedList styles) {
115 int length = 0;
116 Iterator it = styles.iterator();
117 while(it.hasNext()) {
118 TextPropCollection tpc =
119 (TextPropCollection)it.next();
120 length += tpc.getCharactersCovered();
121 }
122 return length;
123 }
124
125 /** All the different kinds of paragraph properties we might handle */
126 public static TextProp[] paragraphTextPropTypes = new TextProp[] {
127 new ParagraphFlagsTextProp(),
128 new TextProp(2, 0x80, "bullet.char"),
129 new TextProp(2, 0x10, "bullet.font"),
130 new TextProp(2, 0x40, "bullet.size"),
131 new TextProp(4, 0x20, "bullet.color"),
132 new AlignmentTextProp(),
133 new TextProp(2, 0x100, "text.offset"),
134 new TextProp(2, 0x200, "para_unknown_2"),
135 new TextProp(2, 0x400, "bullet.offset"),
136 new TextProp(2, 0x1000, "linespacing"),
137 new TextProp(2, 0x2000, "spacebefore"),
138 new TextProp(2, 0x4000, "spaceafter"),
139 new TextProp(2, 0x8000, "para_unknown_4"),
140 new TextProp(2, 0x10000, "para_unknown_5"),
141 new TextProp(2, 0xA0000, "para_unknown_6"),
142 new TextProp(2, 0x200000, "para_unknown_7")
143 };
144 /** All the different kinds of character properties we might handle */
145 public static TextProp[] characterTextPropTypes = new TextProp[] {
146 new CharFlagsTextProp(),
147 new TextProp(2, 0x10000, "font.index"),
148 new TextProp(2, 0x200000, "asian_or_complex"),
149 new TextProp(2, 0x400000, "char_unknown_2"),
150 new TextProp(2, 0x800000, "symbol"),
151 new TextProp(2, 0x20000, "font.size"),
152 new TextProp(4, 0x40000, "font.color"),
153 new TextProp(2, 0x80000, "superscript"),
154 new TextProp(2, 0x100000, "char_unknown_1"),
155 new TextProp(2, 0x1000000, "char_unknown_3"),
156 new TextProp(2, 0x2000000, "char_unknown_4"),
157 new TextProp(2, 0x4000000, "char_unknown_5"),
158 new TextProp(2, 0x8000000, "char_unknown_6"),
159 new TextProp(2, 0x10000000, "char_unknown_7"),
160 new TextProp(2, 0x20000000, "char_unknown_8"),
161 new TextProp(2, 0x40000000, "char_unknown_9"),
162 new TextProp(2, 0x80000000, "char_unknown_10"),
163 };
164
165 /* *************** record code follows ********************** */
166
167 /**
168 * For the Text Style Properties (StyleTextProp) Atom
169 */
170 public StyleTextPropAtom(byte[] source, int start, int len) {
171 // Sanity Checking - we're always at least 8+10 bytes long
172 if(len < 18) {
173 len = 18;
174 if(source.length - start < 18) {
175 throw new RuntimeException("Not enough data to form a StyleTextPropAtom (min size 18 bytes long) - found " + (source.length - start));
176 }
177 }
178
179 // Get the header
180 _header = new byte[8];
181 System.arraycopy(source,start,_header,0,8);
182
183 // Save the contents of the atom, until we're asked to go and
184 // decode them (via a call to setParentTextSize(int)
185 rawContents = new byte[len-8];
186 System.arraycopy(source,start+8,rawContents,0,rawContents.length);
187 reserved = new byte[0];
188
189 // Set empty linked lists, ready for when they call setParentTextSize
190 paragraphStyles = new LinkedList();
191 charStyles = new LinkedList();
192 }
193
194
195 /**
196 * A new set of text style properties for some text without any.
197 */
198 public StyleTextPropAtom(int parentTextSize) {
199 _header = new byte[8];
200 rawContents = new byte[0];
201 reserved = new byte[0];
202
203 // Set our type
204 LittleEndian.putInt(_header,2,(short)_type);
205 // Our initial size is 10
206 LittleEndian.putInt(_header,4,10);
207
208 // Set empty paragraph and character styles
209 paragraphStyles = new LinkedList();
210 charStyles = new LinkedList();
211
212 TextPropCollection defaultParagraphTextProps =
213 new TextPropCollection(parentTextSize, (short)0);
214 paragraphStyles.add(defaultParagraphTextProps);
215
216 TextPropCollection defaultCharacterTextProps =
217 new TextPropCollection(parentTextSize);
218 charStyles.add(defaultCharacterTextProps);
219
220 // Set us as now initialised
221 initialised = true;
222 }
223
224
225 /**
226 * We are of type 4001
227 */
228 public long getRecordType() { return _type; }
229
230
231 /**
232 * Write the contents of the record back, so it can be written
233 * to disk
234 */
235 public void writeOut(OutputStream out) throws IOException {
236 // First thing to do is update the raw bytes of the contents, based
237 // on the properties
238 updateRawContents();
239
240 // Now ensure that the header size is correct
241 int newSize = rawContents.length + reserved.length;
242 LittleEndian.putInt(_header,4,newSize);
243
244 // Write out the (new) header
245 out.write(_header);
246
247 // Write out the styles
248 out.write(rawContents);
249
250 // Write out any extra bits
251 out.write(reserved);
252 }
253
254
255 /**
256 * Tell us how much text the parent TextCharsAtom or TextBytesAtom
257 * contains, so we can go ahead and initialise ourselves.
258 */
259 public void setParentTextSize(int size) {
260 int pos = 0;
261 int textHandled = 0;
262
263 // While we have text in need of paragraph stylings, go ahead and
264 // grok the contents as paragraph formatting data
265 int prsize = size;
266 while(pos < rawContents.length && textHandled < prsize) {
267 // First up, fetch the number of characters this applies to
268 int textLen = LittleEndian.getInt(rawContents,pos);
269 textHandled += textLen;
270 pos += 4;
271
272 // Fetch the 2 byte value that is safe to ignore as 0
273 short paraIgn = LittleEndian.getShort(rawContents,pos);
274 pos += 2;
275
276 // Grab the 4 byte value that tells us what properties follow
277 int paraFlags = LittleEndian.getInt(rawContents,pos);
278 pos += 4;
279
280 // Now make sense of those properties
281 TextPropCollection thisCollection = new TextPropCollection(textLen, paraIgn);
282 int plSize = thisCollection.buildTextPropList(
283 paraFlags, paragraphTextPropTypes, rawContents, pos);
284 pos += plSize;
285
286 // Save this properties set
287 paragraphStyles.add(thisCollection);
288
289 // Handle extra 1 paragraph styles at the end
290 if(pos < rawContents.length && textHandled == size) {
291 prsize++;
292 }
293
294 }
295 if (rawContents.length > 0 && textHandled != (size+1)){
296 logger.log(POILogger.WARN, "Problem reading paragraph style runs: textHandled = " + textHandled + ", text.size+1 = " + (size+1));
297 }
298
299 // Now do the character stylings
300 textHandled = 0;
301 int chsize = size;
302 while(pos < rawContents.length && textHandled < chsize) {
303 // First up, fetch the number of characters this applies to
304 int textLen = LittleEndian.getInt(rawContents,pos);
305 textHandled += textLen;
306 pos += 4;
307
308 // There is no 2 byte value
309 short no_val = -1;
310
311 // Grab the 4 byte value that tells us what properties follow
312 int charFlags = LittleEndian.getInt(rawContents,pos);
313 pos += 4;
314
315 // Now make sense of those properties
316 // (Assuming we actually have some)
317 TextPropCollection thisCollection = new TextPropCollection(textLen, no_val);
318 int chSize = thisCollection.buildTextPropList(
319 charFlags, characterTextPropTypes, rawContents, pos);
320 pos += chSize;
321
322 // Save this properties set
323 charStyles.add(thisCollection);
324
325 // Handle extra 1 char styles at the end
326 if(pos < rawContents.length && textHandled == size) {
327 chsize++;
328 }
329 }
330 if (rawContents.length > 0 && textHandled != (size+1)){
331 logger.log(POILogger.WARN, "Problem reading character style runs: textHandled = " + textHandled + ", text.size+1 = " + (size+1));
332 }
333
334 // Handle anything left over
335 if(pos < rawContents.length) {
336 reserved = new byte[rawContents.length-pos];
337 System.arraycopy(rawContents,pos,reserved,0,reserved.length);
338 }
339
340 initialised = true;
341 }
342
343
344 /**
345 * Updates the cache of the raw contents. Serialised the styles out.
346 */
347 private void updateRawContents() throws IOException {
348 if(!initialised) {
349 // We haven't groked the styles since creation, so just stick
350 // with what we found
351 return;
352 }
353
354 ByteArrayOutputStream baos = new ByteArrayOutputStream();
355
356 // First up, we need to serialise the paragraph properties
357 for(int i=0; i<paragraphStyles.size(); i++) {
358 TextPropCollection tpc = (TextPropCollection)paragraphStyles.get(i);
359 tpc.writeOut(baos);
360 }
361
362 // Now, we do the character ones
363 for(int i=0; i<charStyles.size(); i++) {
364 TextPropCollection tpc = (TextPropCollection)charStyles.get(i);
365 tpc.writeOut(baos);
366 }
367
368 rawContents = baos.toByteArray();
369 }
370
371 /**
372 * Create a new Paragraph TextPropCollection, and add it to the list
373 * @param charactersCovered The number of characters this TextPropCollection will cover
374 * @return the new TextPropCollection, which will then be in the list
375 */
376 public TextPropCollection addParagraphTextPropCollection(int charactersCovered) {
377 TextPropCollection tpc = new TextPropCollection(charactersCovered, (short)0);
378 paragraphStyles.add(tpc);
379 return tpc;
380 }
381 /**
382 * Create a new Character TextPropCollection, and add it to the list
383 * @param charactersCovered The number of characters this TextPropCollection will cover
384 * @return the new TextPropCollection, which will then be in the list
385 */
386 public TextPropCollection addCharacterTextPropCollection(int charactersCovered) {
387 TextPropCollection tpc = new TextPropCollection(charactersCovered);
388 charStyles.add(tpc);
389 return tpc;
390 }
391
392 /* ************************************************************************ */
393
394
395 /**
396 * Dump the record content into <code>StringBuffer</code>
397 *
398 * @return the string representation of the record data
399 */
400 public String toString(){
401 StringBuffer out = new StringBuffer();
402 out.append("Paragraph properties\n");
403 for (Iterator it1 = getParagraphStyles().iterator(); it1.hasNext();) {
404 TextPropCollection pr = (TextPropCollection)it1.next();
405 out.append(" chars covered: " + pr.getCharactersCovered() + "\n");
406 for (Iterator it2 = pr.getTextPropList().iterator(); it2.hasNext(); ) {
407 TextProp p = (TextProp)it2.next();
408 out.append(" " + p.getName() + " = " + p.getValue() + "\n");
409 }
410 }
411
412 out.append("Character properties\n");
413 for (Iterator it1 = getCharacterStyles().iterator(); it1.hasNext();) {
414 TextPropCollection pr = (TextPropCollection)it1.next();
415 out.append(" chars covered: " + pr.getCharactersCovered() + "\n");
416 for (Iterator it2 = pr.getTextPropList().iterator(); it2.hasNext(); ) {
417 TextProp p = (TextProp)it2.next();
418 out.append(" " + p.getName() + " = " + p.getValue() + "\n");
419 }
420 }
421
422 return out.toString();
423 }
424 }