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.

XSLFTextShape.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  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. package org.apache.poi.xslf.usermodel;
  20. import java.awt.geom.Rectangle2D;
  21. import java.util.ArrayList;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import org.apache.poi.POIXMLException;
  25. import org.apache.poi.sl.draw.DrawFactory;
  26. import org.apache.poi.sl.draw.DrawTextShape;
  27. import org.apache.poi.sl.usermodel.Insets2D;
  28. import org.apache.poi.sl.usermodel.TextShape;
  29. import org.apache.poi.sl.usermodel.VerticalAlignment;
  30. import org.apache.poi.util.Beta;
  31. import org.apache.poi.util.Units;
  32. import org.apache.poi.xslf.model.PropertyFetcher;
  33. import org.apache.poi.xslf.model.TextBodyPropertyFetcher;
  34. import org.apache.xmlbeans.XmlObject;
  35. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
  36. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties;
  37. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties;
  38. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextListStyle;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
  40. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties;
  41. import org.openxmlformats.schemas.drawingml.x2006.main.STTextAnchoringType;
  42. import org.openxmlformats.schemas.drawingml.x2006.main.STTextVerticalType;
  43. import org.openxmlformats.schemas.drawingml.x2006.main.STTextWrappingType;
  44. import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
  45. /**
  46. * Represents a shape that can hold text.
  47. */
  48. @Beta
  49. public abstract class XSLFTextShape extends XSLFSimpleShape
  50. implements TextShape<XSLFShape,XSLFTextParagraph> {
  51. private final List<XSLFTextParagraph> _paragraphs;
  52. @SuppressWarnings("deprecation")
  53. /*package*/ XSLFTextShape(XmlObject shape, XSLFSheet sheet) {
  54. super(shape, sheet);
  55. _paragraphs = new ArrayList<XSLFTextParagraph>();
  56. CTTextBody txBody = getTextBody(false);
  57. if (txBody != null) {
  58. for (CTTextParagraph p : txBody.getPArray()) {
  59. _paragraphs.add(new XSLFTextParagraph(p, this));
  60. }
  61. }
  62. }
  63. public Iterator<XSLFTextParagraph> iterator(){
  64. return getTextParagraphs().iterator();
  65. }
  66. /**
  67. *
  68. * @return text contained within this shape or empty string
  69. */
  70. public String getText() {
  71. StringBuilder out = new StringBuilder();
  72. for (XSLFTextParagraph p : _paragraphs) {
  73. if (out.length() > 0) out.append('\n');
  74. out.append(p.getText());
  75. }
  76. return out.toString();
  77. }
  78. /**
  79. * unset text from this shape
  80. */
  81. public void clearText(){
  82. _paragraphs.clear();
  83. CTTextBody txBody = getTextBody(true);
  84. txBody.setPArray(null); // remove any existing paragraphs
  85. }
  86. @Override
  87. public XSLFTextRun setText(String text) {
  88. // copy properties from first paragraph / textrun
  89. CTTextParagraphProperties pPr = null;
  90. CTTextCharacterProperties rPr = null;
  91. if (!_paragraphs.isEmpty()) {
  92. XSLFTextParagraph p0 = _paragraphs.get(0);
  93. pPr = p0.getXmlObject().getPPr();
  94. if (!p0.getTextRuns().isEmpty()) {
  95. XSLFTextRun r0 = p0.getTextRuns().get(0);
  96. rPr = r0.getXmlObject().getRPr();
  97. }
  98. }
  99. // can't call clearText otherwise we receive a XmlValueDisconnectedException
  100. _paragraphs.clear();
  101. CTTextBody txBody = getTextBody(true);
  102. int cntPs = txBody.sizeOfPArray();
  103. // split text by paragraph and new line char
  104. XSLFTextRun r = null;
  105. for (String paraText : text.split("\\r\\n?|\\n")) {
  106. XSLFTextParagraph para = addNewTextParagraph();
  107. if (pPr != null) {
  108. para.getXmlObject().setPPr(pPr);
  109. }
  110. boolean first = true;
  111. for (String runText : paraText.split("[\u000b]")) {
  112. if (!first) {
  113. para.addLineBreak();
  114. }
  115. r = para.addNewTextRun();
  116. r.setText(runText);
  117. if (rPr != null) {
  118. r.getXmlObject().setRPr(rPr);
  119. }
  120. first = false;
  121. }
  122. }
  123. // simply setting a new pArray leads to XmlValueDisconnectedException
  124. for (int i = cntPs-1; i >= 0; i--) {
  125. txBody.removeP(i);
  126. }
  127. return r;
  128. }
  129. @Override
  130. public List<XSLFTextParagraph> getTextParagraphs() {
  131. return _paragraphs;
  132. }
  133. /**
  134. * add a new paragraph run to this shape
  135. *
  136. * @return created paragraph run
  137. */
  138. public XSLFTextParagraph addNewTextParagraph() {
  139. CTTextBody txBody = getTextBody(true);
  140. CTTextParagraph p = txBody.addNewP();
  141. XSLFTextParagraph paragraph = new XSLFTextParagraph(p, this);
  142. _paragraphs.add(paragraph);
  143. return paragraph;
  144. }
  145. @Override
  146. public void setVerticalAlignment(VerticalAlignment anchor){
  147. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  148. if (bodyPr != null) {
  149. if(anchor == null) {
  150. if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor();
  151. } else {
  152. bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1));
  153. }
  154. }
  155. }
  156. @Override
  157. public VerticalAlignment getVerticalAlignment(){
  158. PropertyFetcher<VerticalAlignment> fetcher = new TextBodyPropertyFetcher<VerticalAlignment>(){
  159. public boolean fetch(CTTextBodyProperties props){
  160. if(props.isSetAnchor()){
  161. int val = props.getAnchor().intValue();
  162. setValue(VerticalAlignment.values()[val - 1]);
  163. return true;
  164. }
  165. return false;
  166. }
  167. };
  168. fetchShapeProperty(fetcher);
  169. return fetcher.getValue() == null ? VerticalAlignment.TOP : fetcher.getValue();
  170. }
  171. @Override
  172. public void setHorizontalCentered(Boolean isCentered){
  173. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  174. if (bodyPr != null) {
  175. if (isCentered == null) {
  176. if (bodyPr.isSetAnchorCtr()) bodyPr.unsetAnchorCtr();
  177. } else {
  178. bodyPr.setAnchorCtr(isCentered);
  179. }
  180. }
  181. }
  182. @Override
  183. public boolean isHorizontalCentered(){
  184. PropertyFetcher<Boolean> fetcher = new TextBodyPropertyFetcher<Boolean>(){
  185. public boolean fetch(CTTextBodyProperties props){
  186. if(props.isSetAnchorCtr()){
  187. setValue(props.getAnchorCtr());
  188. return true;
  189. }
  190. return false;
  191. }
  192. };
  193. fetchShapeProperty(fetcher);
  194. return fetcher.getValue() == null ? false : fetcher.getValue();
  195. }
  196. @Override
  197. public void setTextDirection(TextDirection orientation){
  198. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  199. if (bodyPr != null) {
  200. if(orientation == null) {
  201. if(bodyPr.isSetVert()) bodyPr.unsetVert();
  202. } else {
  203. bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1));
  204. }
  205. }
  206. }
  207. @Override
  208. public TextDirection getTextDirection(){
  209. CTTextBodyProperties bodyPr = getTextBodyPr();
  210. if (bodyPr != null) {
  211. STTextVerticalType.Enum val = bodyPr.getVert();
  212. if(val != null){
  213. return TextDirection.values()[val.intValue() - 1];
  214. }
  215. }
  216. return TextDirection.HORIZONTAL;
  217. }
  218. @Override
  219. public Double getTextRotation() {
  220. CTTextBodyProperties bodyPr = getTextBodyPr();
  221. if (bodyPr != null && bodyPr.isSetRot()) {
  222. return bodyPr.getRot() / 60000.;
  223. }
  224. return null;
  225. }
  226. @Override
  227. public void setTextRotation(Double rotation) {
  228. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  229. if (bodyPr != null) {
  230. bodyPr.setRot((int)(rotation * 60000.));
  231. }
  232. }
  233. /**
  234. * Returns the distance (in points) between the bottom of the text frame
  235. * and the bottom of the inscribed rectangle of the shape that contains the text.
  236. *
  237. * @return the bottom inset in points
  238. */
  239. public double getBottomInset(){
  240. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  241. public boolean fetch(CTTextBodyProperties props){
  242. if(props.isSetBIns()){
  243. double val = Units.toPoints(props.getBIns());
  244. setValue(val);
  245. return true;
  246. }
  247. return false;
  248. }
  249. };
  250. fetchShapeProperty(fetcher);
  251. // If this attribute is omitted, then a value of 0.05 inches is implied
  252. return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
  253. }
  254. /**
  255. * Returns the distance (in points) between the left edge of the text frame
  256. * and the left edge of the inscribed rectangle of the shape that contains
  257. * the text.
  258. *
  259. * @return the left inset in points
  260. */
  261. public double getLeftInset(){
  262. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  263. public boolean fetch(CTTextBodyProperties props){
  264. if(props.isSetLIns()){
  265. double val = Units.toPoints(props.getLIns());
  266. setValue(val);
  267. return true;
  268. }
  269. return false;
  270. }
  271. };
  272. fetchShapeProperty(fetcher);
  273. // If this attribute is omitted, then a value of 0.1 inches is implied
  274. return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
  275. }
  276. /**
  277. * Returns the distance (in points) between the right edge of the
  278. * text frame and the right edge of the inscribed rectangle of the shape
  279. * that contains the text.
  280. *
  281. * @return the right inset in points
  282. */
  283. public double getRightInset(){
  284. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  285. public boolean fetch(CTTextBodyProperties props){
  286. if(props.isSetRIns()){
  287. double val = Units.toPoints(props.getRIns());
  288. setValue(val);
  289. return true;
  290. }
  291. return false;
  292. }
  293. };
  294. fetchShapeProperty(fetcher);
  295. // If this attribute is omitted, then a value of 0.1 inches is implied
  296. return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
  297. }
  298. /**
  299. * Returns the distance (in points) between the top of the text frame
  300. * and the top of the inscribed rectangle of the shape that contains the text.
  301. *
  302. * @return the top inset in points
  303. */
  304. public double getTopInset(){
  305. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  306. public boolean fetch(CTTextBodyProperties props){
  307. if(props.isSetTIns()){
  308. double val = Units.toPoints(props.getTIns());
  309. setValue(val);
  310. return true;
  311. }
  312. return false;
  313. }
  314. };
  315. fetchShapeProperty(fetcher);
  316. // If this attribute is omitted, then a value of 0.05 inches is implied
  317. return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
  318. }
  319. /**
  320. * Sets the bottom margin.
  321. * @see #getBottomInset()
  322. *
  323. * @param margin the bottom margin
  324. */
  325. public void setBottomInset(double margin){
  326. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  327. if (bodyPr != null) {
  328. if(margin == -1) bodyPr.unsetBIns();
  329. else bodyPr.setBIns(Units.toEMU(margin));
  330. }
  331. }
  332. /**
  333. * Sets the left margin.
  334. * @see #getLeftInset()
  335. *
  336. * @param margin the left margin
  337. */
  338. public void setLeftInset(double margin){
  339. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  340. if (bodyPr != null) {
  341. if(margin == -1) bodyPr.unsetLIns();
  342. else bodyPr.setLIns(Units.toEMU(margin));
  343. }
  344. }
  345. /**
  346. * Sets the right margin.
  347. * @see #getRightInset()
  348. *
  349. * @param margin the right margin
  350. */
  351. public void setRightInset(double margin){
  352. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  353. if (bodyPr != null) {
  354. if(margin == -1) bodyPr.unsetRIns();
  355. else bodyPr.setRIns(Units.toEMU(margin));
  356. }
  357. }
  358. /**
  359. * Sets the top margin.
  360. * @see #getTopInset()
  361. *
  362. * @param margin the top margin
  363. */
  364. public void setTopInset(double margin){
  365. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  366. if (bodyPr != null) {
  367. if(margin == -1) bodyPr.unsetTIns();
  368. else bodyPr.setTIns(Units.toEMU(margin));
  369. }
  370. }
  371. @Override
  372. public Insets2D getInsets() {
  373. Insets2D insets = new Insets2D(getTopInset(), getLeftInset(), getBottomInset(), getRightInset());
  374. return insets;
  375. }
  376. @Override
  377. public void setInsets(Insets2D insets) {
  378. setTopInset(insets.top);
  379. setLeftInset(insets.left);
  380. setBottomInset(insets.bottom);
  381. setRightInset(insets.right);
  382. }
  383. @Override
  384. public boolean getWordWrap(){
  385. PropertyFetcher<Boolean> fetcher = new TextBodyPropertyFetcher<Boolean>(){
  386. public boolean fetch(CTTextBodyProperties props){
  387. if(props.isSetWrap()){
  388. setValue(props.getWrap() == STTextWrappingType.SQUARE);
  389. return true;
  390. }
  391. return false;
  392. }
  393. };
  394. fetchShapeProperty(fetcher);
  395. return fetcher.getValue() == null ? true : fetcher.getValue();
  396. }
  397. @Override
  398. public void setWordWrap(boolean wrap){
  399. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  400. if (bodyPr != null) {
  401. bodyPr.setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE);
  402. }
  403. }
  404. /**
  405. *
  406. * Specifies that a shape should be auto-fit to fully contain the text described within it.
  407. * Auto-fitting is when text within a shape is scaled in order to contain all the text inside
  408. *
  409. * @param value type of autofit
  410. */
  411. public void setTextAutofit(TextAutofit value){
  412. CTTextBodyProperties bodyPr = getTextBodyPr(true);
  413. if (bodyPr != null) {
  414. if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit();
  415. if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit();
  416. if(bodyPr.isSetNormAutofit()) bodyPr.unsetNormAutofit();
  417. switch(value){
  418. case NONE: bodyPr.addNewNoAutofit(); break;
  419. case NORMAL: bodyPr.addNewNormAutofit(); break;
  420. case SHAPE: bodyPr.addNewSpAutoFit(); break;
  421. }
  422. }
  423. }
  424. /**
  425. *
  426. * @return type of autofit
  427. */
  428. public TextAutofit getTextAutofit(){
  429. CTTextBodyProperties bodyPr = getTextBodyPr();
  430. if (bodyPr != null) {
  431. if(bodyPr.isSetNoAutofit()) return TextAutofit.NONE;
  432. else if (bodyPr.isSetNormAutofit()) return TextAutofit.NORMAL;
  433. else if (bodyPr.isSetSpAutoFit()) return TextAutofit.SHAPE;
  434. }
  435. return TextAutofit.NORMAL;
  436. }
  437. protected CTTextBodyProperties getTextBodyPr(){
  438. return getTextBodyPr(false);
  439. }
  440. protected CTTextBodyProperties getTextBodyPr(boolean create) {
  441. CTTextBody textBody = getTextBody(create);
  442. if (textBody == null) {
  443. return null;
  444. }
  445. CTTextBodyProperties textBodyPr = textBody.getBodyPr();
  446. if (textBodyPr == null && create) {
  447. textBodyPr = textBody.addNewBodyPr();
  448. }
  449. return textBodyPr;
  450. }
  451. protected abstract CTTextBody getTextBody(boolean create);
  452. @Override
  453. public void setPlaceholder(Placeholder placeholder) {
  454. super.setPlaceholder(placeholder);
  455. }
  456. public Placeholder getTextType(){
  457. CTPlaceholder ph = getCTPlaceholder();
  458. if (ph == null) return null;
  459. int val = ph.getType().intValue();
  460. return Placeholder.values()[val - 1];
  461. }
  462. @Override
  463. public double getTextHeight(){
  464. DrawFactory drawFact = DrawFactory.getInstance(null);
  465. DrawTextShape dts = drawFact.getDrawable(this);
  466. return dts.getTextHeight();
  467. }
  468. /**
  469. * Adjust the size of the shape so it encompasses the text inside it.
  470. *
  471. * @return a <code>Rectangle2D</code> that is the bounds of this shape.
  472. */
  473. public Rectangle2D resizeToFitText(){
  474. Rectangle2D anchor = getAnchor();
  475. if(anchor.getWidth() == 0.) throw new POIXMLException(
  476. "Anchor of the shape was not set.");
  477. double height = getTextHeight();
  478. height += 1; // add a pixel to compensate rounding errors
  479. anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height);
  480. setAnchor(anchor);
  481. return anchor;
  482. }
  483. @Override
  484. void copy(XSLFShape other){
  485. super.copy(other);
  486. XSLFTextShape otherTS = (XSLFTextShape)other;
  487. CTTextBody otherTB = otherTS.getTextBody(false);
  488. CTTextBody thisTB = getTextBody(true);
  489. if (otherTB == null) {
  490. return;
  491. }
  492. thisTB.setBodyPr((CTTextBodyProperties)otherTB.getBodyPr().copy());
  493. if (thisTB.isSetLstStyle()) thisTB.unsetLstStyle();
  494. if (otherTB.isSetLstStyle()) {
  495. thisTB.setLstStyle((CTTextListStyle)otherTB.getLstStyle().copy());
  496. }
  497. boolean srcWordWrap = otherTS.getWordWrap();
  498. if(srcWordWrap != getWordWrap()){
  499. setWordWrap(srcWordWrap);
  500. }
  501. double leftInset = otherTS.getLeftInset();
  502. if(leftInset != getLeftInset()) {
  503. setLeftInset(leftInset);
  504. }
  505. double rightInset = otherTS.getRightInset();
  506. if(rightInset != getRightInset()) {
  507. setRightInset(rightInset);
  508. }
  509. double topInset = otherTS.getTopInset();
  510. if(topInset != getTopInset()) {
  511. setTopInset(topInset);
  512. }
  513. double bottomInset = otherTS.getBottomInset();
  514. if(bottomInset != getBottomInset()) {
  515. setBottomInset(bottomInset);
  516. }
  517. VerticalAlignment vAlign = otherTS.getVerticalAlignment();
  518. if(vAlign != getVerticalAlignment()) {
  519. setVerticalAlignment(vAlign);
  520. }
  521. clearText();
  522. for (XSLFTextParagraph srcP : otherTS.getTextParagraphs()) {
  523. XSLFTextParagraph tgtP = addNewTextParagraph();
  524. tgtP.copy(srcP);
  525. }
  526. }
  527. @Override
  528. public void setTextPlaceholder(TextPlaceholder placeholder) {
  529. switch (placeholder) {
  530. default:
  531. case NOTES:
  532. case HALF_BODY:
  533. case QUARTER_BODY:
  534. case BODY:
  535. setPlaceholder(Placeholder.BODY);
  536. break;
  537. case TITLE:
  538. setPlaceholder(Placeholder.TITLE);
  539. break;
  540. case CENTER_BODY:
  541. setPlaceholder(Placeholder.BODY);
  542. setHorizontalCentered(true);
  543. break;
  544. case CENTER_TITLE:
  545. setPlaceholder(Placeholder.CENTERED_TITLE);
  546. break;
  547. case OTHER:
  548. setPlaceholder(Placeholder.CONTENT);
  549. break;
  550. }
  551. }
  552. @Override
  553. public TextPlaceholder getTextPlaceholder() {
  554. Placeholder ph = getTextType();
  555. if (ph == null) return TextPlaceholder.BODY;
  556. switch (ph) {
  557. case BODY: return TextPlaceholder.BODY;
  558. case TITLE: return TextPlaceholder.TITLE;
  559. case CENTERED_TITLE: return TextPlaceholder.CENTER_TITLE;
  560. default:
  561. case CONTENT: return TextPlaceholder.OTHER;
  562. }
  563. }
  564. }