/*
 *  ====================================================================
 *    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.xslf.usermodel;

import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import org.apache.poi.ooxml.POIXMLException;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawTextShape;
import org.apache.poi.sl.usermodel.Insets2D;
import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.TextShape;
import org.apache.poi.sl.usermodel.VerticalAlignment;
import org.apache.poi.util.Beta;
import org.apache.poi.util.Units;
import org.apache.poi.xddf.usermodel.text.TextContainer;
import org.apache.poi.xddf.usermodel.text.XDDFTextBody;
import org.apache.poi.xddf.usermodel.text.XDDFTextParagraph;
import org.apache.poi.xslf.model.PropertyFetcher;
import org.apache.poi.xslf.model.TextBodyPropertyFetcher;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextListStyle;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextAnchoringType;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextVerticalType;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextWrappingType;

Represents a shape that can hold text.
/** * Represents a shape that can hold text. */
@Beta public abstract class XSLFTextShape extends XSLFSimpleShape implements TextContainer, TextShape<XSLFShape, XSLFTextParagraph> { private final List<XSLFTextParagraph> _paragraphs; /* package */ XSLFTextShape(XmlObject shape, XSLFSheet sheet) { super(shape, sheet); _paragraphs = new ArrayList<>(); CTTextBody txBody = getTextBody(false); if (txBody != null) { for (CTTextParagraph p : txBody.getPArray()) { _paragraphs.add(newTextParagraph(p)); } } } protected static void initTextBody(XDDFTextBody body) { XDDFTextParagraph p = body.getParagraph(0); p.appendRegularRun(""); } @Beta public XDDFTextBody getTextBody() { CTTextBody txBody = getTextBody(false); if (txBody == null) { return null; } return new XDDFTextBody(this, txBody); } @Override public Iterator<XSLFTextParagraph> iterator() { return getTextParagraphs().iterator(); } @Override public String getText() { StringBuilder out = new StringBuilder(); for (XSLFTextParagraph p : _paragraphs) { if (out.length() > 0) { out.append('\n'); } out.append(p.getText()); } return out.toString(); }
unset text from this shape
/** * unset text from this shape */
public void clearText() { _paragraphs.clear(); CTTextBody txBody = getTextBody(true); txBody.setPArray(null); // remove any existing paragraphs } @Override public XSLFTextRun setText(String text) { // calling clearText or setting to a new Array leads to a // XmlValueDisconnectedException if (!_paragraphs.isEmpty()) { CTTextBody txBody = getTextBody(false); int cntPs = txBody.sizeOfPArray(); for (int i = cntPs; i > 1; i--) { txBody.removeP(i - 1); _paragraphs.remove(i - 1); } _paragraphs.get(0).clearButKeepProperties(); } return appendText(text, false); } @Override public XSLFTextRun appendText(String text, boolean newParagraph) { if (text == null) { return null; } // copy properties from last paragraph / textrun or paragraph end marker CTTextParagraphProperties otherPPr = null; CTTextCharacterProperties otherRPr = null; boolean firstPara; XSLFTextParagraph para; if (_paragraphs.isEmpty()) { firstPara = false; para = null; } else { firstPara = !newParagraph; para = _paragraphs.get(_paragraphs.size() - 1); CTTextParagraph ctp = para.getXmlObject(); otherPPr = ctp.getPPr(); List<XSLFTextRun> runs = para.getTextRuns(); if (!runs.isEmpty()) { XSLFTextRun r0 = runs.get(runs.size() - 1); otherRPr = r0.getRPr(false); if (otherRPr == null) { otherRPr = ctp.getEndParaRPr(); } } // don't copy endParaRPr to the run in case there aren't any other // runs // this is the case when setText() was called initially // otherwise the master style will be overridden/ignored } XSLFTextRun run = null; for (String lineTxt : text.split("\\r\\n?|\\n")) { if (!firstPara) { if (para != null) { CTTextParagraph ctp = para.getXmlObject(); CTTextCharacterProperties unexpectedRPr = ctp.getEndParaRPr(); if (unexpectedRPr != null && unexpectedRPr != otherRPr) { ctp.unsetEndParaRPr(); } } para = addNewTextParagraph(); if (otherPPr != null) { para.getXmlObject().setPPr(otherPPr); } } boolean firstRun = true; for (String runText : lineTxt.split("[\u000b]")) { if (!firstRun) { para.addLineBreak(); } run = para.addNewTextRun(); run.setText(runText); if (otherRPr != null) { run.getRPr(true).set(otherRPr); } firstRun = false; } firstPara = false; } assert (run != null); return run; } @Override public List<XSLFTextParagraph> getTextParagraphs() { return _paragraphs; }
add a new paragraph run to this shape
Returns:created paragraph run
/** * add a new paragraph run to this shape * * @return created paragraph run */
public XSLFTextParagraph addNewTextParagraph() { CTTextBody txBody = getTextBody(false); CTTextParagraph p; if (txBody == null) { txBody = getTextBody(true); p = txBody.getPArray(0); p.removeR(0); } else { p = txBody.addNewP(); } XSLFTextParagraph paragraph = newTextParagraph(p); _paragraphs.add(paragraph); return paragraph; } @Override public void setVerticalAlignment(VerticalAlignment anchor) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (anchor == null) { if (bodyPr.isSetAnchor()) { bodyPr.unsetAnchor(); } } else { bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1)); } } } @Override public VerticalAlignment getVerticalAlignment() { PropertyFetcher<VerticalAlignment> fetcher = new TextBodyPropertyFetcher<VerticalAlignment>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetAnchor()) { int val = props.getAnchor().intValue(); setValue(VerticalAlignment.values()[val - 1]); return true; } return false; } }; fetchShapeProperty(fetcher); return fetcher.getValue() == null ? VerticalAlignment.TOP : fetcher.getValue(); } @Override public void setHorizontalCentered(Boolean isCentered) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (isCentered == null) { if (bodyPr.isSetAnchorCtr()) { bodyPr.unsetAnchorCtr(); } } else { bodyPr.setAnchorCtr(isCentered); } } } @Override public boolean isHorizontalCentered() { PropertyFetcher<Boolean> fetcher = new TextBodyPropertyFetcher<Boolean>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetAnchorCtr()) { setValue(props.getAnchorCtr()); return true; } return false; } }; fetchShapeProperty(fetcher); return fetcher.getValue() == null ? false : fetcher.getValue(); } @Override public void setTextDirection(TextDirection orientation) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (orientation == null) { if (bodyPr.isSetVert()) { bodyPr.unsetVert(); } } else { bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1)); } } } @Override public TextDirection getTextDirection() { CTTextBodyProperties bodyPr = getTextBodyPr(); if (bodyPr != null) { STTextVerticalType.Enum val = bodyPr.getVert(); if (val != null) { switch (val.intValue()) { default: case STTextVerticalType.INT_HORZ: return TextDirection.HORIZONTAL; case STTextVerticalType.INT_EA_VERT: case STTextVerticalType.INT_MONGOLIAN_VERT: case STTextVerticalType.INT_VERT: return TextDirection.VERTICAL; case STTextVerticalType.INT_VERT_270: return TextDirection.VERTICAL_270; case STTextVerticalType.INT_WORD_ART_VERT_RTL: case STTextVerticalType.INT_WORD_ART_VERT: return TextDirection.STACKED; } } } return TextDirection.HORIZONTAL; } @Override public Double getTextRotation() { CTTextBodyProperties bodyPr = getTextBodyPr(); if (bodyPr != null && bodyPr.isSetRot()) { return bodyPr.getRot() / 60000.; } return null; } @Override public void setTextRotation(Double rotation) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { bodyPr.setRot((int) (rotation * 60000.)); } }
Returns the distance (in points) between the bottom of the text frame and the bottom of the inscribed rectangle of the shape that contains the text.
Returns:the bottom inset in points
/** * Returns the distance (in points) between the bottom of the text frame and * the bottom of the inscribed rectangle of the shape that contains the * text. * * @return the bottom inset in points */
public double getBottomInset() { PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetBIns()) { double val = Units.toPoints(props.getBIns()); setValue(val); return true; } return false; } }; fetchShapeProperty(fetcher); // If this attribute is omitted, then a value of 0.05 inches is implied return fetcher.getValue() == null ? 3.6 : fetcher.getValue(); }
Returns the distance (in points) between the left edge of the text frame and the left edge of the inscribed rectangle of the shape that contains the text.
Returns:the left inset in points
/** * Returns the distance (in points) between the left edge of the text frame * and the left edge of the inscribed rectangle of the shape that contains * the text. * * @return the left inset in points */
public double getLeftInset() { PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetLIns()) { double val = Units.toPoints(props.getLIns()); setValue(val); return true; } return false; } }; fetchShapeProperty(fetcher); // If this attribute is omitted, then a value of 0.1 inches is implied return fetcher.getValue() == null ? 7.2 : fetcher.getValue(); }
Returns the distance (in points) between the right edge of the text frame and the right edge of the inscribed rectangle of the shape that contains the text.
Returns:the right inset in points
/** * Returns the distance (in points) between the right edge of the text frame * and the right edge of the inscribed rectangle of the shape that contains * the text. * * @return the right inset in points */
public double getRightInset() { PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetRIns()) { double val = Units.toPoints(props.getRIns()); setValue(val); return true; } return false; } }; fetchShapeProperty(fetcher); // If this attribute is omitted, then a value of 0.1 inches is implied return fetcher.getValue() == null ? 7.2 : fetcher.getValue(); }
Returns the distance (in points) between the top of the text frame and the top of the inscribed rectangle of the shape that contains the text.
Returns:the top inset in points
/** * Returns the distance (in points) between the top of the text frame and * the top of the inscribed rectangle of the shape that contains the text. * * @return the top inset in points */
public double getTopInset() { PropertyFetcher<Double> fetcher = new TextBodyPropertyFetcher<Double>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetTIns()) { double val = Units.toPoints(props.getTIns()); setValue(val); return true; } return false; } }; fetchShapeProperty(fetcher); // If this attribute is omitted, then a value of 0.05 inches is implied return fetcher.getValue() == null ? 3.6 : fetcher.getValue(); }
Sets the bottom margin.
Params:
  • margin – the bottom margin
See Also:
  • getBottomInset()
/** * Sets the bottom margin. * * @see #getBottomInset() * * @param margin * the bottom margin */
public void setBottomInset(double margin) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (margin == -1) { bodyPr.unsetBIns(); } else { bodyPr.setBIns(Units.toEMU(margin)); } } }
Sets the left margin.
Params:
  • margin – the left margin
See Also:
  • getLeftInset()
/** * Sets the left margin. * * @see #getLeftInset() * * @param margin * the left margin */
public void setLeftInset(double margin) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (margin == -1) { bodyPr.unsetLIns(); } else { bodyPr.setLIns(Units.toEMU(margin)); } } }
Sets the right margin.
Params:
  • margin – the right margin
See Also:
  • getRightInset()
/** * Sets the right margin. * * @see #getRightInset() * * @param margin * the right margin */
public void setRightInset(double margin) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (margin == -1) { bodyPr.unsetRIns(); } else { bodyPr.setRIns(Units.toEMU(margin)); } } }
Sets the top margin.
Params:
  • margin – the top margin
See Also:
  • getTopInset()
/** * Sets the top margin. * * @see #getTopInset() * * @param margin * the top margin */
public void setTopInset(double margin) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (margin == -1) { bodyPr.unsetTIns(); } else { bodyPr.setTIns(Units.toEMU(margin)); } } } @Override public Insets2D getInsets() { return new Insets2D(getTopInset(), getLeftInset(), getBottomInset(), getRightInset()); } @Override public void setInsets(Insets2D insets) { setTopInset(insets.top); setLeftInset(insets.left); setBottomInset(insets.bottom); setRightInset(insets.right); } @Override public boolean getWordWrap() { PropertyFetcher<Boolean> fetcher = new TextBodyPropertyFetcher<Boolean>() { @Override public boolean fetch(CTTextBodyProperties props) { if (props.isSetWrap()) { setValue(props.getWrap() == STTextWrappingType.SQUARE); return true; } return false; } }; fetchShapeProperty(fetcher); return fetcher.getValue() == null ? true : fetcher.getValue(); } @Override public void setWordWrap(boolean wrap) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { bodyPr.setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE); } }
Specifies that a shape should be auto-fit to fully contain the text described within it. Auto-fitting is when text within a shape is scaled in order to contain all the text inside
Params:
  • value – type of autofit
/** * * Specifies that a shape should be auto-fit to fully contain the text * described within it. Auto-fitting is when text within a shape is scaled * in order to contain all the text inside * * @param value * type of autofit */
public void setTextAutofit(TextAutofit value) { CTTextBodyProperties bodyPr = getTextBodyPr(true); if (bodyPr != null) { if (bodyPr.isSetSpAutoFit()) { bodyPr.unsetSpAutoFit(); } if (bodyPr.isSetNoAutofit()) { bodyPr.unsetNoAutofit(); } if (bodyPr.isSetNormAutofit()) { bodyPr.unsetNormAutofit(); } switch (value) { case NONE: bodyPr.addNewNoAutofit(); break; case NORMAL: bodyPr.addNewNormAutofit(); break; case SHAPE: bodyPr.addNewSpAutoFit(); break; } } }
Returns:type of autofit
/** * * @return type of autofit */
public TextAutofit getTextAutofit() { CTTextBodyProperties bodyPr = getTextBodyPr(); if (bodyPr != null) { if (bodyPr.isSetNoAutofit()) { return TextAutofit.NONE; } else if (bodyPr.isSetNormAutofit()) { return TextAutofit.NORMAL; } else if (bodyPr.isSetSpAutoFit()) { return TextAutofit.SHAPE; } } return TextAutofit.NORMAL; } protected CTTextBodyProperties getTextBodyPr() { return getTextBodyPr(false); } protected CTTextBodyProperties getTextBodyPr(boolean create) { CTTextBody textBody = getTextBody(create); if (textBody == null) { return null; } CTTextBodyProperties textBodyPr = textBody.getBodyPr(); if (textBodyPr == null && create) { textBodyPr = textBody.addNewBodyPr(); } return textBodyPr; } protected abstract CTTextBody getTextBody(boolean create); @Override public void setPlaceholder(Placeholder placeholder) { super.setPlaceholder(placeholder); } public Placeholder getTextType() { return getPlaceholder(); } @Override public double getTextHeight() { return getTextHeight(null); } @Override public double getTextHeight(Graphics2D graphics) { DrawFactory drawFact = DrawFactory.getInstance(graphics); DrawTextShape dts = drawFact.getDrawable(this); return dts.getTextHeight(graphics); } @Override public Rectangle2D resizeToFitText() { return resizeToFitText(null); } @Override public Rectangle2D resizeToFitText(Graphics2D graphics) { Rectangle2D anchor = getAnchor(); if (anchor.getWidth() == 0.) { throw new POIXMLException("Anchor of the shape was not set."); } double height = getTextHeight(graphics); height += 1; // add a pixel to compensate rounding errors Insets2D insets = getInsets(); anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height + insets.top + insets.bottom); setAnchor(anchor); return anchor; } @Override void copy(XSLFShape other) { super.copy(other); XSLFTextShape otherTS = (XSLFTextShape) other; CTTextBody otherTB = otherTS.getTextBody(false); CTTextBody thisTB = getTextBody(true); if (otherTB == null) { return; } thisTB.setBodyPr((CTTextBodyProperties) otherTB.getBodyPr().copy()); if (thisTB.isSetLstStyle()) { thisTB.unsetLstStyle(); } if (otherTB.isSetLstStyle()) { thisTB.setLstStyle((CTTextListStyle) otherTB.getLstStyle().copy()); } boolean srcWordWrap = otherTS.getWordWrap(); if (srcWordWrap != getWordWrap()) { setWordWrap(srcWordWrap); } double leftInset = otherTS.getLeftInset(); if (leftInset != getLeftInset()) { setLeftInset(leftInset); } double rightInset = otherTS.getRightInset(); if (rightInset != getRightInset()) { setRightInset(rightInset); } double topInset = otherTS.getTopInset(); if (topInset != getTopInset()) { setTopInset(topInset); } double bottomInset = otherTS.getBottomInset(); if (bottomInset != getBottomInset()) { setBottomInset(bottomInset); } VerticalAlignment vAlign = otherTS.getVerticalAlignment(); if (vAlign != getVerticalAlignment()) { setVerticalAlignment(vAlign); } clearText(); for (XSLFTextParagraph srcP : otherTS.getTextParagraphs()) { XSLFTextParagraph tgtP = addNewTextParagraph(); tgtP.copy(srcP); } } @Override public void setTextPlaceholder(TextPlaceholder placeholder) { switch (placeholder) { default: case NOTES: case HALF_BODY: case QUARTER_BODY: case BODY: setPlaceholder(Placeholder.BODY); break; case TITLE: setPlaceholder(Placeholder.TITLE); break; case CENTER_BODY: setPlaceholder(Placeholder.BODY); setHorizontalCentered(true); break; case CENTER_TITLE: setPlaceholder(Placeholder.CENTERED_TITLE); break; case OTHER: setPlaceholder(Placeholder.CONTENT); break; } } @Override public TextPlaceholder getTextPlaceholder() { Placeholder ph = getTextType(); if (ph == null) { return TextPlaceholder.BODY; } switch (ph) { case BODY: return TextPlaceholder.BODY; case TITLE: return TextPlaceholder.TITLE; case CENTERED_TITLE: return TextPlaceholder.CENTER_TITLE; default: case CONTENT: return TextPlaceholder.OTHER; } }
Helper method to allow subclasses to provide their own text paragraph
Params:
  • p – the xml reference
Returns:a new text paragraph
Since:POI 3.15-beta2
/** * Helper method to allow subclasses to provide their own text paragraph * * @param p * the xml reference * * @return a new text paragraph * * @since POI 3.15-beta2 */
protected XSLFTextParagraph newTextParagraph(CTTextParagraph p) { return new XSLFTextParagraph(p, this); } @Override public <R> Optional<R> findDefinedParagraphProperty(Function<CTTextParagraphProperties, Boolean> isSet, Function<CTTextParagraphProperties, R> getter) { // TODO Auto-generated method stub return Optional.empty(); } @Override public <R> Optional<R> findDefinedRunProperty(Function<CTTextCharacterProperties, Boolean> isSet, Function<CTTextCharacterProperties, R> getter) { // TODO Auto-generated method stub return Optional.empty(); } }