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.

XSLFTextParagraph.java 35KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  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.xslf.usermodel;
  16. import java.awt.Color;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Objects;
  22. import java.util.function.Consumer;
  23. import java.util.function.Function;
  24. import java.util.function.Supplier;
  25. import org.apache.poi.ooxml.util.POIXMLUnits;
  26. import org.apache.poi.sl.draw.DrawPaint;
  27. import org.apache.poi.sl.usermodel.AutoNumberingScheme;
  28. import org.apache.poi.sl.usermodel.PaintStyle;
  29. import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
  30. import org.apache.poi.sl.usermodel.TabStop.TabStopType;
  31. import org.apache.poi.sl.usermodel.TextParagraph;
  32. import org.apache.poi.util.Beta;
  33. import org.apache.poi.util.Internal;
  34. import org.apache.poi.util.Units;
  35. import org.apache.poi.xslf.model.ParagraphPropertyFetcher;
  36. import org.apache.poi.xslf.model.ParagraphPropertyFetcher.ParaPropFetcher;
  37. import org.apache.xmlbeans.XmlCursor;
  38. import org.apache.xmlbeans.XmlObject;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.*;
  40. import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
  41. import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType;
  42. import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_PRESENTATIONML;
  43. /**
  44. * Represents a paragraph of text within the containing text body.
  45. * The paragraph is the highest level text separation mechanism.
  46. *
  47. * @since POI-3.8
  48. */
  49. @Beta
  50. public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagraph,XSLFTextRun> {
  51. private final CTTextParagraph _p;
  52. private final List<XSLFTextRun> _runs;
  53. private final XSLFTextShape _shape;
  54. @FunctionalInterface
  55. private interface Procedure {
  56. void accept();
  57. }
  58. XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape) {
  59. _p = p;
  60. _runs = new ArrayList<>();
  61. _shape = shape;
  62. try (XmlCursor c = _p.newCursor()) {
  63. if (c.toFirstChild()) {
  64. do {
  65. XmlObject r = c.getObject();
  66. if (r instanceof CTTextLineBreak) {
  67. _runs.add(new XSLFLineBreak((CTTextLineBreak)r, this));
  68. } else if (r instanceof CTRegularTextRun || r instanceof CTTextField) {
  69. _runs.add(new XSLFTextRun(r, this));
  70. }
  71. } while (c.toNextSibling());
  72. }
  73. }
  74. }
  75. public String getText() {
  76. StringBuilder out = new StringBuilder();
  77. for (XSLFTextRun r : _runs) {
  78. out.append(r.getRawText());
  79. }
  80. return out.toString();
  81. }
  82. @Internal
  83. public CTTextParagraph getXmlObject() {
  84. return _p;
  85. }
  86. @Override
  87. public XSLFTextShape getParentShape() {
  88. return _shape;
  89. }
  90. @Override
  91. public List<XSLFTextRun> getTextRuns() {
  92. return Collections.unmodifiableList(_runs);
  93. }
  94. @Override
  95. public Iterator<XSLFTextRun> iterator() {
  96. return getTextRuns().iterator();
  97. }
  98. /**
  99. * Add a new run of text
  100. *
  101. * @return a new run of text
  102. */
  103. public XSLFTextRun addNewTextRun() {
  104. CTRegularTextRun r = _p.addNewR();
  105. CTTextCharacterProperties rPr = r.addNewRPr();
  106. rPr.setLang("en-US");
  107. XSLFTextRun run = newTextRun(r);
  108. _runs.add(run);
  109. return run;
  110. }
  111. /**
  112. * Remove a text run
  113. *
  114. * @param textRun a run of text
  115. * @return whether the run was removed
  116. * @since POI 5.2.2
  117. */
  118. public boolean removeTextRun(XSLFTextRun textRun) {
  119. if (_runs.remove(textRun)) {
  120. XmlObject xo = textRun.getXmlObject();
  121. if (xo instanceof CTRegularTextRun) {
  122. for (int i = 0; i < getXmlObject().sizeOfRArray(); i++) {
  123. if (getXmlObject().getRArray(i).equals(xo)) {
  124. getXmlObject().removeR(i);
  125. return true;
  126. }
  127. }
  128. } else if (xo instanceof CTTextField) {
  129. for (int i = 0; i < getXmlObject().sizeOfFldArray(); i++) {
  130. if (getXmlObject().getFldArray(i).equals(xo)) {
  131. getXmlObject().removeFld(i);
  132. return true;
  133. }
  134. }
  135. } else if (xo instanceof CTTextLineBreak) {
  136. for (int i = 0; i < getXmlObject().sizeOfBrArray(); i++) {
  137. if (getXmlObject().getBrArray(i).equals(xo)) {
  138. getXmlObject().removeBr(i);
  139. return true;
  140. }
  141. }
  142. }
  143. return false;
  144. }
  145. return false;
  146. }
  147. /**
  148. * Insert a line break
  149. *
  150. * @return text run representing this line break ('\n')
  151. */
  152. @SuppressWarnings("WeakerAccess")
  153. public XSLFTextRun addLineBreak() {
  154. XSLFLineBreak run = new XSLFLineBreak(_p.addNewBr(), this);
  155. CTTextCharacterProperties brProps = run.getRPr(true);
  156. if (!_runs.isEmpty()) {
  157. // by default line break has the font size of the last text run
  158. CTTextCharacterProperties prevRun = _runs.get(_runs.size() - 1).getRPr(true);
  159. brProps.set(prevRun);
  160. // don't copy hlink properties
  161. if (brProps.isSetHlinkClick()) {
  162. brProps.unsetHlinkClick();
  163. }
  164. if (brProps.isSetHlinkMouseOver()) {
  165. brProps.unsetHlinkMouseOver();
  166. }
  167. }
  168. _runs.add(run);
  169. return run;
  170. }
  171. @Override
  172. public TextAlign getTextAlign() {
  173. return fetchParagraphProperty((props,val) -> {
  174. if (props.isSetAlgn()) {
  175. val.accept(TextAlign.values()[props.getAlgn().intValue() - 1]);
  176. }
  177. });
  178. }
  179. @Override
  180. public void setTextAlign(TextAlign align) {
  181. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  182. if(align == null) {
  183. if(pr.isSetAlgn()) {
  184. pr.unsetAlgn();
  185. }
  186. } else {
  187. pr.setAlgn(STTextAlignType.Enum.forInt(align.ordinal() + 1));
  188. }
  189. }
  190. @Override
  191. public FontAlign getFontAlign() {
  192. return fetchParagraphProperty((props,val) -> {
  193. if (props.isSetFontAlgn()) {
  194. val.accept(FontAlign.values()[props.getFontAlgn().intValue() - 1]);
  195. }
  196. });
  197. }
  198. /**
  199. * Specifies the font alignment that is to be applied to the paragraph.
  200. * Possible values for this include auto, top, center, baseline and bottom.
  201. * see {@link org.apache.poi.sl.usermodel.TextParagraph.FontAlign}.
  202. *
  203. * @param align font align
  204. */
  205. @SuppressWarnings("unused")
  206. public void setFontAlign(FontAlign align) {
  207. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  208. if(align == null) {
  209. if(pr.isSetFontAlgn()) {
  210. pr.unsetFontAlgn();
  211. }
  212. } else {
  213. pr.setFontAlgn(STTextFontAlignType.Enum.forInt(align.ordinal() + 1));
  214. }
  215. }
  216. /**
  217. * @return the font to be used on bullet characters within a given paragraph
  218. */
  219. @SuppressWarnings("WeakerAccess")
  220. public String getBulletFont() {
  221. return fetchParagraphProperty((props, val) -> {
  222. if (props.isSetBuFont()) {
  223. val.accept(props.getBuFont().getTypeface());
  224. }
  225. });
  226. }
  227. @SuppressWarnings("WeakerAccess")
  228. public void setBulletFont(String typeface) {
  229. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  230. CTTextFont font = pr.isSetBuFont() ? pr.getBuFont() : pr.addNewBuFont();
  231. font.setTypeface(typeface);
  232. }
  233. /**
  234. * @return the character to be used in place of the standard bullet point
  235. */
  236. @SuppressWarnings("WeakerAccess")
  237. public String getBulletCharacter() {
  238. return fetchParagraphProperty((props, val) -> {
  239. if (props.isSetBuChar()) {
  240. val.accept(props.getBuChar().getChar());
  241. }
  242. });
  243. }
  244. @SuppressWarnings("WeakerAccess")
  245. public void setBulletCharacter(String str) {
  246. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  247. CTTextCharBullet c = pr.isSetBuChar() ? pr.getBuChar() : pr.addNewBuChar();
  248. c.setChar(str);
  249. }
  250. /**
  251. *
  252. * @return the color of bullet characters within a given paragraph.
  253. * A <code>null</code> value means to use the text font color.
  254. */
  255. @SuppressWarnings("WeakerAccess")
  256. public PaintStyle getBulletFontColor() {
  257. Color col = fetchParagraphProperty(this::fetchBulletFontColor);
  258. return (col == null) ? null : DrawPaint.createSolidPaint(col);
  259. }
  260. private void fetchBulletFontColor(CTTextParagraphProperties props, Consumer<Color> val) {
  261. final XSLFSheet sheet = getParentShape().getSheet();
  262. final XSLFTheme theme = sheet.getTheme();
  263. if(props.isSetBuClr()) {
  264. XSLFColor c = new XSLFColor(props.getBuClr(), theme, null, sheet);
  265. val.accept(c.getColor());
  266. }
  267. }
  268. @SuppressWarnings("WeakerAccess")
  269. public void setBulletFontColor(Color color) {
  270. setBulletFontColor(DrawPaint.createSolidPaint(color));
  271. }
  272. /**
  273. * Set the color to be used on bullet characters within a given paragraph.
  274. *
  275. * @param color the bullet color
  276. */
  277. @SuppressWarnings("WeakerAccess")
  278. public void setBulletFontColor(PaintStyle color) {
  279. if (!(color instanceof SolidPaint)) {
  280. throw new IllegalArgumentException("Currently XSLF only supports SolidPaint");
  281. }
  282. // TODO: implement setting bullet color to null
  283. SolidPaint sp = (SolidPaint)color;
  284. Color col = DrawPaint.applyColorTransform(sp.getSolidColor());
  285. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  286. CTColor c = pr.isSetBuClr() ? pr.getBuClr() : pr.addNewBuClr();
  287. CTSRgbColor clr = c.isSetSrgbClr() ? c.getSrgbClr() : c.addNewSrgbClr();
  288. clr.setVal(new byte[]{(byte) col.getRed(), (byte) col.getGreen(), (byte) col.getBlue()});
  289. }
  290. /**
  291. * Returns the bullet size that is to be used within a paragraph.
  292. * This may be specified in two different ways, percentage spacing and font point spacing:
  293. * <p>
  294. * If bulletSize &gt;= 0, then bulletSize is a percentage of the font size.
  295. * If bulletSize &lt; 0, then it specifies the size in points
  296. * </p>
  297. *
  298. * @return the bullet size
  299. */
  300. @SuppressWarnings("WeakerAccess")
  301. public Double getBulletFontSize() {
  302. return fetchParagraphProperty(XSLFTextParagraph::fetchBulletFontSize);
  303. }
  304. private static void fetchBulletFontSize(CTTextParagraphProperties props, Consumer<Double> val) {
  305. if(props.isSetBuSzPct()) {
  306. val.accept(POIXMLUnits.parsePercent(props.getBuSzPct().xgetVal()) * 0.001);
  307. }
  308. if(props.isSetBuSzPts()) {
  309. val.accept( - props.getBuSzPts().getVal() * 0.01);
  310. }
  311. }
  312. /**
  313. * Sets the bullet size that is to be used within a paragraph.
  314. * This may be specified in two different ways, percentage spacing and font point spacing:
  315. * <p>
  316. * If bulletSize &gt;= 0, then bulletSize is a percentage of the font size.
  317. * If bulletSize &lt; 0, then it specifies the size in points
  318. * </p>
  319. */
  320. @SuppressWarnings("WeakerAccess")
  321. public void setBulletFontSize(double bulletSize) {
  322. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  323. if(bulletSize >= 0) {
  324. CTTextBulletSizePercent pt = pr.isSetBuSzPct() ? pr.getBuSzPct() : pr.addNewBuSzPct();
  325. pt.setVal(Integer.toString((int)(bulletSize*1000)));
  326. if(pr.isSetBuSzPts()) {
  327. pr.unsetBuSzPts();
  328. }
  329. } else {
  330. CTTextBulletSizePoint pt = pr.isSetBuSzPts() ? pr.getBuSzPts() : pr.addNewBuSzPts();
  331. pt.setVal((int)(-bulletSize*100));
  332. if(pr.isSetBuSzPct()) {
  333. pr.unsetBuSzPct();
  334. }
  335. }
  336. }
  337. /**
  338. * @return the auto numbering scheme, or null if not defined
  339. */
  340. @SuppressWarnings("WeakerAccess")
  341. public AutoNumberingScheme getAutoNumberingScheme() {
  342. return fetchParagraphProperty(XSLFTextParagraph::fetchAutoNumberingScheme);
  343. }
  344. private static void fetchAutoNumberingScheme(CTTextParagraphProperties props, Consumer<AutoNumberingScheme> val) {
  345. if (props.isSetBuAutoNum()) {
  346. AutoNumberingScheme ans = AutoNumberingScheme.forOoxmlID(props.getBuAutoNum().getType().intValue());
  347. if (ans != null) {
  348. val.accept(ans);
  349. }
  350. }
  351. }
  352. /**
  353. * @return the auto numbering starting number, or null if not defined
  354. */
  355. @SuppressWarnings("WeakerAccess")
  356. public Integer getAutoNumberingStartAt() {
  357. return fetchParagraphProperty((props, val) -> {
  358. if (props.isSetBuAutoNum() && props.getBuAutoNum().isSetStartAt()) {
  359. val.accept(props.getBuAutoNum().getStartAt());
  360. }
  361. });
  362. }
  363. @Override
  364. public void setIndent(Double indent) {
  365. if ((indent == null) && !_p.isSetPPr()) {
  366. return;
  367. }
  368. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  369. if(indent == null) {
  370. if(pr.isSetIndent()) {
  371. pr.unsetIndent();
  372. }
  373. } else {
  374. pr.setIndent(Units.toEMU(indent));
  375. }
  376. }
  377. @Override
  378. public Double getIndent() {
  379. return fetchParagraphProperty((props, val) -> {
  380. if (props.isSetIndent()) {
  381. val.accept(Units.toPoints(props.getIndent()));
  382. }
  383. });
  384. }
  385. @Override
  386. public void setLeftMargin(Double leftMargin) {
  387. if (leftMargin == null && !_p.isSetPPr()) {
  388. return;
  389. }
  390. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  391. if (leftMargin == null) {
  392. if(pr.isSetMarL()) {
  393. pr.unsetMarL();
  394. }
  395. } else {
  396. pr.setMarL(Units.toEMU(leftMargin));
  397. }
  398. }
  399. /**
  400. * @return the left margin (in points) of the paragraph, null if unset
  401. */
  402. @Override
  403. public Double getLeftMargin() {
  404. return fetchParagraphProperty((props, val) -> {
  405. if (props.isSetMarL()) {
  406. val.accept(Units.toPoints(props.getMarL()));
  407. }
  408. });
  409. }
  410. @Override
  411. public void setRightMargin(Double rightMargin) {
  412. if (rightMargin == null && !_p.isSetPPr()) {
  413. return;
  414. }
  415. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  416. if(rightMargin == null) {
  417. if(pr.isSetMarR()) {
  418. pr.unsetMarR();
  419. }
  420. } else {
  421. pr.setMarR(Units.toEMU(rightMargin));
  422. }
  423. }
  424. /**
  425. *
  426. * @return the right margin of the paragraph, null if unset
  427. */
  428. @Override
  429. public Double getRightMargin() {
  430. return fetchParagraphProperty((props, val) -> {
  431. if (props.isSetMarR()) {
  432. val.accept(Units.toPoints(props.getMarR()));
  433. }
  434. });
  435. }
  436. @Override
  437. public Double getDefaultTabSize() {
  438. return fetchParagraphProperty((props, val) -> {
  439. if (props.isSetDefTabSz()) {
  440. val.accept(Units.toPoints(POIXMLUnits.parseLength(props.xgetDefTabSz())));
  441. }
  442. });
  443. }
  444. @SuppressWarnings("WeakerAccess")
  445. public double getTabStop(final int idx) {
  446. Double d = fetchParagraphProperty((props,val) -> fetchTabStop(idx,props,val));
  447. return (d == null) ? 0. : d;
  448. }
  449. private static void fetchTabStop(final int idx, CTTextParagraphProperties props, Consumer<Double> val) {
  450. if (props.isSetTabLst()) {
  451. CTTextTabStopList tabStops = props.getTabLst();
  452. if(idx < tabStops.sizeOfTabArray() ) {
  453. CTTextTabStop ts = tabStops.getTabArray(idx);
  454. val.accept(Units.toPoints(POIXMLUnits.parseLength(ts.xgetPos())));
  455. }
  456. }
  457. }
  458. @SuppressWarnings("WeakerAccess")
  459. public void addTabStop(double value) {
  460. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  461. CTTextTabStopList tabStops = pr.isSetTabLst() ? pr.getTabLst() : pr.addNewTabLst();
  462. tabStops.addNewTab().setPos(Units.toEMU(value));
  463. }
  464. @Override
  465. public void setLineSpacing(Double lineSpacing) {
  466. setSpacing(lineSpacing, props -> props::getLnSpc, props -> props::addNewLnSpc, props -> props::unsetLnSpc);
  467. }
  468. @Override
  469. public Double getLineSpacing() {
  470. final Double lnSpc = getSpacing(props -> props::getLnSpc);
  471. if (lnSpc != null && lnSpc > 0) {
  472. // check if the percentage value is scaled
  473. final CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit();
  474. if (normAutofit != null) {
  475. final double scale = 1 - POIXMLUnits.parsePercent(normAutofit.xgetLnSpcReduction()) / 100_000.;
  476. return lnSpc * scale;
  477. }
  478. }
  479. return lnSpc;
  480. }
  481. @Override
  482. public void setSpaceBefore(Double spaceBefore) {
  483. setSpacing(spaceBefore, props -> props::getSpcBef, props -> props::addNewSpcBef, props -> props::unsetSpcBef);
  484. }
  485. @Override
  486. public Double getSpaceBefore() {
  487. return getSpacing(props -> props::getSpcBef);
  488. }
  489. @Override
  490. public void setSpaceAfter(Double spaceAfter) {
  491. setSpacing(spaceAfter, props -> props::getSpcAft, props -> props::addNewSpcAft, props -> props::unsetSpcAft);
  492. }
  493. @Override
  494. public Double getSpaceAfter() {
  495. return getSpacing(props -> props::getSpcAft);
  496. }
  497. private void setSpacing(final Double space,
  498. final Function<CTTextParagraphProperties,Supplier<CTTextSpacing>> getSpc,
  499. final Function<CTTextParagraphProperties,Supplier<CTTextSpacing>> addSpc,
  500. final Function<CTTextParagraphProperties,Procedure> unsetSpc
  501. ) {
  502. final CTTextParagraphProperties pPr = (space == null || _p.isSetPPr()) ? _p.getPPr() : _p.addNewPPr();
  503. if (pPr == null) {
  504. return;
  505. }
  506. CTTextSpacing spc = getSpc.apply(pPr).get();
  507. if (space == null) {
  508. if (spc != null) {
  509. // unset the space before on null input
  510. unsetSpc.apply(pPr).accept();
  511. }
  512. return;
  513. }
  514. if (spc == null) {
  515. spc = addSpc.apply(pPr).get();
  516. }
  517. if (space >= 0) {
  518. if (spc.isSetSpcPts()) {
  519. spc.unsetSpcPts();
  520. }
  521. final CTTextSpacingPercent pct = spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct();
  522. pct.setVal((int)(space*1000));
  523. } else {
  524. if (spc.isSetSpcPct()) {
  525. spc.unsetSpcPct();
  526. }
  527. final CTTextSpacingPoint pts = spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts();
  528. pts.setVal((int)(-space*100));
  529. }
  530. }
  531. private Double getSpacing(final Function<CTTextParagraphProperties,Supplier<CTTextSpacing>> getSpc) {
  532. return fetchParagraphProperty((props,val) -> fetchSpacing(getSpc,props,val));
  533. }
  534. private static void fetchSpacing(final Function<CTTextParagraphProperties,Supplier<CTTextSpacing>> getSpc,
  535. CTTextParagraphProperties props, Consumer<Double> val) {
  536. final CTTextSpacing spc = getSpc.apply(props).get();
  537. if (spc != null) {
  538. if (spc.isSetSpcPct()) {
  539. val.accept( POIXMLUnits.parsePercent(spc.getSpcPct().xgetVal())*0.001 );
  540. } else if (spc.isSetSpcPts()) {
  541. val.accept( -spc.getSpcPts().getVal()*0.01 );
  542. }
  543. }
  544. }
  545. @Override
  546. public void setIndentLevel(int level) {
  547. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  548. pr.setLvl(level);
  549. }
  550. @Override
  551. public int getIndentLevel() {
  552. CTTextParagraphProperties pr = _p.getPPr();
  553. return (pr == null || !pr.isSetLvl()) ? 0 : pr.getLvl();
  554. }
  555. /**
  556. * Returns whether this paragraph has bullets
  557. */
  558. public boolean isBullet() {
  559. Boolean b = fetchParagraphProperty(XSLFTextParagraph::fetchIsBullet);
  560. return b == null ? false : b;
  561. }
  562. private static void fetchIsBullet(CTTextParagraphProperties props, Consumer<Boolean> val) {
  563. if (props.isSetBuNone()) {
  564. val.accept(false);
  565. } else if(props.isSetBuFont() || props.isSetBuChar()) {
  566. val.accept(true);
  567. }
  568. }
  569. /**
  570. *
  571. * @param flag whether text in this paragraph has bullets
  572. */
  573. public void setBullet(boolean flag) {
  574. if(isBullet() == flag) {
  575. return;
  576. }
  577. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  578. if(flag) {
  579. pr.addNewBuFont().setTypeface("Arial");
  580. pr.addNewBuChar().setChar("\u2022");
  581. } else {
  582. if (pr.isSetBuFont()) {
  583. pr.unsetBuFont();
  584. }
  585. if (pr.isSetBuChar()) {
  586. pr.unsetBuChar();
  587. }
  588. if (pr.isSetBuAutoNum()) {
  589. pr.unsetBuAutoNum();
  590. }
  591. if (pr.isSetBuBlip()) {
  592. pr.unsetBuBlip();
  593. }
  594. if (pr.isSetBuClr()) {
  595. pr.unsetBuClr();
  596. }
  597. if (pr.isSetBuClrTx()) {
  598. pr.unsetBuClrTx();
  599. }
  600. if (pr.isSetBuFont()) {
  601. pr.unsetBuFont();
  602. }
  603. if (pr.isSetBuFontTx()) {
  604. pr.unsetBuFontTx();
  605. }
  606. if (pr.isSetBuSzPct()) {
  607. pr.unsetBuSzPct();
  608. }
  609. if (pr.isSetBuSzPts()) {
  610. pr.unsetBuSzPts();
  611. }
  612. if (pr.isSetBuSzTx()) {
  613. pr.unsetBuSzTx();
  614. }
  615. pr.addNewBuNone();
  616. }
  617. }
  618. /**
  619. * Specifies that automatic numbered bullet points should be applied to this paragraph
  620. *
  621. * @param scheme type of auto-numbering
  622. * @param startAt the number that will start number for a given sequence of automatically
  623. numbered bullets (1-based).
  624. */
  625. @SuppressWarnings("WeakerAccess")
  626. public void setBulletAutoNumber(AutoNumberingScheme scheme, int startAt) {
  627. if(startAt < 1) {
  628. throw new IllegalArgumentException("Start Number must be greater or equal that 1") ;
  629. }
  630. CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
  631. CTTextAutonumberBullet lst = pr.isSetBuAutoNum() ? pr.getBuAutoNum() : pr.addNewBuAutoNum();
  632. lst.setType(STTextAutonumberScheme.Enum.forInt(scheme.ooxmlId));
  633. lst.setStartAt(startAt);
  634. }
  635. @Override
  636. public String toString() {
  637. return "[" + getClass() + "]" + getText();
  638. }
  639. /**
  640. * @return master style text paragraph properties, or <code>null</code> if
  641. * there are no master slides or the master slides do not contain a text paragraph
  642. */
  643. @Internal
  644. public CTTextParagraphProperties getDefaultMasterStyle() {
  645. CTPlaceholder ph = _shape.getPlaceholderDetails().getCTPlaceholder(false);
  646. String defaultStyleSelector;
  647. switch(ph == null ? -1 : ph.getType().intValue()) {
  648. case STPlaceholderType.INT_TITLE:
  649. case STPlaceholderType.INT_CTR_TITLE:
  650. defaultStyleSelector = "titleStyle";
  651. break;
  652. case -1: // no placeholder means plain text box
  653. case STPlaceholderType.INT_FTR:
  654. case STPlaceholderType.INT_SLD_NUM:
  655. case STPlaceholderType.INT_DT:
  656. defaultStyleSelector = "otherStyle";
  657. break;
  658. default:
  659. defaultStyleSelector = "bodyStyle";
  660. break;
  661. }
  662. int level = getIndentLevel();
  663. // wind up and find the root master sheet which must be slide master
  664. final String nsPML = NS_PRESENTATIONML;
  665. XSLFSheet masterSheet = _shape.getSheet();
  666. for (XSLFSheet m = masterSheet; m != null; m = (XSLFSheet)m.getMasterSheet()) {
  667. masterSheet = m;
  668. XmlObject xo = masterSheet.getXmlObject();
  669. try (XmlCursor cur = xo.newCursor()) {
  670. cur.push();
  671. if ((cur.toChild(nsPML, "txStyles") && cur.toChild(nsPML, defaultStyleSelector)) ||
  672. (cur.pop() && cur.toChild(nsPML, "notesStyle"))) {
  673. while (level >= 0) {
  674. cur.push();
  675. if (cur.toChild(XSLFRelation.NS_DRAWINGML, "lvl" +(level+1)+ "pPr")) {
  676. return (CTTextParagraphProperties)cur.getObject();
  677. }
  678. cur.pop();
  679. level--;
  680. }
  681. }
  682. }
  683. }
  684. return null;
  685. }
  686. private <T> T fetchParagraphProperty(ParaPropFetcher<T> fetcher) {
  687. final XSLFTextShape shape = getParentShape();
  688. return new ParagraphPropertyFetcher<>(this, fetcher).fetchProperty(shape);
  689. }
  690. void copy(XSLFTextParagraph other) {
  691. if (other == this) {
  692. return;
  693. }
  694. CTTextParagraph thisP = getXmlObject();
  695. CTTextParagraph otherP = other.getXmlObject();
  696. if (thisP.isSetPPr()) {
  697. thisP.unsetPPr();
  698. }
  699. if (thisP.isSetEndParaRPr()) {
  700. thisP.unsetEndParaRPr();
  701. }
  702. _runs.clear();
  703. for (int i=thisP.sizeOfBrArray(); i>0; i--) {
  704. thisP.removeBr(i-1);
  705. }
  706. for (int i=thisP.sizeOfRArray(); i>0; i--) {
  707. thisP.removeR(i-1);
  708. }
  709. for (int i=thisP.sizeOfFldArray(); i>0; i--) {
  710. thisP.removeFld(i-1);
  711. }
  712. for (XSLFTextRun tr : other.getTextRuns()) {
  713. XmlObject xo = tr.getXmlObject().copy();
  714. XSLFTextRun run = addNewTextRun();
  715. run.getXmlObject().set(xo);
  716. run.copy(tr);
  717. }
  718. // set properties again, in case we are based on a different
  719. // template
  720. TextAlign srcAlign = other.getTextAlign();
  721. if(srcAlign != getTextAlign()) {
  722. setTextAlign(srcAlign);
  723. }
  724. boolean isBullet = other.isBullet();
  725. if(isBullet != isBullet()) {
  726. setBullet(isBullet);
  727. if(isBullet) {
  728. String buFont = other.getBulletFont();
  729. if(buFont != null && !buFont.equals(getBulletFont())) {
  730. setBulletFont(buFont);
  731. }
  732. String buChar = other.getBulletCharacter();
  733. if(buChar != null && !buChar.equals(getBulletCharacter())) {
  734. setBulletCharacter(buChar);
  735. }
  736. PaintStyle buColor = other.getBulletFontColor();
  737. if(buColor != null && !buColor.equals(getBulletFontColor())) {
  738. setBulletFontColor(buColor);
  739. }
  740. Double buSize = other.getBulletFontSize();
  741. if(doubleNotEquals(buSize, getBulletFontSize())) {
  742. setBulletFontSize(buSize);
  743. }
  744. }
  745. }
  746. Double leftMargin = other.getLeftMargin();
  747. if (doubleNotEquals(leftMargin, getLeftMargin())) {
  748. setLeftMargin(leftMargin);
  749. }
  750. Double indent = other.getIndent();
  751. if (doubleNotEquals(indent, getIndent())) {
  752. setIndent(indent);
  753. }
  754. Double spaceAfter = other.getSpaceAfter();
  755. if (doubleNotEquals(spaceAfter, getSpaceAfter())) {
  756. setSpaceAfter(spaceAfter);
  757. }
  758. Double spaceBefore = other.getSpaceBefore();
  759. if (doubleNotEquals(spaceBefore, getSpaceBefore())) {
  760. setSpaceBefore(spaceBefore);
  761. }
  762. Double lineSpacing = other.getLineSpacing();
  763. if (doubleNotEquals(lineSpacing, getLineSpacing())) {
  764. setLineSpacing(lineSpacing);
  765. }
  766. }
  767. private static boolean doubleNotEquals(Double d1, Double d2) {
  768. return !Objects.equals(d1, d2);
  769. }
  770. @Override
  771. public Double getDefaultFontSize() {
  772. CTTextCharacterProperties endPr = _p.getEndParaRPr();
  773. if (endPr == null || !endPr.isSetSz()) {
  774. // inherit the font size from the master style
  775. CTTextParagraphProperties masterStyle = getDefaultMasterStyle();
  776. if (masterStyle != null) {
  777. endPr = masterStyle.getDefRPr();
  778. }
  779. }
  780. return (endPr == null || !endPr.isSetSz()) ? 12 : (endPr.getSz() / 100.);
  781. }
  782. @Override
  783. public String getDefaultFontFamily() {
  784. String family = (_runs.isEmpty() ? null : _runs.get(0).getFontFamily());
  785. return (family == null) ? "Arial" : family;
  786. }
  787. @Override
  788. public BulletStyle getBulletStyle() {
  789. if (!isBullet()) {
  790. return null;
  791. }
  792. return new BulletStyle() {
  793. @Override
  794. public String getBulletCharacter() {
  795. return XSLFTextParagraph.this.getBulletCharacter();
  796. }
  797. @Override
  798. public String getBulletFont() {
  799. return XSLFTextParagraph.this.getBulletFont();
  800. }
  801. @Override
  802. public Double getBulletFontSize() {
  803. return XSLFTextParagraph.this.getBulletFontSize();
  804. }
  805. @Override
  806. public PaintStyle getBulletFontColor() {
  807. return XSLFTextParagraph.this.getBulletFontColor();
  808. }
  809. @Override
  810. public void setBulletFontColor(Color color) {
  811. setBulletFontColor(DrawPaint.createSolidPaint(color));
  812. }
  813. @Override
  814. public void setBulletFontColor(PaintStyle color) {
  815. XSLFTextParagraph.this.setBulletFontColor(color);
  816. }
  817. @Override
  818. public AutoNumberingScheme getAutoNumberingScheme() {
  819. return XSLFTextParagraph.this.getAutoNumberingScheme();
  820. }
  821. @Override
  822. public Integer getAutoNumberingStartAt() {
  823. return XSLFTextParagraph.this.getAutoNumberingStartAt();
  824. }
  825. };
  826. }
  827. @Override
  828. public void setBulletStyle(Object... styles) {
  829. if (styles.length == 0) {
  830. setBullet(false);
  831. } else {
  832. setBullet(true);
  833. for (Object ostyle : styles) {
  834. if (ostyle instanceof Number) {
  835. setBulletFontSize(((Number)ostyle).doubleValue());
  836. } else if (ostyle instanceof Color) {
  837. setBulletFontColor((Color)ostyle);
  838. } else if (ostyle instanceof Character) {
  839. setBulletCharacter(ostyle.toString());
  840. } else if (ostyle instanceof String) {
  841. setBulletFont((String)ostyle);
  842. } else if (ostyle instanceof AutoNumberingScheme) {
  843. setBulletAutoNumber((AutoNumberingScheme)ostyle, 0);
  844. }
  845. }
  846. }
  847. }
  848. @Override
  849. public List<XSLFTabStop> getTabStops() {
  850. return fetchParagraphProperty(XSLFTextParagraph::fetchTabStops);
  851. }
  852. private static void fetchTabStops(CTTextParagraphProperties props, Consumer<List<XSLFTabStop>> val) {
  853. if (props.isSetTabLst()) {
  854. final List<XSLFTabStop> list = new ArrayList<>();
  855. //noinspection deprecation
  856. for (final CTTextTabStop ta : props.getTabLst().getTabArray()) {
  857. list.add(new XSLFTabStop(ta));
  858. }
  859. val.accept(list);
  860. }
  861. }
  862. @Override
  863. public void addTabStops(double positionInPoints, TabStopType tabStopType) {
  864. final XSLFSheet sheet = getParentShape().getSheet();
  865. final CTTextParagraphProperties tpp;
  866. if (sheet instanceof XSLFSlideMaster) {
  867. tpp = getDefaultMasterStyle();
  868. } else {
  869. final CTTextParagraph xo = getXmlObject();
  870. tpp = (xo.isSetPPr()) ? xo.getPPr() : xo.addNewPPr();
  871. }
  872. if (tpp == null) {
  873. return;
  874. }
  875. final CTTextTabStopList stl = (tpp.isSetTabLst()) ? tpp.getTabLst() : tpp.addNewTabLst();
  876. XSLFTabStop tab = new XSLFTabStop(stl.addNewTab());
  877. tab.setPositionInPoints(positionInPoints);
  878. tab.setType(tabStopType);
  879. }
  880. @Override
  881. public void clearTabStops() {
  882. final XSLFSheet sheet = getParentShape().getSheet();
  883. CTTextParagraphProperties tpp = (sheet instanceof XSLFSlideMaster) ? getDefaultMasterStyle() : getXmlObject().getPPr();
  884. if (tpp != null && tpp.isSetTabLst()) {
  885. tpp.unsetTabLst();
  886. }
  887. }
  888. /**
  889. * Helper method for appending text and keeping paragraph and character properties.
  890. * The character properties are moved to the end paragraph marker
  891. */
  892. /* package */ void clearButKeepProperties() {
  893. CTTextParagraph thisP = getXmlObject();
  894. for (int i=thisP.sizeOfBrArray(); i>0; i--) {
  895. thisP.removeBr(i-1);
  896. }
  897. for (int i=thisP.sizeOfFldArray(); i>0; i--) {
  898. thisP.removeFld(i-1);
  899. }
  900. if (!_runs.isEmpty()) {
  901. int size = _runs.size();
  902. XSLFTextRun lastRun = _runs.get(size-1);
  903. CTTextCharacterProperties cpOther = lastRun.getRPr(false);
  904. if (cpOther != null) {
  905. if (thisP.isSetEndParaRPr()) {
  906. thisP.unsetEndParaRPr();
  907. }
  908. CTTextCharacterProperties cp = thisP.addNewEndParaRPr();
  909. cp.set(cpOther);
  910. }
  911. for (int i=size; i>0; i--) {
  912. thisP.removeR(i-1);
  913. }
  914. _runs.clear();
  915. }
  916. }
  917. @Override
  918. public boolean isHeaderOrFooter() {
  919. CTPlaceholder ph = _shape.getPlaceholderDetails().getCTPlaceholder(false);
  920. int phId = (ph == null ? -1 : ph.getType().intValue());
  921. switch (phId) {
  922. case STPlaceholderType.INT_SLD_NUM:
  923. case STPlaceholderType.INT_DT:
  924. case STPlaceholderType.INT_FTR:
  925. case STPlaceholderType.INT_HDR:
  926. return true;
  927. default:
  928. return false;
  929. }
  930. }
  931. /**
  932. * Helper method to allow subclasses to provide their own text run
  933. *
  934. * @param r the xml reference
  935. *
  936. * @return a new text paragraph
  937. *
  938. * @since POI 3.15-beta2
  939. */
  940. protected XSLFTextRun newTextRun(XmlObject r) {
  941. return new XSLFTextRun(r, this);
  942. }
  943. @SuppressWarnings("WeakerAccess")
  944. protected XSLFTextRun newTextRun(CTTextLineBreak r) {
  945. return new XSLFLineBreak(r, this);
  946. }
  947. }