import java.io.ByteArrayInputStream;\r
import java.io.ByteArrayOutputStream;\r
import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
\r
-import org.apache.poi.sl.draw.Drawable;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
import org.apache.poi.sl.usermodel.PictureData;\r
import org.apache.poi.sl.usermodel.Shape;\r
import org.apache.poi.sl.usermodel.ShapeContainer;\r
import org.apache.poi.sl.usermodel.TextParagraph;\r
import org.apache.poi.sl.usermodel.TextRun;\r
import org.apache.poi.sl.usermodel.TextShape;\r
-import org.apache.poi.util.JvmBugs;\r
\r
public abstract class SlideShowHandler extends POIFSFileHandler {\r
public void handleSlideShow(SlideShow<?,?> ss) throws IOException {\r
\r
// read in the writen file\r
SlideShow<?,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray()));\r
- assertNotNull(read);\r
- \r
- readContent(read);\r
- \r
+ try {\r
+ assertNotNull(read);\r
+ readContent(read);\r
+ } finally {\r
+ read.close();\r
+ }\r
}\r
\r
private ByteArrayOutputStream writeToArray(SlideShow<?,?> ss) throws IOException {\r
for (Slide<?,?> s : ss.getSlides()) {\r
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);\r
Graphics2D graphics = img.createGraphics();\r
- fixFonts(graphics);\r
+ DrawFactory.getInstance(graphics).fixFonts(graphics);\r
\r
// default rendering options\r
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
img.flush();\r
}\r
}\r
-\r
- @SuppressWarnings("unchecked")\r
- private static void fixFonts(Graphics2D graphics) {\r
- if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
- Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
- if (fontMap == null) fontMap = new HashMap<String,String>();\r
- fontMap.put("Calibri", "Lucida Sans");\r
- fontMap.put("Cambria", "Lucida Bright");\r
- graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); \r
- }\r
-\r
}
\ No newline at end of file
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;
}
public int getNumberOfElementsInArray() {
- if (emptyComplexPart){
- return 0;
- }
- return LittleEndian.getUShort(_complexData, 0);
+ return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0);
}
public void setNumberOfElementsInArray(int numberOfElements) {
}
public int getNumberOfElementsInMemory() {
- return LittleEndian.getUShort(_complexData, 2);
+ return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2);
}
public void setNumberOfElementsInMemory(int numberOfElements) {
}
public short getSizeOfElements() {
- return LittleEndian.getShort( _complexData, 4 );
+ return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 );
}
public void setSizeOfElements(int sizeOfElements) {
import org.apache.poi.sl.usermodel.Background;\r
import org.apache.poi.sl.usermodel.PlaceableShape;\r
import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.Sheet;\r
\r
\r
public class DrawBackground extends DrawShape {\r
public void setFlipVertical(boolean flip) {}\r
public boolean getFlipHorizontal() { return false; }\r
public boolean getFlipVertical() { return false; }\r
+ public Sheet<?,?> getSheet() { return shape.getSheet(); }\r
};\r
\r
DrawFactory drawFact = DrawFactory.getInstance(graphics);\r
Rectangle2D anchor2 = getAnchor(graphics, anchor);\r
\r
if(fill != null) {\r
+ graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, anchor);\r
graphics.setPaint(fill);\r
graphics.fill(anchor2);\r
}\r
\r
package org.apache.poi.sl.draw;\r
\r
-import static org.apache.poi.sl.draw.Drawable.DRAW_FACTORY;\r
-\r
import java.awt.Graphics2D;\r
import java.awt.font.TextLayout;\r
import java.text.AttributedString;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
\r
import org.apache.poi.sl.usermodel.Background;\r
import org.apache.poi.sl.usermodel.ConnectorShape;\r
import org.apache.poi.sl.usermodel.TextBox;\r
import org.apache.poi.sl.usermodel.TextParagraph;\r
import org.apache.poi.sl.usermodel.TextShape;\r
+import org.apache.poi.util.JvmBugs;\r
\r
public class DrawFactory {\r
protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<DrawFactory>();\r
DrawFactory factory = null;\r
boolean isHint = false;\r
if (graphics != null) {\r
- factory = (DrawFactory)graphics.getRenderingHint(DRAW_FACTORY);\r
+ factory = (DrawFactory)graphics.getRenderingHint(Drawable.DRAW_FACTORY);\r
isHint = (factory != null);\r
}\r
// secondly try the thread local default\r
factory = new DrawFactory();\r
}\r
if (graphics != null && !isHint) {\r
- graphics.setRenderingHint(DRAW_FACTORY, factory);\r
+ graphics.setRenderingHint(Drawable.DRAW_FACTORY, factory);\r
}\r
return factory;\r
}\r
public DrawPaint getPaint(PlaceableShape<?,?> shape) {\r
return new DrawPaint(shape);\r
}\r
-}\r
+\r
+\r
+ /**\r
+ * Replace font families for Windows JVM 6, which contains a font rendering error.\r
+ * This is likely to be removed, when POI upgrades to JDK 7\r
+ *\r
+ * @param graphics the graphics context which will contain the font mapping\r
+ */\r
+ public void fixFonts(Graphics2D graphics) {\r
+ if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
+ @SuppressWarnings("unchecked")\r
+ Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
+ if (fontMap == null) {\r
+ fontMap = new HashMap<String,String>();\r
+ graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);\r
+ }\r
+ \r
+ String fonts[][] = { { "Calibri", "Lucida Sans" }, { "Cambria", "Lucida Bright" } };\r
+\r
+ for (String f[] : fonts) {\r
+ if (!fontMap.containsKey(f[0])) {\r
+ fontMap.put(f[0], f[1]);\r
+ }\r
+ }\r
+ }\r
+}
\ No newline at end of file
ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, fill.getContentType());\r
\r
try {\r
- renderer.loadImage(is, fill.getContentType());\r
- is.close();\r
+ try {\r
+ renderer.loadImage(is, fill.getContentType());\r
+ } finally {\r
+ is.close();\r
+ }\r
} catch (IOException e) {\r
LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e);\r
return null;\r
\r
Point2D p2 = new Point2D.Double(anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight() / 2);\r
p2 = at.transform(p2, null);\r
-\r
+ \r
snapToAnchor(p1, anchor);\r
snapToAnchor(p2, anchor);\r
\r
+ if (p1.equals(p2)) {\r
+ // gradient paint on the same point throws an exception ... and doesn't make sense\r
+ return null;\r
+ }\r
+\r
float[] fractions = fill.getGradientFractions();\r
Color[] colors = new Color[fractions.length];\r
\r
int i = 0;\r
for (ColorStyle fc : fill.getGradientColors()) {\r
+ if (fc == null) {\r
+ // get color of background\r
+ fc = new ColorStyle() {\r
+ public int getTint() { return -1; }\r
+ public int getShade() { return -1; }\r
+ public int getSatOff() { return -1; }\r
+ public int getSatMod() { return -1; }\r
+ public int getLumOff() { return -1; }\r
+ public int getLumMod() { return -1; }\r
+ public int getHueOff() { return -1; }\r
+ public int getHueMod() { return -1; }\r
+ public Color getColor() { return Color.white; }\r
+ public int getAlpha() { return 0; }\r
+ };\r
+ }\r
colors[i++] = applyColorTransform(fc);\r
}\r
\r
renderer.loadImage(data.getData(), data.getContentType());\r
renderer.drawImage(graphics, anchor, insets);\r
} catch (IOException e) {\r
- // TODO: draw specific runtime exception?\r
- throw new RuntimeException(e);\r
+ LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e);\r
}\r
} \r
\r
\r
Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D();\r
\r
- scaleX = anchor.getWidth() == 0. ? 1.0 : anchor.getWidth() / anchor2.getWidth();\r
- scaleY = anchor.getHeight() == 0. ? 1.0 : anchor.getHeight() / anchor2.getHeight();\r
+ scaleX = safeScale(anchor.getWidth(), anchor2.getWidth());\r
+ scaleY = safeScale(anchor.getHeight(), anchor2.getHeight());\r
} else {\r
quadrant = 0;\r
}\r
\r
// transformation is applied reversed ...\r
graphics.translate(centerX, centerY);\r
- graphics.rotate(Math.toRadians(rotation-quadrant*90.));\r
+ double rot = Math.toRadians(rotation-quadrant*90.);\r
+ if (rot != 0) {\r
+ graphics.rotate(rot);\r
+ }\r
graphics.scale(scaleX, scaleY);\r
- graphics.rotate(Math.toRadians(quadrant*90));\r
+ rot = Math.toRadians(quadrant*90);\r
+ if (rot != 0) {\r
+ graphics.rotate(rot);\r
+ }\r
graphics.translate(-centerX, -centerY);\r
}\r
\r
}\r
}\r
\r
+ private static double safeScale(double dim1, double dim2) {\r
+ if (dim1 == 0.) {\r
+ return 1;\r
+ }\r
+ return (dim2 == 0.) ? 1 : dim1/dim2;\r
+ }\r
\r
public void draw(Graphics2D graphics) {\r
}\r
import java.awt.Paint;\r
import java.awt.geom.AffineTransform;\r
import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
import java.awt.geom.Rectangle2D;\r
import java.io.IOException;\r
import java.io.InputStream;\r
case STEALTH:\r
case ARROW:\r
p = new Path(false, true);\r
- GeneralPath arrow = new GeneralPath();\r
- arrow.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+ Path2D.Double arrow = new Path2D.Double();\r
+ arrow.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
arrow.lineTo(0, 0);\r
- arrow.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+ arrow.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));\r
tailShape = arrow;\r
at.translate(x2, y2);\r
at.rotate(alpha);\r
break;\r
case TRIANGLE:\r
p = new Path();\r
- GeneralPath triangle = new GeneralPath();\r
- triangle.moveTo((float) (-lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+ Path2D.Double triangle = new Path2D.Double();\r
+ triangle.moveTo((-lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
triangle.lineTo(0, 0);\r
- triangle.lineTo((float) (-lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+ triangle.lineTo((-lineWidth * scaleX), (lineWidth * scaleY / 2));\r
triangle.closePath();\r
tailShape = triangle;\r
at.translate(x2, y2);\r
case STEALTH:\r
case ARROW:\r
p = new Path(false, true);\r
- GeneralPath arrow = new GeneralPath();\r
- arrow.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+ Path2D.Double arrow = new Path2D.Double();\r
+ arrow.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
arrow.lineTo(0, 0);\r
- arrow.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+ arrow.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));\r
headShape = arrow;\r
at.translate(x1, y1);\r
at.rotate(alpha);\r
break;\r
case TRIANGLE:\r
p = new Path();\r
- GeneralPath triangle = new GeneralPath();\r
- triangle.moveTo((float) (lineWidth * scaleX), (float) (-lineWidth * scaleY / 2));\r
+ Path2D.Double triangle = new Path2D.Double();\r
+ triangle.moveTo((lineWidth * scaleX), (-lineWidth * scaleY / 2));\r
triangle.lineTo(0, 0);\r
- triangle.lineTo((float) (lineWidth * scaleX), (float) (lineWidth * scaleY / 2));\r
+ triangle.lineTo((lineWidth * scaleX), (lineWidth * scaleY / 2));\r
triangle.closePath();\r
headShape = triangle;\r
at.translate(x1, y1);\r
import org.apache.poi.sl.usermodel.PaintStyle;\r
import org.apache.poi.sl.usermodel.PlaceableShape;\r
import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.Sheet;\r
import org.apache.poi.sl.usermodel.TextParagraph;\r
import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;\r
import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;\r
public void setFlipVertical(boolean flip) {}\r
public boolean getFlipHorizontal() { return false; }\r
public boolean getFlipVertical() { return false; }\r
+ public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); }\r
};\r
return ps;\r
}\r
attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex));\r
}\r
\r
- Hyperlink hl = run.getHyperlink();\r
+ Hyperlink<?,?> hl = run.getHyperlink();\r
if (hl != null) {\r
attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex));\r
attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex));\r
import java.awt.geom.AffineTransform;\r
import java.awt.geom.Rectangle2D;\r
import java.awt.image.BufferedImage;\r
-import java.util.*;\r
+import java.util.Iterator;\r
\r
-import org.apache.poi.sl.usermodel.*;\r
+import org.apache.poi.sl.usermodel.Insets2D;\r
+import org.apache.poi.sl.usermodel.PlaceableShape;\r
+import org.apache.poi.sl.usermodel.ShapeContainer;\r
+import org.apache.poi.sl.usermodel.TextParagraph;\r
import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;\r
-import org.apache.poi.util.JvmBugs;\r
+import org.apache.poi.sl.usermodel.TextRun;\r
+import org.apache.poi.sl.usermodel.TextShape;\r
\r
public class DrawTextShape extends DrawSimpleShape {\r
\r
\r
@Override\r
public void drawContent(Graphics2D graphics) {\r
- fixFonts(graphics);\r
+ DrawFactory.getInstance(graphics).fixFonts(graphics);\r
\r
TextShape<?,?> s = getShape();\r
\r
}\r
\r
Double textRot = s.getTextRotation();\r
- if (textRot != null) {\r
+ if (textRot != null && textRot != 0) {\r
graphics.translate(anchor.getCenterX(), anchor.getCenterY());\r
graphics.rotate(Math.toRadians(textRot));\r
graphics.translate(-anchor.getCenterX(), -anchor.getCenterY());\r
\r
double y0 = y;\r
//noinspection RedundantCast\r
- Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs = (Iterator<? extends TextParagraph<?, ?, ? extends TextRun>>) getShape().iterator();\r
-\r
+ Iterator<? extends TextParagraph<?,?,? extends TextRun>> paragraphs =\r
+ (Iterator<? extends TextParagraph<?,?,? extends TextRun>>) getShape().iterator();\r
+ \r
boolean isFirstLine = true;\r
for (int autoNbrIdx=0; paragraphs.hasNext(); autoNbrIdx++){\r
TextParagraph<?,?,? extends TextRun> p = paragraphs.next();\r
// dry-run in a 1x1 image and return the vertical advance\r
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);\r
Graphics2D graphics = img.createGraphics();\r
- fixFonts(graphics);\r
+ DrawFactory.getInstance(graphics).fixFonts(graphics);\r
return drawParagraphs(graphics, 0, 0);\r
}\r
\r
- @SuppressWarnings("unchecked")\r
- private static void fixFonts(Graphics2D graphics) {\r
- if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
- Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
- if (fontMap == null) {\r
- fontMap = new HashMap<String,String>();\r
- graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);\r
- }\r
- \r
- if (!fontMap.containsKey("Calibri")) fontMap.put("Calibri", "Lucida Sans");\r
- if (!fontMap.containsKey("Cambria")) fontMap.put("Cambria", "Lucida Bright");\r
- }\r
-\r
@Override\r
protected TextShape<?,?> getShape() {\r
return (TextShape<?,?>)shape;\r
) {\r
shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE);\r
if (shape == null) {\r
- throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint PathGradientPaint.GRADIANT_SHAPE.");\r
+ throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint Drawable.GRADIANT_SHAPE.");\r
}\r
\r
this.deviceBounds = deviceBounds;\r
return childRaster;\r
}\r
\r
- protected int getGradientSteps(Shape shape) {\r
- Rectangle rect = shape.getBounds();\r
+ protected int getGradientSteps(Shape gradientShape) {\r
+ Rectangle rect = gradientShape.getBounds();\r
int lower = 1;\r
int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0);\r
while (lower < upper-1) {\r
int mid = lower + (upper - lower) / 2;\r
BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle);\r
- Area area = new Area(bs.createStrokedShape(shape));\r
+ Area area = new Area(bs.createStrokedShape(gradientShape));\r
if (area.isSingular()) {\r
upper = mid;\r
} else {\r
import java.awt.geom.Ellipse2D;\r
import java.awt.geom.GeneralPath;\r
import java.awt.geom.Line2D;\r
+import java.awt.geom.Path2D;\r
import java.awt.geom.RoundRectangle2D;\r
import java.awt.image.BufferedImage;\r
import java.awt.image.BufferedImageOp;\r
* @see #setComposite\r
*/\r
public void draw(Shape shape){\r
- GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));\r
+ Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));\r
FreeformShape<?,?> p = _group.createFreeform();\r
p.setPath(path);\r
p.setFillColor(null);\r
* @see #setClip\r
*/\r
public void fill(Shape shape){\r
- GeneralPath path = new GeneralPath(_transform.createTransformedShape(shape));\r
+ Path2D.Double path = new Path2D.Double(_transform.createTransformedShape(shape));\r
FreeformShape<?,?> p = _group.createFreeform();\r
p.setPath(path);\r
applyPaint(p);\r
*/\r
public void drawRoundRect(int x, int y, int width, int height,\r
int arcWidth, int arcHeight){\r
- RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+ RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);\r
draw(rect);\r
}\r
\r
* @see java.awt.Graphics#drawOval\r
*/\r
public void fillOval(int x, int y, int width, int height){\r
- Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);\r
+ Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);\r
fill(oval);\r
}\r
\r
public void fillRoundRect(int x, int y, int width, int height,\r
int arcWidth, int arcHeight){\r
\r
- RoundRectangle2D rect = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+ RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight);\r
fill(rect);\r
}\r
\r
* relative to the start angle.\r
* @see java.awt.Graphics#drawArc\r
*/\r
- public void fillArc(int x, int y, int width, int height,\r
- int startAngle, int arcAngle){\r
- Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);\r
+ public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){\r
+ Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);\r
fill(arc);\r
}\r
\r
* relative to the start angle.\r
* @see java.awt.Graphics#fillArc\r
*/\r
- public void drawArc(int x, int y, int width, int height,\r
- int startAngle, int arcAngle) {\r
- Arc2D arc = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);\r
+ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {\r
+ Arc2D arc = new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);\r
draw(arc);\r
}\r
\r
* @see java.awt.Graphics#fillOval\r
*/\r
public void drawOval(int x, int y, int width, int height){\r
- Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);\r
+ Ellipse2D oval = new Ellipse2D.Double(x, y, width, height);\r
draw(oval);\r
}\r
\r
* @param y2 the second point's <i>y</i> coordinate.\r
*/\r
public void drawLine(int x1, int y1, int x2, int y2){\r
- Line2D line = new Line2D.Float(x1, y1, x2, y2);\r
+ Line2D line = new Line2D.Double(x1, y1, x2, y2);\r
draw(line);\r
}\r
\r
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:
*
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;
package org.apache.poi.sl.draw.geom;
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
/**
* Date: 10/25/11
ClosePathCommand(){
}
- public void execute(GeneralPath path, Context ctx){
+ public void execute(Path2D.Double path, Context ctx){
path.closePath();
}
}
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
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);
}
}
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
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);
}
}
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
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);
}
}
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
}
/**
- * 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;
package org.apache.poi.sl.draw.geom;
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
/**
* A path command in DrawingML. One of:
* @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);
}
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
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);
}
}
\r
package org.apache.poi.sl.usermodel;\r
\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
\r
public interface FreeformShape<\r
S extends Shape<S,P>,\r
*\r
* @return the path\r
*/\r
- GeneralPath getPath();\r
+ Path2D.Double getPath();\r
\r
/**\r
* Set the shape path\r
* @param path shape outline\r
* @return the number of points written\r
*/\r
- int setPath(GeneralPath path);\r
+ int setPath(Path2D.Double path);\r
}\r
> {\r
ShapeContainer<S,P> getParent();\r
\r
+ /**\r
+ * @return the sheet this shape belongs to\r
+ */\r
+ Sheet<S,P> getSheet();\r
+ \r
/**\r
* @return the position of this shape within the drawing canvas.\r
* The coordinates are expressed in points\r
> {
ShapeContainer<S,P> getParent();
- /**
- *
+ /**
* @return the sheet this shape belongs to
*/
Sheet<S,P> getSheet();
package org.apache.poi.xslf.usermodel;\r
\r
import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
import java.awt.geom.PathIterator;\r
import java.awt.geom.Rectangle2D;\r
\r
}\r
\r
@Override\r
- public int setPath(GeneralPath path) {\r
+ public int setPath(Path2D.Double path) {\r
CTPath2D ctPath = CTPath2D.Factory.newInstance();\r
\r
Rectangle2D bounds = path.getBounds2D();\r
}\r
\r
@Override\r
- public GeneralPath getPath() {\r
- GeneralPath path = new GeneralPath();\r
+ public Path2D.Double getPath() {\r
+ Path2D.Double path = new Path2D.Double();\r
Rectangle2D bounds = getAnchor();\r
\r
CTCustomGeometry2D geom = getSpPr().getCustGeom();\r
// The returned path should fit in the bounding rectangle\r
AffineTransform at = new AffineTransform();\r
at.translate(bounds.getX(), bounds.getY());\r
- return new GeneralPath(at.createTransformedShape(path));\r
+ return new Path2D.Double(at.createTransformedShape(path));\r
}\r
/**\r
* @param shapeId 1-based shapeId\r
\r
@Override\r
public String getAddress() {\r
- if (!_link.isSetId()) {\r
+ String id = _link.getId();\r
+ if (id == null || "".equals(id)) {\r
return _link.getAction();\r
}\r
\r
- String id = _link.getId();\r
URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI();\r
\r
return targetURI.toASCIIString();\r
\r
@Override\r
public XSLFHyperlink getHyperlink(){\r
- if(!_r.getRPr().isSetHlinkClick()) return null;\r
- return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet());\r
+ CTTextCharacterProperties rPr = _r.getRPr();\r
+ if (rPr == null) { \r
+ return null;\r
+ }\r
+ CTHyperlink hl = rPr.getHlinkClick();\r
+ if (hl == null) {\r
+ return null;\r
+ }\r
+ return new XSLFHyperlink(hl, _p.getParentShape().getSheet());\r
}\r
\r
private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){\r
import java.awt.RenderingHints;\r
import java.awt.image.BufferedImage;\r
import java.io.File;\r
-import java.util.HashMap;\r
import java.util.List;\r
import java.util.Locale;\r
-import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
\r
import javax.imageio.ImageIO;\r
\r
-import org.apache.poi.sl.draw.Drawable;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
import org.apache.poi.sl.usermodel.Slide;\r
import org.apache.poi.sl.usermodel.SlideShow;\r
import org.apache.poi.sl.usermodel.SlideShowFactory;\r
-import org.apache.poi.util.JvmBugs;\r
\r
/**\r
* An utility to convert slides of a .pptx slide show to a PNG image\r
return;\r
}\r
\r
- int slidenum = -1;\r
+ String slidenumStr = "-1";\r
float scale = 1;\r
File file = null;\r
String format = "png";\r
if ("-scale".equals(args[i])) {\r
scale = Float.parseFloat(args[++i]);\r
} else if ("-slide".equals(args[i])) {\r
- slidenum = Integer.parseInt(args[++i]);\r
+ slidenumStr = args[++i];\r
} else if ("-format".equals(args[i])) {\r
format = args[++i];\r
} else if ("-outdir".equals(args[i])) {\r
SlideShow<?,?> ss = SlideShowFactory.create(file, null, true);\r
List<? extends Slide<?,?>> slides = ss.getSlides();\r
\r
+ Set<Integer> slidenum = slideIndexes(slides.size(), slidenumStr);\r
\r
- if (slidenum < -1 || slidenum == 0 || slidenum > slides.size()) {\r
+ if (slidenum.isEmpty()) {\r
usage("slidenum must be either -1 (for all) or within range: [1.."+slides.size()+"] for "+file);\r
+ ss.close();\r
return;\r
}\r
\r
int width = (int) (pgsize.width * scale);\r
int height = (int) (pgsize.height * scale);\r
\r
- int slideNo=1;\r
- for(Slide<?,?> slide : slides) {\r
- if (slidenum == -1 || slideNo == slidenum) {\r
- String title = slide.getTitle();\r
- if (!quiet) {\r
- System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title));\r
- }\r
+ for(Integer slideNo : slidenum) {\r
+ Slide<?,?> slide = slides.get(slideNo);\r
+ String title = slide.getTitle();\r
+ if (!quiet) {\r
+ System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title));\r
+ }\r
\r
- BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
- Graphics2D graphics = img.createGraphics();\r
- fixFonts(graphics);\r
- \r
- // default rendering options\r
- graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
- graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\r
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
- graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
-\r
- graphics.scale(scale, scale);\r
-\r
- // draw stuff\r
- slide.draw(graphics);\r
-\r
- // save the result\r
- if (!"null".equals(format)) {\r
- String outname = file.getName().replaceFirst(".pptx?", "");\r
- outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format);\r
- File outfile = new File(outdir, outname);\r
- ImageIO.write(img, format, outfile);\r
- }\r
- } \r
- slideNo++;\r
- }\r
+ BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
+ Graphics2D graphics = img.createGraphics();\r
+ DrawFactory.getInstance(graphics).fixFonts(graphics);\r
+ \r
+ // default rendering options\r
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+ graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\r
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
+ graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
+\r
+ graphics.scale(scale, scale);\r
+\r
+ // draw stuff\r
+ slide.draw(graphics);\r
+\r
+ // save the result\r
+ if (!"null".equals(format)) {\r
+ String outname = file.getName().replaceFirst(".pptx?", "");\r
+ outname = String.format(Locale.ROOT, "%1$s-%2$04d.%3$s", outname, slideNo, format);\r
+ File outfile = new File(outdir, outname);\r
+ ImageIO.write(img, format, outfile);\r
+ }\r
+ } \r
\r
if (!quiet) {\r
System.out.println("Done");\r
\r
ss.close();\r
}\r
-\r
- @SuppressWarnings("unchecked")\r
- private static void fixFonts(Graphics2D graphics) {\r
- if (!JvmBugs.hasLineBreakMeasurerBug()) return;\r
- Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);\r
- if (fontMap == null) fontMap = new HashMap<String,String>();\r
- fontMap.put("Calibri", "Lucida Sans");\r
- fontMap.put("Cambria", "Lucida Bright");\r
- graphics.setRenderingHint(Drawable.FONT_MAP, fontMap); \r
+ \r
+ private static Set<Integer> slideIndexes(final int slideCount, String range) {\r
+ Set<Integer> slideIdx = new TreeSet<Integer>();\r
+ if ("-1".equals(range)) {\r
+ for (int i=0; i<slideCount; i++) {\r
+ slideIdx.add(i);\r
+ }\r
+ } else {\r
+ for (String subrange : range.split(",")) {\r
+ String idx[] = subrange.split("-");\r
+ switch (idx.length) {\r
+ default:\r
+ case 0: break;\r
+ case 1: {\r
+ int subidx = Integer.parseInt(idx[0]);\r
+ if (subrange.contains("-")) {\r
+ int startIdx = subrange.startsWith("-") ? 0 : subidx;\r
+ int endIdx = subrange.endsWith("-") ? slideCount : Math.min(subidx,slideCount);\r
+ for (int i=Math.max(startIdx,1); i<endIdx; i++) {\r
+ slideIdx.add(i-1);\r
+ }\r
+ } else {\r
+ slideIdx.add(Math.max(subidx,1)-1);\r
+ }\r
+ break;\r
+ }\r
+ case 2: {\r
+ int startIdx = Math.min(Integer.parseInt(idx[0]), slideCount);\r
+ int endIdx = Math.min(Integer.parseInt(idx[1]), slideCount);\r
+ for (int i=Math.max(startIdx,1); i<endIdx; i++) {\r
+ slideIdx.add(i-1);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return slideIdx;\r
}\r
}\r
public void render() throws Exception {\r
POIDataSamples samples = POIDataSamples.getSlideShowInstance();\r
\r
- String[] testFiles = {"alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",};\r
-// String[] testFiles = {"41246-2.ppt","45543.ppt","53446.ppt","ParagraphStylesShorterThanCharStyles.ppt"};\r
+// File testFilesX[] = new File("tmp_ppt").listFiles(new FileFilter() {\r
+// public boolean accept(File pathname) {\r
+// return pathname.getName().toLowerCase().contains("ppt");\r
+// }\r
+// });\r
+// String testFiles[] = new String[testFilesX.length];\r
+// for (int i=0; i<testFilesX.length; i++) {\r
+// testFiles[i] = testFilesX[i].getPath();\r
+// }\r
+ \r
+\r
+ String[] testFiles = {"53446.ppt", "alterman_security.ppt","alterman_security.pptx","KEY02.pptx","themes.pptx","backgrounds.pptx","layouts.pptx", "sample.pptx", "shapes.pptx",};\r
String[] args = {\r
"-format", "null", // png,gif,jpg or null for test\r
"-slide", "-1", // -1 for all\r
"-outdir", new File("build/tmp/").getCanonicalPath(),\r
- "-quite",\r
+ "-quiet",\r
"dummyfile"\r
};\r
for(String sampleFile : testFiles){\r
args[args.length-1] = samples.getFile(sampleFile).getCanonicalPath();\r
+// args[args.length-1] = new File(sampleFile).getCanonicalPath();\r
try {\r
PPTX2PNG.main(args);\r
} catch (IllegalStateException e) {\r
import static org.junit.Assert.assertEquals;\r
\r
import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
import java.awt.geom.Rectangle2D;\r
+import java.io.IOException;\r
\r
import org.junit.Test;\r
\r
public class TestXSLFFreeformShape {\r
\r
@Test\r
- public void testSetPath() {\r
+ public void testSetPath() throws IOException {\r
XMLSlideShow ppt = new XMLSlideShow();\r
XSLFSlide slide = ppt.createSlide();\r
XSLFFreeformShape shape1 = slide.createFreeform();\r
// comples path consisting of a rectangle and an ellipse inside it\r
- GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(150, 150, 300, 300));\r
+ Path2D.Double path1 = new Path2D.Double(new Rectangle2D.Double(150, 150, 300, 300));\r
path1.append(new Ellipse2D.Double(200, 200, 100, 50), false);\r
shape1.setPath(path1);\r
\r
- GeneralPath path2 = shape1.getPath();\r
+ Path2D.Double path2 = shape1.getPath();\r
\r
// YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ?\r
// one way is to create another XSLFFreeformShape from path2 and compare the resulting xml\r
shape2.setPath(path2);\r
\r
assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString());\r
+ \r
+ ppt.close();\r
}\r
}
\ No newline at end of file
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;
* @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);
* @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);
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.
*/
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];
}
/**
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
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
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.
}
}
+ @SuppressWarnings("resource")
protected EscherBSERecord getEscherBSERecord(int idx){
HSLFSheet sheet = shape.getSheet();
if(sheet == null) {
/**
* <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);
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;
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.
*
}
@Override
- public int setPath(GeneralPath path) {
+ public int setPath(Path2D.Double path) {
Rectangle2D bounds = path.getBounds2D();
PathIterator it = path.getPathIterator(new AffineTransform());
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) {
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();
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);
}
}
InteractiveInfo hldr = (InteractiveInfo)r;
InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
+ if (info == null) {
+ continue;
+ }
int id = info.getHyperlinkID();
ExHyperlink exHyper = exobj.get(id);
if (exHyper == null) {
? 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
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();
} 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);
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);
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", "");
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);
}
String propNames[] = propName.split(",");\r
for (String pn : propNames) {\r
TextProp prop = props.findByName(pn);\r
- if (prop != null) return prop;\r
- }\r
+ if (prop == null) {\r
+ continue;\r
+ }\r
\r
+ // Font properties (maybe other too???) can have an index of -1\r
+ // so we check the master for this font index then\r
+ if (pn.contains("font") && prop.getValue() == -1) {\r
+ return getMasterPropVal(props, pn, paragraph);\r
+ }\r
+ \r
+ return prop;\r
+ }\r
+ \r
+ return getMasterPropVal(props, propName, paragraph);\r
+ }\r
+ \r
+ private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) {\r
+ String propNames[] = propName.split(",");\r
+ \r
BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME);\r
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0);\r
if (hardAttribute) return null;\r
}\r
\r
public void setViewportOrg(double x, double y) {\r
+ if (viewport == null) {\r
+ viewport = (Rectangle2D)window.clone();\r
+ }\r
double w = viewport.getWidth();\r
double h = viewport.getHeight();\r
viewport.setRect(x, y, w, h);\r
import java.awt.TexturePaint;\r
import java.awt.font.TextAttribute;\r
import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
import java.awt.geom.Rectangle2D;\r
import java.awt.image.BufferedImage;\r
import java.text.AttributedString;\r
import java.util.LinkedList;\r
import java.util.List;\r
import java.util.ListIterator;\r
+import java.util.Map;\r
import java.util.NoSuchElementException;\r
\r
import org.apache.poi.hwmf.record.HwmfBrushStyle;\r
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;\r
import org.apache.poi.hwmf.record.HwmfPenStyle;\r
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;\r
+import org.apache.poi.sl.draw.DrawFactory;\r
+import org.apache.poi.sl.draw.DrawFontManager;\r
+import org.apache.poi.sl.draw.Drawable;\r
\r
public class HwmfGraphics {\r
private final Graphics2D graphicsCtx;\r
this.graphicsCtx = graphicsCtx;\r
this.bbox = (Rectangle2D)bbox.clone();\r
this.initialAT = graphicsCtx.getTransform();\r
+ DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);\r
}\r
\r
public HwmfDrawProperties getProperties() {\r
\r
public void fill(Shape shape) {\r
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {\r
- GeneralPath gp = new GeneralPath(shape);\r
- gp.setWindingRule(prop.getPolyfillMode().awtFlag);\r
+// GeneralPath gp = new GeneralPath(shape);\r
+// gp.setWindingRule(prop.getPolyfillMode().awtFlag);\r
graphicsCtx.setPaint(getFill());\r
graphicsCtx.fill(shape);\r
}\r
if (view == null) {\r
view = win;\r
}\r
- float width = (float)(prop.getPenWidth() * view.getWidth() / win.getWidth());\r
+ \r
+ // TODO: fix line width calculation\r
+ float width = (float)prop.getPenWidth();\r
+ if (width == 0) {\r
+ width = 1;\r
+ }\r
HwmfPenStyle ps = prop.getPenStyle();\r
int cap = ps.getLineCap().awtFlag;\r
int join = ps.getLineJoin().awtFlag;\r
}\r
stackIndex = curIdx + index;\r
}\r
+ if (stackIndex == -1) {\r
+ // roll to last when curIdx == 0\r
+ stackIndex = propStack.size()-1;\r
+ }\r
prop = propStack.get(stackIndex);\r
}\r
\r
}\r
\r
public void drawString(String text, Rectangle2D bounds) {\r
+ drawString(text, bounds, null);\r
+ }\r
+ \r
+ public void drawString(String text, Rectangle2D bounds, int dx[]) {\r
HwmfFont font = prop.getFont();\r
- if (font == null) {\r
+ if (font == null || text == null || text.isEmpty()) {\r
return;\r
}\r
+ \r
+ double fontH = getFontHeight(font);\r
+ // TODO: another approx. ...\r
+ double fontW = fontH/1.8;\r
+ \r
+ int len = text.length();\r
AttributedString as = new AttributedString(text);\r
- as.addAttribute(TextAttribute.FAMILY, font.getFacename());\r
- // TODO: fix font height calculation\r
- as.addAttribute(TextAttribute.SIZE, Math.abs(font.getHeight()));\r
- as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());\r
- if (font.isUnderline()) {\r
- as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);\r
- }\r
- if (font.isItalic()) {\r
- as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);\r
+ if (dx == null || dx.length == 0) {\r
+ addAttributes(as, font, 0, len);\r
+ } else {\r
+ for (int i=0; i<len; i++) {\r
+ addAttributes(as, font, i, i+1);\r
+ // Tracking works as a prefix/advance space on characters whereas\r
+ // dx[...] is the complete width of the current char\r
+ // therefore we need to add the additional/suffix width to the next char\r
+ if (i<len-1) {\r
+ as.addAttribute(TextAttribute.TRACKING, (dx[i]-fontW)/fontH, i+1, i+2);\r
+ }\r
+ }\r
}\r
- as.addAttribute(TextAttribute.WEIGHT, font.getWeight());\r
+ \r
\r
double angle = Math.toRadians(-font.getEscapement()/10.);\r
\r
\r
final AffineTransform at = graphicsCtx.getTransform();\r
try {\r
- graphicsCtx.translate(bounds.getX(), bounds.getY());\r
+ graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH);\r
graphicsCtx.rotate(angle);\r
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {\r
// TODO: validate bounds\r
graphicsCtx.setBackground(prop.getBackgroundColor().getColor());\r
- graphicsCtx.fill(bounds);\r
+ graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));\r
}\r
graphicsCtx.setColor(prop.getTextColor().getColor());\r
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());\r
graphicsCtx.setTransform(at);\r
}\r
}\r
+ \r
+ private void addAttributes(AttributedString as, HwmfFont font, int start, int end) {\r
+ DrawFontManager fontHandler = (DrawFontManager)graphicsCtx.getRenderingHint(Drawable.FONT_HANDLER);\r
+ String fontFamily = null;\r
+ @SuppressWarnings("unchecked")\r
+ Map<String,String> fontMap = (Map<String,String>)graphicsCtx.getRenderingHint(Drawable.FONT_MAP);\r
+ if (fontMap != null && fontMap.containsKey(font.getFacename())) {\r
+ fontFamily = fontMap.get(font.getFacename());\r
+ }\r
+ if (fontHandler != null) {\r
+ fontFamily = fontHandler.getRendererableFont(font.getFacename(), font.getPitchAndFamily());\r
+ }\r
+ if (fontFamily == null) {\r
+ fontFamily = font.getFacename();\r
+ }\r
+ \r
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);\r
+ as.addAttribute(TextAttribute.SIZE, getFontHeight(font));\r
+ as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());\r
+ if (font.isUnderline()) {\r
+ as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);\r
+ }\r
+ if (font.isItalic()) {\r
+ as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);\r
+ }\r
+ as.addAttribute(TextAttribute.WEIGHT, font.getWeight());\r
+ }\r
+ \r
+ private double getFontHeight(HwmfFont font) {\r
+ // see HwmfFont#height for details\r
+ double fontHeight = font.getHeight();\r
+ if (fontHeight == 0) {\r
+ return 12;\r
+ } else if (fontHeight < 0) {\r
+ return -fontHeight;\r
+ } else {\r
+ // TODO: fix font height calculation \r
+ // the height is given as font size + ascent + descent\r
+ // as an approximation we reduce the height by a static factor \r
+ return fontHeight*3/4;\r
+ }\r
+ }\r
}\r
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hwmf.record;\r
+\r
+/**\r
+ * The BinaryRasterOperation Enumeration section lists the binary raster-operation codes.\r
+ * Rasteroperation codes define how metafile processing combines the bits from the selected\r
+ * pen with the bits in the destination bitmap.\r
+ *\r
+ * Each raster-operation code represents a Boolean operation in which the values of the pixels in the\r
+ * selected pen and the destination bitmap are combined. Following are the two operands used in these\r
+ * operations.\r
+ *\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>P</td><td>Selected pen</td></tr>\r
+ * <tr><td>D</td><td>Destination bitmap</td></tr>\r
+ * </table>\r
+ *\r
+ * Following are the Boolean operators used in these operations.\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>a</td><td>Bitwise AND</td></tr>\r
+ * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>\r
+ * <tr><td>o</td><td>Bitwise OR</td></tr>\r
+ * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>\r
+ * </table>\r
+ *\r
+ * All Boolean operations are presented in reverse Polish notation. For example, the following\r
+ * operation replaces the values of the pixels in the destination bitmap with a combination of the pixel\r
+ * values of the pen and the selected brush: DPo.\r
+ *\r
+ * Each raster-operation code is a 32-bit integer whose high-order word is a Boolean operation index and\r
+ * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit\r
+ * value that represents all possible outcomes resulting from the Boolean operation on two parameters\r
+ * (in this case, the pen and destination values). For example, the operation indexes for the DPo and\r
+ * DPan operations are shown in the following list.\r
+ *\r
+ * <table>\r
+ * <tr><th>P</th><th>D</th><th>DPo</th><th>DPan</th></tr>\r
+ * <tr><td>0</td><td>0</td><td>0</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>1</td><td>0</td></tr>\r
+ * </table>\r
+ *\r
+ */\r
+public enum HwmfBinaryRasterOp {\r
+ /** 0, Pixel is always 0 */\r
+ R2_BLACK(0x0001),\r
+ /** DPon, Pixel is the inverse of the R2_MERGEPEN color. */\r
+ R2_NOTMERGEPEN(0x0002),\r
+ /** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */\r
+ R2_MASKNOTPEN(0x0003),\r
+ /** Pn, Pixel is the inverse of the pen color. */\r
+ R2_NOTCOPYPEN(0x0004),\r
+ /** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */\r
+ R2_MASKPENNOT(0x0005),\r
+ /** Dn, Pixel is the inverse of the screen color. */\r
+ R2_NOT(0x0006),\r
+ /** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */\r
+ R2_XORPEN(0x0007),\r
+ /** DPan, Pixel is the inverse of the R2_MASKPEN color. */\r
+ R2_NOTMASKPEN(0x0008),\r
+ /** DPa, Pixel is a combination of the colors common to both the pen and the screen. */\r
+ R2_MASKPEN(0x0009),\r
+ /** DPxn, Pixel is the inverse of the R2_XORPEN color. */\r
+ R2_NOTXORPEN(0x000A),\r
+ /** D, Pixel remains unchanged. */\r
+ R2_NOP(0x000B),\r
+ /** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */\r
+ R2_MERGENOTPEN(0x000C),\r
+ /** P, Pixel is the pen color. */\r
+ R2_COPYPEN(0x000D),\r
+ /** PDno, Pixel is a combination of the pen color and the inverse of the screen color.*/\r
+ R2_MERGEPENNOT(0x000E),\r
+ /** DPo, Pixel is a combination of the pen color and the screen color. */\r
+ R2_MERGEPEN(0x000F),\r
+ /** 1, Pixel is always 1 */\r
+ R2_WHITE(0x0010);\r
+\r
+ int opIndex;\r
+\r
+ HwmfBinaryRasterOp(int opIndex) {\r
+ this.opIndex=opIndex;\r
+ }\r
+\r
+ public static HwmfBinaryRasterOp valueOf(int opIndex) {\r
+ for (HwmfBinaryRasterOp bb : HwmfBinaryRasterOp.values()) {\r
+ if (bb.opIndex == opIndex) {\r
+ return bb;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+}\r
package org.apache.poi.hwmf.record;\r
\r
import java.awt.Color;\r
+import java.awt.Graphics2D;\r
import java.awt.image.BufferedImage;\r
-import java.io.BufferedInputStream;\r
import java.io.ByteArrayInputStream;\r
import java.io.IOException;\r
import java.io.InputStream;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
\r
import javax.imageio.ImageIO;\r
\r
import org.apache.poi.util.LittleEndian;\r
import org.apache.poi.util.LittleEndianConsts;\r
import org.apache.poi.util.LittleEndianInputStream;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
\r
/**\r
* The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format.\r
}\r
}\r
\r
+ private static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);\r
private static final int BMP_HEADER_SIZE = 14;\r
\r
private int headerSize;\r
private long headerColorUsed = -1;\r
@SuppressWarnings("unused")\r
private long headerColorImportant = -1;\r
-\r
- @SuppressWarnings("unused")\r
private Color colorTable[];\r
@SuppressWarnings("unused")\r
private int colorMaskR=0,colorMaskG=0,colorMaskB=0;\r
\r
protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException {\r
int size = 0;\r
- List<Color> colorList = new ArrayList<Color>();\r
+ colorTable = new Color[count];\r
for (int i=0; i<count; i++) {\r
int blue = leis.readUByte();\r
int green = leis.readUByte();\r
int red = leis.readUByte();\r
@SuppressWarnings("unused")\r
int reserved = leis.readUByte();\r
- Color c = new Color(red, green, blue);\r
- colorList.add(c);\r
+ colorTable[i] = new Color(red, green, blue);\r
size += 4 * LittleEndianConsts.BYTE_SIZE;\r
}\r
- colorTable = colorList.toArray(new Color[colorList.size()]);\r
return size;\r
}\r
\r
public InputStream getBMPStream() {\r
+ return new ByteArrayInputStream(getBMPData());\r
+ }\r
+\r
+ private byte[] getBMPData() {\r
if (imageData == null) {\r
throw new RecordFormatException("bitmap not initialized ... need to call init() before");\r
}\r
// fill the "known" image data\r
System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length);\r
\r
- return new ByteArrayInputStream(buf);\r
+ return buf;\r
}\r
\r
public BufferedImage getImage() {\r
try {\r
return ImageIO.read(getBMPStream());\r
} catch (IOException e) {\r
- throw new RecordFormatException("invalid bitmap data", e);\r
+ logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");\r
+ BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);\r
+ Graphics2D g = bi.createGraphics();\r
+ g.setPaint(Color.black);\r
+ g.fillRect(0, 0, headerWidth, headerHeight);\r
+ g.dispose();\r
+ return bi;\r
}\r
}\r
}\r
\r
package org.apache.poi.hwmf.record;\r
\r
+import java.awt.Color;\r
import java.awt.Shape;\r
import java.awt.geom.Arc2D;\r
import java.awt.geom.Area;\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
- Path2D p = getShape();\r
- p.closePath();\r
- p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);\r
+ Path2D shape = getShape();\r
+// shape.closePath();\r
+ Path2D p = (Path2D)shape.clone();\r
+ p.setWindingRule(getWindingRule(ctx));\r
ctx.fill(p);\r
}\r
\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
- Path2D p = getShape();\r
- p.setWindingRule(ctx.getProperties().getPolyfillMode().awtFlag);\r
+ Path2D shape = getShape();\r
+ Path2D p = (Path2D)shape.clone();\r
+ p.setWindingRule(getWindingRule(ctx));\r
ctx.draw(p);\r
}\r
}\r
\r
for (int nPoints : pointsPerPolygon) {\r
/**\r
- * An array of 16-bit unsigned integers that define the coordinates of the polygons.\r
+ * An array of 16-bit signed integers that define the coordinates of the polygons.\r
+ * (Note: MS-WMF wrongly says unsigned integers ...)\r
*/\r
Path2D poly = new Path2D.Double();\r
for (int i=0; i<nPoints; i++) {\r
- int x = leis.readUShort();\r
- int y = leis.readUShort();\r
+ int x = leis.readShort();\r
+ int y = leis.readShort();\r
size += 2*LittleEndianConsts.SHORT_SIZE;\r
if (i == 0) {\r
poly.moveTo(x, y);\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
- int windingRule = ctx.getProperties().getPolyfillMode().awtFlag;\r
- Area area = new Area();\r
+ if (polyList.isEmpty()) {\r
+ return;\r
+ }\r
+ \r
+ int windingRule = getWindingRule(ctx);\r
+ Area area = null;\r
for (Path2D poly : polyList) {\r
Path2D p = (Path2D)poly.clone();\r
p.setWindingRule(windingRule);\r
- area.add(new Area(p));\r
+ Area newArea = new Area(p);\r
+ if (area == null) {\r
+ area = newArea;\r
+ } else {\r
+ area.exclusiveOr(newArea);\r
+ }\r
}\r
- ctx.draw(area);\r
+ ctx.fill(area);\r
}\r
}\r
\r
ctx.applyObjectTableEntry(objectIndex);\r
}\r
}\r
+ \r
+ private static int getWindingRule(HwmfGraphics ctx) {\r
+ return ctx.getProperties().getPolyfillMode().awtFlag;\r
+ }\r
}\r
import org.apache.poi.util.LittleEndianConsts;\r
import org.apache.poi.util.LittleEndianInputStream;\r
\r
+/**\r
+ * The MetafileEscapes specifies printer driver functionality that\r
+ * might not be directly accessible through WMF records\r
+ */\r
public class HwmfEscape implements HwmfRecord {\r
\r
+ public enum EscapeFunction {\r
+ /** Notifies the printer driver that the application has finished writing to a page. */\r
+ NEWFRAME(0x0001),\r
+ /** Stops processing the current document. */\r
+ ABORTDOC(0x0002),\r
+ /** Notifies the printer driver that the application has finished writing to a band. */\r
+ NEXTBAND(0x0003),\r
+ /** Sets color table values. */\r
+ SETCOLORTABLE(0x0004),\r
+ /** Gets color table values. */\r
+ GETCOLORTABLE(0x0005),\r
+ /** Causes all pending output to be flushed to the output device. */\r
+ FLUSHOUT(0x0006),\r
+ /** Indicates that the printer driver SHOULD print text only, and no graphics. */\r
+ DRAFTMODE(0x0007),\r
+ /** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */\r
+ QUERYESCSUPPORT(0x0008),\r
+ /** Sets the application-defined function that allows a print job to be canceled during printing. */\r
+ SETABORTPROC(0x0009),\r
+ /** Notifies the printer driver that a new print job is starting. */\r
+ STARTDOC(0x000A),\r
+ /** Notifies the printer driver that the current print job is ending. */\r
+ ENDDOC(0x000B),\r
+ /** Retrieves the physical page size currently selected on an output device. */\r
+ GETPHYSPAGESIZE(0x000C),\r
+ /** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */\r
+ GETPRINTINGOFFSET(0x000D),\r
+ /** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */\r
+ GETSCALINGFACTOR(0x000E),\r
+ /** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */\r
+ META_ESCAPE_ENHANCED_METAFILE(0x000F),\r
+ /** Sets the width of a pen in pixels. */\r
+ SETPENWIDTH(0x0010),\r
+ /** Sets the number of copies. */\r
+ SETCOPYCOUNT(0x0011),\r
+ /** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */\r
+ SETPAPERSOURCE(0x0012),\r
+ /** This record passes through arbitrary data. */\r
+ PASSTHROUGH(0x0013),\r
+ /** Gets information concerning graphics technology that is supported on a device. */\r
+ GETTECHNOLOGY(0x0014),\r
+ /** Specifies the line-drawing mode to use in output to a device. */\r
+ SETLINECAP(0x0015),\r
+ /** Specifies the line-joining mode to use in output to a device. */\r
+ SETLINEJOIN(0x0016),\r
+ /** Sets the limit for the length of miter joins to use in output to a device. */\r
+ SETMITERLIMIT(0x0017),\r
+ /** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */\r
+ BANDINFO(0x0018),\r
+ /** Draws a rectangle with a defined pattern. */\r
+ DRAWPATTERNRECT(0x0019),\r
+ /** Retrieves the physical pen size currently defined on a device. */\r
+ GETVECTORPENSIZE(0x001A),\r
+ /** Retrieves the physical brush size currently defined on a device. */\r
+ GETVECTORBRUSHSIZE(0x001B),\r
+ /** Enables or disables double-sided (duplex) printing on a device. */\r
+ ENABLEDUPLEX(0x001C),\r
+ /** Retrieves or specifies the source of output forms on a device. */\r
+ GETSETPAPERBINS(0x001D),\r
+ /** Retrieves or specifies the paper orientation on a device. */\r
+ GETSETPRINTORIENT(0x001E),\r
+ /** Retrieves information concerning the sources of different forms on an output device. */\r
+ ENUMPAPERBINS(0x001F),\r
+ /** Specifies the scaling of device-independent bitmaps (DIBs). */\r
+ SETDIBSCALING(0x0020),\r
+ /** Indicates the start and end of an encapsulated PostScript (EPS) section. */\r
+ EPSPRINTING(0x0021),\r
+ /** Queries a printer driver for paper dimensions and other forms data. */\r
+ ENUMPAPERMETRICS(0x0022),\r
+ /** Retrieves or specifies paper dimensions and other forms data on an output device. */\r
+ GETSETPAPERMETRICS(0x0023),\r
+ /** Sends arbitrary PostScript data to an output device. */\r
+ POSTSCRIPT_DATA(0x0025),\r
+ /** Notifies an output device to ignore PostScript data. */\r
+ POSTSCRIPT_IGNORE(0x0026),\r
+ /** Gets the device units currently configured on an output device. */\r
+ GETDEVICEUNITS(0x002A),\r
+ /** Gets extended text metrics currently configured on an output device. */\r
+ GETEXTENDEDTEXTMETRICS(0x0100),\r
+ /** Gets the font kern table currently defined on an output device. */\r
+ GETPAIRKERNTABLE(0x0102),\r
+ /** Draws text using the currently selected font, background color, and text color. */\r
+ EXTTEXTOUT(0x0200),\r
+ /** Gets the font face name currently configured on a device. */\r
+ GETFACENAME(0x0201),\r
+ /** Sets the font face name on a device. */\r
+ DOWNLOADFACE(0x0202),\r
+ /** Queries a printer driver about the support for metafiles on an output device. */\r
+ METAFILE_DRIVER(0x0801),\r
+ /** Queries the printer driver about its support for DIBs on an output device. */\r
+ QUERYDIBSUPPORT(0x0C01),\r
+ /** Opens a path. */\r
+ BEGIN_PATH(0x1000),\r
+ /** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */\r
+ CLIP_TO_PATH(0x1001),\r
+ /** Ends a path. */\r
+ END_PATH(0x1002),\r
+ /** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */\r
+ OPEN_CHANNEL(0x100E),\r
+ /** Instructs the printer driver to download sets of PostScript procedures. */\r
+ DOWNLOADHEADER(0x100F),\r
+ /** The same as ENDDOC. See OPEN_CHANNEL. */\r
+ CLOSE_CHANNEL(0x1010),\r
+ /** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */\r
+ POSTSCRIPT_PASSTHROUGH(0x1013),\r
+ /** Sends arbitrary data directly to the printer driver. */\r
+ ENCAPSULATED_POSTSCRIPT(0x1014),\r
+ /** Sets the printer driver to either PostScript or GDI mode. */\r
+ POSTSCRIPT_IDENTIFY(0x1015),\r
+ /** Inserts a block of raw data into a PostScript stream. The input MUST be\r
+ a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the\r
+ injection point, and a 16-bit quantity specifying the page number, followed by the bytes to\r
+ inject. */\r
+ POSTSCRIPT_INJECTION(0x1016),\r
+ /** Checks whether the printer supports a JPEG image. */\r
+ CHECKJPEGFORMAT(0x1017),\r
+ /** Checks whether the printer supports a PNG image */\r
+ CHECKPNGFORMAT(0x1018),\r
+ /** Gets information on a specified feature setting for a PostScript printer driver. */\r
+ GET_PS_FEATURESETTING(0x1019),\r
+ /** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */\r
+ MXDC_ESCAPE(0x101A),\r
+ /** Enables applications to include private procedures and other arbitrary data in documents. */\r
+ SPCLPASSTHROUGH2(0x11D8);\r
+ \r
+ int flag;\r
+ EscapeFunction(int flag) {\r
+ this.flag = flag;\r
+ }\r
+\r
+ static EscapeFunction valueOf(int flag) {\r
+ for (EscapeFunction hs : values()) {\r
+ if (hs.flag == flag) return hs;\r
+ }\r
+ return null;\r
+ }\r
+ }\r
+ \r
/**\r
* A 16-bit unsigned integer that defines the escape function. The \r
* value MUST be from the MetafileEscapes enumeration.\r
*/\r
- private int escapeFunction;\r
+ private EscapeFunction escapeFunction;\r
/**\r
* A 16-bit unsigned integer that specifies the size, in bytes, of the \r
* EscapeData field.\r
\r
@Override\r
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
- escapeFunction = leis.readUShort();\r
+ escapeFunction = EscapeFunction.valueOf(leis.readUShort());\r
byteCount = leis.readUShort();\r
escapeData = new byte[byteCount];\r
leis.read(escapeData);\r
import java.awt.Shape;\r
import java.awt.geom.Path2D;\r
import java.awt.image.BufferedImage;\r
-import java.io.File;\r
import java.io.IOException;\r
\r
-import javax.imageio.ImageIO;\r
-\r
import org.apache.poi.hwmf.draw.HwmfGraphics;\r
-import org.apache.poi.hwmf.record.HwmfWindowing.WmfCreateRegion;\r
import org.apache.poi.util.LittleEndianConsts;\r
import org.apache.poi.util.LittleEndianInputStream;\r
\r
\r
@Override\r
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
- polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort());\r
+ polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);\r
return LittleEndianConsts.SHORT_SIZE;\r
}\r
\r
}\r
\r
/**\r
+ * The META_STRETCHBLT record specifies the transfer of a block of pixels according to a raster\r
+ * operation, with possible expansion or contraction.\r
+ * The destination of the transfer is the current output region in the playback device context.\r
+ * There are two forms of META_STRETCHBLT, one which specifies a bitmap as the source, and the other\r
+ * which uses the playback device context as the source. Definitions follow for the fields that are the\r
+ * same in the two forms of META_STRETCHBLT are defined below. The subsections that follow specify\r
+ * the packet structures of the two forms of META_STRETCHBLT.\r
+ * The expansion or contraction is performed according to the stretching mode currently set in the\r
+ * playback device context, which MUST be a value from the StretchMode.\r
*/\r
public static class WmfStretchBlt implements HwmfRecord {\r
/**\r
\r
/**\r
* The META_STRETCHDIB record specifies the transfer of color data from a\r
- * block of pixels in deviceindependent format according to a raster operation,\r
+ * block of pixels in device independent format according to a raster operation,\r
* with possible expansion or contraction.\r
* The source of the color data is a DIB, and the destination of the transfer is\r
* the current output region in the playback device context.\r
*/\r
- public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {\r
+ public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
/**\r
* A 32-bit unsigned integer that defines how the source pixels, the current brush in\r
* the playback device context, and the destination pixels are to be combined to\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
\r
}\r
\r
* using deviceindependent color data.\r
* The source of the color data is a DIB\r
*/\r
- public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord {\r
+ public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
\r
/**\r
* A 16-bit unsigned integer that defines whether the Colors field of the\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
\r
}\r
\r
}\r
\r
\r
- public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord {\r
+ public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
\r
/**\r
* A 32-bit unsigned integer that defines how the source pixels, the current brush\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
\r
}\r
\r
}\r
}\r
\r
- public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord {\r
+ public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {\r
/**\r
* A 32-bit unsigned integer that defines how the source pixels, the current brush\r
* in the playback device context, and the destination pixels are to be combined to form the\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
\r
}\r
\r
*/\r
WmfFontQuality quality;\r
\r
+ /**\r
+ * A PitchAndFamily object that defines the pitch and the family of the font.\r
+ * Font families specify the look of fonts in a general way and are intended for\r
+ * specifying fonts when the exact typeface wanted is not available.\r
+ */\r
+ int pitchAndFamily;\r
+ \r
/**\r
* Font families specify the look of fonts in a general way and are\r
* intended for specifying fonts when the exact typeface wanted is not available.\r
outPrecision = WmfOutPrecision.valueOf(leis.readUByte());\r
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());\r
quality = WmfFontQuality.valueOf(leis.readUByte());\r
- int pitchAndFamily = leis.readUByte();\r
- family = WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);\r
- pitch = WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);\r
+ pitchAndFamily = leis.readUByte();\r
\r
byte buf[] = new byte[32], b, readBytes = 0;\r
do {\r
return quality;\r
}\r
\r
+ public int getPitchAndFamily() {\r
+ return pitchAndFamily;\r
+ }\r
+\r
public WmfFontFamilyClass getFamily() {\r
- return family;\r
+ return WmfFontFamilyClass.valueOf(pitchAndFamily & 0xF);\r
}\r
\r
public WmfFontPitch getPitch() {\r
- return pitch;\r
+ return WmfFontPitch.valueOf((pitchAndFamily >>> 6) & 3);\r
}\r
\r
public String getFacename() {\r
for (HwmfMapMode mm : values()) {\r
if (mm.flag == flag) return mm;\r
}\r
- return null;\r
+ return MM_ISOTROPIC;\r
} \r
}
\ No newline at end of file
\r
/**\r
* A 16-bit unsigned integer that defines the foreground binary raster\r
- * operation mixing mode. This MUST be one of the values:\r
- * R2_BLACK = 0x0001,\r
- * R2_NOTMERGEPEN = 0x0002,\r
- * R2_MASKNOTPEN = 0x0003,\r
- * R2_NOTCOPYPEN = 0x0004,\r
- * R2_MASKPENNOT = 0x0005,\r
- * R2_NOT = 0x0006,\r
- * R2_XORPEN = 0x0007,\r
- * R2_NOTMASKPEN = 0x0008,\r
- * R2_MASKPEN = 0x0009,\r
- * R2_NOTXORPEN = 0x000A,\r
- * R2_NOP = 0x000B,\r
- * R2_MERGENOTPEN = 0x000C,\r
- * R2_COPYPEN = 0x000D,\r
- * R2_MERGEPENNOT = 0x000E,\r
- * R2_MERGEPEN = 0x000F,\r
- * R2_WHITE = 0x0010\r
+ * operation mixing mode\r
*/\r
- private int drawMode;\r
+ private HwmfBinaryRasterOp drawMode;\r
\r
@Override\r
public HwmfRecordType getRecordType() {\r
\r
@Override\r
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {\r
- drawMode = leis.readUShort();\r
+ drawMode = HwmfBinaryRasterOp.valueOf(leis.readUShort());\r
return LittleEndianConsts.SHORT_SIZE;\r
}\r
\r
}\r
}\r
\r
- public static abstract class WmfPaletteParent implements HwmfRecord {\r
+ public static abstract class WmfPaletteParent implements HwmfRecord, HwmfObjectTableEntry {\r
\r
/**\r
* Start (2 bytes): A 16-bit unsigned integer that defines the offset into the Palette Object when\r
return size;\r
}\r
\r
+ @Override\r
+ public final void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
protected List<PaletteEntry> getPaletteCopy() {\r
List<PaletteEntry> newPalette = new ArrayList<PaletteEntry>();\r
for (PaletteEntry et : palette) {\r
return HwmfRecordType.createPalette;\r
}\r
\r
- @Override\r
- public void draw(HwmfGraphics ctx) {\r
- ctx.addObjectTableEntry(this);\r
- }\r
-\r
@Override\r
public void applyObject(HwmfGraphics ctx) {\r
ctx.getProperties().setPalette(getPaletteCopy());\r
}\r
\r
@Override\r
- public void draw(HwmfGraphics ctx) {\r
+ public void applyObject(HwmfGraphics ctx) {\r
HwmfDrawProperties props = ctx.getProperties();\r
List<PaletteEntry> palette = props.getPalette();\r
if (palette == null) {\r
* The META_RESIZEPALETTE record redefines the size of the logical palette that is defined in the\r
* playback device context.\r
*/\r
- public static class WmfResizePalette implements HwmfRecord {\r
+ public static class WmfResizePalette implements HwmfRecord, HwmfObjectTableEntry {\r
/**\r
* A 16-bit unsigned integer that defines the number of entries in\r
* the logical palette.\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
HwmfDrawProperties props = ctx.getProperties();\r
List<PaletteEntry> palette = props.getPalette();\r
if (palette == null) {\r
}\r
\r
@Override\r
- public void draw(HwmfGraphics ctx) {\r
+ public void applyObject(HwmfGraphics ctx) {\r
HwmfDrawProperties props = ctx.getProperties();\r
List<PaletteEntry> dest = props.getPalette();\r
List<PaletteEntry> src = getPaletteCopy();\r
\r
package org.apache.poi.hwmf.record;\r
\r
+/**\r
+ * Each ternary raster operation code represents a Boolean operation in which the values of the pixels in\r
+ * the source, the selected brush, and the destination are combined. Following are the three operands\r
+ * used in these operations.\r
+ *\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>D</td><td>Destination bitmap</td></tr>\r
+ * <tr><td>P</td><td>Selected brush (also called pattern)</td></tr>\r
+ * <tr><td>S</td><td>Source bitmap</td></tr>\r
+ * </table>\r
+ *\r
+ * Following are the Boolean operators used in these operations.\r
+ * <table>\r
+ * <tr><th>Operand</th><th>Meaning</th></tr>\r
+ * <tr><td>a</td><td>Bitwise AND</td></tr>\r
+ * <tr><td>n</td><td>Bitwise NOT (inverse)</td></tr>\r
+ * <tr><td>o</td><td>Bitwise OR</td></tr>\r
+ * <tr><td>x</td><td>Bitwise exclusive OR (XOR)</td></tr>\r
+ * </table>\r
+ *\r
+ * All Boolean operations are presented in reverse Polish notation. For example, the following operation\r
+ * replaces the values of the pixels in the destination bitmap with a combination of the pixel values of the\r
+ * source and brush: PSo.\r
+ * \r
+ * The following operation combines the values of the pixels in the source and brush with the pixel values\r
+ * of the destination bitmap: DPSoo (there are alternative spellings of some functions, so although a\r
+ * particular spelling MAY NOT be listed in the enumeration, an equivalent form SHOULD be).\r
+ * \r
+ * Each raster operation code is a 32-bit integer whose high-order word is a Boolean operation index and\r
+ * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit\r
+ * value that represents the result of the Boolean operation on predefined brush, source, and destination\r
+ * values. For example, the operation indexes for the PSo and DPSoo operations are shown in the\r
+ * following list.\r
+ * \r
+ * <table>\r
+ * <tr><th>P</th><th>S</th><th>D</th><th>DPo</th><th>DPan</th></tr>\r
+ * <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>\r
+ * <tr><td>0</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>0</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>0</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>\r
+ * <tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>\r
+ * </table>\r
+ * \r
+ * The operation indexes are determined by reading the binary values in a column of the table from the\r
+ * bottom up. For example, in the PSo column, the binary value is 11111100, which is equivalent to 00FC\r
+ * (hexadecimal is implicit for these values), which is the operation index for PSo.\r
+ * \r
+ * Using this method, DPSoo can be seen to have the operation index 00FE. Operation indexes define the\r
+ * locations of corresponding raster operation codes in the preceding enumeration. The PSo operation is\r
+ * in line 252 (0x00FC) of the enumeration; DPSoo is in line 254 (0x00FE).\r
+ * \r
+ * The most commonly used raster operations have been given explicit enumeration names, which\r
+ * SHOULD be used; examples are PATCOPY and WHITENESS.\r
+ * \r
+ * When the source and destination bitmaps are monochrome, a bit value of 0 represents a black pixel\r
+ * and a bit value of 1 represents a white pixel. When the source and the destination bitmaps are color,\r
+ * those colors are represented with red green blue (RGB) values.\r
+ */\r
public enum HwmfTernaryRasterOp {\r
BLACKNESS(0x0000,0x0042,"0"),\r
DPSOON(0x0001,0x0289,"DPSoon"),\r
PSDNOO(0x00FD,0x0A0A,"PSDnoo"),\r
DPSOO(0x00FE,0x02A9,"DPSoo"),\r
WHITENESS(0x00FF,0x0062,"1");\r
- \r
+\r
int opIndex;\r
int opCode;\r
String opCmd;\r
- \r
+\r
HwmfTernaryRasterOp(int opIndex, int opCode, String opCmd) {\r
this.opIndex=opIndex;\r
this.opCode=opCode;\r
this.opCmd=opCmd;\r
}\r
- \r
+\r
public static HwmfTernaryRasterOp valueOf(int opIndex) {\r
for (HwmfTernaryRasterOp bb : HwmfTernaryRasterOp.values()) {\r
if (bb.opIndex == opIndex) {\r
}\r
return null;\r
}\r
- \r
+\r
public String describeCmd() {\r
String stack[] = new String[10];\r
int stackPnt = 0;\r
- \r
+\r
for (char c : opCmd.toCharArray()) {\r
switch (c) {\r
case 'S':\r
\r
import java.awt.geom.Rectangle2D;\r
import java.io.IOException;\r
-import java.text.AttributedString;\r
\r
import org.apache.poi.hwmf.draw.HwmfDrawProperties;\r
import org.apache.poi.hwmf.draw.HwmfGraphics;\r
import org.apache.poi.util.LittleEndianConsts;\r
import org.apache.poi.util.LittleEndianInputStream;\r
import org.apache.poi.util.LocaleUtil;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
import org.apache.poi.util.RecordFormatException;\r
\r
public class HwmfText {\r
-\r
+ private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);\r
+ \r
/**\r
* The META_SETTEXTCHAREXTRA record defines inter-character spacing for text justification in the \r
* playback device context. Spacing is added to the white space between each character, including\r
*/\r
public static class WmfExtTextOut implements HwmfRecord {\r
\r
+ /**\r
+ * Indicates that the background color that is defined in the playback device context \r
+ * SHOULD be used to fill the rectangle.\r
+ */ \r
+ private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);\r
+ \r
+ /**\r
+ * Indicates that the text SHOULD be clipped to the rectangle.\r
+ */\r
+ private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);\r
+\r
+ /**\r
+ * Indicates that the string to be output SHOULD NOT require further processing \r
+ * with respect to the placement of the characters, and an array of character \r
+ * placement values SHOULD be provided. This character placement process is \r
+ * useful for fonts in which diacritical characters affect character spacing.\r
+ */\r
+ private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);\r
+\r
+ /**\r
+ * Indicates that the text MUST be laid out in right-to-left reading order, instead of \r
+ * the default left-to-right order. This SHOULD be applied only when the font that is \r
+ * defined in the playback device context is either Hebrew or Arabic.\r
+ */\r
+ private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);\r
+\r
+ /**\r
+ * Indicates that to display numbers, digits appropriate to the locale SHOULD be used.\r
+ */\r
+ private static final BitField ETO_NUMERICSLOCAL = BitFieldFactory.getInstance(0x0400);\r
+\r
+ /**\r
+ * Indicates that to display numbers, European digits SHOULD be used.\r
+ */\r
+ private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);\r
+\r
+ /**\r
+ * Indicates that both horizontal and vertical character displacement values \r
+ * SHOULD be provided.\r
+ */\r
+ private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);\r
+\r
/**\r
* A 16-bit signed integer that defines the y-coordinate, in logical units, where the \r
text string is to be located.\r
* A 16-bit signed integer that defines the length of the string.\r
*/\r
private int stringLength;\r
- /**\r
- * A 16-bit unsigned integer that defines the use of the application-defined \r
- * rectangle. This member can be a combination of one or more values in the \r
- * ExtTextOutOptions Flags:\r
- * \r
- * ETO_OPAQUE (0x0002):\r
- * Indicates that the background color that is defined in the playback device context \r
- * SHOULD be used to fill the rectangle.\r
- * \r
- * ETO_CLIPPED (0x0004):\r
- * Indicates that the text SHOULD be clipped to the rectangle.\r
- * \r
- * ETO_GLYPH_INDEX (0x0010):\r
- * Indicates that the string to be output SHOULD NOT require further processing \r
- * with respect to the placement of the characters, and an array of character \r
- * placement values SHOULD be provided. This character placement process is \r
- * useful for fonts in which diacritical characters affect character spacing.\r
- * \r
- * ETO_RTLREADING (0x0080):\r
- * Indicates that the text MUST be laid out in right-to-left reading order, instead of \r
- * the default left-to-right order. This SHOULD be applied only when the font that is \r
- * defined in the playback device context is either Hebrew or Arabic. <37>\r
- * \r
- * ETO_NUMERICSLOCAL (0x0400):\r
- * Indicates that to display numbers, digits appropriate to the locale SHOULD be \r
- * used.\r
- * \r
- * ETO_NUMERICSLATIN (0x0800):\r
- * Indicates that to display numbers, European digits SHOULD be used. <39>\r
- * \r
- * ETO_PDY (0x2000):\r
- * Indicates that both horizontal and vertical character displacement values \r
- * SHOULD be provided.\r
- */\r
+\r
+ /**\r
+ * A 16-bit unsigned integer that defines the use of the application-defined \r
+ * rectangle. This member can be a combination of one or more values in the \r
+ * ExtTextOutOptions Flags (ETO_*)\r
+ */\r
private int fwOpts;\r
/**\r
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the \r
\r
int size = 4*LittleEndianConsts.SHORT_SIZE;\r
\r
- if (fwOpts != 0 && size+8<=remainingRecordSize) {\r
+ // Check if we have a rectangle\r
+ if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {\r
// the bounding rectangle is optional and only read when fwOpts are given\r
left = leis.readShort();\r
top = leis.readShort();\r
text = new String(buf, 0, stringLength, LocaleUtil.CHARSET_1252);\r
size += buf.length;\r
\r
- if (size < remainingRecordSize) {\r
- if (size + stringLength*LittleEndianConsts.SHORT_SIZE < remainingRecordSize) {\r
- throw new RecordFormatException("can't read Dx array - given recordSize doesn't contain enough values for string length "+stringLength);\r
- }\r
- \r
- dx = new int[stringLength];\r
- for (int i=0; i<dx.length; i++) {\r
- dx[i] = leis.readShort();\r
- }\r
- size += dx.length*LittleEndianConsts.SHORT_SIZE;\r
+ if (size >= remainingRecordSize) {\r
+ logger.log(POILogger.INFO, "META_EXTTEXTOUT doesn't contain character tracking info");\r
+ return size;\r
+ }\r
+ \r
+ int dxLen = Math.min(stringLength, (remainingRecordSize-size)/LittleEndianConsts.SHORT_SIZE);\r
+ if (dxLen < stringLength) {\r
+ logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters");\r
+ }\r
+\r
+ dx = new int[stringLength]; \r
+ for (int i=0; i<dxLen; i++) {\r
+ dx[i] = leis.readShort();\r
+ size += LittleEndianConsts.SHORT_SIZE;\r
}\r
\r
return size;\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
-\r
+ Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);\r
+ ctx.drawString(text, bounds, dx);\r
}\r
}\r
\r
* The META_OFFSETCLIPRGN record moves the clipping region in the playback device context by the\r
* specified offsets.\r
*/\r
- public static class WmfOffsetClipRgn implements HwmfRecord {\r
+ public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {\r
\r
/**\r
* A 16-bit signed integer that defines the number of logical units to move up or down.\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
-\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
}\r
}\r
\r
* The META_EXCLUDECLIPRECT record sets the clipping region in the playback device context to the\r
* existing clipping region minus the specified rectangle.\r
*/\r
- public static class WmfExcludeClipRect implements HwmfRecord {\r
+ public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {\r
\r
/**\r
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
-\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
}\r
}\r
\r
* The META_INTERSECTCLIPRECT record sets the clipping region in the playback device context to the\r
* intersection of the existing clipping region and the specified rectangle.\r
*/\r
- public static class WmfIntersectClipRect implements HwmfRecord {\r
+ public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {\r
\r
/**\r
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the\r
\r
@Override\r
public void draw(HwmfGraphics ctx) {\r
-\r
+ ctx.addObjectTableEntry(this);\r
+ }\r
+ \r
+ @Override\r
+ public void applyObject(HwmfGraphics ctx) {\r
}\r
}\r
\r
\r
import java.awt.Dimension;\r
import java.awt.Graphics2D;\r
-import java.awt.GraphicsConfiguration;\r
import java.awt.geom.AffineTransform;\r
import java.awt.geom.Rectangle2D;\r
-import java.awt.geom.Rectangle2D.Double;\r
import java.io.BufferedInputStream;\r
import java.io.IOException;\r
import java.io.InputStream;\r
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowExt;\r
import org.apache.poi.hwmf.record.HwmfWindowing.WmfSetWindowOrg;\r
import org.apache.poi.util.LittleEndianInputStream;\r
+import org.apache.poi.util.POILogFactory;\r
+import org.apache.poi.util.POILogger;\r
import org.apache.poi.util.Units;\r
\r
public class HwmfPicture {\r
+ private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);\r
+ \r
final List<HwmfRecord> records = new ArrayList<HwmfRecord>();\r
final HwmfPlaceableHeader placeableHeader;\r
final HwmfHeader header;\r
header = new HwmfHeader(leis);\r
\r
for (;;) {\r
+ if (leis.available() < 6) {\r
+ logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated");\r
+ break;\r
+ }\r
// recordSize in DWORDs\r
long recordSize = leis.readUInt()*2;\r
int recordFunction = leis.readShort();\r
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;
@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);
@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);
@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);
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());
}
}
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;
*
*/
@Test
- public void multiplePictures() throws Exception {
+ public void multiplePictures() throws IOException {
HSLFSlideShow ppt = new HSLFSlideShow();
HSLFSlide s = ppt.createSlide();
EscherBSERecord bse3 = pict.getEscherBSERecord();
assertSame(bse2, bse3);
assertEquals(3, bse1.getRef());
+
+ ppt.close();
}
/**
* 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);
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();
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",
} 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));
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);
- }
}
import java.awt.Dimension;\r
import java.awt.Graphics2D;\r
import java.awt.RenderingHints;\r
+import java.awt.geom.Rectangle2D;\r
import java.awt.image.BufferedImage;\r
import java.io.File;\r
import java.io.FileFilter;\r
@Ignore("This is work-in-progress and not a real unit test ...")\r
public void paint() throws IOException {\r
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");\r
-// File f = new File("E:\\project\\poi\\misc\\govdocs-ppt", "000133-0001.wmf");\r
+ // File f = new File("bla.wmf");\r
FileInputStream fis = new FileInputStream(f);\r
HwmfPicture wmf = new HwmfPicture(fis);\r
fis.close();\r
int width = Units.pointsToPixel(dim.getWidth());\r
// keep aspect ratio for height\r
int height = Units.pointsToPixel(dim.getHeight());\r
+ double max = Math.max(width, height);\r
+ if (max > 1500) {\r
+ width *= 1500/max;\r
+ height *= 1500/max;\r
+ }\r
\r
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
Graphics2D g = bufImg.createGraphics();\r
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);\r
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
\r
- wmf.draw(g);\r
+ wmf.draw(g, new Rectangle2D.Double(0,0,width,height));\r
\r
g.dispose();\r
\r
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;
}
});
for(Path p : geom){
- GeneralPath path = p.getPath(ctx);
+ Path2D path = p.getPath(ctx);
assertNotNull(path);
}
}