From 9968e86b14188b43920f0ef28f68559e3dadf7be Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 11 Feb 2018 20:39:18 +0000 Subject: [PATCH] #62096 - Add support for tabstops git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1823893 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/sl/usermodel/MasterSheet.java | 9 +- .../org/apache/poi/sl/usermodel/TabStop.java | 66 ++++++ .../poi/sl/usermodel/TextParagraph.java | 30 +++ .../apache/poi/xslf/usermodel/XSLFSheet.java | 4 + .../poi/xslf/usermodel/XSLFTabStop.java | 60 +++++ .../poi/xslf/usermodel/XSLFTextParagraph.java | 104 ++++++--- .../poi/xslf/usermodel/TestXMLSlideShow.java | 25 ++ .../model/textproperties/HSLFTabStop.java | 116 ++++++++++ .../HSLFTabStopPropCollection.java | 158 +++++++++++++ .../textproperties/TabStopPropCollection.java | 123 ---------- .../hslf/model/textproperties/TextProp.java | 6 +- .../textproperties/TextPropCollection.java | 41 ++-- .../apache/poi/hslf/record/TextRulerAtom.java | 196 ++++++++-------- .../poi/hslf/usermodel/HSLFMasterSheet.java | 22 +- .../poi/hslf/usermodel/HSLFSlideMaster.java | 59 ++--- .../poi/hslf/usermodel/HSLFTextParagraph.java | 218 +++++++++++------- .../poi/hslf/usermodel/HSLFTextRun.java | 65 +++--- .../poi/hslf/usermodel/HSLFTitleMaster.java | 10 +- .../poi/hslf/model/TestSlideMaster.java | 57 +++-- .../poi/hslf/record/TestTextRulerAtom.java | 33 +-- .../poi/hslf/usermodel/TestHSLFSlideShow.java | 27 +++ .../poi/sl/usermodel/BaseTestSlideShow.java | 65 ++++++ 22 files changed, 1029 insertions(+), 465 deletions(-) create mode 100644 src/java/org/apache/poi/sl/usermodel/TabStop.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTabStop.java create mode 100644 src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStop.java create mode 100644 src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStopPropCollection.java delete mode 100644 src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TabStopPropCollection.java diff --git a/src/java/org/apache/poi/sl/usermodel/MasterSheet.java b/src/java/org/apache/poi/sl/usermodel/MasterSheet.java index ac23bc3bba..81da0bb19a 100644 --- a/src/java/org/apache/poi/sl/usermodel/MasterSheet.java +++ b/src/java/org/apache/poi/sl/usermodel/MasterSheet.java @@ -21,5 +21,12 @@ public interface MasterSheet< S extends Shape, P extends TextParagraph > extends Sheet { - + /** + * 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 getPlaceholder(Placeholder type); } diff --git a/src/java/org/apache/poi/sl/usermodel/TabStop.java b/src/java/org/apache/poi/sl/usermodel/TabStop.java new file mode 100644 index 0000000000..479b1fec44 --- /dev/null +++ b/src/java/org/apache/poi/sl/usermodel/TabStop.java @@ -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); +} diff --git a/src/java/org/apache/poi/sl/usermodel/TextParagraph.java b/src/java/org/apache/poi/sl/usermodel/TextParagraph.java index 1c0913614c..0f6a25b9cc 100644 --- a/src/java/org/apache/poi/sl/usermodel/TextParagraph.java +++ b/src/java/org/apache/poi/sl/usermodel/TextParagraph.java @@ -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 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(); } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java index 89617009c7..8936f209b0 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -472,6 +472,10 @@ implements XSLFShapeContainer, Sheet { return null; } + public XSLFSimpleShape getPlaceholder(Placeholder ph) { + return getPlaceholderByType(ph.ooxmlId); + } + XSLFSimpleShape getPlaceholder(CTPlaceholder ph) { XSLFSimpleShape shape = null; if(ph.isSetIdx()) { diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTabStop.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTabStop.java new file mode 100644 index 0000000000..7f87205bd0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTabStop.java @@ -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) ); + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java index 4c535cfc04..a51bc79f33 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -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 boolean fetchParagraphProperty(ParagraphPropertyFetcher 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 getTabStops() { + ParagraphPropertyFetcher> fetcher = new ParagraphPropertyFetcher>(getIndentLevel()){ + public boolean fetch(CTTextParagraphProperties props) { + if (props.isSetTabLst()) { + final List 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 diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java index 3ef1988b28..2864e39038 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java @@ -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; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStop.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStop.java new file mode 100644 index 0000000000..78cc3fab4d --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStop.java @@ -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; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStopPropCollection.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStopPropCollection.java new file mode 100644 index 0000000000..7f7f6bd9cf --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStopPropCollection.java @@ -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 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 readTabStops(final LittleEndianInput lei) { + final int count = lei.readUShort(); + final List tabs = new ArrayList<>(count); + for (int i=0; i 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 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(); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TabStopPropCollection.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TabStopPropCollection.java deleted file mode 100644 index 1934cf8983..0000000000 --- a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TabStopPropCollection.java +++ /dev/null @@ -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 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(); - for (TabStop ts : tabStops) { - TabStop tso = new TabStop(ts.getPosition(), ts.getType()); - other.tabStops.add(tso); - } - return other; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextProp.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextProp.java index 9333968f07..dfb1a1e544 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextProp.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextProp.java @@ -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()); } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java index 93a036fd07..976e8550f5 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java @@ -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 findByName(String textPropName) { + return (T)textProps.get(textPropName); } - public final TextProp removeByName(String name) { - return textProps.remove(name); + @SuppressWarnings("unchecked") + public final 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 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 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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java index 53666d9184..e0c45a5daf 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java @@ -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 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 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 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; } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java index d73374620d..526a3ceed2 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java @@ -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 { public HSLFMasterSheet(SheetContainer container, int sheetNo){ @@ -34,10 +31,19 @@ public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet 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 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 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)); - } } } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java index 695500eb01..4e68c63fed 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java @@ -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 _runs = new ArrayList<>(); @@ -107,6 +91,33 @@ public final class HSLFTextParagraph implements TextParagraph 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 getTabStops() { + final List 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 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 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 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 tp.setValue(val)); + } + + private void setPropValInner(final TextPropCollection props, final String name, Consumer 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 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; } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTitleMaster.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTitleMaster.java index b8e3948ccc..59b0e3f287 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTitleMaster.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTitleMaster.java @@ -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. */ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestSlideMaster.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestSlideMaster.java index 80772d3a1c..db56e0e775 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestSlideMaster.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestSlideMaster.java @@ -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 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 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 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)); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java index 475918cd1c..eee4ffe88c 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java @@ -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 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); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java index 7f5385e352..7c2f7fe473 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java @@ -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; + } + } } diff --git a/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java b/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java index e02260844a..47b886e174 100644 --- a/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java +++ b/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java @@ -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 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 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 master2_tp = master2_as.getTextParagraphs().get(0); + final List 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 slide2_tp = slide2_as.getTextParagraphs().get(0); + final List 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++; + } + } + } + } } -- 2.39.5