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.

HSLFSlideShow.java 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183
  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.usermodel;
  16. import java.awt.Dimension;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.Closeable;
  20. import java.io.File;
  21. import java.io.FileInputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. import org.apache.poi.hslf.record.MainMaster;
  32. import org.apache.poi.ddf.EscherBSERecord;
  33. import org.apache.poi.ddf.EscherContainerRecord;
  34. import org.apache.poi.ddf.EscherOptRecord;
  35. import org.apache.poi.hpsf.ClassID;
  36. import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
  37. import org.apache.poi.hslf.exceptions.HSLFException;
  38. import org.apache.poi.hslf.model.HeadersFooters;
  39. import org.apache.poi.hslf.model.MovieShape;
  40. import org.apache.poi.hslf.model.PPFont;
  41. import org.apache.poi.hslf.record.Document;
  42. import org.apache.poi.hslf.record.DocumentAtom;
  43. import org.apache.poi.hslf.record.ExAviMovie;
  44. import org.apache.poi.hslf.record.ExControl;
  45. import org.apache.poi.hslf.record.ExEmbed;
  46. import org.apache.poi.hslf.record.ExEmbedAtom;
  47. import org.apache.poi.hslf.record.ExHyperlink;
  48. import org.apache.poi.hslf.record.ExHyperlinkAtom;
  49. import org.apache.poi.hslf.record.ExMCIMovie;
  50. import org.apache.poi.hslf.record.ExObjList;
  51. import org.apache.poi.hslf.record.ExObjListAtom;
  52. import org.apache.poi.hslf.record.ExOleObjAtom;
  53. import org.apache.poi.hslf.record.ExOleObjStg;
  54. import org.apache.poi.hslf.record.ExVideoContainer;
  55. import org.apache.poi.hslf.record.FontCollection;
  56. import org.apache.poi.hslf.record.FontEntityAtom;
  57. import org.apache.poi.hslf.record.HeadersFootersContainer;
  58. import org.apache.poi.hslf.record.Notes;
  59. import org.apache.poi.hslf.record.PersistPtrHolder;
  60. import org.apache.poi.hslf.record.PositionDependentRecord;
  61. import org.apache.poi.hslf.record.PositionDependentRecordContainer;
  62. import org.apache.poi.hslf.record.Record;
  63. import org.apache.poi.hslf.record.RecordContainer;
  64. import org.apache.poi.hslf.record.RecordTypes;
  65. import org.apache.poi.hslf.record.Slide;
  66. import org.apache.poi.hslf.record.SlideListWithText;
  67. import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet;
  68. import org.apache.poi.hslf.record.SlidePersistAtom;
  69. import org.apache.poi.hslf.record.TxMasterStyleAtom;
  70. import org.apache.poi.hslf.record.UserEditAtom;
  71. import org.apache.poi.poifs.filesystem.DirectoryNode;
  72. import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
  73. import org.apache.poi.poifs.filesystem.POIFSFileSystem;
  74. import org.apache.poi.sl.usermodel.MasterSheet;
  75. import org.apache.poi.sl.usermodel.PictureData.PictureType;
  76. import org.apache.poi.sl.usermodel.Resources;
  77. import org.apache.poi.sl.usermodel.SlideShow;
  78. import org.apache.poi.util.POILogFactory;
  79. import org.apache.poi.util.POILogger;
  80. import org.apache.poi.util.Units;
  81. /**
  82. * This class is a friendly wrapper on top of the more scary HSLFSlideShow.
  83. *
  84. * TODO: - figure out how to match notes to their correct sheet (will involve
  85. * understanding DocSlideList and DocNotesList) - handle Slide creation cleaner
  86. *
  87. * @author Nick Burch
  88. * @author Yegor kozlov
  89. */
  90. public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagraph>, Closeable {
  91. enum LoadSavePhase {
  92. INIT, LOADED;
  93. }
  94. private static ThreadLocal<LoadSavePhase> loadSavePhase = new ThreadLocal<LoadSavePhase>();
  95. // What we're based on
  96. private HSLFSlideShowImpl _hslfSlideShow;
  97. // Pointers to the most recent versions of the core records
  98. // (Document, Notes, Slide etc)
  99. private Record[] _mostRecentCoreRecords;
  100. // Lookup between the PersitPtr "sheet" IDs, and the position
  101. // in the mostRecentCoreRecords array
  102. private Map<Integer,Integer> _sheetIdToCoreRecordsLookup;
  103. // Records that are interesting
  104. private Document _documentRecord;
  105. // Friendly objects for people to deal with
  106. private final List<HSLFSlideMaster> _masters = new ArrayList<HSLFSlideMaster>();
  107. private final List<HSLFTitleMaster> _titleMasters = new ArrayList<HSLFTitleMaster>();
  108. private final List<HSLFSlide> _slides = new ArrayList<HSLFSlide>();
  109. private final List<HSLFNotes> _notes = new ArrayList<HSLFNotes>();
  110. private FontCollection _fonts;
  111. // For logging
  112. private POILogger logger = POILogFactory.getLogger(this.getClass());
  113. /**
  114. * Constructs a Powerpoint document from the underlying
  115. * HSLFSlideShow object. Finds the model stuff from this
  116. *
  117. * @param hslfSlideShow the HSLFSlideShow to base on
  118. */
  119. public HSLFSlideShow(HSLFSlideShowImpl hslfSlideShow) {
  120. loadSavePhase.set(LoadSavePhase.INIT);
  121. // Get useful things from our base slideshow
  122. _hslfSlideShow = hslfSlideShow;
  123. // Handle Parent-aware Records
  124. for (Record record : _hslfSlideShow.getRecords()) {
  125. if(record instanceof RecordContainer){
  126. RecordContainer.handleParentAwareRecords((RecordContainer)record);
  127. }
  128. }
  129. // Find the versions of the core records we'll want to use
  130. findMostRecentCoreRecords();
  131. // Build up the model level Slides and Notes
  132. buildSlidesAndNotes();
  133. loadSavePhase.set(LoadSavePhase.LOADED);
  134. }
  135. /**
  136. * Constructs a new, empty, Powerpoint document.
  137. */
  138. public HSLFSlideShow() {
  139. this(HSLFSlideShowImpl.create());
  140. }
  141. /**
  142. * Constructs a Powerpoint document from an input stream.
  143. */
  144. @SuppressWarnings("resource")
  145. public HSLFSlideShow(InputStream inputStream) throws IOException {
  146. this(new HSLFSlideShowImpl(inputStream));
  147. }
  148. /**
  149. * Constructs a Powerpoint document from an POIFSFileSystem.
  150. */
  151. @SuppressWarnings("resource")
  152. public HSLFSlideShow(NPOIFSFileSystem npoifs) throws IOException {
  153. this(new HSLFSlideShowImpl(npoifs));
  154. }
  155. /**
  156. * Constructs a Powerpoint document from an DirectoryNode.
  157. */
  158. @SuppressWarnings("resource")
  159. public HSLFSlideShow(DirectoryNode root) throws IOException {
  160. this(new HSLFSlideShowImpl(root));
  161. }
  162. /**
  163. * @return the current loading/saving phase
  164. */
  165. protected static LoadSavePhase getLoadSavePhase() {
  166. return loadSavePhase.get();
  167. }
  168. /**
  169. * Use the PersistPtrHolder entries to figure out what is the "most recent"
  170. * version of all the core records (Document, Notes, Slide etc), and save a
  171. * record of them. Do this by walking from the oldest PersistPtr to the
  172. * newest, overwriting any references found along the way with newer ones
  173. */
  174. private void findMostRecentCoreRecords() {
  175. // To start with, find the most recent in the byte offset domain
  176. Map<Integer,Integer> mostRecentByBytes = new HashMap<Integer,Integer>();
  177. for (Record record : _hslfSlideShow.getRecords()) {
  178. if (record instanceof PersistPtrHolder) {
  179. PersistPtrHolder pph = (PersistPtrHolder) record;
  180. // If we've already seen any of the "slide" IDs for this
  181. // PersistPtr, remove their old positions
  182. int[] ids = pph.getKnownSlideIDs();
  183. for (int id : ids) {
  184. if (mostRecentByBytes.containsKey(id)) {
  185. mostRecentByBytes.remove(id);
  186. }
  187. }
  188. // Now, update the byte level locations with their latest values
  189. Map<Integer,Integer> thisSetOfLocations = pph.getSlideLocationsLookup();
  190. for (int id : ids) {
  191. mostRecentByBytes.put(id, thisSetOfLocations.get(id));
  192. }
  193. }
  194. }
  195. // We now know how many unique special records we have, so init
  196. // the array
  197. _mostRecentCoreRecords = new Record[mostRecentByBytes.size()];
  198. // We'll also want to be able to turn the slide IDs into a position
  199. // in this array
  200. _sheetIdToCoreRecordsLookup = new HashMap<Integer,Integer>();
  201. Integer[] allIDs = mostRecentByBytes.keySet().toArray(new Integer[mostRecentByBytes.size()]);
  202. Arrays.sort(allIDs);
  203. for (int i = 0; i < allIDs.length; i++) {
  204. _sheetIdToCoreRecordsLookup.put(allIDs[i], i);
  205. }
  206. Map<Integer,Integer> mostRecentByBytesRev = new HashMap<Integer,Integer>(mostRecentByBytes.size());
  207. for (Map.Entry<Integer,Integer> me : mostRecentByBytes.entrySet()) {
  208. mostRecentByBytesRev.put(me.getValue(), me.getKey());
  209. }
  210. // Now convert the byte offsets back into record offsets
  211. for (Record record : _hslfSlideShow.getRecords()) {
  212. if (!(record instanceof PositionDependentRecord)) continue;
  213. PositionDependentRecord pdr = (PositionDependentRecord) record;
  214. int recordAt = pdr.getLastOnDiskOffset();
  215. Integer thisID = mostRecentByBytesRev.get(recordAt);
  216. if (thisID == null) continue;
  217. // Bingo. Now, where do we store it?
  218. int storeAt = _sheetIdToCoreRecordsLookup.get(thisID);
  219. // Tell it its Sheet ID, if it cares
  220. if (pdr instanceof PositionDependentRecordContainer) {
  221. PositionDependentRecordContainer pdrc = (PositionDependentRecordContainer) record;
  222. pdrc.setSheetId(thisID);
  223. }
  224. // Finally, save the record
  225. _mostRecentCoreRecords[storeAt] = record;
  226. }
  227. // Now look for the interesting records in there
  228. for (Record record : _mostRecentCoreRecords) {
  229. // Check there really is a record at this number
  230. if (record != null) {
  231. // Find the Document, and interesting things in it
  232. if (record.getRecordType() == RecordTypes.Document.typeID) {
  233. _documentRecord = (Document) record;
  234. _fonts = _documentRecord.getEnvironment().getFontCollection();
  235. }
  236. } else {
  237. // No record at this number
  238. // Odd, but not normally a problem
  239. }
  240. }
  241. }
  242. /**
  243. * For a given SlideAtomsSet, return the core record, based on the refID
  244. * from the SlidePersistAtom
  245. */
  246. private Record getCoreRecordForSAS(SlideAtomsSet sas) {
  247. SlidePersistAtom spa = sas.getSlidePersistAtom();
  248. int refID = spa.getRefID();
  249. return getCoreRecordForRefID(refID);
  250. }
  251. /**
  252. * For a given refID (the internal, 0 based numbering scheme), return the
  253. * core record
  254. *
  255. * @param refID
  256. * the refID
  257. */
  258. private Record getCoreRecordForRefID(int refID) {
  259. Integer coreRecordId = _sheetIdToCoreRecordsLookup.get(refID);
  260. if (coreRecordId != null) {
  261. Record r = _mostRecentCoreRecords[coreRecordId];
  262. return r;
  263. }
  264. logger.log(POILogger.ERROR,
  265. "We tried to look up a reference to a core record, but there was no core ID for reference ID "
  266. + refID);
  267. return null;
  268. }
  269. /**
  270. * Build up model level Slide and Notes objects, from the underlying
  271. * records.
  272. */
  273. private void buildSlidesAndNotes() {
  274. // Ensure we really found a Document record earlier
  275. // If we didn't, then the file is probably corrupt
  276. if (_documentRecord == null) {
  277. throw new CorruptPowerPointFileException(
  278. "The PowerPoint file didn't contain a Document Record in its PersistPtr blocks. It is probably corrupt.");
  279. }
  280. // Fetch the SlideListWithTexts in the most up-to-date Document Record
  281. //
  282. // As far as we understand it:
  283. // * The first SlideListWithText will contain a SlideAtomsSet
  284. // for each of the master slides
  285. // * The second SlideListWithText will contain a SlideAtomsSet
  286. // for each of the slides, in their current order
  287. // These SlideAtomsSets will normally contain text
  288. // * The third SlideListWithText (if present), will contain a
  289. // SlideAtomsSet for each Notes
  290. // These SlideAtomsSets will not normally contain text
  291. //
  292. // Having indentified the masters, slides and notes + their orders,
  293. // we have to go and find their matching records
  294. // We always use the latest versions of these records, and use the
  295. // SlideAtom/NotesAtom to match them with the StyleAtomSet
  296. findMasterSlides();
  297. // Having sorted out the masters, that leaves the notes and slides
  298. Map<Integer,Integer> slideIdToNotes = new HashMap<Integer,Integer>();
  299. // Start by finding the notes records
  300. findNotesSlides(slideIdToNotes);
  301. // Now, do the same thing for our slides
  302. findSlides(slideIdToNotes);
  303. }
  304. /**
  305. * Find master slides
  306. * These can be MainMaster records, but oddly they can also be
  307. * Slides or Notes, and possibly even other odd stuff....
  308. * About the only thing you can say is that the master details are in the first SLWT.
  309. */
  310. private void findMasterSlides() {
  311. SlideListWithText masterSLWT = _documentRecord.getMasterSlideListWithText();
  312. if (masterSLWT == null) {
  313. return;
  314. }
  315. for (SlideAtomsSet sas : masterSLWT.getSlideAtomsSets()) {
  316. Record r = getCoreRecordForSAS(sas);
  317. int sheetNo = sas.getSlidePersistAtom().getSlideIdentifier();
  318. if (r instanceof Slide) {
  319. HSLFTitleMaster master = new HSLFTitleMaster((Slide)r, sheetNo);
  320. master.setSlideShow(this);
  321. _titleMasters.add(master);
  322. } else if (r instanceof MainMaster) {
  323. HSLFSlideMaster master = new HSLFSlideMaster((MainMaster)r, sheetNo);
  324. master.setSlideShow(this);
  325. _masters.add(master);
  326. }
  327. }
  328. }
  329. private void findNotesSlides(Map<Integer,Integer> slideIdToNotes) {
  330. SlideListWithText notesSLWT = _documentRecord.getNotesSlideListWithText();
  331. if (notesSLWT == null) {
  332. return;
  333. }
  334. // Match up the records and the SlideAtomSets
  335. int idx = -1;
  336. for (SlideAtomsSet notesSet : notesSLWT.getSlideAtomsSets()) {
  337. idx++;
  338. // Get the right core record
  339. Record r = getCoreRecordForSAS(notesSet);
  340. SlidePersistAtom spa = notesSet.getSlidePersistAtom();
  341. String loggerLoc = "A Notes SlideAtomSet at "+idx+" said its record was at refID "+spa.getRefID();
  342. // we need to add null-records, otherwise the index references to other existing don't work anymore
  343. if (r == null) {
  344. logger.log(POILogger.WARN, loggerLoc+", but that record didn't exist - record ignored.");
  345. continue;
  346. }
  347. // Ensure it really is a notes record
  348. if (!(r instanceof Notes)) {
  349. logger.log(POILogger.ERROR, loggerLoc+", but that was actually a " + r);
  350. continue;
  351. }
  352. Notes notesRecord = (Notes) r;
  353. // Record the match between slide id and these notes
  354. int slideId = spa.getSlideIdentifier();
  355. slideIdToNotes.put(slideId, idx);
  356. HSLFNotes hn = new HSLFNotes(notesRecord);
  357. hn.setSlideShow(this);
  358. _notes.add(hn);
  359. }
  360. }
  361. private void findSlides(Map<Integer,Integer> slideIdToNotes) {
  362. SlideListWithText slidesSLWT = _documentRecord.getSlideSlideListWithText();
  363. if (slidesSLWT == null) {
  364. return;
  365. }
  366. // Match up the records and the SlideAtomSets
  367. int idx = -1;
  368. for (SlideAtomsSet sas : slidesSLWT.getSlideAtomsSets()) {
  369. idx++;
  370. // Get the right core record
  371. SlidePersistAtom spa = sas.getSlidePersistAtom();
  372. Record r = getCoreRecordForSAS(sas);
  373. // Ensure it really is a slide record
  374. if (!(r instanceof Slide)) {
  375. logger.log(POILogger.ERROR, "A Slide SlideAtomSet at " + idx
  376. + " said its record was at refID "
  377. + spa.getRefID()
  378. + ", but that was actually a " + r);
  379. continue;
  380. }
  381. Slide slide = (Slide)r;
  382. // Do we have a notes for this?
  383. HSLFNotes notes = null;
  384. // Slide.SlideAtom.notesId references the corresponding notes slide.
  385. // 0 if slide has no notes.
  386. int noteId = slide.getSlideAtom().getNotesID();
  387. if (noteId != 0) {
  388. Integer notesPos = slideIdToNotes.get(noteId);
  389. if (notesPos != null && 0 <= notesPos && notesPos < _notes.size()) {
  390. notes = _notes.get(notesPos);
  391. } else {
  392. logger.log(POILogger.ERROR, "Notes not found for noteId=" + noteId);
  393. }
  394. }
  395. // Now, build our slide
  396. int slideIdentifier = spa.getSlideIdentifier();
  397. HSLFSlide hs = new HSLFSlide(slide, notes, sas, slideIdentifier, (idx + 1));
  398. hs.setSlideShow(this);
  399. _slides.add(hs);
  400. }
  401. }
  402. @Override
  403. public void write(OutputStream out) throws IOException {
  404. // check for text paragraph modifications
  405. for (HSLFSlide sl : getSlides()) {
  406. writeDirtyParagraphs(sl);
  407. }
  408. for (HSLFSlideMaster sl : getSlideMasters()) {
  409. boolean isDirty = false;
  410. for (List<HSLFTextParagraph> paras : sl.getTextParagraphs()) {
  411. for (HSLFTextParagraph p : paras) {
  412. isDirty |= p.isDirty();
  413. }
  414. }
  415. if (isDirty) {
  416. for (TxMasterStyleAtom sa : sl.getTxMasterStyleAtoms()) {
  417. if (sa != null) {
  418. // not all master style atoms are set - index 3 is typically null
  419. sa.updateStyles();
  420. }
  421. }
  422. }
  423. }
  424. _hslfSlideShow.write(out);
  425. }
  426. private void writeDirtyParagraphs(HSLFShapeContainer container) {
  427. for (HSLFShape sh : container.getShapes()) {
  428. if (sh instanceof HSLFShapeContainer) {
  429. writeDirtyParagraphs((HSLFShapeContainer)sh);
  430. } else if (sh instanceof HSLFTextShape) {
  431. HSLFTextShape hts = (HSLFTextShape)sh;
  432. boolean isDirty = false;
  433. for (HSLFTextParagraph p : hts.getTextParagraphs()) {
  434. isDirty |= p.isDirty();
  435. }
  436. if (isDirty) hts.storeText();
  437. }
  438. }
  439. }
  440. /**
  441. * Returns an array of the most recent version of all the interesting
  442. * records
  443. */
  444. public Record[] getMostRecentCoreRecords() {
  445. return _mostRecentCoreRecords;
  446. }
  447. /**
  448. * Returns an array of all the normal Slides found in the slideshow
  449. */
  450. @Override
  451. public List<HSLFSlide> getSlides() {
  452. return _slides;
  453. }
  454. /**
  455. * Returns an array of all the normal Notes found in the slideshow
  456. */
  457. public List<HSLFNotes> getNotes() {
  458. return _notes;
  459. }
  460. /**
  461. * Returns an array of all the normal Slide Masters found in the slideshow
  462. */
  463. @Override
  464. public List<HSLFSlideMaster> getSlideMasters() {
  465. return _masters;
  466. }
  467. /**
  468. * Returns an array of all the normal Title Masters found in the slideshow
  469. */
  470. public List<HSLFTitleMaster> getTitleMasters() {
  471. return _titleMasters;
  472. }
  473. @Override
  474. public List<HSLFPictureData> getPictureData() {
  475. return _hslfSlideShow.getPictureData();
  476. }
  477. /**
  478. * Returns the data of all the embedded OLE object in the SlideShow
  479. */
  480. public HSLFObjectData[] getEmbeddedObjects() {
  481. return _hslfSlideShow.getEmbeddedObjects();
  482. }
  483. /**
  484. * Returns the data of all the embedded sounds in the SlideShow
  485. */
  486. public HSLFSoundData[] getSoundData() {
  487. return HSLFSoundData.find(_documentRecord);
  488. }
  489. @Override
  490. public Dimension getPageSize() {
  491. DocumentAtom docatom = _documentRecord.getDocumentAtom();
  492. int pgx = (int)Units.masterToPoints((int)docatom.getSlideSizeX());
  493. int pgy = (int)Units.masterToPoints((int)docatom.getSlideSizeY());
  494. return new Dimension(pgx, pgy);
  495. }
  496. @Override
  497. public void setPageSize(Dimension pgsize) {
  498. DocumentAtom docatom = _documentRecord.getDocumentAtom();
  499. docatom.setSlideSizeX(Units.pointsToMaster(pgsize.width));
  500. docatom.setSlideSizeY(Units.pointsToMaster(pgsize.height));
  501. }
  502. /**
  503. * Helper method for usermodel: Get the font collection
  504. */
  505. protected FontCollection getFontCollection() {
  506. return _fonts;
  507. }
  508. /**
  509. * Helper method for usermodel and model: Get the document record
  510. */
  511. public Document getDocumentRecord() {
  512. return _documentRecord;
  513. }
  514. /**
  515. * Re-orders a slide, to a new position.
  516. *
  517. * @param oldSlideNumber
  518. * The old slide number (1 based)
  519. * @param newSlideNumber
  520. * The new slide number (1 based)
  521. */
  522. public void reorderSlide(int oldSlideNumber, int newSlideNumber) {
  523. // Ensure these numbers are valid
  524. if (oldSlideNumber < 1 || newSlideNumber < 1) {
  525. throw new IllegalArgumentException("Old and new slide numbers must be greater than 0");
  526. }
  527. if (oldSlideNumber > _slides.size() || newSlideNumber > _slides.size()) {
  528. throw new IllegalArgumentException(
  529. "Old and new slide numbers must not exceed the number of slides ("
  530. + _slides.size() + ")");
  531. }
  532. // The order of slides is defined by the order of slide atom sets in the
  533. // SlideListWithText container.
  534. SlideListWithText slwt = _documentRecord.getSlideSlideListWithText();
  535. SlideAtomsSet[] sas = slwt.getSlideAtomsSets();
  536. SlideAtomsSet tmp = sas[oldSlideNumber - 1];
  537. sas[oldSlideNumber - 1] = sas[newSlideNumber - 1];
  538. sas[newSlideNumber - 1] = tmp;
  539. Collections.swap(_slides, oldSlideNumber - 1, newSlideNumber - 1);
  540. _slides.get(newSlideNumber - 1).setSlideNumber(newSlideNumber);
  541. _slides.get(oldSlideNumber - 1).setSlideNumber(oldSlideNumber);
  542. ArrayList<Record> lst = new ArrayList<Record>();
  543. for (SlideAtomsSet s : sas) {
  544. lst.add(s.getSlidePersistAtom());
  545. lst.addAll(Arrays.asList(s.getSlideRecords()));
  546. }
  547. Record[] r = lst.toArray(new Record[lst.size()]);
  548. slwt.setChildRecord(r);
  549. }
  550. /**
  551. * Removes the slide at the given index (0-based).
  552. * <p>
  553. * Shifts any subsequent slides to the left (subtracts one from their slide
  554. * numbers).
  555. * </p>
  556. *
  557. * @param index
  558. * the index of the slide to remove (0-based)
  559. * @return the slide that was removed from the slide show.
  560. */
  561. public HSLFSlide removeSlide(int index) {
  562. int lastSlideIdx = _slides.size() - 1;
  563. if (index < 0 || index > lastSlideIdx) {
  564. throw new IllegalArgumentException("Slide index (" + index + ") is out of range (0.."
  565. + lastSlideIdx + ")");
  566. }
  567. SlideListWithText slwt = _documentRecord.getSlideSlideListWithText();
  568. SlideAtomsSet[] sas = slwt.getSlideAtomsSets();
  569. List<Record> records = new ArrayList<Record>();
  570. List<SlideAtomsSet> sa = new ArrayList<SlideAtomsSet>(Arrays.asList(sas));
  571. HSLFSlide removedSlide = _slides.remove(index);
  572. _notes.remove(removedSlide.getNotes());
  573. sa.remove(index);
  574. int i=0;
  575. for (HSLFSlide s : _slides) s.setSlideNumber(i++);
  576. for (SlideAtomsSet s : sa) {
  577. records.add(s.getSlidePersistAtom());
  578. records.addAll(Arrays.asList(s.getSlideRecords()));
  579. }
  580. if (sa.isEmpty()) {
  581. _documentRecord.removeSlideListWithText(slwt);
  582. } else {
  583. slwt.setSlideAtomsSets(sa.toArray(new SlideAtomsSet[sa.size()]));
  584. slwt.setChildRecord(records.toArray(new Record[records.size()]));
  585. }
  586. // if the removed slide had notes - remove references to them too
  587. int notesId = removedSlide.getSlideRecord().getSlideAtom().getNotesID();
  588. if (notesId != 0) {
  589. SlideListWithText nslwt = _documentRecord.getNotesSlideListWithText();
  590. records = new ArrayList<Record>();
  591. ArrayList<SlideAtomsSet> na = new ArrayList<SlideAtomsSet>();
  592. for (SlideAtomsSet ns : nslwt.getSlideAtomsSets()) {
  593. if (ns.getSlidePersistAtom().getSlideIdentifier() == notesId) continue;
  594. na.add(ns);
  595. records.add(ns.getSlidePersistAtom());
  596. if (ns.getSlideRecords() != null) {
  597. records.addAll(Arrays.asList(ns.getSlideRecords()));
  598. }
  599. }
  600. if (na.isEmpty()) {
  601. _documentRecord.removeSlideListWithText(nslwt);
  602. } else {
  603. nslwt.setSlideAtomsSets(na.toArray(new SlideAtomsSet[na.size()]));
  604. nslwt.setChildRecord(records.toArray(new Record[records.size()]));
  605. }
  606. }
  607. return removedSlide;
  608. }
  609. /**
  610. * Create a blank <code>Slide</code>.
  611. *
  612. * @return the created <code>Slide</code>
  613. */
  614. @Override
  615. public HSLFSlide createSlide() {
  616. SlideListWithText slist = null;
  617. // We need to add the records to the SLWT that deals
  618. // with Slides.
  619. // Add it, if it doesn't exist
  620. slist = _documentRecord.getSlideSlideListWithText();
  621. if (slist == null) {
  622. // Need to add a new one
  623. slist = new SlideListWithText();
  624. slist.setInstance(SlideListWithText.SLIDES);
  625. _documentRecord.addSlideListWithText(slist);
  626. }
  627. // Grab the SlidePersistAtom with the highest Slide Number.
  628. // (Will stay as null if no SlidePersistAtom exists yet in
  629. // the slide, or only master slide's ones do)
  630. SlidePersistAtom prev = null;
  631. for (SlideAtomsSet sas : slist.getSlideAtomsSets()) {
  632. SlidePersistAtom spa = sas.getSlidePersistAtom();
  633. if (spa.getSlideIdentifier() < 0) {
  634. // This is for a master slide
  635. // Odd, since we only deal with the Slide SLWT
  636. } else {
  637. // Must be for a real slide
  638. if (prev == null) {
  639. prev = spa;
  640. }
  641. if (prev.getSlideIdentifier() < spa.getSlideIdentifier()) {
  642. prev = spa;
  643. }
  644. }
  645. }
  646. // Set up a new SlidePersistAtom for this slide
  647. SlidePersistAtom sp = new SlidePersistAtom();
  648. // First slideId is always 256
  649. sp.setSlideIdentifier(prev == null ? 256 : (prev.getSlideIdentifier() + 1));
  650. // Add this new SlidePersistAtom to the SlideListWithText
  651. slist.addSlidePersistAtom(sp);
  652. // Create a new Slide
  653. HSLFSlide slide = new HSLFSlide(sp.getSlideIdentifier(), sp.getRefID(), _slides.size() + 1);
  654. slide.setSlideShow(this);
  655. slide.onCreate();
  656. // Add in to the list of Slides
  657. _slides.add(slide);
  658. logger.log(POILogger.INFO, "Added slide " + _slides.size() + " with ref " + sp.getRefID()
  659. + " and identifier " + sp.getSlideIdentifier());
  660. // Add the core records for this new Slide to the record tree
  661. Slide slideRecord = slide.getSlideRecord();
  662. int psrId = addPersistentObject(slideRecord);
  663. sp.setRefID(psrId);
  664. slideRecord.setSheetId(psrId);
  665. slide.setMasterSheet(_masters.get(0));
  666. // All done and added
  667. return slide;
  668. }
  669. @Override
  670. public HSLFPictureData addPicture(byte[] data, PictureType format) throws IOException {
  671. if (format == null || format.nativeId == -1) {
  672. throw new IllegalArgumentException("Unsupported picture format: " + format);
  673. }
  674. byte[] uid = HSLFPictureData.getChecksum(data);
  675. for (HSLFPictureData pd : getPictureData()) {
  676. if (Arrays.equals(pd.getUID(), uid)) {
  677. return pd;
  678. }
  679. }
  680. EscherContainerRecord bstore;
  681. EscherContainerRecord dggContainer = _documentRecord.getPPDrawingGroup().getDggContainer();
  682. bstore = (EscherContainerRecord) HSLFShape.getEscherChild(dggContainer,
  683. EscherContainerRecord.BSTORE_CONTAINER);
  684. if (bstore == null) {
  685. bstore = new EscherContainerRecord();
  686. bstore.setRecordId(EscherContainerRecord.BSTORE_CONTAINER);
  687. dggContainer.addChildBefore(bstore, EscherOptRecord.RECORD_ID);
  688. }
  689. HSLFPictureData pict = HSLFPictureData.create(format);
  690. pict.setData(data);
  691. int offset = _hslfSlideShow.addPicture(pict);
  692. EscherBSERecord bse = new EscherBSERecord();
  693. bse.setRecordId(EscherBSERecord.RECORD_ID);
  694. bse.setOptions((short) (0x0002 | (format.nativeId << 4)));
  695. bse.setSize(pict.getRawData().length + 8);
  696. bse.setUid(uid);
  697. bse.setBlipTypeMacOS((byte) format.nativeId);
  698. bse.setBlipTypeWin32((byte) format.nativeId);
  699. if (format == PictureType.EMF) {
  700. bse.setBlipTypeMacOS((byte) PictureType.PICT.nativeId);
  701. } else if (format == PictureType.WMF) {
  702. bse.setBlipTypeMacOS((byte) PictureType.PICT.nativeId);
  703. } else if (format == PictureType.PICT) {
  704. bse.setBlipTypeWin32((byte) PictureType.WMF.nativeId);
  705. }
  706. bse.setRef(0);
  707. bse.setOffset(offset);
  708. bse.setRemainingData(new byte[0]);
  709. bstore.addChildRecord(bse);
  710. int count = bstore.getChildRecords().size();
  711. bstore.setOptions((short) ((count << 4) | 0xF));
  712. return pict;
  713. }
  714. /**
  715. * Adds a picture to this presentation and returns the associated index.
  716. *
  717. * @param pict
  718. * the file containing the image to add
  719. * @param format
  720. * the format of the picture. One of constans defined in the
  721. * <code>Picture</code> class.
  722. * @return the index to this picture (1 based).
  723. */
  724. public HSLFPictureData addPicture(File pict, PictureType format) throws IOException {
  725. int length = (int) pict.length();
  726. byte[] data = new byte[length];
  727. FileInputStream is = null;
  728. try {
  729. is = new FileInputStream(pict);
  730. is.read(data);
  731. } finally {
  732. if(is != null) is.close();
  733. }
  734. return addPicture(data, format);
  735. }
  736. /**
  737. * Add a font in this presentation
  738. *
  739. * @param font
  740. * the font to add
  741. * @return 0-based index of the font
  742. */
  743. public int addFont(PPFont font) {
  744. FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
  745. int idx = fonts.getFontIndex(font.getFontName());
  746. if (idx == -1) {
  747. idx = fonts.addFont(font.getFontName(), font.getCharSet(), font.getFontFlags(), font
  748. .getFontType(), font.getPitchAndFamily());
  749. }
  750. return idx;
  751. }
  752. /**
  753. * Get a font by index
  754. *
  755. * @param idx
  756. * 0-based index of the font
  757. * @return of an instance of <code>PPFont</code> or <code>null</code> if not
  758. * found
  759. */
  760. public PPFont getFont(int idx) {
  761. FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
  762. for (Record ch : fonts.getChildRecords()) {
  763. if (ch instanceof FontEntityAtom) {
  764. FontEntityAtom atom = (FontEntityAtom) ch;
  765. if (atom.getFontIndex() == idx) {
  766. return new PPFont(atom);
  767. }
  768. }
  769. }
  770. return null;
  771. }
  772. /**
  773. * get the number of fonts in the presentation
  774. *
  775. * @return number of fonts
  776. */
  777. public int getNumberOfFonts() {
  778. return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts();
  779. }
  780. /**
  781. * Return Header / Footer settings for slides
  782. *
  783. * @return Header / Footer settings for slides
  784. */
  785. public HeadersFooters getSlideHeadersFooters() {
  786. // detect if this ppt was saved in Office2007
  787. String tag = getSlideMasters().get(0).getProgrammableTag();
  788. boolean ppt2007 = "___PPT12".equals(tag);
  789. HeadersFootersContainer hdd = null;
  790. for (Record ch : _documentRecord.getChildRecords()) {
  791. if (ch instanceof HeadersFootersContainer
  792. && ((HeadersFootersContainer) ch).getOptions() == HeadersFootersContainer.SlideHeadersFootersContainer) {
  793. hdd = (HeadersFootersContainer) ch;
  794. break;
  795. }
  796. }
  797. boolean newRecord = false;
  798. if (hdd == null) {
  799. hdd = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer);
  800. newRecord = true;
  801. }
  802. return new HeadersFooters(hdd, this, newRecord, ppt2007);
  803. }
  804. /**
  805. * Return Header / Footer settings for notes
  806. *
  807. * @return Header / Footer settings for notes
  808. */
  809. public HeadersFooters getNotesHeadersFooters() {
  810. // detect if this ppt was saved in Office2007
  811. String tag = getSlideMasters().get(0).getProgrammableTag();
  812. boolean ppt2007 = "___PPT12".equals(tag);
  813. HeadersFootersContainer hdd = null;
  814. for (Record ch : _documentRecord.getChildRecords()) {
  815. if (ch instanceof HeadersFootersContainer
  816. && ((HeadersFootersContainer) ch).getOptions() == HeadersFootersContainer.NotesHeadersFootersContainer) {
  817. hdd = (HeadersFootersContainer) ch;
  818. break;
  819. }
  820. }
  821. boolean newRecord = false;
  822. if (hdd == null) {
  823. hdd = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer);
  824. newRecord = true;
  825. }
  826. if (ppt2007 && !_notes.isEmpty()) {
  827. return new HeadersFooters(hdd, _notes.get(0), newRecord, ppt2007);
  828. }
  829. return new HeadersFooters(hdd, this, newRecord, ppt2007);
  830. }
  831. /**
  832. * Add a movie in this presentation
  833. *
  834. * @param path
  835. * the path or url to the movie
  836. * @return 0-based index of the movie
  837. */
  838. public int addMovie(String path, int type) {
  839. ExMCIMovie mci;
  840. switch (type) {
  841. case MovieShape.MOVIE_MPEG:
  842. mci = new ExMCIMovie();
  843. break;
  844. case MovieShape.MOVIE_AVI:
  845. mci = new ExAviMovie();
  846. break;
  847. default:
  848. throw new IllegalArgumentException("Unsupported Movie: " + type);
  849. }
  850. ExVideoContainer exVideo = mci.getExVideo();
  851. exVideo.getExMediaAtom().setMask(0xE80000);
  852. exVideo.getPathAtom().setText(path);
  853. int objectId = addToObjListAtom(mci);
  854. exVideo.getExMediaAtom().setObjectId(objectId);
  855. return objectId;
  856. }
  857. /**
  858. * Add a control in this presentation
  859. *
  860. * @param name
  861. * name of the control, e.g. "Shockwave Flash Object"
  862. * @param progId
  863. * OLE Programmatic Identifier, e.g.
  864. * "ShockwaveFlash.ShockwaveFlash.9"
  865. * @return 0-based index of the control
  866. */
  867. public int addControl(String name, String progId) {
  868. ExControl ctrl = new ExControl();
  869. ctrl.setProgId(progId);
  870. ctrl.setMenuName(name);
  871. ctrl.setClipboardName(name);
  872. ExOleObjAtom oleObj = ctrl.getExOleObjAtom();
  873. oleObj.setDrawAspect(ExOleObjAtom.DRAW_ASPECT_VISIBLE);
  874. oleObj.setType(ExOleObjAtom.TYPE_CONTROL);
  875. oleObj.setSubType(ExOleObjAtom.SUBTYPE_DEFAULT);
  876. int objectId = addToObjListAtom(ctrl);
  877. oleObj.setObjID(objectId);
  878. return objectId;
  879. }
  880. /**
  881. * Add a hyperlink to this presentation
  882. *
  883. * @return 0-based index of the hyperlink
  884. */
  885. public int addHyperlink(HSLFHyperlink link) {
  886. ExHyperlink ctrl = new ExHyperlink();
  887. ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
  888. if(link.getType() == HSLFHyperlink.LINK_SLIDENUMBER) {
  889. ctrl.setLinkURL(link.getAddress(), 0x30);
  890. } else {
  891. ctrl.setLinkURL(link.getAddress());
  892. }
  893. ctrl.setLinkTitle(link.getLabel());
  894. int objectId = addToObjListAtom(ctrl);
  895. link.setId(objectId);
  896. obj.setNumber(objectId);
  897. return objectId;
  898. }
  899. /**
  900. * Add a embedded object to this presentation
  901. *
  902. * @return 0-based index of the embedded object
  903. */
  904. public int addEmbed(POIFSFileSystem poiData) {
  905. DirectoryNode root = poiData.getRoot();
  906. // prepare embedded data
  907. if (new ClassID().equals(root.getStorageClsid())) {
  908. // need to set class id
  909. Map<String,ClassID> olemap = getOleMap();
  910. ClassID classID = null;
  911. for (Map.Entry<String,ClassID> entry : olemap.entrySet()) {
  912. if (root.hasEntry(entry.getKey())) {
  913. classID = entry.getValue();
  914. break;
  915. }
  916. }
  917. if (classID == null) {
  918. throw new IllegalArgumentException("Unsupported embedded document");
  919. }
  920. root.setStorageClsid(classID);
  921. }
  922. ExEmbed exEmbed = new ExEmbed();
  923. // remove unneccessary infos, so we don't need to specify the type
  924. // of the ole object multiple times
  925. Record children[] = exEmbed.getChildRecords();
  926. exEmbed.removeChild(children[2]);
  927. exEmbed.removeChild(children[3]);
  928. exEmbed.removeChild(children[4]);
  929. ExEmbedAtom eeEmbed = exEmbed.getExEmbedAtom();
  930. eeEmbed.setCantLockServerB(true);
  931. ExOleObjAtom eeAtom = exEmbed.getExOleObjAtom();
  932. eeAtom.setDrawAspect(ExOleObjAtom.DRAW_ASPECT_VISIBLE);
  933. eeAtom.setType(ExOleObjAtom.TYPE_EMBEDDED);
  934. // eeAtom.setSubType(ExOleObjAtom.SUBTYPE_EXCEL);
  935. // should be ignored?!?, see MS-PPT ExOleObjAtom, but Libre Office sets it ...
  936. eeAtom.setOptions(1226240);
  937. ExOleObjStg exOleObjStg = new ExOleObjStg();
  938. try {
  939. final String OLESTREAM_NAME = "\u0001Ole";
  940. if (!root.hasEntry(OLESTREAM_NAME)) {
  941. // the following data was taken from an example libre office document
  942. // beside this "\u0001Ole" record there were several other records, e.g. CompObj,
  943. // OlePresXXX, but it seems, that they aren't neccessary
  944. byte oleBytes[] = { 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  945. poiData.createDocument(new ByteArrayInputStream(oleBytes), OLESTREAM_NAME);
  946. }
  947. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  948. poiData.writeFilesystem(bos);
  949. exOleObjStg.setData(bos.toByteArray());
  950. } catch (IOException e) {
  951. throw new HSLFException(e);
  952. }
  953. int psrId = addPersistentObject(exOleObjStg);
  954. exOleObjStg.setPersistId(psrId);
  955. eeAtom.setObjStgDataRef(psrId);
  956. int objectId = addToObjListAtom(exEmbed);
  957. eeAtom.setObjID(objectId);
  958. return objectId;
  959. }
  960. protected int addToObjListAtom(RecordContainer exObj) {
  961. ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
  962. if (lst == null) {
  963. lst = new ExObjList();
  964. _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
  965. }
  966. ExObjListAtom objAtom = lst.getExObjListAtom();
  967. // increment the object ID seed
  968. int objectId = (int) objAtom.getObjectIDSeed() + 1;
  969. objAtom.setObjectIDSeed(objectId);
  970. lst.addChildAfter(exObj, objAtom);
  971. return objectId;
  972. }
  973. protected static Map<String,ClassID> getOleMap() {
  974. Map<String,ClassID> olemap = new HashMap<String,ClassID>();
  975. olemap.put("PowerPoint Document", ClassID.PPT_SHOW);
  976. olemap.put("Workbook", ClassID.EXCEL97); // as per BIFF8 spec
  977. olemap.put("WORKBOOK", ClassID.EXCEL97); // Typically from third party programs
  978. olemap.put("BOOK", ClassID.EXCEL97); // Typically odd Crystal Reports exports
  979. // ... to be continued
  980. return olemap;
  981. }
  982. protected int addPersistentObject(PositionDependentRecord slideRecord) {
  983. slideRecord.setLastOnDiskOffset(HSLFSlideShowImpl.UNSET_OFFSET);
  984. _hslfSlideShow.appendRootLevelRecord((Record)slideRecord);
  985. // For position dependent records, hold where they were and now are
  986. // As we go along, update, and hand over, to any Position Dependent
  987. // records we happen across
  988. Map<RecordTypes,PositionDependentRecord> interestingRecords =
  989. new HashMap<RecordTypes,PositionDependentRecord>();
  990. try {
  991. _hslfSlideShow.updateAndWriteDependantRecords(null,interestingRecords);
  992. } catch (IOException e) {
  993. throw new HSLFException(e);
  994. }
  995. PersistPtrHolder ptr = (PersistPtrHolder)interestingRecords.get(RecordTypes.PersistPtrIncrementalBlock);
  996. UserEditAtom usr = (UserEditAtom)interestingRecords.get(RecordTypes.UserEditAtom);
  997. // persist ID is UserEditAtom.maxPersistWritten + 1
  998. int psrId = usr.getMaxPersistWritten() + 1;
  999. // Last view is now of the slide
  1000. usr.setLastViewType((short) UserEditAtom.LAST_VIEW_SLIDE_VIEW);
  1001. // increment the number of persistent objects
  1002. usr.setMaxPersistWritten(psrId);
  1003. // Add the new slide into the last PersistPtr
  1004. // (Also need to tell it where it is)
  1005. int slideOffset = slideRecord.getLastOnDiskOffset();
  1006. slideRecord.setLastOnDiskOffset(slideOffset);
  1007. ptr.addSlideLookup(psrId, slideOffset);
  1008. logger.log(POILogger.INFO, "New slide/object ended up at " + slideOffset);
  1009. return psrId;
  1010. }
  1011. public MasterSheet<HSLFShape,HSLFTextParagraph> createMasterSheet() throws IOException {
  1012. // TODO Auto-generated method stub
  1013. return null;
  1014. }
  1015. public Resources getResources() {
  1016. // TODO Auto-generated method stub
  1017. return null;
  1018. }
  1019. @Override
  1020. public void close() throws IOException {
  1021. _hslfSlideShow.close();
  1022. }
  1023. }