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;
@Beta
public abstract class XSLFTextShape extends XSLFSimpleShape
implements TextContainer, TextShape<XSLFShape, XSLFTextParagraph> {
private final List<XSLFTextParagraph> _paragraphs;
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();
}
public void clearText() {
_paragraphs.clear();
CTTextBody txBody = getTextBody(true);
txBody.setPArray(null);
}
@Override
public XSLFTextRun setText(String text) {
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;
}
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();
}
}
}
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;
}
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.));
}
}
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);
return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
}
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);
return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
}
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);
return fetcher.getValue() == null ? 7.2 : fetcher.getValue();
}
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);
return fetcher.getValue() == null ? 3.6 : fetcher.getValue();
}
public void setBottomInset(double margin) {
CTTextBodyProperties bodyPr = getTextBodyPr(true);
if (bodyPr != null) {
if (margin == -1) {
bodyPr.unsetBIns();
} else {
bodyPr.setBIns(Units.toEMU(margin));
}
}
}
public void setLeftInset(double margin) {
CTTextBodyProperties bodyPr = getTextBodyPr(true);
if (bodyPr != null) {
if (margin == -1) {
bodyPr.unsetLIns();
} else {
bodyPr.setLIns(Units.toEMU(margin));
}
}
}
public void setRightInset(double margin) {
CTTextBodyProperties bodyPr = getTextBodyPr(true);
if (bodyPr != null) {
if (margin == -1) {
bodyPr.unsetRIns();
} else {
bodyPr.setRIns(Units.toEMU(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);
}
}
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;
}
}
}
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;
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;
}
}
protected XSLFTextParagraph newTextParagraph(CTTextParagraph p) {
return new XSLFTextParagraph(p, this);
}
@Override
public <R> Optional<R> findDefinedParagraphProperty(Function<CTTextParagraphProperties, Boolean> isSet,
Function<CTTextParagraphProperties, R> getter) {
return Optional.empty();
}
@Override
public <R> Optional<R> findDefinedRunProperty(Function<CTTextCharacterProperties, Boolean> isSet,
Function<CTTextCharacterProperties, R> getter) {
return Optional.empty();
}
}