]> source.dussan.org Git - poi.git/commitdiff
#62096 - Add support for tabstops
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 11 Feb 2018 20:39:18 +0000 (20:39 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 11 Feb 2018 20:39:18 +0000 (20:39 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1823893 13f79535-47bb-0310-9956-ffa450edef68

22 files changed:
src/java/org/apache/poi/sl/usermodel/MasterSheet.java
src/java/org/apache/poi/sl/usermodel/TabStop.java [new file with mode: 0644]
src/java/org/apache/poi/sl/usermodel/TextParagraph.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTabStop.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java
src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStop.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/model/textproperties/HSLFTabStopPropCollection.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TabStopPropCollection.java [deleted file]
src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextProp.java
src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPropCollection.java
src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTitleMaster.java
src/scratchpad/testcases/org/apache/poi/hslf/model/TestSlideMaster.java
src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java
src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java

index ac23bc3bbaa71fe5fe3afc84df26eb67bf96e5f7..81da0bb19ae59554598d8872c250cbae527abe09 100644 (file)
@@ -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);
 }
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 (file)
index 0000000..479b1fe
--- /dev/null
@@ -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);
+}
index 1c0913614cf351e05334347dde1a3fe63edbeee5..0f6a25b9cce919167e39818826b5300e1085b19c 100644 (file)
@@ -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();
 }
\ No newline at end of file
index 89617009c7c3ff7d10afbf69acee39d7ab8992d1..8936f209b0796c9c3180afabe7b325d718b22825 100644 (file)
@@ -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()) {
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 (file)
index 0000000..7f87205
--- /dev/null
@@ -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) );
+    }
+}
index 4c535cfc04b263b4762617e63f3a7785bce7b84b..a51bc79f3389056062d76e44a8e84b293770a49f 100644 (file)
@@ -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
index 3ef1988b287f8f8b93766816ac057112198821a4..2864e3903818e73dfe3e657949c7d91629ca3356 100644 (file)
@@ -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 (file)
index 0000000..78cc3fa
--- /dev/null
@@ -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 (file)
index 0000000..7f7f6bd
--- /dev/null
@@ -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();
+    }
+    
+}
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 (file)
index 1934cf8..0000000
+++ /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<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;
-    }
-}
index 9333968f0766926839745b1734c4688e9e150c41..dfb1a1e544e7abac5a7d956e774449a2361c7649 100644 (file)
@@ -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
index 93a036fd071cfe1f50092614cefa91365d5336c1..976e8550f5f68d4b6af959f2573e688581475f09 100644 (file)
@@ -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;
index 53666d9184236eb266b6bfcefdbb4369a3508598..e0c45a5dafd49c40bdf03e571d8778b8add0a39c 100644 (file)
 
 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;
     }
 }
index d73374620d1c5f58533fa204c58a2902dca98759..526a3ceed282a390c5c5e96f6fba18dd3343c9ba 100644 (file)
 
 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);
 
 
     /**
index f6cd41f41d2342f8f19afcd2365053344731b96e..c7535f709a8a8abb5d57694e5c7c4b60d5f96efa 100644 (file)
@@ -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));
-                }
             }
         }
     }
index 695500eb01fd77ef522d9f5bbbed141fd32ac9c0..4e68c63fedfa4c83432c2fd8812ae69469b46032 100644 (file)
@@ -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();
     }
 
index c33272e05b592b5ae92e9447c5adac707aac49a0..a444a5b2b60d70f08c13e51a1e56734973d96d7b 100644 (file)
@@ -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;
         }
index b8e3948cccc72e94f4baaa43ac3c1a2916a9f932..59b0e3f287a6ac5a294a023b8e4235317000887a 100644 (file)
@@ -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.
      */
index 80772d3a1c8319a9949e0691ab7719b28dc00e9d..db56e0e775e4c7d9f266f75e4cbe6d0e74139108 100644 (file)
 
 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));
 
index 475918cd1c399f3038fdbfa36687fe8c1c9cc821..eee4ffe88c1aff3b7d59c4b61604eb02fe61b2f3 100644 (file)
 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);
        }
 }
index 7f5385e35260204c4c3b8269ebe5028437f65e53..7c2f7fe473eb6a5e2cc84c2939f7671de580859c 100644 (file)
 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;
+        }
+    }
 }
index e02260844a61663fc488888ed12fc845cd335ab9..47b886e174494f613a22e99f181715b6d8242d39 100644 (file)
@@ -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++;
+                }
+            }
+        }        
+    }
 }