diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java index c5a2ce069d..38c298275b 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -1,1079 +1,1079 @@ -/* ==================================================================== - 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.Color; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.apache.poi.sl.draw.DrawPaint; -import org.apache.poi.sl.usermodel.AutoNumberingScheme; -import org.apache.poi.sl.usermodel.PaintStyle; -import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; -import org.apache.poi.sl.usermodel.TextParagraph; -import org.apache.poi.util.Beta; -import org.apache.poi.util.Internal; -import org.apache.poi.util.Units; -import org.apache.poi.xslf.model.ParagraphPropertyFetcher; -import org.apache.xmlbeans.XmlCursor; -import org.apache.xmlbeans.XmlObject; -import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; -import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; -import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextAutonumberBullet; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePercent; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePoint; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharBullet; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextField; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextSpacing; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStopList; -import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType; -import org.openxmlformats.schemas.drawingml.x2006.main.STTextAutonumberScheme; -import org.openxmlformats.schemas.drawingml.x2006.main.STTextFontAlignType; -import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; -import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; - -/** - * Represents a paragraph of text within the containing text body. - * The paragraph is the highest level text separation mechanism. - * - * @since POI-3.8 - */ -@Beta -public class XSLFTextParagraph implements TextParagraph { - private final CTTextParagraph _p; - private final List _runs; - private final XSLFTextShape _shape; - - XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape){ - _p = p; - _runs = new ArrayList(); - _shape = shape; - - for(XmlObject ch : _p.selectPath("*")){ - if(ch instanceof CTRegularTextRun){ - CTRegularTextRun r = (CTRegularTextRun)ch; - _runs.add(newTextRun(r)); - } else if (ch instanceof CTTextLineBreak){ - CTTextLineBreak br = (CTTextLineBreak)ch; - CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); - r.setRPr(br.getRPr()); - r.setT("\n"); - _runs.add(newTextRun(r)); - } else if (ch instanceof CTTextField){ - CTTextField f = (CTTextField)ch; - CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); - r.setRPr(f.getRPr()); - r.setT(f.getT()); - _runs.add(newTextRun(r)); - } - } - } - - public String getText(){ - StringBuilder out = new StringBuilder(); - for (XSLFTextRun r : _runs) { - out.append(r.getRawText()); - } - return out.toString(); - } - - String getRenderableText(){ - StringBuilder out = new StringBuilder(); - for (XSLFTextRun r : _runs) { - out.append(r.getRenderableText()); - } - return out.toString(); - } - - @Internal - public CTTextParagraph getXmlObject(){ - return _p; - } - - public XSLFTextShape getParentShape() { - return _shape; - - } - - @Override - public List getTextRuns(){ - return _runs; - } - - public Iterator iterator(){ - return _runs.iterator(); - } - - /** - * Add a new run of text - * - * @return a new run of text - */ - public XSLFTextRun addNewTextRun(){ - CTRegularTextRun r = _p.addNewR(); - CTTextCharacterProperties rPr = r.addNewRPr(); - rPr.setLang("en-US"); - XSLFTextRun run = newTextRun(r); - _runs.add(run); - return run; - } - - /** - * Insert a line break - * - * @return text run representing this line break ('\n') - */ - public XSLFTextRun 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(true); - brProps.set(prevRun); - } - CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); - r.setRPr(brProps); - r.setT("\n"); - XSLFTextRun run = new XSLFLineBreak(r, this, brProps); - _runs.add(run); - return run; - } - - @Override - public TextAlign getTextAlign(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - @Override - 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)); - } - } - - @Override - public FontAlign getFontAlign(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - public boolean fetch(CTTextParagraphProperties props){ - if(props.isSetFontAlgn()){ - FontAlign val = FontAlign.values()[props.getFontAlgn().intValue() - 1]; - setValue(val); - return true; - } - return false; - } - }; - fetchParagraphProperty(fetcher); - return fetcher.getValue(); - } - - /** - * Specifies the font alignment that is to be applied to the paragraph. - * Possible values for this include auto, top, center, baseline and bottom. - * see {@link org.apache.poi.sl.usermodel.TextParagraph.FontAlign}. - * - * @param align font align - */ - public void setFontAlign(FontAlign 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 fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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 fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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 null value means to use the text font color. - */ - public PaintStyle getBulletFontColor(){ - final XSLFTheme theme = getParentShape().getSheet().getTheme(); - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - public boolean fetch(CTTextParagraphProperties props){ - if(props.isSetBuClr()){ - XSLFColor c = new XSLFColor(props.getBuClr(), theme, null); - setValue(c.getColor()); - return true; - } - return false; - } - }; - fetchParagraphProperty(fetcher); - Color col = fetcher.getValue(); - return (col == null) ? null : DrawPaint.createSolidPaint(col); - } - - public void setBulletFontColor(Color color) { - setBulletFontColor(DrawPaint.createSolidPaint(color)); - } - - - /** - * Set the color to be used on bullet characters within a given paragraph. - * - * @param color the bullet color - */ - public void setBulletFontColor(PaintStyle color) { - if (!(color instanceof SolidPaint)) { - throw new IllegalArgumentException("Currently XSLF only supports SolidPaint"); - } - - // TODO: implement setting bullet color to null - SolidPaint sp = (SolidPaint)color; - Color col = DrawPaint.applyColorTransform(sp.getSolidColor()); - - 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) col.getRed(), (byte) col.getGreen(), (byte) col.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: - *

- * If bulletSize >= 0, then bulletSize is a percentage of the font size. - * If bulletSize < 0, then it specifies the size in points - *

- * - * @return the bullet size - */ - public Double getBulletFontSize(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - /** - * 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: - *

- * If bulletSize >= 0, then bulletSize is a percentage of the font size. - * If bulletSize < 0, then it specifies the size in points - *

- */ - 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(); - } - } - - /** - * @return the auto numbering scheme, or null if not defined - */ - public AutoNumberingScheme getAutoNumberingScheme() { - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()) { - public boolean fetch(CTTextParagraphProperties props) { - if (props.isSetBuAutoNum()) { - AutoNumberingScheme ans = AutoNumberingScheme.forOoxmlID(props.getBuAutoNum().getType().intValue()); - if (ans != null) { - setValue(ans); - return true; - } - } - return false; - } - }; - fetchParagraphProperty(fetcher); - return fetcher.getValue(); - } - - /** - * @return the auto numbering starting number, or null if not defined - */ - public Integer getAutoNumberingStartAt() { - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()) { - public boolean fetch(CTTextParagraphProperties props) { - if (props.isSetBuAutoNum()) { - if (props.getBuAutoNum().isSetStartAt()) { - setValue(props.getBuAutoNum().getStartAt()); - return true; - } - } - return false; - } - }; - fetchParagraphProperty(fetcher); - return fetcher.getValue(); - } - - - @Override - public void setIndent(Double indent){ - if ((indent == null) && !_p.isSetPPr()) return; - CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(indent == null) { - if(pr.isSetIndent()) pr.unsetIndent(); - } else { - pr.setIndent(Units.toEMU(indent)); - } - } - - @Override - public Double getIndent() { - - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - public boolean fetch(CTTextParagraphProperties props){ - if(props.isSetIndent()){ - setValue(Units.toPoints(props.getIndent())); - return true; - } - return false; - } - }; - fetchParagraphProperty(fetcher); - - return fetcher.getValue(); - } - - @Override - public void setLeftMargin(Double leftMargin){ - if (leftMargin == null && !_p.isSetPPr()) return; - CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if (leftMargin == null) { - if(pr.isSetMarL()) pr.unsetMarL(); - } else { - pr.setMarL(Units.toEMU(leftMargin)); - } - - } - - /** - * @return the left margin (in points) of the paragraph, null if unset - */ - @Override - public Double getLeftMargin(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - @Override - public void setRightMargin(Double rightMargin){ - if (rightMargin == null && !_p.isSetPPr()) return; - CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(rightMargin == null) { - if(pr.isSetMarR()) pr.unsetMarR(); - } else { - pr.setMarR(Units.toEMU(rightMargin)); - } - } - - /** - * - * @return the right margin of the paragraph, null if unset - */ - @Override - public Double getRightMargin(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - public boolean fetch(CTTextParagraphProperties props){ - if(props.isSetMarR()){ - double val = Units.toPoints(props.getMarR()); - setValue(val); - return true; - } - return false; - } - }; - fetchParagraphProperty(fetcher); - return fetcher.getValue(); - } - - @Override - public Double getDefaultTabSize(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - public double getTabStop(final int idx){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - 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)); - } - - @Override - public void setLineSpacing(Double lineSpacing){ - if (lineSpacing == null && !_p.isSetPPr()) return; - CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - if(lineSpacing == null) { - if (pr.isSetLnSpc()) pr.unsetLnSpc(); - } else { - CTTextSpacing spc = (pr.isSetLnSpc()) ? pr.getLnSpc() : pr.addNewLnSpc(); - if (lineSpacing >= 0) { - (spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct()).setVal((int)(lineSpacing*1000)); - if (spc.isSetSpcPts()) spc.unsetSpcPts(); - } else { - (spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts()).setVal((int)(-lineSpacing*100)); - if (spc.isSetSpcPct()) spc.unsetSpcPct(); - } - } - } - - @Override - public Double getLineSpacing(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - if (lnSpc != null && lnSpc > 0) { - // check if the percentage value is scaled - CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit(); - if(normAutofit != null) { - double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000; - lnSpc *= scale; - } - } - - return lnSpc; - } - - @Override - public void setSpaceBefore(Double spaceBefore){ - if (spaceBefore == null && !_p.isSetPPr()) { - return; - } - - // unset the space before on null input - if (spaceBefore == null) { - if(_p.getPPr().isSetSpcBef()) { - _p.getPPr().unsetSpcBef(); - } - return; - } - - 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); - } - - @Override - public Double getSpaceBefore(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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); - - return fetcher.getValue(); - } - - @Override - public void setSpaceAfter(Double spaceAfter){ - if (spaceAfter == null && !_p.isSetPPr()) { - return; - } - - // unset the space before on null input - if (spaceAfter == null) { - if(_p.getPPr().isSetSpcAft()) { - _p.getPPr().unsetSpcAft(); - } - return; - } - - 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); - } - - @Override - public Double getSpaceAfter(){ - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - 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(); - } - - @Override - public void setIndentLevel(int level){ - CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); - pr.setLvl(level); - } - - @Override - public int getIndentLevel() { - CTTextParagraphProperties pr = _p.getPPr(); - return (pr == null || !pr.isSetLvl()) ? 0 : pr.getLvl(); - } - - /** - * Returns whether this paragraph has bullets - */ - public boolean isBullet() { - ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ - public boolean fetch(CTTextParagraphProperties props){ - if(props.isSetBuNone()) { - setValue(false); - return true; - } - if(props.isSetBuFont() || props.isSetBuChar()){ - setValue(true); - return true; - } - return false; - } - }; - fetchParagraphProperty(fetcher); - return fetcher.getValue() == null ? false : fetcher.getValue(); - } - - /** - * - * @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.addNewBuFont().setTypeface("Arial"); - pr.addNewBuChar().setChar("\u2022"); - } else { - if (pr.isSetBuFont()) pr.unsetBuFont(); - if (pr.isSetBuChar()) pr.unsetBuChar(); - if (pr.isSetBuAutoNum()) pr.unsetBuAutoNum(); - if (pr.isSetBuBlip()) pr.unsetBuBlip(); - 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(); - pr.addNewBuNone(); - } - } - - /** - * Specifies that automatic numbered bullet points should be applied to this paragraph - * - * @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 setBulletAutoNumber(AutoNumberingScheme 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.ooxmlId)); - lst.setStartAt(startAt); - } - - @Override - public String toString(){ - return "[" + getClass() + "]" + getText(); - } - - - /** - * @return master style text paragraph properties, or null if - * there are no master slides or the master slides do not contain a text paragraph - */ - /* package */ CTTextParagraphProperties getDefaultMasterStyle(){ - CTPlaceholder ph = _shape.getCTPlaceholder(); - String defaultStyleSelector; - switch(ph == null ? -1 : ph.getType().intValue()) { - case STPlaceholderType.INT_TITLE: - case STPlaceholderType.INT_CTR_TITLE: - defaultStyleSelector = "titleStyle"; - break; - case -1: // no placeholder means plain text box - case STPlaceholderType.INT_FTR: - case STPlaceholderType.INT_SLD_NUM: - case STPlaceholderType.INT_DT: - defaultStyleSelector = "otherStyle"; - break; - default: - defaultStyleSelector = "bodyStyle"; - break; - } - int level = getIndentLevel(); - - // wind up and find the root master sheet which must be slide master - final String nsPML = "http://schemas.openxmlformats.org/presentationml/2006/main"; - final String nsDML = "http://schemas.openxmlformats.org/drawingml/2006/main"; - XSLFSheet masterSheet = _shape.getSheet(); - for (XSLFSheet m = masterSheet; m != null; m = (XSLFSheet)m.getMasterSheet()) { - masterSheet = m; - XmlObject xo = masterSheet.getXmlObject(); - XmlCursor cur = xo.newCursor(); - try { - cur.push(); - if ((cur.toChild(nsPML, "txStyles") && cur.toChild(nsPML, defaultStyleSelector)) || - (cur.pop() && cur.toChild(nsPML, "notesStyle"))) { - while (level >= 0) { - cur.push(); - if (cur.toChild(nsDML, "lvl" +(level+1)+ "pPr")) { - return (CTTextParagraphProperties)cur.getObject(); - } - cur.pop(); - level--; - } - } - } finally { - cur.dispose(); - } - } - - return null; - } - - private boolean fetchParagraphProperty(ParagraphPropertyFetcher visitor){ - boolean ok = false; - XSLFTextShape shape = getParentShape(); - XSLFSheet sheet = shape.getSheet(); - - if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr()); - if (ok) return true; - - ok = shape.fetchShapeProperty(visitor); - if (ok) return true; - - - CTPlaceholder ph = shape.getCTPlaceholder(); - if(ph == null){ - // if it is a plain text box then take defaults from presentation.xml - @SuppressWarnings("resource") - XMLSlideShow ppt = sheet.getSlideShow(); - CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel()); - if (themeProps != null) ok = visitor.fetch(themeProps); - } - if (ok) return true; - - // defaults for placeholders are defined in the slide master - CTTextParagraphProperties defaultProps = getDefaultMasterStyle(); - // TODO: determine master shape - if(defaultProps != null) ok = visitor.fetch(defaultProps); - if (ok) return true; - - return false; - } - - void copy(XSLFTextParagraph other){ - if (other == this) return; - - CTTextParagraph thisP = getXmlObject(); - CTTextParagraph otherP = other.getXmlObject(); - - if (thisP.isSetPPr()) thisP.unsetPPr(); - if (thisP.isSetEndParaRPr()) thisP.unsetEndParaRPr(); - - _runs.clear(); - for (int i=thisP.sizeOfBrArray(); i>0; i--) { - thisP.removeBr(i-1); - } - for (int i=thisP.sizeOfRArray(); i>0; i--) { - thisP.removeR(i-1); - } - for (int i=thisP.sizeOfFldArray(); i>0; i--) { - thisP.removeFld(i-1); - } - - XmlCursor thisC = thisP.newCursor(); - thisC.toEndToken(); - XmlCursor otherC = otherP.newCursor(); - otherC.copyXmlContents(thisC); - otherC.dispose(); - thisC.dispose(); - - List otherRs = other.getTextRuns(); - int i=0; - for(CTRegularTextRun rtr : thisP.getRArray()) { - XSLFTextRun run = newTextRun(rtr); - run.copy(otherRs.get(i++)); - _runs.add(run); - } - - - // set properties again, in case we are based on a different - // template - TextAlign srcAlign = other.getTextAlign(); - if(srcAlign != getTextAlign()){ - setTextAlign(srcAlign); - } - - boolean isBullet = other.isBullet(); - if(isBullet != isBullet()){ - setBullet(isBullet); - if(isBullet) { - String buFont = other.getBulletFont(); - if(buFont != null && !buFont.equals(getBulletFont())){ - setBulletFont(buFont); - } - String buChar = other.getBulletCharacter(); - if(buChar != null && !buChar.equals(getBulletCharacter())){ - setBulletCharacter(buChar); - } - PaintStyle buColor = other.getBulletFontColor(); - if(buColor != null && !buColor.equals(getBulletFontColor())){ - setBulletFontColor(buColor); - } - Double buSize = other.getBulletFontSize(); - if(!doubleEquals(buSize, getBulletFontSize())){ - setBulletFontSize(buSize); - } - } - } - - Double leftMargin = other.getLeftMargin(); - if (!doubleEquals(leftMargin, getLeftMargin())){ - setLeftMargin(leftMargin); - } - - Double indent = other.getIndent(); - if (!doubleEquals(indent, getIndent())) { - setIndent(indent); - } - - Double spaceAfter = other.getSpaceAfter(); - if (!doubleEquals(spaceAfter, getSpaceAfter())) { - setSpaceAfter(spaceAfter); - } - - Double spaceBefore = other.getSpaceBefore(); - if (!doubleEquals(spaceBefore, getSpaceBefore())) { - setSpaceBefore(spaceBefore); - } - - Double lineSpacing = other.getLineSpacing(); - if (!doubleEquals(lineSpacing, getLineSpacing())) { - setLineSpacing(lineSpacing); - } - } - - private static boolean doubleEquals(Double d1, Double d2) { - return (d1 == d2 || (d1 != null && d1.equals(d2))); - } - - @Override - public Double getDefaultFontSize() { - CTTextCharacterProperties endPr = _p.getEndParaRPr(); - if (endPr == null || !endPr.isSetSz()) { - // inherit the font size from the master style - CTTextParagraphProperties masterStyle = getDefaultMasterStyle(); - if (masterStyle != null) { - endPr = masterStyle.getDefRPr(); - } - } - return (endPr == null || !endPr.isSetSz()) ? 12 : (endPr.getSz() / 100.); - } - - @Override - public String getDefaultFontFamily() { - return (_runs.isEmpty() ? "Arial" : _runs.get(0).getFontFamily()); - } - - @Override - public BulletStyle getBulletStyle() { - if (!isBullet()) return null; - return new BulletStyle(){ - @Override - public String getBulletCharacter() { - return XSLFTextParagraph.this.getBulletCharacter(); - } - - @Override - public String getBulletFont() { - return XSLFTextParagraph.this.getBulletFont(); - } - - @Override - public Double getBulletFontSize() { - return XSLFTextParagraph.this.getBulletFontSize(); - } - - @Override - public PaintStyle getBulletFontColor() { - return XSLFTextParagraph.this.getBulletFontColor(); - } - - @Override - public void setBulletFontColor(Color color) { - setBulletFontColor(DrawPaint.createSolidPaint(color)); - } - - @Override - public void setBulletFontColor(PaintStyle color) { - XSLFTextParagraph.this.setBulletFontColor(color); - } - - @Override - public AutoNumberingScheme getAutoNumberingScheme() { - return XSLFTextParagraph.this.getAutoNumberingScheme(); - } - - @Override - public Integer getAutoNumberingStartAt() { - return XSLFTextParagraph.this.getAutoNumberingStartAt(); - } - - }; - } - - @Override - public void setBulletStyle(Object... styles) { - if (styles.length == 0) { - setBullet(false); - } else { - setBullet(true); - for (Object ostyle : styles) { - if (ostyle instanceof Number) { - setBulletFontSize(((Number)ostyle).doubleValue()); - } else if (ostyle instanceof Color) { - setBulletFontColor((Color)ostyle); - } else if (ostyle instanceof Character) { - setBulletCharacter(ostyle.toString()); - } else if (ostyle instanceof String) { - setBulletFont((String)ostyle); - } else if (ostyle instanceof AutoNumberingScheme) { - setBulletAutoNumber((AutoNumberingScheme)ostyle, 0); - } - } - } - } - - /** - * Helper method for appending text and keeping paragraph and character properties. - * The character properties are moved to the end paragraph marker - */ - /* package */ void clearButKeepProperties() { - CTTextParagraph thisP = getXmlObject(); - for (int i=thisP.sizeOfBrArray(); i>0; i--) { - thisP.removeBr(i-1); - } - for (int i=thisP.sizeOfFldArray(); i>0; i--) { - thisP.removeFld(i-1); - } - if (!_runs.isEmpty()) { - int size = _runs.size(); - XSLFTextRun lastRun = _runs.get(size-1); - CTTextCharacterProperties cpOther = lastRun.getRPr(false); - if (cpOther != null) { - if (thisP.isSetEndParaRPr()) { - thisP.unsetEndParaRPr(); - } - CTTextCharacterProperties cp = thisP.addNewEndParaRPr(); - cp.set(cpOther); - } - for (int i=size; i>0; i--) { - thisP.removeR(i-1); - } - _runs.clear(); - } - } - - @Override - public boolean isHeaderOrFooter() { - CTPlaceholder ph = _shape.getCTPlaceholder(); - int phId = (ph == null ? -1 : ph.getType().intValue()); - switch (phId) { - case STPlaceholderType.INT_SLD_NUM: - case STPlaceholderType.INT_DT: - case STPlaceholderType.INT_FTR: - case STPlaceholderType.INT_HDR: - return true; - default: - return false; - } - } - - /** - * Helper method to allow subclasses to provide their own text run - * - * @param r the xml reference - * - * @return a new text paragraph - * - * @since POI 3.15-beta2 - */ - protected XSLFTextRun newTextRun(CTRegularTextRun r) { - return new XSLFTextRun(r, this); - } -} +/* ==================================================================== + 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.Color; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.sl.draw.DrawPaint; +import org.apache.poi.sl.usermodel.AutoNumberingScheme; +import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; +import org.apache.poi.sl.usermodel.TextParagraph; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Internal; +import org.apache.poi.util.Units; +import org.apache.poi.xslf.model.ParagraphPropertyFetcher; +import org.apache.xmlbeans.XmlCursor; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextAutonumberBullet; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePercent; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePoint; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharBullet; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextField; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextSpacing; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStopList; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextAutonumberScheme; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextFontAlignType; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; +import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; + +/** + * Represents a paragraph of text within the containing text body. + * The paragraph is the highest level text separation mechanism. + * + * @since POI-3.8 + */ +@Beta +public class XSLFTextParagraph implements TextParagraph { + private final CTTextParagraph _p; + private final List _runs; + private final XSLFTextShape _shape; + + XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape){ + _p = p; + _runs = new ArrayList(); + _shape = shape; + + for(XmlObject ch : _p.selectPath("*")){ + if(ch instanceof CTRegularTextRun){ + CTRegularTextRun r = (CTRegularTextRun)ch; + _runs.add(newTextRun(r)); + } else if (ch instanceof CTTextLineBreak){ + CTTextLineBreak br = (CTTextLineBreak)ch; + CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); + r.setRPr(br.getRPr()); + r.setT("\n"); + _runs.add(newTextRun(r)); + } else if (ch instanceof CTTextField){ + CTTextField f = (CTTextField)ch; + CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); + r.setRPr(f.getRPr()); + r.setT(f.getT()); + _runs.add(newTextRun(r)); + } + } + } + + public String getText(){ + StringBuilder out = new StringBuilder(); + for (XSLFTextRun r : _runs) { + out.append(r.getRawText()); + } + return out.toString(); + } + + String getRenderableText(){ + StringBuilder out = new StringBuilder(); + for (XSLFTextRun r : _runs) { + out.append(r.getRenderableText()); + } + return out.toString(); + } + + @Internal + public CTTextParagraph getXmlObject(){ + return _p; + } + + public XSLFTextShape getParentShape() { + return _shape; + + } + + @Override + public List getTextRuns(){ + return _runs; + } + + public Iterator iterator(){ + return _runs.iterator(); + } + + /** + * Add a new run of text + * + * @return a new run of text + */ + public XSLFTextRun addNewTextRun(){ + CTRegularTextRun r = _p.addNewR(); + CTTextCharacterProperties rPr = r.addNewRPr(); + rPr.setLang("en-US"); + XSLFTextRun run = newTextRun(r); + _runs.add(run); + return run; + } + + /** + * Insert a line break + * + * @return text run representing this line break ('\n') + */ + public XSLFTextRun 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(true); + brProps.set(prevRun); + } + CTRegularTextRun r = CTRegularTextRun.Factory.newInstance(); + r.setRPr(brProps); + r.setT("\n"); + XSLFTextRun run = new XSLFLineBreak(r, this, brProps); + _runs.add(run); + return run; + } + + @Override + public TextAlign getTextAlign(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + @Override + 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)); + } + } + + @Override + public FontAlign getFontAlign(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props){ + if(props.isSetFontAlgn()){ + FontAlign val = FontAlign.values()[props.getFontAlgn().intValue() - 1]; + setValue(val); + return true; + } + return false; + } + }; + fetchParagraphProperty(fetcher); + return fetcher.getValue(); + } + + /** + * Specifies the font alignment that is to be applied to the paragraph. + * Possible values for this include auto, top, center, baseline and bottom. + * see {@link org.apache.poi.sl.usermodel.TextParagraph.FontAlign}. + * + * @param align font align + */ + public void setFontAlign(FontAlign 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 fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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 fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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 null value means to use the text font color. + */ + public PaintStyle getBulletFontColor(){ + final XSLFTheme theme = getParentShape().getSheet().getTheme(); + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props){ + if(props.isSetBuClr()){ + XSLFColor c = new XSLFColor(props.getBuClr(), theme, null); + setValue(c.getColor()); + return true; + } + return false; + } + }; + fetchParagraphProperty(fetcher); + Color col = fetcher.getValue(); + return (col == null) ? null : DrawPaint.createSolidPaint(col); + } + + public void setBulletFontColor(Color color) { + setBulletFontColor(DrawPaint.createSolidPaint(color)); + } + + + /** + * Set the color to be used on bullet characters within a given paragraph. + * + * @param color the bullet color + */ + public void setBulletFontColor(PaintStyle color) { + if (!(color instanceof SolidPaint)) { + throw new IllegalArgumentException("Currently XSLF only supports SolidPaint"); + } + + // TODO: implement setting bullet color to null + SolidPaint sp = (SolidPaint)color; + Color col = DrawPaint.applyColorTransform(sp.getSolidColor()); + + 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) col.getRed(), (byte) col.getGreen(), (byte) col.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: + *

+ * If bulletSize >= 0, then bulletSize is a percentage of the font size. + * If bulletSize < 0, then it specifies the size in points + *

+ * + * @return the bullet size + */ + public Double getBulletFontSize(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + /** + * 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: + *

+ * If bulletSize >= 0, then bulletSize is a percentage of the font size. + * If bulletSize < 0, then it specifies the size in points + *

+ */ + 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(); + } + } + + /** + * @return the auto numbering scheme, or null if not defined + */ + public AutoNumberingScheme getAutoNumberingScheme() { + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()) { + public boolean fetch(CTTextParagraphProperties props) { + if (props.isSetBuAutoNum()) { + AutoNumberingScheme ans = AutoNumberingScheme.forOoxmlID(props.getBuAutoNum().getType().intValue()); + if (ans != null) { + setValue(ans); + return true; + } + } + return false; + } + }; + fetchParagraphProperty(fetcher); + return fetcher.getValue(); + } + + /** + * @return the auto numbering starting number, or null if not defined + */ + public Integer getAutoNumberingStartAt() { + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()) { + public boolean fetch(CTTextParagraphProperties props) { + if (props.isSetBuAutoNum()) { + if (props.getBuAutoNum().isSetStartAt()) { + setValue(props.getBuAutoNum().getStartAt()); + return true; + } + } + return false; + } + }; + fetchParagraphProperty(fetcher); + return fetcher.getValue(); + } + + + @Override + public void setIndent(Double indent){ + if ((indent == null) && !_p.isSetPPr()) return; + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if(indent == null) { + if(pr.isSetIndent()) pr.unsetIndent(); + } else { + pr.setIndent(Units.toEMU(indent)); + } + } + + @Override + public Double getIndent() { + + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props){ + if(props.isSetIndent()){ + setValue(Units.toPoints(props.getIndent())); + return true; + } + return false; + } + }; + fetchParagraphProperty(fetcher); + + return fetcher.getValue(); + } + + @Override + public void setLeftMargin(Double leftMargin){ + if (leftMargin == null && !_p.isSetPPr()) return; + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if (leftMargin == null) { + if(pr.isSetMarL()) pr.unsetMarL(); + } else { + pr.setMarL(Units.toEMU(leftMargin)); + } + + } + + /** + * @return the left margin (in points) of the paragraph, null if unset + */ + @Override + public Double getLeftMargin(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + @Override + public void setRightMargin(Double rightMargin){ + if (rightMargin == null && !_p.isSetPPr()) return; + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if(rightMargin == null) { + if(pr.isSetMarR()) pr.unsetMarR(); + } else { + pr.setMarR(Units.toEMU(rightMargin)); + } + } + + /** + * + * @return the right margin of the paragraph, null if unset + */ + @Override + public Double getRightMargin(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props){ + if(props.isSetMarR()){ + double val = Units.toPoints(props.getMarR()); + setValue(val); + return true; + } + return false; + } + }; + fetchParagraphProperty(fetcher); + return fetcher.getValue(); + } + + @Override + public Double getDefaultTabSize(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + public double getTabStop(final int idx){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + 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)); + } + + @Override + public void setLineSpacing(Double lineSpacing){ + if (lineSpacing == null && !_p.isSetPPr()) return; + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if(lineSpacing == null) { + if (pr.isSetLnSpc()) pr.unsetLnSpc(); + } else { + CTTextSpacing spc = (pr.isSetLnSpc()) ? pr.getLnSpc() : pr.addNewLnSpc(); + if (lineSpacing >= 0) { + (spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct()).setVal((int)(lineSpacing*1000)); + if (spc.isSetSpcPts()) spc.unsetSpcPts(); + } else { + (spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts()).setVal((int)(-lineSpacing*100)); + if (spc.isSetSpcPct()) spc.unsetSpcPct(); + } + } + } + + @Override + public Double getLineSpacing(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + if (lnSpc != null && lnSpc > 0) { + // check if the percentage value is scaled + CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit(); + if(normAutofit != null) { + double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000; + lnSpc *= scale; + } + } + + return lnSpc; + } + + @Override + public void setSpaceBefore(Double spaceBefore){ + if (spaceBefore == null && !_p.isSetPPr()) { + return; + } + + // unset the space before on null input + if (spaceBefore == null) { + if(_p.getPPr().isSetSpcBef()) { + _p.getPPr().unsetSpcBef(); + } + return; + } + + 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); + } + + @Override + public Double getSpaceBefore(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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); + + return fetcher.getValue(); + } + + @Override + public void setSpaceAfter(Double spaceAfter){ + if (spaceAfter == null && !_p.isSetPPr()) { + return; + } + + // unset the space before on null input + if (spaceAfter == null) { + if(_p.getPPr().isSetSpcAft()) { + _p.getPPr().unsetSpcAft(); + } + return; + } + + 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); + } + + @Override + public Double getSpaceAfter(){ + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + 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(); + } + + @Override + public void setIndentLevel(int level){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + pr.setLvl(level); + } + + @Override + public int getIndentLevel() { + CTTextParagraphProperties pr = _p.getPPr(); + return (pr == null || !pr.isSetLvl()) ? 0 : pr.getLvl(); + } + + /** + * Returns whether this paragraph has bullets + */ + public boolean isBullet() { + ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props){ + if(props.isSetBuNone()) { + setValue(false); + return true; + } + if(props.isSetBuFont() || props.isSetBuChar()){ + setValue(true); + return true; + } + return false; + } + }; + fetchParagraphProperty(fetcher); + return fetcher.getValue() == null ? false : fetcher.getValue(); + } + + /** + * + * @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.addNewBuFont().setTypeface("Arial"); + pr.addNewBuChar().setChar("\u2022"); + } else { + if (pr.isSetBuFont()) pr.unsetBuFont(); + if (pr.isSetBuChar()) pr.unsetBuChar(); + if (pr.isSetBuAutoNum()) pr.unsetBuAutoNum(); + if (pr.isSetBuBlip()) pr.unsetBuBlip(); + 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(); + pr.addNewBuNone(); + } + } + + /** + * Specifies that automatic numbered bullet points should be applied to this paragraph + * + * @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 setBulletAutoNumber(AutoNumberingScheme 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.ooxmlId)); + lst.setStartAt(startAt); + } + + @Override + public String toString(){ + return "[" + getClass() + "]" + getText(); + } + + + /** + * @return master style text paragraph properties, or null if + * there are no master slides or the master slides do not contain a text paragraph + */ + /* package */ CTTextParagraphProperties getDefaultMasterStyle(){ + CTPlaceholder ph = _shape.getCTPlaceholder(); + String defaultStyleSelector; + switch(ph == null ? -1 : ph.getType().intValue()) { + case STPlaceholderType.INT_TITLE: + case STPlaceholderType.INT_CTR_TITLE: + defaultStyleSelector = "titleStyle"; + break; + case -1: // no placeholder means plain text box + case STPlaceholderType.INT_FTR: + case STPlaceholderType.INT_SLD_NUM: + case STPlaceholderType.INT_DT: + defaultStyleSelector = "otherStyle"; + break; + default: + defaultStyleSelector = "bodyStyle"; + break; + } + int level = getIndentLevel(); + + // wind up and find the root master sheet which must be slide master + final String nsPML = "http://schemas.openxmlformats.org/presentationml/2006/main"; + final String nsDML = "http://schemas.openxmlformats.org/drawingml/2006/main"; + XSLFSheet masterSheet = _shape.getSheet(); + for (XSLFSheet m = masterSheet; m != null; m = (XSLFSheet)m.getMasterSheet()) { + masterSheet = m; + XmlObject xo = masterSheet.getXmlObject(); + XmlCursor cur = xo.newCursor(); + try { + cur.push(); + if ((cur.toChild(nsPML, "txStyles") && cur.toChild(nsPML, defaultStyleSelector)) || + (cur.pop() && cur.toChild(nsPML, "notesStyle"))) { + while (level >= 0) { + cur.push(); + if (cur.toChild(nsDML, "lvl" +(level+1)+ "pPr")) { + return (CTTextParagraphProperties)cur.getObject(); + } + cur.pop(); + level--; + } + } + } finally { + cur.dispose(); + } + } + + return null; + } + + private boolean fetchParagraphProperty(ParagraphPropertyFetcher visitor){ + boolean ok = false; + XSLFTextShape shape = getParentShape(); + XSLFSheet sheet = shape.getSheet(); + + if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr()); + if (ok) return true; + + ok = shape.fetchShapeProperty(visitor); + if (ok) return true; + + + CTPlaceholder ph = shape.getCTPlaceholder(); + if(ph == null){ + // if it is a plain text box then take defaults from presentation.xml + @SuppressWarnings("resource") + XMLSlideShow ppt = sheet.getSlideShow(); + CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel()); + if (themeProps != null) ok = visitor.fetch(themeProps); + } + if (ok) return true; + + // defaults for placeholders are defined in the slide master + CTTextParagraphProperties defaultProps = getDefaultMasterStyle(); + // TODO: determine master shape + if(defaultProps != null) ok = visitor.fetch(defaultProps); + if (ok) return true; + + return false; + } + + void copy(XSLFTextParagraph other){ + if (other == this) return; + + CTTextParagraph thisP = getXmlObject(); + CTTextParagraph otherP = other.getXmlObject(); + + if (thisP.isSetPPr()) thisP.unsetPPr(); + if (thisP.isSetEndParaRPr()) thisP.unsetEndParaRPr(); + + _runs.clear(); + for (int i=thisP.sizeOfBrArray(); i>0; i--) { + thisP.removeBr(i-1); + } + for (int i=thisP.sizeOfRArray(); i>0; i--) { + thisP.removeR(i-1); + } + for (int i=thisP.sizeOfFldArray(); i>0; i--) { + thisP.removeFld(i-1); + } + + XmlCursor thisC = thisP.newCursor(); + thisC.toEndToken(); + XmlCursor otherC = otherP.newCursor(); + otherC.copyXmlContents(thisC); + otherC.dispose(); + thisC.dispose(); + + List otherRs = other.getTextRuns(); + int i=0; + for(CTRegularTextRun rtr : thisP.getRArray()) { + XSLFTextRun run = newTextRun(rtr); + run.copy(otherRs.get(i++)); + _runs.add(run); + } + + + // set properties again, in case we are based on a different + // template + TextAlign srcAlign = other.getTextAlign(); + if(srcAlign != getTextAlign()){ + setTextAlign(srcAlign); + } + + boolean isBullet = other.isBullet(); + if(isBullet != isBullet()){ + setBullet(isBullet); + if(isBullet) { + String buFont = other.getBulletFont(); + if(buFont != null && !buFont.equals(getBulletFont())){ + setBulletFont(buFont); + } + String buChar = other.getBulletCharacter(); + if(buChar != null && !buChar.equals(getBulletCharacter())){ + setBulletCharacter(buChar); + } + PaintStyle buColor = other.getBulletFontColor(); + if(buColor != null && !buColor.equals(getBulletFontColor())){ + setBulletFontColor(buColor); + } + Double buSize = other.getBulletFontSize(); + if(!doubleEquals(buSize, getBulletFontSize())){ + setBulletFontSize(buSize); + } + } + } + + Double leftMargin = other.getLeftMargin(); + if (!doubleEquals(leftMargin, getLeftMargin())){ + setLeftMargin(leftMargin); + } + + Double indent = other.getIndent(); + if (!doubleEquals(indent, getIndent())) { + setIndent(indent); + } + + Double spaceAfter = other.getSpaceAfter(); + if (!doubleEquals(spaceAfter, getSpaceAfter())) { + setSpaceAfter(spaceAfter); + } + + Double spaceBefore = other.getSpaceBefore(); + if (!doubleEquals(spaceBefore, getSpaceBefore())) { + setSpaceBefore(spaceBefore); + } + + Double lineSpacing = other.getLineSpacing(); + if (!doubleEquals(lineSpacing, getLineSpacing())) { + setLineSpacing(lineSpacing); + } + } + + private static boolean doubleEquals(Double d1, Double d2) { + return (d1 == d2 || (d1 != null && d1.equals(d2))); + } + + @Override + public Double getDefaultFontSize() { + CTTextCharacterProperties endPr = _p.getEndParaRPr(); + if (endPr == null || !endPr.isSetSz()) { + // inherit the font size from the master style + CTTextParagraphProperties masterStyle = getDefaultMasterStyle(); + if (masterStyle != null) { + endPr = masterStyle.getDefRPr(); + } + } + return (endPr == null || !endPr.isSetSz()) ? 12 : (endPr.getSz() / 100.); + } + + @Override + public String getDefaultFontFamily() { + return (_runs.isEmpty() ? "Arial" : _runs.get(0).getFontFamily()); + } + + @Override + public BulletStyle getBulletStyle() { + if (!isBullet()) return null; + return new BulletStyle(){ + @Override + public String getBulletCharacter() { + return XSLFTextParagraph.this.getBulletCharacter(); + } + + @Override + public String getBulletFont() { + return XSLFTextParagraph.this.getBulletFont(); + } + + @Override + public Double getBulletFontSize() { + return XSLFTextParagraph.this.getBulletFontSize(); + } + + @Override + public PaintStyle getBulletFontColor() { + return XSLFTextParagraph.this.getBulletFontColor(); + } + + @Override + public void setBulletFontColor(Color color) { + setBulletFontColor(DrawPaint.createSolidPaint(color)); + } + + @Override + public void setBulletFontColor(PaintStyle color) { + XSLFTextParagraph.this.setBulletFontColor(color); + } + + @Override + public AutoNumberingScheme getAutoNumberingScheme() { + return XSLFTextParagraph.this.getAutoNumberingScheme(); + } + + @Override + public Integer getAutoNumberingStartAt() { + return XSLFTextParagraph.this.getAutoNumberingStartAt(); + } + + }; + } + + @Override + public void setBulletStyle(Object... styles) { + if (styles.length == 0) { + setBullet(false); + } else { + setBullet(true); + for (Object ostyle : styles) { + if (ostyle instanceof Number) { + setBulletFontSize(((Number)ostyle).doubleValue()); + } else if (ostyle instanceof Color) { + setBulletFontColor((Color)ostyle); + } else if (ostyle instanceof Character) { + setBulletCharacter(ostyle.toString()); + } else if (ostyle instanceof String) { + setBulletFont((String)ostyle); + } else if (ostyle instanceof AutoNumberingScheme) { + setBulletAutoNumber((AutoNumberingScheme)ostyle, 0); + } + } + } + } + + /** + * Helper method for appending text and keeping paragraph and character properties. + * The character properties are moved to the end paragraph marker + */ + /* package */ void clearButKeepProperties() { + CTTextParagraph thisP = getXmlObject(); + for (int i=thisP.sizeOfBrArray(); i>0; i--) { + thisP.removeBr(i-1); + } + for (int i=thisP.sizeOfFldArray(); i>0; i--) { + thisP.removeFld(i-1); + } + if (!_runs.isEmpty()) { + int size = _runs.size(); + XSLFTextRun lastRun = _runs.get(size-1); + CTTextCharacterProperties cpOther = lastRun.getRPr(false); + if (cpOther != null) { + if (thisP.isSetEndParaRPr()) { + thisP.unsetEndParaRPr(); + } + CTTextCharacterProperties cp = thisP.addNewEndParaRPr(); + cp.set(cpOther); + } + for (int i=size; i>0; i--) { + thisP.removeR(i-1); + } + _runs.clear(); + } + } + + @Override + public boolean isHeaderOrFooter() { + CTPlaceholder ph = _shape.getCTPlaceholder(); + int phId = (ph == null ? -1 : ph.getType().intValue()); + switch (phId) { + case STPlaceholderType.INT_SLD_NUM: + case STPlaceholderType.INT_DT: + case STPlaceholderType.INT_FTR: + case STPlaceholderType.INT_HDR: + return true; + default: + return false; + } + } + + /** + * Helper method to allow subclasses to provide their own text run + * + * @param r the xml reference + * + * @return a new text paragraph + * + * @since POI 3.15-beta2 + */ + protected XSLFTextRun newTextRun(CTRegularTextRun r) { + return new XSLFTextRun(r, this); + } +}