| /* ==================================================================== |
| 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 static org.apache.poi.sl.usermodel.TextShape.TextPlaceholder.CENTER_TITLE; |
| import static org.apache.poi.sl.usermodel.TextShape.TextPlaceholder.TITLE; |
| |
| import java.awt.Graphics2D; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.ddf.AbstractEscherOptRecord; |
| import org.apache.poi.ddf.EscherContainerRecord; |
| import org.apache.poi.ddf.EscherPropertyTypes; |
| 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.Units; |
| |
| /** |
| * A common superclass of all shapes that can hold text. |
| */ |
| public abstract class HSLFTextShape extends HSLFSimpleShape |
| implements TextShape<HSLFShape,HSLFTextParagraph> { |
| private static final Logger LOG = LogManager.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<>(); |
| |
| /** |
| * 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); |
| } |
| } |
| |
| @Override |
| public Rectangle2D resizeToFitText() { |
| return resizeToFitText(null); |
| } |
| |
| @Override |
| public Rectangle2D resizeToFitText(Graphics2D graphics) { |
| Rectangle2D anchor = getAnchor(); |
| if(anchor.getWidth() == 0.) { |
| LOG.atWarn().log("Width of shape wasn't set. Defaulting to 200px"); |
| anchor.setRect(anchor.getX(), anchor.getY(), 200., anchor.getHeight()); |
| setAnchor(anchor); |
| } |
| double height = getTextHeight(graphics); |
| height += 1; // add a pixel to compensate rounding errors |
| |
| Insets2D insets = getInsets(); |
| anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), height+insets.top+insets.bottom); |
| setAnchor(anchor); |
| |
| return anchor; |
| } |
| |
| /** |
| * Returns the type of the text, from the TextHeaderAtom. |
| * Possible values can be seen from TextHeaderAtom |
| * @see TextHeaderAtom |
| */ |
| public int getRunType() { |
| getEscherTextboxWrapper(); |
| if (_txtbox == null) { |
| return -1; |
| } |
| List<HSLFTextParagraph> paras = HSLFTextParagraph.findTextParagraphs(_txtbox, getSheet()); |
| return (paras.isEmpty() || paras.get(0).getIndex() == -1) ? -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 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, EscherPropertyTypes.TEXT__ANCHORTEXT); |
| final HSLFTextAnchor align; |
| 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 != TextPlaceholder.OTHER.nativeId) { |
| align = masterShape.getAlignment(); |
| } else { |
| //not found in the master sheet. Use the hardcoded defaults. |
| align = (TextPlaceholder.isTitle(type)) ? HSLFTextAnchor.MIDDLE : HSLFTextAnchor.TOP; |
| } |
| } else { |
| align = HSLFTextAnchor.fromNativeId(prop.getPropertyValue()); |
| } |
| |
| return (align == null) ? HSLFTextAnchor.TOP : 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(EscherPropertyTypes.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(EscherPropertyTypes.TEXT__TEXTBOTTOM, .05); |
| } |
| |
| /** |
| * Sets the botom margin. |
| * @see #getBottomInset() |
| * |
| * @param margin the bottom margin |
| */ |
| public void setBottomInset(double margin){ |
| setInset(EscherPropertyTypes.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(EscherPropertyTypes.TEXT__TEXTLEFT, .1); |
| } |
| |
| /** |
| * Sets the left margin. |
| * @see #getLeftInset() |
| * |
| * @param margin the left margin |
| */ |
| public void setLeftInset(double margin){ |
| setInset(EscherPropertyTypes.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(EscherPropertyTypes.TEXT__TEXTRIGHT, .1); |
| } |
| |
| /** |
| * Sets the right margin. |
| * @see #getRightInset() |
| * |
| * @param margin the right margin |
| */ |
| public void setRightInset(double margin){ |
| setInset(EscherPropertyTypes.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(EscherPropertyTypes.TEXT__TEXTTOP, .05); |
| } |
| |
| /** |
| * Sets the top margin. |
| * @see #getTopInset() |
| * |
| * @param margin the top margin |
| */ |
| public void setTopInset(double margin){ |
| setInset(EscherPropertyTypes.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 type the type of the inset edge |
| * @return the inset in points |
| */ |
| private double getInset(EscherPropertyTypes type, double defaultInch) { |
| AbstractEscherOptRecord opt = getEscherOptRecord(); |
| EscherSimpleProperty prop = getEscherProperty(opt, type); |
| int val = prop == null ? (int)(Units.toEMU(Units.POINT_DPI)*defaultInch) : prop.getPropertyValue(); |
| return Units.toPoints(val); |
| } |
| |
| /** |
| * @param type the type of the inset edge |
| * @param margin the inset in points |
| */ |
| private void setInset(EscherPropertyTypes type, double margin){ |
| setEscherProperty(type, 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, EscherPropertyTypes.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(EscherPropertyTypes.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, EscherPropertyTypes.TEXT__TEXTID); |
| return prop == null ? 0 : prop.getPropertyValue(); |
| } |
| |
| /** |
| * Sets text ID |
| * |
| * @param id of the text |
| */ |
| public void setTextId(int id){ |
| setEscherProperty(EscherPropertyTypes.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.atWarn().log("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() { |
| return |
| ((getPlaceholderAtom() != null) || |
| //special case for files saved in Office 2007 |
| (getHFPlaceholderAtom() != null)); |
| } |
| |
| |
| @Override |
| public Iterator<HSLFTextParagraph> iterator() { |
| return _paragraphs.iterator(); |
| } |
| |
| @Override |
| public Insets2D getInsets() { |
| return new Insets2D(getTopInset(), getLeftInset(), getBottomInset(), getRightInset()); |
| } |
| |
| @Override |
| public void setInsets(Insets2D insets) { |
| setTopInset(insets.top); |
| setLeftInset(insets.left); |
| setBottomInset(insets.bottom); |
| setRightInset(insets.right); |
| } |
| |
| @Override |
| public double getTextHeight() { |
| return getTextHeight(null); |
| } |
| |
| @Override |
| public double getTextHeight(Graphics2D graphics) { |
| DrawFactory drawFact = DrawFactory.getInstance(graphics); |
| DrawTextShape dts = drawFact.getDrawable(this); |
| return dts.getTextHeight(graphics); |
| } |
| |
| @Override |
| public TextDirection getTextDirection() { |
| // see 2.4.5 MSOTXFL |
| AbstractEscherOptRecord opt = getEscherOptRecord(); |
| EscherSimpleProperty prop = getEscherProperty(opt, EscherPropertyTypes.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, EscherPropertyTypes.TEXT__TEXTFLOW, msotxfl); |
| } |
| |
| @Override |
| public Double getTextRotation() { |
| // see 2.4.6 MSOCDIR |
| AbstractEscherOptRecord opt = getEscherOptRecord(); |
| EscherSimpleProperty prop = getEscherProperty(opt, EscherPropertyTypes.TEXT__FONTROTATION); |
| return (prop == null) ? null : (90. * prop.getPropertyValue()); |
| } |
| |
| @Override |
| public void setTextRotation(Double rotation) { |
| AbstractEscherOptRecord opt = getEscherOptRecord(); |
| if (rotation == null) { |
| opt.removeEscherProperty(EscherPropertyTypes.TEXT__FONTROTATION); |
| } else { |
| int rot = (int)(Math.round(rotation / 90.) % 4L); |
| setEscherProperty(EscherPropertyTypes.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 = TextPlaceholder.BODY.nativeId; |
| ph = Placeholder.BODY; |
| break; |
| case TITLE: |
| runType = TITLE.nativeId; |
| ph = Placeholder.TITLE; |
| break; |
| case CENTER_BODY: |
| runType = TextPlaceholder.CENTER_BODY.nativeId; |
| ph = Placeholder.BODY; |
| break; |
| case CENTER_TITLE: |
| runType = CENTER_TITLE.nativeId; |
| ph = Placeholder.TITLE; |
| break; |
| case HALF_BODY: |
| runType = TextPlaceholder.HALF_BODY.nativeId; |
| ph = Placeholder.BODY; |
| break; |
| case QUARTER_BODY: |
| runType = TextPlaceholder.QUARTER_BODY.nativeId; |
| ph = Placeholder.BODY; |
| break; |
| case NOTES: |
| runType = TextPlaceholder.NOTES.nativeId; |
| break; |
| case OTHER: |
| runType = TextPlaceholder.OTHER.nativeId; |
| break; |
| } |
| setRunType(runType); |
| if (ph != null) { |
| setPlaceholder(ph); |
| } |
| } |
| |
| @Override |
| public TextPlaceholder getTextPlaceholder() { |
| return TextPlaceholder.fromNativeId(getRunType()); |
| } |
| |
| |
| /** |
| * 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<>(this); |
| return mbs.getShape(); |
| } |
| } |