934 lines
32 KiB
Java
934 lines
32 KiB
Java
/* ====================================================================
|
|
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.hslf.usermodel;
|
|
|
|
import static org.apache.poi.hslf.record.RecordTypes.OEPlaceholderAtom;
|
|
import static org.apache.poi.hslf.record.RecordTypes.RoundTripHFPlaceholder12;
|
|
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
import org.apache.poi.ddf.AbstractEscherOptRecord;
|
|
import org.apache.poi.ddf.EscherContainerRecord;
|
|
import org.apache.poi.ddf.EscherProperties;
|
|
import org.apache.poi.ddf.EscherSimpleProperty;
|
|
import org.apache.poi.ddf.EscherTextboxRecord;
|
|
import org.apache.poi.hslf.exceptions.HSLFException;
|
|
import org.apache.poi.hslf.model.HSLFMetroShape;
|
|
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
|
|
import org.apache.poi.hslf.record.EscherTextboxWrapper;
|
|
import org.apache.poi.hslf.record.OEPlaceholderAtom;
|
|
import org.apache.poi.hslf.record.PPDrawing;
|
|
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
|
|
import org.apache.poi.hslf.record.StyleTextPropAtom;
|
|
import org.apache.poi.hslf.record.TextBytesAtom;
|
|
import org.apache.poi.hslf.record.TextCharsAtom;
|
|
import org.apache.poi.hslf.record.TextHeaderAtom;
|
|
import org.apache.poi.sl.draw.DrawFactory;
|
|
import org.apache.poi.sl.draw.DrawTextShape;
|
|
import org.apache.poi.sl.usermodel.Insets2D;
|
|
import org.apache.poi.sl.usermodel.Placeholder;
|
|
import org.apache.poi.sl.usermodel.ShapeContainer;
|
|
import org.apache.poi.sl.usermodel.TextParagraph;
|
|
import org.apache.poi.sl.usermodel.TextRun;
|
|
import org.apache.poi.sl.usermodel.TextShape;
|
|
import org.apache.poi.sl.usermodel.VerticalAlignment;
|
|
import org.apache.poi.util.POILogFactory;
|
|
import org.apache.poi.util.POILogger;
|
|
import org.apache.poi.util.Units;
|
|
|
|
/**
|
|
* A common superclass of all shapes that can hold text.
|
|
*/
|
|
public abstract class HSLFTextShape extends HSLFSimpleShape
|
|
implements TextShape<HSLFShape,HSLFTextParagraph> {
|
|
private static final POILogger LOG = POILogFactory.getLogger(HSLFTextShape.class);
|
|
|
|
/**
|
|
* How to anchor the text
|
|
*/
|
|
private enum HSLFTextAnchor {
|
|
TOP (0, VerticalAlignment.TOP, false, false),
|
|
MIDDLE (1, VerticalAlignment.MIDDLE, false, false),
|
|
BOTTOM (2, VerticalAlignment.BOTTOM, false, false),
|
|
TOP_CENTER (3, VerticalAlignment.TOP, true, false),
|
|
MIDDLE_CENTER (4, VerticalAlignment.MIDDLE, true, false),
|
|
BOTTOM_CENTER (5, VerticalAlignment.BOTTOM, true, false),
|
|
TOP_BASELINE (6, VerticalAlignment.TOP, false, true),
|
|
BOTTOM_BASELINE (7, VerticalAlignment.BOTTOM, false, true),
|
|
TOP_CENTER_BASELINE (8, VerticalAlignment.TOP, true, true),
|
|
BOTTOM_CENTER_BASELINE(9, VerticalAlignment.BOTTOM, true, true);
|
|
|
|
public final int nativeId;
|
|
public final VerticalAlignment vAlign;
|
|
public final boolean centered;
|
|
public final Boolean baseline;
|
|
|
|
HSLFTextAnchor(int nativeId, VerticalAlignment vAlign, boolean centered, Boolean baseline) {
|
|
this.nativeId = nativeId;
|
|
this.vAlign = vAlign;
|
|
this.centered = centered;
|
|
this.baseline = baseline;
|
|
}
|
|
|
|
static HSLFTextAnchor fromNativeId(int nativeId) {
|
|
for (HSLFTextAnchor ta : values()) {
|
|
if (ta.nativeId == nativeId) {
|
|
return ta;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specifies that a line of text will continue on subsequent lines instead
|
|
* of extending into or beyond a margin.
|
|
* Office Excel 2007, Excel 2010, PowerPoint 97, and PowerPoint 2010 read
|
|
* and use this value properly but do not write it.
|
|
*/
|
|
public static final int WrapSquare = 0;
|
|
/**
|
|
* Specifies a wrapping rule that is equivalent to that of WrapSquare
|
|
* Excel 97, Excel 2000, Excel 2002, and Office Excel 2003 use this value.
|
|
* All other product versions listed at the beginning of this appendix ignore this value.
|
|
*/
|
|
public static final int WrapByPoints = 1;
|
|
/**
|
|
* Specifies that a line of text will extend into or beyond a margin instead
|
|
* of continuing on subsequent lines.
|
|
* Excel 97, Word 97, Excel 2000, Word 2000, Excel 2002,
|
|
* and Office Excel 2003 do not use this value.
|
|
*/
|
|
public static final int WrapNone = 2;
|
|
/**
|
|
* Specifies a wrapping rule that is undefined and MUST be ignored.
|
|
*/
|
|
public static final int WrapTopBottom = 3;
|
|
/**
|
|
* Specifies a wrapping rule that is undefined and MUST be ignored.
|
|
*/
|
|
public static final int WrapThrough = 4;
|
|
|
|
|
|
/**
|
|
* TextRun object which holds actual text and format data
|
|
*/
|
|
private List<HSLFTextParagraph> _paragraphs = new ArrayList<HSLFTextParagraph>();
|
|
|
|
/**
|
|
* Escher container which holds text attributes such as
|
|
* TextHeaderAtom, TextBytesAtom or TextCharsAtom, StyleTextPropAtom etc.
|
|
*/
|
|
private EscherTextboxWrapper _txtbox;
|
|
|
|
/**
|
|
* This setting is used for supporting a deprecated alignment
|
|
*
|
|
* @see <a href=""></a>
|
|
*/
|
|
// boolean alignToBaseline = false;
|
|
|
|
/**
|
|
* Create a TextBox object and initialize it from the supplied Record container.
|
|
*
|
|
* @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape
|
|
* @param parent the parent of the shape
|
|
*/
|
|
protected HSLFTextShape(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){
|
|
super(escherRecord, parent);
|
|
}
|
|
|
|
/**
|
|
* Create a new TextBox. This constructor is used when a new shape is created.
|
|
*
|
|
* @param parent the parent of this Shape. For example, if this text box is a cell
|
|
* in a table then the parent is Table.
|
|
*/
|
|
public HSLFTextShape(ShapeContainer<HSLFShape,HSLFTextParagraph> parent){
|
|
super(null, parent);
|
|
createSpContainer(parent instanceof HSLFGroupShape);
|
|
}
|
|
|
|
/**
|
|
* Create a new TextBox. This constructor is used when a new shape is created.
|
|
*
|
|
*/
|
|
public HSLFTextShape(){
|
|
this(null);
|
|
}
|
|
|
|
/**
|
|
* Set default properties for the TextRun.
|
|
* Depending on the text and shape type the defaults are different:
|
|
* TextBox: align=left, valign=top
|
|
* AutoShape: align=center, valign=middle
|
|
*
|
|
*/
|
|
protected void setDefaultTextProperties(HSLFTextParagraph _txtrun){
|
|
|
|
}
|
|
|
|
/**
|
|
* When a textbox is added to a sheet we need to tell upper-level
|
|
* <code>PPDrawing</code> about it.
|
|
*
|
|
* @param sh the sheet we are adding to
|
|
*/
|
|
@Override
|
|
protected void afterInsert(HSLFSheet sh){
|
|
super.afterInsert(sh);
|
|
|
|
storeText();
|
|
|
|
EscherTextboxWrapper thisTxtbox = getEscherTextboxWrapper();
|
|
if(thisTxtbox != null){
|
|
getSpContainer().addChildRecord(thisTxtbox.getEscherRecord());
|
|
|
|
PPDrawing ppdrawing = sh.getPPDrawing();
|
|
ppdrawing.addTextboxWrapper(thisTxtbox);
|
|
// Ensure the escher layer knows about the added records
|
|
try {
|
|
thisTxtbox.writeOut(null);
|
|
} catch (IOException e){
|
|
throw new HSLFException(e);
|
|
}
|
|
boolean isInitialAnchor = getAnchor().equals(new Rectangle2D.Double());
|
|
boolean isFilledTxt = !"".equals(getText());
|
|
if (isInitialAnchor && isFilledTxt) {
|
|
resizeToFitText();
|
|
}
|
|
}
|
|
for (HSLFTextParagraph htp : _paragraphs) {
|
|
htp.setShapeId(getShapeId());
|
|
}
|
|
sh.onAddTextShape(this);
|
|
}
|
|
|
|
protected EscherTextboxWrapper getEscherTextboxWrapper(){
|
|
if(_txtbox != null) {
|
|
return _txtbox;
|
|
}
|
|
|
|
EscherTextboxRecord textRecord = getEscherChild(EscherTextboxRecord.RECORD_ID);
|
|
if (textRecord == null) {
|
|
return null;
|
|
}
|
|
|
|
HSLFSheet sheet = getSheet();
|
|
if (sheet != null) {
|
|
PPDrawing drawing = sheet.getPPDrawing();
|
|
if (drawing != null) {
|
|
EscherTextboxWrapper wrappers[] = drawing.getTextboxWrappers();
|
|
if (wrappers != null) {
|
|
for (EscherTextboxWrapper w : wrappers) {
|
|
// check for object identity
|
|
if (textRecord == w.getEscherRecord()) {
|
|
_txtbox = w;
|
|
return _txtbox;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_txtbox = new EscherTextboxWrapper(textRecord);
|
|
return _txtbox;
|
|
}
|
|
|
|
private void createEmptyParagraph() {
|
|
TextHeaderAtom tha = (TextHeaderAtom)_txtbox.findFirstOfType(TextHeaderAtom._type);
|
|
if (tha == null) {
|
|
tha = new TextHeaderAtom();
|
|
tha.setParentRecord(_txtbox);
|
|
_txtbox.appendChildRecord(tha);
|
|
}
|
|
|
|
TextBytesAtom tba = (TextBytesAtom)_txtbox.findFirstOfType(TextBytesAtom._type);
|
|
TextCharsAtom tca = (TextCharsAtom)_txtbox.findFirstOfType(TextCharsAtom._type);
|
|
if (tba == null && tca == null) {
|
|
tba = new TextBytesAtom();
|
|
tba.setText(new byte[0]);
|
|
_txtbox.appendChildRecord(tba);
|
|
}
|
|
|
|
final String text = ((tba != null) ? tba.getText() : tca.getText());
|
|
|
|
StyleTextPropAtom sta = (StyleTextPropAtom)_txtbox.findFirstOfType(StyleTextPropAtom._type);
|
|
TextPropCollection paraStyle = null, charStyle = null;
|
|
if (sta == null) {
|
|
int parSiz = text.length();
|
|
sta = new StyleTextPropAtom(parSiz+1);
|
|
if (_paragraphs.isEmpty()) {
|
|
paraStyle = sta.addParagraphTextPropCollection(parSiz+1);
|
|
charStyle = sta.addCharacterTextPropCollection(parSiz+1);
|
|
} else {
|
|
for (HSLFTextParagraph htp : _paragraphs) {
|
|
int runsLen = 0;
|
|
for (HSLFTextRun htr : htp.getTextRuns()) {
|
|
runsLen += htr.getLength();
|
|
charStyle = sta.addCharacterTextPropCollection(htr.getLength());
|
|
htr.setCharacterStyle(charStyle);
|
|
}
|
|
paraStyle = sta.addParagraphTextPropCollection(runsLen);
|
|
htp.setParagraphStyle(paraStyle);
|
|
}
|
|
assert (paraStyle != null && charStyle != null);
|
|
}
|
|
_txtbox.appendChildRecord(sta);
|
|
} else {
|
|
paraStyle = sta.getParagraphStyles().get(0);
|
|
charStyle = sta.getCharacterStyles().get(0);
|
|
}
|
|
|
|
if (_paragraphs.isEmpty()) {
|
|
HSLFTextParagraph htp = new HSLFTextParagraph(tha, tba, tca, _paragraphs);
|
|
htp.setParagraphStyle(paraStyle);
|
|
htp.setParentShape(this);
|
|
_paragraphs.add(htp);
|
|
|
|
HSLFTextRun htr = new HSLFTextRun(htp);
|
|
htr.setCharacterStyle(charStyle);
|
|
htr.setText(text);
|
|
htp.addTextRun(htr);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Adjust the size of the shape so it encompasses the text inside it.
|
|
*
|
|
* @return a <code>Rectangle2D</code> that is the bounds of this shape.
|
|
*/
|
|
public Rectangle2D resizeToFitText(){
|
|
Rectangle2D anchor = getAnchor();
|
|
if(anchor.getWidth() == 0.) {
|
|
LOG.log(POILogger.WARN, "Width of shape wasn't set. Defaulting to 200px");
|
|
anchor.setRect(anchor.getX(), anchor.getY(), 200., anchor.getHeight());
|
|
setAnchor(anchor);
|
|
}
|
|
double height = getTextHeight();
|
|
height += 1; // add a pixel to compensate rounding errors
|
|
|
|
anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height);
|
|
setAnchor(anchor);
|
|
|
|
return anchor;
|
|
}
|
|
|
|
/**
|
|
* Returns the type of the text, from the TextHeaderAtom.
|
|
* Possible values can be seen from TextHeaderAtom
|
|
* @see org.apache.poi.hslf.record.TextHeaderAtom
|
|
*/
|
|
public int getRunType() {
|
|
getEscherTextboxWrapper();
|
|
if (_txtbox == null) {
|
|
return -1;
|
|
}
|
|
List<HSLFTextParagraph> paras = HSLFTextParagraph.findTextParagraphs(_txtbox, getSheet());
|
|
return (paras.isEmpty()) ? -1 : paras.get(0).getRunType();
|
|
}
|
|
|
|
/**
|
|
* Changes the type of the text. Values should be taken
|
|
* from TextHeaderAtom. No checking is done to ensure you
|
|
* set this to a valid value!
|
|
* @see org.apache.poi.hslf.record.TextHeaderAtom
|
|
*/
|
|
public void setRunType(int type) {
|
|
getEscherTextboxWrapper();
|
|
if (_txtbox == null) {
|
|
return;
|
|
}
|
|
List<HSLFTextParagraph> paras = HSLFTextParagraph.findTextParagraphs(_txtbox, getSheet());
|
|
if (!paras.isEmpty()) {
|
|
paras.get(0).setRunType(type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the type of vertical alignment for the text.
|
|
* One of the <code>Anchor*</code> constants defined in this class.
|
|
*
|
|
* @return the type of alignment
|
|
*/
|
|
/* package */ HSLFTextAnchor getAlignment(){
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT);
|
|
HSLFTextAnchor align = HSLFTextAnchor.TOP;
|
|
if (prop == null){
|
|
/**
|
|
* If vertical alignment was not found in the shape properties then try to
|
|
* fetch the master shape and search for the align property there.
|
|
*/
|
|
int type = getRunType();
|
|
HSLFSheet sh = getSheet();
|
|
HSLFMasterSheet master = (sh != null) ? sh.getMasterSheet() : null;
|
|
HSLFTextShape masterShape = (master != null) ? master.getPlaceholderByTextType(type) : null;
|
|
if (masterShape != null && type != TextHeaderAtom.OTHER_TYPE) {
|
|
align = masterShape.getAlignment();
|
|
} else {
|
|
//not found in the master sheet. Use the hardcoded defaults.
|
|
switch (type){
|
|
case TextHeaderAtom.TITLE_TYPE:
|
|
case TextHeaderAtom.CENTER_TITLE_TYPE:
|
|
align = HSLFTextAnchor.MIDDLE;
|
|
break;
|
|
default:
|
|
align = HSLFTextAnchor.TOP;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
align = HSLFTextAnchor.fromNativeId(prop.getPropertyValue());
|
|
}
|
|
|
|
if (align == null) {
|
|
align = HSLFTextAnchor.TOP;
|
|
}
|
|
|
|
return align;
|
|
}
|
|
|
|
/**
|
|
* Sets the type of alignment for the text.
|
|
* One of the <code>Anchor*</code> constants defined in this class.
|
|
*
|
|
* @param isCentered horizontal centered?
|
|
* @param vAlign vertical alignment
|
|
* @param baseline aligned to baseline?
|
|
*/
|
|
/* package */ void setAlignment(Boolean isCentered, VerticalAlignment vAlign, boolean baseline) {
|
|
for (HSLFTextAnchor hta : HSLFTextAnchor.values()) {
|
|
if (
|
|
(hta.centered == (isCentered != null && isCentered)) &&
|
|
(hta.vAlign == vAlign) &&
|
|
(hta.baseline == null || hta.baseline == baseline)
|
|
) {
|
|
setEscherProperty(EscherProperties.TEXT__ANCHORTEXT, hta.nativeId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true, if vertical alignment is relative to baseline
|
|
* this is only used for older versions less equals Office 2003
|
|
*/
|
|
public boolean isAlignToBaseline() {
|
|
return getAlignment().baseline;
|
|
}
|
|
|
|
/**
|
|
* Sets the vertical alignment relative to the baseline
|
|
*
|
|
* @param alignToBaseline if true, vertical alignment is relative to baseline
|
|
*/
|
|
public void setAlignToBaseline(boolean alignToBaseline) {
|
|
setAlignment(isHorizontalCentered(), getVerticalAlignment(), alignToBaseline);
|
|
}
|
|
|
|
@Override
|
|
public boolean isHorizontalCentered() {
|
|
return getAlignment().centered;
|
|
}
|
|
|
|
@Override
|
|
public void setHorizontalCentered(Boolean isCentered) {
|
|
setAlignment(isCentered, getVerticalAlignment(), getAlignment().baseline);
|
|
}
|
|
|
|
@Override
|
|
public VerticalAlignment getVerticalAlignment() {
|
|
return getAlignment().vAlign;
|
|
}
|
|
|
|
@Override
|
|
public void setVerticalAlignment(VerticalAlignment vAlign) {
|
|
setAlignment(isHorizontalCentered(), vAlign, getAlignment().baseline);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Default value is 1/20 inch.
|
|
*
|
|
* @return the botom margin
|
|
*/
|
|
public double getBottomInset(){
|
|
return getInset(EscherProperties.TEXT__TEXTBOTTOM, .05);
|
|
}
|
|
|
|
/**
|
|
* Sets the botom margin.
|
|
* @see #getBottomInset()
|
|
*
|
|
* @param margin the bottom margin
|
|
*/
|
|
public void setBottomInset(double margin){
|
|
setInset(EscherProperties.TEXT__TEXTBOTTOM, margin);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Default value is 1/10 inch.
|
|
*
|
|
* @return the left margin
|
|
*/
|
|
public double getLeftInset(){
|
|
return getInset(EscherProperties.TEXT__TEXTLEFT, .1);
|
|
}
|
|
|
|
/**
|
|
* Sets the left margin.
|
|
* @see #getLeftInset()
|
|
*
|
|
* @param margin the left margin
|
|
*/
|
|
public void setLeftInset(double margin){
|
|
setInset(EscherProperties.TEXT__TEXTLEFT, margin);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Default value is 1/10 inch.
|
|
*
|
|
* @return the right margin
|
|
*/
|
|
public double getRightInset(){
|
|
return getInset(EscherProperties.TEXT__TEXTRIGHT, .1);
|
|
}
|
|
|
|
/**
|
|
* Sets the right margin.
|
|
* @see #getRightInset()
|
|
*
|
|
* @param margin the right margin
|
|
*/
|
|
public void setRightInset(double margin){
|
|
setInset(EscherProperties.TEXT__TEXTRIGHT, margin);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Default value is 1/20 inch.
|
|
*
|
|
* @return the top margin
|
|
*/
|
|
public double getTopInset(){
|
|
return getInset(EscherProperties.TEXT__TEXTTOP, .05);
|
|
}
|
|
|
|
/**
|
|
* Sets the top margin.
|
|
* @see #getTopInset()
|
|
*
|
|
* @param margin the top margin
|
|
*/
|
|
public void setTopInset(double margin){
|
|
setInset(EscherProperties.TEXT__TEXTTOP, margin);
|
|
}
|
|
|
|
/**
|
|
* Returns the distance (in points) between the edge of the text frame
|
|
* and the edge of the inscribed rectangle of the shape that contains the text.
|
|
* Default value is 1/20 inch.
|
|
*
|
|
* @param propId the id of the inset edge
|
|
* @return the inset in points
|
|
*/
|
|
private double getInset(short propId, double defaultInch) {
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, propId);
|
|
int val = prop == null ? (int)(Units.toEMU(Units.POINT_DPI)*defaultInch) : prop.getPropertyValue();
|
|
return Units.toPoints(val);
|
|
}
|
|
|
|
/**
|
|
* @param propId the id of the inset edge
|
|
* @param margin the inset in points
|
|
*/
|
|
private void setInset(short propId, double margin){
|
|
setEscherProperty(propId, Units.toEMU(margin));
|
|
}
|
|
|
|
/**
|
|
* Returns the value indicating word wrap.
|
|
*
|
|
* @return the value indicating word wrap.
|
|
* Must be one of the <code>Wrap*</code> constants defined in this class.
|
|
*
|
|
* @see <a href="https://msdn.microsoft.com/en-us/library/dd948168(v=office.12).aspx">MSOWRAPMODE</a>
|
|
*/
|
|
public int getWordWrapEx() {
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT);
|
|
return prop == null ? WrapSquare : prop.getPropertyValue();
|
|
}
|
|
|
|
/**
|
|
* Specifies how the text should be wrapped
|
|
*
|
|
* @param wrap the value indicating how the text should be wrapped.
|
|
* Must be one of the <code>Wrap*</code> constants defined in this class.
|
|
*/
|
|
public void setWordWrapEx(int wrap){
|
|
setEscherProperty(EscherProperties.TEXT__WRAPTEXT, wrap);
|
|
}
|
|
|
|
@Override
|
|
public boolean getWordWrap(){
|
|
int ww = getWordWrapEx();
|
|
return (ww != WrapNone);
|
|
}
|
|
|
|
@Override
|
|
public void setWordWrap(boolean wrap) {
|
|
setWordWrapEx(wrap ? WrapSquare : WrapNone);
|
|
}
|
|
|
|
/**
|
|
* @return id for the text.
|
|
*/
|
|
public int getTextId(){
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__TEXTID);
|
|
return prop == null ? 0 : prop.getPropertyValue();
|
|
}
|
|
|
|
/**
|
|
* Sets text ID
|
|
*
|
|
* @param id of the text
|
|
*/
|
|
public void setTextId(int id){
|
|
setEscherProperty(EscherProperties.TEXT__TEXTID, id);
|
|
}
|
|
|
|
@Override
|
|
public List<HSLFTextParagraph> getTextParagraphs(){
|
|
if (!_paragraphs.isEmpty()) {
|
|
return _paragraphs;
|
|
}
|
|
|
|
_txtbox = getEscherTextboxWrapper();
|
|
if (_txtbox == null) {
|
|
_txtbox = new EscherTextboxWrapper();
|
|
createEmptyParagraph();
|
|
} else {
|
|
List<HSLFTextParagraph> pList = HSLFTextParagraph.findTextParagraphs(_txtbox, getSheet());
|
|
if (pList == null) {
|
|
// there are actually TextBoxRecords without extra data - see #54722
|
|
createEmptyParagraph();
|
|
} else {
|
|
_paragraphs = pList;
|
|
}
|
|
|
|
if (_paragraphs.isEmpty()) {
|
|
LOG.log(POILogger.WARN, "TextRecord didn't contained any text lines");
|
|
}
|
|
}
|
|
|
|
for (HSLFTextParagraph p : _paragraphs) {
|
|
p.setParentShape(this);
|
|
}
|
|
|
|
return _paragraphs;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void setSheet(HSLFSheet sheet) {
|
|
super.setSheet(sheet);
|
|
|
|
// Initialize _txtrun object.
|
|
// (We can't do it in the constructor because the sheet
|
|
// is not assigned then, it's only built once we have
|
|
// all the records)
|
|
List<HSLFTextParagraph> ltp = getTextParagraphs();
|
|
HSLFTextParagraph.supplySheet(ltp, sheet);
|
|
}
|
|
|
|
/**
|
|
* Return {@link OEPlaceholderAtom}, the atom that describes a placeholder.
|
|
*
|
|
* @return {@link OEPlaceholderAtom} or {@code null} if not found
|
|
*/
|
|
public OEPlaceholderAtom getPlaceholderAtom(){
|
|
return getClientDataRecord(OEPlaceholderAtom.typeID);
|
|
}
|
|
|
|
/**
|
|
* Return {@link RoundTripHFPlaceholder12}, the atom that describes a header/footer placeholder.
|
|
* Compare the {@link RoundTripHFPlaceholder12#getPlaceholderId()} with
|
|
* {@link Placeholder#HEADER} or {@link Placeholder#FOOTER}, to find out
|
|
* what kind of placeholder this is.
|
|
*
|
|
* @return {@link RoundTripHFPlaceholder12} or {@code null} if not found
|
|
*
|
|
* @since POI 3.14-Beta2
|
|
*/
|
|
public RoundTripHFPlaceholder12 getHFPlaceholderAtom() {
|
|
// special case for files saved in Office 2007
|
|
return getClientDataRecord(RoundTripHFPlaceholder12.typeID);
|
|
}
|
|
|
|
@Override
|
|
public boolean isPlaceholder() {
|
|
OEPlaceholderAtom oep = getPlaceholderAtom();
|
|
if (oep != null) {
|
|
return true;
|
|
}
|
|
|
|
//special case for files saved in Office 2007
|
|
RoundTripHFPlaceholder12 hldr = getHFPlaceholderAtom();
|
|
if (hldr != null) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
@Override
|
|
public Iterator<HSLFTextParagraph> iterator() {
|
|
return _paragraphs.iterator();
|
|
}
|
|
|
|
@Override
|
|
public Insets2D getInsets() {
|
|
Insets2D insets = new Insets2D(getTopInset(), getLeftInset(), getBottomInset(), getRightInset());
|
|
return insets;
|
|
}
|
|
|
|
@Override
|
|
public void setInsets(Insets2D insets) {
|
|
setTopInset(insets.top);
|
|
setLeftInset(insets.left);
|
|
setBottomInset(insets.bottom);
|
|
setRightInset(insets.right);
|
|
}
|
|
|
|
@Override
|
|
public double getTextHeight(){
|
|
DrawFactory drawFact = DrawFactory.getInstance(null);
|
|
DrawTextShape dts = drawFact.getDrawable(this);
|
|
return dts.getTextHeight();
|
|
}
|
|
|
|
@Override
|
|
public TextDirection getTextDirection() {
|
|
// see 2.4.5 MSOTXFL
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__TEXTFLOW);
|
|
int msotxfl = (prop == null) ? 0 : prop.getPropertyValue();
|
|
switch (msotxfl) {
|
|
default:
|
|
case 0: // msotxflHorzN
|
|
case 4: // msotxflHorzA
|
|
return TextDirection.HORIZONTAL;
|
|
case 1: // msotxflTtoBA
|
|
case 3: // msotxflTtoBN
|
|
case 5: // msotxflVertN
|
|
return TextDirection.VERTICAL;
|
|
case 2: // msotxflBtoT
|
|
return TextDirection.VERTICAL_270;
|
|
// TextDirection.STACKED is not supported
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setTextDirection(TextDirection orientation) {
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
int msotxfl;
|
|
if (orientation == null) {
|
|
msotxfl = -1;
|
|
} else {
|
|
switch (orientation) {
|
|
default:
|
|
case STACKED:
|
|
// not supported -> remove
|
|
msotxfl = -1;
|
|
break;
|
|
case HORIZONTAL:
|
|
msotxfl = 0;
|
|
break;
|
|
case VERTICAL:
|
|
msotxfl = 1;
|
|
break;
|
|
case VERTICAL_270:
|
|
// always interpreted as horizontal
|
|
msotxfl = 2;
|
|
break;
|
|
}
|
|
}
|
|
setEscherProperty(opt, EscherProperties.TEXT__TEXTFLOW, msotxfl);
|
|
}
|
|
|
|
@Override
|
|
public Double getTextRotation() {
|
|
// see 2.4.6 MSOCDIR
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherSimpleProperty prop = getEscherProperty(opt, EscherProperties.TEXT__FONTROTATION);
|
|
return (prop == null) ? null : (90. * prop.getPropertyValue());
|
|
}
|
|
|
|
@Override
|
|
public void setTextRotation(Double rotation) {
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
if (rotation == null) {
|
|
opt.removeEscherProperty(EscherProperties.TEXT__FONTROTATION);
|
|
} else {
|
|
int rot = (int)(Math.round(rotation / 90.) % 4L);
|
|
setEscherProperty(EscherProperties.TEXT__FONTROTATION, rot);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the raw text content of the shape. This hasn't had any
|
|
* changes applied to it, and so is probably unlikely to print
|
|
* out nicely.
|
|
*/
|
|
public String getRawText() {
|
|
return HSLFTextParagraph.getRawText(getTextParagraphs());
|
|
}
|
|
|
|
@Override
|
|
public String getText() {
|
|
String rawText = getRawText();
|
|
return HSLFTextParagraph.toExternalString(rawText, getRunType());
|
|
}
|
|
|
|
@Override
|
|
public HSLFTextRun appendText(String text, boolean newParagraph) {
|
|
// init paragraphs
|
|
List<HSLFTextParagraph> paras = getTextParagraphs();
|
|
HSLFTextRun htr = HSLFTextParagraph.appendText(paras, text, newParagraph);
|
|
setTextId(getRawText().hashCode());
|
|
return htr;
|
|
}
|
|
|
|
@Override
|
|
public HSLFTextRun setText(String text) {
|
|
// init paragraphs
|
|
List<HSLFTextParagraph> paras = getTextParagraphs();
|
|
HSLFTextRun htr = HSLFTextParagraph.setText(paras, text);
|
|
setTextId(getRawText().hashCode());
|
|
return htr;
|
|
}
|
|
|
|
/**
|
|
* Saves the modified paragraphs/textrun to the records.
|
|
* Also updates the styles to the correct text length.
|
|
*/
|
|
protected void storeText() {
|
|
List<HSLFTextParagraph> paras = getTextParagraphs();
|
|
HSLFTextParagraph.storeText(paras);
|
|
}
|
|
|
|
/**
|
|
* Returns the array of all hyperlinks in this text run
|
|
*
|
|
* @return the array of all hyperlinks in this text run or <code>null</code>
|
|
* if not found.
|
|
*/
|
|
public List<HSLFHyperlink> getHyperlinks() {
|
|
return HSLFHyperlink.find(this);
|
|
}
|
|
|
|
@Override
|
|
public void setTextPlaceholder(TextPlaceholder placeholder) {
|
|
// TOOD: check for correct placeholder handling - see org.apache.poi.hslf.model.Placeholder
|
|
Placeholder ph = null;
|
|
int runType;
|
|
switch (placeholder) {
|
|
default:
|
|
case BODY:
|
|
runType = TextHeaderAtom.BODY_TYPE;
|
|
ph = Placeholder.BODY;
|
|
break;
|
|
case TITLE:
|
|
runType = TextHeaderAtom.TITLE_TYPE;
|
|
ph = Placeholder.TITLE;
|
|
break;
|
|
case CENTER_BODY:
|
|
runType = TextHeaderAtom.CENTRE_BODY_TYPE;
|
|
ph = Placeholder.BODY;
|
|
break;
|
|
case CENTER_TITLE:
|
|
runType = TextHeaderAtom.CENTER_TITLE_TYPE;
|
|
ph = Placeholder.TITLE;
|
|
break;
|
|
case HALF_BODY:
|
|
runType = TextHeaderAtom.HALF_BODY_TYPE;
|
|
ph = Placeholder.BODY;
|
|
break;
|
|
case QUARTER_BODY:
|
|
runType = TextHeaderAtom.QUARTER_BODY_TYPE;
|
|
ph = Placeholder.BODY;
|
|
break;
|
|
case NOTES:
|
|
runType = TextHeaderAtom.NOTES_TYPE;
|
|
break;
|
|
case OTHER:
|
|
runType = TextHeaderAtom.OTHER_TYPE;
|
|
break;
|
|
}
|
|
setRunType(runType);
|
|
if (ph != null) {
|
|
setPlaceholder(ph);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public TextPlaceholder getTextPlaceholder() {
|
|
switch (getRunType()) {
|
|
default:
|
|
case TextHeaderAtom.BODY_TYPE: return TextPlaceholder.BODY;
|
|
case TextHeaderAtom.TITLE_TYPE: return TextPlaceholder.TITLE;
|
|
case TextHeaderAtom.NOTES_TYPE: return TextPlaceholder.NOTES;
|
|
case TextHeaderAtom.OTHER_TYPE: return TextPlaceholder.OTHER;
|
|
case TextHeaderAtom.CENTRE_BODY_TYPE: return TextPlaceholder.CENTER_BODY;
|
|
case TextHeaderAtom.CENTER_TITLE_TYPE: return TextPlaceholder.CENTER_TITLE;
|
|
case TextHeaderAtom.HALF_BODY_TYPE: return TextPlaceholder.HALF_BODY;
|
|
case TextHeaderAtom.QUARTER_BODY_TYPE: return TextPlaceholder.QUARTER_BODY;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get alternative representation of text shape stored as metro blob escher property.
|
|
* The returned shape is the first shape in stored group shape of the metro blob
|
|
*
|
|
* @return null, if there's no alternative representation, otherwise the text shape
|
|
*/
|
|
public TextShape<?,? extends TextParagraph<?,?,? extends TextRun>> getMetroShape() {
|
|
HSLFMetroShape<TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>> mbs = new HSLFMetroShape<TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>>(this);
|
|
return mbs.getShape();
|
|
}
|
|
} |