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
21 package org.apache.poi.hslf.usermodel;
22
23 import java.util;
24 import java.awt.Dimension;
25 import java.io;
26
27 import org.apache.poi.ddf;
28 import org.apache.poi.hslf;
29 import org.apache.poi.hslf.model;
30 import org.apache.poi.hslf.model.Notes;
31 import org.apache.poi.hslf.model.Slide;
32 import org.apache.poi.hslf.record.SlideListWithText;
33 import org.apache.poi.hslf.record;
34 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
35 import org.apache.poi.hslf.exceptions.HSLFException;
36 import org.apache.poi.util.ArrayUtil;
37 import org.apache.poi.util.POILogFactory;
38 import org.apache.poi.util.POILogger;
39
40 /**
41 * This class is a friendly wrapper on top of the more scary HSLFSlideShow.
42 *
43 * TODO:
44 * - figure out how to match notes to their correct sheet
45 * (will involve understanding DocSlideList and DocNotesList)
46 * - handle Slide creation cleaner
47 *
48 * @author Nick Burch
49 * @author Yegor kozlov
50 */
51
52 public class SlideShow
53 {
54 // What we're based on
55 private HSLFSlideShow _hslfSlideShow;
56
57 // Low level contents, as taken from HSLFSlideShow
58 private Record[] _records;
59
60 // Pointers to the most recent versions of the core records
61 // (Document, Notes, Slide etc)
62 private Record[] _mostRecentCoreRecords;
63 // Lookup between the PersitPtr "sheet" IDs, and the position
64 // in the mostRecentCoreRecords array
65 private Hashtable _sheetIdToCoreRecordsLookup;
66
67 // Records that are interesting
68 private Document _documentRecord;
69
70 // Friendly objects for people to deal with
71 private SlideMaster[] _masters;
72 private TitleMaster[] _titleMasters;
73 private Slide[] _slides;
74 private Notes[] _notes;
75 private FontCollection _fonts;
76
77 // For logging
78 private POILogger logger = POILogFactory.getLogger(this.getClass());
79
80
81 /* ===============================================================
82 * Setup Code
83 * ===============================================================
84 */
85
86
87 /**
88 * Constructs a Powerpoint document from the underlying
89 * HSLFSlideShow object. Finds the model stuff from this
90 *
91 * @param hslfSlideShow the HSLFSlideShow to base on
92 */
93 public SlideShow(HSLFSlideShow hslfSlideShow) throws IOException
94 {
95 // Get useful things from our base slideshow
96 _hslfSlideShow = hslfSlideShow;
97 _records = _hslfSlideShow.getRecords();
98
99 // Handle Parent-aware Reocrds
100 for(int i=0; i<_records.length; i++) {
101 handleParentAwareRecords(_records[i]);
102 }
103
104 // Find the versions of the core records we'll want to use
105 findMostRecentCoreRecords();
106
107 // Build up the model level Slides and Notes
108 buildSlidesAndNotes();
109 }
110
111 /**
112 * Constructs a new, empty, Powerpoint document.
113 */
114 public SlideShow() throws IOException {
115 this(new HSLFSlideShow());
116 }
117
118 /**
119 * Constructs a Powerpoint document from an input stream.
120 */
121 public SlideShow(InputStream inputStream) throws IOException {
122 this(new HSLFSlideShow(inputStream));
123 }
124
125 /**
126 * Find the records that are parent-aware, and tell them
127 * who their parent is
128 */
129 private void handleParentAwareRecords(Record baseRecord) {
130 // Only need to do something if this is a container record
131 if(baseRecord instanceof RecordContainer) {
132 RecordContainer br = (RecordContainer)baseRecord;
133 Record[] childRecords = br.getChildRecords();
134
135 // Loop over child records, looking for interesting ones
136 for(int i=0; i<childRecords.length; i++) {
137 Record record = childRecords[i];
138 // Tell parent aware records of their parent
139 if(record instanceof ParentAwareRecord) {
140 ((ParentAwareRecord)record).setParentRecord(br);
141 }
142 // Walk on down for the case of container records
143 if(record instanceof RecordContainer) {
144 handleParentAwareRecords(record);
145 }
146 }
147 }
148 }
149
150
151 /**
152 * Use the PersistPtrHolder entries to figure out what is
153 * the "most recent" version of all the core records
154 * (Document, Notes, Slide etc), and save a record of them.
155 * Do this by walking from the oldest PersistPtr to the newest,
156 * overwriting any references found along the way with newer ones
157 */
158 private void findMostRecentCoreRecords() {
159 // To start with, find the most recent in the byte offset domain
160 Hashtable mostRecentByBytes = new Hashtable();
161 for(int i=0; i<_records.length; i++) {
162 if(_records[i] instanceof PersistPtrHolder) {
163 PersistPtrHolder pph = (PersistPtrHolder)_records[i];
164
165 // If we've already seen any of the "slide" IDs for this
166 // PersistPtr, remove their old positions
167 int[] ids = pph.getKnownSlideIDs();
168 for(int j=0; j<ids.length; j++) {
169 Integer id = new Integer(ids[j]);
170 if( mostRecentByBytes.containsKey(id)) {
171 mostRecentByBytes.remove(id);
172 }
173 }
174
175 // Now, update the byte level locations with their latest values
176 Hashtable thisSetOfLocations = pph.getSlideLocationsLookup();
177 for(int j=0; j<ids.length; j++) {
178 Integer id = new Integer(ids[j]);
179 mostRecentByBytes.put(id, thisSetOfLocations.get(id));
180 }
181 }
182 }
183
184 // We now know how many unique special records we have, so init
185 // the array
186 _mostRecentCoreRecords = new Record[mostRecentByBytes.size()];
187
188 // We'll also want to be able to turn the slide IDs into a position
189 // in this array
190 _sheetIdToCoreRecordsLookup = new Hashtable();
191 int[] allIDs = new int[_mostRecentCoreRecords.length];
192 Enumeration ids = mostRecentByBytes.keys();
193 for(int i=0; i<allIDs.length; i++) {
194 Integer id = (Integer)ids.nextElement();
195 allIDs[i] = id.intValue();
196 }
197 Arrays.sort(allIDs);
198 for(int i=0; i<allIDs.length; i++) {
199 _sheetIdToCoreRecordsLookup.put(new Integer(allIDs[i]), new Integer(i));
200 }
201
202 // Now convert the byte offsets back into record offsets
203 for(int i=0; i<_records.length; i++) {
204 if(_records[i] instanceof PositionDependentRecord) {
205 PositionDependentRecord pdr = (PositionDependentRecord)_records[i];
206 Integer recordAt = new Integer(pdr.getLastOnDiskOffset());
207
208 // Is it one we care about?
209 for(int j=0; j<allIDs.length; j++) {
210 Integer thisID = new Integer(allIDs[j]);
211 Integer thatRecordAt = (Integer)mostRecentByBytes.get(thisID);
212
213 if(thatRecordAt.equals(recordAt)) {
214 // Bingo. Now, where do we store it?
215 Integer storeAtI =
216 (Integer)_sheetIdToCoreRecordsLookup.get(thisID);
217 int storeAt = storeAtI.intValue();
218
219 // Tell it its Sheet ID, if it cares
220 if(pdr instanceof PositionDependentRecordContainer) {
221 PositionDependentRecordContainer pdrc =
222 (PositionDependentRecordContainer)_records[i];
223 pdrc.setSheetId(thisID.intValue());
224 }
225
226 // Finally, save the record
227 _mostRecentCoreRecords[storeAt] = _records[i];
228 }
229 }
230 }
231 }
232
233 // Now look for the interesting records in there
234 for(int i=0; i<_mostRecentCoreRecords.length; i++) {
235 // Check there really is a record at this number
236 if(_mostRecentCoreRecords[i] != null) {
237 // Find the Document, and interesting things in it
238 if(_mostRecentCoreRecords[i].getRecordType() == RecordTypes.Document.typeID) {
239 _documentRecord = (Document)_mostRecentCoreRecords[i];
240 _fonts = _documentRecord.getEnvironment().getFontCollection();
241 }
242 } else {
243 // No record at this number
244 // Odd, but not normally a problem
245 }
246 }
247 }
248
249 /**
250 * For a given SlideAtomsSet, return the core record, based on the refID from the
251 * SlidePersistAtom
252 */
253 private Record getCoreRecordForSAS(SlideAtomsSet sas) {
254 SlidePersistAtom spa = sas.getSlidePersistAtom();
255 int refID = spa.getRefID();
256 return getCoreRecordForRefID(refID);
257 }
258
259 /**
260 * For a given refID (the internal, 0 based numbering scheme), return the
261 * core record
262 * @param refID the refID
263 */
264 private Record getCoreRecordForRefID(int refID) {
265 Integer coreRecordId = (Integer)
266 _sheetIdToCoreRecordsLookup.get(new Integer(refID));
267 if(coreRecordId != null) {
268 Record r = _mostRecentCoreRecords[coreRecordId.intValue()];
269 return r;
270 } else {
271 logger.log(POILogger.ERROR, "We tried to look up a reference to a core record, but there was no core ID for reference ID " + refID);
272 return null;
273 }
274 }
275
276 /**
277 * Build up model level Slide and Notes objects, from the underlying
278 * records.
279 */
280 private void buildSlidesAndNotes() {
281 // Ensure we really found a Document record earlier
282 // If we didn't, then the file is probably corrupt
283 if(_documentRecord == null) {
284 throw new CorruptPowerPointFileException("The PowerPoint file didn't contain a Document Record in its PersistPtr blocks. It is probably corrupt.");
285 }
286
287
288 // Fetch the SlideListWithTexts in the most up-to-date Document Record
289 //
290 // As far as we understand it:
291 // * The first SlideListWithText will contain a SlideAtomsSet
292 // for each of the master slides
293 // * The second SlideListWithText will contain a SlideAtomsSet
294 // for each of the slides, in their current order
295 // These SlideAtomsSets will normally contain text
296 // * The third SlideListWithText (if present), will contain a
297 // SlideAtomsSet for each Notes
298 // These SlideAtomsSets will not normally contain text
299 //
300 // Having indentified the masters, slides and notes + their orders,
301 // we have to go and find their matching records
302 // We always use the latest versions of these records, and use the
303 // SlideAtom/NotesAtom to match them with the StyleAtomSet
304
305 SlideListWithText masterSLWT = _documentRecord.getMasterSlideListWithText();
306 SlideListWithText slidesSLWT = _documentRecord.getSlideSlideListWithText();
307 SlideListWithText notesSLWT = _documentRecord.getNotesSlideListWithText();
308
309 // Find master slides
310 // These can be MainMaster records, but oddly they can also be
311 // Slides or Notes, and possibly even other odd stuff....
312 // About the only thing you can say is that the master details are in
313 // the first SLWT.
314 SlideAtomsSet[] masterSets = new SlideAtomsSet[0];
315 if (masterSLWT != null){
316 masterSets = masterSLWT.getSlideAtomsSets();
317
318 ArrayList mmr = new ArrayList();
319 ArrayList tmr = new ArrayList();
320
321 for(int i=0; i<masterSets.length; i++) {
322 Record r = getCoreRecordForSAS(masterSets[i]);
323 SlideAtomsSet sas = masterSets[i];
324 int sheetNo = sas.getSlidePersistAtom().getSlideIdentifier();
325 if(r instanceof org.apache.poi.hslf.record.Slide) {
326 TitleMaster master = new TitleMaster((org.apache.poi.hslf.record.Slide)r, sheetNo);
327 master.setSlideShow(this);
328 tmr.add(master);
329 } else if(r instanceof org.apache.poi.hslf.record.MainMaster) {
330 SlideMaster master = new SlideMaster((org.apache.poi.hslf.record.MainMaster)r, sheetNo);
331 master.setSlideShow(this);
332 mmr.add(master);
333 }
334 }
335
336 _masters = new SlideMaster[mmr.size()];
337 mmr.toArray(_masters);
338
339 _titleMasters = new TitleMaster[tmr.size()];
340 tmr.toArray(_titleMasters);
341
342 }
343
344
345 // Having sorted out the masters, that leaves the notes and slides
346
347
348 // Start by finding the notes records to go with the entries in
349 // notesSLWT
350 org.apache.poi.hslf.record.Notes[] notesRecords;
351 SlideAtomsSet[] notesSets = new SlideAtomsSet[0];
352 Hashtable slideIdToNotes = new Hashtable();
353 if(notesSLWT == null) {
354 // None
355 notesRecords = new org.apache.poi.hslf.record.Notes[0];
356 } else {
357 // Match up the records and the SlideAtomSets
358 notesSets = notesSLWT.getSlideAtomsSets();
359 ArrayList notesRecordsL = new ArrayList();
360 for(int i=0; i<notesSets.length; i++) {
361 // Get the right core record
362 Record r = getCoreRecordForSAS(notesSets[i]);
363
364 // Ensure it really is a notes record
365 if(r instanceof org.apache.poi.hslf.record.Notes) {
366 org.apache.poi.hslf.record.Notes notesRecord = (org.apache.poi.hslf.record.Notes)r;
367 notesRecordsL.add( notesRecord );
368
369 // Record the match between slide id and these notes
370 SlidePersistAtom spa = notesSets[i].getSlidePersistAtom();
371 Integer slideId = new Integer(spa.getSlideIdentifier());
372 slideIdToNotes.put(slideId, new Integer(i));
373 } else {
374 logger.log(POILogger.ERROR, "A Notes SlideAtomSet at " + i + " said its record was at refID " + notesSets[i].getSlidePersistAtom().getRefID() + ", but that was actually a " + r);
375 }
376 }
377 notesRecords = new org.apache.poi.hslf.record.Notes[notesRecordsL.size()];
378 notesRecords = (org.apache.poi.hslf.record.Notes[])
379 notesRecordsL.toArray(notesRecords);
380 }
381
382 // Now, do the same thing for our slides
383 org.apache.poi.hslf.record.Slide[] slidesRecords;
384 SlideAtomsSet[] slidesSets = new SlideAtomsSet[0];
385 if(slidesSLWT == null) {
386 // None
387 slidesRecords = new org.apache.poi.hslf.record.Slide[0];
388 } else {
389 // Match up the records and the SlideAtomSets
390 slidesSets = slidesSLWT.getSlideAtomsSets();
391 slidesRecords = new org.apache.poi.hslf.record.Slide[slidesSets.length];
392 for(int i=0; i<slidesSets.length; i++) {
393 // Get the right core record
394 Record r = getCoreRecordForSAS(slidesSets[i]);
395
396 // Ensure it really is a slide record
397 if(r instanceof org.apache.poi.hslf.record.Slide) {
398 slidesRecords[i] = (org.apache.poi.hslf.record.Slide)r;
399 } else {
400 logger.log(POILogger.ERROR, "A Slide SlideAtomSet at " + i + " said its record was at refID " + slidesSets[i].getSlidePersistAtom().getRefID() + ", but that was actually a " + r);
401 }
402 }
403 }
404
405 // Finally, generate model objects for everything
406 // Notes first
407 _notes = new Notes[notesRecords.length];
408 for(int i=0; i<_notes.length; i++) {
409 _notes[i] = new Notes(notesRecords[i]);
410 _notes[i].setSlideShow(this);
411 }
412 // Then slides
413 _slides = new Slide[slidesRecords.length];
414 for(int i=0; i<_slides.length; i++) {
415 SlideAtomsSet sas = slidesSets[i];
416 int slideIdentifier = sas.getSlidePersistAtom().getSlideIdentifier();
417
418 // Do we have a notes for this?
419 Notes notes = null;
420 //Slide.SlideAtom.notesId references the corresponding notes slide. 0 if slide has no notes.
421 int noteId = slidesRecords[i].getSlideAtom().getNotesID();
422 if (noteId != 0){
423 Integer notesPos = (Integer)slideIdToNotes.get(new Integer(noteId));
424 if (notesPos != null) notes = _notes[notesPos.intValue()];
425 else logger.log(POILogger.ERROR, "Notes not found for noteId=" + noteId);
426 }
427
428 // Now, build our slide
429 _slides[i] = new Slide(slidesRecords[i], notes, sas, slideIdentifier, (i+1));
430 _slides[i].setSlideShow(this);
431 }
432 }
433
434 /**
435 * Writes out the slideshow file the is represented by an instance of
436 * this class
437 * @param out The OutputStream to write to.
438 * @throws IOException If there is an unexpected IOException from the passed
439 * in OutputStream
440 */
441 public void write(OutputStream out) throws IOException {
442 _hslfSlideShow.write(out);
443 }
444
445
446 /* ===============================================================
447 * Accessor Code
448 * ===============================================================
449 */
450
451
452 /**
453 * Returns an array of the most recent version of all the interesting
454 * records
455 */
456 public Record[] getMostRecentCoreRecords() { return _mostRecentCoreRecords; }
457
458 /**
459 * Returns an array of all the normal Slides found in the slideshow
460 */
461 public Slide[] getSlides() { return _slides; }
462
463 /**
464 * Returns an array of all the normal Notes found in the slideshow
465 */
466 public Notes[] getNotes() { return _notes; }
467
468 /**
469 * Returns an array of all the normal Slide Masters found in the slideshow
470 */
471 public SlideMaster[] getSlidesMasters() { return _masters; }
472
473 /**
474 * Returns an array of all the normal Title Masters found in the slideshow
475 */
476 public TitleMaster[] getTitleMasters() { return _titleMasters; }
477 /**
478 * Returns the data of all the pictures attached to the SlideShow
479 */
480 public PictureData[] getPictureData() {
481 return _hslfSlideShow.getPictures();
482 }
483
484 /**
485 * Returns the data of all the embedded OLE object in the SlideShow
486 */
487 public ObjectData[] getEmbeddedObjects() {
488 return _hslfSlideShow.getEmbeddedObjects();
489 }
490
491 /**
492 * Returns the data of all the embedded sounds in the SlideShow
493 */
494 public SoundData[] getSoundData() {
495 return SoundData.find(_documentRecord);
496 }
497
498 /**
499 * Return the current page size
500 */
501 public Dimension getPageSize(){
502 DocumentAtom docatom = _documentRecord.getDocumentAtom();
503 int pgx = (int)docatom.getSlideSizeX()*Shape.POINT_DPI/Shape.MASTER_DPI;
504 int pgy = (int)docatom.getSlideSizeY()*Shape.POINT_DPI/Shape.MASTER_DPI;
505 return new Dimension(pgx, pgy);
506 }
507
508 /**
509 * Change the current page size
510 *
511 * @param pgsize page size (in points)
512 */
513 public void setPageSize(Dimension pgsize){
514 DocumentAtom docatom = _documentRecord.getDocumentAtom();
515 docatom.setSlideSizeX(pgsize.width*Shape.MASTER_DPI/Shape.POINT_DPI);
516 docatom.setSlideSizeY(pgsize.height*Shape.MASTER_DPI/Shape.POINT_DPI);
517 }
518
519 /**
520 * Helper method for usermodel: Get the font collection
521 */
522 protected FontCollection getFontCollection() { return _fonts; }
523 /**
524 * Helper method for usermodel and model: Get the document record
525 */
526 public Document getDocumentRecord() { return _documentRecord; }
527
528
529 /* ===============================================================
530 * Re-ordering Code
531 * ===============================================================
532 */
533
534
535 /**
536 * Re-orders a slide, to a new position.
537 * @param oldSlideNumer The old slide number (1 based)
538 * @param newSlideNumber The new slide number (1 based)
539 */
540 public void reorderSlide(int oldSlideNumer, int newSlideNumber) {
541 // Ensure these numbers are valid
542 if(oldSlideNumer < 1 || newSlideNumber < 1) {
543 throw new IllegalArgumentException("Old and new slide numbers must be greater than 0");
544 }
545 if(oldSlideNumer > _slides.length || newSlideNumber > _slides.length) {
546 throw new IllegalArgumentException("Old and new slide numbers must not exceed the number of slides (" + _slides.length + ")");
547 }
548
549 // Shift the SlideAtomsSet
550 SlideListWithText slwt = _documentRecord.getSlideSlideListWithText();
551 slwt.repositionSlideAtomsSet(
552 slwt.getSlideAtomsSets()[(oldSlideNumer-1)],
553 (newSlideNumber-1)
554 );
555
556 // Re-order the slides
557 ArrayUtil.arrayMoveWithin(_slides, (oldSlideNumer-1), (newSlideNumber-1), 1);
558
559 // Tell the appropriate slides their new numbers
560 for(int i=0; i<_slides.length; i++) {
561 _slides[i].setSlideNumber( (i+1) );
562 }
563 }
564
565 /* ===============================================================
566 * Addition Code
567 * ===============================================================
568 */
569
570
571 /**
572 * Create a blank <code>Slide</code>.
573 *
574 * @return the created <code>Slide</code>
575 * @throws IOException
576 */
577 public Slide createSlide() throws IOException {
578 SlideListWithText slist = null;
579
580 // We need to add the records to the SLWT that deals
581 // with Slides.
582 // Add it, if it doesn't exist
583 slist = _documentRecord.getSlideSlideListWithText();
584 if(slist == null) {
585 // Need to add a new one
586 slist = new SlideListWithText();
587 _documentRecord.addSlideListWithText(slist);
588 }
589
590 // Grab the SlidePersistAtom with the highest Slide Number.
591 // (Will stay as null if no SlidePersistAtom exists yet in
592 // the slide, or only master slide's ones do)
593 SlidePersistAtom prev = null;
594 SlideAtomsSet[] sas = slist.getSlideAtomsSets();
595 for(int j=0; j<sas.length; j++) {
596 SlidePersistAtom spa = sas[j].getSlidePersistAtom();
597 if(spa.getSlideIdentifier() < 0) {
598 // This is for a master slide
599 // Odd, since we only deal with the Slide SLWT
600 } else {
601 // Must be for a real slide
602 if(prev == null) { prev = spa; }
603 if(prev.getSlideIdentifier() < spa.getSlideIdentifier()) {
604 prev = spa;
605 }
606 }
607 }
608
609 // Set up a new SlidePersistAtom for this slide
610 SlidePersistAtom sp = new SlidePersistAtom();
611
612 // First slideId is always 256
613 sp.setSlideIdentifier(prev == null ? 256 : (prev.getSlideIdentifier() + 1));
614
615 // Add this new SlidePersistAtom to the SlideListWithText
616 slist.addSlidePersistAtom(sp);
617
618
619 // Create a new Slide
620 Slide slide = new Slide(sp.getSlideIdentifier(), sp.getRefID(), _slides.length+1);
621 slide.setSlideShow(this);
622 slide.onCreate();
623
624 // Add in to the list of Slides
625 Slide[] s = new Slide[_slides.length+1];
626 System.arraycopy(_slides, 0, s, 0, _slides.length);
627 s[_slides.length] = slide;
628 _slides = s;
629 logger.log(POILogger.INFO, "Added slide " + _slides.length + " with ref " + sp.getRefID() + " and identifier " + sp.getSlideIdentifier());
630
631 // Add the core records for this new Slide to the record tree
632 org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord();
633 int slideRecordPos = _hslfSlideShow.appendRootLevelRecord(slideRecord);
634 _records = _hslfSlideShow.getRecords();
635
636
637 // Add the new Slide into the PersistPtr stuff
638 int offset = 0;
639 int slideOffset = 0;
640 PersistPtrHolder ptr = null;
641 UserEditAtom usr = null;
642 for (int i = 0; i < _records.length; i++) {
643 Record record = _records[i];
644 ByteArrayOutputStream out = new ByteArrayOutputStream();
645 record.writeOut(out);
646
647 // Grab interesting records as they come past
648 if(_records[i].getRecordType() == RecordTypes.PersistPtrIncrementalBlock.typeID){
649 ptr = (PersistPtrHolder)_records[i];
650 }
651 if(_records[i].getRecordType() == RecordTypes.UserEditAtom.typeID) {
652 usr = (UserEditAtom)_records[i];
653 }
654
655 if(i == slideRecordPos) {
656 slideOffset = offset;
657 }
658 offset += out.size();
659 }
660
661 // persist ID is UserEditAtom.maxPersistWritten + 1
662 int psrId = usr.getMaxPersistWritten() + 1;
663 sp.setRefID(psrId);
664 slideRecord.setSheetId(psrId);
665
666 // Last view is now of the slide
667 usr.setLastViewType((short)UserEditAtom.LAST_VIEW_SLIDE_VIEW);
668 usr.setMaxPersistWritten(psrId); //increment the number of persit objects
669
670 // Add the new slide into the last PersistPtr
671 // (Also need to tell it where it is)
672 slideRecord.setLastOnDiskOffset(slideOffset);
673 ptr.addSlideLookup(sp.getRefID(), slideOffset);
674 logger.log(POILogger.INFO, "New slide ended up at " + slideOffset);
675
676 // All done and added
677 return slide;
678 }
679
680
681 /**
682 * Adds a picture to this presentation and returns the associated index.
683 *
684 * @param data picture data
685 * @param format the format of the picture. One of constans defined in the <code>Picture</code> class.
686 * @return the index to this picture (1 based).
687 */
688 public int addPicture(byte[] data, int format) throws IOException {
689 byte[] uid = PictureData.getChecksum(data);
690
691 EscherContainerRecord bstore;
692 int offset = 0;
693
694 EscherContainerRecord dggContainer = _documentRecord.getPPDrawingGroup().getDggContainer();
695 bstore = (EscherContainerRecord)Shape.getEscherChild(dggContainer, EscherContainerRecord.BSTORE_CONTAINER);
696 if (bstore == null){
697 bstore = new EscherContainerRecord();
698 bstore.setRecordId( EscherContainerRecord.BSTORE_CONTAINER);
699
700 List child = dggContainer.getChildRecords();
701 for ( int i = 0; i < child.size(); i++ ) {
702 EscherRecord rec = (EscherRecord)child.get(i);
703 if (rec.getRecordId() == EscherOptRecord.RECORD_ID){
704 child.add(i, bstore);
705 i++;
706 }
707 }
708 dggContainer.setChildRecords(child);
709 } else {
710 List lst = bstore.getChildRecords();
711 for ( int i = 0; i < lst.size(); i++ ) {
712 EscherBSERecord bse = (EscherBSERecord) lst.get(i);
713 if (Arrays.equals(bse.getUid(), uid)){
714 return i + 1;
715 }
716 offset += bse.getSize();
717 }
718 }
719
720 PictureData pict = PictureData.create(format);
721 pict.setData(data);
722 pict.setOffset(offset);
723
724 EscherBSERecord bse = new EscherBSERecord();
725 bse.setRecordId(EscherBSERecord.RECORD_ID);
726 bse.setOptions( (short) ( 0x0002 | ( format << 4 ) ) );
727 bse.setSize(pict.getRawData().length + 8);
728 bse.setUid(uid);
729
730 bse.setBlipTypeMacOS((byte)format);
731 bse.setBlipTypeWin32((byte)format);
732
733 if (format == Picture.EMF) bse.setBlipTypeMacOS((byte)Picture.PICT);
734 else if (format == Picture.WMF) bse.setBlipTypeMacOS((byte)Picture.PICT);
735 else if (format == Picture.PICT) bse.setBlipTypeWin32((byte)Picture.WMF);
736
737 bse.setRef(0);
738 bse.setOffset(offset);
739
740 bstore.addChildRecord(bse);
741 int count = bstore.getChildRecords().size();
742 bstore.setOptions((short)( (count << 4) | 0xF ));
743
744 _hslfSlideShow.addPicture(pict);
745
746 return count;
747 }
748
749 /**
750 * Adds a picture to this presentation and returns the associated index.
751 *
752 * @param pict the file containing the image to add
753 * @param format the format of the picture. One of constans defined in the <code>Picture</code> class.
754 * @return the index to this picture (1 based).
755 */
756 public int addPicture(File pict, int format) throws IOException {
757 int length = (int)pict.length();
758 byte[] data = new byte[length];
759 try {
760 FileInputStream is = new FileInputStream(pict);
761 is.read(data);
762 is.close();
763 } catch (IOException e){
764 throw new HSLFException(e);
765 }
766 return addPicture(data, format);
767 }
768
769 /**
770 * Add a font in this presentation
771 *
772 * @param font the font to add
773 * @return 0-based index of the font
774 */
775 public int addFont(PPFont font) {
776 FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
777 int idx = fonts.getFontIndex(font.getFontName());
778 if(idx == -1){
779 idx = fonts.addFont(font.getFontName(), font.getCharSet(), font.getFontFlags(), font.getFontType(), font.getPitchAndFamily());
780 }
781 return idx;
782 }
783
784 /**
785 * Get a font by index
786 *
787 * @param idx 0-based index of the font
788 * @return of an instance of <code>PPFont</code> or <code>null</code> if not found
789 */
790 public PPFont getFont(int idx) {
791 PPFont font = null;
792 FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
793 Record[] ch = fonts.getChildRecords();
794 for (int i = 0; i < ch.length; i++) {
795 if(ch[i] instanceof FontEntityAtom) {
796 FontEntityAtom atom = (FontEntityAtom)ch[i];
797 if(atom.getFontIndex() == idx){
798 font = new PPFont(atom);
799 break;
800 }
801 }
802 }
803 return font;
804 }
805
806 /**
807 * get the number of fonts in the presentation
808 *
809 * @return number of fonts
810 */
811 public int getNumberOfFonts() {
812 return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts();
813 }
814 }