All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.poi.xslf.usermodel.XSLFTextShape Maven / Gradle / Ivy

There is a newer version: 5.2.5
Show newest version
/*
 *  ====================================================================
 *    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.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 {
    private final List _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));
            }
        }
    }

    public Iterator 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 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 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 fetcher = new TextBodyPropertyFetcher(){
            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 fetcher = new TextBodyPropertyFetcher(){
            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 fetcher = new TextBodyPropertyFetcher(){
            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 fetcher = new TextBodyPropertyFetcher(){
            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 fetcher = new TextBodyPropertyFetcher(){
            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 fetcher = new TextBodyPropertyFetcher(){
            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() {
        Insets2D insets = new Insets2D(getTopInset(), getLeftInset(), getBottomInset(), getRightInset());
        return insets;
    }

    @Override
    public void setInsets(Insets2D insets) {
        setTopInset(insets.top);
        setLeftInset(insets.left);
        setBottomInset(insets.bottom);
        setRightInset(insets.right);
    }

    @Override
    public boolean getWordWrap(){
        PropertyFetcher fetcher = new TextBodyPropertyFetcher(){
            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(){
        CTPlaceholder ph = getCTPlaceholder();
        if (ph == null) return null;

        int val = ph.getType().intValue();
        return Placeholder.lookupOoxml(val);
    }

    @Override
    public double getTextHeight(){
        DrawFactory drawFact = DrawFactory.getInstance(null);
        DrawTextShape dts = drawFact.getDrawable(this);
        return dts.getTextHeight();
    }

    /**
     * Adjust the size of the shape so it encompasses the text inside it.
     *
     * @return a Rectangle2D that is the bounds of this shape.
     */
    public Rectangle2D resizeToFitText(){
        Rectangle2D anchor = getAnchor();
        if(anchor.getWidth() == 0.)  throw new POIXMLException(
                "Anchor of the shape was not set.");
        double height = getTextHeight();
        height += 1; // add a pixel to compensate rounding errors

        anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height);
        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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy