123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739 |
- /*
- * ====================================================================
- * 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 org.apache.poi.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.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;
- import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
-
- /**
- * Represents a shape that can hold text.
- */
- @Beta
- public abstract class XSLFTextShape extends XSLFSimpleShape
- implements 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));
- }
- }
- }
-
- @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
- */
- 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
- *
- * @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.
- *
- * @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.
- *
- * @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.
- *
- * @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.
- *
- * @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.
- * @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.
- * @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.
- * @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.
- * @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
- *
- * @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;
- }
- }
- }
-
- /**
- *
- * @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
- *
- * @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);
- }
- }
|