Bug 60656 - Emf image support in slideshows

- fixed WmfExtTextOut dx handling for variable text spacing
- fixed WmfExtTextOut text position for (0,0) references based on the current/last path location
- fixed WmfExtTextOut handling of symbol/wingdings charset (move ascii to unicode private area, because Java font loader maps the glyphs there) - and use existing workaround if the fonts aren't installed, i.e. use corresponding unicode characters of the logcial font then
- provide option in PPTX2PNG to use given file input type, if the file magic is unknown
- provide option in PPTX2PNG to render text as shapes in SVG, as dx handling (above) implemented via TextAttribute.TRACKING is not supported by batik 

source of the sample.wmf, which I've used:
https://stackoverflow.com/questions/58726194/svg-rendering-with-batik-produce-broken-image

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1876136 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2020-04-05 00:42:08 +00:00
parent 1dc771394b
commit 81400a3ee2
9 changed files with 329 additions and 266 deletions

View File

@ -48,20 +48,25 @@ public interface DrawFontManager {
* *
* @param graphics the graphics context to request additional rendering hints * @param graphics the graphics context to request additional rendering hints
* @param fontInfo the font info object corresponding to the text run font * @param fontInfo the font info object corresponding to the text run font
* *
* @return the font to be used as a fallback for the original typeface * @return the font to be used as a fallback for the original typeface
*/ */
FontInfo getFallbackFont(Graphics2D graphics, FontInfo fontInfo); FontInfo getFallbackFont(Graphics2D graphics, FontInfo fontInfo);
/** /**
* Map text charset depending on font family.<p> * Map text charset depending on font family.
* * <p>
* Currently this only maps for wingdings font (into unicode private use area) * Currently this only maps for wingdings and symbol font (into unicode private use area)
* <p>
* Depending if the requested font is installed in the system, tbe mapped string varies:<br>
* If the font is registered into the graphics environment the characters are mapped to the
* private use area. If the font is missing (and hence a AWT logical font is used), the
* characters are mapped to the corresponding unicode characters
* *
* @param graphics the graphics context to request additional rendering hints * @param graphics the graphics context to request additional rendering hints
* @param fontInfo the font info object corresponding to the text run font * @param fontInfo the font info object corresponding to the text run font
* @param text the raw text * @param text the raw text
* *
* @return String with mapped codepoints * @return String with mapped codepoints
* *
* @see <a href="http://stackoverflow.com/questions/8692095">Drawing exotic fonts in a java applet</a> * @see <a href="http://stackoverflow.com/questions/8692095">Drawing exotic fonts in a java applet</a>
@ -77,7 +82,7 @@ public interface DrawFontManager {
* @param size the font size in points * @param size the font size in points
* @param bold {@code true} if the font is bold * @param bold {@code true} if the font is bold
* @param italic {@code true} if the font is italic * @param italic {@code true} if the font is italic
* *
* @return the AWT font object * @return the AWT font object
*/ */
Font createAWTFont(Graphics2D graphics, FontInfo fontInfo, double size, boolean bold, boolean italic); Font createAWTFont(Graphics2D graphics, FontInfo fontInfo, double size, boolean bold, boolean italic);

View File

@ -21,12 +21,16 @@ package org.apache.poi.sl.draw;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import org.apache.poi.common.usermodel.fonts.FontCharset;
import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.sl.draw.Drawable.DrawableHint; import org.apache.poi.sl.draw.Drawable.DrawableHint;
import org.apache.poi.util.StringUtil;
/** /**
* Manages fonts when rendering slides. * Manages fonts when rendering slides.
@ -56,37 +60,36 @@ public class DrawFontManagerDefault implements DrawFontManager {
return fi; return fi;
} }
public String mapFontCharset(Graphics2D graphics, FontInfo fontInfo, String text) {
// TODO: find a real charset mapping solution instead of hard coding for Wingdings
return (fontInfo != null && knownSymbolFonts.contains(fontInfo.getTypeface()))
? mapSymbolChars(text)
: text;
}
/** /**
* Symbol fonts like "Wingdings" or "Symbol" have glyphs mapped to a Unicode private use range via the Java font loader, * Symbol fonts like "Wingdings" or "Symbol" have glyphs mapped to a Unicode private use range via the Java font loader,
* although a system font viewer might show you the glyphs in the ASCII range. * although a system font viewer might show you the glyphs in the ASCII range.
* This helper function maps the chars of the text string to the corresponding private use range chars. * This maps the chars of the text string to the corresponding private use range chars.
*
* @param graphics the used graphics context
* @param fontInfo the font info
* @param text the input string
* *
* @param text the input string, typically consists of ASCII chars
* @return the mapped string, typically consists of chars in the range of 0xf000 to 0xf0ff * @return the mapped string, typically consists of chars in the range of 0xf000 to 0xf0ff
* *
* @since POI 4.0.0 * @since POI 4.0.0
*/ */
public static String mapSymbolChars(String text) { @Override
// wingdings doesn't contain high-surrogates, so chars are ok public String mapFontCharset(Graphics2D graphics, FontInfo fontInfo, String text) {
boolean changed = false; if (fontInfo == null || text == null || text.isEmpty()) {
char[] chrs = text.toCharArray(); return text;
for (int i=0; i<chrs.length; i++) {
// only change valid chars
if ((0x20 <= chrs[i] && chrs[i] <= 0x7f) ||
(0xa0 <= chrs[i] && chrs[i] <= 0xff)) {
chrs[i] |= 0xf000;
changed = true;
}
} }
return changed ? new String(chrs) : text; String typeface = fontInfo.getTypeface();
if (fontInfo.getCharset() == FontCharset.SYMBOL || knownSymbolFonts.contains(typeface)) {
int[] cps = text.codePoints().map(DrawFontManagerDefault::mapSymbolChar).toArray();
String ret = new String(cps, 0, cps.length);
String[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
boolean hasFont = Arrays.asList(allFonts).contains(typeface);
return hasFont ? ret : StringUtil.mapMsCodepointString(ret);
}
return text;
} }
@Override @Override
@ -106,7 +109,7 @@ public class DrawFontManagerDefault implements DrawFontManager {
if (fontMap == null) { if (fontMap == null) {
return fontInfo; return fontInfo;
} }
String f = (fontInfo != null) ? fontInfo.getTypeface() : null; String f = (fontInfo != null) ? fontInfo.getTypeface() : null;
String mappedTypeface = null; String mappedTypeface = null;
if (fontMap.containsKey(f)) { if (fontMap.containsKey(f)) {
@ -117,4 +120,9 @@ public class DrawFontManagerDefault implements DrawFontManager {
return (mappedTypeface != null) ? new DrawFontInfo(mappedTypeface) : fontInfo; return (mappedTypeface != null) ? new DrawFontInfo(mappedTypeface) : fontInfo;
} }
private static int mapSymbolChar(int cp) {
return ((0x20 <= cp && cp <= 0x7f) || (0xa0 <= cp && cp <= 0xff)) ? cp | 0xf000 : cp;
}
} }

View File

@ -19,17 +19,15 @@ package org.apache.poi.util;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
/** /**
* Collection of string handling utilities * Collection of string handling utilities
*/ */
@Internal @Internal
public class StringUtil { public final class StringUtil {
protected static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
//arbitrarily selected; may need to increase //arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 10000000; private static final int MAX_RECORD_LENGTH = 10000000;
@ -38,8 +36,6 @@ public class StringUtil {
public static final Charset WIN_1252 = Charset.forName("cp1252"); public static final Charset WIN_1252 = Charset.forName("cp1252");
public static final Charset BIG5 = Charset.forName("Big5"); public static final Charset BIG5 = Charset.forName("Big5");
private static Map<Integer, Integer> msCodepointToUnicode;
private StringUtil() { private StringUtil() {
// no instances of this class // no instances of this class
} }
@ -293,16 +289,6 @@ public class StringUtil {
return false; return false;
} }
/**
* Checks to see if a given String needs to be represented as Unicode
*
* @param value The string to look at.
* @return true if string needs Unicode to be represented.
*/
public static boolean isUnicodeString(final String value) {
return !value.equals(new String(value.getBytes(ISO_8859_1), ISO_8859_1));
}
/** /**
* Tests if the string starts with the specified prefix, ignoring case consideration. * Tests if the string starts with the specified prefix, ignoring case consideration.
*/ */
@ -381,38 +367,18 @@ public class StringUtil {
if (string == null || string.isEmpty()) { if (string == null || string.isEmpty()) {
return string; return string;
} }
initMsCodepointMap();
StringBuilder sb = new StringBuilder(); int[] cps = string.codePoints().map(StringUtil::mapMsCodepoint).toArray();
final int length = string.length(); return new String(cps, 0, cps.length);
for (int offset = 0; offset < length; ) {
int msCodepoint = string.codePointAt(offset);
Integer uniCodepoint = msCodepointToUnicode.get(msCodepoint);
sb.appendCodePoint(uniCodepoint == null ? msCodepoint : uniCodepoint);
offset += Character.charCount(msCodepoint);
}
return sb.toString();
} }
public static synchronized void mapMsCodepoint(int msCodepoint, int unicodeCodepoint) { private static int mapMsCodepoint(int cp) {
initMsCodepointMap(); if (0xf020 <= cp && cp <= 0xf07f) {
msCodepointToUnicode.put(msCodepoint, unicodeCodepoint); return symbolMap_f020[cp - 0xf020];
} } else if (0xf0a0 <= cp && cp <= 0xf0ff) {
return symbolMap_f0a0[cp - 0xf0a0];
private static synchronized void initMsCodepointMap() {
if (msCodepointToUnicode != null) {
return;
}
msCodepointToUnicode = new HashMap<>();
int i = 0xF020;
for (int ch : symbolMap_f020) {
msCodepointToUnicode.put(i++, ch);
}
i = 0xf0a0;
for (int ch : symbolMap_f0a0) {
msCodepointToUnicode.put(i++, ch);
} }
return cp;
} }
private static final int[] symbolMap_f020 = { private static final int[] symbolMap_f020 = {

View File

@ -37,8 +37,8 @@ public class SVGPOIGraphics2D extends SVGGraphics2D {
private final RenderingHints hints; private final RenderingHints hints;
public SVGPOIGraphics2D(Document document) { public SVGPOIGraphics2D(Document document, boolean textAsShapes) {
super(getCtx(document), false); super(getCtx(document), textAsShapes);
hints = getGeneratorContext().getGraphicContextDefaults().getRenderingHints(); hints = getGeneratorContext().getGraphicContextDefaults().getRenderingHints();
} }

View File

@ -50,6 +50,11 @@ interface OutputFormat extends Closeable {
class SVGFormat implements OutputFormat { class SVGFormat implements OutputFormat {
static final String svgNS = "http://www.w3.org/2000/svg"; static final String svgNS = "http://www.w3.org/2000/svg";
private SVGGraphics2D svgGenerator; private SVGGraphics2D svgGenerator;
private final boolean textAsShapes;
SVGFormat(boolean textAsShapes) {
this.textAsShapes = textAsShapes;
}
@Override @Override
public Graphics2D getGraphics2D(double width, double height) { public Graphics2D getGraphics2D(double width, double height) {
@ -58,7 +63,7 @@ interface OutputFormat extends Closeable {
// Create an instance of org.w3c.dom.Document. // Create an instance of org.w3c.dom.Document.
Document document = domImpl.createDocument(svgNS, "svg", null); Document document = domImpl.createDocument(svgNS, "svg", null);
svgGenerator = new SVGPOIGraphics2D(document); svgGenerator = new SVGPOIGraphics2D(document, textAsShapes);
svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height)); svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height));
return svgGenerator; return svgGenerator;
} }

View File

@ -67,7 +67,11 @@ public final class PPTX2PNG {
" -dump <file> dump the annotated records to a file\n" + " -dump <file> dump the annotated records to a file\n" +
" -quiet do not write to console (for normal processing)\n" + " -quiet do not write to console (for normal processing)\n" +
" -ignoreParse ignore parsing error and continue with the records read until the error\n" + " -ignoreParse ignore parsing error and continue with the records read until the error\n" +
" -extractEmbedded extract embedded parts"; " -extractEmbedded extract embedded parts\n" +
" -inputType <type> default input file type (OLE2,WMF,EMF), default is OLE2 = Powerpoint\n" +
" some files (usually wmf) don't have a header, i.e. an identifiable file magic\n" +
" -textAsShapes text elements are saved as shapes in SVG, necessary for variable spacing\n" +
" often found in math formulas";
System.out.println(msg); System.out.println(msg);
// no System.exit here, as we also run in junit tests! // no System.exit here, as we also run in junit tests!
@ -93,6 +97,8 @@ public final class PPTX2PNG {
private String fixSide = "scale"; private String fixSide = "scale";
private boolean ignoreParse = false; private boolean ignoreParse = false;
private boolean extractEmbedded = false; private boolean extractEmbedded = false;
private FileMagic defaultFileType = FileMagic.OLE2;
private boolean textAsShapes = false;
private PPTX2PNG() { private PPTX2PNG() {
} }
@ -153,6 +159,17 @@ public final class PPTX2PNG {
fixSide = "long"; fixSide = "long";
} }
break; break;
case "-inputType":
if (opt != null) {
defaultFileType = FileMagic.valueOf(opt);
i++;
} else {
defaultFileType = FileMagic.OLE2;
}
break;
case "-textAsShapes":
textAsShapes = true;
break;
case "-ignoreParse": case "-ignoreParse":
ignoreParse = true; ignoreParse = true;
break; break;
@ -238,7 +255,7 @@ public final class PPTX2PNG {
extractEmbedded(proxy, slideNo); extractEmbedded(proxy, slideNo);
try (OutputFormat outputFormat = ("svg".equals(format)) ? new SVGFormat() : new BitmapFormat(format)) { try (OutputFormat outputFormat = ("svg".equals(format)) ? new SVGFormat(textAsShapes) : new BitmapFormat(format)) {
Graphics2D graphics = outputFormat.getGraphics2D(width, height); Graphics2D graphics = outputFormat.getGraphics2D(width, height);
// default rendering options // default rendering options
@ -337,6 +354,9 @@ public final class PPTX2PNG {
if ("stdin".equals(fileName)) { if ("stdin".equals(fileName)) {
InputStream bis = FileMagic.prepareToCheckMagic(System.in); InputStream bis = FileMagic.prepareToCheckMagic(System.in);
FileMagic fm = FileMagic.valueOf(bis); FileMagic fm = FileMagic.valueOf(bis);
if (fm == FileMagic.UNKNOWN) {
fm = defaultFileType;
}
switch (fm) { switch (fm) {
case EMF: case EMF:
proxy = new EMFHandler(); proxy = new EMFHandler();

View File

@ -21,6 +21,7 @@ import java.awt.AlphaComposite;
import java.awt.BasicStroke; import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Composite; import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.Insets; import java.awt.Insets;
@ -29,6 +30,7 @@ import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.TexturePaint; import java.awt.TexturePaint;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
import java.awt.font.TextLayout; import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
@ -39,16 +41,18 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.AttributedString; import java.text.AttributedString;
import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.apache.commons.codec.Charsets; import org.apache.commons.codec.Charsets;
import org.apache.poi.common.usermodel.fonts.FontCharset;
import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfFont; import org.apache.poi.hwmf.record.HwmfFont;
@ -59,12 +63,10 @@ import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.hwmf.record.HwmfText;
import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions; import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions;
import org.apache.poi.sl.draw.BitmapImageRenderer; import org.apache.poi.sl.draw.BitmapImageRenderer;
import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawFontManager; import org.apache.poi.sl.draw.DrawFontManager;
import org.apache.poi.sl.draw.DrawFontManagerDefault;
import org.apache.poi.sl.draw.DrawPictureShape; import org.apache.poi.sl.draw.DrawPictureShape;
import org.apache.poi.sl.draw.ImageRenderer; import org.apache.poi.sl.draw.ImageRenderer;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -107,6 +109,16 @@ public class HwmfGraphics {
1f, TextAttribute.WEIGHT_EXTRA_LIGHT 1f, TextAttribute.WEIGHT_EXTRA_LIGHT
}; };
private static class DxLayout {
double dx;
// Spacing at default tracking value of 0
double pos0;
// Spacing at second tracking value
double pos1;
int beginIndex;
int endIndex;
}
private final List<HwmfDrawProperties> propStack = new LinkedList<>(); private final List<HwmfDrawProperties> propStack = new LinkedList<>();
protected HwmfDrawProperties prop; protected HwmfDrawProperties prop;
@ -189,6 +201,7 @@ public class HwmfGraphics {
draw(shape); draw(shape);
} }
@SuppressWarnings("MagicConstant")
protected BasicStroke getStroke() { protected BasicStroke getStroke() {
HwmfDrawProperties prop = getProperties(); HwmfDrawProperties prop = getProperties();
HwmfPenStyle ps = prop.getPenStyle(); HwmfPenStyle ps = prop.getPenStyle();
@ -285,7 +298,7 @@ public class HwmfGraphics {
* Moreover, each object table index uniquely refers to an object. * Moreover, each object table index uniquely refers to an object.
* Indexes in the WMF Object Table always start at 0. * Indexes in the WMF Object Table always start at 0.
* *
* @param entry * @param entry the object table entry
*/ */
public void addObjectTableEntry(HwmfObjectTableEntry entry) { public void addObjectTableEntry(HwmfObjectTableEntry entry) {
int objIdx = objectIndexes.nextClearBit(0); int objIdx = objectIndexes.nextClearBit(0);
@ -433,155 +446,44 @@ public class HwmfGraphics {
final HwmfDrawProperties prop = getProperties(); final HwmfDrawProperties prop = getProperties();
final AffineTransform at = graphicsCtx.getTransform(); final AffineTransform at = graphicsCtx.getTransform();
try { try {
at.createInverse(); at.createInverse();
} catch (NoninvertibleTransformException e) { } catch (NoninvertibleTransformException e) {
return; return;
} }
HwmfFont font = prop.getFont(); final HwmfFont font = prop.getFont();
if (font == null || text == null || text.length == 0) { if (font == null || text == null || text.length == 0) {
return; return;
} }
double fontH = getFontHeight(font); String textString = trimText(font, isUnicode, text, length);
// TODO: another approx. ...
double fontW = fontH/1.8;
Charset charset;
if (isUnicode) {
charset = Charsets.UTF_16LE;
} else {
charset = font.getCharset().getCharset();
if (charset == null) {
charset = DEFAULT_CHARSET;
}
}
int trimLen;
for (trimLen=0; trimLen<text.length; trimLen+=2) {
if (trimLen == text.length-1) {
if (text[trimLen] != 0) {
trimLen++;
}
break;
} else if ((text[trimLen] == -1 && text[trimLen+1] == -1) ||
((text[trimLen] & 0xE0) == 0 && text[trimLen+1] == 0)) {
break;
}
}
String textString = new String(text, 0, trimLen, charset);
textString = textString.substring(0, Math.min(textString.length(), length));
if (textString.isEmpty()) { if (textString.isEmpty()) {
return; return;
} }
DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx); final DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx);
FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font); final FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font);
if (fontInfo.getCharset() == FontCharset.SYMBOL) { textString = fontHandler.mapFontCharset(graphicsCtx, fontInfo, textString);
textString = DrawFontManagerDefault.mapSymbolChars(textString);
}
AttributedString as = new AttributedString(textString); final AttributedString as = new AttributedString(textString);
addAttributes(as, font, fontInfo.getTypeface()); addAttributes(as::addAttribute, font, fontInfo.getTypeface());
// disabled for the time being, as the results aren't promising
/*
if (dx != null && !dx.isEmpty()) {
//for multi-byte encodings (e.g. Shift_JIS), the byte length
//might not equal the string length().
//The x information is stored in dx[], an array parallel to the
//byte array text[]. dx[] stores the x info in the
//first byte of a multibyte character, but dx[] stores 0
//for the other bytes in that character.
//We need to map this information to the String offsets
//dx[0] = 13 text[0] = -125
//dx[1] = 0 text[1] = 118
//dx[2] = 14 text[2] = -125
//dx[3] = 0 text[3] = -115
// needs to be remapped as:
//dxNormed[0] = 13 textString.get(0) = U+30D7
//dxNormed[1] = 14 textString.get(1) = U+30ED
final int cps = textString.codePointCount(0, textString.length());
final int unicodeSteps = Math.max(dx.size()/cps, 1);
int dxPosition = 0, lastDxPosition = 0;
int beginIndex = 0;
while (beginIndex < textString.length() && dxPosition < dx.size()) {
int endIndex = textString.offsetByCodePoints(beginIndex, 1);
if (beginIndex > 0) {
// Tracking works as a prefix/advance space on characters whereas
// dx[...] is the complete width of the current char
// therefore we need to add the additional/suffix width to the next char
as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(lastDxPosition) - fontW) / fontH), beginIndex, endIndex);
}
lastDxPosition = dxPosition;
dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex);
beginIndex = endIndex;
}
}
*/
double angle = Math.toRadians(-font.getEscapement()/10.);
final HwmfText.HwmfTextAlignment align = prop.getTextAlignLatin();
final HwmfText.HwmfTextVerticalAlignment valign = prop.getTextVAlignLatin();
final FontRenderContext frc = graphicsCtx.getFontRenderContext(); final FontRenderContext frc = graphicsCtx.getFontRenderContext();
final TextLayout layout = new TextLayout(as.getIterator(), frc);
final Rectangle2D pixelBounds = layout.getBounds(); calculateDx(textString, dx, font, fontInfo, frc, as);
AffineTransform tx = new AffineTransform(); final double angle = Math.toRadians(-font.getEscapement()/10.);
switch (align) {
default:
case LEFT:
break;
case CENTER:
tx.translate(-pixelBounds.getWidth() / 2., 0);
break;
case RIGHT:
tx.translate(-layout.getAdvance(), 0);
break;
}
// TODO: check min/max orientation
switch (valign) {
case TOP:
tx.translate(0, layout.getAscent());
break;
default:
case BASELINE:
break;
case BOTTOM:
tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent()));
break;
}
tx.rotate(angle);
Point2D src = new Point2D.Double();
Point2D dst = new Point2D.Double();
tx.transform(src, dst);
final Point2D dst = getRotatedOffset(angle, frc, as);
final Shape clipShape = graphicsCtx.getClip(); final Shape clipShape = graphicsCtx.getClip();
try {
if (clip != null && !clip.getBounds2D().isEmpty()) {
graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY());
graphicsCtx.rotate(angle);
graphicsCtx.translate(clip.getCenterX(), clip.getCenterY());
if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) {
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
graphicsCtx.fill(clip);
}
if (opts.isClipped()) {
graphicsCtx.setClip(clip);
}
graphicsCtx.setTransform(at);
}
graphicsCtx.translate(reference.getX(), reference.getY()); try {
updateClipping(graphicsCtx, clip, angle, opts);
// TODO: Check: certain images don't use the reference of the extTextOut, but rely on a moveto issued beforehand
Point2D moveTo = (reference.distance(0,0) == 0) ? prop.getLocation() : reference;
graphicsCtx.translate(moveTo.getX(), moveTo.getY());
graphicsCtx.rotate(angle); graphicsCtx.rotate(angle);
if (scale != null) { if (scale != null) {
graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1); graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1);
@ -595,17 +497,74 @@ public class HwmfGraphics {
} }
} }
private void addAttributes(AttributedString as, HwmfFont font, String typeface) { /**
as.addAttribute(TextAttribute.FAMILY, typeface); * The dx array indicate the distance between origins of adjacent character cells.
as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); * For example, dx[i] logical units separate the origins of character cell i and character cell i + 1.
* So dx{i] is the complete width of the current char + space to the next character
*
* In AWT we have the {@link TextAttribute#TRACKING} attribute, which works very similar.
* As we don't know (yet) the calculation based on the font size/height, we interpolate
* between the default tracking and a tracking value of 1
*/
private void calculateDx(String textString, List<Integer> dx, HwmfFont font, FontInfo fontInfo, FontRenderContext frc, AttributedString as) {
if (dx == null || dx.isEmpty()) {
return;
}
final List<DxLayout> dxList = new ArrayList<>();
Map<TextAttribute,Object> fontAtt = new HashMap<>();
// Font tracking default (= 0)
addAttributes(fontAtt::put, font, fontInfo.getTypeface());
final GlyphVector gv0 = new Font(fontAtt).createGlyphVector(frc, textString);
// Font tracking = 1
fontAtt.put(TextAttribute.TRACKING, 1);
final GlyphVector gv1 = new Font(fontAtt).createGlyphVector(frc, textString);
int beginIndex = 0;
for (int offset = 0; offset < dx.size(); offset++) {
if (beginIndex >= textString.length()) {
break;
}
DxLayout dxLayout = new DxLayout();
dxLayout.dx = dx.get(offset);
dxLayout.pos0 = gv0.getGlyphPosition(offset).getX();
dxLayout.pos1 = gv1.getGlyphPosition(offset).getX();
dxLayout.beginIndex = beginIndex;
dxLayout.endIndex = textString.offsetByCodePoints(beginIndex, 1);
dxList.add(dxLayout);
beginIndex = dxLayout.endIndex;
}
// Calculate the linear (y ~= Tracking setting / x ~= character spacing / target value)
// y = m * x + n
// y = ((y2-y1)/(x2-x1))x + ((y1x2-y2x1)/(x2-x1))
DxLayout dx0 = null;
for (DxLayout dx1 : dxList) {
if (dx0 != null) {
// Default Tracking = 0 (y1)
double y1 = 0, x1 = dx1.pos0-dx0.pos0;
// Second Tracking = 1 (y2)
double y2 = 1, x2 = dx1.pos1-dx0.pos1;
double track = ((y2-y1)/(x2-x1))*dx0.dx + ((y1*x2-y2*x1)/(x2-x1));
as.addAttribute(TextAttribute.TRACKING, (float)track, dx0.beginIndex, dx0.endIndex);
}
dx0 = dx1;
}
}
private void addAttributes(BiConsumer<TextAttribute,Object> attributes, HwmfFont font, String typeface) {
attributes.accept(TextAttribute.FAMILY, typeface);
attributes.accept(TextAttribute.SIZE, getFontHeight(font));
if (font.isStrikeOut()) { if (font.isStrikeOut()) {
as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); attributes.accept(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
} }
if (font.isUnderline()) { if (font.isUnderline()) {
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); attributes.accept(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
} }
if (font.isItalic()) { if (font.isItalic()) {
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); attributes.accept(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
} }
// convert font weight to awt font weight - usually a font weight of 400 is regarded as regular // convert font weight to awt font weight - usually a font weight of 400 is regarded as regular
final int fw = font.getWeight(); final int fw = font.getWeight();
@ -616,7 +575,7 @@ public class HwmfGraphics {
break; break;
} }
} }
as.addAttribute(TextAttribute.WEIGHT, awtFW); attributes.accept(TextAttribute.WEIGHT, awtFW);
} }
private double getFontHeight(HwmfFont font) { private double getFontHeight(HwmfFont font) {
@ -630,10 +589,102 @@ public class HwmfGraphics {
// TODO: fix font height calculation // TODO: fix font height calculation
// the height is given as font size + ascent + descent // the height is given as font size + ascent + descent
// as an approximation we reduce the height by a static factor // as an approximation we reduce the height by a static factor
//
// see https://stackoverflow.com/a/26564924/2066598 on to get the font size from the cell height
return fontHeight*3/4; return fontHeight*3/4;
} }
} }
private static Charset getCharset(HwmfFont font, boolean isUnicode) {
if (isUnicode) {
return Charsets.UTF_16LE;
}
Charset charset = font.getCharset().getCharset();
return (charset == null) ? DEFAULT_CHARSET : charset;
}
private static String trimText(HwmfFont font, boolean isUnicode, byte[] text, int length) {
final Charset charset = getCharset(font, isUnicode);
int trimLen;
for (trimLen=0; trimLen<text.length; trimLen+=2) {
if (trimLen == text.length-1) {
if (text[trimLen] != 0) {
trimLen++;
}
break;
} else if ((text[trimLen] == -1 && text[trimLen+1] == -1) ||
((text[trimLen] & 0xE0) == 0 && text[trimLen+1] == 0)) {
break;
}
}
String textString = new String(text, 0, trimLen, charset);
return textString.substring(0, Math.min(textString.length(), length));
}
private void updateHorizontalAlign(AffineTransform tx, TextLayout layout) {
switch (prop.getTextAlignLatin()) {
default:
case LEFT:
break;
case CENTER:
tx.translate(-layout.getBounds().getWidth() / 2., 0);
break;
case RIGHT:
tx.translate(-layout.getAdvance(), 0);
break;
}
}
private void updateVerticalAlign(AffineTransform tx, TextLayout layout) {
// TODO: check min/max orientation
switch (prop.getTextVAlignLatin()) {
case TOP:
tx.translate(0, layout.getAscent());
break;
default:
case BASELINE:
break;
case BOTTOM:
tx.translate(0, -(layout.getBounds().getHeight()-layout.getDescent()));
break;
}
}
private void updateClipping(Graphics2D graphicsCtx, Rectangle2D clip, double angle, WmfExtTextOutOptions opts) {
if (clip == null || clip.getBounds2D().isEmpty()) {
return;
}
final AffineTransform at = graphicsCtx.getTransform();
graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY());
graphicsCtx.rotate(angle);
graphicsCtx.translate(clip.getCenterX(), clip.getCenterY());
if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) {
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
graphicsCtx.fill(clip);
}
if (opts.isClipped()) {
graphicsCtx.setClip(clip);
}
graphicsCtx.setTransform(at);
}
private Point2D getRotatedOffset(double angle, FontRenderContext frc, AttributedString as) {
final TextLayout layout = new TextLayout(as.getIterator(), frc);
final AffineTransform tx = new AffineTransform();
updateHorizontalAlign(tx, layout);
updateVerticalAlign(tx, layout);
tx.rotate(angle);
Point2D src = new Point2D.Double();
return tx.transform(src, null);
}
public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) { public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
drawImage(new BufferedImageRenderer(img), srcBounds, dstBounds); drawImage(new BufferedImageRenderer(img), srcBounds, dstBounds);
} }

View File

@ -50,12 +50,12 @@ public class HwmfText {
private static final int MAX_RECORD_LENGTH = 1_000_000; private static final int MAX_RECORD_LENGTH = 1_000_000;
/** /**
* The META_SETTEXTCHAREXTRA record defines inter-character spacing for text justification in the * The META_SETTEXTCHAREXTRA record defines inter-character spacing for text justification in the
* playback device context. Spacing is added to the white space between each character, including * playback device context. Spacing is added to the white space between each character, including
* break characters, when a line of justified text is output. * break characters, when a line of justified text is output.
*/ */
public static class WmfSetTextCharExtra implements HwmfRecord { public static class WmfSetTextCharExtra implements HwmfRecord {
/** /**
* A 16-bit unsigned integer that defines the amount of extra space, in * A 16-bit unsigned integer that defines the amount of extra space, in
* logical units, to be added to each character. If the current mapping mode is not MM_TEXT, * logical units, to be added to each character. If the current mapping mode is not MM_TEXT,
@ -63,12 +63,12 @@ public class HwmfText {
* mapping mode, see META_SETMAPMODE * mapping mode, see META_SETMAPMODE
*/ */
private int charExtra; private int charExtra;
@Override @Override
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextCharExtra; return HwmfRecordType.setTextCharExtra;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
charExtra = leis.readUShort(); charExtra = leis.readUShort();
@ -85,19 +85,19 @@ public class HwmfText {
return GenericRecordUtil.getGenericProperties("charExtra", () -> charExtra); return GenericRecordUtil.getGenericProperties("charExtra", () -> charExtra);
} }
} }
/** /**
* The META_SETTEXTCOLOR record defines the text foreground color in the playback device context. * The META_SETTEXTCOLOR record defines the text foreground color in the playback device context.
*/ */
public static class WmfSetTextColor implements HwmfRecord { public static class WmfSetTextColor implements HwmfRecord {
protected final HwmfColorRef colorRef = new HwmfColorRef(); protected final HwmfColorRef colorRef = new HwmfColorRef();
@Override @Override
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextColor; return HwmfRecordType.setTextColor;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
return colorRef.init(leis); return colorRef.init(leis);
@ -122,18 +122,18 @@ public class HwmfText {
return GenericRecordUtil.getGenericProperties("colorRef", this::getColorRef); return GenericRecordUtil.getGenericProperties("colorRef", this::getColorRef);
} }
} }
/** /**
* The META_SETTEXTJUSTIFICATION record defines the amount of space to add to break characters * The META_SETTEXTJUSTIFICATION record defines the amount of space to add to break characters
* in a string of justified text. * in a string of justified text.
*/ */
public static class WmfSetTextJustification implements HwmfRecord { public static class WmfSetTextJustification implements HwmfRecord {
/** /**
* A 16-bit unsigned integer that specifies the number of space characters in the line. * A 16-bit unsigned integer that specifies the number of space characters in the line.
*/ */
private int breakCount; private int breakCount;
/** /**
* A 16-bit unsigned integer that specifies the total extra space, in logical * A 16-bit unsigned integer that specifies the total extra space, in logical
* units, to be added to the line of text. If the current mapping mode is not MM_TEXT, the value * units, to be added to the line of text. If the current mapping mode is not MM_TEXT, the value
@ -141,12 +141,12 @@ public class HwmfText {
* details about setting the mapping mode, see {@link WmfSetMapMode}. * details about setting the mapping mode, see {@link WmfSetMapMode}.
*/ */
private int breakExtra; private int breakExtra;
@Override @Override
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor; return HwmfRecordType.setBkColor;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
breakCount = leis.readUShort(); breakCount = leis.readUShort();
@ -167,7 +167,7 @@ public class HwmfText {
); );
} }
} }
/** /**
* The META_TEXTOUT record outputs a character string at the specified location by using the font, * The META_TEXTOUT record outputs a character string at the specified location by using the font,
* background color, and text color that are defined in the playback device context. * background color, and text color that are defined in the playback device context.
@ -193,7 +193,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.textOut; return HwmfRecordType.textOut;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
stringLength = leis.readShort(); stringLength = leis.readShort();
@ -239,6 +239,7 @@ public class HwmfText {
} }
} }
@SuppressWarnings("unused")
public static class WmfExtTextOutOptions implements GenericRecord { public static class WmfExtTextOutOptions implements GenericRecord {
/** /**
* Indicates that the background color that is defined in the playback device context * Indicates that the background color that is defined in the playback device context
@ -361,18 +362,18 @@ public class HwmfText {
*/ */
protected final WmfExtTextOutOptions options; protected final WmfExtTextOutOptions options;
/** /**
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the * An optional 8-byte Rect Object (section 2.2.2.18) that defines the
* dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both. * dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both.
* *
* The corners are given in the order left, top, right, bottom. * The corners are given in the order left, top, right, bottom.
* Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of * Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of
* the upper-left corner of the rectangle * the upper-left corner of the rectangle
*/ */
protected final Rectangle2D bounds = new Rectangle2D.Double(); protected final Rectangle2D bounds = new Rectangle2D.Double();
/** /**
* A variable-length string that specifies the text to be drawn. The string does * A variable-length string that specifies the text to be drawn. The string does
* not need to be null-terminated, because StringLength specifies the length of the string. If * not need to be null-terminated, because StringLength specifies the length of the string. If
* the length is odd, an extra byte is placed after it so that the following member (optional Dx) is * the length is odd, an extra byte is placed after it so that the following member (optional Dx) is
* aligned on a 16-bit boundary. * aligned on a 16-bit boundary.
*/ */
protected byte[] rawTextBytes; protected byte[] rawTextBytes;
@ -396,7 +397,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.extTextOut; return HwmfRecordType.extTextOut;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
// -6 bytes of record function and length header // -6 bytes of record function and length header
@ -413,16 +414,16 @@ public class HwmfText {
// the bounding rectangle is optional and only read when options are given // the bounding rectangle is optional and only read when options are given
size += readRectS(leis, bounds); size += readRectS(leis, bounds);
} }
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
leis.readFully(rawTextBytes); leis.readFully(rawTextBytes);
size += rawTextBytes.length; size += rawTextBytes.length;
if (size >= remainingRecordSize) { if (size >= remainingRecordSize) {
logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info"); logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info");
return size; return size;
} }
int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE); int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE);
if (dxLen < stringLength) { if (dxLen < stringLength) {
logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters");
@ -432,7 +433,7 @@ public class HwmfText {
dx.add((int)leis.readShort()); dx.add((int)leis.readShort());
size += LittleEndianConsts.SHORT_SIZE; size += LittleEndianConsts.SHORT_SIZE;
} }
return size; return size;
} }
@ -480,23 +481,24 @@ public class HwmfText {
return GenericRecordUtil.getGenericProperties( return GenericRecordUtil.getGenericProperties(
"reference", this::getReference, "reference", this::getReference,
"bounds", this::getBounds, "bounds", this::getBounds,
"text", this::getGenericText "text", this::getGenericText,
"dx", () -> dx
); );
} }
} }
public enum HwmfTextAlignment { public enum HwmfTextAlignment {
LEFT, LEFT,
RIGHT, RIGHT,
CENTER CENTER
} }
public enum HwmfTextVerticalAlignment { public enum HwmfTextVerticalAlignment {
TOP, TOP,
BOTTOM, BOTTOM,
BASELINE BASELINE
} }
/** /**
* The META_SETTEXTALIGN record defines text-alignment values in the playback device context. * The META_SETTEXTALIGN record defines text-alignment values in the playback device context.
*/ */
@ -572,13 +574,13 @@ public class HwmfText {
* The reference point MUST be on the left edge of the bounding rectangle. * The reference point MUST be on the left edge of the bounding rectangle.
*/ */
private static final int VALIGN_BOTTOM = 1; private static final int VALIGN_BOTTOM = 1;
/** /**
* Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018): * Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text. * The reference point MUST be on the baseline of the text.
*/ */
private static final int VALIGN_BASELINE = 3; private static final int VALIGN_BASELINE = 3;
/** /**
* A 16-bit unsigned integer that defines text alignment. * A 16-bit unsigned integer that defines text alignment.
* This value MUST be a combination of one or more TextAlignmentMode Flags * This value MUST be a combination of one or more TextAlignmentMode Flags
@ -586,12 +588,12 @@ public class HwmfText {
* for text with a vertical baseline. * for text with a vertical baseline.
*/ */
protected int textAlignmentMode; protected int textAlignmentMode;
@Override @Override
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextAlign; return HwmfRecordType.setTextAlign;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
textAlignmentMode = leis.readUShort(); textAlignmentMode = leis.readUShort();
@ -670,7 +672,7 @@ public class HwmfText {
} }
} }
} }
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
protected final HwmfFont font; protected final HwmfFont font;
@ -686,7 +688,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() { public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createFontIndirect; return HwmfRecordType.createFontIndirect;
} }
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
return font.init(leis, recordSize); return font.init(leis, recordSize);
@ -696,7 +698,7 @@ public class HwmfText {
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this); ctx.addObjectTableEntry(this);
} }
@Override @Override
public void applyObject(HwmfGraphics ctx) { public void applyObject(HwmfGraphics ctx) {
ctx.getProperties().setFont(font); ctx.getProperties().setFont(font);

View File

@ -51,6 +51,7 @@ import org.apache.poi.util.IOUtils;
import org.apache.poi.util.RecordFormatException; import org.apache.poi.util.RecordFormatException;
import org.junit.Test; import org.junit.Test;
@SuppressWarnings("StatementWithEmptyBody")
public class TestHemfPicture { public class TestHemfPicture {
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance(); private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
@ -77,44 +78,49 @@ public class TestHemfPicture {
PPTX2PNG.main(args); PPTX2PNG.main(args);
} }
*/ */
/* /*
@Test @Test
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work") @Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
public void paintMultiple() throws Exception { public void paintMultiple() throws Exception {
final byte buf[] = new byte[50_000_000]; Pattern fileExt = Pattern.compile("(?i)^(.+/)*(.+)\\.(emf|wmf)$");
final byte[] buf = new byte[50_000_000];
try (SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z")) try (SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
) { ) {
SevenZArchiveEntry entry; SevenZArchiveEntry entry;
while ((entry = sevenZFile.getNextEntry()) != null) { while ((entry = sevenZFile.getNextEntry()) != null) {
final String etName = entry.getName(); if (entry.isDirectory() || entry.getSize() == 0) continue;
Matcher m = fileExt.matcher(entry.getName());
if (entry.isDirectory() || !etName.endsWith(".emf")) continue; if (!m.matches()) continue;
int size = sevenZFile.read(buf); int size = sevenZFile.read(buf);
ByteArrayInputStream bis = new ByteArrayInputStream(buf, 0, size); ByteArrayInputStream bis = new ByteArrayInputStream(buf, 0, size);
System.setIn(bis); System.setIn(bis);
String lastName = etName.replaceFirst(".+/", "");
String[] args = { String[] args = {
"-format", "png", // png,gif,jpg or null for test "-format", "png", // png,gif,jpg or null for test
"-outdir", new File("build/tmp/").getCanonicalPath(), "-outdir", new File("build/tmp/").getCanonicalPath(),
"-outfile", lastName.replace(".emf", ".png"), "-outfile", m.replaceAll("$2.png"),
"-fixside", "long", "-fixside", "long",
"-scale", "800", "-scale", "800",
"-ignoreParse", "-ignoreParse",
"-inputtype", m.replaceAll("$3").toUpperCase(),
// "-dump", new File("build/tmp/", lastName.replace(".emf",".json")).getCanonicalPath(), // "-dump", new File("build/tmp/", lastName.replace(".emf",".json")).getCanonicalPath(),
// "-quiet", "-quiet",
// "-extractEmbedded", // "-extractEmbedded",
"stdin" "stdin"
}; };
PPTX2PNG.main(args); try {
PPTX2PNG.main(args);
System.out.println("Processing "+entry.getName()+" ok");
} catch (Exception e) {
System.out.println("Processing "+entry.getName()+" failed");
}
} }
} }
} }
*/ */
@Test @Test
public void testBasicWindows() throws Exception { public void testBasicWindows() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) { try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
@ -272,7 +278,7 @@ public class TestHemfPicture {
public void testInfiniteLoopOnFile() throws Exception { public void testInfiniteLoopOnFile() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) { try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
HemfPicture pic = new HemfPicture(is); HemfPicture pic = new HemfPicture(is);
for (HemfRecord record : pic) { for (HemfRecord ignored : pic) {
} }
} }
@ -286,7 +292,7 @@ public class TestHemfPicture {
is.close(); is.close();
HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray())); HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray()));
for (HemfRecord record : pic) { for (HemfRecord ignored : pic) {
} }
} }