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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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 org.apache.poi.util.Beta;
  21. import org.apache.poi.util.Units;
  22. import org.apache.poi.xslf.model.PropertyFetcher;
  23. import org.apache.poi.xslf.model.TextBodyPropertyFetcher;
  24. import org.apache.xmlbeans.XmlObject;
  25. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
  26. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties;
  27. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
  28. import org.openxmlformats.schemas.drawingml.x2006.main.STTextAnchoringType;
  29. import org.openxmlformats.schemas.drawingml.x2006.main.STTextVerticalType;
  30. import org.openxmlformats.schemas.drawingml.x2006.main.STTextWrappingType;
  31. import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
  32. import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
  33. import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
  34. import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType;
  35. import java.awt.Graphics2D;
  36. import java.awt.geom.Rectangle2D;
  37. import java.awt.image.BufferedImage;
  38. import java.util.ArrayList;
  39. import java.util.Iterator;
  40. import java.util.List;
  41. /**
  42. * Represents a shape that can hold text.
  43. *
  44. * @author Yegor Kozlov
  45. */
  46. @Beta
  47. public abstract class XSLFTextShape extends XSLFSimpleShape implements Iterable<XSLFTextParagraph>{
  48. private final List<XSLFTextParagraph> _paragraphs;
  49. /**
  50. * whether the text was broken into lines.
  51. */
  52. private boolean _isTextBroken;
  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.getPList()) {
  59. _paragraphs.add(new XSLFTextParagraph(p, this));
  60. }
  61. }
  62. }
  63. public Iterator<XSLFTextParagraph> iterator(){
  64. return _paragraphs.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. public void setText(String text){
  87. clearText();
  88. addNewTextParagraph().addNewTextRun().setText(text);
  89. }
  90. /**
  91. *
  92. * @return text paragraphs in this shape
  93. */
  94. public List<XSLFTextParagraph> getTextParagraphs() {
  95. return _paragraphs;
  96. }
  97. /**
  98. * add a new paragraph run to this shape
  99. *
  100. * @return created paragraph run
  101. */
  102. public XSLFTextParagraph addNewTextParagraph() {
  103. CTTextBody txBody = getTextBody(true);
  104. CTTextParagraph p = txBody.addNewP();
  105. XSLFTextParagraph paragraph = new XSLFTextParagraph(p, this);
  106. _paragraphs.add(paragraph);
  107. return paragraph;
  108. }
  109. /**
  110. * Sets the type of vertical alignment for the text.
  111. *
  112. * @param anchor - the type of alignment.
  113. * A <code>null</code> values unsets this property.
  114. */
  115. public void setVerticalAlignment(VerticalAlignment anchor){
  116. CTTextBodyProperties bodyPr = getTextBodyPr();
  117. if (bodyPr != null) {
  118. if(anchor == null) {
  119. if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor();
  120. } else {
  121. bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1));
  122. }
  123. }
  124. }
  125. /**
  126. * Returns the type of vertical alignment for the text.
  127. *
  128. * @return the type of vertical alignment
  129. */
  130. public VerticalAlignment getVerticalAlignment(){
  131. PropertyFetcher<VerticalAlignment> fetcher = new TextBodyPropertyFetcher<VerticalAlignment>(){
  132. public boolean fetch(CTTextBodyProperties props){
  133. if(props.isSetAnchor()){
  134. int val = props.getAnchor().intValue();
  135. setValue(VerticalAlignment.values()[val - 1]);
  136. return true;
  137. }
  138. return false;
  139. }
  140. };
  141. fetchShapeProperty(fetcher);
  142. return fetcher.getValue() == null ? VerticalAlignment.TOP : fetcher.getValue();
  143. }
  144. /**
  145. *
  146. * @param orientation vertical orientation of the text
  147. */
  148. public void setTextDirection(TextDirection orientation){
  149. CTTextBodyProperties bodyPr = getTextBodyPr();
  150. if (bodyPr != null) {
  151. if(orientation == null) {
  152. if(bodyPr.isSetVert()) bodyPr.unsetVert();
  153. } else {
  154. bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1));
  155. }
  156. }
  157. }
  158. /**
  159. * @return vertical orientation of the text
  160. */
  161. public TextDirection getTextDirection(){
  162. CTTextBodyProperties bodyPr = getTextBodyPr();
  163. if (bodyPr != null) {
  164. STTextVerticalType.Enum val = bodyPr.getVert();
  165. if(val != null){
  166. return TextDirection.values()[val.intValue() - 1];
  167. }
  168. }
  169. return TextDirection.HORIZONTAL;
  170. }
  171. /**
  172. * Returns the distance (in points) between the bottom of the text frame
  173. * and the bottom of the inscribed rectangle of the shape that contains the text.
  174. *
  175. * @return the bottom inset in points
  176. */
  177. public double getBottomInset(){
  178. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  179. public boolean fetch(CTTextBodyProperties props){
  180. if(props.isSetBIns()){
  181. double val = Units.toPoints(props.getBIns());
  182. setValue(val);
  183. return true;
  184. }
  185. return false;
  186. }
  187. };
  188. fetchShapeProperty(fetcher);
  189. // If this attribute is omitted, then a value of 0.05 inches is implied
  190. return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
  191. }
  192. /**
  193. * Returns the distance (in points) between the left edge of the text frame
  194. * and the left edge of the inscribed rectangle of the shape that contains
  195. * the text.
  196. *
  197. * @return the left inset in points
  198. */
  199. public double getLeftInset(){
  200. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  201. public boolean fetch(CTTextBodyProperties props){
  202. if(props.isSetLIns()){
  203. double val = Units.toPoints(props.getLIns());
  204. setValue(val);
  205. return true;
  206. }
  207. return false;
  208. }
  209. };
  210. fetchShapeProperty(fetcher);
  211. // If this attribute is omitted, then a value of 0.1 inches is implied
  212. return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
  213. }
  214. /**
  215. * Returns the distance (in points) between the right edge of the
  216. * text frame and the right edge of the inscribed rectangle of the shape
  217. * that contains the text.
  218. *
  219. * @return the right inset in points
  220. */
  221. public double getRightInset(){
  222. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  223. public boolean fetch(CTTextBodyProperties props){
  224. if(props.isSetRIns()){
  225. double val = Units.toPoints(props.getRIns());
  226. setValue(val);
  227. return true;
  228. }
  229. return false;
  230. }
  231. };
  232. fetchShapeProperty(fetcher);
  233. // If this attribute is omitted, then a value of 0.1 inches is implied
  234. return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
  235. }
  236. /**
  237. * Returns the distance (in points) between the top of the text frame
  238. * and the top of the inscribed rectangle of the shape that contains the text.
  239. *
  240. * @return the top inset in points
  241. */
  242. public double getTopInset(){
  243. PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>(){
  244. public boolean fetch(CTTextBodyProperties props){
  245. if(props.isSetTIns()){
  246. double val = Units.toPoints(props.getTIns());
  247. setValue(val);
  248. return true;
  249. }
  250. return false;
  251. }
  252. };
  253. fetchShapeProperty(fetcher);
  254. // If this attribute is omitted, then a value of 0.05 inches is implied
  255. return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
  256. }
  257. /**
  258. * Sets the botom margin.
  259. * @see #getBottomInset()
  260. *
  261. * @param margin the bottom margin
  262. */
  263. public void setBottomInset(double margin){
  264. CTTextBodyProperties bodyPr = getTextBodyPr();
  265. if (bodyPr != null) {
  266. if(margin == -1) bodyPr.unsetBIns();
  267. else bodyPr.setBIns(Units.toEMU(margin));
  268. }
  269. }
  270. /**
  271. * Sets the left margin.
  272. * @see #getLeftInset()
  273. *
  274. * @param margin the left margin
  275. */
  276. public void setLeftInset(double margin){
  277. CTTextBodyProperties bodyPr = getTextBodyPr();
  278. if (bodyPr != null) {
  279. if(margin == -1) bodyPr.unsetLIns();
  280. else bodyPr.setLIns(Units.toEMU(margin));
  281. }
  282. }
  283. /**
  284. * Sets the right margin.
  285. * @see #getRightInset()
  286. *
  287. * @param margin the right margin
  288. */
  289. public void setRightInset(double margin){
  290. CTTextBodyProperties bodyPr = getTextBodyPr();
  291. if (bodyPr != null) {
  292. if(margin == -1) bodyPr.unsetRIns();
  293. else bodyPr.setRIns(Units.toEMU(margin));
  294. }
  295. }
  296. /**
  297. * Sets the top margin.
  298. * @see #getTopInset()
  299. *
  300. * @param margin the top margin
  301. */
  302. public void setTopInset(double margin){
  303. CTTextBodyProperties bodyPr = getTextBodyPr();
  304. if (bodyPr != null) {
  305. if(margin == -1) bodyPr.unsetTIns();
  306. else bodyPr.setTIns(Units.toEMU(margin));
  307. }
  308. }
  309. /**
  310. * @return whether to wrap words within the bounding rectangle
  311. */
  312. public boolean getWordWrap(){
  313. PropertyFetcher<Boolean> fetcher = new TextBodyPropertyFetcher<Boolean>(){
  314. public boolean fetch(CTTextBodyProperties props){
  315. if(props.isSetWrap()){
  316. setValue(props.getWrap() == STTextWrappingType.SQUARE);
  317. return true;
  318. }
  319. return false;
  320. }
  321. };
  322. fetchShapeProperty(fetcher);
  323. return fetcher.getValue() == null ? true : fetcher.getValue();
  324. }
  325. /**
  326. *
  327. * @param wrap whether to wrap words within the bounding rectangle
  328. */
  329. public void setWordWrap(boolean wrap){
  330. CTTextBodyProperties bodyPr = getTextBodyPr();
  331. if (bodyPr != null) {
  332. bodyPr.setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE);
  333. }
  334. }
  335. /**
  336. *
  337. * Specifies that a shape should be auto-fit to fully contain the text described within it.
  338. * Auto-fitting is when text within a shape is scaled in order to contain all the text inside
  339. *
  340. * @param value type of autofit
  341. */
  342. public void setTextAutofit(TextAutofit value){
  343. CTTextBodyProperties bodyPr = getTextBodyPr();
  344. if (bodyPr != null) {
  345. if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit();
  346. if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit();
  347. if(bodyPr.isSetNormAutofit()) bodyPr.unsetNormAutofit();
  348. switch(value){
  349. case NONE: bodyPr.addNewNoAutofit(); break;
  350. case NORMAL: bodyPr.addNewNormAutofit(); break;
  351. case SHAPE: bodyPr.addNewSpAutoFit(); break;
  352. }
  353. }
  354. }
  355. /**
  356. *
  357. * @return type of autofit
  358. */
  359. public TextAutofit getTextAutofit(){
  360. CTTextBodyProperties bodyPr = getTextBodyPr();
  361. if (bodyPr != null) {
  362. if(bodyPr.isSetNoAutofit()) return TextAutofit.NONE;
  363. else if (bodyPr.isSetNormAutofit()) return TextAutofit.NORMAL;
  364. else if (bodyPr.isSetSpAutoFit()) return TextAutofit.SHAPE;
  365. }
  366. return TextAutofit.NORMAL;
  367. }
  368. protected CTTextBodyProperties getTextBodyPr(){
  369. CTTextBody textBody = getTextBody(false);
  370. return textBody == null ? null : textBody.getBodyPr();
  371. }
  372. protected abstract CTTextBody getTextBody(boolean create);
  373. public Placeholder getTextType(){
  374. CTPlaceholder ph;
  375. XmlObject[] obj = getXmlObject().selectPath(
  376. "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:nvPr/p:ph");
  377. if(obj.length == 1){
  378. ph = (CTPlaceholder)obj[0];
  379. int val = ph.getType().intValue();
  380. return Placeholder.values()[val - 1];
  381. }
  382. else {
  383. return null;
  384. }
  385. }
  386. /**
  387. * Specifies that the corresponding shape should be represented by the generating application
  388. * as a placeholder. When a shape is considered a placeholder by the generating application
  389. * it can have special properties to alert the user that they may enter content into the shape.
  390. * Different types of placeholders are allowed and can be specified by using the placeholder
  391. * type attribute for this element
  392. *
  393. * @param placeholder
  394. */
  395. public void setPlaceholder(Placeholder placeholder){
  396. CTShape sh = (CTShape)getXmlObject();
  397. CTApplicationNonVisualDrawingProps nv = sh.getNvSpPr().getNvPr();
  398. if(placeholder == null) {
  399. if(nv.isSetPh()) nv.unsetPh();
  400. } else {
  401. nv.addNewPh().setType(STPlaceholderType.Enum.forInt(placeholder.ordinal() + 1));
  402. }
  403. }
  404. /**
  405. * Compute the cumulative height occupied by the text
  406. */
  407. public double getTextHeight(){
  408. // dry-run in a 1x1 image and return the vertical advance
  409. BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
  410. Graphics2D graphics = img.createGraphics();
  411. breakText(graphics);
  412. return drawParagraphs(graphics, 0, 0);
  413. }
  414. /**
  415. * break the contained text into lines
  416. */
  417. private void breakText(Graphics2D graphics){
  418. if(!_isTextBroken) {
  419. for(XSLFTextParagraph p : _paragraphs) p.breakText(graphics);
  420. _isTextBroken = true;
  421. }
  422. }
  423. @Override
  424. public void drawContent(Graphics2D graphics) {
  425. breakText(graphics);
  426. Rectangle2D anchor = getAnchor();
  427. double x = anchor.getX() + getLeftInset();
  428. double y = anchor.getY();
  429. // first dry-run to calculate the total height of the text
  430. double textHeight = getTextHeight();
  431. switch (getVerticalAlignment()){
  432. case TOP:
  433. y += getTopInset();
  434. break;
  435. case BOTTOM:
  436. y += anchor.getHeight() - textHeight - getBottomInset();
  437. break;
  438. default:
  439. case MIDDLE:
  440. double delta = anchor.getHeight() - textHeight -
  441. getTopInset() - getBottomInset();
  442. y += getTopInset() + delta/2;
  443. break;
  444. }
  445. drawParagraphs(graphics, x, y);
  446. }
  447. /**
  448. * pain the paragraphs starting from top left (x,y)
  449. *
  450. * @return the vertical advance, i.e. the cumulative space occupied by the text
  451. */
  452. private double drawParagraphs(Graphics2D graphics, double x, double y) {
  453. double y0 = y;
  454. for(int i = 0; i < _paragraphs.size(); i++){
  455. XSLFTextParagraph p = _paragraphs.get(i);
  456. List<TextFragment> lines = p.getTextLines();
  457. if(i > 0 && lines.size() > 0) {
  458. // the amount of vertical white space before the paragraph
  459. double spaceBefore = p.getSpaceBefore();
  460. if(spaceBefore > 0) {
  461. // positive value means percentage spacing of the height of the first line, e.g.
  462. // the higher the first line, the bigger the space before the paragraph
  463. y += spaceBefore*0.01*lines.get(0).getHeight();
  464. } else {
  465. // negative value means the absolute spacing in points
  466. y += -spaceBefore;
  467. }
  468. }
  469. y += p.draw(graphics, x, y);
  470. if(i < _paragraphs.size() - 1) {
  471. double spaceAfter = p.getSpaceAfter();
  472. if(spaceAfter > 0) {
  473. // positive value means percentage spacing of the height of the last line, e.g.
  474. // the higher the last line, the bigger the space after the paragraph
  475. y += spaceAfter*0.01*lines.get(lines.size() - 1).getHeight();
  476. } else {
  477. // negative value means the absolute spacing in points
  478. y += -spaceAfter;
  479. }
  480. }
  481. }
  482. return y - y0;
  483. }
  484. @Override
  485. void copy(XSLFShape sh){
  486. super.copy(sh);
  487. XSLFTextShape tsh = (XSLFTextShape)sh;
  488. boolean srcWordWrap = tsh.getWordWrap();
  489. if(srcWordWrap != getWordWrap()){
  490. setWordWrap(srcWordWrap);
  491. }
  492. double leftInset = tsh.getLeftInset();
  493. if(leftInset != getLeftInset()) {
  494. setLeftInset(leftInset);
  495. }
  496. double rightInset = tsh.getRightInset();
  497. if(rightInset != getRightInset()) {
  498. setRightInset(rightInset);
  499. }
  500. double topInset = tsh.getTopInset();
  501. if(topInset != getTopInset()) {
  502. setTopInset(topInset);
  503. }
  504. double bottomInset = tsh.getBottomInset();
  505. if(bottomInset != getBottomInset()) {
  506. setBottomInset(bottomInset);
  507. }
  508. VerticalAlignment vAlign = tsh.getVerticalAlignment();
  509. if(vAlign != getVerticalAlignment()) {
  510. setVerticalAlignment(vAlign);
  511. }
  512. List<XSLFTextParagraph> srcP = tsh.getTextParagraphs();
  513. List<XSLFTextParagraph> tgtP = getTextParagraphs();
  514. for(int i = 0; i < srcP.size(); i++){
  515. XSLFTextParagraph p1 = srcP.get(i);
  516. XSLFTextParagraph p2 = tgtP.get(i);
  517. p2.copy(p1);
  518. }
  519. }
  520. }