From: Tim Allison Date: Wed, 31 Jul 2013 00:19:07 +0000 (+0000) Subject: POI-55292 X-Git-Tag: REL_3_10_BETA2~39 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=da070049ddcc2c23d43b61582aaa525510b6aa95;p=poi.git POI-55292 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1508691 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java index 62ce224862..d24bd01e6d 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java @@ -17,6 +17,11 @@ package org.apache.poi.xssf.usermodel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + import org.apache.poi.hssf.util.HSSFColor; import org.openxmlformats.schemas.drawingml.x2006.main.*; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShape; @@ -24,15 +29,19 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShapeNonV import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRElt; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRPrElt; import org.apache.poi.util.Internal; +import org.apache.poi.util.Units; +import org.apache.poi.ss.usermodel.VerticalAlignment; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STUnderlineValues; /** * Represents a shape with a predefined geometry in a SpreadsheetML drawing. * Possible shape types are defined in {@link org.apache.poi.ss.usermodel.ShapeTypes} - * - * @author Yegor Kozlov */ -public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable superclass +public class XSSFSimpleShape extends XSSFShape implements Iterable { // TODO - instantiable superclass + /** + * List of the paragraphs that make up the text in this shape + */ + private final List _paragraphs; /** * A default instance of CTShape used for creating new shapes. */ @@ -46,6 +55,15 @@ public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable supercla protected XSSFSimpleShape(XSSFDrawing drawing, CTShape ctShape) { this.drawing = drawing; this.ctShape = ctShape; + + _paragraphs = new ArrayList(); + + // initialize any existing paragraphs - this will be the default body paragraph in a new shape, + // or existing paragraphs that have been loaded from the file + CTTextBody body = ctShape.getTxBody(); + for(int i = 0; i < body.sizeOfPArray(); i++) { + _paragraphs.add(new XSSFTextParagraph(body.getPArray(i), ctShape)); + } } /** @@ -74,34 +92,18 @@ public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable supercla geom.setPrst(STShapeType.RECT); geom.addNewAvLst(); - CTShapeStyle style = shape.addNewStyle(); - CTSchemeColor scheme = style.addNewLnRef().addNewSchemeClr(); - scheme.setVal(STSchemeColorVal.ACCENT_1); - scheme.addNewShade().setVal(50000); - style.getLnRef().setIdx(2); - - CTStyleMatrixReference fillref = style.addNewFillRef(); - fillref.setIdx(1); - fillref.addNewSchemeClr().setVal(STSchemeColorVal.ACCENT_1); - - CTStyleMatrixReference effectRef = style.addNewEffectRef(); - effectRef.setIdx(0); - effectRef.addNewSchemeClr().setVal(STSchemeColorVal.ACCENT_1); - - CTFontReference fontRef = style.addNewFontRef(); - fontRef.setIdx(STFontCollectionIndex.MINOR); - fontRef.addNewSchemeClr().setVal(STSchemeColorVal.LT_1); - CTTextBody body = shape.addNewTxBody(); CTTextBodyProperties bodypr = body.addNewBodyPr(); - bodypr.setAnchor(STTextAnchoringType.CTR); + bodypr.setAnchor(STTextAnchoringType.T); bodypr.setRtlCol(false); CTTextParagraph p = body.addNewP(); - p.addNewPPr().setAlgn(STTextAlignType.CTR); + p.addNewPPr().setAlgn(STTextAlignType.L); CTTextCharacterProperties endPr = p.addNewEndParaRPr(); endPr.setLang("en-US"); - endPr.setSz(1100); - + endPr.setSz(1100); + CTSolidColorFillProperties scfpr = endPr.addNewSolidFill(); + scfpr.addNewSrgbClr().setVal(new byte[] { 0, 0, 0 }); + body.addNewLstStyle(); prototype = shape; @@ -114,30 +116,255 @@ public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable supercla return ctShape; } + + public Iterator iterator(){ + return _paragraphs.iterator(); + } + /** - * Gets the shape type, one of the constants defined in {@link org.apache.poi.ss.usermodel.ShapeTypes}. - * - * @return the shape type - * @see org.apache.poi.ss.usermodel.ShapeTypes + * Returns the text from all paragraphs in the shape. Paragraphs are separated by new lines. + * + * @return text contained within this shape or empty string */ - public int getShapeType() { - return ctShape.getSpPr().getPrstGeom().getPrst().intValue(); + public String getText() { + final int MAX_LEVELS = 9; + StringBuilder out = new StringBuilder(); + List levelCount = new ArrayList(MAX_LEVELS); // maximum 9 levels + XSSFTextParagraph p = null; + + // initialise the levelCount array - this maintains a record of the numbering to be used at each level + for (int k = 0; k < MAX_LEVELS; k++){ + levelCount.add(0); + } + + for(int i = 0; i < _paragraphs.size(); i++) { + if (out.length() > 0) out.append('\n'); + p = _paragraphs.get(i); + + if(p.isBullet() && p.getText().length() > 0){ + + int level = Math.min(p.getLevel(), MAX_LEVELS - 1); + + if(p.isBulletAutoNumber()){ + i = processAutoNumGroup(i, level, levelCount, out); + } else { + // indent appropriately for the level + for(int j = 0; j < level; j++){ + out.append('\t'); + } + String character = p.getBulletCharacter(); + out.append(character.length() > 0 ? character + " " : "- "); + out.append(p.getText()); + } + } else { + out.append(p.getText()); + + // this paragraph is not a bullet, so reset the count array + for (int k = 0; k < MAX_LEVELS; k++){ + levelCount.set(k, 0); + } + } + } + + return out.toString(); } /** - * Sets the shape types. - * - * @param type the shape type, one of the constants defined in {@link org.apache.poi.ss.usermodel.ShapeTypes}. - * @see org.apache.poi.ss.usermodel.ShapeTypes + * */ - public void setShapeType(int type) { - ctShape.getSpPr().getPrstGeom().setPrst(STShapeType.Enum.forInt(type)); + private int processAutoNumGroup(int index, int level, List levelCount, StringBuilder out){ + XSSFTextParagraph p = null; + XSSFTextParagraph nextp = null; + ListAutoNumber scheme, nextScheme; + int startAt, nextStartAt; + + p = _paragraphs.get(index); + + // The rules for generating the auto numbers are as follows. If the following paragraph is also + // an auto-number, has the same type/scheme (and startAt if defined on this paragraph) then they are + // considered part of the same group. An empty bullet paragraph is counted as part of the same + // group but does not increment the count for the group. A change of type, startAt or the paragraph + // not being a bullet resets the count for that level to 1. + + // first auto-number paragraph so initialise to 1 or the bullets startAt if present + startAt = p.getBulletAutoNumberStart(); + scheme = p.getBulletAutoNumberScheme(); + if(levelCount.get(level) == 0) { + levelCount.set(level, startAt == 0 ? 1 : startAt); + } + // indent appropriately for the level + for(int j = 0; j < level; j++){ + out.append('\t'); + } + if (p.getText().length() > 0){ + out.append(getBulletPrefix(scheme, levelCount.get(level))); + out.append(p.getText()); + } + while(true) { + nextp = (index + 1) == _paragraphs.size() ? null : _paragraphs.get(index + 1); + if(nextp == null) break; // out of paragraphs + if(!(nextp.isBullet() && p.isBulletAutoNumber())) break; // not an auto-number bullet + if(nextp.getLevel() > level) { + // recurse into the new level group + if (out.length() > 0) out.append('\n'); + index = processAutoNumGroup(index + 1, nextp.getLevel(), levelCount, out); + continue; // restart the loop given the new index + } else if(nextp.getLevel() < level) { + break; // changed level + } + nextScheme = nextp.getBulletAutoNumberScheme(); + nextStartAt = nextp.getBulletAutoNumberStart(); + + if(nextScheme == scheme && nextStartAt == startAt) { + // bullet is valid, so increment i + ++index; + if (out.length() > 0) out.append('\n'); + // indent for the level + for(int j = 0; j < level; j++){ + out.append('\t'); + } + // check for empty text - only output a bullet if there is text, but it is still part of the group + if(nextp.getText().length() > 0) { + // increment the count for this level + levelCount.set(level, levelCount.get(level) + 1); + out.append(getBulletPrefix(nextScheme, levelCount.get(level))); + out.append(nextp.getText()); + } + } else { + // something doesn't match so stop + break; + } + } + // end of the group so reset the count for this level + levelCount.set(level, 0); + + return index; } + /** + * Returns a string containing an appropriate prefix for an auto-numbering bullet + * @param scheme the auto-numbering scheme used by the bullet + * @param value the value of the bullet + * @return + */ + private String getBulletPrefix(ListAutoNumber scheme, int value){ + StringBuilder out = new StringBuilder(); + + switch(scheme) { + case ALPHA_LC_PARENT_BOTH: + case ALPHA_LC_PARENT_R: + if(scheme == ListAutoNumber.ALPHA_LC_PARENT_BOTH) out.append('('); + out.append(valueToAlpha(value).toLowerCase()); + out.append(')'); + break; + case ALPHA_UC_PARENT_BOTH: + case ALPHA_UC_PARENT_R: + if(scheme == ListAutoNumber.ALPHA_UC_PARENT_BOTH) out.append('('); + out.append(valueToAlpha(value)); + out.append(')'); + break; + case ALPHA_LC_PERIOD: + out.append(valueToAlpha(value).toLowerCase()); + out.append('.'); + break; + case ALPHA_UC_PERIOD: + out.append(valueToAlpha(value)); + out.append('.'); + break; + case ARABIC_PARENT_BOTH: + case ARABIC_PARENT_R: + if(scheme == ListAutoNumber.ARABIC_PARENT_BOTH) out.append('('); + out.append(value); + out.append(')'); + break; + case ARABIC_PERIOD: + out.append(value); + out.append('.'); + break; + case ARABIC_PLAIN: + out.append(value); + break; + case ROMAN_LC_PARENT_BOTH: + case ROMAN_LC_PARENT_R: + if(scheme == ListAutoNumber.ROMAN_LC_PARENT_BOTH) out.append('('); + out.append(valueToRoman(value).toLowerCase()); + out.append(')'); + break; + case ROMAN_UC_PARENT_BOTH: + case ROMAN_UC_PARENT_R: + if(scheme == ListAutoNumber.ROMAN_UC_PARENT_BOTH) out.append('('); + out.append(valueToRoman(value)); + out.append(')'); + break; + case ROMAN_LC_PERIOD: + out.append(valueToRoman(value).toLowerCase()); + out.append('.'); + break; + case ROMAN_UC_PERIOD: + out.append(valueToRoman(value)); + out.append('.'); + break; + default: + out.append('\u2022'); // can't set the font to wingdings so use the default bullet character + break; + } + out.append(" "); + return out.toString(); + } + + /** + * Convert an integer to its alpha equivalent e.g. 1 = A, 2 = B, 27 = AA etc + */ + private String valueToAlpha(int value) { + String alpha = ""; + int modulo; + while (value > 0) { + modulo = (value - 1) % 26; + alpha = (char)(65 + modulo) + alpha; + value = (int)((value - modulo) / 26); + } + return alpha; + } + + private static String[] _romanChars = new String[] { "M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I" }; + private static int[] _romanAlphaValues = new int[] { 1000,900,500,400,100,90,50,40,10,9,5,4,1 }; + + /** + * Convert an integer to its roman equivalent e.g. 1 = I, 9 = IX etc + */ + private String valueToRoman(int value) { + StringBuilder out = new StringBuilder(); + for(int i = 0; value > 0 && i < _romanChars.length; i++) { + while(_romanAlphaValues[i] <= value) { + out.append(_romanChars[i]); + value -= _romanAlphaValues[i]; + } + } + return out.toString(); + } + + /** + * Clear all text from this shape + */ + public void clearText(){ + _paragraphs.clear(); + CTTextBody txBody = ctShape.getTxBody(); + txBody.setPArray(null); // remove any existing paragraphs + } + + /** + * Set a single paragraph of text on the shape. Note this will replace all existing paragraphs created on the shape. + * @param text string representing the paragraph text + */ + public void setText(String text){ + clearText(); - protected CTShapeProperties getShapeProperties(){ - return ctShape.getSpPr(); + addNewTextParagraph().addNewTextRun().setText(text); } + /** + * Set a single paragraph of text on the shape. Note this will replace all existing paragraphs created on the shape. + * @param str rich text string representing the paragraph text + */ public void setText(XSSFRichTextString str){ XSSFWorkbook wb = (XSSFWorkbook)getDrawing().getParent().getParent(); @@ -166,12 +393,426 @@ public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable supercla r.setT(lt.getT()); } } + + clearText(); ctShape.getTxBody().setPArray(new CTTextParagraph[]{p}); + _paragraphs.add(new XSSFTextParagraph(ctShape.getTxBody().getPArray(0), ctShape)); + } + + /** + * Returns a collection of the XSSFTextParagraphs that are attached to this shape + * + * @return text paragraphs in this shape + */ + public List getTextParagraphs() { + return _paragraphs; + } + + /** + * Add a new paragraph run to this shape + * + * @return created paragraph run + */ + public XSSFTextParagraph addNewTextParagraph() { + CTTextBody txBody = ctShape.getTxBody(); + CTTextParagraph p = txBody.addNewP(); + XSSFTextParagraph paragraph = new XSSFTextParagraph(p, ctShape); + _paragraphs.add(paragraph); + return paragraph; + } + + /** + * Add a new paragraph run to this shape, set to the provided string + * + * @return created paragraph run + */ + public XSSFTextParagraph addNewTextParagraph(String text) { + XSSFTextParagraph paragraph = addNewTextParagraph(); + paragraph.addNewTextRun().setText(text); + return paragraph; + } + + /** + * Add a new paragraph run to this shape, set to the provided rich text string + * + * @return created paragraph run + */ + public XSSFTextParagraph addNewTextParagraph(XSSFRichTextString str) { + CTTextBody txBody = ctShape.getTxBody(); + CTTextParagraph p = txBody.addNewP(); + + if(str.numFormattingRuns() == 0){ + CTRegularTextRun r = p.addNewR(); + CTTextCharacterProperties rPr = r.addNewRPr(); + rPr.setLang("en-US"); + rPr.setSz(1100); + r.setT(str.getString()); + + } else { + for (int i = 0; i < str.getCTRst().sizeOfRArray(); i++) { + CTRElt lt = str.getCTRst().getRArray(i); + CTRPrElt ltPr = lt.getRPr(); + if(ltPr == null) ltPr = lt.addNewRPr(); + + CTRegularTextRun r = p.addNewR(); + CTTextCharacterProperties rPr = r.addNewRPr(); + rPr.setLang("en-US"); + + applyAttributes(ltPr, rPr); + + r.setT(lt.getT()); + } + } + + // Note: the XSSFTextParagraph constructor will create its required XSSFTextRuns from the provided CTTextParagraph + XSSFTextParagraph paragraph = new XSSFTextParagraph(p, ctShape); + _paragraphs.add(paragraph); + + return paragraph; + } + + /** + * Sets the type of horizontal overflow for the text. + * + * @param overflow - the type of horizontal overflow. + * A null values unsets this property. + */ + public void setTextHorizontalOverflow(TextHorizontalOverflow overflow){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(anchor == null) { + if(bodyPr.isSetHorzOverflow()) bodyPr.unsetHorzOverflow(); + } else { + bodyPr.setHorzOverflow(STTextHorzOverflowType.Enum.forInt(overflow.ordinal() + 1)); + } + } + } + + /** + * Returns the type of horizontal overflow for the text. + * + * @return the type of horizontal overflow + */ + public TextHorizontalOverflow getTextHorizontalOverflow(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if(bodyPr != null) { + if(bodyPr.isSetHorzOverflow()){ + return TextHorizontalOverflow.values()[bodyPr.getVertOverflow().intValue() - 1]; + } + } + return TextHorizontalOverflow.OVERFLOW; + } + + /** + * Sets the type of vertical overflow for the text. + * + * @param overflow - the type of vertical overflow. + * A null values unsets this property. + */ + public void setTextVerticalOverflow(TextVerticalOverflow overflow){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(anchor == null) { + if(bodyPr.isSetVertOverflow()) bodyPr.unsetVertOverflow(); + } else { + bodyPr.setVertOverflow(STTextVertOverflowType.Enum.forInt(overflow.ordinal() + 1)); + } + } + } + + /** + * Returns the type of vertical overflow for the text. + * + * @return the type of vertical overflow + */ + public TextVerticalOverflow getTextVerticalOverflow(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if(bodyPr != null) { + if(bodyPr.isSetVertOverflow()){ + return TextVerticalOverflow.values()[bodyPr.getVertOverflow().intValue() - 1]; + } + } + return TextVerticalOverflow.OVERFLOW; + } + + /** + * Sets the type of vertical alignment for the text within the shape. + * + * @param anchor - the type of alignment. + * A null values unsets this property. + */ + public void setVerticalAlignment(VerticalAlignment anchor){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(anchor == null) { + if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor(); + } else { + bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1)); + } + } + } + + /** + * Returns the type of vertical alignment for the text within the shape. + * + * @return the type of vertical alignment + */ + public VerticalAlignment getVerticalAlignment(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if(bodyPr != null) { + if(bodyPr.isSetAnchor()){ + return VerticalAlignment.values()[bodyPr.getAnchor().intValue() - 1]; + } + } + return VerticalAlignment.TOP; + } + + /** + * Sets the vertical orientation of the text + * + * @param orientation vertical orientation of the text + * A null values unsets this property. + */ + public void setTextDirection(TextDirection orientation){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(orientation == null) { + if(bodyPr.isSetVert()) bodyPr.unsetVert(); + } else { + bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1)); + } + } + } + + /** + * Gets the vertical orientation of the text + * + * @return vertical orientation of the text + */ + public TextDirection getTextDirection(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + STTextVerticalType.Enum val = bodyPr.getVert(); + if(val != null){ + return TextDirection.values()[val.intValue() - 1]; + } + } + return TextDirection.HORIZONTAL; + } + + + /** + * 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(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetBIns()){ + return Units.toPoints(bodyPr.getBIns()); + } + } + // If this attribute is omitted, then a value of 0.05 inches is implied + return 3.6; + } + + /** + * 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(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetLIns()){ + return Units.toPoints(bodyPr.getLIns()); + } + } + // If this attribute is omitted, then a value of 0.05 inches is implied + return 3.6; + } + /** + * 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(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetRIns()){ + return Units.toPoints(bodyPr.getRIns()); + } + } + // If this attribute is omitted, then a value of 0.05 inches is implied + return 3.6; } /** + * 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(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetTIns()){ + return Units.toPoints(bodyPr.getTIns()); + } + } + // If this attribute is omitted, then a value of 0.05 inches is implied + return 3.6; + } + + /** + * Sets the bottom inset. + * @see #getBottomInset() + * + * @param margin the bottom margin + */ + public void setBottomInset(double margin){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetBIns(); + else bodyPr.setBIns(Units.toEMU(margin)); + } + } + + /** + * Sets the left inset. + * @see #getLeftInset() + * + * @param margin the left margin + */ + public void setLeftInset(double margin){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetLIns(); + else bodyPr.setLIns(Units.toEMU(margin)); + } + } + + /** + * Sets the right inset. + * @see #getRightInset() + * + * @param margin the right margin + */ + public void setRightInset(double margin){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetRIns(); + else bodyPr.setRIns(Units.toEMU(margin)); + } + } + + /** + * Sets the top inset. + * @see #getTopInset() + * + * @param margin the top margin + */ + public void setTopInset(double margin){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetTIns(); + else bodyPr.setTIns(Units.toEMU(margin)); + } + } + + + /** + * @return whether to wrap words within the bounding rectangle + */ + public boolean getWordWrap(){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetWrap()){ + return bodyPr.getWrap() == STTextWrappingType.SQUARE; + } + } + return true; + } + + /** + * + * @param wrap whether to wrap words within the bounding rectangle + */ + public void setWordWrap(boolean wrap){ + CTTextBodyProperties bodyPr = ctShape.getTxBody().getBodyPr(); + 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 = ctShape.getTxBody().getBodyPr(); + 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 = ctShape.getTxBody().getBodyPr(); + 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; + } + + /** + * Gets the shape type, one of the constants defined in {@link org.apache.poi.ss.usermodel.ShapeTypes}. + * + * @return the shape type + * @see org.apache.poi.ss.usermodel.ShapeTypes + */ + public int getShapeType() { + return ctShape.getSpPr().getPrstGeom().getPrst().intValue(); + } + + /** + * Sets the shape types. + * + * @param type the shape type, one of the constants defined in {@link org.apache.poi.ss.usermodel.ShapeTypes}. + * @see org.apache.poi.ss.usermodel.ShapeTypes + */ + public void setShapeType(int type) { + ctShape.getSpPr().getPrstGeom().setPrst(STShapeType.Enum.forInt(type)); + } + + protected CTShapeProperties getShapeProperties(){ + return ctShape.getSpPr(); + } + + /** * org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRPrElt to * org.openxmlformats.schemas.drawingml.x2006.main.CTFont adapter */ @@ -186,8 +827,8 @@ public class XSSFSimpleShape extends XSSFShape { // TODO - instantiable supercla } if(pr.sizeOfIArray() > 0) rPr.setI(pr.getIArray(0).getVal()); - if(pr.sizeOfFamilyArray() > 0) { - CTTextFont rFont = rPr.addNewLatin(); + if(pr.sizeOfRFontArray() > 0) { + CTTextFont rFont = rPr.isSetLatin() ? rPr.getLatin() : rPr.addNewLatin(); rFont.setTypeface(pr.getRFontArray(0).getVal()); } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFDrawing.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFDrawing.java index 79d0c98555..77f48c74aa 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFDrawing.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFDrawing.java @@ -17,6 +17,10 @@ package org.apache.poi.xssf.usermodel; import java.awt.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.Arrays; import java.util.List; @@ -243,4 +247,426 @@ public class TestXSSFDrawing extends TestCase { } + + /** + * ensure that font and color rich text attributes defined in a XSSFRichTextString + * are passed to XSSFSimpleShape. + * + * See Bugzilla 54969. + */ + public void testRichTextFontAndColor() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + XSSFRichTextString rt = new XSSFRichTextString("Test String"); + + XSSFFont font = wb.createFont(); + font.setColor(new XSSFColor(new Color(0, 128, 128))); + font.setFontName("Arial"); + rt.applyFont(font); + + shape.setText(rt); + + CTTextParagraph pr = shape.getCTShape().getTxBody().getPArray(0); + assertEquals(1, pr.sizeOfRArray()); + + CTTextCharacterProperties rPr = pr.getRArray(0).getRPr(); + assertEquals("Arial", rPr.getLatin().getTypeface()); + assertTrue(Arrays.equals( + new byte[]{0, (byte)128, (byte)128} , + rPr.getSolidFill().getSrgbClr().getVal())); + + } + + /** + * Test setText single paragraph to ensure backwards compatibility + */ + public void testSetTextSingleParagraph() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + XSSFRichTextString rt = new XSSFRichTextString("Test String"); + + XSSFFont font = wb.createFont(); + font.setColor(new XSSFColor(new Color(0, 255, 255))); + font.setFontName("Arial"); + rt.applyFont(font); + + shape.setText(rt); + + List paras = shape.getTextParagraphs(); + assertEquals(1, paras.size()); + assertEquals("Test String", paras.get(0).getText()); + + List runs = paras.get(0).getTextRuns(); + assertEquals(1, runs.size()); + assertEquals("Arial", runs.get(0).getFontFamily()); + + Color clr = runs.get(0).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 255, 255 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + } + + /** + * Test addNewTextParagraph + */ + public void testAddNewTextParagraph() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + + XSSFTextParagraph para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 1"); + + List paras = shape.getTextParagraphs(); + assertEquals(2, paras.size()); // this should be 2 as XSSFSimpleShape creates a default paragraph (no text), and then we add a string to that. + + List runs = para.getTextRuns(); + assertEquals(1, runs.size()); + assertEquals("Line 1", runs.get(0).getText()); + } + + /** + * Test addNewTextParagraph using RichTextString + */ + public void testAddNewTextParagraphWithRTS() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + XSSFRichTextString rt = new XSSFRichTextString("Test Rich Text String"); + + XSSFFont font = wb.createFont(); + font.setColor(new XSSFColor(new Color(0, 255, 255))); + font.setFontName("Arial"); + rt.applyFont(font); + + XSSFFont midfont = wb.createFont(); + midfont.setColor(new XSSFColor(new Color(0, 255, 0))); + rt.applyFont(5, 14, midfont); // set the text "Rich Text" to be green and the default font + + XSSFTextParagraph para = shape.addNewTextParagraph(rt); + + // Save and re-load it + wb = XSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + + // Check + drawing = sheet.createDrawingPatriarch(); + + List shapes = drawing.getShapes(); + assertEquals(1, shapes.size()); + assertTrue(shapes.get(0) instanceof XSSFSimpleShape); + + XSSFSimpleShape sshape = (XSSFSimpleShape) shapes.get(0); + + List paras = sshape.getTextParagraphs(); + assertEquals(2, paras.size()); // this should be 2 as XSSFSimpleShape creates a default paragraph (no text), and then we add a string to that. + + List runs = para.getTextRuns(); + assertEquals(3, runs.size()); + + // first run properties + assertEquals("Test ", runs.get(0).getText()); + assertEquals("Arial", runs.get(0).getFontFamily()); + + Color clr = runs.get(0).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 255, 255 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + + // second run properties + assertEquals("Rich Text", runs.get(1).getText()); + assertEquals(XSSFFont.DEFAULT_FONT_NAME, runs.get(1).getFontFamily()); + + clr = runs.get(1).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 255, 0 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + + // third run properties + assertEquals(" String", runs.get(2).getText()); + assertEquals("Arial", runs.get(2).getFontFamily()); + clr = runs.get(2).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 255, 255 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + } + + /** + * Test add multiple paragraphs and retrieve text + */ + public void testAddMultipleParagraphs() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + + XSSFTextParagraph para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 1"); + + para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 2"); + + para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 3"); + + List paras = shape.getTextParagraphs(); + assertEquals(4, paras.size()); // this should be 4 as XSSFSimpleShape creates a default paragraph (no text), and then we added 3 paragraphs + assertEquals("Line 1\nLine 2\nLine 3", shape.getText()); + } + + /** + * Test setting the text, then adding multiple paragraphs and retrieve text + */ + public void testSetAddMultipleParagraphs() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + + shape.setText("Line 1"); + + XSSFTextParagraph para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 2"); + + para = shape.addNewTextParagraph(); + para.addNewTextRun().setText("Line 3"); + + List paras = shape.getTextParagraphs(); + assertEquals(3, paras.size()); // this should be 3 as we overwrote the default paragraph with setText, then added 2 new paragraphs + assertEquals("Line 1\nLine 2\nLine 3", shape.getText()); + } + + /** + * Test reading text from a textbox in an existing file + */ + public void testReadTextBox(){ + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithDrawing.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); + //the sheet has one relationship and it is XSSFDrawing + List rels = sheet.getRelations(); + assertEquals(1, rels.size()); + assertTrue(rels.get(0) instanceof XSSFDrawing); + + XSSFDrawing drawing = (XSSFDrawing)rels.get(0); + //sheet.createDrawingPatriarch() should return the same instance of XSSFDrawing + assertSame(drawing, sheet.createDrawingPatriarch()); + String drawingId = drawing.getPackageRelationship().getId(); + + //there should be a relation to this drawing in the worksheet + assertTrue(sheet.getCTWorksheet().isSetDrawing()); + assertEquals(drawingId, sheet.getCTWorksheet().getDrawing().getId()); + + List shapes = drawing.getShapes(); + assertEquals(6, shapes.size()); + + assertTrue(shapes.get(4) instanceof XSSFSimpleShape); + + XSSFSimpleShape textbox = (XSSFSimpleShape) shapes.get(4); + assertEquals("Sheet with various pictures\n(jpeg, png, wmf, emf and pict)", textbox.getText()); + } + + + /** + * Test reading multiple paragraphs from a textbox in an existing file + */ + public void testReadTextBoxParagraphs(){ + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithTextBox.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); + //the sheet has one relationship and it is XSSFDrawing + List rels = sheet.getRelations(); + assertEquals(1, rels.size()); + + assertTrue(rels.get(0) instanceof XSSFDrawing); + + XSSFDrawing drawing = (XSSFDrawing)rels.get(0); + + //sheet.createDrawingPatriarch() should return the same instance of XSSFDrawing + assertSame(drawing, sheet.createDrawingPatriarch()); + String drawingId = drawing.getPackageRelationship().getId(); + + //there should be a relation to this drawing in the worksheet + assertTrue(sheet.getCTWorksheet().isSetDrawing()); + assertEquals(drawingId, sheet.getCTWorksheet().getDrawing().getId()); + + List shapes = drawing.getShapes(); + assertEquals(1, shapes.size()); + + assertTrue(shapes.get(0) instanceof XSSFSimpleShape); + + XSSFSimpleShape textbox = (XSSFSimpleShape) shapes.get(0); + + List paras = textbox.getTextParagraphs(); + assertEquals(3, paras.size()); + + assertEquals("Line 2", paras.get(1).getText()); // check content of second paragraph + + assertEquals("Line 1\nLine 2\nLine 3", textbox.getText()); // check content of entire textbox + + // check attributes of paragraphs + assertEquals(TextAlign.LEFT, paras.get(0).getTextAlign()); + assertEquals(TextAlign.CENTER, paras.get(1).getTextAlign()); + assertEquals(TextAlign.RIGHT, paras.get(2).getTextAlign()); + + Color clr = paras.get(0).getTextRuns().get(0).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 255, 0, 0 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + + clr = paras.get(1).getTextRuns().get(0).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 255, 0 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + + clr = paras.get(2).getTextRuns().get(0).getFontColor(); + assertTrue(Arrays.equals( + new int[] { 0, 0, 255 } , + new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() })); + } + /** + * Test adding and reading back paragraphs as bullet points + */ + public void testAddBulletParagraphs() { + + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 10, 20)); + + String paraString1 = "A normal paragraph"; + String paraString2 = "First bullet"; + String paraString3 = "Second bullet (level 1)"; + String paraString4 = "Third bullet"; + String paraString5 = "Another normal paragraph"; + String paraString6 = "First numbered bullet"; + String paraString7 = "Second bullet (level 1)"; + String paraString8 = "Third bullet (level 1)"; + String paraString9 = "Fourth bullet (level 1)"; + String paraString10 = "Fifth Bullet"; + + XSSFTextParagraph para = shape.addNewTextParagraph(paraString1); + para = shape.addNewTextParagraph(paraString2); + para.setBullet(true); + + para = shape.addNewTextParagraph(paraString3); + para.setBullet(true); + para.setLevel(1); + + para = shape.addNewTextParagraph(paraString4); + para.setBullet(true); + + para = shape.addNewTextParagraph(paraString5); + para = shape.addNewTextParagraph(paraString6); + para.setBullet(ListAutoNumber.ARABIC_PERIOD); + + para = shape.addNewTextParagraph(paraString7); + para.setBullet(ListAutoNumber.ARABIC_PERIOD, 3); + para.setLevel(1); + + para = shape.addNewTextParagraph(paraString8); + para.setBullet(ListAutoNumber.ARABIC_PERIOD, 3); + para.setLevel(1); + + para = shape.addNewTextParagraph(""); + para.setBullet(ListAutoNumber.ARABIC_PERIOD, 3); + para.setLevel(1); + + para = shape.addNewTextParagraph(paraString9); + para.setBullet(ListAutoNumber.ARABIC_PERIOD, 3); + para.setLevel(1); + + para = shape.addNewTextParagraph(paraString10); + para.setBullet(ListAutoNumber.ARABIC_PERIOD); + + // Save and re-load it + wb = XSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + + // Check + drawing = sheet.createDrawingPatriarch(); + + List shapes = drawing.getShapes(); + assertEquals(1, shapes.size()); + assertTrue(shapes.get(0) instanceof XSSFSimpleShape); + + XSSFSimpleShape sshape = (XSSFSimpleShape) shapes.get(0); + + List paras = sshape.getTextParagraphs(); + assertEquals(12, paras.size()); // this should be 12 as XSSFSimpleShape creates a default paragraph (no text), and then we added to that + + StringBuilder builder = new StringBuilder(); + + builder.append(paraString1); + builder.append("\n"); + builder.append("\u2022 "); + builder.append(paraString2); + builder.append("\n"); + builder.append("\t\u2022 "); + builder.append(paraString3); + builder.append("\n"); + builder.append("\u2022 "); + builder.append(paraString4); + builder.append("\n"); + builder.append(paraString5); + builder.append("\n"); + builder.append("1. "); + builder.append(paraString6); + builder.append("\n"); + builder.append("\t3. "); + builder.append(paraString7); + builder.append("\n"); + builder.append("\t4. "); + builder.append(paraString8); + builder.append("\n"); + builder.append("\t"); // should be empty + builder.append("\n"); + builder.append("\t5. "); + builder.append(paraString9); + builder.append("\n"); + builder.append("2. "); + builder.append(paraString10); + + assertEquals(builder.toString(), sshape.getText()); + } + + /** + * Test reading bullet numbering from a textbox in an existing file + */ + public void testReadTextBox2(){ + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithTextBox2.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + List shapes = drawing.getShapes(); + XSSFSimpleShape textbox = (XSSFSimpleShape) shapes.get(0); + String extracted = textbox.getText(); + StringBuilder sb = new StringBuilder(); + sb.append("1. content1A\n"); + sb.append("\t1. content1B\n"); + sb.append("\t2. content2B\n"); + sb.append("\t3. content3B\n"); + sb.append("2. content2A\n"); + sb.append("\t3. content2BStartAt3\n"); + sb.append("\t\n\t\n\t"); + sb.append("4. content2BStartAt3Incremented\n"); + sb.append("\t\n\t\n\t\n\t"); + + assertEquals(sb.toString(), extracted); + } } diff --git a/test-data/spreadsheet/WithTextBox.xlsx b/test-data/spreadsheet/WithTextBox.xlsx index b4adac4af7..375e9f11da 100644 Binary files a/test-data/spreadsheet/WithTextBox.xlsx and b/test-data/spreadsheet/WithTextBox.xlsx differ diff --git a/test-data/spreadsheet/WithTextBox2.xlsx b/test-data/spreadsheet/WithTextBox2.xlsx new file mode 100644 index 0000000000..af5bb652d9 Binary files /dev/null and b/test-data/spreadsheet/WithTextBox2.xlsx differ