/* ==================================================================== | |
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.OutlineTextRefAtom; | |
import java.awt.Color; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import org.apache.poi.hslf.exceptions.HSLFException; | |
import org.apache.poi.hslf.model.PPFont; | |
import org.apache.poi.hslf.model.textproperties.BitMaskTextProp; | |
import org.apache.poi.hslf.model.textproperties.FontAlignmentProp; | |
import org.apache.poi.hslf.model.textproperties.IndentProp; | |
import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp; | |
import org.apache.poi.hslf.model.textproperties.TextAlignmentProp; | |
import org.apache.poi.hslf.model.textproperties.TextPFException9; | |
import org.apache.poi.hslf.model.textproperties.TextProp; | |
import org.apache.poi.hslf.model.textproperties.TextPropCollection; | |
import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType; | |
import org.apache.poi.hslf.record.*; | |
import org.apache.poi.sl.draw.DrawPaint; | |
import org.apache.poi.sl.usermodel.AutoNumberingScheme; | |
import org.apache.poi.sl.usermodel.PaintStyle; | |
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; | |
import org.apache.poi.sl.usermodel.TextParagraph; | |
import org.apache.poi.util.Internal; | |
import org.apache.poi.util.LocaleUtil; | |
import org.apache.poi.util.POILogFactory; | |
import org.apache.poi.util.POILogger; | |
import org.apache.poi.util.StringUtil; | |
import org.apache.poi.util.Units; | |
/** | |
* This class represents a run of text in a powerpoint document. That | |
* run could be text on a sheet, or text in a note. | |
* It is only a very basic class for now | |
* | |
* @author Nick Burch | |
*/ | |
public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFTextParagraph,HSLFTextRun> { | |
protected static final POILogger logger = POILogFactory.getLogger(HSLFTextParagraph.class); | |
/** | |
* How to align the text | |
*/ | |
/* package */static final int AlignLeft = 0; | |
/* package */static final int AlignCenter = 1; | |
/* package */static final int AlignRight = 2; | |
/* package */static final int AlignJustify = 3; | |
// Note: These fields are protected to help with unit testing | |
// Other classes shouldn't really go playing with them! | |
private final TextHeaderAtom _headerAtom; | |
private TextBytesAtom _byteAtom; | |
private TextCharsAtom _charAtom; | |
private TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph); | |
protected TextRulerAtom _ruler; | |
protected final List<HSLFTextRun> _runs = new ArrayList<HSLFTextRun>(); | |
protected HSLFTextShape _parentShape; | |
private HSLFSheet _sheet; | |
private int shapeId; | |
private StyleTextProp9Atom styleTextProp9Atom; | |
private boolean _dirty = false; | |
private final List<HSLFTextParagraph> parentList; | |
/** | |
* Constructs a Text Run from a Unicode text block. | |
* Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided. | |
* | |
* @param tha the TextHeaderAtom that defines what's what | |
* @param tba the TextBytesAtom containing the text or null if {@link TextCharsAtom} is provided | |
* @param tca the TextCharsAtom containing the text or null if {@link TextBytesAtom} is provided | |
* @param parentList the list which contains this paragraph | |
*/ | |
/* package */ HSLFTextParagraph( | |
TextHeaderAtom tha, | |
TextBytesAtom tba, | |
TextCharsAtom tca, | |
List<HSLFTextParagraph> parentList | |
) { | |
if (tha == null) { | |
throw new IllegalArgumentException("TextHeaderAtom must be set."); | |
} | |
_headerAtom = tha; | |
_byteAtom = tba; | |
_charAtom = tca; | |
this.parentList = parentList; | |
} | |
/* package */HSLFTextParagraph(HSLFTextParagraph other) { | |
_headerAtom = other._headerAtom; | |
_byteAtom = other._byteAtom; | |
_charAtom = other._charAtom; | |
_parentShape = other._parentShape; | |
_sheet = other._sheet; | |
_ruler = other._ruler; | |
shapeId = other.shapeId; | |
_paragraphStyle.copy(other._paragraphStyle); | |
parentList = other.parentList; | |
} | |
public void addTextRun(HSLFTextRun run) { | |
_runs.add(run); | |
} | |
@Override | |
public List<HSLFTextRun> getTextRuns() { | |
return _runs; | |
} | |
public TextPropCollection getParagraphStyle() { | |
return _paragraphStyle; | |
} | |
public void setParagraphStyle(TextPropCollection paragraphStyle) { | |
_paragraphStyle.copy(paragraphStyle); | |
} | |
/** | |
* Setting a master style reference | |
* | |
* @param paragraphStyle the master style reference | |
* | |
* @since POI 3.14-Beta1 | |
*/ | |
@Internal | |
/* package */ void setMasterStyleReference(TextPropCollection paragraphStyle) { | |
_paragraphStyle = paragraphStyle; | |
} | |
/** | |
* Supply the Sheet we belong to, which might have an assigned SlideShow | |
* Also passes it on to our child RichTextRuns | |
*/ | |
public static void supplySheet(List<HSLFTextParagraph> paragraphs, HSLFSheet sheet) { | |
if (paragraphs == null) { | |
return; | |
} | |
for (HSLFTextParagraph p : paragraphs) { | |
p.supplySheet(sheet); | |
} | |
assert(sheet.getSlideShow() != null); | |
} | |
/** | |
* Supply the Sheet we belong to, which might have an assigned SlideShow | |
* Also passes it on to our child RichTextRuns | |
*/ | |
private void supplySheet(HSLFSheet sheet) { | |
this._sheet = sheet; | |
if (_runs == null) { | |
return; | |
} | |
for (HSLFTextRun rt : _runs) { | |
rt.updateSheet(); | |
} | |
} | |
public HSLFSheet getSheet() { | |
return this._sheet; | |
} | |
/** | |
* @return Shape ID | |
*/ | |
protected int getShapeId() { | |
return shapeId; | |
} | |
/** | |
* @param id Shape ID | |
*/ | |
protected void setShapeId(int id) { | |
shapeId = id; | |
} | |
/** | |
* @return 0-based index of the text run in the SLWT container | |
*/ | |
protected int getIndex() { | |
return (_headerAtom != null) ? _headerAtom.getIndex() : -1; | |
} | |
/** | |
* Sets the index of the paragraph in the SLWT container | |
* | |
* @param index | |
*/ | |
protected void setIndex(int index) { | |
if (_headerAtom != null) { | |
_headerAtom.setIndex(index); | |
} | |
} | |
/** | |
* 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() { | |
return (_headerAtom != null) ? _headerAtom.getTextType() : -1; | |
} | |
public void setRunType(int runType) { | |
if (_headerAtom != null) { | |
_headerAtom.setTextType(runType); | |
} | |
} | |
/** | |
* Is this Text Run one from a {@link PPDrawing}, or is it | |
* one from the {@link SlideListWithText}? | |
*/ | |
public boolean isDrawingBased() { | |
return (getIndex() == -1); | |
} | |
public TextRulerAtom getTextRuler() { | |
return _ruler; | |
} | |
public TextRulerAtom createTextRuler() { | |
_ruler = getTextRuler(); | |
if (_ruler == null) { | |
_ruler = TextRulerAtom.getParagraphInstance(); | |
Record childAfter = _byteAtom; | |
if (childAfter == null) { | |
childAfter = _charAtom; | |
} | |
if (childAfter == null) { | |
childAfter = _headerAtom; | |
} | |
_headerAtom.getParentRecord().addChildAfter(_ruler, childAfter); | |
} | |
return _ruler; | |
} | |
/** | |
* Returns records that make up the list of text paragraphs | |
* (there can be misc InteractiveInfo, TxInteractiveInfo and other records) | |
* | |
* @return text run records | |
*/ | |
public Record[] getRecords() { | |
Record r[] = _headerAtom.getParentRecord().getChildRecords(); | |
return getRecords(r, new int[] { 0 }, _headerAtom); | |
} | |
private static Record[] getRecords(Record[] records, int[] startIdx, TextHeaderAtom headerAtom) { | |
if (records == null) { | |
throw new NullPointerException("records need to be set."); | |
} | |
for (; startIdx[0] < records.length; startIdx[0]++) { | |
Record r = records[startIdx[0]]; | |
if (r instanceof TextHeaderAtom && (headerAtom == null || r == headerAtom)) { | |
break; | |
} | |
} | |
if (startIdx[0] >= records.length) { | |
logger.log(POILogger.INFO, "header atom wasn't found - container might contain only an OutlineTextRefAtom"); | |
return new Record[0]; | |
} | |
int length; | |
for (length = 1; startIdx[0] + length < records.length; length++) { | |
Record r = records[startIdx[0]+length]; | |
if (r instanceof TextHeaderAtom || r instanceof SlidePersistAtom) { | |
break; | |
} | |
} | |
Record result[] = new Record[length]; | |
System.arraycopy(records, startIdx[0], result, 0, length); | |
startIdx[0] += length; | |
return result; | |
} | |
/** Numbered List info */ | |
public void setStyleTextProp9Atom(final StyleTextProp9Atom styleTextProp9Atom) { | |
this.styleTextProp9Atom = styleTextProp9Atom; | |
} | |
/** Numbered List info */ | |
public StyleTextProp9Atom getStyleTextProp9Atom() { | |
return this.styleTextProp9Atom; | |
} | |
@Override | |
public Iterator<HSLFTextRun> iterator() { | |
return _runs.iterator(); | |
} | |
@Override | |
public Double getLeftMargin() { | |
TextProp val = getPropVal(_paragraphStyle, "text.offset", this); | |
return (val == null) ? null : Units.masterToPoints(val.getValue()); | |
} | |
@Override | |
public void setLeftMargin(Double leftMargin) { | |
Integer val = (leftMargin == null) ? null : Units.pointsToMaster(leftMargin); | |
setParagraphTextPropVal("text.offset", val); | |
} | |
@Override | |
public Double getRightMargin() { | |
// TODO: find out, how to determine this value | |
return null; | |
} | |
@Override | |
public void setRightMargin(Double rightMargin) { | |
// TODO: find out, how to set this value | |
} | |
@Override | |
public Double getIndent() { | |
TextProp val = getPropVal(_paragraphStyle, "bullet.offset", this); | |
return (val == null) ? null : Units.masterToPoints(val.getValue()); | |
} | |
@Override | |
public void setIndent(Double indent) { | |
Integer val = (indent == null) ? null : Units.pointsToMaster(indent); | |
setParagraphTextPropVal("bullet.offset", val); | |
} | |
@Override | |
public String getDefaultFontFamily() { | |
String typeface = null; | |
if (!_runs.isEmpty()) { | |
typeface = _runs.get(0).getFontFamily(); | |
} | |
return (typeface != null) ? typeface : "Arial"; | |
} | |
@Override | |
public Double getDefaultFontSize() { | |
Double d = null; | |
if (!_runs.isEmpty()) { | |
d = _runs.get(0).getFontSize(); | |
} | |
return (d != null) ? d : 12d; | |
} | |
@Override | |
public void setTextAlign(TextAlign align) { | |
Integer alignInt = null; | |
if (align != null) { | |
switch (align) { | |
default: | |
case LEFT: alignInt = TextAlignmentProp.LEFT;break; | |
case CENTER: alignInt = TextAlignmentProp.CENTER; break; | |
case RIGHT: alignInt = TextAlignmentProp.RIGHT; break; | |
case DIST: alignInt = TextAlignmentProp.DISTRIBUTED; break; | |
case JUSTIFY: alignInt = TextAlignmentProp.JUSTIFY; break; | |
case JUSTIFY_LOW: alignInt = TextAlignmentProp.JUSTIFYLOW; break; | |
case THAI_DIST: alignInt = TextAlignmentProp.THAIDISTRIBUTED; break; | |
} | |
} | |
setParagraphTextPropVal("alignment", alignInt); | |
} | |
@Override | |
public TextAlign getTextAlign() { | |
TextProp tp = getPropVal(_paragraphStyle, "alignment", this); | |
if (tp == null) { | |
return null; | |
} | |
switch (tp.getValue()) { | |
default: | |
case TextAlignmentProp.LEFT: return TextAlign.LEFT; | |
case TextAlignmentProp.CENTER: return TextAlign.CENTER; | |
case TextAlignmentProp.RIGHT: return TextAlign.RIGHT; | |
case TextAlignmentProp.JUSTIFY: return TextAlign.JUSTIFY; | |
case TextAlignmentProp.JUSTIFYLOW: return TextAlign.JUSTIFY_LOW; | |
case TextAlignmentProp.DISTRIBUTED: return TextAlign.DIST; | |
case TextAlignmentProp.THAIDISTRIBUTED: return TextAlign.THAI_DIST; | |
} | |
} | |
@Override | |
public FontAlign getFontAlign() { | |
TextProp tp = getPropVal(_paragraphStyle, FontAlignmentProp.NAME, this); | |
if (tp == null) { | |
return null; | |
} | |
switch (tp.getValue()) { | |
case FontAlignmentProp.BASELINE: return FontAlign.BASELINE; | |
case FontAlignmentProp.TOP: return FontAlign.TOP; | |
case FontAlignmentProp.CENTER: return FontAlign.CENTER; | |
case FontAlignmentProp.BOTTOM: return FontAlign.BOTTOM; | |
default: return FontAlign.AUTO; | |
} | |
} | |
public AutoNumberingScheme getAutoNumberingScheme() { | |
if (styleTextProp9Atom == null) { | |
return null; | |
} | |
TextPFException9[] ant = styleTextProp9Atom.getAutoNumberTypes(); | |
int level = getIndentLevel(); | |
if (ant == null || level == -1 || level >= ant.length) { | |
return null; | |
} | |
return ant[level].getAutoNumberScheme(); | |
} | |
public Integer getAutoNumberingStartAt() { | |
if (styleTextProp9Atom == null) { | |
return null; | |
} | |
TextPFException9[] ant = styleTextProp9Atom.getAutoNumberTypes(); | |
int level = getIndentLevel(); | |
if (ant == null || level >= ant.length) { | |
return null; | |
} | |
Short startAt = ant[level].getAutoNumberStartNumber(); | |
assert(startAt != null); | |
return startAt.intValue(); | |
} | |
@Override | |
public BulletStyle getBulletStyle() { | |
if (!isBullet() && getAutoNumberingScheme() == null) { | |
return null; | |
} | |
return new BulletStyle() { | |
@Override | |
public String getBulletCharacter() { | |
Character chr = HSLFTextParagraph.this.getBulletChar(); | |
return (chr == null || chr == 0) ? "" : "" + chr; | |
} | |
@Override | |
public String getBulletFont() { | |
return HSLFTextParagraph.this.getBulletFont(); | |
} | |
@Override | |
public Double getBulletFontSize() { | |
return HSLFTextParagraph.this.getBulletSize(); | |
} | |
@Override | |
public void setBulletFontColor(Color color) { | |
setBulletFontColor(DrawPaint.createSolidPaint(color)); | |
} | |
@Override | |
public void setBulletFontColor(PaintStyle color) { | |
if (!(color instanceof SolidPaint)) { | |
throw new IllegalArgumentException("HSLF only supports SolidPaint"); | |
} | |
SolidPaint sp = (SolidPaint)color; | |
Color col = DrawPaint.applyColorTransform(sp.getSolidColor()); | |
HSLFTextParagraph.this.setBulletColor(col); | |
} | |
@Override | |
public PaintStyle getBulletFontColor() { | |
Color col = HSLFTextParagraph.this.getBulletColor(); | |
return DrawPaint.createSolidPaint(col); | |
} | |
@Override | |
public AutoNumberingScheme getAutoNumberingScheme() { | |
return HSLFTextParagraph.this.getAutoNumberingScheme(); | |
} | |
@Override | |
public Integer getAutoNumberingStartAt() { | |
return HSLFTextParagraph.this.getAutoNumberingStartAt(); | |
} | |
}; | |
} | |
@Override | |
public void setBulletStyle(Object... styles) { | |
if (styles.length == 0) { | |
setBullet(false); | |
} else { | |
setBullet(true); | |
for (Object ostyle : styles) { | |
if (ostyle instanceof Number) { | |
setBulletSize(((Number)ostyle).doubleValue()); | |
} else if (ostyle instanceof Color) { | |
setBulletColor((Color)ostyle); | |
} else if (ostyle instanceof Character) { | |
setBulletChar((Character)ostyle); | |
} else if (ostyle instanceof String) { | |
setBulletFont((String)ostyle); | |
} else if (ostyle instanceof AutoNumberingScheme) { | |
throw new HSLFException("setting bullet auto-numberin scheme for HSLF not supported ... yet"); | |
} | |
} | |
} | |
} | |
@Override | |
public HSLFTextShape getParentShape() { | |
return _parentShape; | |
} | |
public void setParentShape(HSLFTextShape parentShape) { | |
_parentShape = parentShape; | |
} | |
@Override | |
public int getIndentLevel() { | |
return _paragraphStyle == null ? 0 : _paragraphStyle.getIndentLevel(); | |
} | |
@Override | |
public void setIndentLevel(int level) { | |
if( _paragraphStyle != null ) { | |
_paragraphStyle.setIndentLevel((short)level); | |
} | |
} | |
/** | |
* Sets whether this rich text run has bullets | |
*/ | |
public void setBullet(boolean flag) { | |
setFlag(ParagraphFlagsTextProp.BULLET_IDX, flag); | |
} | |
/** | |
* Returns whether this rich text run has bullets | |
*/ | |
public boolean isBullet() { | |
return getFlag(ParagraphFlagsTextProp.BULLET_IDX); | |
} | |
/** | |
* Sets the bullet character | |
*/ | |
public void setBulletChar(Character c) { | |
Integer val = (c == null) ? null : (int)c.charValue(); | |
setParagraphTextPropVal("bullet.char", val); | |
} | |
/** | |
* Returns the bullet character | |
*/ | |
public Character getBulletChar() { | |
TextProp tp = getPropVal(_paragraphStyle, "bullet.char", this); | |
return (tp == null) ? null : (char)tp.getValue(); | |
} | |
/** | |
* Sets the bullet size | |
*/ | |
public void setBulletSize(Double size) { | |
setPctOrPoints("bullet.size", size); | |
} | |
/** | |
* Returns the bullet size, null if unset | |
*/ | |
public Double getBulletSize() { | |
return getPctOrPoints("bullet.size"); | |
} | |
/** | |
* Sets the bullet color | |
*/ | |
public void setBulletColor(Color color) { | |
Integer val = (color == null) ? null : new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB(); | |
setParagraphTextPropVal("bullet.color", val); | |
setFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX, (color != null)); | |
} | |
/** | |
* Returns the bullet color | |
*/ | |
public Color getBulletColor() { | |
TextProp tp = getPropVal(_paragraphStyle, "bullet.color", this); | |
boolean hasColor = getFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX); | |
if (tp == null || !hasColor) { | |
// if bullet color is undefined, return color of first run | |
if (_runs.isEmpty()) { | |
return null; | |
} | |
SolidPaint sp = _runs.get(0).getFontColor(); | |
if(sp == null) { | |
return null; | |
} | |
return DrawPaint.applyColorTransform(sp.getSolidColor()); | |
} | |
return getColorFromColorIndexStruct(tp.getValue(), _sheet); | |
} | |
/** | |
* Sets the bullet font | |
*/ | |
public void setBulletFont(String typeface) { | |
if (typeface == null) { | |
setPropVal(_paragraphStyle, "bullet.font", null); | |
setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false); | |
} | |
FontCollection fc = getSheet().getSlideShow().getFontCollection(); | |
int idx = fc.addFont(typeface); | |
setParagraphTextPropVal("bullet.font", idx); | |
setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, true); | |
} | |
/** | |
* Returns the bullet font | |
*/ | |
public String getBulletFont() { | |
TextProp tp = getPropVal(_paragraphStyle, "bullet.font", this); | |
boolean hasFont = getFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX); | |
if (tp == null || !hasFont) { | |
return getDefaultFontFamily(); | |
} | |
PPFont ppFont = getSheet().getSlideShow().getFont(tp.getValue()); | |
assert(ppFont != null); | |
return ppFont.getFontName(); | |
} | |
@Override | |
public void setLineSpacing(Double lineSpacing) { | |
setPctOrPoints("linespacing", lineSpacing); | |
} | |
@Override | |
public Double getLineSpacing() { | |
return getPctOrPoints("linespacing"); | |
} | |
@Override | |
public void setSpaceBefore(Double spaceBefore) { | |
setPctOrPoints("spacebefore", spaceBefore); | |
} | |
@Override | |
public Double getSpaceBefore() { | |
return getPctOrPoints("spacebefore"); | |
} | |
@Override | |
public void setSpaceAfter(Double spaceAfter) { | |
setPctOrPoints("spaceafter", spaceAfter); | |
} | |
@Override | |
public Double getSpaceAfter() { | |
return getPctOrPoints("spaceafter"); | |
} | |
@Override | |
public Double getDefaultTabSize() { | |
// TODO: implement | |
return null; | |
} | |
private Double getPctOrPoints(String propName) { | |
TextProp tp = getPropVal(_paragraphStyle, propName, this); | |
if (tp == null) { | |
return null; | |
} | |
int val = tp.getValue(); | |
return (val < 0) ? Units.masterToPoints(val) : val; | |
} | |
private void setPctOrPoints(String propName, Double dval) { | |
Integer ival = null; | |
if (dval != null) { | |
ival = (dval < 0) ? Units.pointsToMaster(dval) : dval.intValue(); | |
} | |
setParagraphTextPropVal(propName, ival); | |
} | |
private boolean getFlag(int index) { | |
BitMaskTextProp tp = (BitMaskTextProp)getPropVal(_paragraphStyle, ParagraphFlagsTextProp.NAME, this); | |
return (tp == null) ? false : tp.getSubValue(index); | |
} | |
private void setFlag(int index, boolean value) { | |
BitMaskTextProp tp = (BitMaskTextProp)_paragraphStyle.addWithName(ParagraphFlagsTextProp.NAME); | |
tp.setSubValue(value, index); | |
setDirty(); | |
} | |
/** | |
* Fetch the value of the given Paragraph related TextProp. Returns null if | |
* that TextProp isn't present. If the TextProp isn't present, the value | |
* from the appropriate Master Sheet will apply. | |
* | |
* The propName can be a comma-separated list, in case multiple equivalent values | |
* are queried | |
*/ | |
protected static TextProp getPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) { | |
String propNames[] = propName.split(","); | |
for (String pn : propNames) { | |
TextProp prop = props.findByName(pn); | |
if (prop == null) { | |
continue; | |
} | |
// Font properties (maybe other too???) can have an index of -1 | |
// so we check the master for this font index then | |
if (pn.contains("font") && prop.getValue() == -1) { | |
return getMasterPropVal(props, pn, paragraph); | |
} | |
return prop; | |
} | |
return getMasterPropVal(props, propName, paragraph); | |
} | |
private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) { | |
String propNames[] = propName.split(","); | |
BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); | |
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); | |
if (hardAttribute) { | |
return null; | |
} | |
HSLFSheet sheet = paragraph.getSheet(); | |
int txtype = paragraph.getRunType(); | |
HSLFMasterSheet master = sheet.getMasterSheet(); | |
if (master == null) { | |
logger.log(POILogger.WARN, "MasterSheet is not available"); | |
return null; | |
} | |
boolean isChar = props.getTextPropType() == TextPropType.character; | |
for (String pn : propNames) { | |
TextProp prop = master.getStyleAttribute(txtype, paragraph.getIndentLevel(), pn, isChar); | |
if (prop != null) { | |
return prop; | |
} | |
} | |
return null; | |
} | |
/** | |
* Returns the named TextProp, either by fetching it (if it exists) or | |
* adding it (if it didn't) | |
* | |
* @param props the TextPropCollection to fetch from / add into | |
* @param name the name of the TextProp to fetch/add | |
* @param val the value, null if unset | |
*/ | |
protected static void setPropVal(TextPropCollection props, String name, Integer val) { | |
if (val == null) { | |
props.removeByName(name); | |
return; | |
} | |
// Fetch / Add the TextProp | |
TextProp tp = props.addWithName(name); | |
tp.setValue(val); | |
} | |
/** | |
* Check and add linebreaks to text runs leading other paragraphs | |
* | |
* @param paragraphs | |
*/ | |
protected static void fixLineEndings(List<HSLFTextParagraph> paragraphs) { | |
HSLFTextRun lastRun = null; | |
for (HSLFTextParagraph p : paragraphs) { | |
if (lastRun != null && !lastRun.getRawText().endsWith("\r")) { | |
lastRun.setText(lastRun.getRawText() + "\r"); | |
} | |
List<HSLFTextRun> ltr = p.getTextRuns(); | |
if (ltr.isEmpty()) { | |
throw new HSLFException("paragraph without textruns found"); | |
} | |
lastRun = ltr.get(ltr.size() - 1); | |
assert (lastRun.getRawText() != null); | |
} | |
} | |
/** | |
* Search for a StyleTextPropAtom is for this text header (list of paragraphs) | |
* | |
* @param header the header | |
* @param textLen the length of the rawtext, or -1 if the length is not known | |
*/ | |
private static StyleTextPropAtom findStyleAtomPresent(TextHeaderAtom header, int textLen) { | |
boolean afterHeader = false; | |
StyleTextPropAtom style = null; | |
for (Record record : header.getParentRecord().getChildRecords()) { | |
long rt = record.getRecordType(); | |
if (afterHeader && rt == RecordTypes.TextHeaderAtom.typeID) { | |
// already on the next header, quit searching | |
break; | |
} | |
afterHeader |= (header == record); | |
if (afterHeader && rt == RecordTypes.StyleTextPropAtom.typeID) { | |
// found it | |
style = (StyleTextPropAtom) record; | |
} | |
} | |
if (style == null) { | |
logger.log(POILogger.INFO, "styles atom doesn't exist. Creating dummy record for later saving."); | |
style = new StyleTextPropAtom((textLen < 0) ? 1 : textLen); | |
} else { | |
if (textLen >= 0) { | |
style.setParentTextSize(textLen); | |
} | |
} | |
return style; | |
} | |
/** | |
* Saves the modified paragraphs/textrun to the records. | |
* Also updates the styles to the correct text length. | |
*/ | |
protected static void storeText(List<HSLFTextParagraph> paragraphs) { | |
fixLineEndings(paragraphs); | |
updateTextAtom(paragraphs); | |
updateStyles(paragraphs); | |
updateHyperlinks(paragraphs); | |
refreshRecords(paragraphs); | |
for (HSLFTextParagraph p : paragraphs) { | |
p._dirty = false; | |
} | |
} | |
/** | |
* Set the correct text atom depending on the multibyte usage | |
*/ | |
private static void updateTextAtom(List<HSLFTextParagraph> paragraphs) { | |
final String rawText = toInternalString(getRawText(paragraphs)); | |
// Will it fit in a 8 bit atom? | |
boolean isUnicode = StringUtil.hasMultibyte(rawText); | |
// isUnicode = true; | |
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; | |
TextBytesAtom byteAtom = paragraphs.get(0)._byteAtom; | |
TextCharsAtom charAtom = paragraphs.get(0)._charAtom; | |
StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length()); | |
// Store in the appropriate record | |
Record oldRecord = null, newRecord = null; | |
if (isUnicode) { | |
if (byteAtom != null || charAtom == null) { | |
oldRecord = byteAtom; | |
charAtom = new TextCharsAtom(); | |
} | |
newRecord = charAtom; | |
charAtom.setText(rawText); | |
} else { | |
if (charAtom != null || byteAtom == null) { | |
oldRecord = charAtom; | |
byteAtom = new TextBytesAtom(); | |
} | |
newRecord = byteAtom; | |
byte[] byteText = new byte[rawText.length()]; | |
StringUtil.putCompressedUnicode(rawText, byteText, 0); | |
byteAtom.setText(byteText); | |
} | |
assert (newRecord != null); | |
RecordContainer _txtbox = headerAtom.getParentRecord(); | |
Record[] cr = _txtbox.getChildRecords(); | |
int /* headerIdx = -1, */ textIdx = -1, styleIdx = -1; | |
for (int i = 0; i < cr.length; i++) { | |
Record r = cr[i]; | |
if (r == headerAtom) { | |
; // headerIdx = i; | |
} else if (r == oldRecord || r == newRecord) { | |
textIdx = i; | |
} else if (r == styleAtom) { | |
styleIdx = i; | |
} | |
} | |
if (textIdx == -1) { | |
// the old record was never registered, ignore it | |
_txtbox.addChildAfter(newRecord, headerAtom); | |
// textIdx = headerIdx + 1; | |
} else { | |
// swap not appropriated records - noop if unchanged | |
cr[textIdx] = newRecord; | |
} | |
if (styleIdx == -1) { | |
// Add the new StyleTextPropAtom after the TextCharsAtom / TextBytesAtom | |
_txtbox.addChildAfter(styleAtom, newRecord); | |
} | |
for (HSLFTextParagraph p : paragraphs) { | |
if (newRecord == byteAtom) { | |
p._byteAtom = byteAtom; | |
p._charAtom = null; | |
} else { | |
p._byteAtom = null; | |
p._charAtom = charAtom; | |
} | |
} | |
} | |
/** | |
* Update paragraph and character styles - merges them when subsequential styles match | |
*/ | |
private static void updateStyles(List<HSLFTextParagraph> paragraphs) { | |
final String rawText = toInternalString(getRawText(paragraphs)); | |
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; | |
StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length()); | |
// Update the text length for its Paragraph and Character stylings | |
// * reset the length, to the new string's length | |
// * add on +1 if the last block | |
styleAtom.clearStyles(); | |
TextPropCollection lastPTPC = null, lastRTPC = null, ptpc = null, rtpc = null; | |
for (HSLFTextParagraph para : paragraphs) { | |
ptpc = para.getParagraphStyle(); | |
ptpc.updateTextSize(0); | |
if (!ptpc.equals(lastPTPC)) { | |
lastPTPC = styleAtom.addParagraphTextPropCollection(0); | |
lastPTPC.copy(ptpc); | |
} | |
for (HSLFTextRun tr : para.getTextRuns()) { | |
rtpc = tr.getCharacterStyle(); | |
rtpc.updateTextSize(0); | |
if (!rtpc.equals(lastRTPC)) { | |
lastRTPC = styleAtom.addCharacterTextPropCollection(0); | |
lastRTPC.copy(rtpc); | |
} | |
int len = tr.getLength(); | |
ptpc.updateTextSize(ptpc.getCharactersCovered() + len); | |
rtpc.updateTextSize(len); | |
lastPTPC.updateTextSize(lastPTPC.getCharactersCovered() + len); | |
lastRTPC.updateTextSize(lastRTPC.getCharactersCovered() + len); | |
} | |
} | |
if (lastPTPC == null || lastRTPC == null || ptpc == null || rtpc == null) { // NOSONAR | |
throw new HSLFException("Not all TextPropCollection could be determined."); | |
} | |
ptpc.updateTextSize(ptpc.getCharactersCovered() + 1); | |
rtpc.updateTextSize(rtpc.getCharactersCovered() + 1); | |
lastPTPC.updateTextSize(lastPTPC.getCharactersCovered() + 1); | |
lastRTPC.updateTextSize(lastRTPC.getCharactersCovered() + 1); | |
/** | |
* If TextSpecInfoAtom is present, we must update the text size in it, | |
* otherwise the ppt will be corrupted | |
*/ | |
for (Record r : paragraphs.get(0).getRecords()) { | |
if (r instanceof TextSpecInfoAtom) { | |
((TextSpecInfoAtom) r).setParentSize(rawText.length() + 1); | |
break; | |
} | |
} | |
} | |
private static void updateHyperlinks(List<HSLFTextParagraph> paragraphs) { | |
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; | |
RecordContainer _txtbox = headerAtom.getParentRecord(); | |
// remove existing hyperlink records | |
for (Record r : _txtbox.getChildRecords()) { | |
if (r instanceof InteractiveInfo || r instanceof TxInteractiveInfoAtom) { | |
_txtbox.removeChild(r); | |
} | |
} | |
// now go through all the textruns and check for hyperlinks | |
HSLFHyperlink lastLink = null; | |
for (HSLFTextParagraph para : paragraphs) { | |
for (HSLFTextRun run : para) { | |
HSLFHyperlink thisLink = run.getHyperlink(); | |
if (thisLink != null && thisLink == lastLink) { | |
// the hyperlink extends over this text run, increase its length | |
// TODO: the text run might be longer than the hyperlink | |
thisLink.setEndIndex(thisLink.getEndIndex()+run.getLength()); | |
} else { | |
if (lastLink != null) { | |
InteractiveInfo info = lastLink.getInfo(); | |
TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo(); | |
assert(info != null && txinfo != null); | |
_txtbox.appendChildRecord(info); | |
_txtbox.appendChildRecord(txinfo); | |
} | |
} | |
lastLink = thisLink; | |
} | |
} | |
if (lastLink != null) { | |
InteractiveInfo info = lastLink.getInfo(); | |
TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo(); | |
assert(info != null && txinfo != null); | |
_txtbox.appendChildRecord(info); | |
_txtbox.appendChildRecord(txinfo); | |
} | |
} | |
/** | |
* Writes the textbox records back to the document record | |
*/ | |
private static void refreshRecords(List<HSLFTextParagraph> paragraphs) { | |
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom; | |
RecordContainer _txtbox = headerAtom.getParentRecord(); | |
if (_txtbox instanceof EscherTextboxWrapper) { | |
try { | |
((EscherTextboxWrapper) _txtbox).writeOut(null); | |
} catch (IOException e) { | |
throw new HSLFException("failed dummy write", e); | |
} | |
} | |
} | |
/** | |
* Adds the supplied text onto the end of the TextParagraphs, | |
* creating a new RichTextRun for it to sit in. | |
* | |
* @param text the text string used by this object. | |
*/ | |
protected static HSLFTextRun appendText(List<HSLFTextParagraph> paragraphs, String text, boolean newParagraph) { | |
text = toInternalString(text); | |
// check paragraphs | |
assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); | |
HSLFTextParagraph htp = paragraphs.get(paragraphs.size() - 1); | |
HSLFTextRun htr = htp.getTextRuns().get(htp.getTextRuns().size() - 1); | |
boolean addParagraph = newParagraph; | |
for (String rawText : text.split("(?<=\r)")) { | |
// special case, if last text paragraph or run is empty, we will reuse it | |
boolean lastRunEmpty = (htr.getLength() == 0); | |
boolean lastParaEmpty = lastRunEmpty && (htp.getTextRuns().size() == 1); | |
if (addParagraph && !lastParaEmpty) { | |
TextPropCollection tpc = htp.getParagraphStyle(); | |
HSLFTextParagraph prevHtp = htp; | |
htp = new HSLFTextParagraph(htp._headerAtom, htp._byteAtom, htp._charAtom, paragraphs); | |
htp.getParagraphStyle().copy(tpc); | |
htp.setParentShape(prevHtp.getParentShape()); | |
htp.setShapeId(prevHtp.getShapeId()); | |
htp.supplySheet(prevHtp.getSheet()); | |
paragraphs.add(htp); | |
} | |
addParagraph = true; | |
if (!lastRunEmpty) { | |
TextPropCollection tpc = htr.getCharacterStyle(); | |
htr = new HSLFTextRun(htp); | |
htr.getCharacterStyle().copy(tpc); | |
htp.addTextRun(htr); | |
} | |
htr.setText(rawText); | |
} | |
storeText(paragraphs); | |
return htr; | |
} | |
/** | |
* Sets (overwrites) the current text. | |
* Uses the properties of the first paragraph / textrun | |
* | |
* @param text the text string used by this object. | |
*/ | |
public static HSLFTextRun setText(List<HSLFTextParagraph> paragraphs, String text) { | |
// check paragraphs | |
assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty()); | |
Iterator<HSLFTextParagraph> paraIter = paragraphs.iterator(); | |
HSLFTextParagraph htp = paraIter.next(); // keep first | |
assert (htp != null); | |
while (paraIter.hasNext()) { | |
paraIter.next(); | |
paraIter.remove(); | |
} | |
Iterator<HSLFTextRun> runIter = htp.getTextRuns().iterator(); | |
if (runIter.hasNext()) { | |
HSLFTextRun htr = runIter.next(); | |
htr.setText(""); | |
while (runIter.hasNext()) { | |
runIter.next(); | |
runIter.remove(); | |
} | |
} else { | |
HSLFTextRun trun = new HSLFTextRun(htp); | |
htp.addTextRun(trun); | |
} | |
return appendText(paragraphs, text, false); | |
} | |
public static String getText(List<HSLFTextParagraph> paragraphs) { | |
assert (!paragraphs.isEmpty()); | |
String rawText = getRawText(paragraphs); | |
return toExternalString(rawText, paragraphs.get(0).getRunType()); | |
} | |
public static String getRawText(List<HSLFTextParagraph> paragraphs) { | |
StringBuilder sb = new StringBuilder(); | |
for (HSLFTextParagraph p : paragraphs) { | |
for (HSLFTextRun r : p.getTextRuns()) { | |
sb.append(r.getRawText()); | |
} | |
} | |
return sb.toString(); | |
} | |
@Override | |
public String toString() { | |
StringBuilder sb = new StringBuilder(); | |
for (HSLFTextRun r : getTextRuns()) { | |
sb.append(r.getRawText()); | |
} | |
return toExternalString(sb.toString(), getRunType()); | |
} | |
/** | |
* Returns a new string with line breaks converted into internal ppt | |
* representation | |
*/ | |
protected static String toInternalString(String s) { | |
String ns = s.replaceAll("\\r?\\n", "\r"); | |
return ns; | |
} | |
/** | |
* Converts raw text from the text paragraphs to a formatted string, | |
* i.e. it converts certain control characters used in the raw txt | |
* | |
* @param rawText the raw text | |
* @param runType the run type of the shape, paragraph or headerAtom. | |
* use -1 if unknown | |
* @return the formatted string | |
*/ | |
public static String toExternalString(String rawText, int runType) { | |
// PowerPoint seems to store files with \r as the line break | |
// The messes things up on everything but a Mac, so translate | |
// them to \n | |
String text = rawText.replace('\r', '\n'); | |
switch (runType) { | |
// 0xB acts like cariage return in page titles and like blank in the | |
// others | |
case -1: | |
case org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE: | |
case org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE: | |
text = text.replace((char) 0x0B, '\n'); | |
break; | |
default: | |
text = text.replace((char) 0x0B, ' '); | |
break; | |
} | |
return text; | |
} | |
/** | |
* For a given PPDrawing, grab all the TextRuns | |
*/ | |
public static List<List<HSLFTextParagraph>> findTextParagraphs(PPDrawing ppdrawing, HSLFSheet sheet) { | |
List<List<HSLFTextParagraph>> runsV = new ArrayList<List<HSLFTextParagraph>>(); | |
for (EscherTextboxWrapper wrapper : ppdrawing.getTextboxWrappers()) { | |
List<HSLFTextParagraph> p = findTextParagraphs(wrapper, sheet); | |
if (p != null) { | |
runsV.add(p); | |
} | |
} | |
return runsV; | |
} | |
/** | |
* Scans through the supplied record array, looking for | |
* a TextHeaderAtom followed by one of a TextBytesAtom or | |
* a TextCharsAtom. Builds up TextRuns from these | |
* | |
* @param wrapper an EscherTextboxWrapper | |
*/ | |
protected static List<HSLFTextParagraph> findTextParagraphs(EscherTextboxWrapper wrapper, HSLFSheet sheet) { | |
// propagate parents to parent-aware records | |
RecordContainer.handleParentAwareRecords(wrapper); | |
int shapeId = wrapper.getShapeId(); | |
List<HSLFTextParagraph> rv = null; | |
OutlineTextRefAtom ota = (OutlineTextRefAtom)wrapper.findFirstOfType(OutlineTextRefAtom.typeID); | |
if (ota != null) { | |
// if we are based on an outline, there are no further records to be parsed from the wrapper | |
if (sheet == null) { | |
throw new HSLFException("Outline atom reference can't be solved without a sheet record"); | |
} | |
List<List<HSLFTextParagraph>> sheetRuns = sheet.getTextParagraphs(); | |
assert (sheetRuns != null); | |
int idx = ota.getTextIndex(); | |
for (List<HSLFTextParagraph> r : sheetRuns) { | |
if (r.isEmpty()) { | |
continue; | |
} | |
int ridx = r.get(0).getIndex(); | |
if (ridx > idx) { | |
break; | |
} | |
if (ridx == idx) { | |
if (rv == null) { | |
rv = r; | |
} else { | |
// create a new container | |
// TODO: ... is this case really happening? | |
rv = new ArrayList<HSLFTextParagraph>(rv); | |
rv.addAll(r); | |
} | |
} | |
} | |
if (rv == null || rv.isEmpty()) { | |
logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx); | |
} | |
} else { | |
if (sheet != null) { | |
// check sheet runs first, so we get exactly the same paragraph list | |
List<List<HSLFTextParagraph>> sheetRuns = sheet.getTextParagraphs(); | |
assert (sheetRuns != null); | |
for (List<HSLFTextParagraph> paras : sheetRuns) { | |
if (!paras.isEmpty() && paras.get(0)._headerAtom.getParentRecord() == wrapper) { | |
rv = paras; | |
break; | |
} | |
} | |
} | |
if (rv == null) { | |
// if we haven't found the wrapper in the sheet runs, create a new paragraph list from its record | |
List<List<HSLFTextParagraph>> rvl = findTextParagraphs(wrapper.getChildRecords()); | |
switch (rvl.size()) { | |
case 0: break; // nothing found | |
case 1: rv = rvl.get(0); break; // normal case | |
default: | |
throw new HSLFException("TextBox contains more than one list of paragraphs."); | |
} | |
} | |
} | |
if (rv != null) { | |
StyleTextProp9Atom styleTextProp9Atom = wrapper.getStyleTextProp9Atom(); | |
for (HSLFTextParagraph htp : rv) { | |
htp.setShapeId(shapeId); | |
htp.setStyleTextProp9Atom(styleTextProp9Atom); | |
} | |
} | |
return rv; | |
} | |
/** | |
* Scans through the supplied record array, looking for | |
* a TextHeaderAtom followed by one of a TextBytesAtom or | |
* a TextCharsAtom. Builds up TextRuns from these | |
* | |
* @param records the records to build from | |
*/ | |
protected static List<List<HSLFTextParagraph>> findTextParagraphs(Record[] records) { | |
List<List<HSLFTextParagraph>> paragraphCollection = new ArrayList<List<HSLFTextParagraph>>(); | |
int[] recordIdx = { 0 }; | |
for (int slwtIndex = 0; recordIdx[0] < records.length; slwtIndex++) { | |
TextHeaderAtom header = null; | |
TextBytesAtom tbytes = null; | |
TextCharsAtom tchars = null; | |
TextRulerAtom ruler = null; | |
MasterTextPropAtom indents = null; | |
for (Record r : getRecords(records, recordIdx, null)) { | |
long rt = r.getRecordType(); | |
if (RecordTypes.TextHeaderAtom.typeID == rt) { | |
header = (TextHeaderAtom) r; | |
} else if (RecordTypes.TextBytesAtom.typeID == rt) { | |
tbytes = (TextBytesAtom) r; | |
} else if (RecordTypes.TextCharsAtom.typeID == rt) { | |
tchars = (TextCharsAtom) r; | |
} else if (RecordTypes.TextRulerAtom.typeID == rt) { | |
ruler = (TextRulerAtom) r; | |
} else if (RecordTypes.MasterTextPropAtom.typeID == rt) { | |
indents = (MasterTextPropAtom) r; | |
} | |
// don't search for RecordTypes.StyleTextPropAtom.typeID here ... see findStyleAtomPresent below | |
} | |
if (header == null) { | |
break; | |
} | |
if (header.getParentRecord() instanceof SlideListWithText) { | |
// runs found in PPDrawing are not linked with SlideListWithTexts | |
header.setIndex(slwtIndex); | |
} | |
if (tbytes == null && tchars == null) { | |
tbytes = new TextBytesAtom(); | |
// don't add record yet - set it in storeText | |
logger.log(POILogger.INFO, "bytes nor chars atom doesn't exist. Creating dummy record for later saving."); | |
} | |
String rawText = (tchars != null) ? tchars.getText() : tbytes.getText(); | |
StyleTextPropAtom styles = findStyleAtomPresent(header, rawText.length()); | |
List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>(); | |
paragraphCollection.add(paragraphs); | |
// split, but keep delimiter | |
for (String para : rawText.split("(?<=\r)")) { | |
HSLFTextParagraph tpara = new HSLFTextParagraph(header, tbytes, tchars, paragraphs); | |
paragraphs.add(tpara); | |
tpara._ruler = ruler; | |
tpara.getParagraphStyle().updateTextSize(para.length()); | |
HSLFTextRun trun = new HSLFTextRun(tpara); | |
tpara.addTextRun(trun); | |
trun.setText(para); | |
} | |
applyCharacterStyles(paragraphs, styles.getCharacterStyles()); | |
applyParagraphStyles(paragraphs, styles.getParagraphStyles()); | |
if (indents != null) { | |
applyParagraphIndents(paragraphs, indents.getIndents()); | |
} | |
} | |
if (paragraphCollection.isEmpty()) { | |
logger.log(POILogger.DEBUG, "No text records found."); | |
} | |
return paragraphCollection; | |
} | |
protected static void applyHyperlinks(List<HSLFTextParagraph> paragraphs) { | |
List<HSLFHyperlink> links = HSLFHyperlink.find(paragraphs); | |
for (HSLFHyperlink h : links) { | |
int csIdx = 0; | |
for (HSLFTextParagraph p : paragraphs) { | |
if (csIdx > h.getEndIndex()) { | |
break; | |
} | |
List<HSLFTextRun> runs = p.getTextRuns(); | |
for (int rlen=0,rIdx=0; rIdx < runs.size(); csIdx+=rlen, rIdx++) { | |
HSLFTextRun run = runs.get(rIdx); | |
rlen = run.getLength(); | |
if (csIdx < h.getEndIndex() && h.getStartIndex() < csIdx+rlen) { | |
String rawText = run.getRawText(); | |
int startIdx = h.getStartIndex()-csIdx; | |
if (startIdx > 0) { | |
// hyperlink starts within current textrun | |
HSLFTextRun newRun = new HSLFTextRun(p); | |
newRun.setCharacterStyle(run.getCharacterStyle()); | |
newRun.setText(rawText.substring(startIdx)); | |
run.setText(rawText.substring(0, startIdx)); | |
runs.add(rIdx+1, newRun); | |
rlen = startIdx; | |
continue; | |
} | |
int endIdx = Math.min(rlen, h.getEndIndex()-h.getStartIndex()); | |
if (endIdx < rlen) { | |
// hyperlink ends before end of current textrun | |
HSLFTextRun newRun = new HSLFTextRun(p); | |
newRun.setCharacterStyle(run.getCharacterStyle()); | |
newRun.setText(rawText.substring(0, endIdx)); | |
run.setText(rawText.substring(endIdx)); | |
runs.add(rIdx, newRun); | |
rlen = endIdx; | |
run = newRun; | |
} | |
run.setHyperlink(h); | |
} | |
} | |
} | |
} | |
} | |
protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) { | |
int paraIdx = 0, runIdx = 0; | |
HSLFTextRun trun; | |
for (int csIdx = 0; csIdx < charStyles.size(); csIdx++) { | |
TextPropCollection p = charStyles.get(csIdx); | |
for (int ccRun = 0, ccStyle = p.getCharactersCovered(); ccRun < ccStyle;) { | |
HSLFTextParagraph para = paragraphs.get(paraIdx); | |
List<HSLFTextRun> runs = para.getTextRuns(); | |
trun = runs.get(runIdx); | |
final int len = trun.getLength(); | |
if (ccRun + len <= ccStyle) { | |
ccRun += len; | |
} else { | |
String text = trun.getRawText(); | |
trun.setText(text.substring(0, ccStyle - ccRun)); | |
HSLFTextRun nextRun = new HSLFTextRun(para); | |
nextRun.setText(text.substring(ccStyle - ccRun)); | |
runs.add(runIdx + 1, nextRun); | |
ccRun += ccStyle - ccRun; | |
} | |
trun.setCharacterStyle(p); | |
if (paraIdx == paragraphs.size()-1 && runIdx == runs.size()-1) { | |
if (csIdx < charStyles.size() - 1) { | |
// special case, empty trailing text run | |
HSLFTextRun nextRun = new HSLFTextRun(para); | |
nextRun.setText(""); | |
runs.add(nextRun); | |
ccRun++; | |
} else { | |
// need to add +1 to the last run of the last paragraph | |
trun.getCharacterStyle().updateTextSize(trun.getLength()+1); | |
ccRun++; | |
} | |
} | |
// need to compare it again, in case a run has been added after | |
if (++runIdx == runs.size()) { | |
paraIdx++; | |
runIdx = 0; | |
} | |
} | |
} | |
} | |
protected static void applyParagraphStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> paraStyles) { | |
int paraIdx = 0; | |
for (TextPropCollection p : paraStyles) { | |
for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) { | |
if (paraIdx >= paragraphs.size()) { | |
return; | |
} | |
HSLFTextParagraph htp = paragraphs.get(paraIdx); | |
TextPropCollection pCopy = new TextPropCollection(0, TextPropType.paragraph); | |
pCopy.copy(p); | |
htp.setParagraphStyle(pCopy); | |
int len = 0; | |
for (HSLFTextRun trun : htp.getTextRuns()) { | |
len += trun.getLength(); | |
} | |
if (paraIdx == paragraphs.size()-1) { | |
len++; | |
} | |
pCopy.updateTextSize(len); | |
ccPara += len; | |
} | |
} | |
} | |
protected static void applyParagraphIndents(List<HSLFTextParagraph> paragraphs, List<IndentProp> paraStyles) { | |
int paraIdx = 0; | |
for (IndentProp p : paraStyles) { | |
for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) { | |
if (paraIdx >= paragraphs.size() || ccPara >= ccStyle-1) { | |
return; | |
} | |
HSLFTextParagraph para = paragraphs.get(paraIdx); | |
int len = 0; | |
for (HSLFTextRun trun : para.getTextRuns()) { | |
len += trun.getLength(); | |
} | |
para.setIndentLevel(p.getIndentLevel()); | |
ccPara += len + 1; | |
} | |
} | |
} | |
protected static List<HSLFTextParagraph> createEmptyParagraph() { | |
EscherTextboxWrapper wrapper = new EscherTextboxWrapper(); | |
return createEmptyParagraph(wrapper); | |
} | |
protected static List<HSLFTextParagraph> createEmptyParagraph(EscherTextboxWrapper wrapper) { | |
TextHeaderAtom tha = new TextHeaderAtom(); | |
tha.setParentRecord(wrapper); | |
wrapper.appendChildRecord(tha); | |
TextBytesAtom tba = new TextBytesAtom(); | |
tba.setText("".getBytes(LocaleUtil.CHARSET_1252)); | |
wrapper.appendChildRecord(tba); | |
StyleTextPropAtom sta = new StyleTextPropAtom(1); | |
TextPropCollection paraStyle = sta.addParagraphTextPropCollection(1); | |
TextPropCollection charStyle = sta.addCharacterTextPropCollection(1); | |
wrapper.appendChildRecord(sta); | |
List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>(1); | |
HSLFTextParagraph htp = new HSLFTextParagraph(tha, tba, null, paragraphs); | |
htp.setParagraphStyle(paraStyle); | |
paragraphs.add(htp); | |
HSLFTextRun htr = new HSLFTextRun(htp); | |
htr.setCharacterStyle(charStyle); | |
htr.setText(""); | |
htp.addTextRun(htr); | |
return paragraphs; | |
} | |
public EscherTextboxWrapper getTextboxWrapper() { | |
return (EscherTextboxWrapper) _headerAtom.getParentRecord(); | |
} | |
protected static Color getColorFromColorIndexStruct(int rgb, HSLFSheet sheet) { | |
int cidx = rgb >>> 24; | |
Color tmp; | |
switch (cidx) { | |
// Background ... Accent 3 color | |
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: | |
if (sheet == null) { | |
return null; | |
} | |
ColorSchemeAtom ca = sheet.getColorScheme(); | |
tmp = new Color(ca.getColor(cidx), true); | |
break; | |
// Color is an sRGB value specified by red, green, and blue fields. | |
case 0xFE: | |
tmp = new Color(rgb, true); | |
break; | |
// Color is undefined. | |
default: | |
case 0xFF: | |
return null; | |
} | |
return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); | |
} | |
/** | |
* Sets the value of the given Paragraph TextProp, add if required | |
* @param propName The name of the Paragraph TextProp | |
* @param val The value to set for the TextProp | |
*/ | |
public void setParagraphTextPropVal(String propName, Integer val) { | |
setPropVal(_paragraphStyle, propName, val); | |
setDirty(); | |
} | |
/** | |
* marks this paragraph dirty, so its records will be renewed on save | |
*/ | |
public void setDirty() { | |
_dirty = true; | |
} | |
public boolean isDirty() { | |
return _dirty; | |
} | |
/** | |
* Calculates the start index of the given text run | |
* | |
* @param textrun the text run to search for | |
* @return the start index with the paragraph collection or -1 if not found | |
*/ | |
/* package */ int getStartIdxOfTextRun(HSLFTextRun textrun) { | |
int idx = 0; | |
for (HSLFTextParagraph p : parentList) { | |
for (HSLFTextRun r : p) { | |
if (r == textrun) { | |
return idx; | |
} | |
idx += r.getLength(); | |
} | |
} | |
return -1; | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @see RoundTripHFPlaceholder12 | |
*/ | |
@Override | |
public boolean isHeaderOrFooter() { | |
HSLFShape s = getParentShape(); | |
if (s == null) { | |
return false; | |
} | |
RoundTripHFPlaceholder12 hfPl = s.getClientDataRecord(RecordTypes.RoundTripHFPlaceholder12.typeID); | |
if (hfPl == null) { | |
return false; | |
} | |
int plId = hfPl.getPlaceholderId(); | |
switch (plId) { | |
case OEPlaceholderAtom.MasterDate: | |
case OEPlaceholderAtom.MasterSlideNumber: | |
case OEPlaceholderAtom.MasterFooter: | |
case OEPlaceholderAtom.MasterHeader: | |
return true; | |
default: | |
return false; | |
} | |
} | |
} |