git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1823893 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_0_0_FINAL
@@ -21,5 +21,12 @@ public interface MasterSheet< | |||
S extends Shape<S,P>, | |||
P extends TextParagraph<S,P,? extends TextRun> | |||
> extends Sheet<S,P> { | |||
/** | |||
* Return the placeholder shape for the specified type | |||
* | |||
* @return the shape or {@code null} if it is not defined in this mastersheet | |||
* | |||
* @since POI 4.0.0 | |||
*/ | |||
SimpleShape<S,P> getPlaceholder(Placeholder type); | |||
} |
@@ -0,0 +1,66 @@ | |||
/* ==================================================================== | |||
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.sl.usermodel; | |||
public interface TabStop { | |||
enum TabStopType { | |||
LEFT(0,1), CENTER(1,2), RIGHT(2,3), DECIMAL(3,4); | |||
public final int nativeId; | |||
public final int ooxmlId; | |||
TabStopType(int nativeId, int ooxmlId) { | |||
this.nativeId = nativeId; | |||
this.ooxmlId = ooxmlId; | |||
} | |||
public static TabStopType fromNativeId(final int nativeId) { | |||
for (TabStopType tst : values()) { | |||
if (tst.nativeId == nativeId) { | |||
return tst; | |||
} | |||
} | |||
return null; | |||
} | |||
public static TabStopType fromOoxmlId(final int ooxmlId) { | |||
for (TabStopType tst : values()) { | |||
if (tst.ooxmlId == ooxmlId) { | |||
return tst; | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
/** | |||
* Gets the position in points relative to the left side of the paragraph. | |||
* | |||
* @return position in points | |||
*/ | |||
double getPositionInPoints(); | |||
/** | |||
* Sets the position in points relative to the left side of the paragraph | |||
* | |||
* @param position position in points | |||
*/ | |||
void setPositionInPoints(double position); | |||
TabStopType getType(); | |||
void setType(TabStopType type); | |||
} |
@@ -374,4 +374,34 @@ public interface TextParagraph< | |||
* @since POI 3.15-beta2 | |||
*/ | |||
boolean isHeaderOrFooter(); | |||
/** | |||
* Get the {@link TabStop TabStops} - the list can't be and it's entries shouldn't be modified. | |||
* Opposed to other properties, this method is not cascading to the master sheet, | |||
* if the property is not defined on the normal slide level, i.e. the tabstops on | |||
* different levels aren't merged. | |||
* | |||
* @return the tabstop collection or {@code null} if no tabstops are defined | |||
* | |||
* @since POI 4.0.0 | |||
*/ | |||
List<? extends TabStop> getTabStops(); | |||
/** | |||
* Set the {@link TabStop} collection | |||
* | |||
* @param tabStops the {@link TabStop} collection | |||
* | |||
* @since POI 4.0.0 | |||
*/ | |||
void addTabStops(double positionInPoints, TabStop.TabStopType tabStopType); | |||
/** | |||
* Removes the tabstops of this paragraphs. | |||
* This doesn't affect inherited tabstops, e.g. inherited by the slide master | |||
* | |||
* @since POI 4.0.0 | |||
*/ | |||
void clearTabStops(); | |||
} |
@@ -472,6 +472,10 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> { | |||
return null; | |||
} | |||
public XSLFSimpleShape getPlaceholder(Placeholder ph) { | |||
return getPlaceholderByType(ph.ooxmlId); | |||
} | |||
XSLFSimpleShape getPlaceholder(CTPlaceholder ph) { | |||
XSLFSimpleShape shape = null; | |||
if(ph.isSetIdx()) { |
@@ -0,0 +1,60 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.xslf.usermodel; | |||
import org.apache.poi.sl.usermodel.TabStop; | |||
import org.apache.poi.util.Units; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.STTextTabAlignType; | |||
public class XSLFTabStop implements TabStop { | |||
final CTTextTabStop tabStop; | |||
XSLFTabStop(CTTextTabStop tabStop) { | |||
this.tabStop = tabStop; | |||
} | |||
/** position in EMUs */ | |||
public int getPosition() { | |||
return tabStop.getPos(); | |||
} | |||
/** position in EMUs */ | |||
public void setPosition(final int position) { | |||
tabStop.setPos(position); | |||
} | |||
@Override | |||
public double getPositionInPoints() { | |||
return Units.toPoints(getPosition()); | |||
} | |||
@Override | |||
public void setPositionInPoints(final double points) { | |||
setPosition(Units.toEMU(points)); | |||
} | |||
public TabStopType getType() { | |||
return TabStopType.fromOoxmlId(tabStop.getAlgn().intValue()); | |||
} | |||
public void setType(final TabStopType tabStopType) { | |||
tabStop.setAlgn(STTextTabAlignType.Enum.forInt(tabStopType.ooxmlId) ); | |||
} | |||
} |
@@ -25,6 +25,7 @@ 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.TabStop.TabStopType; | |||
import org.apache.poi.sl.usermodel.TextParagraph; | |||
import org.apache.poi.util.Beta; | |||
import org.apache.poi.util.Internal; | |||
@@ -32,26 +33,7 @@ import org.apache.poi.util.Units; | |||
import org.apache.poi.xslf.model.ParagraphPropertyFetcher; | |||
import org.apache.xmlbeans.XmlCursor; | |||
import org.apache.xmlbeans.XmlObject; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextAutonumberBullet; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePercent; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePoint; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharBullet; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextField; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextSpacing; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStopList; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.STTextAutonumberScheme; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.STTextFontAlignType; | |||
import org.openxmlformats.schemas.drawingml.x2006.main.*; | |||
import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; | |||
import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; | |||
@@ -800,25 +782,27 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr | |||
private <T> boolean fetchParagraphProperty(ParagraphPropertyFetcher<T> visitor){ | |||
boolean ok = false; | |||
XSLFTextShape shape = getParentShape(); | |||
XSLFSheet sheet = shape.getSheet(); | |||
final XSLFTextShape shape = getParentShape(); | |||
final XSLFSheet sheet = shape.getSheet(); | |||
if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr()); | |||
if (ok) return true; | |||
ok = shape.fetchShapeProperty(visitor); | |||
if (ok) return true; | |||
CTPlaceholder ph = shape.getCTPlaceholder(); | |||
if(ph == null){ | |||
// if it is a plain text box then take defaults from presentation.xml | |||
@SuppressWarnings("resource") | |||
XMLSlideShow ppt = sheet.getSlideShow(); | |||
CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel()); | |||
if (themeProps != null) ok = visitor.fetch(themeProps); | |||
if (!(sheet instanceof XSLFSlideMaster)) { | |||
if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr()); | |||
if (ok) return true; | |||
ok = shape.fetchShapeProperty(visitor); | |||
if (ok) return true; | |||
CTPlaceholder ph = shape.getCTPlaceholder(); | |||
if(ph == null){ | |||
// if it is a plain text box then take defaults from presentation.xml | |||
@SuppressWarnings("resource") | |||
XMLSlideShow ppt = sheet.getSlideShow(); | |||
CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel()); | |||
if (themeProps != null) ok = visitor.fetch(themeProps); | |||
} | |||
if (ok) return true; | |||
} | |||
if (ok) return true; | |||
// defaults for placeholders are defined in the slide master | |||
CTTextParagraphProperties defaultProps = getDefaultMasterStyle(); | |||
@@ -1011,7 +995,51 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr | |||
} | |||
} | |||
} | |||
@Override | |||
public List<XSLFTabStop> getTabStops() { | |||
ParagraphPropertyFetcher<List<XSLFTabStop>> fetcher = new ParagraphPropertyFetcher<List<XSLFTabStop>>(getIndentLevel()){ | |||
public boolean fetch(CTTextParagraphProperties props) { | |||
if (props.isSetTabLst()) { | |||
final List<XSLFTabStop> list = new ArrayList<>(); | |||
for (final CTTextTabStop ta : props.getTabLst().getTabArray()) { | |||
list.add(new XSLFTabStop(ta)); | |||
} | |||
setValue(list); | |||
return true; | |||
} | |||
return false; | |||
} | |||
}; | |||
fetchParagraphProperty(fetcher); | |||
return fetcher.getValue(); | |||
} | |||
@Override | |||
public void addTabStops(double positionInPoints, TabStopType tabStopType) { | |||
final XSLFSheet sheet = getParentShape().getSheet(); | |||
final CTTextParagraphProperties tpp; | |||
if (sheet instanceof XSLFSlideMaster) { | |||
tpp = getDefaultMasterStyle(); | |||
} else { | |||
final CTTextParagraph xo = getXmlObject(); | |||
tpp = (xo.isSetPPr()) ? xo.getPPr() : xo.addNewPPr(); | |||
} | |||
final CTTextTabStopList stl = (tpp.isSetTabLst()) ? tpp.getTabLst() : tpp.addNewTabLst(); | |||
XSLFTabStop tab = new XSLFTabStop(stl.addNewTab()); | |||
tab.setPositionInPoints(positionInPoints); | |||
tab.setType(tabStopType); | |||
} | |||
@Override | |||
public void clearTabStops() { | |||
final XSLFSheet sheet = getParentShape().getSheet(); | |||
CTTextParagraphProperties tpp = (sheet instanceof XSLFSlideMaster) ? getDefaultMasterStyle() : getXmlObject().getPPr(); | |||
if (tpp != null && tpp.isSetTabLst()) { | |||
tpp.unsetTabLst(); | |||
} | |||
} | |||
/** | |||
* Helper method for appending text and keeping paragraph and character properties. | |||
* The character properties are moved to the end paragraph marker |
@@ -20,12 +20,16 @@ import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import org.apache.poi.openxml4j.opc.OPCPackage; | |||
import org.apache.poi.openxml4j.opc.PackagePart; | |||
import org.apache.poi.sl.usermodel.BaseTestSlideShow; | |||
import org.apache.poi.sl.usermodel.SlideShow; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
@@ -179,4 +183,25 @@ public class TestXMLSlideShow extends BaseTestSlideShow { | |||
xmlComments.close(); | |||
xml.close(); | |||
} | |||
public SlideShow<?, ?> reopen(SlideShow<?, ?> show) { | |||
return reopen((XMLSlideShow)show); | |||
} | |||
public static XMLSlideShow reopen(XMLSlideShow show) { | |||
try { | |||
BufAccessBAOS bos = new BufAccessBAOS(); | |||
show.write(bos); | |||
return new XMLSlideShow(new ByteArrayInputStream(bos.getBuf())); | |||
} catch (IOException e) { | |||
fail(e.getMessage()); | |||
return null; | |||
} | |||
} | |||
private static class BufAccessBAOS extends ByteArrayOutputStream { | |||
public byte[] getBuf() { | |||
return buf; | |||
} | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
/* ==================================================================== | |||
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.model.textproperties; | |||
import org.apache.poi.sl.usermodel.TabStop; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.Units; | |||
@Internal | |||
public class HSLFTabStop implements TabStop, Cloneable { | |||
/** | |||
* A signed integer that specifies an offset, in master units, of the tab stop. | |||
* | |||
* If the TextPFException record that contains this TabStop structure also contains a | |||
* leftMargin, then the value of position is relative to the left margin of the paragraph; | |||
* otherwise, the value is relative to the left side of the paragraph. | |||
* | |||
* If a TextRuler record contains this TabStop structure, the value is relative to the | |||
* left side of the text ruler. | |||
*/ | |||
private int position; | |||
/** | |||
* A enumeration that specifies how text aligns at the tab stop. | |||
*/ | |||
private TabStopType type; | |||
public HSLFTabStop(int position, TabStopType type) { | |||
this.position = position; | |||
this.type = type; | |||
} | |||
public int getPosition() { | |||
return position; | |||
} | |||
public void setPosition(final int position) { | |||
this.position = position; | |||
} | |||
@Override | |||
public double getPositionInPoints() { | |||
return Units.masterToPoints(getPosition()); | |||
} | |||
@Override | |||
public void setPositionInPoints(final double points) { | |||
setPosition(Units.pointsToMaster(points)); | |||
} | |||
@Override | |||
public TabStopType getType() { | |||
return type; | |||
} | |||
@Override | |||
public void setType(TabStopType type) { | |||
this.type = type; | |||
} | |||
@Override | |||
public HSLFTabStop clone() { | |||
try { | |||
return (HSLFTabStop)super.clone(); | |||
} catch (CloneNotSupportedException e) { | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
@Override | |||
public int hashCode() { | |||
final int prime = 31; | |||
int result = 1; | |||
result = prime * result + position; | |||
result = prime * result + ((type == null) ? 0 : type.hashCode()); | |||
return result; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (this == obj) { | |||
return true; | |||
} | |||
if (!(obj instanceof HSLFTabStop)) { | |||
return false; | |||
} | |||
HSLFTabStop other = (HSLFTabStop) obj; | |||
if (position != other.position) { | |||
return false; | |||
} | |||
if (type != other.type) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
@Override | |||
public String toString() { | |||
return type + " @ " + position; | |||
} | |||
} |
@@ -0,0 +1,158 @@ | |||
/* ==================================================================== | |||
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.model.textproperties; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.sl.usermodel.TabStop.TabStopType; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianByteArrayInputStream; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInput; | |||
import org.apache.poi.util.LittleEndianOutput; | |||
import org.apache.poi.util.LittleEndianOutputStream; | |||
/** | |||
* Container for tabstop lists | |||
*/ | |||
@Internal | |||
public class HSLFTabStopPropCollection extends TextProp { | |||
public static final String NAME = "tabStops"; | |||
private final List<HSLFTabStop> tabStops = new ArrayList<>(); | |||
public HSLFTabStopPropCollection() { | |||
super(0, 0x100000, NAME); | |||
} | |||
public HSLFTabStopPropCollection(final HSLFTabStopPropCollection copy) { | |||
super(0, copy.getMask(), copy.getName()); | |||
for (HSLFTabStop ts : copy.tabStops) { | |||
tabStops.add(ts.clone()); | |||
} | |||
} | |||
/** | |||
* Parses the tabstops from TxMasterStyle record | |||
* | |||
* @param data the data stream | |||
* @param offset the offset within the data | |||
*/ | |||
public void parseProperty(byte data[], int offset) { | |||
tabStops.addAll(readTabStops(new LittleEndianByteArrayInputStream(data, offset))); | |||
} | |||
public static List<HSLFTabStop> readTabStops(final LittleEndianInput lei) { | |||
final int count = lei.readUShort(); | |||
final List<HSLFTabStop> tabs = new ArrayList<>(count); | |||
for (int i=0; i<count; i++) { | |||
final int position = lei.readShort(); | |||
final TabStopType type = TabStopType.fromNativeId(lei.readShort()); | |||
tabs.add(new HSLFTabStop(position, type)); | |||
} | |||
return tabs; | |||
} | |||
public void writeProperty(OutputStream out) { | |||
writeTabStops(new LittleEndianOutputStream(out), tabStops); | |||
} | |||
public static void writeTabStops(final LittleEndianOutput leo, List<HSLFTabStop> tabStops) { | |||
final int count = tabStops.size(); | |||
leo.writeShort(count); | |||
for (HSLFTabStop ts : tabStops) { | |||
leo.writeShort(ts.getPosition()); | |||
leo.writeShort(ts.getType().nativeId); | |||
} | |||
} | |||
@Override | |||
public int getValue() { return tabStops.size(); } | |||
@Override | |||
public int getSize() { | |||
return LittleEndianConsts.SHORT_SIZE + tabStops.size()*LittleEndianConsts.INT_SIZE; | |||
} | |||
public List<HSLFTabStop> getTabStops() { | |||
return tabStops; | |||
} | |||
public void clearTabs() { | |||
tabStops.clear(); | |||
} | |||
public void addTabStop(HSLFTabStop ts) { | |||
tabStops.add(ts); | |||
} | |||
@Override | |||
public HSLFTabStopPropCollection clone() { | |||
return new HSLFTabStopPropCollection(this); | |||
} | |||
@Override | |||
public int hashCode() { | |||
final int prime = 31; | |||
int result = super.hashCode(); | |||
result = prime * result | |||
+ ((tabStops == null) ? 0 : tabStops.hashCode()); | |||
return result; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (this == obj) { | |||
return true; | |||
} | |||
if (!(obj instanceof HSLFTabStopPropCollection)) { | |||
return false; | |||
} | |||
HSLFTabStopPropCollection other = (HSLFTabStopPropCollection) obj; | |||
if (!super.equals(other)) { | |||
return false; | |||
} | |||
return tabStops.equals(other.tabStops); | |||
} | |||
@Override | |||
public String toString() { | |||
final StringBuilder sb = new StringBuilder(super.toString()); | |||
sb.append(" [ "); | |||
boolean isFirst = true; | |||
for (HSLFTabStop tabStop : tabStops) { | |||
if (!isFirst) { | |||
sb.append(", "); | |||
} | |||
sb.append(tabStop.getType()); | |||
sb.append(" @ "); | |||
sb.append(tabStop.getPosition()); | |||
isFirst = false; | |||
} | |||
sb.append(" ]"); | |||
return sb.toString(); | |||
} | |||
} |
@@ -1,123 +0,0 @@ | |||
/* ==================================================================== | |||
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.model.textproperties; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
/** | |||
* Container for tabstop lists | |||
*/ | |||
public class TabStopPropCollection extends TextProp { | |||
public enum TabStopType { | |||
LEFT(0), CENTER(1), RIGHT(2), DECIMAL(3); | |||
private final int val; | |||
TabStopType(int val) { | |||
this.val = val; | |||
} | |||
public static TabStopType fromRecordVal(int val) { | |||
for (TabStopType tst : values()) { | |||
if (tst.val == val) return tst; | |||
} | |||
return LEFT; | |||
} | |||
} | |||
public static class TabStop { | |||
/** | |||
* If the TextPFException record that contains this TabStop structure also contains a | |||
* leftMargin, then the value of position is relative to the left margin of the paragraph; | |||
* otherwise, the value is relative to the left side of the paragraph. | |||
* | |||
* If a TextRuler record contains this TabStop structure, the value is relative to the | |||
* left side of the text ruler. | |||
*/ | |||
private int position; | |||
/** | |||
* A enumeration that specifies how text aligns at the tab stop. | |||
*/ | |||
private TabStopType type; | |||
public TabStop(int position, TabStopType type) { | |||
this.position = position; | |||
this.type = type; | |||
} | |||
public int getPosition() { | |||
return position; | |||
} | |||
public void setPosition(int position) { | |||
this.position = position; | |||
} | |||
public TabStopType getType() { | |||
return type; | |||
} | |||
public void setType(TabStopType type) { | |||
this.type = type; | |||
} | |||
} | |||
private List<TabStop> tabStops = new ArrayList<>(); | |||
public TabStopPropCollection() { | |||
super(0, 0x100000, "tabStops"); | |||
} | |||
/** | |||
* Parses the tabstops from TxMasterStyle record | |||
* | |||
* @param data the data stream | |||
* @param offset the offset within the data | |||
*/ | |||
public void parseProperty(byte data[], int offset) { | |||
int count = LittleEndian.getUShort(data, offset); | |||
int off = offset + LittleEndianConsts.SHORT_SIZE; | |||
for (int i=0; i<count; i++) { | |||
int position = LittleEndian.getShort(data, off); | |||
off += LittleEndianConsts.SHORT_SIZE; | |||
int recVal = LittleEndian.getShort(data, off); | |||
TabStopType type = TabStopType.fromRecordVal(recVal); | |||
off += LittleEndianConsts.SHORT_SIZE; | |||
tabStops.add(new TabStop(position, type)); | |||
} | |||
} | |||
@Override | |||
public int getSize() { | |||
return LittleEndianConsts.SHORT_SIZE + tabStops.size()*LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public TabStopPropCollection clone() { | |||
TabStopPropCollection other = (TabStopPropCollection)super.clone(); | |||
other.tabStops = new ArrayList<>(); | |||
for (TabStop ts : tabStops) { | |||
TabStop tso = new TabStop(ts.getPosition(), ts.getType()); | |||
other.tabStops.add(tso); | |||
} | |||
return other; | |||
} | |||
} |
@@ -96,7 +96,7 @@ public class TextProp implements Cloneable { | |||
try { | |||
return (TextProp)super.clone(); | |||
} catch(CloneNotSupportedException e) { | |||
throw new InternalError(e.getMessage()); | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
@@ -145,11 +145,11 @@ public class TextProp implements Cloneable { | |||
@Override | |||
public String toString() { | |||
int len; | |||
switch (sizeOfDataBlock) { | |||
switch (getSize()) { | |||
case 1: len = 4; break; | |||
case 2: len = 6; break; | |||
default: len = 10; break; | |||
} | |||
return String.format(Locale.ROOT, "%s = %d (%0#"+len+"X mask / %d bytes)", propName, dataValue, maskInHeader, sizeOfDataBlock); | |||
return String.format(Locale.ROOT, "%s = %d (%0#"+len+"X mask / %d bytes)", getName(), getValue(), getMask(), getSize()); | |||
} | |||
} |
@@ -58,7 +58,7 @@ public class TextPropCollection { | |||
// 0x200 - Undefined and MUST be ignored | |||
new TextProp(2, 0x400, "bullet.offset"), // indent | |||
new TextProp(2, 0x8000, "defaultTabSize"), | |||
new TabStopPropCollection(), // tabstops size is variable! | |||
new HSLFTabStopPropCollection(), // tabstops size is variable! | |||
new FontAlignmentProp(), | |||
new WrapFlagsTextProp(), | |||
new TextProp(2, 0x200000, "textDirection"), | |||
@@ -130,12 +130,14 @@ public class TextPropCollection { | |||
} | |||
/** Fetch the TextProp with this name, or null if it isn't present */ | |||
public final TextProp findByName(String textPropName) { | |||
return textProps.get(textPropName); | |||
@SuppressWarnings("unchecked") | |||
public final <T extends TextProp> T findByName(String textPropName) { | |||
return (T)textProps.get(textPropName); | |||
} | |||
public final TextProp removeByName(String name) { | |||
return textProps.remove(name); | |||
@SuppressWarnings("unchecked") | |||
public final <T extends TextProp> T removeByName(String name) { | |||
return (T)textProps.remove(name); | |||
} | |||
public final TextPropType getTextPropType() { | |||
@@ -153,10 +155,11 @@ public class TextPropCollection { | |||
* @param name the property name | |||
* @return if found, the property template to copy from | |||
*/ | |||
private TextProp validatePropName(String name) { | |||
@SuppressWarnings("unchecked") | |||
private <T extends TextProp> T validatePropName(final String name) { | |||
for (TextProp tp : getPotentialProperties()) { | |||
if (tp.getName().equals(name)) { | |||
return tp; | |||
return (T)tp; | |||
} | |||
} | |||
String errStr = | |||
@@ -166,13 +169,14 @@ public class TextPropCollection { | |||
} | |||
/** Add the TextProp with this name to the list */ | |||
public final TextProp addWithName(String name) { | |||
@SuppressWarnings("unchecked") | |||
public final <T extends TextProp> T addWithName(final String name) { | |||
// Find the base TextProp to base on | |||
TextProp existing = findByName(name); | |||
T existing = findByName(name); | |||
if (existing != null) return existing; | |||
// Add a copy of this property | |||
TextProp textProp = validatePropName(name).clone(); | |||
T textProp = (T)validatePropName(name).clone(); | |||
textProps.put(name,textProp); | |||
return textProp; | |||
} | |||
@@ -218,11 +222,13 @@ public class TextPropCollection { | |||
// Bingo, data contains this property | |||
TextProp prop = tp.clone(); | |||
int val = 0; | |||
if (prop.getSize() == 2) { | |||
if (prop instanceof HSLFTabStopPropCollection) { | |||
((HSLFTabStopPropCollection)prop).parseProperty(data, dataOffset+bytesPassed); | |||
} else if (prop.getSize() == 2) { | |||
val = LittleEndian.getShort(data,dataOffset+bytesPassed); | |||
} else if(prop.getSize() == 4) { | |||
val = LittleEndian.getInt(data,dataOffset+bytesPassed); | |||
} else if (prop.getSize() == 0 && !(prop instanceof TabStopPropCollection)) { | |||
} else if (prop.getSize() == 0) { | |||
//remember "special" bits. | |||
maskSpecial |= tp.getMask(); | |||
continue; | |||
@@ -230,9 +236,7 @@ public class TextPropCollection { | |||
if (prop instanceof BitMaskTextProp) { | |||
((BitMaskTextProp)prop).setValueWithMask(val, containsField); | |||
} else if (prop instanceof TabStopPropCollection) { | |||
((TabStopPropCollection)prop).parseProperty(data, dataOffset+bytesPassed); | |||
} else { | |||
} else if (!(prop instanceof HSLFTabStopPropCollection)) { | |||
prop.setValue(val); | |||
} | |||
bytesPassed += prop.getSize(); | |||
@@ -311,6 +315,8 @@ public class TextPropCollection { | |||
StyleTextPropAtom.writeLittleEndian((short)val,o); | |||
} else if (textProp.getSize() == 4) { | |||
StyleTextPropAtom.writeLittleEndian(val,o); | |||
} else if (textProp instanceof HSLFTabStopPropCollection) { | |||
((HSLFTabStopPropCollection)textProp).writeProperty(o); | |||
} | |||
} | |||
} | |||
@@ -359,8 +365,9 @@ public class TextPropCollection { | |||
out.append(" indent level: "+getIndentLevel()+"\n"); | |||
} | |||
for(TextProp p : getTextPropList()) { | |||
out.append(" " + p.getName() + " = " + p.getValue() ); | |||
out.append(" (0x" + HexDump.toHex(p.getValue()) + ")\n"); | |||
out.append(" "); | |||
out.append(p.toString()); | |||
out.append("\n"); | |||
if (p instanceof BitMaskTextProp) { | |||
BitMaskTextProp bm = (BitMaskTextProp)p; | |||
int i = 0; |
@@ -17,11 +17,21 @@ | |||
package org.apache.poi.hslf.record; | |||
import static org.apache.poi.util.BitFieldFactory.getInstance; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.hslf.model.textproperties.HSLFTabStop; | |||
import org.apache.poi.hslf.model.textproperties.HSLFTabStopPropCollection; | |||
import org.apache.poi.util.BitField; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianByteArrayInputStream; | |||
import org.apache.poi.util.LittleEndianOutputStream; | |||
import org.apache.poi.util.POILogger; | |||
/** | |||
@@ -31,33 +41,38 @@ public final class TextRulerAtom extends RecordAtom { | |||
//arbitrarily selected; may need to increase | |||
private static final int MAX_RECORD_LENGTH = 100_000; | |||
private static final BitField DEFAULT_TAB_SIZE = getInstance(0x0001); | |||
private static final BitField C_LEVELS = getInstance(0x0002); | |||
private static final BitField TAB_STOPS = getInstance(0x0004); | |||
private static final BitField[] LEFT_MARGIN = { | |||
getInstance(0x0008), getInstance(0x0010), getInstance(0x0020), | |||
getInstance(0x0040), getInstance(0x0080), | |||
}; | |||
private static final BitField[] INDENT = { | |||
getInstance(0x0100), getInstance(0x0200), getInstance(0x0400), | |||
getInstance(0x0800), getInstance(0x1000), | |||
}; | |||
/** | |||
* Record header. | |||
*/ | |||
private byte[] _header; | |||
/** | |||
* Record data. | |||
*/ | |||
private byte[] _data; | |||
private final byte[] _header = new byte[8]; | |||
//ruler internals | |||
private int defaultTabSize; | |||
private int numLevels; | |||
private int[] tabStops; | |||
private int[] bulletOffsets = new int[5]; | |||
private int[] textOffsets = new int[5]; | |||
private Integer defaultTabSize; | |||
private Integer numLevels; | |||
private final List<HSLFTabStop> tabStops = new ArrayList<>(); | |||
//bullet.offset | |||
private final Integer[] leftMargin = new Integer[5]; | |||
//text.offset | |||
private final Integer[] indent = new Integer[5]; | |||
/** | |||
* Constructs a new empty ruler atom. | |||
*/ | |||
public TextRulerAtom() { | |||
_header = new byte[8]; | |||
_data = new byte[0]; | |||
LittleEndian.putShort(_header, 2, (short)getRecordType()); | |||
LittleEndian.putInt(_header, 4, _data.length); | |||
} | |||
/** | |||
@@ -68,18 +83,17 @@ public final class TextRulerAtom extends RecordAtom { | |||
* @param start the start offset into the byte array. | |||
* @param len the length of the slice in the byte array. | |||
*/ | |||
protected TextRulerAtom(byte[] source, int start, int len) { | |||
// Get the header. | |||
_header = new byte[8]; | |||
System.arraycopy(source,start,_header,0,8); | |||
// Get the record data. | |||
_data = IOUtils.safelyAllocate(len-8, MAX_RECORD_LENGTH); | |||
System.arraycopy(source,start+8,_data,0,len-8); | |||
protected TextRulerAtom(final byte[] source, final int start, final int len) { | |||
final LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(source, start, Math.min(len, MAX_RECORD_LENGTH)); | |||
try { | |||
read(); | |||
} catch (Exception e){ | |||
// Get the header. | |||
leis.read(_header); | |||
// Get the record data. | |||
read(leis); | |||
} catch (IOException e){ | |||
logger.log(POILogger.ERROR, "Failed to parse TextRulerAtom: " + e.getMessage()); | |||
} | |||
} | |||
@@ -89,6 +103,7 @@ public final class TextRulerAtom extends RecordAtom { | |||
* | |||
* @return the record type. | |||
*/ | |||
@Override | |||
public long getRecordType() { | |||
return RecordTypes.TextRulerAtom.typeID; | |||
} | |||
@@ -100,109 +115,110 @@ public final class TextRulerAtom extends RecordAtom { | |||
* @param out the output stream to write to. | |||
* @throws java.io.IOException if an error occurs. | |||
*/ | |||
public void writeOut(OutputStream out) throws IOException { | |||
@Override | |||
public void writeOut(final OutputStream out) throws IOException { | |||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(200); | |||
final LittleEndianOutputStream lbos = new LittleEndianOutputStream(bos); | |||
int mask = 0; | |||
mask |= writeIf(lbos, numLevels, C_LEVELS); | |||
mask |= writeIf(lbos, defaultTabSize, DEFAULT_TAB_SIZE); | |||
mask |= writeIf(lbos, tabStops, TAB_STOPS); | |||
for (int i=0; i<5; i++) { | |||
mask |= writeIf(lbos, leftMargin[i], LEFT_MARGIN[i]); | |||
mask |= writeIf(lbos, indent[i], INDENT[i]); | |||
} | |||
LittleEndian.putInt(_header, 4, bos.size()+4); | |||
out.write(_header); | |||
out.write(_data); | |||
LittleEndian.putUShort(mask, out); | |||
LittleEndian.putUShort(0, out); | |||
bos.writeTo(out); | |||
} | |||
private static int writeIf(final LittleEndianOutputStream lbos, Integer value, BitField bit) { | |||
boolean isSet = false; | |||
if (value != null) { | |||
lbos.writeShort(value); | |||
isSet = true; | |||
} | |||
return bit.setBoolean(0, isSet); | |||
} | |||
private static int writeIf(final LittleEndianOutputStream lbos, List<HSLFTabStop> value, BitField bit) { | |||
boolean isSet = false; | |||
if (value != null && !value.isEmpty()) { | |||
HSLFTabStopPropCollection.writeTabStops(lbos, value); | |||
isSet = true; | |||
} | |||
return bit.setBoolean(0, isSet); | |||
} | |||
/** | |||
* Read the record bytes and initialize the internal variables | |||
*/ | |||
private void read(){ | |||
int pos = 0; | |||
short mask = LittleEndian.getShort(_data); pos += 4; | |||
short val; | |||
int[] bits = {1, 0, 2, 3, 8, 4, 9, 5, 10, 6, 11, 7, 12}; | |||
for (int i = 0; i < bits.length; i++) { | |||
if((mask & 1 << bits[i]) != 0){ | |||
switch (bits[i]){ | |||
case 0: | |||
//defaultTabSize | |||
defaultTabSize = LittleEndian.getShort(_data, pos); pos += 2; | |||
break; | |||
case 1: | |||
//numLevels | |||
numLevels = LittleEndian.getShort(_data, pos); pos += 2; | |||
break; | |||
case 2: | |||
//tabStops | |||
val = LittleEndian.getShort(_data, pos); pos += 2; | |||
tabStops = new int[val*2]; | |||
for (int j = 0; j < tabStops.length; j++) { | |||
tabStops[j] = LittleEndian.getUShort(_data, pos); pos += 2; | |||
} | |||
break; | |||
case 3: | |||
case 4: | |||
case 5: | |||
case 6: | |||
case 7: | |||
//bullet.offset | |||
val = LittleEndian.getShort(_data, pos); pos += 2; | |||
bulletOffsets[bits[i]-3] = val; | |||
break; | |||
case 8: | |||
case 9: | |||
case 10: | |||
case 11: | |||
case 12: | |||
//text.offset | |||
val = LittleEndian.getShort(_data, pos); pos += 2; | |||
textOffsets[bits[i]-8] = val; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
private void read(final LittleEndianByteArrayInputStream leis) { | |||
final int mask = leis.readInt(); | |||
numLevels = readIf(leis, mask, C_LEVELS); | |||
defaultTabSize = readIf(leis, mask, DEFAULT_TAB_SIZE); | |||
if (TAB_STOPS.isSet(mask)) { | |||
tabStops.addAll(HSLFTabStopPropCollection.readTabStops(leis)); | |||
} | |||
for (int i=0; i<5; i++) { | |||
leftMargin[i] = readIf(leis, mask, LEFT_MARGIN[i]); | |||
indent[i] = readIf(leis, mask, INDENT[i]); | |||
} | |||
} | |||
private static Integer readIf(final LittleEndianByteArrayInputStream leis, final int mask, final BitField bit) { | |||
return (bit.isSet(mask)) ? (int)leis.readShort() : null; | |||
} | |||
/** | |||
* Default distance between tab stops, in master coordinates (576 dpi). | |||
*/ | |||
public int getDefaultTabSize(){ | |||
return defaultTabSize; | |||
return defaultTabSize == null ? 0 : defaultTabSize; | |||
} | |||
/** | |||
* Number of indent levels (maximum 5). | |||
*/ | |||
public int getNumberOfLevels(){ | |||
return numLevels; | |||
return numLevels == null ? 0 : numLevels; | |||
} | |||
/** | |||
* Default distance between tab stops, in master coordinates (576 dpi). | |||
*/ | |||
public int[] getTabStops(){ | |||
public List<HSLFTabStop> getTabStops(){ | |||
return tabStops; | |||
} | |||
/** | |||
* Paragraph's distance from shape's left margin, in master coordinates (576 dpi). | |||
*/ | |||
public int[] getTextOffsets(){ | |||
return textOffsets; | |||
public Integer[] getTextOffsets(){ | |||
return indent; | |||
} | |||
/** | |||
* First line of paragraph's distance from shape's left margin, in master coordinates (576 dpi). | |||
*/ | |||
public int[] getBulletOffsets(){ | |||
return bulletOffsets; | |||
public Integer[] getBulletOffsets(){ | |||
return leftMargin; | |||
} | |||
public static TextRulerAtom getParagraphInstance(){ | |||
byte[] data = new byte[] { | |||
0x00, 0x00, (byte)0xA6, 0x0F, 0x0A, 0x00, 0x00, 0x00, | |||
0x10, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01 | |||
}; | |||
return new TextRulerAtom(data, 0, data.length); | |||
final TextRulerAtom tra = new TextRulerAtom(); | |||
tra.indent[0] = 249; | |||
tra.indent[1] = tra.leftMargin[1] = 321; | |||
return tra; | |||
} | |||
public void setParagraphIndent(short tetxOffset, short bulletOffset){ | |||
LittleEndian.putShort(_data, 4, tetxOffset); | |||
LittleEndian.putShort(_data, 6, bulletOffset); | |||
LittleEndian.putShort(_data, 8, bulletOffset); | |||
public void setParagraphIndent(short leftMargin, short indent) { | |||
Arrays.fill(this.leftMargin, null); | |||
Arrays.fill(this.indent, null); | |||
this.leftMargin[0] = (int)leftMargin; | |||
this.indent[0] = (int)indent; | |||
this.indent[1] = (int)indent; | |||
} | |||
} |
@@ -17,16 +17,13 @@ | |||
package org.apache.poi.hslf.usermodel; | |||
import org.apache.poi.hslf.model.textproperties.TextPropCollection; | |||
import org.apache.poi.hslf.record.SheetContainer; | |||
import org.apache.poi.hslf.model.textproperties.TextProp; | |||
import org.apache.poi.hslf.record.TextHeaderAtom; | |||
import org.apache.poi.sl.usermodel.MasterSheet; | |||
/** | |||
* The superclass of all master sheets - Slide masters, Notes masters, etc. | |||
* | |||
* For now it's empty. When we understand more about masters in ppt we will add the common functionality here. | |||
* | |||
* @author Yegor Kozlov | |||
*/ | |||
public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet<HSLFShape,HSLFTextParagraph> { | |||
public HSLFMasterSheet(SheetContainer container, int sheetNo){ | |||
@@ -34,10 +31,19 @@ public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet<H | |||
} | |||
/** | |||
* Pickup a style attribute from the master. | |||
* This is the "workhorse" which returns the default style attrubutes. | |||
* Find the master collection for the given txtype/level/name. | |||
* This is the "workhorse" which returns the default style attributes. | |||
* If {@code name = "*"} return the current collection, otherwise if the name is not found | |||
* in the current selection of txtype/level/name, first try lower levels then try parent types, | |||
* if it wasn't found there return {@code null}. | |||
* | |||
* @param txtype the {@link TextHeaderAtom} type | |||
* @param level the indent level of the paragraph, if the level is not defined for the found | |||
* collection, the highest existing level will be used | |||
* @param name the property name, | |||
* @param isCharacter if {@code true} use character styles, otherwise use paragraph styles | |||
*/ | |||
public abstract TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) ; | |||
public abstract TextPropCollection getPropCollection(int txtype, int level, String name, boolean isCharacter); | |||
/** |
@@ -74,42 +74,50 @@ public final class HSLFSlideMaster extends HSLFMasterSheet { | |||
} | |||
/** | |||
* Pickup a style attribute from the master. | |||
* Find the master collection for the given txtype/level/name. | |||
* This is the "workhorse" which returns the default style attributes. | |||
* If {@code name = "*"} return the current collection, otherwise if the name is not found | |||
* in the current selection of txtype/level/name, first try lower levels then try parent types, | |||
* if it wasn't found there return {@code null}. | |||
* | |||
* @param txtype the {@link TextHeaderAtom} type | |||
* @param level the indent level of the paragraph, if the level is not defined for the found | |||
* collection, the highest existing level will be used | |||
* @param name the property name, | |||
* @param isCharacter if {@code true} use character styles, otherwise use paragraph styles | |||
*/ | |||
@Override | |||
public TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) { | |||
if (_txmaster.length <= txtype) { | |||
return null; | |||
} | |||
TxMasterStyleAtom t = _txmaster[txtype]; | |||
List<TextPropCollection> styles = isCharacter ? t.getCharacterStyles() : t.getParagraphStyles(); | |||
TextProp prop = null; | |||
for (int i = Math.min(level, styles.size()-1); prop == null && i >= 0; i--) { | |||
prop = styles.get(i).findByName(name); | |||
public TextPropCollection getPropCollection(final int txtype, final int level, final String name, final boolean isCharacter) { | |||
if (txtype < _txmaster.length) { | |||
final TxMasterStyleAtom t = _txmaster[txtype]; | |||
final List<TextPropCollection> styles = isCharacter ? t.getCharacterStyles() : t.getParagraphStyles(); | |||
// TODO: what is the reaction for readOnly=false and styles.isEmpty()? | |||
final int minLevel = Math.min(level, styles.size()-1); | |||
if ("*".equals(name)) { | |||
return styles.get(minLevel); | |||
} | |||
for (int i=minLevel; i >= 0; i--) { | |||
final TextPropCollection col = styles.get(i); | |||
final TextProp tp = col.findByName(name); | |||
if (tp != null) { | |||
return col; | |||
} | |||
} | |||
} | |||
if (prop != null) { | |||
return prop; | |||
} | |||
switch (txtype) { | |||
case TextHeaderAtom.CENTRE_BODY_TYPE: | |||
case TextHeaderAtom.HALF_BODY_TYPE: | |||
case TextHeaderAtom.QUARTER_BODY_TYPE: | |||
txtype = TextHeaderAtom.BODY_TYPE; | |||
break; | |||
return getPropCollection(TextHeaderAtom.BODY_TYPE, level, name, isCharacter); | |||
case TextHeaderAtom.CENTER_TITLE_TYPE: | |||
txtype = TextHeaderAtom.TITLE_TYPE; | |||
break; | |||
return getPropCollection(TextHeaderAtom.TITLE_TYPE, level, name, isCharacter); | |||
default: | |||
return null; | |||
} | |||
return getStyleAttribute(txtype, level, name, isCharacter); | |||
} | |||
/** | |||
* Assign SlideShow for this slide master. | |||
*/ | |||
@@ -132,7 +140,7 @@ public final class HSLFSlideMaster extends HSLFMasterSheet { | |||
_txmaster[txType] = txrec[i]; | |||
} | |||
} | |||
for (List<HSLFTextParagraph> paras : getTextParagraphs()) { | |||
for (HSLFTextParagraph htp : paras) { | |||
int txType = htp.getRunType(); | |||
@@ -148,11 +156,6 @@ public final class HSLFSlideMaster extends HSLFMasterSheet { | |||
charStyles.size() <= level || paragraphStyles.size() <= level) { | |||
throw new HSLFException("Master styles not initialized"); | |||
} | |||
htp.setMasterStyleReference(paragraphStyles.get(level)); | |||
for (HSLFTextRun htr : htp.getTextRuns()) { | |||
htr.setMasterStyleReference(charStyles.get(level)); | |||
} | |||
} | |||
} | |||
} |
@@ -24,12 +24,16 @@ import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.function.Consumer; | |||
import java.util.stream.Collectors; | |||
import org.apache.poi.common.usermodel.fonts.FontGroup; | |||
import org.apache.poi.common.usermodel.fonts.FontInfo; | |||
import org.apache.poi.hslf.exceptions.HSLFException; | |||
import org.apache.poi.hslf.model.textproperties.BitMaskTextProp; | |||
import org.apache.poi.hslf.model.textproperties.FontAlignmentProp; | |||
import org.apache.poi.hslf.model.textproperties.HSLFTabStop; | |||
import org.apache.poi.hslf.model.textproperties.HSLFTabStopPropCollection; | |||
import org.apache.poi.hslf.model.textproperties.IndentProp; | |||
import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp; | |||
import org.apache.poi.hslf.model.textproperties.TextAlignmentProp; | |||
@@ -37,34 +41,15 @@ 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.ColorSchemeAtom; | |||
import org.apache.poi.hslf.record.EscherTextboxWrapper; | |||
import org.apache.poi.hslf.record.InteractiveInfo; | |||
import org.apache.poi.hslf.record.MasterTextPropAtom; | |||
import org.apache.poi.hslf.record.OutlineTextRefAtom; | |||
import org.apache.poi.hslf.record.PPDrawing; | |||
import org.apache.poi.hslf.record.Record; | |||
import org.apache.poi.hslf.record.RecordContainer; | |||
import org.apache.poi.hslf.record.RecordTypes; | |||
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12; | |||
import org.apache.poi.hslf.record.SlideListWithText; | |||
import org.apache.poi.hslf.record.SlidePersistAtom; | |||
import org.apache.poi.hslf.record.StyleTextProp9Atom; | |||
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.hslf.record.TextRulerAtom; | |||
import org.apache.poi.hslf.record.TextSpecInfoAtom; | |||
import org.apache.poi.hslf.record.TxInteractiveInfoAtom; | |||
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.MasterSheet; | |||
import org.apache.poi.sl.usermodel.PaintStyle; | |||
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; | |||
import org.apache.poi.sl.usermodel.Placeholder; | |||
import org.apache.poi.sl.usermodel.TabStop; | |||
import org.apache.poi.sl.usermodel.TabStop.TabStopType; | |||
import org.apache.poi.sl.usermodel.TextParagraph; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
import org.apache.poi.util.StringUtil; | |||
@@ -93,7 +78,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
private TextBytesAtom _byteAtom; | |||
private TextCharsAtom _charAtom; | |||
private TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph); | |||
private TextPropCollection _masterStyle; | |||
protected TextRulerAtom _ruler; | |||
protected final List<HSLFTextRun> _runs = new ArrayList<>(); | |||
@@ -107,6 +91,33 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
private final List<HSLFTextParagraph> parentList; | |||
private class HSLFTabStopDecorator implements TabStop { | |||
final HSLFTabStop tabStop; | |||
HSLFTabStopDecorator(final HSLFTabStop tabStop) { | |||
this.tabStop = tabStop; | |||
} | |||
public double getPositionInPoints() { | |||
return tabStop.getPositionInPoints(); | |||
} | |||
public void setPositionInPoints(double position) { | |||
tabStop.setPositionInPoints(position); | |||
setDirty(); | |||
} | |||
public TabStopType getType() { | |||
return tabStop.getType(); | |||
} | |||
public void setType(TabStopType type) { | |||
tabStop.setType(type); | |||
setDirty(); | |||
} | |||
} | |||
/** | |||
* Constructs a Text Run from a Unicode text block. | |||
* Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided. | |||
@@ -160,18 +171,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
_paragraphStyle.copy(paragraphStyle); | |||
} | |||
/** | |||
* Setting a master style reference | |||
* | |||
* @param paragraphStyle the master style reference | |||
* | |||
* @since POI 3.14-Beta1 | |||
*/ | |||
@Internal | |||
/* package */ void setMasterStyleReference(TextPropCollection masterStyle) { | |||
_masterStyle = masterStyle; | |||
} | |||
/** | |||
* Supply the Sheet we belong to, which might have an assigned SlideShow | |||
* Also passes it on to our child RichTextRuns | |||
@@ -341,7 +340,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
@Override | |||
public Double getLeftMargin() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "text.offset"); | |||
TextProp tp = getPropVal(_paragraphStyle, "text.offset"); | |||
return (tp == null) ? null : Units.masterToPoints(tp.getValue()); | |||
} | |||
@@ -364,7 +363,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
@Override | |||
public Double getIndent() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.offset"); | |||
TextProp tp = getPropVal(_paragraphStyle, "bullet.offset"); | |||
return (tp == null) ? null : Units.masterToPoints(tp.getValue()); | |||
} | |||
@@ -388,7 +387,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
if (fontInfo == null) { | |||
fontInfo = HSLFFontInfoPredefined.ARIAL; | |||
} | |||
return fontInfo.getTypeface(); | |||
} | |||
@@ -422,7 +421,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
@Override | |||
public TextAlign getTextAlign() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "alignment"); | |||
TextProp tp = getPropVal(_paragraphStyle, "alignment"); | |||
if (tp == null) { | |||
return null; | |||
} | |||
@@ -440,7 +439,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
@Override | |||
public FontAlign getFontAlign() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, FontAlignmentProp.NAME); | |||
TextProp tp = getPropVal(_paragraphStyle, FontAlignmentProp.NAME); | |||
if (tp == null) { | |||
return null; | |||
} | |||
@@ -606,7 +605,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* Returns the bullet character | |||
*/ | |||
public Character getBulletChar() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.char"); | |||
TextProp tp = getPropVal(_paragraphStyle, "bullet.char"); | |||
return (tp == null) ? null : (char)tp.getValue(); | |||
} | |||
@@ -637,7 +636,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* Returns the bullet color | |||
*/ | |||
public Color getBulletColor() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.color"); | |||
TextProp tp = getPropVal(_paragraphStyle, "bullet.color"); | |||
boolean hasColor = getFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX); | |||
if (tp == null || !hasColor) { | |||
// if bullet color is undefined, return color of first run | |||
@@ -661,7 +660,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
*/ | |||
public void setBulletFont(String typeface) { | |||
if (typeface == null) { | |||
setPropVal(_paragraphStyle, _masterStyle, "bullet.font", null); | |||
setPropVal(_paragraphStyle, "bullet.font", null); | |||
setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false); | |||
return; | |||
} | |||
@@ -677,7 +676,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* Returns the bullet font | |||
*/ | |||
public String getBulletFont() { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.font"); | |||
TextProp tp = getPropVal(_paragraphStyle, "bullet.font"); | |||
boolean hasFont = getFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX); | |||
if (tp == null || !hasFont) { | |||
return getDefaultFontFamily(); | |||
@@ -723,8 +722,64 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
return null; | |||
} | |||
@Override | |||
public List<? extends TabStop> getTabStops() { | |||
final List<HSLFTabStop> tabStops; | |||
final TextRulerAtom textRuler; | |||
if (getSheet() instanceof HSLFSlideMaster) { | |||
final HSLFTabStopPropCollection tpc = getMasterPropVal(_paragraphStyle, HSLFTabStopPropCollection.NAME); | |||
if (tpc == null) { | |||
return null; | |||
} | |||
tabStops = tpc.getTabStops(); | |||
textRuler = null; | |||
} else { | |||
textRuler = (TextRulerAtom)_headerAtom.getParentRecord().findFirstOfType(RecordTypes.TextRulerAtom.typeID); | |||
if (textRuler == null) { | |||
return null; | |||
} | |||
tabStops = textRuler.getTabStops(); | |||
} | |||
return tabStops.stream().map((tabStop) -> new HSLFTabStopDecorator(tabStop)).collect(Collectors.toList()); | |||
} | |||
@Override | |||
public void addTabStops(final double positionInPoints, final TabStopType tabStopType) { | |||
final HSLFTabStop ts = new HSLFTabStop(0, tabStopType); | |||
ts.setPositionInPoints(positionInPoints); | |||
if (getSheet() instanceof HSLFSlideMaster) { | |||
final Consumer<HSLFTabStopPropCollection> con = (tp) -> tp.addTabStop(ts); | |||
setPropValInner(_paragraphStyle, HSLFTabStopPropCollection.NAME, con); | |||
} else { | |||
final RecordContainer cont = _headerAtom.getParentRecord(); | |||
TextRulerAtom textRuler = (TextRulerAtom)cont.findFirstOfType(RecordTypes.TextRulerAtom.typeID); | |||
if (textRuler == null) { | |||
textRuler = TextRulerAtom.getParagraphInstance(); | |||
cont.appendChildRecord(textRuler); | |||
} | |||
textRuler.getTabStops().add(ts); | |||
} | |||
} | |||
@Override | |||
public void clearTabStops() { | |||
if (getSheet() instanceof HSLFSlideMaster) { | |||
setPropValInner(_paragraphStyle, HSLFTabStopPropCollection.NAME, null); | |||
} else { | |||
final RecordContainer cont = _headerAtom.getParentRecord(); | |||
final TextRulerAtom textRuler = (TextRulerAtom)cont.findFirstOfType(RecordTypes.TextRulerAtom.typeID); | |||
if (textRuler == null) { | |||
return; | |||
} | |||
textRuler.getTabStops().clear(); | |||
} | |||
} | |||
private Double getPctOrPoints(String propName) { | |||
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, propName); | |||
TextProp tp = getPropVal(_paragraphStyle, propName); | |||
if (tp == null) { | |||
return null; | |||
} | |||
@@ -741,7 +796,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
} | |||
private boolean getFlag(int index) { | |||
BitMaskTextProp tp = (BitMaskTextProp)getPropVal(_paragraphStyle, _masterStyle, ParagraphFlagsTextProp.NAME); | |||
BitMaskTextProp tp = getPropVal(_paragraphStyle, ParagraphFlagsTextProp.NAME); | |||
return (tp == null) ? false : tp.getSubValue(index); | |||
} | |||
@@ -759,56 +814,54 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* The propName can be a comma-separated list, in case multiple equivalent values | |||
* are queried | |||
*/ | |||
protected TextProp getPropVal(TextPropCollection props, TextPropCollection masterProps, String propName) { | |||
protected <T extends TextProp> T getPropVal(TextPropCollection props, String propName) { | |||
String propNames[] = propName.split(","); | |||
for (String pn : propNames) { | |||
TextProp prop = props.findByName(pn); | |||
T prop = props.findByName(pn); | |||
if (isValidProp(prop)) { | |||
return prop; | |||
} | |||
} | |||
return getMasterPropVal(props, masterProps, propName); | |||
return getMasterPropVal(props, propName); | |||
} | |||
private TextProp getMasterPropVal(TextPropCollection props, TextPropCollection masterProps, String propName) { | |||
private <T extends TextProp> T getMasterPropVal(final TextPropCollection props, final String propName) { | |||
boolean isChar = props.getTextPropType() == TextPropType.character; | |||
// check if we can delegate to master for the property | |||
if (!isChar) { | |||
BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); | |||
BitMaskTextProp maskProp = props.findByName(ParagraphFlagsTextProp.NAME); | |||
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); | |||
if (hardAttribute) { | |||
return null; | |||
} | |||
} | |||
String propNames[] = propName.split(","); | |||
if (masterProps == null) { | |||
HSLFSheet sheet = getSheet(); | |||
int txtype = getRunType(); | |||
HSLFMasterSheet master = sheet.getMasterSheet(); | |||
final String propNames[] = propName.split(","); | |||
final HSLFSheet sheet = getSheet(); | |||
final int txtype = getRunType(); | |||
final HSLFMasterSheet master; | |||
if (sheet instanceof HSLFMasterSheet) { | |||
master = (HSLFMasterSheet)sheet; | |||
} else { | |||
master = sheet.getMasterSheet(); | |||
if (master == null) { | |||
logger.log(POILogger.WARN, "MasterSheet is not available"); | |||
return null; | |||
} | |||
} | |||
for (String pn : propNames) { | |||
TextProp prop = master.getStyleAttribute(txtype, getIndentLevel(), pn, isChar); | |||
if (isValidProp(prop)) { | |||
return prop; | |||
} | |||
} | |||
} else { | |||
for (String pn : propNames) { | |||
TextProp prop = masterProps.findByName(pn); | |||
for (String pn : propNames) { | |||
TextPropCollection masterProps = master.getPropCollection(txtype, getIndentLevel(), pn, isChar); | |||
if (masterProps != null) { | |||
T prop = masterProps.findByName(pn); | |||
if (isValidProp(prop)) { | |||
return prop; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
@@ -826,22 +879,33 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* @param name the name of the TextProp to fetch/add | |||
* @param val the value, null if unset | |||
*/ | |||
protected void setPropVal(TextPropCollection props, TextPropCollection masterProps, String name, Integer val) { | |||
TextPropCollection pc = props; | |||
if (getSheet() instanceof MasterSheet && masterProps != null) { | |||
pc = masterProps; | |||
protected void setPropVal(final TextPropCollection props, final String name, final Integer val) { | |||
setPropValInner(props, name, val == null ? null : tp -> tp.setValue(val)); | |||
} | |||
private void setPropValInner(final TextPropCollection props, final String name, Consumer<? extends TextProp> handler) { | |||
final boolean isChar = props.getTextPropType() == TextPropType.character; | |||
final TextPropCollection pc; | |||
if (_sheet instanceof HSLFMasterSheet) { | |||
pc = ((HSLFMasterSheet)_sheet).getPropCollection(getRunType(), getIndentLevel(), "*", isChar); | |||
if (pc == null) { | |||
throw new HSLFException("Master text property collection can't be determined."); | |||
} | |||
} else { | |||
pc = props; | |||
} | |||
if (val == null) { | |||
if (handler == null) { | |||
pc.removeByName(name); | |||
return; | |||
} else { | |||
// Fetch / Add the TextProp | |||
handler.accept(pc.addWithName(name)); | |||
} | |||
// Fetch / Add the TextProp | |||
TextProp tp = pc.addWithName(name); | |||
tp.setValue(val); | |||
setDirty(); | |||
} | |||
/** | |||
* Check and add linebreaks to text runs leading other paragraphs | |||
* | |||
@@ -1595,7 +1659,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
* @param val The value to set for the TextProp | |||
*/ | |||
public void setParagraphTextPropVal(String propName, Integer val) { | |||
setPropVal(_paragraphStyle, _masterStyle, propName, val); | |||
setPropVal(_paragraphStyle, propName, val); | |||
setDirty(); | |||
} | |||
@@ -36,7 +36,6 @@ import org.apache.poi.sl.usermodel.Placeholder; | |||
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.util.Internal; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
@@ -61,8 +60,6 @@ public final class HSLFTextRun implements TextRun { | |||
*/ | |||
private TextPropCollection characterStyle = new TextPropCollection(1, TextPropType.character); | |||
private TextPropCollection masterStyle; | |||
/** | |||
* Create a new wrapper around a rich text string | |||
* @param parentParagraph the parent paragraph | |||
@@ -80,19 +77,6 @@ public final class HSLFTextRun implements TextRun { | |||
this.characterStyle.updateTextSize(_runText.length()); | |||
} | |||
/** | |||
* Setting a master style reference | |||
* | |||
* @param characterStyle the master style reference | |||
* | |||
* @since POI 3.14-Beta1 | |||
*/ | |||
@Internal | |||
/* package */ void setMasterStyleReference(TextPropCollection masterStyle) { | |||
this.masterStyle = masterStyle; | |||
} | |||
/** | |||
* Supply the SlideShow we belong to | |||
*/ | |||
@@ -149,28 +133,34 @@ public final class HSLFTextRun implements TextRun { | |||
} | |||
protected boolean getFlag(int index) { | |||
if (characterStyle == null) { | |||
return false; | |||
} | |||
BitMaskTextProp prop = (BitMaskTextProp)characterStyle.findByName(CharFlagsTextProp.NAME); | |||
BitMaskTextProp prop = (characterStyle == null) ? null : characterStyle.findByName(CharFlagsTextProp.NAME); | |||
if (prop == null || !prop.getSubPropMatches()[index]) { | |||
int txtype = parentParagraph.getRunType(); | |||
HSLFSheet sheet = parentParagraph.getSheet(); | |||
if (sheet != null) { | |||
HSLFMasterSheet master = sheet.getMasterSheet(); | |||
if (master != null){ | |||
prop = (BitMaskTextProp)master.getStyleAttribute(txtype, parentParagraph.getIndentLevel(), CharFlagsTextProp.NAME, true); | |||
} | |||
} else { | |||
logger.log(POILogger.WARN, "MasterSheet is not available"); | |||
} | |||
prop = getMasterProp(CharFlagsTextProp.NAME); | |||
} | |||
return prop == null ? false : prop.getSubValue(index); | |||
} | |||
private <T extends TextProp> T getMasterProp(final String name) { | |||
final int txtype = parentParagraph.getRunType(); | |||
final HSLFSheet sheet = parentParagraph.getSheet(); | |||
if (sheet == null) { | |||
logger.log(POILogger.ERROR, "Sheet is not available"); | |||
return null; | |||
} | |||
final HSLFMasterSheet master = sheet.getMasterSheet(); | |||
if (master == null) { | |||
logger.log(POILogger.WARN, "MasterSheet is not available"); | |||
return null; | |||
} | |||
final TextPropCollection col = master.getPropCollection(txtype, parentParagraph.getIndentLevel(), name, true); | |||
return (col == null) ? null : col.findByName(name); | |||
} | |||
/** | |||
* Set the value of the given flag in the CharFlagsTextProp, adding | |||
* it if required. | |||
@@ -189,8 +179,7 @@ public final class HSLFTextRun implements TextRun { | |||
* @param val The value to set for the TextProp | |||
*/ | |||
public void setCharTextPropVal(String propName, Integer val) { | |||
getTextParagraph().setPropVal(characterStyle, masterStyle, propName, val); | |||
getTextParagraph().setDirty(); | |||
getTextParagraph().setPropVal(characterStyle, propName, val); | |||
} | |||
@@ -270,7 +259,7 @@ public final class HSLFTextRun implements TextRun { | |||
* @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript | |||
*/ | |||
public int getSuperscript() { | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "superscript"); | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, "superscript"); | |||
return tp == null ? 0 : tp.getValue(); | |||
} | |||
@@ -285,7 +274,7 @@ public final class HSLFTextRun implements TextRun { | |||
@Override | |||
public Double getFontSize() { | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.size"); | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.size"); | |||
return tp == null ? null : (double)tp.getValue(); | |||
} | |||
@@ -300,7 +289,7 @@ public final class HSLFTextRun implements TextRun { | |||
* Gets the font index | |||
*/ | |||
public int getFontIndex() { | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.index"); | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.index"); | |||
return tp == null ? -1 : tp.getValue(); | |||
} | |||
@@ -401,7 +390,7 @@ public final class HSLFTextRun implements TextRun { | |||
break; | |||
} | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, propName); | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, propName); | |||
return (tp != null) ? slideShow.getFont(tp.getValue()) : null; | |||
} | |||
@@ -410,7 +399,7 @@ public final class HSLFTextRun implements TextRun { | |||
*/ | |||
@Override | |||
public SolidPaint getFontColor() { | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.color"); | |||
TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.color"); | |||
if (tp == null) { | |||
return null; | |||
} |
@@ -20,7 +20,7 @@ package org.apache.poi.hslf.usermodel; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hslf.model.textproperties.TextProp; | |||
import org.apache.poi.hslf.model.textproperties.TextPropCollection; | |||
import org.apache.poi.hslf.record.SlideAtom; | |||
/** | |||
@@ -55,11 +55,11 @@ public final class HSLFTitleMaster extends HSLFMasterSheet { | |||
* Delegate the call to the underlying slide master. | |||
*/ | |||
@Override | |||
public TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) { | |||
HSLFMasterSheet master = getMasterSheet(); | |||
return (master == null) ? null : master.getStyleAttribute(txtype, level, name, isCharacter); | |||
public TextPropCollection getPropCollection(final int txtype, final int level, final String name, final boolean isCharacter) { | |||
final HSLFMasterSheet master = getMasterSheet(); | |||
return (master == null) ? null : master.getPropCollection(txtype, level, name, isCharacter); | |||
} | |||
/** | |||
* Returns the slide master for this title master. | |||
*/ |
@@ -17,6 +17,10 @@ | |||
package org.apache.poi.hslf.model; | |||
import static org.apache.poi.hslf.record.TextHeaderAtom.BODY_TYPE; | |||
import static org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE; | |||
import static org.apache.poi.hslf.record.TextHeaderAtom.CENTRE_BODY_TYPE; | |||
import static org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
@@ -27,6 +31,7 @@ import java.util.List; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp; | |||
import org.apache.poi.hslf.model.textproperties.TextProp; | |||
import org.apache.poi.hslf.record.Environment; | |||
import org.apache.poi.hslf.record.TextHeaderAtom; | |||
import org.apache.poi.hslf.usermodel.HSLFMasterSheet; | |||
@@ -51,62 +56,70 @@ public final class TestSlideMaster { | |||
*/ | |||
@Test | |||
public void testSlideMaster() throws IOException { | |||
HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt")); | |||
final HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt")); | |||
Environment env = ppt.getDocumentRecord().getEnvironment(); | |||
final Environment env = ppt.getDocumentRecord().getEnvironment(); | |||
List<HSLFSlideMaster> master = ppt.getSlideMasters(); | |||
assertEquals(2, master.size()); | |||
assertEquals(2, ppt.getSlideMasters().size()); | |||
//character attributes | |||
assertEquals(40, master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.size", true).getValue()); | |||
assertEquals(48, master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.size", true).getValue()); | |||
assertEquals(40, getMasterVal(ppt, 0, TITLE_TYPE, "font.size", true)); | |||
assertEquals(48, getMasterVal(ppt, 1, TITLE_TYPE, "font.size", true)); | |||
int font1 = master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.index", true).getValue(); | |||
int font2 = master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.index", true).getValue(); | |||
int font1 = getMasterVal(ppt, 0, TITLE_TYPE, "font.index", true); | |||
int font2 = getMasterVal(ppt, 1, TITLE_TYPE, "font.index", true); | |||
assertEquals("Arial", env.getFontCollection().getFontInfo(font1).getTypeface()); | |||
assertEquals("Georgia", env.getFontCollection().getFontInfo(font2).getTypeface()); | |||
CharFlagsTextProp prop1 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "char_flags", true); | |||
CharFlagsTextProp prop1 = getMasterProp(ppt, 0, TITLE_TYPE, "char_flags", true); | |||
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX)); | |||
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); | |||
CharFlagsTextProp prop2 = (CharFlagsTextProp)master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "char_flags", true); | |||
CharFlagsTextProp prop2 = getMasterProp(ppt, 1, TITLE_TYPE, "char_flags", true); | |||
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(true, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX)); | |||
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); | |||
//now paragraph attributes | |||
assertEquals(0x266B, master.get(0).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.char", false).getValue()); | |||
assertEquals(0x2022, master.get(1).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.char", false).getValue()); | |||
assertEquals(0x266B, getMasterVal(ppt, 0, BODY_TYPE, "bullet.char", false)); | |||
assertEquals(0x2022, getMasterVal(ppt, 1, BODY_TYPE, "bullet.char", false)); | |||
int b1 = master.get(0).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.font", false).getValue(); | |||
int b2 = master.get(1).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.font", false).getValue(); | |||
int b1 = getMasterVal(ppt, 0, BODY_TYPE, "bullet.font", false); | |||
int b2 = getMasterVal(ppt, 1, BODY_TYPE, "bullet.font", false); | |||
assertEquals("Arial", env.getFontCollection().getFontInfo(b1).getTypeface()); | |||
assertEquals("Georgia", env.getFontCollection().getFontInfo(b2).getTypeface()); | |||
ppt.close(); | |||
} | |||
@SuppressWarnings("unchecked") | |||
private static <T extends TextProp> T getMasterProp(HSLFSlideShow ppt, int masterIdx, int txtype, String propName, boolean isCharacter) { | |||
return (T)ppt.getSlideMasters().get(masterIdx).getPropCollection(txtype, 0, propName, isCharacter).findByName(propName); | |||
} | |||
private static int getMasterVal(HSLFSlideShow ppt, int masterIdx, int txtype, String propName, boolean isCharacter) { | |||
return getMasterProp(ppt, masterIdx, txtype, propName, isCharacter).getValue(); | |||
} | |||
/** | |||
* Test we can read default text attributes for a title master sheet | |||
*/ | |||
@Test | |||
public void testTitleMasterTextAttributes() throws IOException { | |||
HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt")); | |||
List<HSLFTitleMaster> master = ppt.getTitleMasters(); | |||
assertEquals(1, master.size()); | |||
assertEquals(1, ppt.getTitleMasters().size()); | |||
assertEquals(32, master.get(0).getStyleAttribute(TextHeaderAtom.CENTER_TITLE_TYPE, 0, "font.size", true).getValue()); | |||
CharFlagsTextProp prop1 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.CENTER_TITLE_TYPE, 0, "char_flags", true); | |||
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(40, getMasterVal(ppt, 0, CENTER_TITLE_TYPE, "font.size", true)); | |||
CharFlagsTextProp prop1 = getMasterProp(ppt, 0, CENTER_TITLE_TYPE, "char_flags", true); | |||
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX)); | |||
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); | |||
assertEquals(20, master.get(0).getStyleAttribute(TextHeaderAtom.CENTRE_BODY_TYPE, 0, "font.size", true).getValue()); | |||
CharFlagsTextProp prop2 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.CENTRE_BODY_TYPE, 0, "char_flags", true); | |||
assertEquals(true, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(32, getMasterVal(ppt, 0, CENTRE_BODY_TYPE, "font.size", true)); | |||
CharFlagsTextProp prop2 = getMasterProp(ppt, 0, CENTRE_BODY_TYPE, "char_flags", true); | |||
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX)); | |||
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX)); | |||
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); | |||
@@ -18,17 +18,17 @@ | |||
package org.apache.poi.hslf.record; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import java.io.ByteArrayOutputStream; | |||
import java.util.List; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hslf.model.textproperties.HSLFTabStop; | |||
import org.junit.Test; | |||
/** | |||
* Tests TextRulerAtom | |||
* | |||
* @author Yegor Kozlov | |||
*/ | |||
public final class TestTextRulerAtom extends TestCase { | |||
public final class TestTextRulerAtom { | |||
//from a real file | |||
private final byte[] data_1 = new byte[] { | |||
@@ -40,25 +40,27 @@ public final class TestTextRulerAtom extends TestCase { | |||
private final byte[] data_2 = new byte[] { | |||
0x00, 0x00, (byte)0xA6, 0x0F, 0x0A, 0x00, 0x00, 0x00, | |||
0x10, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01 | |||
0x08, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01 | |||
}; | |||
@Test | |||
public void testReadRuler() { | |||
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length); | |||
assertEquals(ruler.getNumberOfLevels(), 0); | |||
assertEquals(ruler.getDefaultTabSize(), 0); | |||
int[] tabStops = ruler.getTabStops(); | |||
assertNull(tabStops); | |||
List<HSLFTabStop> tabStops = ruler.getTabStops(); | |||
assertNotNull(tabStops); | |||
int[] textOffsets = ruler.getTextOffsets(); | |||
assertArrayEquals(new int[]{226, 451, 903, 1129, 1526}, textOffsets); | |||
Integer[] textOffsets = ruler.getTextOffsets(); | |||
assertArrayEquals(new Integer[]{226, 451, 903, 1129, 1526}, textOffsets); | |||
int[] bulletOffsets = ruler.getBulletOffsets(); | |||
assertArrayEquals(new int[]{117, 345, 794, 1016, 1526}, bulletOffsets); | |||
Integer[] bulletOffsets = ruler.getBulletOffsets(); | |||
assertArrayEquals(new Integer[]{117, 345, 794, 1016, 1526}, bulletOffsets); | |||
} | |||
@Test | |||
public void testWriteRuler() throws Exception { | |||
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length); | |||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||
@@ -68,6 +70,7 @@ public final class TestTextRulerAtom extends TestCase { | |||
assertArrayEquals(result, data_1); | |||
} | |||
@Test | |||
public void testRead2() throws Exception { | |||
TextRulerAtom ruler = TextRulerAtom.getParagraphInstance(); | |||
ruler.setParagraphIndent((short)249, (short)321); | |||
@@ -75,6 +78,6 @@ public final class TestTextRulerAtom extends TestCase { | |||
ruler.writeOut(out); | |||
byte[] result = out.toByteArray(); | |||
assertArrayEquals(result, data_2); | |||
assertArrayEquals(data_2, result); | |||
} | |||
} |
@@ -17,8 +17,14 @@ | |||
package org.apache.poi.hslf.usermodel; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.fail; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import org.apache.poi.sl.usermodel.BaseTestSlideShow; | |||
import org.apache.poi.sl.usermodel.SlideShow; | |||
import org.junit.Test; | |||
public class TestHSLFSlideShow extends BaseTestSlideShow { | |||
@@ -32,4 +38,25 @@ public class TestHSLFSlideShow extends BaseTestSlideShow { | |||
public void dummy() { | |||
assertNotNull(createSlideShow()); | |||
} | |||
public SlideShow<?, ?> reopen(SlideShow<?, ?> show) { | |||
return reopen((HSLFSlideShow)show); | |||
} | |||
public static HSLFSlideShow reopen(HSLFSlideShow show) { | |||
try { | |||
BufAccessBAOS bos = new BufAccessBAOS(); | |||
show.write(bos); | |||
return new HSLFSlideShow(new ByteArrayInputStream(bos.getBuf())); | |||
} catch (IOException e) { | |||
fail(e.getMessage()); | |||
return null; | |||
} | |||
} | |||
private static class BufAccessBAOS extends ByteArrayOutputStream { | |||
public byte[] getBuf() { | |||
return buf; | |||
} | |||
} | |||
} |
@@ -21,18 +21,24 @@ import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertSame; | |||
import java.awt.Color; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.List; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.sl.usermodel.PictureData.PictureType; | |||
import org.apache.poi.sl.usermodel.TabStop.TabStopType; | |||
import org.junit.Test; | |||
public abstract class BaseTestSlideShow { | |||
protected static final POIDataSamples slTests = POIDataSamples.getSlideShowInstance(); | |||
public abstract SlideShow<?, ?> createSlideShow(); | |||
public abstract SlideShow<?, ?> reopen(SlideShow<?, ?> show); | |||
@Test | |||
public void addPicture_File() throws IOException { | |||
@@ -92,4 +98,63 @@ public abstract class BaseTestSlideShow { | |||
show.close(); | |||
} | |||
@Test | |||
public void addTabStops() throws IOException { | |||
try (final SlideShow<?,?> show1 = createSlideShow()) { | |||
// first set the TabStops in the Master sheet | |||
final MasterSheet<?, ?> master1 = show1.getSlideMasters().get(0); | |||
final AutoShape<?, ?> master1_as = (AutoShape<?,?>)master1.getPlaceholder(Placeholder.BODY); | |||
final TextParagraph<?, ?, ? extends TextRun> master1_tp = master1_as.getTextParagraphs().get(0); | |||
master1_tp.clearTabStops(); | |||
int i1 = 0; | |||
for (final TabStopType tst : TabStopType.values()) { | |||
master1_tp.addTabStops(10+i1*10, tst); | |||
i1++; | |||
} | |||
// then set it on a normal slide | |||
final Slide<?,?> slide1 = show1.createSlide(); | |||
final AutoShape<?, ?> slide1_as = slide1.createAutoShape(); | |||
slide1_as.setText("abc"); | |||
slide1_as.setAnchor(new Rectangle2D.Double(100,100,100,100)); | |||
final TextParagraph<?, ?, ? extends TextRun> slide1_tp = slide1_as.getTextParagraphs().get(0); | |||
slide1_tp.getTextRuns().get(0).setFontColor(new Color(0x563412)); | |||
slide1_tp.clearTabStops(); | |||
int i2 = 0; | |||
for (final TabStopType tst : TabStopType.values()) { | |||
slide1_tp.addTabStops(15+i2*5, tst); | |||
i2++; | |||
} | |||
try (final SlideShow<?, ?> show2 = reopen(show1)) { | |||
final MasterSheet<?, ?> master2 = show2.getSlideMasters().get(0); | |||
final AutoShape<?, ?> master2_as = (AutoShape<?,?>)master2.getPlaceholder(Placeholder.BODY); | |||
final TextParagraph<?, ?, ? extends TextRun> master2_tp = master2_as.getTextParagraphs().get(0); | |||
final List<? extends TabStop> master2_tabStops = master2_tp.getTabStops(); | |||
assertNotNull(master2_tabStops); | |||
int i3 = 0; | |||
for (final TabStopType tst : TabStopType.values()) { | |||
final TabStop ts = master2_tabStops.get(i3); | |||
assertEquals(10+i3*10, ts.getPositionInPoints(), 0.0); | |||
assertEquals(tst, ts.getType()); | |||
i3++; | |||
} | |||
final Slide<?,?> slide2 = show2.getSlides().get(0); | |||
final AutoShape<?,?> slide2_as = (AutoShape<?,?>)slide2.getShapes().get(0); | |||
final TextParagraph<?, ?, ? extends TextRun> slide2_tp = slide2_as.getTextParagraphs().get(0); | |||
final List<? extends TabStop> slide2_tabStops = slide2_tp.getTabStops(); | |||
assertNotNull(slide2_tabStops); | |||
int i4 = 0; | |||
for (final TabStopType tst : TabStopType.values()) { | |||
final TabStop ts = slide2_tabStops.get(i4); | |||
assertEquals(15+i4*5, ts.getPositionInPoints(), 0.0); | |||
assertEquals(tst, ts.getType()); | |||
i4++; | |||
} | |||
} | |||
} | |||
} | |||
} |