Regression fixes for H/XSLF and HWMF

see http://apache-poi.1045710.n5.nabble.com/3-14-beta-2-3-14-final-tt5721829.html

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1732236 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-02-24 22:43:51 +00:00
parent 784bdfe20f
commit c6fa344c54
59 changed files with 1342 additions and 464 deletions

View File

@ -26,10 +26,8 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.sl.draw.Drawable; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.usermodel.PictureData; import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.sl.usermodel.Shape;
import org.apache.poi.sl.usermodel.ShapeContainer; import org.apache.poi.sl.usermodel.ShapeContainer;
@ -39,7 +37,6 @@ import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextRun; import org.apache.poi.sl.usermodel.TextRun;
import org.apache.poi.sl.usermodel.TextShape; import org.apache.poi.sl.usermodel.TextShape;
import org.apache.poi.util.JvmBugs;
public abstract class SlideShowHandler extends POIFSFileHandler { public abstract class SlideShowHandler extends POIFSFileHandler {
public void handleSlideShow(SlideShow<?,?> ss) throws IOException { public void handleSlideShow(SlideShow<?,?> ss) throws IOException {
@ -55,10 +52,12 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
// read in the writen file // read in the writen file
SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray())); SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray()));
try {
assertNotNull(read); assertNotNull(read);
readContent(read); readContent(read);
} finally {
read.close();
}
} }
private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException { private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException {
@ -109,7 +108,7 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
for (Slide<?,?> s : ss.getSlides()) { for (Slide<?,?> s : ss.getSlides()) {
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB); BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
fixFonts(graphics); DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options // default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@ -124,15 +123,4 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
img.flush(); img.flush();
} }
} }
@SuppressWarnings("unchecked")
private static void fixFonts(Graphics2D graphics) {
if (!JvmBugs.hasLineBreakMeasurerBug()) return;
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
if (fontMap == null) fontMap = new HashMap<String,String>();
fontMap.put("Calibri", "Lucida Sans");
fontMap.put("Cambria", "Lucida Bright");
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
}
} }

View File

@ -43,7 +43,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
private boolean sizeIncludesHeaderSize = true; private boolean sizeIncludesHeaderSize = true;
/** /**
* When reading a property from data stream remeber if the complex part is empty and set this flag. * When reading a property from data stream remember if the complex part is empty and set this flag.
*/ */
private boolean emptyComplexPart = false; private boolean emptyComplexPart = false;
@ -65,10 +65,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
} }
public int getNumberOfElementsInArray() { public int getNumberOfElementsInArray() {
if (emptyComplexPart){ return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0);
return 0;
}
return LittleEndian.getUShort(_complexData, 0);
} }
public void setNumberOfElementsInArray(int numberOfElements) { public void setNumberOfElementsInArray(int numberOfElements) {
@ -82,7 +79,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
} }
public int getNumberOfElementsInMemory() { public int getNumberOfElementsInMemory() {
return LittleEndian.getUShort(_complexData, 2); return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2);
} }
public void setNumberOfElementsInMemory(int numberOfElements) { public void setNumberOfElementsInMemory(int numberOfElements) {
@ -96,7 +93,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements
} }
public short getSizeOfElements() { public short getSizeOfElements() {
return LittleEndian.getShort( _complexData, 4 ); return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 );
} }
public void setSizeOfElements(int sizeOfElements) { public void setSizeOfElements(int sizeOfElements) {

View File

@ -25,6 +25,7 @@ import java.awt.geom.Rectangle2D;
import org.apache.poi.sl.usermodel.Background; import org.apache.poi.sl.usermodel.Background;
import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.PlaceableShape;
import org.apache.poi.sl.usermodel.ShapeContainer; import org.apache.poi.sl.usermodel.ShapeContainer;
import org.apache.poi.sl.usermodel.Sheet;
public class DrawBackground extends DrawShape { public class DrawBackground extends DrawShape {
@ -47,6 +48,7 @@ public class DrawBackground extends DrawShape {
public void setFlipVertical(boolean flip) {} public void setFlipVertical(boolean flip) {}
public boolean getFlipHorizontal() { return false; } public boolean getFlipHorizontal() { return false; }
public boolean getFlipVertical() { return false; } public boolean getFlipVertical() { return false; }
public Sheet<?,?> getSheet() { return shape.getSheet(); }
}; };
DrawFactory drawFact = DrawFactory.getInstance(graphics); DrawFactory drawFact = DrawFactory.getInstance(graphics);
@ -55,6 +57,7 @@ public class DrawBackground extends DrawShape {
Rectangle2D anchor2 = getAnchor(graphics, anchor); Rectangle2D anchor2 = getAnchor(graphics, anchor);
if(fill != null) { if(fill != null) {
graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, anchor);
graphics.setPaint(fill); graphics.setPaint(fill);
graphics.fill(anchor2); graphics.fill(anchor2);
} }

View File

@ -17,11 +17,11 @@
package org.apache.poi.sl.draw; package org.apache.poi.sl.draw;
import static org.apache.poi.sl.draw.Drawable.DRAW_FACTORY;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.font.TextLayout; import java.awt.font.TextLayout;
import java.text.AttributedString; import java.text.AttributedString;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.sl.usermodel.Background; import org.apache.poi.sl.usermodel.Background;
import org.apache.poi.sl.usermodel.ConnectorShape; import org.apache.poi.sl.usermodel.ConnectorShape;
@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.TableShape;
import org.apache.poi.sl.usermodel.TextBox; import org.apache.poi.sl.usermodel.TextBox;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextShape; import org.apache.poi.sl.usermodel.TextShape;
import org.apache.poi.util.JvmBugs;
public class DrawFactory { public class DrawFactory {
protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>(); protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>();
@ -58,7 +59,7 @@ public class DrawFactory {
DrawFactory factory = null; DrawFactory factory = null;
boolean isHint = false; boolean isHint = false;
if (graphics != null) { if (graphics != null) {
factory = (DrawFactory)graphics.getRenderingHint(DRAW_FACTORY); factory = (DrawFactory)graphics.getRenderingHint(Drawable.DRAW_FACTORY);
isHint = (factory != null); isHint = (factory != null);
} }
// secondly try the thread local default // secondly try the thread local default
@ -70,7 +71,7 @@ public class DrawFactory {
factory = new DrawFactory(); factory = new DrawFactory();
} }
if (graphics != null && !isHint) { if (graphics != null && !isHint) {
graphics.setRenderingHint(DRAW_FACTORY, factory); graphics.setRenderingHint(Drawable.DRAW_FACTORY, factory);
} }
return factory; return factory;
} }
@ -166,4 +167,29 @@ public class DrawFactory {
public DrawPaint getPaint(PlaceableShape<?,?> shape) { public DrawPaint getPaint(PlaceableShape<?,?> shape) {
return new DrawPaint(shape); return new DrawPaint(shape);
} }
/**
* Replace font families for Windows JVM 6, which contains a font rendering error.
* This is likely to be removed, when POI upgrades to JDK 7
*
* @param graphics the graphics context which will contain the font mapping
*/
public void fixFonts(Graphics2D graphics) {
if (!JvmBugs.hasLineBreakMeasurerBug()) return;
@SuppressWarnings("unchecked")
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
if (fontMap == null) {
fontMap = new HashMap<String,String>();
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
}
String fonts[][] = { { "Calibri", "Lucida Sans" }, { "Cambria", "Lucida Bright" } };
for (String f[] : fonts) {
if (!fontMap.containsKey(f[0])) {
fontMap.put(f[0], f[1]);
}
}
}
} }

View File

@ -133,9 +133,12 @@ public class DrawPaint {
ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType()); ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType());
try {
try { try {
renderer.loadImage(is, fill.getContentType()); renderer.loadImage(is, fill.getContentType());
} finally {
is.close(); is.close();
}
} catch (IOException e) { } catch (IOException e) {
LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e); LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e);
return null; return null;
@ -278,11 +281,31 @@ public class DrawPaint {
snapToAnchor(p1, anchor); snapToAnchor(p1, anchor);
snapToAnchor(p2, anchor); snapToAnchor(p2, anchor);
if (p1.equals(p2)) {
// gradient paint on the same point throws an exception ... and doesn't make sense
return null;
}
float[] fractions = fill.getGradientFractions(); float[] fractions = fill.getGradientFractions();
Color[] colors = new Color[fractions.length]; Color[] colors = new Color[fractions.length];
int i = 0; int i = 0;
for (ColorStyle fc : fill.getGradientColors()) { for (ColorStyle fc : fill.getGradientColors()) {
if (fc == null) {
// get color of background
fc = new ColorStyle() {
public int getTint() { return -1; }
public int getShade() { return -1; }
public int getSatOff() { return -1; }
public int getSatMod() { return -1; }
public int getLumOff() { return -1; }
public int getLumMod() { return -1; }
public int getHueOff() { return -1; }
public int getHueMod() { return -1; }
public Color getColor() { return Color.white; }
public int getAlpha() { return 0; }
};
}
colors[i++] = applyColorTransform(fc); colors[i++] = applyColorTransform(fc);
} }

View File

@ -52,8 +52,7 @@ public class DrawPictureShape extends DrawSimpleShape {
renderer.loadImage(data.getData(), data.getContentType()); renderer.loadImage(data.getData(), data.getContentType());
renderer.drawImage(graphics, anchor, insets); renderer.drawImage(graphics, anchor, insets);
} catch (IOException e) { } catch (IOException e) {
// TODO: draw specific runtime exception? LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e);
throw new RuntimeException(e);
} }
} }

View File

@ -90,17 +90,23 @@ public class DrawShape implements Drawable {
Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D(); Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D();
scaleX = anchor.getWidth() == 0. ? 1.0 : anchor.getWidth() / anchor2.getWidth(); scaleX = safeScale(anchor.getWidth(), anchor2.getWidth());
scaleY = anchor.getHeight() == 0. ? 1.0 : anchor.getHeight() / anchor2.getHeight(); scaleY = safeScale(anchor.getHeight(), anchor2.getHeight());
} else { } else {
quadrant = 0; quadrant = 0;
} }
// transformation is applied reversed ... // transformation is applied reversed ...
graphics.translate(centerX, centerY); graphics.translate(centerX, centerY);
graphics.rotate(Math.toRadians(rotation-quadrant*90.)); double rot = Math.toRadians(rotation-quadrant*90.);
if (rot != 0) {
graphics.rotate(rot);
}
graphics.scale(scaleX, scaleY); graphics.scale(scaleX, scaleY);
graphics.rotate(Math.toRadians(quadrant*90)); rot = Math.toRadians(quadrant*90);
if (rot != 0) {
graphics.rotate(rot);
}
graphics.translate(-centerX, -centerY); graphics.translate(-centerX, -centerY);
} }
@ -119,6 +125,12 @@ public class DrawShape implements Drawable {
} }
} }
private static double safeScale(double dim1, double dim2) {
if (dim1 == 0.) {
return 1;
}
return (dim2 == 0.) ? 1 : dim1/dim2;
}
public void draw(Graphics2D graphics) { public void draw(Graphics2D graphics) {
} }

View File

@ -23,7 +23,7 @@ import java.awt.Graphics2D;
import java.awt.Paint; import java.awt.Paint;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -179,20 +179,20 @@ public class DrawSimpleShape extends DrawShape {
case STEALTH: case STEALTH:
case ARROW: case ARROW:
p = new Path(false, true); p = new Path(false, true);
GeneralPath arrow = new GeneralPath(); Path2D.Double arrow = new Path2D.Double();
arrow.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));
arrow.lineTo(0, 0); arrow.lineTo(0, 0);
arrow.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));
tailShape = arrow; tailShape = arrow;
at.translate(x2, y2); at.translate(x2, y2);
at.rotate(alpha); at.rotate(alpha);
break; break;
case TRIANGLE: case TRIANGLE:
p = new Path(); p = new Path();
GeneralPath triangle = new GeneralPath(); Path2D.Double triangle = new Path2D.Double();
triangle.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));
triangle.lineTo(0, 0); triangle.lineTo(0, 0);
triangle.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));
triangle.closePath(); triangle.closePath();
tailShape = triangle; tailShape = triangle;
at.translate(x2, y2); at.translate(x2, y2);
@ -252,20 +252,20 @@ public class DrawSimpleShape extends DrawShape {
case STEALTH: case STEALTH:
case ARROW: case ARROW:
p = new Path(false, true); p = new Path(false, true);
GeneralPath arrow = new GeneralPath(); Path2D.Double arrow = new Path2D.Double();
arrow.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));
arrow.lineTo(0, 0); arrow.lineTo(0, 0);
arrow.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));
headShape = arrow; headShape = arrow;
at.translate(x1, y1); at.translate(x1, y1);
at.rotate(alpha); at.rotate(alpha);
break; break;
case TRIANGLE: case TRIANGLE:
p = new Path(); p = new Path();
GeneralPath triangle = new GeneralPath(); Path2D.Double triangle = new Path2D.Double();
triangle.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));
triangle.lineTo(0, 0); triangle.lineTo(0, 0);
triangle.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));
triangle.closePath(); triangle.closePath();
headShape = triangle; headShape = triangle;
at.translate(x1, y1); at.translate(x1, y1);

View File

@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.Insets2D;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PlaceableShape; import org.apache.poi.sl.usermodel.PlaceableShape;
import org.apache.poi.sl.usermodel.ShapeContainer; import org.apache.poi.sl.usermodel.ShapeContainer;
import org.apache.poi.sl.usermodel.Sheet;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle; import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;
import org.apache.poi.sl.usermodel.TextParagraph.TextAlign; import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
@ -465,6 +466,7 @@ public class DrawTextParagraph implements Drawable {
public void setFlipVertical(boolean flip) {} public void setFlipVertical(boolean flip) {}
public boolean getFlipHorizontal() { return false; } public boolean getFlipHorizontal() { return false; }
public boolean getFlipVertical() { return false; } public boolean getFlipVertical() { return false; }
public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); }
}; };
return ps; return ps;
} }
@ -530,7 +532,7 @@ public class DrawTextParagraph implements Drawable {
attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex)); attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex));
} }
Hyperlink hl = run.getHyperlink(); Hyperlink<?,?> hl = run.getHyperlink();
if (hl != null) { if (hl != null) {
attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex)); attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex));
attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex)); attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex));

View File

@ -21,11 +21,15 @@ import java.awt.Graphics2D;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.*; import java.util.Iterator;
import org.apache.poi.sl.usermodel.*; import org.apache.poi.sl.usermodel.Insets2D;
import org.apache.poi.sl.usermodel.PlaceableShape;
import org.apache.poi.sl.usermodel.ShapeContainer;
import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle; import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;
import org.apache.poi.util.JvmBugs; import org.apache.poi.sl.usermodel.TextRun;
import org.apache.poi.sl.usermodel.TextShape;
public class DrawTextShape extends DrawSimpleShape { public class DrawTextShape extends DrawSimpleShape {
@ -35,7 +39,7 @@ public class DrawTextShape extends DrawSimpleShape {
@Override @Override
public void drawContent(Graphics2D graphics) { public void drawContent(Graphics2D graphics) {
fixFonts(graphics); DrawFactory.getInstance(graphics).fixFonts(graphics);
TextShape<?,?> s = getShape(); TextShape<?,?> s = getShape();
@ -71,7 +75,7 @@ public class DrawTextShape extends DrawSimpleShape {
} }
Double textRot = s.getTextRotation(); Double textRot = s.getTextRotation();
if (textRot != null) { if (textRot != null && textRot != 0) {
graphics.translate(anchor.getCenterX(), anchor.getCenterY()); graphics.translate(anchor.getCenterX(), anchor.getCenterY());
graphics.rotate(Math.toRadians(textRot)); graphics.rotate(Math.toRadians(textRot));
graphics.translate(-anchor.getCenterX(), -anchor.getCenterY()); graphics.translate(-anchor.getCenterX(), -anchor.getCenterY());
@ -110,7 +114,8 @@ public class DrawTextShape extends DrawSimpleShape {
double y0 = y; double y0 = y;
//noinspection RedundantCast //noinspection RedundantCast
Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs = (Iterator<? extends TextParagraph<?, ?, ? extends TextRun>>) getShape().iterator(); Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs =
(Iterator<? extends TextParagraph<?,?,? extends TextRun>>) getShape().iterator();
boolean isFirstLine = true; boolean isFirstLine = true;
for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){ for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){
@ -170,23 +175,10 @@ public class DrawTextShape extends DrawSimpleShape {
// dry-run in a 1x1 image and return the vertical advance // dry-run in a 1x1 image and return the vertical advance
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
fixFonts(graphics); DrawFactory.getInstance(graphics).fixFonts(graphics);
return drawParagraphs(graphics, 0, 0); return drawParagraphs(graphics, 0, 0);
} }
@SuppressWarnings("unchecked")
private static void fixFonts(Graphics2D graphics) {
if (!JvmBugs.hasLineBreakMeasurerBug()) return;
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
if (fontMap == null) {
fontMap = new HashMap<String,String>();
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
}
if (!fontMap.containsKey("Calibri")) fontMap.put("Calibri", "Lucida Sans");
if (!fontMap.containsKey("Cambria")) fontMap.put("Cambria", "Lucida Bright");
}
@Override @Override
protected TextShape<?,?> getShape() { protected TextShape<?,?> getShape() {
return (TextShape<?,?>)shape; return (TextShape<?,?>)shape;

View File

@ -87,7 +87,7 @@ class PathGradientPaint implements Paint {
) { ) {
shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE); shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE);
if (shape == null) { if (shape == null) {
throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint PathGradientPaint.GRADIANT_SHAPE."); throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint Drawable.GRADIANT_SHAPE.");
} }
this.deviceBounds = deviceBounds; this.deviceBounds = deviceBounds;
@ -137,14 +137,14 @@ class PathGradientPaint implements Paint {
return childRaster; return childRaster;
} }
protected int getGradientSteps(Shape shape) { protected int getGradientSteps(Shape gradientShape) {
Rectangle rect = shape.getBounds(); Rectangle rect = gradientShape.getBounds();
int lower = 1; int lower = 1;
int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0); int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0);
while (lower < upper-1) { while (lower < upper-1) {
int mid = lower + (upper - lower) / 2; int mid = lower + (upper - lower) / 2;
BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle); BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle);
Area area = new Area(bs.createStrokedShape(shape)); Area area = new Area(bs.createStrokedShape(gradientShape));
if (area.isSingular()) { if (area.isSingular()) {
upper = mid; upper = mid;
} else { } else {

View File

@ -42,6 +42,7 @@ import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath; import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D; import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp; import java.awt.image.BufferedImageOp;
@ -243,7 +244,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* @see #setComposite * @see #setComposite
*/ */
public void draw(Shape shape){ public void draw(Shape shape){
GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
FreeformShape<?,?> p = _group.createFreeform(); FreeformShape<?,?> p = _group.createFreeform();
p.setPath(path); p.setPath(path);
p.setFillColor(null); p.setFillColor(null);
@ -339,7 +340,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* @see #setClip * @see #setClip
*/ */
public void fill(Shape shape){ public void fill(Shape shape){
GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
FreeformShape<?,?> p = _group.createFreeform(); FreeformShape<?,?> p = _group.createFreeform();
p.setPath(path); p.setPath(path);
applyPaint(p); applyPaint(p);
@ -450,7 +451,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
*/ */
public void drawRoundRect(int x, int y, int width, int height, public void drawRoundRect(int x, int y, int width, int height,
int arcWidth, int arcHeight){ int arcWidth, int arcHeight){
RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight); RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);
draw(rect); draw(rect);
} }
@ -481,7 +482,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* @see java.awt.Graphics#drawOval * @see java.awt.Graphics#drawOval
*/ */
public void fillOval(int x, int y, int width, int height){ public void fillOval(int x, int y, int width, int height){
Ellipse2D oval = new Ellipse2D.Float(x, y, width, height); Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);
fill(oval); fill(oval);
} }
@ -504,7 +505,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
public void fillRoundRect(int x, int y, int width, int height, public void fillRoundRect(int x, int y, int width, int height,
int arcWidth, int arcHeight){ int arcWidth, int arcHeight){
RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight); RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);
fill(rect); fill(rect);
} }
@ -544,9 +545,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* relative to the start angle. * relative to the start angle.
* @see java.awt.Graphics#drawArc * @see java.awt.Graphics#drawArc
*/ */
public void fillArc(int x, int y, int width, int height, public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){
int startAngle, int arcAngle){ Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);
Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);
fill(arc); fill(arc);
} }
@ -587,9 +587,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* relative to the start angle. * relative to the start angle.
* @see java.awt.Graphics#fillArc * @see java.awt.Graphics#fillArc
*/ */
public void drawArc(int x, int y, int width, int height, public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
int startAngle, int arcAngle) { Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);
Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);
draw(arc); draw(arc);
} }
@ -636,7 +635,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* @see java.awt.Graphics#fillOval * @see java.awt.Graphics#fillOval
*/ */
public void drawOval(int x, int y, int width, int height){ public void drawOval(int x, int y, int width, int height){
Ellipse2D oval = new Ellipse2D.Float(x, y, width, height); Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);
draw(oval); draw(oval);
} }
@ -932,7 +931,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable {
* @param y2 the second point's <i>y</i> coordinate. * @param y2 the second point's <i>y</i> coordinate.
*/ */
public void drawLine(int x1, int y1, int x2, int y2){ public void drawLine(int x1, int y1, int x2, int y2){
Line2D line = new Line2D.Float(x1, y1, x2, y2); Line2D line = new Line2D.Double(x1, y1, x2, y2);
draw(line); draw(line);
} }

View File

@ -19,12 +19,12 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
import java.awt.geom.Arc2D; import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
/** /**
* ArcTo command within a shape path in DrawingML: * ArcTo command within a shape path in DrawingML:
* *
@ -48,7 +48,7 @@ public class ArcToCommand implements PathCommand {
swAng = arc.getSwAng().toString(); swAng = arc.getSwAng().toString();
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
double rx = ctx.getValue(wr); double rx = ctx.getValue(wr);
double ry = ctx.getValue(hr); double ry = ctx.getValue(hr);
double start = ctx.getValue(stAng) / 60000; double start = ctx.getValue(stAng) / 60000;

View File

@ -19,7 +19,7 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
/** /**
* Date: 10/25/11 * Date: 10/25/11
@ -31,7 +31,7 @@ public class ClosePathCommand implements PathCommand {
ClosePathCommand(){ ClosePathCommand(){
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
path.closePath(); path.closePath();
} }
} }

View File

@ -19,9 +19,9 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import org.apache.poi.sl.draw.binding.CTAdjPoint2D; import java.awt.geom.Path2D;
import java.awt.geom.GeneralPath; import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
/** /**
* Date: 10/25/11 * Date: 10/25/11
@ -40,13 +40,13 @@ public class CurveToCommand implements PathCommand {
arg6 = pt3.getY().toString(); arg6 = pt3.getY().toString();
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
double x1 = ctx.getValue(arg1); double x1 = ctx.getValue(arg1);
double y1 = ctx.getValue(arg2); double y1 = ctx.getValue(arg2);
double x2 = ctx.getValue(arg3); double x2 = ctx.getValue(arg3);
double y2 = ctx.getValue(arg4); double y2 = ctx.getValue(arg4);
double x3 = ctx.getValue(arg5); double x3 = ctx.getValue(arg5);
double y3 = ctx.getValue(arg6); double y3 = ctx.getValue(arg6);
path.curveTo((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3); path.curveTo(x1, y1, x2, y2, x3, y3);
} }
} }

View File

@ -19,9 +19,9 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import org.apache.poi.sl.draw.binding.CTAdjPoint2D; import java.awt.geom.Path2D;
import java.awt.geom.GeneralPath; import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
/** /**
* Date: 10/25/11 * Date: 10/25/11
@ -41,9 +41,9 @@ public class LineToCommand implements PathCommand {
arg2 = s2; arg2 = s2;
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
double x = ctx.getValue(arg1); double x = ctx.getValue(arg1);
double y = ctx.getValue(arg2); double y = ctx.getValue(arg2);
path.lineTo((float)x, (float)y); path.lineTo(x, y);
} }
} }

View File

@ -19,9 +19,9 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import org.apache.poi.sl.draw.binding.CTAdjPoint2D; import java.awt.geom.Path2D;
import java.awt.geom.GeneralPath; import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
/** /**
* Date: 10/25/11 * Date: 10/25/11
@ -41,9 +41,9 @@ public class MoveToCommand implements PathCommand {
arg2 = s2; arg2 = s2;
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
double x = ctx.getValue(arg1); double x = ctx.getValue(arg1);
double y = ctx.getValue(arg2); double y = ctx.getValue(arg2);
path.moveTo((float)x, (float)y); path.moveTo(x, y);
} }
} }

View File

@ -19,11 +19,19 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.sl.draw.binding.*; import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
import org.apache.poi.sl.draw.binding.CTPath2D;
import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
import org.apache.poi.sl.draw.binding.CTPath2DClose;
import org.apache.poi.sl.draw.binding.CTPath2DCubicBezierTo;
import org.apache.poi.sl.draw.binding.CTPath2DLineTo;
import org.apache.poi.sl.draw.binding.CTPath2DMoveTo;
import org.apache.poi.sl.draw.binding.CTPath2DQuadBezierTo;
import org.apache.poi.sl.draw.binding.STPathFillMode;
/** /**
* Specifies a creation path consisting of a series of moves, lines and curves * Specifies a creation path consisting of a series of moves, lines and curves
@ -90,10 +98,10 @@ public class Path {
} }
/** /**
* Convert the internal represenation to java.awt.GeneralPath * Convert the internal represenation to java.awt.geom.Path2D
*/ */
public GeneralPath getPath(Context ctx) { public Path2D.Double getPath(Context ctx) {
GeneralPath path = new GeneralPath(); Path2D.Double path = new Path2D.Double();
for(PathCommand cmd : commands) for(PathCommand cmd : commands)
cmd.execute(path, ctx); cmd.execute(path, ctx);
return path; return path;

View File

@ -19,7 +19,7 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
/** /**
* A path command in DrawingML. One of: * A path command in DrawingML. One of:
@ -41,5 +41,5 @@ public interface PathCommand {
* @param path the path to append the result to * @param path the path to append the result to
* @param ctx the context to lookup variables * @param ctx the context to lookup variables
*/ */
void execute(GeneralPath path, Context ctx); void execute(Path2D.Double path, Context ctx);
} }

View File

@ -19,9 +19,9 @@
package org.apache.poi.sl.draw.geom; package org.apache.poi.sl.draw.geom;
import org.apache.poi.sl.draw.binding.CTAdjPoint2D; import java.awt.geom.Path2D;
import java.awt.geom.GeneralPath; import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
/** /**
* Date: 10/25/11 * Date: 10/25/11
@ -38,11 +38,11 @@ public class QuadToCommand implements PathCommand {
arg4 = pt2.getY().toString(); arg4 = pt2.getY().toString();
} }
public void execute(GeneralPath path, Context ctx){ public void execute(Path2D.Double path, Context ctx){
double x1 = ctx.getValue(arg1); double x1 = ctx.getValue(arg1);
double y1 = ctx.getValue(arg2); double y1 = ctx.getValue(arg2);
double x2 = ctx.getValue(arg3); double x2 = ctx.getValue(arg3);
double y2 = ctx.getValue(arg4); double y2 = ctx.getValue(arg4);
path.quadTo((float)x1, (float)y1, (float)x2, (float)y2); path.quadTo(x1, y1, x2, y2);
} }
} }

View File

@ -17,7 +17,7 @@
package org.apache.poi.sl.usermodel; package org.apache.poi.sl.usermodel;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
public interface FreeformShape< public interface FreeformShape<
S extends Shape<S,P>, S extends Shape<S,P>,
@ -33,7 +33,7 @@ public interface FreeformShape<
* *
* @return the path * @return the path
*/ */
GeneralPath getPath(); Path2D.Double getPath();
/** /**
* Set the shape path * Set the shape path
@ -41,5 +41,5 @@ public interface FreeformShape<
* @param path shape outline * @param path shape outline
* @return the number of points written * @return the number of points written
*/ */
int setPath(GeneralPath path); int setPath(Path2D.Double path);
} }

View File

@ -25,6 +25,11 @@ public interface PlaceableShape<
> { > {
ShapeContainer<S,P> getParent(); ShapeContainer<S,P> getParent();
/**
* @return the sheet this shape belongs to
*/
Sheet<S,P> getSheet();
/** /**
* @return the position of this shape within the drawing canvas. * @return the position of this shape within the drawing canvas.
* The coordinates are expressed in points * The coordinates are expressed in points

View File

@ -26,7 +26,6 @@ public interface Shape<
ShapeContainer<S,P> getParent(); ShapeContainer<S,P> getParent();
/** /**
*
* @return the sheet this shape belongs to * @return the sheet this shape belongs to
*/ */
Sheet<S,P> getSheet(); Sheet<S,P> getSheet();

View File

@ -20,7 +20,7 @@
package org.apache.poi.xslf.usermodel; package org.apache.poi.xslf.usermodel;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.PathIterator; import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
@ -57,7 +57,7 @@ public class XSLFFreeformShape extends XSLFAutoShape
} }
@Override @Override
public int setPath(GeneralPath path) { public int setPath(Path2D.Double path) {
CTPath2D ctPath = CTPath2D.Factory.newInstance(); CTPath2D ctPath = CTPath2D.Factory.newInstance();
Rectangle2D bounds = path.getBounds2D(); Rectangle2D bounds = path.getBounds2D();
@ -119,8 +119,8 @@ public class XSLFFreeformShape extends XSLFAutoShape
} }
@Override @Override
public GeneralPath getPath() { public Path2D.Double getPath() {
GeneralPath path = new GeneralPath(); Path2D.Double path = new Path2D.Double();
Rectangle2D bounds = getAnchor(); Rectangle2D bounds = getAnchor();
CTCustomGeometry2D geom = getSpPr().getCustGeom(); CTCustomGeometry2D geom = getSpPr().getCustGeom();
@ -168,7 +168,7 @@ public class XSLFFreeformShape extends XSLFAutoShape
// The returned path should fit in the bounding rectangle // The returned path should fit in the bounding rectangle
AffineTransform at = new AffineTransform(); AffineTransform at = new AffineTransform();
at.translate(bounds.getX(), bounds.getY()); at.translate(bounds.getX(), bounds.getY());
return new GeneralPath(at.createTransformedShape(path)); return new Path2D.Double(at.createTransformedShape(path));
} }
/** /**
* @param shapeId 1-based shapeId * @param shapeId 1-based shapeId

View File

@ -48,11 +48,11 @@ public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> {
@Override @Override
public String getAddress() { public String getAddress() {
if (!_link.isSetId()) { String id = _link.getId();
if (id == null || "".equals(id)) {
return _link.getAction(); return _link.getAction();
} }
String id = _link.getId();
URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI(); URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI();
return targetURI.toASCIIString(); return targetURI.toASCIIString();

View File

@ -455,8 +455,15 @@ public class XSLFTextRun implements TextRun {
@Override @Override
public XSLFHyperlink getHyperlink(){ public XSLFHyperlink getHyperlink(){
if(!_r.getRPr().isSetHlinkClick()) return null; CTTextCharacterProperties rPr = _r.getRPr();
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet()); if (rPr == null) {
return null;
}
CTHyperlink hl = rPr.getHlinkClick();
if (hl == null) {
return null;
}
return new XSLFHyperlink(hl, _p.getParentShape().getSheet());
} }
private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){ private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){

View File

@ -24,18 +24,17 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Set;
import java.util.TreeSet;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.poi.sl.draw.Drawable; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.Slide;
import org.apache.poi.sl.usermodel.SlideShow; import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.sl.usermodel.SlideShowFactory; import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.util.JvmBugs;
/** /**
* An utility to convert slides of a .pptx slide show to a PNG image * An utility to convert slides of a .pptx slide show to a PNG image
@ -65,7 +64,7 @@ public class PPTX2PNG {
return; return;
} }
int slidenum = -1; String slidenumStr = "-1";
float scale = 1; float scale = 1;
File file = null; File file = null;
String format = "png"; String format = "png";
@ -77,7 +76,7 @@ public class PPTX2PNG {
if ("-scale".equals(args[i])) { if ("-scale".equals(args[i])) {
scale = Float.parseFloat(args[++i]); scale = Float.parseFloat(args[++i]);
} else if ("-slide".equals(args[i])) { } else if ("-slide".equals(args[i])) {
slidenum = Integer.parseInt(args[++i]); slidenumStr = args[++i];
} else if ("-format".equals(args[i])) { } else if ("-format".equals(args[i])) {
format = args[++i]; format = args[++i];
} else if ("-outdir".equals(args[i])) { } else if ("-outdir".equals(args[i])) {
@ -120,9 +119,11 @@ public class PPTX2PNG {
SlideShow<?,?> ss = SlideShowFactory.create(file, null, true); SlideShow<?,?> ss = SlideShowFactory.create(file, null, true);
List<? extends Slide<?,?>> slides = ss.getSlides(); List<? extends Slide<?,?>> slides = ss.getSlides();
Set<Integer> slidenum = slideIndexes(slides.size(), slidenumStr);
if (slidenum < -1 || slidenum == 0 || slidenum > slides.size()) { if (slidenum.isEmpty()) {
usage("slidenum must be either -1 (for all) or within range: [1.."+slides.size()+"] for "+file); usage("slidenum must be either -1 (for all) or within range: [1.."+slides.size()+"] for "+file);
ss.close();
return; return;
} }
@ -130,9 +131,8 @@ public class PPTX2PNG {
int width = (int) (pgsize.width * scale); int width = (int) (pgsize.width * scale);
int height = (int) (pgsize.height * scale); int height = (int) (pgsize.height * scale);
int slideNo=1; for(Integer slideNo : slidenum) {
for(Slide<?,?> slide : slides) { Slide<?,?> slide = slides.get(slideNo);
if (slidenum == -1 || slideNo == slidenum) {
String title = slide.getTitle(); String title = slide.getTitle();
if (!quiet) { if (!quiet) {
System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title)); System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title));
@ -140,7 +140,7 @@ public class PPTX2PNG {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
fixFonts(graphics); DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options // default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@ -161,8 +161,6 @@ public class PPTX2PNG {
ImageIO.write(img, format, outfile); ImageIO.write(img, format, outfile);
} }
} }
slideNo++;
}
if (!quiet) { if (!quiet) {
System.out.println("Done"); System.out.println("Done");
@ -171,13 +169,42 @@ public class PPTX2PNG {
ss.close(); ss.close();
} }
@SuppressWarnings("unchecked") private static Set<Integer> slideIndexes(final int slideCount, String range) {
private static void fixFonts(Graphics2D graphics) { Set<Integer> slideIdx = new TreeSet<Integer>();
if (!JvmBugs.hasLineBreakMeasurerBug()) return; if ("-1".equals(range)) {
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP); for (int i=0; i<slideCount; i++) {
if (fontMap == null) fontMap = new HashMap<String,String>(); slideIdx.add(i);
fontMap.put("Calibri", "Lucida Sans"); }
fontMap.put("Cambria", "Lucida Bright"); } else {
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); for (String subrange : range.split(",")) {
String idx[] = subrange.split("-");
switch (idx.length) {
default:
case 0: break;
case 1: {
int subidx = Integer.parseInt(idx[0]);
if (subrange.contains("-")) {
int startIdx = subrange.startsWith("-") ? 0 : subidx;
int endIdx = subrange.endsWith("-") ? slideCount : Math.min(subidx,slideCount);
for (int i=Math.max(startIdx,1); i<endIdx; i++) {
slideIdx.add(i-1);
}
} else {
slideIdx.add(Math.max(subidx,1)-1);
}
break;
}
case 2: {
int startIdx = Math.min(Integer.parseInt(idx[0]), slideCount);
int endIdx = Math.min(Integer.parseInt(idx[1]), slideCount);
for (int i=Math.max(startIdx,1); i<endIdx; i++) {
slideIdx.add(i-1);
}
break;
}
}
}
}
return slideIdx;
} }
} }

View File

@ -36,17 +36,28 @@ public class TestPPTX2PNG {
public void render() throws Exception { public void render() throws Exception {
POIDataSamples samples = POIDataSamples.getSlideShowInstance(); POIDataSamples samples = POIDataSamples.getSlideShowInstance();
String[] testFiles = {"alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",}; // File testFilesX[] = new File("tmp_ppt").listFiles(new FileFilter() {
// String[] testFiles = {"41246-2.ppt","45543.ppt","53446.ppt","ParagraphStylesShorterThanCharStyles.ppt"}; // public boolean accept(File pathname) {
// return pathname.getName().toLowerCase().contains("ppt");
// }
// });
// String testFiles[] = new String[testFilesX.length];
// for (int i=0; i<testFilesX.length; i++) {
// testFiles[i] = testFilesX[i].getPath();
// }
String[] testFiles = {"53446.ppt", "alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",};
String[] args = { String[] args = {
"-format", "null", // png,gif,jpg or null for test "-format", "null", // png,gif,jpg or null for test
"-slide", "-1", // -1 for all "-slide", "-1", // -1 for all
"-outdir", new File("build/tmp/").getCanonicalPath(), "-outdir", new File("build/tmp/").getCanonicalPath(),
"-quite", "-quiet",
"dummyfile" "dummyfile"
}; };
for(String sampleFile : testFiles){ for(String sampleFile : testFiles){
args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath(); args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath();
// args[args.length-1] = new File(sampleFile).getCanonicalPath();
try { try {
PPTX2PNG.main(args); PPTX2PNG.main(args);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {

View File

@ -19,8 +19,9 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
@ -30,16 +31,16 @@ import org.junit.Test;
public class TestXSLFFreeformShape { public class TestXSLFFreeformShape {
@Test @Test
public void testSetPath() { public void testSetPath() throws IOException {
XMLSlideShow ppt = new XMLSlideShow(); XMLSlideShow ppt = new XMLSlideShow();
XSLFSlide slide = ppt.createSlide(); XSLFSlide slide = ppt.createSlide();
XSLFFreeformShape shape1 = slide.createFreeform(); XSLFFreeformShape shape1 = slide.createFreeform();
// comples path consisting of a rectangle and an ellipse inside it // comples path consisting of a rectangle and an ellipse inside it
GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(150, 150, 300, 300)); Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(150, 150, 300, 300));
path1.append(new Ellipse2D.Double(200, 200, 100, 50), false); path1.append(new Ellipse2D.Double(200, 200, 100, 50), false);
shape1.setPath(path1); shape1.setPath(path1);
GeneralPath path2 = shape1.getPath(); Path2D.Double path2 = shape1.getPath();
// YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ? // YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ?
// one way is to create another XSLFFreeformShape from path2 and compare the resulting xml // one way is to create another XSLFFreeformShape from path2 and compare the resulting xml
@ -49,5 +50,7 @@ public class TestXSLFFreeformShape {
shape2.setPath(path2); shape2.setPath(path2);
assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString()); assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString());
ppt.close();
} }
} }

View File

@ -42,6 +42,7 @@ import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath; import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D; import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp; import java.awt.image.BufferedImageOp;
@ -244,7 +245,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable {
* @see #setComposite * @see #setComposite
*/ */
public void draw(Shape shape){ public void draw(Shape shape){
GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
HSLFFreeformShape p = new HSLFFreeformShape(_group); HSLFFreeformShape p = new HSLFFreeformShape(_group);
p.setPath(path); p.setPath(path);
p.getFill().setForegroundColor(null); p.getFill().setForegroundColor(null);
@ -346,7 +347,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable {
* @see #setClip * @see #setClip
*/ */
public void fill(Shape shape){ public void fill(Shape shape){
GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape)); Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));
HSLFFreeformShape p = new HSLFFreeformShape(_group); HSLFFreeformShape p = new HSLFFreeformShape(_group);
p.setPath(path); p.setPath(path);
applyPaint(p); applyPaint(p);

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogger;
/** /**
* This class represents the metadata of a link in a slide/notes/etc. * This class represents the metadata of a link in a slide/notes/etc.
@ -59,11 +60,12 @@ public class InteractiveInfo extends RecordContainer {
*/ */
private void findInterestingChildren() { private void findInterestingChildren() {
// First child should be the InteractiveInfoAtom // First child should be the InteractiveInfoAtom
if(_children[0] instanceof InteractiveInfoAtom) { if (_children == null || _children.length == 0 || !(_children[0] instanceof InteractiveInfoAtom)) {
infoAtom = (InteractiveInfoAtom)_children[0]; logger.log(POILogger.WARN, "First child record wasn't a InteractiveInfoAtom - leaving this atom in an invalid state...");
} else { return;
throw new IllegalStateException("First child record wasn't a InteractiveInfoAtom, was of type " + _children[0].getRecordType());
} }
infoAtom = (InteractiveInfoAtom)_children[0];
} }
/** /**

View File

@ -168,7 +168,7 @@ public abstract class Record
try { try {
c = RecordTypes.forTypeID((short)type).handlingClass; c = RecordTypes.forTypeID((short)type).handlingClass;
if(c == null) { if(c == null) {
// How odd. RecordTypes normally subsitutes in // How odd. RecordTypes normally substitutes in
// a default handler class if it has heard of the record // a default handler class if it has heard of the record
// type but there's no support for it. Explicitly request // type but there's no support for it. Explicitly request
// that now // that now

View File

@ -23,23 +23,28 @@ import java.io.InputStream;
import java.util.List; import java.util.List;
import org.apache.poi.ddf.AbstractEscherOptRecord; import org.apache.poi.ddf.AbstractEscherOptRecord;
import org.apache.poi.ddf.EscherArrayProperty;
import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherColorRef;
import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.ddf.EscherSimpleProperty;
import org.apache.poi.hslf.record.Document; import org.apache.poi.hslf.record.Document;
import org.apache.poi.sl.draw.DrawPaint; import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.ColorStyle;
import org.apache.poi.sl.usermodel.FillStyle; import org.apache.poi.sl.usermodel.FillStyle;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint.GradientType;
import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.Units;
/** /**
* Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint.
*
* @author Yegor Kozlov
*/ */
public final class HSLFFill { public final class HSLFFill {
// For logging // For logging
@ -118,12 +123,94 @@ public final class HSLFFill {
public FillStyle getFillStyle() { public FillStyle getFillStyle() {
return new FillStyle() { return new FillStyle() {
public PaintStyle getPaint() { public PaintStyle getPaint() {
switch (getFillType()) { final int fillType = getFillType();
// TODO: fix gradient types, this mismatches with the MS-ODRAW definition ...
// need to handle (not only) the type (radial,rectangular,linear),
// the direction, e.g. top right, and bounds (e.g. for rectangular boxes)
switch (fillType) {
case FILL_SOLID: case FILL_SOLID:
return DrawPaint.createSolidPaint(getForegroundColor()); return DrawPaint.createSolidPaint(getForegroundColor());
case FILL_PICTURE: { case FILL_SHADE_SHAPE:
return getGradientPaint(GradientType.shape);
case FILL_SHADE_CENTER:
case FILL_SHADE_TITLE:
return getGradientPaint(GradientType.circular);
case FILL_SHADE:
case FILL_SHADE_SCALE:
return getGradientPaint(GradientType.linear);
case FILL_PICTURE:
return getTexturePaint();
default:
logger.log(POILogger.WARN, "unsuported fill type: " + fillType);
return null;
}
}
};
}
private GradientPaint getGradientPaint(final GradientType gradientType) {
final AbstractEscherOptRecord opt = shape.getEscherOptRecord();
final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS);
final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray();
return new GradientPaint() {
public double getGradientAngle() {
// A value of type FixedPoint, as specified in [MS-OSHARED] section 2.2.1.6,
// that specifies the angle of the gradient fill. Zero degrees represents a vertical vector from
// bottom to top. The default value for this property is 0x00000000.
int rot = shape.getEscherProperty(EscherProperties.FILL__ANGLE);
return 90-Units.fixedPointToDouble(rot);
}
public ColorStyle[] getGradientColors() {
ColorStyle cs[];
if (colorCnt == 0) {
cs = new ColorStyle[2];
cs[0] = wrapColor(getBackgroundColor());
cs[1] = wrapColor(getForegroundColor());
} else {
cs = new ColorStyle[colorCnt];
int idx = 0;
// TODO: handle palette colors and alpha(?) value
for (byte data[] : ep) {
EscherColorRef ecr = new EscherColorRef(data, 0, 4);
cs[idx++] = wrapColor(shape.getColor(ecr));
}
}
return cs;
}
private ColorStyle wrapColor(Color col) {
return (col == null) ? null : DrawPaint.createSolidPaint(col).getSolidColor();
}
public float[] getGradientFractions() {
float frc[];
if (colorCnt == 0) {
frc = new float[]{0, 1};
} else {
frc = new float[colorCnt];
int idx = 0;
for (byte data[] : ep) {
double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4));
frc[idx++] = (float)pos;
}
}
return frc;
}
public boolean isRotatedWithShape() {
return false;
}
public GradientType getGradientType() {
return gradientType;
}
};
}
private TexturePaint getTexturePaint() {
final HSLFPictureData pd = getPictureData(); final HSLFPictureData pd = getPictureData();
if (pd == null) break; if (pd == null) {
return null;
}
return new TexturePaint() { return new TexturePaint() {
public InputStream getImageData() { public InputStream getImageData() {
@ -139,14 +226,6 @@ public final class HSLFFill {
} }
}; };
} }
default:
logger.log(POILogger.WARN, "unsuported fill type: " + getFillType());
break;
}
return null;
}
};
}
/** /**
* Returns fill type. * Returns fill type.
@ -172,6 +251,7 @@ public final class HSLFFill {
} }
} }
@SuppressWarnings("resource")
protected EscherBSERecord getEscherBSERecord(int idx){ protected EscherBSERecord getEscherBSERecord(int idx){
HSLFSheet sheet = shape.getSheet(); HSLFSheet sheet = shape.getSheet();
if(sheet == null) { if(sheet == null) {
@ -258,6 +338,7 @@ public final class HSLFFill {
/** /**
* <code>PictureData</code> object used in a texture, pattern of picture fill. * <code>PictureData</code> object used in a texture, pattern of picture fill.
*/ */
@SuppressWarnings("resource")
public HSLFPictureData getPictureData(){ public HSLFPictureData getPictureData(){
AbstractEscherOptRecord opt = shape.getEscherOptRecord(); AbstractEscherOptRecord opt = shape.getEscherOptRecord();
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE); EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE);

View File

@ -18,22 +18,25 @@
package org.apache.poi.hslf.usermodel; package org.apache.poi.hslf.usermodel;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.PathIterator; import java.awt.geom.PathIterator;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.poi.ddf.AbstractEscherOptRecord; import org.apache.poi.ddf.AbstractEscherOptRecord;
import org.apache.poi.ddf.EscherArrayProperty; import org.apache.poi.ddf.EscherArrayProperty;
import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.ddf.EscherProperty;
import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.ddf.EscherSimpleProperty;
import org.apache.poi.sl.usermodel.FreeformShape; import org.apache.poi.sl.usermodel.FreeformShape;
import org.apache.poi.sl.usermodel.ShapeContainer; import org.apache.poi.sl.usermodel.ShapeContainer;
import org.apache.poi.sl.usermodel.ShapeType; import org.apache.poi.sl.usermodel.ShapeType;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.Units; import org.apache.poi.util.Units;
@ -57,6 +60,85 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
public static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60}; public static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60};
public static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80}; public static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80};
private static BitField PATH_INFO = BitFieldFactory.getInstance(0xE000);
private static BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00);
enum PathInfo {
lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6);
int flag;
PathInfo(int flag) {
this.flag = flag;
}
static PathInfo valueOf(int flag) {
for (PathInfo v : values()) {
if (v.flag == flag) {
return v;
}
}
return null;
}
}
enum EscapeInfo {
EXTENSION(0x0000),
ANGLE_ELLIPSE_TO(0x0001),
ANGLE_ELLIPSE(0x0002),
ARC_TO(0x0003),
ARC(0x0004),
CLOCKWISE_ARC_TO(0x0005),
CLOCKWISE_ARC(0x0006),
ELLIPTICAL_QUADRANT_X(0x0007),
ELLIPTICAL_QUADRANT_Y(0x0008),
QUADRATIC_BEZIER(0x0009),
NO_FILL(0X000A),
NO_LINE(0X000B),
AUTO_LINE(0X000C),
AUTO_CURVE(0X000D),
CORNER_LINE(0X000E),
CORNER_CURVE(0X000F),
SMOOTH_LINE(0X0010),
SMOOTH_CURVE(0X0011),
SYMMETRIC_LINE(0X0012),
SYMMETRIC_CURVE(0X0013),
FREEFORM(0X0014),
FILL_COLOR(0X0015),
LINE_COLOR(0X0016);
int flag;
EscapeInfo(int flag) {
this.flag = flag;
}
static EscapeInfo valueOf(int flag) {
for (EscapeInfo v : values()) {
if (v.flag == flag) {
return v;
}
}
return null;
}
}
enum ShapePath {
LINES(0),
LINES_CLOSED(1),
CURVES(2),
CURVES_CLOSED(3),
COMPLEX(4);
int flag;
ShapePath(int flag) {
this.flag = flag;
}
static ShapePath valueOf(int flag) {
for (ShapePath v : values()) {
if (v.flag == flag) {
return v;
}
}
return null;
}
}
/** /**
* Create a Freeform object and initialize it from the supplied Record container. * Create a Freeform object and initialize it from the supplied Record container.
* *
@ -88,7 +170,7 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
} }
@Override @Override
public int setPath(GeneralPath path) { public int setPath(Path2D.Double path) {
Rectangle2D bounds = path.getBounds2D(); Rectangle2D bounds = path.getBounds2D();
PathIterator it = path.getPathIterator(new AffineTransform()); PathIterator it = path.getPathIterator(new AffineTransform());
@ -179,18 +261,14 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
} }
@Override @Override
public GeneralPath getPath(){ public Path2D.Double getPath(){
AbstractEscherOptRecord opt = getEscherOptRecord(); AbstractEscherOptRecord opt = getEscherOptRecord();
opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__SHAPEPATH, 0x4));
EscherArrayProperty verticesProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES + 0x4000)); EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES);
if(verticesProp == null) verticesProp = getEscherProperty(opt, EscherProperties.GEOMETRY__VERTICES); EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO);
EscherArrayProperty segmentsProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO + 0x4000));
if(segmentsProp == null) segmentsProp = getEscherProperty(opt, EscherProperties.GEOMETRY__SEGMENTINFO);
// return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188 // return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188
GeneralPath path = new GeneralPath(); Path2D.Double path = new Path2D.Double();
//sanity check //sanity check
if(verticesProp == null) { if(verticesProp == null) {
@ -202,45 +280,59 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
return path; return path;
} }
int numPoints = verticesProp.getNumberOfElementsInArray(); Iterator<byte[]> vertIter = verticesProp.iterator();
int numSegments = segmentsProp.getNumberOfElementsInArray(); Iterator<byte[]> segIter = segmentsProp.iterator();
for (int i = 0, j = 0; i < numSegments && j < numPoints; i++) {
byte[] elem = segmentsProp.getElement(i);
if(Arrays.equals(elem, SEGMENTINFO_MOVETO)){
byte[] p = verticesProp.getElement(j++);
short x = LittleEndian.getShort(p, 0);
short y = LittleEndian.getShort(p, 2);
path.moveTo(Units.masterToPoints(x), Units.masterToPoints(y));
} else if (Arrays.equals(elem, SEGMENTINFO_CUBICTO) || Arrays.equals(elem, SEGMENTINFO_CUBICTO2)){
i++;
byte[] p1 = verticesProp.getElement(j++);
short x1 = LittleEndian.getShort(p1, 0);
short y1 = LittleEndian.getShort(p1, 2);
byte[] p2 = verticesProp.getElement(j++);
short x2 = LittleEndian.getShort(p2, 0);
short y2 = LittleEndian.getShort(p2, 2);
byte[] p3 = verticesProp.getElement(j++);
short x3 = LittleEndian.getShort(p3, 0);
short y3 = LittleEndian.getShort(p3, 2);
path.curveTo(
Units.masterToPoints(x1), Units.masterToPoints(y1),
Units.masterToPoints(x2), Units.masterToPoints(y2),
Units.masterToPoints(x3), Units.masterToPoints(y3));
} else if (Arrays.equals(elem, SEGMENTINFO_LINETO)){ byte segPushBack[] = null;
i++; while (vertIter.hasNext() && segIter.hasNext()) {
byte[] pnext = segmentsProp.getElement(i); byte[] segElem = (segPushBack != null) ? segPushBack : segIter.next();
if(Arrays.equals(pnext, SEGMENTINFO_ESCAPE)){ segPushBack = null;
if(j + 1 < numPoints){ PathInfo pi = getPathInfo(segElem);
byte[] p = verticesProp.getElement(j++); switch (pi) {
short x = LittleEndian.getShort(p, 0); case escape: {
short y = LittleEndian.getShort(p, 2); handleEscapeInfo(path, segElem, vertIter);
path.lineTo(Units.masterToPoints(x), Units.masterToPoints(y)); break;
} }
} else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){ case moveTo: {
byte[] p = vertIter.next();
double x = Units.masterToPoints(LittleEndian.getShort(p, 0));
double y = Units.masterToPoints(LittleEndian.getShort(p, 2));
path.moveTo(x,y);
break;
}
case curveTo: {
byte[] p1 = vertIter.next();
double x1 = Units.masterToPoints(LittleEndian.getShort(p1, 0));
double y1 = Units.masterToPoints(LittleEndian.getShort(p1, 2));
byte[] p2 = vertIter.next();
double x2 = Units.masterToPoints(LittleEndian.getShort(p2, 0));
double y2 = Units.masterToPoints(LittleEndian.getShort(p2, 2));
byte[] p3 = vertIter.next();
double x3 = Units.masterToPoints(LittleEndian.getShort(p3, 0));
double y3 = Units.masterToPoints(LittleEndian.getShort(p3, 2));
path.curveTo(x1,y1,x2,y2,x3,y3);
break;
}
case lineTo:
if (vertIter.hasNext()) {
byte[] p = vertIter.next();
double x = Units.masterToPoints(LittleEndian.getShort(p, 0));
double y = Units.masterToPoints(LittleEndian.getShort(p, 2));
path.lineTo(x,y);
}
break;
case close:
path.closePath(); path.closePath();
break;
default:
break;
} }
} }
EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH);
ShapePath sp = ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue());
if (sp == ShapePath.LINES_CLOSED || sp == ShapePath.CURVES_CLOSED) {
path.closePath();
} }
Rectangle2D anchor = getAnchor(); Rectangle2D anchor = getAnchor();
@ -251,6 +343,81 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
anchor.getWidth()/bounds.getWidth(), anchor.getWidth()/bounds.getWidth(),
anchor.getHeight()/bounds.getHeight() anchor.getHeight()/bounds.getHeight()
); );
return new GeneralPath(at.createTransformedShape(path)); return new Path2D.Double(at.createTransformedShape(path));
}
private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) {
T prop = getEscherProperty(opt, (short)(propId + 0x4000));
if (prop == null) {
prop = getEscherProperty(opt, propId);
}
return prop;
}
private void handleEscapeInfo(Path2D path, byte segElem[], Iterator<byte[]> vertIter) {
EscapeInfo ei = getEscapeInfo(segElem);
switch (ei) {
case EXTENSION:
break;
case ANGLE_ELLIPSE_TO:
break;
case ANGLE_ELLIPSE:
break;
case ARC_TO:
break;
case ARC:
break;
case CLOCKWISE_ARC_TO:
break;
case CLOCKWISE_ARC:
break;
case ELLIPTICAL_QUADRANT_X:
break;
case ELLIPTICAL_QUADRANT_Y:
break;
case QUADRATIC_BEZIER:
break;
case NO_FILL:
break;
case NO_LINE:
break;
case AUTO_LINE:
break;
case AUTO_CURVE:
break;
case CORNER_LINE:
break;
case CORNER_CURVE:
break;
case SMOOTH_LINE:
break;
case SMOOTH_CURVE:
break;
case SYMMETRIC_LINE:
break;
case SYMMETRIC_CURVE:
break;
case FREEFORM:
break;
case FILL_COLOR:
break;
case LINE_COLOR:
break;
default:
break;
}
}
private static PathInfo getPathInfo(byte elem[]) {
int elemUS = LittleEndian.getUShort(elem, 0);
int pathInfo = PATH_INFO.getValue(elemUS);
return PathInfo.valueOf(pathInfo);
}
private static EscapeInfo getEscapeInfo(byte elem[]) {
int elemUS = LittleEndian.getUShort(elem, 0);
int escInfo = ESCAPE_INFO.getValue(elemUS);
return EscapeInfo.valueOf(escInfo);
} }
} }

View File

@ -363,6 +363,9 @@ public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagrap
InteractiveInfo hldr = (InteractiveInfo)r; InteractiveInfo hldr = (InteractiveInfo)r;
InteractiveInfoAtom info = hldr.getInteractiveInfoAtom(); InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
if (info == null) {
continue;
}
int id = info.getHyperlinkID(); int id = info.getHyperlinkID();
ExHyperlink exHyper = exobj.get(id); ExHyperlink exHyper = exobj.get(id);
if (exHyper == null) { if (exHyper == null) {

View File

@ -210,6 +210,13 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS
: new Insets((int)(top*100000), (int)(left*100000), (int)(bottom*100000), (int)(right*100000)); : new Insets((int)(top*100000), (int)(left*100000), (int)(bottom*100000), (int)(right*100000));
} }
@Override
public ShapeType getShapeType() {
// this is kind of a hack, as picture/ole shapes can have a shape type of "frame"
// but rendering is handled like a rectangle
return ShapeType.RECT;
}
/** /**
* @return the fractional property or 0 if not defined * @return the fractional property or 0 if not defined
*/ */

View File

@ -346,7 +346,13 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
int val = (p == null) ? defaultColor : p.getPropertyValue(); int val = (p == null) ? defaultColor : p.getPropertyValue();
EscherColorRef ecr = new EscherColorRef(val); EscherColorRef ecr = new EscherColorRef(val);
Color col = getColor(ecr);
double alpha = getAlpha(opacityProperty);
return new Color(col.getRed(), col.getGreen(), col.getBlue(), (int)(alpha*255.0));
}
Color getColor(EscherColorRef ecr) {
boolean fPaletteIndex = ecr.hasPaletteIndexFlag(); boolean fPaletteIndex = ecr.hasPaletteIndexFlag();
boolean fPaletteRGB = ecr.hasPaletteRGBFlag(); boolean fPaletteRGB = ecr.hasPaletteRGBFlag();
boolean fSystemRGB = ecr.hasSystemRGBFlag(); boolean fSystemRGB = ecr.hasSystemRGBFlag();
@ -374,8 +380,7 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
//TODO //TODO
} }
double alpha = getAlpha(opacityProperty); return new Color(rgb[0], rgb[1], rgb[2]);
return new Color(rgb[0], rgb[1], rgb[2], (int)(alpha*255.0));
} }
double getAlpha(short opacityProperty) { double getAlpha(short opacityProperty) {

View File

@ -111,8 +111,15 @@ public final class HSLFShapeFactory {
shape = createNonPrimitive(spContainer, parent); shape = createNonPrimitive(spContainer, parent);
break; break;
default: default:
if (parent instanceof HSLFTable) {
EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID); EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID);
if (parent instanceof HSLFTable && etr != null) { if (etr == null) {
logger.log(POILogger.WARN, "invalid ppt - add EscherTextboxRecord to cell");
etr = new EscherTextboxRecord();
etr.setRecordId(EscherTextboxRecord.RECORD_ID);
etr.setOptions((short)15);
spContainer.addChildRecord(etr);
}
shape = new HSLFTableCell(spContainer, (HSLFTable)parent); shape = new HSLFTableCell(spContainer, (HSLFTable)parent);
} else { } else {
shape = new HSLFAutoShape(spContainer, parent); shape = new HSLFAutoShape(spContainer, parent);

View File

@ -275,7 +275,8 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
public Guide getAdjustValue(String name) { public Guide getAdjustValue(String name) {
if (name == null || !name.matches("adj([1-9]|10)?")) { if (name == null || !name.matches("adj([1-9]|10)?")) {
throw new IllegalArgumentException("Adjust value '"+name+"' not supported."); logger.log(POILogger.INFO, "Adjust value '"+name+"' not supported. Using default value.");
return null;
} }
name = name.replace("adj", ""); name = name.replace("adj", "");
@ -296,7 +297,13 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
default: throw new RuntimeException(); default: throw new RuntimeException();
} }
// TODO: the adjust values need to be corrected somehow depending on the shape width/height
// see https://social.msdn.microsoft.com/Forums/en-US/3f69ebb3-62a0-4fdd-b367-64790dfb2491/presetshapedefinitionsxml-does-not-specify-width-and-height-form-some-autoshapes?forum=os_binaryfile
// the adjust value can be format dependent, e.g. hexagon has different values,
// other shape types have the same adjust values in OOXML and native
int adjval = getEscherProperty(escherProp, -1); int adjval = getEscherProperty(escherProp, -1);
return (adjval == -1) ? null : new Guide(name, "val "+adjval); return (adjval == -1) ? null : new Guide(name, "val "+adjval);
} }

View File

@ -717,9 +717,25 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
String propNames[] = propName.split(","); String propNames[] = propName.split(",");
for (String pn : propNames) { for (String pn : propNames) {
TextProp prop = props.findByName(pn); TextProp prop = props.findByName(pn);
if (prop != null) return prop; if (prop == null) {
continue;
} }
// Font properties (maybe other too???) can have an index of -1
// so we check the master for this font index then
if (pn.contains("font") && prop.getValue() == -1) {
return getMasterPropVal(props, pn, paragraph);
}
return prop;
}
return getMasterPropVal(props, propName, paragraph);
}
private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) {
String propNames[] = propName.split(",");
BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME);
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0);
if (hardAttribute) return null; if (hardAttribute) return null;

View File

@ -115,6 +115,9 @@ public class HwmfDrawProperties {
} }
public void setViewportOrg(double x, double y) { public void setViewportOrg(double x, double y) {
if (viewport == null) {
viewport = (Rectangle2D)window.clone();
}
double w = viewport.getWidth(); double w = viewport.getWidth();
double h = viewport.getHeight(); double h = viewport.getHeight();
viewport.setRect(x, y, w, h); viewport.setRect(x, y, w, h);

View File

@ -27,7 +27,6 @@ import java.awt.Shape;
import java.awt.TexturePaint; import java.awt.TexturePaint;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.text.AttributedString; import java.text.AttributedString;
@ -35,6 +34,7 @@ import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfBrushStyle;
@ -45,6 +45,9 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry; 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.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawFontManager;
import org.apache.poi.sl.draw.Drawable;
public class HwmfGraphics { public class HwmfGraphics {
private final Graphics2D graphicsCtx; private final Graphics2D graphicsCtx;
@ -65,6 +68,7 @@ public class HwmfGraphics {
this.graphicsCtx = graphicsCtx; this.graphicsCtx = graphicsCtx;
this.bbox = (Rectangle2D)bbox.clone(); this.bbox = (Rectangle2D)bbox.clone();
this.initialAT = graphicsCtx.getTransform(); this.initialAT = graphicsCtx.getTransform();
DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);
} }
public HwmfDrawProperties getProperties() { public HwmfDrawProperties getProperties() {
@ -96,8 +100,8 @@ public class HwmfGraphics {
public void fill(Shape shape) { public void fill(Shape shape) {
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) { if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
GeneralPath gp = new GeneralPath(shape); // GeneralPath gp = new GeneralPath(shape);
gp.setWindingRule(prop.getPolyfillMode().awtFlag); // gp.setWindingRule(prop.getPolyfillMode().awtFlag);
graphicsCtx.setPaint(getFill()); graphicsCtx.setPaint(getFill());
graphicsCtx.fill(shape); graphicsCtx.fill(shape);
} }
@ -111,7 +115,12 @@ public class HwmfGraphics {
if (view == null) { if (view == null) {
view = win; view = win;
} }
float width = (float)(prop.getPenWidth() * view.getWidth() / win.getWidth());
// TODO: fix line width calculation
float width = (float)prop.getPenWidth();
if (width == 0) {
width = 1;
}
HwmfPenStyle ps = prop.getPenStyle(); HwmfPenStyle ps = prop.getPenStyle();
int cap = ps.getLineCap().awtFlag; int cap = ps.getLineCap().awtFlag;
int join = ps.getLineJoin().awtFlag; int join = ps.getLineJoin().awtFlag;
@ -261,6 +270,10 @@ public class HwmfGraphics {
} }
stackIndex = curIdx + index; stackIndex = curIdx + index;
} }
if (stackIndex == -1) {
// roll to last when curIdx == 0
stackIndex = propStack.size()-1;
}
prop = propStack.get(stackIndex); prop = propStack.get(stackIndex);
} }
@ -306,14 +319,72 @@ public class HwmfGraphics {
} }
public void drawString(String text, Rectangle2D bounds) { public void drawString(String text, Rectangle2D bounds) {
drawString(text, bounds, null);
}
public void drawString(String text, Rectangle2D bounds, int dx[]) {
HwmfFont font = prop.getFont(); HwmfFont font = prop.getFont();
if (font == null) { if (font == null || text == null || text.isEmpty()) {
return; return;
} }
double fontH = getFontHeight(font);
// TODO: another approx. ...
double fontW = fontH/1.8;
int len = text.length();
AttributedString as = new AttributedString(text); AttributedString as = new AttributedString(text);
as.addAttribute(TextAttribute.FAMILY, font.getFacename()); if (dx == null || dx.length == 0) {
// TODO: fix font height calculation addAttributes(as, font, 0, len);
as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight())); } else {
for (int i=0; i<len; i++) {
addAttributes(as, font, i, i+1);
// 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
if (i<len-1) {
as.addAttribute(TextAttribute.TRACKING, (dx[i]-fontW)/fontH, i+1, i+2);
}
}
}
double angle = Math.toRadians(-font.getEscapement()/10.);
final AffineTransform at = graphicsCtx.getTransform();
try {
graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH);
graphicsCtx.rotate(angle);
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
// TODO: validate bounds
graphicsCtx.setBackground(prop.getBackgroundColor().getColor());
graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));
}
graphicsCtx.setColor(prop.getTextColor().getColor());
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
} finally {
graphicsCtx.setTransform(at);
}
}
private void addAttributes(AttributedString as, HwmfFont font, int start, int end) {
DrawFontManager fontHandler = (DrawFontManager)graphicsCtx.getRenderingHint(Drawable.FONT_HANDLER);
String fontFamily = null;
@SuppressWarnings("unchecked")
Map<String,String> fontMap = (Map<String,String>)graphicsCtx.getRenderingHint(Drawable.FONT_MAP);
if (fontMap != null && fontMap.containsKey(font.getFacename())) {
fontFamily = fontMap.get(font.getFacename());
}
if (fontHandler != null) {
fontFamily = fontHandler.getRendererableFont(font.getFacename(), font.getPitchAndFamily());
}
if (fontFamily == null) {
fontFamily = font.getFacename();
}
as.addAttribute(TextAttribute.FAMILY, fontFamily);
as.addAttribute(TextAttribute.SIZE, getFontHeight(font));
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());
if (font.isUnderline()) { if (font.isUnderline()) {
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
@ -322,23 +393,20 @@ public class HwmfGraphics {
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
} }
as.addAttribute(TextAttribute.WEIGHT, font.getWeight()); as.addAttribute(TextAttribute.WEIGHT, font.getWeight());
}
double angle = Math.toRadians(-font.getEscapement()/10.); private double getFontHeight(HwmfFont font) {
// see HwmfFont#height for details
double fontHeight = font.getHeight();
final AffineTransform at = graphicsCtx.getTransform(); if (fontHeight == 0) {
try { return 12;
graphicsCtx.translate(bounds.getX(), bounds.getY()); } else if (fontHeight < 0) {
graphicsCtx.rotate(angle); return -fontHeight;
if (prop.getBkMode() == HwmfBkMode.OPAQUE) { } else {
// TODO: validate bounds // TODO: fix font height calculation
graphicsCtx.setBackground(prop.getBackgroundColor().getColor()); // the height is given as font size + ascent + descent
graphicsCtx.fill(bounds); // as an approximation we reduce the height by a static factor
} return fontHeight*3/4;
graphicsCtx.setColor(prop.getTextColor().getColor());
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
} finally {
graphicsCtx.setTransform(at);
} }
} }
} }

View File

@ -0,0 +1,112 @@
/* ====================================================================
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.hwmf.record;
/**
* The BinaryRasterOperation Enumeration section lists the binary raster-operation codes.
* Rasteroperation codes define how metafile processing combines the bits from the selected
* pen with the bits in the destination bitmap.
*
* Each raster-operation code represents a Boolean operation in which the values of the pixels in the
* selected pen and the destination bitmap are combined. Following are the two operands used in these
* operations.
*
* <table>
* <tr><th>Operand</th><th>Meaning</th></tr>
* <tr><td>P</td><td>Selected pen</td></tr>
* <tr><td>D</td><td>Destination bitmap</td></tr>
* </table>
*
* Following are the Boolean operators used in these operations.
* <table>
* <tr><th>Operand</th><th>Meaning</th></tr>
* <tr><td>a</td><td>Bitwise AND</td></tr>
* <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>
* <tr><td>o</td><td>Bitwise OR</td></tr>
* <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>
* </table>
*
* All Boolean operations are presented in reverse Polish notation. For example, the following
* operation replaces the values of the pixels in the destination bitmap with a combination of the pixel
* values of the pen and the selected brush: DPo.
*
* Each raster-operation code is a 32-bit integer whose high-order word is a Boolean operation index and
* whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit
* value that represents all possible outcomes resulting from the Boolean operation on two parameters
* (in this case, the pen and destination values). For example, the operation indexes for the DPo and
* DPan operations are shown in the following list.
*
* <table>
* <tr><th>P</th><th>D</th><th>DPo</th><th>DPan</th></tr>
* <tr><td>0</td><td>0</td><td>0</td><td>1</td></tr>
* <tr><td>0</td><td>1</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>0</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>1</td><td>1</td><td>0</td></tr>
* </table>
*
*/
public enum HwmfBinaryRasterOp {
/** 0, Pixel is always 0 */
R2_BLACK(0x0001),
/** DPon, Pixel is the inverse of the R2_MERGEPEN color. */
R2_NOTMERGEPEN(0x0002),
/** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */
R2_MASKNOTPEN(0x0003),
/** Pn, Pixel is the inverse of the pen color. */
R2_NOTCOPYPEN(0x0004),
/** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */
R2_MASKPENNOT(0x0005),
/** Dn, Pixel is the inverse of the screen color. */
R2_NOT(0x0006),
/** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */
R2_XORPEN(0x0007),
/** DPan, Pixel is the inverse of the R2_MASKPEN color. */
R2_NOTMASKPEN(0x0008),
/** DPa, Pixel is a combination of the colors common to both the pen and the screen. */
R2_MASKPEN(0x0009),
/** DPxn, Pixel is the inverse of the R2_XORPEN color. */
R2_NOTXORPEN(0x000A),
/** D, Pixel remains unchanged. */
R2_NOP(0x000B),
/** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */
R2_MERGENOTPEN(0x000C),
/** P, Pixel is the pen color. */
R2_COPYPEN(0x000D),
/** PDno, Pixel is a combination of the pen color and the inverse of the screen color.*/
R2_MERGEPENNOT(0x000E),
/** DPo, Pixel is a combination of the pen color and the screen color. */
R2_MERGEPEN(0x000F),
/** 1, Pixel is always 1 */
R2_WHITE(0x0010);
int opIndex;
HwmfBinaryRasterOp(int opIndex) {
this.opIndex=opIndex;
}
public static HwmfBinaryRasterOp valueOf(int opIndex) {
for (HwmfBinaryRasterOp bb : HwmfBinaryRasterOp.values()) {
if (bb.opIndex == opIndex) {
return bb;
}
}
return null;
}
}

View File

@ -18,13 +18,11 @@
package org.apache.poi.hwmf.record; package org.apache.poi.hwmf.record;
import java.awt.Color; import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -32,6 +30,8 @@ import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
/** /**
* The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format. * The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format.
@ -188,6 +188,7 @@ public class HwmfBitmapDib {
} }
} }
private static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
private static final int BMP_HEADER_SIZE = 14; private static final int BMP_HEADER_SIZE = 14;
private int headerSize; private int headerSize;
@ -204,8 +205,6 @@ public class HwmfBitmapDib {
private long headerColorUsed = -1; private long headerColorUsed = -1;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private long headerColorImportant = -1; private long headerColorImportant = -1;
@SuppressWarnings("unused")
private Color colorTable[]; private Color colorTable[];
@SuppressWarnings("unused") @SuppressWarnings("unused")
private int colorMaskR=0,colorMaskG=0,colorMaskB=0; private int colorMaskR=0,colorMaskG=0,colorMaskB=0;
@ -360,22 +359,24 @@ public class HwmfBitmapDib {
protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException { protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException {
int size = 0; int size = 0;
List<Color> colorList = new ArrayList<Color>(); colorTable = new Color[count];
for (int i=0; i<count; i++) { for (int i=0; i<count; i++) {
int blue = leis.readUByte(); int blue = leis.readUByte();
int green = leis.readUByte(); int green = leis.readUByte();
int red = leis.readUByte(); int red = leis.readUByte();
@SuppressWarnings("unused") @SuppressWarnings("unused")
int reserved = leis.readUByte(); int reserved = leis.readUByte();
Color c = new Color(red, green, blue); colorTable[i] = new Color(red, green, blue);
colorList.add(c);
size += 4 * LittleEndianConsts.BYTE_SIZE; size += 4 * LittleEndianConsts.BYTE_SIZE;
} }
colorTable = colorList.toArray(new Color[colorList.size()]);
return size; return size;
} }
public InputStream getBMPStream() { public InputStream getBMPStream() {
return new ByteArrayInputStream(getBMPData());
}
private byte[] getBMPData() {
if (imageData == null) { if (imageData == null) {
throw new RecordFormatException("bitmap not initialized ... need to call init() before"); throw new RecordFormatException("bitmap not initialized ... need to call init() before");
} }
@ -398,14 +399,20 @@ public class HwmfBitmapDib {
// fill the "known" image data // fill the "known" image data
System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length); System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length);
return new ByteArrayInputStream(buf); return buf;
} }
public BufferedImage getImage() { public BufferedImage getImage() {
try { try {
return ImageIO.read(getBMPStream()); return ImageIO.read(getBMPStream());
} catch (IOException e) { } catch (IOException e) {
throw new RecordFormatException("invalid bitmap data", e); logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");
BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setPaint(Color.black);
g.fillRect(0, 0, headerWidth, headerHeight);
g.dispose();
return bi;
} }
} }
} }

View File

@ -17,6 +17,7 @@
package org.apache.poi.hwmf.record; package org.apache.poi.hwmf.record;
import java.awt.Color;
import java.awt.Shape; import java.awt.Shape;
import java.awt.geom.Arc2D; import java.awt.geom.Arc2D;
import java.awt.geom.Area; import java.awt.geom.Area;
@ -146,9 +147,10 @@ public class HwmfDraw {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
Path2D p = getShape(); Path2D shape = getShape();
p.closePath(); // shape.closePath();
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); Path2D p = (Path2D)shape.clone();
p.setWindingRule(getWindingRule(ctx));
ctx.fill(p); ctx.fill(p);
} }
@ -170,8 +172,9 @@ public class HwmfDraw {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
Path2D p = getShape(); Path2D shape = getShape();
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); Path2D p = (Path2D)shape.clone();
p.setWindingRule(getWindingRule(ctx));
ctx.draw(p); ctx.draw(p);
} }
} }
@ -320,12 +323,13 @@ public class HwmfDraw {
for (int nPoints : pointsPerPolygon) { for (int nPoints : pointsPerPolygon) {
/** /**
* An array of 16-bit unsigned integers that define the coordinates of the polygons. * An array of 16-bit signed integers that define the coordinates of the polygons.
* (Note: MS-WMF wrongly says unsigned integers ...)
*/ */
Path2D poly = new Path2D.Double(); Path2D poly = new Path2D.Double();
for (int i=0; i<nPoints; i++) { for (int i=0; i<nPoints; i++) {
int x = leis.readUShort(); int x = leis.readShort();
int y = leis.readUShort(); int y = leis.readShort();
size += 2*LittleEndianConsts.SHORT_SIZE; size += 2*LittleEndianConsts.SHORT_SIZE;
if (i == 0) { if (i == 0) {
poly.moveTo(x, y); poly.moveTo(x, y);
@ -342,14 +346,23 @@ public class HwmfDraw {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
int windingRule = ctx.getProperties().getPolyfillMode().awtFlag; if (polyList.isEmpty()) {
Area area = new Area(); return;
}
int windingRule = getWindingRule(ctx);
Area area = null;
for (Path2D poly : polyList) { for (Path2D poly : polyList) {
Path2D p = (Path2D)poly.clone(); Path2D p = (Path2D)poly.clone();
p.setWindingRule(windingRule); p.setWindingRule(windingRule);
area.add(new Area(p)); Area newArea = new Area(p);
if (area == null) {
area = newArea;
} else {
area.exclusiveOr(newArea);
} }
ctx.draw(area); }
ctx.fill(area);
} }
} }
@ -679,4 +692,8 @@ public class HwmfDraw {
ctx.applyObjectTableEntry(objectIndex); ctx.applyObjectTableEntry(objectIndex);
} }
} }
private static int getWindingRule(HwmfGraphics ctx) {
return ctx.getProperties().getPolyfillMode().awtFlag;
}
} }

View File

@ -24,13 +24,155 @@ import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
/**
* The MetafileEscapes specifies printer driver functionality that
* might not be directly accessible through WMF records
*/
public class HwmfEscape implements HwmfRecord { public class HwmfEscape implements HwmfRecord {
public enum EscapeFunction {
/** Notifies the printer driver that the application has finished writing to a page. */
NEWFRAME(0x0001),
/** Stops processing the current document. */
ABORTDOC(0x0002),
/** Notifies the printer driver that the application has finished writing to a band. */
NEXTBAND(0x0003),
/** Sets color table values. */
SETCOLORTABLE(0x0004),
/** Gets color table values. */
GETCOLORTABLE(0x0005),
/** Causes all pending output to be flushed to the output device. */
FLUSHOUT(0x0006),
/** Indicates that the printer driver SHOULD print text only, and no graphics. */
DRAFTMODE(0x0007),
/** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */
QUERYESCSUPPORT(0x0008),
/** Sets the application-defined function that allows a print job to be canceled during printing. */
SETABORTPROC(0x0009),
/** Notifies the printer driver that a new print job is starting. */
STARTDOC(0x000A),
/** Notifies the printer driver that the current print job is ending. */
ENDDOC(0x000B),
/** Retrieves the physical page size currently selected on an output device. */
GETPHYSPAGESIZE(0x000C),
/** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */
GETPRINTINGOFFSET(0x000D),
/** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */
GETSCALINGFACTOR(0x000E),
/** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */
META_ESCAPE_ENHANCED_METAFILE(0x000F),
/** Sets the width of a pen in pixels. */
SETPENWIDTH(0x0010),
/** Sets the number of copies. */
SETCOPYCOUNT(0x0011),
/** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */
SETPAPERSOURCE(0x0012),
/** This record passes through arbitrary data. */
PASSTHROUGH(0x0013),
/** Gets information concerning graphics technology that is supported on a device. */
GETTECHNOLOGY(0x0014),
/** Specifies the line-drawing mode to use in output to a device. */
SETLINECAP(0x0015),
/** Specifies the line-joining mode to use in output to a device. */
SETLINEJOIN(0x0016),
/** Sets the limit for the length of miter joins to use in output to a device. */
SETMITERLIMIT(0x0017),
/** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */
BANDINFO(0x0018),
/** Draws a rectangle with a defined pattern. */
DRAWPATTERNRECT(0x0019),
/** Retrieves the physical pen size currently defined on a device. */
GETVECTORPENSIZE(0x001A),
/** Retrieves the physical brush size currently defined on a device. */
GETVECTORBRUSHSIZE(0x001B),
/** Enables or disables double-sided (duplex) printing on a device. */
ENABLEDUPLEX(0x001C),
/** Retrieves or specifies the source of output forms on a device. */
GETSETPAPERBINS(0x001D),
/** Retrieves or specifies the paper orientation on a device. */
GETSETPRINTORIENT(0x001E),
/** Retrieves information concerning the sources of different forms on an output device. */
ENUMPAPERBINS(0x001F),
/** Specifies the scaling of device-independent bitmaps (DIBs). */
SETDIBSCALING(0x0020),
/** Indicates the start and end of an encapsulated PostScript (EPS) section. */
EPSPRINTING(0x0021),
/** Queries a printer driver for paper dimensions and other forms data. */
ENUMPAPERMETRICS(0x0022),
/** Retrieves or specifies paper dimensions and other forms data on an output device. */
GETSETPAPERMETRICS(0x0023),
/** Sends arbitrary PostScript data to an output device. */
POSTSCRIPT_DATA(0x0025),
/** Notifies an output device to ignore PostScript data. */
POSTSCRIPT_IGNORE(0x0026),
/** Gets the device units currently configured on an output device. */
GETDEVICEUNITS(0x002A),
/** Gets extended text metrics currently configured on an output device. */
GETEXTENDEDTEXTMETRICS(0x0100),
/** Gets the font kern table currently defined on an output device. */
GETPAIRKERNTABLE(0x0102),
/** Draws text using the currently selected font, background color, and text color. */
EXTTEXTOUT(0x0200),
/** Gets the font face name currently configured on a device. */
GETFACENAME(0x0201),
/** Sets the font face name on a device. */
DOWNLOADFACE(0x0202),
/** Queries a printer driver about the support for metafiles on an output device. */
METAFILE_DRIVER(0x0801),
/** Queries the printer driver about its support for DIBs on an output device. */
QUERYDIBSUPPORT(0x0C01),
/** Opens a path. */
BEGIN_PATH(0x1000),
/** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */
CLIP_TO_PATH(0x1001),
/** Ends a path. */
END_PATH(0x1002),
/** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */
OPEN_CHANNEL(0x100E),
/** Instructs the printer driver to download sets of PostScript procedures. */
DOWNLOADHEADER(0x100F),
/** The same as ENDDOC. See OPEN_CHANNEL. */
CLOSE_CHANNEL(0x1010),
/** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */
POSTSCRIPT_PASSTHROUGH(0x1013),
/** Sends arbitrary data directly to the printer driver. */
ENCAPSULATED_POSTSCRIPT(0x1014),
/** Sets the printer driver to either PostScript or GDI mode. */
POSTSCRIPT_IDENTIFY(0x1015),
/** Inserts a block of raw data into a PostScript stream. The input MUST be
a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the
injection point, and a 16-bit quantity specifying the page number, followed by the bytes to
inject. */
POSTSCRIPT_INJECTION(0x1016),
/** Checks whether the printer supports a JPEG image. */
CHECKJPEGFORMAT(0x1017),
/** Checks whether the printer supports a PNG image */
CHECKPNGFORMAT(0x1018),
/** Gets information on a specified feature setting for a PostScript printer driver. */
GET_PS_FEATURESETTING(0x1019),
/** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */
MXDC_ESCAPE(0x101A),
/** Enables applications to include private procedures and other arbitrary data in documents. */
SPCLPASSTHROUGH2(0x11D8);
int flag;
EscapeFunction(int flag) {
this.flag = flag;
}
static EscapeFunction valueOf(int flag) {
for (EscapeFunction hs : values()) {
if (hs.flag == flag) return hs;
}
return null;
}
}
/** /**
* A 16-bit unsigned integer that defines the escape function. The * A 16-bit unsigned integer that defines the escape function. The
* value MUST be from the MetafileEscapes enumeration. * value MUST be from the MetafileEscapes enumeration.
*/ */
private int escapeFunction; private EscapeFunction escapeFunction;
/** /**
* A 16-bit unsigned integer that specifies the size, in bytes, of the * A 16-bit unsigned integer that specifies the size, in bytes, of the
* EscapeData field. * EscapeData field.
@ -48,7 +190,7 @@ public class HwmfEscape implements HwmfRecord {
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
escapeFunction = leis.readUShort(); escapeFunction = EscapeFunction.valueOf(leis.readUShort());
byteCount = leis.readUShort(); byteCount = leis.readUShort();
escapeData = new byte[byteCount]; escapeData = new byte[byteCount];
leis.read(escapeData); leis.read(escapeData);

View File

@ -20,13 +20,9 @@ package org.apache.poi.hwmf.record;
import java.awt.Shape; import java.awt.Shape;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.imageio.ImageIO;
import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfCreateRegion;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
@ -240,7 +236,7 @@ public class HwmfFill {
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort()); polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
return LittleEndianConsts.SHORT_SIZE; return LittleEndianConsts.SHORT_SIZE;
} }
@ -402,6 +398,15 @@ public class HwmfFill {
} }
/** /**
* The META_STRETCHBLT record specifies the transfer of a block of pixels according to a raster
* operation, with possible expansion or contraction.
* The destination of the transfer is the current output region in the playback device context.
* There are two forms of META_STRETCHBLT, one which specifies a bitmap as the source, and the other
* which uses the playback device context as the source. Definitions follow for the fields that are the
* same in the two forms of META_STRETCHBLT are defined below. The subsections that follow specify
* the packet structures of the two forms of META_STRETCHBLT.
* The expansion or contraction is performed according to the stretching mode currently set in the
* playback device context, which MUST be a value from the StretchMode.
*/ */
public static class WmfStretchBlt implements HwmfRecord { public static class WmfStretchBlt implements HwmfRecord {
/** /**
@ -507,7 +512,7 @@ public class HwmfFill {
* The source of the color data is a DIB, and the destination of the transfer is * The source of the color data is a DIB, and the destination of the transfer is
* the current output region in the playback device context. * the current output region in the playback device context.
*/ */
public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord { public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
/** /**
* A 32-bit unsigned integer that defines how the source pixels, the current brush in * A 32-bit unsigned integer that defines how the source pixels, the current brush in
* the playback device context, and the destination pixels are to be combined to * the playback device context, and the destination pixels are to be combined to
@ -599,6 +604,11 @@ public class HwmfFill {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
@ -706,7 +716,7 @@ public class HwmfFill {
* using deviceindependent color data. * using deviceindependent color data.
* The source of the color data is a DIB * The source of the color data is a DIB
*/ */
public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord { public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
/** /**
* A 16-bit unsigned integer that defines whether the Colors field of the * A 16-bit unsigned integer that defines whether the Colors field of the
@ -783,6 +793,11 @@ public class HwmfFill {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
@ -793,7 +808,7 @@ public class HwmfFill {
} }
public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord { public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
/** /**
* A 32-bit unsigned integer that defines how the source pixels, the current brush * A 32-bit unsigned integer that defines how the source pixels, the current brush
@ -877,6 +892,11 @@ public class HwmfFill {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
@ -886,7 +906,7 @@ public class HwmfFill {
} }
} }
public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord { public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
/** /**
* A 32-bit unsigned integer that defines how the source pixels, the current brush * A 32-bit unsigned integer that defines how the source pixels, the current brush
* in the playback device context, and the destination pixels are to be combined to form the * in the playback device context, and the destination pixels are to be combined to form the
@ -978,6 +998,11 @@ public class HwmfFill {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }

View File

@ -448,6 +448,13 @@ public class HwmfFont {
*/ */
WmfFontQuality quality; WmfFontQuality quality;
/**
* A PitchAndFamily object that defines the pitch and the family of the font.
* Font families specify the look of fonts in a general way and are intended for
* specifying fonts when the exact typeface wanted is not available.
*/
int pitchAndFamily;
/** /**
* Font families specify the look of fonts in a general way and are * Font families specify the look of fonts in a general way and are
* intended for specifying fonts when the exact typeface wanted is not available. * intended for specifying fonts when the exact typeface wanted is not available.
@ -480,9 +487,7 @@ public class HwmfFont {
outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());
quality = WmfFontQuality.valueOf(leis.readUByte()); quality = WmfFontQuality.valueOf(leis.readUByte());
int pitchAndFamily = leis.readUByte(); pitchAndFamily = leis.readUByte();
family = WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);
pitch = WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);
byte buf[] = new byte[32], b, readBytes = 0; byte buf[] = new byte[32], b, readBytes = 0;
do { do {
@ -546,12 +551,16 @@ public class HwmfFont {
return quality; return quality;
} }
public int getPitchAndFamily() {
return pitchAndFamily;
}
public WmfFontFamilyClass getFamily() { public WmfFontFamilyClass getFamily() {
return family; return WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);
} }
public WmfFontPitch getPitch() { public WmfFontPitch getPitch() {
return pitch; return WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);
} }
public String getFacename() { public String getFacename() {

View File

@ -109,6 +109,6 @@ public enum HwmfMapMode {
for (HwmfMapMode mm : values()) { for (HwmfMapMode mm : values()) {
if (mm.flag == flag) return mm; if (mm.flag == flag) return mm;
} }
return null; return MM_ISOTROPIC;
} }
} }

View File

@ -265,25 +265,9 @@ public class HwmfMisc {
/** /**
* A 16-bit unsigned integer that defines the foreground binary raster * A 16-bit unsigned integer that defines the foreground binary raster
* operation mixing mode. This MUST be one of the values: * operation mixing mode
* R2_BLACK = 0x0001,
* R2_NOTMERGEPEN = 0x0002,
* R2_MASKNOTPEN = 0x0003,
* R2_NOTCOPYPEN = 0x0004,
* R2_MASKPENNOT = 0x0005,
* R2_NOT = 0x0006,
* R2_XORPEN = 0x0007,
* R2_NOTMASKPEN = 0x0008,
* R2_MASKPEN = 0x0009,
* R2_NOTXORPEN = 0x000A,
* R2_NOP = 0x000B,
* R2_MERGENOTPEN = 0x000C,
* R2_COPYPEN = 0x000D,
* R2_MERGEPENNOT = 0x000E,
* R2_MERGEPEN = 0x000F,
* R2_WHITE = 0x0010
*/ */
private int drawMode; private HwmfBinaryRasterOp drawMode;
@Override @Override
public HwmfRecordType getRecordType() { public HwmfRecordType getRecordType() {
@ -292,7 +276,7 @@ public class HwmfMisc {
@Override @Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
drawMode = leis.readUShort(); drawMode = HwmfBinaryRasterOp.valueOf(leis.readUShort());
return LittleEndianConsts.SHORT_SIZE; return LittleEndianConsts.SHORT_SIZE;
} }

View File

@ -93,7 +93,7 @@ public class HwmfPalette {
} }
} }
public static abstract class WmfPaletteParent implements HwmfRecord { public static abstract class WmfPaletteParent implements HwmfRecord, HwmfObjectTableEntry {
/** /**
* Start (2 bytes): A 16-bit unsigned integer that defines the offset into the Palette Object when * Start (2 bytes): A 16-bit unsigned integer that defines the offset into the Palette Object when
@ -121,6 +121,11 @@ public class HwmfPalette {
return size; return size;
} }
@Override
public final void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
protected List<PaletteEntry> getPaletteCopy() { protected List<PaletteEntry> getPaletteCopy() {
List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>(); List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>();
for (PaletteEntry et : palette) { for (PaletteEntry et : palette) {
@ -143,11 +148,6 @@ public class HwmfPalette {
return HwmfRecordType.createPalette; return HwmfRecordType.createPalette;
} }
@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override @Override
public void applyObject(HwmfGraphics ctx) { public void applyObject(HwmfGraphics ctx) {
ctx.getProperties().setPalette(getPaletteCopy()); ctx.getProperties().setPalette(getPaletteCopy());
@ -165,7 +165,7 @@ public class HwmfPalette {
} }
@Override @Override
public void draw(HwmfGraphics ctx) { public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties(); HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> palette = props.getPalette(); List<PaletteEntry> palette = props.getPalette();
if (palette == null) { if (palette == null) {
@ -192,7 +192,7 @@ public class HwmfPalette {
* The META_RESIZEPALETTE record redefines the size of the logical palette that is defined in the * The META_RESIZEPALETTE record redefines the size of the logical palette that is defined in the
* playback device context. * playback device context.
*/ */
public static class WmfResizePalette implements HwmfRecord { public static class WmfResizePalette implements HwmfRecord, HwmfObjectTableEntry {
/** /**
* A 16-bit unsigned integer that defines the number of entries in * A 16-bit unsigned integer that defines the number of entries in
* the logical palette. * the logical palette.
@ -212,6 +212,11 @@ public class HwmfPalette {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties(); HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> palette = props.getPalette(); List<PaletteEntry> palette = props.getPalette();
if (palette == null) { if (palette == null) {
@ -292,7 +297,7 @@ public class HwmfPalette {
} }
@Override @Override
public void draw(HwmfGraphics ctx) { public void applyObject(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties(); HwmfDrawProperties props = ctx.getProperties();
List<PaletteEntry> dest = props.getPalette(); List<PaletteEntry> dest = props.getPalette();
List<PaletteEntry> src = getPaletteCopy(); List<PaletteEntry> src = getPaletteCopy();

View File

@ -17,6 +17,68 @@
package org.apache.poi.hwmf.record; package org.apache.poi.hwmf.record;
/**
* Each ternary raster operation code represents a Boolean operation in which the values of the pixels in
* the source, the selected brush, and the destination are combined. Following are the three operands
* used in these operations.
*
* <table>
* <tr><th>Operand</th><th>Meaning</th></tr>
* <tr><td>D</td><td>Destination bitmap</td></tr>
* <tr><td>P</td><td>Selected brush (also called pattern)</td></tr>
* <tr><td>S</td><td>Source bitmap</td></tr>
* </table>
*
* Following are the Boolean operators used in these operations.
* <table>
* <tr><th>Operand</th><th>Meaning</th></tr>
* <tr><td>a</td><td>Bitwise AND</td></tr>
* <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>
* <tr><td>o</td><td>Bitwise OR</td></tr>
* <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>
* </table>
*
* All Boolean operations are presented in reverse Polish notation. For example, the following operation
* replaces the values of the pixels in the destination bitmap with a combination of the pixel values of the
* source and brush: PSo.
*
* The following operation combines the values of the pixels in the source and brush with the pixel values
* of the destination bitmap: DPSoo (there are alternative spellings of some functions, so although a
* particular spelling MAY NOT be listed in the enumeration, an equivalent form SHOULD be).
*
* Each raster operation code is a 32-bit integer whose high-order word is a Boolean operation index and
* whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit
* value that represents the result of the Boolean operation on predefined brush, source, and destination
* values. For example, the operation indexes for the PSo and DPSoo operations are shown in the
* following list.
*
* <table>
* <tr><th>P</th><th>S</th><th>D</th><th>DPo</th><th>DPan</th></tr>
* <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
* <tr><td>0</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>
* <tr><td>0</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>
* <tr><td>0</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>0</td><td>1</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>
* <tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
* </table>
*
* The operation indexes are determined by reading the binary values in a column of the table from the
* bottom up. For example, in the PSo column, the binary value is 11111100, which is equivalent to 00FC
* (hexadecimal is implicit for these values), which is the operation index for PSo.
*
* Using this method, DPSoo can be seen to have the operation index 00FE. Operation indexes define the
* locations of corresponding raster operation codes in the preceding enumeration. The PSo operation is
* in line 252 (0x00FC) of the enumeration; DPSoo is in line 254 (0x00FE).
*
* The most commonly used raster operations have been given explicit enumeration names, which
* SHOULD be used; examples are PATCOPY and WHITENESS.
*
* When the source and destination bitmaps are monochrome, a bit value of 0 represents a black pixel
* and a bit value of 1 represents a white pixel. When the source and the destination bitmaps are color,
* those colors are represented with red green blue (RGB) values.
*/
public enum HwmfTernaryRasterOp { public enum HwmfTernaryRasterOp {
BLACKNESS(0x0000,0x0042,"0"), BLACKNESS(0x0000,0x0042,"0"),
DPSOON(0x0001,0x0289,"DPSoon"), DPSOON(0x0001,0x0289,"DPSoon"),

View File

@ -19,7 +19,6 @@ package org.apache.poi.hwmf.record;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
import java.text.AttributedString;
import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.draw.HwmfGraphics;
@ -29,9 +28,12 @@ import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.RecordFormatException; import org.apache.poi.util.RecordFormatException;
public class HwmfText { public class HwmfText {
private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);
/** /**
* 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
@ -185,6 +187,48 @@ public class HwmfText {
*/ */
public static class WmfExtTextOut implements HwmfRecord { public static class WmfExtTextOut implements HwmfRecord {
/**
* Indicates that the background color that is defined in the playback device context
* SHOULD be used to fill the rectangle.
*/
private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);
/**
* Indicates that the text SHOULD be clipped to the rectangle.
*/
private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);
/**
* Indicates that the string to be output SHOULD NOT require further processing
* with respect to the placement of the characters, and an array of character
* placement values SHOULD be provided. This character placement process is
* useful for fonts in which diacritical characters affect character spacing.
*/
private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);
/**
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
* the default left-to-right order. This SHOULD be applied only when the font that is
* defined in the playback device context is either Hebrew or Arabic.
*/
private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);
/**
* Indicates that to display numbers, digits appropriate to the locale SHOULD be used.
*/
private static final BitField ETO_NUMERICSLOCAL = BitFieldFactory.getInstance(0x0400);
/**
* Indicates that to display numbers, European digits SHOULD be used.
*/
private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);
/**
* Indicates that both horizontal and vertical character displacement values
* SHOULD be provided.
*/
private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);
/** /**
* A 16-bit signed integer that defines the y-coordinate, in logical units, where the * A 16-bit signed integer that defines the y-coordinate, in logical units, where the
text string is to be located. text string is to be located.
@ -199,39 +243,11 @@ public class HwmfText {
* A 16-bit signed integer that defines the length of the string. * A 16-bit signed integer that defines the length of the string.
*/ */
private int stringLength; private int stringLength;
/** /**
* A 16-bit unsigned integer that defines the use of the application-defined * A 16-bit unsigned integer that defines the use of the application-defined
* rectangle. This member can be a combination of one or more values in the * rectangle. This member can be a combination of one or more values in the
* ExtTextOutOptions Flags: * ExtTextOutOptions Flags (ETO_*)
*
* ETO_OPAQUE (0x0002):
* Indicates that the background color that is defined in the playback device context
* SHOULD be used to fill the rectangle.
*
* ETO_CLIPPED (0x0004):
* Indicates that the text SHOULD be clipped to the rectangle.
*
* ETO_GLYPH_INDEX (0x0010):
* Indicates that the string to be output SHOULD NOT require further processing
* with respect to the placement of the characters, and an array of character
* placement values SHOULD be provided. This character placement process is
* useful for fonts in which diacritical characters affect character spacing.
*
* ETO_RTLREADING (0x0080):
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
* the default left-to-right order. This SHOULD be applied only when the font that is
* defined in the playback device context is either Hebrew or Arabic. <37>
*
* ETO_NUMERICSLOCAL (0x0400):
* Indicates that to display numbers, digits appropriate to the locale SHOULD be
* used.
*
* ETO_NUMERICSLATIN (0x0800):
* Indicates that to display numbers, European digits SHOULD be used. <39>
*
* ETO_PDY (0x2000):
* Indicates that both horizontal and vertical character displacement values
* SHOULD be provided.
*/ */
private int fwOpts; private int fwOpts;
/** /**
@ -275,7 +291,8 @@ public class HwmfText {
int size = 4*LittleEndianConsts.SHORT_SIZE; int size = 4*LittleEndianConsts.SHORT_SIZE;
if (fwOpts != 0 && size+8<=remainingRecordSize) { // Check if we have a rectangle
if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {
// the bounding rectangle is optional and only read when fwOpts are given // the bounding rectangle is optional and only read when fwOpts are given
left = leis.readShort(); left = leis.readShort();
top = leis.readShort(); top = leis.readShort();
@ -289,16 +306,20 @@ public class HwmfText {
text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252); text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252);
size += buf.length; size += buf.length;
if (size < remainingRecordSize) { if (size >= remainingRecordSize) {
if (size + stringLength*LittleEndianConsts.SHORT_SIZE < remainingRecordSize) { logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info");
throw new RecordFormatException("can't read Dx array - given recordSize doesn't contain enough values for string length "+stringLength); 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");
} }
dx = new int[stringLength]; dx = new int[stringLength];
for (int i=0; i<dx.length; i++) { for (int i=0; i<dxLen; i++) {
dx[i] = leis.readShort(); dx[i] = leis.readShort();
} size += LittleEndianConsts.SHORT_SIZE;
size += dx.length*LittleEndianConsts.SHORT_SIZE;
} }
return size; return size;
@ -306,7 +327,8 @@ public class HwmfText {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);
ctx.drawString(text, bounds, dx);
} }
} }

View File

@ -376,7 +376,7 @@ public class HwmfWindowing {
* The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the * The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the
* specified offsets. * specified offsets.
*/ */
public static class WmfOffsetClipRgn implements HwmfRecord { public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {
/** /**
* A 16-bit signed integer that defines the number of logical units to move up or down. * A 16-bit signed integer that defines the number of logical units to move up or down.
@ -402,7 +402,11 @@ public class HwmfWindowing {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
} }
@ -410,7 +414,7 @@ public class HwmfWindowing {
* The META_EXCLUDECLIPRECT record sets the clipping region in the playback device context to the * The META_EXCLUDECLIPRECT record sets the clipping region in the playback device context to the
* existing clipping region minus the specified rectangle. * existing clipping region minus the specified rectangle.
*/ */
public static class WmfExcludeClipRect implements HwmfRecord { public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {
/** /**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
@ -452,7 +456,11 @@ public class HwmfWindowing {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
} }
@ -461,7 +469,7 @@ public class HwmfWindowing {
* The META_INTERSECTCLIPRECT record sets the clipping region in the playback device context to the * The META_INTERSECTCLIPRECT record sets the clipping region in the playback device context to the
* intersection of the existing clipping region and the specified rectangle. * intersection of the existing clipping region and the specified rectangle.
*/ */
public static class WmfIntersectClipRect implements HwmfRecord { public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {
/** /**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
@ -503,7 +511,11 @@ public class HwmfWindowing {
@Override @Override
public void draw(HwmfGraphics ctx) { public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
} }
} }

View File

@ -19,10 +19,8 @@ package org.apache.poi.hwmf.usermodel;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -38,9 +36,13 @@ import org.apache.poi.hwmf.record.HwmfRecordType;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt; import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt;
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg; import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.Units; import org.apache.poi.util.Units;
public class HwmfPicture { public class HwmfPicture {
private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);
final List<HwmfRecord> records = new ArrayList<HwmfRecord>(); final List<HwmfRecord> records = new ArrayList<HwmfRecord>();
final HwmfPlaceableHeader placeableHeader; final HwmfPlaceableHeader placeableHeader;
final HwmfHeader header; final HwmfHeader header;
@ -52,6 +54,10 @@ public class HwmfPicture {
header = new HwmfHeader(leis); header = new HwmfHeader(leis);
for (;;) { for (;;) {
if (leis.available() < 6) {
logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated");
break;
}
// recordSize in DWORDs // recordSize in DWORDs
long recordSize = leis.readUInt()*2; long recordSize = leis.readUInt()*2;
int recordFunction = leis.readShort(); int recordFunction = leis.readShort();

View File

@ -20,7 +20,10 @@ package org.apache.poi.hslf.model;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.awt.geom.*; import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import org.apache.poi.hslf.usermodel.HSLFFreeformShape; import org.apache.poi.hslf.usermodel.HSLFFreeformShape;
import org.junit.Test; import org.junit.Test;
@ -38,7 +41,7 @@ public final class TestFreeform {
@Test @Test
public void testClosedPath() { public void testClosedPath() {
GeneralPath path1 = new GeneralPath(); Path2D.Double path1 = new Path2D.Double();
path1.moveTo(100, 100); path1.moveTo(100, 100);
path1.lineTo(200, 100); path1.lineTo(200, 100);
path1.lineTo(200, 200); path1.lineTo(200, 200);
@ -55,7 +58,7 @@ public final class TestFreeform {
@Test @Test
public void testLine() { public void testLine() {
GeneralPath path1 = new GeneralPath(new Line2D.Double(100, 100, 200, 100)); Path2D.Double path1 = new Path2D.Double(new Line2D.Double(100, 100, 200, 100));
HSLFFreeformShape p = new HSLFFreeformShape(); HSLFFreeformShape p = new HSLFFreeformShape();
p.setPath(path1); p.setPath(path1);
@ -67,7 +70,7 @@ public final class TestFreeform {
@Test @Test
public void testRectangle() { public void testRectangle() {
GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(100, 100, 200, 50)); Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(100, 100, 200, 50));
HSLFFreeformShape p = new HSLFFreeformShape(); HSLFFreeformShape p = new HSLFFreeformShape();
p.setPath(path1); p.setPath(path1);
@ -84,8 +87,8 @@ public final class TestFreeform {
public void test54188() { public void test54188() {
HSLFFreeformShape p = new HSLFFreeformShape(); HSLFFreeformShape p = new HSLFFreeformShape();
GeneralPath path = p.getPath(); Path2D.Double path = p.getPath();
GeneralPath emptyPath = new GeneralPath(); Path2D.Double emptyPath = new Path2D.Double();
assertEquals(emptyPath.getBounds2D(), path.getBounds2D()); assertEquals(emptyPath.getBounds2D(), path.getBounds2D());
} }
} }

View File

@ -30,23 +30,22 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.BitSet; import java.util.BitSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.hssf.usermodel.DummyGraphics2d; import org.apache.poi.hssf.usermodel.DummyGraphics2d;
import org.apache.poi.sl.draw.Drawable; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.Slide;
import org.apache.poi.sl.usermodel.SlideShow; import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.util.JvmBugs;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -64,7 +63,7 @@ public final class TestPicture {
* *
*/ */
@Test @Test
public void multiplePictures() throws Exception { public void multiplePictures() throws IOException {
HSLFSlideShow ppt = new HSLFSlideShow(); HSLFSlideShow ppt = new HSLFSlideShow();
HSLFSlide s = ppt.createSlide(); HSLFSlide s = ppt.createSlide();
@ -92,6 +91,8 @@ public final class TestPicture {
EscherBSERecord bse3 = pict.getEscherBSERecord(); EscherBSERecord bse3 = pict.getEscherBSERecord();
assertSame(bse2, bse3); assertSame(bse2, bse3);
assertEquals(3, bse1.getRef()); assertEquals(3, bse1.getRef());
ppt.close();
} }
/** /**
@ -99,7 +100,7 @@ public final class TestPicture {
* was not found. The correct behaviour is to return null. * was not found. The correct behaviour is to return null.
*/ */
@Test @Test
public void bug46122() { public void bug46122() throws IOException {
HSLFSlideShow ppt = new HSLFSlideShow(); HSLFSlideShow ppt = new HSLFSlideShow();
HSLFSlide slide = ppt.createSlide(); HSLFSlide slide = ppt.createSlide();
HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG); HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG);
@ -112,10 +113,12 @@ public final class TestPicture {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
pict.draw(graphics); pict.draw(graphics);
ppt.close();
} }
@Test @Test
public void macImages() throws Exception { public void macImages() throws IOException {
HSLFSlideShowImpl hss = new HSLFSlideShowImpl(_slTests.openResourceAsStream("53446.ppt")); HSLFSlideShowImpl hss = new HSLFSlideShowImpl(_slTests.openResourceAsStream("53446.ppt"));
List<HSLFPictureData> pictures = hss.getPictureData(); List<HSLFPictureData> pictures = hss.getPictureData();
@ -154,11 +157,14 @@ public final class TestPicture {
break; break;
} }
} }
hss.close();
} }
@Test @Test
@Ignore("Just for visual validation - antialiasing is different on various systems") @Ignore("Just for visual validation - antialiasing is different on various systems")
public void bug54541() throws Exception { public void bug54541()
throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
String files[] = { String files[] = {
// "sample_pptx_grouping_issues.pptx", // "sample_pptx_grouping_issues.pptx",
// "54542_cropped_bitmap.pptx", // "54542_cropped_bitmap.pptx",
@ -196,7 +202,7 @@ public final class TestPicture {
} else { } else {
BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics(); Graphics2D graphics = img.createGraphics();
fixFonts(graphics); DrawFactory.getInstance(graphics).fixFonts(graphics);
slide.draw(graphics); slide.draw(graphics);
graphics.setColor(Color.BLACK); graphics.setColor(Color.BLACK);
graphics.setStroke(new BasicStroke(1)); graphics.setStroke(new BasicStroke(1));
@ -204,16 +210,8 @@ public final class TestPicture {
ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png")); ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png"));
} }
} }
}
}
@SuppressWarnings("unchecked") ss.close();
private void fixFonts(Graphics2D graphics) { }
if (!JvmBugs.hasLineBreakMeasurerBug()) return;
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
if (fontMap == null) fontMap = new HashMap<String,String>();
fontMap.put("Calibri", "Lucida Sans");
fontMap.put("Cambria", "Lucida Bright");
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
} }
} }

View File

@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
@ -64,7 +65,7 @@ public class TestHwmfParsing {
@Ignore("This is work-in-progress and not a real unit test ...") @Ignore("This is work-in-progress and not a real unit test ...")
public void paint() throws IOException { public void paint() throws IOException {
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf"); File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");
// File f = new File("E:\\project\\poi\\misc\\govdocs-ppt", "000133-0001.wmf"); // File f = new File("bla.wmf");
FileInputStream fis = new FileInputStream(f); FileInputStream fis = new FileInputStream(f);
HwmfPicture wmf = new HwmfPicture(fis); HwmfPicture wmf = new HwmfPicture(fis);
fis.close(); fis.close();
@ -73,6 +74,11 @@ public class TestHwmfParsing {
int width = Units.pointsToPixel(dim.getWidth()); int width = Units.pointsToPixel(dim.getWidth());
// keep aspect ratio for height // keep aspect ratio for height
int height = Units.pointsToPixel(dim.getHeight()); int height = Units.pointsToPixel(dim.getHeight());
double max = Math.max(width, height);
if (max > 1500) {
width *= 1500/max;
height *= 1500/max;
}
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bufImg.createGraphics(); Graphics2D g = bufImg.createGraphics();
@ -81,7 +87,7 @@ public class TestHwmfParsing {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
wmf.draw(g); wmf.draw(g, new Rectangle2D.Double(0,0,width,height));
g.dispose(); g.dispose();

View File

@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.awt.geom.GeneralPath; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.net.URL; import java.net.URL;
import java.util.Enumeration; import java.util.Enumeration;
@ -44,7 +44,7 @@ public class TestPresetGeometries {
} }
}); });
for(Path p : geom){ for(Path p : geom){
GeneralPath path = p.getPath(ctx); Path2D path = p.getPath(ctx);
assertNotNull(path); assertNotNull(path);
} }
} }