--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel;
+
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.Units;
+import org.apache.poi.xssf.usermodel.TextAlign;
+import org.apache.poi.xssf.model.ParagraphPropertyFetcher;
+import org.apache.xmlbeans.XmlObject;
+import org.openxmlformats.schemas.drawingml.x2006.main.*;
+import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShape;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a paragraph of text within the containing text body.
+ * The paragraph is the highest level text separation mechanism.
+ */
+public class XSSFTextParagraph implements Iterable<XSSFTextRun>{
+ private final CTTextParagraph _p;
+ private final CTShape _shape;
+ private final List<XSSFTextRun> _runs;
+
+ XSSFTextParagraph(CTTextParagraph p, CTShape ctShape){
+ _p = p;
+ _shape = ctShape;
+ _runs = new ArrayList<XSSFTextRun>();
+
+ for(XmlObject ch : _p.selectPath("*")){
+ if(ch instanceof CTRegularTextRun){
+ CTRegularTextRun r = (CTRegularTextRun)ch;
+ _runs.add(new XSSFTextRun(r, this));
+ } else if (ch instanceof CTTextLineBreak){
+ CTTextLineBreak br = (CTTextLineBreak)ch;
+ CTRegularTextRun r = CTRegularTextRun.Factory.newInstance();
+ r.setRPr(br.getRPr());
+ r.setT("\n");
+ _runs.add(new XSSFTextRun(r, this));
+ } else if (ch instanceof CTTextField){
+ CTTextField f = (CTTextField)ch;
+ CTRegularTextRun r = CTRegularTextRun.Factory.newInstance();
+ r.setRPr(f.getRPr());
+ r.setT(f.getT());
+ _runs.add(new XSSFTextRun(r, this));
+ }
+ }
+ }
+
+ public String getText(){
+ StringBuilder out = new StringBuilder();
+ for (XSSFTextRun r : _runs) {
+ out.append(r.getText());
+ }
+ return out.toString();
+ }
+
+ @Internal
+ public CTTextParagraph getXmlObject(){
+ return _p;
+ }
+
+ @Internal
+ public CTShape getParentShape(){
+ return _shape;
+ }
+
+ public List<XSSFTextRun> getTextRuns(){
+ return _runs;
+ }
+
+ public Iterator<XSSFTextRun> iterator(){
+ return _runs.iterator();
+ }
+
+ /**
+ * Add a new run of text
+ *
+ * @return a new run of text
+ */
+ public XSSFTextRun addNewTextRun(){
+ CTRegularTextRun r = _p.addNewR();
+ CTTextCharacterProperties rPr = r.addNewRPr();
+ rPr.setLang("en-US");
+ XSSFTextRun run = new XSSFTextRun(r, this);
+ _runs.add(run);
+ return run;
+ }
+
+ /**
+ * Insert a line break
+ *
+ * @return text run representing this line break ('\n')
+ */
+ public XSSFTextRun addLineBreak(){
+ CTTextLineBreak br = _p.addNewBr();
+ CTTextCharacterProperties brProps = br.addNewRPr();
+ if(_runs.size() > 0){
+ // by default line break has the font size of the last text run
+ CTTextCharacterProperties prevRun = _runs.get(_runs.size() - 1).getRPr();
+ brProps.set(prevRun);
+ }
+ CTRegularTextRun r = CTRegularTextRun.Factory.newInstance();
+ r.setRPr(brProps);
+ r.setT("\n");
+ XSSFTextRun run = new XSSFLineBreak(r, this, brProps);
+ _runs.add(run);
+ return run;
+ }
+
+ /**
+ * Returns the alignment that is applied to the paragraph.
+ *
+ * If this attribute is omitted, then a value of left is implied.
+ * @return alignment that is applied to the paragraph
+ */
+ public TextAlign getTextAlign(){
+ ParagraphPropertyFetcher<TextAlign> fetcher = new ParagraphPropertyFetcher<TextAlign>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetAlgn()){
+ TextAlign val = TextAlign.values()[props.getAlgn().intValue() - 1];
+ setValue(val);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? TextAlign.LEFT : fetcher.getValue();
+ }
+
+ /**
+ * Specifies the alignment that is to be applied to the paragraph.
+ * Possible values for this include left, right, centered, justified and distributed,
+ * see {@link org.apache.poi.xssf.usermodel.TextAlign}.
+ *
+ * @param align text align
+ */
+ public void setTextAlign(TextAlign align){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(align == null) {
+ if(pr.isSetAlgn()) pr.unsetAlgn();
+ } else {
+ pr.setAlgn(STTextAlignType.Enum.forInt(align.ordinal() + 1));
+ }
+ }
+
+ /**
+ * Returns where vertically on a line of text the actual words are positioned. This deals
+ * with vertical placement of the characters with respect to the baselines.
+ *
+ * If this attribute is omitted, then a value of baseline is implied.
+ * @return alignment that is applied to the paragraph
+ */
+ public TextFontAlign getTextFontAlign(){
+ ParagraphPropertyFetcher<TextFontAlign> fetcher = new ParagraphPropertyFetcher<TextFontAlign>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetAlgn()){
+ TextFontAlign val = TextFontAlign.values()[props.getFontAlgn().intValue() - 1];
+ setValue(val);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? TextFontAlign.BASELINE : fetcher.getValue();
+ }
+
+ /**
+ * Determines where vertically on a line of text the actual words are positioned. This deals
+ * with vertical placement of the characters with respect to the baselines. For instance
+ * having text anchored to the top baseline, anchored to the bottom baseline, centered in
+ * between, etc.
+ *
+ * @param align text font align
+ */
+ public void setTextFontAlign(TextFontAlign align){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(align == null) {
+ if(pr.isSetFontAlgn()) pr.unsetFontAlgn();
+ } else {
+ pr.setFontAlgn(STTextFontAlignType.Enum.forInt(align.ordinal() + 1));
+ }
+ }
+
+ /**
+ * @return the font to be used on bullet characters within a given paragraph
+ */
+ public String getBulletFont(){
+ ParagraphPropertyFetcher<String> fetcher = new ParagraphPropertyFetcher<String>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuFont()){
+ setValue(props.getBuFont().getTypeface());
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue();
+ }
+
+ public void setBulletFont(String typeface){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextFont font = pr.isSetBuFont() ? pr.getBuFont() : pr.addNewBuFont();
+ font.setTypeface(typeface);
+ }
+
+ /**
+ * @return the character to be used in place of the standard bullet point
+ */
+ public String getBulletCharacter(){
+ ParagraphPropertyFetcher<String> fetcher = new ParagraphPropertyFetcher<String>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuChar()){
+ setValue(props.getBuChar().getChar());
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue();
+ }
+
+ public void setBulletCharacter(String str){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextCharBullet c = pr.isSetBuChar() ? pr.getBuChar() : pr.addNewBuChar();
+ c.setChar(str);
+ }
+
+ /**
+ *
+ * @return the color of bullet characters within a given paragraph.
+ * A <code>null</code> value means to use the text font color.
+ */
+ public Color getBulletFontColor(){
+ ParagraphPropertyFetcher<Color> fetcher = new ParagraphPropertyFetcher<Color>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuClr()){
+ if(props.getBuClr().isSetSrgbClr()){
+ CTSRgbColor clr = props.getBuClr().getSrgbClr();
+ byte[] rgb = clr.getVal();
+ setValue(new Color(0xFF & rgb[0], 0xFF & rgb[1], 0xFF & rgb[2]));
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue();
+ }
+
+ /**
+ * Set the color to be used on bullet characters within a given paragraph.
+ *
+ * @param color the bullet color
+ */
+ public void setBulletFontColor(Color color){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTColor c = pr.isSetBuClr() ? pr.getBuClr() : pr.addNewBuClr();
+ CTSRgbColor clr = c.isSetSrgbClr() ? c.getSrgbClr() : c.addNewSrgbClr();
+ clr.setVal(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()});
+ }
+
+ /**
+ * Returns the bullet size that is to be used within a paragraph.
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If bulletSize >= 0, then bulletSize is a percentage of the font size.
+ * If bulletSize < 0, then it specifies the size in points
+ * </p>
+ *
+ * @return the bullet size
+ */
+ public double getBulletFontSize(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuSzPct()){
+ setValue(props.getBuSzPct().getVal() * 0.001);
+ return true;
+ }
+ if(props.isSetBuSzPts()){
+ setValue( - props.getBuSzPts().getVal() * 0.01);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? 100 : fetcher.getValue();
+ }
+
+ /**
+ * Sets the bullet size that is to be used within a paragraph.
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If bulletSize >= 0, then bulletSize is a percentage of the font size.
+ * If bulletSize < 0, then it specifies the size in points
+ * </p>
+ */
+ public void setBulletFontSize(double bulletSize){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+
+ if(bulletSize >= 0) {
+ CTTextBulletSizePercent pt = pr.isSetBuSzPct() ? pr.getBuSzPct() : pr.addNewBuSzPct();
+ pt.setVal((int)(bulletSize*1000));
+ if(pr.isSetBuSzPts()) pr.unsetBuSzPts();
+ } else {
+ CTTextBulletSizePoint pt = pr.isSetBuSzPts() ? pr.getBuSzPts() : pr.addNewBuSzPts();
+ pt.setVal((int)(-bulletSize*100));
+ if(pr.isSetBuSzPct()) pr.unsetBuSzPct();
+ }
+ }
+
+ /**
+ * Specifies the indent size that will be applied to the first line of text in the paragraph.
+ *
+ * @param value the indent in points.
+ */
+ public void setIndent(double value){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(value == -1) {
+ if(pr.isSetIndent()) pr.unsetIndent();
+ } else {
+ pr.setIndent(Units.toEMU(value));
+ }
+ }
+
+ /**
+ *
+ * @return the indent applied to the first line of text in the paragraph.
+ */
+ public double getIndent(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetIndent()){
+ setValue(Units.toPoints(props.getIndent()));
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+
+
+ /**
+ * Specifies the left margin of the paragraph. This is specified in addition to the text body
+ * inset and applies only to this text paragraph. That is the text body inset and the LeftMargin
+ * attributes are additive with respect to the text position.
+ *
+ * @param value the left margin of the paragraph
+ */
+ public void setLeftMargin(double value){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(value == -1) {
+ if(pr.isSetMarL()) pr.unsetMarL();
+ } else {
+ pr.setMarL(Units.toEMU(value));
+ }
+
+ }
+
+ /**
+ *
+ * @return the left margin of the paragraph
+ */
+ public double getLeftMargin(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetMarL()){
+ double val = Units.toPoints(props.getMarL());
+ setValue(val);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ // if the marL attribute is omitted, then a value of 347663 is implied
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+ /**
+ * Specifies the right margin of the paragraph. This is specified in addition to the text body
+ * inset and applies only to this text paragraph. That is the text body inset and the marR
+ * attributes are additive with respect to the text position.
+ *
+ * @param value the right margin of the paragraph
+ */
+ public void setRightMargin(double value){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(value == -1) {
+ if(pr.isSetMarR()) pr.unsetMarR();
+ } else {
+ pr.setMarR(Units.toEMU(value));
+ }
+
+ }
+
+ /**
+ *
+ * @return the right margin of the paragraph
+ */
+ public double getRightMargin(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetMarR()){
+ double val = Units.toPoints(props.getMarR());
+ setValue(val);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ // if the marL attribute is omitted, then a value of 347663 is implied
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+ /**
+ *
+ * @return the default size for a tab character within this paragraph in points
+ */
+ public double getDefaultTabSize(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetDefTabSz()){
+ double val = Units.toPoints(props.getDefTabSz());
+ setValue(val);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+ public double getTabStop(final int idx){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetTabLst()){
+ CTTextTabStopList tabStops = props.getTabLst();
+ if(idx < tabStops.sizeOfTabArray() ) {
+ CTTextTabStop ts = tabStops.getTabArray(idx);
+ double val = Units.toPoints(ts.getPos());
+ setValue(val);
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? 0. : fetcher.getValue();
+ }
+ /**
+ * Add a single tab stop to be used on a line of text when there are one or more tab characters
+ * present within the text.
+ *
+ * @param value the position of the tab stop relative to the left margin
+ */
+ public void addTabStop(double value){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextTabStopList tabStops = pr.isSetTabLst() ? pr.getTabLst() : pr.addNewTabLst();
+ tabStops.addNewTab().setPos(Units.toEMU(value));
+ }
+
+ /**
+ * This element specifies the vertical line spacing that is to be used within a paragraph.
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If linespacing >= 0, then linespacing is a percentage of normal line height
+ * If linespacing < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ * Examples:
+ * <pre><code>
+ * // spacing will be 120% of the size of the largest text on each line
+ * paragraph.setLineSpacing(120);
+ *
+ * // spacing will be 200% of the size of the largest text on each line
+ * paragraph.setLineSpacing(200);
+ *
+ * // spacing will be 48 points
+ * paragraph.setLineSpacing(-48.0);
+ * </code></pre>
+ *
+ * @param linespacing the vertical line spacing
+ */
+ public void setLineSpacing(double linespacing){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextSpacing spc = CTTextSpacing.Factory.newInstance();
+ if(linespacing >= 0) spc.addNewSpcPct().setVal((int)(linespacing*1000));
+ else spc.addNewSpcPts().setVal((int)(-linespacing*100));
+ pr.setLnSpc(spc);
+ }
+
+ /**
+ * Returns the vertical line spacing that is to be used within a paragraph.
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If linespacing >= 0, then linespacing is a percentage of normal line height.
+ * If linespacing < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ *
+ * @return the vertical line spacing.
+ */
+ public double getLineSpacing(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetLnSpc()){
+ CTTextSpacing spc = props.getLnSpc();
+
+ if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
+ else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+
+ double lnSpc = fetcher.getValue() == null ? 100 : fetcher.getValue();
+ if(lnSpc > 0) {
+ // check if the percentage value is scaled
+ CTTextNormalAutofit normAutofit = _shape.getTxBody().getBodyPr().getNormAutofit();
+ if(normAutofit != null) {
+ double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000;
+ lnSpc *= scale;
+ }
+ }
+
+ return lnSpc;
+ }
+
+ /**
+ * Set the amount of vertical white space that will be present before the paragraph.
+ * This space is specified in either percentage or points:
+ * <p>
+ * If spaceBefore >= 0, then space is a percentage of normal line height.
+ * If spaceBefore < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ * Examples:
+ * <pre><code>
+ * // The paragraph will be formatted to have a spacing before the paragraph text.
+ * // The spacing will be 200% of the size of the largest text on each line
+ * paragraph.setSpaceBefore(200);
+ *
+ * // The spacing will be a size of 48 points
+ * paragraph.setSpaceBefore(-48.0);
+ * </code></pre>
+ *
+ * @param spaceBefore the vertical white space before the paragraph.
+ */
+ public void setSpaceBefore(double spaceBefore){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextSpacing spc = CTTextSpacing.Factory.newInstance();
+ if(spaceBefore >= 0) spc.addNewSpcPct().setVal((int)(spaceBefore*1000));
+ else spc.addNewSpcPts().setVal((int)(-spaceBefore*100));
+ pr.setSpcBef(spc);
+ }
+
+ /**
+ * The amount of vertical white space before the paragraph
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If spaceBefore >= 0, then space is a percentage of normal line height.
+ * If spaceBefore < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ *
+ * @return the vertical white space before the paragraph
+ */
+ public double getSpaceBefore(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetSpcBef()){
+ CTTextSpacing spc = props.getSpcBef();
+
+ if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
+ else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+
+ double spcBef = fetcher.getValue() == null ? 0 : fetcher.getValue();
+ return spcBef;
+ }
+
+ /**
+ * Set the amount of vertical white space that will be present after the paragraph.
+ * This space is specified in either percentage or points:
+ * <p>
+ * If spaceAfter >= 0, then space is a percentage of normal line height.
+ * If spaceAfter < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ * Examples:
+ * <pre><code>
+ * // The paragraph will be formatted to have a spacing after the paragraph text.
+ * // The spacing will be 200% of the size of the largest text on each line
+ * paragraph.setSpaceAfter(200);
+ *
+ * // The spacing will be a size of 48 points
+ * paragraph.setSpaceAfter(-48.0);
+ * </code></pre>
+ *
+ * @param spaceAfter the vertical white space after the paragraph.
+ */
+ public void setSpaceAfter(double spaceAfter){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextSpacing spc = CTTextSpacing.Factory.newInstance();
+ if(spaceAfter >= 0) spc.addNewSpcPct().setVal((int)(spaceAfter*1000));
+ else spc.addNewSpcPts().setVal((int)(-spaceAfter*100));
+ pr.setSpcAft(spc);
+ }
+
+ /**
+ * The amount of vertical white space after the paragraph
+ * This may be specified in two different ways, percentage spacing and font point spacing:
+ * <p>
+ * If spaceBefore >= 0, then space is a percentage of normal line height.
+ * If spaceBefore < 0, the absolute value of linespacing is the spacing in points
+ * </p>
+ *
+ * @return the vertical white space after the paragraph
+ */
+ public double getSpaceAfter(){
+ ParagraphPropertyFetcher<Double> fetcher = new ParagraphPropertyFetcher<Double>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetSpcAft()){
+ CTTextSpacing spc = props.getSpcAft();
+
+ if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
+ else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+ /**
+ * Specifies the particular level text properties that this paragraph will follow.
+ * The value for this attribute formats the text according to the corresponding level
+ * paragraph properties defined in the list of styles associated with the body of text
+ * that this paragraph belongs to (therefore in the parent shape).
+ * <p>
+ * Note that the closest properties object to the text is used, therefore if there is
+ * a conflict between the text paragraph properties and the list style properties for
+ * this level then the text paragraph properties will take precedence.
+ * </p>
+ *
+ * @param level the level (0 ... 4)
+ */
+ public void setLevel(int level){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+
+ pr.setLvl(level);
+ }
+
+ /**
+ * Returns the level of text properties that this paragraph will follow.
+ *
+ * @return the text level of this paragraph (0-based). Default is 0.
+ */
+ public int getLevel(){
+ CTTextParagraphProperties pr = _p.getPPr();
+ if(pr == null) return 0;
+
+ return pr.getLvl();
+ }
+
+
+ /**
+ * Returns whether this paragraph has bullets
+ */
+ public boolean isBullet() {
+ ParagraphPropertyFetcher<Boolean> fetcher = new ParagraphPropertyFetcher<Boolean>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuNone()) {
+ setValue(false);
+ return true;
+ }
+ if(props.isSetBuFont() || props.isSetBuChar() || props.isSetBuAutoNum()){
+ setValue(true);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? false : fetcher.getValue();
+ }
+
+ /**
+ * Set or unset this paragraph as a bullet point
+ *
+ * @param flag whether text in this paragraph has bullets
+ */
+ public void setBullet(boolean flag) {
+ if(isBullet() == flag) return;
+
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ if(!flag) {
+ pr.addNewBuNone();
+
+ if(pr.isSetBuAutoNum()) pr.unsetBuAutoNum();
+ if(pr.isSetBuBlip()) pr.unsetBuBlip();
+ if(pr.isSetBuChar()) pr.unsetBuChar();
+ if(pr.isSetBuClr()) pr.unsetBuClr();
+ if(pr.isSetBuClrTx()) pr.unsetBuClrTx();
+ if(pr.isSetBuFont()) pr.unsetBuFont();
+ if(pr.isSetBuFontTx()) pr.unsetBuFontTx();
+ if(pr.isSetBuSzPct()) pr.unsetBuSzPct();
+ if(pr.isSetBuSzPts()) pr.unsetBuSzPts();
+ if(pr.isSetBuSzTx()) pr.unsetBuSzTx();
+ } else {
+ if(pr.isSetBuNone()) pr.unsetBuNone();
+ if(!pr.isSetBuFont()) pr.addNewBuFont().setTypeface("Arial");
+ if(!pr.isSetBuAutoNum()) pr.addNewBuChar().setChar("\u2022");
+ }
+ }
+
+ /**
+ * Set this paragraph as an automatic numbered bullet point
+ *
+ * @param scheme type of auto-numbering
+ * @param startAt the number that will start number for a given sequence of automatically
+ * numbered bullets (1-based).
+ */
+ public void setBullet(ListAutoNumber scheme, int startAt) {
+ if(startAt < 1) throw new IllegalArgumentException("Start Number must be greater or equal that 1") ;
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextAutonumberBullet lst = pr.isSetBuAutoNum() ? pr.getBuAutoNum() : pr.addNewBuAutoNum();
+ lst.setType(STTextAutonumberScheme.Enum.forInt(scheme.ordinal() + 1));
+ lst.setStartAt(startAt);
+
+ if(!pr.isSetBuFont()) pr.addNewBuFont().setTypeface("Arial");
+ if(pr.isSetBuNone()) pr.unsetBuNone();
+ // remove these elements if present as it results in invalid content when opening in Excel.
+ if(pr.isSetBuBlip()) pr.unsetBuBlip();
+ if(pr.isSetBuChar()) pr.unsetBuChar();
+ }
+
+ /**
+ * Set this paragraph as an automatic numbered bullet point
+ *
+ * @param scheme type of auto-numbering
+ */
+ public void setBullet(ListAutoNumber scheme) {
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextAutonumberBullet lst = pr.isSetBuAutoNum() ? pr.getBuAutoNum() : pr.addNewBuAutoNum();
+ lst.setType(STTextAutonumberScheme.Enum.forInt(scheme.ordinal() + 1));
+
+ if(!pr.isSetBuFont()) pr.addNewBuFont().setTypeface("Arial");
+ if(pr.isSetBuNone()) pr.unsetBuNone();
+ // remove these elements if present as it results in invalid content when opening in Excel.
+ if(pr.isSetBuBlip()) pr.unsetBuBlip();
+ if(pr.isSetBuChar()) pr.unsetBuChar();
+ }
+
+ /**
+ * Returns whether this paragraph has automatic numbered bullets
+ */
+ public boolean isBulletAutoNumber() {
+ ParagraphPropertyFetcher<Boolean> fetcher = new ParagraphPropertyFetcher<Boolean>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuAutoNum()) {
+ setValue(true);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? false : fetcher.getValue();
+ }
+
+ /**
+ * Returns the starting number if this paragraph has automatic numbered bullets, otherwise returns 0
+ */
+ public int getBulletAutoNumberStart() {
+ ParagraphPropertyFetcher<Integer> fetcher = new ParagraphPropertyFetcher<Integer>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuAutoNum() && props.getBuAutoNum().isSetStartAt()) {
+ setValue(props.getBuAutoNum().getStartAt());
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+ return fetcher.getValue() == null ? 0 : fetcher.getValue();
+ }
+
+ /**
+ * Returns the auto number scheme if this paragraph has automatic numbered bullets, otherwise returns ListAutoNumber.ARABIC_PLAIN
+ */
+ public ListAutoNumber getBulletAutoNumberScheme() {
+ ParagraphPropertyFetcher<ListAutoNumber> fetcher = new ParagraphPropertyFetcher<ListAutoNumber>(getLevel()){
+ public boolean fetch(CTTextParagraphProperties props){
+ if(props.isSetBuAutoNum()) {
+ setValue(ListAutoNumber.values()[props.getBuAutoNum().getType().intValue() - 1]);
+ return true;
+ }
+ return false;
+ }
+ };
+ fetchParagraphProperty(fetcher);
+
+ // Note: documentation does not define a default, return ListAutoNumber.ARABIC_PLAIN (1,2,3...)
+ return fetcher.getValue() == null ? ListAutoNumber.ARABIC_PLAIN : fetcher.getValue();
+ }
+
+
+ @SuppressWarnings("rawtypes")
+ private boolean fetchParagraphProperty(ParagraphPropertyFetcher visitor){
+ boolean ok = false;
+
+ if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr());
+
+ if(!ok) {
+ ok = visitor.fetch(_shape);
+ }
+
+ return ok;
+ }
+
+ @Override
+ public String toString(){
+ return "[" + getClass() + "]" + getText();
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel;
+
+import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit;
+import org.openxmlformats.schemas.drawingml.x2006.main.STTextStrikeType;
+import org.openxmlformats.schemas.drawingml.x2006.main.STTextUnderlineType;
+
+import java.awt.Color;
+
+/**
+ * Represents a run of text within the containing text body. The run element is the
+ * lowest level text separation mechanism within a text body.
+ */
+public class XSSFTextRun {
+ private final CTRegularTextRun _r;
+ private final XSSFTextParagraph _p;
+
+ XSSFTextRun(CTRegularTextRun r, XSSFTextParagraph p){
+ _r = r;
+ _p = p;
+ }
+
+ XSSFTextParagraph getParentParagraph(){
+ return _p;
+ }
+
+ public String getText(){
+ return _r.getT();
+ }
+
+ public void setText(String text){
+ _r.setT(text);
+ }
+
+ public CTRegularTextRun getXmlObject(){
+ return _r;
+ }
+
+ public void setFontColor(Color color){
+ CTTextCharacterProperties rPr = getRPr();
+ CTSolidColorFillProperties fill = rPr.isSetSolidFill() ? rPr.getSolidFill() : rPr.addNewSolidFill();
+ CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr();
+ clr.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()});
+
+ if(fill.isSetHslClr()) fill.unsetHslClr();
+ if(fill.isSetPrstClr()) fill.unsetPrstClr();
+ if(fill.isSetSchemeClr()) fill.unsetSchemeClr();
+ if(fill.isSetScrgbClr()) fill.unsetScrgbClr();
+ if(fill.isSetSysClr()) fill.unsetSysClr();
+
+ }
+
+ public Color getFontColor(){
+
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetSolidFill()){
+ CTSolidColorFillProperties fill = rPr.getSolidFill();
+
+ if(fill.isSetSrgbClr()){
+ CTSRgbColor clr = fill.getSrgbClr();
+ byte[] rgb = clr.getVal();
+ return new Color(0xFF & rgb[0], 0xFF & rgb[1], 0xFF & rgb[2]);
+ }
+ }
+
+ return new Color(0, 0, 0);
+ }
+
+ /**
+ *
+ * @param fontSize font size in points.
+ * The value of <code>-1</code> unsets the Sz attribute from the underlying xml bean
+ */
+ public void setFontSize(double fontSize){
+ CTTextCharacterProperties rPr = getRPr();
+ if(fontSize == -1.0) {
+ if(rPr.isSetSz()) rPr.unsetSz();
+ } else {
+ if(fontSize < 1.0) {
+ throw new IllegalArgumentException("Minimum font size is 1pt but was " + fontSize);
+ }
+
+ rPr.setSz((int)(100*fontSize));
+ }
+ }
+
+ /**
+ * @return font size in points or -1 if font size is not set.
+ */
+ public double getFontSize(){
+ double scale = 1;
+ double size = XSSFFont.DEFAULT_FONT_SIZE; // default font size
+ CTTextNormalAutofit afit = getParentParagraph().getParentShape().getTxBody().getBodyPr().getNormAutofit();
+ if(afit != null) scale = (double)afit.getFontScale() / 100000;
+
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetSz()){
+ size = rPr.getSz()*0.01;
+ }
+
+ return size * scale;
+ }
+
+ /**
+ *
+ * @return the spacing between characters within a text run,
+ * If this attribute is omitted then a value of 0 or no adjustment is assumed.
+ */
+ public double getCharacterSpacing(){
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetSpc()){
+ return rPr.getSpc()*0.01;
+ }
+ return 0;
+ }
+
+ /**
+ * Set the spacing between characters within a text run.
+ * <p>
+ * The spacing is specified in points. Positive values will cause the text to expand,
+ * negative values to condense.
+ * </p>
+ *
+ * @param spc character spacing in points.
+ */
+ public void setCharacterSpacing(double spc){
+ CTTextCharacterProperties rPr = getRPr();
+ if(spc == 0.0) {
+ if(rPr.isSetSpc()) rPr.unsetSpc();
+ } else {
+ rPr.setSpc((int)(100*spc));
+ }
+ }
+
+ /**
+ * Specifies the typeface, or name of the font that is to be used for this text run.
+ *
+ * @param typeface the font to apply to this text run.
+ * The value of <code>null</code> unsets the Typeface attribute from the underlying xml.
+ */
+ public void setFont(String typeface){
+ setFontFamily(typeface, (byte)-1, (byte)-1, false);
+ }
+
+ public void setFontFamily(String typeface, byte charset, byte pictAndFamily, boolean isSymbol){
+ CTTextCharacterProperties rPr = getRPr();
+
+ if(typeface == null){
+ if(rPr.isSetLatin()) rPr.unsetLatin();
+ if(rPr.isSetCs()) rPr.unsetCs();
+ if(rPr.isSetSym()) rPr.unsetSym();
+ } else {
+ if(isSymbol){
+ CTTextFont font = rPr.isSetSym() ? rPr.getSym() : rPr.addNewSym();
+ font.setTypeface(typeface);
+ } else {
+ CTTextFont latin = rPr.isSetLatin() ? rPr.getLatin() : rPr.addNewLatin();
+ latin.setTypeface(typeface);
+ if(charset != -1) latin.setCharset(charset);
+ if(pictAndFamily != -1) latin.setPitchFamily(pictAndFamily);
+ }
+ }
+ }
+
+ /**
+ * @return font family or null if not set
+ */
+ public String getFontFamily(){
+ CTTextCharacterProperties rPr = getRPr();
+ CTTextFont font = rPr.getLatin();
+ if(font != null){
+ return font.getTypeface();
+ }
+ return XSSFFont.DEFAULT_FONT_NAME;
+ }
+
+ public byte getPitchAndFamily(){
+ CTTextCharacterProperties rPr = getRPr();
+ CTTextFont font = rPr.getLatin();
+ if(font != null){
+ return font.getPitchFamily();
+ }
+ return 0;
+ }
+
+ /**
+ * Specifies whether a run of text will be formatted as strikethrough text.
+ *
+ * @param strike whether a run of text will be formatted as strikethrough text.
+ */
+ public void setStrikethrough(boolean strike) {
+ getRPr().setStrike(strike ? STTextStrikeType.SNG_STRIKE : STTextStrikeType.NO_STRIKE);
+ }
+
+ /**
+ * @return whether a run of text will be formatted as strikethrough text. Default is false.
+ */
+ public boolean isStrikethrough() {
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetStrike()){
+ return rPr.getStrike() != STTextStrikeType.NO_STRIKE;
+ }
+ return false;
+ }
+
+ /**
+ * @return whether a run of text will be formatted as a superscript text. Default is false.
+ */
+ public boolean isSuperscript() {
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetBaseline()){
+ return rPr.getBaseline() > 0;
+ }
+ return false;
+ }
+
+ /**
+ * Set the baseline for both the superscript and subscript fonts.
+ * <p>
+ * The size is specified using a percentage.
+ * Positive values indicate superscript, negative values indicate subscript.
+ * </p>
+ *
+ * @param baselineOffset
+ */
+ public void setBaselineOffset(double baselineOffset){
+ getRPr().setBaseline((int) baselineOffset * 1000);
+ }
+
+ /**
+ * Set whether the text in this run is formatted as superscript.
+ * Default base line offset is 30%
+ *
+ * @see #setBaselineOffset(double)
+ */
+ public void setSuperscript(boolean flag){
+ setBaselineOffset(flag ? 30. : 0.);
+ }
+
+ /**
+ * Set whether the text in this run is formatted as subscript.
+ * Default base line offset is -25%.
+ *
+ * @see #setBaselineOffset(double)
+ */
+ public void setSubscript(boolean flag){
+ setBaselineOffset(flag ? -25.0 : 0.);
+ }
+
+ /**
+ * @return whether a run of text will be formatted as a superscript text. Default is false.
+ */
+ public boolean isSubscript() {
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetBaseline()){
+ return rPr.getBaseline() < 0;
+ }
+ return false;
+ }
+
+ /**
+ * @return whether a run of text will be formatted as a superscript text. Default is false.
+ */
+ public TextCap getTextCap() {
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetCap()){
+ return TextCap.values()[rPr.getCap().intValue() - 1];
+ }
+ return TextCap.NONE;
+ }
+
+ /**
+ * Specifies whether this run of text will be formatted as bold text
+ *
+ * @param bold whether this run of text will be formatted as bold text
+ */
+ public void setBold(boolean bold){
+ getRPr().setB(bold);
+ }
+
+ /**
+ * @return whether this run of text is formatted as bold text
+ */
+ public boolean isBold(){
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetB()){
+ return rPr.getB();
+ }
+ return false;
+ }
+
+ /**
+ * @param italic whether this run of text is formatted as italic text
+ */
+ public void setItalic(boolean italic){
+ getRPr().setI(italic);
+ }
+
+ /**
+ * @return whether this run of text is formatted as italic text
+ */
+ public boolean isItalic(){
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetI()){
+ return rPr.getI();
+ }
+ return false;
+ }
+
+ /**
+ * @param underline whether this run of text is formatted as underlined text
+ */
+ public void setUnderline(boolean underline) {
+ getRPr().setU(underline ? STTextUnderlineType.SNG : STTextUnderlineType.NONE);
+ }
+
+ /**
+ * @return whether this run of text is formatted as underlined text
+ */
+ public boolean isUnderline(){
+ CTTextCharacterProperties rPr = getRPr();
+ if(rPr.isSetU()){
+ return rPr.getU() != STTextUnderlineType.NONE;
+ }
+ return false;
+ }
+
+ protected CTTextCharacterProperties getRPr(){
+ return _r.isSetRPr() ? _r.getRPr() : _r.addNewRPr();
+ }
+
+ @Override
+ public String toString(){
+ return "[" + getClass() + "]" + getText();
+ }
+}