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.

Sheet.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hslf.model;
  16. import org.apache.poi.ddf.*;
  17. import org.apache.poi.hslf.record.*;
  18. import org.apache.poi.hslf.usermodel.SlideShow;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.awt.*;
  23. /**
  24. * This class defines the common format of "Sheets" in a powerpoint
  25. * document. Such sheets could be Slides, Notes, Master etc
  26. *
  27. * @author Nick Burch
  28. * @author Yegor Kozlov
  29. */
  30. public abstract class Sheet {
  31. /**
  32. * The <code>SlideShow</code> we belong to
  33. */
  34. private SlideShow _slideShow;
  35. /**
  36. * Sheet background
  37. */
  38. private Background _background;
  39. /**
  40. * Record container that holds sheet data.
  41. * For slides it is org.apache.poi.hslf.record.Slide,
  42. * for notes it is org.apache.poi.hslf.record.Notes,
  43. * for slide masters it is org.apache.poi.hslf.record.SlideMaster, etc.
  44. */
  45. private SheetContainer _container;
  46. private int _sheetNo;
  47. public Sheet(SheetContainer container, int sheetNo) {
  48. _container = container;
  49. _sheetNo = sheetNo;
  50. }
  51. /**
  52. * Returns an array of all the TextRuns in the sheet.
  53. */
  54. public abstract TextRun[] getTextRuns();
  55. /**
  56. * Returns the (internal, RefID based) sheet number, as used
  57. * to in PersistPtr stuff.
  58. */
  59. public int _getSheetRefId() {
  60. return _container.getSheetId();
  61. }
  62. /**
  63. * Returns the (internal, SlideIdentifier based) sheet number, as used
  64. * to reference this sheet from other records.
  65. */
  66. public int _getSheetNumber() {
  67. return _sheetNo;
  68. }
  69. /**
  70. * Fetch the PPDrawing from the underlying record
  71. */
  72. protected PPDrawing getPPDrawing() {
  73. return _container.getPPDrawing();
  74. }
  75. /**
  76. * Fetch the SlideShow we're attached to
  77. */
  78. public SlideShow getSlideShow() {
  79. return _slideShow;
  80. }
  81. /**
  82. * Return record container for this sheet
  83. */
  84. public SheetContainer getSheetContainer() {
  85. return _container;
  86. }
  87. /**
  88. * Set the SlideShow we're attached to.
  89. * Also passes it on to our child RichTextRuns
  90. */
  91. public void setSlideShow(SlideShow ss) {
  92. _slideShow = ss;
  93. TextRun[] trs = getTextRuns();
  94. if (trs != null) {
  95. for (int i = 0; i < trs.length; i++) {
  96. trs[i].supplySlideShow(_slideShow);
  97. }
  98. }
  99. }
  100. /**
  101. * For a given PPDrawing, grab all the TextRuns
  102. */
  103. public static TextRun[] findTextRuns(PPDrawing ppdrawing) {
  104. final List<TextRun> runsV = new ArrayList<TextRun>();
  105. EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers();
  106. for (int i = 0; i < wrappers.length; i++) {
  107. int s1 = runsV.size();
  108. // propagate parents to parent-aware records
  109. RecordContainer.handleParentAwareRecords(wrappers[i]);
  110. findTextRuns(wrappers[i].getChildRecords(), runsV);
  111. int s2 = runsV.size();
  112. if (s2 != s1){
  113. TextRun t = runsV.get(runsV.size()-1);
  114. t.setShapeId(wrappers[i].getShapeId());
  115. }
  116. }
  117. return runsV.toArray(new TextRun[runsV.size()]);
  118. }
  119. /**
  120. * Scans through the supplied record array, looking for
  121. * a TextHeaderAtom followed by one of a TextBytesAtom or
  122. * a TextCharsAtom. Builds up TextRuns from these
  123. *
  124. * @param records the records to build from
  125. * @param found vector to add any found to
  126. */
  127. protected static void findTextRuns(Record[] records, List<TextRun> found) {
  128. // Look for a TextHeaderAtom
  129. for (int i = 0, slwtIndex=0; i < (records.length - 1); i++) {
  130. if (records[i] instanceof TextHeaderAtom) {
  131. TextRun trun = null;
  132. TextHeaderAtom tha = (TextHeaderAtom) records[i];
  133. StyleTextPropAtom stpa = null;
  134. // Look for a subsequent StyleTextPropAtom
  135. if (i < (records.length - 2)) {
  136. if (records[i + 2] instanceof StyleTextPropAtom) {
  137. stpa = (StyleTextPropAtom) records[i + 2];
  138. }
  139. }
  140. // See what follows the TextHeaderAtom
  141. if (records[i + 1] instanceof TextCharsAtom) {
  142. TextCharsAtom tca = (TextCharsAtom) records[i + 1];
  143. trun = new TextRun(tha, tca, stpa);
  144. } else if (records[i + 1] instanceof TextBytesAtom) {
  145. TextBytesAtom tba = (TextBytesAtom) records[i + 1];
  146. trun = new TextRun(tha, tba, stpa);
  147. } else if (records[i + 1].getRecordType() == 4001l) {
  148. // StyleTextPropAtom - Safe to ignore
  149. } else if (records[i + 1].getRecordType() == 4010l) {
  150. // TextSpecInfoAtom - Safe to ignore
  151. } else {
  152. System.err.println("Found a TextHeaderAtom not followed by a TextBytesAtom or TextCharsAtom: Followed by " + records[i + 1].getRecordType());
  153. }
  154. if (trun != null) {
  155. ArrayList lst = new ArrayList();
  156. for (int j = i; j < records.length; j++) {
  157. if(j > i && records[j] instanceof TextHeaderAtom) break;
  158. lst.add(records[j]);
  159. }
  160. Record[] recs = new Record[lst.size()];
  161. lst.toArray(recs);
  162. trun._records = recs;
  163. trun.setIndex(slwtIndex);
  164. found.add(trun);
  165. i++;
  166. } else {
  167. // Not a valid one, so skip on to next and look again
  168. }
  169. slwtIndex++;
  170. }
  171. }
  172. }
  173. /**
  174. * Returns all shapes contained in this Sheet
  175. *
  176. * @return all shapes contained in this Sheet (Slide or Notes)
  177. */
  178. public Shape[] getShapes() {
  179. PPDrawing ppdrawing = getPPDrawing();
  180. EscherContainerRecord dg = (EscherContainerRecord) ppdrawing.getEscherRecords()[0];
  181. EscherContainerRecord spgr = null;
  182. for (Iterator<EscherRecord> it = dg.getChildIterator(); it.hasNext();) {
  183. EscherRecord rec = it.next();
  184. if (rec.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) {
  185. spgr = (EscherContainerRecord) rec;
  186. break;
  187. }
  188. }
  189. if (spgr == null) {
  190. throw new IllegalStateException("spgr not found");
  191. }
  192. List<Shape> shapes = new ArrayList<Shape>();
  193. Iterator<EscherRecord> it = spgr.getChildIterator();
  194. if (it.hasNext()) {
  195. // skip first item
  196. it.next();
  197. }
  198. for (; it.hasNext();) {
  199. EscherContainerRecord sp = (EscherContainerRecord) it.next();
  200. Shape sh = ShapeFactory.createShape(sp, null);
  201. sh.setSheet(this);
  202. shapes.add(sh);
  203. }
  204. return shapes.toArray(new Shape[shapes.size()]);
  205. }
  206. /**
  207. * Add a new Shape to this Slide
  208. *
  209. * @param shape - the Shape to add
  210. */
  211. public void addShape(Shape shape) {
  212. PPDrawing ppdrawing = getPPDrawing();
  213. EscherContainerRecord dgContainer = (EscherContainerRecord) ppdrawing.getEscherRecords()[0];
  214. EscherContainerRecord spgr = (EscherContainerRecord) Shape.getEscherChild(dgContainer, EscherContainerRecord.SPGR_CONTAINER);
  215. spgr.addChildRecord(shape.getSpContainer());
  216. shape.setSheet(this);
  217. shape.setShapeId(allocateShapeId());
  218. shape.afterInsert(this);
  219. }
  220. /**
  221. * Allocates new shape id for the new drawing group id.
  222. *
  223. * @return a new shape id.
  224. */
  225. public int allocateShapeId()
  226. {
  227. EscherDggRecord dgg = _slideShow.getDocumentRecord().getPPDrawingGroup().getEscherDggRecord();
  228. EscherDgRecord dg = _container.getPPDrawing().getEscherDgRecord();
  229. dgg.setNumShapesSaved( dgg.getNumShapesSaved() + 1 );
  230. // Add to existing cluster if space available
  231. for (int i = 0; i < dgg.getFileIdClusters().length; i++)
  232. {
  233. EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i];
  234. if (c.getDrawingGroupId() == dg.getDrawingGroupId() && c.getNumShapeIdsUsed() != 1024)
  235. {
  236. int result = c.getNumShapeIdsUsed() + (1024 * (i+1));
  237. c.incrementShapeId();
  238. dg.setNumShapes( dg.getNumShapes() + 1 );
  239. dg.setLastMSOSPID( result );
  240. if (result >= dgg.getShapeIdMax())
  241. dgg.setShapeIdMax( result + 1 );
  242. return result;
  243. }
  244. }
  245. // Create new cluster
  246. dgg.addCluster( dg.getDrawingGroupId(), 0, false );
  247. dgg.getFileIdClusters()[dgg.getFileIdClusters().length-1].incrementShapeId();
  248. dg.setNumShapes( dg.getNumShapes() + 1 );
  249. int result = (1024 * dgg.getFileIdClusters().length);
  250. dg.setLastMSOSPID( result );
  251. if (result >= dgg.getShapeIdMax())
  252. dgg.setShapeIdMax( result + 1 );
  253. return result;
  254. }
  255. /**
  256. * Removes the specified shape from this sheet.
  257. *
  258. * @param shape shape to be removed from this sheet, if present.
  259. * @return <tt>true</tt> if the shape was deleted.
  260. */
  261. public boolean removeShape(Shape shape) {
  262. PPDrawing ppdrawing = getPPDrawing();
  263. EscherContainerRecord dg = (EscherContainerRecord) ppdrawing.getEscherRecords()[0];
  264. EscherContainerRecord spgr = null;
  265. for (Iterator<EscherRecord> it = dg.getChildIterator(); it.hasNext();) {
  266. EscherRecord rec = it.next();
  267. if (rec.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) {
  268. spgr = (EscherContainerRecord) rec;
  269. break;
  270. }
  271. }
  272. if(spgr == null) {
  273. return false;
  274. }
  275. List<EscherRecord> lst = spgr.getChildRecords();
  276. boolean result = lst.remove(shape.getSpContainer());
  277. spgr.setChildRecords(lst);
  278. return result;
  279. }
  280. /**
  281. * Called by SlideShow ater a new sheet is created
  282. */
  283. public void onCreate(){
  284. }
  285. /**
  286. * Return the master sheet .
  287. */
  288. public abstract MasterSheet getMasterSheet();
  289. /**
  290. * Color scheme for this sheet.
  291. */
  292. public ColorSchemeAtom getColorScheme() {
  293. return _container.getColorScheme();
  294. }
  295. /**
  296. * Returns the background shape for this sheet.
  297. *
  298. * @return the background shape for this sheet.
  299. */
  300. public Background getBackground() {
  301. if (_background == null) {
  302. PPDrawing ppdrawing = getPPDrawing();
  303. EscherContainerRecord dg = (EscherContainerRecord) ppdrawing.getEscherRecords()[0];
  304. EscherContainerRecord spContainer = null;
  305. for (Iterator<EscherRecord> it = dg.getChildIterator(); it.hasNext();) {
  306. EscherRecord rec = it.next();
  307. if (rec.getRecordId() == EscherContainerRecord.SP_CONTAINER) {
  308. spContainer = (EscherContainerRecord) rec;
  309. break;
  310. }
  311. }
  312. _background = new Background(spContainer, null);
  313. _background.setSheet(this);
  314. }
  315. return _background;
  316. }
  317. public void draw(Graphics2D graphics){
  318. }
  319. /**
  320. * Subclasses should call this method and update the array of text runs
  321. * when a text shape is added
  322. *
  323. * @param shape
  324. */
  325. protected void onAddTextShape(TextShape shape) {
  326. }
  327. /**
  328. * Return placeholder by text type
  329. *
  330. * @param type type of text, See {@link org.apache.poi.hslf.record.TextHeaderAtom}
  331. * @return <code>TextShape</code> or <code>null</code>
  332. */
  333. public TextShape getPlaceholderByTextType(int type){
  334. Shape[] shape = getShapes();
  335. for (int i = 0; i < shape.length; i++) {
  336. if(shape[i] instanceof TextShape){
  337. TextShape tx = (TextShape)shape[i];
  338. TextRun run = tx.getTextRun();
  339. if(run != null && run.getRunType() == type){
  340. return tx;
  341. }
  342. }
  343. }
  344. return null;
  345. }
  346. /**
  347. * Search text placeholer by its type
  348. *
  349. * @param type type of placeholder to search. See {@link org.apache.poi.hslf.record.OEPlaceholderAtom}
  350. * @return <code>TextShape</code> or <code>null</code>
  351. */
  352. public TextShape getPlaceholder(int type){
  353. Shape[] shape = getShapes();
  354. for (int i = 0; i < shape.length; i++) {
  355. if(shape[i] instanceof TextShape){
  356. TextShape tx = (TextShape)shape[i];
  357. int placeholderId = 0;
  358. OEPlaceholderAtom oep = tx.getPlaceholderAtom();
  359. if(oep != null) {
  360. placeholderId = oep.getPlaceholderId();
  361. } else {
  362. //special case for files saved in Office 2007
  363. RoundTripHFPlaceholder12 hldr = (RoundTripHFPlaceholder12)tx.getClientDataRecord(RecordTypes.RoundTripHFPlaceholder12.typeID);
  364. if(hldr != null) placeholderId = hldr.getPlaceholderId();
  365. }
  366. if(placeholderId == type){
  367. return tx;
  368. }
  369. }
  370. }
  371. return null;
  372. }
  373. /**
  374. * Return programmable tag associated with this sheet, e.g. <code>___PPT12</code>.
  375. *
  376. * @return programmable tag associated with this sheet.
  377. */
  378. public String getProgrammableTag(){
  379. String tag = null;
  380. RecordContainer progTags = (RecordContainer)
  381. getSheetContainer().findFirstOfType(
  382. RecordTypes.ProgTags.typeID
  383. );
  384. if(progTags != null) {
  385. RecordContainer progBinaryTag = (RecordContainer)
  386. progTags.findFirstOfType(
  387. RecordTypes.ProgBinaryTag.typeID
  388. );
  389. if(progBinaryTag != null) {
  390. CString binaryTag = (CString)
  391. progBinaryTag.findFirstOfType(
  392. RecordTypes.CString.typeID
  393. );
  394. if(binaryTag != null) tag = binaryTag.getText();
  395. }
  396. }
  397. return tag;
  398. }
  399. }