#63028 - Provide font embedding for slideshows

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849898 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-12-28 23:43:31 +00:00
parent a78bd71fc1
commit c039da1b94
34 changed files with 1483 additions and 558 deletions

View 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());

View 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"),
/**

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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();
}
}

View File

@ -19,10 +19,7 @@
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.");
}
}

View File

@ -18,10 +18,14 @@
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) {
private void printTextParagraphs(final List<P> paras, final Consumer<String> consumer) {
printTextParagraphs(paras, consumer, "\n");
}
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);
}
}
}
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;
final StringBuilder footer = new StringBuilder("\n");
addSheetPlaceholderDatails(sheet, Placeholder.HEADER, sb);
addSheetPlaceholderDatails(sheet, Placeholder.FOOTER, footer);
addSheetPlaceholderDatails(sheet, Placeholder.HEADER, consumer);
addSheetPlaceholderDatails(sheet, Placeholder.FOOTER, footerCon);
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;
}
if (!masterByDefault) {
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;
}
}
return (footer.length() > 1) ? footer.toString() : "";
}
private void addSheetPlaceholderDatails(final Sheet<S,P> sheet, final Placeholder placeholder, final StringBuilder sb) {
private void addSheetPlaceholderDatails(final Sheet<S,P> sheet, final Placeholder placeholder, final Consumer<String> consumer) {
final PlaceholderDetails headerPD = sheet.getPlaceholderDetails(placeholder);
if (headerPD == null) {
return;
final String headerStr = (headerPD != null) ? headerPD.getText() : null;
if (headerStr != null && filter.test(headerPD)) {
consumer.accept(headerStr);
}
final String headerStr = headerPD.getText();
if (headerStr == null) {
return;
}
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 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);
}
}

View File

@ -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 {
}

View File

@ -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);
}

View 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]"

View 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();
}

View 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();
}

View 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.clear();
if (_presentation.isSetSldMasterIdLst()) {
_presentation.getSldMasterIdLst().getSldMasterIdList().forEach(
id -> _masters.add(masterMap.get(id.getId2()))
);
}
_masters = new ArrayList<>(masterMap.size());
for (CTSlideMasterIdListEntry masterId : _presentation.getSldMasterIdLst().getSldMasterIdList()) {
XSLFSlideMaster master = masterMap.get(masterId.getId2());
_masters.add(master);
}
_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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View 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);

View 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;
}
}

View 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);
}
}

View 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 {

View 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]);
}
/**

View File

@ -17,25 +17,25 @@
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);
}
/**

View File

@ -18,13 +18,19 @@
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());
}
}

View File

@ -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;
}
}

View 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){

View 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),

View File

@ -17,13 +17,18 @@
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;
}
}

View 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.");
}
}

View 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
*/

View 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;
}
}

View 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+

View 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);
}
}
}
}

View 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;
}
}

View 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());
}
}
}
}

Binary file not shown.