PageRenderTime 80ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/ppt/scratchpad/src/org/apache/poi/hslf/model/TextShape.java

https://github.com/minstrelsy/POI-Android
Java | 611 lines | 293 code | 65 blank | 253 comment | 65 complexity | 4ae551d90bbc16377c5513186d4e9dc1 MD5 | raw file
  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 and.awt.Font;
  17. import and.awt.Rectangle;
  18. import and.awt.geom.AffineTransform;
  19. import and.awt.geom.Rectangle2D;
  20. import java.io.IOException;
  21. import net.pbdavey.awt.Graphics2D;
  22. import org.apache.poi.ddf.EscherContainerRecord;
  23. import org.apache.poi.ddf.EscherOptRecord;
  24. import org.apache.poi.ddf.EscherProperties;
  25. import org.apache.poi.ddf.EscherSimpleProperty;
  26. import org.apache.poi.ddf.EscherSpRecord;
  27. import org.apache.poi.ddf.EscherTextboxRecord;
  28. import org.apache.poi.hslf.exceptions.HSLFException;
  29. import org.apache.poi.hslf.record.EscherTextboxWrapper;
  30. import org.apache.poi.hslf.record.InteractiveInfo;
  31. import org.apache.poi.hslf.record.InteractiveInfoAtom;
  32. import org.apache.poi.hslf.record.OEPlaceholderAtom;
  33. import org.apache.poi.hslf.record.OutlineTextRefAtom;
  34. import org.apache.poi.hslf.record.PPDrawing;
  35. import org.apache.poi.hslf.record.Record;
  36. import org.apache.poi.hslf.record.RecordTypes;
  37. import org.apache.poi.hslf.record.StyleTextPropAtom;
  38. import org.apache.poi.hslf.record.TextCharsAtom;
  39. import org.apache.poi.hslf.record.TextHeaderAtom;
  40. import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
  41. import org.apache.poi.hslf.usermodel.RichTextRun;
  42. import org.apache.poi.util.POILogger;
  43. /**
  44. * A common superclass of all shapes that can hold text.
  45. *
  46. * @author Yegor Kozlov
  47. */
  48. public abstract class TextShape extends SimpleShape {
  49. /**
  50. * How to anchor the text
  51. */
  52. public static final int AnchorTop = 0;
  53. public static final int AnchorMiddle = 1;
  54. public static final int AnchorBottom = 2;
  55. public static final int AnchorTopCentered = 3;
  56. public static final int AnchorMiddleCentered = 4;
  57. public static final int AnchorBottomCentered = 5;
  58. public static final int AnchorTopBaseline = 6;
  59. public static final int AnchorBottomBaseline = 7;
  60. public static final int AnchorTopCenteredBaseline = 8;
  61. public static final int AnchorBottomCenteredBaseline = 9;
  62. /**
  63. * How to wrap the text
  64. */
  65. public static final int WrapSquare = 0;
  66. public static final int WrapByPoints = 1;
  67. public static final int WrapNone = 2;
  68. public static final int WrapTopBottom = 3;
  69. public static final int WrapThrough = 4;
  70. /**
  71. * How to align the text
  72. */
  73. public static final int AlignLeft = 0;
  74. public static final int AlignCenter = 1;
  75. public static final int AlignRight = 2;
  76. public static final int AlignJustify = 3;
  77. /**
  78. * TextRun object which holds actual text and format data
  79. */
  80. protected TextRun _txtrun;
  81. /**
  82. * Escher container which holds text attributes such as
  83. * TextHeaderAtom, TextBytesAtom ot TextCharsAtom, StyleTextPropAtom etc.
  84. */
  85. protected EscherTextboxWrapper _txtbox;
  86. /**
  87. * Used to calculate text bounds
  88. */
  89. // XXX: DDD
  90. // protected static final FontRenderContext _frc = new FontRenderContext(null, true, true);
  91. /**
  92. * Create a TextBox object and initialize it from the supplied Record container.
  93. *
  94. * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape
  95. * @param parent the parent of the shape
  96. */
  97. protected TextShape(EscherContainerRecord escherRecord, Shape parent){
  98. super(escherRecord, parent);
  99. }
  100. /**
  101. * Create a new TextBox. This constructor is used when a new shape is created.
  102. *
  103. * @param parent the parent of this Shape. For example, if this text box is a cell
  104. * in a table then the parent is Table.
  105. */
  106. public TextShape(Shape parent){
  107. super(null, parent);
  108. _escherContainer = createSpContainer(parent instanceof ShapeGroup);
  109. }
  110. /**
  111. * Create a new TextBox. This constructor is used when a new shape is created.
  112. *
  113. */
  114. public TextShape(){
  115. this(null);
  116. }
  117. public TextRun createTextRun(){
  118. _txtbox = getEscherTextboxWrapper();
  119. if(_txtbox == null) _txtbox = new EscherTextboxWrapper();
  120. _txtrun = getTextRun();
  121. if(_txtrun == null){
  122. TextHeaderAtom tha = new TextHeaderAtom();
  123. tha.setParentRecord(_txtbox);
  124. _txtbox.appendChildRecord(tha);
  125. TextCharsAtom tca = new TextCharsAtom();
  126. _txtbox.appendChildRecord(tca);
  127. StyleTextPropAtom sta = new StyleTextPropAtom(0);
  128. _txtbox.appendChildRecord(sta);
  129. _txtrun = new TextRun(tha,tca,sta);
  130. _txtrun._records = new Record[]{tha, tca, sta};
  131. _txtrun.setText("");
  132. _escherContainer.addChildRecord(_txtbox.getEscherRecord());
  133. setDefaultTextProperties(_txtrun);
  134. }
  135. return _txtrun;
  136. }
  137. /**
  138. * Set default properties for the TextRun.
  139. * Depending on the text and shape type the defaults are different:
  140. * TextBox: align=left, valign=top
  141. * AutoShape: align=center, valign=middle
  142. *
  143. */
  144. protected void setDefaultTextProperties(TextRun _txtrun){
  145. }
  146. /**
  147. * Returns the text contained in this text frame.
  148. *
  149. * @return the text string for this textbox.
  150. */
  151. public String getText(){
  152. TextRun tx = getTextRun();
  153. return tx == null ? null : tx.getText();
  154. }
  155. /**
  156. * Sets the text contained in this text frame.
  157. *
  158. * @param text the text string used by this object.
  159. */
  160. public void setText(String text){
  161. TextRun tx = getTextRun();
  162. if(tx == null){
  163. tx = createTextRun();
  164. }
  165. tx.setText(text);
  166. setTextId(text.hashCode());
  167. }
  168. /**
  169. * When a textbox is added to a sheet we need to tell upper-level
  170. * <code>PPDrawing</code> about it.
  171. *
  172. * @param sh the sheet we are adding to
  173. */
  174. protected void afterInsert(Sheet sh){
  175. super.afterInsert(sh);
  176. EscherTextboxWrapper _txtbox = getEscherTextboxWrapper();
  177. if(_txtbox != null){
  178. PPDrawing ppdrawing = sh.getPPDrawing();
  179. ppdrawing.addTextboxWrapper(_txtbox);
  180. // Ensure the escher layer knows about the added records
  181. try {
  182. _txtbox.writeOut(null);
  183. } catch (IOException e){
  184. throw new HSLFException(e);
  185. }
  186. if(getAnchor().equals(new Rectangle()) && !"".equals(getText())) resizeToFitText();
  187. }
  188. if(_txtrun != null) {
  189. _txtrun.setShapeId(getShapeId());
  190. sh.onAddTextShape(this);
  191. }
  192. }
  193. protected EscherTextboxWrapper getEscherTextboxWrapper(){
  194. if(_txtbox == null){
  195. EscherTextboxRecord textRecord = (EscherTextboxRecord)Shape.getEscherChild(_escherContainer, EscherTextboxRecord.RECORD_ID);
  196. if(textRecord != null) _txtbox = new EscherTextboxWrapper(textRecord);
  197. }
  198. return _txtbox;
  199. }
  200. /**
  201. * Adjust the size of the TextShape so it encompasses the text inside it.
  202. *
  203. * @return a <code>Rectangle2D</code> that is the bounds of this <code>TextShape</code>.
  204. */
  205. public Rectangle2D resizeToFitText(){
  206. // String txt = getText();
  207. // if(txt == null || txt.length() == 0) return new Rectangle2D.Float();
  208. //
  209. // RichTextRun rt = getTextRun().getRichTextRuns()[0];
  210. // int size = rt.getFontSize();
  211. // int style = 0;
  212. // if (rt.isBold()) style |= Font.BOLD;
  213. // if (rt.isItalic()) style |= Font.ITALIC;
  214. // String fntname = rt.getFontName();
  215. // Font font = new Font(fntname, style, size);
  216. //
  217. // float width = 0, height = 0, leading = 0;
  218. // String[] lines = txt.split("\n");
  219. // for (int i = 0; i < lines.length; i++) {
  220. // if(lines[i].length() == 0) continue;
  221. //
  222. // TextLayout layout = new TextLayout(lines[i], font, _frc);
  223. //
  224. // leading = Math.max(leading, layout.getLeading());
  225. // width = Math.max(width, layout.getAdvance());
  226. // height = Math.max(height, (height + (layout.getDescent() + layout.getAscent())));
  227. // }
  228. //
  229. // // add one character to width
  230. // Rectangle2D charBounds = font.getMaxCharBounds(_frc);
  231. // width += getMarginLeft() + getMarginRight() + charBounds.getWidth();
  232. //
  233. // // add leading to height
  234. // height += getMarginTop() + getMarginBottom() + leading;
  235. //
  236. // Rectangle2D anchor = getAnchor2D();
  237. // anchor.setRect(anchor.getX(), anchor.getY(), width, height);
  238. // setAnchor(anchor);
  239. //
  240. // return anchor;
  241. // XXX: DDD
  242. System.out.println("resizeToFitText");
  243. Rectangle2D anchor = getAnchor2D();
  244. // anchor.setRect(anchor.getX(), anchor.getY(), 200, 100);
  245. setAnchor(anchor);
  246. return anchor;
  247. }
  248. /**
  249. * Returns the type of vertical alignment for the text.
  250. * One of the <code>Anchor*</code> constants defined in this class.
  251. *
  252. * @return the type of alignment
  253. */
  254. public int getVerticalAlignment(){
  255. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  256. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT);
  257. int valign = TextShape.AnchorTop;
  258. if (prop == null){
  259. /**
  260. * If vertical alignment was not found in the shape properties then try to
  261. * fetch the master shape and search for the align property there.
  262. */
  263. int type = getTextRun().getRunType();
  264. MasterSheet master = getSheet().getMasterSheet();
  265. if(master != null){
  266. TextShape masterShape = master.getPlaceholderByTextType(type);
  267. if(masterShape != null) valign = masterShape.getVerticalAlignment();
  268. } else {
  269. //not found in the master sheet. Use the hardcoded defaults.
  270. switch (type){
  271. case TextHeaderAtom.TITLE_TYPE:
  272. case TextHeaderAtom.CENTER_TITLE_TYPE:
  273. valign = TextShape.AnchorMiddle;
  274. break;
  275. default:
  276. valign = TextShape.AnchorTop;
  277. break;
  278. }
  279. }
  280. } else {
  281. valign = prop.getPropertyValue();
  282. }
  283. return valign;
  284. }
  285. /**
  286. * Sets the type of vertical alignment for the text.
  287. * One of the <code>Anchor*</code> constants defined in this class.
  288. *
  289. * @param align - the type of alignment
  290. */
  291. public void setVerticalAlignment(int align){
  292. setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, align);
  293. }
  294. /**
  295. * Sets the type of horizontal alignment for the text.
  296. * One of the <code>Align*</code> constants defined in this class.
  297. *
  298. * @param align - the type of horizontal alignment
  299. */
  300. public void setHorizontalAlignment(int align){
  301. TextRun tx = getTextRun();
  302. if(tx != null) tx.getRichTextRuns()[0].setAlignment(align);
  303. }
  304. /**
  305. * Gets the type of horizontal alignment for the text.
  306. * One of the <code>Align*</code> constants defined in this class.
  307. *
  308. * @return align - the type of horizontal alignment
  309. */
  310. public int getHorizontalAlignment(){
  311. TextRun tx = getTextRun();
  312. return tx == null ? -1 : tx.getRichTextRuns()[0].getAlignment();
  313. }
  314. /**
  315. * Returns the distance (in points) between the bottom of the text frame
  316. * and the bottom of the inscribed rectangle of the shape that contains the text.
  317. * Default value is 1/20 inch.
  318. *
  319. * @return the botom margin
  320. */
  321. public float getMarginBottom(){
  322. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  323. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM);
  324. int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();
  325. return (float)val/EMU_PER_POINT;
  326. }
  327. /**
  328. * Sets the botom margin.
  329. * @see #getMarginBottom()
  330. *
  331. * @param margin the bottom margin
  332. */
  333. public void setMarginBottom(float margin){
  334. setEscherProperty(EscherProperties.TEXT__TEXTBOTTOM, (int)(margin*EMU_PER_POINT));
  335. }
  336. /**
  337. * Returns the distance (in points) between the left edge of the text frame
  338. * and the left edge of the inscribed rectangle of the shape that contains
  339. * the text.
  340. * Default value is 1/10 inch.
  341. *
  342. * @return the left margin
  343. */
  344. public float getMarginLeft(){
  345. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  346. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTLEFT);
  347. int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();
  348. return (float)val/EMU_PER_POINT;
  349. }
  350. /**
  351. * Sets the left margin.
  352. * @see #getMarginLeft()
  353. *
  354. * @param margin the left margin
  355. */
  356. public void setMarginLeft(float margin){
  357. setEscherProperty(EscherProperties.TEXT__TEXTLEFT, (int)(margin*EMU_PER_POINT));
  358. }
  359. /**
  360. * Returns the distance (in points) between the right edge of the
  361. * text frame and the right edge of the inscribed rectangle of the shape
  362. * that contains the text.
  363. * Default value is 1/10 inch.
  364. *
  365. * @return the right margin
  366. */
  367. public float getMarginRight(){
  368. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  369. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTRIGHT);
  370. int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue();
  371. return (float)val/EMU_PER_POINT;
  372. }
  373. /**
  374. * Sets the right margin.
  375. * @see #getMarginRight()
  376. *
  377. * @param margin the right margin
  378. */
  379. public void setMarginRight(float margin){
  380. setEscherProperty(EscherProperties.TEXT__TEXTRIGHT, (int)(margin*EMU_PER_POINT));
  381. }
  382. /**
  383. * Returns the distance (in points) between the top of the text frame
  384. * and the top of the inscribed rectangle of the shape that contains the text.
  385. * Default value is 1/20 inch.
  386. *
  387. * @return the top margin
  388. */
  389. public float getMarginTop(){
  390. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  391. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTTOP);
  392. int val = prop == null ? EMU_PER_INCH/20 : prop.getPropertyValue();
  393. return (float)val/EMU_PER_POINT;
  394. }
  395. /**
  396. * Sets the top margin.
  397. * @see #getMarginTop()
  398. *
  399. * @param margin the top margin
  400. */
  401. public void setMarginTop(float margin){
  402. setEscherProperty(EscherProperties.TEXT__TEXTTOP, (int)(margin*EMU_PER_POINT));
  403. }
  404. /**
  405. * Returns the value indicating word wrap.
  406. *
  407. * @return the value indicating word wrap.
  408. * Must be one of the <code>Wrap*</code> constants defined in this class.
  409. */
  410. public int getWordWrap(){
  411. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  412. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT);
  413. return prop == null ? WrapSquare : prop.getPropertyValue();
  414. }
  415. /**
  416. * Specifies how the text should be wrapped
  417. *
  418. * @param wrap the value indicating how the text should be wrapped.
  419. * Must be one of the <code>Wrap*</code> constants defined in this class.
  420. */
  421. public void setWordWrap(int wrap){
  422. setEscherProperty(EscherProperties.TEXT__WRAPTEXT, wrap);
  423. }
  424. /**
  425. * @return id for the text.
  426. */
  427. public int getTextId(){
  428. EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
  429. EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTID);
  430. return prop == null ? 0 : prop.getPropertyValue();
  431. }
  432. /**
  433. * Sets text ID
  434. *
  435. * @param id of the text
  436. */
  437. public void setTextId(int id){
  438. setEscherProperty(EscherProperties.TEXT__TEXTID, id);
  439. }
  440. /**
  441. * @return the TextRun object for this text box
  442. */
  443. public TextRun getTextRun(){
  444. if(_txtrun == null) initTextRun();
  445. return _txtrun;
  446. }
  447. public void setSheet(Sheet sheet) {
  448. _sheet = sheet;
  449. // Initialize _txtrun object.
  450. // (We can't do it in the constructor because the sheet
  451. // is not assigned then, it's only built once we have
  452. // all the records)
  453. TextRun tx = getTextRun();
  454. if (tx != null) {
  455. // Supply the sheet to our child RichTextRuns
  456. tx.setSheet(_sheet);
  457. RichTextRun[] rt = tx.getRichTextRuns();
  458. for (int i = 0; i < rt.length; i++) {
  459. rt[i].supplySlideShow(_sheet.getSlideShow());
  460. }
  461. }
  462. }
  463. protected void initTextRun(){
  464. EscherTextboxWrapper txtbox = getEscherTextboxWrapper();
  465. Sheet sheet = getSheet();
  466. if(sheet == null || txtbox == null) return;
  467. OutlineTextRefAtom ota = null;
  468. Record[] child = txtbox.getChildRecords();
  469. for (int i = 0; i < child.length; i++) {
  470. if (child[i] instanceof OutlineTextRefAtom) {
  471. ota = (OutlineTextRefAtom)child[i];
  472. break;
  473. }
  474. }
  475. TextRun[] runs = _sheet.getTextRuns();
  476. if (ota != null) {
  477. int idx = ota.getTextIndex();
  478. for (int i = 0; i < runs.length; i++) {
  479. if(runs[i].getIndex() == idx){
  480. _txtrun = runs[i];
  481. break;
  482. }
  483. }
  484. if(_txtrun == null) {
  485. logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);
  486. }
  487. } else {
  488. EscherSpRecord escherSpRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID);
  489. int shapeId = escherSpRecord.getShapeId();
  490. if(runs != null) for (int i = 0; i < runs.length; i++) {
  491. if(runs[i].getShapeId() == shapeId){
  492. _txtrun = runs[i];
  493. break;
  494. }
  495. }
  496. }
  497. // ensure the same references child records of TextRun
  498. if(_txtrun != null) for (int i = 0; i < child.length; i++) {
  499. for (Record r : _txtrun.getRecords()) {
  500. if (child[i].getRecordType() == r.getRecordType()) {
  501. child[i] = r;
  502. }
  503. }
  504. }
  505. }
  506. public void draw(Graphics2D graphics){
  507. // AffineTransform at = graphics.getTransform();
  508. graphics.canvas.save();
  509. ShapePainter.paint(this, graphics);
  510. new TextPainter(this).paint(graphics);
  511. // graphics.setTransform(at);
  512. graphics.canvas.restore();
  513. }
  514. /**
  515. * Return <code>OEPlaceholderAtom</code>, the atom that describes a placeholder.
  516. *
  517. * @return <code>OEPlaceholderAtom</code> or <code>null</code> if not found
  518. */
  519. public OEPlaceholderAtom getPlaceholderAtom(){
  520. return (OEPlaceholderAtom)getClientDataRecord(RecordTypes.OEPlaceholderAtom.typeID);
  521. }
  522. /**
  523. *
  524. * Assigns a hyperlink to this text shape
  525. *
  526. * @param linkId id of the hyperlink, @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
  527. * @param beginIndex the beginning index, inclusive.
  528. * @param endIndex the ending index, exclusive.
  529. * @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
  530. */
  531. public void setHyperlink(int linkId, int beginIndex, int endIndex){
  532. //TODO validate beginIndex and endIndex and throw IllegalArgumentException
  533. InteractiveInfo info = new InteractiveInfo();
  534. InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
  535. infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
  536. infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
  537. infoAtom.setHyperlinkID(linkId);
  538. _txtbox.appendChildRecord(info);
  539. TxInteractiveInfoAtom txiatom = new TxInteractiveInfoAtom();
  540. txiatom.setStartIndex(beginIndex);
  541. txiatom.setEndIndex(endIndex);
  542. _txtbox.appendChildRecord(txiatom);
  543. }
  544. }