diff --git a/src/java/org/apache/poi/sl/draw/DrawFontManager.java b/src/java/org/apache/poi/sl/draw/DrawFontManager.java
index 0c16dd994a..72216b36bd 100644
--- a/src/java/org/apache/poi/sl/draw/DrawFontManager.java
+++ b/src/java/org/apache/poi/sl/draw/DrawFontManager.java
@@ -48,20 +48,25 @@ public interface DrawFontManager {
*
* @param graphics the graphics context to request additional rendering hints
* @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
*/
FontInfo getFallbackFont(Graphics2D graphics, FontInfo fontInfo);
/**
- * Map text charset depending on font family.
- *
- * Currently this only maps for wingdings font (into unicode private use area)
+ * Map text charset depending on font family.
+ *
+ * Currently this only maps for wingdings and symbol font (into unicode private use area)
+ *
+ * Depending if the requested font is installed in the system, tbe mapped string varies:
+ * 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 fontInfo the font info object corresponding to the text run font
* @param text the raw text
- *
+ *
* @return String with mapped codepoints
*
* @see Drawing exotic fonts in a java applet
@@ -77,7 +82,7 @@ public interface DrawFontManager {
* @param size the font size in points
* @param bold {@code true} if the font is bold
* @param italic {@code true} if the font is italic
- *
+ *
* @return the AWT font object
*/
Font createAWTFont(Graphics2D graphics, FontInfo fontInfo, double size, boolean bold, boolean italic);
diff --git a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
index 7dcd704c4e..e5b5f51349 100644
--- a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
+++ b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
@@ -21,12 +21,16 @@ package org.apache.poi.sl.draw;
import java.awt.Font;
import java.awt.Graphics2D;
+import java.awt.GraphicsEnvironment;
+import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import org.apache.poi.common.usermodel.fonts.FontCharset;
import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.sl.draw.Drawable.DrawableHint;
+import org.apache.poi.util.StringUtil;
/**
* Manages fonts when rendering slides.
@@ -56,37 +60,36 @@ public class DrawFontManagerDefault implements DrawFontManager {
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,
* 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
*
* @since POI 4.0.0
*/
- public static String mapSymbolChars(String text) {
- // wingdings doesn't contain high-surrogates, so chars are ok
- boolean changed = false;
- char[] chrs = text.toCharArray();
- for (int i=0; i msCodepointToUnicode;
-
private StringUtil() {
// no instances of this class
}
@@ -293,16 +289,6 @@ public class StringUtil {
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.
*/
@@ -381,38 +367,18 @@ public class StringUtil {
if (string == null || string.isEmpty()) {
return string;
}
- initMsCodepointMap();
- StringBuilder sb = new StringBuilder();
- final int length = string.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();
+ int[] cps = string.codePoints().map(StringUtil::mapMsCodepoint).toArray();
+ return new String(cps, 0, cps.length);
}
- public static synchronized void mapMsCodepoint(int msCodepoint, int unicodeCodepoint) {
- initMsCodepointMap();
- msCodepointToUnicode.put(msCodepoint, unicodeCodepoint);
- }
-
- 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);
+ private static int mapMsCodepoint(int cp) {
+ if (0xf020 <= cp && cp <= 0xf07f) {
+ return symbolMap_f020[cp - 0xf020];
+ } else if (0xf0a0 <= cp && cp <= 0xf0ff) {
+ return symbolMap_f0a0[cp - 0xf0a0];
}
+ return cp;
}
private static final int[] symbolMap_f020 = {
diff --git a/src/ooxml/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java b/src/ooxml/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
index a057d6db7d..b2b40f9163 100644
--- a/src/ooxml/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
+++ b/src/ooxml/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java
@@ -37,8 +37,8 @@ public class SVGPOIGraphics2D extends SVGGraphics2D {
private final RenderingHints hints;
- public SVGPOIGraphics2D(Document document) {
- super(getCtx(document), false);
+ public SVGPOIGraphics2D(Document document, boolean textAsShapes) {
+ super(getCtx(document), textAsShapes);
hints = getGeneratorContext().getGraphicContextDefaults().getRenderingHints();
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java b/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java
index 9f0a6f7742..23c71b0d76 100644
--- a/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java
+++ b/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java
@@ -50,6 +50,11 @@ interface OutputFormat extends Closeable {
class SVGFormat implements OutputFormat {
static final String svgNS = "http://www.w3.org/2000/svg";
private SVGGraphics2D svgGenerator;
+ private final boolean textAsShapes;
+
+ SVGFormat(boolean textAsShapes) {
+ this.textAsShapes = textAsShapes;
+ }
@Override
public Graphics2D getGraphics2D(double width, double height) {
@@ -58,7 +63,7 @@ interface OutputFormat extends Closeable {
// Create an instance of org.w3c.dom.Document.
Document document = domImpl.createDocument(svgNS, "svg", null);
- svgGenerator = new SVGPOIGraphics2D(document);
+ svgGenerator = new SVGPOIGraphics2D(document, textAsShapes);
svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height));
return svgGenerator;
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
index 1e4c92a5b1..4b2a7a600b 100644
--- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
+++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
@@ -67,7 +67,11 @@ public final class PPTX2PNG {
" -dump dump the annotated records to a file\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" +
- " -extractEmbedded extract embedded parts";
+ " -extractEmbedded extract embedded parts\n" +
+ " -inputType 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);
// no System.exit here, as we also run in junit tests!
@@ -93,6 +97,8 @@ public final class PPTX2PNG {
private String fixSide = "scale";
private boolean ignoreParse = false;
private boolean extractEmbedded = false;
+ private FileMagic defaultFileType = FileMagic.OLE2;
+ private boolean textAsShapes = false;
private PPTX2PNG() {
}
@@ -153,6 +159,17 @@ public final class PPTX2PNG {
fixSide = "long";
}
break;
+ case "-inputType":
+ if (opt != null) {
+ defaultFileType = FileMagic.valueOf(opt);
+ i++;
+ } else {
+ defaultFileType = FileMagic.OLE2;
+ }
+ break;
+ case "-textAsShapes":
+ textAsShapes = true;
+ break;
case "-ignoreParse":
ignoreParse = true;
break;
@@ -238,7 +255,7 @@ public final class PPTX2PNG {
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);
// default rendering options
@@ -337,6 +354,9 @@ public final class PPTX2PNG {
if ("stdin".equals(fileName)) {
InputStream bis = FileMagic.prepareToCheckMagic(System.in);
FileMagic fm = FileMagic.valueOf(bis);
+ if (fm == FileMagic.UNKNOWN) {
+ fm = defaultFileType;
+ }
switch (fm) {
case EMF:
proxy = new EMFHandler();
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
index 37919c9d69..cf35b716ae 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
@@ -21,6 +21,7 @@ import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
+import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
@@ -29,6 +30,7 @@ import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
@@ -39,16 +41,18 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.nio.charset.Charset;
import java.text.AttributedString;
+import java.util.ArrayList;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.BiConsumer;
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.hwmf.record.HwmfBrushStyle;
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.HwmfLineDash;
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.sl.draw.BitmapImageRenderer;
import org.apache.poi.sl.draw.DrawFactory;
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.ImageRenderer;
import org.apache.poi.util.Internal;
@@ -107,6 +109,16 @@ public class HwmfGraphics {
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 propStack = new LinkedList<>();
protected HwmfDrawProperties prop;
@@ -189,6 +201,7 @@ public class HwmfGraphics {
draw(shape);
}
+ @SuppressWarnings("MagicConstant")
protected BasicStroke getStroke() {
HwmfDrawProperties prop = getProperties();
HwmfPenStyle ps = prop.getPenStyle();
@@ -285,7 +298,7 @@ public class HwmfGraphics {
* Moreover, each object table index uniquely refers to an object.
* Indexes in the WMF Object Table always start at 0.
*
- * @param entry
+ * @param entry the object table entry
*/
public void addObjectTableEntry(HwmfObjectTableEntry entry) {
int objIdx = objectIndexes.nextClearBit(0);
@@ -433,155 +446,44 @@ public class HwmfGraphics {
final HwmfDrawProperties prop = getProperties();
final AffineTransform at = graphicsCtx.getTransform();
-
try {
at.createInverse();
} catch (NoninvertibleTransformException e) {
return;
}
- HwmfFont font = prop.getFont();
+ final HwmfFont font = prop.getFont();
if (font == null || text == null || text.length == 0) {
return;
}
- double fontH = getFontHeight(font);
- // 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 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 AttributedString as = new AttributedString(textString);
+ addAttributes(as::addAttribute, font, fontInfo.getTypeface());
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();
- 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 double angle = Math.toRadians(-font.getEscapement()/10.);
+ final Point2D dst = getRotatedOffset(angle, frc, as);
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);
if (scale != null) {
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);
- as.addAttribute(TextAttribute.SIZE, getFontHeight(font));
+ /**
+ * The dx array indicate the distance between origins of adjacent character cells.
+ * 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 dx, HwmfFont font, FontInfo fontInfo, FontRenderContext frc, AttributedString as) {
+ if (dx == null || dx.isEmpty()) {
+ return;
+ }
+ final List dxList = new ArrayList<>();
+
+ Map 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 attributes, HwmfFont font, String typeface) {
+ attributes.accept(TextAttribute.FAMILY, typeface);
+ attributes.accept(TextAttribute.SIZE, getFontHeight(font));
if (font.isStrikeOut()) {
- as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
+ attributes.accept(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
}
if (font.isUnderline()) {
- as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
+ attributes.accept(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
}
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
final int fw = font.getWeight();
@@ -616,7 +575,7 @@ public class HwmfGraphics {
break;
}
}
- as.addAttribute(TextAttribute.WEIGHT, awtFW);
+ attributes.accept(TextAttribute.WEIGHT, awtFW);
}
private double getFontHeight(HwmfFont font) {
@@ -630,10 +589,102 @@ public class HwmfGraphics {
// TODO: fix font height calculation
// the height is given as font size + ascent + descent
// 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;
}
}
+ 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 charExtra);
}
}
-
+
/**
* The META_SETTEXTCOLOR record defines the text foreground color in the playback device context.
*/
public static class WmfSetTextColor implements HwmfRecord {
-
+
protected final HwmfColorRef colorRef = new HwmfColorRef();
-
+
@Override
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextColor;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
return colorRef.init(leis);
@@ -122,18 +122,18 @@ public class HwmfText {
return GenericRecordUtil.getGenericProperties("colorRef", this::getColorRef);
}
}
-
+
/**
* The META_SETTEXTJUSTIFICATION record defines the amount of space to add to break characters
* in a string of justified text.
*/
public static class WmfSetTextJustification implements HwmfRecord {
-
+
/**
* A 16-bit unsigned integer that specifies the number of space characters in the line.
*/
private int breakCount;
-
+
/**
* 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
@@ -141,12 +141,12 @@ public class HwmfText {
* details about setting the mapping mode, see {@link WmfSetMapMode}.
*/
private int breakExtra;
-
+
@Override
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
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,
* background color, and text color that are defined in the playback device context.
@@ -193,7 +193,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.textOut;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
stringLength = leis.readShort();
@@ -239,6 +239,7 @@ public class HwmfText {
}
}
+ @SuppressWarnings("unused")
public static class WmfExtTextOutOptions implements GenericRecord {
/**
* Indicates that the background color that is defined in the playback device context
@@ -361,18 +362,18 @@ public class HwmfText {
*/
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.
- *
+ *
* 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
*/
protected final Rectangle2D bounds = new Rectangle2D.Double();
/**
- * 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
- * the length is odd, an extra byte is placed after it so that the following member (optional Dx) is
+ * 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
+ * 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.
*/
protected byte[] rawTextBytes;
@@ -396,7 +397,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.extTextOut;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
// -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
size += readRectS(leis, bounds);
}
-
+
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
leis.readFully(rawTextBytes);
size += rawTextBytes.length;
-
+
if (size >= remainingRecordSize) {
logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info");
return size;
}
-
+
int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE);
if (dxLen < stringLength) {
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());
size += LittleEndianConsts.SHORT_SIZE;
}
-
+
return size;
}
@@ -480,23 +481,24 @@ public class HwmfText {
return GenericRecordUtil.getGenericProperties(
"reference", this::getReference,
"bounds", this::getBounds,
- "text", this::getGenericText
+ "text", this::getGenericText,
+ "dx", () -> dx
);
}
}
-
+
public enum HwmfTextAlignment {
LEFT,
RIGHT,
CENTER
}
-
+
public enum HwmfTextVerticalAlignment {
TOP,
BOTTOM,
BASELINE
}
-
+
/**
* 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.
*/
private static final int VALIGN_BOTTOM = 1;
-
+
/**
* Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text.
*/
private static final int VALIGN_BASELINE = 3;
-
+
/**
* A 16-bit unsigned integer that defines text alignment.
* 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.
*/
protected int textAlignmentMode;
-
+
@Override
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextAlign;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
textAlignmentMode = leis.readUShort();
@@ -670,7 +672,7 @@ public class HwmfText {
}
}
}
-
+
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
protected final HwmfFont font;
@@ -686,7 +688,7 @@ public class HwmfText {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createFontIndirect;
}
-
+
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
return font.init(leis, recordSize);
@@ -696,7 +698,7 @@ public class HwmfText {
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
-
+
@Override
public void applyObject(HwmfGraphics ctx) {
ctx.getProperties().setFont(font);
diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
index 375d8a8cf5..0cea2261d7 100644
--- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
+++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
@@ -51,6 +51,7 @@ import org.apache.poi.util.IOUtils;
import org.apache.poi.util.RecordFormatException;
import org.junit.Test;
+@SuppressWarnings("StatementWithEmptyBody")
public class TestHemfPicture {
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
@@ -77,44 +78,49 @@ public class TestHemfPicture {
PPTX2PNG.main(args);
}
*/
+
/*
@Test
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
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"))
) {
SevenZArchiveEntry entry;
while ((entry = sevenZFile.getNextEntry()) != null) {
- final String etName = entry.getName();
-
- if (entry.isDirectory() || !etName.endsWith(".emf")) continue;
+ if (entry.isDirectory() || entry.getSize() == 0) continue;
+ Matcher m = fileExt.matcher(entry.getName());
+ if (!m.matches()) continue;
int size = sevenZFile.read(buf);
ByteArrayInputStream bis = new ByteArrayInputStream(buf, 0, size);
System.setIn(bis);
- String lastName = etName.replaceFirst(".+/", "");
-
String[] args = {
"-format", "png", // png,gif,jpg or null for test
"-outdir", new File("build/tmp/").getCanonicalPath(),
- "-outfile", lastName.replace(".emf", ".png"),
+ "-outfile", m.replaceAll("$2.png"),
"-fixside", "long",
"-scale", "800",
"-ignoreParse",
+ "-inputtype", m.replaceAll("$3").toUpperCase(),
// "-dump", new File("build/tmp/", lastName.replace(".emf",".json")).getCanonicalPath(),
- // "-quiet",
+ "-quiet",
// "-extractEmbedded",
"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
public void testBasicWindows() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
@@ -272,7 +278,7 @@ public class TestHemfPicture {
public void testInfiniteLoopOnFile() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
HemfPicture pic = new HemfPicture(is);
- for (HemfRecord record : pic) {
+ for (HemfRecord ignored : pic) {
}
}
@@ -286,7 +292,7 @@ public class TestHemfPicture {
is.close();
HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray()));
- for (HemfRecord record : pic) {
+ for (HemfRecord ignored : pic) {
}
}