]> source.dussan.org Git - poi.git/commitdiff
#63028 - Provide font embedding for slideshows
authorAndreas Beeker <kiwiwings@apache.org>
Fri, 28 Dec 2018 23:43:31 +0000 (23:43 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Fri, 28 Dec 2018 23:43:31 +0000 (23:43 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849898 13f79535-47bb-0310-9956-ffa450edef68

34 files changed:
src/integrationtest/org/apache/poi/TestAllFiles.java
src/java/org/apache/poi/common/usermodel/fonts/FontCharset.java
src/java/org/apache/poi/common/usermodel/fonts/FontFacet.java [new file with mode: 0644]
src/java/org/apache/poi/common/usermodel/fonts/FontHeader.java [new file with mode: 0644]
src/java/org/apache/poi/common/usermodel/fonts/FontInfo.java
src/java/org/apache/poi/sl/draw/DrawFontInfo.java
src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java
src/java/org/apache/poi/sl/usermodel/FontCollection.java [deleted file]
src/java/org/apache/poi/sl/usermodel/Resources.java [deleted file]
src/java/org/apache/poi/sl/usermodel/Slide.java
src/java/org/apache/poi/sl/usermodel/SlideShow.java
src/java/org/apache/poi/sl/usermodel/TextRun.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontData.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontInfo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java
src/ooxml/testcases/org/apache/poi/xslf/extractor/TestXSLFPowerPointExtractor.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java
src/scratchpad/src/org/apache/poi/hslf/record/Document.java
src/scratchpad/src/org/apache/poi/hslf/record/DocumentAtom.java
src/scratchpad/src/org/apache/poi/hslf/record/FontCollection.java
src/scratchpad/src/org/apache/poi/hslf/record/FontEmbeddedData.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/record/FontEntityAtom.java
src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFontInfo.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFontInfoPredefined.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java
src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestHSLFSlideShow.java
src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java
test-data/slideshow/font.fntdata [new file with mode: 0644]

index 2255e8bbf9a4dc90784396b9eb96cc05f7074458..33bd696195f56712fa9c6415ce0558a9ef4d68fe 100644 (file)
@@ -187,11 +187,11 @@ public class TestAllFiles {
         HANDLERS.put(".tif", new NullFileHandler());
         HANDLERS.put(".tiff", new NullFileHandler());
         HANDLERS.put(".wav", new NullFileHandler());
-        HANDLERS.put(".pfx", new NullFileHandler());
         HANDLERS.put(".xml", new NullFileHandler());
         HANDLERS.put(".csv", new NullFileHandler());
         HANDLERS.put(".ods", new NullFileHandler());
         HANDLERS.put(".ttf", new NullFileHandler());
+        HANDLERS.put(".fntdata", new NullFileHandler());
         // VBA source files
         HANDLERS.put(".vba", new NullFileHandler());
         HANDLERS.put(".bas", new NullFileHandler());
index aeeca9284ca5b25107f4785286ef7cb4310b7f09..32915149f2b963584080022418628f31e7054949 100644 (file)
@@ -70,7 +70,7 @@ public enum FontCharset {
     /** Specifies the Russian Cyrillic character set. */
     RUSSIAN(0x000000CC, "Cp1251"),
     /** Specifies the Thai character set. */
-    THAI_(0x000000DE, "x-windows-874"),
+    THAI(0x000000DE, "x-windows-874"),
     /** Specifies a Eastern European character set. */
     EASTEUROPE(0x000000EE, "Cp1250"),
     /**
diff --git a/src/java/org/apache/poi/common/usermodel/fonts/FontFacet.java b/src/java/org/apache/poi/common/usermodel/fonts/FontFacet.java
new file mode 100644 (file)
index 0000000..5fb1690
--- /dev/null
@@ -0,0 +1,75 @@
+/* ====================================================================
+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.common.usermodel.fonts;
+
+import org.apache.poi.util.Beta;
+
+/**
+ * A FontFacet holds the font data for a shape of a font, i.e. a regular,
+ * italic, bold or bold-italic version of a Font.
+ */
+@SuppressWarnings("unused")
+@Beta
+public interface FontFacet {
+    /**
+     * Get the font weight.<p>
+     *
+     * The weight of the font in the range 0 through 1000.
+     * For example, 400 is normal and 700 is bold.
+     * If this value is zero, a default weight is used.
+     *
+     * @return the font weight
+     *
+     * @since POI 4.1.0
+     */
+    default int getWeight() {
+        return FontHeader.REGULAR_WEIGHT;
+    }
+
+    /**
+     * Set the font weight
+     *
+     * @param weight the font weight
+     */
+    default void setWeight(int weight) {
+        throw new UnsupportedOperationException("FontFacet is read-only.");
+    }
+
+    /**
+     * @return {@code true}, if the font is italic
+     */
+    default boolean isItalic() {
+        return false;
+    }
+
+    /**
+     * Set the font posture
+     *
+     * @param italic {@code true} for italic, {@code false} for regular
+     */
+    default void setItalic(boolean italic) {
+        throw new UnsupportedOperationException("FontFacet is read-only.");
+    }
+
+    /**
+     * @return the wrapper object holding the font data
+     */
+    default Object getFontData() {
+        return null;
+    }
+}
diff --git a/src/java/org/apache/poi/common/usermodel/fonts/FontHeader.java b/src/java/org/apache/poi/common/usermodel/fonts/FontHeader.java
new file mode 100644 (file)
index 0000000..9777f0c
--- /dev/null
@@ -0,0 +1,227 @@
+/* ====================================================================
+   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.common.usermodel.fonts;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianInputStream;
+
+
+/**
+ * The header data of an EOT font.<p>
+ *
+ * Currently only version 1 fields are read to identify a stream to be embedded.
+ *
+ * @see <a href="http://www.w3.org/Submission/EOT">Embedded OpenType (EOT) File Format</a>
+ */
+@SuppressWarnings({"FieldCanBeLocal", "unused", "Duplicates"})
+public class FontHeader implements FontInfo {
+    /**
+     * Fonts with a font weight of 400 are regarded as regular weighted.
+     * Higher font weights (up to 1000) are bold - lower weights are thin.
+     */
+    public static final int REGULAR_WEIGHT = 400;
+
+    private int eotSize;
+    private int fontDataSize;
+    private int version;
+    private int flags;
+    private final byte[] panose = new byte[10];
+    private byte charset;
+    private byte italic;
+    private int weight;
+    private int fsType;
+    private int magic;
+    private int unicodeRange1;
+    private int unicodeRange2;
+    private int unicodeRange3;
+    private int unicodeRange4;
+    private int codePageRange1;
+    private int codePageRange2;
+    private int checkSumAdjustment;
+    private String familyName;
+    private String styleName;
+    private String versionName;
+    private String fullName;
+
+    public void init(byte[] source, int offset, int length) {
+        init(new LittleEndianByteArrayInputStream(source, offset, length));
+    }
+
+    public void init(LittleEndianInput leis) {
+        eotSize = leis.readInt();
+        fontDataSize = leis.readInt();
+        version = leis.readInt();
+        if (version != 0x00010000 && version != 0x00020001 && version != 0x00020002) {
+            throw new RuntimeException("not a EOT font data stream");
+        }
+        flags = leis.readInt();
+        leis.readFully(panose);
+        charset = leis.readByte();
+        italic = leis.readByte();
+        weight = leis.readInt();
+        fsType = leis.readUShort();
+        magic = leis.readUShort();
+        if (magic != 0x504C) {
+            throw new RuntimeException("not a EOT font data stream");
+        }
+        unicodeRange1 = leis.readInt();
+        unicodeRange2 = leis.readInt();
+        unicodeRange3 = leis.readInt();
+        unicodeRange4 = leis.readInt();
+        codePageRange1 = leis.readInt();
+        codePageRange2 = leis.readInt();
+        checkSumAdjustment = leis.readInt();
+        int reserved1 = leis.readInt();
+        int reserved2 = leis.readInt();
+        int reserved3 = leis.readInt();
+        int reserved4 = leis.readInt();
+        familyName = readName(leis);
+        styleName = readName(leis);
+        versionName = readName(leis);
+        fullName = readName(leis);
+
+    }
+
+    public InputStream bufferInit(InputStream fontStream) throws IOException {
+        LittleEndianInputStream is = new LittleEndianInputStream(fontStream);
+        is.mark(1000);
+        init(is);
+        is.reset();
+        return is;
+    }
+
+    private String readName(LittleEndianInput leis) {
+        // padding
+        leis.readShort();
+        int nameSize = leis.readUShort();
+        byte[] nameBuf = IOUtils.safelyAllocate(nameSize, 1000);
+        leis.readFully(nameBuf);
+        // may be 0-terminated, just trim it away
+        return new String(nameBuf, 0, nameSize, StandardCharsets.UTF_16LE).trim();
+    }
+
+    public boolean isItalic() {
+        return italic != 0;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public boolean isBold() {
+        return getWeight() > REGULAR_WEIGHT;
+    }
+
+    public byte getCharsetByte() {
+        return charset;
+    }
+
+    public FontCharset getCharset() {
+        return FontCharset.valueOf(getCharsetByte());
+    }
+
+    public FontPitch getPitch() {
+        byte familyKind = panose[0];
+        switch (familyKind) {
+            default:
+            // Any
+            case 0:
+            // No Fit
+            case 1:
+                return FontPitch.VARIABLE;
+
+            // Latin Text
+            case 2:
+                // Latin Decorative
+            case 4:
+                byte proportion = panose[3];
+                return proportion == 9 ? FontPitch.FIXED : FontPitch.VARIABLE;
+
+            // Latin Hand Written
+            case 3:
+                // Latin Symbol
+            case 5:
+                byte spacing = panose[3];
+                return spacing == 3 ? FontPitch.FIXED : FontPitch.VARIABLE;
+        }
+
+    }
+
+    public FontFamily getFamily() {
+        switch (panose[0]) {
+            // Any
+            case 0:
+            // No Fit
+            case 1:
+                return FontFamily.FF_DONTCARE;
+            // Latin Text
+            case 2:
+                byte serifStyle = panose[1];
+                return (10 <= serifStyle && serifStyle <= 15)
+                    ? FontFamily.FF_SWISS : FontFamily.FF_ROMAN;
+            // Latin Hand Written
+            case 3:
+                return FontFamily.FF_SCRIPT;
+            // Latin Decorative
+            default:
+            case 4:
+                return FontFamily.FF_DECORATIVE;
+            // Latin Symbol
+            case 5:
+                return FontFamily.FF_MODERN;
+        }
+    }
+
+    public String getFamilyName() {
+        return familyName;
+    }
+
+    public String getStyleName() {
+        return styleName;
+    }
+
+    public String getVersionName() {
+        return versionName;
+    }
+
+    public String getFullName() {
+        return fullName;
+    }
+
+    public byte[] getPanose() {
+        return panose;
+    }
+
+    @Override
+    public String getTypeface() {
+        return getFamilyName();
+    }
+
+    public int getFlags() {
+        return flags;
+    }
+}
+
+
+
index ecb5a6968762a36ca58bb43233300c024182363f..b47b02e53b5bad456b2d69e5f407da1b904bcc9d 100644 (file)
@@ -17,6 +17,11 @@ limitations under the License.
 
 package org.apache.poi.common.usermodel.fonts;
 
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.poi.util.Beta;
+
 /**
  * A FontInfo object holds information about a font configuration.
  * It is roughly an equivalent to the LOGFONT structure in Windows GDI.<p>
@@ -30,6 +35,7 @@ package org.apache.poi.common.usermodel.fonts;
  *
  * @see <a href="https://msdn.microsoft.com/en-us/library/dd145037.aspx">LOGFONT structure</a>
  */
+@SuppressWarnings("unused")
 public interface FontInfo {
 
     /**
@@ -37,7 +43,9 @@ public interface FontInfo {
      * @return unique index number of the underlying record this Font represents
      *   (probably you don't care unless you're comparing which one is which)
      */
-    Integer getIndex();
+    default Integer getIndex() {
+        return null;
+    }
 
     /**
      * Sets the index within the collection of Font objects
@@ -46,7 +54,9 @@ public interface FontInfo {
      *
      * @throws UnsupportedOperationException if unsupported
      */
-    void setIndex(int index);
+    default void setIndex(int index) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
     
     
     /**
@@ -60,36 +70,48 @@ public interface FontInfo {
      * @param typeface the full name of the font, when {@code null} removes the font definition -
      *    removal is implementation specific
      */
-    void setTypeface(String typeface);
+    default void setTypeface(String typeface) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
 
     /**
      * @return the font charset
      */
-    FontCharset getCharset();
+    default FontCharset getCharset() {
+        return FontCharset.ANSI;
+    }
 
     /**
      * Sets the charset
      *
      * @param charset the charset
      */
-    void setCharset(FontCharset charset);
+    default void setCharset(FontCharset charset) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
 
     /**
      * @return the family class
      */
-    FontFamily getFamily();
+    default FontFamily getFamily() {
+        return FontFamily.FF_DONTCARE;
+    }
 
     /**
      * Sets the font family class
      *
      * @param family the font family class
      */
-    void setFamily(FontFamily family);
+    default void setFamily(FontFamily family) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
 
     /**
      * @return the font pitch or {@code null} if unsupported
      */
-    FontPitch getPitch();
+    default FontPitch getPitch() {
+        return null;
+    }
 
     /**
      * Set the font pitch
@@ -98,5 +120,33 @@ public interface FontInfo {
      *
      * @throws UnsupportedOperationException if unsupported
      */
-    void setPitch(FontPitch pitch);
+    default void setPitch(FontPitch pitch) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
+
+    /**
+     * @return panose info in binary form or {@code null} if unknown
+     */
+    default byte[] getPanose() {
+        return null;
+    }
+
+    /**
+     * Set the panose in binary form
+     * @param panose the panose bytes
+     */
+    default void setPanose(byte[] panose) {
+        throw new UnsupportedOperationException("FontInfo is read-only.");
+    }
+
+
+    /**
+     * If font facets are embedded in the document, return the list of embedded facets.
+     * The font embedding is experimental, therefore the API can change.
+     * @return the list of embedded EOT font data
+     */
+    @Beta
+    default List<? extends FontFacet> getFacets() {
+        return Collections.emptyList();
+    }
 }
\ No newline at end of file
index dc7afb4e240255be2c094c3bc2dce37b2886d060..da1979cb9e7bd84fe654a24a415cb8e537128b43 100644 (file)
 
 package org.apache.poi.sl.draw;
 
-import org.apache.poi.common.usermodel.fonts.FontCharset;
-import org.apache.poi.common.usermodel.fonts.FontFamily;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
-import org.apache.poi.common.usermodel.fonts.FontPitch;
 import org.apache.poi.util.Internal;
 
 /**
@@ -37,53 +34,8 @@ import org.apache.poi.util.Internal;
         this.typeface = typeface;
     }
     
-    @Override
-    public Integer getIndex() {
-        return null;
-    }
-
-    @Override
-    public void setIndex(int index) {
-        throw new UnsupportedOperationException("DrawFontManagers FontInfo can't be changed.");
-    }
-
     @Override
     public String getTypeface() {
         return typeface;
     }
-
-    @Override
-    public void setTypeface(String typeface) {
-        throw new UnsupportedOperationException("DrawFontManagers FontInfo can't be changed.");
-    }
-
-    @Override
-    public FontCharset getCharset() {
-        return FontCharset.ANSI;
-    }
-
-    @Override
-    public void setCharset(FontCharset charset) {
-        throw new UnsupportedOperationException("DrawFontManagers FontInfo can't be changed.");
-    }
-
-    @Override
-    public FontFamily getFamily() {
-        return FontFamily.FF_SWISS;
-    }
-
-    @Override
-    public void setFamily(FontFamily family) {
-        throw new UnsupportedOperationException("DrawFontManagers FontInfo can't be changed.");
-    }
-
-    @Override
-    public FontPitch getPitch() {
-        return FontPitch.VARIABLE;
-    }
-
-    @Override
-    public void setPitch(FontPitch pitch) {
-        throw new UnsupportedOperationException("DrawFontManagers FontInfo can't be changed.");
-    }
 }
index dee4d44a03eff91f19fdc1caab67f99f10e39a86..7173c24e978c44836a83f0855cc1662b947d9b89 100644 (file)
 package org.apache.poi.sl.extractor;
 
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 import org.apache.poi.extractor.POITextExtractor;
-import org.apache.poi.sl.usermodel.Comment;
 import org.apache.poi.sl.usermodel.MasterSheet;
 import org.apache.poi.sl.usermodel.Notes;
 import org.apache.poi.sl.usermodel.ObjectShape;
@@ -52,6 +56,10 @@ public class SlideShowExtractor<
 > extends POITextExtractor {
     private static final POILogger LOG = POILogFactory.getLogger(SlideShowExtractor.class);
 
+    // placeholder text for slide numbers
+    private static final String SLIDE_NUMBER_PH = "‹#›";
+
+
     private SlideShow<S,P> slideshow;
 
     private boolean slidesByDefault = true;
@@ -59,7 +67,8 @@ public class SlideShowExtractor<
     private boolean commentsByDefault;
     private boolean masterByDefault;
 
-    
+    private Predicate<Object> filter = o -> true;
+
     public SlideShowExtractor(final SlideShow<S,P> slideshow) {
         setFilesystem(slideshow);
         this.slideshow = slideshow;
@@ -115,9 +124,8 @@ public class SlideShowExtractor<
     @Override
     public String getText() {
         final StringBuilder sb = new StringBuilder();
-        
         for (final Slide<S, P> slide : slideshow.getSlides()) {
-            sb.append(getText(slide));
+            getText(slide, sb::append);
         }
 
         return sb.toString();
@@ -125,34 +133,37 @@ public class SlideShowExtractor<
 
     public String getText(final Slide<S,P> slide) {
         final StringBuilder sb = new StringBuilder();
+        getText(slide, sb::append);
+        return sb.toString();
+    }
 
+
+    private void getText(final Slide<S,P> slide, final Consumer<String> consumer) {
         if (slidesByDefault) {
-            printShapeText(slide, sb);
+            printShapeText(slide, consumer);
         }
 
         if (masterByDefault) {
             final MasterSheet<S,P> ms = slide.getMasterSheet();
-            printSlideMaster(ms, sb);
+            printSlideMaster(ms, consumer);
 
             // only print slide layout, if it's a different instance
             final MasterSheet<S,P> sl = slide.getSlideLayout();
             if (sl != ms) {
-                printSlideMaster(sl, sb);
+                printSlideMaster(sl, consumer);
             }
         }
 
         if (commentsByDefault) {
-            printComments(slide, sb);
+            printComments(slide, consumer);
         }
 
         if (notesByDefault) {
-            printNotes(slide, sb);
+            printNotes(slide, consumer);
         }
-
-        return sb.toString();
     }
 
-    private void printSlideMaster(final MasterSheet<S,P> master, final StringBuilder sb) {
+    private void printSlideMaster(final MasterSheet<S,P> master, final Consumer<String> consumer) {
         if (master == null) {
             return;
         }
@@ -163,163 +174,140 @@ public class SlideShowExtractor<
                 if (text == null || text.isEmpty() || "*".equals(text)) {
                     continue;
                 }
+
                 if (ts.isPlaceholder()) {
                     // don't bother about boiler plate text on master sheets
                     LOG.log(POILogger.INFO, "Ignoring boiler plate (placeholder) text on slide master:", text);
                     continue;
                 }
-                sb.append(text);
-                if (!text.endsWith("\n")) {
-                    sb.append("\n");
-                }
 
+                printTextParagraphs(ts.getTextParagraphs(), consumer);
             }
         }
     }
 
-    private String printHeaderReturnFooter(final Sheet<S,P> sheet, final StringBuilder sb) {
-        final Sheet<S, P> m = (sheet instanceof Slide) ? sheet.getMasterSheet() : sheet;
-        final StringBuilder footer = new StringBuilder("\n");
-        addSheetPlaceholderDatails(sheet, Placeholder.HEADER, sb);
-        addSheetPlaceholderDatails(sheet, Placeholder.FOOTER, footer);
+    private void printTextParagraphs(final List<P> paras, final Consumer<String> consumer) {
+        printTextParagraphs(paras, consumer, "\n");
+    }
 
-        if (masterByDefault) {
-            // write header texts and determine footer text
-            for (Shape<S, P> s : m) {
-                if (!(s instanceof TextShape)) {
-                    continue;
-                }
-                final TextShape<S, P> ts = (TextShape<S, P>) s;
-                final PlaceholderDetails pd = ts.getPlaceholderDetails();
-                if (pd == null || !pd.isVisible() || pd.getPlaceholder() == null) {
-                    continue;
-                }
-                switch (pd.getPlaceholder()) {
-                    case HEADER:
-                        sb.append(ts.getText());
-                        sb.append('\n');
-                        break;
-                    case SLIDE_NUMBER:
-                        if (sheet instanceof Slide) {
-                            footer.append(ts.getText().replace("‹#›", Integer.toString(((Slide<S, P>) sheet).getSlideNumber() + 1)));
-                            footer.append('\n');
-                        }
-                        break;
-                    case FOOTER:
-                        footer.append(ts.getText());
-                        footer.append('\n');
-                        break;
-                    case DATETIME:
-                        // currently not supported
-                    default:
-                        break;
+
+    private void printTextParagraphs(final List<P> paras, final Consumer<String> consumer, String trailer) {
+        printTextParagraphs(paras, consumer, trailer, SlideShowExtractor::replaceTextCap);
+    }
+
+    private void printTextParagraphs(final List<P> paras, final Consumer<String> consumer, String trailer, final Function<TextRun,String> converter) {
+        for (P p : paras) {
+            for (TextRun r : p) {
+                if (filter.test(r)) {
+                    consumer.accept(converter.apply(r));
                 }
             }
+            if (!trailer.isEmpty() && filter.test(trailer)) {
+                consumer.accept(trailer);
+            }
         }
-
-        return (footer.length() > 1) ? footer.toString() : "";
     }
 
-    private void addSheetPlaceholderDatails(final Sheet<S,P> sheet, final Placeholder placeholder, final StringBuilder sb) {
-        final PlaceholderDetails headerPD = sheet.getPlaceholderDetails(placeholder);
-        if (headerPD == null) {
+    private void printHeaderFooter(final Sheet<S,P> sheet, final Consumer<String> consumer, final Consumer<String> footerCon) {
+        final Sheet<S, P> m = (sheet instanceof Slide) ? sheet.getMasterSheet() : sheet;
+        addSheetPlaceholderDatails(sheet, Placeholder.HEADER, consumer);
+        addSheetPlaceholderDatails(sheet, Placeholder.FOOTER, footerCon);
+
+        if (!masterByDefault) {
             return;
         }
-        final String headerStr = headerPD.getText();
-        if (headerStr == null) {
-            return;
+
+        // write header texts and determine footer text
+        for (Shape<S, P> s : m) {
+            if (!(s instanceof TextShape)) {
+                continue;
+            }
+            final TextShape<S, P> ts = (TextShape<S, P>) s;
+            final PlaceholderDetails pd = ts.getPlaceholderDetails();
+            if (pd == null || !pd.isVisible() || pd.getPlaceholder() == null) {
+                continue;
+            }
+            switch (pd.getPlaceholder()) {
+                case HEADER:
+                    printTextParagraphs(ts.getTextParagraphs(), consumer);
+                    break;
+                case FOOTER:
+                    printTextParagraphs(ts.getTextParagraphs(), footerCon);
+                    break;
+                case SLIDE_NUMBER:
+                    printTextParagraphs(ts.getTextParagraphs(), footerCon, "\n", SlideShowExtractor::replaceSlideNumber);
+                    break;
+                case DATETIME:
+                    // currently not supported
+                default:
+                    break;
+            }
         }
-        sb.append(headerStr);
     }
 
-    private void printShapeText(final Sheet<S,P> sheet, final StringBuilder sb) {
-        final String footer = printHeaderReturnFooter(sheet, sb);
-        printShapeText((ShapeContainer<S,P>)sheet, sb);
-        sb.append(footer);
+
+    private void addSheetPlaceholderDatails(final Sheet<S,P> sheet, final Placeholder placeholder, final Consumer<String> consumer) {
+        final PlaceholderDetails headerPD = sheet.getPlaceholderDetails(placeholder);
+        final String headerStr = (headerPD != null) ? headerPD.getText() : null;
+        if (headerStr != null && filter.test(headerPD)) {
+            consumer.accept(headerStr);
+        }
+    }
+
+    private void printShapeText(final Sheet<S,P> sheet, final Consumer<String> consumer) {
+        final List<String> footer = new LinkedList<>();
+        printHeaderFooter(sheet, consumer, footer::add);
+        printShapeText((ShapeContainer<S,P>)sheet, consumer);
+        footer.forEach(consumer);
     }
 
     @SuppressWarnings("unchecked")
-    private void printShapeText(final ShapeContainer<S,P> container, final StringBuilder sb) {
+    private void printShapeText(final ShapeContainer<S,P> container, final Consumer<String> consumer) {
         for (Shape<S,P> shape : container) {
             if (shape instanceof TextShape) {
-                printShapeText((TextShape<S,P>)shape, sb);
+                printTextParagraphs(((TextShape<S,P>)shape).getTextParagraphs(), consumer);
             } else if (shape instanceof TableShape) {
-                printShapeText((TableShape<S,P>)shape, sb);
+                printShapeText((TableShape<S,P>)shape, consumer);
             } else if (shape instanceof ShapeContainer) {
-                printShapeText((ShapeContainer<S,P>)shape, sb);
+                printShapeText((ShapeContainer<S,P>)shape, consumer);
             }
         }
     }
 
-    private void printShapeText(final TextShape<S,P> shape, final StringBuilder sb) {
-        final List<P> paraList = shape.getTextParagraphs();
-        if (paraList.isEmpty()) {
-            sb.append('\n');
-            return;
-        }
-        for (final P para : paraList) {
-            for (final TextRun tr : para) {
-                final String str = tr.getRawText().replace("\r", "");
-                final String newStr;
-                switch (tr.getTextCap()) {
-                    case ALL:
-                        newStr = str.toUpperCase(LocaleUtil.getUserLocale());
-                        break;
-                    case SMALL:
-                        newStr = str.toLowerCase(LocaleUtil.getUserLocale());
-                        break;
-                    default:
-                    case NONE:
-                        newStr = str;
-                        break;
-                }
-                sb.append(newStr);
-            }
-            sb.append('\n');
-        }
-    }
-
     @SuppressWarnings("Duplicates")
-    private void printShapeText(final TableShape<S,P> shape, final StringBuilder sb) {
+    private void printShapeText(final TableShape<S,P> shape, final Consumer<String> consumer) {
         final int nrows = shape.getNumberOfRows();
         final int ncols = shape.getNumberOfColumns();
-        for (int row = 0; row < nrows; row++){
+        for (int row = 0; row < nrows; row++) {
+            String trailer = "";
             for (int col = 0; col < ncols; col++){
                 TableCell<S, P> cell = shape.getCell(row, col);
                 //defensive null checks; don't know if they're necessary
-                if (cell != null){
-                    String txt = cell.getText();
-                    txt = (txt == null) ? "" : txt;
-                    sb.append(txt);
-                    if (col < ncols-1){
-                        sb.append('\t');
-                    }
+                if (cell != null) {
+                    trailer = col < ncols-1 ? "\t" : "\n";
+                    printTextParagraphs(cell.getTextParagraphs(), consumer, trailer);
                 }
             }
-            sb.append('\n');
+            if (!trailer.equals("\n") && filter.test("\n")) {
+                consumer.accept("\n");
+            }
         }
     }
 
-    private void printComments(final Slide<S,P> slide, final StringBuilder sb) {
-        for (final Comment comment : slide.getComments()) {
-            sb.append(comment.getAuthor());
-            sb.append(" - ");
-            sb.append(comment.getText());
-            sb.append("\n");
-        }
+    private void printComments(final Slide<S,P> slide, final Consumer<String> consumer) {
+        slide.getComments().stream().filter(filter).map(c -> c.getAuthor()+" - "+c.getText()).forEach(consumer);
     }
 
-    private void printNotes(final Slide<S,P> slide, final StringBuilder sb) {
+    private void printNotes(final Slide<S,P> slide, final Consumer<String> consumer) {
         final Notes<S, P> notes = slide.getNotes();
         if (notes == null) {
             return;
         }
 
-        final String footer = printHeaderReturnFooter(notes, sb);
-
-        printShapeText(notes, sb);
-
-        sb.append(footer);
+        List<String> footer = new LinkedList<>();
+        printHeaderFooter(notes, consumer, footer::add);
+        printShapeText(notes, consumer);
+        footer.forEach(consumer);
     }
 
     public List<? extends ObjectShape<S,P>> getOLEShapes() {
@@ -342,4 +330,83 @@ public class SlideShowExtractor<
             }
         }
     }
+
+    private static String replaceSlideNumber(TextRun tr) {
+        String raw = tr.getRawText();
+
+        if (!raw.contains(SLIDE_NUMBER_PH)) {
+            return raw;
+        }
+
+        TextParagraph tp = tr.getParagraph();
+        TextShape ps = (tp != null) ? tp.getParentShape() : null;
+        Sheet sh = (ps != null) ? ps.getSheet() : null;
+        String slideNr = (sh instanceof Slide) ? Integer.toString(((Slide)sh).getSlideNumber() + 1) : "";
+
+        return raw.replace(SLIDE_NUMBER_PH, slideNr);
+    }
+
+    private static String replaceTextCap(TextRun tr) {
+        final TextParagraph tp = tr.getParagraph();
+        final TextShape sh = (tp != null) ? tp.getParentShape() : null;
+        final Placeholder ph = (sh != null) ? sh.getPlaceholder() : null;
+
+        // 0xB acts like cariage return in page titles and like blank in the others
+        final char sep = (
+            ph == Placeholder.TITLE ||
+            ph == Placeholder.CENTERED_TITLE ||
+            ph == Placeholder.SUBTITLE
+        ) ? '\n' : ' ';
+
+        // PowerPoint seems to store files with \r as the line break
+        // The messes things up on everything but a Mac, so translate them to \n
+        String txt = tr.getRawText();
+        txt = txt.replace('\r', '\n');
+        txt = txt.replace((char) 0x0B, sep);
+
+        switch (tr.getTextCap()) {
+            case ALL:
+                txt = txt.toUpperCase(LocaleUtil.getUserLocale());
+            case SMALL:
+                txt = txt.toLowerCase(LocaleUtil.getUserLocale());
+        }
+
+        return txt;
+    }
+
+    /**
+     * Extract the used codepoints for font embedding / subsetting
+     * @param typeface the typeface/font family of the textruns to examine
+     * @param italic use {@code true} for italic TextRuns, {@code false} for non-italic ones and
+     *      {@code null} if it doesn't matter
+     * @param bold use {@code true} for bold TextRuns, {@code false} for non-bold ones and
+     *      {@code null} if it doesn't matter
+     * @return a bitset with the marked/used codepoints
+     */
+    public BitSet getCodepoints(String typeface, Boolean italic, Boolean bold) {
+        final BitSet glyphs = new BitSet();
+
+        Predicate<Object> filterOld = filter;
+        try {
+            filter = o -> filterFonts(o, typeface, italic, bold);
+            slideshow.getSlides().forEach(slide ->
+                getText(slide, s -> s.codePoints().forEach(glyphs::set))
+            );
+        } finally {
+            filter = filterOld;
+        }
+
+        return glyphs;
+    }
+
+    private static boolean filterFonts(Object o, String typeface, Boolean italic, Boolean bold) {
+        if (!(o instanceof TextRun)) {
+            return false;
+        }
+        TextRun tr = (TextRun)o;
+        return
+            typeface.equalsIgnoreCase(tr.getFontFamily()) &&
+            (italic == null || tr.isItalic() == italic) &&
+            (bold == null || tr.isBold() == bold);
+    }
 }
diff --git a/src/java/org/apache/poi/sl/usermodel/FontCollection.java b/src/java/org/apache/poi/sl/usermodel/FontCollection.java
deleted file mode 100644 (file)
index 61278f4..0000000
+++ /dev/null
@@ -1,22 +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.sl.usermodel;
-
-public interface FontCollection {
-
-}
diff --git a/src/java/org/apache/poi/sl/usermodel/Resources.java b/src/java/org/apache/poi/sl/usermodel/Resources.java
deleted file mode 100644 (file)
index 96170e5..0000000
+++ /dev/null
@@ -1,29 +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.sl.usermodel;
-
-/**
- * Common SlideShow resources, such as fonts, pictures
- *  and multimedia data
- */
-public interface Resources {
-       public FontCollection getFontCollection();
-
-       public PictureData[] getPictureData();
-       public int addPictureData(PictureData pict);
-}
index 91b80f107e48296926a2413f96d4212ff5ca6129..7c0d5661380ec1cadd3c9f7e9cf14402b84bfaf4 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.sl.usermodel;
 
 import java.util.List;
 
+@SuppressWarnings("unused")
 public interface Slide<
     S extends Shape<S,P>,
     P extends TextParagraph<S,P,? extends TextRun>
@@ -82,7 +83,7 @@ public interface Slide<
      *
      * @since POI 4.0.0
      */
-    MasterSheet getSlideLayout();
+    MasterSheet<S,P> getSlideLayout();
 
     /**
      * @return the slide name, defaults to "Slide[slideNumber]"
index 175ad2b00e56f0c3805c49cc1fef371f925ce255..751379de927a33d96b31ad239e023b0696ee4cd9 100644 (file)
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
 
+import org.apache.poi.common.usermodel.fonts.FontInfo;
 import org.apache.poi.extractor.POITextExtractor;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
 
@@ -44,8 +45,6 @@ public interface SlideShow<
      */
        List<? extends MasterSheet<S,P>> getSlideMasters();
 
-       Resources getResources();
-
     /**
      * Returns the current page size
      *
@@ -135,4 +134,30 @@ public interface SlideShow<
      * @since POI 4.0.0
      */
     Object getPersistDocument();
+
+    /**
+     * Add an EOT font to the slideshow.
+     * An EOT or MTX font is a transformed True-Type (.ttf) or Open-Type (.otf) font.
+     * To transform a True-Type font use the sfntly library (see "see also" below)<p>
+     *
+     * (Older?) Powerpoint versions handle embedded fonts by converting them to .ttf files
+     * and put them into the Windows fonts directory. If the user is not allowed to install
+     * fonts, the slideshow can't be opened. While the slideshow is opened, its possible
+     * to copy the extracted .ttfs from the fonts directory. When the slideshow is closed,
+     * they will be removed.
+     *
+     * @param fontData the EOT font as stream
+     * @return the font info object containing the new font data
+     * @throws IOException if the fontData can't be saved or if the fontData is no EOT font
+     *
+     * @see <a href="http://www.w3.org/Submission/EOT">EOT specification</a>
+     * @see <a href="https://github.com/googlei18n/sfntly">googles sfntly library</a>
+     * @see <a href="https://github.com/kiwiwings/poi-font-mbender">Example on how to subset and embed fonts</a>
+     */
+    FontInfo addFont(InputStream fontData) throws IOException;
+
+    /**
+     * @return a list of registered fonts
+     */
+    List<? extends FontInfo> getFonts();
 }
index 394166071cc36751287c761865cbffa2ee36c509..7dfd4933d83f8fc1c0905bd467c21a299c08ed21 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.poi.util.Internal;
 /**
  * Some text.
  */
+@SuppressWarnings("unused")
 public interface TextRun {
     /**
      * Type of text capitals
@@ -243,4 +244,11 @@ public interface TextRun {
      */
     @Internal
     FieldType getFieldType();
+
+    /**
+     * @return the paragraph which contains this TextRun
+     *
+     * @since POI 4.1.0
+     */
+    TextParagraph<?,?,?> getParagraph();
 }
index d596993fe88d84bd2c0d5e9026c36bced97ffb0f..ff12d6aad2d70838074d63ea527a5030a561e036 100644 (file)
@@ -30,19 +30,20 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.OptionalLong;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import org.apache.poi.ooxml.POIXMLDocument;
 import org.apache.poi.ooxml.POIXMLDocumentPart;
 import org.apache.poi.ooxml.POIXMLException;
 import org.apache.poi.ooxml.extractor.POIXMLPropertiesTextExtractor;
 import org.apache.poi.ooxml.util.PackageHelper;
-import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.sl.usermodel.MasterSheet;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
-import org.apache.poi.sl.usermodel.Resources;
 import org.apache.poi.sl.usermodel.SlideShow;
 import org.apache.poi.util.Beta;
 import org.apache.poi.util.IOUtils;
@@ -60,7 +61,6 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMasterIdListE
 import org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdList;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdListEntry;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideSize;
 import org.openxmlformats.schemas.presentationml.x2006.main.PresentationDocument;
 
@@ -70,6 +70,7 @@ import org.openxmlformats.schemas.presentationml.x2006.main.PresentationDocument
  * they are reading or writing a slideshow. It is also the
  * top level object for creating new slides/etc.
  */
+@SuppressWarnings("WeakerAccess")
 @Beta
 public class XMLSlideShow extends POIXMLDocument
         implements SlideShow<XSLFShape, XSLFTextParagraph> {
@@ -78,10 +79,10 @@ public class XMLSlideShow extends POIXMLDocument
     private static final int MAX_RECORD_LENGTH = 1_000_000;
 
     private CTPresentation _presentation;
-    private List<XSLFSlide> _slides;
-    private List<XSLFSlideMaster> _masters;
-    private List<XSLFPictureData> _pictures;
-    private List<XSLFChart> _charts;
+    private final List<XSLFSlide> _slides = new ArrayList<>();
+    private final List<XSLFSlideMaster> _masters = new ArrayList<>();
+    private final List<XSLFPictureData> _pictures = new ArrayList<>();
+    private final List<XSLFChart> _charts = new ArrayList<>();
     private XSLFTableStyles _tableStyles;
     private XSLFNotesMaster _notesMaster;
     private XSLFCommentAuthors _commentAuthors;
@@ -153,27 +154,26 @@ public class XMLSlideShow extends POIXMLDocument
                 }
             }
 
-            _charts = new ArrayList<>(chartMap.size());
-            for (XSLFChart chart : chartMap.values()) {
-                _charts.add(chart);
-            }
+            _charts.clear();
+            _charts.addAll(chartMap.values());
 
-            _masters = new ArrayList<>(masterMap.size());
-            for (CTSlideMasterIdListEntry masterId : _presentation.getSldMasterIdLst().getSldMasterIdList()) {
-                XSLFSlideMaster master = masterMap.get(masterId.getId2());
-                _masters.add(master);
+            _masters.clear();
+            if (_presentation.isSetSldMasterIdLst()) {
+                _presentation.getSldMasterIdLst().getSldMasterIdList().forEach(
+                   id -> _masters.add(masterMap.get(id.getId2()))
+                );
             }
 
-            _slides = new ArrayList<>(shIdMap.size());
+            _slides.clear();
             if (_presentation.isSetSldIdLst()) {
-                for (CTSlideIdListEntry slId : _presentation.getSldIdLst().getSldIdList()) {
-                    XSLFSlide sh = shIdMap.get(slId.getId2());
+                _presentation.getSldIdLst().getSldIdList().forEach(id -> {
+                    XSLFSlide sh = shIdMap.get(id.getId2());
                     if (sh == null) {
-                        LOG.log(POILogger.WARN, "Slide with r:id " + slId.getId() + " was defined, but didn't exist in package, skipping");
-                        continue;
+                        LOG.log(POILogger.WARN, "Slide with r:id " + id.getId() + " was defined, but didn't exist in package, skipping");
+                    } else {
+                        _slides.add(sh);
                     }
-                    _slides.add(sh);
-                }
+                });
             }
         } catch (XmlException e) {
             throw new POIXMLException(e);
@@ -192,7 +192,7 @@ public class XMLSlideShow extends POIXMLDocument
      * Get the document's embedded files.
      */
     @Override
-    public List<PackagePart> getAllEmbeddedParts() throws OpenXML4JException {
+    public List<PackagePart> getAllEmbeddedParts() {
         return Collections.unmodifiableList(
                 getPackage().getPartsByName(Pattern.compile("/ppt/embeddings/.*?"))
         );
@@ -200,14 +200,12 @@ public class XMLSlideShow extends POIXMLDocument
 
     @Override
     public List<XSLFPictureData> getPictureData() {
-        if (_pictures == null) {
-            List<PackagePart> mediaParts = getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?"));
-            _pictures = new ArrayList<>(mediaParts.size());
-            for (PackagePart part : mediaParts) {
+        if (_pictures.isEmpty()) {
+            getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?")).forEach(part -> {
                 XSLFPictureData pd = new XSLFPictureData(part);
                 pd.setIndex(_pictures.size());
                 _pictures.add(pd);
-            }
+            });
         }
         return Collections.unmodifiableList(_pictures);
     }
@@ -219,20 +217,16 @@ public class XMLSlideShow extends POIXMLDocument
      * @return created slide
      */
     public XSLFSlide createSlide(XSLFSlideLayout layout) {
-        int slideNumber = 256, cnt = 1;
-        CTSlideIdList slideList;
-        XSLFRelation relationType = XSLFRelation.SLIDE;
-        if (!_presentation.isSetSldIdLst()) {
-            slideList = _presentation.addNewSldIdLst();
-        } else {
-            slideList = _presentation.getSldIdLst();
-            for (CTSlideIdListEntry slideId : slideList.getSldIdArray()) {
-                slideNumber = (int) Math.max(slideId.getId() + 1, slideNumber);
-                cnt++;
-            }
+        CTSlideIdList slideList = _presentation.isSetSldIdLst()
+            ? _presentation.getSldIdLst() : _presentation.addNewSldIdLst();
 
-            cnt = findNextAvailableFileNameIndex(relationType, cnt);
-        }
+        @SuppressWarnings("deprecation")
+        OptionalLong maxId = Stream.of(slideList.getSldIdArray())
+            .mapToLong(CTSlideIdListEntry::getId).max();
+
+        final XSLFRelation relationType = XSLFRelation.SLIDE;
+        final int slideNumber = (int)(Math.max(maxId.orElse(0),255)+1);
+        final int cnt = findNextAvailableFileNameIndex(relationType);
 
         RelationPart rp = createRelationship
                 (relationType, XSLFFactory.getInstance(), cnt, false);
@@ -250,33 +244,14 @@ public class XMLSlideShow extends POIXMLDocument
         return slide;
     }
 
-    private int findNextAvailableFileNameIndex(XSLFRelation relationType, int idx) {
+    private int findNextAvailableFileNameIndex(XSLFRelation relationType) {
         // Bug 55791: We also need to check that the resulting file name is not already taken
         // this can happen when removing/adding slides, notes or charts
-        while (true) {
-            String fileName = relationType.getFileName(idx);
-            boolean found = false;
-            for (POIXMLDocumentPart relation : getRelations()) {
-                if (relation.getPackagePart() != null &&
-                        fileName.equals(relation.getPackagePart().getPartName().getName())) {
-                    // name is taken => try next one
-                    found = true;
-                    break;
-                }
-            }
-
-            if (!found &&
-                    getPackage().getPartsByName(Pattern.compile(Pattern.quote(fileName))).size() > 0) {
-                // name is taken => try next one
-                found = true;
-            }
-
-            if (!found) {
-                break;
-            }
-            idx++;
+        try {
+            return getPackage().getUnusedPartIndex(relationType.getDefaultFileName());
+        } catch (InvalidFormatException e) {
+            throw new RuntimeException(e);
         }
-        return idx;
     }
 
     /**
@@ -313,8 +288,8 @@ public class XMLSlideShow extends POIXMLDocument
      * @since POI 4.1.0
      */
     public XSLFChart createChart() {
-        int chartIdx = findNextAvailableFileNameIndex(XSLFRelation.CHART, _charts.size() + 1);
-        XSLFChart chart = (XSLFChart) createRelationship(XSLFRelation.CHART, XSLFFactory.getInstance(), chartIdx, true).getDocumentPart();
+        int chartIdx = findNextAvailableFileNameIndex(XSLFRelation.CHART);
+        XSLFChart chart = createRelationship(XSLFRelation.CHART, XSLFFactory.getInstance(), chartIdx, true).getDocumentPart();
         chart.setChartIndex(chartIdx);
         _charts.add(chart);
         return chart;
@@ -341,10 +316,8 @@ public class XMLSlideShow extends POIXMLDocument
             createNotesMaster();
         }
 
-        int slideIndex = XSLFRelation.SLIDE.getFileNameIndex(slide);
-
         XSLFRelation relationType = XSLFRelation.NOTES;
-        slideIndex = findNextAvailableFileNameIndex(relationType, slideIndex);
+        int slideIndex = findNextAvailableFileNameIndex(relationType);
 
         // add notes slide to presentation
         XSLFNotes notesSlide = (XSLFNotes) createRelationship
@@ -453,6 +426,7 @@ public class XMLSlideShow extends POIXMLDocument
 
         // fix ordering in the low-level xml
         CTSlideIdList sldIdLst = _presentation.getSldIdLst();
+        @SuppressWarnings("deprecation")
         CTSlideIdListEntry[] entries = sldIdLst.getSldIdArray();
         CTSlideIdListEntry oldEntry = entries[oldIndex];
         if (oldIndex < newIndex) {
@@ -517,14 +491,21 @@ public class XMLSlideShow extends POIXMLDocument
             return img;
         }
 
-        int imageNumber = _pictures.size();
+
         XSLFRelation relType = XSLFPictureData.getRelationForType(format);
         if (relType == null) {
             throw new IllegalArgumentException("Picture type " + format + " is not supported.");
         }
 
-        img = createRelationship(relType, XSLFFactory.getInstance(), imageNumber + 1, true).getDocumentPart();
-        img.setIndex(imageNumber);
+        int imageNumber;
+        try {
+            imageNumber = getPackage().getUnusedPartIndex("/ppt/media/image#\\..+");
+        } catch (InvalidFormatException e) {
+            imageNumber = _pictures.size() + 1;
+        }
+
+        img = createRelationship(relType, XSLFFactory.getInstance(), imageNumber, true).getDocumentPart();
+        img.setIndex(_pictures.size());
         _pictures.add(img);
 
         try (OutputStream out = img.getPackagePart().getOutputStream()) {
@@ -624,18 +605,13 @@ public class XMLSlideShow extends POIXMLDocument
         return null;
     }
 
+    @SuppressWarnings("RedundantThrows")
     @Override
     public MasterSheet<XSLFShape, XSLFTextParagraph> createMasterSheet() throws IOException {
         // TODO: implement!
         throw new UnsupportedOperationException();
     }
 
-    @Override
-    public Resources getResources() {
-        // TODO: implement!
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public POIXMLPropertiesTextExtractor getMetadataTextExtractor() {
         return new POIXMLPropertiesTextExtractor(this);
@@ -645,4 +621,14 @@ public class XMLSlideShow extends POIXMLDocument
     public Object getPersistDocument() {
         return this;
     }
+
+    @Override
+    public XSLFFontInfo addFont(InputStream fontStream) throws IOException {
+        return XSLFFontInfo.addFontToSlideShow(this, fontStream);
+    }
+
+    @Override
+    public List<XSLFFontInfo> getFonts() {
+        return XSLFFontInfo.getFonts(this);
+    }
 }
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontData.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontData.java
new file mode 100644 (file)
index 0000000..8b2cb09
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  ====================================================================
+ *    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 java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.util.Beta;
+
+/**
+ * A container for fontdata files, i.e. MTX fonts derived from
+ * true (TTF) or open (OTF) type fonts.
+ *
+ * @since POI 4.1.0
+ */
+@Beta
+public class XSLFFontData extends POIXMLDocumentPart {
+    /**
+     * Create a new XSLFFontData node
+     */
+    @SuppressWarnings("unused")
+    protected XSLFFontData() {
+        super();
+    }
+
+    /**
+     * Construct XSLFFontData from a package part
+     *
+     * @param part the package part holding the ole data
+     */
+    @SuppressWarnings("unused")
+    public XSLFFontData(final PackagePart part) {
+        super(part);
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return getPackagePart().getInputStream();
+    }
+
+    public OutputStream getOutputStream() {
+        final PackagePart pp = getPackagePart();
+        pp.clear();
+        return pp.getOutputStream();
+    }
+
+    /**
+     * XSLFFontData objects store the actual content in the part directly without keeping a
+     * copy like all others therefore we need to handle them differently.
+     */
+    @Override
+    protected void prepareForCommit() {
+        // do not clear the part here
+    }
+
+
+    public void setData(final byte[] data) throws IOException {
+        try (final OutputStream os = getPackagePart().getOutputStream()) {
+            os.write(data);
+        }
+    }
+
+
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontInfo.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFontInfo.java
new file mode 100644 (file)
index 0000000..1e74621
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ *  ====================================================================
+ *    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 java.awt.Font;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.poi.common.usermodel.fonts.FontCharset;
+import org.apache.poi.common.usermodel.fonts.FontFacet;
+import org.apache.poi.common.usermodel.fonts.FontFamily;
+import org.apache.poi.common.usermodel.fonts.FontHeader;
+import org.apache.poi.common.usermodel.fonts.FontInfo;
+import org.apache.poi.common.usermodel.fonts.FontPitch;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.util.IOUtils;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTEmbeddedFontDataId;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTEmbeddedFontList;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTEmbeddedFontListEntry;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation;
+
+@SuppressWarnings("WeakerAccess")
+public class XSLFFontInfo implements FontInfo {
+    final XMLSlideShow ppt;
+    final String typeface;
+    final CTEmbeddedFontListEntry fontListEntry;
+
+    public XSLFFontInfo(XMLSlideShow ppt, String typeface) {
+        this.ppt = ppt;
+        this.typeface = typeface;
+
+        final CTPresentation pres = ppt.getCTPresentation();
+        CTEmbeddedFontList fontList = pres.isSetEmbeddedFontLst()
+            ? pres.getEmbeddedFontLst() : pres.addNewEmbeddedFontLst();
+
+        for (CTEmbeddedFontListEntry fe : fontList.getEmbeddedFontArray()) {
+            if (typeface.equalsIgnoreCase(fe.getFont().getTypeface())) {
+                fontListEntry = fe;
+                return;
+            }
+        }
+
+        fontListEntry = fontList.addNewEmbeddedFont();
+        fontListEntry.addNewFont().setTypeface(typeface);
+    }
+
+    public XSLFFontInfo(XMLSlideShow ppt, CTEmbeddedFontListEntry fontListEntry) {
+        this.ppt = ppt;
+        this.typeface = fontListEntry.getFont().getTypeface();
+        this.fontListEntry = fontListEntry;
+    }
+
+    @Override
+    public String getTypeface() {
+        return getFont().getTypeface();
+    }
+
+    @Override
+    public void setTypeface(String typeface) {
+        getFont().setTypeface(typeface);
+    }
+
+    @Override
+    public FontCharset getCharset() {
+        return FontCharset.valueOf(getFont().getCharset());
+    }
+
+    @Override
+    public void setCharset(FontCharset charset) {
+        getFont().setCharset((byte)charset.getNativeId());
+    }
+
+    @Override
+    public FontFamily getFamily() {
+        return FontFamily.valueOfPitchFamily(getFont().getPitchFamily());
+    }
+
+    @Override
+    public void setFamily(FontFamily family) {
+        byte pitchAndFamily = getFont().getPitchFamily();
+        FontPitch pitch = FontPitch.valueOfPitchFamily(pitchAndFamily);
+        getFont().setPitchFamily(FontPitch.getNativeId(pitch, family));
+    }
+
+    @Override
+    public FontPitch getPitch() {
+        return FontPitch.valueOfPitchFamily(getFont().getPitchFamily());
+    }
+
+    @Override
+    public void setPitch(FontPitch pitch) {
+        byte pitchAndFamily = getFont().getPitchFamily();
+        FontFamily family = FontFamily.valueOfPitchFamily(pitchAndFamily);
+        getFont().setPitchFamily(FontPitch.getNativeId(pitch, family));
+    }
+
+    @Override
+    public byte[] getPanose() {
+        return getFont().getPanose();
+    }
+
+    @Override
+    public List<FontFacet> getFacets() {
+        List<FontFacet> facetList = new ArrayList<>();
+        if (fontListEntry.isSetRegular()) {
+            facetList.add(new XSLFFontFacet((fontListEntry.getRegular())));
+        }
+        if (fontListEntry.isSetItalic()) {
+            facetList.add(new XSLFFontFacet((fontListEntry.getItalic())));
+        }
+        if (fontListEntry.isSetBold()) {
+            facetList.add(new XSLFFontFacet((fontListEntry.getBold())));
+        }
+        if (fontListEntry.isSetBoldItalic()) {
+            facetList.add(new XSLFFontFacet((fontListEntry.getBoldItalic())));
+        }
+        return facetList;
+    }
+
+    public FontFacet addFacet(InputStream fontData) throws IOException {
+        FontHeader header = new FontHeader();
+        InputStream is = header.bufferInit(fontData);
+
+        final CTPresentation pres = ppt.getCTPresentation();
+        pres.setEmbedTrueTypeFonts(true);
+        pres.setSaveSubsetFonts(true);
+
+        final CTEmbeddedFontDataId dataId;
+        final int style =
+                (header.getWeight() > 400 ? Font.BOLD : Font.PLAIN) |
+                        (header.isItalic() ? Font.ITALIC : Font.PLAIN);
+        switch (style) {
+            case Font.PLAIN:
+                dataId = fontListEntry.isSetRegular()
+                    ? fontListEntry.getRegular() : fontListEntry.addNewRegular();
+                break;
+            case Font.BOLD:
+                dataId = fontListEntry.isSetBold()
+                    ? fontListEntry.getBold() : fontListEntry.addNewBold();
+                break;
+            case Font.ITALIC:
+                dataId = fontListEntry.isSetItalic()
+                    ? fontListEntry.getItalic() : fontListEntry.addNewItalic();
+                break;
+            default:
+                dataId = fontListEntry.isSetBoldItalic()
+                    ? fontListEntry.getBoldItalic() : fontListEntry.addNewBoldItalic();
+                break;
+        }
+
+        XSLFFontFacet facet = new XSLFFontFacet(dataId);
+        facet.setFontData(is);
+        return facet;
+    }
+
+    private final class XSLFFontFacet implements FontFacet {
+        private final CTEmbeddedFontDataId fontEntry;
+        private final FontHeader header = new FontHeader();
+
+        private XSLFFontFacet(CTEmbeddedFontDataId fontEntry) {
+            this.fontEntry = fontEntry;
+        }
+
+        @Override
+        public int getWeight() {
+            init();
+            return header.getWeight();
+        }
+
+        @Override
+        public boolean isItalic() {
+            init();
+            return header.isItalic();
+        }
+
+        @Override
+        public XSLFFontData getFontData() {
+            return ppt.getRelationPartById(fontEntry.getId()).getDocumentPart();
+        }
+
+        void setFontData(InputStream is) throws IOException {
+            final XSLFRelation fntRel = XSLFRelation.FONT;
+            final String relId = fontEntry.getId();
+            final XSLFFontData fntData;
+            if (relId == null || relId.isEmpty()) {
+                final int fntDataIdx;
+                try {
+                    fntDataIdx = ppt.getPackage().getUnusedPartIndex(fntRel.getDefaultFileName());
+                } catch (InvalidFormatException e) {
+                    throw new RuntimeException(e);
+                }
+
+                POIXMLDocumentPart.RelationPart rp = ppt.createRelationship(fntRel, XSLFFactory.getInstance(), fntDataIdx, false);
+                fntData = rp.getDocumentPart();
+                fontEntry.setId(rp.getRelationship().getId());
+            } else {
+                fntData = (XSLFFontData)ppt.getRelationById(relId);
+            }
+
+            assert (fntData != null);
+            try (OutputStream os = fntData.getOutputStream()) {
+                IOUtils.copy(is, os);
+            }
+        }
+
+        private void init() {
+            if (header.getFamilyName() == null) {
+                try (InputStream is = getFontData().getInputStream()) {
+                    byte[] buf = IOUtils.toByteArray(is, 1000);
+                    header.init(buf, 0, buf.length);
+                } catch (IOException e) {
+                    // TODO: better exception class
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+
+    private CTTextFont getFont() {
+        return fontListEntry.getFont();
+    }
+
+
+
+    /**
+     * Adds or updates a (MTX-) font
+     * @param ppt the slideshow which will contain the font
+     * @param fontStream the (MTX) font data as stream
+     * @return a font data object
+     * @throws IOException if the font data can't be stored
+     *
+     * @since POI 4.1.0
+     */
+    public static XSLFFontInfo addFontToSlideShow(XMLSlideShow ppt, InputStream fontStream)
+    throws IOException {
+        FontHeader header = new FontHeader();
+        InputStream is = header.bufferInit(fontStream);
+
+        XSLFFontInfo fontInfo = new XSLFFontInfo(ppt, header.getFamilyName());
+        fontInfo.addFacet(is);
+        return fontInfo;
+    }
+
+    /**
+     * Return all registered fonts
+     * @param ppt the slideshow containing the fonts
+     * @return the list of registered fonts
+     */
+    public static List<XSLFFontInfo> getFonts(XMLSlideShow ppt) {
+        final CTPresentation pres = ppt.getCTPresentation();
+
+        //noinspection deprecation
+        return pres.isSetEmbeddedFontLst()
+            ? Stream.of(pres.getEmbeddedFontLst().getEmbeddedFontArray())
+                .map(fe -> new XSLFFontInfo(ppt, fe)).collect(Collectors.toList())
+            : Collections.emptyList();
+    }
+
+}
index 9b9fc9c8445b6bf20fdbabdb2a6fdb0409ef9ef1..ca2d9d7ba2bd8ab551d80af6268c161cffd9cc45 100644 (file)
@@ -260,6 +260,13 @@ public final class XSLFRelation extends POIXMLRelation {
             XSLFObjectData.class
     );
 
+    public static final XSLFRelation FONT = new XSLFRelation(
+            "application/x-fontdata",
+            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font",
+            "/ppt/fonts/font#.fntdata",
+            XSLFFontData.class
+    );
+
 
     private XSLFRelation(String type, String rel, String defaultName, Class<? extends POIXMLDocumentPart> cls) {
         super(type, rel, defaultName, cls);
index be714cbd858967fbcd002fbb40625c406b5a7c79..6d2d215181062bdad33345bd5ba147bbb924a8d5 100644 (file)
@@ -28,6 +28,7 @@ import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.sl.draw.DrawPaint;
 import org.apache.poi.sl.usermodel.PaintStyle;
 import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
+import org.apache.poi.sl.usermodel.TextParagraph;
 import org.apache.poi.sl.usermodel.TextRun;
 import org.apache.poi.util.Beta;
 import org.apache.poi.util.POILogFactory;
@@ -628,16 +629,6 @@ public class XSLFTextRun implements TextRun {
             }
         }
 
-        @Override
-        public Integer getIndex() {
-            return null;
-        }
-
-        @Override
-        public void setIndex(int index) {
-            throw new UnsupportedOperationException("setIndex not supported by XSLFFontInfo.");
-        }
-
         @Override
         public String getTypeface() {
             CTTextFont tf = getXmlObject(false);
@@ -829,4 +820,9 @@ public class XSLFTextRun implements TextRun {
             return font;
         }
     }
+
+    @Override
+    public XSLFTextParagraph getParagraph() {
+        return _p;
+    }
 }
index e63762d2909b081b06fdfd8511d315b81719e4e2..3f71533dd5a7b40dcd58b7f76427179e90887c40 100644 (file)
@@ -59,7 +59,7 @@ public class TestXSLFPowerPointExtractor {
 
             // Check Basics
             assertStartsWith(text, "Lorem ipsum dolor sit amet\n");
-            assertContains(text, "amet\n\n");
+            assertContains(text, "amet\n");
 
             // Our placeholder master text
             // This shouldn't show up in the output
@@ -96,7 +96,7 @@ public class TestXSLFPowerPointExtractor {
             extractor.setSlidesByDefault(false);
             extractor.setNotesByDefault(true);
             text = extractor.getText();
-            assertEquals("\n\n1\n\n\n2\n", text);
+            assertEquals("\n1\n\n2\n", text);
 
             // Both
             extractor.setSlidesByDefault(true);
@@ -105,14 +105,14 @@ public class TestXSLFPowerPointExtractor {
             String bothText =
                 "Lorem ipsum dolor sit amet\n" +
                 "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" +
-                "\n\n\n1\n" +
+                "\n\n1\n" +
                 "Lorem ipsum dolor sit amet\n" +
                 "Lorem\n" +
                 "ipsum\n" +
                 "dolor\n" +
                 "sit\n" +
                 "amet\n" +
-                "\n\n\n2\n";
+                "\n\n2\n";
             assertEquals(bothText, text);
 
             // With Slides and Master Text
@@ -141,21 +141,21 @@ public class TestXSLFPowerPointExtractor {
             String snmText =
                 "Lorem ipsum dolor sit amet\n" +
                 "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" +
-                "\n\n\n1\n" +
+                "\n\n1\n" +
                 "Lorem ipsum dolor sit amet\n" +
                 "Lorem\n" +
                 "ipsum\n" +
                 "dolor\n" +
                 "sit\n" +
                 "amet\n" +
-                "\n\n\n2\n";
+                "\n\n2\n";
             assertEquals(snmText, text);
 
             // Via set defaults
             extractor.setSlidesByDefault(false);
             extractor.setNotesByDefault(true);
             text = extractor.getText();
-            assertEquals("\n\n1\n\n\n2\n", text);
+            assertEquals("\n1\n\n2\n", text);
         }
        }
 
index afaf8d65b52b7f06e67bc71ee3e07995f93a8151..bb6167ee2fec4429bcec8367407833ad5f731122 100644 (file)
@@ -38,7 +38,7 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMasterIdListE
 import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdListEntry;
 
-public class TestXMLSlideShow extends BaseTestSlideShow {
+public class TestXMLSlideShow extends BaseTestSlideShow<XSLFShape,XSLFTextParagraph> {
    private OPCPackage pack;
    
    @Override
@@ -81,6 +81,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
       xml.close();
    }
 
+   @SuppressWarnings("deprecation")
    @Test
    public void testSlideBasics() throws IOException {
       XMLSlideShow xml = new XMLSlideShow(pack);
@@ -136,7 +137,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
       assertEquals(0, xml.getProperties().getExtendedProperties().getUnderlyingProperties().getCharacters());
       assertEquals(0, xml.getProperties().getExtendedProperties().getUnderlyingProperties().getLines());
 
-      assertEquals(null, xml.getProperties().getCoreProperties().getTitle());
+      assertNull(xml.getProperties().getCoreProperties().getTitle());
       assertFalse(xml.getProperties().getCoreProperties().getUnderlyingProperties().getSubjectProperty().isPresent());
       
       xml.close();
@@ -146,8 +147,8 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
    public void testComments() throws Exception {
       // Default sample file has none
       XMLSlideShow xml = new XMLSlideShow(pack);
-      
-      assertEquals(null, xml.getCommentAuthors());
+
+      assertNull(xml.getCommentAuthors());
       
       for (XSLFSlide slide : xml.getSlides()) {
          assertTrue(slide.getComments().isEmpty());
@@ -186,19 +187,16 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
       xml.close();
    }
    
-   public SlideShow<?, ?> reopen(SlideShow<?, ?> show) {
-       return reopen((XMLSlideShow)show);
-   }
-
-   private 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;
-       }
+   @Override
+   public XMLSlideShow reopen(SlideShow<XSLFShape,XSLFTextParagraph> 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 {
index 5e4019b0015e53be8ec322ff1bedb389e34a4e76..4fd37a9b35e0e3af3b78bbe1595aac798e4d09d6 100644 (file)
@@ -87,11 +87,11 @@ public final class Document extends PositionDependentRecordContainer
         *  Master Slides
         */
        public SlideListWithText getMasterSlideListWithText() {
-        for (int i = 0; i < slwts.length; i++) {
-            if(slwts[i].getInstance() == SlideListWithText.MASTER) {
-                return slwts[i];
-            }
-        }
+               for (SlideListWithText slwt : slwts) {
+                       if (slwt.getInstance() == SlideListWithText.MASTER) {
+                               return slwt;
+                       }
+               }
         return null;
     }
 
@@ -100,11 +100,11 @@ public final class Document extends PositionDependentRecordContainer
         *  Slides, or null if there isn't one
         */
        public SlideListWithText getSlideSlideListWithText() {
-        for (int i = 0; i < slwts.length; i++) {
-            if(slwts[i].getInstance() == SlideListWithText.SLIDES) {
-                return slwts[i];
-            }
-        }
+               for (SlideListWithText slwt : slwts) {
+                       if (slwt.getInstance() == SlideListWithText.SLIDES) {
+                               return slwt;
+                       }
+               }
                return null;
     }
        /**
@@ -112,11 +112,11 @@ public final class Document extends PositionDependentRecordContainer
         *  notes, or null if there isn't one
         */
        public SlideListWithText getNotesSlideListWithText() {
-        for (int i = 0; i < slwts.length; i++) {
-            if(slwts[i].getInstance() == SlideListWithText.NOTES) {
-                return slwts[i];
-            }
-        }
+               for (SlideListWithText slwt : slwts) {
+                       if (slwt.getInstance() == SlideListWithText.NOTES) {
+                               return slwt;
+                       }
+               }
                return null;
     }
 
@@ -124,7 +124,7 @@ public final class Document extends PositionDependentRecordContainer
        /**
         * Set things up, and find our more interesting children
         */
-       protected Document(byte[] source, int start, int len) {
+       /* package */ Document(byte[] source, int start, int len) {
                // Grab the header
                _header = new byte[8];
                System.arraycopy(source,start,_header,0,8);
@@ -186,7 +186,7 @@ public final class Document extends PositionDependentRecordContainer
                // The new SlideListWithText should go in
                //  just before the EndDocumentRecord
                Record endDoc = _children[_children.length - 1];
-               if(endDoc.getRecordType() == RecordTypes.RoundTripCustomTableStyles12Atom.typeID) {
+               if(endDoc.getRecordType() == RecordTypes.RoundTripCustomTableStyles12.typeID) {
                    // last record can optionally be a RoundTripCustomTableStyles12Atom
                    endDoc = _children[_children.length - 2];
                }
@@ -213,7 +213,7 @@ public final class Document extends PositionDependentRecordContainer
                 removeChild(slwt);
             }
         }
-        slwts = lst.toArray(new SlideListWithText[lst.size()]);
+        slwts = lst.toArray(new SlideListWithText[0]);
     }
 
        /**
index e2c49a9f35865dd0c50b26b30814b89005bdbe4c..cf01f0b2c9c82bf3029b76a2c31473c173dc1142 100644 (file)
 
 package org.apache.poi.hslf.record;
 
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.LittleEndian;
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+
 /**
  * A Document Atom (type 1001). Holds misc information on the PowerPoint
  * document, lots of them size and scale related.
- *
- * @author Nick Burch
  */
 
+@SuppressWarnings({"WeakerAccess", "unused"})
 public final class DocumentAtom extends RecordAtom
 {
        //arbitrarily selected; may need to increase
        private static final int MAX_RECORD_LENGTH = 1_000_000;
 
-       private byte[] _header;
-       private static long _type = 1001l;
+       private final byte[] _header = new byte[8];
+       private static long _type = RecordTypes.DocumentAtom.typeID;
 
        private long slideSizeX; // PointAtom, assume 1st 4 bytes = X
        private long slideSizeY; // PointAtom, assume 2nd 4 bytes = Y
@@ -87,6 +87,11 @@ public final class DocumentAtom extends RecordAtom
                return saveWithFonts != 0;
        }
 
+       /** Set the font embedding state */
+       public void setSaveWithFonts(boolean saveWithFonts) {
+               this.saveWithFonts = (byte)(saveWithFonts ? 1 : 0);
+       }
+
        /** Have the placeholders on the title slide been omitted? */
        public boolean getOmitTitlePlace() {
                return omitTitlePlace != 0;
@@ -108,41 +113,41 @@ public final class DocumentAtom extends RecordAtom
        /**
         * For the Document Atom
         */
-       protected DocumentAtom(byte[] source, int start, int len) {
-               // Sanity Checking
-               if(len < 48) { len = 48; }
+       /* package */ DocumentAtom(byte[] source, int start, int len) {
+               final int maxLen = Math.max(len, 48);
+               LittleEndianByteArrayInputStream leis =
+                       new LittleEndianByteArrayInputStream(source, start, maxLen);
 
                // Get the header
-               _header = new byte[8];
-               System.arraycopy(source,start,_header,0,8);
+               leis.readFully(_header);
 
                // Get the sizes and zoom ratios
-               slideSizeX = LittleEndian.getInt(source,start+0+8);
-               slideSizeY = LittleEndian.getInt(source,start+4+8);
-               notesSizeX = LittleEndian.getInt(source,start+8+8);
-               notesSizeY = LittleEndian.getInt(source,start+12+8);
-               serverZoomFrom = LittleEndian.getInt(source,start+16+8);
-               serverZoomTo   = LittleEndian.getInt(source,start+20+8);
+               slideSizeX = leis.readInt();
+               slideSizeY = leis.readInt();
+               notesSizeX = leis.readInt();
+               notesSizeY = leis.readInt();
+               serverZoomFrom = leis.readInt();
+               serverZoomTo = leis.readInt();
 
                // Get the master persists
-               notesMasterPersist = LittleEndian.getInt(source,start+24+8);
-               handoutMasterPersist = LittleEndian.getInt(source,start+28+8);
+               notesMasterPersist = leis.readInt();
+               handoutMasterPersist = leis.readInt();
 
                // Get the ID of the first slide
-               firstSlideNum = LittleEndian.getShort(source,start+32+8);
+               firstSlideNum = leis.readShort();
 
                // Get the slide size type
-               slideSizeType = LittleEndian.getShort(source,start+34+8);
+               slideSizeType = leis.readShort();
 
                // Get the booleans as bytes
-               saveWithFonts = source[start+36+8];
-               omitTitlePlace = source[start+37+8];
-               rightToLeft = source[start+38+8];
-               showComments = source[start+39+8];
+               saveWithFonts = leis.readByte();
+               omitTitlePlace = leis.readByte();
+               rightToLeft = leis.readByte();
+               showComments = leis.readByte();
 
                // If there's any other bits of data, keep them about
-               reserved = IOUtils.safelyAllocate(len-40-8, MAX_RECORD_LENGTH);
-               System.arraycopy(source,start+48,reserved,0,reserved.length);
+               reserved = IOUtils.safelyAllocate(maxLen-48, MAX_RECORD_LENGTH);
+               leis.readFully(reserved);
        }
 
        /**
index fef1e797d44a3d98c1cf8fa71c0f87638427d173..bddb79efc67af2b434760e8624bab2ed3b49e391 100644 (file)
 package org.apache.poi.hslf.record;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.poi.common.usermodel.fonts.FontHeader;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
+import org.apache.poi.common.usermodel.fonts.FontPitch;
 import org.apache.poi.hslf.usermodel.HSLFFontInfo;
 import org.apache.poi.hslf.usermodel.HSLFFontInfoPredefined;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.POILogger;
 
 /**
@@ -32,11 +38,12 @@ import org.apache.poi.util.POILogger;
  * about all the fonts in the presentation.
  */
 
+@SuppressWarnings("WeakerAccess")
 public final class FontCollection extends RecordContainer {
     private final Map<String,HSLFFontInfo> fonts = new LinkedHashMap<>();
     private byte[] _header;
 
-       protected FontCollection(byte[] source, int start, int len) {
+       /* package */ FontCollection(byte[] source, int start, int len) {
                _header = new byte[8];
                System.arraycopy(source,start,_header,0,8);
 
@@ -44,8 +51,13 @@ public final class FontCollection extends RecordContainer {
 
                for (Record r : _children){
                        if(r instanceof FontEntityAtom) {
-                           HSLFFontInfo fi = new HSLFFontInfo((FontEntityAtom)r);
-                   fonts.put(fi.getTypeface(), fi);
+                HSLFFontInfo fi = new HSLFFontInfo((FontEntityAtom) r);
+                fonts.put(fi.getTypeface(), fi);
+            } else if (r instanceof FontEmbeddedData) {
+                FontEmbeddedData fed = (FontEmbeddedData)r;
+                           FontHeader fontHeader = fed.getFontHeader();
+                           HSLFFontInfo fi = addFont(fontHeader);
+                           fi.addFacet(fed);
                        } else {
                                logger.log(POILogger.WARN, "Warning: FontCollection child wasn't a FontEntityAtom, was " + r.getClass().getSimpleName());
                        }
@@ -88,7 +100,7 @@ public final class FontCollection extends RecordContainer {
         fi = new HSLFFontInfo(fontInfo);
         fi.setIndex(fonts.size());
         fonts.put(fi.getTypeface(), fi);
-        
+
         FontEntityAtom fnt = fi.createRecord();
 
         // Append new child to the end
@@ -98,6 +110,58 @@ public final class FontCollection extends RecordContainer {
         return fi;
     }
 
+    public HSLFFontInfo addFont(InputStream fontData) throws IOException {
+        FontHeader fontHeader = new FontHeader();
+        InputStream is = fontHeader.bufferInit(fontData);
+
+        HSLFFontInfo fi = addFont(fontHeader);
+
+        // always overwrite the font info properties when a font data given
+        // as the font info properties are assigned generically when only a typeface is given
+        FontEntityAtom fea = fi.getFontEntityAtom();
+        assert (fea != null);
+        fea.setCharSet(fontHeader.getCharsetByte());
+        fea.setPitchAndFamily(FontPitch.getNativeId(fontHeader.getPitch(),fontHeader.getFamily()));
+
+        // always activate subsetting
+        fea.setFontFlags(1);
+        // true type font and no font substitution
+        fea.setFontType(12);
+
+        Record after = fea;
+
+        final int insertIdx = getFacetIndex(fontHeader.isItalic(), fontHeader.isBold());
+
+        FontEmbeddedData newChild = null;
+        for (FontEmbeddedData fed : fi.getFacets()) {
+            FontHeader fh = fed.getFontHeader();
+            final int curIdx = getFacetIndex(fh.isItalic(), fh.isBold());
+
+            if (curIdx == insertIdx) {
+                newChild = fed;
+                break;
+            } else if (curIdx > insertIdx) {
+                // the new facet needs to be inserted before the current facet
+                break;
+            }
+
+            after = fed;
+        }
+
+        if (newChild == null) {
+            newChild = new FontEmbeddedData();
+            addChildAfter(newChild, after);
+            fi.addFacet(newChild);
+        }
+
+        newChild.setFontData(IOUtils.toByteArray(is));
+        return fi;
+    }
+
+    private static int getFacetIndex(boolean isItalic, boolean isBold) {
+        return (isItalic ? 2 : 0) | (isBold ? 1 : 0);
+    }
+
 
     /**
      * Lookup a FontInfo object by its typeface
@@ -132,4 +196,8 @@ public final class FontCollection extends RecordContainer {
     public int getNumberOfFonts() {
         return fonts.size();
     }
+
+    public List<HSLFFontInfo> getFonts() {
+        return new ArrayList<>(fonts.values());
+    }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/FontEmbeddedData.java b/src/scratchpad/src/org/apache/poi/hslf/record/FontEmbeddedData.java
new file mode 100644 (file)
index 0000000..f60ba4a
--- /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.record;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.common.usermodel.fonts.FontFacet;
+import org.apache.poi.common.usermodel.fonts.FontHeader;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndian;
+
+@SuppressWarnings("WeakerAccess")
+public class FontEmbeddedData extends RecordAtom implements FontFacet {
+    //arbitrarily selected; may need to increase
+    private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+    /**
+     * Record header.
+     */
+    private byte[] _header;
+
+    /**
+     * Record data - An EOT Font
+     */
+    private byte[] _data;
+
+    /**
+     * Constructs a brand new font embedded record.
+     */
+    /* package */ FontEmbeddedData() {
+        _header = new byte[8];
+        _data = new byte[4];
+
+        LittleEndian.putShort(_header, 2, (short)getRecordType());
+        LittleEndian.putInt(_header, 4, _data.length);
+    }
+
+    /**
+     * Constructs the font embedded record from its source data.
+     *
+     * @param source the source data as a byte array.
+     * @param start the start offset into the byte array.
+     * @param len the length of the slice in the byte array.
+     */
+    /* package */ FontEmbeddedData(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);
+
+        // Must be at least 4 bytes long
+        if(_data.length < 4) {
+            throw new IllegalArgumentException("The length of the data for a ExObjListAtom must be at least 4 bytes, but was only " + _data.length);
+        }
+    }
+
+    @Override
+    public long getRecordType() {
+        return RecordTypes.FontEmbeddedData.typeID;
+    }
+
+    @Override
+    public void writeOut(OutputStream out) throws IOException {
+        out.write(_header);
+        out.write(_data);
+    }
+
+    public void setFontData(byte[] fontData) {
+        _data = fontData.clone();
+        LittleEndian.putInt(_header, 4, _data.length);
+    }
+
+    public FontHeader getFontHeader() {
+        FontHeader h = new FontHeader();
+        h.init(_data, 0, _data.length);
+        return h;
+    }
+
+    @Override
+    public int getWeight() {
+        return getFontHeader().getWeight();
+    }
+
+    @Override
+    public boolean isItalic() {
+        return getFontHeader().isItalic();
+    }
+
+    public String getTypeface() {
+        return getFontHeader().getFamilyName();
+    }
+
+    @Override
+    public Object getFontData() {
+        return this;
+    }
+}
index 4f3cdd89bc4800e3b3db411bd41373df11c4ea31..2d172d7b5e2f0aea86556756deb73fd90e66d1be 100644 (file)
@@ -43,7 +43,7 @@ public final class FontEntityAtom extends RecordAtom {
     /**
      * record header
      */
-    private byte[] _header;
+    private final byte[] _header = new byte[8];
 
        /**
      * record data
@@ -53,9 +53,8 @@ public final class FontEntityAtom extends RecordAtom {
     /**
      * Build an instance of <code>FontEntityAtom</code> from on-disk data
      */
-       protected FontEntityAtom(byte[] source, int start, int len) {
+       /* package */ FontEntityAtom(byte[] source, int start, int len) {
                // Get the header
-               _header = new byte[8];
                System.arraycopy(source,start,_header,0,8);
 
                // Grab the record data
@@ -69,7 +68,6 @@ public final class FontEntityAtom extends RecordAtom {
     public FontEntityAtom() {
         _recdata = new byte[68];
 
-        _header = new byte[8];
         LittleEndian.putShort(_header, 2, (short)getRecordType());
         LittleEndian.putInt(_header, 4, _recdata.length);
     }
@@ -108,7 +106,7 @@ public final class FontEntityAtom extends RecordAtom {
         byte[] bytes = StringUtil.getToUnicodeLE(name);
         System.arraycopy(bytes, 0, _recdata, 0, bytes.length);
         // null the remaining bytes
-        Arrays.fill(_recdata, 64-bytes.length, 64, (byte)0);
+        Arrays.fill(_recdata, bytes.length, 64, (byte)0);
     }
 
     public void setFontIndex(int idx){
index 233d54a67ff15bf6f4ffe799d0180f6ab2e5d104..a1697d511e4aa625c72d4be4cf70054710c14ef4 100644 (file)
@@ -62,7 +62,18 @@ public enum RecordTypes {
     NamedShow(1041,null),
     NamedShowSlides(1042,null),
     SheetProperties(1044,null),
-    RoundTripCustomTableStyles12Atom(1064,null),
+    OriginalMainMasterId(1052,null),
+    CompositeMasterId(1052,null),
+    RoundTripContentMasterInfo12(1054,null),
+    RoundTripShapeId12(1055,null),
+    RoundTripHFPlaceholder12(1056,RoundTripHFPlaceholder12::new),
+    RoundTripContentMasterId(1058,null),
+    RoundTripOArtTextStyles12(1059,null),
+    RoundTripShapeCheckSumForCustomLayouts12(1062,null),
+    RoundTripNotesMasterTextStyles12(1063,null),
+    RoundTripCustomTableStyles12(1064,null),
+
+
     List(2000,DocInfoListContainer::new),
     FontCollection(2005,FontCollection::new),
     BookmarkCollection(2019,null),
@@ -92,7 +103,7 @@ public enum RecordTypes {
     DefaultRulerAtom(4011,null),
     StyleTextProp9Atom(4012, StyleTextProp9Atom::new), //0x0FAC RT_StyleTextProp9Atom
     FontEntityAtom(4023,FontEntityAtom::new),
-    FontEmbeddedData(4024,null),
+    FontEmbeddedData(4024,FontEmbeddedData::new),
     CString(4026,CString::new),
     MetaFile(4033,null),
     ExOleObjAtom(4035,ExOleObjAtom::new),
@@ -159,17 +170,6 @@ public enum RecordTypes {
     // Records ~12050 seem to be related to Document Encryption
     DocumentEncryptionAtom(12052,DocumentEncryptionAtom::new),
 
-    OriginalMainMasterId(1052,null),
-    CompositeMasterId(1052,null),
-    RoundTripContentMasterInfo12(1054,null),
-    RoundTripShapeId12(1055,null),
-    RoundTripHFPlaceholder12(1056,RoundTripHFPlaceholder12::new),
-    RoundTripContentMasterId(1058,null),
-    RoundTripOArtTextStyles12(1059,null),
-    RoundTripShapeCheckSumForCustomLayouts12(1062,null),
-    RoundTripNotesMasterTextStyles12(1063,null),
-    RoundTripCustomTableStyles12(1064,null),
-
     // records greater then 0xF000 belong to with Microsoft Office Drawing format also known as Escher
     EscherDggContainer(0xF000,null),
     EscherDgg(0xf006,null),
index 57c473173a458567afbe7a4d810a667d8689eaca..3b509276cae49f207a64d72d514f7553d0523f2f 100644 (file)
 
 package org.apache.poi.hslf.usermodel;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.poi.common.usermodel.fonts.FontCharset;
 import org.apache.poi.common.usermodel.fonts.FontFamily;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
 import org.apache.poi.common.usermodel.fonts.FontPitch;
+import org.apache.poi.hslf.record.FontEmbeddedData;
 import org.apache.poi.hslf.record.FontEntityAtom;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.Internal;
 
 /**
  * Represents a Font used in a presentation.<p>
@@ -32,6 +37,7 @@ import org.apache.poi.util.BitFieldFactory;
  * 
  * @since POI 3.17-beta2
  */
+@SuppressWarnings("WeakerAccess")
 public class HSLFFontInfo implements FontInfo {
 
     public enum FontRenderType {
@@ -53,6 +59,8 @@ public class HSLFFontInfo implements FontInfo {
     private FontPitch pitch = FontPitch.VARIABLE;
     private boolean isSubsetted;
     private boolean isSubstitutable = true;
+    private final List<FontEmbeddedData> facets = new ArrayList<>();
+    private FontEntityAtom fontEntityAtom;
 
     /**
      * Creates a new instance of HSLFFontInfo with more or sensible defaults.<p>
@@ -70,6 +78,7 @@ public class HSLFFontInfo implements FontInfo {
      * Creates a new instance of HSLFFontInfo and initialize it from the supplied font atom
      */
     public HSLFFontInfo(FontEntityAtom fontAtom){
+        fontEntityAtom = fontAtom;
         setIndex(fontAtom.getFontIndex());
         setTypeface(fontAtom.getFontName());
         setCharset(FontCharset.valueOf(fontAtom.getCharSet()));
@@ -187,7 +196,11 @@ public class HSLFFontInfo implements FontInfo {
     }
     
     public FontEntityAtom createRecord() {
+        assert(fontEntityAtom == null);
+
         FontEntityAtom fnt = new FontEntityAtom();
+        fontEntityAtom = fnt;
+
         fnt.setFontIndex(getIndex() << 4);
         fnt.setFontName(getTypeface());
         fnt.setCharSet(getCharset().getNativeId());
@@ -212,4 +225,18 @@ public class HSLFFontInfo implements FontInfo {
         fnt.setPitchAndFamily(FontPitch.getNativeId(pitch, family));
         return fnt;
     }
+
+    public void addFacet(FontEmbeddedData facet) {
+        facets.add(facet);
+    }
+
+    @Override
+    public List<FontEmbeddedData> getFacets() {
+        return facets;
+    }
+
+    @Internal
+    public FontEntityAtom getFontEntityAtom() {
+        return fontEntityAtom;
+    }
 }
index 1f016f99e596cca2de5fef7f7947203d6395b5cf..d2dc44095e3addbfb3ce84eb4b09d6b95bdbc7c8 100644 (file)
@@ -44,54 +44,24 @@ public enum HSLFFontInfoPredefined implements FontInfo {
         this.pitch = pitch;
         this.family = family;
     }
-    
-    @Override
-    public Integer getIndex() {
-        return -1;
-    }
-
-    @Override
-    public void setIndex(int index) {
-        throw new UnsupportedOperationException("Predefined enum can't be changed.");
-    }
 
     @Override
     public String getTypeface() {
         return typeface;
     }
 
-    @Override
-    public void setTypeface(String typeface) {
-        throw new UnsupportedOperationException("Predefined enum can't be changed.");
-    }
-
     @Override
     public FontCharset getCharset() {
         return charset;
     }
 
-    @Override
-    public void setCharset(FontCharset charset) {
-        throw new UnsupportedOperationException("Predefined enum can't be changed.");
-    }
-
     @Override
     public FontFamily getFamily() {
         return family;
     }
 
-    @Override
-    public void setFamily(FontFamily family) {
-        throw new UnsupportedOperationException("Predefined enum can't be changed.");
-    }
-
     @Override
     public FontPitch getPitch() {
         return pitch;
     }
-
-    @Override
-    public void setPitch(FontPitch pitch) {
-        throw new UnsupportedOperationException("Predefined enum can't be changed.");
-    }
 }
index de90334840196215286a983d58db542cc782b4cd..2343ccc48911f6436336f92841db956ebaac5bc7 100644 (file)
@@ -50,7 +50,6 @@ import org.apache.poi.poifs.filesystem.Ole10Native;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 import org.apache.poi.sl.usermodel.MasterSheet;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
-import org.apache.poi.sl.usermodel.Resources;
 import org.apache.poi.sl.usermodel.SlideShow;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
@@ -891,6 +890,21 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
                return getDocumentRecord().getEnvironment().getFontCollection().addFont(fontInfo);
        }
 
+       /**
+        * Add a font in this presentation and also embed its font data
+        *
+        * @param fontData the EOT font data as stream
+        *
+        * @return the registered HSLFFontInfo - the font info object is unique based on the typeface
+        *
+        * @since POI 4.1.0
+        */
+       public HSLFFontInfo addFont(InputStream fontData) throws IOException {
+               Document doc = getDocumentRecord();
+               doc.getDocumentAtom().setSaveWithFonts(true);
+               return doc.getEnvironment().getFontCollection().addFont(fontData);
+       }
+
        /**
         * Get a font by index
         *
@@ -912,6 +926,11 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
                return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts();
        }
 
+       @Override
+       public List<HSLFFontInfo> getFonts() {
+               return getDocumentRecord().getEnvironment().getFontCollection().getFonts();
+       }
+
        /**
         * Return Header / Footer settings for slides
         *
@@ -1127,12 +1146,6 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
         return null;
     }
 
-    @Override
-    public Resources getResources() {
-        // TODO implement or throw exception if not supported
-        return null;
-    }
-
     /**
      * @return the handler class which holds the hslf records
      */
index a444a5b2b60d70f08c13e51a1e56734973d96d7b..94cea55ab2aab08d2ec8e03171855871876d772b 100644 (file)
@@ -44,8 +44,9 @@ import org.apache.poi.util.POILogger;
  * Represents a run of text, all with the same style
  *
  */
+@SuppressWarnings({"WeakerAccess", "Duplicates", "unused"})
 public final class HSLFTextRun implements TextRun {
-       protected POILogger logger = POILogFactory.getLogger(this.getClass());
+       private static final POILogger logger = POILogFactory.getLogger(HSLFTextRun.class);
 
        /** The TextRun we belong to */
        private HSLFTextParagraph parentParagraph;
@@ -132,17 +133,17 @@ public final class HSLFTextRun implements TextRun {
                return getFlag(index);
        }
 
-       protected boolean getFlag(int index) {
+       boolean getFlag(int index) {
                BitMaskTextProp prop = (characterStyle == null) ? null : characterStyle.findByName(CharFlagsTextProp.NAME);
 
                if (prop == null || !prop.getSubPropMatches()[index]) {
-                   prop = getMasterProp(CharFlagsTextProp.NAME);
+                   prop = getMasterProp();
                }
 
-               return prop == null ? false : prop.getSubValue(index);
+               return prop != null && prop.getSubValue(index);
        }
 
-       private <T extends TextProp> T getMasterProp(final String name) {
+       private <T extends TextProp> T getMasterProp() {
         final int txtype = parentParagraph.getRunType();
         final HSLFSheet sheet = parentParagraph.getSheet();
         if (sheet == null) {
@@ -155,7 +156,8 @@ public final class HSLFTextRun implements TextRun {
             logger.log(POILogger.WARN, "MasterSheet is not available");
             return null;
         }
-        
+
+        String name = CharFlagsTextProp.NAME;
         final TextPropCollection col = master.getPropCollection(txtype, parentParagraph.getIndentLevel(), name, true);
         return (col == null) ? null : col.findByName(name);
        }
@@ -302,7 +304,7 @@ public final class HSLFTextRun implements TextRun {
 
        @Override
        public void setFontFamily(String typeface) {
-           setFontInfo(new HSLFFontInfo(typeface), FontGroup.LATIN);
+               setFontFamily(typeface, FontGroup.LATIN);
        }
 
     @Override
@@ -330,7 +332,7 @@ public final class HSLFTextRun implements TextRun {
         switch (fg) {
         default:
         case LATIN:
-            propName = "font.index";
+            propName = "ansi.font.index";
             break;
         case COMPLEX_SCRIPT:
             // TODO: implement TextCFException10 structure
@@ -350,6 +352,7 @@ public final class HSLFTextRun implements TextRun {
         }
 
 
+               setCharTextPropVal("font.index", fontIdx);
         setCharTextPropVal(propName, fontIdx);
     }
 
@@ -435,8 +438,8 @@ public final class HSLFTextRun implements TextRun {
                setFontColor(rgb);
        }
 
-    protected void setFlag(int index, boolean value) {
-        BitMaskTextProp prop = (BitMaskTextProp)characterStyle.addWithName(CharFlagsTextProp.NAME);
+    private void setFlag(int index, boolean value) {
+        BitMaskTextProp prop = characterStyle.addWithName(CharFlagsTextProp.NAME);
         prop.setSubValue(value, index);
     }
 
@@ -469,7 +472,7 @@ public final class HSLFTextRun implements TextRun {
      *
      * @param link the hyperlink
      */
-    protected void setHyperlink(HSLFHyperlink link) {
+    /* package */ void setHyperlink(HSLFHyperlink link) {
         this.link = link;
     }
 
@@ -521,4 +524,9 @@ public final class HSLFTextRun implements TextRun {
     private FontGroup safeFontGroup(FontGroup fontGroup) {
         return (fontGroup != null) ? fontGroup : FontGroup.getFontGroupFirst(getRawText());
     }
+
+       @Override
+       public HSLFTextParagraph getParagraph() {
+               return parentParagraph;
+       }
 }
index 621fa6a095f4ba8dbc56bf4006235e56387b2ffe..211c9fc263785e1aad4ced897d941bd76f294348 100644 (file)
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
 
 import org.apache.poi.common.usermodel.fonts.FontCharset;
 import org.apache.poi.common.usermodel.fonts.FontFamily;
+import org.apache.poi.common.usermodel.fonts.FontHeader;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
 import org.apache.poi.common.usermodel.fonts.FontPitch;
 import org.apache.poi.util.BitField;
@@ -32,6 +33,7 @@ import org.apache.poi.util.LittleEndianInputStream;
 /**
  * The Font object specifies the attributes of a logical font
  */
+@SuppressWarnings({"unused", "Duplicates"})
 public class HwmfFont implements FontInfo {
 
     /**
@@ -289,7 +291,7 @@ public class HwmfFont implements FontInfo {
 
     /**
      * An 8-bit unsigned integer that defines the character set.
-     * It SHOULD be set to a value in the {@link WmfCharset} Enumeration.
+     * It SHOULD be set to a value in the {@link FontCharset} Enumeration.
      *
      * The DEFAULT_CHARSET value MAY be used to allow the name and size of a font to fully
      * describe the logical font. If the specified font name does not exist, a font in another character
@@ -373,7 +375,7 @@ public class HwmfFont implements FontInfo {
         height = -12;
         width = 0;
         escapement = 0;
-        weight = 400;
+        weight = FontHeader.REGULAR_WEIGHT;
         italic = false;
         underline = false;
         strikeOut = false;
@@ -437,51 +439,21 @@ public class HwmfFont implements FontInfo {
         return FontFamily.valueOf(pitchAndFamily & 0xF);
     }
 
-    @Override
-    public void setFamily(FontFamily family) {
-        throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
-    }
-
     @Override
     public FontPitch getPitch() {
         return FontPitch.valueOf((pitchAndFamily >>> 6) & 3);
     }
 
-    @Override
-    public void setPitch(FontPitch pitch) {
-        throw new UnsupportedOperationException("setPitch not supported by HwmfFont.");
-    }
-
-    @Override
-    public Integer getIndex() {
-        return null;
-    }
-
-    @Override
-    public void setIndex(int index) {
-        throw new UnsupportedOperationException("setIndex not supported by HwmfFont.");
-    }
-
     @Override
     public String getTypeface() {
         return facename;
     }
 
-    @Override
-    public void setTypeface(String typeface) {
-        throw new UnsupportedOperationException("setTypeface not supported by HwmfFont.");
-    }
-
     @Override
     public FontCharset getCharset() {
         return charSet;
     }
 
-    @Override
-    public void setCharset(FontCharset charset) {
-        throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
-    }
-
     @Override
     public String toString() {
         return "{ height: "+height+
index 78ca26ee3b838a87cdac55fc4466184cb2b3cc23..9fa47b410719e5465b0f1ddd4643955ef6d5edbf 100644 (file)
@@ -29,7 +29,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.BitSet;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.hslf.usermodel.HSLFObjectShape;
@@ -52,12 +54,22 @@ public final class TestExtractor {
     /**
      * Extractor primed on the 2 page basic test data
      */
-    private static final String expectText = "This is a test title\nThis is a test subtitle\nThis is on page 1\nThis is the title on page 2\nThis is page two\nIt has several blocks of text\nNone of them have formatting\n";
+    private static final String EXPECTED_PAGE1 =
+        "This is a test title\n" +
+        "This is a test subtitle\n\n" +
+        "This is on page 1\n";
 
-    /**
-     * Extractor primed on the 1 page but text-box'd test data
-     */
-    private static final String expectText2 = "Hello, World!!!\nI am just a poor boy\nThis is Times New Roman\nPlain Text \n";
+    private static final String EXPECTED_PAGE2 =
+        "This is the title on page 2\n" +
+        "This is page two\n\n" +
+        "It has several blocks of text\n\n" +
+        "None of them have formatting\n";
+
+    private static final String NOTES_PAGE1 =
+        "\nThese are the notes for page 1\n";
+
+    private static final String NOTES_PAGE2 =
+        "\nThese are the notes on page two, again lacking formatting\n";
 
     /**
      * Where our embeded files live
@@ -75,9 +87,16 @@ public final class TestExtractor {
     public void testReadSheetText() throws IOException {
         // Basic 2 page example
         try (SlideShowExtractor ppe = openExtractor("basic_test_ppt_file.ppt")) {
-            assertEquals(expectText, ppe.getText());
+            assertEquals(EXPECTED_PAGE1+EXPECTED_PAGE2, ppe.getText());
         }
 
+        // Extractor primed on the 1 page but text-box'd test data
+        final String expectText2 =
+            "Hello, World!!!\n" +
+            "I am just a poor boy\n" +
+            "This is Times New Roman\n" +
+            "Plain Text \n";
+
         // 1 page example with text boxes
         try (SlideShowExtractor ppe = openExtractor("with_textbox.ppt")) {
             assertEquals(expectText2, ppe.getText());
@@ -92,8 +111,7 @@ public final class TestExtractor {
             ppe.setSlidesByDefault(false);
             ppe.setMasterByDefault(false);
             String notesText = ppe.getText();
-            String expText = "\nThese are the notes for page 1\n\nThese are the notes on page two, again lacking formatting\n";
-            assertEquals(expText, notesText);
+            assertEquals(NOTES_PAGE1+NOTES_PAGE2, notesText);
         }
 
         // Other one doesn't have notes
@@ -109,14 +127,8 @@ public final class TestExtractor {
 
     @Test
     public void testReadBoth() throws IOException {
-        String[] slText = new String[]{
-                "This is a test title\nThis is a test subtitle\nThis is on page 1\n",
-                "This is the title on page 2\nThis is page two\nIt has several blocks of text\nNone of them have formatting\n"
-        };
-        String[] ntText = new String[]{
-                "\nThese are the notes for page 1\n",
-                "\nThese are the notes on page two, again lacking formatting\n"
-        };
+        String[] slText = { EXPECTED_PAGE1, EXPECTED_PAGE2 };
+        String[] ntText = { NOTES_PAGE1, NOTES_PAGE2 };
 
         try (SlideShowExtractor ppe = openExtractor("basic_test_ppt_file.ppt")) {
             ppe.setSlidesByDefault(true);
@@ -165,8 +177,8 @@ public final class TestExtractor {
             final DirectoryNode root = fs.getRoot();
 
             final String[] TEST_SET = {
-                "MBD0000A3B6", "Sample PowerPoint file\nThis is the 1st file\nNot much too it\n",
-                "MBD0000A3B3", "Sample PowerPoint file\nThis is the 2nd file\nNot much too it either\n"
+                "MBD0000A3B6", "Sample PowerPoint file\nThis is the 1st file\n\nNot much too it\n",
+                "MBD0000A3B3", "Sample PowerPoint file\nThis is the 2nd file\n\nNot much too it either\n"
             };
 
             for (int i=0; i<TEST_SET.length; i+=2) {
@@ -386,7 +398,7 @@ public final class TestExtractor {
             // Open directly
             try (SlideShow<?,?> ppt = SlideShowFactory.create(npoifs.getRoot());
                 SlideShowExtractor<?,?> extractor = new SlideShowExtractor<>(ppt)) {
-                assertEquals(expectText, extractor.getText());
+                assertEquals(EXPECTED_PAGE1+EXPECTED_PAGE2, extractor.getText());
             }
         }
     }
@@ -457,4 +469,19 @@ public final class TestExtractor {
     private static int countMatches(final String base, final String find) {
         return base.split(find).length-1;
     }
+
+    @Test
+    public void glyphCounting() throws IOException {
+        String[] expected = {
+            "Times New Roman", "\t\n ,-./01234679:ABDEFGILMNOPRSTVWabcdefghijklmnoprstuvwxyz\u00F3\u201C\u201D",
+            "Arial", " Lacdilnost"
+        };
+        try (SlideShowExtractor ppt = openExtractor("45543.ppt")) {
+            for (int i=0; i<expected.length; i+=2) {
+                BitSet l = ppt.getCodepoints(expected[i], null, null);
+                String s = l.stream().mapToObj(Character::toChars).map(String::valueOf).collect(Collectors.joining());
+                assertEquals(expected[i+1], s);
+            }
+        }
+    }
 }
index 7c2f7fe473eb6a5e2cc84c2939f7671de580859c..8e1ad7a92888336ccda4466005795eaf7bccd2d6 100644 (file)
@@ -27,7 +27,7 @@ import org.apache.poi.sl.usermodel.BaseTestSlideShow;
 import org.apache.poi.sl.usermodel.SlideShow;
 import org.junit.Test;
 
-public class TestHSLFSlideShow extends BaseTestSlideShow {
+public class TestHSLFSlideShow extends BaseTestSlideShow<HSLFShape,HSLFTextParagraph> {
     @Override
     public HSLFSlideShow createSlideShow() {
         return new HSLFSlideShow();
@@ -39,11 +39,7 @@ public class TestHSLFSlideShow extends BaseTestSlideShow {
         assertNotNull(createSlideShow());
     }
 
-    public SlideShow<?, ?> reopen(SlideShow<?, ?> show) {
-        return reopen((HSLFSlideShow)show);
-    }
-
-    public static HSLFSlideShow reopen(HSLFSlideShow show) {
+    public HSLFSlideShow reopen(SlideShow<HSLFShape,HSLFTextParagraph> show) {
         try {
             BufAccessBAOS bos = new BufAccessBAOS();
             show.write(bos);
@@ -55,7 +51,7 @@ public class TestHSLFSlideShow extends BaseTestSlideShow {
     }
 
     private static class BufAccessBAOS extends ByteArrayOutputStream {
-        public byte[] getBuf() {
+        byte[] getBuf() {
             return buf;
         }
     }
index c4daa50b3f16a067ee28d222e51deb9ad6340e8f..c12c96559fb73ad30dc3b431d6b7f19cf376827c 100644 (file)
@@ -17,6 +17,7 @@
 package org.apache.poi.sl.usermodel;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -29,20 +30,24 @@ import java.io.InputStream;
 import java.util.List;
 
 import org.apache.poi.POIDataSamples;
+import org.apache.poi.common.usermodel.fonts.FontInfo;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
 import org.apache.poi.sl.usermodel.TabStop.TabStopType;
 import org.junit.Test;
 
-public abstract class BaseTestSlideShow {
+public abstract class BaseTestSlideShow<
+        S extends Shape<S,P>,
+        P extends TextParagraph<S,P,? extends TextRun>
+> {
     protected static final POIDataSamples slTests = POIDataSamples.getSlideShowInstance();
     
-    public abstract SlideShow<?, ?> createSlideShow();
+    public abstract SlideShow<S,P> createSlideShow();
 
-    public abstract SlideShow<?, ?> reopen(SlideShow<?, ?> show);
+    public abstract SlideShow<S,P> reopen(SlideShow<S,P> show);
     
     @Test
     public void addPicture_File() throws IOException {
-        SlideShow<?,?> show = createSlideShow();
+        SlideShow<S,P> show = createSlideShow();
         File f = slTests.getFile("clock.jpg");
         
         assertEquals(0, show.getPictureData().size());
@@ -55,26 +60,18 @@ public abstract class BaseTestSlideShow {
     
     @Test
     public void addPicture_Stream() throws IOException {
-        SlideShow<?,?> show = createSlideShow();
-        try {
-            InputStream stream = slTests.openResourceAsStream("clock.jpg");
-            try {
-                assertEquals(0, show.getPictureData().size());
-                PictureData picture = show.addPicture(stream, PictureType.JPEG);
-                assertEquals(1, show.getPictureData().size());
-                assertSame(picture, show.getPictureData().get(0));
-
-            } finally {
-                stream.close();
-            }
-        } finally {
-            show.close();
+        try (SlideShow<S,P> show = createSlideShow();
+             InputStream stream = slTests.openResourceAsStream("clock.jpg")) {
+            assertEquals(0, show.getPictureData().size());
+            PictureData picture = show.addPicture(stream, PictureType.JPEG);
+            assertEquals(1, show.getPictureData().size());
+            assertSame(picture, show.getPictureData().get(0));
         }
     }
     
     @Test
     public void addPicture_ByteArray() throws IOException {
-        SlideShow<?,?> show = createSlideShow();
+        SlideShow<S,P> show = createSlideShow();
         byte[] data = slTests.readFile("clock.jpg");
         
         assertEquals(0, show.getPictureData().size());
@@ -87,7 +84,7 @@ public abstract class BaseTestSlideShow {
     
     @Test
     public void findPicture() throws IOException {
-        SlideShow<?,?> show = createSlideShow();
+        SlideShow<S,P> show = createSlideShow();
         byte[] data = slTests.readFile("clock.jpg");
         
         assertNull(show.findPictureData(data));
@@ -101,11 +98,11 @@ public abstract class BaseTestSlideShow {
     
     @Test
     public void addTabStops() throws IOException {
-        try (final SlideShow<?,?> show1 = createSlideShow()) {
+        try (final SlideShow<S,P> 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);
+            final MasterSheet<S,P> master1 = show1.getSlideMasters().get(0);
+            final AutoShape<S,P> master1_as = (AutoShape<S,P>)master1.getPlaceholder(Placeholder.BODY);
+            final P master1_tp = master1_as.getTextParagraphs().get(0);
             master1_tp.clearTabStops();
             int i1 = 0;
             for (final TabStopType tst : TabStopType.values()) {
@@ -114,11 +111,11 @@ public abstract class BaseTestSlideShow {
             }
             
             // then set it on a normal slide
-            final Slide<?,?> slide1 = show1.createSlide();
-            final AutoShape<?, ?> slide1_as = slide1.createAutoShape();
+            final Slide<S,P> slide1 = show1.createSlide();
+            final AutoShape<S,P> 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);
+            final P slide1_tp = slide1_as.getTextParagraphs().get(0);
             slide1_tp.getTextRuns().get(0).setFontColor(new Color(0x563412));
             slide1_tp.clearTabStops();
             int i2 = 0;
@@ -127,10 +124,10 @@ public abstract class BaseTestSlideShow {
                 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);
+            try (final SlideShow<S,P> show2 = reopen(show1)) {
+                final MasterSheet<S,P> master2 = show2.getSlideMasters().get(0);
+                final AutoShape<S,P> master2_as = (AutoShape<S,P>)master2.getPlaceholder(Placeholder.BODY);
+                final P master2_tp = master2_as.getTextParagraphs().get(0);
                 final List<? extends TabStop> master2_tabStops = master2_tp.getTabStops();
                 assertNotNull(master2_tabStops);
                 int i3 = 0;
@@ -142,9 +139,10 @@ public abstract class BaseTestSlideShow {
                 }
                 
                 
-                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 Slide<S,P> slide2 = show2.getSlides().get(0);
+                @SuppressWarnings("unchecked")
+                final AutoShape<S,P> slide2_as = (AutoShape<S,P>)slide2.getShapes().get(0);
+                final P slide2_tp = slide2_as.getTextParagraphs().get(0);
                 final List<? extends TabStop> slide2_tabStops = slide2_tp.getTabStops();
                 assertNotNull(slide2_tabStops);
                 int i4 = 0;
@@ -162,18 +160,35 @@ public abstract class BaseTestSlideShow {
     public void shapeAndSlideName() throws IOException {
         final String file = "SampleShow.ppt"+(getClass().getSimpleName().contains("XML")?"x":"");
         try (final InputStream is = slTests.openResourceAsStream(file);
-             final SlideShow<? extends Shape, ?> ppt = SlideShowFactory.create(is)) {
-            final List<? extends Shape> shapes1 = ppt.getSlides().get(0).getShapes();
+             final SlideShow<S,P> ppt = SlideShowFactory.create(is)) {
+            final List<S> shapes1 = ppt.getSlides().get(0).getShapes();
             assertEquals("The Title", shapes1.get(0).getShapeName());
             assertEquals("Another Subtitle", shapes1.get(1).getShapeName());
-            final List<? extends Shape> shapes2 = ppt.getSlides().get(1).getShapes();
+            final List<S> shapes2 = ppt.getSlides().get(1).getShapes();
             assertEquals("Title 1", shapes2.get(0).getShapeName());
             assertEquals("Content Placeholder 2", shapes2.get(1).getShapeName());
 
-            for (final Slide<?,?> slide : ppt.getSlides()) {
+            for (final Slide<S,P> slide : ppt.getSlides()) {
                 final String expected = slide.getSlideNumber()==1 ? "FirstSlide" : "Slide2";
                 assertEquals(expected, slide.getSlideName());
             }
         }
     }
+
+    @Test
+    public void addFont() throws IOException {
+        try (SlideShow<S,P> ppt = createSlideShow()) {
+            ppt.createSlide();
+            try (InputStream fontData = slTests.openResourceAsStream("font.fntdata")) {
+                ppt.addFont(fontData);
+            }
+
+            try (SlideShow<S, P> ppt2 = reopen(ppt)) {
+                List<? extends FontInfo> fonts = ppt2.getFonts();
+                assertFalse(fonts.isEmpty());
+                FontInfo fi = fonts.get(fonts.size()-1);
+                assertEquals("Harlow Solid Italic", fi.getTypeface());
+            }
+        }
+    }
 }
diff --git a/test-data/slideshow/font.fntdata b/test-data/slideshow/font.fntdata
new file mode 100644 (file)
index 0000000..b00e97a
Binary files /dev/null and b/test-data/slideshow/font.fntdata differ