diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 77f9608e6..b4515d39c 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + Support for Headers / Footers in HSLF 44953 - Extensive fixes for data validation 45519 - Fixed to keep datavalidation records together Support for creating new HSLF CurrentUserAtoms diff --git a/src/documentation/content/xdocs/hslf/how-to-shapes.xml b/src/documentation/content/xdocs/hslf/how-to-shapes.xml index 7959eedaf..40ef32aa7 100644 --- a/src/documentation/content/xdocs/hslf/how-to-shapes.xml +++ b/src/documentation/content/xdocs/hslf/how-to-shapes.xml @@ -46,6 +46,7 @@
  • How to create shapes of arbitrary geometry
  • Shapes and Graphics2D
  • How to convert slides into images
  • +
  • Headers / Footers
  • Features @@ -620,6 +621,48 @@
    + +
    How to extract Headers / Footers from an existing presentation + + + FileInputStream is = new FileInputStream("slideshow.ppt"); + SlideShow ppt = new SlideShow(is); + is.close(); + Slide[] slides = ppt.getSlides(); + + //presentation-scope headers / footers + HeadersFooters hdd = ppt.getSlideHeadersFooters(); + if(hdd.isFooterVisible()) { + String footerText = hdd.getFooterText(); + } + + //per-slide headers / footers + for (int i=0; i < slides.length; i++){ + HeadersFooters hdd2 = slides[i].getHeadersFooters(); + if(hdd2.isFooterVisible()) { + String footerText = hdd2.getFooterText(); + } + if(hdd2.isUserDateVisible()) { + String customDate = hdd2.getDateTimeText(); + } + if(hdd2.isSlideNumberVisible()){ + int slideNUm = slides[i].getSlideNumber(); + } + + } + +
    +
    How to set Headers / Footers + + + SlideShow ppt = new SlideShow(); + + //presentation-scope headers / footers + HeadersFooters hdd = ppt.getSlideHeadersFooters(); + hdd.setSlideNumberVisible(true); + hdd.setFootersText("Created by POI-HSLF"); + +
    diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index ef4274eea..7dbddb6f8 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + Support for Headers / Footers in HSLF 44953 - Extensive fixes for data validation 45519 - Fixed to keep datavalidation records together Support for creating new HSLF CurrentUserAtoms diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java new file mode 100644 index 000000000..3ebcecc90 --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java @@ -0,0 +1,51 @@ +/* ==================================================================== + 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.examples; + +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hslf.model.HeadersFooters; +import org.apache.poi.hslf.model.Slide; + +import java.io.FileOutputStream; + +/** + * Demonstrates how to set headers / footers + * + * @author Yegor Kozlov + */ +public class HeadersFootersDemo { + public static void main(String[] args) throws Exception { + SlideShow ppt = new SlideShow(); + + HeadersFooters slideHeaders = ppt.getSlideHeadersFooters(); + slideHeaders.setFootersText("Created by POI-HSLF"); + slideHeaders.setSlideNumberVisible(true); + slideHeaders.setDateTimeText("custom date time"); + + HeadersFooters notesHeaders = ppt.getNotesHeadersFooters(); + notesHeaders.setFootersText("My notes footers"); + notesHeaders.setHeaderText("My notes header"); + + Slide slide = ppt.createSlide(); + + FileOutputStream out = new FileOutputStream("headers_footers.ppt"); + ppt.write(out); + out.close(); + + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java new file mode 100644 index 000000000..e94bcb981 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java @@ -0,0 +1,226 @@ + +/* ==================================================================== + 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; + +import org.apache.poi.hslf.record.*; +import org.apache.poi.hslf.usermodel.SlideShow; + +/** + * Header / Footer settings. + * + * @author Yegor Kozlov + */ +public class HeadersFooters { + + private HeadersFootersContainer _container; + private boolean _newRecord; + private SlideShow _ppt; + + public HeadersFooters(HeadersFootersContainer rec, SlideShow ppt, boolean newRecord){ + _container = rec; + _newRecord = newRecord; + _ppt = ppt; + } + + /** + * Headers's text + * + * @return Headers's text + */ + public String getHeaderText(){ + CString cs = _container.getHeaderAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets headers's text + * + * @param text headers's text + */ + public void setHeaderText(String text){ + if(_newRecord) attach(); + + setHeaderVisible(true); + CString cs = _container.getHeaderAtom(); + if(cs == null) cs = _container.addHeaderAtom(); + + cs.setText(text); + } + + /** + * Footer's text + * + * @return Footer's text + */ + public String getFooterText(){ + CString cs = _container.getFooterAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets footers's text + * + * @param text footers's text + */ + public void setFootersText(String text){ + if(_newRecord) attach(); + + setFooterVisible(true); + CString cs = _container.getFooterAtom(); + if(cs == null) cs = _container.addFooterAtom(); + + cs.setText(text); + } + + /** + * This is the date that the user wants in the footers, instead of today's date. + * + * @return custom user date + */ + public String getDateTimeText(){ + CString cs = _container.getUserDateAtom(); + return cs == null ? null : cs.getText(); + } + + /** + * Sets custom user date to be displayed instead of today's date. + * + * @param text custom user date + */ + public void setDateTimeText(String text){ + if(_newRecord) attach(); + + setUserDateVisible(true); + setDateTimeVisible(true); + CString cs = _container.getUserDateAtom(); + if(cs == null) cs = _container.addUserDateAtom(); + + cs.setText(text); + } + + /** + * whether the footer text is displayed. + */ + public boolean isFooterVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasFooter); + } + + /** + * whether the footer text is displayed. + */ + public void setFooterVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasFooter, flag); + } + + /** + * whether the header text is displayed. + */ + public boolean isHeaderVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasHeader); + } + + /** + * whether the header text is displayed. + */ + public void setHeaderVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasHeader, flag); + } + + /** + * whether the date is displayed in the footer. + */ + public boolean isDateTimeVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasDate); + } + + /** + * whether the date is displayed in the footer. + */ + public void setDateTimeVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasDate, flag); + } + + /** + * whether the custom user date is used instead of today's date. + */ + public boolean isUserDateVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasUserDate); + } + + /** + * whether the date is displayed in the footer. + */ + public void setUserDateVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasUserDate, flag); + } + + /** + * whether the slide number is displayed in the footer. + */ + public boolean isSlideNumberVisible(){ + return _container.getHeadersFootersAtom().getFlag(HeadersFootersAtom.fHasSlideNumber); + } + + /** + * whether the slide number is displayed in the footer. + */ + public void setSlideNumberVisible(boolean flag){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFlag(HeadersFootersAtom.fHasSlideNumber, flag); + } + + /** + * An integer that specifies the format ID to be used to style the datetime. + * + * @return an integer that specifies the format ID to be used to style the datetime. + */ + public int getDateTimeFormat(){ + return _container.getHeadersFootersAtom().getFormatId(); + } + + /** + * An integer that specifies the format ID to be used to style the datetime. + * + * @param formatId an integer that specifies the format ID to be used to style the datetime. + */ + public void setDateTimeFormat(int formatId){ + if(_newRecord) attach(); + _container.getHeadersFootersAtom().setFormatId(formatId); + } + + /** + * Attach this HeadersFootersContainer to the parent Document record + */ + private void attach(){ + Document doc = _ppt.getDocumentRecord(); + Record[] ch = doc.getChildRecords(); + Record lst = null; + for (int i=0; i < ch.length; i++){ + if(ch[i].getRecordType() == RecordTypes.List.typeID){ + lst = ch[i]; + break; + } + } + doc.addChildAfter(_container, lst); + _newRecord = false; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index ee32868eb..670a86655 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -24,9 +24,7 @@ import java.util.Vector; import java.util.Iterator; import java.awt.*; -import org.apache.poi.hslf.record.SlideAtom; -import org.apache.poi.hslf.record.TextHeaderAtom; -import org.apache.poi.hslf.record.ColorSchemeAtom; +import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; import org.apache.poi.ddf.EscherDggRecord; import org.apache.poi.ddf.EscherContainerRecord; @@ -381,4 +379,22 @@ public class Slide extends Sheet } } + /** + * Header / Footer settings for this slide. + * + * @return Header / Footer settings for this slide + */ + public HeadersFooters getHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = getSheetContainer().getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) return getSlideShow().getSlideHeadersFooters(); + else return new HeadersFooters(hdd, getSlideShow(), newRecord); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/CString.java b/src/scratchpad/src/org/apache/poi/hslf/record/CString.java index b17efc40b..e7e46baa0 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/CString.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/CString.java @@ -56,7 +56,7 @@ public class CString extends RecordAtom { * Grabs the count, from the first two bytes of the header. * The meaning of the count is specific to the type of the parent record */ - public int getCount() { + public int getOptions() { return (int)LittleEndian.getShort(_header); } @@ -64,7 +64,7 @@ public class CString extends RecordAtom { * Sets the count * The meaning of the count is specific to the type of the parent record */ - public void setCount(int count) { + public void setOptions(int count) { LittleEndian.putShort(_header, (short)count); } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java b/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java index 91ee8ea3a..31ef11a66 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Comment2000.java @@ -140,9 +140,9 @@ public class Comment2000 extends RecordContainer { CString csa = new CString(); CString csb = new CString(); CString csc = new CString(); - csa.setCount(0x00); - csb.setCount(0x10); - csc.setCount(0x20); + csa.setOptions(0x00); + csb.setOptions(0x10); + csc.setOptions(0x20); _children[0] = csa; _children[1] = csb; _children[2] = csc; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java index 501712e9d..6d61f2ef7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExEmbed.java @@ -74,8 +74,8 @@ public class ExEmbed extends RecordContainer { CString cs1 = new CString(); CString cs2 = new CString(); CString cs3 = new CString(); -// cs1.setCount(0x00); -// cs2.setCount(0x10); +// cs1.setOptions(0x00); +// cs2.setOptions(0x10); _children[0] = new ExEmbedAtom(); _children[1] = new ExOleObjAtom(); _children[2] = cs1; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java index 8ba58cdb6..b0bc1e191 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java @@ -136,8 +136,8 @@ public class ExHyperlink extends RecordContainer { // Setup our child records CString csa = new CString(); CString csb = new CString(); - csa.setCount(0x00); - csb.setCount(0x10); + csa.setOptions(0x00); + csb.setOptions(0x10); _children[0] = new ExHyperlinkAtom(); _children[1] = csa; _children[2] = csb; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java new file mode 100644 index 000000000..a9457a6de --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersAtom.java @@ -0,0 +1,207 @@ + +/* ==================================================================== + 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.record; + +import org.apache.poi.util.LittleEndian; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An atom record that specifies options for displaying headers and footers + * on a presentation slide or notes slide. + * + * @author Yegor Kozlov + */ + +public class HeadersFootersAtom extends RecordAtom { + + /** + * A bit that specifies whether the date is displayed in the footer. + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasDate = 1; + + /** + * A bit that specifies whether the current datetime is used for displaying the datetime. + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasTodayDate = 2; + + /** + * A bit that specifies whether the date specified in UserDateAtom record + * is used for displaying the datetime. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasUserDate = 4; + + /** + * A bit that specifies whether the slide number is displayed in the footer. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasSlideNumber = 8; + + /** + * bit that specifies whether the header text is displayed. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasHeader = 16; + + /** + * bit that specifies whether the footer text is displayed. + * + * @see {@link #getMask()}, {@link #setMask(int)}}, + */ + public static final int fHasFooter = 32; + + /** + * record header + */ + private byte[] _header; + + /** + * record data + */ + private byte[] _recdata; + + /** + * Build an instance of HeadersFootersAtom from on-disk data + */ + protected HeadersFootersAtom(byte[] source, int start, int len) { + // Get the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Grab the record data + _recdata = new byte[len-8]; + System.arraycopy(source,start+8,_recdata,0,len-8); + } + + /** + * Create a new instance of HeadersFootersAtom + */ + public HeadersFootersAtom() { + _recdata = new byte[4]; + + _header = new byte[8]; + LittleEndian.putShort(_header, 2, (short)getRecordType()); + LittleEndian.putInt(_header, 4, _recdata.length); + } + + public long getRecordType() { + return RecordTypes.HeadersFootersAtom.typeID; + } + + /** + * Write the contents of the record back, so it can be written to disk + */ + public void writeOut(OutputStream out) throws IOException { + out.write(_header); + out.write(_recdata); + } + + /** + * A signed integer that specifies the format ID to be used to style the datetime. + *

    + * It MUST be in the range [0, 12].
    + * This value is converted into a string as specified by the index field of the DateTimeMCAtom record. + * It MUST be ignored unless fHasTodayDate is TRUE. + * + * + * @return A signed integer that specifies the format ID to be used to style the datetime. + */ + public int getFormatId(){ + return LittleEndian.getShort(_recdata, 0); + } + + /** + * A signed integer that specifies the format ID to be used to style the datetime. + * + * @param formatId A signed integer that specifies the format ID to be used to style the datetime. + */ + public void setFormatId(int formatId){ + LittleEndian.putUShort(_recdata, 0, formatId); + } + + /** + * A bit mask specifying options for displaying headers and footers + * + *

  • A - {@link #fHasDate} (1 bit): A bit that specifies whether the date is displayed in the footer. + *
  • B - {@link #fHasTodayDate} (1 bit): A bit that specifies whether the current datetime is used for + * displaying the datetime. + *
  • C - {@link #fHasUserDate} (1 bit): A bit that specifies whether the date specified in UserDateAtom record + * is used for displaying the datetime. + *
  • D - {@link #fHasSlideNumber} (1 bit): A bit that specifies whether the slide number is displayed in the footer. + *
  • E - {@link #fHasHeader} (1 bit): A bit that specifies whether the header text specified by HeaderAtom + * record is displayed. + *
  • F - {@link #fHasFooter} (1 bit): A bit that specifies whether the footer text specified by FooterAtom + * record is displayed. + *
  • reserved (10 bits): MUST be zero and MUST be ignored. + * + * @return A bit mask specifying options for displaying headers and footers + */ + public int getMask(){ + return LittleEndian.getShort(_recdata, 2); + } + + /** + * A bit mask specifying options for displaying headers and footers + * + * @param mask A bit mask specifying options for displaying headers and footers + */ + public void setMask(int mask){ + LittleEndian.putUShort(_recdata, 2, mask); + } + + /** + * @param bit the bit to check + * @return whether the specified flag is set + */ + public boolean getFlag(int bit){ + return (getMask() & bit) != 0; + } + + /** + * @param bit the bit to set + * @param value whether the specified bit is set + */ + public void setFlag(int bit, boolean value){ + int mask = getMask(); + if(value) mask |= bit; + else mask &= ~bit; + setMask(mask); + } + + public String toString(){ + StringBuffer buf = new StringBuffer(); + buf.append("HeadersFootersAtom\n"); + buf.append("\tFormatId: " + getFormatId() + "\n"); + buf.append("\tMask : " + getMask() + "\n"); + buf.append("\t fHasDate : " + getFlag(fHasDate) + "\n"); + buf.append("\t fHasTodayDate : " + getFlag(fHasTodayDate) + "\n"); + buf.append("\t fHasUserDate : " + getFlag(fHasUserDate) + "\n"); + buf.append("\t fHasSlideNumber : " + getFlag(fHasSlideNumber) + "\n"); + buf.append("\t fHasHeader : " + getFlag(fHasHeader) + "\n"); + buf.append("\t fHasFooter : " + getFlag(fHasFooter) + "\n"); + return buf.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java new file mode 100644 index 000000000..e121574bc --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/HeadersFootersContainer.java @@ -0,0 +1,212 @@ + +/* ==================================================================== + 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.record; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * A container record that specifies information about the footers on a presentation slide. + *

    + * It contains:
    + *

  • 1. {@link HeadersFootersAtom} + *
  • 2. {@link CString }, Instance UserDate (0), optional: Stores the user's date. + * This is the date that the user wants in the footers, instead of today's date. + *
  • 3. {@link CString }, Instance Header (1), optional: Stores the Header's contents. + *
  • 4. {@link CString }, Instance Footer (2), optional: Stores the Footer's contents. + *

    + * + * @author Yegor Kozlov + */ +public class HeadersFootersContainer extends RecordContainer { + + /** + * "instance" field in the record header indicating that this HeadersFootersContaine + * is applied for slides + */ + public static final short SlideHeadersFootersContainer = 0x3F; + /** + * "instance" field in the record header indicating that this HeadersFootersContaine + * is applied for notes and handouts + */ + public static final short NotesHeadersFootersContainer = 0x4F; + + public static final int USERDATEATOM = 0; + public static final int HEADERATOM = 1; + public static final int FOOTERATOM = 2; + + private byte[] _header; + private HeadersFootersAtom hdAtom; + private CString csDate, csHeader, csFooter; + + protected HeadersFootersContainer(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + _children = Record.findChildRecords(source,start+8,len-8); + for(int i=0; i < _children.length; i++){ + if(_children[i] instanceof HeadersFootersAtom) hdAtom = (HeadersFootersAtom)_children[i]; + else if(_children[i] instanceof CString) { + CString cs = (CString)_children[i]; + int opts = cs.getOptions() >> 4; + switch(opts){ + case USERDATEATOM: csDate = cs; break; + case HEADERATOM: csHeader = cs; break; + case FOOTERATOM: csFooter = cs; break; + default: + logger.log(POILogger.WARN, "Unexpected CString.Options in HeadersFootersContainer: " + opts); + break; + } + } else { + logger.log(POILogger.WARN, "Unexpected record in HeadersFootersContainer: " + _children[i]); + } + } + + } + + public HeadersFootersContainer(short options) { + _header = new byte[8]; + LittleEndian.putShort(_header, 0, options); + LittleEndian.putShort(_header, 2, (short)getRecordType()); + + hdAtom = new HeadersFootersAtom(); + _children = new Record[]{ + hdAtom + }; + csDate = csHeader = csFooter = null; + + } + + /** + * Return the type, which is {@link RecordTypes#HeadersFooters} + */ + public long getRecordType() { + return RecordTypes.HeadersFooters.typeID; + } + + /** + * Must be either {@link #SlideHeadersFootersContainer} or {@link #NotesHeadersFootersContainer} + * + * @return "instance" field in the record header + */ + public int getOptions(){ + return LittleEndian.getShort(_header, 0); + } + + /** + * Write the contents of the record back, so it can be written to disk + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } + + /** + * HeadersFootersAtom stores the basic information of the header and footer structure. + * + * @return HeadersFootersAtom + */ + public HeadersFootersAtom getHeadersFootersAtom(){ + return hdAtom; + } + + /** + * A {@link CString} record that stores the user's date. + *

    This is the date that the user wants in the footers, instead of today's date.

    + * + * @return A {@link CString} record that stores the user's date or null + */ + public CString getUserDateAtom(){ + return csDate; + } + + /** + * A {@link CString} record that stores the Header's contents. + * + * @return A {@link CString} record that stores the Header's contents or null + */ + public CString getHeaderAtom(){ + return csHeader; + } + + /** + * A {@link CString} record that stores the Footers's contents. + * + * @return A {@link CString} record that stores the Footers's contents or null + */ + public CString getFooterAtom(){ + return csFooter; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addUserDateAtom(){ + if(csDate != null) return csDate; + + csDate = new CString(); + csDate.setOptions(USERDATEATOM << 4); + + addChildAfter(csDate, hdAtom); + + return csDate; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addHeaderAtom(){ + if(csHeader != null) return csHeader; + + csHeader = new CString(); + csHeader.setOptions(HEADERATOM << 4); + + Record r = hdAtom; + if(csDate != null) r = hdAtom; + addChildAfter(csHeader, r); + + return csHeader; + } + + /** + * Insert a {@link CString} record that stores the user's date. + * + * @return the created {@link CString} record that stores the user's date. + */ + public CString addFooterAtom(){ + if(csFooter != null) return csFooter; + + csFooter = new CString(); + csFooter.setOptions(FOOTERATOM << 4); + + Record r = hdAtom; + if(csHeader != null) r = csHeader; + else if(csDate != null) r = csDate; + addChildAfter(csFooter, r); + + return csFooter; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index a6f00da12..d7a664725 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -111,8 +111,8 @@ public class RecordTypes { public static final Type ExHyperlinkAtom = new Type(4051,ExHyperlinkAtom.class); public static final Type ExHyperlink = new Type(4055,ExHyperlink.class); public static final Type SlideNumberMCAtom = new Type(4056,null); - public static final Type HeadersFooters = new Type(4057,null); - public static final Type HeadersFootersAtom = new Type(4058,null); + public static final Type HeadersFooters = new Type(4057,HeadersFootersContainer.class); + public static final Type HeadersFootersAtom = new Type(4058,HeadersFootersAtom.class); public static final Type TxInteractiveInfoAtom = new Type(4063,TxInteractiveInfoAtom.class); public static final Type CharFormatAtom = new Type(4066,null); public static final Type ParaFormatAtom = new Type(4067,null); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 56e94431e..f38cc7716 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -811,4 +811,50 @@ public class SlideShow public int getNumberOfFonts() { return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts(); } + + /** + * Return Header / Footer settings for slides + * + * @return Header / Footer settings for slides + */ + public HeadersFooters getSlideHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = _documentRecord.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer && + ((HeadersFootersContainer)ch[i]).getOptions() == HeadersFootersContainer.SlideHeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) { + hdd = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer); + newRecord = true; + } + return new HeadersFooters(hdd, this, newRecord); + } + + /** + * Return Header / Footer settings for notes + * + * @return Header / Footer settings for notes + */ + public HeadersFooters getNotesHeadersFooters(){ + HeadersFootersContainer hdd = null; + Record[] ch = _documentRecord.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof HeadersFootersContainer && + ((HeadersFootersContainer)ch[i]).getOptions() == HeadersFootersContainer.NotesHeadersFootersContainer){ + hdd = (HeadersFootersContainer)ch[i]; + break; + } + } + boolean newRecord = false; + if(hdd == null) { + hdd = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer); + newRecord = true; + } + return new HeadersFooters(hdd, this, newRecord); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt new file mode 100644 index 000000000..891d73f95 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/headers_footers.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java new file mode 100644 index 000000000..8b1cdbe09 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHeadersFooters.java @@ -0,0 +1,118 @@ + +/* ==================================================================== + 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; + +import java.io.*; +import org.apache.poi.hslf.usermodel.SlideShow; + +import junit.framework.TestCase; + +/** + * Test {@link org.apache.poi.hslf.model.HeadersFooters} object + */ +public class TestHeadersFooters extends TestCase +{ + + public static final String cwd = System.getProperty("HSLF.testdata.path"); + + public void testRead() throws Exception + { + File file = new File(cwd, "headers_footers.ppt"); + FileInputStream is = new FileInputStream(file); + SlideShow ppt = new SlideShow(is); + is.close(); + + HeadersFooters slideHdd = ppt.getSlideHeadersFooters(); + assertTrue(slideHdd.isFooterVisible()); + assertEquals("Global Slide Footer", slideHdd.getFooterText()); + assertTrue(slideHdd.isSlideNumberVisible()); + assertFalse(slideHdd.isHeaderVisible()); + assertNull(slideHdd.getHeaderText()); + assertFalse(slideHdd.isUserDateVisible()); + assertNull(slideHdd.getDateTimeText()); + + + HeadersFooters notesHdd = ppt.getNotesHeadersFooters(); + assertTrue(notesHdd.isFooterVisible()); + assertEquals("Notes Footer", notesHdd.getFooterText()); + assertTrue(notesHdd.isHeaderVisible()); + assertEquals("Notes Header", notesHdd.getHeaderText()); + assertTrue(notesHdd.isUserDateVisible()); + assertNull(notesHdd.getDateTimeText()); + + Slide[] slide = ppt.getSlides(); + //the first slide uses presentation-scope headers / footers + HeadersFooters hd1 = slide[0].getHeadersFooters(); + assertEquals(slideHdd.isFooterVisible(), hd1.isFooterVisible()); + assertEquals(slideHdd.getFooterText(), hd1.getFooterText()); + assertEquals(slideHdd.isSlideNumberVisible(), hd1.isSlideNumberVisible()); + assertEquals(slideHdd.isHeaderVisible(), hd1.isHeaderVisible()); + assertEquals(slideHdd.getHeaderText(), hd1.getHeaderText()); + assertEquals(slideHdd.isUserDateVisible(), hd1.isUserDateVisible()); + assertEquals(slideHdd.getDateTimeText(), hd1.getDateTimeText()); + + //the first slide uses per-slide headers / footers + HeadersFooters hd2 = slide[1].getHeadersFooters(); + assertEquals(true, hd2.isFooterVisible()); + assertEquals("per-slide footer", hd2.getFooterText()); + assertEquals(true, hd2.isUserDateVisible()); + assertEquals("custom date format", hd2.getDateTimeText()); + } + + public void testCreateSlideFooters() throws Exception + { + SlideShow ppt = new SlideShow(); + HeadersFooters hdd = ppt.getSlideHeadersFooters(); + hdd.setFootersText("My slide footer"); + hdd.setSlideNumberVisible(true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + byte[] b = out.toByteArray(); + + SlideShow ppt2 = new SlideShow(new ByteArrayInputStream(b)); + HeadersFooters hdd2 = ppt2.getSlideHeadersFooters(); + assertTrue(hdd2.isSlideNumberVisible()); + assertTrue(hdd2.isFooterVisible()); + assertEquals("My slide footer", hdd2.getFooterText()); + } + + public void testCreateNotesFooters() throws Exception + { + SlideShow ppt = new SlideShow(); + HeadersFooters hdd = ppt.getNotesHeadersFooters(); + hdd.setFootersText("My notes footer"); + hdd.setHeaderText("My notes header"); + hdd.setSlideNumberVisible(true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + byte[] b = out.toByteArray(); + + SlideShow ppt2 = new SlideShow(new ByteArrayInputStream(b)); + HeadersFooters hdd2 = ppt2.getNotesHeadersFooters(); + assertTrue(hdd2.isSlideNumberVisible()); + assertTrue(hdd2.isFooterVisible()); + assertEquals("My notes footer", hdd2.getFooterText()); + assertTrue(hdd2.isHeaderVisible()); + assertEquals("My notes header", hdd2.getHeaderText()); + } +} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java index 3f3d1a144..df8e1a3da 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestCString.java @@ -46,12 +46,12 @@ public class TestCString extends TestCase { } public void testCount() throws Exception { CString ca = new CString(data_a, 0, data_a.length); - assertEquals(0, ca.getCount()); + assertEquals(0, ca.getOptions()); CString cb = new CString(data_b, 0, data_a.length); - assertEquals(0x10, cb.getCount()); + assertEquals(0x10, cb.getOptions()); - ca.setCount(28); - assertEquals(28, ca.getCount()); + ca.setOptions(28); + assertEquals(28, ca.getOptions()); } public void testText() throws Exception { @@ -90,7 +90,7 @@ public class TestCString extends TestCase { public void testChange() throws Exception { CString ca = new CString(data_a, 0, data_a.length); ca.setText("Comments"); - ca.setCount(0x10); + ca.setOptions(0x10); try { for(int i=0; i> 4); + + assertEquals("My Footer - 1", csFooter.getText()); + + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + } + + public void testWriteSlideHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(slideData, 0, slideData.length); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(slideData, b)); + } + + public void testNewSlideHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer); + + assertNotNull(record.getHeadersFootersAtom()); + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + assertNull(record.getFooterAtom()); + + HeadersFootersAtom hd = record.getHeadersFootersAtom(); + hd.setFlag(HeadersFootersAtom.fHasDate, true); + hd.setFlag(HeadersFootersAtom.fHasTodayDate, true); + hd.setFlag(HeadersFootersAtom.fHasFooter, true); + + CString csFooter = record.addFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + csFooter.setText("My Footer - 1"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(slideData, b)); + } + + public void testReadNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(notesData, 0, notesData.length); + assertEquals(RecordTypes.HeadersFooters.typeID, record.getRecordType()); + assertEquals(HeadersFootersContainer.NotesHeadersFootersContainer, record.getOptions()); + assertEquals(3, record.getChildRecords().length); + + HeadersFootersAtom hdd = record.getHeadersFootersAtom(); + assertNotNull(hdd); + + CString csHeader = record.getHeaderAtom(); + assertNotNull(csHeader); + assertEquals(HeadersFootersContainer.HEADERATOM, csHeader.getOptions() >> 4); + assertEquals("Note Header", csHeader.getText()); + + CString csFooter = record.getFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + assertEquals("Note Footer", csFooter.getText()); + } + + public void testWriteNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(notesData, 0, notesData.length); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(notesData, b)); + } + + public void testNewNotesHeadersFootersContainer() throws Exception { + HeadersFootersContainer record = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer); + + assertNotNull(record.getHeadersFootersAtom()); + assertNull(record.getUserDateAtom()); + assertNull(record.getHeaderAtom()); + assertNull(record.getFooterAtom()); + + HeadersFootersAtom hd = record.getHeadersFootersAtom(); + hd.setFlag(HeadersFootersAtom.fHasDate, true); + hd.setFlag(HeadersFootersAtom.fHasTodayDate, false); + hd.setFlag(HeadersFootersAtom.fHasUserDate, true); + hd.setFlag(HeadersFootersAtom.fHasSlideNumber, true); + hd.setFlag(HeadersFootersAtom.fHasHeader, true); + hd.setFlag(HeadersFootersAtom.fHasFooter, true); + + CString csHeader = record.addHeaderAtom(); + assertNotNull(csHeader); + assertEquals(HeadersFootersContainer.HEADERATOM, csHeader.getOptions() >> 4); + csHeader.setText("Note Header"); + + CString csFooter = record.addFooterAtom(); + assertNotNull(csFooter); + assertEquals(HeadersFootersContainer.FOOTERATOM, csFooter.getOptions() >> 4); + csFooter.setText("Note Footer"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + record.writeOut(baos); + byte[] b = baos.toByteArray(); + + assertTrue(Arrays.equals(notesData, b)); + } + +} \ No newline at end of file