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
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==================================================================== */
17
18 package org.apache.poi.hssf.record;
19
20 import org.apache.poi.ddf;
21 import org.apache.poi.hssf.usermodel;
22 import org.apache.poi.hssf.model.AbstractShape;
23 import org.apache.poi.hssf.model.TextboxShape;
24 import org.apache.poi.hssf.model.DrawingManager2;
25 import org.apache.poi.hssf.model.ConvertAnchor;
26 import org.apache.poi.hssf.model.CommentShape;
27 import org.apache.poi.util.POILogFactory;
28 import org.apache.poi.util.POILogger;
29
30 import java.util;
31
32 /**
33 * This class is used to aggregate the MSODRAWING and OBJ record
34 * combinations. This is necessary due to the bizare way in which
35 * these records are serialized. What happens is that you get a
36 * combination of MSODRAWING -> OBJ -> MSODRAWING -> OBJ records
37 * but the escher records are serialized _across_ the MSODRAWING
38 * records.
39 * <p>
40 * It gets even worse when you start looking at TXO records.
41 * <p>
42 * So what we do with this class is aggregate lazily. That is
43 * we don't aggregate the MSODRAWING -> OBJ records unless we
44 * need to modify them.
45 *
46 *
47 * @author Glen Stampoultzis (glens at apache.org)
48 */
49 public class EscherAggregate extends AbstractEscherHolderRecord
50 {
51 public static final short sid = 9876;
52 private static POILogger log = POILogFactory.getLogger(EscherAggregate.class);
53
54 public static final short ST_MIN = (short) 0;
55 public static final short ST_NOT_PRIMATIVE = ST_MIN;
56 public static final short ST_RECTANGLE = (short) 1;
57 public static final short ST_ROUNDRECTANGLE = (short) 2;
58 public static final short ST_ELLIPSE = (short) 3;
59 public static final short ST_DIAMOND = (short) 4;
60 public static final short ST_ISOCELESTRIANGLE = (short) 5;
61 public static final short ST_RIGHTTRIANGLE = (short) 6;
62 public static final short ST_PARALLELOGRAM = (short) 7;
63 public static final short ST_TRAPEZOID = (short) 8;
64 public static final short ST_HEXAGON = (short) 9;
65 public static final short ST_OCTAGON = (short) 10;
66 public static final short ST_PLUS = (short) 11;
67 public static final short ST_STAR = (short) 12;
68 public static final short ST_ARROW = (short) 13;
69 public static final short ST_THICKARROW = (short) 14;
70 public static final short ST_HOMEPLATE = (short) 15;
71 public static final short ST_CUBE = (short) 16;
72 public static final short ST_BALLOON = (short) 17;
73 public static final short ST_SEAL = (short) 18;
74 public static final short ST_ARC = (short) 19;
75 public static final short ST_LINE = (short) 20;
76 public static final short ST_PLAQUE = (short) 21;
77 public static final short ST_CAN = (short) 22;
78 public static final short ST_DONUT = (short) 23;
79 public static final short ST_TEXTSIMPLE = (short) 24;
80 public static final short ST_TEXTOCTAGON = (short) 25;
81 public static final short ST_TEXTHEXAGON = (short) 26;
82 public static final short ST_TEXTCURVE = (short) 27;
83 public static final short ST_TEXTWAVE = (short) 28;
84 public static final short ST_TEXTRING = (short) 29;
85 public static final short ST_TEXTONCURVE = (short) 30;
86 public static final short ST_TEXTONRING = (short) 31;
87 public static final short ST_STRAIGHTCONNECTOR1 = (short) 32;
88 public static final short ST_BENTCONNECTOR2 = (short) 33;
89 public static final short ST_BENTCONNECTOR3 = (short) 34;
90 public static final short ST_BENTCONNECTOR4 = (short) 35;
91 public static final short ST_BENTCONNECTOR5 = (short) 36;
92 public static final short ST_CURVEDCONNECTOR2 = (short) 37;
93 public static final short ST_CURVEDCONNECTOR3 = (short) 38;
94 public static final short ST_CURVEDCONNECTOR4 = (short) 39;
95 public static final short ST_CURVEDCONNECTOR5 = (short) 40;
96 public static final short ST_CALLOUT1 = (short) 41;
97 public static final short ST_CALLOUT2 = (short) 42;
98 public static final short ST_CALLOUT3 = (short) 43;
99 public static final short ST_ACCENTCALLOUT1 = (short) 44;
100 public static final short ST_ACCENTCALLOUT2 = (short) 45;
101 public static final short ST_ACCENTCALLOUT3 = (short) 46;
102 public static final short ST_BORDERCALLOUT1 = (short) 47;
103 public static final short ST_BORDERCALLOUT2 = (short) 48;
104 public static final short ST_BORDERCALLOUT3 = (short) 49;
105 public static final short ST_ACCENTBORDERCALLOUT1 = (short) 50;
106 public static final short ST_ACCENTBORDERCALLOUT2 = (short) 51;
107 public static final short ST_ACCENTBORDERCALLOUT3 = (short) 52;
108 public static final short ST_RIBBON = (short) 53;
109 public static final short ST_RIBBON2 = (short) 54;
110 public static final short ST_CHEVRON = (short) 55;
111 public static final short ST_PENTAGON = (short) 56;
112 public static final short ST_NOSMOKING = (short) 57;
113 public static final short ST_SEAL8 = (short) 58;
114 public static final short ST_SEAL16 = (short) 59;
115 public static final short ST_SEAL32 = (short) 60;
116 public static final short ST_WEDGERECTCALLOUT = (short) 61;
117 public static final short ST_WEDGERRECTCALLOUT = (short) 62;
118 public static final short ST_WEDGEELLIPSECALLOUT = (short) 63;
119 public static final short ST_WAVE = (short) 64;
120 public static final short ST_FOLDEDCORNER = (short) 65;
121 public static final short ST_LEFTARROW = (short) 66;
122 public static final short ST_DOWNARROW = (short) 67;
123 public static final short ST_UPARROW = (short) 68;
124 public static final short ST_LEFTRIGHTARROW = (short) 69;
125 public static final short ST_UPDOWNARROW = (short) 70;
126 public static final short ST_IRREGULARSEAL1 = (short) 71;
127 public static final short ST_IRREGULARSEAL2 = (short) 72;
128 public static final short ST_LIGHTNINGBOLT = (short) 73;
129 public static final short ST_HEART = (short) 74;
130 public static final short ST_PICTUREFRAME = (short) 75;
131 public static final short ST_QUADARROW = (short) 76;
132 public static final short ST_LEFTARROWCALLOUT = (short) 77;
133 public static final short ST_RIGHTARROWCALLOUT = (short) 78;
134 public static final short ST_UPARROWCALLOUT = (short) 79;
135 public static final short ST_DOWNARROWCALLOUT = (short) 80;
136 public static final short ST_LEFTRIGHTARROWCALLOUT = (short) 81;
137 public static final short ST_UPDOWNARROWCALLOUT = (short) 82;
138 public static final short ST_QUADARROWCALLOUT = (short) 83;
139 public static final short ST_BEVEL = (short) 84;
140 public static final short ST_LEFTBRACKET = (short) 85;
141 public static final short ST_RIGHTBRACKET = (short) 86;
142 public static final short ST_LEFTBRACE = (short) 87;
143 public static final short ST_RIGHTBRACE = (short) 88;
144 public static final short ST_LEFTUPARROW = (short) 89;
145 public static final short ST_BENTUPARROW = (short) 90;
146 public static final short ST_BENTARROW = (short) 91;
147 public static final short ST_SEAL24 = (short) 92;
148 public static final short ST_STRIPEDRIGHTARROW = (short) 93;
149 public static final short ST_NOTCHEDRIGHTARROW = (short) 94;
150 public static final short ST_BLOCKARC = (short) 95;
151 public static final short ST_SMILEYFACE = (short) 96;
152 public static final short ST_VERTICALSCROLL = (short) 97;
153 public static final short ST_HORIZONTALSCROLL = (short) 98;
154 public static final short ST_CIRCULARARROW = (short) 99;
155 public static final short ST_NOTCHEDCIRCULARARROW = (short) 100;
156 public static final short ST_UTURNARROW = (short) 101;
157 public static final short ST_CURVEDRIGHTARROW = (short) 102;
158 public static final short ST_CURVEDLEFTARROW = (short) 103;
159 public static final short ST_CURVEDUPARROW = (short) 104;
160 public static final short ST_CURVEDDOWNARROW = (short) 105;
161 public static final short ST_CLOUDCALLOUT = (short) 106;
162 public static final short ST_ELLIPSERIBBON = (short) 107;
163 public static final short ST_ELLIPSERIBBON2 = (short) 108;
164 public static final short ST_FLOWCHARTPROCESS = (short) 109;
165 public static final short ST_FLOWCHARTDECISION = (short) 110;
166 public static final short ST_FLOWCHARTINPUTOUTPUT = (short) 111;
167 public static final short ST_FLOWCHARTPREDEFINEDPROCESS = (short) 112;
168 public static final short ST_FLOWCHARTINTERNALSTORAGE = (short) 113;
169 public static final short ST_FLOWCHARTDOCUMENT = (short) 114;
170 public static final short ST_FLOWCHARTMULTIDOCUMENT = (short) 115;
171 public static final short ST_FLOWCHARTTERMINATOR = (short) 116;
172 public static final short ST_FLOWCHARTPREPARATION = (short) 117;
173 public static final short ST_FLOWCHARTMANUALINPUT = (short) 118;
174 public static final short ST_FLOWCHARTMANUALOPERATION = (short) 119;
175 public static final short ST_FLOWCHARTCONNECTOR = (short) 120;
176 public static final short ST_FLOWCHARTPUNCHEDCARD = (short) 121;
177 public static final short ST_FLOWCHARTPUNCHEDTAPE = (short) 122;
178 public static final short ST_FLOWCHARTSUMMINGJUNCTION = (short) 123;
179 public static final short ST_FLOWCHARTOR = (short) 124;
180 public static final short ST_FLOWCHARTCOLLATE = (short) 125;
181 public static final short ST_FLOWCHARTSORT = (short) 126;
182 public static final short ST_FLOWCHARTEXTRACT = (short) 127;
183 public static final short ST_FLOWCHARTMERGE = (short) 128;
184 public static final short ST_FLOWCHARTOFFLINESTORAGE = (short) 129;
185 public static final short ST_FLOWCHARTONLINESTORAGE = (short) 130;
186 public static final short ST_FLOWCHARTMAGNETICTAPE = (short) 131;
187 public static final short ST_FLOWCHARTMAGNETICDISK = (short) 132;
188 public static final short ST_FLOWCHARTMAGNETICDRUM = (short) 133;
189 public static final short ST_FLOWCHARTDISPLAY = (short) 134;
190 public static final short ST_FLOWCHARTDELAY = (short) 135;
191 public static final short ST_TEXTPLAINTEXT = (short) 136;
192 public static final short ST_TEXTSTOP = (short) 137;
193 public static final short ST_TEXTTRIANGLE = (short) 138;
194 public static final short ST_TEXTTRIANGLEINVERTED = (short) 139;
195 public static final short ST_TEXTCHEVRON = (short) 140;
196 public static final short ST_TEXTCHEVRONINVERTED = (short) 141;
197 public static final short ST_TEXTRINGINSIDE = (short) 142;
198 public static final short ST_TEXTRINGOUTSIDE = (short) 143;
199 public static final short ST_TEXTARCHUPCURVE = (short) 144;
200 public static final short ST_TEXTARCHDOWNCURVE = (short) 145;
201 public static final short ST_TEXTCIRCLECURVE = (short) 146;
202 public static final short ST_TEXTBUTTONCURVE = (short) 147;
203 public static final short ST_TEXTARCHUPPOUR = (short) 148;
204 public static final short ST_TEXTARCHDOWNPOUR = (short) 149;
205 public static final short ST_TEXTCIRCLEPOUR = (short) 150;
206 public static final short ST_TEXTBUTTONPOUR = (short) 151;
207 public static final short ST_TEXTCURVEUP = (short) 152;
208 public static final short ST_TEXTCURVEDOWN = (short) 153;
209 public static final short ST_TEXTCASCADEUP = (short) 154;
210 public static final short ST_TEXTCASCADEDOWN = (short) 155;
211 public static final short ST_TEXTWAVE1 = (short) 156;
212 public static final short ST_TEXTWAVE2 = (short) 157;
213 public static final short ST_TEXTWAVE3 = (short) 158;
214 public static final short ST_TEXTWAVE4 = (short) 159;
215 public static final short ST_TEXTINFLATE = (short) 160;
216 public static final short ST_TEXTDEFLATE = (short) 161;
217 public static final short ST_TEXTINFLATEBOTTOM = (short) 162;
218 public static final short ST_TEXTDEFLATEBOTTOM = (short) 163;
219 public static final short ST_TEXTINFLATETOP = (short) 164;
220 public static final short ST_TEXTDEFLATETOP = (short) 165;
221 public static final short ST_TEXTDEFLATEINFLATE = (short) 166;
222 public static final short ST_TEXTDEFLATEINFLATEDEFLATE = (short) 167;
223 public static final short ST_TEXTFADERIGHT = (short) 168;
224 public static final short ST_TEXTFADELEFT = (short) 169;
225 public static final short ST_TEXTFADEUP = (short) 170;
226 public static final short ST_TEXTFADEDOWN = (short) 171;
227 public static final short ST_TEXTSLANTUP = (short) 172;
228 public static final short ST_TEXTSLANTDOWN = (short) 173;
229 public static final short ST_TEXTCANUP = (short) 174;
230 public static final short ST_TEXTCANDOWN = (short) 175;
231 public static final short ST_FLOWCHARTALTERNATEPROCESS = (short) 176;
232 public static final short ST_FLOWCHARTOFFPAGECONNECTOR = (short) 177;
233 public static final short ST_CALLOUT90 = (short) 178;
234 public static final short ST_ACCENTCALLOUT90 = (short) 179;
235 public static final short ST_BORDERCALLOUT90 = (short) 180;
236 public static final short ST_ACCENTBORDERCALLOUT90 = (short) 181;
237 public static final short ST_LEFTRIGHTUPARROW = (short) 182;
238 public static final short ST_SUN = (short) 183;
239 public static final short ST_MOON = (short) 184;
240 public static final short ST_BRACKETPAIR = (short) 185;
241 public static final short ST_BRACEPAIR = (short) 186;
242 public static final short ST_SEAL4 = (short) 187;
243 public static final short ST_DOUBLEWAVE = (short) 188;
244 public static final short ST_ACTIONBUTTONBLANK = (short) 189;
245 public static final short ST_ACTIONBUTTONHOME = (short) 190;
246 public static final short ST_ACTIONBUTTONHELP = (short) 191;
247 public static final short ST_ACTIONBUTTONINFORMATION = (short) 192;
248 public static final short ST_ACTIONBUTTONFORWARDNEXT = (short) 193;
249 public static final short ST_ACTIONBUTTONBACKPREVIOUS = (short) 194;
250 public static final short ST_ACTIONBUTTONEND = (short) 195;
251 public static final short ST_ACTIONBUTTONBEGINNING = (short) 196;
252 public static final short ST_ACTIONBUTTONRETURN = (short) 197;
253 public static final short ST_ACTIONBUTTONDOCUMENT = (short) 198;
254 public static final short ST_ACTIONBUTTONSOUND = (short) 199;
255 public static final short ST_ACTIONBUTTONMOVIE = (short) 200;
256 public static final short ST_HOSTCONTROL = (short) 201;
257 public static final short ST_TEXTBOX = (short) 202;
258 public static final short ST_NIL = (short) 0x0FFF;
259
260 protected HSSFPatriarch patriarch;
261
262 /** Maps shape container objects to their OBJ records */
263 private Map shapeToObj = new HashMap();
264 private DrawingManager2 drawingManager;
265 private short drawingGroupId;
266
267 /**
268 * list of "tail" records that need to be serialized after all drawing group records
269 */
270 private List tailRec = new ArrayList();
271
272 public EscherAggregate( DrawingManager2 drawingManager )
273 {
274 this.drawingManager = drawingManager;
275 }
276
277 /**
278 * @return Returns the current sid.
279 */
280 public short getSid()
281 {
282 return sid;
283 }
284
285 /**
286 * Unused since this is an aggregate record. Use createAggregate().
287 *
288 * @see #createAggregate
289 */
290 protected void fillFields( byte[] data, short size, int offset )
291 {
292 throw new IllegalStateException( "Should not reach here" );
293 }
294
295 /**
296 * Calculates the string representation of this record. This is
297 * simply a dump of all the records.
298 */
299 public String toString()
300 {
301 String nl = System.getProperty( "line.separtor" );
302
303 StringBuffer result = new StringBuffer();
304 result.append( '[' ).append( getRecordName() ).append( ']' + nl );
305 for ( Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); )
306 {
307 EscherRecord escherRecord = (EscherRecord) iterator.next();
308 result.append( escherRecord.toString() );
309 }
310 result.append( "[/" ).append( getRecordName() ).append( ']' + nl );
311
312 return result.toString();
313 }
314
315 /**
316 * Collapses the drawing records into an aggregate.
317 */
318 public static EscherAggregate createAggregate( List records, int locFirstDrawingRecord, DrawingManager2 drawingManager )
319 {
320 // Keep track of any shape records created so we can match them back to the object id's.
321 // Textbox objects are also treated as shape objects.
322 final List shapeRecords = new ArrayList();
323 EscherRecordFactory recordFactory = new DefaultEscherRecordFactory()
324 {
325 public EscherRecord createRecord( byte[] data, int offset )
326 {
327 EscherRecord r = super.createRecord( data, offset );
328 if ( r.getRecordId() == EscherClientDataRecord.RECORD_ID || r.getRecordId() == EscherTextboxRecord.RECORD_ID )
329 {
330 shapeRecords.add( r );
331 }
332 return r;
333 }
334 };
335
336 // Calculate the size of the buffer
337 EscherAggregate agg = new EscherAggregate(drawingManager);
338 int loc = locFirstDrawingRecord;
339 int dataSize = 0;
340 while ( loc + 1 < records.size()
341 && sid( records, loc ) == DrawingRecord.sid
342 && isObjectRecord( records, loc + 1 ) )
343 {
344 dataSize += ( (DrawingRecord) records.get( loc ) ).getData().length;
345 loc += 2;
346 }
347
348 // Create one big buffer
349 byte buffer[] = new byte[dataSize];
350 int offset = 0;
351 loc = locFirstDrawingRecord;
352 while ( loc + 1 < records.size()
353 && sid( records, loc ) == DrawingRecord.sid
354 && isObjectRecord( records, loc + 1 ) )
355 {
356 DrawingRecord drawingRecord = (DrawingRecord) records.get( loc );
357 System.arraycopy( drawingRecord.getData(), 0, buffer, offset, drawingRecord.getData().length );
358 offset += drawingRecord.getData().length;
359 loc += 2;
360 }
361
362 // Decode the shapes
363 // agg.escherRecords = new ArrayList();
364 int pos = 0;
365 while ( pos < dataSize )
366 {
367 EscherRecord r = recordFactory.createRecord( buffer, pos );
368 int bytesRead = r.fillFields( buffer, pos, recordFactory );
369 agg.addEscherRecord( r );
370 pos += bytesRead;
371 }
372
373 // Associate the object records with the shapes
374 loc = locFirstDrawingRecord;
375 int shapeIndex = 0;
376 agg.shapeToObj = new HashMap();
377 while ( loc + 1 < records.size()
378 && sid( records, loc ) == DrawingRecord.sid
379 && isObjectRecord( records, loc + 1 ) )
380 {
381 Record objRecord = (Record) records.get( loc + 1 );
382 agg.shapeToObj.put( shapeRecords.get( shapeIndex++ ), objRecord );
383 loc += 2;
384 }
385
386 return agg;
387
388 }
389
390 /**
391 * Serializes this aggregate to a byte array. Since this is an aggregate
392 * record it will effectively serialize the aggregated records.
393 *
394 * @param offset The offset into the start of the array.
395 * @param data The byte array to serialize to.
396 * @return The number of bytes serialized.
397 */
398 public int serialize( int offset, byte[] data )
399 {
400 convertUserModelToRecords();
401
402 // Determine buffer size
403 List records = getEscherRecords();
404 int size = getEscherRecordSize( records );
405 byte[] buffer = new byte[size];
406
407
408 // Serialize escher records into one big data structure and keep note of ending offsets.
409 final List spEndingOffsets = new ArrayList();
410 final List shapes = new ArrayList();
411 int pos = 0;
412 for ( Iterator iterator = records.iterator(); iterator.hasNext(); )
413 {
414 EscherRecord e = (EscherRecord) iterator.next();
415 pos += e.serialize( pos, buffer, new EscherSerializationListener()
416 {
417 public void beforeRecordSerialize( int offset, short recordId, EscherRecord record )
418 {
419 }
420
421 public void afterRecordSerialize( int offset, short recordId, int size, EscherRecord record )
422 {
423 if ( recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID )
424 {
425 spEndingOffsets.add( new Integer( offset ) );
426 shapes.add( record );
427 }
428 }
429 } );
430 }
431 // todo: fix this
432 shapes.add( 0, null );
433 spEndingOffsets.add( 0, null );
434
435 // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on
436 // the first one because it's the patriach).
437 pos = offset;
438 for ( int i = 1; i < shapes.size(); i++ )
439 {
440 int endOffset = ( (Integer) spEndingOffsets.get( i ) ).intValue() - 1;
441 int startOffset;
442 if ( i == 1 )
443 startOffset = 0;
444 else
445 startOffset = ( (Integer) spEndingOffsets.get( i - 1 ) ).intValue();
446
447 // Create and write a new MSODRAWING record
448 DrawingRecord drawing = new DrawingRecord();
449 byte[] drawingData = new byte[endOffset - startOffset + 1];
450 System.arraycopy( buffer, startOffset, drawingData, 0, drawingData.length );
451 drawing.setData( drawingData );
452 int temp = drawing.serialize( pos, data );
453 pos += temp;
454
455 // Write the matching OBJ record
456 Record obj = (Record) shapeToObj.get( shapes.get( i ) );
457 temp = obj.serialize( pos, data );
458 pos += temp;
459
460 }
461
462 // write records that need to be serialized after all drawing group records
463 for ( int i = 0; i < tailRec.size(); i++ )
464 {
465 Record rec = (Record)tailRec.get(i);
466 pos += rec.serialize( pos, data );
467 }
468
469 int bytesWritten = pos - offset;
470 if ( bytesWritten != getRecordSize() )
471 throw new RecordFormatException( bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize() );
472 return bytesWritten;
473 }
474
475 /**
476 * How many bytes do the raw escher records contain.
477 * @param records List of escher records
478 * @return the number of bytes
479 */
480 private int getEscherRecordSize( List records )
481 {
482 int size = 0;
483 for ( Iterator iterator = records.iterator(); iterator.hasNext(); )
484 size += ( (EscherRecord) iterator.next() ).getRecordSize();
485 return size;
486 }
487
488 /**
489 * The number of bytes required to serialize this record.
490 */
491 public int getRecordSize()
492 {
493 convertUserModelToRecords();
494 List records = getEscherRecords();
495 int rawEscherSize = getEscherRecordSize( records );
496 int drawingRecordSize = rawEscherSize + ( shapeToObj.size() ) * 4;
497 int objRecordSize = 0;
498 for ( Iterator iterator = shapeToObj.values().iterator(); iterator.hasNext(); )
499 {
500 Record r = (Record) iterator.next();
501 objRecordSize += r.getRecordSize();
502 }
503 int tailRecordSize = 0;
504 for ( Iterator iterator = tailRec.iterator(); iterator.hasNext(); )
505 {
506 Record r = (Record) iterator.next();
507 tailRecordSize += r.getRecordSize();
508 }
509 return drawingRecordSize + objRecordSize + tailRecordSize;
510 }
511
512 /**
513 * Associates an escher record to an OBJ record or a TXO record.
514 */
515 public Object assoicateShapeToObjRecord( EscherRecord r, Record objRecord )
516 {
517 return shapeToObj.put( r, objRecord );
518 }
519
520 public HSSFPatriarch getPatriarch()
521 {
522 return patriarch;
523 }
524
525 public void setPatriarch( HSSFPatriarch patriarch )
526 {
527 this.patriarch = patriarch;
528 }
529
530 /**
531 * Converts the Records into UserModel
532 * objects on the bound HSSFPatriarch
533 */
534 public void convertRecordsToUserModel() {
535 if(patriarch == null) {
536 throw new IllegalStateException("Must call setPatriarch() first");
537 }
538
539 // The top level container ought to have
540 // the DgRecord and the container of one container
541 // per shape group (patriach overall first)
542 EscherContainerRecord topContainer =
543 (EscherContainerRecord)getEscherContainer();
544 if(topContainer == null) {
545 return;
546 }
547 topContainer = (EscherContainerRecord)
548 topContainer.getChildContainers().get(0);
549
550 List tcc = topContainer.getChildContainers();
551 if(tcc.size() == 0) {
552 throw new IllegalStateException("No child escher containers at the point that should hold the patriach data, and one container per top level shape!");
553 }
554
555 // First up, get the patriach position
556 // This is in the first EscherSpgrRecord, in
557 // the first container, with a EscherSRecord too
558 EscherContainerRecord patriachContainer =
559 (EscherContainerRecord)tcc.get(0);
560 EscherSpgrRecord spgr = null;
561 for(Iterator it = patriachContainer.getChildRecords().iterator(); it.hasNext();) {
562 EscherRecord r = (EscherRecord)it.next();
563 if(r instanceof EscherSpgrRecord) {
564 spgr = (EscherSpgrRecord)r;
565 break;
566 }
567 }
568 if(spgr != null) {
569 patriarch.setCoordinates(
570 spgr.getRectX1(), spgr.getRectY1(),
571 spgr.getRectX2(), spgr.getRectY2()
572 );
573 }
574
575 // Now process the containers for each group
576 // and objects
577 for(int i=1; i<tcc.size(); i++) {
578 EscherContainerRecord shapeContainer =
579 (EscherContainerRecord)tcc.get(i);
580 //System.err.println("\n\n*****\n\n");
581 //System.err.println(shapeContainer);
582
583 // Could be a group, or a base object
584 if(shapeContainer.getChildRecords().size() == 1 &&
585 shapeContainer.getChildContainers().size() == 1) {
586 // Group
587 HSSFShapeGroup group =
588 new HSSFShapeGroup(null, new HSSFClientAnchor());
589 patriarch.getChildren().add(group);
590
591 EscherContainerRecord groupContainer =
592 (EscherContainerRecord)shapeContainer.getChild(0);
593 convertRecordsToUserModel(groupContainer, group);
594 } else if(shapeContainer.hasChildOfType((short)0xF00D)) {
595 // TextBox
596 HSSFTextbox box =
597 new HSSFTextbox(null, new HSSFClientAnchor());
598 patriarch.getChildren().add(box);
599
600 convertRecordsToUserModel(shapeContainer, box);
601 } else if(shapeContainer.hasChildOfType((short)0xF011)) {
602 // Not yet supporting EscherClientDataRecord stuff
603 } else {
604 // Base level
605 convertRecordsToUserModel(shapeContainer, patriarch);
606 }
607 }
608
609 // Now, clear any trace of what records make up
610 // the patriarch
611 // Otherwise, everything will go horribly wrong
612 // when we try to write out again....
613 // clearEscherRecords();
614 drawingManager.getDgg().setFileIdClusters(new EscherDggRecord.FileIdCluster[0]);
615
616 // TODO: Support converting our records
617 // back into shapes
618 log.log(POILogger.WARN, "Not processing objects into Patriarch!");
619 }
620
621 private void convertRecordsToUserModel(EscherContainerRecord shapeContainer, Object model) {
622 for(Iterator it = shapeContainer.getChildRecords().iterator(); it.hasNext();) {
623 EscherRecord r = (EscherRecord)it.next();
624 if(r instanceof EscherSpgrRecord) {
625 // This may be overriden by a later EscherClientAnchorRecord
626 EscherSpgrRecord spgr = (EscherSpgrRecord)r;
627
628 if(model instanceof HSSFShapeGroup) {
629 HSSFShapeGroup g = (HSSFShapeGroup)model;
630 g.setCoordinates(
631 spgr.getRectX1(), spgr.getRectY1(),
632 spgr.getRectX2(), spgr.getRectY2()
633 );
634 } else {
635 throw new IllegalStateException("Got top level anchor but not processing a group");
636 }
637 }
638 else if(r instanceof EscherClientAnchorRecord) {
639 EscherClientAnchorRecord car = (EscherClientAnchorRecord)r;
640
641 if(model instanceof HSSFShape) {
642 HSSFShape g = (HSSFShape)model;
643 g.getAnchor().setDx1(car.getDx1());
644 g.getAnchor().setDx2(car.getDx2());
645 g.getAnchor().setDy1(car.getDy1());
646 g.getAnchor().setDy2(car.getDy2());
647 } else {
648 throw new IllegalStateException("Got top level anchor but not processing a group or shape");
649 }
650 }
651 else if(r instanceof EscherTextboxRecord) {
652 EscherTextboxRecord tbr = (EscherTextboxRecord)r;
653
654 // Also need to find the TextObjectRecord too
655 // TODO
656 }
657 else if(r instanceof EscherSpRecord) {
658 // Use flags if needed
659 }
660 else if(r instanceof EscherOptRecord) {
661 // Use properties if needed
662 }
663 else {
664 //System.err.println(r);
665 }
666 }
667 }
668
669 public void clear()
670 {
671 clearEscherRecords();
672 shapeToObj.clear();
673 // lastShapeId = 1024;
674 }
675
676 protected String getRecordName()
677 {
678 return "ESCHERAGGREGATE";
679 }
680
681 // =============== Private methods ========================
682
683 private static boolean isObjectRecord( List records, int loc )
684 {
685 return sid( records, loc ) == ObjRecord.sid || sid( records, loc ) == TextObjectRecord.sid;
686 }
687
688 private void convertUserModelToRecords()
689 {
690 if ( patriarch != null )
691 {
692 shapeToObj.clear();
693 tailRec.clear();
694 clearEscherRecords();
695 if ( patriarch.getChildren().size() != 0 )
696 {
697 convertPatriarch( patriarch );
698 EscherContainerRecord dgContainer = (EscherContainerRecord) getEscherRecord( 0 );
699 EscherContainerRecord spgrContainer = null;
700 for ( int i = 0; i < dgContainer.getChildRecords().size(); i++ )
701 if ( dgContainer.getChild( i ).getRecordId() == EscherContainerRecord.SPGR_CONTAINER )
702 spgrContainer = (EscherContainerRecord) dgContainer.getChild( i );
703 convertShapes( patriarch, spgrContainer, shapeToObj );
704
705 patriarch = null;
706 }
707 }
708 }
709
710 private void convertShapes( HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj )
711 {
712 if ( escherParent == null ) throw new IllegalArgumentException( "Parent record required" );
713
714 List shapes = parent.getChildren();
715 for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); )
716 {
717 HSSFShape shape = (HSSFShape) iterator.next();
718 if ( shape instanceof HSSFShapeGroup )
719 {
720 convertGroup( (HSSFShapeGroup) shape, escherParent, shapeToObj );
721 }
722 else
723 {
724 AbstractShape shapeModel = AbstractShape.createShape(
725 shape,
726 drawingManager.allocateShapeId(drawingGroupId) );
727 shapeToObj.put( findClientData( shapeModel.getSpContainer() ), shapeModel.getObjRecord() );
728 if ( shapeModel instanceof TextboxShape )
729 {
730 EscherRecord escherTextbox = ( (TextboxShape) shapeModel ).getEscherTextbox();
731 shapeToObj.put( escherTextbox, ( (TextboxShape) shapeModel ).getTextObjectRecord() );
732 // escherParent.addChildRecord(escherTextbox);
733
734 if ( shapeModel instanceof CommentShape ){
735 CommentShape comment = (CommentShape)shapeModel;
736 tailRec.add(comment.getNoteRecord());
737 }
738
739 }
740 escherParent.addChildRecord( shapeModel.getSpContainer() );
741 }
742 }
743 // drawingManager.newCluster( (short)1 );
744 // drawingManager.newCluster( (short)2 );
745
746 }
747
748 private void convertGroup( HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj )
749 {
750 EscherContainerRecord spgrContainer = new EscherContainerRecord();
751 EscherContainerRecord spContainer = new EscherContainerRecord();
752 EscherSpgrRecord spgr = new EscherSpgrRecord();
753 EscherSpRecord sp = new EscherSpRecord();
754 EscherOptRecord opt = new EscherOptRecord();
755 EscherRecord anchor;
756 EscherClientDataRecord clientData = new EscherClientDataRecord();
757
758 spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER );
759 spgrContainer.setOptions( (short) 0x000F );
760 spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER );
761 spContainer.setOptions( (short) 0x000F );
762 spgr.setRecordId( EscherSpgrRecord.RECORD_ID );
763 spgr.setOptions( (short) 0x0001 );
764 spgr.setRectX1( shape.getX1() );
765 spgr.setRectY1( shape.getY1() );
766 spgr.setRectX2( shape.getX2() );
767 spgr.setRectY2( shape.getY2() );
768 sp.setRecordId( EscherSpRecord.RECORD_ID );
769 sp.setOptions( (short) 0x0002 );
770 int shapeId = drawingManager.allocateShapeId(drawingGroupId);
771 sp.setShapeId( shapeId );
772 if (shape.getAnchor() instanceof HSSFClientAnchor)
773 sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR );
774 else
775 sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_CHILD );
776 opt.setRecordId( EscherOptRecord.RECORD_ID );
777 opt.setOptions( (short) 0x0023 );
778 opt.addEscherProperty( new EscherBoolProperty( EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x00040004 ) );
779 opt.addEscherProperty( new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x00080000 ) );
780
781 anchor = ConvertAnchor.createAnchor( shape.getAnchor() );
782 // clientAnchor.setCol1( ( (HSSFClientAnchor) shape.getAnchor() ).getCol1() );
783 // clientAnchor.setRow1( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow1() );
784 // clientAnchor.setDx1( (short) shape.getAnchor().getDx1() );
785 // clientAnchor.setDy1( (short) shape.getAnchor().getDy1() );
786 // clientAnchor.setCol2( ( (HSSFClientAnchor) shape.getAnchor() ).getCol2() );
787 // clientAnchor.setRow2( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow2() );
788 // clientAnchor.setDx2( (short) shape.getAnchor().getDx2() );
789 // clientAnchor.setDy2( (short) shape.getAnchor().getDy2() );
790 clientData.setRecordId( EscherClientDataRecord.RECORD_ID );
791 clientData.setOptions( (short) 0x0000 );
792
793 spgrContainer.addChildRecord( spContainer );
794 spContainer.addChildRecord( spgr );
795 spContainer.addChildRecord( sp );
796 spContainer.addChildRecord( opt );
797 spContainer.addChildRecord( anchor );
798 spContainer.addChildRecord( clientData );
799
800 ObjRecord obj = new ObjRecord();
801 CommonObjectDataSubRecord cmo = new CommonObjectDataSubRecord();
802 cmo.setObjectType( CommonObjectDataSubRecord.OBJECT_TYPE_GROUP );
803 cmo.setObjectId( (short) ( shapeId ) );
804 cmo.setLocked( true );
805 cmo.setPrintable( true );
806 cmo.setAutofill( true );
807 cmo.setAutoline( true );
808 GroupMarkerSubRecord gmo = new GroupMarkerSubRecord();
809 EndSubRecord end = new EndSubRecord();
810 obj.addSubRecord( cmo );
811 obj.addSubRecord( gmo );
812 obj.addSubRecord( end );
813 shapeToObj.put( clientData, obj );
814
815 escherParent.addChildRecord( spgrContainer );
816
817 convertShapes( shape, spgrContainer, shapeToObj );
818
819 }
820
821 private EscherRecord findClientData( EscherContainerRecord spContainer )
822 {
823 for ( Iterator iterator = spContainer.getChildRecords().iterator(); iterator.hasNext(); )
824 {
825 EscherRecord r = (EscherRecord) iterator.next();
826 if ( r.getRecordId() == EscherClientDataRecord.RECORD_ID )
827 return r;
828 }
829 throw new IllegalArgumentException( "Can not find client data record" );
830 }
831
832 private void convertPatriarch( HSSFPatriarch patriarch )
833 {
834 EscherContainerRecord dgContainer = new EscherContainerRecord();
835 EscherDgRecord dg;
836 EscherContainerRecord spgrContainer = new EscherContainerRecord();
837 EscherContainerRecord spContainer1 = new EscherContainerRecord();
838 EscherSpgrRecord spgr = new EscherSpgrRecord();
839 EscherSpRecord sp1 = new EscherSpRecord();
840
841 dgContainer.setRecordId( EscherContainerRecord.DG_CONTAINER );
842 dgContainer.setOptions( (short) 0x000F );
843 dg = drawingManager.createDgRecord();
844 drawingGroupId = dg.getDrawingGroupId();
845 // dg.setOptions( (short) ( drawingId << 4 ) );
846 // dg.setNumShapes( getNumberOfShapes( patriarch ) );
847 // dg.setLastMSOSPID( 0 ); // populated after all shape id's are assigned.
848 spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER );
849 spgrContainer.setOptions( (short) 0x000F );
850 spContainer1.setRecordId( EscherContainerRecord.SP_CONTAINER );
851 spContainer1.setOptions( (short) 0x000F );
852 spgr.setRecordId( EscherSpgrRecord.RECORD_ID );
853 spgr.setOptions( (short) 0x0001 ); // version
854 spgr.setRectX1( patriarch.getX1() );
855 spgr.setRectY1( patriarch.getY1() );
856 spgr.setRectX2( patriarch.getX2() );
857 spgr.setRectY2( patriarch.getY2() );
858 sp1.setRecordId( EscherSpRecord.RECORD_ID );
859 sp1.setOptions( (short) 0x0002 );
860 sp1.setShapeId( drawingManager.allocateShapeId(dg.getDrawingGroupId()) );
861 sp1.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_PATRIARCH );
862
863 dgContainer.addChildRecord( dg );
864 dgContainer.addChildRecord( spgrContainer );
865 spgrContainer.addChildRecord( spContainer1 );
866 spContainer1.addChildRecord( spgr );
867 spContainer1.addChildRecord( sp1 );
868
869 addEscherRecord( dgContainer );
870 }
871
872 /** Retrieve the number of shapes (including the patriarch). */
873 // private int getNumberOfShapes( HSSFPatriarch patriarch )
874 // {
875 // return patriarch.countOfAllChildren();
876 // }
877
878 private static short sid( List records, int loc )
879 {
880 return ( (Record) records.get( loc ) ).getSid();
881 }
882
883
884 }