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-ffa450edef68tags/REL_3_14_FINAL
@@ -26,10 +26,8 @@ import java.awt.image.BufferedImage; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
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.Shape; | |||
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.TextRun; | |||
import org.apache.poi.sl.usermodel.TextShape; | |||
import org.apache.poi.util.JvmBugs; | |||
public abstract class SlideShowHandler extends POIFSFileHandler { | |||
public void handleSlideShow(SlideShow<?,?> ss) throws IOException { | |||
@@ -55,10 +52,12 @@ public abstract class SlideShowHandler extends POIFSFileHandler { | |||
// read in the writen file | |||
SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray())); | |||
assertNotNull(read); | |||
readContent(read); | |||
try { | |||
assertNotNull(read); | |||
readContent(read); | |||
} finally { | |||
read.close(); | |||
} | |||
} | |||
private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException { | |||
@@ -109,7 +108,7 @@ public abstract class SlideShowHandler extends POIFSFileHandler { | |||
for (Slide<?,?> s : ss.getSlides()) { | |||
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
fixFonts(graphics); | |||
DrawFactory.getInstance(graphics).fixFonts(graphics); | |||
// default rendering options | |||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | |||
@@ -124,15 +123,4 @@ public abstract class SlideShowHandler extends POIFSFileHandler { | |||
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); | |||
} | |||
} |
@@ -43,7 +43,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements | |||
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; | |||
@@ -65,10 +65,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements | |||
} | |||
public int getNumberOfElementsInArray() { | |||
if (emptyComplexPart){ | |||
return 0; | |||
} | |||
return LittleEndian.getUShort(_complexData, 0); | |||
return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0); | |||
} | |||
public void setNumberOfElementsInArray(int numberOfElements) { | |||
@@ -82,7 +79,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements | |||
} | |||
public int getNumberOfElementsInMemory() { | |||
return LittleEndian.getUShort(_complexData, 2); | |||
return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2); | |||
} | |||
public void setNumberOfElementsInMemory(int numberOfElements) { | |||
@@ -96,7 +93,7 @@ public final class EscherArrayProperty extends EscherComplexProperty implements | |||
} | |||
public short getSizeOfElements() { | |||
return LittleEndian.getShort( _complexData, 4 ); | |||
return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 ); | |||
} | |||
public void setSizeOfElements(int sizeOfElements) { |
@@ -25,6 +25,7 @@ import java.awt.geom.Rectangle2D; | |||
import org.apache.poi.sl.usermodel.Background; | |||
import org.apache.poi.sl.usermodel.PlaceableShape; | |||
import org.apache.poi.sl.usermodel.ShapeContainer; | |||
import org.apache.poi.sl.usermodel.Sheet; | |||
public class DrawBackground extends DrawShape { | |||
@@ -47,6 +48,7 @@ public class DrawBackground extends DrawShape { | |||
public void setFlipVertical(boolean flip) {} | |||
public boolean getFlipHorizontal() { return false; } | |||
public boolean getFlipVertical() { return false; } | |||
public Sheet<?,?> getSheet() { return shape.getSheet(); } | |||
}; | |||
DrawFactory drawFact = DrawFactory.getInstance(graphics); | |||
@@ -55,6 +57,7 @@ public class DrawBackground extends DrawShape { | |||
Rectangle2D anchor2 = getAnchor(graphics, anchor); | |||
if(fill != null) { | |||
graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, anchor); | |||
graphics.setPaint(fill); | |||
graphics.fill(anchor2); | |||
} |
@@ -17,11 +17,11 @@ | |||
package org.apache.poi.sl.draw; | |||
import static org.apache.poi.sl.draw.Drawable.DRAW_FACTORY; | |||
import java.awt.Graphics2D; | |||
import java.awt.font.TextLayout; | |||
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.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.TextParagraph; | |||
import org.apache.poi.sl.usermodel.TextShape; | |||
import org.apache.poi.util.JvmBugs; | |||
public class DrawFactory { | |||
protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>(); | |||
@@ -58,7 +59,7 @@ public class DrawFactory { | |||
DrawFactory factory = null; | |||
boolean isHint = false; | |||
if (graphics != null) { | |||
factory = (DrawFactory)graphics.getRenderingHint(DRAW_FACTORY); | |||
factory = (DrawFactory)graphics.getRenderingHint(Drawable.DRAW_FACTORY); | |||
isHint = (factory != null); | |||
} | |||
// secondly try the thread local default | |||
@@ -70,7 +71,7 @@ public class DrawFactory { | |||
factory = new DrawFactory(); | |||
} | |||
if (graphics != null && !isHint) { | |||
graphics.setRenderingHint(DRAW_FACTORY, factory); | |||
graphics.setRenderingHint(Drawable.DRAW_FACTORY, factory); | |||
} | |||
return factory; | |||
} | |||
@@ -166,4 +167,29 @@ public class DrawFactory { | |||
public DrawPaint getPaint(PlaceableShape<?,?> 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]); | |||
} | |||
} | |||
} | |||
} |
@@ -134,8 +134,11 @@ public class DrawPaint { | |||
ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType()); | |||
try { | |||
renderer.loadImage(is, fill.getContentType()); | |||
is.close(); | |||
try { | |||
renderer.loadImage(is, fill.getContentType()); | |||
} finally { | |||
is.close(); | |||
} | |||
} catch (IOException e) { | |||
LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e); | |||
return null; | |||
@@ -274,15 +277,35 @@ public class DrawPaint { | |||
Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2); | |||
p2 = at.transform(p2, null); | |||
snapToAnchor(p1, 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(); | |||
Color[] colors = new Color[fractions.length]; | |||
int i = 0; | |||
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); | |||
} | |||
@@ -52,8 +52,7 @@ public class DrawPictureShape extends DrawSimpleShape { | |||
renderer.loadImage(data.getData(), data.getContentType()); | |||
renderer.drawImage(graphics, anchor, insets); | |||
} catch (IOException e) { | |||
// TODO: draw specific runtime exception? | |||
throw new RuntimeException(e); | |||
LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e); | |||
} | |||
} | |||
@@ -90,17 +90,23 @@ public class DrawShape implements Drawable { | |||
Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D(); | |||
scaleX = anchor.getWidth() == 0. ? 1.0 : anchor.getWidth() / anchor2.getWidth(); | |||
scaleY = anchor.getHeight() == 0. ? 1.0 : anchor.getHeight() / anchor2.getHeight(); | |||
scaleX = safeScale(anchor.getWidth(), anchor2.getWidth()); | |||
scaleY = safeScale(anchor.getHeight(), anchor2.getHeight()); | |||
} else { | |||
quadrant = 0; | |||
} | |||
// transformation is applied reversed ... | |||
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.rotate(Math.toRadians(quadrant*90)); | |||
rot = Math.toRadians(quadrant*90); | |||
if (rot != 0) { | |||
graphics.rotate(rot); | |||
} | |||
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) { | |||
} |
@@ -23,7 +23,7 @@ import java.awt.Graphics2D; | |||
import java.awt.Paint; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Ellipse2D; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
@@ -179,20 +179,20 @@ public class DrawSimpleShape extends DrawShape { | |||
case STEALTH: | |||
case ARROW: | |||
p = new Path(false, true); | |||
GeneralPath arrow = new GeneralPath(); | |||
arrow.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); | |||
Path2D.Double arrow = new Path2D.Double(); | |||
arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2)); | |||
arrow.lineTo(0, 0); | |||
arrow.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); | |||
arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2)); | |||
tailShape = arrow; | |||
at.translate(x2, y2); | |||
at.rotate(alpha); | |||
break; | |||
case TRIANGLE: | |||
p = new Path(); | |||
GeneralPath triangle = new GeneralPath(); | |||
triangle.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); | |||
Path2D.Double triangle = new Path2D.Double(); | |||
triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2)); | |||
triangle.lineTo(0, 0); | |||
triangle.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); | |||
triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2)); | |||
triangle.closePath(); | |||
tailShape = triangle; | |||
at.translate(x2, y2); | |||
@@ -252,20 +252,20 @@ public class DrawSimpleShape extends DrawShape { | |||
case STEALTH: | |||
case ARROW: | |||
p = new Path(false, true); | |||
GeneralPath arrow = new GeneralPath(); | |||
arrow.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); | |||
Path2D.Double arrow = new Path2D.Double(); | |||
arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2)); | |||
arrow.lineTo(0, 0); | |||
arrow.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); | |||
arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2)); | |||
headShape = arrow; | |||
at.translate(x1, y1); | |||
at.rotate(alpha); | |||
break; | |||
case TRIANGLE: | |||
p = new Path(); | |||
GeneralPath triangle = new GeneralPath(); | |||
triangle.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2)); | |||
Path2D.Double triangle = new Path2D.Double(); | |||
triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2)); | |||
triangle.lineTo(0, 0); | |||
triangle.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2)); | |||
triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2)); | |||
triangle.closePath(); | |||
headShape = triangle; | |||
at.translate(x1, y1); |
@@ -38,6 +38,7 @@ import org.apache.poi.sl.usermodel.Insets2D; | |||
import org.apache.poi.sl.usermodel.PaintStyle; | |||
import org.apache.poi.sl.usermodel.PlaceableShape; | |||
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.BulletStyle; | |||
import org.apache.poi.sl.usermodel.TextParagraph.TextAlign; | |||
@@ -465,6 +466,7 @@ public class DrawTextParagraph implements Drawable { | |||
public void setFlipVertical(boolean flip) {} | |||
public boolean getFlipHorizontal() { return false; } | |||
public boolean getFlipVertical() { return false; } | |||
public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); } | |||
}; | |||
return ps; | |||
} | |||
@@ -530,7 +532,7 @@ public class DrawTextParagraph implements Drawable { | |||
attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex)); | |||
} | |||
Hyperlink hl = run.getHyperlink(); | |||
Hyperlink<?,?> hl = run.getHyperlink(); | |||
if (hl != null) { | |||
attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex)); | |||
attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex)); |
@@ -21,11 +21,15 @@ import java.awt.Graphics2D; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Rectangle2D; | |||
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.util.JvmBugs; | |||
import org.apache.poi.sl.usermodel.TextRun; | |||
import org.apache.poi.sl.usermodel.TextShape; | |||
public class DrawTextShape extends DrawSimpleShape { | |||
@@ -35,7 +39,7 @@ public class DrawTextShape extends DrawSimpleShape { | |||
@Override | |||
public void drawContent(Graphics2D graphics) { | |||
fixFonts(graphics); | |||
DrawFactory.getInstance(graphics).fixFonts(graphics); | |||
TextShape<?,?> s = getShape(); | |||
@@ -71,7 +75,7 @@ public class DrawTextShape extends DrawSimpleShape { | |||
} | |||
Double textRot = s.getTextRotation(); | |||
if (textRot != null) { | |||
if (textRot != null && textRot != 0) { | |||
graphics.translate(anchor.getCenterX(), anchor.getCenterY()); | |||
graphics.rotate(Math.toRadians(textRot)); | |||
graphics.translate(-anchor.getCenterX(), -anchor.getCenterY()); | |||
@@ -110,8 +114,9 @@ public class DrawTextShape extends DrawSimpleShape { | |||
double y0 = y; | |||
//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; | |||
for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){ | |||
TextParagraph<?,?,? extends TextRun> p = paragraphs.next(); | |||
@@ -170,23 +175,10 @@ public class DrawTextShape extends DrawSimpleShape { | |||
// dry-run in a 1x1 image and return the vertical advance | |||
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
fixFonts(graphics); | |||
DrawFactory.getInstance(graphics).fixFonts(graphics); | |||
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 | |||
protected TextShape<?,?> getShape() { | |||
return (TextShape<?,?>)shape; |
@@ -87,7 +87,7 @@ class PathGradientPaint implements Paint { | |||
) { | |||
shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE); | |||
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; | |||
@@ -137,14 +137,14 @@ class PathGradientPaint implements Paint { | |||
return childRaster; | |||
} | |||
protected int getGradientSteps(Shape shape) { | |||
Rectangle rect = shape.getBounds(); | |||
protected int getGradientSteps(Shape gradientShape) { | |||
Rectangle rect = gradientShape.getBounds(); | |||
int lower = 1; | |||
int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0); | |||
while (lower < upper-1) { | |||
int mid = lower + (upper - lower) / 2; | |||
BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle); | |||
Area area = new Area(bs.createStrokedShape(shape)); | |||
Area area = new Area(bs.createStrokedShape(gradientShape)); | |||
if (area.isSingular()) { | |||
upper = mid; | |||
} else { |
@@ -42,6 +42,7 @@ import java.awt.geom.Arc2D; | |||
import java.awt.geom.Ellipse2D; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Line2D; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.RoundRectangle2D; | |||
import java.awt.image.BufferedImage; | |||
import java.awt.image.BufferedImageOp; | |||
@@ -243,7 +244,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* @see #setComposite | |||
*/ | |||
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(); | |||
p.setPath(path); | |||
p.setFillColor(null); | |||
@@ -339,7 +340,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* @see #setClip | |||
*/ | |||
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(); | |||
p.setPath(path); | |||
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, | |||
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); | |||
} | |||
@@ -481,7 +482,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* @see java.awt.Graphics#drawOval | |||
*/ | |||
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); | |||
} | |||
@@ -504,7 +505,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
public void fillRoundRect(int x, int y, int width, int height, | |||
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); | |||
} | |||
@@ -544,9 +545,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* relative to the start angle. | |||
* @see java.awt.Graphics#drawArc | |||
*/ | |||
public void fillArc(int x, int y, int width, int height, | |||
int startAngle, int arcAngle){ | |||
Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE); | |||
public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){ | |||
Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE); | |||
fill(arc); | |||
} | |||
@@ -587,9 +587,8 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* relative to the start angle. | |||
* @see java.awt.Graphics#fillArc | |||
*/ | |||
public void drawArc(int x, int y, int width, int height, | |||
int startAngle, int arcAngle) { | |||
Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN); | |||
public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { | |||
Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN); | |||
draw(arc); | |||
} | |||
@@ -636,7 +635,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* @see java.awt.Graphics#fillOval | |||
*/ | |||
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); | |||
} | |||
@@ -932,7 +931,7 @@ public final class SLGraphics extends Graphics2D implements Cloneable { | |||
* @param y2 the second point's <i>y</i> coordinate. | |||
*/ | |||
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); | |||
} | |||
@@ -19,12 +19,12 @@ | |||
package org.apache.poi.sl.draw.geom; | |||
import org.apache.poi.sl.draw.binding.CTPath2DArcTo; | |||
import java.awt.geom.Arc2D; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Point2D; | |||
import org.apache.poi.sl.draw.binding.CTPath2DArcTo; | |||
/** | |||
* ArcTo command within a shape path in DrawingML: | |||
* | |||
@@ -48,7 +48,7 @@ public class ArcToCommand implements PathCommand { | |||
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 ry = ctx.getValue(hr); | |||
double start = ctx.getValue(stAng) / 60000; |
@@ -19,7 +19,7 @@ | |||
package org.apache.poi.sl.draw.geom; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
/** | |||
* Date: 10/25/11 | |||
@@ -31,7 +31,7 @@ public class ClosePathCommand implements PathCommand { | |||
ClosePathCommand(){ | |||
} | |||
public void execute(GeneralPath path, Context ctx){ | |||
public void execute(Path2D.Double path, Context ctx){ | |||
path.closePath(); | |||
} | |||
} |
@@ -19,9 +19,9 @@ | |||
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 | |||
@@ -40,13 +40,13 @@ public class CurveToCommand implements PathCommand { | |||
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 y1 = ctx.getValue(arg2); | |||
double x2 = ctx.getValue(arg3); | |||
double y2 = ctx.getValue(arg4); | |||
double x3 = ctx.getValue(arg5); | |||
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); | |||
} | |||
} |
@@ -19,9 +19,9 @@ | |||
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 | |||
@@ -41,9 +41,9 @@ public class LineToCommand implements PathCommand { | |||
arg2 = s2; | |||
} | |||
public void execute(GeneralPath path, Context ctx){ | |||
public void execute(Path2D.Double path, Context ctx){ | |||
double x = ctx.getValue(arg1); | |||
double y = ctx.getValue(arg2); | |||
path.lineTo((float)x, (float)y); | |||
path.lineTo(x, y); | |||
} | |||
} |
@@ -19,9 +19,9 @@ | |||
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 | |||
@@ -41,9 +41,9 @@ public class MoveToCommand implements PathCommand { | |||
arg2 = s2; | |||
} | |||
public void execute(GeneralPath path, Context ctx){ | |||
public void execute(Path2D.Double path, Context ctx){ | |||
double x = ctx.getValue(arg1); | |||
double y = ctx.getValue(arg2); | |||
path.moveTo((float)x, (float)y); | |||
path.moveTo(x, y); | |||
} | |||
} |
@@ -19,11 +19,19 @@ | |||
package org.apache.poi.sl.draw.geom; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.util.ArrayList; | |||
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 | |||
@@ -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) { | |||
GeneralPath path = new GeneralPath(); | |||
public Path2D.Double getPath(Context ctx) { | |||
Path2D.Double path = new Path2D.Double(); | |||
for(PathCommand cmd : commands) | |||
cmd.execute(path, ctx); | |||
return path; |
@@ -19,7 +19,7 @@ | |||
package org.apache.poi.sl.draw.geom; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
/** | |||
* A path command in DrawingML. One of: | |||
@@ -41,5 +41,5 @@ public interface PathCommand { | |||
* @param path the path to append the result to | |||
* @param ctx the context to lookup variables | |||
*/ | |||
void execute(GeneralPath path, Context ctx); | |||
void execute(Path2D.Double path, Context ctx); | |||
} |
@@ -19,9 +19,9 @@ | |||
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 | |||
@@ -38,11 +38,11 @@ public class QuadToCommand implements PathCommand { | |||
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 y1 = ctx.getValue(arg2); | |||
double x2 = ctx.getValue(arg3); | |||
double y2 = ctx.getValue(arg4); | |||
path.quadTo((float)x1, (float)y1, (float)x2, (float)y2); | |||
path.quadTo(x1, y1, x2, y2); | |||
} | |||
} |
@@ -17,7 +17,7 @@ | |||
package org.apache.poi.sl.usermodel; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
public interface FreeformShape< | |||
S extends Shape<S,P>, | |||
@@ -33,7 +33,7 @@ public interface FreeformShape< | |||
* | |||
* @return the path | |||
*/ | |||
GeneralPath getPath(); | |||
Path2D.Double getPath(); | |||
/** | |||
* Set the shape path | |||
@@ -41,5 +41,5 @@ public interface FreeformShape< | |||
* @param path shape outline | |||
* @return the number of points written | |||
*/ | |||
int setPath(GeneralPath path); | |||
int setPath(Path2D.Double path); | |||
} |
@@ -25,6 +25,11 @@ public interface PlaceableShape< | |||
> { | |||
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. | |||
* The coordinates are expressed in points |
@@ -25,8 +25,7 @@ public interface Shape< | |||
> { | |||
ShapeContainer<S,P> getParent(); | |||
/** | |||
* | |||
/** | |||
* @return the sheet this shape belongs to | |||
*/ | |||
Sheet<S,P> getSheet(); |
@@ -20,7 +20,7 @@ | |||
package org.apache.poi.xslf.usermodel; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.PathIterator; | |||
import java.awt.geom.Rectangle2D; | |||
@@ -57,7 +57,7 @@ public class XSLFFreeformShape extends XSLFAutoShape | |||
} | |||
@Override | |||
public int setPath(GeneralPath path) { | |||
public int setPath(Path2D.Double path) { | |||
CTPath2D ctPath = CTPath2D.Factory.newInstance(); | |||
Rectangle2D bounds = path.getBounds2D(); | |||
@@ -119,8 +119,8 @@ public class XSLFFreeformShape extends XSLFAutoShape | |||
} | |||
@Override | |||
public GeneralPath getPath() { | |||
GeneralPath path = new GeneralPath(); | |||
public Path2D.Double getPath() { | |||
Path2D.Double path = new Path2D.Double(); | |||
Rectangle2D bounds = getAnchor(); | |||
CTCustomGeometry2D geom = getSpPr().getCustGeom(); | |||
@@ -168,7 +168,7 @@ public class XSLFFreeformShape extends XSLFAutoShape | |||
// The returned path should fit in the bounding rectangle | |||
AffineTransform at = new AffineTransform(); | |||
at.translate(bounds.getX(), bounds.getY()); | |||
return new GeneralPath(at.createTransformedShape(path)); | |||
return new Path2D.Double(at.createTransformedShape(path)); | |||
} | |||
/** | |||
* @param shapeId 1-based shapeId |
@@ -48,11 +48,11 @@ public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> { | |||
@Override | |||
public String getAddress() { | |||
if (!_link.isSetId()) { | |||
String id = _link.getId(); | |||
if (id == null || "".equals(id)) { | |||
return _link.getAction(); | |||
} | |||
String id = _link.getId(); | |||
URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI(); | |||
return targetURI.toASCIIString(); |
@@ -455,8 +455,15 @@ public class XSLFTextRun implements TextRun { | |||
@Override | |||
public XSLFHyperlink getHyperlink(){ | |||
if(!_r.getRPr().isSetHlinkClick()) return null; | |||
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet()); | |||
CTTextCharacterProperties rPr = _r.getRPr(); | |||
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){ |
@@ -24,18 +24,17 @@ import java.awt.Graphics2D; | |||
import java.awt.RenderingHints; | |||
import java.awt.image.BufferedImage; | |||
import java.io.File; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.TreeSet; | |||
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.SlideShow; | |||
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 | |||
@@ -65,7 +64,7 @@ public class PPTX2PNG { | |||
return; | |||
} | |||
int slidenum = -1; | |||
String slidenumStr = "-1"; | |||
float scale = 1; | |||
File file = null; | |||
String format = "png"; | |||
@@ -77,7 +76,7 @@ public class PPTX2PNG { | |||
if ("-scale".equals(args[i])) { | |||
scale = Float.parseFloat(args[++i]); | |||
} else if ("-slide".equals(args[i])) { | |||
slidenum = Integer.parseInt(args[++i]); | |||
slidenumStr = args[++i]; | |||
} else if ("-format".equals(args[i])) { | |||
format = args[++i]; | |||
} else if ("-outdir".equals(args[i])) { | |||
@@ -120,9 +119,11 @@ public class PPTX2PNG { | |||
SlideShow<?,?> ss = SlideShowFactory.create(file, null, true); | |||
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); | |||
ss.close(); | |||
return; | |||
} | |||
@@ -130,39 +131,36 @@ public class PPTX2PNG { | |||
int width = (int) (pgsize.width * scale); | |||
int height = (int) (pgsize.height * scale); | |||
int slideNo=1; | |||
for(Slide<?,?> slide : slides) { | |||
if (slidenum == -1 || slideNo == slidenum) { | |||
String title = slide.getTitle(); | |||
if (!quiet) { | |||
System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title)); | |||
} | |||
for(Integer slideNo : slidenum) { | |||
Slide<?,?> slide = slides.get(slideNo); | |||
String title = slide.getTitle(); | |||
if (!quiet) { | |||
System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title)); | |||
} | |||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
fixFonts(graphics); | |||
// default rendering options | |||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | |||
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); | |||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); | |||
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); | |||
graphics.scale(scale, scale); | |||
// draw stuff | |||
slide.draw(graphics); | |||
// save the result | |||
if (!"null".equals(format)) { | |||
String outname = file.getName().replaceFirst(".pptx?", ""); | |||
outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format); | |||
File outfile = new File(outdir, outname); | |||
ImageIO.write(img, format, outfile); | |||
} | |||
} | |||
slideNo++; | |||
} | |||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
DrawFactory.getInstance(graphics).fixFonts(graphics); | |||
// default rendering options | |||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | |||
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); | |||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); | |||
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); | |||
graphics.scale(scale, scale); | |||
// draw stuff | |||
slide.draw(graphics); | |||
// save the result | |||
if (!"null".equals(format)) { | |||
String outname = file.getName().replaceFirst(".pptx?", ""); | |||
outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format); | |||
File outfile = new File(outdir, outname); | |||
ImageIO.write(img, format, outfile); | |||
} | |||
} | |||
if (!quiet) { | |||
System.out.println("Done"); | |||
@@ -170,14 +168,43 @@ public class PPTX2PNG { | |||
ss.close(); | |||
} | |||
@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); | |||
private static Set<Integer> slideIndexes(final int slideCount, String range) { | |||
Set<Integer> slideIdx = new TreeSet<Integer>(); | |||
if ("-1".equals(range)) { | |||
for (int i=0; i<slideCount; i++) { | |||
slideIdx.add(i); | |||
} | |||
} else { | |||
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; | |||
} | |||
} |
@@ -36,17 +36,28 @@ public class TestPPTX2PNG { | |||
public void render() throws Exception { | |||
POIDataSamples samples = POIDataSamples.getSlideShowInstance(); | |||
String[] testFiles = {"alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",}; | |||
// String[] testFiles = {"41246-2.ppt","45543.ppt","53446.ppt","ParagraphStylesShorterThanCharStyles.ppt"}; | |||
// File testFilesX[] = new File("tmp_ppt").listFiles(new FileFilter() { | |||
// 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 = { | |||
"-format", "null", // png,gif,jpg or null for test | |||
"-slide", "-1", // -1 for all | |||
"-outdir", new File("build/tmp/").getCanonicalPath(), | |||
"-quite", | |||
"-quiet", | |||
"dummyfile" | |||
}; | |||
for(String sampleFile : testFiles){ | |||
args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath(); | |||
// args[args.length-1] = new File(sampleFile).getCanonicalPath(); | |||
try { | |||
PPTX2PNG.main(args); | |||
} catch (IllegalStateException e) { |
@@ -19,8 +19,9 @@ package org.apache.poi.xslf.usermodel; | |||
import static org.junit.Assert.assertEquals; | |||
import java.awt.geom.Ellipse2D; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import org.junit.Test; | |||
@@ -30,16 +31,16 @@ import org.junit.Test; | |||
public class TestXSLFFreeformShape { | |||
@Test | |||
public void testSetPath() { | |||
public void testSetPath() throws IOException { | |||
XMLSlideShow ppt = new XMLSlideShow(); | |||
XSLFSlide slide = ppt.createSlide(); | |||
XSLFFreeformShape shape1 = slide.createFreeform(); | |||
// 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); | |||
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() ? | |||
// one way is to create another XSLFFreeformShape from path2 and compare the resulting xml | |||
@@ -49,5 +50,7 @@ public class TestXSLFFreeformShape { | |||
shape2.setPath(path2); | |||
assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString()); | |||
ppt.close(); | |||
} | |||
} |
@@ -42,6 +42,7 @@ import java.awt.geom.Arc2D; | |||
import java.awt.geom.Ellipse2D; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Line2D; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.RoundRectangle2D; | |||
import java.awt.image.BufferedImage; | |||
import java.awt.image.BufferedImageOp; | |||
@@ -244,7 +245,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { | |||
* @see #setComposite | |||
*/ | |||
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); | |||
p.setPath(path); | |||
p.getFill().setForegroundColor(null); | |||
@@ -346,7 +347,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { | |||
* @see #setClip | |||
*/ | |||
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); | |||
p.setPath(path); | |||
applyPaint(p); |
@@ -20,6 +20,7 @@ import java.io.IOException; | |||
import java.io.OutputStream; | |||
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. | |||
@@ -59,11 +60,12 @@ public class InteractiveInfo extends RecordContainer { | |||
*/ | |||
private void findInterestingChildren() { | |||
// First child should be the InteractiveInfoAtom | |||
if(_children[0] instanceof InteractiveInfoAtom) { | |||
infoAtom = (InteractiveInfoAtom)_children[0]; | |||
} else { | |||
throw new IllegalStateException("First child record wasn't a InteractiveInfoAtom, was of type " + _children[0].getRecordType()); | |||
} | |||
if (_children == null || _children.length == 0 || !(_children[0] instanceof InteractiveInfoAtom)) { | |||
logger.log(POILogger.WARN, "First child record wasn't a InteractiveInfoAtom - leaving this atom in an invalid state..."); | |||
return; | |||
} | |||
infoAtom = (InteractiveInfoAtom)_children[0]; | |||
} | |||
/** |
@@ -168,7 +168,7 @@ public abstract class Record | |||
try { | |||
c = RecordTypes.forTypeID((short)type).handlingClass; | |||
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 | |||
// type but there's no support for it. Explicitly request | |||
// that now |
@@ -23,23 +23,28 @@ import java.io.InputStream; | |||
import java.util.List; | |||
import org.apache.poi.ddf.AbstractEscherOptRecord; | |||
import org.apache.poi.ddf.EscherArrayProperty; | |||
import org.apache.poi.ddf.EscherBSERecord; | |||
import org.apache.poi.ddf.EscherColorRef; | |||
import org.apache.poi.ddf.EscherContainerRecord; | |||
import org.apache.poi.ddf.EscherProperties; | |||
import org.apache.poi.ddf.EscherRecord; | |||
import org.apache.poi.ddf.EscherSimpleProperty; | |||
import org.apache.poi.hslf.record.Document; | |||
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.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.util.LittleEndian; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
import org.apache.poi.util.Units; | |||
/** | |||
* Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. | |||
* | |||
* @author Yegor Kozlov | |||
*/ | |||
public final class HSLFFill { | |||
// For logging | |||
@@ -118,36 +123,110 @@ public final class HSLFFill { | |||
public FillStyle getFillStyle() { | |||
return new FillStyle() { | |||
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: | |||
return DrawPaint.createSolidPaint(getForegroundColor()); | |||
case FILL_PICTURE: { | |||
final HSLFPictureData pd = getPictureData(); | |||
if (pd == null) break; | |||
return new TexturePaint() { | |||
public InputStream getImageData() { | |||
return new ByteArrayInputStream(pd.getData()); | |||
} | |||
public String getContentType() { | |||
return pd.getContentType(); | |||
} | |||
public int getAlpha() { | |||
return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0); | |||
} | |||
}; | |||
} | |||
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: " + getFillType()); | |||
break; | |||
logger.log(POILogger.WARN, "unsuported fill type: " + fillType); | |||
return null; | |||
} | |||
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(); | |||
if (pd == null) { | |||
return null; | |||
} | |||
return new TexturePaint() { | |||
public InputStream getImageData() { | |||
return new ByteArrayInputStream(pd.getData()); | |||
} | |||
public String getContentType() { | |||
return pd.getContentType(); | |||
} | |||
public int getAlpha() { | |||
return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0); | |||
} | |||
}; | |||
} | |||
/** | |||
* Returns fill type. | |||
* Must be one of the <code>FILL_*</code> constants defined in this class. | |||
@@ -172,6 +251,7 @@ public final class HSLFFill { | |||
} | |||
} | |||
@SuppressWarnings("resource") | |||
protected EscherBSERecord getEscherBSERecord(int idx){ | |||
HSLFSheet sheet = shape.getSheet(); | |||
if(sheet == null) { | |||
@@ -258,6 +338,7 @@ public final class HSLFFill { | |||
/** | |||
* <code>PictureData</code> object used in a texture, pattern of picture fill. | |||
*/ | |||
@SuppressWarnings("resource") | |||
public HSLFPictureData getPictureData(){ | |||
AbstractEscherOptRecord opt = shape.getEscherOptRecord(); | |||
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE); |
@@ -18,22 +18,25 @@ | |||
package org.apache.poi.hslf.usermodel; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.PathIterator; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import org.apache.poi.ddf.AbstractEscherOptRecord; | |||
import org.apache.poi.ddf.EscherArrayProperty; | |||
import org.apache.poi.ddf.EscherContainerRecord; | |||
import org.apache.poi.ddf.EscherProperties; | |||
import org.apache.poi.ddf.EscherProperty; | |||
import org.apache.poi.ddf.EscherSimpleProperty; | |||
import org.apache.poi.sl.usermodel.FreeformShape; | |||
import org.apache.poi.sl.usermodel.ShapeContainer; | |||
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.POILogger; | |||
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_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. | |||
* | |||
@@ -88,7 +170,7 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh | |||
} | |||
@Override | |||
public int setPath(GeneralPath path) { | |||
public int setPath(Path2D.Double path) { | |||
Rectangle2D bounds = path.getBounds2D(); | |||
PathIterator it = path.getPathIterator(new AffineTransform()); | |||
@@ -174,23 +256,19 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh | |||
opt.sortProperties(); | |||
setAnchor(bounds); | |||
return numPoints; | |||
} | |||
@Override | |||
public GeneralPath getPath(){ | |||
public Path2D.Double getPath(){ | |||
AbstractEscherOptRecord opt = getEscherOptRecord(); | |||
opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__SHAPEPATH, 0x4)); | |||
EscherArrayProperty verticesProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES + 0x4000)); | |||
if(verticesProp == null) verticesProp = getEscherProperty(opt, EscherProperties.GEOMETRY__VERTICES); | |||
EscherArrayProperty segmentsProp = getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO + 0x4000)); | |||
if(segmentsProp == null) segmentsProp = getEscherProperty(opt, EscherProperties.GEOMETRY__SEGMENTINFO); | |||
EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES); | |||
EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO); | |||
// 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 | |||
if(verticesProp == null) { | |||
@@ -202,46 +280,60 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh | |||
return path; | |||
} | |||
int numPoints = verticesProp.getNumberOfElementsInArray(); | |||
int numSegments = segmentsProp.getNumberOfElementsInArray(); | |||
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)){ | |||
i++; | |||
byte[] pnext = segmentsProp.getElement(i); | |||
if(Arrays.equals(pnext, SEGMENTINFO_ESCAPE)){ | |||
if(j + 1 < numPoints){ | |||
byte[] p = verticesProp.getElement(j++); | |||
short x = LittleEndian.getShort(p, 0); | |||
short y = LittleEndian.getShort(p, 2); | |||
path.lineTo(Units.masterToPoints(x), Units.masterToPoints(y)); | |||
Iterator<byte[]> vertIter = verticesProp.iterator(); | |||
Iterator<byte[]> segIter = segmentsProp.iterator(); | |||
byte segPushBack[] = null; | |||
while (vertIter.hasNext() && segIter.hasNext()) { | |||
byte[] segElem = (segPushBack != null) ? segPushBack : segIter.next(); | |||
segPushBack = null; | |||
PathInfo pi = getPathInfo(segElem); | |||
switch (pi) { | |||
case escape: { | |||
handleEscapeInfo(path, segElem, vertIter); | |||
break; | |||
} | |||
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); | |||
} | |||
} else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){ | |||
break; | |||
case close: | |||
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 bounds = path.getBounds2D(); | |||
@@ -251,6 +343,81 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh | |||
anchor.getWidth()/bounds.getWidth(), | |||
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); | |||
} | |||
} |
@@ -363,6 +363,9 @@ public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagrap | |||
InteractiveInfo hldr = (InteractiveInfo)r; | |||
InteractiveInfoAtom info = hldr.getInteractiveInfoAtom(); | |||
if (info == null) { | |||
continue; | |||
} | |||
int id = info.getHyperlinkID(); | |||
ExHyperlink exHyper = exobj.get(id); | |||
if (exHyper == null) { |
@@ -209,6 +209,13 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS | |||
? null | |||
: 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 |
@@ -346,7 +346,13 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> { | |||
int val = (p == null) ? defaultColor : p.getPropertyValue(); | |||
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 fPaletteRGB = ecr.hasPaletteRGBFlag(); | |||
boolean fSystemRGB = ecr.hasSystemRGBFlag(); | |||
@@ -373,11 +379,10 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> { | |||
} else if (fSysIndex){ | |||
//TODO | |||
} | |||
double alpha = getAlpha(opacityProperty); | |||
return new Color(rgb[0], rgb[1], rgb[2], (int)(alpha*255.0)); | |||
return new Color(rgb[0], rgb[1], rgb[2]); | |||
} | |||
double getAlpha(short opacityProperty) { | |||
AbstractEscherOptRecord opt = getEscherOptRecord(); | |||
EscherSimpleProperty op = getEscherProperty(opt, opacityProperty); |
@@ -111,8 +111,15 @@ public final class HSLFShapeFactory { | |||
shape = createNonPrimitive(spContainer, parent); | |||
break; | |||
default: | |||
EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID); | |||
if (parent instanceof HSLFTable && etr != null) { | |||
if (parent instanceof HSLFTable) { | |||
EscherTextboxRecord etr = spContainer.getChildById(EscherTextboxRecord.RECORD_ID); | |||
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); | |||
} else { | |||
shape = new HSLFAutoShape(spContainer, parent); |
@@ -275,7 +275,8 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H | |||
public Guide getAdjustValue(String name) { | |||
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", ""); | |||
@@ -296,7 +297,13 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H | |||
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); | |||
return (adjval == -1) ? null : new Guide(name, "val "+adjval); | |||
} | |||
@@ -717,9 +717,25 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText | |||
String propNames[] = propName.split(","); | |||
for (String pn : propNames) { | |||
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); | |||
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); | |||
if (hardAttribute) return null; |
@@ -115,6 +115,9 @@ public class HwmfDrawProperties { | |||
} | |||
public void setViewportOrg(double x, double y) { | |||
if (viewport == null) { | |||
viewport = (Rectangle2D)window.clone(); | |||
} | |||
double w = viewport.getWidth(); | |||
double h = viewport.getHeight(); | |||
viewport.setRect(x, y, w, h); |
@@ -27,7 +27,6 @@ import java.awt.Shape; | |||
import java.awt.TexturePaint; | |||
import java.awt.font.TextAttribute; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.image.BufferedImage; | |||
import java.text.AttributedString; | |||
@@ -35,6 +34,7 @@ import java.util.ArrayList; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.ListIterator; | |||
import java.util.Map; | |||
import java.util.NoSuchElementException; | |||
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.HwmfPenStyle; | |||
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 { | |||
private final Graphics2D graphicsCtx; | |||
@@ -65,6 +68,7 @@ public class HwmfGraphics { | |||
this.graphicsCtx = graphicsCtx; | |||
this.bbox = (Rectangle2D)bbox.clone(); | |||
this.initialAT = graphicsCtx.getTransform(); | |||
DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx); | |||
} | |||
public HwmfDrawProperties getProperties() { | |||
@@ -96,8 +100,8 @@ public class HwmfGraphics { | |||
public void fill(Shape shape) { | |||
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) { | |||
GeneralPath gp = new GeneralPath(shape); | |||
gp.setWindingRule(prop.getPolyfillMode().awtFlag); | |||
// GeneralPath gp = new GeneralPath(shape); | |||
// gp.setWindingRule(prop.getPolyfillMode().awtFlag); | |||
graphicsCtx.setPaint(getFill()); | |||
graphicsCtx.fill(shape); | |||
} | |||
@@ -111,7 +115,12 @@ public class HwmfGraphics { | |||
if (view == null) { | |||
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(); | |||
int cap = ps.getLineCap().awtFlag; | |||
int join = ps.getLineJoin().awtFlag; | |||
@@ -261,6 +270,10 @@ public class HwmfGraphics { | |||
} | |||
stackIndex = curIdx + index; | |||
} | |||
if (stackIndex == -1) { | |||
// roll to last when curIdx == 0 | |||
stackIndex = propStack.size()-1; | |||
} | |||
prop = propStack.get(stackIndex); | |||
} | |||
@@ -306,34 +319,47 @@ public class HwmfGraphics { | |||
} | |||
public void drawString(String text, Rectangle2D bounds) { | |||
drawString(text, bounds, null); | |||
} | |||
public void drawString(String text, Rectangle2D bounds, int dx[]) { | |||
HwmfFont font = prop.getFont(); | |||
if (font == null) { | |||
if (font == null || text == null || text.isEmpty()) { | |||
return; | |||
} | |||
double fontH = getFontHeight(font); | |||
// TODO: another approx. ... | |||
double fontW = fontH/1.8; | |||
int len = text.length(); | |||
AttributedString as = new AttributedString(text); | |||
as.addAttribute(TextAttribute.FAMILY, font.getFacename()); | |||
// TODO: fix font height calculation | |||
as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight())); | |||
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); | |||
if (font.isUnderline()) { | |||
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); | |||
} | |||
if (font.isItalic()) { | |||
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); | |||
if (dx == null || dx.length == 0) { | |||
addAttributes(as, font, 0, len); | |||
} 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); | |||
} | |||
} | |||
} | |||
as.addAttribute(TextAttribute.WEIGHT, font.getWeight()); | |||
double angle = Math.toRadians(-font.getEscapement()/10.); | |||
final AffineTransform at = graphicsCtx.getTransform(); | |||
try { | |||
graphicsCtx.translate(bounds.getX(), bounds.getY()); | |||
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(bounds); | |||
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()); | |||
@@ -341,4 +367,46 @@ public class HwmfGraphics { | |||
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()); | |||
if (font.isUnderline()) { | |||
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); | |||
} | |||
if (font.isItalic()) { | |||
as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); | |||
} | |||
as.addAttribute(TextAttribute.WEIGHT, font.getWeight()); | |||
} | |||
private double getFontHeight(HwmfFont font) { | |||
// see HwmfFont#height for details | |||
double fontHeight = font.getHeight(); | |||
if (fontHeight == 0) { | |||
return 12; | |||
} else if (fontHeight < 0) { | |||
return -fontHeight; | |||
} else { | |||
// TODO: fix font height calculation | |||
// the height is given as font size + ascent + descent | |||
// as an approximation we reduce the height by a static factor | |||
return fontHeight*3/4; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -18,13 +18,11 @@ | |||
package org.apache.poi.hwmf.record; | |||
import java.awt.Color; | |||
import java.awt.Graphics2D; | |||
import java.awt.image.BufferedImage; | |||
import java.io.BufferedInputStream; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
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.LittleEndianConsts; | |||
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. | |||
@@ -188,6 +188,7 @@ public class HwmfBitmapDib { | |||
} | |||
} | |||
private static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class); | |||
private static final int BMP_HEADER_SIZE = 14; | |||
private int headerSize; | |||
@@ -204,8 +205,6 @@ public class HwmfBitmapDib { | |||
private long headerColorUsed = -1; | |||
@SuppressWarnings("unused") | |||
private long headerColorImportant = -1; | |||
@SuppressWarnings("unused") | |||
private Color colorTable[]; | |||
@SuppressWarnings("unused") | |||
private int colorMaskR=0,colorMaskG=0,colorMaskB=0; | |||
@@ -360,22 +359,24 @@ public class HwmfBitmapDib { | |||
protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException { | |||
int size = 0; | |||
List<Color> colorList = new ArrayList<Color>(); | |||
colorTable = new Color[count]; | |||
for (int i=0; i<count; i++) { | |||
int blue = leis.readUByte(); | |||
int green = leis.readUByte(); | |||
int red = leis.readUByte(); | |||
@SuppressWarnings("unused") | |||
int reserved = leis.readUByte(); | |||
Color c = new Color(red, green, blue); | |||
colorList.add(c); | |||
colorTable[i] = new Color(red, green, blue); | |||
size += 4 * LittleEndianConsts.BYTE_SIZE; | |||
} | |||
colorTable = colorList.toArray(new Color[colorList.size()]); | |||
return size; | |||
} | |||
public InputStream getBMPStream() { | |||
return new ByteArrayInputStream(getBMPData()); | |||
} | |||
private byte[] getBMPData() { | |||
if (imageData == null) { | |||
throw new RecordFormatException("bitmap not initialized ... need to call init() before"); | |||
} | |||
@@ -398,14 +399,20 @@ public class HwmfBitmapDib { | |||
// fill the "known" image data | |||
System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length); | |||
return new ByteArrayInputStream(buf); | |||
return buf; | |||
} | |||
public BufferedImage getImage() { | |||
try { | |||
return ImageIO.read(getBMPStream()); | |||
} 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; | |||
} | |||
} | |||
} |
@@ -17,6 +17,7 @@ | |||
package org.apache.poi.hwmf.record; | |||
import java.awt.Color; | |||
import java.awt.Shape; | |||
import java.awt.geom.Arc2D; | |||
import java.awt.geom.Area; | |||
@@ -146,9 +147,10 @@ public class HwmfDraw { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Path2D p = getShape(); | |||
p.closePath(); | |||
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); | |||
Path2D shape = getShape(); | |||
// shape.closePath(); | |||
Path2D p = (Path2D)shape.clone(); | |||
p.setWindingRule(getWindingRule(ctx)); | |||
ctx.fill(p); | |||
} | |||
@@ -170,8 +172,9 @@ public class HwmfDraw { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Path2D p = getShape(); | |||
p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag); | |||
Path2D shape = getShape(); | |||
Path2D p = (Path2D)shape.clone(); | |||
p.setWindingRule(getWindingRule(ctx)); | |||
ctx.draw(p); | |||
} | |||
} | |||
@@ -320,12 +323,13 @@ public class HwmfDraw { | |||
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(); | |||
for (int i=0; i<nPoints; i++) { | |||
int x = leis.readUShort(); | |||
int y = leis.readUShort(); | |||
int x = leis.readShort(); | |||
int y = leis.readShort(); | |||
size += 2*LittleEndianConsts.SHORT_SIZE; | |||
if (i == 0) { | |||
poly.moveTo(x, y); | |||
@@ -342,14 +346,23 @@ public class HwmfDraw { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int windingRule = ctx.getProperties().getPolyfillMode().awtFlag; | |||
Area area = new Area(); | |||
if (polyList.isEmpty()) { | |||
return; | |||
} | |||
int windingRule = getWindingRule(ctx); | |||
Area area = null; | |||
for (Path2D poly : polyList) { | |||
Path2D p = (Path2D)poly.clone(); | |||
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); | |||
} | |||
} | |||
private static int getWindingRule(HwmfGraphics ctx) { | |||
return ctx.getProperties().getPolyfillMode().awtFlag; | |||
} | |||
} |
@@ -24,13 +24,155 @@ import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
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 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 | |||
* 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 | |||
* EscapeData field. | |||
@@ -48,7 +190,7 @@ public class HwmfEscape implements HwmfRecord { | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
escapeFunction = leis.readUShort(); | |||
escapeFunction = EscapeFunction.valueOf(leis.readUShort()); | |||
byteCount = leis.readUShort(); | |||
escapeData = new byte[byteCount]; | |||
leis.read(escapeData); |
@@ -20,13 +20,9 @@ package org.apache.poi.hwmf.record; | |||
import java.awt.Shape; | |||
import java.awt.geom.Path2D; | |||
import java.awt.image.BufferedImage; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import javax.imageio.ImageIO; | |||
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.LittleEndianInputStream; | |||
@@ -240,7 +236,7 @@ public class HwmfFill { | |||
@Override | |||
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; | |||
} | |||
@@ -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 { | |||
/** | |||
@@ -502,12 +507,12 @@ public class HwmfFill { | |||
/** | |||
* The META_STRETCHDIB record specifies the transfer of color data from a | |||
* block of pixels in deviceindependent format according to a raster operation, | |||
* block of pixels in device independent format according to a raster operation, | |||
* with possible expansion or contraction. | |||
* 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. | |||
*/ | |||
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 | |||
* the playback device context, and the destination pixels are to be combined to | |||
@@ -599,6 +604,11 @@ public class HwmfFill { | |||
@Override | |||
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. | |||
* 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 | |||
@@ -783,6 +793,11 @@ public class HwmfFill { | |||
@Override | |||
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 | |||
@@ -877,6 +892,11 @@ public class HwmfFill { | |||
@Override | |||
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 | |||
* in the playback device context, and the destination pixels are to be combined to form the | |||
@@ -978,6 +998,11 @@ public class HwmfFill { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
@@ -448,6 +448,13 @@ public class HwmfFont { | |||
*/ | |||
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 | |||
* intended for specifying fonts when the exact typeface wanted is not available. | |||
@@ -480,9 +487,7 @@ public class HwmfFont { | |||
outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); | |||
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); | |||
quality = WmfFontQuality.valueOf(leis.readUByte()); | |||
int pitchAndFamily = leis.readUByte(); | |||
family = WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF); | |||
pitch = WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3); | |||
pitchAndFamily = leis.readUByte(); | |||
byte buf[] = new byte[32], b, readBytes = 0; | |||
do { | |||
@@ -546,12 +551,16 @@ public class HwmfFont { | |||
return quality; | |||
} | |||
public int getPitchAndFamily() { | |||
return pitchAndFamily; | |||
} | |||
public WmfFontFamilyClass getFamily() { | |||
return family; | |||
return WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF); | |||
} | |||
public WmfFontPitch getPitch() { | |||
return pitch; | |||
return WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3); | |||
} | |||
public String getFacename() { |
@@ -109,6 +109,6 @@ public enum HwmfMapMode { | |||
for (HwmfMapMode mm : values()) { | |||
if (mm.flag == flag) return mm; | |||
} | |||
return null; | |||
return MM_ISOTROPIC; | |||
} | |||
} |
@@ -265,25 +265,9 @@ public class HwmfMisc { | |||
/** | |||
* A 16-bit unsigned integer that defines the foreground binary raster | |||
* operation mixing mode. This MUST be one of the values: | |||
* 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 | |||
* operation mixing mode | |||
*/ | |||
private int drawMode; | |||
private HwmfBinaryRasterOp drawMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
@@ -292,7 +276,7 @@ public class HwmfMisc { | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
drawMode = leis.readUShort(); | |||
drawMode = HwmfBinaryRasterOp.valueOf(leis.readUShort()); | |||
return LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -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 | |||
@@ -121,6 +121,11 @@ public class HwmfPalette { | |||
return size; | |||
} | |||
@Override | |||
public final void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
protected List<PaletteEntry> getPaletteCopy() { | |||
List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>(); | |||
for (PaletteEntry et : palette) { | |||
@@ -143,11 +148,6 @@ public class HwmfPalette { | |||
return HwmfRecordType.createPalette; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
ctx.getProperties().setPalette(getPaletteCopy()); | |||
@@ -165,7 +165,7 @@ public class HwmfPalette { | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
public void applyObject(HwmfGraphics ctx) { | |||
HwmfDrawProperties props = ctx.getProperties(); | |||
List<PaletteEntry> palette = props.getPalette(); | |||
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 | |||
* 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 | |||
* the logical palette. | |||
@@ -212,6 +212,11 @@ public class HwmfPalette { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
HwmfDrawProperties props = ctx.getProperties(); | |||
List<PaletteEntry> palette = props.getPalette(); | |||
if (palette == null) { | |||
@@ -292,7 +297,7 @@ public class HwmfPalette { | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
public void applyObject(HwmfGraphics ctx) { | |||
HwmfDrawProperties props = ctx.getProperties(); | |||
List<PaletteEntry> dest = props.getPalette(); | |||
List<PaletteEntry> src = getPaletteCopy(); |
@@ -17,6 +17,68 @@ | |||
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 { | |||
BLACKNESS(0x0000,0x0042,"0"), | |||
DPSOON(0x0001,0x0289,"DPSoon"), | |||
@@ -274,17 +336,17 @@ public enum HwmfTernaryRasterOp { | |||
PSDNOO(0x00FD,0x0A0A,"PSDnoo"), | |||
DPSOO(0x00FE,0x02A9,"DPSoo"), | |||
WHITENESS(0x00FF,0x0062,"1"); | |||
int opIndex; | |||
int opCode; | |||
String opCmd; | |||
HwmfTernaryRasterOp(int opIndex, int opCode, String opCmd) { | |||
this.opIndex=opIndex; | |||
this.opCode=opCode; | |||
this.opCmd=opCmd; | |||
} | |||
public static HwmfTernaryRasterOp valueOf(int opIndex) { | |||
for (HwmfTernaryRasterOp bb : HwmfTernaryRasterOp.values()) { | |||
if (bb.opIndex == opIndex) { | |||
@@ -293,11 +355,11 @@ public enum HwmfTernaryRasterOp { | |||
} | |||
return null; | |||
} | |||
public String describeCmd() { | |||
String stack[] = new String[10]; | |||
int stackPnt = 0; | |||
for (char c : opCmd.toCharArray()) { | |||
switch (c) { | |||
case 'S': |
@@ -19,7 +19,6 @@ package org.apache.poi.hwmf.record; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import java.text.AttributedString; | |||
import org.apache.poi.hwmf.draw.HwmfDrawProperties; | |||
import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
@@ -29,10 +28,13 @@ import org.apache.poi.util.BitFieldFactory; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.LocaleUtil; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
import org.apache.poi.util.RecordFormatException; | |||
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 | |||
* playback device context. Spacing is added to the white space between each character, including | |||
@@ -185,6 +187,48 @@ public class HwmfText { | |||
*/ | |||
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 | |||
text string is to be located. | |||
@@ -199,40 +243,12 @@ public class HwmfText { | |||
* A 16-bit signed integer that defines the length of the string. | |||
*/ | |||
private int stringLength; | |||
/** | |||
* 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 | |||
* ExtTextOutOptions Flags: | |||
* | |||
* 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. | |||
*/ | |||
/** | |||
* 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 | |||
* ExtTextOutOptions Flags (ETO_*) | |||
*/ | |||
private int fwOpts; | |||
/** | |||
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the | |||
@@ -275,7 +291,8 @@ public class HwmfText { | |||
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 | |||
left = leis.readShort(); | |||
top = leis.readShort(); | |||
@@ -289,16 +306,20 @@ public class HwmfText { | |||
text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252); | |||
size += buf.length; | |||
if (size < remainingRecordSize) { | |||
if (size + stringLength*LittleEndianConsts.SHORT_SIZE < remainingRecordSize) { | |||
throw new RecordFormatException("can't read Dx array - given recordSize doesn't contain enough values for string length "+stringLength); | |||
} | |||
dx = new int[stringLength]; | |||
for (int i=0; i<dx.length; i++) { | |||
dx[i] = leis.readShort(); | |||
} | |||
size += dx.length*LittleEndianConsts.SHORT_SIZE; | |||
if (size >= remainingRecordSize) { | |||
logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info"); | |||
return size; | |||
} | |||
int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE); | |||
if (dxLen < stringLength) { | |||
logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); | |||
} | |||
dx = new int[stringLength]; | |||
for (int i=0; i<dxLen; i++) { | |||
dx[i] = leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
return size; | |||
@@ -306,7 +327,8 @@ public class HwmfText { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); | |||
ctx.drawString(text, bounds, dx); | |||
} | |||
} | |||
@@ -376,7 +376,7 @@ public class HwmfWindowing { | |||
* The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the | |||
* 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. | |||
@@ -402,7 +402,11 @@ public class HwmfWindowing { | |||
@Override | |||
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 | |||
* 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 | |||
@@ -452,7 +456,11 @@ public class HwmfWindowing { | |||
@Override | |||
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 | |||
* 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 | |||
@@ -503,7 +511,11 @@ public class HwmfWindowing { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
} | |||
@@ -19,10 +19,8 @@ package org.apache.poi.hwmf.usermodel; | |||
import java.awt.Dimension; | |||
import java.awt.Graphics2D; | |||
import java.awt.GraphicsConfiguration; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.geom.Rectangle2D.Double; | |||
import java.io.BufferedInputStream; | |||
import java.io.IOException; | |||
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.WmfSetWindowOrg; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
import org.apache.poi.util.Units; | |||
public class HwmfPicture { | |||
private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class); | |||
final List<HwmfRecord> records = new ArrayList<HwmfRecord>(); | |||
final HwmfPlaceableHeader placeableHeader; | |||
final HwmfHeader header; | |||
@@ -52,6 +54,10 @@ public class HwmfPicture { | |||
header = new HwmfHeader(leis); | |||
for (;;) { | |||
if (leis.available() < 6) { | |||
logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated"); | |||
break; | |||
} | |||
// recordSize in DWORDs | |||
long recordSize = leis.readUInt()*2; | |||
int recordFunction = leis.readShort(); |
@@ -20,7 +20,10 @@ package org.apache.poi.hslf.model; | |||
import static org.junit.Assert.assertEquals; | |||
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.junit.Test; | |||
@@ -38,7 +41,7 @@ public final class TestFreeform { | |||
@Test | |||
public void testClosedPath() { | |||
GeneralPath path1 = new GeneralPath(); | |||
Path2D.Double path1 = new Path2D.Double(); | |||
path1.moveTo(100, 100); | |||
path1.lineTo(200, 100); | |||
path1.lineTo(200, 200); | |||
@@ -55,7 +58,7 @@ public final class TestFreeform { | |||
@Test | |||
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(); | |||
p.setPath(path1); | |||
@@ -67,7 +70,7 @@ public final class TestFreeform { | |||
@Test | |||
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(); | |||
p.setPath(path1); | |||
@@ -84,8 +87,8 @@ public final class TestFreeform { | |||
public void test54188() { | |||
HSLFFreeformShape p = new HSLFFreeformShape(); | |||
GeneralPath path = p.getPath(); | |||
GeneralPath emptyPath = new GeneralPath(); | |||
Path2D.Double path = p.getPath(); | |||
Path2D.Double emptyPath = new Path2D.Double(); | |||
assertEquals(emptyPath.getBounds2D(), path.getBounds2D()); | |||
} | |||
} |
@@ -30,23 +30,22 @@ import java.awt.Rectangle; | |||
import java.awt.image.BufferedImage; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.lang.reflect.Constructor; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.util.BitSet; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import javax.imageio.ImageIO; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.ddf.EscherBSERecord; | |||
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.Slide; | |||
import org.apache.poi.sl.usermodel.SlideShow; | |||
import org.apache.poi.util.JvmBugs; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
@@ -64,7 +63,7 @@ public final class TestPicture { | |||
* | |||
*/ | |||
@Test | |||
public void multiplePictures() throws Exception { | |||
public void multiplePictures() throws IOException { | |||
HSLFSlideShow ppt = new HSLFSlideShow(); | |||
HSLFSlide s = ppt.createSlide(); | |||
@@ -92,6 +91,8 @@ public final class TestPicture { | |||
EscherBSERecord bse3 = pict.getEscherBSERecord(); | |||
assertSame(bse2, bse3); | |||
assertEquals(3, bse1.getRef()); | |||
ppt.close(); | |||
} | |||
/** | |||
@@ -99,7 +100,7 @@ public final class TestPicture { | |||
* was not found. The correct behaviour is to return null. | |||
*/ | |||
@Test | |||
public void bug46122() { | |||
public void bug46122() throws IOException { | |||
HSLFSlideShow ppt = new HSLFSlideShow(); | |||
HSLFSlide slide = ppt.createSlide(); | |||
HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG); | |||
@@ -112,10 +113,12 @@ public final class TestPicture { | |||
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
pict.draw(graphics); | |||
ppt.close(); | |||
} | |||
@Test | |||
public void macImages() throws Exception { | |||
public void macImages() throws IOException { | |||
HSLFSlideShowImpl hss = new HSLFSlideShowImpl(_slTests.openResourceAsStream("53446.ppt")); | |||
List<HSLFPictureData> pictures = hss.getPictureData(); | |||
@@ -154,11 +157,14 @@ public final class TestPicture { | |||
break; | |||
} | |||
} | |||
hss.close(); | |||
} | |||
@Test | |||
@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[] = { | |||
// "sample_pptx_grouping_issues.pptx", | |||
// "54542_cropped_bitmap.pptx", | |||
@@ -196,7 +202,7 @@ public final class TestPicture { | |||
} else { | |||
BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB); | |||
Graphics2D graphics = img.createGraphics(); | |||
fixFonts(graphics); | |||
DrawFactory.getInstance(graphics).fixFonts(graphics); | |||
slide.draw(graphics); | |||
graphics.setColor(Color.BLACK); | |||
graphics.setStroke(new BasicStroke(1)); | |||
@@ -204,16 +210,8 @@ public final class TestPicture { | |||
ImageIO.write(img, "PNG", new File(file.replaceFirst(".pptx?", "-")+slideNo+".png")); | |||
} | |||
} | |||
ss.close(); | |||
} | |||
} | |||
@SuppressWarnings("unchecked") | |||
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); | |||
} | |||
} |
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; | |||
import java.awt.Dimension; | |||
import java.awt.Graphics2D; | |||
import java.awt.RenderingHints; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.image.BufferedImage; | |||
import java.io.File; | |||
import java.io.FileFilter; | |||
@@ -64,7 +65,7 @@ public class TestHwmfParsing { | |||
@Ignore("This is work-in-progress and not a real unit test ...") | |||
public void paint() throws IOException { | |||
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); | |||
HwmfPicture wmf = new HwmfPicture(fis); | |||
fis.close(); | |||
@@ -73,6 +74,11 @@ public class TestHwmfParsing { | |||
int width = Units.pointsToPixel(dim.getWidth()); | |||
// keep aspect ratio for height | |||
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); | |||
Graphics2D g = bufImg.createGraphics(); | |||
@@ -81,7 +87,7 @@ public class TestHwmfParsing { | |||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); | |||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); | |||
wmf.draw(g); | |||
wmf.draw(g, new Rectangle2D.Double(0,0,width,height)); | |||
g.dispose(); | |||
@@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import java.awt.geom.GeneralPath; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.net.URL; | |||
import java.util.Enumeration; | |||
@@ -44,7 +44,7 @@ public class TestPresetGeometries { | |||
} | |||
}); | |||
for(Path p : geom){ | |||
GeneralPath path = p.getPath(ctx); | |||
Path2D path = p.getPath(ctx); | |||
assertNotNull(path); | |||
} | |||
} |